Files
photo-live/docs/plans/2026-09-21-UI01-实施计划.md
T
2026-09-21 12:56:35 +08:00

270 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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