update
This commit is contained in:
@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"fashionapi/internal/dto"
|
||||
"fashionapi/internal/model"
|
||||
@ -19,11 +20,11 @@ type StreetSnapService interface {
|
||||
List(ctx context.Context, q dto.StreetSnapQuery) ([]dto.PublicStreetSnap, int64, error)
|
||||
// AdminList 后台街拍列表:读基表,包含 pending / rejected(否则审核员看不到待审内容)。
|
||||
AdminList(ctx context.Context, q dto.StreetSnapQuery) ([]dto.PublicStreetSnap, int64, error)
|
||||
Detail(ctx context.Context, id string) (*dto.PublicStreetSnapDetail, error)
|
||||
Detail(ctx context.Context, id string, locale string) (*dto.PublicStreetSnapDetail, error)
|
||||
// DetailAdmin 后台详情:读基表,能渲染 pending / rejected(供后台详情页复用)。
|
||||
DetailAdmin(ctx context.Context, id string) (*dto.PublicStreetSnapDetail, error)
|
||||
DetailAdmin(ctx context.Context, id string, locale string) (*dto.PublicStreetSnapDetail, error)
|
||||
// Popular 返回热度前 N 个街拍(按图片数降序),供 SSG 首页热门街拍展示。
|
||||
Popular(ctx context.Context, limit int) ([]dto.PublicStreetSnap, error)
|
||||
Popular(ctx context.Context, limit int, locale string) ([]dto.PublicStreetSnap, error)
|
||||
// GetForEdit 取原始街拍记录与图片,供后台编辑表单预填。
|
||||
GetForEdit(ctx context.Context, id uint32) (*model.StreetSnap, []model.StreetSnapImage, error)
|
||||
// UpdateSnap 编辑后更新字段(字段白名单在仓储层强制)。
|
||||
@ -70,7 +71,7 @@ func (s *streetSnapService) List(ctx context.Context, q dto.StreetSnapQuery) ([]
|
||||
for _, r := range rows {
|
||||
ids = append(ids, r.ID)
|
||||
}
|
||||
list := s.toPublicSnaps(rows)
|
||||
list := s.toPublicSnaps(rows, q.Locale)
|
||||
|
||||
if q.Images > 0 && len(list) > 0 {
|
||||
if err := s.attachImages(ctx, list, ids, q.Images); err != nil {
|
||||
@ -91,17 +92,17 @@ func (s *streetSnapService) AdminList(ctx context.Context, q dto.StreetSnapQuery
|
||||
if err != nil {
|
||||
return nil, 0, internalErr(err.Error())
|
||||
}
|
||||
return s.toPublicSnaps(rows), total, nil
|
||||
return s.toPublicSnaps(rows, q.Locale), total, nil
|
||||
}
|
||||
|
||||
// toPublicSnaps 把街拍实体映射为对外列表结构(卡片字段:编码 id / 标题 / 缩略封面 / 图片数)。
|
||||
// 公开 List 与后台 AdminList 共用,避免两处重复维护映射(封面统一走缩略图样式,小尺寸省流量)。
|
||||
func (s *streetSnapService) toPublicSnaps(rows []model.StreetSnap) []dto.PublicStreetSnap {
|
||||
func (s *streetSnapService) toPublicSnaps(rows []model.StreetSnap, locale string) []dto.PublicStreetSnap {
|
||||
list := make([]dto.PublicStreetSnap, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
list = append(list, dto.PublicStreetSnap{
|
||||
UID: hashid.EncodeWithType(r.ID, hashid.TypeSnap),
|
||||
Title: r.Title,
|
||||
Title: pickLocale(r.Title, r.TitleCn, locale),
|
||||
Cover: s.img.ComposeThumb(r.Cover),
|
||||
ImageCount: r.ImageCount,
|
||||
Status: r.Status, // 仅后台模板渲染状态列用(json:"-" 不进公开响应)
|
||||
@ -149,7 +150,7 @@ func excludeCoverSnap(imgs []dto.PublicArticleImage, cover string) []dto.PublicA
|
||||
|
||||
// Detail 公开详情:读公开视图,pending / rejected 视同不存在(ErrStreetSnapNotFound)。
|
||||
// 图片统一走展示样式(VIP 与免费同质量)。
|
||||
func (s *streetSnapService) Detail(ctx context.Context, id string) (*dto.PublicStreetSnapDetail, error) {
|
||||
func (s *streetSnapService) Detail(ctx context.Context, id string, locale string) (*dto.PublicStreetSnapDetail, error) {
|
||||
snap, err := s.repo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, s.detailErr(err)
|
||||
@ -158,14 +159,14 @@ func (s *streetSnapService) Detail(ctx context.Context, id string) (*dto.PublicS
|
||||
if err != nil {
|
||||
return nil, internalErr(err.Error())
|
||||
}
|
||||
return s.buildSnapDetail(snap, imgs), nil
|
||||
return s.buildSnapDetail(snap, imgs, locale), nil
|
||||
}
|
||||
|
||||
// DetailAdmin 后台详情:读基表,能渲染 pending / rejected。
|
||||
//
|
||||
// 后台列表(AdminList)故意把待审行链到详情页,且详情页是通往编辑页的唯一入口;
|
||||
// 若后台详情也走公开视图,待审项会 404、审核无从下手。
|
||||
func (s *streetSnapService) DetailAdmin(ctx context.Context, id string) (*dto.PublicStreetSnapDetail, error) {
|
||||
func (s *streetSnapService) DetailAdmin(ctx context.Context, id string, locale string) (*dto.PublicStreetSnapDetail, error) {
|
||||
snap, err := s.repo.FindByIDBase(ctx, id)
|
||||
if err != nil {
|
||||
return nil, s.detailErr(err)
|
||||
@ -174,7 +175,7 @@ func (s *streetSnapService) DetailAdmin(ctx context.Context, id string) (*dto.Pu
|
||||
if err != nil {
|
||||
return nil, internalErr(err.Error())
|
||||
}
|
||||
return s.buildSnapDetail(snap, imgs), nil
|
||||
return s.buildSnapDetail(snap, imgs, locale), nil
|
||||
}
|
||||
|
||||
// detailErr 统一详情取数失败的错误映射(公开 Detail 与 DetailAdmin 共用)。
|
||||
@ -186,10 +187,10 @@ func (s *streetSnapService) detailErr(err error) error {
|
||||
}
|
||||
|
||||
// buildSnapDetail 把街拍行与图片集映射为详情 DTO(公开/后台共用,差异只在数据来源)。
|
||||
func (s *streetSnapService) buildSnapDetail(snap *model.StreetSnap, imgs []model.StreetSnapImage) *dto.PublicStreetSnapDetail {
|
||||
func (s *streetSnapService) buildSnapDetail(snap *model.StreetSnap, imgs []model.StreetSnapImage, locale string) *dto.PublicStreetSnapDetail {
|
||||
return &dto.PublicStreetSnapDetail{
|
||||
UID: hashid.EncodeWithType(snap.ID, hashid.TypeSnap),
|
||||
Title: snap.Title,
|
||||
Title: pickLocale(snap.Title, snap.TitleCn, locale),
|
||||
Cover: s.img.Compose(snap.Cover),
|
||||
Images: s.buildSnapImages(imgs),
|
||||
}
|
||||
@ -198,7 +199,7 @@ func (s *streetSnapService) buildSnapDetail(snap *model.StreetSnap, imgs []model
|
||||
// buildSnapImages 把街拍图片组装成「主图 + 其副图」的嵌套结构(与走秀详情同形):
|
||||
// Images 只含主图(is_detail=0),每张主图的副图挂在其 Detail 子数组下。
|
||||
// 父引用指向「副图」时沿链向上归并到该副图所在组的主图;父行缺失 / 成环则把该图按主图渲染(不丢图)。
|
||||
// LookIndex 是合成的 1-based 主图序号(街拍不落库组序号)。
|
||||
// 主图 / 副图的归并完全靠 parent_image_id 表达(街拍不落库组序号)。
|
||||
func (s *streetSnapService) buildSnapImages(imgs []model.StreetSnapImage) []dto.PublicArticleImage {
|
||||
if len(imgs) == 0 {
|
||||
return nil
|
||||
@ -237,12 +238,12 @@ func (s *streetSnapService) buildSnapImages(imgs []model.StreetSnapImage) []dto.
|
||||
continue
|
||||
}
|
||||
pub := s.toPublicSnapImage(im)
|
||||
pub.LookIndex = uint32(len(out) + 1) // 合成的 1-based 主图序号
|
||||
|
||||
index[im.ID] = len(out)
|
||||
out = append(out, pub)
|
||||
}
|
||||
|
||||
// 第二遍:把副图挂到所属主图的 Detail 下(LookIndex 取所属主图的值)。
|
||||
// 第二遍:把副图挂到所属主图的 Detail 下(副图沿用所属主图的 id 作为分组键)。
|
||||
for _, im := range imgs {
|
||||
root := resolveRoot(im.ID)
|
||||
if root == im.ID {
|
||||
@ -254,14 +255,14 @@ func (s *streetSnapService) buildSnapImages(imgs []model.StreetSnapImage) []dto.
|
||||
}
|
||||
pub := s.toPublicSnapImage(im)
|
||||
pub.IsDetail = 1
|
||||
pub.LookIndex = out[gi].LookIndex
|
||||
|
||||
out[gi].Detail = append(out[gi].Detail, pub)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Popular 返回热度前 N 个街拍(按图片数降序),供 SSG 首页热门街拍展示。
|
||||
func (s *streetSnapService) Popular(ctx context.Context, limit int) ([]dto.PublicStreetSnap, error) {
|
||||
func (s *streetSnapService) Popular(ctx context.Context, limit int, locale string) ([]dto.PublicStreetSnap, error) {
|
||||
rows, err := s.repo.Popular(ctx, limit)
|
||||
if err != nil {
|
||||
return nil, internalErr(err.Error())
|
||||
@ -270,7 +271,7 @@ func (s *streetSnapService) Popular(ctx context.Context, limit int) ([]dto.Publi
|
||||
for _, r := range rows {
|
||||
list = append(list, dto.PublicStreetSnap{
|
||||
UID: hashid.EncodeWithType(r.ID, hashid.TypeSnap),
|
||||
Title: r.Title,
|
||||
Title: pickLocale(r.Title, r.TitleCn, locale),
|
||||
Cover: s.img.Compose(r.Cover), // 首页热门街拍卡片为大图展示,用高清样式(high)
|
||||
ImageCount: r.ImageCount,
|
||||
})
|
||||
@ -323,7 +324,50 @@ func (s *streetSnapService) toThumbSnapImages(imgs []model.StreetSnapImage) []dt
|
||||
// ── 后台编辑支持 ──────────────────────────────────────────────────────────────
|
||||
|
||||
func (s *streetSnapService) GetForEdit(ctx context.Context, id uint32) (*model.StreetSnap, []model.StreetSnapImage, error) {
|
||||
return s.repo.GetForEdit(ctx, id)
|
||||
snap, imgs, err := s.repo.GetForEdit(ctx, id)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// 编辑页按「主图 → 紧邻其副图」重排:副图的 sort_order 通常排在全部主图之后,
|
||||
// 若原样平铺,副图会被甩到几百张图的最末尾,看起来像「副图没显示」。
|
||||
// 重排后主图与其副图相邻,分组一眼可见;孤儿副图(父不在本记录)追加末尾,不丢图。
|
||||
return snap, orderStreetImagesForEdit(imgs), nil
|
||||
}
|
||||
|
||||
// orderStreetImagesForEdit 把街拍图片重排为「每张主图后紧跟其副图」的扁平顺序。
|
||||
// 同一主图下的副图按 sort_order 升序;父引用指向副图(理论上不发生)的也兜住不丢。
|
||||
func orderStreetImagesForEdit(imgs []model.StreetSnapImage) []model.StreetSnapImage {
|
||||
byID := make(map[uint32]*model.StreetSnapImage, len(imgs))
|
||||
detailsByParent := make(map[uint32][]model.StreetSnapImage)
|
||||
var mains []model.StreetSnapImage
|
||||
for i := range imgs {
|
||||
im := &imgs[i]
|
||||
byID[im.ID] = im
|
||||
if im.IsDetail == 0 {
|
||||
mains = append(mains, *im)
|
||||
} else {
|
||||
detailsByParent[im.ParentImageID] = append(detailsByParent[im.ParentImageID], *im)
|
||||
}
|
||||
}
|
||||
out := make([]model.StreetSnapImage, 0, len(imgs))
|
||||
emitted := make(map[uint32]bool, len(imgs))
|
||||
for _, m := range mains {
|
||||
out = append(out, m)
|
||||
emitted[m.ID] = true
|
||||
ds := detailsByParent[m.ID]
|
||||
sort.Slice(ds, func(i, j int) bool { return ds[i].SortOrder < ds[j].SortOrder })
|
||||
for _, d := range ds {
|
||||
out = append(out, d)
|
||||
emitted[d.ID] = true
|
||||
}
|
||||
}
|
||||
// 未被上述主图认领的副图(父缺失 / 父本身是副图):追加末尾,保证不丢图。
|
||||
for _, im := range imgs {
|
||||
if im.IsDetail == 1 && !emitted[im.ID] {
|
||||
out = append(out, im)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *streetSnapService) UpdateSnap(ctx context.Context, id uint32, fields map[string]any) error {
|
||||
|
||||
Reference in New Issue
Block a user