refactor(street): 审核页改为统一网格+勾选成组;移除并入上一张
This commit is contained in:
@ -43,7 +43,7 @@ func NewBackstageHandler(auth service.AuthService, brand service.BrandService, a
|
||||
// 以「base」为根模板并只含一个 define;renderHTML 每次渲染时 Clone 它再灌入页面正文,
|
||||
// 这样页面模板即可直接引用 {{template "pager"}} 而无需各自复制翻页 HTML。
|
||||
var pagerTmpl = template.Must(template.New("base").Funcs(template.FuncMap{
|
||||
// reviewDetailURL 生成审核详情页地址;main 非零时附加 ?main= 以保留折叠状态。
|
||||
// reviewDetailURL 生成审核详情页地址。
|
||||
"reviewDetailURL": reviewDetailURL,
|
||||
}).Parse(`{{define "pager"}}<div class="pager">
|
||||
{{if .HasPrev}}<a href="{{.PrevURL}}">上一页</a>{{else}}<a class="off">上一页</a>{{end}}
|
||||
@ -397,8 +397,6 @@ func (h *BackstageHandler) ReviewDetail(c *gin.Context) {
|
||||
renderHTML(c, http.StatusNotFound, tplError, gin.H{"Error": "草稿不存在或已删除: " + c.Param("id")})
|
||||
return
|
||||
}
|
||||
// 当前选定的主图(用于审核页按组折叠时保持折叠状态),来自 ?main= 查询参数。
|
||||
draft.MainID = uint32(atoiDefault(c.Query("main"), 0))
|
||||
// 有品牌归属的模块(如走秀)解析品牌名;其余模块 BrandID=0 跳过。
|
||||
brandName := ""
|
||||
if draft.BrandID > 0 {
|
||||
@ -470,7 +468,8 @@ func (h *BackstageHandler) ReviewDraftImageDelete(c *gin.Context) {
|
||||
c.Redirect(http.StatusFound, "/admin/reviews/"+kind+"/"+c.Param("id"))
|
||||
}
|
||||
|
||||
// ReviewAttachDraftImages 把若干张草稿图并入指定主图(街拍审核页「批量并入」)。
|
||||
// ReviewAttachDraftImages 把勾选的草稿图并入指定主图(街拍审核页「合并选中为一组」)。
|
||||
// 主图由表单里的单选 name="main" 指定;待并入的图由多个 checkbox name="img" 提交。
|
||||
func (h *BackstageHandler) ReviewAttachDraftImages(c *gin.Context) {
|
||||
kind := c.Param("kind")
|
||||
id := atoiDefault(c.Param("id"), 0)
|
||||
@ -485,32 +484,19 @@ func (h *BackstageHandler) ReviewAttachDraftImages(c *gin.Context) {
|
||||
imgIDs = append(imgIDs, uint32(v))
|
||||
}
|
||||
}
|
||||
if len(imgIDs) == 0 {
|
||||
renderHTML(c, http.StatusBadRequest, tplError, gin.H{"Error": "请先勾选要合并的图片"})
|
||||
return
|
||||
}
|
||||
if mainID == 0 {
|
||||
renderHTML(c, http.StatusBadRequest, tplError, gin.H{"Error": "请先选定主图"})
|
||||
renderHTML(c, http.StatusBadRequest, tplError, gin.H{"Error": "请在要作为主图的图片上点选「主图」"})
|
||||
return
|
||||
}
|
||||
if err := h.review.AttachDraftImages(c.Request.Context(), kind, uint32(id), mainID, imgIDs); err != nil {
|
||||
renderHTML(c, http.StatusInternalServerError, tplError, gin.H{"Error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.Redirect(http.StatusFound, reviewDetailURL(kind, uint32(id), mainID))
|
||||
}
|
||||
|
||||
// ReviewAttachPrevDraftImage 把某张图并入「同草稿内上一行所属组的主图」。
|
||||
func (h *BackstageHandler) ReviewAttachPrevDraftImage(c *gin.Context) {
|
||||
kind := c.Param("kind")
|
||||
id := atoiDefault(c.Param("id"), 0)
|
||||
imgID := atoiDefault(c.Param("img"), 0)
|
||||
if id == 0 || imgID == 0 {
|
||||
renderHTML(c, http.StatusBadRequest, tplError, gin.H{"Error": "参数无效"})
|
||||
return
|
||||
}
|
||||
if err := h.review.AttachPrevDraftImage(c.Request.Context(), kind, uint32(id), uint32(imgID)); err != nil {
|
||||
renderHTML(c, http.StatusInternalServerError, tplError, gin.H{"Error": err.Error()})
|
||||
return
|
||||
}
|
||||
mainID := uint32(atoiDefault(c.Query("main"), 0))
|
||||
c.Redirect(http.StatusFound, reviewDetailURL(kind, uint32(id), mainID))
|
||||
c.Redirect(http.StatusFound, reviewDetailURL(kind, uint32(id)))
|
||||
}
|
||||
|
||||
// ReviewDetachDraftImage 把副图拆出恢复为主图。
|
||||
@ -526,18 +512,12 @@ func (h *BackstageHandler) ReviewDetachDraftImage(c *gin.Context) {
|
||||
renderHTML(c, http.StatusInternalServerError, tplError, gin.H{"Error": err.Error()})
|
||||
return
|
||||
}
|
||||
// 拆出后该图自身成为主图,跳转回到「以它为主图」的折叠视图。
|
||||
c.Redirect(http.StatusFound, reviewDetailURL(kind, uint32(id), uint32(imgID)))
|
||||
c.Redirect(http.StatusFound, reviewDetailURL(kind, uint32(id)))
|
||||
}
|
||||
|
||||
// reviewDetailURL 生成审核详情页跳转地址;main 非零时附加 ?main= 以保留折叠状态。
|
||||
// main 为可选参数:省略或传 0 则不附加查询串。
|
||||
func reviewDetailURL(kind string, id uint32, mainArgs ...uint32) string {
|
||||
u := "/admin/reviews/" + kind + "/" + strconv.FormatUint(uint64(id), 10)
|
||||
if len(mainArgs) > 0 && mainArgs[0] != 0 {
|
||||
u += "?main=" + strconv.FormatUint(uint64(mainArgs[0]), 10)
|
||||
}
|
||||
return u
|
||||
// reviewDetailURL 生成审核详情页跳转地址。
|
||||
func reviewDetailURL(kind string, id uint32) string {
|
||||
return "/admin/reviews/" + kind + "/" + strconv.FormatUint(uint64(id), 10)
|
||||
}
|
||||
|
||||
// reviewListURL 根据 kind 生成审核列表跳转地址(带 kind 段或裸 /admin/reviews)。
|
||||
@ -1338,32 +1318,18 @@ button.ghost{background:#fff;color:#111;border:1px solid #ccc}
|
||||
.kind{font-size:12px;padding:1px 7px;border-radius:10px;background:#eef2ff;color:#3b4bb5}
|
||||
.kind.street{background:#eafaf0;color:#1a7f43}
|
||||
.hint{position:sticky;top:0;background:#fffbe6;border:1px solid #f5e08a;color:#7a5b00;padding:8px 12px;border-radius:6px;margin:12px 0;font-size:13px}
|
||||
.strip-group{background:#fff;border:1px solid #eee;border-radius:10px;padding:12px;margin:12px 0}
|
||||
.strip-group.cur{border-color:#1a7f43}
|
||||
.strip-group .main-img{display:flex;gap:14px;align-items:flex-start}
|
||||
.strip-group .main-img img{width:140px;height:210px;object-fit:cover;border-radius:8px}
|
||||
.strip-group .main-img .acts{flex:1;display:flex;gap:6px;flex-wrap:wrap}
|
||||
.strip-group .main-img .acts button{margin:0;padding:7px 12px;font-size:13px}
|
||||
.strip-group form.inline{display:inline}
|
||||
.strip{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:8px;margin-top:10px}
|
||||
.strip .det{position:relative;background:#fafafa;border:1px solid #eee;border-radius:8px;overflow:hidden}
|
||||
.strip .det img{width:100%;height:160px;object-fit:cover;display:block}
|
||||
.strip .det .acts{padding:6px;display:flex;gap:6px}
|
||||
.strip .det .acts button{margin:0;flex:1;padding:6px;font-size:12px}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:10px;margin-top:14px}
|
||||
.cell{background:#fff;border:1px solid #eee;border-radius:8px;overflow:hidden}
|
||||
.cell img{width:100%;display:block;aspect-ratio:2/3;object-fit:cover}
|
||||
.cell .role{display:block;padding:6px 8px 0;font-size:12px;color:#1a7f43}
|
||||
.cell .nm{display:block;padding:2px 8px 6px;font-size:12px;color:#111;word-break:break-all}
|
||||
.cell.det{background:#f8faff;border-color:#c7d2fe}
|
||||
.cell.det .role{color:#3b4bb5}
|
||||
.cell .acts{padding:8px;display:flex;flex-wrap:wrap;gap:6px}
|
||||
.cell .acts button{margin:0;flex:1 1 auto;padding:7px;font-size:13px}
|
||||
.btn{display:inline-block;flex:1 1 auto;margin:0;padding:7px 10px;background:#fff;color:#111;border:1px solid #ccc;border-radius:6px;text-decoration:none;font-size:13px;text-align:center}
|
||||
.chk{display:flex;align-items:center;gap:4px;flex:1 1 auto;font-size:12px;color:#333;white-space:nowrap;cursor:pointer}
|
||||
.chk input{margin:0}
|
||||
.meta{flex:1;min-width:0}
|
||||
.cap{font-size:12px;color:#666;margin-bottom:2px}
|
||||
.cap b{color:#1a7f43}
|
||||
.nm{font-size:13px;color:#111;margin-bottom:6px;word-break:break-all}
|
||||
.tag{display:inline-block;font-size:11px;padding:1px 6px;border-radius:8px;background:#eafaf0;color:#1a7f43}
|
||||
.tag.det{background:#eef2ff;color:#3b4bb5}
|
||||
.batchbar{position:sticky;bottom:0;background:#fff;border-top:1px solid #eee;padding:10px 0;margin-top:16px;display:flex;align-items:center;gap:10px}
|
||||
</style></head>
|
||||
<body>
|
||||
@ -1375,39 +1341,23 @@ button.ghost{background:#fff;color:#111;border:1px solid #ccc}
|
||||
<button type="submit">通过并发布</button>
|
||||
</form>
|
||||
{{if eq .Draft.Kind "street"}}
|
||||
<div class="hint">{{if .Draft.MainID}}当前主图:<b>#{{.Draft.MainID}}</b>。{{else}}尚未选定主图。{{end}}最快路径:<b>连拍同一个人</b> → 从上往下逐张点「并入上一张」;<b>非相邻</b>的图要归到某张主图下 → 先点那张图的「设为主图」,再勾选要并入的图,最后点底部「并入到当前主图」。</div>
|
||||
<h2>图片流({{len .Draft.Groups}} 张主图 · 共 {{.Draft.ImageCount}} 张)</h2>
|
||||
<form id="batch" method="post" action="/admin/reviews/{{.Draft.Kind}}/{{.Draft.ID}}/images/attach">
|
||||
<input type="hidden" name="main" value="{{.Draft.MainID}}">
|
||||
</form>
|
||||
{{if .Draft.Groups}}{{range $gi, $g := .Draft.Groups}}<div class="strip-group{{if eq $g.Image.ID $.Draft.MainID}} cur{{end}}" id="i{{$g.Image.ID}}">
|
||||
<div class="main-img">
|
||||
<img src="{{$g.Image.Image}}" loading="lazy">
|
||||
<div class="meta">
|
||||
<div class="cap"><span class="tag">主图</span>{{if $g.Image.DetailCount}} · {{$g.Image.DetailCount}} 张副图{{end}}{{if eq $g.Image.ID $.Draft.MainID}} · <b>当前主图</b>{{end}}</div>
|
||||
<div class="nm">{{$g.Image.Name}}</div>
|
||||
<div class="acts">
|
||||
<a class="btn" href="{{reviewDetailURL $.Draft.Kind $.Draft.ID $g.Image.ID}}#i{{$g.Image.ID}}">设为主图</a>
|
||||
{{if $gi}}<form method="post" action="/admin/reviews/{{$.Draft.Kind}}/{{$.Draft.ID}}/images/{{$g.Image.ID}}/attach-prev{{if $.Draft.MainID}}?main={{$.Draft.MainID}}{{end}}"><button type="submit">并入上一张</button></form>{{end}}
|
||||
{{if ne $g.Image.ID $.Draft.MainID}}<label class="chk"><input type="checkbox" form="batch" name="img" value="{{$g.Image.ID}}"> 选入</label>{{end}}
|
||||
<form method="post" action="/admin/reviews/{{$.Draft.Kind}}/{{$.Draft.ID}}/images/{{$g.Image.ID}}/delete"><button type="submit" class="reject" onclick="return confirm('确认删除这张图片?')">删除</button></form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{if $g.Details}}<div class="strip">
|
||||
{{range $g.Details}}<div class="det" id="i{{.ID}}">
|
||||
<div class="hint">操作:勾选属于同一个人的图片 → 在<b>其中一张</b>上点「主图」→ 点底部「合并选中为一组」。副图上的「拆出」可恢复为独立主图。</div>
|
||||
<h2>图片({{len .Draft.Images}} 张 · {{len .Draft.Groups}} 组)</h2>
|
||||
<form id="batch" method="post" action="/admin/reviews/{{.Draft.Kind}}/{{.Draft.ID}}/images/attach"></form>
|
||||
<div class="grid">
|
||||
{{range .Draft.Images}}<div class="cell{{if eq .IsDetail 1}} det{{end}}">
|
||||
<img src="{{.Image}}" loading="lazy">
|
||||
<div class="cap"><span class="tag det">副图</span> → #{{$g.Image.ID}}</div>
|
||||
<div class="nm">{{.Name}}</div>
|
||||
<span class="role">{{if eq .IsDetail 1}}副图 → #{{.ParentImageID}}{{else}}主图{{if .DetailCount}} · {{.DetailCount}} 副图{{end}}{{end}}</span>
|
||||
<span class="nm">{{.Name}}</span>
|
||||
<div class="acts">
|
||||
<form method="post" action="/admin/reviews/{{$.Draft.Kind}}/{{$.Draft.ID}}/images/{{.ID}}/attach-prev{{if $.Draft.MainID}}?main={{$.Draft.MainID}}{{end}}"><button type="submit">并入上一张</button></form>
|
||||
<form method="post" action="/admin/reviews/{{$.Draft.Kind}}/{{$.Draft.ID}}/images/{{.ID}}/detach"><button type="submit">拆出</button></form>
|
||||
<label class="chk"><input type="checkbox" form="batch" name="img" value="{{.ID}}"> 选入</label>
|
||||
{{if eq .IsDetail 0}}<label class="chk"><input type="radio" form="batch" name="main" value="{{.ID}}"> 主图</label>{{end}}
|
||||
{{if eq .IsDetail 1}}<form 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>
|
||||
</div>
|
||||
</div>{{end}}
|
||||
</div>{{end}}
|
||||
</div>{{end}}{{else}}<p class="muted">该草稿暂无图片。</p>{{end}}
|
||||
<div class="batchbar"><button type="submit" form="batch"{{if not .Draft.MainID}} disabled{{end}}>并入到当前主图{{if .Draft.MainID}} #{{.Draft.MainID}}{{end}}</button>{{if not .Draft.MainID}}<span class="muted">(先点某张主图上的「设为主图」)</span>{{end}}</div>
|
||||
</div>
|
||||
<div class="batchbar"><button type="submit" form="batch">合并选中为一组</button><span class="muted">勾选同一人的图,并在其中一张主图上点「主图」</span></div>
|
||||
{{else}}
|
||||
<h2>图片管理({{len .Draft.Images}} 张)</h2>
|
||||
<div class="grid">
|
||||
|
||||
Reference in New Issue
Block a user