update
This commit is contained in:
@ -78,15 +78,14 @@ type PublicArticleImage struct {
|
|||||||
Image string `json:"image"`
|
Image string `json:"image"`
|
||||||
Thumb string `json:"thumb,omitempty"`
|
Thumb string `json:"thumb,omitempty"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
IsDetail uint8 `json:"is_detail"` // 0=主图 1=细节图(详情接口 images 仅含主图;细节在 Details 分组)
|
IsDetail uint8 `json:"is_detail"` // 0=主图 1=细节图(详情接口 images 仅含主图,细节图挂在所属主图的 Detail 下)
|
||||||
LookIndex uint32 `json:"look_index"` // 细节图归属的主图序号(用于前端分组展开)
|
LookIndex uint32 `json:"look_index"` // 该图归属的主图序号;细节图与所属主图同值
|
||||||
Favorited bool `json:"favorited,omitempty"` // 已登录时该图片是否被当前用户收藏(image 级)
|
Favorited bool `json:"favorited,omitempty"` // 已登录时该图片是否被当前用户收藏(image 级)
|
||||||
}
|
|
||||||
|
|
||||||
// RunwayLookDetails 某个主图(look)对应的细节图集合,供详情页「细节入口」按需展开。
|
// Detail 仅详情接口填充:该主图(look)下的细节图子集,元素形状与 Images 递归同构。
|
||||||
type RunwayLookDetails struct {
|
// 「look → 细节」的层级与前端 UI 一致,前端直接读它即可,无需再维护一份
|
||||||
LookIndex uint32 `json:"look_index"` // 主图序号(与 images 中对应主图的 look_index 一致)
|
// 「按 look_index 分组」的映射表。列表接口不填(omitempty 后不出现)。
|
||||||
Images []PublicArticleImage `json:"images"` // 该主图的细节图列表
|
Detail []PublicArticleImage `json:"detail,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PublicArticle 对外展示用的精简文章结构(走秀列表)。
|
// PublicArticle 对外展示用的精简文章结构(走秀列表)。
|
||||||
@ -110,15 +109,15 @@ type PublicArticle struct {
|
|||||||
// 与列表一致,只返回前端渲染所需的字段;后台管理用的额外元数据(季节 / 年份 / 系列等)
|
// 与列表一致,只返回前端渲染所需的字段;后台管理用的额外元数据(季节 / 年份 / 系列等)
|
||||||
// 见 AdminRunway,由后台列表接口单独返回。
|
// 见 AdminRunway,由后台列表接口单独返回。
|
||||||
type PublicArticleDetail struct {
|
type PublicArticleDetail struct {
|
||||||
UID string `json:"id"` // 编码后的文章 id(无序串)
|
UID string `json:"id"` // 编码后的文章 id(无序串)
|
||||||
BrandUID string `json:"brand_id"` // 编码后的品牌 id(无序串)
|
BrandUID string `json:"brand_id"` // 编码后的品牌 id(无序串)
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Cover string `json:"cover"`
|
Cover string `json:"cover"`
|
||||||
BrandName string `json:"brand_name"`
|
BrandName string `json:"brand_name"`
|
||||||
Images []PublicArticleImage `json:"images"` // 仅主图(look 图),默认展示
|
// Images 仅含主图(look 图),默认展示;每个主图的细节图挂在它自己的 Detail 子数组下。
|
||||||
Details []RunwayLookDetails `json:"details"` // 按主图分组的细节图(「细节入口」展开用);无细节时为空
|
Images []PublicArticleImage `json:"images"`
|
||||||
Favorited bool `json:"favorited,omitempty"` // 已登录时该图集是否被当前用户收藏(gallery 级,r= 前缀)
|
Favorited bool `json:"favorited,omitempty"` // 已登录时该图集是否被当前用户收藏(gallery 级,r= 前缀)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminRunway 后台管理用的走秀列表项:在 PublicArticle 基础上补回管理字段,
|
// AdminRunway 后台管理用的走秀列表项:在 PublicArticle 基础上补回管理字段,
|
||||||
|
|||||||
@ -101,8 +101,9 @@ func (h *ArticleHandler) Detail(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 未登录:图片集仅返回预览上限(PreviewLimit)张,并打 preview=true,
|
// 未登录:主图只返回预览上限(PreviewLimit)张并打 preview=true,前端据此展示
|
||||||
// 前端据此在详情页展示「登录查看全部图片」门禁;已登录(带有效 Bearer)返回完整图片集。
|
// 「登录查看全部图片」门禁。细节图(Detail 子数组)不额外剥离 —— 游客也能看前几张主图的细节,
|
||||||
|
// 体验更完整;而第 PreviewLimit 张之后的主图连同其细节图会被整组截断丢弃,故无法借此绕过门禁。
|
||||||
anon := isAnon(c)
|
anon := isAnon(c)
|
||||||
preview := false
|
preview := false
|
||||||
imageTotal := len(detail.Images)
|
imageTotal := len(detail.Images)
|
||||||
@ -111,16 +112,21 @@ func (h *ArticleHandler) Detail(c *gin.Context) {
|
|||||||
preview = true
|
preview = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 已登录:把当前用户对该图集(gallery 级)与每张图片(image 级)的收藏态随详情返回,
|
// 已登录:把当前用户对该图集(gallery 级)与每张图片(image 级,含细节图)的收藏态
|
||||||
// 前端无需再单独调 /me/favorites/checks。gallery uid(r=)与图片 id(i=)前缀不同,
|
// 随详情返回,前端无需再单独调 /me/favorites/checks。gallery uid(r=)与图片 id(i=)
|
||||||
// 混传 FilterExisting 不影响查询;匿名请求不查(保持公开接口无用户态、可缓存)。
|
// 前缀不同,混传 FilterExisting 不影响查询;匿名请求不查(保持公开接口无用户态、可缓存)。
|
||||||
if uid, ok := middleware.UserIDFrom(c); ok {
|
if uid, ok := middleware.UserIDFrom(c); ok {
|
||||||
ids := make([]string, 0, len(detail.Images)+1)
|
ids := make([]string, 0, len(detail.Images)*2+1)
|
||||||
ids = append(ids, detail.UID)
|
ids = append(ids, detail.UID)
|
||||||
for _, im := range detail.Images {
|
for _, im := range detail.Images {
|
||||||
if im.ID != "" {
|
if im.ID != "" {
|
||||||
ids = append(ids, im.ID)
|
ids = append(ids, im.ID)
|
||||||
}
|
}
|
||||||
|
for _, kid := range im.Detail {
|
||||||
|
if kid.ID != "" {
|
||||||
|
ids = append(ids, kid.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if hit, err := h.favs.Check(c.Request.Context(), uid, ids); err == nil {
|
if hit, err := h.favs.Check(c.Request.Context(), uid, ids); err == nil {
|
||||||
set := make(map[string]bool, len(hit))
|
set := make(map[string]bool, len(hit))
|
||||||
@ -130,6 +136,9 @@ func (h *ArticleHandler) Detail(c *gin.Context) {
|
|||||||
detail.Favorited = set[detail.UID]
|
detail.Favorited = set[detail.UID]
|
||||||
for i := range detail.Images {
|
for i := range detail.Images {
|
||||||
detail.Images[i].Favorited = set[detail.Images[i].ID]
|
detail.Images[i].Favorited = set[detail.Images[i].ID]
|
||||||
|
for j := range detail.Images[i].Detail {
|
||||||
|
detail.Images[i].Detail[j].Favorited = set[detail.Images[i].Detail[j].ID]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
|
||||||
|
|
||||||
"fashionapi/internal/dto"
|
"fashionapi/internal/dto"
|
||||||
"fashionapi/internal/model"
|
"fashionapi/internal/model"
|
||||||
@ -168,8 +167,9 @@ func (s *articleService) Detail(ctx context.Context, id string, locale string) (
|
|||||||
return nil, internalErr(err.Error())
|
return nil, internalErr(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认仅主图(is_detail=0)进 Images;细节图按 look_index 分组进 Details,
|
// 主图(is_detail=0)进 Images,默认展示;细节图(is_detail=1)按 look_index
|
||||||
// 供前端「细节入口」按需展开。这样既满足「默认只展示主图」,又不丢失细节图数据。
|
// 直接挂到所属主图的 Detail 子数组下 —— 「look → 细节」的层级与前端 UI 一致,
|
||||||
|
// 前端读 img.detail 即可,无需再单独维护一份「按 look_index 分组」的映射表。
|
||||||
mains := make([]model.BrandRunwayImage, 0, len(imgs))
|
mains := make([]model.BrandRunwayImage, 0, len(imgs))
|
||||||
byLook := make(map[uint32][]model.BrandRunwayImage)
|
byLook := make(map[uint32][]model.BrandRunwayImage)
|
||||||
for _, im := range imgs {
|
for _, im := range imgs {
|
||||||
@ -179,17 +179,14 @@ func (s *articleService) Detail(ctx context.Context, id string, locale string) (
|
|||||||
byLook[im.LookIndex] = append(byLook[im.LookIndex], im)
|
byLook[im.LookIndex] = append(byLook[im.LookIndex], im)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
details := make([]dto.RunwayLookDetails, 0, len(byLook))
|
pubMains := s.toPublicImages(mains)
|
||||||
lookIdxs := make([]uint32, 0, len(byLook))
|
for i := range pubMains {
|
||||||
for k := range byLook {
|
// 挂载后即从 byLook 摘除:主图 look_index 若有重复,只归属第一张,避免同一批
|
||||||
lookIdxs = append(lookIdxs, k)
|
// 细节图被复制进多个主图(正常数据不会重复,此处仅作防御)。
|
||||||
}
|
if kids, ok := byLook[pubMains[i].LookIndex]; ok && len(kids) > 0 {
|
||||||
sort.Slice(lookIdxs, func(i, j int) bool { return lookIdxs[i] < lookIdxs[j] })
|
pubMains[i].Detail = s.toPublicImages(kids)
|
||||||
for _, k := range lookIdxs {
|
delete(byLook, pubMains[i].LookIndex)
|
||||||
details = append(details, dto.RunwayLookDetails{
|
}
|
||||||
LookIndex: k,
|
|
||||||
Images: s.toPublicImages(byLook[k]),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &dto.PublicArticleDetail{
|
return &dto.PublicArticleDetail{
|
||||||
@ -200,8 +197,7 @@ func (s *articleService) Detail(ctx context.Context, id string, locale string) (
|
|||||||
Cover: s.img.Compose(row.Cover),
|
Cover: s.img.Compose(row.Cover),
|
||||||
// 品牌名强制优先英文("en"),不随 locale 切换,保持英文观感。
|
// 品牌名强制优先英文("en"),不随 locale 切换,保持英文观感。
|
||||||
BrandName: pickLocale(row.BrandNameEn, row.BrandNameCn, "en"),
|
BrandName: pickLocale(row.BrandNameEn, row.BrandNameCn, "en"),
|
||||||
Images: s.toPublicImages(mains),
|
Images: pubMains,
|
||||||
Details: details,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user