# 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