Files
happy-life-star/docs/superpowers/plans/2026-06-03-user-profile-real-data.md
T

693 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 小程序用户信息去写死 + 编辑资料保存生效 — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 去除小程序人生轨迹页和编辑资料页的所有硬编码用户信息,扩展后端 `t_user_profile` 表支持 `city`/`industry`/`company`/`personalityTags`/`birthday` 字段,确保编辑资料能完整保存和回显。
**Architecture:** 后端通过数据库 ALTER + Java 实体/DTO 扩展新增5个字段;前端同步扩展 Service 转换函数和 Store 数据结构,并移除两个页面中所有 `|| '硬编码默认值'` 回退和示例事件数据。
**Tech Stack:** Java 17 / Spring Boot / MyBatis-Plus / MySQLuni-app (Vue 3) / JavaScript
---
## File Structure
| 文件 | 改动 | 职责 |
|------|------|------|
| `sql/emotion_museum_ddl.sql` | 追加 | 数据库迁移:新增5列 |
| `server/.../entity/UserProfile.java` | 修改 | ORM 实体新增字段 |
| `server/.../request/UserProfileCreateRequest.java` | 修改 | 创建请求 DTO 新增字段 |
| `server/.../request/UserProfileUpdateRequest.java` | 修改 | 更新请求 DTO 新增字段 |
| `server/.../response/UserProfileResponse.java` | 修改 | 响应 DTO 新增字段 |
| `mini-program/src/services/userProfile.js` | 修改 | 前后端数据格式双向转换 |
| `mini-program/src/stores/app.js` | 修改 | 全局状态 registrationData 扩展 |
| `mini-program/src/pages/main/RecordView.vue` | 修改 | 去除写死默认值和 sampleEvents |
| `mini-program/src/pages/onboarding/index.vue` | 修改 | 去除硬编码默认值,空值友好展示 |
---
### Task 1: 数据库迁移 — 新增5个字段
**Files:**
- Modify: `sql/emotion_museum_ddl.sql`
- [ ] **Step 1: 追加 ALTER TABLE 语句**
`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 '生日';
```
- [ ] **Step 2: Commit**
```bash
git add sql/emotion_museum_ddl.sql
git commit -m "db: add city, industry, company, personality_tags, birthday to t_user_profile"
```
---
### Task 2: 后端实体扩展 — UserProfile.java
**Files:**
- Modify: `server/src/main/java/com/emotion/entity/UserProfile.java`
- [ ] **Step 1: 在 idealLife 字段后新增5个字段**
找到 `idealLife` 字段定义(约第115行),在其后、`scripts` 字段前插入:
```java
/**
* 所在城市
*/
@TableField("city")
private String city;
/**
* 行业
*/
@TableField("industry")
private String industry;
/**
* 公司
*/
@TableField("company")
private String company;
/**
* 性格标签 (JSON字符串)
*/
@TableField("personality_tags")
private String personalityTags;
/**
* 生日
*/
@TableField("birthday")
private LocalDate birthday;
```
注意:`LocalDate` 的 import 已在文件顶部存在(第12行),无需新增 import。
- [ ] **Step 2: Commit**
```bash
git add server/src/main/java/com/emotion/entity/UserProfile.java
git commit -m "feat: extend UserProfile entity with city/industry/company/personalityTags/birthday"
```
---
### Task 3: 后端创建请求 DTO 扩展 — UserProfileCreateRequest.java
**Files:**
- Modify: `server/src/main/java/com/emotion/dto/request/userprofile/UserProfileCreateRequest.java`
- [ ] **Step 1: 在 idealLife 字段后新增5个字段**
找到 `idealLife` 字段定义(约第86行),在其后、`scripts` 字段前插入:
```java
/**
* 所在城市
*/
private String city;
/**
* 行业
*/
private String industry;
/**
* 公司
*/
private String company;
/**
* 性格标签 (JSON字符串)
*/
private String personalityTags;
/**
* 生日
*/
private LocalDate birthday;
```
- [ ] **Step 2: Commit**
```bash
git add server/src/main/java/com/emotion/dto/request/userprofile/UserProfileCreateRequest.java
git commit -m "feat: extend UserProfileCreateRequest with city/industry/company/personalityTags/birthday"
```
---
### Task 4: 后端更新请求 DTO 扩展 — UserProfileUpdateRequest.java
**Files:**
- Modify: `server/src/main/java/com/emotion/dto/request/userprofile/UserProfileUpdateRequest.java`
- [ ] **Step 1: 在 idealLife 字段后新增5个字段**
找到 `idealLife` 字段定义(约第90行),在其后、`scripts` 字段前插入:
```java
/**
* 所在城市
*/
private String city;
/**
* 行业
*/
private String industry;
/**
* 公司
*/
private String company;
/**
* 性格标签 (JSON字符串)
*/
private String personalityTags;
/**
* 生日
*/
private LocalDate birthday;
```
- [ ] **Step 2: Commit**
```bash
git add server/src/main/java/com/emotion/dto/request/userprofile/UserProfileUpdateRequest.java
git commit -m "feat: extend UserProfileUpdateRequest with city/industry/company/personalityTags/birthday"
```
---
### Task 5: 后端响应 DTO 扩展 — UserProfileResponse.java
**Files:**
- Modify: `server/src/main/java/com/emotion/dto/response/userprofile/UserProfileResponse.java`
- [ ] **Step 1: 在 idealLife 字段后新增5个字段**
找到 `idealLife` 字段定义(约第105行),在其后、`scripts` 字段前插入:
```java
/**
* 所在城市
*/
private String city;
/**
* 行业
*/
private String industry;
/**
* 公司
*/
private String company;
/**
* 性格标签 (JSON字符串)
*/
private String personalityTags;
/**
* 生日
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate birthday;
```
- [ ] **Step 2: Commit**
```bash
git add server/src/main/java/com/emotion/dto/response/userprofile/UserProfileResponse.java
git commit -m "feat: extend UserProfileResponse with city/industry/company/personalityTags/birthday"
```
---
### Task 6: 前端 Service 扩展 — userProfile.js
**Files:**
- Modify: `mini-program/src/services/userProfile.js`
- [ ] **Step 1: 扩展 transformToBackendFormat 的解构和返回对象**
原解构(第47-59行)新增5个字段:
```js
const {
id,
nickname,
gender,
zodiac,
profession,
mbti,
hobbies,
childhood,
joy,
low,
future,
city,
industry,
company,
personalityTags,
birthday
} = frontendData
```
原返回对象(第61-83行)在 `idealLife` 后新增:
```js
return {
id,
nickname,
gender,
zodiac,
profession,
mbti,
// 兴趣爱好转为JSON字符串
hobbies: Array.isArray(hobbies) ? JSON.stringify(hobbies) : hobbies,
// 童年经历
childhoodDate: childhood?.date || null,
childhoodContent: childhood?.text || null,
// 高光时刻(对应前端的joy
peakDate: joy?.date || null,
peakContent: joy?.text || null,
// 低谷时期(对应前端的low
valleyDate: low?.date || null,
valleyContent: low?.text || null,
// 未来期许
futureVision: future?.vision || null,
// 理想生活状态
idealLife: future?.ideal || null,
// 新增字段
city: city || null,
industry: industry || null,
company: company || null,
personalityTags: Array.isArray(personalityTags) ? JSON.stringify(personalityTags) : personalityTags,
birthday: birthday || null
}
```
- [ ] **Step 2: 扩展 transformToFrontendFormat 的解构和返回对象**
原解构(第94-111行)新增5个字段:
```js
const {
id,
userId,
nickname,
gender,
zodiac,
profession,
mbti,
hobbies,
childhoodDate,
childhoodContent,
peakDate,
peakContent,
valleyDate,
valleyContent,
futureVision,
idealLife,
city,
industry,
company,
personalityTags,
birthday
} = backendData
```
原返回对象(第113-143行)在 `future` 对象后新增:
```js
return {
id,
userId,
nickname: nickname || '',
gender: gender || '',
zodiac: zodiac || '',
profession: profession || '',
mbti: mbti || '',
// 兴趣爱好从JSON字符串解析
hobbies: hobbies ? (typeof hobbies === 'string' ? JSON.parse(hobbies) : hobbies) : [],
// 童年经历
childhood: {
date: childhoodDate || '',
text: childhoodContent || ''
},
// 高光时刻
joy: {
date: peakDate || '',
text: peakContent || ''
},
// 低谷时期
low: {
date: valleyDate || '',
text: valleyContent || ''
},
// 未来期许
future: {
vision: futureVision || '',
ideal: idealLife || ''
},
// 新增字段
city: city || '',
industry: industry || '',
company: company || '',
personalityTags: personalityTags ? (typeof personalityTags === 'string' ? JSON.parse(personalityTags) : personalityTags) : [],
birthday: birthday || ''
}
```
- [ ] **Step 3: Commit**
```bash
git add mini-program/src/services/userProfile.js
git commit -m "feat: add city/industry/company/personalityTags/birthday to userProfile transforms"
```
---
### Task 7: 前端 Store 扩展 — app.js
**Files:**
- Modify: `mini-program/src/stores/app.js`
- [ ] **Step 1: 扩展 registrationData 初始结构**
找到 `registrationData` 定义(第19-30行),在 `future` 对象后新增:
```js
registrationData: {
nickname: '',
gender: '',
mbti: '',
zodiac: '',
profession: '',
hobbies: [],
childhood: { date: '', text: '' },
joy: { date: '', text: '' },
low: { date: '', text: '' },
future: { vision: '', ideal: '' },
city: '',
industry: '',
company: '',
personalityTags: [],
birthday: ''
}
```
注意:此文件使用 CRLF 行尾(`\r\n`),使用 Edit 工具时请按 Read 工具输出的格式匹配。
- [ ] **Step 2: Commit**
```bash
git add mini-program/src/stores/app.js
git commit -m "feat: extend registrationData with city/industry/company/personalityTags/birthday"
```
---
### Task 8: 人生轨迹页去写死 — RecordView.vue
**Files:**
- Modify: `mini-program/src/pages/main/RecordView.vue`
- [ ] **Step 1: 删除 sampleEvents 数组**
删除第143-176行的 `sampleEvents` 数组定义。
- [ ] **Step 2: 修改 events computed 去除 sampleEvents 回退**
将第179-182行:
```js
const events = computed(() => {
const list = store.events || []
return list.length ? list : sampleEvents
})
```
改为:
```js
const events = computed(() => store.events || [])
```
- [ ] **Step 3: 修改 heroTags computed 去除硬编码默认值**
将第183-187行:
```js
const heroTags = computed(() => {
const hobbies = profile.value.hobbies
if (Array.isArray(hobbies) && hobbies.length) return hobbies.slice(0, 4)
return ['阅读', '旅行', '音乐', '创作']
})
```
改为:
```js
const heroTags = computed(() => {
const hobbies = profile.value.hobbies
if (Array.isArray(hobbies) && hobbies.length) return hobbies.slice(0, 4)
return []
})
```
- [ ] **Step 4: 修改 avatar computed 去除硬编码昵称**
将第189-192行:
```js
const avatar = computed(() => {
const nickname = profile.value.nickname || 'Zoey'
return `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(nickname)}&backgroundColor=b982ff`
})
```
改为:
```js
const avatar = computed(() => {
const nickname = profile.value.nickname || 'User'
return `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(nickname)}&backgroundColor=b982ff`
})
```
- [ ] **Step 5: 修改模板中硬编码默认值**
将第13行:
```vue
<text class="profile-name">{{ profile.nickname || 'Zoey' }}</text>
```
改为:
```vue
<text class="profile-name">{{ profile.nickname || '未设置' }}</text>
```
将第31行:
```vue
<text class="meta-value">{{ profile.zodiac || '巨蟹座' }}</text>
```
改为:
```vue
<text class="meta-value">{{ profile.zodiac || '未设置' }}</text>
```
将第38行:
```vue
<text class="meta-value">{{ profile.mbti || 'ENTJ' }}</text>
```
改为:
```vue
<text class="meta-value">{{ profile.mbti || '未设置' }}</text>
```
将第45行:
```vue
<text class="meta-value">{{ profile.profession || '产品经理' }}</text>
```
改为:
```vue
<text class="meta-value">{{ profile.profession || '未设置' }}</text>
```
将第56行:
```vue
<text class="hobby-text">{{ heroTags.join(' · ') }}</text>
```
改为:
```vue
<text class="hobby-text">{{ heroTags.length ? heroTags.join(' · ') : '未设置' }}</text>
```
- [ ] **Step 6: Commit**
```bash
git add mini-program/src/pages/main/RecordView.vue
git commit -m "fix: remove hardcoded defaults and sampleEvents from RecordView"
```
---
### Task 9: 编辑资料页去写死 — onboarding/index.vue
**Files:**
- Modify: `mini-program/src/pages/onboarding/index.vue`
- [ ] **Step 1: 修改 avatarUrl computed 去除硬编码昵称**
将第234-238行:
```js
const avatarUrl = computed(() => {
if (avatarLocal.value) return avatarLocal.value
const nickname = form.nickname || 'Zoey'
return `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(nickname)}&backgroundColor=b982ff`
})
```
改为:
```js
const avatarUrl = computed(() => {
if (avatarLocal.value) return avatarLocal.value
const nickname = form.nickname || 'User'
return `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(nickname)}&backgroundColor=b982ff`
})
```
- [ ] **Step 2: 修改 birthdayDisplay computed 去除硬编码日期**
将第240-244行:
```js
const birthdayDisplay = computed(() => {
if (!birthday.value) return '1998年06月18日'
const [year, month, day] = birthday.value.split('-')
return `${year}${month}${day}`
})
```
改为:
```js
const birthdayDisplay = computed(() => {
if (!birthday.value) return '请选择生日'
const [year, month, day] = birthday.value.split('-')
return `${year}${month}${day}`
})
```
- [ ] **Step 3: 修改 syncFromStore 去除所有硬编码默认值**
将第246-265行的 `syncFromStore` 函数整体替换为:
```js
const syncFromStore = () => {
const source = store.userProfile || store.registrationData || {}
Object.assign(form, {
nickname: source.nickname || '',
gender: source.gender || '',
zodiac: source.zodiac || '',
mbti: source.mbti || '',
profession: source.profession || '',
city: source.city || '',
industry: source.industry || '',
company: source.company || '',
personalityTags: Array.isArray(source.personalityTags) ? [...source.personalityTags] : [],
hobbies: Array.isArray(source.hobbies) ? [...source.hobbies] : [],
childhood: { date: source.childhood?.date || '', text: source.childhood?.text || '' },
joy: { date: source.joy?.date || '', text: source.joy?.text || '' },
low: { date: source.low?.date || '', text: source.low?.text || '' },
future: { vision: source.future?.vision || '', ideal: source.future?.ideal || '' }
})
birthday.value = source.birthday || ''
}
```
注意:此文件使用 CRLF 行尾(`\r\n`),使用 Edit 工具时请按 Read 工具输出的格式匹配。
- [ ] **Step 4: Commit**
```bash
git add mini-program/src/pages/onboarding/index.vue
git commit -m "fix: remove hardcoded defaults from profile edit page"
```
---
### Task 10: 验证测试
**Files:**
- 无需修改,纯验证
- [ ] **Step 1: 后端编译验证**
```bash
cd server && mvn compile -q
```
预期:编译成功,无错误。
- [ ] **Step 2: 前端语法检查(如有 linter)**
```bash
cd mini-program && npm run lint 2>/dev/null || echo "No lint script, skipping"
```
- [ ] **Step 3: 手动功能验证清单**
1. 启动后端服务,确认无启动错误。
2. 在数据库执行 `DESCRIBE t_user_profile;`,确认 `city``industry``company``personality_tags``birthday` 五列已存在。
3. 小程序登录后进入"人生轨迹"页:
- 未设置资料时,昵称显示"未设置",星座/MBTI/职业显示"未设置",爱好显示"未设置"。
- 无人生事件时,显示"还没有人生轨迹"空状态,无 sampleEvents。
4. 点击"编辑资料"
- 首次进入(无资料)时,所有输入框为空,生日显示"请选择生日",性格标签和爱好无选中项。
- 填写所有字段(包括城市、行业、公司、性格标签、生日)后保存。
- 保存成功后返回人生轨迹页,数据已更新。
- 再次进入编辑资料页,所有字段正确回显。
5. 已有用户(老数据,无新字段)登录后,页面正常展示,无报错,新字段显示为空。
- [ ] **Step 4: Commit(如测试通过)**
```bash
git log --oneline -10
```
预期看到 9 个 commitsTask 1-9 各一个)。
---
## Self-Review
**1. Spec coverage:**
- 数据库扩展5个字段 → Task 1 ✅
- 后端实体扩展 → Task 2 ✅
- 后端 DTO 扩展(Create/Update/Response)→ Task 3/4/5 ✅
- 前端 Service 双向转换扩展 → Task 6 ✅
- 前端 Store 扩展 → Task 7 ✅
- 人生轨迹页去除写死数据和 sampleEvents → Task 8 ✅
- 编辑资料页去除硬编码默认值 → Task 9 ✅
- 测试验证 → Task 10 ✅
**2. Placeholder scan:**
- 无 TBD/TODO/"implement later"/"fill in details" ✅
- 无 "Add appropriate error handling" 等模糊描述 ✅
- 无 "Similar to Task N" ✅
- 每个代码步骤都包含完整代码 ✅
**3. Type consistency:**
- 后端5个字段名在 Entity/CreateRequest/UpdateRequest/Response 中完全一致:`city`, `industry`, `company`, `personalityTags`, `birthday`
- 前端 Service 中 `transformToBackendFormat``transformToFrontendFormat` 字段名一致 ✅
- Store 中 `registrationData` 字段名与 Service 一致 ✅
- `personalityTags` 在前端始终作为数组处理,后端作为 JSON 字符串,转换逻辑与 `hobbies` 保持一致 ✅
- `birthday` 前后端均为 `yyyy-MM-dd` 字符串格式 ✅