ソースを参照

docs(spec): 家庭成员页可编辑家庭名称设计规格

Agnes Agent 1 週間 前
親
コミット
945d690b7f

+ 0 - 0
cfc-frontend/components/MemberPickerPopup.vue → cfc-frontend/pages/health/components/MemberPickerPopup.vue


+ 99 - 0
docs/superpowers/specs/2026-11-01-family-members-rename-design.md

@@ -0,0 +1,99 @@
+# 设计规格:家庭成员页可编辑家庭名称
+
+- 日期:2026-11-01
+- 模块:cfc-frontend(微信小程序)+ cfc-backend(Spring Boot)
+- 状态:已批准
+
+## 1. 背景与目标
+
+家庭成员页 `pages/profile-extra/family-members.vue` 顶部展示「XX的家庭」,但家庭名称目前无法修改。需求:在家庭名称处增加编辑功能,仅家庭管理员可修改。
+
+非目标(YAGNI):
+- 不改其他页面、不改数据库结构(`families.name` 已存在,无 DDL 迁移)。
+- 不做家庭名称唯一性校验、不做审计日志(超出本次范围)。
+
+## 2. 权限模型
+
+- **仅家庭管理员可改**。判定复用后端现有口径 `FamilyMemberService.isFamilyAdminOperator(userId, familyId)`:家庭创建者(`families.creator_id`)或其成员记录 `family_members.is_admin=1`。
+- 前端:非管理员不显示编辑入口。
+- 后端:仍强校验(防绕过前端直接调接口),非管理员返回错误。
+
+## 3. 后端设计
+
+### 3.1 新接口
+
+`POST /api/family/member/rename-family`
+
+挂在 `FamilyMembersController`(基路径 `/api/family/member`),请求体 `{ "name": "新家庭名" }`,返回 `Result<String>`(更新后的名称)。
+
+理由:家庭管理员鉴权逻辑在 `FamilyMemberService`,放同控制器/服务可复用同一口径,避免在 `FamilyController` 重复实现鉴权。
+
+### 3.2 Service 方法
+
+`FamilyMemberService.renameFamily(Long userId, String name)`:
+
+1. 校验 `name`:非空、trim 后非空、`name.trim().length() <= 20`(与成员昵称上限一致),否则抛 `IllegalArgumentException`。
+2. 取用户 `familyId`:`userMapper.selectById(userId)`;无用户或无家庭 → 抛「请先创建或加入家庭」。
+3. 鉴权:`isFamilyAdminOperator(userId, familyId)` 为 false → 抛「仅家庭管理员可修改家庭名称」。
+4. 更新 `families.name`(trim 后值)与 `updatedAt`,`familyMapper.updateById(family)`。
+5. 返回更新后的名称。
+
+无 DDL、幂等。
+
+## 4. 前端设计
+
+### 4.1 `utils/api.js`
+
+```js
+export const renameFamily = (data) => request('/api/family/member/rename-family', 'POST', data)
+```
+
+### 4.2 `family-members.vue`
+
+**状态(data)**:`showRenameModal: false`、`renameInput: ''`、`renameSaving: false`。
+
+**头部(line 4-12)**:家庭名称旁加编辑图标(✏️),`v-if="isAdmin"`,`@click="openRenameModal"`。
+
+```html
+<text class="family-name">{{ familyName || '我的家庭' }}</text>
+<text class="family-name-edit" v-if="isAdmin" @click="openRenameModal">✏️</text>
+```
+
+**方法**:
+
+- `openRenameModal()`:`this.renameInput = this.familyName || ''; this.showRenameModal = true`
+- `closeRenameModal()`:`this.showRenameModal = false`
+- `saveRename()`:
+  - 若 `renameSaving` 或 trim 后为空 → toast「家庭名称不能为空」并 return。
+  - `renameSaving = true`;调 `renameFamily({ name: this.renameInput.trim() })`。
+  - 成功(`res.code === 200`):`this.familyName = res.data`;关弹窗;toast「已更新」。
+  - 失败:toast `res.message`。
+  - finally:`renameSaving = false`。
+
+**弹窗模板**:复用现有 `.modal-mask` 弹窗风格(同 `showPermModal`),包含标题「编辑家庭名称」、输入框(`v-model="renameInput"`,`maxlength="20"`)、保存/取消按钮(保存按钮 `:loading="renameSaving"`)。
+
+**样式**:新增 `.family-name-edit`(名称旁小图标,可点击)与弹窗内输入框/按钮样式,复用 `$brand-primary` 等令牌。
+
+**小程序约束遵守**:不用可选链 `?.`;`:key` 用方法;不新增 `uni.getSystemInfoSync()`。
+
+## 5. 边界情况
+
+| 场景 | 行为 |
+|------|------|
+| 非管理员 | 前端无编辑图标;后端返回「仅家庭管理员可修改家庭名称」 |
+| 名称首尾空格 | 保存时 trim |
+| 空名称 | 前端 toast「家庭名称不能为空」;后端抛错 |
+| 名称 >20 字 | 输入框 `maxlength=20` 截断;后端抛错 |
+| 无家庭 | 后端返回「请先创建或加入家庭」 |
+| 当前显示兜底「我的家庭」 | 仍可编辑,保存后生效 |
+
+## 6. 验证
+
+- 后端:`mvn clean compile` 通过。
+- 前端:`node scripts/verify-vue-compile.js pages/profile-extra/family-members.vue` 三项 PASS。
+- 前端:`node scripts/audit-mp-antipatterns.js` ERROR 归零。
+- 不执行 HBuilderX 打包(按 AGENTS.md 规范,打包由用户在 HBuilderX 完成)。
+
+## 7. 接口文档同步
+
+在 `docs/superpowers/api/API_REFERENCE.md` 记录新接口 `POST /api/family/member/rename-family`。