Files
photo-live/docs/plans/2026-09-21-UI02-实施计划.md
T
2026-09-21 21:01:59 +08:00

217 lines
12 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-02 观众页视觉重构 实施计划
> **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:** 按 [UI02 方案](2026-09-21-UI02-观众页效果图落地实施方案.md)和[设计概念](../designs/viewer-mobile-concept-2026-09-21.png)完成观众页视觉重构:蓝白活动首页(品牌栏+宣传图+信息+筛选+瀑布流)+ 深色沉浸式大图查看器(缩略图导航+点赞粒子+分享下载)——不改变任何业务逻辑。
**Architecture:** 纯 viewer 前端改造(apps/viewer)。不新增 API/契约变更(UI-02 §9.3 明确:如果实现发现必须扩展,先记录再兼容变更)。不修改 App/admin/embed。样式作用域隔离(.viewer-shell / .album-page / .photo-viewer),不影响 EmbedPage。
**Tech Stack:** Vue 3 + TypeScript + CSS(新 CSS 变量文件),零新框架依赖。
**对应验收:** V-A01 至 V-A15(方案 §9.2)。
**依赖基线:** 当前 HEAD(含 UI-01 tag ui01)。
---
## 方案分析
| 维度 | 评估 |
|---|---|
| 复杂度 | 高——PhotoLightbox 完全重构(缩略图条+底部操作栏+粒子动效),PhotoFeed 重排(响应式列数+点赞角标+新图提示) |
| 风险点 | ① EmbedPage 共用 viewer 工程——样式必须作用域隔离 ② 缩略图条与 usePhotoSequence 的集成 ③ 粒子动效的 DOM 数量控制 ④ 位置计数的真实回退逻辑(不伪造总数) |
| 与 UI-01 的关系 | UI-01 已改 embed 组件样式——UI-02 只改普通相册路由的样式,通过 CSS 作用域隔离 |
| 不变项 | usePhotoSequence 单一数据源、useLikes 业务逻辑、useAlbumRealtime WS/轮询、useDownload 权限、路由语义、浏览去重上报 |
---
## Task 1: V00 基线与代码核对
- [ ] Git 状态核对(UI-01 已提交,工作树应清洁);记录当前 HEAD
- [ ] 启动 viewer dev server + 后端;截图当前首页(zh/en)+ 大图(zh/en)作为基线
- [ ] 阅读 ActivityPage.vue / PhotoFeed.vue / PhotoLightbox.vue / usePhotoSequence / useLikes / useAlbumRealtime / useDownload / zoom.ts——记录每个文件的职责和现有数据流
- [ ] 确认 EmbedPage 的样式不受影响(检查 App.vue 中的布局条件)
- [ ] 创建 `docs/plans/progress/UI02-验收记录.md` 骨架
- [ ] Commit `docs(ui02): baseline screenshots and acceptance skeleton`
---
## Task 2: V01+V02 视觉基础 + 首页信息区
**Files:**
- Create: `apps/viewer/src/styles/viewer-tokens.css`(全部 CSS 变量)
- Modify: `apps/viewer/src/App.vue`(普通布局顶栏+容器)
- Modify: `apps/viewer/src/components/LocaleSwitcher.vue`(紧凑化)
- Create: `apps/viewer/src/components/album/AlbumHeader.vue`(宣传图+信息区)
- Modify: `apps/viewer/src/pages/ActivityPage.vue`(使用新组件)
- [ ] **CSS 变量**(方案 §3.1 精确值):
```css
.viewer-shell {
--viewer-primary: #1A73E8;
--viewer-surface: #FFFFFF;
--viewer-page-bg: #F3F6FA;
--viewer-text: #172033;
--viewer-text-secondary: #647084;
--viewer-divider: #E5EAF1;
--viewer-dark-bg: #0E1218;
--viewer-like-accent: #FF667D;
--viewer-radius-img: 4px;
--viewer-radius-pill: 20px;
--viewer-padding-info: 16px;
--viewer-padding-photo: 4px;
}
```
- [ ] **App.vue 顶栏**:品牌名(左)+ 语言切换(右),高 48-52px,白色背景,底部细线。减少动画入口放语言旁的轻量菜单(可发现但不占整行)。普通路由有顶栏;embed 路由无顶栏无外边距(验证不被破坏)
- [ ] **LocaleSwitcher**:紧凑 "EN ▾" 形态(非全宽下拉)
- [ ] **AlbumHeader.vue**(新组件):
- 宣传图全宽(约 2.4:1 手机,桌面限高 320px,contain 优先不裁切海报文字;无图收起)
- 标题(≤2 行 + 展开查看)+ 分享按钮(右侧,不压缩标题)
- 说明(≤3 行 + 展开/收起,保留换行)
- 元信息行:浏览次数(真实 viewCount, Intl.NumberFormat,未知显示 "—")+ 日期(相册时区)+ 地点;窄屏可换行
- 缺字段自然省略(null/undefined 不显示)
- [ ] **排序/分类工具栏**:
- Latest / Popular tab(高 44px,选中蓝字+底部 2px 线)
- 分类胶囊(选中蓝底白字;横向滚动+溢出提示;键盘可访问)
- 无分类省略分类条;All = 不传分类筛选
- [ ] vitest:token 值断言、AlbumHeader 各字段渲染/缺失省略/展开收起、工具栏 tab 切换回调
- [ ] Commit `feat(viewer): UI-02 visual foundation, top bar and album info`
---
## Task 3: V03 瀑布流重排
**Files:**
- Modify: `apps/viewer/src/components/PhotoFeed.vue`
- Modify: `apps/viewer/src/feed/layout.ts`
- [ ] **响应式列数**:
- <600px: 2 列, gap 4px
- 600-959px: 3 列, gap 6px
- ≥960px: 4 列, gap 8px(主内容 max-width 1120px)
- 窗口 resize 重新布局(现有 ResizeObserver 或 LayoutBuilder)
- [ ] **图片卡片重设计**:
- 无白色厚卡片、无常驻文件名、无额外标题
- 圆角 4-6px
- 右下角:心形图标 + 真实点赞数(半透明暗色底确保可读)
- 点赞控件独立交互(阻止冒泡,不嵌套 button)
- 图片点击 → 打开大图(现有行为保留)
- [ ] **新照片提示**:
- 白色 pill "有新照片 ↑",点击加载合并(现有行为)
- 只有确切数量时才显示数字("8 new photos"),否则只写"有新照片"
- 不在用户浏览时强制跳到顶部
- [ ] **骨架与加载**:
- 首屏骨架按真实瀑布流比例占位(不是均匀网格)
- 翻页加载失败底部重试(不清空已有图片)
- "已到底" 文案
- [ ] **宽高安全**:缺失/异常宽高用安全占位比例(避免 NaN/无限高度)
- [ ] vitest:列数计算(320/600/960 断点)、点赞角标渲染+冒泡阻止、新图提示显隐、骨架占位比例
- [ ] Commit `feat(viewer): UI-02 responsive waterfall with like badges`
---
## Task 4: V04 深色大图查看器
**Files:**
- Rewrite: `apps/viewer/src/components/PhotoLightbox.vue`
- Create: `apps/viewer/src/components/viewer/ThumbnailStrip.vue`(新)
- Create: `apps/viewer/src/components/viewer/PhotoActionBar.vue`(新)
- [ ] **布局层级**(方案 §5.1 ASCII 图):
```
顶栏(52-56px,深色半透明): × 位置 ⋯菜单
图片舞台(flex:1): 完整大图 contain + ‹ › 切换
粒子层: pointer-events:none
缩略图条(~56px): [thumb][current][thumb]...
底部操作栏(88-104px+安全区): ♥ Like Share Download
```
- [ ] **顶栏**:
- 关闭 ×(左,有效区≥44px)
- 位置计数(中)——真实回退:有 total 显示 "12 / 248";无 total 显示 "已加载 24 张";深链未定位显示 "照片预览";全部加载完成显示当前筛选序列 i/n(方案 §5.2 精确规则)
- 更多菜单 ⋯(右)——至少包含"减少动画"开关;无真实动作则不渲染
- [ ] **图片舞台**:
- contain 完整显示,不裁切
- browse 变体 URL(不默认加载原图)
- 左右按钮靠近舞台边界
- 双指缩放 + 单指平移(复用 zoom.ts;放大时平移不误触切图)
- 键盘:← → 切换,Escape 关闭
- [ ] **缩略图条**(新组件):
- 显示当前附近最多 7 项(手机可见 5)
- 当前项蓝色 2px 描边
- 宽 56-64px,高 44-52px
- 点击切换到该 photoId(不新增浏览器历史)
- 切换后选中项滚动到可见范围
- 使用 thumb 变体
- [ ] **底部操作栏**(新组件):
- Like: coral (#FF667D) 心形 + 累计数 + "Like/点赞" + 下方提示 "连续点击,为照片点赞"
- Share: 白色分享图标 + "Share/分享"
- Download: 白色下载图标 + "Download/下载"(权限关闭时移除)
- 三个均匀分布,有效触控≥44px
- 原图下载:Download 长按或二级菜单(仅 originalDownloadEnabled 时)
- [ ] **深色主题**:#0E1218 背景,白色/半透明文字
- [ ] **安全区**:dvh + safe-area-inset 处理
- [ ] **焦点管理**:打开后焦点进入对话框,Escape 关闭,关闭后回到触发图片
- [ ] **背景滚动锁定**:打开时锁住 body 滚动
- [ ] vitest (~15):位置回退三种模式、缩略图渲染+点击切换、操作栏权限显隐、焦点管理、键盘导航、安全区
- [ ] Commit `feat(viewer): UI-02 dark immersive photo viewer with thumbnail strip`
---
## Task 5: V05 分享下载与互动粒子
**Files:**
- Modify: `apps/viewer/src/components/PhotoLightbox.vue`(集成粒子)
- Create: `apps/viewer/src/components/viewer/HeartParticles.vue`(新)
- Create: `apps/viewer/src/components/viewer/InteractionNotice.vue`(新)
- Modify: `apps/viewer/src/likes/useLikes.ts`(暴露本地点击动效事件)
- [ ] **心形粒子**(HeartParticles.vue):
- 触发:当前照片本地点击 或 该照片收到他人点赞 WS 事件
- 视觉:18-30px coral 心形,2-3 条漂移轨迹,持续 1.2-1.8s
- 大图内靠右侧上浮;首页在照片区右侧
- pointer-events: none, aria-hidden: true
- 同时最多 12 个装饰粒子(超出丢弃最旧)
- 页面隐藏/关闭上下文时清理
- 减少动画时不漂浮(计数正常更新)
- [ ] **跨照片互动提示**(InteractionNotice.vue):
- 保留现有缩略图+心形+delta 可识别提示及点击定位
- 大图内放底部操作栏上方安全区
- 同时最多 3 条
- 不把其他照片计数加到当前照片
- [ ] **分享**:
- navigator.share 优先;不支持时复制链接
- 相册分享:相册路由;照片分享:当前 photoId 深链+语言参数
- 复制失败展示可选中文本
- 用户取消原生分享不显示错误
- [ ] **下载**(复用 useDownload):
- 权限关闭时移除下载按钮(不留可绕过直链)
- 原图仅在 originalDownloadEnabled 时提供二级选项
- 下载中防重复;异步错误归属正确 photoId
- 微信等环境提示保留
- [ ] **自身回声去重**:同一批次 WS 回传不重复累计、不再制造"他人点赞"粒子
- [ ] vitest (~12):粒子生成/清理/上限、减少动画禁用、自身回声过滤、分享复制回退、下载权限组合、异步错误归属
- [ ] Commit `feat(viewer): UI-02 heart particles, interaction notices, share and download`
---
## Task 6: V06+V07 状态国际化与验收
- [ ] **全部新文案 zh-CN + en**(方案 §3-§7 所有 UI 文字)
- [ ] **状态矩阵**(方案 §7 表格逐项验证):加载中骨架/不可公开/空相册/网络失败/加载更多失败/URL 过期/隐藏删除/深链不存在/WS 断线/分享下载失败
- [ ] **无障碍**:aria 标签、焦点可见、模态焦点不穿透、tabular-nums 数字宽度
- [ ] **EmbedPage 回归**:确认全局样式不影响嵌入组件(截图+Playwright)
- [ ] **全量测试**:`pnpm --filter @photo/viewer lint && typecheck && test && build && test:e2e`
- [ ] **截图矩阵**(方案 §9.1):390×844 / 320×568 / 430×932 / 844×390 / 1440×900 × zh/en × 首页/大图
- [ ] **V-A01 至 V-A15 逐项验证** + 验收记录完成
- [ ] 总纲 UI-02 注记;tag `ui02`;Commit `docs: UI-02 acceptance record`
---
## 执行注意
- **样式隔离红线**:所有新 CSS 在 `.viewer-shell` / `.album-page` / `.photo-viewer` 作用域内;不用全局 `button`/`img` 选择器;验证 EmbedPage 不受影响
- **业务零变更**:usePhotoSequence/useLikes/useAlbumRealtime/useDownload 的数据流不改(仅 UI 层消费方式变化)
- **数据诚实**:不伪造总数/在线人数/准确位置——按方案 §5.2 的回退规则
- **性能红线**:粒子 DOM 有上限、缩略图不加载整相册、不预加载全部 browse 图
- 每任务 TDD + 提交带 Co-Authored-By trailer