docs(plan): UI-01 implementation plan from spec analysis

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-21 12:56:35 +08:00
parent 92e64b7f10
commit 6754cd4d44
+269
View File
@@ -0,0 +1,269 @@
# UI-01 App 与悬浮组件体验优化 实施计划
> **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:** 按 [UI01 方案](2026-09-21-UI01-App与悬浮组件体验优化实施方案.md)完成 App 与 JS 悬浮组件的产品化体验升级:统一主题、图片主导布局、上传状态清晰化、组件完整图片模式与可访问性——同时补齐封面摘要、受权限管理照片预览等必要契约。
**Architecture:** 纯 UI/UX 层改造 + 少量兼容契约扩展。不重写上传引擎/原生桥/同步架构;不改数据库 schema(仅 openapi.yaml 可选字段 + 服务端组装逻辑);Flutter 复用 Material + Riverpod;embed loader 保持零依赖手写。
**Tech Stack:** 既有全栈(Flutter 3.44 / NestJS / Vue3 / 手写 loader),零新框架依赖。
**对应验收:** UI-A01 至 UI-A16(方案 §9.1)。
**依赖基线:** 当前 HEAD(含 P5 准备件 + G1 + 测试服务器部署中产生的修复)。
---
## 方案分析与关键决策
### 分析结论
| 维度 | 评估 |
|---|---|
| 完整性 | 方案文档极完整:设计值精确到 px、状态机表、ASCII 布局图、验收矩阵——不需要再问用户常规样式问题 |
| 复杂度 | 中高:Flutter 端 ~10 个页面重构 + 主题系统 + 契约扩展;embed 端 loader+iframe+admin 预览全链路 |
| 风险点 | ① 相册卡片封面→需要契约扩展 cover 字段 ② 协作者 own 照片列表→需要 scope=own 查询语义 ③ embed 新配置字段→需要 cn→ov 投影验证 ④ 跨源 Playwright 宿主测试需要双端口环境 |
| 与既有代码的关系 | 零破坏性:新字段全部可选/nullable;旧客户端不 break;组件 sm/md 保留兼容 |
### 实施切分策略
方案 §8 定义了 U00-U09 十个任务,依赖链 U00→U01→U02→U03/U04/U05→U06→U07→U08→U09。为子代理执行效率优化为:
- **M0 基线**:U00 截图 + 契约预研
- **M1 契约与数据**:U01 全量(cover + preview + scope=own + embed config)
- **M2 App 主题与导航**:U02(设计系统 + 底部导航 + 相册卡片样板)
- **M3 App 相册与照片**:U03(封面卡片 + 详情 + 照片网格 + 大图 + 权限操作)
- **M4 App 上传与拍摄**:U04 + U05(上传中心 + 拍摄 + 表单/分享/设置统一)
- **M5 嵌入组件**:U06(loader + iframe 轮播 + 状态 + 可访问性)
- **M6 后台预览**:U07(展示位配置 + 本地预览)
- **M7 回归与验收**:U08 + U09(全量矩阵 + 截图 + 记录 + 交接)
---
## Task 1: U00 基线与现状截图
**Files:**
- Create: `docs/plans/progress/UI01-验收记录.md`(骨架)
- 采集截图目录:`docs/plans/progress/ui01-screenshots/baseline/`
- [ ] Git 状态核对(未提交改动记录,不清理不覆盖);记录当前 HEAD
- [ ] 启动本地全栈环境(compose + dev services);不可用的页面标注原因并跳过截图
- [ ] Flutter App 截图:相册首页、详情、照片管理、上传队列、拍摄、创建/编辑、分享、设置(8 张,MuMu 模拟器)
- [ ] Embed 组件截图:展开态、收起态、空态、宿主页(4 张,viewer e2e 宿主页面)
- [ ] Admin 后台截图:展示位配置当前表单(1 张)
- [ ] 初始化验收记录骨架(基线 commit、环境信息、截图索引)
- [ ] Commit `docs(ui01): baseline screenshots and acceptance record skeleton`
---
## Task 2: U01 契约与数据配合
**Files:**
- Modify: `packages/contracts/openapi.yaml`(+gen)
- Modify: `services/api/src/albums/`(cover 摘要批量组装)
- Modify: `services/api/src/photos/`(preview + scope=own)
- Modify: `services/api/src/embed/`(新配置字段校验)
- Modify: ov 侧展示位公开响应投影
- Modify: `apps/mobile/lib/features/`(Flutter 模型新字段)
- Test: e2e 权限矩阵
- [ ] **契约扩展**(全部可选/nullable,旧客户端零影响):
- AlbumSummary += `cover: {url, expiresAt, width, height} | null`(批量查 album_assets kind=cover,无封面返回 null)
- AdminPhoto += `preview: {thumb: SignedUrl, browse: SignedUrl} | null`(有变体才签发;已删除返回 null;隐藏给有权限管理者)
- listAlbumPhotos query += `scope: 'all' | 'own'`(默认 all=现有行为不变;own=协作者仅自己上传,服务端过滤)
- EmbedSlotSettings += `imageFit: 'contain'|'cover'`、`offsetX: 0-120`、`offsetBottom: 0-240`、`zIndex: 1-2147483000`(全部带默认值,旧 JSON 兼容)
- [ ] gen + lint + 全 web 回归绿(旧字段缺省兼容确认)
- [ ] **服务端实现**:
- cover:albums 列表按页批量查 assets(不 N+1),签发短期 URL
- preview:photos 管理列表按权限逐张判断(admin 全部/owner 本相册/collab 仅自己),scope=own 服务端过滤+成员资格逐次检查
- embed config:新字段在 cn 管理 PATCH 校验 + slot-state payload 携带 + ov 公开响应返回
- [ ] **e2e 权限矩阵**(~12 用例):admin/owner/collab-own/collab-other/cross-album × cover/preview/scope;隐藏照片有权限可见无权限 404;删除照片 preview=null;旧请求无新字段正常
- [ ] **同步验证**:新配置字段经 slot-state 到 ov 投影完整传递
- [ ] **Flutter 模型**:Dart 模型增加 cover/preview/scope 字段(nullable,旧响应不 break)
- [ ] Commit `feat(contracts,api): UI-01 cover, preview, scope=own and embed config extensions`
---
## Task 3: U02 设计基础与导航
**Files:**
- Create: `apps/mobile/lib/ui/tokens.dart`(色彩/间距/圆角/字号常量)
- Create: `apps/mobile/lib/ui/components/`(StateBadge/EmptyState/ErrorState/PhotoThumbnail/SectionHeader)
- Modify: `apps/mobile/lib/app/theme.dart`(完整 ColorScheme+TextTheme+组件主题)
- Modify: `apps/mobile/lib/app/app.dart`(导航标签改名)
- Modify: `apps/mobile/lib/pages/home_page.dart`(底部导航三 tab + 角标)
- Modify: `apps/mobile/lib/l10n/app_zh.arc / app_en.arb`
- [ ] **主题系统**:
- 浅色 ColorScheme(方案 §3.1 色值精确落地:primary=#1A73E8, surface=#FFFFFF, background=#F6F8FB 等)
- 深色 ColorScheme(对应语义色,不反转图片)
- TextTheme(22-24 标题 / 17-18 分组 / 14-16 正文 / 12-13 辅助)
- 按钮(主操作高 48-52)、输入框(圆角 12)、AppBar、NavigationBar、Dialog、BottomSheet 主题
- 动效时长常量:轻量 150-220ms / 轮播 250-350ms / reducedMotion 时禁用非必要动效
- [ ] **公共组件**(仅跨页面复用的,不为每个 SizedBox 封装):
- StateBadge:成功/等待/错误/中性 + 图标 + 文案(不只靠颜色)
- EmptyState:图标+标题+说明+可选操作按钮
- ErrorState:错误说明+重试按钮
- PhotoThumbnail:网络图片+占位+失败替代图+宽高比预留
- SectionHeader:分组标题
- [ ] **底部导航**:三 tab 改名"相册/上传/设置"(Albums/Uploads/Settings);上传 tab 角标=当前账号未完成任务数(drift countsByState 聚合,不跨账号);IndexedStack 保持页面状态
- [ ] **相册卡片样板页**:完成一张可运行的封面卡片(封面图+标题+时间地点+公开/上传状态),同时验证浅色/深色/中英文
- [ ] flutter analyze 0 + 新增 widget 测试(~10:主题一致性、StateBadge 各状态、PhotoThumbnail 加载/失败/占位、EmptyState、角标绑定)
- [ ] Commit `feat(mobile): UI-01 theme system, shared components and navigation`
---
## Task 4: U03 相册与照片工作流
**Files:**
- Modify: `apps/mobile/lib/pages/albums_tab.dart`(封面卡片列表)
- Modify: `apps/mobile/lib/pages/album_detail_page.dart`(紧凑信息+底部上传)
- Rewrite: `apps/mobile/lib/pages/album_photos_page.dart`(网格+大图)
- Modify: `apps/mobile/lib/core/api_client.dart`(scope=own 参数 + preview 刷新调用)
- Modify: `apps/mobile/lib/features/photos/models.dart`
- [ ] **相册列表**:
- 卡片顺序:封面 → 标题(≤2 行)→ 时间/地点 → 公开/上传状态 badge
- 移除 publicToken 和四开关堆叠(完整配置在详情设置)
- 手机单列 / 平板两列(按可用宽度,不挤压英文标题)
- 无封面固定比例中性占位;网络失败替代图+重试
- 标题回退:界面语言 → 相册默认语言 → 首个有效翻译
- 时间用相册时区(不用设备时区)
- [ ] **相册详情**:
- 布局:紧凑封面+信息 → 开关状态 → 照片入口/预览 → 次级管理入口(编辑/成员/分类/分享进工具菜单)
- 底部固定"拍摄上传"(主按钮)+ "从相册选择"(次按钮),避让安全区
- 公开/上传分开显示;上传关闭时说明原因+禁用
- 无照片→"拍摄第一张照片"引导入口
- [ ] **照片管理网格 + 大图**:
- 三列网格(极窄两列,平板按最小格宽扩列);缩略图 cover 裁切
- 角标:处理中/失败/隐藏/删除
- 点开大图:contain 展示、左右切换、按需加载更多、关闭回原位置
- 大图详情:上传者/时区时间/尺寸大小(ID 放诊断区域)
- 隐藏/恢复/删除单张操作(权限校验+确认弹窗)
- 签名 URL 过期→一次受控刷新
- 协作者 own 列表(scope=own,服务端过滤)
- [ ] widget 测试(~15:封面有无/标题回退/网格列数/大图切换返回/角标状态/权限操作/URL 刷新)
- [ ] Commit `feat(mobile): UI-01 album cards, photo grid and full viewer`
---
## Task 5: U04+U05 上传中心 + 辅助页面统一
**Files:**
- Modify: `apps/mobile/lib/pages/queue_tab.dart`(摘要+状态分组)
- Modify: `apps/mobile/lib/pages/capture_page.dart`(深色取景+反馈)
- Modify: `apps/mobile/lib/pages/album_form_page.dart`(分组表单)
- Modify: `apps/mobile/lib/pages/share_qr_dialog.dart`(分层布局)
- Modify: `apps/mobile/lib/pages/login_page.dart` / `password_change_page.dart` / settings section
- Modify: `apps/mobile/lib/pages/server_history_page.dart`
- [ ] **上传中心**:
- 顶部摘要条(筛选范围标注:"今天·本设备"):82 完成 3 上传中 13 等待 2 需关注
- 状态 chips:全部/进行中/需关注/已完成
- 状态行映射(方案 §4.4 表格完整落地,每种状态独立文案+颜色+图标+操作)
- "一键重试"仅作用于可重试任务,文案说明范围
- "移除记录"替代"清理"(明确不删 OSS 照片)
- 进度行级订阅(UploadProgressHub 已有,避免整列表重建)
- 本机队列与服务端历史分开标题+说明
- [ ] **拍摄页**:
- 深色取景器;顶部相册/分类常驻;底部快门+入队计数
- 入队成功短暂反馈(不逐张弹窗);连拍模式切换只影响新照片提示
- [ ] **创建/编辑表单**:
- 分组:活动信息 / 多语言内容 / 访问设置
- 默认语言必填;另一语言按需填写(tab 切换)
- 保存按钮键盘打开时可达(Scrollable + bottomNavigationBar)
- [ ] **分享**:名称/二维码/链接清晰分层;复制链接必做
- [ ] **设置**:账号信息/语言/密码/上传帮助/版本分组;退出不承诺后台继续上传
- [ ] **登录/改密**:样式统一(主题组件应用)
- [ ] widget 测试(~12:摘要计算/状态映射/分组表单校验/分享布局/设置分组)
- [ ] Commit `feat(mobile): UI-01 upload center, capture and auxiliary pages`
---
## Task 6: U06 嵌入轮播与 loader
**Files:**
- Modify: `packages/embed-loader/src/loader.js` / `params.js`
- Modify: `packages/embed-loader/src/loader.css`(新增:舞台样式/收起入口/响应式)
- Modify: `apps/viewer/src/pages/EmbedPage.vue`
- Modify: `apps/viewer/src/embed/useEmbedFeed.ts` / `feed.ts` / `postMessage.ts`
- Create: `apps/viewer/src/pages/embed/` 组件拆分(Carousel/Stage/Footer/CollapsedHandle)
- Modify: `packages/embed-loader/README-embed.md`
- [ ] **loader 升级**:
- 新配置字段支持:imageFit/offsetX/offsetBottom/zIndex(校验+合并,优先级 data-* > config消息 > 后台设置 > 默认)
- 收起入口重新设计:图片图标+Photos/照片,高≥44px,把手有效点击区≥44×44
- 修复内联 SVG fill="%23ffffff" 为有效颜色
- 多 slot 同侧纵向排列+间距;高度不足时后续收起
- 窄屏(<480px)默认收起+sm 尺寸
- destroy 消息接收(白名单 origin+source+type 严格校验)→ 移除卡片+把手+监听器
- config 动态消息接收(configVersion 校验+旧请求丢弃+loader 二次验证)
- 窗口 resize/横竖屏切换重新布局
- [ ] **iframe 轮播页重构**:
- 组件拆分:Carousel(切换逻辑)/ Stage(4:3 图片舞台)/ Footer(名称+页码+入口)/ CollapsedHandle
- object-fit: contain 默认(中性深色舞台);cover 可配置
- 页码"当前序号/队列数量"替代圆点
- 预加载:仅当前+下一张;decode 成功再切换;失败保留上一张+尝试后续
- 暂停/恢复:用户暂停/指针悬停/键盘焦点/页面隐藏/组件收起 全部停止;恢复后校准一次
- reducedMotion 时取消过渡+默认暂停+手动上/下一张按钮
- **轮播与取数彻底解耦**:轮播 intervalMs 不影响取数轮询周期(30000ms 基准)
- ETag 304 过期 URL 处理:304 后签名仍可能过期→强制刷新路径(有限重试)
- 停用/解绑/404/410 → postMessage destroy(loader 移除自身)
- [ ] **可访问性**:
- 隐藏轮播帧不可 Tab/读屏(aria-hidden + 仅渲染当前可交互帧)
- 所有操作 button/a 语义+中英文标签+可见焦点
- 图片 aria-label="活动照片,第 N 张"(不编造描述)
- [ ] **宿主测试环境**:
- 新增跨源 Playwright 宿主:两个端口(如 5175+5176 模拟不同 origin),测试白名单拒绝非法 origin/source/config
- 模拟宿主全局 button CSS 干扰、多 slot 同侧
- [ ] 单测(loader params ~15:缺省/显式/config 消息合并+校验+边界) + vitest(viewer ~12:暂停恢复/计时器独立/失败图片/签名刷新/非当前帧不可聚焦)
- [ ] Playwright(~8:跨源接入/收起展开暂停/键盘焦点/多 slot/全局 CSS 干扰/destroy/304+URL 过期/窄屏)
- [ ] build + sync loader(带内容哈希产物 + 旧 v1 兼容入口保留)
- [ ] Commit `feat(embed,viewer): UI-01 complete carousel, loader config and accessibility`
---
## Task 7: U07 后台配置与预览
**Files:**
- Modify: `apps/admin/src/views/system/embed-slots/`(表单+预览)
- Modify: `apps/admin/src/api/embedSlots.ts` / `types/api/embedSlot.d.ts`
- [ ] **配置表单增强**:
- 新增:左右位置选择、尺寸(sm=280/md=320)、轮播间隔滑块、语言选择、收起默认开关、图片适配(contain/cover)、水平偏移(0-120px)、底部偏移(0-240px)、层级(zIndex)
- 保存才更新线上(表单状态 vs 已保存状态视觉区分)
- [ ] **本地预览**:
- 表单旁/下方:桌面预览(320px 框)+ 手机预览(375px 框)
- 未保存修改即时反映到预览(纯本地,不写展示位不上报浏览)
- 预览使用本地示例数据(渐变色块+文字标注"预览")
- 预览与生产组件共享展示结构(CSS 变量/组件引用,非复制两套)
- [ ] **嵌入代码卡**:只包含用户显式选择的 data-* 覆盖,默认不填(让后台配置生效)
- [ ] vitest(~6:表单→payload 新字段、预览状态绑定、代码卡只含显式覆盖)
- [ ] Playwright(~4:表单配置→预览更新→保存→ov 响应新字段、代码卡内容验证)
- [ ] Commit `feat(admin): UI-01 embed slot config form and live preview`
---
## Task 8: U08+U09 回归验收 + 文档交接
- [ ] **全量回归**:`pnpm lint && typecheck && test && build && e2e` + `flutter analyze && flutter test` + loader build+sync
- [ ] **UI 验收矩阵**(方案 §9.1 UI-A01 至 UI-A16 逐项):
- 截图采集:优化后全部页面+组件(对照 Task 1 基线)
- 320/390/430/768/1440 逻辑宽度覆盖
- 中英文×浅色深色
- 记录设备/视口/语言/主题
- [ ] **性能记录**(改造前后对比):首屏请求数、组件预加载数、loader gzip 大小(不编百分比)
- [ ] **验收记录**:完成 UI01-验收记录.md 全部章节(方案 §10 模板)
- [ ] **总纲更新**:UI-01 状态注记(不标记 P5 完成)
- [ ] Commit + tag `ui01`
---
## 执行注意
- **保护规则**(方案 §2 全文有效):不覆盖已有未提交改动、不重写上传引擎/原生桥/同步架构、不通过观众 API 获取管理照片、不新增重型 UI 框架、所有文案进语言资源
- **契约兼容红线**:新字段全部 nullable/optional;旧客户端零 break;不做破坏性 schema 变更
- **性能红线**:相册列表封面批量查询(不 N+1);上传进度行级订阅(不整列表重建);组件仅预加载当前+下一张
- **组件安全红线**:postMessage 双向白名单(origin+source+type);loader 不向宿主注入全局 CSS;外链 noopener/noreferrer
- Flutter 命令前置 `source scripts/flutter-env.sh`
- 每任务 TDD + 提交带 Co-Authored-By trailer