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

12 KiB
Raw Blame History

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 方案和设计概念完成观众页视觉重构:蓝白活动首页(品牌栏+宣传图+信息+筛选+瀑布流)+ 深色沉浸式大图查看器(缩略图导航+点赞粒子+分享下载)——不改变任何业务逻辑。

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 精确值):

    .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