Răsfoiți Sursa

feat: integrate RecommendedSection into all 6 dimension pages

E2E Test Bot 1 lună în urmă
părinte
comite
af800699c5

+ 122 - 0
cfc-frontend/mixins/recommend-mixin.js

@@ -0,0 +1,122 @@
+/**
+ * 推荐系统 Mixin — 为维度页面提供个性化推荐加载与行为追踪
+ *
+ * 用法:
+ *   import recommendMixin from '../../mixins/recommend-mixin'
+ *   export default {
+ *     mixins: [recommendMixin],
+ *     methods: {
+ *       loadDimensionData: function() {
+ *         this.loadRecommendations('action')  // 传入当前维度代码
+ *       }
+ *     }
+ *   }
+ *
+ * 模板:
+ *   <RecommendedSection
+ *     v-if="recommendItems.length > 0"
+ *     :items="recommendItems"
+ *     :hasMore="recommendHasMore"
+ *     @itemClick="onRecommendItemClick"
+ *     @more="$emit('more')" />
+ */
+import { getRecommendList, trackRecommendAction } from '../../utils/api'
+
+var recommendMixin = {
+  data: function() {
+    return {
+      recommendItems: [],
+      recommendHasMore: false,
+      _recommendDimension: null
+    }
+  },
+  methods: {
+    /**
+     * 加载个性化推荐列表
+     * @param {String} dimensionCode 维度代码 body/mind/wisdom/action/wealth
+     * @param {Number} limit 推荐数量
+     */
+    loadRecommendations: function(dimensionCode, limit) {
+      limit = limit || 6
+      this._recommendDimension = dimensionCode
+      var self = this
+      getRecommendList({
+        dimension: dimensionCode,
+        limit: limit
+      }).then(function(res) {
+        if (res.code === 200 && res.data && res.data.items) {
+          self.recommendItems = res.data.items || []
+          self.recommendHasMore = res.data.hasMore || false
+        }
+      }).catch(function() {
+        // 静默失败,不阻塞页面
+      })
+    },
+
+    /**
+     * 推荐项点击处理 — 跳转 + 行为追踪
+     */
+    onRecommendItemClick: function(item) {
+      if (!item) return
+      // 追踪点击行为
+      this._trackRecommend(item, 'click')
+      // 根据内容类型跳转
+      this._navigateToRecommend(item)
+    },
+
+    /**
+     * 内部:追踪推荐行为
+     */
+    _trackRecommend: function(item, action) {
+      trackRecommendAction({
+        contentType: item.contentType,
+        contentId: item.id,
+        action: action
+      }).catch(function() {
+        // 静默
+      })
+    },
+
+    /**
+     * 内部:根据内容类型导航
+     */
+    _navigateToRecommend: function(item) {
+      var url = ''
+      switch (item.contentType) {
+        case 'article':
+          url = '/pages/article-center/article-detail/article-detail?id=' + item.id
+          break
+        case 'task':
+          url = '/pages/tasks/task-detail/task-detail?id=' + item.id
+          break
+        case 'product':
+          url = '/pages/shop/product-detail/product-detail?id=' + item.id
+          break
+        case 'activity':
+          url = '/pages/activity/activity-detail/activity-detail?id=' + item.id
+          break
+        default:
+          return
+      }
+      if (url) {
+        uni.navigateTo({ url: url })
+      }
+    }
+  },
+
+  /**
+   * 页面显示时展示推荐内容(曝光追踪)
+   */
+  onShow: function() {
+    if (this.recommendItems && this.recommendItems.length > 0) {
+      var self = this
+      // 只追踪前3条曝光(避免过多请求)
+      var showItems = this.recommendItems.slice(0, 3)
+      showItems.forEach(function(item) {
+        self._trackRecommend(item, 'show')
+      })
+    }
+  }
+}
+
+export default recommendMixin

+ 16 - 1
cfc-frontend/pages/action/index.vue

@@ -114,6 +114,14 @@
           </view>
         </view>
 
+        <!-- 推荐 -->
+        <RecommendedSection
+          v-if="recommendItems.length > 0"
+          :items="recommendItems"
+          :hasMore="recommendHasMore"
+          @itemClick="onRecommendItemClick"
+          @more="goMoreRecommend" />
+
         <!-- 圈子详情弹窗 -->
         <CircleDetail
           :visible="circleDetailVisible"
@@ -154,10 +162,13 @@ import CircleDetail from '../../components/CircleDetail.vue'
 import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
 import WuxingSandbox from '../../components/wuxing-sandbox.vue'
 import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
+import RecommendedSection from '../../components/RecommendedSection.vue'
+import recommendMixin from '../../mixins/recommend-mixin'
 import { getVisibleSections, getEnergyOverview, getFamilyEnergySandbox, getTodayTasksByCategory, getActivityList, getProductsByDomain, getChildren, getVisibleFamilyMembers, getFeaturedArticles, getMyCircles, discoverCircles, joinGeneralCircle, leaveGeneralCircle } from '../../utils/api.js'
 
 export default {
-  components: { TabTransition, PageBanner, LoginGuideCard, FamilyEnergyBar, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, FamilyRelationGraph, WuxingSandbox, FamilyMemberStrip, CircleCard, CircleDetail },
+  mixins: [recommendMixin],
+  components: { TabTransition, PageBanner, LoginGuideCard, FamilyEnergyBar, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, FamilyRelationGraph, WuxingSandbox, FamilyMemberStrip, CircleCard, CircleDetail, RecommendedSection },
   data() {
     return {
       showTabTransition: true,
@@ -315,6 +326,10 @@ export default {
       this.loadDimensionActivities()
       this.loadDimensionProducts()
       this.loadCircleData()
+      this.loadRecommendations('action')
+    },
+    goMoreRecommend: function() {
+      uni.navigateTo({ url: '/pages/discover/index' })
     },
     loadEnergyData: function() {
       var self = this

+ 15 - 2
cfc-frontend/pages/body/index.vue

@@ -121,7 +121,13 @@
       @articleClick="goArticleDetail"
       @moreArticles="goMoreArticles" />
 
-    
+    <!-- 个性化推荐 -->
+    <RecommendedSection
+      v-if="recommendItems.length > 0"
+      :items="recommendItems"
+      :hasMore="recommendHasMore"
+      @itemClick="onRecommendItemClick"
+      @more="goMoreRecommend" />
 
     <!-- 底部占位 -->
     <view class="bottom-spacer"></view>
@@ -142,10 +148,13 @@ import DimensionArticles from '../../components/DimensionArticles.vue'
 import RadarChart from '../../components/RadarChart.vue'
 import AIFloatingAvatar from '../../components/AIFloatingAvatar.vue'
 import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
+import RecommendedSection from '../../components/RecommendedSection.vue'
+import recommendMixin from '../../mixins/recommend-mixin'
 import { getVisibleSections, getEnergyOverview, getChildren, getTodayTasksByCategory, getActivityList, getProductsByDomain, getFamilyEnergySandbox, getVisibleFamilyMembers, getDimensionOverview, getFeaturedArticles } from '../../utils/api.js'
 
 export default {
-  components: { TabTransition, PageBanner, LoginGuideCard, FamilyEnergyBar, DimensionTasks, DimensionActivities, DimensionProducts, DimensionProductList, DimensionArticles, RadarChart, AIFloatingAvatar, FamilyMemberStrip },
+  mixins: [recommendMixin],
+  components: { TabTransition, PageBanner, LoginGuideCard, FamilyEnergyBar, DimensionTasks, DimensionActivities, DimensionProducts, DimensionProductList, DimensionArticles, RadarChart, AIFloatingAvatar, FamilyMemberStrip, RecommendedSection },
   data() {
     return {
       isLoggedIn: false,
@@ -295,6 +304,10 @@ export default {
       this.loadDimensionProducts()
       this.loadSandboxData()
       this.loadHealthDimensions()
+      this.loadRecommendations('body')
+    },
+    goMoreRecommend: function() {
+      uni.navigateTo({ url: '/pages/discover/index' })
     },
     loadHealthDimensions: function() {
       var memberId = this.activeChildId || (this.userInfo && this.userInfo.userId)

+ 17 - 1
cfc-frontend/pages/index-home/index.vue

@@ -147,6 +147,14 @@
       </view>
     </view>
 
+    <!-- 个性化推荐 -->
+    <RecommendedSection
+      v-if="recommendItems.length > 0"
+      :items="recommendItems"
+      :hasMore="recommendHasMore"
+      @itemClick="onRecommendItemClick"
+      @more="goMoreRecommend" />
+
     <!-- 圈子详情弹窗 -->
     <CircleDetail
       :visible="circleDetailVisible"
@@ -353,6 +361,8 @@ import CircleCard from '../../components/CircleCard.vue'
 import CircleDetail from '../../components/CircleDetail.vue'
 import LoginGuideCard from '../../components/LoginGuideCard.vue'
 import FamilyChallengeCard from '../../components/FamilyChallengeCard.vue'
+import RecommendedSection from '../../components/RecommendedSection.vue'
+import recommendMixin from '../../mixins/recommend-mixin'
 import { acceptParentInvite, productList, getActivityList, getFeaturedArticles, getFamilyEnergySandbox, getVisibleFamilyMembers, getTodayTasksByCategory, getChildren, getEnergyOverview, getProductsByDomain, getChallengeList, getMyCircles, discoverCircles, joinGeneralCircle, leaveGeneralCircle } from '../../utils/api.js'
 
 var DIMENSIONS = [
@@ -364,6 +374,7 @@ var DIMENSIONS = [
 ]
 
 export default {
+  mixins: [recommendMixin],
   components: {
     PageBanner,
     WuxingSandbox,
@@ -377,7 +388,8 @@ export default {
     CircleCard,
     CircleDetail,
     LoginGuideCard,
-    FamilyChallengeCard
+    FamilyChallengeCard,
+    RecommendedSection
   },
   data() {
     // 从 storage 预取登录态/角色,确保首次渲染即正确(WeChat 渲染层/逻辑层分离架构下 onLoad 执行前已渲染)
@@ -562,6 +574,10 @@ export default {
       this.loadChallenges()
       this.loadMyCircles()
       this.loadDiscoverCircles()
+      this.loadRecommendations('action')
+    },
+    goMoreRecommend: function() {
+      uni.navigateTo({ url: '/pages/discover/index' })
     },
     loadEnergyData: function() {
       var self = this

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

@@ -152,6 +152,14 @@
       </view>
     </view>
 
+    <!-- 个性化推荐 -->
+    <RecommendedSection
+      v-if="recommendItems.length > 0"
+      :items="recommendItems"
+      :hasMore="recommendHasMore"
+      @itemClick="onRecommendItemClick"
+      @more="goMoreRecommend" />
+
     <!-- 底部占位 -->
     <view class="bottom-spacer"></view>
     <AIFloatingAvatar />
@@ -174,9 +182,12 @@ import FamilyTianpanCard from '../../components/FamilyTianpanCard.vue'
 import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
 import { getVisibleSections, getFeaturedArticles, getEmiReport, getFamilyEnergySandbox, getTodayTasksByCategory, getActivityList, getProductsByDomain, getChildren, getVisibleFamilyMembers, getEnergyOverview, getContactList, getHealthAlerts, getMilestones, getDanReportByChild, getFamilyFortune } from '../../utils/api.js'
 import config from '../../config.js'
+import RecommendedSection from '../../components/RecommendedSection.vue'
+import recommendMixin from '../../mixins/recommend-mixin'
 
 export default {
-  components: { TabTransition, PageBanner, RadarChart, LoginGuideCard, FamilyEnergyBar, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, PsychCrisisBanner, AIFloatingAvatar, FamilyTianpanCard, FamilyMemberStrip },
+  mixins: [recommendMixin],
+components: { TabTransition, PageBanner, RadarChart, LoginGuideCard, FamilyEnergyBar, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, PsychCrisisBanner, AIFloatingAvatar, FamilyTianpanCard, FamilyMemberStrip, RecommendedSection },
   data() {
     return {
       isLoggedIn: false,
@@ -448,6 +459,7 @@ export default {
     // 游客也能浏览活动和商品
     this.loadDimensionActivities()
     this.loadDimensionProducts()
+    this.loadRecommendations('mind')
     this.loadFamilyFortune()
     this._watchPageReady()
   },
@@ -748,6 +760,9 @@ export default {
       var id = article.id || article
       uni.navigateTo({ url: '/pages/mind-detail/article-detail?id=' + id })
     },
+    goMoreRecommend: function() {
+      uni.navigateTo({ url: '/pages/discover/index' })
+    },
     // ===== 孩子列表与维度数据 =====
     loadChildren: function() {
       var self = this

+ 16 - 1
cfc-frontend/pages/wealth/index.vue

@@ -156,6 +156,14 @@
         :familyId="activeChildId"
         title="为你推荐" />
 
+      <!-- 个性化推荐 -->
+      <RecommendedSection
+        v-if="recommendItems.length > 0"
+        :items="recommendItems"
+        :hasMore="recommendHasMore"
+        @itemClick="onRecommendItemClick"
+        @more="goMoreRecommend" />
+
       <!-- 底部菜单 -->
       <view class="menu-list">
         <view class="menu-item" @click="goToProfile">
@@ -182,6 +190,8 @@ import DimensionProductList from '../../components/DimensionProductList.vue'
 import WealthSubScores from '../../components/WealthSubScores.vue'
 import BodyWealthAlert from '../../components/BodyWealthAlert.vue'
 import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
+import RecommendedSection from '../../components/RecommendedSection.vue'
+import recommendMixin from '../../mixins/recommend-mixin'
 import {
   verifyPassword,
   getChildren,
@@ -193,7 +203,8 @@ import {
 } from '../../utils/api.js'
 
 export default {
-  components: { TabTransition, PageBanner, DimensionProductList, WealthSubScores, BodyWealthAlert, FamilyMemberStrip },
+  mixins: [recommendMixin],
+  components: { TabTransition, PageBanner, DimensionProductList, WealthSubScores, BodyWealthAlert, FamilyMemberStrip, RecommendedSection },
   data() {
     return {
       showTabTransition: true,
@@ -264,12 +275,16 @@ export default {
     this.loadWealthDetail()
     this.loadWealthData()
     this.loadFamilyMembersVisible()
+    this.loadRecommendations('wealth')
     this._watchPageReady()
   },
   methods: {
     onFamilyMemberSelect(member) {
       this.selectedMemberId = member.id
     },
+    goMoreRecommend: function() {
+      uni.navigateTo({ url: '/pages/discover/index' })
+    },
     loadFamilyMembersVisible() {
       var self = this
       getVisibleFamilyMembers().then(function(res) {

+ 16 - 1
cfc-frontend/pages/wisdom/index.vue

@@ -134,6 +134,14 @@
       </view>
     </view>
 
+    <!-- 个性化推荐 -->
+    <RecommendedSection
+      v-if="recommendItems.length > 0"
+      :items="recommendItems"
+      :hasMore="recommendHasMore"
+      @itemClick="onRecommendItemClick"
+      @more="goMoreRecommend" />
+
     <!-- 底部占位 -->
     <view class="bottom-spacer"></view>
     <AIFloatingAvatar />
@@ -152,10 +160,13 @@ import DimensionProducts from '../../components/DimensionProducts.vue'
 import DimensionArticles from '../../components/DimensionArticles.vue'
 import AIFloatingAvatar from '../../components/AIFloatingAvatar.vue'
 import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
+import RecommendedSection from '../../components/RecommendedSection.vue'
+import recommendMixin from '../../mixins/recommend-mixin'
 import { getVisibleSections, getChildren, getTodayTasksByCategory, getActivityList, getProductsByDomain, getFamilyEnergySandbox, getAssessmentLatestResult, getFeaturedArticles, getDanReportByChild } from '../../utils/api.js'
 
 export default {
-  components: { TabTransition, PageBanner, LoginGuideCard, RadarChart, FamilyEnergyBar, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, AIFloatingAvatar, FamilyMemberStrip },
+  mixins: [recommendMixin],
+  components: { TabTransition, PageBanner, LoginGuideCard, RadarChart, FamilyEnergyBar, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, AIFloatingAvatar, FamilyMemberStrip, RecommendedSection },
   data() {
     return {
       isLoggedIn: false,
@@ -239,9 +250,13 @@ export default {
     this.loadDimensionProducts()
     // 游客也能浏览文章
     this.loadFeaturedArticles()
+    this.loadRecommendations('wisdom')
     this._watchPageReady()
   },
   methods: {
+    goMoreRecommend: function() {
+      uni.navigateTo({ url: '/pages/discover/index' })
+    },
     onFamilyMemberSelect: function(member) {
       this.selectedMemberId = member.id
     },