package dto // ---------- 请求 ---------- // ArticleQuery 走秀列表查询条件(GET /public/runways)。 // // 设计原则:参数尽可能少、不复用。 // - 所有筛选均为单值(前端已全改为单选),不再支持多选数组; // - 按品牌过滤走查询参数 brand_id(hashid 编码串),不再使用独立嵌套路由; // - 关键词搜索已移除(无前端调用);season_code 冗余(season+year 已覆盖)也已移除。 // - 分页 size 与附图 images 由服务端固定(ArticleFixedSize / ArticleFixedImages),前端不传、不可调。 type ArticleQuery struct { Page int Size int // 服务端固定为 ArticleFixedSize,不从 query 读取 Collection string // 单值:rtw / menswear / couture / resort / pre_fall Season string // 单值:spring / fall Year int // 单值年份,0 表示不过滤 Sort string // newest(默认)/ year_desc / year_asc Images int // 服务端固定为 ArticleFixedImages,不从 query 读取 // Locale 语言:cn | en。空时由 service 回落默认 en。 // 决定 title / description / brand_name 出参选取 *_en 还是 *_cn。 Locale string // BrandIDs 由列表接口从查询参数 brand_id(hashid 编码串)解码注入;为空表示不过滤。 BrandIDs []uint32 } // 列表分页与附图数量由服务端固定(前端无调整入口),不再从 query 读取。 const ( ArticleFixedSize = 24 // 每页条数(runway-looks 分页固定值) articleMaxSize = 100 ArticleFixedImages = 6 // 列表每篇附带前 N 张缩略图 imagesMax = 12 // PreviewLimit 未登录用户在列表接口可见的预览条数上限; // 超过部分由前端登录门禁提示,避免公开接口一次性吐出全部图集。 PreviewLimit = 5 ) // Normalize 校正分页与附图参数,防止非法值打穿数据库。 func (q *ArticleQuery) Normalize() { if q.Page < 1 { q.Page = 1 } if q.Size < 1 || q.Size > articleMaxSize { q.Size = ArticleFixedSize } if q.Images < 0 { q.Images = 0 } if q.Images > imagesMax { q.Images = imagesMax } if q.Sort == "" { q.Sort = "newest" } } // Offset 返回 SQL 偏移量。 func (q ArticleQuery) Offset() int { return (q.Page - 1) * q.Size } // ---------- 响应 ---------- // PublicArticleImage 对外展示用的图片结构。 // // ID 是图片自身的编码 id(hashid,i=走秀单图 / j=街拍单图),供「单图收藏」定位到具体某一张; // 此前只有 image + name,前端无法稳定标识单张图片,故补充该字段。 // // Image 与 Thumb 的关系: // - Image 是「该接口场景下的主用地址」——详情接口给展示样式(high),列表接口直接给缩略图(thumb, // 列表只需小图,不给大图以免浪费带宽)。 // - Thumb 恒为缩略图地址(thumb 样式),**仅详情接口填充**:详情返回的是展示样式大图, // 但大图灯箱左栏缩略图条 / 右下角细节缩略图只要几十到一百多像素,用大图纯属浪费, // 故额外给出 Thumb 供这些「小尺寸场景」使用。列表接口不填(其 Image 本身就是缩略图)。 type PublicArticleImage struct { ID string `json:"id"` Image string `json:"image"` Thumb string `json:"thumb,omitempty"` Name string `json:"name"` IsDetail uint8 `json:"is_detail"` // 0=主图 1=细节图(详情接口 images 仅含主图;细节在 Details 分组) LookIndex uint32 `json:"look_index"` // 细节图归属的主图序号(用于前端分组展开) Favorited bool `json:"favorited,omitempty"` // 已登录时该图片是否被当前用户收藏(image 级) } // RunwayLookDetails 某个主图(look)对应的细节图集合,供详情页「细节入口」按需展开。 type RunwayLookDetails struct { LookIndex uint32 `json:"look_index"` // 主图序号(与 images 中对应主图的 look_index 一致) Images []PublicArticleImage `json:"images"` // 该主图的细节图列表 } // PublicArticle 对外展示用的精简文章结构(走秀列表)。 // // 只返回前端真正渲染的字段:id / brand_id / title / cover / brand_name / image_count / images。 // year / season / season_code / collection_type / published_at / summary 前端从不展示, // 且排序已由后端按真实走秀新旧(year + season 优先级)完成,故响应不再携带这些元数据。 // 对外只暴露编码后的 UID(非自增主键),防止爬虫顺序枚举;brand_id 同样编码为 brand_uid。 type PublicArticle struct { UID string `json:"id"` // 编码后的文章 id(无序串) BrandUID string `json:"brand_id"` // 编码后的品牌 id(无序串) Title string `json:"title"` Cover string `json:"cover"` // 封面相对路径,如 /uploads/xxx.jpg BrandName string `json:"brand_name"` // JOIN brand 得到 ImageCount uint16 `json:"image_count"` // 该走秀图片总数(列表卡片 "N+" 角标用) Images []PublicArticleImage `json:"images"` // 列表附带的每篇前 N 张缩略图 } // PublicArticleDetail 对外只读文章详情:含正文与完整图片集,不暴露管理字段。 // // 与列表一致,只返回前端渲染所需的字段;后台管理用的额外元数据(季节 / 年份 / 系列等) // 见 AdminRunway,由后台列表接口单独返回。 type PublicArticleDetail struct { UID string `json:"id"` // 编码后的文章 id(无序串) BrandUID string `json:"brand_id"` // 编码后的品牌 id(无序串) Title string `json:"title"` Description string `json:"description"` Cover string `json:"cover"` BrandName string `json:"brand_name"` Images []PublicArticleImage `json:"images"` // 仅主图(look 图),默认展示 Details []RunwayLookDetails `json:"details"` // 按主图分组的细节图(「细节入口」展开用);无细节时为空 Favorited bool `json:"favorited,omitempty"` // 已登录时该图集是否被当前用户收藏(gallery 级,r= 前缀) } // AdminRunway 后台管理用的走秀列表项:在 PublicArticle 基础上补回管理字段, // 供后台按季节 / 年份 / 系列筛选与展示(对外 PublicArticle 刻意不暴露这些元数据)。 type AdminRunway struct { UID string `json:"id"` // 编码后的文章 id(无序串) BrandUID string `json:"brand_id"` // 编码后的品牌 id(无序串) BrandName string `json:"brand_name"` // JOIN brand 得到(优先 en) Title string `json:"title"` Year uint16 `json:"year"` // 年份(如 2024) Season string `json:"season"` // spring / fall CollectionType string `json:"collection_type"` // rtw / menswear / couture / resort / pre_fall SeasonCode string `json:"season_code"` // SS26 / FW25 / RES26 / PF25 ImageCount uint16 `json:"image_count"` }