Co-Authored-By: Claude Code <noreply@anthropic.com>
17 KiB
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 方案完成 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 兼容)
- AlbumSummary +=
-
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