diff --git a/docs/superpowers/specs/2026-06-03-user-profile-real-data-design.md b/docs/superpowers/specs/2026-06-03-user-profile-real-data-design.md new file mode 100644 index 0000000..9914b8f --- /dev/null +++ b/docs/superpowers/specs/2026-06-03-user-profile-real-data-design.md @@ -0,0 +1,210 @@ +# 小程序用户信息去写死 + 编辑资料保存生效 — 设计文档 + +## 背景 + +小程序"人生轨迹"页和"编辑资料"页存在大量硬编码的用户信息(昵称、星座、MBTI、职业、爱好、示例事件等)。即使用户已登录并设置了真实资料,页面仍展示写死的数据。同时,编辑资料页中的 `city`/`industry`/`company`/`personalityTags`/`birthday` 字段因后端 API 不支持而无法真正保存,导致"保存成功但回显丢失"。 + +## 目标 + +1. 人生轨迹页(`RecordView.vue`)必须展示当前登录用户的真实资料,去除一切写死数据。 +2. 编辑资料页(`onboarding/index.vue`)必须能成功保存、更新、回显用户信息。 +3. 后端扩展 `t_user_profile` 表和相关 DTO,支持前端已有的 `city`/`industry`/`company`/`personalityTags`/`birthday` 字段。 + +## 非目标 + +- 个人中心页(`MineView.vue`)的修复不在本次任务范围内(已有独立任务)。 +- 不修改 UI 视觉风格,只改数据绑定和默认值策略。 + +## 架构与数据流 + +``` +编辑资料页 (onboarding/index.vue) + ↓ form 数据 +前端 userProfileService.transformToBackendFormat + ↓ 新增 city/industry/company/personalityTags/birthday +后端 UserProfileController → UserProfileService + ↓ +数据库 t_user_profile (新增5个字段) + ↓ +后端返回 UserProfileResponse (含新增字段) + ↓ +前端 userProfileService.transformToFrontendFormat + ↓ store.userProfile +人生轨迹页 (RecordView.vue) / 编辑资料页回显 +``` + +## 后端改动 + +### 1. 数据库迁移 + +文件:`sql/emotion_museum_ddl.sql`(追加) + +```sql +-- 用户档案扩展字段:城市、行业、公司、性格标签、生日 +alter table emotion_museum.t_user_profile + add city varchar(100) null comment '所在城市', + add industry varchar(100) null comment '行业', + add company varchar(200) null comment '公司', + add personality_tags json null comment '性格标签列表', + add birthday date null comment '生日'; +``` + +### 2. 实体类扩展 + +文件:`backend-single/src/main/java/com/emotion/entity/UserProfile.java` + +新增字段(与数据库列名通过 `@TableField` 映射): + +| 字段名 | 类型 | 数据库列 | 说明 | +|--------|------|----------|------| +| city | String | city | 所在城市 | +| industry | String | industry | 行业 | +| company | String | company | 公司 | +| personalityTags | String | personality_tags | 性格标签 JSON 字符串 | +| birthday | LocalDate | birthday | 生日 | + +### 3. DTO 扩展 + +以下三个文件新增相同 5 个字段: + +- `backend-single/src/main/java/com/emotion/dto/request/userprofile/UserProfileCreateRequest.java` +- `backend-single/src/main/java/com/emotion/dto/request/userprofile/UserProfileUpdateRequest.java` +- `backend-single/src/main/java/com/emotion/dto/response/userprofile/UserProfileResponse.java` + +`UserProfileResponse` 中的 `birthday` 使用 `@JsonFormat(pattern = "yyyy-MM-dd")` 注解,与现有日期字段保持一致。 + +### 4. Service 层 + +文件:`backend-single/src/main/java/com/emotion/service/impl/UserProfileServiceImpl.java` + +无需改动。`BeanUtils.copyProperties` 和 `BeanUtil.copyProperties(..., CopyOptions.create().setIgnoreNullValue(true))` 会自动映射同名字段。 + +## 前端改动 + +### 1. 人生轨迹页(`mini-program/src/pages/main/RecordView.vue`) + +**移除示例事件:** +- 删除 `sampleEvents` 数组(4 条写死的人生事件数据)。 +- `events` computed 逻辑改为: + ```js + const events = computed(() => store.events || []) + ``` +- 空事件时保留现有空状态 UI("还没有人生轨迹")。 + +**移除硬编码默认值:** +| 原代码 | 修改后 | +|--------|--------| +| `profile.nickname \|\| 'Zoey'` | `profile.nickname \|\| ''` | +| `profile.zodiac \|\| '巨蟹座'` | `profile.zodiac \|\| ''` | +| `profile.mbti \|\| 'ENTJ'` | `profile.mbti \|\| ''` | +| `profile.profession \|\| '产品经理'` | `profile.profession \|\| ''` | +| `heroTags` 默认 `['阅读', '旅行', '音乐', '创作']` | `[]` | +| `avatar` 计算中的默认 `'Zoey'` | 保留 `'User'` 作为 Dicebear seed 的兜底(这是头像生成种子,非展示名称) | + +**空值 UI 处理:** +- 昵称空时,profile-info 区域不显示名称,但保留"正在成为更清晰的自己"作为固定标语(非用户数据)。 +- 星座/MBTI/职业空时,对应 meta-item 的 `meta-value` 显示 `"未设置"`。 +- 爱好空时,`hobby-text` 显示 `"未设置"`。 + +### 2. 编辑资料页(`mini-program/src/pages/onboarding/index.vue`) + +**`syncFromStore()` 去除硬编码默认值:** + +原逻辑在 `source` 为空时回退到大量硬编码值,改为全部回退到空值/空数组: + +| 字段 | 原默认值 | 新默认值 | +|------|----------|----------| +| nickname | `''` | `''`(不变) | +| gender | `'女'` | `''` | +| zodiac | `'巨蟹座'` | `''` | +| mbti | `'ENTJ'` | `''` | +| profession | `'产品经理'` | `''` | +| city | `'上海市'` | `''` | +| industry | `'互联网'` | `''` | +| company | `''` | `''`(不变) | +| personalityTags | `['理性', '乐观', ...]` | `[]` | +| hobbies | `['阅读', '旅行', '音乐']` | `[]` | +| future.ideal | 长文本默认 | `''` | +| birthday | `'1998-06-18'` | `''` | + +**空值显示优化:** +- `birthdayDisplay` computed:空值时返回 `"请选择生日"`。 +- `avatarUrl`:空昵称时保留默认 Dicebear 头像(seed 用 `"User"`),仅作为头像生成参数,不展示给用户。 + +**保存逻辑:** +- `saveProfile` 逻辑不变,继续调用 `store.updateRegistration()` 和 `store.saveUserProfile()`。 +- 由于后端已扩展字段,所有字段均可正确保存。 + +### 3. 前端 Service 扩展(`mini-program/src/services/userProfile.js`) + +**`transformToBackendFormat` 增加:** +```js +return { + // ... 原有字段 ... + city: frontendData.city || null, + industry: frontendData.industry || null, + company: frontendData.company || null, + personalityTags: Array.isArray(frontendData.personalityTags) + ? JSON.stringify(frontendData.personalityTags) + : frontendData.personalityTags, + birthday: frontendData.birthday || null +} +``` + +**`transformToFrontendFormat` 增加:** +```js +return { + // ... 原有字段 ... + city: backendData.city || '', + industry: backendData.industry || '', + company: backendData.company || '', + personalityTags: backendData.personalityTags + ? (typeof backendData.personalityTags === 'string' + ? JSON.parse(backendData.personalityTags) + : backendData.personalityTags) + : [], + birthday: backendData.birthday || '' +} +``` + +### 4. Store 扩展(`mini-program/src/stores/app.js`) + +`registrationData` 初始结构增加: +```js +registrationData: { + // ... 原有字段 ... + city: '', + industry: '', + company: '', + personalityTags: [], + birthday: '' +} +``` + +## 文件变更清单 + +| 文件 | 改动类型 | 说明 | +|------|----------|------| +| `sql/emotion_museum_ddl.sql` | 追加 | 5 个 ALTER TABLE 语句 | +| `backend-single/.../entity/UserProfile.java` | 修改 | 新增 5 个字段 | +| `backend-single/.../request/.../UserProfileCreateRequest.java` | 修改 | 新增 5 个字段 | +| `backend-single/.../request/.../UserProfileUpdateRequest.java` | 修改 | 新增 5 个字段 | +| `backend-single/.../response/.../UserProfileResponse.java` | 修改 | 新增 5 个字段 | +| `mini-program/src/services/userProfile.js` | 修改 | 双向转换增加 5 个字段 | +| `mini-program/src/stores/app.js` | 修改 | `registrationData` 扩展 | +| `mini-program/src/pages/main/RecordView.vue` | 修改 | 去除写死默认值和 sampleEvents | +| `mini-program/src/pages/onboarding/index.vue` | 修改 | 去除硬编码默认值,优化空值展示 | + +## 错误处理 + +- **保存失败**:`saveProfile` 已有 `uni.showToast({ title: res.error || '保存失败' })`,无需额外改动。 +- **后端字段新增后老数据兼容**:新增字段均为 `NULL`,老数据读取时前端 `transformToFrontendFormat` 会转为空字符串/空数组,不影响现有逻辑。 +- **数据库列已存在**:ALTER TABLE 添加已存在列会报错。本次为全新列名,安全。若生产环境已存在(极小概率),需人工确认。 + +## 测试验证点 + +1. 新用户首次编辑资料,所有字段留空,保存成功,回显为空。 +2. 填写城市、行业、公司、性格标签、生日后保存,重新进入编辑页,数据正确回显。 +3. 人生轨迹页未设置资料时,用户信息和爱好显示"未设置"或留空,不展示任何写死数据。 +4. 人生轨迹页无事件时,显示"还没有人生轨迹"空状态,不展示 sampleEvents。 +5. 已有用户(老数据)登录后,页面正常展示,无报错。