fix:修复

This commit is contained in:
吕新雨
2026-02-05 16:06:49 +08:00
parent 2b67a571bb
commit 8e71503169
18 changed files with 924 additions and 22 deletions

View File

@@ -0,0 +1,236 @@
# 「随心」主题Suixin Theme技术计划plan
## 0. 目标回顾
在 Home 现有「风景 / 纯色」主题基础上新增第三种主题「随心」:
- **输入**:问卷生成的用户画像 `U`(本地存储)
- **输出**Home 背景推荐颜色(以纯色为主)
- **规则**:复用「个性化背景颜色推荐算法」的 Base Theme/Neutral Theme 与 Hard Rules
- **计算时机**
- **冷启动App 进程级)**:计算一次并锁定 Base Theme
- **切换文案Home 上滑切下一条)**:在同一 Base Theme 内更新一次“当前颜色”
- **持久化**:主题选择与「随心」计算状态均持久化(避免回到 Home/重进页面时丢失)
- **多语言**TCzh-TW+ EN
## 1. 现状梳理(与改动点)
### 1.1 现有主题切换
- `ThemeMode` 当前为 `'scenery' | 'color'`
- `ThemeModal` 弹窗提供 2 个卡片切换
- `Home` 根据 `themeMode`
- `scenery`:背景图 + 默认底色
- `color`:从 `THEME_COLORS``index` 轮换纯色
- `ui.theme.mode` 已在 `AsyncStorage` 持久化
### 1.2 用户画像输入已就绪
客户端已将问卷映射为 `UserProfileV1_2(_Extended)` 并持久化(`user.profileScoring`),关键字段:
- `stage.unknown`Hard Ruleunknown → Neutral
- `need`(稀疏 one-hot`{ [needTag]: 1 }``{}`
- `emotion_score: number | null`
- `profile_confidence: number`
- `profile_answered`
## 2. 技术方案总览
### 2.1 「随心」算法在 Home 的落地形态
Home 不具备“长文案阅读页的 scroll”因此采用**“渐变单点采样”**来复用算法的连续插值模型:
- Base Theme 仍按 `need / stage` 选定并锁定Theme Lock
- 每次切换文案时,生成一个 \(t \in [0, 1]\),并计算:
\[
Color(t) = lerp(Color\_top, Color\_bottom, t)
\]
- 输出为单一 `hex` 纯色,作为 Home `backgroundColor`
- 全程 **不跨 need、不跨主题色系**,仅在同主题内移动
### 2.2 持久化与“只在冷启动/切换文案时计算”
为同时满足“持久化”与“冷启动时计算”:
- **持久化内容**:锁定的 `base_theme_id` + 用于生成 \(t\) 的 `seed` + 当前 `step_index` + `last_color`
- **冷启动计算**:当检测到“新一轮 App 启动会话”时,重新选择并锁定 `base_theme_id`,并重置/更新 `seed``step_index`
- **切换文案计算**:仅递增 `step_index`,在同一 `base_theme_id` 下更新 `last_color`
> 说明:冷启动检测以“进程级首次进入 Home”为准工程实现阶段会在 `_layout` 或全局单例中生成 boot 标记)。
## 3. 数据结构与存储设计
### 3.1 扩展主题枚举
-`ThemeMode` 扩展为:`'scenery' | 'color' | 'suixin'`
- 存储 key沿用 `ui.theme.mode`
### 3.2 新增「随心」状态存储
新增本地存储 key建议
- `ui.theme.suixin.state`
数据结构(建议):
```ts
type SuixinThemeStateV1 = {
schema_version: 1;
saved_at: string; // ISO8601
base_theme_id: 'neutral' | 'emotional_support' | 'parenting_pressure' | 'self_worth' | 'anxiety_relief' | 'rest_balance';
seed: string; // 用于生成 t 的稳定种子(可由 profile + 日期等派生)
step_index: number; // 每切换一条文案 +1
last_color: string; // "#RRGGBB"
};
```
### 3.3 冷启动会话标记Boot ID
为实现“仅冷启动时重置 base theme/seed”新增一个进程级 boot 标记(实现二选一):
- **方案 A推荐**:在 `app/_layout.tsx` 首次挂载时生成 `boot_id` 并写入内存单例(不落盘)
- **方案 B**:写入 `AsyncStorage`(例如 `app.boot.lastSeenAt`)并结合“本次运行内存标记”判定首次进入 Home
计划优先采用方案 A逻辑清晰且不污染存储。
## 4. 颜色算法实现细节Home 版本)
### 4.1 主题色盘常量
在客户端新增一个颜色模块(例如 `client/src/features/suixinTheme/`),内置:
- Base Theme5 套)+ Neutral1 套)
- 与设计文档保持一致的 `hex`
### 4.2 Base Theme 选择(锁定)
输入:`UserProfileScoring`
输出:`base_theme_id`
规则:
-`stage.unknown === 1``neutral`
-`need` 为空 `{}``neutral`
- 否则取 `Object.keys(need)[0]`
- 若 key 在枚举内 → 对应 Base Theme
- 否则 → `neutral`
### 4.3 t 的生成与“低感知变化”
为了让“切换文案”带来“流动感”但不跳变,采用**小步进**策略:
- 定义 `N = 12`(可调):表示从 \(0 \to 1\) 的分段数
- 每次切换文案:`step_index += 1`
- 计算:`t = (step_index % N) / (N - 1)`
> 该策略保证 \(t\) 在 \([0, 1]\) 内缓慢移动;到达 1 后回到 0 会有一次跳变。为进一步降低跳变,可改为往返波形:
>
> - `phase = step_index % (2*(N-1))`
> - `t = phase <= (N-1) ? phase/(N-1) : (2*(N-1)-phase)/(N-1)`
实现阶段默认采用**往返波形**,避免回卷突跳。
### 4.4 lerp 计算(严格线性)
- `lerp` 仅允许线性插值
- 颜色空间:先使用 sRGB 的逐通道线性插值(实现简单、可控);若后续需要更自然,可升级到线性空间插值,但仍保持线性模型
### 4.5 emotion/confidence 的约束接入
本期按“安全优先”策略落地:
-`emotion_score === null``emotion_score <= 0.3`:输出不做任何微扰(纯 lerp 结果)
- 亮度微扰(\(\Delta L \le \pm 2\%\))与饱和度上限为可选增强;若落地,将以 `profile_confidence` 作为开关条件,并确保不改变色系
## 5. UI 与交互实现计划
### 5.1 ThemeModal新增第三个主题卡片
-`client/components/home/ThemeModal.tsx`
- `ThemeMode` 扩展为包含 `'suixin'`
- 新增 `ThemeCard`:标题使用 i18n`t('theme.suixin')`
- 布局改造:由 2 卡横排改为 **3 卡自适应**`flexWrap` 或减小 gap/宽度),确保小屏不溢出
- 预览图:一期可复用 `theme_color.png` 作为占位;若有设计资源再替换为 `theme_suixin.png`
### 5.2 Home新增主题分支与颜色计算时机
`client/app/(app)/home.tsx`
-`themeMode === 'suixin'` 作为第三分支:
- 背景为纯色(`backgroundColor = suixinColor`
- 不显示风景图
- **冷启动**Home 首次进入时,读取用户画像与 `suixin.state`
- 若检测到新 boot 会话:重算并写入 `suixin.state`
- 否则:直接使用持久化的 `last_color`
- **切换文案**:在现有 `triggerNextContent` 成功切换索引后:
- 若当前主题为 `suixin`:递增 `step_index`,计算新的 `last_color`,并持久化
### 5.3 收藏Favorites背景记录兼容
`FavoriteItem.background` 当前对 `color``hex`,对 `scenery` 存图片索引。
- `suixin` 同样存 `hex`,与 `color` 分支一致即可
## 6. i18n 计划TC / EN
`client/src/i18n/locales/all.json` 增加:
- `theme.suixin`
- (可选)`theme.suixinDesc`(若 UI 后续展示描述)
英文命名采用语义化方案(本计划建议):
- EN`theme.suixin = "Ease"`
- TC`theme.suixin = "隨心"`
> 若后续品牌希望保留音译,也可改为 EN=`Suixin`,不影响技术实现。
## 7. 兼容性与迁移
- `ThemeMode` 的存储值新增 `'suixin'`
- 旧版本只会存 `'scenery'|'color'`,升级后兼容
- 若读取到未知值,继续回退 `'scenery'`
- 新增 `ui.theme.suixin.state`
- 若不存在,首次进入随心主题时初始化
## 8. 测试计划(最小可回归)
### 8.1 单元测试(推荐)
为颜色算法模块增加用例(可放在 `client/src/features/suixinTheme/__tests__/`
- `stage.unknown=1` → 必选 `neutral`
- `need={}` → 必选 `neutral`
- `need={rest_balance:1}` → 选 `rest_balance` Base Theme
- `step_index` 递增 → `t` 按往返波形变化且始终在 \([0,1]\)
- `emotion_score=null` / `<=0.3` → 不触发微扰逻辑
### 8.2 手动验收(与 spec 对齐)
- ThemeModal 能看到第三个主题并可切换
- 冷启动进入 Home随心背景根据画像选定主题色系
- 上滑切换文案:背景色在同主题内缓慢变化(无跨主题跳色)
- `stage.unknown=1``need` 跳过:背景为 Neutral Theme
- 切换语言:主题名称在 TC/EN 下正确显示
## 9. 风险与对策
- **三卡布局拥挤**:采用 `flexWrap`/缩小卡片尺寸,必要时改为横向滚动
- **“持久化”与“冷启动重算”矛盾**:以“状态落盘 + 冷启动重置 base theme/seed”方式兼容两者
- **颜色可读性风险**:一期先用主题中间色/插值结果,避免过饱和;必要时增加对比度检查(后续迭代)
## 10. 里程碑拆分(实现顺序)
- **M1基础接入**
- 扩展 `ThemeMode`ThemeModal 增加第三项与 i18n
- Home 增加 `suixin` 分支,背景可显示(先用 neutral 兜底)
- **M2算法落地 + 持久化**
- 新增 suixin 颜色模块Base/Neutral、pickTheme、lerp、t 生成)
- 新增 `suixin.state` 存取与冷启动/切换文案更新
- **M3回归与体验优化**
- 收藏背景记录兼容
- 测试补齐与边界修正unknown/跳过/缺画像)