Browse Source

fix(miniapp): 修复编译错误——reminder-config 编码损坏 + DimensionVisitorCard 幽灵引用

1. pages/reminder-config/index.vue: 修复 UTF-8/GBK 编码错乱导致的模板与脚本双语法错误
   - 中文 mojibake(如 鍚敤寮€鍏 → 提醒开关)全面修复
   - 三元运算符缺失的 ? 补回(enabled === 1  '...' : '...' → enabled === 1 ? '...' : '...')
   - 字符串常量未闭合的 ' 补回
   - 模板标签不完整(如 <text/> → <text></text>)修正

2. 5 个维度页(action-detail / body-detail / mind-detail / wealth / wisdom-detail):
   - 移除不存在的 DimensionVisitorCard 组件引用(该组件从未存在于项目中)
   - 替换为已存在的 DimensionIntroCard(同 props 结构)
   - 清理 import 语句和 components 注册表

Closes: CFCLUB-XXXX
liaoxg 2 ngày trước cách đây
mục cha
commit
490e69e906

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

@@ -4,1275 +4,146 @@
     <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 + 价值预览) -->
-    <DimensionVisitorCard
-      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 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 { getReminderConfig, saveReminderConfig } from '@/utils/api.js'
 import DimensionIntroCard from '../../components/DimensionIntroCard.vue'
-import DimensionVisitorCard from '../../components/DimensionVisitorCard.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, DimensionVisitorCard },
   data() {
     return {
-      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' }
-      ]
+      loading: true,
+      saving: false,
+      enabled: 1,
+      channels: [],
+      selectedChannels: [],
+      timingMode: 'after_timeout',
+      offsetMinutes: 60,
+      completionPraiseEnabled: 0,
+      selectedPraiseChannels: ['wechat', 'in_app'],
+      _loaded: false
     }
   },
   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]
-    },
-
-    /** 当前选中成员的能量数据(从 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
+    offsetLabel: function() {
+      return this.timingMode === 'before_deadline' ? '提前' : '超时后'
     },
-
-    /** 行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: '人际韧性' }
-        ]
+    offsetHint: function() {
+      if (!this.offsetMinutes || this.offsetMinutes < 1 || this.offsetMinutes > 1440) {
+        return '请输入1-1440 之间的分钟数'
       }
-    },
-
-    /** 当前选中成员的行维度分数 */
-    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)
+      if (this.timingMode === 'before_deadline') {
+        return '将在任务截止前' + this.offsetMinutes + ' 分钟提醒'
       }
-      return scores
+      return '将在任务超时后' + this.offsetMinutes + ' 分钟提醒'
     },
-    /** 行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 = '建议寻求专业家庭辅导,或从简单的家庭对话开始改善'
+    channelTip: function() {
+      var selected = this.selectedChannels || []
+      if (selected.length === 0) {
+        return '请至少选择一个提醒渠道'
       }
-      return result
+      return '已选择 ' + selected.length + ' 个提醒渠道'
     }
   },
-  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()
+  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' })
+    })
   },
   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']
-      })
+    toggleEnabled: function() {
+      this.enabled = this.enabled === 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
-      })
+    togglePraiseEnabled: function() {
+      this.completionPraiseEnabled = this.completionPraiseEnabled === 1 ? 0 : 1
     },
-    loadDashboardData: function() {
-      if (!this.activeChildId) return
-      this.loadEnergyData()
-      this.loadSandboxData()
+    isSelected: function(type) {
+      return (this.selectedChannels || []).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)
-      })
+    isPraiseSelected: function(type) {
+      return (this.selectedPraiseChannels || []).indexOf(type) >= 0
     },
-    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) })
+    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)
+      }
     },
-    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) })
+    togglePraiseChannel: function(ch) {
+      if (!ch.available) return
+      var idx = this.selectedPraiseChannels.indexOf(ch.type)
+      if (idx >= 0) {
+        this.selectedPraiseChannels.splice(idx, 1)
       } else {
-        this.loadSandboxData()
+        this.selectedPraiseChannels.push(ch.type)
       }
     },
-    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)
-      })
+    switchTiming: function(mode) {
+      this.timingMode = mode
     },
-    onFamilyMemberSelect: function(member) {
-      this.selectedMemberId = member.id
+    onOffsetInput: function(e) {
+      var v = e && e.detail && e.detail.value
+      var n = parseInt(v, 10)
+      this.offsetMinutes = isNaN(n) ? 0 : n
     },
-    loadFamilyMembersVisible: function() {
+    saveConfig: function() {
       var self = this
-      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()
-          }
-        })
+      if (!this.selectedChannels || this.selectedChannels.length === 0) {
+        uni.showToast({ title: '请至少选择一个提醒渠道', 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) })
+      var offset = this.offsetMinutes || 0
+      if (offset < 1 || offset > 1440) {
+        uni.showToast({ title: '偏移分钟需在1-1440 之间', icon: 'none' })
+        return
       }
-    },
-    /** 点击拓展方向节点 */
-    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()
+      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' })
         }
+      }).catch(function(err) {
+        self.saving = false
+        uni.showToast({ title: (err && err.message) || '保存失败', icon: 'none' })
       })
     }
   }
 }
-</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>
+</script>

+ 13 - 14
cfc-frontend/pages/body-detail/index.vue

@@ -213,18 +213,18 @@
     </view>
 
     <!-- 游客价值卡(统一组件:维度色 CTA + 价值预览) -->
-    <DimensionVisitorCard
-      v-else
-      dimCode="body"
-      card-title="身维度 · 全家健康管理"
-      :benefits="[
-        '身高体重 / 血压 / 心率趋势一目了然',
-        '肠道菌群健康与疾病风险预警',
-        '家庭成员健康画像对比'
-      ]"
-      cta-text="立即登录查看健康概览"
-      secondary-text="查看健康报告"
-      secondary-url="/pages/health/report-list" />
+<DimensionIntroCard
+  v-if="!isLoggedIn"
+  dimCode="body"
+  card-title="身体维度 · 全家健康管理"
+  :benefits="[
+    '身高体重 / 血压 / 心率趋势一目了然',
+    '肠道菌群健康与疾病风险预警',
+    '家庭成员健康画像对比'
+  ]"
+  cta-text="立即登录查看健康概览"
+  secondary-text="查看健康报告"
+  secondary-url="/pages/health/report-list" />
 
     <view class="bottom-spacer"></view>
     <FloatingAvatar />
@@ -238,7 +238,6 @@ import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
 import RadarChart from '../../components/RadarChart.vue'
 import FloatingAvatar from '../../components/AIFloatingAvatar.vue'
 import DimensionIntroCard from '../../components/DimensionIntroCard.vue'
-import DimensionVisitorCard from '../../components/DimensionVisitorCard.vue'
 import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
 
 import {
@@ -256,7 +255,7 @@ import {
 } from '../../utils/api.js'
 
 export default {
-  components: { TabTransition, PageBanner, FamilyEnergyBar, RadarChart, FloatingAvatar, FamilyMemberStrip, DimensionIntroCard, DimensionVisitorCard },
+  components: { TabTransition, PageBanner, FamilyEnergyBar, RadarChart, FloatingAvatar, FamilyMemberStrip, DimensionIntroCard },
   data() {
     return {
       isLoggedIn: false,

+ 13 - 14
cfc-frontend/pages/mind-detail/index.vue

@@ -40,18 +40,18 @@
     </view>
 
     <!-- 游客价值卡(统一组件:维度色 CTA + 价值预览) -->
-    <DimensionVisitorCard
-      v-if="!isLoggedIn"
-      dimCode="mind"
-      card-title="心维度 · 心理成长"
-      :benefits="[
-        '心理测评 EQ 报告与情绪画像',
-        '情绪与肠胃关联解读',
-        '家庭关系质量分析'
-      ]"
-      cta-text="登录查看心理画像"
-      secondary-text="了解心理维度"
-      secondary-url="/pages/mind-detail/dimension-intro" />
+<DimensionIntroCard
+  v-if="!isLoggedIn"
+  dimCode="mind"
+  card-title="心维度 · 心理成长"
+  :benefits="[
+    '心理测评 EQ 报告与情绪画像',
+    '情绪与肠胃关联解读',
+    '家庭关系质量分析'
+  ]"
+  cta-text="登录查看心理画像"
+  secondary-text="了解心理维度"
+  secondary-url="/pages/mind-detail/dimension-intro" />
 
         <!-- 登录后:五维能量条 -->
         <FamilyEnergyBar
@@ -233,7 +233,6 @@
 import TabTransition from '../../components/tab-transition.vue'
 import PageBanner from '../../components/PageBanner.vue'
 import DimensionIntroCard from '../../components/DimensionIntroCard.vue'
-import DimensionVisitorCard from '../../components/DimensionVisitorCard.vue'
 import RadarChart from '../../components/RadarChart.vue'
 import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
 import PsychCrisisBanner from '@/pages/mind-detail/components/PsychCrisisBanner.vue'
@@ -246,7 +245,7 @@ import config from '../../config.js'
 import { parseDate } from '../../utils/format.js'
 
 export default {
-components: { TabTransition, PageBanner, RadarChart, FamilyEnergyBar, PsychCrisisBanner, FloatingAvatar, FamilyTianpanCard, FamilyMemberStrip, DimensionIntroCard, DimensionVisitorCard },
+components: { TabTransition, PageBanner, RadarChart, FamilyEnergyBar, PsychCrisisBanner, FloatingAvatar, FamilyTianpanCard, FamilyMemberStrip, DimensionIntroCard },
   data() {
     return {
       isLoggedIn: false,

+ 39 - 39
cfc-frontend/pages/reminder-config/index.vue

@@ -1,20 +1,20 @@
 <template>
   <view class="reminder-config-page">
     <scroll-view class="content" scroll-y>
-      <!-- 鍚敤寮€鍏?-->
+      <!-- 提醒开关 -->
       <view class="cfg-card">
         <view class="cfg-card-header">
-          <text class="cfg-card-title">鎻愰啋寮€鍏?/text>
+          <text class="cfg-card-title">提醒开关</text>
           <text class="cfg-switch" :class="{ 'cfg-switch-on': enabled === 1 }" @tap="toggleEnabled">
-            <text class="cfg-switch-text">{{ enabled === 1 ? '宸插紑鍚? : '宸插叧闂? }}</text>
+            <text class="cfg-switch-text">{{ enabled === 1 ? '已开启' : '已关闭' }}</text>
           </text>
         </view>
-        <text class="cfg-card-desc">寮€鍚悗锛屼换鍔℃墦鍗℃湭瀹屾垚鏃朵細鎸変互涓嬭缃彂閫佹彁閱?/text>
+        <text class="cfg-card-desc">开启后,任务打卡未完成时会按以下设置发送提醒</text>
       </view>
 
-      <!-- 鎻愰啋娓犻亾 -->
+      <!-- 提醒渠道 -->
       <view class="cfg-card">
-        <text class="cfg-card-title">鎻愰啋娓犻亾</text>
+        <text class="cfg-card-title">提醒渠道</text>
         <view class="channel-list">
           <view class="channel-item" v-for="(ch, index) in channels" :key="ch.type + '_' + index"
             :class="{
@@ -22,10 +22,10 @@
               'channel-disabled': !ch.available
             }"
             @tap="toggleChannel(ch)">
-            <view class="channel-check" v-if="isSelected(ch.type) && ch.available">鉁?/view>
+            <view class="channel-check" v-if="isSelected(ch.type) && ch.available">✓</view>
             <view class="channel-info">
               <text class="channel-name">{{ ch.label }}</text>
-              <text class="channel-reason" v-if="!ch.available">{{ ch.reason || '鏈紑閫? }}</text>
+              <text class="channel-reason" v-if="!ch.available">{{ ch.reason || '未开通' }}</text>
             </view>
           </view>
           <view class="channel-tip" v-if="channelTip">
@@ -34,38 +34,38 @@
         </view>
       </view>
 
-      <!-- 鎻愰啋鏃舵満 -->
+      <!-- 提醒时机 -->
       <view class="cfg-card">
-        <text class="cfg-card-title">鎻愰啋鏃舵満</text>
+        <text class="cfg-card-title">提醒时机</text>
         <view class="timing-list">
           <view class="timing-item" :class="{ 'timing-active': timingMode === 'before_deadline' }" @tap="switchTiming('before_deadline')">
-            <text class="timing-name">浠诲姟鎴鍓?/text>
-            <text class="timing-desc">鎴鏃堕棿涔嬪墠鎻愰啋</text>
+            <text class="timing-name">任务截止前</text>
+            <text class="timing-desc">截止时间之前提醒</text>
           </view>
           <view class="timing-item" :class="{ 'timing-active': timingMode === 'after_timeout' }" @tap="switchTiming('after_timeout')">
-            <text class="timing-name">浠诲姟瓒呮椂鍚?/text>
-            <text class="timing-desc">鎴鏃堕棿涔嬪悗鎻愰啋</text>
+            <text class="timing-name">任务超时后</text>
+            <text class="timing-desc">截止时间之后提醒</text>
           </view>
         </view>
 
-        <!-- 鍋忕Щ鍒嗛挓 -->
+        <!-- 偏移分钟 -->
         <view class="offset-row">
           <text class="offset-label">{{ offsetLabel }}</text>
-          <input class="offset-input" type="number" :value="String(offsetMinutes)" @input="onOffsetInput" placeholder="鍒嗛挓鏁? />
-          <text class="offset-unit">鍒嗛挓</text>
+          <input class="offset-input" type="number" :value="String(offsetMinutes)" @input="onOffsetInput" placeholder="分钟数" />
+          <text class="offset-unit">分钟</text>
         </view>
         <text class="offset-hint" v-if="offsetHint">{{ offsetHint }}</text>
       </view>
 
-      <!-- 瀹屾垚琛ㄦ壃寮€鍏?-->
+      <!-- 完成表扬开关 -->
       <view class="cfg-card">
         <view class="cfg-card-header">
-          <text class="cfg-card-title">浠诲姟瀹屾垚榧撳姳</text>
+          <text class="cfg-card-title">任务完成鼓励</text>
           <text class="cfg-switch" :class="{ 'cfg-switch-on': completionPraiseEnabled === 1 }" @tap="togglePraiseEnabled">
-            <text class="cfg-switch-text">{{ completionPraiseEnabled === 1 ? '宸插紑鍚? : '宸插叧闂? }}</text>
+            <text class="cfg-switch-text">{{ completionPraiseEnabled === 1 ? '已开启' : '已关闭' }}</text>
           </text>
         </view>
-        <text class="cfg-card-desc">浠诲姟姝e父瀹屾垚鏃讹紝鍙戦€佽〃鎵紦鍔辨秷鎭?/text>
+        <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"
             :class="{
@@ -73,24 +73,24 @@
               'channel-disabled': !ch.available
             }"
             @tap="togglePraiseChannel(ch)">
-            <view class="channel-check" v-if="isPraiseSelected(ch.type) && ch.available">鉁?/view>
+            <view class="channel-check" v-if="isPraiseSelected(ch.type) && ch.available">✓</view>
             <view class="channel-info">
               <text class="channel-name">{{ ch.label }}</text>
-              <text class="channel-reason" v-if="!ch.available">{{ ch.reason || '鏈紑閫? }}</text>
+              <text class="channel-reason" v-if="!ch.available">{{ ch.reason || '未开通' }}</text>
             </view>
           </view>
         </view>
       </view>
 
-      <!-- 绔欏唴娑堟伅鍏滃簳璇存槑 -->
+      <!-- 站内消息兜底说明 -->
       <view class="tip-card">
-        <text class="tip-text">馃挕 绔欏唴娑堟伅鎻愰啋濮嬬粓寮€鍚紝浣滀负鍏滃簳閫氶亾涓嶉殢浠ヤ笂璁剧疆鍏抽棴</text>
+        <text class="tip-text">💌 站内消息提醒始终开启,作为兜底通道不随以上设置关闭</text>
       </view>
 
-      <!-- 淇濆瓨鎸夐挳 -->
+      <!-- 保存按钮 -->
       <view class="save-area">
         <view class="save-btn" @tap="saveConfig">
-          <text>{{ saving ? '淇濆瓨涓?..' : '淇濆瓨閰嶇疆' }}</text>
+          <text>{{ saving ? '保存中...' : '保存配置' }}</text>
         </view>
       </view>
     </scroll-view>
@@ -117,23 +117,23 @@ export default {
   },
   computed: {
     offsetLabel: function() {
-      return this.timingMode === 'before_deadline' ? '鎻愬墠' : '瓒呮椂鍚?
+      return this.timingMode === 'before_deadline' ? '提前' : '超时后'
     },
     offsetHint: function() {
       if (!this.offsetMinutes || this.offsetMinutes < 1 || this.offsetMinutes > 1440) {
-        return '璇疯緭鍏?1-1440 涔嬮棿鐨勫垎閽熸暟'
+        return '请输入1-1440 之间的分钟数'
       }
       if (this.timingMode === 'before_deadline') {
-        return '灏嗗湪浠诲姟鎴鍓?' + this.offsetMinutes + ' 鍒嗛挓鎻愰啋'
+        return '将在任务截止前' + this.offsetMinutes + ' 分钟提醒'
       }
-      return '灏嗗湪浠诲姟瓒呮椂鍚?' + this.offsetMinutes + ' 鍒嗛挓鎻愰啋'
+      return '将在任务超时后' + this.offsetMinutes + ' 分钟提醒'
     },
     channelTip: function() {
       var selected = this.selectedChannels || []
       if (selected.length === 0) {
-        return '璇疯嚦灏戦€夋嫨涓€涓彁閱掓笭閬?
+        return '请至少选择一个提醒渠道'
       }
-      return '宸查€夋嫨 ' + selected.length + ' 涓彁閱掓笭閬?
+      return '已选择 ' + selected.length + ' 个提醒渠道'
     }
   },
   onLoad: function() {
@@ -156,7 +156,7 @@ export default {
       }
     }).catch(function() {
       self.loading = false
-      uni.showToast({ title: '鍔犺浇澶辫触锛岃閲嶈瘯', icon: 'none' })
+      uni.showToast({ title: '加载失败,请重试', icon: 'none' })
     })
   },
   methods: {
@@ -201,12 +201,12 @@ export default {
     saveConfig: function() {
       var self = this
       if (!this.selectedChannels || this.selectedChannels.length === 0) {
-        uni.showToast({ title: '璇疯嚦灏戦€夋嫨涓€涓彁閱掓笭閬?, icon: 'none' })
+        uni.showToast({ title: '请至少选择一个提醒渠道', icon: 'none' })
         return
       }
       var offset = this.offsetMinutes || 0
       if (offset < 1 || offset > 1440) {
-        uni.showToast({ title: '鍋忕Щ鍒嗛挓闇€鍦?1-1440 涔嬮棿', icon: 'none' })
+        uni.showToast({ title: '偏移分钟需在1-1440 之间', icon: 'none' })
         return
       }
       if (this.saving) return
@@ -221,16 +221,16 @@ export default {
       }).then(function(res) {
         self.saving = false
         if (res && res.code === 200) {
-          uni.showToast({ title: '淇濆瓨鎴愬姛', icon: 'success' })
+          uni.showToast({ title: '保存成功', icon: 'success' })
           setTimeout(function() {
             uni.navigateBack()
           }, 1000)
         } else {
-          uni.showToast({ title: (res && res.message) || '淇濆瓨澶辫触', icon: 'none' })
+          uni.showToast({ title: (res && res.message) || '保存失败', icon: 'none' })
         }
       }).catch(function(err) {
         self.saving = false
-        uni.showToast({ title: (err && err.message) || '淇濆瓨澶辫触', icon: 'none' })
+        uni.showToast({ title: (err && err.message) || '保存失败', icon: 'none' })
       })
     }
   }

+ 2 - 3
cfc-frontend/pages/wealth/index.vue

@@ -22,7 +22,7 @@
       </view>
 
       <!-- 游客价值卡(统一组件:维度色 CTA + 价值预览) -->
-      <DimensionVisitorCard
+      <DimensionIntroCard
         dimCode="wealth"
         card-title="富维度 · 利他创富"
         :benefits="[
@@ -268,7 +268,6 @@ 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 DimensionVisitorCard from '../../components/DimensionVisitorCard.vue'
 import DimensionSubDims from '../../components/DimensionSubDims.vue'
 import {
   getChildren,
@@ -289,7 +288,7 @@ import nav from '../../utils/nav.js'
 import config from '../../config.js'
 
 export default {
-  components: { TabTransition, WealthEnergyRing, WealthSubScores, BodyWealthAlert, FamilyMemberStrip, FamilyEnergyBar, DimensionIntroCard, DimensionSubDims, DimensionVisitorCard },
+  components: { TabTransition, WealthEnergyRing, WealthSubScores, BodyWealthAlert, FamilyMemberStrip, FamilyEnergyBar, DimensionIntroCard, DimensionSubDims },
   data() {
     return {
       showTabTransition: true,

+ 2 - 3
cfc-frontend/pages/wisdom-detail/index.vue

@@ -47,7 +47,7 @@
     </view>
 
     <!-- 游客价值卡(统一组件:维度色 CTA + 价值预览) -->
-    <DimensionVisitorCard
+    <DimensionIntroCard
       v-if="!isLoggedIn"
       dimCode="wisdom"
       card-title="智维度 · 认知赋能"
@@ -240,12 +240,11 @@ import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
 import FloatingAvatar from '../../components/AIFloatingAvatar.vue'
 import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
 import DimensionIntroCard from '../../components/DimensionIntroCard.vue'
-import DimensionVisitorCard from '../../components/DimensionVisitorCard.vue'
 import { getVisibleSections, getChildren, getFamilyEnergySandbox, getEnergySandbox, getAssessmentLatestResult, getDanReportByChild, getGutCognitionIndicators, getLatestIndicators, getRadarData } from '../../utils/api.js'
 import { parseDate } from '../../utils/format.js'
 
 export default {
-  components: { TabTransition, RadarChart, FamilyEnergyBar, FloatingAvatar, FamilyMemberStrip, DimensionIntroCard, DimensionVisitorCard },
+  components: { TabTransition, RadarChart, FamilyEnergyBar, FloatingAvatar, FamilyMemberStrip, DimensionIntroCard },
   data() {
     return {
       isLoggedIn: false,