docs:小程序编辑资料页性格标签与兴趣爱好库扩展设计

This commit is contained in:
2026-06-24 21:20:41 +08:00
parent 3cd7de1f55
commit 1cdee47f3a
@@ -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 真实错误,不静默成功。