Co-Authored-By: Claude Code <noreply@anthropic.com>
12 KiB
P1b 观众页与管理后台 实施计划
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: 在 P1a 后端核心链路之上交付:观众页(Vue3 轻量入口:活动信息/瀑布流/大图/多语言/分类/新图轮询)、管理后台最小页(Art Design Pro:登录/相册/成员)、宣传图上传链路、管理端照片列表与账号查询接口、Playwright 冒烟——完成总纲 P1 阶段整体出口。
Architecture: apps/viewer(Vue 3 + Vite + TS,独立轻量入口,读 cn 公开 API)+ apps/admin(Art Design Pro 模板裁剪,读管理 API)+ services/api 增量(assets 上传/管理照片列表/账号查询)。后端继续契约先行:本计划先扩 openapi.yaml 再实现。对齐《实施总纲》P1 明细与 P1a 验收记录移交清单。
Tech Stack: 沿用 P1a(NestJS/Prisma/MySQL/StoragePort-MinIO;Node ≥22.12——.nvmrc 22 / engines 对齐,开发机实际 24)+ Vue 3.4 · Vite 5 · TypeScript · vue-i18n · vue-router · Pinia(viewer 轻量,Pinia 仅 admin 用)· Art Design Pro(锁定版本,执行时登记仓库地址与精确版本)· Playwright(devDep 置于仓库根,viewer/admin e2e 共用)
对应验收: A08(大图操作基础级)、A12 部分(语言切换)、R09(宣传图)、P1a 终审修缮项。A11/A14 等随 P2。
依赖基线: tag p1a(8a09001+43de31f),compose 基础设施运行中。
Task 1: P1a 终审修缮(4 项)
Files:
-
Modify:
packages/contracts/src/errors.ts+openapi.yaml(新增NOT_FOUND错误码:框架级未知路由/资源;Error enum + gen) -
Modify:
services/api/src/all-exceptions.filter.ts(404 默认映射改NOT_FOUND;业务 404 全部经 ApiException 显式携带原码——回归现有 e2e) -
Modify:
services/api/src/main-cn.ts(app.disable('x-powered-by')或等价 helmet 配置;e2e 断言头消失) -
Create:
services/api/src/shared/pagination.ts(parseCursor/parseLimit 从 albums.controller.ts 迁入;members/uploads/public 改引用;删除原处导出) -
Modify:
infra/docker-compose.yml(宿主绑定改127.0.0.1:3306:3306等四项——注意 Windows 下 Docker Desktop 需重建容器docker compose up -d --force-recreate,数据卷保留;同步把.env.example各 localhost 改 127.0.0.1 防 ::1 解析边缘) -
Step 1: 失败测试先行(404 未知路由 → code NOT_FOUND;响应无 x-powered-by)
-
Step 2: 实现四项 + 契约 gen + 全量回归绿(api e2e 120 基线不降)
-
Step 3: Commit
chore(api): p1a final-review hygiene items
Task 2: 契约扩展 —— assets 上传 / 管理照片列表 / 账号查询
Files:
-
Modify:
packages/contracts/openapi.yaml(+gen 提交) -
Step 1: 新增路径(字段以技术方案 §4 album_assets 与既定模式为准):
POST /albums/{albumId}/assets:{kind: banner|cover|logo, locale?, filename, bytes, contentType} → 201 {assetId, objectKey, credential(UploadCredential 同形)};POST /albums/{albumId}/assets/{assetId}/complete→ 200 {assetId, url(短期签名)}(服务端 head 核验后挂到相册;同 kind+locale 替换语义=新行生效、旧行置空,响应取最新——公开响应已按此取 latest)GET /albums/{albumId}/photos:管理列表(query: visibility?、categoryId?、cursor、limit)→ PhotoRecord 分页(含未发布/隐藏/删除外全部状态;上传人信息 account/displayName)GET /admin/users?accountPrefix=&limit=:admin/owner 可用,按 account 前缀(≥2 字符)搜索激活用户,返回 {id, account, displayName} 脱敏列表(邀请协作者前置;不返回邮箱手机)- 语义与错误沿用既有码(ALBUM_NOT_FOUND/AUTH_FORBIDDEN/VALIDATION_FAILED/UPLOAD_VERIFY_FAILED)
-
Step 2:
gen+lint:api绿;⏸ 快速确认点:向用户展示三组新端点要点(可跳过逐字段评审) -
Step 3: Commit
feat(contracts): p1b asset upload, management photo list, account lookup
Task 3: assets 上传链路(后端)
Files:
-
Create:
services/api/src/albums/assets.service.ts+ controller 挂 AlbumsModule;test/assets.e2e-spec.ts -
Step 1: 失败 e2e:owner 上传 banner(presign PUT 真实 jpeg → complete)→ 201/200;DB album_assets 行 + MinIO 对象;
GET /public/albums/{token}banner 返回可取回的签名 URL;替换上传(同 kind 新 complete)→ 公开响应取新 URL;非 owner 403;bytes/contentType 校验同 uploads 域;kind=logo locale 维度可选;audit album.asset.add -
Step 2: 实现(复用 uploads 域的 presign/核验/大小白名单模式;对象键
photo-live/dev/albums/{albumId}/assets/{assetId}.{ext};无幂等 clientTaskId 需求——按 assetId 一次性会话即可) -
Step 3: 通过 + Commit
feat(api): album asset upload chain
Task 4: 管理照片列表 + 账号查询(后端)
Files:
-
Modify:
services/api/src/photos/(新增 list 管理);Createservices/api/src/admin/users-lookup.*;test/photos-admin.e2e-spec.ts -
Step 1: 失败 e2e:owner/admin 查管理列表(全状态可见、visibility 过滤、分页稳定、uploader 脱敏信息);collaborator → 403;账号查询:admin/owner 前缀搜索(<2 字符 400、不泄露凭据字段、限 20 条);outlier 401/403
-
Step 2: 实现(共享 keyset;账号查询
WHERE account LIKE prefix%走 bin 校对大小写敏感——文档说明) -
Step 3: 通过 + Commit
feat(api): management photo list and account lookup
Task 5: viewer 脚手架
Files:
-
Create:
apps/viewer/(Vite + Vue3 + TS:package.json @photo/viewer、vite.config(proxy /v1 → :8080 dev)、router(/a/:token活动页、/a/:token/p/:photoId单图定位)、i18n(zh-CN/en 词条文件、localStorage 持久、检测浏览器语言)、api client(fetch 封装 + 契约生成类型 + 错误码→文案映射表)、Pinia 不引入(保持轻量)) -
Step 1:
pnpm create vite起最小骨架并裁剪至需求;加入 workspace;lint/typecheck/build/test(vitest)/test:e2e(Playwright) 全部接入 turbo(Task 12 回归门依赖) -
Step 2: 冒烟:dev server 起、路由可达、i18n 切换生效(vitest 组件测试 2-3 个)
-
Step 3: Commit
feat(viewer): vite vue3 scaffold with routing and i18n
Task 6: viewer 活动页(信息区 + 空态 + 时区)
- Step 1: 组件测试 + 实现:宣传图(banner 签名 URL、无则优雅降级布局)、活动信息(标题/介绍/时间按相册 timezone 以 Intl.DateTimeFormat 展示、地点)、语言切换入口(易发现)、空相册本地化空态("照片即将上线")、viewCount 占位(null 不渲染)
- Step 2: Playwright 前置:
apps/viewer/e2e/(配置 + fixtures:seed 脚本起真实后端或 mock —— 决策:用真实后端 + Playwright,compose 依赖;fixture 走 API seed) - Step 3: Commit
feat(viewer): album page with locale fallback and timezone display
Task 7: viewer 瀑布流
- Step 1: 组件/交互实现:双列瀑布流(CSS columns 或绝对定位两列——选绝对定位方案以支持滚动位置恢复;条目高按 width/height 预留)、IntersectionObserver 懒加载图片(签名 URL 加载失败重试一次=刷新签名)、分类筛选 chips、游标分页无限滚动(nextCursor 耗尽停)、新照片轮询提示(30s 间隔 polling 最新页,有新图显示顶部提示条"新照片"点击刷新——不自动插入避免跳动,需求 1.2 建议基线)
- Step 2: Playwright:滚动加载两页、分类切换、(轮询提示用注入延时验证)
- Step 3: Commit
feat(viewer): waterfall feed with lazy load, filter and new-photo hint
Task 8: viewer 大图查看器
- Step 1: 实现:点击开大图(browse URL)、左右切换(保持列表浏览序列)、双指/滚轮缩放(CSS transform 受控)、关闭回原列表位置(滚动锚点)、单图定位路由(
/a/:token/p/:photoId:先加载该照片再拉相邻页)、404 照片本地化提示、签名过期→静默重取单图接口刷新 - Step 2: Playwright:开-关回位、切换序列、深链直达
- Step 3: Commit
feat(viewer): photo viewer with zoom, navigation and deep link
Task 9: admin 引入 Art Design Pro
Files:
-
Create:
apps/admin/(模板引入——git clone 指定版本或下载 release zip;锁定版本记录在 package.json + README 注明来源与裁剪说明) -
Step 1: 引入并裁剪(保留布局/登录/权限框架与依赖最小集;移除 demo 页;name @photo/admin 入 workspace;lint/typecheck/build 接 turbo——模板自带 eslint 需对齐根配置或豁免说明)
-
Step 2: 登录对接:模板登录页改调
POST /v1/auth/password(sessionToken 内存+sessionStorage、401 统一跳登录、refresh 定时静默续期);dev proxy /v1 → 8080 -
Step 3: 冒烟(手动 + vitest 冒烟);Commit
feat(admin): art-design-pro integration with local session auth
Task 10: admin 相册与成员页
- Step 1: 实现:相册列表(游标分页、开关状态展示)、创建(多语言表单 zh-CN/en、校验同契约)、编辑(开关独立、translations 全量替换语义提示);相册详情含成员管理(账号前缀搜索邀请、移除、角色展示)与分类管理(增改删)——用 Task 2 的账号查询与管理列表接口
- Step 2: Playwright(admin 流:登录→建相册→邀成员→传 banner→管理列表可见照片状态)可与 Task 11 合并冒烟
- Step 3: Commit
feat(admin): album management pages with members and categories
Task 11: admin 上传与 viewer 联调冒烟(Playwright 全流程)
- Step 1: Playwright 完整剧本(真实后端):admin 登录→建相册(zh+en)→传 banner→建分类→邀 staffB;staffB(API 或 UI 若简单)上传 3 张真图→worker 处理(compose 内起 dev:worker 或测试内直调);viewer:开活动页(en)→瀑布流 3 张→分类过滤→大图切换→深链;admin 隐藏 1 张→viewer 列表变 2;空相册态冒烟另起一例
- Step 2: 全绿 + Commit
test(e2e): p1b cross-frontend playwright journey
Task 12: P1b 回归门 + P1 阶段验收记录
- Step 1: 全量回归
pnpm lint && pnpm typecheck && pnpm test && pnpm e2e && pnpm build全绿(含新增 viewer/admin 任务) - Step 2: 写
docs/plans/progress/P1b-验收记录.md(A08 基础级、A12 部分、R09、P1a 终审修缮 4 项的落实登记——P1a 记录偏差表未含、此处补登、偏差登记、P2 移交:outbox 消费/限速/热门排序);更新总纲 P1 行进度注记(P1 整体完成、G1 可选关口状态) - Step 3:
git tag p1;Commitdocs: p1b acceptance record
执行注意
- 沿用 P1a 全部约定:TDD 节奏、根目录执行、.env 唯一、新 env 同步 example、契约先行(改 openapi 必 gen+提交)、e2e reset 隔离、turbo concurrency=1、jest maxWorkers=1。
- viewer/admin 的 node 工具链同一 Node 24;Vite dev 端口避开 8080(建议 5173 viewer / 5174 admin,CORS_ORIGINS 已含 5173,admin 端口需补入 env)。
- Playwright 首次安装浏览器走
pnpm exec playwright install chromium(仅 chromium,控制体积)。 - Art Design Pro 若与根 ESLint 9 冲突:admin 包内保留模板自带 lint 配置并在根配置 ignore 该包(登记偏差),不强改模板;执行时在 apps/admin/README 或 package.json 登记模板来源仓库与锁定版本。
- pnpm 10 默认拦截依赖构建脚本:引入 Vite/esbuild/admin 模板依赖时若遇到二进制缺失,向根 package.json 增补
pnpm.onlyBuiltDependencies(esbuild 等)后重装,登记偏差。 - UI 实现遵循 frontend-design 直觉但不做过度视觉打磨——P1b 是功能最小页,视觉完善随 P4a。