|
@@ -0,0 +1,666 @@
|
|
|
|
|
+# 小程序首页功能修复计划
|
|
|
|
|
+
|
|
|
|
|
+> **For agentic workers:** 使用 superpowers:subagent-driven-development 或 superpowers:executing-plans 来执行此计划。
|
|
|
|
|
+
|
|
|
|
|
+**目标:** 修复小程序首页(parent-index / child-index)的 10 个实现问题
|
|
|
|
|
+
|
|
|
|
|
+**问题总览:**
|
|
|
|
|
+
|
|
|
|
|
+| # | 严重度 | 问题 | 文件 | 影响 |
|
|
|
|
|
+|---|---|---|---|---|
|
|
|
|
|
+| A1 | 🔴高 | FamilyEnergyBar 硬编码 3 个未注册页面路径,点击成员行→404 | `FamilyEnergyBar.vue:134-139` | 运行时导航失败 |
|
|
|
|
|
+| A2 | 🟡中 | DimensionActivities 组件自加载 API,父组件 prop 数据被忽略 | `DimensionActivities.vue:47-65` | 重复 API 调用,prop 冗余 |
|
|
|
|
|
+| A3 | 🟡中 | DimensionProducts 组件自加载 API,父组件 prop 数据被忽略 | `DimensionProducts.vue:39-53` | 同 A2 |
|
|
|
|
|
+| A4 | 🟡中 | `onDomainClick` 导航 URL 缺少 `childId` 参数 | `child-index.vue:580-585` | 能量详情页无法加载孩子数据 |
|
|
|
|
|
+| A5 | 🟡中 | 成就徽章 3/4/5 无解锁触发逻辑 | `child-index.vue:499-504` | 运动/艺术/家务徽章永不解锁 |
|
|
|
|
|
+| A6 | 🟡中 | 模板残留孤立标签 `@moreProducts` | `parent-index.vue:229` | 微信 XML 解析警告,事件绑定丢失 |
|
|
|
|
|
+| A7 | 🟢低 | preview 模式每次 mount 重算随机值,视觉不稳定 | `wuxing-sandbox.vue:304-310` | 沙盘数值闪烁 |
|
|
|
|
|
+| A8 | 🟢低 | FamilyMemberCard `roleLabel` 依赖后端,可能未填充 | `FamilyMemberCard.vue:9` | 角色标签空白 |
|
|
|
|
|
+| A9 | 🟢低 | 降级路径 `!c.role` 条件混入非孩子角色 | `parent-index.vue:432-433` | 成员过滤不精确 |
|
|
|
|
|
+| A10 | 🟢低 | FamilyMemberStrip scroll-view 无固定高度 | `FamilyMemberStrip.vue:109-112` | 小屏设备布局风险 |
|
|
|
|
|
+
|
|
|
|
|
+**优先级排序:** A1 → A6 → A4 → A5 → A2 → A3 → A7 → A8 → A9 → A10
|
|
|
|
|
+
|
|
|
|
|
+**架构:** 10 个任务,每个只改一个文件。Task 1-6 为高/中优先级,Task 7-10 为低优先级可并行处理。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Task 1: 修复 FamilyEnergyBar 硬编码未注册页面路径(A1)
|
|
|
|
|
+
|
|
|
|
|
+**优先级:** 🔴 高(运行时 404)
|
|
|
|
|
+
|
|
|
|
|
+**文件:** `cfc-frontend/components/FamilyEnergyBar.vue`
|
|
|
|
|
+
|
|
|
|
|
+### 问题定位
|
|
|
|
|
+
|
|
|
|
|
+L134-139:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+var detailPages = {
|
|
|
|
|
+ body: '/pages/body/member-body-detail',
|
|
|
|
|
+ mind: '/pages/mind/member-mind-detail',
|
|
|
|
|
+ wisdom: '/pages/mind/member-mind-detail', // ⚠️ wisdom 和 mind 共用同一页
|
|
|
|
|
+ action: '/pages/action/member-action-detail'
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`member-body-detail`、`member-mind-detail`、`member-action-detail` 这 3 个页面**未在 `pages.json` 中注册**,点击成员行直接 404。
|
|
|
|
|
+
|
|
|
|
|
+### 修复方案
|
|
|
|
|
+
|
|
|
|
|
+**选项 A(推荐):** 在 `pages.json` 中注册这 3 个成员详情页面(需要评估是否有对应的 Vue 文件存在)。
|
|
|
|
|
+
|
|
|
|
|
+**选项 B(保守):** 将 `onMemberClick` 方法中的跳转改为 `uni.showToast({ title: '功能即将上线', icon: 'none' })`,阻止 404。
|
|
|
|
|
+
|
|
|
|
|
+**选项 C:** 若这些页面存在但未注册,补全 `pages.json` 注册条目。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 确认目标页面文件是否存在于 `pages/` 目录**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+ls /root/cfc/cfc-frontend/pages/body/member-body-detail* 2>/dev/null || echo "NOT FOUND"
|
|
|
|
|
+ls /root/cfc/cfc-frontend/pages/mind/member-mind-detail* 2>/dev/null || echo "NOT FOUND"
|
|
|
|
|
+ls /root/cfc/cfc-frontend/pages/action/member-action-detail* 2>/dev/null || echo "NOT FOUND"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 根据 Step 1 结果选择修复路径**
|
|
|
|
|
+
|
|
|
|
|
+**若文件存在但未注册(选项 A):** 在 `pages.json` 中补充注册:
|
|
|
|
|
+
|
|
|
|
|
+```json
|
|
|
|
|
+{
|
|
|
|
|
+ "path": "pages/body/member-body-detail/index",
|
|
|
|
|
+ "style": { "navigationBarTitleText": "身体详情" }
|
|
|
|
|
+},
|
|
|
|
|
+{
|
|
|
|
|
+ "path": "pages/mind/member-mind-detail/index",
|
|
|
|
|
+ "style": { "navigationBarTitleText": "心智详情" }
|
|
|
|
|
+},
|
|
|
|
|
+{
|
|
|
|
|
+ "path": "pages/action/member-action-detail/index",
|
|
|
|
|
+ "style": { "navigationBarTitleText": "行动详情" }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+然后检查对应组件是否完整实现,若不完整则标记后续任务。
|
|
|
|
|
+
|
|
|
|
|
+**若文件不存在(选项 B):** 将 L133-145 替换为:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+onMemberClick: function(member) {
|
|
|
|
|
+ // TODO: 注册 member-xxx-detail 页面后替换为真实跳转
|
|
|
|
|
+ uni.showToast({
|
|
|
|
|
+ title: '家庭成员详情功能即将上线',
|
|
|
|
|
+ icon: 'none',
|
|
|
|
|
+ duration: 2000
|
|
|
|
|
+ })
|
|
|
|
|
+},
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 验证 pages.json 中无重复注册**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+grep -n "member-body-detail\|member-mind-detail\|member-action-detail" /root/cfc/cfc-frontend/pages.json
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Task 2: 修复 parent-index.vue 模板孤立标签(A6)
|
|
|
|
|
+
|
|
|
|
|
+**优先级:** 🟡 中
|
|
|
|
|
+
|
|
|
|
|
+**文件:** `cfc-frontend/pages/index/parent-index.vue`
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 确认问题行**
|
|
|
|
|
+
|
|
|
|
|
+文件 L221–229 区域当前状态:
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+221: <!-- ===== 推荐商品 ===== -->
|
|
|
|
|
+222: <DimensionProducts
|
|
|
|
|
+223: dimensionCode="all"
|
|
|
|
|
+224: :products="recommendedProducts"
|
|
|
|
|
+225: @productClick="goProductDetail" /> ← 自闭合,@moreProducts 丢失
|
|
|
|
|
+226: <view class="error-state" v-if="errorProducts">
|
|
|
|
|
+227: <text class="error-state-text">商品推荐加载失败</text>
|
|
|
|
|
+228: </view>
|
|
|
|
|
+229: @moreProducts="goMoreProducts" /> ← 孤立残留行,必须删除
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 将 `@moreProducts` 合并到 DimensionProducts 组件上,删除孤立行**
|
|
|
|
|
+
|
|
|
|
|
+将 L222–225 改为:
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+222: <DimensionProducts
|
|
|
|
|
+223: dimensionCode="all"
|
|
|
|
|
+224: :products="recommendedProducts"
|
|
|
|
|
+225: @productClick="goProductDetail"
|
|
|
|
|
+226: @moreProducts="goMoreProducts" />
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+删除 L229(`@moreProducts="goMoreProducts" />`)。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 验证 `goMoreProducts` 方法存在**
|
|
|
|
|
+
|
|
|
|
|
+L687-688:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+goMoreProducts() {
|
|
|
|
|
+ uni.switchTab({ url: '/pages/discover/index' })
|
|
|
|
|
+},
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+已存在,无需修改。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 4: lint 检查**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+cd /root/cfc/cfc-frontend && npx eslint pages/index/parent-index.vue --no-error-on-unmatched-pattern 2>&1 | head -20
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Task 3: 修复 child-index.vue onDomainClick 缺少 childId(A4)
|
|
|
|
|
+
|
|
|
|
|
+**优先级:** 🟡 中
|
|
|
|
|
+
|
|
|
|
|
+**文件:** `cfc-frontend/pages/index/child-index.vue`
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 确认当前错误实现**
|
|
|
|
|
+
|
|
|
|
|
+L580–585:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+onDomainClick(evt) {
|
|
|
|
|
+ var code = evt && evt.code
|
|
|
|
|
+ if (!code) return
|
|
|
|
|
+ uni.navigateTo({
|
|
|
|
|
+ url: '/pages/energy/detail?code=' + code // ❌ 缺少 childId
|
|
|
|
|
+ })
|
|
|
|
|
+},
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 修复 onDomainClick,追加 childId**
|
|
|
|
|
+
|
|
|
|
|
+替换 L580–585 为:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+onDomainClick(evt) {
|
|
|
|
|
+ var code = evt && evt.code
|
|
|
|
|
+ if (!code || !this.childId) return
|
|
|
|
|
+ uni.navigateTo({
|
|
|
|
|
+ url: '/pages/energy/detail?childId=' + this.childId + '&code=' + code
|
|
|
|
|
+ })
|
|
|
|
|
+},
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 验证与 parent-index 逻辑一致**
|
|
|
|
|
+
|
|
|
|
|
+parent-index.vue L656–665 的 `goToDomain` 方法:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+goToDomain(code) {
|
|
|
|
|
+ var childId = this.currentChildId
|
|
|
|
|
+ if (!childId || !code) return
|
|
|
|
|
+ uni.navigateTo({
|
|
|
|
|
+ url: '/pages/energy/detail?childId=' + childId + '&code=' + code
|
|
|
|
|
+ })
|
|
|
|
|
+},
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+child-index 修复后行为与 parent-index 一致。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 4: lint 检查**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+cd /root/cfc/cfc-frontend && npx eslint pages/index/child-index.vue --no-error-on-unmatched-pattern 2>&1 | head -20
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Task 4: 补充成就徽章解锁触发逻辑(A5)
|
|
|
|
|
+
|
|
|
|
|
+**优先级:** 🟡 中
|
|
|
|
|
+
|
|
|
|
|
+**文件:** `cfc-frontend/pages/index/child-index.vue`
|
|
|
|
|
+
|
|
|
|
|
+### 徽章 → 解锁条件
|
|
|
|
|
+
|
|
|
|
|
+| 徽章 ID | 名称 | 解锁条件 |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| 1 | 🌟 早起鸟 | `streakDays >= 7` ✅ 已有 |
|
|
|
|
|
+| 2 | 📚 阅读小达人 | `totalPoints >= 1000` ✅ 已有 |
|
|
|
|
|
+| 3 | 🏃 运动健将 | 完成 `运动类` 任务 ≥ 5 次 |
|
|
|
|
|
+| 4 | 🎨 小艺术家 | 完成 `艺术类` 任务 ≥ 5 次 |
|
|
|
|
|
+| 5 | 🧹 家务小能手 | 完成 `家务类` 任务 ≥ 5 次 |
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 确认任务数据位置**
|
|
|
|
|
+
|
|
|
|
|
+L492–496:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+const tasksRes = await getTodayTasks(this.childId)
|
|
|
|
|
+const rawTasks = tasksRes.data || []
|
|
|
|
|
+this.todayTasks = rawTasks
|
|
|
|
|
+this.completedCount = rawTasks.filter(function(t) { return t.status === 'completed' }).length
|
|
|
|
|
+this.totalTaskCount = rawTasks.length
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 替换 L498–504 解锁逻辑区域**
|
|
|
|
|
+
|
|
|
|
|
+当前代码(L498–504):
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+// 解锁成就
|
|
|
|
|
+if (this.streakDays >= 7) {
|
|
|
|
|
+ this.unlockBadge(1)
|
|
|
|
|
+}
|
|
|
|
|
+if (this.totalPoints >= 1000) {
|
|
|
|
|
+ this.unlockBadge(2)
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+替换为:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+// 解锁成就
|
|
|
|
|
+if (this.streakDays >= 7) {
|
|
|
|
|
+ this.unlockBadge(1)
|
|
|
|
|
+}
|
|
|
|
|
+if (this.totalPoints >= 1000) {
|
|
|
|
|
+ this.unlockBadge(2)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 按类别统计已完成任务次数,解锁对应徽章
|
|
|
|
|
+var categoryCompletedCount = { '运动类': 0, '艺术类': 0, '家务类': 0 }
|
|
|
|
|
+for (var i = 0; i < rawTasks.length; i++) {
|
|
|
|
|
+ var task = rawTasks[i]
|
|
|
|
|
+ if (task.status === 'completed') {
|
|
|
|
|
+ var cat = task.category || '其他'
|
|
|
|
|
+ if (cat === '运动类') {
|
|
|
|
|
+ categoryCompletedCount['运动类']++
|
|
|
|
|
+ } else if (cat === '艺术类') {
|
|
|
|
|
+ categoryCompletedCount['艺术类']++
|
|
|
|
|
+ } else if (cat === '家务类') {
|
|
|
|
|
+ categoryCompletedCount['家务类']++
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+if (categoryCompletedCount['运动类'] >= 5) {
|
|
|
|
|
+ this.unlockBadge(3)
|
|
|
|
|
+}
|
|
|
|
|
+if (categoryCompletedCount['艺术类'] >= 5) {
|
|
|
|
|
+ this.unlockBadge(4)
|
|
|
|
|
+}
|
|
|
|
|
+if (categoryCompletedCount['家务类'] >= 5) {
|
|
|
|
|
+ this.unlockBadge(5)
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> **注意**:`task.status === 'completed'` — 与 L495 的过滤条件保持一致,不使用 `|| task.status === 'done'`。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: lint 检查**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+cd /root/cfc/cfc-frontend && npx eslint pages/index/child-index.vue --no-error-on-unmatched-pattern 2>&1 | head -20
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Task 5: 修复 DimensionActivities 重复 API 调用(A2)
|
|
|
|
|
+
|
|
|
|
|
+**优先级:** 🟡 中(架构问题)
|
|
|
|
|
+
|
|
|
|
|
+**文件:** `cfc-frontend/components/DimensionActivities.vue`
|
|
|
|
|
+
|
|
|
|
|
+### 问题分析
|
|
|
|
|
+
|
|
|
|
|
+**双重调用链路:**
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+parent-index / child-index:
|
|
|
|
|
+ loadData() → getActivityList() → dimensionActivities → :activities prop
|
|
|
|
|
+ ↑
|
|
|
|
|
+ ↑(第一次调用,prop 数据被忽略)
|
|
|
|
|
+
|
|
|
|
|
+DimensionActivities 组件:
|
|
|
|
|
+ created() → getActivityList() → activitiesData → displayActivities
|
|
|
|
|
+ ↑
|
|
|
|
|
+ ↑(第二次调用,实际生效)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`displayActivities` 优先级:`activitiesData.length > 0 ? activitiesData : activities`,组件自加载的 `activitiesData` 永远有值,父组件 prop 永远被忽略。
|
|
|
|
|
+
|
|
|
|
|
+### 修复方案
|
|
|
|
|
+
|
|
|
|
|
+**选项 A(推荐):** 移除组件 `created()` 中的自加载逻辑,改为完全依赖 prop 数据(消除重复调用)。
|
|
|
|
|
+
|
|
|
|
|
+**选项 B:** 父组件不调用 API,直接传空数组,组件自加载生效(保持现状但承认架构)。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 确认 DimensionActivities 的调用方传入了什么**
|
|
|
|
|
+
|
|
|
|
|
+parent-index L212-216:
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+<DimensionActivities
|
|
|
|
|
+ dimensionCode="all"
|
|
|
|
|
+ :activities="recommendedActivities"
|
|
|
|
|
+ @activityClick="goActivityDetail"
|
|
|
|
|
+ @moreActivities="goMoreActivities" />
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+child-index L269-274:
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+<DimensionActivities
|
|
|
|
|
+ dimensionCode="child"
|
|
|
|
|
+ :activities="dimensionActivities"
|
|
|
|
|
+ @activityClick="goActivityDetail"
|
|
|
|
|
+ @moreActivities="goMoreProducts" />
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**两个调用方都已经在 loadData 中调用了 `getActivityList` 并通过 prop 传入**,组件自加载是冗余的。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 移除组件 `created()` 自加载逻辑**
|
|
|
|
|
+
|
|
|
|
|
+删除 `DimensionActivities.vue` L47-65 的 `created()` 钩子和 `loadActivities` 方法:
|
|
|
|
|
+
|
|
|
|
|
+删除:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+created: function() {
|
|
|
|
|
+ this.loadActivities()
|
|
|
|
|
+},
|
|
|
|
|
+methods: {
|
|
|
|
|
+ loadActivities: function() {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ import('@/utils/api.js').then(function(api) {
|
|
|
|
|
+ api.getActivityList({ dimensionCode: self.dimensionCode, page: 1, size: 6 }).then(function(res) {
|
|
|
|
|
+ var records = (res.data && res.data.records) ? res.data.records : []
|
|
|
|
|
+ self.activitiesData = records
|
|
|
|
|
+ }).catch(function() {
|
|
|
|
|
+ // silently fail
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+同时删除 `data()` 中的 `activitiesData: []`(L38-39),因为不再需要内部状态。
|
|
|
|
|
+
|
|
|
|
|
+`displayActivities` computed 简化为:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+displayActivities: function() {
|
|
|
|
|
+ return this.activities
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 验证修改后 dimensionCode prop 仍传递给父组件(事件链路不变)**
|
|
|
|
|
+
|
|
|
|
|
+`dimensionCode` 仅用于传给父组件的事件参数,不需要在组件内部使用。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 4: lint 检查**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+cd /root/cfc/cfc-frontend && npx eslint components/DimensionActivities.vue --no-error-on-unmatched-pattern 2>&1 | head -20
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Task 6: 修复 DimensionProducts 重复 API 调用(A3)
|
|
|
|
|
+
|
|
|
|
|
+**优先级:** 🟡 中(架构问题)
|
|
|
|
|
+
|
|
|
|
|
+**文件:** `cfc-frontend/components/DimensionProducts.vue`
|
|
|
|
|
+
|
|
|
|
|
+### 问题分析
|
|
|
|
|
+
|
|
|
|
|
+与 Task 5 完全相同:
|
|
|
|
|
+- 父组件在 `loadData` 中调用 `getProductsByDomain` 并通过 prop 传入
|
|
|
|
|
+- 组件 `created()` 自加载,`activitiesData` 优先级覆盖 prop
|
|
|
|
|
+- 造成重复 API 调用
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 确认调用方传入数据**
|
|
|
|
|
+
|
|
|
|
|
+parent-index L222-225:
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+<DimensionProducts
|
|
|
|
|
+ dimensionCode="all"
|
|
|
|
|
+ :products="recommendedProducts"
|
|
|
|
|
+ @productClick="goProductDetail"
|
|
|
|
|
+ @moreProducts="goMoreProducts" />
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+child-index L277-282:
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+<DimensionProducts
|
|
|
|
|
+ dimensionCode="child"
|
|
|
|
|
+ :products="dimensionProducts"
|
|
|
|
|
+ @productClick="goProductDetail"
|
|
|
|
|
+ @moreProducts="goMoreProducts" />
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 移除组件自加载(删除 `created()`、`loadProducts` 方法、`productsData` 状态)**
|
|
|
|
|
+
|
|
|
|
|
+与 Task 5 完全对称的处理,删除 L39-53:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+data: function() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ productsData: [] // 删除
|
|
|
|
|
+ }
|
|
|
|
|
+},
|
|
|
|
|
+created: function() {
|
|
|
|
|
+ this.loadProducts() // 删除
|
|
|
|
|
+},
|
|
|
|
|
+methods: {
|
|
|
|
|
+ loadProducts: function() { ... } // 删除
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`displayProducts` computed 简化为:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+displayProducts: function() {
|
|
|
|
|
+ return this.products
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: lint 检查**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+cd /root/cfc/cfc-frontend && npx eslint components/DimensionProducts.vue --no-error-on-unmatched-pattern 2>&1 | head -20
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Task 7: 优化 wuxing-sandbox preview 模式随机值抖动(A7)
|
|
|
|
|
+
|
|
|
|
|
+**优先级:** 🟢 低
|
|
|
|
|
+
|
|
|
|
|
+**文件:** `cfc-frontend/components/wuxing-sandbox.vue`
|
|
|
|
|
+
|
|
|
|
|
+### 问题分析
|
|
|
|
|
+
|
|
|
|
|
+L304-310,每次组件重新 mount 时 `Math.random()` 生成新的随机分数,导致数值闪烁:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+if (this.isPreview) {
|
|
|
|
|
+ var scores = []
|
|
|
|
|
+ for (var i = 0; i < 5; i++) {
|
|
|
|
|
+ scores.push(Math.floor(Math.random() * 51) + 35) // 35-85 随机
|
|
|
|
|
+ }
|
|
|
|
|
+ this.previewScores = scores // ⚠️ 每次 mount 重算
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 修复方案
|
|
|
|
|
+
|
|
|
|
|
+将预览数据持久化到 Storage,首次生成后缓存,后续 mount 读取缓存。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 在 mounted 中加入缓存逻辑**
|
|
|
|
|
+
|
|
|
|
|
+将 L304-310 替换为:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+if (this.isPreview) {
|
|
|
|
|
+ var cacheKey = '_wuxing_preview_scores'
|
|
|
|
|
+ var cached = uni.getStorageSync(cacheKey)
|
|
|
|
|
+ if (cached && Array.isArray(cached) && cached.length === 5) {
|
|
|
|
|
+ this.previewScores = cached
|
|
|
|
|
+ } else {
|
|
|
|
|
+ var scores = []
|
|
|
|
|
+ for (var i = 0; i < 5; i++) {
|
|
|
|
|
+ scores.push(Math.floor(Math.random() * 51) + 35)
|
|
|
|
|
+ }
|
|
|
|
|
+ this.previewScores = scores
|
|
|
|
|
+ uni.setStorageSync(cacheKey, scores)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**权衡**:这意味着游客第一次访问生成的随机值会一直保持,直到清除缓存。若希望"仅在当前 session 稳定"而非"永久稳定",将 `uni.setStorageSync` 替换为 `uni.setStorageSync(cacheKey, scores)` 并在 `onUnload` 清除。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Task 8: 确认 FamilyMemberCard roleLabel 后端填充(A8)
|
|
|
|
|
+
|
|
|
|
|
+**优先级:** 🟢 低(需后端确认)
|
|
|
|
|
+
|
|
|
|
|
+**文件:** `cfc-frontend/components/FamilyMemberCard.vue`
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 搜索后端 FamilyMemberVO 是否填充了 roleLabel**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+grep -n "roleLabel" /root/cfc/cfc-backend/src/main/java/com/etotem/cfc/dto/FamilyMemberVO.java
|
|
|
|
|
+grep -n "roleLabel" /root/cfc/cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java | head -20
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 根据搜索结果处理**
|
|
|
|
|
+
|
|
|
|
|
+**若后端已填充**:在 `FamilyMemberCard.vue` 增加兜底显示:
|
|
|
|
|
+
|
|
|
|
|
+L9:
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+<text class="member-card__role">{{ member.roleLabel || (member.memberType === 'child' ? '孩子' : '家长') }}</text>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**若后端未填充**:建议在后端 `UserService.getVisibleMembers()` 中填充 `roleLabel` 字段,此任务标记为"需后端配合"。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Task 9: 收紧 parent-index 降级路径成员过滤条件(A9)
|
|
|
|
|
+
|
|
|
|
|
+**优先级:** 🟢 低
|
|
|
|
|
+
|
|
|
|
|
+**文件:** `cfc-frontend/pages/index/parent-index.vue`
|
|
|
|
|
+
|
|
|
|
|
+### 问题分析
|
|
|
|
|
+
|
|
|
|
|
+L432-433:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+var filteredChildren = dashChildren.filter(function(c) {
|
|
|
|
|
+ return c && c.id && c.id !== currentUserId && (c.role === 'child' || c.memberType === 'child' || !c.role)
|
|
|
|
|
+})
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`!c.role` 的意图是"若成员没有 role 字段也保留",但这可能导致非孩子角色被误包含。
|
|
|
|
|
+
|
|
|
|
|
+### 修复方案
|
|
|
|
|
+
|
|
|
|
|
+收紧条件,明确只允许孩子角色:
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 替换 L432-433 过滤条件**
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+var filteredChildren = dashChildren.filter(function(c) {
|
|
|
|
|
+ return c && c.id && c.id !== currentUserId && (c.role === 'child' || c.memberType === 'child')
|
|
|
|
|
+})
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+删除 `|| !c.role`。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Task 10: 修复 FamilyMemberStrip scroll-view 高度(A10)
|
|
|
|
|
+
|
|
|
|
|
+**优先级:** 🟢 低
|
|
|
|
|
+
|
|
|
|
|
+**文件:** `cfc-frontend/components/FamilyMemberStrip.vue`
|
|
|
|
|
+
|
|
|
|
|
+### 问题分析
|
|
|
|
|
+
|
|
|
|
|
+L109-112:
|
|
|
|
|
+
|
|
|
|
|
+```css
|
|
|
|
|
+.member-strip__scroll {
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`scroll-view` 组件若无固定高度,在部分 Android 机型上可能无法正确滚动。
|
|
|
|
|
+
|
|
|
|
|
+### 修复方案
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 在 scroll-view 上添加固定高度**
|
|
|
|
|
+
|
|
|
|
|
+L6-13 区域,将 `<scroll-view>` 标签添加 style:
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+<scroll-view
|
|
|
|
|
+ v-if="allMembers.length >= 4"
|
|
|
|
|
+ class="member-strip__scroll"
|
|
|
|
|
+ scroll-x
|
|
|
|
|
+ :scroll-with-animation="true"
|
|
|
|
|
+ :enhanced="true"
|
|
|
|
|
+ :show-scrollbar="false"
|
|
|
|
|
+ style="height: 160rpx;"
|
|
|
|
|
+>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+160rpx 基于成员卡片高度(约 104rpx avatar 72rpx + padding 16rpx + 名称行 22rpx × 2)估算,留出足够空间。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 执行验证
|
|
|
|
|
+
|
|
|
|
|
+### 编译验证
|
|
|
|
|
+```bash
|
|
|
|
|
+cd /root/cfc/cfc-backend && mvn clean compile -q
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 前端语法检查
|
|
|
|
|
+```bash
|
|
|
|
|
+cd /root/cfc/cfc-frontend && npm run build:mp-weixin 2>&1 | grep -E "ERROR|error|Warning" | head -30
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 微信开发者工具验证
|
|
|
|
|
+
|
|
|
|
|
+| 操作 | 预期 |
|
|
|
|
|
+|---|---|
|
|
|
|
|
+| 家长账号 → 首页 → 点击能量横条成员行 | 不再 404(Toast 提示或正常跳转) |
|
|
|
|
|
+| 家长账号 → 首页 → 推荐商品 → 查看更多 | 跳转发现页 |
|
|
|
|
|
+| 孩子账号 → 首页 → 点击沙盘点 | 跳转能量详情页,正常显示孩子数据 |
|
|
|
|
|
+| 孩子完成 5 个运动类任务 → 返回首页 | 运动健将徽章解锁 |
|
|
|
|
|
+| 孩子完成 5 个艺术类任务 → 返回首页 | 小艺术家徽章解锁 |
|
|
|
|
|
+| 孩子完成 5 个家务类任务 → 返回首页 | 家务小能手徽章解锁 |
|
|
|
|
|
+
|
|
|
|
|
+### 回退方案
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git checkout cfc-frontend/pages/index/parent-index.vue \
|
|
|
|
|
+ cfc-frontend/pages/index/child-index.vue \
|
|
|
|
|
+ cfc-frontend/components/FamilyEnergyBar.vue \
|
|
|
|
|
+ cfc-frontend/components/DimensionActivities.vue \
|
|
|
|
|
+ cfc-frontend/components/DimensionProducts.vue \
|
|
|
|
|
+ cfc-frontend/components/wuxing-sandbox.vue \
|
|
|
|
|
+ cfc-frontend/components/FamilyMemberCard.vue \
|
|
|
|
|
+ cfc-frontend/components/FamilyMemberStrip.vue \
|
|
|
|
|
+ cfc-frontend/pages.json
|
|
|
|
|
+```
|