DriftLedger/docs/design/ui-redesign-p5-plan.md
fengmengqi bf04400852 feat: OCR 模型按需下载 + 三层独立开关 + 日志持久化 + 原生浮层主题同步 + 文档重构
OCR 模型按需下载(P8 瘦身):
- 移除 plugins/ppocr/assets/ 内置模型(det 9.8MB + rec 21MB + dict),APK 减包 ~31MB
- 新增 modelDownloader.ts:优先从 HuggingFace/CDN 下载,兜底从 APK assets 拷贝
- OcrModule.kt 新增 setModelDir,支持从 filesystem 加载模型,回退 assets 兼容旧用户
- settingsStore 持久化 ocrModelVersion / ocrModelDir
- 自动化页集成模型状态检查与一键下载 UI

OCR 三层独立控制:
- OcrProcessorConfig 从单一 aiVisionEnabled 拆分为 layer1/2/3 三个独立开关
- 设置页可按层启停(L1 正则规则 / L2 本地 OCR / L3 AI Vision)
- OCR 处理增加耗时与字符数日志

日志系统升级:
- logger.ts 新增 LogFileBackend 抽象,支持磁盘持久化(按日期 app-YYYY-MM-DD.log)
- 新增 logBackend.ts(ExpoLogFileBackend)+ 日志中心页 settings/logs.tsx
- 日志中心:实时缓冲 + 历史文件、4 级过滤、Tag/关键词搜索、JSON 展开、分享导出、7 天过期清理
- _layout.tsx 启动时初始化文件后端 + 日志脱敏(验证码/卡号)

原生浮层 UI 主题同步(P6):
- 新增 floatingUiConfig.ts:JS 侧从 theme tokens + i18n 构建 FloatingUiConfig 推送原生
- 新增 FloatingUiConfigStore.kt:SharedPreferences 存储,三浮层组件读取
- FloatingBillView 重设计:颜色/文案走配置、新增币种 chip、金额校验改 BigDecimal
- FloatingHelper / FloatingTip 同步适配
- _layout.tsx 新增 FloatingUiConfigSyncer,主题/语言切换自动推送

UI 与组件增强:
- FormModal 新增 select/dropdown 控件、行内布局(row/flex)、联动回调 onValuesChange
- 新增 Touchable 通用触摸组件、AccountCreateModal 快速建账弹窗
- 信用卡页展示账单周期/到期还款日/本期应还/剩余可用额度,关联账户改下拉选择
- AI 设置页重做:OpenAI/Gemini/DeepSeek 预设 + 默认 URL/模型
- 引导页新增 Android 权限检查步骤(无障碍/通知/短信/存储/悬浮窗)

去重优化:
- 对手方匹配改为模糊包含(includes),双方均无对手方时判定低置信度重复
- DedupResult 新增 matchedItem 返回匹配对比项

文档重构:
- README.md 重写为入口索引(品牌更新 + 模块概览 + 文档导航表)
- 新增 AGENTS.md(AI 助手贡献指南)、docs/architecture.md(Mermaid 数据流/分层/OCR 级联图)
- 新增 docs/development.md(环境/命令/编码规范/测试/提交规范)、plugins/README.md
- UI 重设计文档(design spec + p1-p8)移入 docs/design/

其他:
- i18n 新增权限/信用卡详情/日志中心/AI 设置等翻译键
- ppocr Config Plugin 修复 import 注入去重;size-optimization 增强
- 新增测试:logger.test.ts、floating-ui-config.test.ts
2026-07-23 19:03:38 +08:00

15 KiB
Raw Permalink Blame History

UI 重设计 P5管理页模板化 + 清零收尾 Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax. 不执行任何 git add/commit(用户要求,改动留工作区)。不创建额外任务清单。

Goal: 7 个管理页套 ManagementScreen 模板、account 页与 3 个设置二级页统一 ScreenHeader、图表/fontFamily/emoji/星期头清理、numpadGlobalEntry 翻默认(含 modal 未保存守卫)、清零审计验收。

Spec: docs/ui-redesign-design.md §7.5/§8。已确认现状2026-07-22 审计hex 硬编码已清零(#[0-9A-Fa-f]{6} 在 src/ 仅剩 presets.ts/palette.tsfontFamily 残留 14 处emoji 5 处monthlySummary.ts 4 + ai/chat.tsx 1tag 页已是模板试点。

与 spec 的偏差①account 页是「账户浏览器」(类型 Tab + 开户/调余额/关户三个交互),不套 ManagementScreen只做 ScreenHeader 统一;②numpadGlobalEntry 翻默认只影响新安装(已持久化的 false 不覆盖,尊重用户选择)。

模板适配决策(已通读 7 页源码):

  • budget/rules/remark-template → 直套T1
  • category → headerContent 放支出/收入 chips新增按当前类型T2
  • recurring → 卡片保留显式编辑/删除按钮(用 handlers.openEdit/confirmDelete不用长按T2
  • credit-card → 富 renderItem账单盒直套T2

Task 1: 管理页模板化批次 1budget / rules / remark-template

Files:

  • Modify: src/app/budget/index.tsxsrc/app/rules/index.tsxsrc/app/remark-template/index.tsx

  • Step 1: 三页套 ManagementScreen(参照试点 src/app/tag/index.tsx 与模板 src/components/ManagementScreen.tsx

通用映射:页面 state 消失modal/editing 由模板持有);手写 header/addBtn/Alert/FormModal 全删;footercommon.clickEditLongDelete 提示;deleteConfirmTitle 传各页 t('xxx.deleteTitle')

budget/index.tsx

  • items={budgets}keyExtractor={b => b.id}addLabel={t('budget.add')}emptyText={t('budget.empty')}
  • renderItem保留现有 Card + 进度条结构Pressable onPress={openEdit} onLongPress={confirmDelete} 包裹)。today 改用 toDateString(new Date())(修掉 toISOString().slice(0,10) 的 UTC 偏移)
  • formFields5 字段name/amount decimal-pad/period/categoryAccount/startDatedefaultValue 从 editing 取
  • onSubmit保留校验amount parseFloat ≤ 0 → Alert + return falseadd/edit 分支调 addBudget/updateBudget
  • onDeleteremoveBudget(budget.id)deleteConfirmTextt('budget.deleteConfirm', { name: budget.name })

rules/index.tsx

  • items={[...rules].sort((a, b) => b.priority - a.priority)}
  • renderItem保留规则行narration/P 徽标/条件/目标账户+命中次数);删除 fontFamily: theme.typography.caption.fontFamily(审计项)
  • formFields7 字段priority numeric/counterpartyContains/memoContains/sourceAccount/categoryAccount/narration/tags
  • onSubmit保留现有 Rule 组装逻辑addRule {...rule, id: generateId('rule'), hits: 0} / updateRule

remark-template/index.tsx

  • 2 字段name/templaterenderItem 的模板文本 fontFamily: 'monospace'fontVariant: ['tabular-nums'](审计项)

  • onSubmitname 空回退 t('common.untitled')

  • Step 2: 验证

Run: npm run typecheck && npm test → 全绿 Run: grep -ln "FormModal\|arrow-back" src/app/budget/index.tsx src/app/rules/index.tsx src/app/remark-template/index.tsx → 无输出


Task 2: 管理页模板化批次 2category / recurring / credit-card

Files:

  • Modify: src/app/category/index.tsxsrc/app/recurring/index.tsxsrc/app/credit-card/index.tsx

  • Step 1: category/index.tsx — headerContent 类型 chips

  • 保留一个本地 stateconst [catType, setCatType] = useState<'expense' | 'income'>('expense')

  • headerContent:两个 chip支出/收入commonStyles.chip/chipActive切换 setCatType

  • items={categories.filter(c => c.type === catType)}

  • formTitleediting ? t('category.editTitle') : t('category.addTitle', { type: catType === 'income' ? t('category.income') : t('category.expense') })(插值语法以现有键为准,先 grep category.addTitle

  • formFields3 字段name/linkedAccount/keywords编辑时带 defaultValue

  • onSubmitadd 时 type: catTypelinkedAccount 空回退按类型Income:/Expenses:Uncategorizedkeywords 拆分逻辑保留edit 保留原逻辑

  • renderItem保留名称/linkedAccount/关键词行

  • Step 2: recurring/index.tsx — 显式按钮用 handlers

  • renderItem保留信息行 Card卡片底部两个按钮编辑/删除)分别调 handlers.openEdit() / handlers.confirmDelete()用长按,交互更显式);外层 Pressable 去掉

  • formFields6 字段;new Date().toISOString().slice(0, 10) 两处 → toDateString(new Date())

  • onSubmit保留校验/draft 组装/成功 Alert返回 true校验失败 Alert 后 return false

  • 成功 Alert 在 onSubmit 内setModal(null) 由模板接管——删除手动 setModal 调用)

  • Step 3: credit-card/index.tsx — 富 renderItem 直套

  • accountBalances useMemo 保留在页面顶层

  • renderItem保留整张卡银行/账单日/还款日/额度/账单盒Pressable onPress={openEdit} onLongPress={confirmDelete}

  • formFields8 字段onSubmit 保留组装逻辑

  • Step 4: 验证

Run: npm run typecheck && npm test → 全绿 Run: grep -ln "FormModal\|arrow-back" src/app/category/index.tsx src/app/recurring/index.tsx src/app/credit-card/index.tsx → 无输出


Task 3: ScreenHeader 统一account + settings 三页)

Files:

  • Modify: src/app/account/index.tsxsrc/app/settings/sync.tsxsrc/app/settings/ai.tsxsrc/app/settings/preferences.tsx

  • Step 1: account/index.tsx

手写 headerarrow-back + title替换为 <ScreenHeader title={t('account.title')} />(组件保持其余逻辑不变:类型 Tab/开户 FormModal/调余额 FormModal/关户 Alert删除 styles.header。调余额表单的 date 默认值 new Date().toISOString().slice(0, 10)toDateString(new Date())

  • Step 2: settings/sync.tsx、ai.tsx、preferences.tsx

三页手写 header约在 sync.tsx:460、ai.tsx:51、preferences.tsx:44替换为 ScreenHeader标题沿用原文案键删除对应 styles.header 与不再使用的 importIonicons/router 若仅 header 使用。sync.tsx 546 行,只动 header 块,其余不动。

  • Step 3: 验证

Run: npm run typecheck && npm test → 全绿 Run: grep -rn "arrow-back" src/app/ | grep -v ScreenHeader → 无输出


Task 4: 图表与杂项清理fontFamily / emoji / 星期头 i18n

Files:

  • Modify: src/components/charts/CategoryPie.tsxsrc/components/charts/TrendLine.tsxsrc/components/charts/NetWorthChart.tsxsrc/components/CalendarView.tsxsrc/components/charts/CalendarHeatmap.tsxsrc/components/DatePickerField.tsxsrc/components/NumpadKeyboard.tsxsrc/components/NumpadSheet.tsxsrc/ai/monthlySummary.tssrc/app/ai/chat.tsxsrc/app/transaction/[id].tsxsrc/app/_error.tsxsrc/app/import/index.tsxsrc/components/PostingEditor.tsxsrc/i18n/zh.tssrc/i18n/en.ts

  • Step 1: fontFamily 清零

  • theme.typography.X.fontFamily 引用CategoryPie 2 处、TrendLine 4 处、ai/chat.tsx 1 处直接删除该属性P1 后 typography 无 fontFamily 字段,这些是无效引用)

  • fontFamily: 'monospace'

    • 金额/数字场景CategoryPie:35、transaction/[id].tsx:314、PostingEditor:63、import/index.tsx:391fontVariant: ['tabular-nums']
    • 纯文本场景_error.tsx:70 堆栈文本)→ 保留 monospace堆栈对齐需要或删除——保留加注释说明
  • NetWorthChart/CalendarHeatmap 顺便检查一遍grep 未命中但过一眼 token 合规)

  • Step 2: emoji 清零

  • src/ai/monthlySummary.ts:81-85:剥离 📊/💰/💸/📝 前缀,保留纯文本行(如 '总收入:...'

  • src/app/ai/chat.tsx:123:方向 emoji💰/🔄/💸替换为符号文本income ''、transfer '⇄'、expense ''(或直接金额上色——读上下文选与周边一致的处理;金额色用 theme.colors.financial.*

  • Step 3: 星期头 i18n3 处硬编码中文)

  • i18n 加键zh/en 对等,单键逗号分隔):'datepicker.weekdays': '一,二,三,四,五,六,日' / 'Mo,Tu,We,Th,Fr,Sa,Su'周一起DatePickerField 用);'calendar.weekdays': '日,一,二,三,四,五,六' / 'Su,Mo,Tu,We,Th,Fr,Sa'周日起CalendarView/CalendarHeatmap 用)

  • DatePickerField.tsxCalendarView.tsxCalendarHeatmap.tsx 的 WEEKDAYS 常量改为 t('datepicker.weekdays').split(',') / t('calendar.weekdays').split(',')(组件内 useT注意保持周一/周日起始顺序与各自网格一致,不要换序)

  • Step 4: NumpadKeyboard backspace 标签 i18n

  • i18n 加键:'numpad.backspace': '退格' / 'Backspace'

  • NumpadKeyboard props 加 backspaceLabel: stringbackspace 键 accessibilityLabel 用它;NumpadSheet.tsx 调用处传 t('numpad.backspace')

  • Step 5: 验证

Run: npm run typecheck && npm test → 全绿i18n 对等校验覆盖新键) Run: grep -rn "fontFamily" src/ --include="*.tsx" | grep -v "_error.tsx" → 无输出


Task 5: 收尾(翻默认 + modal 守卫 + MonthlyReport 删除 + budgets 时区修复 + duplicate 增强)

Files:

  • Modify: src/store/settingsStore.tssrc/components/NumpadSheet.tsxsrc/components/NumpadSheetHost.tsxsrc/domain/budgets.tssrc/app/(tabs)/transactions.tsxtests/budgets.test.ts(或现有 budget 测试文件)

  • Delete: src/components/charts/MonthlyReport.tsx

  • Step 1: numpadGlobalEntry 默认翻 true

src/store/settingsStore.ts DEFAULTS 中 numpadGlobalEntry: falsetrue注意:已持久化 false 的用户不受影响hydrate 合并优先级),仅新安装生效——在代码注释中写明。

  • Step 2: modal 模式未保存守卫(翻默认的前置)

  • NumpadSheet.tsx:加可选 prop onDirtyChange?: (dirty: boolean) => void;把 beforeRemove 守卫里的 dirty 计算抽成 computeDirty()(读 formRefuseEffect(() => { onDirtyChange?.(computeDirty()); })(每次渲染后上报,轻量)

  • NumpadSheetHost.tsxconst dirtyRef = useRef(false)NumpadSheet 传 onDirtyChange={d => { dirtyRef.current = d; }}overlay onPress 与 Modal onRequestClose 改调 requestClose

const requestClose = () => {
  if (!dirtyRef.current) { close(); return; }
  Alert.alert(t('transaction.unsavedTitle'), t('transaction.unsavedMessage'), [
    { text: t('common.cancel'), style: 'cancel' },
    { text: t('common.discard'), style: 'destructive', onPress: close },
  ]);
};

Host 需 useT + Alert importcommon.discard 键先 grep 确认存在。)

  • Step 3: 删除 MonthlyReport.tsxP4 确认无引用的死文件)

Run: rm src/components/charts/MonthlyReport.tsx,再 grep -rn "MonthlyReport" src/ → 无输出

  • Step 4: budgets.ts getPeriodRange 本地时区修复

src/domain/budgets.tsgetPeriodRange(约 93 行):new Date('YYYY-MM-DD') 是 UTC 解析,负时区会把边界日算到前一天。改为 split 构造本地 Dateconst [y, m, d] = dateStr.split('-').map(Number); new Date(y, m - 1, d)),与 periodNav 的 parse 一致。在现有 budget 测试文件(ls tests/ | grep -i budget)追加边界用例:getPeriodRange('monthly', '2026-07-01') 的 start 必须是 '2026-07-01'、end '2026-07-31'getPeriodRange('weekly', ...)/yearly 各补一例(先读现有测试风格与函数实际行为写期望值——若修复改变了现有行为导致旧测试失败,停止报告 BLOCKED

  • Step 5: handleDuplicate 带 tags

src/app/(tabs)/transactions.tsx 的 handleDuplicate draftJson 加 tags: tx.tags;同时检查 NumpadSheet.tsx 的 draftJson 预填 effect——目前不解析 tags参照 editId 预填的 tags 回填逻辑tags store 查名 → fallback TAG_COLORS[3])补上 draft.tags 解析(Array.isArray(draft.tags)。cost/price 不带hasExtras 逻辑已保证含 cost 交易从编辑入口进高级模式;复制是新建,丢 cost 属可接受简化,注释说明)。

  • Step 6: 验证

Run: npm run typecheck && npm test → 全绿


Task 6: P5 清零审计 + 全项目验收

  • Step 1: 清零审计(全部应无输出或仅剩允许项)
grep -rn "#[0-9A-Fa-f]\{6\}" src/ --include="*.ts" --include="*.tsx" | grep -v "theme/presets.ts\|theme/palette.ts"   # 无输出
grep -rnE "#[0-9A-Fa-f]{3}\b" src/ --include="*.ts" --include="*.tsx" | grep -v "theme/presets.ts\|theme/palette.ts" # 无输出
grep -rn "fontFamily" src/ --include="*.tsx"                                                                       # 仅 _error.tsx monospace
grep -rn "📊\|💰\|💸\|📝\|🔄\|🎉\|✨" src/ --include="*.ts" --include="*.tsx"                                       # 无输出
grep -rn "MonthlyReport\|SpeedDial" src/                                                                            # 无输出
grep -rn "arrow-back" src/app/                                                                                      # 无输出ScreenHeader 内部除外)
grep -rn "WEEKDAYS" src/components/                                                                                 # 无输出(全部走 i18n
  • Step 2: 全量测试 + typecheck

Run: npm test → 全绿 Run: npm run typecheck → 无错误

  • Step 3: 手工走查(需设备/模拟器)

Run: npm run android 走查清单(浅/暗双主题):

  1. 6 个管理页(分类/预算/规则/周期/信用卡/备注模板新增、点按编辑、删除recurring 为显式按钮,其余长按)、空态文案
  2. account 页:类型 Tab、开户、调余额、关户
  3. settings/sync、ai、preferencesheader 统一有返回箭头
  4. +按钮默认开全局面板(新装/清数据后):任意页面弹 modal输入金额后点遮罩/Android 返回 → 弹「放弃修改」确认;无输入直接关
  5. 设置→偏好里开关关闭 → +回到跳整页
  6. 交易页左滑复制带标签的交易 → 标签预填
  7. 报表月 Tab 日历、年报节奏图、TrendLine/CategoryPie 显示正常
  8. 英文语言:日期选择器/日历星期头为英文缩写

完成后的项目状态

P1P5 全部交付spec §8 验收标准全达成。剩余已知非阻塞项不修记录TransactionCard memo 被内联 onPress 击穿性能todayStr useMemo 跨午夜不刷新NumpadSheet page 模式无可见返回按钮spec 允许_error.tsx monospace 保留(堆栈对齐)。