Files
photo-live/docs/plans/2026-09-18-P1b-观众页与管理后台.md
T
2026-09-18 08:05:06 +08:00

124 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.
# 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 管理);Create `services/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`;Commit `docs: 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。