From 9807752e4eba672320965ead91827d1c606d1346 Mon Sep 17 00:00:00 2001 From: toom1996 <23cm.cn@gmail.com> Date: Mon, 21 Sep 2026 23:24:29 +0800 Subject: [PATCH] =?UTF-8?q?docs(street):=20=E8=A7=84=E6=A0=BC=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E4=B8=8E=E8=B5=B0=E7=A7=80=E5=90=8C=E5=BD=A2=E5=B5=8C?= =?UTF-8?q?=E5=A5=97=E7=BB=93=E6=9E=84=E5=B9=B6=E8=AE=B0=E5=BD=95=E5=AE=A1?= =?UTF-8?q?=E6=A0=B8=E9=A1=B5=E5=9B=BE=E7=89=87=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-09-21-street-main-detail-design.md | 64 +++++++++++-------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/docs/superpowers/specs/2026-09-21-street-main-detail-design.md b/docs/superpowers/specs/2026-09-21-street-main-detail-design.md index 18011a2..ff31d76 100644 --- a/docs/superpowers/specs/2026-09-21-street-main-detail-design.md +++ b/docs/superpowers/specs/2026-09-21-street-main-detail-design.md @@ -54,6 +54,9 @@ parent_image_id integer not null default 0 -- 副图指向所属主图的「 **不变量**:`is_detail = 1` 的行,其 `parent_image_id` 必须指向**同一草稿 / 同一专辑内**一张 `is_detail = 0` 的行。 +> **落点已变更(2026-09-21)**:`AutoMigrate` / `EnsureDedupSchema` 已移除,结构改由 `cmd/dbtool` 的 dump 维护。 +> 上述两列已落在 `db/migrations/2026-09-21-01-street-main-detail.sql`(已应用)。 + - **写入侧**保证该不变量(并入操作先校验主图存在且属于同一 owner)。 - **读侧**容错:父行不存在(或已被软删)时,把该副图**按主图渲染**,不隐藏、不报错。 @@ -69,16 +72,27 @@ parent_image_id integer not null default 0 -- 副图指向所属主图的「 | `POST /admin/reviews/:kind/:id/images/:img/attach-prev` | **并入上一张**(相邻快捷,零勾选)。覆盖「连拍同一个人」这一最常见场景 | | `POST /admin/reviews/:kind/:id/images/:img/detach` | **拆出**:把副图恢复为主图(`is_detail=0, parent_image_id=0`) | -**不单设「设为主图」路由**:选定主图 = 在页面上点击某张图的「设为主图」链接,它只是把 URL 变成 `?main=`(一个普通 GET 链接,无需服务端写入)。随后其他图的 checkbox 表单通过**隐藏字段**携带该 `main` 值。 +**不单设「设为主图」路由**:选定主图 = 点击某张主图的「设为主图」链接,它只是把 URL 变成 `?main=`(普通 GET 链接,无需服务端写入),并带 `#i` 锚点使刷新后回到原位置。 -这样「选定主图 → 滚到任意位置勾选 → 并入」全程**不需要同屏**,且天然支持批量。 +**审核页布局(实际实现)**:街拍草稿详情页渲染为**一条「图片流」**(合并了原「分组」与「全部图片」两块,消除重复): + +- 主图卡片:徽标「主图 · N 张副图」+ `设为主图`(GET,带 `#i` 锚点) + `并入上一张` + 批量勾选框 + `删除`;其副图以缩略小条紧跟在卡片下方,带 `并入上一张` / `拆出` / `删除`。 +- 底部常驻(sticky)提交条:把勾选的图 `并入到当前主图 #N`。 +- 顶部常驻提示条:显示「当前主图 #N」与两条操作路径。 + +> **「并入上一张」必须覆盖每一张图(除首张)**:这是「连拍同一个人」的快捷路径,无需先选主图。此前实现只在「已是副图」的行上渲染该按钮,导致新建草稿(全是主图)时该快捷完全不可用——已修正。 + +两条路径: +- **连拍**:从上往下,对属于同一人的图逐张点「并入上一张」。 +- **非相邻**:先点目标主图的「设为主图」→ 勾选要并入的图 → 点底部「并入到当前主图」。 ### 3.3 详情页渲染(按组折叠) -- 主图区:逐个渲染主图卡片(大图 + 名称) -- 每个主图卡片下方:一条**窄的副图缩略条**,副图带「拆出」按钮 -- 未分组的图按主图渲染(默认 `is_detail=0`,因此**存量数据无需迁移即可正常显示**) -- 主图卡片上显示「N 张副图」计数 +**审核页**:见 §3.2「图片流」——主图卡片 + 其下副图缩略条,主图卡片显示「N 张副图」计数。 + +**公开详情页**:`Images` 只含主图,副图挂在每张主图的 `Detail` 子数组下(与走秀详情同形,见 §3.5)。 + +- 未分组的图按主图渲染(默认 `is_detail=0`,因此**存量数据无需迁移即可正常显示**)。 ### 3.4 晋升到正式表:必须重建父引用 @@ -88,31 +102,29 @@ parent_image_id integer not null default 0 -- 副图指向所属主图的「 漏掉这一步会导致:副图的父引用指向一个不存在(或属于别的图)的 id,详情页折叠结构错乱。 -### 3.5 公开 API(保守扩展) +### 3.5 公开 API(与走秀详情同形) -**保留** `PublicStreetSnapDetail.Images`(扁平、按 `sort_order` 升序)不变——现有前端不受影响。 - -**新增**分组字段: +**决策(2026-09-21 修订)**:采用与走秀详情**完全一致**的嵌套结构(`images` 只含主图 + 每张主图自带 `detail`),**不采用**「保留扁平 `images` + 新增平行 `groups`」的保守方案。 ```go -type PublicStreetSnapGroup struct { - Image PublicArticleImage `json:"image"` // 主图 - Details []PublicArticleImage `json:"details"` // 该主图下的副图(可为空) -} - type PublicStreetSnapDetail struct { - UID string - Title string - Cover string - Images []PublicArticleImage // 保持原样(扁平全部) - Groups []PublicStreetSnapGroup // 新增;未分组时为每张主图一个 group、details 为空 + UID string + Title string + Cover string // 封面:独立字段,与主/副图分组无关 + Images []PublicArticleImage // 只含主图(is_detail=0);每张主图的 .Detail 挂其副图 Favorited bool } +// 复用走秀的 PublicArticleImage: +// IsDetail 0=主图 / 1=副图 +// LookIndex 该图归属的主图序号(街拍侧合成:主图按 sort_order 的 1-based 序;街拍不落库组序号) +// Detail []PublicArticleImage —— 该主图下的副图 ``` -`Groups` 与 `Images` **同源同序**:`Groups` 只是 `Images` 按主副关系重排后的视图,不引入新的数据来源。 +- 与走秀详情(`PublicArticleService.Detail`)同形,前端可复用同一套渲染组件。 +- `parent_image_id` 指向「副图」时沿链向上归并到该副图所在组的主图;父行缺失 / 成环 → 该图按主图渲染(**不丢图**)。这是与走秀读侧的刻意区别:走秀会丢弃「找不到主图的细节图」,街拍不丢。 +- **列表**(`PublicStreetSnap`)不动;`image_count` 语义不变(仍计全部图)。 -**列表**(`PublicStreetSnap`)不动。 +> ⚠️ **破坏性变更**:`Images` 含义从「全部图(扁平)」变为「只含主图」。前端若原样遍历 `Images` 只会看到主图,需改读每张主图的 `.Detail`(跨仓库,本次只保证后端结构)。 ### 3.6 明确不做 @@ -130,10 +142,10 @@ internal/model/street_snap.go +is_detail / +parent_image_id internal/repository/review_repository.go attach / attach-prev / detach 三个写方法 internal/repository/review_repository.go SaveStreetSnapFromDraft 重建父引用 internal/service/review_service.go DraftImageRef 加分组信息;分组写入的校验 -internal/handler/backstage_handler.go 详情页折叠渲染 + 常驻主图条 + 三个表单 +internal/handler/backstage_handler.go 审核页「图片流」渲染 + 常驻主图条 + 三个表单 internal/router/backstage.go 三个新路由 -internal/dto/street_snap.go +Groups(保守扩展) -internal/service/street_snap_service.go 详情组装 Groups +internal/dto/street_snap.go 详情改为嵌套(images 只含主图,复用 PublicArticleImage) +internal/service/street_snap_service.go 详情组装嵌套 images(buildSnapImages) ``` --- @@ -156,6 +168,6 @@ internal/service/street_snap_service.go 详情组装 Groups ## 6. 待确认 -1. 公开 API 采用「保留扁平 `images` + 新增 `groups`」的保守方案。若你希望**直接改成**只返回分组结构(更干净但会打断现有前端),需要另行确认。 +1. ~~公开 API 保守方案~~ **已决(2026-09-21)**:改为与走秀同形的嵌套结构(`images` 只含主图、副图挂 `detail`),见 §3.5。 2. `image_count` 暂不改语义(仍计全部图)。若希望改成「只算主图」(与 runway 一致,含义变成「N 个人」),需要前端同步,另议。 3. 分组的「组序号」不落库,由读时按 `sort_order` 顺序推导(主图顺序即组序)。