From 1cdee47f3a98cd99ec6c6200ab52bd82bd008be5 Mon Sep 17 00:00:00 2001 From: Peanut Date: Wed, 24 Jun 2026 21:20:41 +0800 Subject: [PATCH] =?UTF-8?q?docs=EF=BC=9A=E5=B0=8F=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E8=B5=84=E6=96=99=E9=A1=B5=E6=80=A7=E6=A0=BC?= =?UTF-8?q?=E6=A0=87=E7=AD=BE=E4=B8=8E=E5=85=B4=E8=B6=A3=E7=88=B1=E5=A5=BD?= =?UTF-8?q?=E5=BA=93=E6=89=A9=E5=B1=95=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...26-06-24-personality-tag-library-design.md | 240 ++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-24-personality-tag-library-design.md diff --git a/docs/superpowers/specs/2026-06-24-personality-tag-library-design.md b/docs/superpowers/specs/2026-06-24-personality-tag-library-design.md new file mode 100644 index 0000000..10518b1 --- /dev/null +++ b/docs/superpowers/specs/2026-06-24-personality-tag-library-design.md @@ -0,0 +1,240 @@ +--- +author: Peanut +created_at: 2026-06-24 +purpose: 明确小程序编辑资料页"性格标签"与"兴趣爱好"两个模块的扩展设计:支持用户新增自定义标签、统一管理标签库、删除标签,并保证编辑页标签容器自适应撑开展示所有标签。 +--- + +# 小程序编辑资料页:性格标签 / 兴趣爱好 标签库扩展设计 + +## 背景 + +当前小程序的编辑资料页面(`mini-program/src/pages/onboarding/index.vue`)已具备性格标签和兴趣爱好的基础选择功能: + +- 性格标签预设 11 个(理性、感性、乐观、独立、有创造力、坚韧、细腻、好奇、内敛、冒险、自由)。 +- 兴趣爱好预设 10 个(阅读、旅行、音乐、写作、摄影、电影、运动、绘画、咖啡、游戏)。 +- 兴趣爱好模块支持通过弹窗输入自定义标签。 +- 性格标签模块 UI 上已有"+ 添加标签"占位,但未绑定事件。 +- 两个模块均不支持删除标签,也没有统一的管理入口。 +- 后端 `UserProfile` 实体只保存"用户已选中的标签"两个 JSON 字段(`personality_tags` / `hobbies`),不区分预设与自定义,也没有完整标签库的概念。 + +用户新增需求:性格标签和兴趣爱好两个模块均支持**用户自由新增自定义标签、保存、回显、删除**;编辑资料页的标签容器要**自适应撑开**,保证所有标签都能展示;**新增的标签展示在最前面**。 + +## 目标 + +1. 性格标签和兴趣爱好两个模块支持用户新增自定义标签、保存、回显、删除。 +2. 新增的自定义标签展示在列表最前面。 +3. 编辑资料页标签容器自适应撑开,不限行数,展示所有标签。 +4. 提供独立的"管理标签"页面,支持统一删除(含预设与自定义)。 +5. 老用户数据平滑迁移,不破坏现有已选标签。 +6. 输入校验完整,避免重复、空值、超长。 + +## 非目标 + +- 不引入标签的分类、排序、分组等高级管理能力。 +- 不做标签的全局共享、推荐、热门度等运营功能。 +- 不修改后端存储架构(继续用 JSON 字段,不拆子表)。 +- 不在编辑资料页做长按手势删除,统一收敛到管理页。 + +## 关键设计决策 + +| 决策点 | 选择 | 理由 | +|---|---|---| +| 新增标签输入方式 | `uni.showModal` 弹窗输入 | 与现有"自定义兴趣"交互保持一致,改动成本最低 | +| 删除入口 | 独立的"管理标签"页面 | 统一删除操作入口,避免编辑页交互过载;预设与自定义标签都能被删除 | +| 删除后的可见性 | 删除后从当前用户账号彻底移除 | 用户希望不再看到的标签就不会再出现 | +| 数据存储 | `UserProfile` 新增 `personality_tag_library` / `hobby_library` 两个 JSON 字段 | 后端需区分"用户已选中"与"用户可见的完整标签库",原有字段语义保留不变 | +| 预设 vs 自定义标签 | 前端维护一份内置预设数组;库里存的是用户专属合并后的字符串数组 | 预设标签库存在代码中便于维护,用户库字段只关心"当前账号可见的全部标签" | +| 编辑页标签顺序 | 新增的自定义标签 → 已选中的其他标签 → 其余未选中标签 | 严格保证"新增的标签展示在最前面",其他已选中标签紧随其后 | + +## 后端改动 + +### 实体与数据库 + +`backend-single` 模块: + +- `UserProfile` 实体新增两个字段: + +| 字段名 | Java 类型 | 数据库列 | 类型 | 说明 | +|---|---|---|---|---| +| `personalityTagLibrary` | `String` | `personality_tag_library` | `TEXT` | 用户的完整性格标签库,JSON 字符串(字符串数组) | +| `hobbyLibrary` | `String` | `hobby_library` | `TEXT` | 用户的完整兴趣爱好库,JSON 字符串(字符串数组) | + +- 原 `personalityTags` / `hobbies` 字段保持不变,继续表示"用户已选中的标签"。 +- 新增数据库迁移 SQL: + +```sql +ALTER TABLE user_profile + ADD COLUMN personality_tag_library TEXT DEFAULT NULL COMMENT '用户完整性格标签库(JSON数组)', + ADD COLUMN hobby_library TEXT DEFAULT NULL COMMENT '用户完整兴趣爱好库(JSON数组)'; +``` + +### 接口变更 + +`UserProfileController` / `UserProfileService` 无需新增接口,复用现有: + +- `PUT /user-profile/update`:入参 DTO 增加 `personalityTagLibrary` / `hobbyLibrary`(均为 `String`),直接持久化。 +- `GET /user-profile/me`:返回体增加上述两个字段。 +- `POST /user-profile/create`:同上。 + +### 数据兼容性 + +- 老用户(`personalityTagLibrary` 为空或 `null`)查询接口返回 `null`,由前端做兜底初始化。 +- 后端不做兜底填充,严格遵守"没有就是没有"的禁止 mock 规则。 + +## 前端改动 + +### 数据层(`stores/app.js` 与 `services/userProfile.js`) + +- 请求返回的 `userProfile` 新增 `personalityTagLibrary` / `hobbyLibrary` 字段解析(JSON.parse)。 +- `saveUserProfile` 提交时把两个库字段 `JSON.stringify` 后一并提交。 + +### 编辑资料页(`mini-program/src/pages/onboarding/index.vue`) + +#### 标签区结构(性格标签与兴趣爱好同构) + +``` +┌──────────────────────────────────────┐ +│ 😊 性格标签 管理 > │ ← 标题行,右侧"管理"按钮跳转管理页 +│ (最多选择5个) │ +│ ┌────┐┌────┐┌────┐┌────┐ │ +│ │ 理性 ││ 感性 ││ 乐观 ││ 独立 │... │ ← 标签网格,flex-wrap 自适应 +│ └────┘└────┘└────┘└────┘ │ +│ ┌───────┐ │ +│ │ + 添加 │ │ ← 末尾占位,点击打开弹窗 +│ └───────┘ │ +└──────────────────────────────────────┘ +``` + +- 容器使用 `display: flex; flex-wrap: wrap;`,**高度随内容自适应撑开**。 +- 标签顺序规则(严格保证"新增的标签展示在最前面"): + 1. **新增的自定义标签**排在最前面(按创建时间倒序)。 + 2. **已选中的其他标签**(含预设与早期自定义)紧随其后。 + 3. **其余未选中的预设标签**排在最后。 +- 样式沿用现有 `.tag-choice` / `.tag-choice.active`。 +- 点击标签:沿用 `toggleList(list, tag, 5)` 逻辑,超限 toast 提示。 + +#### 新增标签弹窗 + +- 点击"+ 添加标签"触发 `uni.showModal`,`editable: true`,占位符"请输入标签"。 +- 校验规则: + - 非空、去除首尾空格。 + - 长度 ≤ 8 个字符。 + - 不能和当前库中任何标签完全相等(字符串完全一致比对)。 +- 校验失败通过 `uni.showToast` 提示原因,不静默忽略。 +- 校验通过后: + 1. 新标签**插入到对应库数组的最前面**。 + 2. 若已选未满 5 个,自动加入已选数组;满则 toast 提示"已达上限,请先进入管理页删除其他标签",仍把新标签加入库,但不加入已选。 + +#### 保存流程 + +- 点击"保存"按钮时,把以下数据一起提交: + - `personalityTags`(已选中数组) + - `personalityTagLibrary`(完整库数组) + - `hobbies`(已选中数组) + - `hobbyLibrary`(完整库数组) +- 保存失败:toast 提示后端返回的真实错误信息,页面状态保留不变。 +- 保存成功:toast 提示"已保存",350ms 后 `uni.navigateBack()`。 + +### 管理页(新增独立页面) + +路径:`mini-program/src/pages/onboarding/tag-manage.vue`。 +通过 `pages.json` 注册路由:`/pages/onboarding/tag-manage`。 + +#### 入口与参数 + +- 编辑资料页的"管理"按钮跳转:`/pages/onboarding/tag-manage?type=personality` 或 `?type=hobby`。 +- 页面标题:根据 type 显示"管理性格标签"或"管理兴趣爱好"。 + +#### 页面结构 + +- 列表渲染当前库中全部标签(含剩余预设 + 自定义)。 +- 每行结构: + +``` +┌────────────────────────────────────┐ +│ 理性 × │ +├────────────────────────────────────┤ +│ 感性 × │ +├────────────────────────────────────┤ +│ ... │ +└────────────────────────────────────┘ +``` + +- **左滑删除**:使用 UniApp 的 `uni-swipe-action` 组件或自定义实现,向右滑动显示"删除"按钮。 +- **点击 × 按钮**:同样触发删除。 +- 删除前 `uni.showModal` 二次确认,提示"确定删除该标签吗?删除后不可恢复"。 +- 删除后: + - 从库数组中移除该标签。 + - 若该标签同时在已选数组中,一并移除。 + - 页面列表立即刷新。 +- 删除接口调用:删除动作本身不单独调用后端,仅在用户返回编辑资料页点击"保存"时统一提交。 + +#### 返回与数据同步 + +- 管理页的库数据存储在页面局部响应式变量中。 +- 返回编辑资料页时,通过 `onUnload` 生命周期或全局 store 的 `updateRegistration` 把最新库数据回传给编辑资料页。 +- 推荐方案:使用 `uni.$emit('tag-library-updated', { type, library })` 事件,编辑资料页在 `onShow` 中监听并合并。 + +## 数据初始化兼容方案 + +编辑资料页在 `onLoad` / `onShow` 时执行: + +``` +if (后端返回的 personalityTagLibrary 为空) { + 库 = [...内置预设数组] + if (后端返回的 personalityTags 非空) { + // 把已选中的标签也合入库(老用户首次编辑时保留) + 库 = 去重合并(库, 已选数组) + } +} +``` + +兴趣爱好同理。此逻辑保证老用户数据不丢失,新用户直接拿预设库。 + +## 错误处理 + +- 接口请求失败:按现有 `request.js` 约定,`try/catch` 返回 `{ success: false, error: 真实错误 }`,页面 toast 提示真实错误信息。**禁止本地 mock 成功响应、禁止兜底默认值**。 +- 管理页删除:若保存时接口失败,本地已删除的标签状态**回滚**(恢复删除前的库内容),toast 提示失败原因。 +- 网络超时:按 `request.js` 的现有超时策略处理。 + +## 文件清单 + +### 新增文件 + +- `mini-program/src/pages/onboarding/tag-manage.vue`:管理标签独立页面。 + +### 修改文件 + +**后端(`backend-single/src/main/java/com/emotion/`):** + +- `entity/UserProfile.java`:新增 `personalityTagLibrary`、`hobbyLibrary` 两个 `String` 字段。 +- `controller/UserProfileController.java`:无需新增接口,入参 DTO 通过 `UserProfileUpdateRequest` / `UserProfileCreateRequest` 已支持新字段透传。 +- `dto/request/userprofile/UserProfileUpdateRequest.java`:新增 `personalityTagLibrary`、`hobbyLibrary` 两个 `String` 字段。 +- `dto/request/userprofile/UserProfileCreateRequest.java`:同上。 +- `dto/response/userprofile/UserProfileResponse.java`:新增 `personalityTagLibrary`、`hobbyLibrary` 两个 `String` 字段。 +- `service/UserProfileService.java` 与 `service/impl/UserProfileServiceImpl.java`:无业务逻辑改动,字段通过 MyBatis-Plus 自动映射持久化。 +- 数据库迁移脚本:`backend-single/src/main/resources/db/migration/2026-06-24-user-profile-tag-library.sql`,遵循现有 `YYYY-MM-DD-描述.sql` 命名约定。 + +**前端(`mini-program/`):** + +- `src/pages/onboarding/index.vue`:重构标签区,新增管理入口、弹窗新增逻辑、自适应样式。 +- `src/services/userProfile.js`:请求体与响应解析增加两个库字段。 +- `src/stores/app.js`:`saveUserProfile` 提交时携带库字段。 +- `src/pages.json`:注册 `tag-manage` 路由。 + +**不修改的文件:** + +- `src/pages/main/MineView.vue`:展示区只读"已选中的标签"(沿用 `hobbies` 字段),不需要感知完整库,**无需修改**。 + +## 验收标准 + +1. 进入编辑资料页,性格标签、兴趣爱好两个区域的标签容器高度随内容自适应撑开,无截断。 +2. 点击"+ 添加标签"或"+ 自定义兴趣",弹窗输入后,新标签出现在列表最前面。 +3. 重复输入同名标签,toast 提示"标签已存在"。 +4. 空输入、超长输入(>8 字符)分别 toast 提示对应原因。 +5. 点击"管理"按钮,跳转到管理页,列表展示全部标签(预设 + 自定义)。 +6. 管理页左滑或点 × 删除标签,二次确认后标签从列表消失。 +7. 删除已在"已选"中的标签,返回编辑资料页后已选列表同步移除。 +8. 保存后重新进入编辑资料页,新增的标签仍展示在最前面。 +9. 老用户(无新字段)首次进入编辑资料页,已选标签保留,预设标签可见。 +10. 接口失败时,页面保留原状态并 toast 真实错误,不静默成功。