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

18 KiB
Raw Blame History

小程序用户信息去写死 + 编辑资料保存生效 — 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列
backend-single/.../entity/UserProfile.java 修改 ORM 实体新增字段
backend-single/.../request/UserProfileCreateRequest.java 修改 创建请求 DTO 新增字段
backend-single/.../request/UserProfileUpdateRequest.java 修改 更新请求 DTO 新增字段
backend-single/.../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 文件末尾追加:

-- 用户档案扩展字段:城市、行业、公司、性格标签、生日
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
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: backend-single/src/main/java/com/emotion/entity/UserProfile.java

  • Step 1: 在 idealLife 字段后新增5个字段

找到 idealLife 字段定义(约第115行),在其后、scripts 字段前插入:

    /**
     * 所在城市
     */
    @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
git add backend-single/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: backend-single/src/main/java/com/emotion/dto/request/userprofile/UserProfileCreateRequest.java

  • Step 1: 在 idealLife 字段后新增5个字段

找到 idealLife 字段定义(约第86行),在其后、scripts 字段前插入:

    /**
     * 所在城市
     */
    private String city;

    /**
     * 行业
     */
    private String industry;

    /**
     * 公司
     */
    private String company;

    /**
     * 性格标签 (JSON字符串)
     */
    private String personalityTags;

    /**
     * 生日
     */
    private LocalDate birthday;
  • Step 2: Commit
git add backend-single/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: backend-single/src/main/java/com/emotion/dto/request/userprofile/UserProfileUpdateRequest.java

  • Step 1: 在 idealLife 字段后新增5个字段

找到 idealLife 字段定义(约第90行),在其后、scripts 字段前插入:

    /**
     * 所在城市
     */
    private String city;

    /**
     * 行业
     */
    private String industry;

    /**
     * 公司
     */
    private String company;

    /**
     * 性格标签 (JSON字符串)
     */
    private String personalityTags;

    /**
     * 生日
     */
    private LocalDate birthday;
  • Step 2: Commit
git add backend-single/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: backend-single/src/main/java/com/emotion/dto/response/userprofile/UserProfileResponse.java

  • Step 1: 在 idealLife 字段后新增5个字段

找到 idealLife 字段定义(约第105行),在其后、scripts 字段前插入:

    /**
     * 所在城市
     */
    private String city;

    /**
     * 行业
     */
    private String industry;

    /**
     * 公司
     */
    private String company;

    /**
     * 性格标签 (JSON字符串)
     */
    private String personalityTags;

    /**
     * 生日
     */
    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate birthday;
  • Step 2: Commit
git add backend-single/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个字段:

const {
    id,
    nickname,
    gender,
    zodiac,
    profession,
    mbti,
    hobbies,
    childhood,
    joy,
    low,
    future,
    city,
    industry,
    company,
    personalityTags,
    birthday
  } = frontendData

原返回对象(第61-83行)在 idealLife 后新增:

  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个字段:

  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 对象后新增:

  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
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 对象后新增:

  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
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行:

const events = computed(() => {
  const list = store.events || []
  return list.length ? list : sampleEvents
})

改为:

const events = computed(() => store.events || [])
  • Step 3: 修改 heroTags computed 去除硬编码默认值

将第183-187行:

const heroTags = computed(() => {
  const hobbies = profile.value.hobbies
  if (Array.isArray(hobbies) && hobbies.length) return hobbies.slice(0, 4)
  return ['阅读', '旅行', '音乐', '创作']
})

改为:

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行:

const avatar = computed(() => {
  const nickname = profile.value.nickname || 'Zoey'
  return `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(nickname)}&backgroundColor=b982ff`
})

改为:

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行:

<text class="profile-name">{{ profile.nickname || 'Zoey' }}</text>

改为:

<text class="profile-name">{{ profile.nickname || '未设置' }}</text>

将第31行:

<text class="meta-value">{{ profile.zodiac || '巨蟹座' }}</text>

改为:

<text class="meta-value">{{ profile.zodiac || '未设置' }}</text>

将第38行:

<text class="meta-value">{{ profile.mbti || 'ENTJ' }}</text>

改为:

<text class="meta-value">{{ profile.mbti || '未设置' }}</text>

将第45行:

<text class="meta-value">{{ profile.profession || '产品经理' }}</text>

改为:

<text class="meta-value">{{ profile.profession || '未设置' }}</text>

将第56行:

<text class="hobby-text">{{ heroTags.join(' · ') }}</text>

改为:

<text class="hobby-text">{{ heroTags.length ? heroTags.join(' · ') : '未设置' }}</text>
  • Step 6: Commit
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行:

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`
})

改为:

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行:

const birthdayDisplay = computed(() => {
  if (!birthday.value) return '1998年06月18日'
  const [year, month, day] = birthday.value.split('-')
  return `${year}${month}${day}日`
})

改为:

const birthdayDisplay = computed(() => {
  if (!birthday.value) return '请选择生日'
  const [year, month, day] = birthday.value.split('-')
  return `${year}${month}${day}日`
})
  • Step 3: 修改 syncFromStore 去除所有硬编码默认值

将第246-265行的 syncFromStore 函数整体替换为:

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
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: 后端编译验证

cd backend-single && mvn compile -q

预期:编译成功,无错误。

  • Step 2: 前端语法检查(如有 linter)
cd mini-program && npm run lint 2>/dev/null || echo "No lint script, skipping"
  • Step 3: 手动功能验证清单
  1. 启动后端服务,确认无启动错误。
  2. 在数据库执行 DESCRIBE t_user_profile;,确认 cityindustrycompanypersonality_tagsbirthday 五列已存在。
  3. 小程序登录后进入"人生轨迹"页:
    • 未设置资料时,昵称显示"未设置",星座/MBTI/职业显示"未设置",爱好显示"未设置"。
    • 无人生事件时,显示"还没有人生轨迹"空状态,无 sampleEvents。
  4. 点击"编辑资料"
    • 首次进入(无资料)时,所有输入框为空,生日显示"请选择生日",性格标签和爱好无选中项。
    • 填写所有字段(包括城市、行业、公司、性格标签、生日)后保存。
    • 保存成功后返回人生轨迹页,数据已更新。
    • 再次进入编辑资料页,所有字段正确回显。
  5. 已有用户(老数据,无新字段)登录后,页面正常展示,无报错,新字段显示为空。
  • Step 4: Commit(如测试通过)
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 中 transformToBackendFormattransformToFrontendFormat 字段名一致
  • Store 中 registrationData 字段名与 Service 一致
  • personalityTags 在前端始终作为数组处理,后端作为 JSON 字符串,转换逻辑与 hobbies 保持一致
  • birthday 前后端均为 yyyy-MM-dd 字符串格式