ソースを参照

docs: 家庭成员页面重设计实施计划 + 饮水对比移动端轮播式重设计

- plans/2026-07-31-family-members-redesign.md: 家庭成员页重写计划(模板/脚本/样式/验证四任务)
- specs/2026-07-31-compare-table-mobile-redesign.md: 饮水对比表格移动端轮播卡片设计
- PROJECT-OVERVIEW.md: 文档索引同步新增两条
Xiaogang Liao 3 ヶ月 前
親
コミット
9fbc2ebe83

+ 2 - 0
docs/superpowers/PROJECT-OVERVIEW.md

@@ -312,6 +312,7 @@
 | `2026-07-22-active-health-six-stage-design.md` | ✅ 已实施(P0-P3 全栈完成) | 主动健康六阶模型改进总设计 |
 | `2026-07-10-certainty-ppt-design.md` | 🔄 已废弃(一次性PPT设计,无代码实现) | 确定性叙事PPT |
 | `2026-07-30-service-role-apply-design.md` | ✅ 已实施 | 服务角色统一申请(规划师/营养师/管家/服务商/文章管理员/活动提供商) |
+| `2026-07-31-compare-table-mobile-redesign.md` | 🟡 设计稿 | 四大饮水方案对比 — 移动端轮播式重设计 |
 
 ### 实施计划(plans/)
 
@@ -365,6 +366,7 @@
 | `2026-07-19-report-upload-purchase-links.md` | 🟡 计划中 | 报告购买链接 |
 | `2026-07-24-cfc-web-responsive-implementation.md` | ✅ 已实施(commit 3ee7289e) | 管理后台手机自适应 |
 | `2026-07-28-membership-price-tier-update.md` | ✅ 全栈完成(FREE/FAMILY/PREMIUM 三等级,7 次提交) | 会员等级价格体系更新 |
+| `2026-07-31-family-members-redesign.md` | 🟡 计划中 | 家庭成员页面重新设计 |
 
 ### 计划与设计文档(specs/)
 

+ 329 - 0
docs/superpowers/plans/2026-07-31-family-members-redesign.md

@@ -0,0 +1,329 @@
+# 家庭成员页面重新设计 — 实施计划
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 重新设计家庭管理员页面,改为温暖家庭风的卡片式布局,"添加"和"邀请"改为底部固定按钮,邀请直接弹出海报。
+
+**Architecture:** 单页面重写 `pages/profile-extra/family-members.vue`,移除齿轮设置弹窗、筛选标签、行内三维评分条,新增暖橙渐变头部、卡片式成员列表、底部固定操作栏,邀请流程直接调用 `generateFamilyInviteQrCode` 后弹出 SharePoster。
+
+**Tech Stack:** uni-app Vue 2 Options API,微信小程序
+
+**参考设计文档:** `docs/superpowers/specs/2026-07-31-family-members-redesign.md`
+
+## Global Constraints
+
+- 禁止可选链 `?.`(用 `&&` 替代)
+- 禁止 CSS Grid(用 flexbox)
+- 禁止 `:key` 表达式(用方法调用替代)
+- 使用 Vue 2 Options API
+- 品牌色:暖橙 `#F97316` / 米白 `#FFFCF5` / 暖灰 `#F5F0EB`
+
+---
+
+## 文件结构
+
+| 文件 | 操作 | 说明 |
+|------|------|------|
+| `pages/profile-extra/family-members.vue` | 重写 | 完整替换模板/脚本/样式 |
+| `components/SharePoster.vue` | 无变更 | 保持现有,直接调用 |
+
+---
+
+### Task 1: 重写页面模板 — 头部 + 成员列表 + 空状态
+
+**Files:**
+- Modify: `pages/profile-extra/family-members.vue` (template section)
+
+**Interfaces:**
+- Consumes: `members` (Array), `filteredMembers` (computed→直接返回 members), `familyName` (String), `loading` (Boolean)
+- Produces: 渲染完整的页面结构(头部、成员列表、空状态、底部栏)
+
+- [ ] **Step 1: 替换 `<template>` 头部**
+
+替换现有头部(紫色渐变 + 齿轮图标)为暖橙渐变头部:
+
+```html
+<!-- ===== 家庭信息头部 ===== -->
+<view class="family-header">
+  <view class="family-header-content">
+    <text class="family-header-icon">🏠</text>
+    <view class="family-header-text">
+      <text class="family-name">{{ familyName || '我的家庭' }}</text>
+      <text class="family-member-count">{{ members.length }} 位成员</text>
+    </view>
+  </view>
+</view>
+```
+
+- [ ] **Step 2: 替换成员列表为卡片式布局**
+
+移除筛选标签和行内三维评分条,改为纯卡片:
+
+```html
+<!-- ===== 成员卡片列表 ===== -->
+<view class="member-list">
+  <view class="member-card" v-for="(m, i) in filteredMembers" :key="getMemberKey(m, i)" @click="showMemberDetail(m)">
+    <image class="member-avatar" :src="m.avatar || '/static/default-avatar.png'" mode="aspectFill"></image>
+    <view class="member-info">
+      <text class="member-name">{{ m.nickname || '未设置' }}</text>
+      <text class="member-role-tag">{{ m.relativeLabel || m.roleLabel || (m.effectiveRole === 'parent' ? '家长' : '孩子') }}</text>
+    </view>
+    <text class="member-arrow">›</text>
+  </view>
+</view>
+```
+
+- [ ] **Step 3: 替换空状态**
+
+```html
+<!-- ===== 空状态 ===== -->
+<view v-if="members.length === 0" class="empty-state">
+  <text class="empty-icon">👨‍👩‍👧‍👦</text>
+  <text class="empty-title">还没有家庭成员</text>
+  <text class="empty-desc">邀请家人加入,一起记录成长</text>
+  <button class="empty-invite-btn" @click="onInvite">📨 邀请家人</button>
+</view>
+```
+
+- [ ] **Step 4: 添加底部固定栏**
+
+```html
+<!-- ===== 底部操作栏 ===== -->
+<view class="bottom-bar">
+  <button class="bottom-btn btn-add" @click="onAddMember">+ 添加成员</button>
+  <button class="bottom-btn btn-invite" @click="onInvite">📨 邀请成员</button>
+</view>
+```
+
+- [ ] **Step 5: 保留成员详情弹窗模板(简化关闭按钮)**
+
+保留 `member-detail-card` 弹窗,仅微调视觉(去掉原来紫色相关样式,改为暖橙风格)。移除 `showSettings` 和 `showInviteCard` 相关弹窗。
+
+- [ ] **Step 6: 保留 SharePoster 引用**
+
+```html
+<SharePoster :show="showPoster" :referralCode="inviteCode" :qrCodeBase64="inviteQrCode" :nickname="userNickname" :userAvatar="userAvatar" @close="showPoster = false" />
+```
+
+---
+
+### Task 2: 重写页面脚本
+
+**Files:**
+- Modify: `pages/profile-extra/family-members.vue` (script section)
+
+**Interfaces:**
+- Consumes: `listFamilyMembers`, `switchToFamilyMember`, `kickFamilyMemberById`, `generateFamilyInviteQrCode`, `getFamilyMembers`, `updateFamilyMember` from api.js
+- Produces: 所有页面交互逻辑
+
+- [ ] **Step 1: 精简 imports 和 data**
+
+移除 `showSettings`, `showInviteCard` 相关字段。保留 `showPoster`, `showMemberDetailModal`, `detailMember`, `memberScores` 等。
+
+```js
+import {
+  listFamilyMembers,
+  switchToFamilyMember,
+  kickFamilyMemberById,
+  generateFamilyInviteQrCode,
+  getFamilyMembers,
+  updateFamilyMember
+} from '../../utils/api.js'
+import store from '../../store/index.js'
+import SharePoster from '@/components/SharePoster.vue'
+
+export default {
+  components: { SharePoster },
+  data() {
+    return {
+      userId: 0, familyId: 0, familyName: '', inviteCode: '', inviteQrCode: '',
+      creatorId: null, members: [], isAdmin: false, loading: false,
+      showPoster: false, showMemberDetailModal: false, detailMember: null,
+      memberScores: {}, userNickname: '', userAvatar: ''
+    }
+  },
+```
+
+- [ ] **Step 2: 简化 computed**
+
+移除 `filteredMembers` 的筛选逻辑(直接返回 members),移除 `detailMemberScores`(保留,因为详情弹窗仍显示评分)。
+
+```js
+computed: {
+  filteredMembers() {
+    return this.members  // 无筛选标签,直接返回全部
+  },
+  detailMemberScores() {
+    if (!this.detailMember) return {}
+    return this.memberScores[this.detailMember.id] || {}
+  }
+},
+```
+
+- [ ] **Step 3: 重写 `onInvite` 方法 — 直接弹出海报**
+
+```js
+async onInvite() {
+  this.inviteQrCode = ''
+  this.inviteCode = ''
+  uni.showLoading({ title: '生成邀请...' })
+  try {
+    var res = await generateFamilyInviteQrCode()
+    if (res.code === 200 && res.data) {
+      this.inviteCode = res.data.inviteCode
+      this.inviteQrCode = res.data.qrCodeBase64
+      this.showPoster = true
+    } else {
+      uni.showToast({ title: res.message || '生成失败', icon: 'none' })
+    }
+  } catch (e) {
+    uni.showToast({ title: '生成失败', icon: 'none' })
+  } finally {
+    uni.hideLoading()
+  }
+},
+```
+
+- [ ] **Step 4: 移除 `onAddMember` 中的 `showSettings = false`**
+
+```js
+onAddMember() {
+  uni.navigateTo({ url: '/pages/family/add-member' })
+},
+```
+
+- [ ] **Step 5: 保留其他方法不变**
+
+保留:`loadMembers`, `buildScoreMap`, `loadFamilyInfo`, `showMemberDetail`, `canEditMember`, `onSwitchMember`, `confirmKick`, `onKickMember`, `onCopyCode`, `shareInviteCard`, `onEditRelation`, `doQuestionnaire`, `recordInteraction`
+
+移除:`onInvite` 旧实现(已替换)
+
+---
+
+### Task 3: 重写页面样式
+
+**Files:**
+- Modify: `pages/profile-extra/family-members.vue` (style section)
+
+**Interfaces:**
+- Produces: 温暖家庭风视觉风格
+
+- [ ] **Step 1: 重写全局容器样式**
+
+```css
+.container {
+  padding: 30rpx 30rpx 140rpx;
+  min-height: 100vh;
+  background: #F5F0EB;
+}
+```
+
+- [ ] **Step 2: 暖橙渐变头部**
+
+```css
+.family-header {
+  background: linear-gradient(135deg, #F97316, #EA580C);
+  border-radius: 20rpx;
+  padding: 36rpx 30rpx;
+  margin-bottom: 24rpx;
+  color: #fff;
+  text-align: center;
+}
+.family-header-content { display: flex; align-items: center; justify-content: center; flex-direction: column; }
+.family-header-icon { font-size: 48rpx; margin-bottom: 8rpx; }
+.family-header-text { text-align: center; }
+.family-name { font-size: 36rpx; font-weight: bold; display: block; }
+.family-member-count { font-size: 24rpx; opacity: 0.85; margin-top: 4rpx; display: block; }
+```
+
+- [ ] **Step 3: 成员卡片样式**
+
+```css
+.member-card {
+  display: flex;
+  align-items: center;
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 24rpx 28rpx;
+  margin-bottom: 16rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
+}
+.member-card:active { background: #FFFCF5; }
+.member-avatar { width: 80rpx; height: 80rpx; border-radius: 50%; margin-right: 20rpx; background: #f0f0f0; flex-shrink: 0; }
+.member-info { flex: 1; min-width: 0; }
+.member-name { font-size: 30rpx; font-weight: 600; display: block; color: #333; }
+.member-role-tag { display: inline-block; font-size: 22rpx; color: #F97316; background: #FFF7ED; padding: 4rpx 14rpx; border-radius: 8rpx; margin-top: 6rpx; }
+.member-arrow { font-size: 36rpx; color: #ccc; margin-left: 12rpx; flex-shrink: 0; }
+```
+
+- [ ] **Step 4: 空状态样式**
+
+```css
+.empty-state { text-align: center; padding: 100rpx 30rpx; }
+.empty-icon { font-size: 100rpx; display: block; margin-bottom: 20rpx; }
+.empty-title { font-size: 32rpx; color: #666; display: block; margin-bottom: 12rpx; }
+.empty-desc { font-size: 26rpx; color: #999; display: block; margin-bottom: 40rpx; }
+.empty-invite-btn { background: linear-gradient(135deg, #F97316, #EA580C); color: #fff; border: none; border-radius: 40rpx; padding: 20rpx 50rpx; font-size: 28rpx; }
+```
+
+- [ ] **Step 5: 底部固定栏样式**
+
+```css
+.bottom-bar {
+  position: fixed;
+  left: 0; right: 0; bottom: 0;
+  padding: 16rpx 30rpx calc(16rpx + constant(safe-area-inset-bottom));
+  padding: 16rpx 30rpx calc(16rpx + env(safe-area-inset-bottom));
+  background: #fff;
+  display: flex;
+  gap: 20rpx;
+  box-shadow: 0 -2rpx 12rpx rgba(0,0,0,0.06);
+  z-index: 50;
+}
+.bottom-btn {
+  flex: 1;
+  height: 80rpx;
+  line-height: 80rpx;
+  border-radius: 40rpx;
+  font-size: 28rpx;
+  text-align: center;
+  border: none;
+}
+.bottom-btn:active { opacity: 0.85; }
+.btn-add { background: #fff; color: #F97316; border: 2rpx solid #F97316; }
+.btn-invite { background: linear-gradient(135deg, #F97316, #EA580C); color: #fff; }
+```
+
+- [ ] **Step 6: 更新成员详情弹窗样式暖色风**
+
+将详情弹窗中紫色 `#667eea` 替换为暖橙 `#F97316`,保留原有布局和功能按钮样式,将 `.action-switch` 等按钮颜色微调为暖色调。
+
+- [ ] **Step 7: 移除旧样式**
+
+移除不再使用的样式:`.filter-tabs`, `.filter-tab`, `.settings-card`, `.settings-header`, `.settings-body`, `.settings-item`, `.invite-card`, `.invite-qr-wrap`, `.invite-code-section`, `.invite-actions`, `.member-quality-scores`, `.score-item`, `.member-source-tag`, `.member-extra`
+
+---
+
+### Task 4: 验证
+
+**Files:**
+- Verify: `pages/profile-extra/family-members.vue`
+
+- [ ] **Step 1: 编译验证**
+
+```bash
+cd /app/cfc/cfc-frontend && npm run build:mp-weixin 2>&1 | tail -20
+```
+
+- [ ] **Step 2: 最终检查**
+
+检查是否满足所有需求:
+- 暖橙渐变头部 ✓
+- 卡片式成员列表(头像 + 昵称 + 关系标签)✓
+- 无筛选标签 ✓
+- 无行内三维评分 ✓
+- 底部固定栏(添加 + 邀请)✓
+- 邀请直接弹出海报 ✓
+- 无齿轮设置弹窗 ✓
+- 无邀请码卡片弹窗 ✓
+- 成员详情弹窗保留 ✓

+ 46 - 0
docs/superpowers/specs/2026-07-31-compare-table-mobile-redesign.md

@@ -0,0 +1,46 @@
+# 四大饮水方案对比 — 移动端轮播式重设计
+
+## 背景
+
+`web/articles/plastic-health.html` 的 `#compare` 区段包含一个 5 列 × 9 行的 HTML 表格,对比四种饮水方案。在手机屏幕上,5 列宽度不足,需横向滚动,体验不佳。
+
+## 目标
+
+移动端(< 768px)将表格改为**左右滑动轮播卡片**,桌面端(≥ 768px)保留现有表格。
+
+## 设计方案
+
+### 移动端:轮播卡片
+
+- 4 张卡片,顺序:桶装水 → 自来水烧开 → RO纯水机 → 电解还原水机
+- CSS `scroll-snap` 实现原生滑动,无需外部库
+- 每张卡片显示全部 9 项对比指标
+- 底部 4 个导航圆点 + 左右箭头按钮
+- 少量 JavaScript(约 30 行)处理圆点/箭头交互
+
+### 卡片颜色方案
+
+| 卡片 | 顶部标签颜色 | 标签文字 |
+|------|-------------|---------|
+| 桶装水/瓶装水 | 红色 `#E53E3E` | ❌ 风险最高 |
+| 自来水烧开 | 绿色 `#38A169` | ✅ 安全经济之选 |
+| RO反渗透纯水机 | 橙色 `#DD6B20` | ⚠️ 缺矿物质 |
+| 电解还原水机 | 蓝色 `#3182CE` | 🥇 综合最优解 |
+
+### 桌面端
+
+- 现有表格完全保留,通过 `@media (max-width: 767px)` 隐藏
+- 轮播卡片在桌面端不显示
+
+### 技术实现
+
+- 纯 CSS `scroll-snap-type: x mandatory` + `scroll-snap-align: start`
+- 轮播容器 `.compare-carousel`,内嵌 `.carousel-track` 包含 4 个 `.carousel-card`
+- 底部圆点 `.carousel-dots` 通过 JS 监听 `scroll` 事件更新激活状态
+- 箭头按钮通过 JS 调用 `scrollBy` / `scrollIntoView`
+
+## 文件变更
+
+- 仅修改 `web/articles/plastic-health.html`
+- 新增样式(约 80 行 CSS)+ 轮播 HTML 结构(约 200 行)+ 交互 JS(约 40 行)
+- 现有表格不变,仅添加 `d-none d-md-block` 类控制显示