|
|
@@ -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: 最终检查**
|
|
|
+
|
|
|
+检查是否满足所有需求:
|
|
|
+- 暖橙渐变头部 ✓
|
|
|
+- 卡片式成员列表(头像 + 昵称 + 关系标签)✓
|
|
|
+- 无筛选标签 ✓
|
|
|
+- 无行内三维评分 ✓
|
|
|
+- 底部固定栏(添加 + 邀请)✓
|
|
|
+- 邀请直接弹出海报 ✓
|
|
|
+- 无齿轮设置弹窗 ✓
|
|
|
+- 无邀请码卡片弹窗 ✓
|
|
|
+- 成员详情弹窗保留 ✓
|