Преглед изворни кода

feat(frontend): 行维度落地页挂载 - 五维弹窗 action 跳转到专属页面

- 新增 pages/action-detail/index.vue 行维度落地页(tab-transition 过渡 + PageBanner + FamilyMemberStrip + FamilyEnergyBar + FamilyRelationGraph 关系图谱 + 功能入口 + DimensionTasks/Activities/Products + DimensionIntroCard)
- pages.json 注册 pages/action-detail/index 页面
- custom-tab-bar dimMap.action 从首页改为 /pages/action-detail/index
asus пре 2 месеци
родитељ
комит
1d774b8446

+ 1 - 1
cfc-frontend/custom-tab-bar/index.vue

@@ -176,7 +176,7 @@ export default {
     goDimension(dim) {
       this.closeDimPanel()
       var dimMap = {
-        action: '/pages/index-home/index',
+        action: '/pages/action-detail/index',
         mind: '/pages/mind-detail/index',
         body: '/pages/body-detail/index',
         wisdom: '/pages/wisdom-detail/index',

+ 6 - 0
cfc-frontend/pages.json

@@ -1079,6 +1079,12 @@
     {
       "root": "pages/action-detail",
       "pages": [
+        {
+          "path": "index",
+          "style": {
+            "navigationBarTitleText": "行"
+          }
+        },
         {
           "path": "interaction-log",
           "style": {

+ 451 - 0
cfc-frontend/pages/action-detail/index.vue

@@ -0,0 +1,451 @@
+<template>
+  <view class="container">
+    <!-- Tabbar 切换过渡页 -->
+    <tab-transition v-if="showTabTransition" dimCode="action" ref="tabTransition" @close="showTabTransition = false" />
+
+    <!-- 品牌头部 -->
+    <PageBanner theme="action"
+      tagline="人际和谐 · 关系滋养"
+      quote="和谐关系滋养幸福家庭" />
+
+    <!-- 品牌定位语(登录前显示) -->
+    <view class="brand-strap" v-if="!isLoggedIn">
+      <text class="brand-strap-text">改善全家关系与沟通能力</text>
+    </view>
+
+    <!-- 家庭成员选择条 -->
+    <FamilyMemberStrip
+      v-if="isLoggedIn"
+      :members="familyMembersVisible"
+      :selectedMemberId="selectedMemberId"
+      @select="onFamilyMemberSelect"
+      @removed="loadFamilyMembersVisible" />
+
+    <!-- 游客引导语(每月更换) -->
+    <view class="monthly-guidance" v-if="!isLoggedIn">
+      <text class="guidance-icon">🌿</text>
+      <text class="guidance-text">{{ monthlyGuidance }}</text>
+    </view>
+
+    <!-- 登录后:五维能量条 -->
+    <FamilyEnergyBar
+      v-if="isLoggedIn"
+      dimensionCode="action"
+      :sandboxData="sandboxData"
+      :dualDimension="dualDimension" />
+
+    <!-- 登录后:家庭关系图谱(行维度核心) -->
+    <view class="section" v-if="isLoggedIn">
+      <view class="section-header">
+        <text class="section-title">家庭关系图谱</text>
+        <text class="section-sub">拖拽节点,查看家人能量</text>
+      </view>
+      <FamilyRelationGraph
+        dimensionCode="action"
+        :selfId="selfId"
+        :members="graphMembers"
+        :energyMap="energyMapForGraph"
+        :intimacyMap="intimacyMapForGraph"
+        :interactive="true" />
+    </view>
+
+    <!-- 功能入口 -->
+    <view class="func-section" v-if="sectionVisible('func_entries')">
+      <view class="func-grid">
+        <view class="func-item" v-for="item in funcList" :key="item.label" @click="onFuncClick(item)">
+          <view class="func-icon-wrap">
+            <text class="func-icon">{{ item.icon }}</text>
+          </view>
+          <text class="func-label">{{ item.label }}</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 登录后:今日任务 -->
+    <DimensionTasks
+      v-if="isLoggedIn"
+      :memberId="activeChildId"
+      @taskClick="onTaskClick"
+      @moreTasks="goTasks" />
+
+    <!-- 登录后:活动 -->
+    <DimensionActivities
+      v-if="isLoggedIn"
+      :isLoggedIn="true"
+      @activityClick="goActivityDetail"
+      @moreActivities="goMoreActivities" />
+
+    <!-- 登录后:商品 -->
+    <DimensionProducts
+      v-if="isLoggedIn"
+      :isLoggedIn="true"
+      @productClick="goProductDetail"
+      @moreProducts="goMoreProducts" />
+
+    <!-- 行维度介绍卡片(登录后显示) -->
+    <DimensionIntroCard dimCode="action" v-if="isLoggedIn" learn-more-url="/pages/action-detail/dimension-intro" />
+
+    <!-- 底部占位 -->
+    <view class="bottom-spacer"></view>
+    <AIFloatingAvatar />
+  </view>
+</template>
+
+<script>
+import TabTransition from '../../components/tab-transition.vue'
+import PageBanner from '../../components/PageBanner.vue'
+import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
+import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
+import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
+import DimensionTasks from '../../components/DimensionTasks.vue'
+import DimensionActivities from '../../components/DimensionActivities.vue'
+import DimensionProducts from '../../components/DimensionProducts.vue'
+import DimensionIntroCard from '../../components/DimensionIntroCard.vue'
+import AIFloatingAvatar from '../../components/AIFloatingAvatar.vue'
+import { getVisibleSections, getEnergyOverview, getChildren, getFamilyEnergySandbox, getVisibleFamilyMembers } from '../../utils/api.js'
+
+export default {
+  components: { TabTransition, PageBanner, FamilyEnergyBar, FamilyRelationGraph, FamilyMemberStrip, DimensionTasks, DimensionActivities, DimensionProducts, DimensionIntroCard, AIFloatingAvatar },
+  data() {
+    return {
+      isLoggedIn: false,
+      role: null,
+      showTabTransition: true,
+      activeChildId: null,
+      selectedMemberId: null,
+      activeChildName: '',
+      userName: '',
+      children: [],
+      familyMembersVisible: [],
+      childrenChecked: false,
+      membersChecked: false,
+      sandboxData: null,
+      dualDimension: null,
+      selfId: null,
+      visibleSections: [],
+      funcList: [
+        { icon: '\u{1F4AC}', label: '记录互动', needLogin: true, page: '/pages/action-detail/interaction-log' },
+        { icon: '\u{1F4DD}', label: '关系问卷', needLogin: true, page: '/pages/action-detail/relationship-questionnaire' },
+        { icon: '\u{1F465}', label: '家庭成员', needLogin: true, page: '/pages/profile-extra/family-members' },
+        { icon: '\u{1F4CB}', label: '任务中心', needLogin: true, page: '/pages/tasks/tasks' }
+      ]
+    }
+  },
+  computed: {
+    /** 关系图谱:成员节点(来自沙盘数据) */
+    graphMembers: function() {
+      if (!this.sandboxData || !this.sandboxData.members) return []
+      return this.sandboxData.members.map(function(m) {
+        return {
+          id: m.memberId || m.id,
+          nickname: m.name || m.nickname || '成员',
+          memberType: m.memberType || 'child'
+        }
+      })
+    },
+    /** 关系图谱:节点能量映射 */
+    energyMapForGraph: function() {
+      var map = {}
+      if (this.sandboxData && this.sandboxData.members) {
+        for (var i = 0; i < this.sandboxData.members.length; i++) {
+          var m = this.sandboxData.members[i]
+          map[m.memberId || m.id] = {
+            bodyScore: m.bodyScore || 0,
+            mindScore: m.mindScore || 0,
+            actionScore: m.actionScore || 0
+          }
+        }
+      }
+      return map
+    },
+    intimacyMapForGraph: function() {
+      return {}
+    },
+    /** 每月引导语 */
+    monthlyGuidance: function() {
+      var month = new Date().getMonth()
+      var messages = [
+        '一月:新年伊始,增进家庭沟通',
+        '二月:春节团聚,珍惜亲子时光',
+        '三月:春暖花开,多陪伴孩子',
+        '四月:清明踏青,共享家庭活动',
+        '五月:劳动教育,培养家庭责任感',
+        '六月:儿童节,倾听孩子心声',
+        '七月:暑期陪伴,增进亲子关系',
+        '八月:开学准备,共同规划',
+        '九月:新学期,建立家庭规则',
+        '十月:国庆假期,家庭协作时光',
+        '十一月:感恩季节,表达家庭关爱',
+        '十二月:年终总结,回顾家庭成长'
+      ]
+      return messages[month] || messages[0]
+    }
+  },
+  onShow() {
+    this.showTabTransition = true
+
+    var token = uni.getStorageSync('token')
+    this.isLoggedIn = !!token
+    if (this.isLoggedIn) {
+      this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
+      this.userName = uni.getStorageSync('nickname') || uni.getStorageSync('userName') || '家长'
+      this.selfId = uni.getStorageSync('userId') || null
+    }
+    this.loadSectionConfig()
+    if (this.isLoggedIn) {
+      this.loadChildren()
+      this.loadFamilyMembersVisible()
+    }
+    this._watchPageReady()
+  },
+  methods: {
+    loadSectionConfig: function() {
+      var baseSections = ['func_entries']
+      if (!uni.getStorageSync('token')) {
+        this.visibleSections = baseSections
+        return
+      }
+      var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
+      var self = this
+      getVisibleSections({ pageKey: 'action', role: role }).then(function(res) {
+        if (res.code === 200 && res.data) {
+          var keys = res.data.map(function(s) { return s.sectionKey })
+          var merged = baseSections.slice()
+          keys.forEach(function(k) { if (merged.indexOf(k) === -1) merged.push(k) })
+          self.visibleSections = merged
+        }
+      }).catch(function(e) {
+        self.visibleSections = ['func_entries']
+      })
+    },
+    loadChildren: function() {
+      var self = this
+      getChildren().then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.children = res.data
+          var savedChildId = uni.getStorageSync('currentChildId')
+          var found = false
+          for (var i = 0; i < self.children.length; i++) {
+            if (self.children[i].memberId === savedChildId) {
+              self.activeChildId = savedChildId
+              self.activeChildName = self.children[i].name || self.children[i].nickname || '孩子'
+              found = true
+              break
+            }
+          }
+          if (!found && self.children.length > 0) {
+            self.activeChildId = self.children[0].memberId
+            self.activeChildName = self.children[0].name || self.children[0].nickname || '孩子'
+            uni.setStorageSync('currentChildId', self.activeChildId)
+          }
+          if (self.activeChildId) {
+            self.loadDashboardData()
+          }
+        }
+        self.childrenChecked = true
+      }).catch(function(e) {
+        console.log('获取孩子列表失败', e)
+        self.childrenChecked = true
+      })
+    },
+    loadDashboardData: function() {
+      if (!this.activeChildId) return
+      this.loadEnergyData()
+      this.loadSandboxData()
+    },
+    loadSandboxData: function() {
+      var self = this
+      getFamilyEnergySandbox().then(function(res) {
+        if (res && res.data) {
+          self.sandboxData = res.data
+        }
+      }).catch(function(e) {
+        console.log('获取沙盘数据失败', e)
+      })
+    },
+    loadEnergyData: function() {
+      var self = this
+      getEnergyOverview(this.activeChildId).then(function(res) {
+        if (res && res.data) {
+          var dims = res.data.dimensions
+          if (dims && dims.length > 0) {
+            for (var i = 0; i < dims.length; i++) {
+              if (dims[i].code === 'action') {
+                self.dualDimension = dims[i]
+                break
+              }
+            }
+          }
+        }
+      }).catch(function(e) {
+        console.log('获取能量概览失败', e)
+      })
+    },
+    onFamilyMemberSelect: function(member) {
+      this.selectedMemberId = member.id
+    },
+    loadFamilyMembersVisible: function() {
+      var self = this
+      getVisibleFamilyMembers().then(function(res) {
+        self.familyMembersVisible = res.data || []
+        self.membersChecked = true
+      }).catch(function(e) {
+        console.log('获取可见家庭成员失败', e)
+        self.familyMembersVisible = []
+        self.membersChecked = true
+      })
+    },
+    sectionVisible: function(key) {
+      return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1
+    },
+    onFuncClick: function(item) {
+      if (item.needLogin && !this.isLoggedIn) {
+        uni.showModal({
+          title: '提示',
+          content: '请先登录后使用此功能',
+          success: function(res) {
+            if (res.confirm) uni.navigateTo({ url: '/pages/login/login' })
+          }
+        })
+        return
+      }
+      if (item.page) uni.navigateTo({ url: item.page })
+    },
+    onTaskClick: function(task) {
+      uni.navigateTo({ url: '/pages/tasks/tasks' })
+    },
+    goActivityDetail: function(act) {
+      if (act && act.id) uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + act.id })
+    },
+    goMoreActivities: function() {
+      uni.navigateTo({ url: '/pages/activity/index' })
+    },
+    goProductDetail: function(prod) {
+      if (prod && prod.id) uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + prod.id })
+    },
+    goMoreProducts: function() {
+      uni.navigateTo({ url: '/pages/shop/index/index' })
+    },
+    goTasks: function() {
+      uni.navigateTo({ url: '/pages/tasks/tasks' })
+    },
+    _watchPageReady() {
+      var self = this
+      var unwatch = this.$watch(function() {
+        if (self.isLoggedIn) {
+          return !!(self.dualDimension || self.sandboxData)
+        }
+        return true
+      }, function(val) {
+        if (val) {
+          if (unwatch) unwatch()
+          if (self.$refs.tabTransition) self.$refs.tabTransition.markReady()
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding-bottom: 120rpx;
+}
+
+/* 品牌定位语 */
+.brand-strap {
+  margin: 16rpx 30rpx 0;
+  padding: 20rpx 28rpx;
+  background: linear-gradient(135deg, #F0FFF4, rgba(16,185,129,0.12));
+  border-radius: 16rpx;
+  border-left: 4rpx solid #10B981;
+}
+.brand-strap-text {
+  font-size: 26rpx;
+  color: #065F46;
+  font-weight: 500;
+}
+
+/* 每月引导语 */
+.monthly-guidance {
+  margin: 16rpx 30rpx 0;
+  padding: 20rpx 28rpx;
+  background: linear-gradient(135deg, #F0FFF4, rgba(16,185,129,0.08));
+  border-radius: 16rpx;
+  border-left: 4rpx solid #10B981;
+  display: flex;
+  align-items: center;
+  gap: 12rpx;
+}
+.guidance-icon { font-size: 32rpx; }
+.guidance-text {
+  font-size: 26rpx;
+  color: #065F46;
+  font-weight: 500;
+}
+
+/* ===== 通用区块 ===== */
+.section {
+  margin: 20rpx 30rpx 0;
+}
+.section-header {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 16rpx;
+}
+.section-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+}
+.section-sub {
+  font-size: 22rpx;
+  color: #999;
+}
+
+/* ===== 功能入口 ===== */
+.func-section {
+  margin: 20rpx 30rpx 0;
+}
+.func-grid {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx 16rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.func-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  flex: 1;
+}
+.func-icon-wrap {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 40rpx;
+  background: linear-gradient(135deg, #D1FAE5, #ECFDF5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 10rpx;
+}
+.func-icon {
+  font-size: 40rpx;
+}
+.func-label {
+  font-size: 22rpx;
+  color: #666;
+  font-weight: 500;
+}
+
+.bottom-spacer {
+  height: 40rpx;
+}
+</style>