Browse Source

refactor: replace inline 推荐阅读 with DimensionArticles组件 on action/mind pages

User 4 months ago
parent
commit
8ffb26b811

+ 1 - 1
cfc-frontend/App.vue

@@ -43,7 +43,7 @@ export default {
             this.saveLoginInfo(res.data)
             console.log('openid自动登录成功')
             // 刷新页面到登录态
-            uni.reLaunch({ url: '/pages/index/index' })
+            uni.switchTab({ url: '/pages/index/index' })
             return
           }
         }

+ 129 - 0
cfc-frontend/components/AIFloatingAvatar.vue

@@ -0,0 +1,129 @@
+<template>
+  <view class="ai-float-wrapper" @click="goToChat">
+    <view class="ai-float-avatar" :class="mascotCode">
+      <text class="ai-float-icon">{{ mascotIcon }}</text>
+    </view>
+    <view class="ai-float-pulse" v-if="showPulse"></view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'AIFloatingAvatar',
+  data() {
+    return {
+      mascotCode: 'xibao',
+      mascotIcon: '🌟',
+      showPulse: true
+    }
+  },
+  mounted() {
+    this.loadMascot()
+  },
+  onShow() {
+    this.loadMascot()
+  },
+  methods: {
+    loadMascot() {
+      var userInfo = uni.getStorageSync('userInfo')
+      if (userInfo && userInfo.mascot) {
+        if (userInfo.mascot === 'xibao') {
+          this.mascotCode = 'xibao'
+          this.mascotIcon = '🌟'
+        } else if (userInfo.mascot === 'fubao') {
+          this.mascotCode = 'fubao'
+          this.mascotIcon = '💪'
+        }
+      } else {
+        // 默认浠宝
+        this.mascotCode = 'xibao'
+        this.mascotIcon = '🌟'
+      }
+    },
+    goToChat() {
+      // 获取当前页面路径作为来源标识
+      var pages = getCurrentPages()
+      var currentPage = pages[pages.length - 1]
+      var fromPage = ''
+      if (currentPage) {
+        fromPage = currentPage.route || ''
+      }
+      var url = '/pages/ai/chat'
+      if (fromPage) {
+        url += '?fromPage=' + encodeURIComponent(fromPage)
+      }
+      uni.navigateTo({ url: url })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.ai-float-wrapper {
+  position: fixed;
+  right: 32rpx;
+  bottom: calc(160rpx + constant(safe-area-inset-bottom));
+  bottom: calc(160rpx + env(safe-area-inset-bottom));
+  z-index: 999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 112rpx;
+  height: 112rpx;
+}
+
+.ai-float-avatar {
+  width: 96rpx;
+  height: 96rpx;
+  border-radius: 50%;
+  background: #fff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.15), 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
+  animation: floatBounce 3s ease-in-out infinite;
+  position: relative;
+  z-index: 2;
+  transition: transform 0.2s;
+}
+
+.ai-float-avatar:active {
+  transform: scale(0.92);
+}
+
+.ai-float-avatar.xibao {
+  background: linear-gradient(135deg, #FFF7ED 0%, #FFE4CC 100%);
+  border: 3rpx solid #FF8C42;
+}
+
+.ai-float-avatar.fubao {
+  background: linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%);
+  border: 3rpx solid #F59E0B;
+}
+
+.ai-float-icon {
+  font-size: 48rpx;
+  line-height: 1;
+}
+
+/* 脉冲动画 */
+.ai-float-pulse {
+  position: absolute;
+  width: 96rpx;
+  height: 96rpx;
+  border-radius: 50%;
+  background: rgba(249, 115, 22, 0.25);
+  animation: pulseRing 2s ease-out infinite;
+  z-index: 1;
+}
+
+@keyframes floatBounce {
+  0%, 100% { transform: translateY(0); }
+  50% { transform: translateY(-8rpx); }
+}
+
+@keyframes pulseRing {
+  0% { transform: scale(1); opacity: 0.6; }
+  100% { transform: scale(1.6); opacity: 0; }
+}
+</style>

+ 1 - 1
cfc-frontend/components/tab-transition.vue

@@ -148,4 +148,4 @@ export default {
     width: 100%;
   }
 }
-</style>
+</style>

+ 13 - 27
cfc-frontend/pages/action/index.vue

@@ -68,31 +68,12 @@
       @moreProducts="goMoreProducts" />
 
     <!-- 推荐阅读 -->
-    <view class="section" v-if="actionArticles.length > 0">
-      <view class="section-header">
-        <text class="section-title">推荐阅读</text>
-      </view>
-      <view class="article-list">
-        <view class="article-card" v-for="article in actionArticles" :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>
+    <DimensionArticles
+      dimensionCode="action"
+      :articles="articles"
+      :isLoggedIn="isLoggedIn"
+      @articleClick="goArticleDetail"
+      @moreArticles="goMoreArticles" />
 
     <!-- ===== 重要关系维护 ===== -->
     <view class="contact-section" v-if="isLoggedIn">
@@ -199,13 +180,14 @@ import UserQuickEntry from '../../components/UserQuickEntry.vue'
 import DimensionTasks from '../../components/DimensionTasks.vue'
 import DimensionActivities from '../../components/DimensionActivities.vue'
 import DimensionProducts from '../../components/DimensionProducts.vue'
+import DimensionArticles from '../../components/DimensionArticles.vue'
 import ContactCard from '../../components/ContactCard.vue'
 import ContactImport from '../../components/ContactImport.vue'
 import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
 import { getVisibleSections, getEnergyOverview, getFamilyEnergySandbox, getTodayTasksByCategory, getActivityList, getProductsByDomain, getChildren, getContactList, getVisibleFamilyMembers, getFeaturedArticles, getHealthAlerts, getMilestones } from '../../utils/api.js'
 
 export default {
-  components: { TabTransition, PageBanner, LoginGuideCard, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, ContactCard, ContactImport, FamilyRelationGraph },
+  components: { TabTransition, PageBanner, LoginGuideCard, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, ContactCard, ContactImport, FamilyRelationGraph },
   data() {
     return {
       showTabTransition: true,
@@ -518,9 +500,13 @@ export default {
         }
       }).catch(function() {})
     },
-    goArticleDetail: function(id) {
+    goArticleDetail: function(article) {
+      var id = article.id || article
       uni.navigateTo({ url: '/pages/mind-detail/article-detail?id=' + id })
     },
+    goMoreArticles: function() {
+      uni.navigateTo({ url: '/pages/mind-detail/articles' })
+    },
     loadRelationshipHealth: function() {
       var self = this
       getHealthAlerts({}).then(function(res) {

+ 1 - 0
cfc-frontend/pages/ai/chat.vue

@@ -154,6 +154,7 @@ export default {
     this.reportId = options.reportId ? parseInt(options.reportId) : null
     this.surveyId = options.surveyId ? parseInt(options.surveyId) : null
     this.mode = options.mode || ''
+    this.fromPage = options.fromPage ? decodeURIComponent(options.fromPage) : ''
     if (options.firstMessage) {
       this.pendingFirstMessage = decodeURIComponent(options.firstMessage)
     }

+ 264 - 29
cfc-frontend/pages/index/index.vue

@@ -1,5 +1,34 @@
 <template>
 <view>
+  <!-- 首次进入的 splash loading 蒙板 — 覆盖首页直到数据加载完毕 -->
+  <view class="splash-overlay" :class="{'fade-out': splashOverlayFading}" v-if="splashOverlay || splashOverlayFading">
+    <view class="splash-overlay-bg">
+      <view class="splash-overlay-circle c1"></view>
+      <view class="splash-overlay-circle c2"></view>
+      <view class="splash-overlay-circle c3"></view>
+    </view>
+    <view class="splash-overlay-body">
+      <view class="splash-overlay-logo-wrap">
+        <image class="splash-overlay-logo" src="/static/logo.png" mode="aspectFit" />
+      </view>
+      <text class="splash-overlay-brand">浠艾福</text>
+      <text class="splash-overlay-en">Care Family</text>
+      <view class="splash-overlay-dims">
+        <view class="splash-overlay-dim"><text class="splash-overlay-dim-icon">🌏</text><text class="splash-overlay-dim-label">身</text></view>
+        <view class="splash-overlay-dim"><text class="splash-overlay-dim-icon">🔥</text><text class="splash-overlay-dim-label">心</text></view>
+        <view class="splash-overlay-dim"><text class="splash-overlay-dim-icon">⚡</text><text class="splash-overlay-dim-label">智</text></view>
+        <view class="splash-overlay-dim"><text class="splash-overlay-dim-icon">🌿</text><text class="splash-overlay-dim-label">行</text></view>
+        <view class="splash-overlay-dim"><text class="splash-overlay-dim-icon">💧</text><text class="splash-overlay-dim-label">富</text></view>
+      </view>
+      <text class="splash-overlay-tagline">给全家的一站式幸福提案</text>
+      <view class="splash-overlay-loading">
+        <view class="splash-overlay-dot"></view>
+        <view class="splash-overlay-dot"></view>
+        <view class="splash-overlay-dot"></view>
+      </view>
+    </view>
+  </view>
+
   <!-- Tabbar 切换过渡页 -->
   <tab-transition v-if="showTabTransition" dimCode="action" />
 
@@ -82,12 +111,18 @@
     </view>
 
     <!-- 推荐阅读(无条件展示) -->
-    <view class="section" v-if="indexArticles.length > 0">
+    <view class="section">
       <view class="section-header">
         <text class="section-title">📖 推荐阅读</text>
         <text class="section-more" @click="goArticles">更多 ›</text>
       </view>
-      <view class="article-list">
+      <view v-if="articlesLoading" class="loading-state">
+        <text class="loading-text">加载中...</text>
+      </view>
+      <view v-else-if="indexArticles.length === 0" class="empty-state" style="padding: 40rpx 0;">
+        <text class="empty-text">暂无文章</text>
+      </view>
+      <view class="article-list" v-else>
         <view class="article-card" v-for="article in indexArticles" :key="article.id" @click="goArticleDetail(article.id)">
           <view class="article-category" :style="{ background: article.categoryColor }">
             <text class="article-category-text">{{ article.category }}</text>
@@ -194,6 +229,10 @@ export default {
       currentRole: _currentRole,
       showTabTransition: true,
 
+      // splash loading ovarlay(首次进入时展示,数据加载完关闭)
+      splashOverlay: false,
+      splashOverlayFading: false,
+
       typeList: [
         { label: '全部', value: '' },
         { label: '活动', value: 'activity' },
@@ -206,29 +245,75 @@ export default {
       products: [],
       loading: false,
       dimensionActivities: [],
-      indexArticles: []
+      indexArticles: [],
+      articlesLoading: false
     }
   },
   onLoad(options) {
     this._doInit(options)
   },
   onShow() {
-    // Tabbar 切换过渡动画:从 splash 首次进入时跳过(splash 本身就是过渡)
-    var fromSplash = uni.getStorageSync('_fromSplash')
-    if (fromSplash === '1') {
-      uni.removeStorageSync('_fromSplash')
-    } else {
-      this.showTabTransition = true
-      var self = this
-      setTimeout(function() {
-        self.showTabTransition = false
-      }, 1100)
+    // 从 splash 首次进入:展示 splash overlay,数据加载完后自动关闭
+    var showOverlay = uni.getStorageSync('_showSplashOverlay')
+    if (showOverlay === '1') {
+      uni.removeStorageSync('_showSplashOverlay')
+      this._showSplashOverlayMask()
     }
 
+    // Tabbar 切换过渡动画
+    this.showTabTransition = true
+    var self = this
+    setTimeout(function() {
+      self.showTabTransition = false
+    }, 1800)
+
     // 始终执行初始化,确保登录态变更后及时切换视图
     this._doInit()
   },
   methods: {
+    // ===== Splash overlay (首次进入蒙板) =====
+    _showSplashOverlayMask() {
+      this.splashOverlay = true
+      this._splashLoadCount = 0
+      this._splashMinPassed = false
+
+      // 最小展示 800ms,让 overlay 有足够时间呈现
+      var self = this
+      setTimeout(function() {
+        self._splashMinPassed = true
+        self._tryHideSplash()
+      }, 800)
+
+      // 最长 6 秒强制关闭,防止 API 卡死导致蒙板不消失
+      setTimeout(function() {
+        if (self.splashOverlay) {
+          self._hideSplashOverlay()
+        }
+      }, 6000)
+    },
+    _checkSplashReady() {
+      this._splashLoadCount++
+      this._tryHideSplash()
+    },
+    _tryHideSplash() {
+      if (this.splashOverlayFading) return
+      // 最小展示时间未到 → 等待
+      if (!this._splashMinPassed) return
+      // 已登录用户只需最小时间;游客需要等待 3 个 API 全部返回
+      if (this.isLoggedIn() || this._splashLoadCount >= 3) {
+        this._hideSplashOverlay()
+      }
+    },
+    _hideSplashOverlay() {
+      if (this.splashOverlayFading) return
+      this.splashOverlayFading = true
+      var self = this
+      setTimeout(function() {
+        self.splashOverlay = false
+        self.splashOverlayFading = false
+      }, 400)
+    },
+
     _doInit(options) {
       options = options || {}
       if (!this.isLoggedIn()) {
@@ -279,34 +364,45 @@ export default {
       uni.setStorageSync('currentRole', this.currentRole)
     },
     // ===== 发现页 / 未登录态方法 =====
-    loadProducts() {
+    loadProducts: function() {
       this.loading = true
       var params = { page: 1, size: 10 }
       if (this.currentType) {
         params.productType = this.currentType
       }
+      var self = this
       productList(params).then(function(res) {
-        this.loading = false
-        if (res.code === 200 && res.data) {
-          this.products = res.data.records || []
+        self.loading = false
+        if (res && res.code === 200) {
+          // res.data is the response wrapper, products are in res.data.data or res.data.records
+          var rawData = res.data && (Array.isArray(res.data) ? res.data : (res.data.data || res.data.records || []))
+          self.products = Array.isArray(rawData) ? rawData : []
         }
-      }.bind(this)).catch(function() {
-        this.loading = false
-      }.bind(this))
+        self._checkSplashReady()
+      }).catch(function() {
+        self.loading = false
+        self._checkSplashReady()
+      })
     },
     loadDimensionActivities: function() {
       var self = this
       getActivityList({ dimensionCode: '', page: 1, size: 3 }).then(function(res) {
-        if (res && res.data) {
-          var list = Array.isArray(res.data) ? res.data : (res.data.records || [])
-          self.dimensionActivities = list.slice(0, 3)
+        if (res && res.code === 200) {
+          // res.data is the response wrapper, activities are in res.data.data or res.data.records
+          var rawData = res.data && (Array.isArray(res.data) ? res.data : (res.data.data || res.data.records || []))
+          self.dimensionActivities = Array.isArray(rawData) ? rawData.slice(0, 3) : []
         }
-      }.bind(this)).catch(function() {})
+        self._checkSplashReady()
+      }).catch(function() {
+        self._checkSplashReady()
+      })
     },
     loadFeaturedArticles: function() {
       var self = this
+      this.articlesLoading = true
       getFeaturedArticles({ size: 5 }).then(function(res) {
-        if (res.code === 200 && res.data) {
+        self.articlesLoading = false
+        if (res && res.code === 200) {
           var gradientColors = [
             'linear-gradient(135deg, #10B981, #34D399)',
             'linear-gradient(135deg, #5B9BD5, #8FC5E8)',
@@ -314,11 +410,13 @@ export default {
             'linear-gradient(135deg, #F97316, #FB923C)',
             'linear-gradient(135deg, #6366F1, #818CF8)'
           ]
-          var list = Array.isArray(res.data) ? res.data : (res.data.records || [])
+          // res.data is the response wrapper, articles are in res.data.data
+          var rawList = res.data && (Array.isArray(res.data) ? res.data : (res.data.data || res.data.records || []))
+          var list = Array.isArray(rawList) ? rawList : []
           self.indexArticles = list.map(function(item, index) {
             return {
               id: item.id,
-              category: item.categoryName || '热门文章',
+              category: item.categoryName || item.category || '热门文章',
               categoryColor: gradientColors[index % gradientColors.length],
               title: item.title || '',
               summary: item.summary || '',
@@ -327,7 +425,11 @@ export default {
             }
           })
         }
-      }.bind(this)).catch(function() {})
+        self._checkSplashReady()
+      }).catch(function() {
+        self.articlesLoading = false
+        self._checkSplashReady()
+      })
     },
     goActivities: function() {
       uni.navigateTo({ url: '/pages/discover/index?type=activity' })
@@ -383,7 +485,7 @@ export default {
                   uni.showToast({ title: '加入成功', icon: 'success' })
                   // 刷新页面
                   setTimeout(() => {
-                    uni.reLaunch({ url: '/pages/index/index' })
+                    uni.switchTab({ url: '/pages/index/index' })
                   }, 1500)
                 }
               } catch (e) {
@@ -813,4 +915,137 @@ export default {
   font-size: 22rpx;
   color: #bbb;
 }
+
+/* ===== Splash Loading Overlay(首次进入蒙板) ===== */
+.splash-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 9999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: linear-gradient(160deg, #FFF7ED 0%, #FFEDD5 50%, #FED7AA 100%);
+  overflow: hidden;
+  opacity: 1;
+  transition: opacity 0.4s ease;
+}
+.splash-overlay.fade-out {
+  opacity: 0;
+  pointer-events: none;
+}
+.splash-overlay-bg {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  pointer-events: none;
+}
+.splash-overlay-circle {
+  position: absolute;
+  border-radius: 50%;
+  opacity: 0.15;
+}
+.splash-overlay-circle.c1 {
+  width: 400rpx; height: 400rpx;
+  background: #FF8C42;
+  top: -100rpx; right: -100rpx;
+  animation: splash-float 6s ease-in-out infinite;
+}
+.splash-overlay-circle.c2 {
+  width: 300rpx; height: 300rpx;
+  background: #F59E0B;
+  bottom: 100rpx; left: -80rpx;
+  animation: splash-float 8s ease-in-out infinite reverse;
+}
+.splash-overlay-circle.c3 {
+  width: 200rpx; height: 200rpx;
+  background: #FF6B9D;
+  top: 300rpx; right: 50rpx;
+  animation: splash-float 5s ease-in-out infinite 1s;
+}
+@keyframes splash-float {
+  0%, 100% { transform: translateY(0) scale(1); }
+  50% { transform: translateY(-20rpx) scale(1.05); }
+}
+
+.splash-overlay-body {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  animation: splash-overlay-fade-in 0.8s ease forwards;
+}
+@keyframes splash-overlay-fade-in {
+  from { opacity: 0; transform: translateY(30rpx); }
+  to   { opacity: 1; transform: translateY(0); }
+}
+
+.splash-overlay-logo-wrap {
+  width: 160rpx; height: 160rpx;
+  border-radius: 40rpx;
+  background: rgba(255,255,255,0.95);
+  display: flex; align-items: center; justify-content: center;
+  margin-bottom: 24rpx;
+  box-shadow: 0 8rpx 32rpx rgba(249,115,22,0.25);
+}
+.splash-overlay-logo {
+  width: 100rpx; height: 100rpx;
+}
+
+.splash-overlay-brand {
+  font-size: 60rpx; font-weight: 700;
+  color: #7C2D12;
+  letter-spacing: 8rpx; margin-bottom: 4rpx;
+  text-shadow: 0 2rpx 12rpx rgba(0,0,0,0.1);
+}
+.splash-overlay-en {
+  font-size: 22rpx;
+  color: rgba(124,45,18,0.6);
+  letter-spacing: 4rpx; margin-bottom: 20rpx;
+}
+
+.splash-overlay-dims {
+  display: flex; flex-direction: row;
+  align-items: center; justify-content: center;
+  gap: 20rpx; margin: 24rpx 0;
+}
+.splash-overlay-dim {
+  display: flex; flex-direction: column;
+  align-items: center; gap: 6rpx;
+}
+.splash-overlay-dim-icon { font-size: 36rpx; line-height: 1; }
+.splash-overlay-dim-label { font-size: 20rpx; color: rgba(124,45,18,0.7); line-height: 1; font-weight: 500; }
+
+.splash-overlay-tagline {
+  font-size: 28rpx;
+  color: rgba(124,45,18,0.8);
+  letter-spacing: 2rpx; font-weight: 500;
+}
+
+/* Loading dots */
+.splash-overlay-loading {
+  position: absolute;
+  bottom: 120rpx;
+  left: 0; right: 0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  gap: 12rpx;
+}
+.splash-overlay-dot {
+  width: 14rpx; height: 14rpx;
+  border-radius: 50%;
+  background: rgba(249,115,22,0.6);
+  animation: splash-dot-bounce 1.4s ease-in-out infinite both;
+}
+.splash-overlay-dot:nth-child(1) { animation-delay: -0.32s; }
+.splash-overlay-dot:nth-child(2) { animation-delay: -0.16s; }
+.splash-overlay-dot:nth-child(3) { animation-delay: 0s; }
+@keyframes splash-dot-bounce {
+  0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
+  40%           { transform: scale(1);   opacity: 1; }
+}
 </style>

+ 11 - 29
cfc-frontend/pages/mind/index.vue

@@ -156,31 +156,12 @@
     </view>
 
     <!-- 推荐阅读 -->
-    <view class="section" v-if="sectionVisible('recommended_reading')">
-      <view class="section-header">
-        <text class="section-title">推荐阅读</text>
-      </view>
-      <view class="article-list">
-        <view class="article-card" v-for="article in emotionArticles" :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>
+    <DimensionArticles
+      dimensionCode="mind"
+      :articles="emotionArticles"
+      :isLoggedIn="isLoggedIn"
+      @articleClick="goArticleDetail"
+      @moreArticles="goArticles" />
 
     <!-- ===== 传统文化(八字/五行/星座/灵数) ===== -->
     <view class="culture-section" v-if="isLoggedIn && traditionalData">
@@ -298,13 +279,14 @@ import UserQuickEntry from '../../components/UserQuickEntry.vue'
 import DimensionTasks from '../../components/DimensionTasks.vue'
 import DimensionActivities from '../../components/DimensionActivities.vue'
 import DimensionProducts from '../../components/DimensionProducts.vue'
+import DimensionArticles from '../../components/DimensionArticles.vue'
 import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
 import PsychCrisisBanner from '../../components/PsychCrisisBanner.vue'
 import { getVisibleSections, getFeaturedArticles, getEmiReport, getDailyTip, getFamilyEnergySandbox, getTodayTasksByCategory, getActivityList, getProductsByDomain, getChildren, getVisibleFamilyMembers, getEnergyOverview } from '../../utils/api.js'
 import config from '../../config.js'
 
 export default {
-  components: { TabTransition, PageBanner, RadarChart, LoginGuideCard, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, FamilyRelationGraph, PsychCrisisBanner },
+  components: { TabTransition, PageBanner, RadarChart, LoginGuideCard, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, FamilyRelationGraph, PsychCrisisBanner },
   data() {
     return {
       isLoggedIn: false,
@@ -649,9 +631,9 @@ export default {
       }
       if (item.page) uni.navigateTo({ url: item.page })
     },
-    goArticleDetail: function(id) {
-      var url = '/pages/mind-detail/article-detail?id=' + id
-      uni.navigateTo({ url: url })
+    goArticleDetail: function(article) {
+      var id = article.id || article
+      uni.navigateTo({ url: '/pages/mind-detail/article-detail?id=' + id })
     },
     // ===== 孩子列表与维度数据 =====
     loadChildren: function() {

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

@@ -102,7 +102,7 @@ export default {
     var self = this
     setTimeout(function() {
       self.showTabTransition = false
-    }, 1100)
+    }, 1800)
 
     if (!uni.getStorageSync('token')) return
     this.loadMascot()

+ 5 - 59
cfc-frontend/pages/splash/splash.vue

@@ -53,13 +53,6 @@
 
       <!-- Tagline -->
       <text class="splash-tagline">给全家的一站式幸福提案</text>
-
-      <!-- Loading dots at bottom -->
-      <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>
 </template>
@@ -67,23 +60,11 @@
 <script>
 export default {
   name: 'SplashPage',
-  data() {
-    return {
-      ready: false
-    }
-  },
-  onLoad() {
-    this.ready = true
-  },
   onReady() {
-    // 标记来自 splash 页,首页首次加载跳过 tab-transition
-    uni.setStorageSync('_fromSplash', '1')
-    // 至少展示 1 秒后跳转
-    setTimeout(function() {
-      uni.switchTab({
-        url: '/pages/index/index'
-      })
-    }, 1200)
+    // 标记需要在首页显示 splash overlay(仅在首次进入时)
+    uni.setStorageSync('_showSplashOverlay', '1')
+    // 立即跳转首页,首页会展示 splash overlay 并加载数据
+    uni.switchTab({ url: '/pages/index/index' })
   }
 }
 </script>
@@ -214,41 +195,6 @@ export default {
   font-weight: 500;
 }
 
-/* ===== Loading Dots ===== */
-.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(249, 115, 22, 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;
@@ -306,4 +252,4 @@ export default {
     transform: translateY(0);
   }
 }
-</style>
+</style>