Ver código fonte

refactor(miniapp): DimensionSubDims 组件下沉到分包

1. 将 DimensionSubDims 从主包 components/ 移出,复制到两个消费者分包:
   - pages/wealth/components/DimensionSubDims.vue
   - pages/action-detail/components/DimensionSubDims.vue
   原因:两者均为独立 subpackage root,微信禁止跨分包 import

2. 更新 import 路径为本地相对路径('./components/DimensionSubDims.vue')
   - pages/wealth/index.vue
   - pages/action-detail/index.vue(同时去重 DimensionIntroCard 重复注册)

3. pages/reminder-config/index.vue: 修复 :key 表达式警告
   - ch.type + '_' + index → getChannelKey(ch, index)
   - 'p-' + pIndex → getPraiseKey(pIndex)
   符合 uni-app 非 H5 平台 :key 规范(需使用方法调用而非表达式)
liaoxg 1 dia atrás
pai
commit
770a6ac4a2

+ 0 - 0
cfc-frontend/components/DimensionSubDims.vue → cfc-frontend/pages/action-detail/components/DimensionSubDims.vue


+ 1231 - 103
cfc-frontend/pages/action-detail/index.vue

@@ -4,146 +4,1274 @@
     <login-sheet ref="loginSheet" @success="onSheetLoginSuccess" />
     <!-- 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="currentView === 'self' && 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>
+
+    <!-- 游客价值卡(统一组件:维度色 CTA + 价值预览) -->
+<DimensionIntroCard
+  v-if="!isLoggedIn"
+  dimCode="action"
+  card-title="行维度 · 关系滋养"
+  :benefits="[
+    '家庭关系图谱与关键人分析',
+    '章鱼图 / 珍珠图能力资产',
+    '家庭关系质量与改善建议'
+  ]"
+  cta-text="登录绘制关系网络"
+  secondary-text="了解关系维度"
+  secondary-url="/pages/action-detail/dimension-intro" />
+
+    <!-- 登录后:五维能量条 -->
+    <FamilyEnergyBar
+      v-if="isLoggedIn"
+      dimensionCode="action"
+      :sandboxData="sandboxData"
+      :dualDimension="dualDimension" />
+
+    <!-- 关系解读 -->
+    <view class="section score-explain-card" :class="scoreExplainClass" v-if="isLoggedIn && currentView === 'self' && actionScore != null">
+      <view class="explain-header">
+        <view class="explain-icon-wrap" :class="'explain-icon-' + scoreExplainLevel">
+          <text class="explain-icon">{{ actionScore >= 80 ? '\u2713' : actionScore >= 60 ? '\u26A0' : '\u2715' }}</text>
+        </view>
+        <view class="explain-info">
+          <text class="explain-title">关系解读</text>
+          <text class="explain-score">{{ actionScore }}<text class="explain-score-unit">分</text></text>
+        </view>
+      </view>
+      <text class="explain-text">{{ actionScore >= 80 ? '家庭关系和谐,家人沟通顺畅。' : actionScore >= 60 ? '家庭关系一般,建议多花时间陪伴家人。' : '家庭关系需要改善,建议进行家庭沟通活动。' }}</text>
+    </view>
+    <!-- 行维度画像 - 总体说明 -->
+    <view class="dimension-portrait-section" v-if="isLoggedIn && actionPortrait">
+      <view class="section-header">
+        <text class="section-title">🧭 行维度画像</text>
+        <text class="portrait-tier">{{ actionPortrait.tierLabel }}</text>
+      </view>
+      <view class="portrait-summary">
+        <text class="portrait-desc">{{ actionPortrait.summary }}</text>
+      </view>
+      <view class="portrait-dimensions">
+        <view class="portrait-dim-item" v-for="dim in actionPortrait.dims" :key="dim.code">
+          <view class="portrait-dim-header">
+            <view class="portrait-dim-icon" :style="{ background: dim.color }">{{ dim.icon }}</view>
+            <text class="portrait-dim-name">{{ dim.name }}</text>
+            <text class="portrait-dim-score">{{ dim.score }}</text>
+          </view>
+          <view class="portrait-dim-bar-track">
+            <view class="portrait-dim-bar-fill" :style="{ width: dim.score + '%', background: dim.color }"></view>
+          </view>
+          <text class="portrait-dim-desc">{{ dim.desc }}</text>
+        </view>
+      </view>
+      <view class="portrait-tip" v-if="actionPortrait.tip">
+        <text class="portrait-tip-text">💡 {{ actionPortrait.tip }}</text>
+      </view>
+    </view>
+    <!-- 关系六维 -->
+    <dimension-sub-dims
+      v-if="isLoggedIn && actionSubDims.length > 0"
+      title="🌲 关系六维"
+      subtitle="家庭关系的六个方面"
+      accent-color="#10B981"
+      :dims="actionSubDims" />
+
+    <!-- 登录后:家庭关系(行维度核心) -->
+    <view class="section" v-if="isLoggedIn">
+      <view class="section-header">
+        <view class="section-title-wrap">
+          <text class="help-icon" @tap="showHelp('家庭关系', '了解每位家人的关系质量,改善沟通方式。点击家人节点,查看关系详情。')">?</text>
+          <view>
+            <text class="section-title">家庭关系</text>
+            <text class="section-sub">了解每位家人的关系质量,改善沟通方式 · 点击家人节点查看详情</text>
+          </view>
+        </view>
+      </view>
+      <FamilyRelationGraph
+        dimensionCode="action"
+        :selfId="selfId"
+        :members="graphMembers"
+        :energyMap="energyMapForGraph"
+        :intimacyMap="intimacyMapForGraph"
+        :interactive="true" />
+    </view>
+
+    <!-- 登录后:章鱼图(关键人拓展) -->
+    <view class="section" v-if="isLoggedIn">
+      <view class="section-header">
+        <view class="section-title-wrap">
+          <text class="help-icon" @tap="showHelp('章鱼图', '从成效出发,挖掘关键人,画出拓展路径。记录感恩时刻,建立有温度的人脉网络。')">?</text>
+          <view>
+            <text class="section-title">🐙 章鱼图</text>
+            <text class="section-sub">从成效出发,挖掘关键人,画出拓展路径</text>
+          </view>
+        </view>
+        <text class="section-link section-link-octopus" @tap="goOctopusEffectsList">+ 感恩记录</text>
+      </view>
+      <OctopusDiagram
+        ref="octopusDiagram"
+        :selfId="selfId"
+        :effects="octopusEffects"
+        :expansions="octopusExpansions"
+        :interactive="true"
+        @effect-click="onEffectClick"
+        @expansion-click="onExpansionClick"
+        @add-effect="goOctopusAddEffect"
+        @reanalyze="onReanalyze" />
+    </view>
+
+    <!-- 登录后:珍珠图(我有什么) -->
+    <view class="section" v-if="isLoggedIn">
+      <view class="section-header">
+        <view class="section-title-wrap">
+          <text class="help-icon" @tap="showHelp('珍珠图', '把可调用的资源串成珍珠项链——盘点我有什么。登记人脉、资源、信息缺口,画出一条清晰的成长珍珠链。')">?</text>
+          <view>
+            <text class="section-title">🫧 珍珠图</text>
+            <text class="section-sub">把可调用的资源串成珍珠项链——盘点我有什么</text>
+          </view>
+        </view>
+        <text class="section-link section-link-pearl" @tap="goPearlAddResource">+ 登记</text>
+      </view>
+      <PearlDiagram
+        ref="pearlDiagram"
+        :selfId="selfId"
+        :resources="pearlData"
+        :overdue-map="overdueMap"
+        :interactive="true"
+        @add="goPearlAddResource"
+        @delete="onDeletePearlResource" />
+    </view>
+
+    <!-- 登录后:能力图(谁会什么) -->
+    <view class="section" v-if="isLoggedIn">
+      <view class="section-header">
+        <view class="section-title-wrap">
+          <text class="help-icon" @tap="showHelp('能力图', '身边藏着什么技能——谁有能力帮到你。记录技能类帮助后,这里会画出大家的能力图谱。')">?</text>
+          <view>
+            <text class="section-title">💡 能力图</text>
+            <text class="section-sub">身边藏着什么技能——谁有能力帮到你</text>
+          </view>
+        </view>
+        <text class="section-link section-link-ability" @tap="goInteractionLog">+ 记录帮助</text>
+      </view>
+      <AbilityDiagram
+        ref="abilityDiagram"
+        :selfId="selfId"
+        :abilities="abilityData"
+        :interactive="true" />
+    </view>
+
+    <!-- 行6维关系域雷达图(登录后) -->
+    <view class="section" v-if="isLoggedIn && currentMemberEnergies">
+      <view class="section-header">
+        <text class="section-title">🫂 关系质量</text>
+      </view>
+      <RadarChart
+        :dimensions="actionDimensions"
+        :scores="actionScores"
+        :avg-scores="actionFamilyScores"
+        :population-scores="actionPopulationScores"
+        fillColor="#10B981"
+        gridColor="#D1FAE5"
+        labelColor="#065F46"
+        :width="580"
+        :height="580" />
+    </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"
+      dimension-code="action"
+      @productClick="goProductDetail"
+      @moreProducts="goMoreProducts" />
+
+    <!-- 行维度介绍卡片(登录后显示) -->
+    <DimensionIntroCard dimCode="action" v-if="isLoggedIn" learn-more-url="/pages/action-detail/dimension-intro" />
+
+    <!-- 底部占位 -->
+    <view class="bottom-spacer"></view>
+    <FloatingAvatar />
+
+    <!-- 帮助说明弹窗 -->
+    <view class="help-mask" v-if="helpPopup.visible" @tap="closeHelp">
+      <view class="help-dialog" @tap.stop>
+        <view class="help-dialog-header">
+          <text class="help-dialog-title">{{ helpPopup.title }}</text>
+          <text class="help-dialog-close" @tap="closeHelp">✕</text>
+        </view>
+        <text class="help-dialog-content">{{ helpPopup.content }}</text>
+      </view>
+    </view>
   </view>
 </template>
 
 <script>
-import { getReminderConfig, saveReminderConfig } from '@/utils/api.js'
+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 PearlDiagram from '@/pages/action-detail/components/PearlDiagram.vue'
+import AbilityDiagram from '@/pages/action-detail/components/AbilityDiagram.vue'
+import OctopusDiagram from '@/pages/action-detail/components/OctopusDiagram.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 FloatingAvatar from '../../components/AIFloatingAvatar.vue'
+import RadarChart from '../../components/RadarChart.vue'
+import DimensionSubDims from './components/DimensionSubDims.vue'
+import { getVisibleSections, getEnergyOverview, getChildren, getFamilyEnergySandbox, getEnergySandbox, getFamilyMemberList, getOctopusTentacles, getPearlResources, deletePearlResource, getAbilityMap, getRadarData, getPearlReminders, getOctopusEffects, getOctopusAnalysisSummary, getOctopusExpansionSuggest, getOctopusEffectKeyPersons } from '../../utils/api.js'
 
 export default {
+  components: { TabTransition, PageBanner, FamilyEnergyBar, FamilyRelationGraph, PearlDiagram, AbilityDiagram, OctopusDiagram, FamilyMemberStrip, DimensionTasks, DimensionActivities, DimensionProducts, DimensionIntroCard, FloatingAvatar, RadarChart, DimensionSubDims },
   data() {
     return {
-      loading: true,
-      saving: false,
-      enabled: 1,
-      channels: [],
-      selectedChannels: [],
-      timingMode: 'after_timeout',
-      offsetMinutes: 60,
-      completionPraiseEnabled: 0,
-      selectedPraiseChannels: ['wechat', 'in_app'],
-      _loaded: false
+      isLoggedIn: false,
+      role: null,
+      showTabTransition: true,
+      activeChildId: null,
+      selectedMemberId: null,
+      activeChildName: '',
+      userName: '',
+      children: [],
+      familyMembersVisible: [],
+      childrenChecked: false,
+      membersChecked: false,
+            actionSubDims: [],
+      sandboxData: null,
+      actionPopulationScoreMap: {},
+      currentView: 'self', // 'self' | 'family'
+      dualDimension: null,
+      selfId: null,
+      visibleSections: [],
+      pearlData: null,
+      abilityData: null,
+      octopusEffects: null,
+      octopusExpansions: [],
+      octopusKeyPersons: {},
+      overdueMap: null,
+      // 帮助弹窗
+      helpPopup: { visible: false, title: '', content: '' },
+      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: {
-    offsetLabel: function() {
-      return this.timingMode === 'before_deadline' ? '提前' : '超时后'
+    /** 关系图谱:成员节点(来自沙盘数据) */
+    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]
+    },
+
+    /** 当前选中成员的能量数据(从 sandboxData.members 中查找) */
+    currentMemberEnergies: function() {
+      if (!this.sandboxData || !this.sandboxData.members) return null
+      var members = this.sandboxData.members
+      var targetId = this.selectedMemberId || this.activeChildId
+      for (var i = 0; i < members.length; i++) {
+        var m = members[i]
+        if (m && (m.memberId === targetId)) return m
+      }
+      return members[0] || null
     },
-    offsetHint: function() {
-      if (!this.offsetMinutes || this.offsetMinutes < 1 || this.offsetMinutes > 1440) {
-        return '请输入1-1440 之间的分钟数'
+
+    /** 行6维关系域维度定义(根据角色切换家长版/孩子版) */
+    actionDimensions: function() {
+      var role = this.role || uni.getStorageSync('currentRole') || ''
+      var isParent = (role === 'parent')
+      if (isParent) {
+        return [
+          { key: 'actionParentChild', label: '亲子关系' },
+          { key: 'actionMarital', label: '夫妻关系' },
+          { key: 'actionParental', label: '父母关系' },
+          { key: 'actionLeadership', label: '领导关系' },
+          { key: 'actionSociality', label: '社会性' },
+          { key: 'actionInterpersonal', label: '人际韧性' }
+        ]
+      } else {
+        return [
+          { key: 'actionChildParentChild', label: '亲子关系' },
+          { key: 'actionSibling', label: '同胞关系' },
+          { key: 'actionRelative', label: '亲属关系' },
+          { key: 'actionTeacherStudent', label: '师生关系' },
+          { key: 'actionChildSociality', label: '社会性' },
+          { key: 'actionChildInterpersonal', label: '人际韧性' }
+        ]
       }
-      if (this.timingMode === 'before_deadline') {
-        return '将在任务截止前' + this.offsetMinutes + ' 分钟提醒'
+    },
+
+    /** 当前选中成员的行维度分数 */
+    actionScore: function() {
+      var member = this.currentMemberEnergies
+      return member ? (member.actionScore || 0) : null
+    },
+
+    /** 行6维分值(从当前成员中提取,与 dimensions 顺序对应) */
+    actionScores: function() {
+      var member = this.currentMemberEnergies
+      if (!member) return []
+      var dims = this.actionDimensions
+      var scores = []
+      for (var i = 0; i < dims.length; i++) {
+        scores.push(member[dims[i].key] || 0)
       }
-      return '将在任务超时后' + this.offsetMinutes + ' 分钟提醒'
+      return scores
     },
-    channelTip: function() {
-      var selected = this.selectedChannels || []
-      if (selected.length === 0) {
-        return '请至少选择一个提醒渠道'
+    /** 行6维家庭平均分(从 sandboxData.members 各成员同维度均值) */
+    actionFamilyScores: function() {
+      if (!this.sandboxData || !this.sandboxData.members) return []
+      var members = this.sandboxData.members
+      var dims = this.actionDimensions
+      var scores = []
+      for (var i = 0; i < dims.length; i++) {
+        var key = dims[i].key
+        var sum = 0, count = 0
+        for (var j = 0; j < members.length; j++) {
+          var val = members[j][key]
+          if (val !== undefined && val !== null && val > 0) { sum += val; count++ }
+        }
+        scores.push(count > 0 ? Math.round(sum / count) : 0)
+      }
+      return scores
+    },
+    /** 行6维人群位置分数(按 actionDimensions 顺序从 map 取值) */
+    actionPopulationScores: function() {
+      var dims = this.actionDimensions
+      var scores = []
+      for (var i = 0; i < dims.length; i++) {
+        scores.push(this.actionPopulationScoreMap[dims[i].key] || 0)
+      }
+      return scores
+    },
+
+    /** 关系解读状态:good(≥80) | warn(≥60) | bad(<60) */
+    scoreExplainLevel: function() {
+      var s = this.actionScore
+      if (s == null) return 'bad'
+      if (s >= 80) return 'good'
+      if (s >= 60) return 'warn'
+      return 'bad'
+    },
+    /** 关系解读卡片状态类(用于 :class 绑定) */
+    scoreExplainClass: function() {
+      return 'score-explain-' + this.scoreExplainLevel
+    },
+    actionPortrait: function() {
+      var self = this
+      if (!this.isLoggedIn) return null
+      var member = this.currentMemberEnergies
+      if (!member) return null
+      var role = this.role || uni.getStorageSync('currentRole') || ''
+      var isParent = (role === 'parent')
+      var parentDims = [
+        { key: 'actionParentChild', label: '亲子关系', color: '#10B981', desc: '与子女的沟通与陪伴质量' },
+        { key: 'actionMarital', label: '夫妻关系', color: '#FF6B9D', desc: '夫妻间的理解与支持' },
+        { key: 'actionParental', label: '父母关系', color: '#F59E0B', desc: '与长辈的相处模式' },
+        { key: 'actionLeadership', label: '领导关系', color: '#6366F1', desc: '职场上下级沟通能力' },
+        { key: 'actionSociality', label: '社会性', color: '#FF8C42', desc: '社会交往与参与度' },
+        { key: 'actionInterpersonal', label: '人际韧性', color: '#06B6D4', desc: '应对人际关系挫折的能力' }
+      ]
+      var childDims = [
+        { key: 'actionChildParentChild', label: '亲子关系', color: '#10B981', desc: '与父母的互动质量' },
+        { key: 'actionSibling', label: '同胞关系', color: '#FF6B9D', desc: '兄弟姐妹间的关系' },
+        { key: 'actionRelative', label: '亲属关系', color: '#F59E0B', desc: '与亲戚的交往频率' },
+        { key: 'actionTeacherStudent', label: '师生关系', color: '#6366F1', desc: '与老师的沟通情况' },
+        { key: 'actionChildSociality', label: '社会性', color: '#FF8C42', desc: '学校与社会参与度' },
+        { key: 'actionChildInterpersonal', label: '人际韧性', color: '#06B6D4', desc: '应对社交困难的能力' }
+      ]
+      var dims = isParent ? parentDims : childDims
+      var result = { overall: 0, tierLabel: '', summary: '', dims: [], tip: '' }
+      var total = 0
+      var count = 0
+      var dimList = []
+      for (var i = 0; i < dims.length; i++) {
+        var key = dims[i].key
+        var score = member[key] || 0
+        if (isNaN(score)) score = 0
+        score = Math.max(0, Math.min(100, Number(score)))
+        total += score
+        count++
+        dimList.push({
+          code: key,
+          name: dims[i].label,
+          score: Math.round(score),
+          color: dims[i].color,
+          desc: dims[i].desc
+        })
+      }
+      if (count === 0) return null
+      result.overall = Math.round(total / count)
+      result.dims = dimList
+      var o = result.overall
+      if (o >= 80) {
+        result.tierLabel = '关系大师'
+        result.summary = '您的家庭关系和谐稳定,与家人建立了深厚的信任纽带。'
+        result.tip = '继续保持沟通习惯,也可分享经验帮助其他家庭'
+      } else if (o >= 60) {
+        result.tierLabel = '关系进取者'
+        result.summary = '您的家庭关系处于良好水平,具备基本的沟通能力。'
+        result.tip = '多倾听家人心声,主动表达关爱,持续提升关系质量'
+      } else if (o >= 40) {
+        result.tierLabel = '关系探索者'
+        result.summary = '您的家庭关系正在建立基础,需要更多沟通与理解。'
+        result.tip = '建议定期进行家庭活动,增进彼此了解与信任'
+      } else {
+        result.tierLabel = '关系关怀者'
+        result.summary = '您可能面临家庭关系挑战,需要更多关注与努力。'
+        result.tip = '建议寻求专业家庭辅导,或从简单的家庭对话开始改善'
       }
-      return '已选择 ' + selected.length + ' 个提醒渠道'
+      return result
     }
   },
-  onLoad: function() {
-    var self = this
-    this.loading = true
-    getReminderConfig().then(function(res) {
-      self.loading = false
-      self._loaded = true
-      if (res && res.code === 200 && res.data) {
-        var d = res.data
-        self.enabled = d.enabled === 0 ? 0 : 1
-        self.timingMode = d.timingMode || 'after_timeout'
-        self.offsetMinutes = d.offsetMinutes || 60
-        self.selectedChannels = Array.isArray(d.channels) ? d.channels.slice() : []
-        self.channels = Array.isArray(d.availableChannels) ? d.availableChannels.filter(function(c) {
-          return c && c.type !== 'in_app'
-        }) : []
-        self.completionPraiseEnabled = d.completionPraiseEnabled === 1 ? 1 : 0
-        self.selectedPraiseChannels = Array.isArray(d.completionPraiseChannels) ? d.completionPraiseChannels.slice() : ['wechat', 'in_app']
-      }
-    }).catch(function() {
-      self.loading = false
-      uni.showToast({ title: '加载失败,请重试', icon: 'none' })
-    })
+  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.loadPearlResources()
+      this.loadAbilityMap()
+      this.loadOctopusData()
+      this.loadActionRadarPopulationData()
+    }
+    this._watchPageReady()
   },
   methods: {
-    toggleEnabled: function() {
-      this.enabled = this.enabled === 1 ? 0 : 1
+    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']
+      })
     },
-    togglePraiseEnabled: function() {
-      this.completionPraiseEnabled = this.completionPraiseEnabled === 1 ? 0 : 1
+    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
+      })
     },
-    isSelected: function(type) {
-      return (this.selectedChannels || []).indexOf(type) >= 0
+    loadDashboardData: function() {
+      if (!this.activeChildId) return
+      this.loadEnergyData()
+      this.loadSandboxData()
     },
-    isPraiseSelected: function(type) {
-      return (this.selectedPraiseChannels || []).indexOf(type) >= 0
+    loadSandboxData: function() {
+      var self = this
+      getFamilyEnergySandbox().then(function(res) {
+        if (res && res.data) {
+          self.sandboxData = res.data
+        }
+      }).catch(function(e) {
+        console.log('获取沙盘数据失败', e)
+      })
     },
-    toggleChannel: function(ch) {
-      if (!ch.available) return
-      var idx = this.selectedChannels.indexOf(ch.type)
-      if (idx >= 0) {
-        this.selectedChannels.splice(idx, 1)
-      } else {
-        this.selectedChannels.push(ch.type)
-      }
+    loadActionRadarPopulationData: function() {
+      var self = this
+      var memberId = this.selectedMemberId || this.activeChildId
+      if (!memberId) return
+      getRadarData({
+        memberId: memberId,
+        dimensions: ['actionParentChild','actionMarital','actionParental','actionLeadership','actionSociality','actionInterpersonal',
+          'actionChildParentChild','actionSibling','actionRelative','actionTeacherStudent','actionChildSociality','actionChildInterpersonal']
+      }).then(function(res) {
+        if (res && res.data && res.data.populationScores) {
+          var dims = res.data.dimensions || []
+          var scores = res.data.populationScores
+          var map = {}
+          for (var i = 0; i < dims.length && i < scores.length; i++) {
+            map[dims[i]] = scores[i]
+          }
+          self.actionPopulationScoreMap = map
+        }
+      }).catch(function(e) { console.warn('[action] radar-data failed', e) })
     },
-    togglePraiseChannel: function(ch) {
-      if (!ch.available) return
-      var idx = this.selectedPraiseChannels.indexOf(ch.type)
-      if (idx >= 0) {
-        this.selectedPraiseChannels.splice(idx, 1)
+    switchView: function(view) {
+      this.currentView = view
+      if (view === 'family') {
+        this.selectedMemberId = null
+        var self = this
+        getEnergySandbox('family').then(function(res) {
+          if (res && res.data && !res.data.locked) {
+            self.sandboxData = res.data.familyData || res.data
+          }
+        }).catch(function(e) { console.log('获取家庭数据失败', e) })
       } else {
-        this.selectedPraiseChannels.push(ch.type)
+        this.loadSandboxData()
       }
     },
-    switchTiming: function(mode) {
-      this.timingMode = mode
+    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)
+      })
     },
-    onOffsetInput: function(e) {
-      var v = e && e.detail && e.detail.value
-      var n = parseInt(v, 10)
-      this.offsetMinutes = isNaN(n) ? 0 : n
+    onFamilyMemberSelect: function(member) {
+      this.selectedMemberId = member.id
     },
-    saveConfig: function() {
+    loadFamilyMembersVisible: function() {
       var self = this
-      if (!this.selectedChannels || this.selectedChannels.length === 0) {
-        uni.showToast({ title: '请至少选择一个提醒渠道', icon: 'none' })
+      getFamilyMemberList({ visibleOnly: true }).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
+    },
+    // 登录浮层成功回调:刷新行动维度数据
+    onLoginSuccess: function() {
+      this.loadDashboardData()
+    },
+    onFuncClick: function(item) {
+      if (item.needLogin && !this.isLoggedIn) {
+        uni.showModal({
+          title: '提示',
+          content: '请先登录后使用此功能',
+          success: (res) => {
+            if (res.confirm) this.openLoginSheet()
+          }
+        })
         return
       }
-      var offset = this.offsetMinutes || 0
-      if (offset < 1 || offset > 1440) {
-        uni.showToast({ title: '偏移分钟需在1-1440 之间', icon: 'none' })
-        return
+      if (item.page) uni.navigateTo({ url: item.page })
+    },
+    /** 加载珍珠图数据 */
+    loadPearlResources: function() {
+      var self = this
+      getPearlResources().then(function(res) {
+        self.pearlData = (res.code === 200 && res.data) ? res.data : null
+      }).catch(function(e) {
+        console.log('获取珍珠图数据失败', e)
+        self.pearlData = null
+      })
+      // 构建逾期映射
+      getPearlReminders().then(function(res) {
+        var map = {}
+        if (res.code === 200 && res.data) {
+          for (var i = 0; i < res.data.length; i++) {
+            map['' + res.data[i].resourceItemId] = res.data[i].daysSinceLast
+          }
+        }
+        self.overdueMap = map
+      }).catch(function(e) {
+        self.overdueMap = null
+      })
+    },
+    /** 登记珍珠图资源入口 */
+    goPearlAddResource: function() {
+      uni.navigateTo({ url: '/pages/action-detail/pearl-add-resource' })
+    },
+    /** 能力图记录帮助入口(跳转互动记录,记录技能类帮助) */
+    goInteractionLog: function() {
+      uni.navigateTo({ url: '/pages/action-detail/interaction-log' })
+    },
+    /** 删除珍珠图资源 */
+    onDeletePearlResource: function(item) {
+      if (!item || !item.id) return
+      var self = this
+      uni.showModal({
+        title: '提示',
+        content: '确定删除该资源吗?',
+        success: function(res) {
+          if (!res.confirm) return
+          deletePearlResource({ itemId: item.id }).then(function(r) {
+            if (r.code === 200) {
+              uni.showToast({ title: '已删除', icon: 'success' })
+              self.loadPearlResources()
+            } else {
+              uni.showToast({ title: r.message || '删除失败', icon: 'none' })
+            }
+          }).catch(function(e) {
+            console.log('删除珍珠图资源失败', e)
+            uni.showToast({ title: '删除失败', icon: 'none' })
+          })
+        }
+      })
+    },
+    /** 加载能力图数据 */
+    loadAbilityMap: function() {
+      var self = this
+      getAbilityMap().then(function(res) {
+        self.abilityData = (res.code === 200 && Array.isArray(res.data)) ? res.data : []
+      }).catch(function(e) {
+        console.log('获取能力图数据失败', e)
+        self.abilityData = []
+      })
+    },
+    /** 加载章鱼图数据 */
+    loadOctopusData: function() {
+      var self = this
+      getOctopusEffects().then(function(res) {
+        self.octopusEffects = (res.code === 200 && res.data) ? res.data : null
+      }).catch(function(e) {
+        console.log('获取章鱼图数据失败', e)
+        self.octopusEffects = null
+      })
+      getOctopusAnalysisSummary().then(function(res) {
+        if (res.code === 200 && res.data && res.data.directions) {
+          self.octopusExpansions = res.data.directions
+        }
+      }).catch(function(e) {
+        console.log('获取拓展方向失败', e)
+      })
+    },
+    /** 跳转成效录入页 */
+    goOctopusAddEffect: function() {
+      uni.navigateTo({ url: '/pages/action-detail/octopus-add-effect' })
+    },
+    /** 跳转感恩记录维护列表 */
+    goOctopusEffectsList: function() {
+      uni.navigateTo({ url: '/pages/action-detail/octopus-effects-list' })
+    },
+    /** 点击成效节点 */
+    onEffectClick: function(effect) {
+      var self = this
+      if (!self.octopusKeyPersons[effect.id]) {
+        getOctopusEffectKeyPersons({ effectId: effect.id }).then(function(res) {
+          if (res.code === 200) self.$set(self.octopusKeyPersons, effect.id, res.data || [])
+        }).catch(function(e) { console.log('获取关键人失败', e) })
       }
-      if (this.saving) return
-      this.saving = true
-      saveReminderConfig({
-        channels: this.selectedChannels.slice(),
-        timingMode: this.timingMode,
-        offsetMinutes: offset,
-        enabled: this.enabled === 1 ? 1 : 0,
-        completionPraiseEnabled: this.completionPraiseEnabled === 1 ? 1 : 0,
-        completionPraiseChannels: this.selectedPraiseChannels.slice()
-      }).then(function(res) {
-        self.saving = false
-        if (res && res.code === 200) {
-          uni.showToast({ title: '保存成功', icon: 'success' })
-          setTimeout(function() {
-            uni.navigateBack()
-          }, 1000)
-        } else {
-          uni.showToast({ title: (res && res.message) || '保存失败', icon: 'none' })
+    },
+    /** 点击拓展方向节点 */
+    onExpansionClick: function(expansion) {
+      var prefill = encodeURIComponent(JSON.stringify(expansion.prefill || {}))
+      uni.navigateTo({ url: '/pages/action-detail/octopus-add-key-person?prefill=' + prefill })
+    },
+    /** 重新分析 */
+    onReanalyze: function() {
+      this.loadOctopusData()
+    },
+    /** 显示帮助弹窗 */
+    showHelp: function(title, content) {
+      this.helpPopup = { visible: true, title: title, content: content }
+    },
+    closeHelp: function() {
+      this.helpPopup.visible = false
+    },
+    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()
         }
-      }).catch(function(err) {
-        self.saving = false
-        uni.showToast({ title: (err && err.message) || '保存失败', icon: 'none' })
       })
     }
   }
 }
-</script>
+</script>
+
+<style scoped>
+.container {
+  min-height: 100vh;
+  background: linear-gradient(180deg, #F0FFF4 0%, #F5F7FA 40%);
+  padding-bottom: 120rpx;
+}
+
+/* 品牌定位语 */
+.brand-strap {
+  margin: 24rpx 30rpx 0;
+  padding: 28rpx 32rpx;
+  background: linear-gradient(135deg, #10B981 0%, #34D399 50%, #6EE7B7 100%);
+  border-radius: 24rpx;
+  box-shadow: 0 8rpx 24rpx rgba(16, 185, 129, 0.25);
+  position: relative;
+  overflow: hidden;
+}
+.brand-strap::before {
+  content: '';
+  position: absolute;
+  top: -30rpx;
+  right: -30rpx;
+  width: 120rpx;
+  height: 120rpx;
+  background: rgba(255, 255, 255, 0.15);
+  border-radius: 50%;
+}
+.brand-strap::after {
+  content: '';
+  position: absolute;
+  bottom: -20rpx;
+  left: 20rpx;
+  width: 60rpx;
+  height: 60rpx;
+  background: rgba(255, 255, 255, 0.1);
+  border-radius: 50%;
+}
+.brand-strap-text {
+  font-size: 30rpx;
+  color: #FFFFFF;
+  font-weight: 600;
+  text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
+  position: relative;
+  z-index: 1;
+}
+
+/* 每月引导语 */
+.monthly-guidance {
+  margin: 20rpx 30rpx 0;
+  padding: 24rpx 28rpx;
+  background: linear-gradient(135deg, #FFFFFF 0%, #F0FFF4 100%);
+  border-radius: 20rpx;
+  border: 1rpx solid rgba(16, 185, 129, 0.15);
+  box-shadow: 0 4rpx 16rpx rgba(16, 185, 129, 0.08);
+  display: flex;
+  align-items: center;
+  gap: 12rpx;
+}
+.guidance-icon { font-size: 36rpx; }
+.guidance-text {
+  font-size: 26rpx;
+  color: #065F46;
+  font-weight: 500;
+}
+
+/* ===== 通用区块 ===== */
+.section {
+  margin: 24rpx 30rpx 0;
+  position: relative;
+}
+.section::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 24rpx;
+  right: 24rpx;
+  height: 3rpx;
+  border-radius: 3rpx 3rpx 0 0;
+  background: linear-gradient(90deg, #10B981, #34D399);
+  opacity: 0.7;
+}
+.section-header {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 12rpx;
+}
+.section-title-wrap {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  gap: 8rpx;
+  border-left: 8rpx solid #10B981;
+  padding-left: 16rpx;
+}
+.help-icon {
+  width: 36rpx;
+  height: 36rpx;
+  border-radius: 50%;
+  border: 2rpx solid #D1D5DB;
+  color: #9CA3AF;
+  font-size: 22rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  transition: all 0.2s;
+  background: #FFFFFF;
+}
+.help-icon:active {
+  border-color: #10B981;
+  color: #10B981;
+  background: #F0FFF4;
+  transform: scale(1.1);
+}
+.section-title {
+  font-size: 34rpx;
+  font-weight: 700;
+  color: #1E293B;
+  letter-spacing: 0.5rpx;
+}
+.section-sub {
+  font-size: 24rpx;
+  color: #94A3B8;
+  margin-top: 4rpx;
+  line-height: 1.4;
+}
+.section-link {
+  display: inline-flex;
+  align-items: center;
+  padding: 12rpx 28rpx;
+  border-radius: 40rpx;
+  font-size: 24rpx;
+  font-weight: 500;
+  white-space: nowrap;
+  transition: all 0.2s;
+  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+}
+.section-link:active {
+  opacity: 0.85;
+  transform: scale(0.98);
+}
+.section-link-octopus {
+  background: linear-gradient(135deg, #F97316, #FB923C);
+  color: #fff;
+  box-shadow: 0 4rpx 16rpx rgba(249,115,22,0.3);
+}
+.section-link-pearl {
+  background: linear-gradient(135deg, #0EA5E9, #38BDF8);
+  color: #fff;
+  box-shadow: 0 4rpx 16rpx rgba(14,165,233,0.3);
+}
+.section-link-ability {
+  background: linear-gradient(135deg, #10B981, #34D399);
+  color: #fff;
+  box-shadow: 0 4rpx 16rpx rgba(16,185,129,0.3);
+}
+
+/* 关系解读卡片 — 状态色 */
+.score-explain-card {
+  background: linear-gradient(135deg, #FFFFFF 0%, #F0FFF4 100%);
+  border-radius: 20rpx;
+  padding: 28rpx 32rpx;
+  border: 1rpx solid rgba(16, 185, 129, 0.12);
+  box-shadow: 0 4rpx 16rpx rgba(16, 185, 129, 0.08);
+}
+.score-explain-good {
+  background: linear-gradient(135deg, #FFFFFF 0%, #D1FAE5 100%);
+  border-color: rgba(16, 185, 129, 0.25);
+  box-shadow: 0 4rpx 20rpx rgba(16, 185, 129, 0.15);
+}
+.score-explain-warn {
+  background: linear-gradient(135deg, #FFFFFF 0%, #FEF3C7 100%);
+  border-color: rgba(245, 158, 11, 0.25);
+  box-shadow: 0 4rpx 20rpx rgba(245, 158, 11, 0.12);
+}
+.score-explain-bad {
+  background: linear-gradient(135deg, #FFFFFF 0%, #FEE2E2 100%);
+  border-color: rgba(239, 68, 68, 0.25);
+  box-shadow: 0 4rpx 20rpx rgba(239, 68, 68, 0.12);
+}
+.explain-header {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  gap: 16rpx;
+  margin-bottom: 12rpx;
+}
+.explain-icon-wrap {
+  width: 56rpx;
+  height: 56rpx;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  transition: transform 0.2s;
+}
+.explain-icon-wrap:active { transform: scale(0.95); }
+.explain-icon-good { background: linear-gradient(135deg, #10B981, #34D399); box-shadow: 0 4rpx 12rpx rgba(16,185,129,0.3); }
+.explain-icon-warn { background: linear-gradient(135deg, #F59E0B, #FBBF24); box-shadow: 0 4rpx 12rpx rgba(245,158,11,0.3); }
+.explain-icon-bad { background: linear-gradient(135deg, #EF4444, #F87171); box-shadow: 0 4rpx 12rpx rgba(239,68,68,0.3); }
+.explain-icon {
+  font-size: 28rpx;
+  color: #FFFFFF;
+  font-weight: 700;
+}
+.explain-info {
+  display: flex;
+  flex-direction: column;
+}
+.explain-title {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #1E293B;
+  line-height: 1.3;
+}
+.explain-score {
+  font-size: 48rpx;
+  font-weight: 800;
+  line-height: 1;
+  letter-spacing: -2rpx;
+}
+.score-explain-good .explain-score { color: #10B981; }
+.score-explain-warn .explain-score { color: #D97706; }
+.score-explain-bad .explain-score { color: #DC2626; }
+.explain-score-unit {
+  font-size: 24rpx;
+  font-weight: 500;
+  color: #94A3B8;
+  margin-left: 2rpx;
+  letter-spacing: 0;
+}
+.explain-text {
+  font-size: 26rpx;
+  color: #64748B;
+  line-height: 1.7;
+}
+
+/* 帮助说明弹窗 */
+.help-mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 999;
+}
+.help-dialog {
+  background: #FFFFFF;
+  border-radius: 24rpx;
+  width: 85%;
+  max-width: 520rpx;
+  max-height: 75vh;
+  overflow: hidden;
+  box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.15);
+}
+.help-dialog-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 28rpx 32rpx 20rpx;
+  border-bottom: 1rpx solid #F1F5F9;
+  background: linear-gradient(135deg, #F0FFF4, #FFFFFF);
+}
+.help-dialog-title {
+  font-size: 32rpx;
+  font-weight: 700;
+  color: #1E293B;
+}
+.help-dialog-close {
+  font-size: 36rpx;
+  color: #94A3B8;
+  cursor: pointer;
+  width: 48rpx;
+  height: 48rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 50%;
+  transition: background 0.2s;
+}
+.help-dialog-close:active {
+  background: #F1F5F9;
+}
+.help-dialog-content {
+  padding: 28rpx 32rpx;
+  font-size: 28rpx;
+  color: #64748B;
+  line-height: 1.8;
+}
+
+/* 功能入口网格 */
+.func-section {
+  margin: 24rpx 30rpx 0;
+}
+.func-grid {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-around;
+  background: linear-gradient(135deg, #FFFFFF 0%, #F0FFF4 100%);
+  border-radius: 24rpx;
+  padding: 32rpx 16rpx;
+  box-shadow: 0 8rpx 24rpx rgba(16, 185, 129, 0.1);
+  border: 1rpx solid rgba(16, 185, 129, 0.08);
+}
+.func-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  flex: 1;
+  padding: 8rpx;
+}
+.func-icon-wrap {
+  width: 96rpx;
+  height: 96rpx;
+  border-radius: 28rpx;
+  background: linear-gradient(135deg, #D1FAE5, #A7F3D0);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 12rpx;
+  box-shadow: 0 4rpx 12rpx rgba(16, 185, 129, 0.15);
+  transition: all 0.2s;
+}
+.func-item:active .func-icon-wrap {
+  transform: scale(0.95);
+  box-shadow: 0 2rpx 8rpx rgba(16, 185, 129, 0.2);
+}
+.func-icon {
+  font-size: 44rpx;
+}
+.func-label {
+  font-size: 24rpx;
+  color: #475569;
+  font-weight: 500;
+}
+
+.bottom-spacer {
+  height: 40rpx;
+}
+
+/* 维度画像样式 */
+.dimension-portrait-section {
+  margin: 24rpx 30rpx;
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 32rpx 24rpx;
+  box-shadow: 0 4rpx 20rpx rgba(16, 185, 129, 0.12);
+  border: 2rpx solid rgba(16, 185, 129, 0.1);
+}
+.portrait-tier {
+  font-size: 26rpx;
+  color: #10B981;
+  font-weight: bold;
+  margin-left: 16rpx;
+}
+.portrait-summary {
+  margin-bottom: 24rpx;
+}
+.portrait-desc {
+  font-size: 26rpx;
+  color: #666;
+  line-height: 1.6;
+  display: block;
+}
+.portrait-dimensions {
+  margin-bottom: 20rpx;
+}
+.portrait-dim-item {
+  margin-bottom: 18rpx;
+}
+.portrait-dim-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 6rpx;
+}
+.portrait-dim-icon {
+  width: 36rpx;
+  height: 36rpx;
+  border-radius: 18rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 10rpx;
+  font-size: 20rpx;
+  background: linear-gradient(135deg, #D1FAE5, #A7F3D0);
+}
+.portrait-dim-name {
+  font-size: 24rpx;
+  color: #333;
+  flex: 1;
+}
+.portrait-dim-score {
+  font-size: 26rpx;
+  font-weight: bold;
+  color: #10B981;
+  margin-right: 12rpx;
+}
+.portrait-dim-bar-track {
+  height: 10rpx;
+  background: #D1FAE5;
+  border-radius: 5rpx;
+  overflow: hidden;
+  margin-bottom: 4rpx;
+  flex: 1;
+}
+.portrait-dim-bar-fill {
+  height: 100%;
+  border-radius: 5rpx;
+  transition: width 0.3s ease;
+}
+.portrait-dim-desc {
+  font-size: 20rpx;
+  color: #666;
+  display: block;
+}
+.portrait-tip {
+  padding: 16rpx 20rpx;
+  background: linear-gradient(135deg, #F0FFF4, #E6FFF2);
+  border-radius: 12rpx;
+  border-left: 8rpx solid #10B981;
+  margin-top: 16rpx;
+}
+.portrait-tip-text {
+  font-size: 22rpx;
+  color: #065F46;
+  line-height: 1.5;
+}
+</style>

+ 8 - 2
cfc-frontend/pages/reminder-config/index.vue

@@ -16,7 +16,7 @@
       <view class="cfg-card">
         <text class="cfg-card-title">提醒渠道</text>
         <view class="channel-list">
-          <view class="channel-item" v-for="(ch, index) in channels" :key="ch.type + '_' + index"
+          <view class="channel-item" v-for="(ch, index) in channels" :key="getChannelKey(ch, index)"
             :class="{
               'channel-selected': isSelected(ch.type),
               'channel-disabled': !ch.available
@@ -67,7 +67,7 @@
         </view>
         <text class="cfg-card-desc">任务正常完成时,发送表扬鼓励消息</text>
         <view class="channel-list" v-if="completionPraiseEnabled === 1" style="margin-top:20rpx">
-          <view class="channel-item" v-for="(ch, pIndex) in channels" :key="'p-' + pIndex"
+          <view class="channel-item" v-for="(ch, pIndex) in channels" :key="getPraiseKey(pIndex)"
             :class="{
               'channel-selected': isPraiseSelected(ch.type),
               'channel-disabled': !ch.available
@@ -160,6 +160,12 @@ export default {
     })
   },
   methods: {
+    getChannelKey: function(ch, index) {
+      return ch.type + '_' + index
+    },
+    getPraiseKey: function(pIndex) {
+      return 'p-' + pIndex
+    },
     toggleEnabled: function() {
       this.enabled = this.enabled === 1 ? 0 : 1
     },

+ 181 - 0
cfc-frontend/pages/wealth/components/DimensionSubDims.vue

@@ -0,0 +1,181 @@
+<template>
+  <view class="dim-subdims" :style="{ '--accent': accentColor }">
+    <view class="dim-header">
+      <text class="dim-title">{{ title }}</text>
+      <text class="dim-subtitle">{{ subtitle }}</text>
+    </view>
+    <view class="dim-list">
+      <view
+        class="dim-item"
+        v-for="(d, i) in dims"
+        :key="d.label"
+        :style="{ '--dim-color': d.color || accentColor }"
+      >
+        <view class="dim-item-top">
+          <view class="dim-item-info">
+            <text class="dim-item-label">{{ d.label }}</text>
+            <text class="dim-item-desc">{{ d.desc }}</text>
+          </view>
+          <view class="dim-item-score-wrap">
+            <text
+              class="dim-item-score"
+              :class="getScoreClass(d.score)"
+            >{{ d.score }}</text>
+            <text
+              class="dim-item-level"
+              :class="'level-' + getScoreLevel(d.score)"
+            >{{ getScoreLevelText(d.score) }}</text>
+          </view>
+        </view>
+        <view class="dim-bar-track">
+          <view
+            class="dim-bar-fill"
+            :style="{ width: d.score + '%', background: d.color || accentColor }"
+          ></view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'DimensionSubDims',
+  props: {
+    title: { type: String, default: '维度评分' },
+    subtitle: { type: String, default: '' },
+    accentColor: { type: String, default: '#6366F1' },
+    dims: { type: Array, default: () => [] }
+  },
+  methods: {
+    getScoreLevel(score) {
+      if (score >= 80) return 'good'
+      if (score >= 60) return 'ok'
+      return 'low'
+    },
+    getScoreLevelText(score) {
+      if (score >= 80) return '优秀'
+      if (score >= 60) return '良好'
+      if (score > 0) return '待提升'
+      return '-'
+    },
+    getScoreClass(score) {
+      if (score >= 80) return 'score-good'
+      if (score >= 60) return 'score-ok'
+      return 'score-low'
+    }
+  }
+}
+</script>
+
+<style scoped>
+.dim-subdims {
+  margin: 20rpx 30rpx;
+  background: rgba(255, 255, 255, 0.95);
+  border-radius: 24rpx;
+  padding: 28rpx 24rpx;
+  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
+  border: 1rpx solid rgba(255, 255, 255, 0.8);
+}
+.dim-header {
+  margin-bottom: 24rpx;
+}
+.dim-title {
+  font-size: 32rpx;
+  font-weight: 700;
+  color: #1a1a2e;
+  display: block;
+}
+.dim-subtitle {
+  font-size: 24rpx;
+  color: #94a3b8;
+  margin-top: 6rpx;
+  display: block;
+}
+.dim-list {
+  display: flex;
+  flex-direction: column;
+  gap: 0;
+}
+.dim-item {
+  padding: 20rpx 0;
+  border-bottom: 1rpx solid rgba(0, 0, 0, 0.05);
+}
+.dim-item:last-child {
+  border-bottom: none;
+  padding-bottom: 0;
+}
+.dim-item:first-child {
+  padding-top: 0;
+}
+.dim-item-top {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 12rpx;
+}
+.dim-item-info {
+  flex: 1;
+  min-width: 0;
+}
+.dim-item-label {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #1e293b;
+  display: block;
+  line-height: 1.4;
+}
+.dim-item-desc {
+  font-size: 22rpx;
+  color: #94a3b8;
+  margin-top: 4rpx;
+  display: block;
+  line-height: 1.4;
+}
+.dim-item-score-wrap {
+  text-align: right;
+  flex-shrink: 0;
+  margin-left: 16rpx;
+}
+.dim-item-score {
+  font-size: 36rpx;
+  font-weight: 700;
+  line-height: 1;
+}
+.score-good { color: #16a34a; }
+.score-ok { color: #d97706; }
+.score-low { color: #dc2626; }
+.dim-item-level {
+  font-size: 20rpx;
+  font-weight: 500;
+  display: block;
+  margin-top: 4rpx;
+  padding: 2rpx 12rpx;
+  border-radius: 8rpx;
+  width: fit-content;
+  margin-left: auto;
+}
+.level-good {
+  color: #16a34a;
+  background: #f0fdf4;
+}
+.level-ok {
+  color: #d97706;
+  background: #fffbeb;
+}
+.level-low {
+  color: #dc2626;
+  background: #fef2f2;
+}
+.dim-bar-track {
+  height: 8rpx;
+  background: rgba(0, 0, 0, 0.06);
+  border-radius: 4rpx;
+  overflow: hidden;
+}
+.dim-bar-fill {
+  height: 100%;
+  border-radius: 4rpx;
+  transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
+}
+</style>

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

@@ -268,7 +268,7 @@ import BodyWealthAlert from '@/pages/wealth/components/BodyWealthAlert.vue'
 import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
 import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
 import DimensionIntroCard from '../../components/DimensionIntroCard.vue'
-import DimensionSubDims from '../../components/DimensionSubDims.vue'
+import DimensionSubDims from './components/DimensionSubDims.vue'
 import {
   getChildren,
   getWealthDetail,