소스 검색

feat: 开屏splash页+tabbar切换过渡动画

- splash页设为pages.json首页,每次冷启动展示至少1秒
- 移除index.vue内联splash覆盖层(由独立splash.vue页面替代)
- 新增tab-transition.vue组件:五维品牌过渡(行/身/智/心/富)
- 5个tabbar页集成tab-transition:onShow时显示1.1秒过渡
- 修复health-dimensions.vue的getSpokeStyle方法调用兼容性
User 4 달 전
부모
커밋
86a3b858c5

+ 151 - 0
cfc-frontend/components/tab-transition.vue

@@ -0,0 +1,151 @@
+<template>
+  <view class="tab-transition-overlay" :style="'background:' + bgGradient">
+    <view class="transition-content" :class="{ 'fade-out': fadeOut }">
+      <!-- Large dimension character -->
+      <text class="dim-char" :style="'color:' + dimConfig.color">{{ dimConfig.char }}</text>
+      <!-- Icon -->
+      <text class="dim-icon">{{ dimConfig.icon }}</text>
+      <!-- Dimension label -->
+      <text class="dim-label" :style="'color:' + dimConfig.color">{{ dimConfig.label }}</text>
+      <!-- Animated energy bar -->
+      <view class="energy-bar-track">
+        <view class="energy-bar-fill" :style="'background:' + dimConfig.color"></view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+var DIM_MAP = {
+  action:  { char: '行', icon: '🌿', color: '#10B981', label: '知行合一' },
+  body:    { char: '身', icon: '🌏', color: '#8D6E63', label: '主动健康' },
+  wisdom:  { char: '智', icon: '⚡', color: '#6366F1', label: '赋能未来' },
+  mind:    { char: '心', icon: '🔥', color: '#FF6B9D', label: '大爱传承' },
+  wealth:  { char: '富', icon: '💧', color: '#F59E0B', label: '利他创富' }
+}
+
+export default {
+  props: {
+    dimCode: {
+      type: String,
+      required: true
+    }
+  },
+  data() {
+    return {
+      fadeOut: false
+    }
+  },
+  computed: {
+    dimConfig: function() {
+      return DIM_MAP[this.dimCode] || DIM_MAP['action']
+    },
+    bgGradient: function() {
+      var cfg = this.dimConfig
+      var hex = cfg.color
+      var r = parseInt(hex.slice(1, 3), 16)
+      var g = parseInt(hex.slice(3, 5), 16)
+      var b = parseInt(hex.slice(5, 7), 16)
+      return 'linear-gradient(160deg, #FFF7ED 0%, rgba(' + r + ',' + g + ',' + b + ',0.4) 100%)'
+    }
+  },
+  mounted() {
+    var self = this
+    setTimeout(function() {
+      self.fadeOut = true
+    }, 800)
+  }
+}
+</script>
+
+<style scoped>
+.tab-transition-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 9998;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.transition-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  opacity: 0;
+  animation: tab-fade-in 0.2s ease forwards;
+}
+.transition-content.fade-out {
+  animation: tab-fade-out 0.3s ease forwards;
+}
+
+@keyframes tab-fade-in {
+  from {
+    opacity: 0;
+    transform: scale(0.92);
+  }
+  to {
+    opacity: 1;
+    transform: scale(1);
+  }
+}
+
+@keyframes tab-fade-out {
+  from {
+    opacity: 1;
+    transform: scale(1);
+  }
+  to {
+    opacity: 0;
+    transform: scale(0.92);
+  }
+}
+
+.dim-char {
+  font-size: 120rpx;
+  font-weight: 700;
+  line-height: 1.2;
+  margin-bottom: 8rpx;
+}
+
+.dim-icon {
+  font-size: 48rpx;
+  line-height: 1;
+  margin-bottom: 16rpx;
+}
+
+.dim-label {
+  font-size: 32rpx;
+  font-weight: 500;
+  line-height: 1;
+  margin-bottom: 40rpx;
+  opacity: 0.8;
+}
+
+.energy-bar-track {
+  width: 200rpx;
+  height: 4rpx;
+  background: rgba(0, 0, 0, 0.08);
+  border-radius: 2rpx;
+  overflow: hidden;
+}
+
+.energy-bar-fill {
+  width: 0%;
+  height: 100%;
+  border-radius: 2rpx;
+  animation: bar-grow 0.6s ease 0.2s forwards;
+}
+
+@keyframes bar-grow {
+  from {
+    width: 0%;
+  }
+  to {
+    width: 100%;
+  }
+}
+</style>

+ 8 - 8
cfc-frontend/pages.json

@@ -1,6 +1,13 @@
 {
   "pages": [
-    // ===== 入口页 + TabBar 页面(必须在主包,第一项为小程序首页)=====
+    // ===== 品牌开屏页(第一项为小程序首页)=====
+    {
+      "path": "pages/splash/splash",
+      "style": {
+        "navigationStyle": "custom"
+      }
+    },
+    // ===== 入口页 + TabBar 页面(必须在主包)=====
     {
       "path": "pages/index/index",
       "style": {
@@ -20,13 +27,6 @@
         "enablePullDownRefresh": true
       }
     },
-    // ===== 品牌开屏页 =====
-    {
-      "path": "pages/splash/splash",
-      "style": {
-        "navigationStyle": "custom"
-      }
-    },
     // ===== 主包:其他页面 =====
     {
       "path": "pages/discover/index",

+ 19 - 12
cfc-frontend/pages/body/health-dimensions.vue

@@ -26,7 +26,7 @@
               class="spoke-item"
               v-for="(dim, idx) in healthData.dimensions"
               :key="dim.dimension"
-              :style="getSpokeStyle(idx, dim.score)"
+              :style="spokeStyles[idx]"
             >
               <view class="spoke-dot" :class="'level-' + dim.level"></view>
               <view class="spoke-label">{{ dim.label }}</view>
@@ -168,6 +168,24 @@ export default {
       ]
     }
   },
+  computed: {
+    spokeStyles: function() {
+      if (!this.healthData || !this.healthData.dimensions) return []
+      var cache = []
+      var dims = this.healthData.dimensions
+      for (var i = 0; i < dims.length; i++) {
+        var angle = i * 51.4 - 90
+        var radius = 85
+        var normalizedScore = (dims[i].score || 0) / 100
+        var translateY = -radius * normalizedScore
+        cache.push({
+          transform: 'rotate(' + angle + 'deg) translateY(' + translateY + 'px)',
+          '-webkit-transform': 'rotate(' + angle + 'deg) translateY(' + translateY + 'px)'
+        })
+      }
+      return cache
+    }
+  },
   onLoad: function(options) {
     var childId = options.childId || uni.getStorageSync('currentChildId')
     var childName = options.name || ''
@@ -197,17 +215,6 @@ export default {
         console.log('获取维度数据失败', e)
       })
     },
-    getSpokeStyle: function(idx, score) {
-      var angle = idx * 51.4 - 90
-      var radius = 85
-      var normalizedScore = (score || 0) / 100
-      var translateY = -radius * normalizedScore
-      var rotate = angle
-      return {
-        transform: 'rotate(' + rotate + 'deg) translateY(' + translateY + 'px)',
-        '-webkit-transform': 'rotate(' + rotate + 'deg) translateY(' + translateY + 'px)'
-      }
-    },
     getLevelText: function(level) {
       var map = {
         'excellent': '优秀',

+ 13 - 1
cfc-frontend/pages/body/index.vue

@@ -1,5 +1,8 @@
 <template>
   <view class="body-container">
+    <!-- Tabbar 切换过渡页 -->
+    <tab-transition v-if="showTabTransition" dimCode="body" />
+
     <!-- 品牌头部 -->
     <PageBanner theme="body"
       tagline="科学管理全家健康"
@@ -219,6 +222,7 @@
 </template>
 
 <script>
+import TabTransition from '../../components/tab-transition.vue'
 import PageBanner from '../../components/PageBanner.vue'
 import LoginGuideCard from '../../components/LoginGuideCard.vue'
 import HealthTips from '../../components/HealthTips.vue'
@@ -259,11 +263,12 @@ const healthRequest = function(url, data) {
 }
 
 export default {
-  components: { PageBanner, LoginGuideCard, HealthTips, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, FamilyRelationGraph },
+  components: { TabTransition, PageBanner, LoginGuideCard, HealthTips, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, FamilyRelationGraph },
   data() {
     return {
       isLoggedIn: false,
       role: null,
+      showTabTransition: false,
       activeChildId: null,
       activeChildName: '',
       userName: '',
@@ -316,6 +321,13 @@ export default {
     }
   },
   onShow() {
+    // Tabbar 切换过渡动画(最少停留 1 秒)
+    this.showTabTransition = true
+    var self = this
+    setTimeout(function() {
+      self.showTabTransition = false
+    }, 1100)
+
     var token = uni.getStorageSync('token')
     this.isLoggedIn = !!token
     if (this.isLoggedIn) {

+ 18 - 175
cfc-frontend/pages/index/index.vue

@@ -1,34 +1,7 @@
 <template>
 <view>
-  <!-- 品牌启动页(冷启动时覆盖全屏) -->
-  <view class="splash-overlay" v-if="showSplash">
-    <view class="splash-content">
-      <view class="splash-logo-mark">
-        <text class="splash-logo-char">浠</text>
-      </view>
-      <text class="splash-brand-name">浠艾福</text>
-      <!-- 五维图标行 -->
-      <view class="splash-dims-row">
-        <view
-          v-for="(item, index) in splashDimList"
-          :key="item.code"
-          class="splash-dim-item"
-          :style="'animation-delay:' + (0.8 + index * 0.25) + 's'"
-        >
-          <view class="splash-dim-circle" :style="'background:' + hexToRgba(item.color, 0.25)">
-            <text class="splash-dim-icon">{{ item.icon }}</text>
-          </view>
-          <text class="splash-dim-label">{{ item.name }}</text>
-        </view>
-      </view>
-      <text class="splash-tagline">给全家的一站式幸福提案</text>
-      <view class="splash-loading">
-        <view class="splash-dot splash-dot--1"></view>
-        <view class="splash-dot splash-dot--2"></view>
-        <view class="splash-dot splash-dot--3"></view>
-      </view>
-    </view>
-  </view>
+  <!-- Tabbar 切换过渡页 -->
+  <tab-transition v-if="showTabTransition" dimCode="action" />
 
   <!-- 未登录态 — 发现页/引流内容,直接内嵌,不跳转 -->
   <view v-if="!currentRole" class="discover-container">
@@ -140,6 +113,7 @@ import ChildIndex from './child-index.vue'
 // TeacherIndex 已废弃,教师功能折入 parent-index
 import PageBanner from '../../components/PageBanner.vue'
 import WuxingSandbox from '../../components/wuxing-sandbox.vue'
+import TabTransition from '../../components/tab-transition.vue'
 import { acceptParentInvite, productList } from '../../utils/api.js'
 
 var DIMENSIONS = [
@@ -155,7 +129,8 @@ export default {
     ParentIndex,
     ChildIndex,
     PageBanner,
-    WuxingSandbox
+    WuxingSandbox,
+    TabTransition
   },
   data() {
     // 从 storage 预取登录态/角色,确保首次渲染即正确(WeChat 渲染层/逻辑层分离架构下 onLoad 执行前已渲染)
@@ -166,7 +141,8 @@ export default {
     }
     return {
       currentRole: _currentRole,
-      showSplash: false,
+      showTabTransition: false,
+
       typeList: [
         { label: '全部', value: '' },
         { label: '活动', value: 'activity' },
@@ -181,21 +157,17 @@ export default {
     }
   },
   onLoad(options) {
-    // 冷启动检查:是否已展示品牌启动页
-    if (!uni.getStorageSync('_splashReady') && !this.showSplash) {
-      this.showSplash = true
-      // 立即标记已展示,防止 reLaunch 等场景下重复触发 splash
-      uni.setStorageSync('_splashReady', true)
-      var self = this
-      setTimeout(function() {
-        self.showSplash = false
-      }, 2500)
-      // 不因 splash 阻塞初始化逻辑 — splash 仅是视觉遮盖层
-    }
     this._doInit(options)
   },
   onShow() {
-    // 始终执行初始化,不因 splash 状态跳过,确保登录态变更后及时切换视图
+    // Tabbar 切换过渡动画(最少停留 1 秒)
+    this.showTabTransition = true
+    var self = this
+    setTimeout(function() {
+      self.showTabTransition = false
+    }, 1100)
+
+    // 始终执行初始化,确保登录态变更后及时切换视图
     this._doInit()
   },
   methods: {
@@ -203,9 +175,7 @@ export default {
       options = options || {}
       if (!this.isLoggedIn()) {
         this.currentRole = ''
-        if (!this.showSplash) {
-          this.loadProducts()
-        }
+        this.loadProducts()
         return
       }
       this.syncRoleAndTabBar()
@@ -318,20 +288,13 @@ export default {
         })
       }, 500)
     },
-    hexToRgba: function(hex, alpha) {
-      var r = parseInt(hex.slice(1, 3), 16)
-      var g = parseInt(hex.slice(3, 5), 16)
-      var b = parseInt(hex.slice(5, 7), 16)
-      return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')'
-    }
+
   },
   computed: {
     dimList: function() {
       return DIMENSIONS
     },
-    splashDimList: function() {
-      return DIMENSIONS
-    },
+
     energyDotColors: function() {
       return ['#8D6E63', '#FF6B35', '#FFD700', '#4CAF50', '#42A5F5']
     }
@@ -555,126 +518,6 @@ export default {
   height: 100vh;
 }
 
-/* ===== 品牌启动页覆盖 ===== */
-.splash-overlay {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background: linear-gradient(160deg, #FFF7ED 0%, #F97316 100%);
-  z-index: 9999;
-}
-.splash-content {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  opacity: 0;
-  animation: splash-fade-in 0.8s ease forwards;
-}
-@keyframes splash-fade-in {
-  from { opacity: 0; transform: translateY(20rpx); }
-  to { opacity: 1; transform: translateY(0); }
-}
-.splash-logo-mark {
-  width: 144rpx;
-  height: 144rpx;
-  border-radius: 36rpx;
-  background: rgba(255,255,255,0.3);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 24rpx;
-}
-.splash-logo-char {
-  font-size: 72rpx;
-  font-weight: 700;
-  color: #FFFFFF;
-  line-height: 1;
-}
-.splash-brand-name {
-  font-size: 56rpx;
-  font-weight: 700;
-  color: #FFFFFF;
-  letter-spacing: 8rpx;
-  margin-bottom: 12rpx;
-  text-shadow: 0 2rpx 12rpx rgba(0,0,0,0.1);
-}
-.splash-tagline {
-  font-size: 28rpx;
-  color: rgba(255,255,255,0.85);
-  letter-spacing: 4rpx;
-  font-weight: 400;
-}
-.splash-loading {
-  position: absolute;
-  bottom: 120rpx;
-  left: 0;
-  right: 0;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  gap: 12rpx;
-}
-.splash-dot {
-  width: 14rpx;
-  height: 14rpx;
-  border-radius: 50%;
-  background: rgba(255,255,255,0.6);
-  animation: splash-dot-bounce 1.4s ease-in-out infinite both;
-}
-.splash-dot--1 { animation-delay: -0.32s; }
-.splash-dot--2 { animation-delay: -0.16s; }
-.splash-dot--3 { animation-delay: 0s; }
-@keyframes splash-dot-bounce {
-  0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
-  40% { transform: scale(1); opacity: 1; }
-}
-
-/* ===== 五维图标行 ===== */
-.splash-dims-row {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: center;
-  gap: 16rpx;
-  margin: 16rpx 0 20rpx;
-}
-.splash-dim-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  gap: 6rpx;
-  opacity: 0;
-  transform: translateY(20rpx);
-  animation: splash-dim-enter 0.4s ease forwards;
-}
-.splash-dim-circle {
-  width: 64rpx;
-  height: 64rpx;
-  border-radius: 50%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-.splash-dim-icon {
-  font-size: 28rpx;
-  line-height: 1;
-  color: #FFFFFF;
-}
-.splash-dim-label {
-  font-size: 18rpx;
-  color: rgba(255, 255, 255, 0.7);
-  line-height: 1;
-}
-@keyframes splash-dim-enter {
-  from { opacity: 0; transform: translateY(20rpx); }
-  to { opacity: 1; transform: translateY(0); }
-}
-
 /* ===== 角色切换淡入淡出 ===== */
 .role-switch-wrapper {
   width: 100%;

+ 13 - 1
cfc-frontend/pages/mind/index.vue

@@ -1,5 +1,8 @@
 <template>
   <view class="container">
+    <!-- Tabbar 切换过渡页 -->
+    <tab-transition v-if="showTabTransition" dimCode="mind" />
+
     <!-- 品牌头部 -->
     <PageBanner theme="mind-wisdom"
       tagline="心智成长 · 智慧人生"
@@ -382,6 +385,7 @@
 </template>
 
 <script>
+import TabTransition from '../../components/tab-transition.vue'
 import PageBanner from '../../components/PageBanner.vue'
 import LoginGuideCard from '../../components/LoginGuideCard.vue'
 import RadarChart from '../../components/RadarChart.vue'
@@ -395,11 +399,12 @@ import { getVisibleSections, getFeaturedArticles, getEmiReport, getAssessmentLat
 import config from '../../config.js'
 
 export default {
-  components: { PageBanner, RadarChart, LoginGuideCard, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, FamilyRelationGraph },
+  components: { TabTransition, PageBanner, RadarChart, LoginGuideCard, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, FamilyRelationGraph },
   data() {
     return {
       isLoggedIn: false,
       role: null,
+      showTabTransition: false,
       currentChildId: null,
       visibleSections: [],
       familyMembersVisible: [],
@@ -526,6 +531,13 @@ export default {
     }
   },
   onShow() {
+    // Tabbar 切换过渡动画(最少停留 1 秒)
+    this.showTabTransition = true
+    var self = this
+    setTimeout(function() {
+      self.showTabTransition = false
+    }, 1100)
+
     var token = uni.getStorageSync('token')
     this.isLoggedIn = !!token
     if (this.isLoggedIn) {

+ 14 - 1
cfc-frontend/pages/profile/profile.vue

@@ -1,5 +1,8 @@
 <template>
   <view class="container">
+    <!-- Tabbar 切换过渡页 -->
+    <tab-transition v-if="showTabTransition" dimCode="wealth" />
+
     <!-- ===== 未登录状态 ===== -->
     <view v-if="!isLoggedIn" class="login-prompt">
       <view class="prompt-icon">👤</view>
@@ -32,6 +35,7 @@
 </template>
 
 <script>
+import TabTransition from '../../components/tab-transition.vue'
 import PageBanner from '../../components/PageBanner.vue'
 import ProfileHeader from './components/ProfileHeader.vue'
 import ProfileStats from './components/ProfileStats.vue'
@@ -41,6 +45,7 @@ import ProfileMenu from './components/ProfileMenu.vue'
 
 export default {
   components: {
+    TabTransition,
     PageBanner,
     ProfileHeader,
     ProfileStats,
@@ -50,7 +55,8 @@ export default {
   },
   data() {
     return {
-      shareData: null
+      shareData: null,
+      showTabTransition: false
     }
   },
   computed: {
@@ -71,6 +77,13 @@ export default {
     }
   },
   onShow() {
+    // Tabbar 切换过渡动画(最少停留 1 秒)
+    this.showTabTransition = true
+    var self = this
+    setTimeout(function() {
+      self.showTabTransition = false
+    }, 1100)
+
     if (!uni.getStorageSync('token')) return
     const currentRole = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || 'parent'
     if (currentRole === 'teacher') {

+ 2 - 3
cfc-frontend/pages/splash/splash.vue

@@ -69,10 +69,9 @@ export default {
     this.ready = true
   },
   onReady() {
-    // 延迟 2500ms 后跳转到首页
+    // 延迟 2500ms 后跳转到首页(至少 1s 展示保证)
     setTimeout(function() {
-      uni.setStorageSync('_splashReady', true)
-      uni.reLaunch({
+      uni.switchTab({
         url: '/pages/index/index'
       })
     }, 2500)

+ 88 - 3
cfc-frontend/pages/wisdom/index.vue

@@ -1,5 +1,8 @@
 <template>
   <view class="wisdom-container">
+    <!-- Tabbar 切换过渡页 -->
+    <tab-transition v-if="showTabTransition" dimCode="wisdom" />
+
     <!-- 1. 品牌头部 -->
     <PageBanner theme="wisdom"
       tagline="学以启智 · 慧及未来"
@@ -130,6 +133,33 @@
       @productClick="goProductDetail"
       @moreProducts="goMoreProducts" />
 
+    <!-- 推荐阅读 -->
+    <view class="section" v-if="sectionVisible('recommended_reading') && wisdomArticles.length > 0">
+      <view class="section-header">
+        <text class="section-title">推荐阅读</text>
+      </view>
+      <view class="article-list">
+        <view class="article-card" v-for="article in wisdomArticles" :key="article.id" @click="goArticleDetail(article.id)">
+          <view class="article-category" :style="{ background: article.categoryColor }">
+            <text class="article-category-text">{{ article.category }}</text>
+          </view>
+          <text class="article-title">{{ article.title }}</text>
+          <text class="article-summary">{{ article.summary }}</text>
+          <view class="article-meta">
+            <view class="meta-item">
+              <text class="meta-icon">&#x1F4D6;</text>
+              <text class="meta-text">{{ article.views }}</text>
+            </view>
+            <view class="meta-item">
+              <text class="meta-icon">&#x1F44D;</text>
+              <text class="meta-text">{{ article.likes }}</text>
+            </view>
+            <text class="article-date">{{ article.date }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
     <!-- 13. 智慧小贴士 -->
     <view class="section" v-if="sectionVisible('wisdom_tips')">
       <view class="section-header">
@@ -165,6 +195,7 @@
 </template>
 
 <script>
+import TabTransition from '../../components/tab-transition.vue'
 import PageBanner from '../../components/PageBanner.vue'
 import LoginGuideCard from '../../components/LoginGuideCard.vue'
 import WisdomTips from '../../components/WisdomTips.vue'
@@ -174,14 +205,15 @@ import DimensionTasks from '../../components/DimensionTasks.vue'
 import DimensionActivities from '../../components/DimensionActivities.vue'
 import DimensionProducts from '../../components/DimensionProducts.vue'
 import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
-import { getVisibleSections, getChildren, getTodayTasksByCategory, getActivityList, getProductsByDomain, getFamilyEnergySandbox, getAssessmentLatestResult } from '../../utils/api.js'
+import { getVisibleSections, getChildren, getTodayTasksByCategory, getActivityList, getProductsByDomain, getFamilyEnergySandbox, getAssessmentLatestResult, getFeaturedArticles } from '../../utils/api.js'
 
 export default {
-  components: { PageBanner, LoginGuideCard, WisdomTips, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, FamilyRelationGraph },
+  components: { TabTransition, PageBanner, LoginGuideCard, WisdomTips, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, FamilyRelationGraph },
   data() {
     return {
       isLoggedIn: false,
       role: null,
+      showTabTransition: false,
       activeChildId: null,
       activeChildName: '',
       userName: '',
@@ -195,6 +227,7 @@ export default {
       dimensionTasks: [],
       dimensionActivities: [],
       dimensionProducts: [],
+      articles: [],
       funcList: [
         { icon: '\u{1F4CA}', label: '认知报告', needLogin: true, page: '/pages/wisdom/cognitive-report' },
         { icon: '\u{270F}\uFE0F', label: '自己出题', needLogin: true, page: '/pages/wisdom/self-quiz' },
@@ -227,9 +260,28 @@ export default {
     },
     intimacyMapForGraph: function() {
       return {}
+    },
+    wisdomArticles: function() {
+      if (!this.articles || this.articles.length === 0) return []
+      var result = []
+      for (var i = 0; i < this.articles.length; i++) {
+        var a = this.articles[i]
+        if (a.relatedDimensions && a.relatedDimensions.indexOf('wisdom') !== -1) {
+          result.push(a)
+        }
+      }
+      if (result.length === 0) return this.articles.slice(0, 5)
+      return result.slice(0, 5)
     }
   },
   onShow() {
+    // Tabbar 切换过渡动画(最少停留 1 秒)
+    this.showTabTransition = true
+    var self = this
+    setTimeout(function() {
+      self.showTabTransition = false
+    }, 1100)
+
     var token = uni.getStorageSync('token')
     this.isLoggedIn = !!token
     if (this.isLoggedIn) {
@@ -244,6 +296,8 @@ export default {
     }
     this.loadDimensionActivities()
     this.loadDimensionProducts()
+    // 游客也能浏览文章
+    this.loadFeaturedArticles()
   },
   methods: {
     loadSectionConfig: function() {
@@ -420,7 +474,38 @@ export default {
         uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/wisdom/index') })
       }
     },
-    goMoreProducts: function() {}
+    goMoreProducts: function() {},
+    loadFeaturedArticles: function() {
+      var self = this
+      getFeaturedArticles({ size: 5 }).then(function(res) {
+        if (res.code === 200 && res.data) {
+          var gradientColors = [
+            'linear-gradient(135deg, #6366F1, #818CF8)',
+            'linear-gradient(135deg, #8B5CF6, #A78BFA)',
+            'linear-gradient(135deg, #5B9BD5, #8FC5E8)',
+            'linear-gradient(135deg, #10B981, #34D399)',
+            'linear-gradient(135deg, #F97316, #FB923C)'
+          ]
+          var list = Array.isArray(res.data) ? res.data : (res.data.records || [])
+          self.articles = list.map(function(item, index) {
+            return {
+              id: item.id,
+              category: item.categoryName || '智慧文章',
+              categoryColor: gradientColors[index % gradientColors.length],
+              title: item.title || '',
+              summary: item.summary || '',
+              views: item.viewCount ? String(item.viewCount) : '0',
+              likes: '0',
+              date: item.publishedAt ? item.publishedAt.slice(0, 10) : '',
+              relatedDimensions: item.relatedDimensions || []
+            }
+          })
+        }
+      }).catch(function() {})
+    },
+    goArticleDetail: function(id) {
+      uni.navigateTo({ url: '/pages/wisdom/article-detail?id=' + id })
+    }
   }
 }
 </script>