Compare commits

...

12 Commits

5 changed files with 792 additions and 333 deletions
@@ -0,0 +1,445 @@
# 个人信息组件合并 实现计划
> **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:** 将人生轨迹页面(RecordView)的个人信息展示区域完整合并到我的页面(MineView),取并集展示所有字段,RecordView 移除个人信息区域。
**Architecture:** MineView 保留现有宇宙星空风格和居中头像布局,在 stats-grid 下方新增「个人档案」信息区块,使用三栏网格展示所有个人信息字段。RecordView 完全移除 profile-card 区域及相关代码。
**Tech Stack:** Vue 3 Composition API, Taro/UniApp 小程序, SCSS scoped styles
**设计文档:** `docs/superpowers/specs/2026-06-15-profile-merge-design.md`
---
## 文件结构
| 操作 | 文件路径 | 职责 |
|------|---------|------|
| 修改 | `mini-program/src/pages/main/MineView.vue` | 新增「个人档案」区块(computed + template + styles |
| 修改 | `mini-program/src/pages/main/RecordView.vue` | 移除 profile-card 区域及相关代码 |
---
### Task 1: MineView — 新增 computed 属性
**Files:**
- Modify: `mini-program/src/pages/main/MineView.vue:63-138`
- [ ] **Step 1: 在 `<script setup>` 中新增字段 computed 属性**
`harmonyPercent` computed 之后、`openProfileSettings` 方法之前,新增以下 computed:
```js
const genderText = computed(() => {
const map = { male: '男', female: '女', other: '其他' }
return map[profile.value.gender] || null
})
const age = computed(() => {
const birthday = profile.value.birthday
if (!birthday) return null
const birthYear = Number(String(birthday).slice(0, 4))
if (!birthYear) return null
return new Date().getFullYear() - birthYear
})
const hobbyText = computed(() => {
const hobbies = profile.value.hobbies
if (Array.isArray(hobbies) && hobbies.length) return hobbies.slice(0, 4).join(' · ')
return null
})
const goEditProfile = () => {
uni.navigateTo({ url: '/pages/onboarding/index?edit=1' })
}
```
- [ ] **Step 2: 提交**
```bash
git add mini-program/src/pages/main/MineView.vue
git commit -m "featMineView 新增个人档案字段 computed 属性"
```
---
### Task 2: MineView — 新增「个人档案」模板
**Files:**
- Modify: `mini-program/src/pages/main/MineView.vue:32-34`
- [ ] **Step 1: 在 stats-grid 和 menu-list 之间插入个人档案区块**
在第 32 行 `</view>`stats-grid 结束标签)之后、第 34 行 `<view class="menu-list">` 之前,插入以下模板:
```html
<view class="profile-section" @click="goEditProfile">
<view class="section-header">
<text class="section-star"></text>
<text class="section-title">个人档案</text>
</view>
<view class="profile-grid">
<view class="grid-item">
<text class="grid-icon"></text>
<text class="grid-label">星座</text>
<text class="grid-value">{{ profile.zodiac || '未设置' }}</text>
</view>
<view class="grid-item">
<text class="grid-icon"></text>
<text class="grid-label">MBTI</text>
<text class="grid-value">{{ profile.mbti || '未设置' }}</text>
</view>
<view class="grid-item no-border">
<text class="grid-icon"></text>
<text class="grid-label">职业</text>
<text class="grid-value">{{ profile.profession || '未设置' }}</text>
</view>
</view>
<view class="profile-grid">
<view class="grid-item">
<text class="grid-icon">📍</text>
<text class="grid-label">城市</text>
<text class="grid-value">{{ profile.city || '未设置' }}</text>
</view>
<view class="grid-item">
<text class="grid-icon">🏢</text>
<text class="grid-label">行业</text>
<text class="grid-value">{{ profile.industry || '未设置' }}</text>
</view>
<view class="grid-item no-border">
<text class="grid-icon">🏗</text>
<text class="grid-label">公司</text>
<text class="grid-value">{{ profile.company || '未设置' }}</text>
</view>
</view>
<view class="profile-grid">
<view class="grid-item">
<text class="grid-icon">🎂</text>
<text class="grid-label">生日</text>
<text class="grid-value">{{ profile.birthday ? String(profile.birthday).slice(5).replace('-', '.') : '未设置' }}</text>
</view>
<view class="grid-item">
<text class="grid-icon"></text>
<text class="grid-label">性别</text>
<text class="grid-value">{{ genderText || '未设置' }}</text>
</view>
<view class="grid-item no-border">
<text class="grid-icon"></text>
<text class="grid-label">年龄</text>
<text class="grid-value">{{ age ? age + '岁' : '未设置' }}</text>
</view>
</view>
<view class="hobby-line">
<text class="hobby-heart"></text>
<text class="hobby-label">爱好</text>
<text class="hobby-value">{{ hobbyText || '未设置' }}</text>
</view>
</view>
```
- [ ] **Step 2: 提交**
```bash
git add mini-program/src/pages/main/MineView.vue
git commit -m "featMineView 新增个人档案区块模板"
```
---
### Task 3: MineView — 新增「个人档案」样式
**Files:**
- Modify: `mini-program/src/pages/main/MineView.vue:141-435`
- [ ] **Step 1: 在 `<style scoped>` 中新增个人档案区块样式**
`.stat-value` 样式块之后、`.menu-list` 样式块之前,插入以下样式:
```css
.profile-section {
position: relative;
z-index: 1;
margin-top: 42rpx;
}
.section-header {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 28rpx;
}
.section-star {
color: #ffd184;
font-size: 28rpx;
}
.section-title {
color: #fff;
font-size: 40rpx;
font-weight: 800;
}
.profile-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
margin-bottom: 24rpx;
}
.grid-item {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6rpx;
padding-right: 16rpx;
border-right: 1rpx solid rgba(180, 139, 255, 0.22);
}
.grid-item + .grid-item {
padding-left: 16rpx;
}
.grid-item.no-border {
border-right: 0;
}
.grid-icon {
font-size: 30rpx;
line-height: 1;
margin-bottom: 4rpx;
}
.grid-label {
color: rgba(219, 204, 247, 0.54);
font-size: 20rpx;
}
.grid-value {
color: #fff;
font-size: 23rpx;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
}
.hobby-line {
display: flex;
align-items: center;
gap: 16rpx;
padding-top: 20rpx;
border-top: 1rpx solid rgba(180, 139, 255, 0.22);
}
.hobby-heart {
color: #a855ff;
font-size: 30rpx;
text-shadow: 0 0 24rpx rgba(168, 85, 255, 0.7);
}
.hobby-label {
color: rgba(219, 204, 247, 0.54);
font-size: 20rpx;
flex-shrink: 0;
}
.hobby-value {
color: #fff;
font-size: 23rpx;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
```
- [ ] **Step 2: 提交**
```bash
git add mini-program/src/pages/main/MineView.vue
git commit -m "featMineView 新增个人档案区块样式"
```
---
### Task 4: RecordView — 移除 profile-card 模板
**Files:**
- Modify: `mini-program/src/pages/main/RecordView.vue:1-59`
- [ ] **Step 1: 移除 profile-card 区域的模板代码**
将模板中的第 3-59 行(从 `<view class="profile-card kos-card">` 到其闭合 `</view>`)完全删除。
删除后,模板应从 `<view class="record-view">` 直接开始,紧接 `<view class="section-head">`
修改后的模板开头应为:
```html
<template>
<view class="record-view">
<view class="section-head">
<view>
<view class="title-line">
<text class="section-title">人生轨迹</text>
<text class="star gold"></text>
</view>
<text class="section-subtitle">你的成长之路,正在展开</text>
</view>
<view class="social-import-btn" @click="openSocialImport">
<text>导入社交数据</text>
</view>
</view>
<scroll-view class="filters" scroll-x :show-scrollbar="false">
...(后续保持不变)
```
- [ ] **Step 2: 提交**
```bash
git add mini-program/src/pages/main/RecordView.vue
git commit -m "featRecordView 移除 profile-card 模板"
```
---
### Task 5: RecordView — 移除相关 script 代码
**Files:**
- Modify: `mini-program/src/pages/main/RecordView.vue:126-247`
- [ ] **Step 1: 移除不再需要的 computed 和方法**
`<script setup>` 中删除以下内容:
1. 删除 `profile` computed(约第 143 行):
```js
const profile = computed(() => store.userProfile || store.registrationData || {})
```
2. 删除 `heroTags` computed(约第 145-149 行):
```js
const heroTags = computed(() => {
const hobbies = profile.value.hobbies
if (Array.isArray(hobbies) && hobbies.length) return hobbies.slice(0, 4)
return []
})
```
3. 删除 `avatar` computed(约第 151-154 行):
```js
const avatar = computed(() => {
const nickname = profile.value.nickname || 'User'
return `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(nickname)}&backgroundColor=b982ff`
})
```
4. 删除 `editProfile` 方法(约第 223-225 行):
```js
const editProfile = () => {
uni.navigateTo({ url: '/pages/onboarding/index?edit=1' })
}
```
5. `getAgeText` 方法中仍使用 `profile.value.birthYear`,需要保留 `profile` computed 或改为直接引用。由于 `getAgeText` 仍需要 profile 数据,**保留 `profile` computed**,只删除 `heroTags``avatar``editProfile`
- [ ] **Step 2: 提交**
```bash
git add mini-program/src/pages/main/RecordView.vue
git commit -m "featRecordView 移除无用的 computed 和方法"
```
---
### Task 6: RecordView — 移除相关样式
**Files:**
- Modify: `mini-program/src/pages/main/RecordView.vue:249-784`
- [ ] **Step 1: 移除 profile-card 相关的所有样式**
删除以下样式规则:
- `.profile-card` 及其 `::after` 伪元素
- `.profile-top`, `.avatar-wrap`, `.avatar`, `.avatar-edit`, `.edit-pen`, `.tiny-pen`
- `.profile-info`, `.name-row`, `.profile-name`, `.star`(注意:`.star` 在 section-head 中也有使用,只删除 profile 相关的,保留 `.star.gold`
- `.profile-subtitle`, `.edit-profile`
- `.profile-divider`, `.profile-divider.small`
- `.meta-grid`, `.meta-item`, `.meta-icon`, `.meta-label`, `.meta-value`
- `.person-icon`, `.job-icon` 及其伪元素
- `.hobby-row`, `.hobby-text`
- `.heart`
**注意**`.star` 样式**不要删除**,因为 section-head 中的 `<text class="star gold">✦</text>` 仍在使用它(提供 `font-size: 26rpx`)。
保留的样式(不要删除):
- `.record-view`
- `.section-head`, `.title-line`, `.section-title`, `.star`, `.gold`, `.section-subtitle`
- `.social-import-btn`
- `.filters`, `.filter-row`, `.filter-chip`, `.add-filter`
- `.timeline` 及所有子样式
- `.event-card` 及所有子样式
- `.empty-card`, `.empty-title`, `.empty-text`
- `.create-fab`, `.plus-core`
- `.star.gold`section-head 中使用)
- [ ] **Step 2: 提交**
```bash
git add mini-program/src/pages/main/RecordView.vue
git commit -m "featRecordView 移除 profile-card 相关样式"
```
---
### Task 7: 浏览器验证
- [ ] **Step 1: 启动 H5 开发服务器**
```bash
cd mini-program
npm run dev:h5
```
- [ ] **Step 2: 验证 MineView(我的页面)**
打开浏览器访问 `http://localhost:5173`,切换到「我的」tab
检查项:
- [ ] 头像和昵称正常显示
- [ ] 觉醒深度和星历契合正常显示
- [ ] 「个人档案」区块正常显示,包含:
- 星座、MBTI、职业三栏
- 城市、行业、公司三栏
- 生日、性别、年龄三栏
- 爱好行
- [ ] 未设置的字段显示"未设置"
- [ ] 点击「个人档案」区块可跳转到编辑页
- [ ] 菜单列表和退出按钮正常
- [ ] 整体星空风格无变化
- [ ] **Step 3: 验证 RecordView(人生轨迹页面)**
切换到「人生轨迹」tab
检查项:
- [ ] 个人信息卡片已完全移除
- [ ] "人生轨迹"标题和"导入社交数据"按钮正常
- [ ] 筛选器正常
- [ ] 事件时间线正常显示
- [ ] 创建 FAB 按钮正常
- [ ] 页面无 Console 报错
- [ ] **Step 4: 最终提交**
```bash
git add .
git commit -m "feat:完成个人信息组件合并,RecordView 移除个人信息区域,MineView 新增完整个人档案区块"
```
@@ -0,0 +1,152 @@
---
author: claude
created_at: 2026-06-15
purpose: 将人生轨迹页面的个人信息展示区域合并到我的页面,统一信息展示
---
# 个人信息组件合并设计
## 背景
当前小程序中,个人信息分散在两个页面展示:
- **RecordView(人生轨迹页面)**:顶部个人信息卡片,展示头像、昵称、星座、MBTI、职业、爱好
- **MineView(我的页面)**:展示头像、昵称、MBTI·星座·职业(一行)、觉醒深度、星历契合、菜单
两个页面存在大量重复信息,且 MineView 展示的个人字段较少。需要将 RecordView 的个人信息卡片完整合并到 MineView,取并集展示所有字段,去除重复。
## 设计目标
1. RecordView 完全移除个人信息展示区域
2. MineView 保留现有宇宙星空风格,新增「个人档案」信息区块
3. 所有可用个人信息字段取并集,不重复展示
4. 不新增后端接口,全部从现有 store 数据读取
## 变更范围
### RecordView.vue — 移除个人信息区域
**模板变更**
- 移除 `profile-card` 整个区域(包含头像、昵称、副标题、编辑按钮、meta-grid、爱好行、分隔线)
**脚本变更**
- 移除 `profile` computed`store.userProfile || store.registrationData`
- 移除 `avatar` computeddicebear 头像 URL
- 移除 `heroTags` computed(爱好数组)
- 移除 `editProfile` 方法
**样式变更**
- 移除所有 `profile-card``profile-top``avatar-wrap``avatar``avatar-edit``edit-pen``tiny-pen``profile-info``name-row``profile-name``star``profile-subtitle``edit-profile``profile-divider``meta-grid``meta-item``meta-icon``meta-label``meta-value``person-icon``job-icon``heart``hobby-row``hobby-text` 相关样式
**保留不变**
- `section-head`"人生轨迹"标题 + "导入社交数据"按钮)
- `filters`(筛选器)
- `timeline`(事件时间线)
- 空状态卡片
- `create-fab`(创建 FAB 按钮)
### MineView.vue — 新增「个人档案」区块
**位置**:在 `stats-grid`(觉醒深度 | 星历契合)下方、`menu-list` 上方。
**布局结构**
```
✦ 个人档案 ← 区块标题
┌────────┬────────┬────────┐
│ ♋ │ ◉ │ ◻ │
│ 星座 │ MBTI │ 职业 │ ← 第一行:来自 RecordView
│ 巨蟹座 │ INTJ │ 设计师 │
├────────┼────────┼────────┤
│ 📍 │ 🏢 │ 🏗 │
│ 城市 │ 行业 │ 公司 │ ← 第二行:新增字段
│ 上海 │ 互联网 │ xx公司 │
├────────┼────────┼────────┤
│ 🎂 │ ♂ │ ⏳ │
│ 生日 │ 性别 │ 年龄 │ ← 第三行:新增字段
│ 6.15 │ 男 │ 28 │
└────────┴────────┴────────┘
♡ 爱好 摄影 · 阅读 · 音乐 ← 第四行:爱好
```
**字段列表(并集)**
| 字段 | 数据来源 | 图标 | 空值显示 |
|------|---------|------|---------|
| 星座 | `profile.zodiac` | ♋ | 未设置 |
| MBTI | `profile.mbti` | ◉ | 未设置 |
| 职业 | `profile.profession` | ◻ | 未设置 |
| 城市 | `profile.city` | 📍 | 未设置 |
| 行业 | `profile.industry` | 🏢 | 未设置 |
| 公司 | `profile.company` | 🏗 | 未设置 |
| 生日 | `profile.birthday` | 🎂 | 未设置 |
| 性别 | `profile.gender` | ♂ | 未设置 |
| 年龄 | 由 birthday 计算 | ⏳ | 未设置 |
| 爱好 | `profile.hobbies` | ♡ | 未设置 |
**编辑入口**:整个区块可点击,跳转到 `/pages/onboarding/index?edit=1`
### 样式规范
**区块标题**
- 字号 40rpx,字重 800,白色
- 前缀 ✦ 金色星号(`#ffd184`),与 RecordView 的 `section-title` 风格一致
- 与上方 `stats-grid` 间距 42rpx
**字段网格**
- CSS Grid 三栏布局:`grid-template-columns: repeat(3, 1fr)`
- 每栏结构:图标(上)→ 标签(中,20rpx,`rgba(219, 204, 247, 0.54)`)→ 值(下,23rpx,白色,加粗 600)
- 栏间竖线分隔:`border-right: 1rpx solid rgba(180, 139, 255, 0.22)`,最后一栏无右边框
- 行间距 24rpx
**爱好行**
- ♡ 图标 + "爱好" 标签 + 值,水平排列
- 与网格间距 20rpx,上方有细分隔线(`1rpx solid rgba(180, 139, 255, 0.22)`
- 爱好值用 ` · ` 连接,最多显示 4 个
**整体容器**
- 不加卡片背景,透明融入 MineView 星空背景
-`stats-grid``menu-list` 的间距保持一致(42rpx
### 数据逻辑
**年龄计算**
```js
const age = computed(() => {
const birthday = profile.value.birthday
if (!birthday) return null
const birthYear = Number(String(birthday).slice(0, 4))
if (!birthYear) return null
return new Date().getFullYear() - birthYear
})
```
**性别映射**
```js
const genderText = computed(() => {
const map = { male: '男', female: '女', other: '其他' }
return map[profile.value.gender] || null
})
```
**数据来源**:全部从 `store.userProfile || store.registrationData` 读取,不新增后端接口。
### MineView 现有信息处理
**保留不变**
- 头像(居中展示,保留现有宇宙光晕效果)
- 昵称(大字号居中)
- 签名行(`metaLine`:MBTI · 星座 · 职业 的组合文本)
- 统计数据(觉醒深度、星历契合)
- 菜单列表(个人档案设置、多账号切换、与开发者对话)
- 退出登录按钮
**签名行处理**`metaLine` 保留,作为昵称下方的补充签名文本,与下方「个人档案」区块的三栏网格不冲突。
## 不在范围内
- 不修改后端接口
- 不修改 `stores/app.js`
- 不修改 `profile/index.vue`(该页面只是 MineView 的包装)
- 不新增个人信息字段到 store(只展示已有字段)
+180 -22
View File
@@ -3,12 +3,6 @@
<view class="profile-stars layer-a"></view>
<view class="profile-stars layer-b"></view>
<view class="profile-topbar">
<view class="top-placeholder"></view>
<text class="page-title">个人中心</text>
<view class="top-placeholder"></view>
</view>
<view class="avatar-stage">
<view class="avatar-glow"></view>
<view class="avatar-circle">
@@ -31,6 +25,65 @@
</view>
</view>
<view class="profile-section" @click="goEditProfile">
<view class="section-header">
<text class="section-star"></text>
<text class="section-title">个人档案</text>
</view>
<view class="profile-grid">
<view class="grid-item">
<text class="grid-icon"></text>
<text class="grid-label">星座</text>
<text class="grid-value">{{ profile.zodiac || '未设置' }}</text>
</view>
<view class="grid-item">
<text class="grid-icon"></text>
<text class="grid-label">职业</text>
<text class="grid-value">{{ profile.profession || '未设置' }}</text>
</view>
<view class="grid-item">
<text class="grid-icon">📍</text>
<text class="grid-label">城市</text>
<text class="grid-value">{{ profile.city || '未设置' }}</text>
</view>
<view class="grid-item no-border">
<text class="grid-icon">🏢</text>
<text class="grid-label">行业</text>
<text class="grid-value">{{ profile.industry || '未设置' }}</text>
</view>
</view>
<view class="profile-grid">
<view class="grid-item">
<text class="grid-icon">🏗</text>
<text class="grid-label">公司</text>
<text class="grid-value">{{ profile.company || '未设置' }}</text>
</view>
<view class="grid-item">
<text class="grid-icon">🎂</text>
<text class="grid-label">生日</text>
<text class="grid-value">{{ profile.birthday ? String(profile.birthday).slice(5).replace('-', '.') : '未设置' }}</text>
</view>
<view class="grid-item">
<text class="grid-icon"></text>
<text class="grid-label">性别</text>
<text class="grid-value">{{ genderText || '未设置' }}</text>
</view>
<view class="grid-item no-border">
<text class="grid-icon"></text>
<text class="grid-label">年龄</text>
<text class="grid-value">{{ age ? age + '岁' : '未设置' }}</text>
</view>
</view>
<view class="hobby-line">
<text class="hobby-heart"></text>
<text class="hobby-label">爱好</text>
<text class="hobby-value">{{ hobbyText || '未设置' }}</text>
</view>
</view>
<view class="menu-list">
<view class="menu-item" @click="openProfileSettings">
<view class="menu-icon user-icon">
@@ -111,6 +164,30 @@ const harmonyPercent = computed(() => {
return Math.round((filled / 7) * 100) + '%'
})
const genderText = computed(() => {
// 兼容中文值(编辑页保存格式)和英文 key
const map = { male: '男', female: '女', other: '其他', '男': '男', '女': '女', '不透露': '不透露' }
return map[profile.value.gender] || profile.value.gender || null
})
const age = computed(() => {
const birthday = profile.value.birthday
if (!birthday) return null
const birthYear = Number(String(birthday).slice(0, 4))
if (!birthYear) return null
return new Date().getFullYear() - birthYear
})
const hobbyText = computed(() => {
const hobbies = profile.value.hobbies
if (Array.isArray(hobbies) && hobbies.length) return hobbies.slice(0, 4).join(' · ')
return null
})
const goEditProfile = () => {
uni.navigateTo({ url: '/pages/onboarding/index?edit=1' })
}
const openProfileSettings = () => {
uni.navigateTo({ url: '/pages/onboarding/index?edit=1' })
}
@@ -188,7 +265,6 @@ const terminateLifeHarmony = () => {
background-position: 60rpx 20rpx, 120rpx 100rpx;
}
.profile-topbar,
.stats-grid,
.menu-item {
position: relative;
@@ -197,21 +273,6 @@ const terminateLifeHarmony = () => {
align-items: center;
}
.profile-topbar {
height: 68rpx;
justify-content: space-between;
}
.top-placeholder {
width: 72rpx;
}
.page-title {
font-size: 34rpx;
font-weight: 500;
letter-spacing: 4rpx;
}
.avatar-stage {
position: relative;
z-index: 1;
@@ -329,6 +390,103 @@ const terminateLifeHarmony = () => {
font-weight: 800;
}
.profile-section {
position: relative;
z-index: 1;
margin-top: 42rpx;
}
.section-header {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 28rpx;
}
.section-star {
color: #ffd184;
font-size: 28rpx;
}
.section-title {
color: #fff;
font-size: 40rpx;
font-weight: 800;
}
.profile-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
margin-bottom: 24rpx;
}
.grid-item {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6rpx;
padding-right: 16rpx;
border-right: 1rpx solid rgba(180, 139, 255, 0.22);
}
.grid-item + .grid-item {
padding-left: 16rpx;
}
.grid-item.no-border {
border-right: 0;
}
.grid-icon {
font-size: 30rpx;
line-height: 1;
margin-bottom: 4rpx;
}
.grid-label {
color: rgba(219, 204, 247, 0.54);
font-size: 20rpx;
}
.grid-value {
color: #fff;
font-size: 23rpx;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
}
.hobby-line {
display: flex;
align-items: center;
gap: 16rpx;
padding-top: 20rpx;
border-top: 1rpx solid rgba(180, 139, 255, 0.22);
}
.hobby-heart {
color: #a855ff;
font-size: 30rpx;
text-shadow: 0 0 24rpx rgba(168, 85, 255, 0.7);
}
.hobby-label {
color: rgba(219, 204, 247, 0.54);
font-size: 20rpx;
flex-shrink: 0;
}
.hobby-value {
color: #fff;
font-size: 23rpx;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.menu-list {
position: relative;
z-index: 1;
-310
View File
@@ -1,63 +1,5 @@
<template>
<view class="record-view">
<view class="profile-card kos-card">
<view class="profile-top">
<view class="avatar-wrap">
<image class="avatar" :src="avatar" mode="aspectFill" />
<view class="avatar-edit" @click="editProfile">
<view class="edit-pen"></view>
</view>
</view>
<view class="profile-info">
<view class="name-row">
<text class="profile-name">{{ profile.nickname || '未设置' }}</text>
<text class="star"></text>
</view>
<text class="profile-subtitle">正在成为更清晰的自己</text>
</view>
<view class="edit-profile kos-pill" @click="editProfile">
<view class="tiny-pen"></view>
<text>编辑资料</text>
</view>
</view>
<view class="profile-divider"></view>
<view class="meta-grid">
<view class="meta-item">
<text class="meta-icon"></text>
<view>
<text class="meta-label">星座</text>
<text class="meta-value">{{ profile.zodiac || '未设置' }}</text>
</view>
</view>
<view class="meta-item">
<view class="person-icon"></view>
<view>
<text class="meta-label">MBTI</text>
<text class="meta-value">{{ profile.mbti || '未设置' }}</text>
</view>
</view>
<view class="meta-item no-border">
<view class="job-icon"></view>
<view>
<text class="meta-label">职业</text>
<text class="meta-value">{{ profile.profession || '未设置' }}</text>
</view>
</view>
</view>
<view class="profile-divider small"></view>
<view class="hobby-row">
<text class="heart"></text>
<view>
<text class="meta-label">爱好</text>
<text class="hobby-text">{{ heroTags.length ? heroTags.join(' · ') : '未设置' }}</text>
</view>
</view>
</view>
<view class="section-head">
<view>
<view class="title-line">
@@ -142,16 +84,6 @@ const filters = computed(() => [...baseFilters, ...customFilters.value])
const profile = computed(() => store.userProfile || store.registrationData || {})
const events = computed(() => store.events || [])
const heroTags = computed(() => {
const hobbies = profile.value.hobbies
if (Array.isArray(hobbies) && hobbies.length) return hobbies.slice(0, 4)
return []
})
const avatar = computed(() => {
const nickname = profile.value.nickname || 'User'
return `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(nickname)}&backgroundColor=b982ff`
})
const displayEvents = computed(() => {
if (activeFilter.value === 'all') return events.value
@@ -220,10 +152,6 @@ const openDetail = (event) => {
uni.navigateTo({ url: `/pages/life-event/detail?id=${event.id}` })
}
const editProfile = () => {
uni.navigateTo({ url: '/pages/onboarding/index?edit=1' })
}
const openSocialImport = () => {
uni.navigateTo({ url: '/pages/social-import/index' })
}
@@ -255,249 +183,11 @@ const addFilter = () => {
padding-bottom: 238rpx;
}
.profile-card {
position: relative;
overflow: hidden;
border-radius: 34rpx;
padding: 34rpx 30rpx 28rpx;
}
.profile-card::after {
content: '';
position: absolute;
right: -28rpx;
bottom: -20rpx;
width: 220rpx;
height: 156rpx;
background: radial-gradient(circle, rgba(122, 58, 255, 0.35), transparent 62%);
border: 1rpx solid rgba(158, 88, 255, 0.26);
border-radius: 50%;
transform: rotate(-18deg);
}
.profile-top,
.meta-grid,
.hobby-row {
position: relative;
z-index: 1;
}
.profile-top {
display: flex;
align-items: center;
gap: 22rpx;
}
.avatar-wrap {
position: relative;
flex-shrink: 0;
width: 112rpx;
height: 112rpx;
padding: 5rpx;
border-radius: 50%;
background: linear-gradient(135deg, #fff, #9b54ff 38%, #4a67ff);
box-shadow: 0 0 34rpx rgba(149, 89, 255, 0.52);
}
.avatar {
width: 100%;
height: 100%;
border-radius: 50%;
background: #20123d;
}
.avatar-edit {
position: absolute;
right: -5rpx;
bottom: -5rpx;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background: linear-gradient(135deg, #8b4dff, #4a2cff);
box-shadow: 0 0 20rpx rgba(158, 91, 255, 0.6);
}
.edit-pen,
.tiny-pen {
width: 18rpx;
height: 6rpx;
border-radius: 6rpx;
background: #fff;
transform: rotate(-45deg);
margin: 17rpx auto;
}
.profile-info {
flex: 1;
min-width: 0;
}
.name-row {
display: flex;
align-items: center;
gap: 14rpx;
}
.profile-name {
color: #fff;
font-size: 36rpx;
line-height: 1.1;
font-weight: 800;
}
.star {
color: #ffd589;
font-size: 26rpx;
}
.profile-subtitle {
display: block;
margin-top: 10rpx;
color: rgba(239, 232, 255, 0.78);
font-size: 24rpx;
}
.edit-profile {
flex-shrink: 0;
height: 56rpx;
padding: 0 18rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
gap: 8rpx;
color: #dccbff;
font-size: 22rpx;
}
.tiny-pen {
width: 16rpx;
margin: 0;
background: currentColor;
}
.profile-divider {
position: relative;
z-index: 1;
height: 1rpx;
margin: 28rpx 0 20rpx;
background: rgba(180, 139, 255, 0.22);
}
.profile-divider.small {
margin: 20rpx 0;
}
.meta-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.meta-item {
min-width: 0;
display: flex;
align-items: center;
gap: 10rpx;
padding-right: 12rpx;
border-right: 1rpx solid rgba(180, 139, 255, 0.22);
}
.meta-item + .meta-item {
padding-left: 16rpx;
}
.meta-item.no-border {
border-right: 0;
}
.meta-item > view,
.hobby-row > view {
min-width: 0;
}
.meta-icon,
.heart {
color: #a855ff;
font-size: 34rpx;
line-height: 1;
text-shadow: 0 0 24rpx rgba(168, 85, 255, 0.7);
}
.person-icon,
.job-icon {
position: relative;
width: 32rpx;
height: 32rpx;
flex-shrink: 0;
}
.person-icon::before {
content: '';
position: absolute;
left: 8rpx;
top: 0;
width: 14rpx;
height: 14rpx;
border: 3rpx solid #a855ff;
border-radius: 50%;
}
.person-icon::after {
content: '';
position: absolute;
left: 1rpx;
bottom: 0;
width: 28rpx;
height: 16rpx;
border: 3rpx solid #a855ff;
border-radius: 18rpx 18rpx 4rpx 4rpx;
}
.job-icon {
border: 4rpx solid #a855ff;
border-radius: 8rpx;
box-sizing: border-box;
}
.job-icon::before {
content: '';
position: absolute;
left: 8rpx;
top: -8rpx;
width: 12rpx;
height: 7rpx;
border: 3rpx solid #a855ff;
border-bottom: 0;
border-radius: 8rpx 8rpx 0 0;
}
.meta-label,
.meta-value,
.hobby-text {
display: block;
}
.meta-label {
color: rgba(219, 204, 247, 0.54);
font-size: 20rpx;
}
.meta-value,
.hobby-text {
margin-top: 3rpx;
color: #fff;
font-size: 23rpx;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hobby-row {
display: flex;
align-items: center;
gap: 22rpx;
}
.section-head {
display: flex;
align-items: center;
+15 -1
View File
@@ -227,7 +227,11 @@
:class="{ user: message.role === 'user', system: message.role === 'assistant', pending: message.pending }"
>
<text selectable user-select>{{ getMessageDisplayContent(message) }}</text>
<text v-if="message.pending" class="typing-cursor">|</text>
<view v-if="message.pending" class="thinking-dots">
<view></view>
<view></view>
<view></view>
</view>
<view
v-if="isAssistantMessage(message)"
class="message-toggle"
@@ -240,6 +244,16 @@
</view>
</view>
<view v-if="resultChatting" class="generation-loading">
<view class="loading-orbit streaming">
<view class="orbit-ring outer"></view>
<view class="orbit-ring inner"></view>
<view class="orbit-core"></view>
</view>
<text class="loading-copy">正在为你重写人生</text>
<text class="loading-subcopy">AI 正在思考下一步方向</text>
</view>
<view :id="resultScrollAnchor" class="result-scroll-anchor"></view>
</view>
</scroll-view>