refactor(publish): 审核侧切到正式表,通过即改状态不再晋升
This commit is contained in:
@ -240,7 +240,8 @@ tbody tr:last-child td { border-bottom: 0; }
|
||||
}
|
||||
.badge.pending { background: #fff4e0; color: #a85b00; }
|
||||
.badge.processing { background: #eef2ff; color: #3b4bb5; }
|
||||
.badge.approved, .badge.done { background: #e6f7ec; color: #1a7f43; }
|
||||
/* published 是单表发布模型的「已通过」(旧值 approved 已被 published 取代)。 */
|
||||
.badge.published, .badge.approved, .badge.done { background: #e6f7ec; color: #1a7f43; }
|
||||
.badge.rejected, .badge.failed { background: #fdeaea; color: #c0392b; }
|
||||
.badge.dup { background: #f3e8ff; color: #6b21a8; }
|
||||
.badge.free { background: #eef2ff; color: #3b4bb5; }
|
||||
|
||||
@ -32,7 +32,7 @@ type reviewBatchReq struct {
|
||||
Items []reviewBatchItem `json:"items"`
|
||||
}
|
||||
|
||||
// ReviewBatch 批量通过 / 驳回审核草稿。
|
||||
// ReviewBatch 批量通过 / 驳回审核记录。
|
||||
//
|
||||
// 请求体:{"action":"approve","items":[{"kind":"street","id":12}, ...]}
|
||||
// 响应: {"ok":2,"failed":[{"id":9,"error":"..."}],"pending":17}
|
||||
@ -49,7 +49,7 @@ func (h *BackstageHandler) ReviewBatch(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if len(req.Items) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "没有选中任何草稿"})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "没有选中任何记录"})
|
||||
return
|
||||
}
|
||||
if len(req.Items) > maxReviewBatch {
|
||||
@ -99,9 +99,10 @@ type reviewAttachReq struct {
|
||||
Imgs []uint32 `json:"imgs"`
|
||||
}
|
||||
|
||||
// ReviewAttachImages 把多张草稿图并入指定主图(街拍审核页「合并选中为一组」的 AJAX 版)。
|
||||
// ReviewAttachImagesAPI 把多张图并入指定主图(街拍审核页「合并选中为一组」的 AJAX 版)。
|
||||
// 响应带回最新的图片分组投影,前端据此就地更新标签,无需整页刷新。
|
||||
func (h *BackstageHandler) ReviewAttachImages(c *gin.Context) {
|
||||
// 加 API 后缀以区别于表单版 ReviewAttachImages:本方法收发 JSON,表单版返回 302。
|
||||
func (h *BackstageHandler) ReviewAttachImagesAPI(c *gin.Context) {
|
||||
kind := c.Param("kind")
|
||||
id := atoiDefault(c.Param("id"), 0)
|
||||
if id == 0 {
|
||||
@ -121,15 +122,15 @@ func (h *BackstageHandler) ReviewAttachImages(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请先勾选要合并的图片"})
|
||||
return
|
||||
}
|
||||
if err := h.review.AttachDraftImages(c.Request.Context(), kind, uint32(id), req.Main, req.Imgs); err != nil {
|
||||
if err := h.review.AttachImages(c.Request.Context(), kind, uint32(id), req.Main, req.Imgs); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
h.respondDraftImages(c, kind, uint32(id))
|
||||
h.respondRecordImages(c, kind, uint32(id))
|
||||
}
|
||||
|
||||
// ReviewDetachImage 把副图拆出恢复为主图(AJAX 版),响应同 ReviewAttachImages。
|
||||
func (h *BackstageHandler) ReviewDetachImage(c *gin.Context) {
|
||||
// ReviewDetachImageAPI 把副图拆出恢复为主图(AJAX 版),响应同 ReviewAttachImagesAPI。
|
||||
func (h *BackstageHandler) ReviewDetachImageAPI(c *gin.Context) {
|
||||
kind := c.Param("kind")
|
||||
id := atoiDefault(c.Param("id"), 0)
|
||||
imgID := atoiDefault(c.Param("img"), 0)
|
||||
@ -137,18 +138,18 @@ func (h *BackstageHandler) ReviewDetachImage(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "参数无效"})
|
||||
return
|
||||
}
|
||||
if err := h.review.DetachDraftImage(c.Request.Context(), kind, uint32(id), uint32(imgID)); err != nil {
|
||||
if err := h.review.DetachImage(c.Request.Context(), kind, uint32(id), uint32(imgID)); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
h.respondDraftImages(c, kind, uint32(id))
|
||||
h.respondRecordImages(c, kind, uint32(id))
|
||||
}
|
||||
|
||||
// respondDraftImages 重新读取草稿并把「图片分组状态」按前端需要的形状返回。
|
||||
// respondRecordImages 重新读取记录并把「图片分组状态」按前端需要的形状返回。
|
||||
//
|
||||
// 投影只带前端更新标签必需的字段,不重复返回图片 URL(页面上的 <img> 已经有了)。
|
||||
func (h *BackstageHandler) respondDraftImages(c *gin.Context, kind string, id uint32) {
|
||||
view, err := h.review.DraftDetail(c.Request.Context(), kind, id)
|
||||
func (h *BackstageHandler) respondRecordImages(c *gin.Context, kind string, id uint32) {
|
||||
view, err := h.review.RecordDetail(c.Request.Context(), kind, id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
|
||||
@ -304,9 +304,9 @@ func (h *BackstageHandler) StreetSnapDetail(c *gin.Context) {
|
||||
renderPage(c, http.StatusOK, "street-snap-detail", gin.H{"Snap": snap})
|
||||
}
|
||||
|
||||
// ── 审核草稿 ───────────────────────────────────────────────────────────────────
|
||||
// ── 审核记录 ───────────────────────────────────────────────────────────────────
|
||||
|
||||
// ReviewList 待审草稿列表:按类型(kind,缺省全部)与状态筛选。
|
||||
// ReviewList 待审记录列表:按类型(kind,缺省全部)与状态筛选。
|
||||
// 类型标签由审核注册表生成(见 ReviewService.KindTabs),新增模块自动出现,无需改此分支。
|
||||
func (h *BackstageHandler) ReviewList(c *gin.Context) {
|
||||
kind := c.Param("kind")
|
||||
@ -329,7 +329,7 @@ func (h *BackstageHandler) ReviewList(c *gin.Context) {
|
||||
if kind != "" && kind != "all" && !registered[kind] {
|
||||
kind = ""
|
||||
}
|
||||
drafts, total, err := h.review.ListDrafts(c.Request.Context(), kind, status, page, 20)
|
||||
records, total, err := h.review.ListRecords(c.Request.Context(), kind, status, page, 20)
|
||||
if err != nil {
|
||||
renderPage(c, http.StatusInternalServerError, "error", gin.H{"Error": err.Error()})
|
||||
return
|
||||
@ -342,7 +342,7 @@ func (h *BackstageHandler) ReviewList(c *gin.Context) {
|
||||
p := pagination.Params{Page: page, PageSize: 20}
|
||||
meta := p.NewMeta(total)
|
||||
renderPage(c, http.StatusOK, "review-list", gin.H{
|
||||
"Drafts": drafts,
|
||||
"Records": records,
|
||||
"Total": total,
|
||||
"Status": status,
|
||||
"Kind": kind,
|
||||
@ -358,23 +358,23 @@ func (h *BackstageHandler) ReviewList(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// ReviewDetail 草稿详情:展示预览 + 可编辑字段 + 通过/驳回操作。
|
||||
// ReviewDetail 记录详情:展示预览 + 可编辑字段 + 通过/驳回操作。
|
||||
func (h *BackstageHandler) ReviewDetail(c *gin.Context) {
|
||||
kind := c.Param("kind")
|
||||
id := atoiDefault(c.Param("id"), 0)
|
||||
if id == 0 {
|
||||
renderPage(c, http.StatusNotFound, "error", gin.H{"Error": "草稿不存在: " + c.Param("id")})
|
||||
renderPage(c, http.StatusNotFound, "error", gin.H{"Error": "记录不存在: " + c.Param("id")})
|
||||
return
|
||||
}
|
||||
draft, err := h.review.DraftDetail(c.Request.Context(), kind, uint32(id))
|
||||
record, err := h.review.RecordDetail(c.Request.Context(), kind, uint32(id))
|
||||
if err != nil {
|
||||
renderPage(c, http.StatusNotFound, "error", gin.H{"Error": "草稿不存在或已删除: " + c.Param("id")})
|
||||
renderPage(c, http.StatusNotFound, "error", gin.H{"Error": "记录不存在或已删除: " + c.Param("id")})
|
||||
return
|
||||
}
|
||||
// 有品牌归属的模块(如走秀)解析品牌名;其余模块 BrandID=0 跳过。
|
||||
brandName := ""
|
||||
if draft.BrandID > 0 {
|
||||
if b, berr := h.brand.Get(c.Request.Context(), hashid.Encode(draft.BrandID)); berr == nil {
|
||||
if record.BrandID > 0 {
|
||||
if b, berr := h.brand.Get(c.Request.Context(), hashid.Encode(record.BrandID)); berr == nil {
|
||||
brandName = b.NameEn
|
||||
if brandName == "" {
|
||||
brandName = b.NameCn
|
||||
@ -382,21 +382,21 @@ func (h *BackstageHandler) ReviewDetail(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
renderPage(c, http.StatusOK, "review-detail", gin.H{
|
||||
"Draft": draft,
|
||||
"Record": record,
|
||||
"BrandName": brandName,
|
||||
})
|
||||
}
|
||||
|
||||
// ReviewApprove 通过:先应用审核人的字段微调,再晋升到正式表。
|
||||
// ReviewApprove 通过:先应用审核人的字段微调,再把记录状态置为已发布(单表模型:不再晋升重建)。
|
||||
func (h *BackstageHandler) ReviewApprove(c *gin.Context) {
|
||||
kind := c.Param("kind")
|
||||
id := atoiDefault(c.Param("id"), 0)
|
||||
if id == 0 {
|
||||
renderPage(c, http.StatusNotFound, "error", gin.H{"Error": "草稿不存在: " + c.Param("id")})
|
||||
renderPage(c, http.StatusNotFound, "error", gin.H{"Error": "记录不存在: " + c.Param("id")})
|
||||
return
|
||||
}
|
||||
fields := draftFormFields(c)
|
||||
if err := h.review.UpdateDraft(c.Request.Context(), kind, uint32(id), fields); err != nil {
|
||||
fields := reviewFormFields(c)
|
||||
if err := h.review.UpdateRecord(c.Request.Context(), kind, uint32(id), fields); err != nil {
|
||||
renderPage(c, http.StatusInternalServerError, "error", gin.H{"Error": err.Error()})
|
||||
return
|
||||
}
|
||||
@ -413,7 +413,7 @@ func (h *BackstageHandler) ReviewReject(c *gin.Context) {
|
||||
kind := c.Param("kind")
|
||||
id := atoiDefault(c.Param("id"), 0)
|
||||
if id == 0 {
|
||||
renderPage(c, http.StatusNotFound, "error", gin.H{"Error": "草稿不存在: " + c.Param("id")})
|
||||
renderPage(c, http.StatusNotFound, "error", gin.H{"Error": "记录不存在: " + c.Param("id")})
|
||||
return
|
||||
}
|
||||
reason := c.PostForm("reason")
|
||||
@ -425,9 +425,9 @@ func (h *BackstageHandler) ReviewReject(c *gin.Context) {
|
||||
c.Redirect(http.StatusFound, reviewListURL(kind))
|
||||
}
|
||||
|
||||
// ReviewDraftImageDelete 软删审核草稿里的单张图片(审核阶段剔除坏图,不删S4文件)。
|
||||
// 校验 kind 合法后委派给审核服务,成功后跳回草稿详情页。
|
||||
func (h *BackstageHandler) ReviewDraftImageDelete(c *gin.Context) {
|
||||
// ReviewImageDelete 软删审核记录里的单张图片(审核阶段剔除坏图,不删S4文件)。
|
||||
// 校验 kind 合法后委派给审核服务,成功后跳回记录详情页。
|
||||
func (h *BackstageHandler) ReviewImageDelete(c *gin.Context) {
|
||||
kind := c.Param("kind")
|
||||
id := atoiDefault(c.Param("id"), 0)
|
||||
imgID := atoiDefault(c.Param("img"), 0)
|
||||
@ -435,16 +435,17 @@ func (h *BackstageHandler) ReviewDraftImageDelete(c *gin.Context) {
|
||||
renderPage(c, http.StatusBadRequest, "error", gin.H{"Error": "参数无效"})
|
||||
return
|
||||
}
|
||||
if err := h.review.DeleteDraftImage(c.Request.Context(), kind, uint32(id), uint32(imgID)); err != nil {
|
||||
if err := h.review.DeleteImage(c.Request.Context(), kind, uint32(id), uint32(imgID)); err != nil {
|
||||
renderPage(c, http.StatusInternalServerError, "error", gin.H{"Error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.Redirect(http.StatusFound, "/admin/reviews/"+kind+"/"+c.Param("id"))
|
||||
}
|
||||
|
||||
// ReviewAttachDraftImages 把勾选的草稿图并入指定主图(街拍审核页「合并选中为一组」)。
|
||||
// ReviewAttachImages 把勾选的图并入指定主图(街拍审核页「合并选中为一组」的表单版)。
|
||||
// 主图由表单里的单选 name="main" 指定;待并入的图由多个 checkbox name="img" 提交。
|
||||
func (h *BackstageHandler) ReviewAttachDraftImages(c *gin.Context) {
|
||||
// (AJAX 版同名的 JSON 接口是 ReviewAttachImagesAPI —— 两者响应形态不同,故不能合并成一个。)
|
||||
func (h *BackstageHandler) ReviewAttachImages(c *gin.Context) {
|
||||
kind := c.Param("kind")
|
||||
id := atoiDefault(c.Param("id"), 0)
|
||||
if id == 0 {
|
||||
@ -466,15 +467,15 @@ func (h *BackstageHandler) ReviewAttachDraftImages(c *gin.Context) {
|
||||
renderPage(c, http.StatusBadRequest, "error", gin.H{"Error": "请在要作为主图的图片上点选「主图」"})
|
||||
return
|
||||
}
|
||||
if err := h.review.AttachDraftImages(c.Request.Context(), kind, uint32(id), mainID, imgIDs); err != nil {
|
||||
if err := h.review.AttachImages(c.Request.Context(), kind, uint32(id), mainID, imgIDs); err != nil {
|
||||
renderPage(c, http.StatusInternalServerError, "error", gin.H{"Error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.Redirect(http.StatusFound, reviewDetailURL(kind, uint32(id)))
|
||||
}
|
||||
|
||||
// ReviewDetachDraftImage 把副图拆出恢复为主图。
|
||||
func (h *BackstageHandler) ReviewDetachDraftImage(c *gin.Context) {
|
||||
// ReviewDetachImage 把副图拆出恢复为主图(表单版;AJAX 版见 ReviewDetachImageAPI)。
|
||||
func (h *BackstageHandler) ReviewDetachImage(c *gin.Context) {
|
||||
kind := c.Param("kind")
|
||||
id := atoiDefault(c.Param("id"), 0)
|
||||
imgID := atoiDefault(c.Param("img"), 0)
|
||||
@ -482,7 +483,7 @@ func (h *BackstageHandler) ReviewDetachDraftImage(c *gin.Context) {
|
||||
renderPage(c, http.StatusBadRequest, "error", gin.H{"Error": "参数无效"})
|
||||
return
|
||||
}
|
||||
if err := h.review.DetachDraftImage(c.Request.Context(), kind, uint32(id), uint32(imgID)); err != nil {
|
||||
if err := h.review.DetachImage(c.Request.Context(), kind, uint32(id), uint32(imgID)); err != nil {
|
||||
renderPage(c, http.StatusInternalServerError, "error", gin.H{"Error": err.Error()})
|
||||
return
|
||||
}
|
||||
@ -571,9 +572,9 @@ func (h *BackstageHandler) IngestJobRetry(c *gin.Context) {
|
||||
c.Redirect(http.StatusFound, "/admin/ingest-jobs")
|
||||
}
|
||||
|
||||
// draftFormFields 从表单收集可编辑草稿字段(runway + street 一并收集,
|
||||
// reviewFormFields 从表单收集可编辑字段(runway + street 一并收集,
|
||||
// 最终白名单在仓储层按 kind 强制,非本 kind 的字段会被忽略)。
|
||||
func draftFormFields(c *gin.Context) map[string]any {
|
||||
func reviewFormFields(c *gin.Context) map[string]any {
|
||||
f := map[string]any{}
|
||||
if v := c.PostForm("title_en"); v != "" {
|
||||
f["title_en"] = v
|
||||
|
||||
@ -74,7 +74,7 @@ func TestBackstageTemplatesRender(t *testing.T) {
|
||||
"UID": "abc123", "NameEn": "Acne", "NameCn": "", "Account": "",
|
||||
"Images": []any{},
|
||||
// 列表数据
|
||||
"Items": []any{}, "Users": []any{}, "Kinds": []any{}, "Drafts": []any{},
|
||||
"Items": []any{}, "Users": []any{}, "Kinds": []any{}, "Records": []any{},
|
||||
// 详情页实体(.Snap 在两个页面里类型不同,见下面的 perPage 覆盖)
|
||||
"Article": &dto.PublicArticleDetail{UID: "abc123"},
|
||||
"Snap": &dto.PublicStreetSnapDetail{UID: "abc123"},
|
||||
@ -100,7 +100,7 @@ func TestBackstageTemplatesRender(t *testing.T) {
|
||||
for k, v := range perPage[page] {
|
||||
data[k] = v
|
||||
}
|
||||
data["Draft"] = &service.DraftDetailView{Kind: kind, ID: 7, Title: "T", Status: "pending"}
|
||||
data["Record"] = &service.RecordDetailView{Kind: kind, ID: 7, Title: "T", Status: "pending"}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
|
||||
@ -1,37 +1,37 @@
|
||||
{{define "title"}}草稿审核 · 管理后台{{end}}
|
||||
{{define "barLabel"}}管理后台 · 草稿审核 #{{.Draft.ID}}{{end}}
|
||||
{{define "barRight"}}<a href="{{reviewDetailURL .Draft.Kind .Draft.ID}}">返回列表</a> · <a href="/admin/logout">退出</a>{{end}}
|
||||
{{define "barLabel"}}管理后台 · 草稿审核 #{{.Record.ID}}{{end}}
|
||||
{{define "barRight"}}<a href="{{reviewDetailURL .Record.Kind .Record.ID}}">返回列表</a> · <a href="/admin/logout">退出</a>{{end}}
|
||||
{{define "content"}}
|
||||
{{template "bar" .}}
|
||||
<div class="main mid">
|
||||
<p class="muted">类型 <b>{{.Draft.Kind}}</b>{{if .BrandName}} · 品牌 {{.BrandName}}{{end}} · 状态 <b>{{.Draft.Status}}</b></p>
|
||||
<p class="muted">类型 <b>{{.Record.Kind}}</b>{{if .BrandName}} · 品牌 {{.BrandName}}{{end}} · 状态 <b>{{.Record.Status}}</b></p>
|
||||
|
||||
{{/* 字段微调 + 通过:仍是普通表单提交(无 JS 也能用);键盘 a 会触发同一个表单 */}}
|
||||
<form method="post" action="/admin/reviews/{{.Draft.Kind}}/{{.Draft.ID}}/approve">
|
||||
{{range .Draft.Fields}}<div><label>{{.Label}}</label>{{if eq .Type "textarea"}}<textarea name="{{.Name}}">{{.Value}}</textarea>{{else}}<input name="{{.Name}}" value="{{.Value}}"{{if eq .Type "number"}} inputmode="numeric"{{end}}>{{end}}</div>{{end}}
|
||||
<form method="post" action="/admin/reviews/{{.Record.Kind}}/{{.Record.ID}}/approve">
|
||||
{{range .Record.Fields}}<div><label>{{.Label}}</label>{{if eq .Type "textarea"}}<textarea name="{{.Name}}">{{.Value}}</textarea>{{else}}<input name="{{.Name}}" value="{{.Value}}"{{if eq .Type "number"}} inputmode="numeric"{{end}}>{{end}}</div>{{end}}
|
||||
<button type="submit">通过并发布</button>
|
||||
</form>
|
||||
|
||||
{{if eq .Draft.Kind "street"}}
|
||||
{{if eq .Record.Kind "street"}}
|
||||
<div class="hint">
|
||||
操作:勾选属于同一个人的图片 → 在<b>其中一张</b>上点「主图」→ 点底部「合并选中为一组」。副图上的「拆出」可恢复为独立主图。<br>
|
||||
快捷键:<b>j</b>/<b>k</b> 上下移动 · <b>x</b> 勾选 · <b>m</b> 设为主图 · <b>d</b> 拆出 · <b>Enter</b> 看大图 · <b>a</b> 通过
|
||||
<button type="button" class="x" id="hint-dismiss" aria-label="收起提示">✕</button>
|
||||
</div>
|
||||
|
||||
<h2>图片(<span id="img-stat">{{len .Draft.Images}} 张 · {{len .Draft.Groups}} 组</span>)</h2>
|
||||
<h2>图片(<span id="img-stat">{{len .Record.Images}} 张 · {{len .Record.Groups}} 组</span>)</h2>
|
||||
{{/* 合并表单本体在 grid 之前;各格的 radio/checkbox 通过 form="batch" 关联过来 */}}
|
||||
<form id="batch" method="post" action="/admin/reviews/{{.Draft.Kind}}/{{.Draft.ID}}/images/attach"></form>
|
||||
<form id="batch" method="post" action="/admin/reviews/{{.Record.Kind}}/{{.Record.ID}}/images/attach"></form>
|
||||
<div class="grid">
|
||||
{{range .Draft.Images}}<div class="cell{{if eq .IsDetail 1}} det{{end}}" data-img-id="{{.ID}}">
|
||||
{{range .Record.Images}}<div class="cell{{if eq .IsDetail 1}} det{{end}}" data-img-id="{{.ID}}">
|
||||
<button class="thumb" type="button" data-zoom><img src="{{.Image}}" loading="lazy"></button>
|
||||
<span class="role">{{if eq .IsDetail 1}}副图 → #{{.ParentImageID}}{{else}}主图{{if .DetailCount}} · {{.DetailCount}} 副图{{end}}{{end}}</span>
|
||||
<span class="nm">{{.Name}}</span>
|
||||
<div class="acts">
|
||||
{{if eq .IsDetail 0}}<label class="chk" data-only="main"><input type="radio" form="batch" name="main" value="{{.ID}}"> 主图</label>{{end}}
|
||||
<label class="chk"><input type="checkbox" form="batch" name="img" value="{{.ID}}"> 选入</label>
|
||||
{{if eq .IsDetail 1}}<form data-only="detail" method="post" action="/admin/reviews/{{$.Draft.Kind}}/{{$.Draft.ID}}/images/{{.ID}}/detach"><button type="submit">拆出</button></form>{{end}}
|
||||
<form method="post" action="/admin/reviews/{{$.Draft.Kind}}/{{$.Draft.ID}}/images/{{.ID}}/delete"><button type="submit" class="reject" onclick="return confirm('确认删除这张图片?')">删除</button></form>
|
||||
{{if eq .IsDetail 1}}<form data-only="detail" method="post" action="/admin/reviews/{{$.Record.Kind}}/{{$.Record.ID}}/images/{{.ID}}/detach"><button type="submit">拆出</button></form>{{end}}
|
||||
<form method="post" action="/admin/reviews/{{$.Record.Kind}}/{{$.Record.ID}}/images/{{.ID}}/delete"><button type="submit" class="reject" onclick="return confirm('确认删除这张图片?')">删除</button></form>
|
||||
</div>
|
||||
</div>{{end}}
|
||||
</div>
|
||||
@ -41,13 +41,13 @@
|
||||
<span class="muted">勾选同一人的图,并在其中一张主图上点「主图」</span>
|
||||
</div>
|
||||
{{else}}
|
||||
<h2>图片管理({{len .Draft.Images}} 张)</h2>
|
||||
<h2>图片管理({{len .Record.Images}} 张)</h2>
|
||||
<div class="grid">
|
||||
{{range .Draft.Images}}<div class="cell" data-img-id="{{.ID}}">
|
||||
{{range .Record.Images}}<div class="cell" data-img-id="{{.ID}}">
|
||||
<button class="thumb" type="button" data-zoom><img src="{{.Image}}" loading="lazy"></button>
|
||||
<span class="nm">{{.Name}}</span>
|
||||
<div class="acts">
|
||||
<form method="post" action="/admin/reviews/{{$.Draft.Kind}}/{{$.Draft.ID}}/images/{{.ID}}/delete"><button type="submit" class="reject" onclick="return confirm('确认删除这张图片?')">删除</button></form>
|
||||
<form method="post" action="/admin/reviews/{{$.Record.Kind}}/{{$.Record.ID}}/images/{{.ID}}/delete"><button type="submit" class="reject" onclick="return confirm('确认删除这张图片?')">删除</button></form>
|
||||
</div>
|
||||
</div>{{end}}
|
||||
</div>
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
<div class="tabs">
|
||||
<a href="/admin/reviews{{.ListBase}}" class="{{if eq .Status ""}}on{{end}}">全部</a>
|
||||
<a href="/admin/reviews{{.ListBase}}?status=pending" class="{{if eq .Status "pending"}}on{{end}}">待审核</a>
|
||||
<a href="/admin/reviews{{.ListBase}}?status=approved" class="{{if eq .Status "approved"}}on{{end}}">已通过</a>
|
||||
<a href="/admin/reviews{{.ListBase}}?status=published" class="{{if eq .Status "published"}}on{{end}}">已通过</a>
|
||||
<a href="/admin/reviews{{.ListBase}}?status=rejected" class="{{if eq .Status "rejected"}}on{{end}}">已驳回</a>
|
||||
</div>
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
<th>ID</th><th>类型</th><th>标题</th><th>副标题</th><th>图数</th><th>状态</th><th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{{range .Drafts}}<tr data-kind="{{.Kind}}" data-id="{{.ID}}">
|
||||
{{range .Records}}<tr data-kind="{{.Kind}}" data-id="{{.ID}}">
|
||||
<td><input type="checkbox" class="row-pick" aria-label="选择这条"></td>
|
||||
<td><code>{{.ID}}</code></td>
|
||||
<td>{{if eq .Kind "street"}}<span class="kind street">{{.Kind}}</span>{{else}}<span class="kind">{{.Kind}}</span>{{end}}</td>
|
||||
@ -32,7 +32,7 @@
|
||||
<td class="muted">{{.Subtitle}}</td>
|
||||
<td>{{.ImageCount}}</td>
|
||||
<td><span class="badge {{.Status}}">{{.Status}}</span></td>
|
||||
<td><a class="view" href="/admin/reviews/{{.Kind}}/{{.ID}}">查看/审核</a>{{if eq .Status "pending"}} · <button type="button" class="btn" data-quick-approve title="按当前内容直接晋升到正式表">通过</button>{{end}}</td>
|
||||
<td><a class="view" href="/admin/reviews/{{.Kind}}/{{.ID}}">查看/审核</a>{{if eq .Status "pending"}} · <button type="button" class="btn" data-quick-approve title="按当前内容直接发布">通过</button>{{end}}</td>
|
||||
</tr>{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@ -29,9 +29,9 @@
|
||||
<a href="/admin/runways">清除</a>
|
||||
</form>
|
||||
<table>
|
||||
<thead><tr><th>编码 ID</th><th>品牌</th><th>标题</th><th>系列</th><th>季节</th><th>年份</th><th>季节码</th><th>图数</th><th></th></tr></thead>
|
||||
<thead><tr><th>编码 ID</th><th>品牌</th><th>标题</th><th>系列</th><th>季节</th><th>年份</th><th>季节码</th><th>图数</th><th>状态</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{{range .Items}}<tr><td><code>{{.UID}}</code></td><td>{{.BrandName}}</td><td>{{.Title}}</td><td>{{.CollectionType}}</td><td>{{.Season}}</td><td>{{.Year}}</td><td>{{.SeasonCode}}</td><td>{{.ImageCount}}</td><td><a class="view" href="/admin/runways/{{.UID}}">查看</a></td></tr>{{end}}
|
||||
{{range .Items}}<tr><td><code>{{.UID}}</code></td><td>{{.BrandName}}</td><td>{{.Title}}</td><td>{{.CollectionType}}</td><td>{{.Season}}</td><td>{{.Year}}</td><td>{{.SeasonCode}}</td><td>{{.ImageCount}}</td><td><span class="badge {{.Status}}">{{.Status}}</span></td><td><a class="view" href="/admin/runways/{{.UID}}">查看</a></td></tr>{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{template "pager" .Pager}}
|
||||
|
||||
@ -17,9 +17,9 @@
|
||||
<a href="/admin/street-snaps">清除</a>
|
||||
</form>
|
||||
<table>
|
||||
<thead><tr><th>编码 ID</th><th>标题</th><th>图片数</th><th></th></tr></thead>
|
||||
<thead><tr><th>编码 ID</th><th>标题</th><th>图片数</th><th>状态</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{{range .Items}}<tr><td><code>{{.UID}}</code></td><td>{{.Title}}</td><td>{{.ImageCount}}</td><td><a class="view" href="/admin/street-snaps/{{.UID}}">查看</a></td></tr>{{end}}
|
||||
{{range .Items}}<tr><td><code>{{.UID}}</code></td><td>{{.Title}}</td><td>{{.ImageCount}}</td><td><span class="badge {{.Status}}">{{.Status}}</span></td><td><a class="view" href="/admin/street-snaps/{{.UID}}">查看</a></td></tr>{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{template "pager" .Pager}}
|
||||
|
||||
Reference in New Issue
Block a user