refactor(street): 审核页改为统一网格+勾选成组;移除并入上一张

This commit is contained in:
toom1996
2026-09-21 23:59:59 +08:00
parent 9807752e4e
commit 0d4000bba5
7 changed files with 63 additions and 206 deletions

View File

@ -96,7 +96,6 @@ func NewBackstage(opt BackstageOptions) *gin.Engine {
admin.POST("/reviews/:kind/:id/images/:img/delete", opt.Backstage.ReviewDraftImageDelete)
// 街拍主副图分组操作(零 JS:同步表单 POST,成功 302 回详情页)。
admin.POST("/reviews/:kind/:id/images/attach", opt.Backstage.ReviewAttachDraftImages)
admin.POST("/reviews/:kind/:id/images/:img/attach-prev", opt.Backstage.ReviewAttachPrevDraftImage)
admin.POST("/reviews/:kind/:id/images/:img/detach", opt.Backstage.ReviewDetachDraftImage)
// 爬虫入库任务监控:列出处理进度 + 失败重试
admin.GET("/ingest-jobs", opt.Backstage.IngestJobs)

View File

@ -324,13 +324,6 @@ func (f *fakeReview) AttachDraftImages(_ context.Context, kind string, draftID,
}
return nil
}
func (f *fakeReview) AttachPrevDraftImage(_ context.Context, kind string, draftID, imgID uint32) error {
f.last.kind, f.last.draftID, f.last.imgID, f.last.op = kind, draftID, imgID, "attach-prev"
if !f.attachOK {
return errors.New("主图无效")
}
return nil
}
func (f *fakeReview) DetachDraftImage(_ context.Context, kind string, draftID, imgID uint32) error {
f.last.kind, f.last.draftID, f.last.imgID, f.last.op = kind, draftID, imgID, "detach"
if !f.attachOK {
@ -363,7 +356,7 @@ func reviewToken(t *testing.T, r *gin.Engine) *http.Cookie {
return cookies[0]
}
// TestBackstageStreetAttachRedirect 批量并入应 302 跳回带 ?main= 的详情页,且参数透传到 service。
// TestBackstageStreetAttachRedirect 合并选中应 302 跳回详情页,且参数透传到 service。
func TestBackstageStreetAttachRedirect(t *testing.T) {
r, rev := newReviewRouter(t)
tok := reviewToken(t, r)
@ -374,34 +367,35 @@ func TestBackstageStreetAttachRedirect(t *testing.T) {
req.AddCookie(tok)
r.ServeHTTP(w, req)
if w.Code != http.StatusFound {
t.Fatalf("并入应 302,实际 %d %s", w.Code, w.Body.String())
t.Fatalf("合并应 302,实际 %d %s", w.Code, w.Body.String())
}
if !strings.Contains(w.Header().Get("Location"), "/admin/reviews/street/7?main=1") {
t.Fatalf("重定向应带 ?main=1,实际 %s", w.Header().Get("Location"))
if !strings.Contains(w.Header().Get("Location"), "/admin/reviews/street/7") {
t.Fatalf("应重定向回详情页,实际 %s", w.Header().Get("Location"))
}
if rev.last.op != "attach" || rev.last.mainID != 1 || len(rev.last.imgIDs) != 2 {
t.Fatalf("参数透传错误: %+v", rev.last)
}
}
// TestBackstageStreetAttachPrev 并入上一张应 302。
func TestBackstageStreetAttachPrev(t *testing.T) {
// TestBackstageStreetAttachNeedsMain 未选主图时应 400,而不是静默失败。
func TestBackstageStreetAttachNeedsMain(t *testing.T) {
r, rev := newReviewRouter(t)
tok := reviewToken(t, r)
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/admin/reviews/street/7/images/2/attach-prev", nil)
req := httptest.NewRequest(http.MethodPost, "/admin/reviews/street/7/images/attach", strings.NewReader("img=2&img=3"))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.AddCookie(tok)
r.ServeHTTP(w, req)
if w.Code != http.StatusFound {
t.Fatalf("并入上一张应 302,实际 %d", w.Code)
if w.Code != http.StatusBadRequest {
t.Fatalf("未选主图应 400,实际 %d", w.Code)
}
if rev.last.op != "attach-prev" || rev.last.imgID != 2 {
t.Fatalf("参数透传错误: %+v", rev.last)
if rev.last.op == "attach" {
t.Fatalf("未选主图不应调用 service")
}
}
// TestBackstageStreetDetach 拆出应 302 跳回「以该图为主图」的详情页。
// TestBackstageStreetDetach 拆出应 302 跳回详情页。
func TestBackstageStreetDetach(t *testing.T) {
r, rev := newReviewRouter(t)
tok := reviewToken(t, r)
@ -413,22 +407,22 @@ func TestBackstageStreetDetach(t *testing.T) {
if w.Code != http.StatusFound {
t.Fatalf("拆出应 302,实际 %d", w.Code)
}
if !strings.Contains(w.Header().Get("Location"), "/admin/reviews/street/7?main=2") {
t.Fatalf("拆出后应以该图为主图跳转,实际 %s", w.Header().Get("Location"))
if !strings.Contains(w.Header().Get("Location"), "/admin/reviews/street/7") {
t.Fatalf("应跳回详情页,实际 %s", w.Header().Get("Location"))
}
if rev.last.op != "detach" || rev.last.imgID != 2 {
t.Fatalf("参数透传错误: %+v", rev.last)
}
}
// TestBackstageStreetRenderGroups 详情页应按组折叠渲染(strip-group + 副图名)。
// TestBackstageStreetRenderGroups 详情页渲染统一网格:复选框 + 主图单选 + 底部合并按钮,且不再有「并入上一张」。
func TestBackstageStreetRenderGroups(t *testing.T) {
r, rev := newReviewRouter(t)
tok := reviewToken(t, r)
rev.view = &service.DraftDetailView{
Kind: "street", ID: 7, Title: "T", Year: 2026, City: "Beijing", ImageCount: 3, Status: "pending",
Images: []service.DraftImageRef{
{ID: 1, Image: "main.jpg", Name: "main", IsDetail: 0},
{ID: 1, Image: "main.jpg", Name: "main", IsDetail: 0, DetailCount: 1},
{ID: 2, Image: "det.jpg", Name: "detail-a", IsDetail: 1, ParentImageID: 1},
{ID: 3, Image: "solo.jpg", Name: "solo", IsDetail: 0},
},
@ -439,36 +433,32 @@ func TestBackstageStreetRenderGroups(t *testing.T) {
}
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/admin/reviews/street/7?main=1", nil)
req := httptest.NewRequest(http.MethodGet, "/admin/reviews/street/7", nil)
req.AddCookie(tok)
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("详情应 200,实际 %d %s", w.Code, w.Body.String())
}
body := w.Body.String()
if !strings.Contains(body, "strip-group") {
t.Fatalf("应按组折叠渲染 strip-group")
}
if !strings.Contains(body, "detail-a") {
t.Fatalf("应渲染副图 detail-a")
}
// 「设为主图」必须是带 ?main= 的 GET 链接(且带 #i<id> 锚点,选完不跳回顶部)。
// (曾被误写成 POST 到 /admin/reviews/street/7,而该路由只有 GET,导致 404、主图根本选不了。)
if !strings.Contains(body, `href="/admin/reviews/street/7?main=1#i1"`) {
t.Fatalf("「设为主图」应是带 ?main= 与 #i<id> 锚点的 GET 链接")
// 副图徽标显示所属主图(沿链归并后的实际主图)。
if !strings.Contains(body, "副图 → #1") {
t.Fatalf("副图应显示所属主图")
}
if strings.Contains(body, `action="/admin/reviews/street/7"`) {
t.Fatalf("不应存在 POST 到详情页的表单(该路由只有 GET)")
}
// 批量并入:勾选框 + 独立提交按钮,用 form="batch" 关联(零 JS、不嵌套表单)。
// 统一网格 + 勾选 + 主图单选 + 底部提交按钮,全部用 form="batch" 关联(零 JS、不嵌套表单)。
if !strings.Contains(body, `id="batch"`) || !strings.Contains(body, `form="batch"`) {
t.Fatalf("应有勾选批量并入当前主图的表单")
t.Fatalf("应有勾选合并的表单")
}
// 「并入上一张」必须出现在每一张图(除第一张)上,否则「连拍同一个人」无法快捷归并。
if !strings.Contains(body, `/admin/reviews/street/7/images/3/attach-prev`) {
t.Fatalf("非首张图应有「并入上一张」")
if !strings.Contains(body, `name="main"`) {
t.Fatalf("应有主图单选(name=main)")
}
if strings.Contains(body, `/admin/reviews/street/7/images/1/attach-prev`) {
t.Fatalf("首张图不应有「并入上一张」(没有上一张)")
if !strings.Contains(body, "合并选中为一组") {
t.Fatalf("底部应有「合并选中为一组」按钮")
}
// 「并入上一张」已废弃:不应再出现在页面或表单里。
if strings.Contains(body, "并入上一张") || strings.Contains(body, "attach-prev") {
t.Fatalf("不应再出现「并入上一张」")
}
}