|
|
@@ -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`。
|