crawl(品牌抓取任务队列) - /admin/internal/crawl/tasks 领取 / :id/result 回报,spider 可常驻拉任务 - 后台品牌列表支持「加入抓取队列」,队列页可看状态与重试 - 租约超时的 running 自动回收为 queued,避免 worker 崩溃后任务卡死 media(媒体机直传) - ingest 载荷新增 main_key/detail_keys/main_size/detail_sizes: 有 key 就直接落库,跳过下载与上传;与 URL 形态可逐图混用 - 细节图按 Details/DetailKeys 两者较长者遍历(只给 key 时不再丢图) - 媒体机上传的 key 不进失败回滚清单(内容寻址、可能与他人共享) - /admin/internal/upload-url 签发 S4 PUT 预签名(spider 不持有 AK/SK) embeddings(向量回填) - GET /admin/internal/embeddings/pending 派发缺向量的图(含可直接下载的 url) - POST /admin/internal/embeddings 回写向量并重算近邻;失败项计入 image_embedding_failures,连续失败到上限后退出队列(坏图不占位) - 近邻重算改为异步串行:同步会让 100 张的批次 POST 超时(实测 >60s) - 新增 key 分支单测:混用 key/url、细节图只给 key、字节数、回滚清单、向量占位
82 lines
5.7 KiB
HTML
82 lines
5.7 KiB
HTML
{{/*
|
||
共享片段:所有后台页面通用的顶部导航与翻页器。
|
||
|
||
由 templates.go 在启动时与 layout.html 一起解析进「基模板」,各页面模板只需
|
||
{{template "bar" .}} / {{template "pager" .Pager}} 引用,改一处即全局生效
|
||
—— 这也是把导航从 14 份复制粘贴里提出来、补全模块入口的原因。
|
||
|
||
页面需要提供的数据:
|
||
.NavKey 由 renderPage 自动注入(= 当前页面名),用于导航高亮
|
||
.PendingReviews 由 backstageNavStats 中间件注入(待审总数,导航角标)
|
||
barLabel/barRight 两个 define 由各页面模板自己提供(见 pages/*.html)
|
||
*/}}
|
||
|
||
{{define "bar"}}
|
||
<div class="bar">
|
||
<span class="bar-title">{{template "barLabel" .}}</span>
|
||
<nav class="bar-nav">
|
||
<a href="/admin/" class="{{if eq .NavKey "dashboard"}}on{{end}}">首页</a>
|
||
<a href="/admin/articles" class="{{if or (eq .NavKey "articles") (eq .NavKey "runway-edit") (eq .NavKey "snap-edit") (eq .NavKey "runway-detail") (eq .NavKey "street-snap-detail")}}on{{end}}">文章{{if gt .PendingReviews 0}}<span class="bar-badge">{{.PendingReviews}}</span>{{end}}</a>
|
||
<a href="/admin/brands" class="{{if or (eq .NavKey "brands") (eq .NavKey "brand-form")}}on{{end}}">品牌</a>
|
||
<a href="/admin/crawl" class="{{if eq .NavKey "crawl"}}on{{end}}">抓取任务</a>
|
||
<a href="/admin/ingest-jobs" class="{{if eq .NavKey "ingest-jobs"}}on{{end}}">入库任务</a>
|
||
<a href="/admin/users" class="{{if eq .NavKey "users"}}on{{end}}">用户</a>
|
||
<span class="sep"></span>
|
||
{{template "barRight" .}}
|
||
</nav>
|
||
</div>
|
||
{{end}}
|
||
|
||
{{define "pager"}}
|
||
<div class="pager">
|
||
{{if .HasPrev}}<a href="{{.PrevURL}}">上一页</a>{{else}}<a class="off">上一页</a>{{end}}
|
||
{{if .Label}}<span class="muted">{{.Label}}</span>{{end}}
|
||
{{if .HasNext}}<a href="{{.NextURL}}">下一页</a>{{else}}<a class="off">下一页</a>{{end}}
|
||
</div>
|
||
{{end}}
|
||
|
||
{{/*
|
||
共享片段:街拍 / 走秀编辑页共用的「图片管理」块(网格 + 合并为一组 + 拆出)。
|
||
两个后台编辑页之前各写一份几乎相同的图片管理 HTML,现已抽到这里,改一处全局生效。
|
||
|
||
页面需要提供的字段:
|
||
.Kind "street" | "runway"(决定封面/删除路由前缀与主/副图文案)
|
||
.UID 记录的 hashid(用于 /admin/{street-snaps|runways}/<UID>/... 路由)
|
||
.RecordID 记录数字 id(用于 /admin/articles/<kind>/<id>/... 路由)
|
||
.Status 记录状态(pending 时显示待审角标)
|
||
.Cells []gin.H,每项 {id, image, name, isDetail, role}
|
||
*/}}
|
||
{{define "imageGrid"}}
|
||
<h2>图片管理(<span id="img-stat">{{len .Cells}} 张</span>{{if eq .Status "pending"}} · <span class="badge pending">待审核</span>{{end}})</h2>
|
||
<div class="hint">
|
||
操作:勾选属于同一个人的图片 → 在<b>其中一张</b>上点「主图」→ 点底部「合并选中为一组」。副图上的「拆出」可恢复为独立主图。<br>
|
||
{{if eq .Kind "runway"}}走秀图按 Look 分组:同一 Look 的主图 + 其细节图(副图)。{{end}}
|
||
<button type="button" class="x" id="hint-dismiss" aria-label="收起提示">✕</button>
|
||
</div>
|
||
{{$kind := .Kind}}{{$uid := .UID}}{{$rid := .RecordID}}
|
||
<form id="batch" method="post" action="/admin/articles/{{$kind}}/{{$rid}}/images/attach"></form>
|
||
<div class="grid">
|
||
{{range .Cells}}<div class="cell{{if eq .isDetail 1}} det{{if .orphan}} shown{{end}}{{end}}" id="img-{{.id}}" data-img-id="{{.id}}"{{if eq .isDetail 1}} data-parent="{{.parent}}"{{end}}>
|
||
<button class="thumb" type="button" data-zoom data-full="{{imgURL .image}}"><img src="{{imgThumb .image}}" loading="lazy"></button>
|
||
<span class="role">{{.role}}</span>
|
||
<span class="nm">{{.name}}</span>
|
||
{{range .similarHints}}<a class="badge similar" href="/admin/{{if eq $kind "runway"}}runways{{else}}street-snaps{{end}}/{{.UID}}/edit#img-{{.ImageID}}" target="_blank" title="疑似同款:与「{{.OwnerTitle}}」的 {{.Name}}(#{{.ImageID}})余弦相似度 {{printf "%.2f" .Similar}}。点击跳到那张图。仅供人工参考,不影响前台展示">{{if .Image}}<img src="{{imgThumb .Image}}" loading="lazy" alt="">{{end}}<span class="sim-text"><b>{{.OwnerTitle}}</b>{{.Name}} · {{printf "%.2f" .Similar}}</span></a>{{end}}
|
||
<div class="acts">
|
||
{{if eq $kind "runway"}}<form method="post" action="/admin/runways/{{$uid}}/cover"><input type="hidden" name="image" value="{{.image}}"><button type="submit">设封面</button></form>
|
||
<form method="post" action="/admin/runways/{{$uid}}/images/{{.id}}/delete"><button type="submit" class="reject">删除</button></form>
|
||
{{else}}<form method="post" action="/admin/street-snaps/{{$uid}}/cover"><input type="hidden" name="image" value="{{.image}}"><button type="submit">设封面</button></form>
|
||
<form method="post" action="/admin/street-snaps/{{$uid}}/images/{{.id}}/delete"><button type="submit" class="reject">删除</button></form>
|
||
{{end}}
|
||
{{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 and (eq .isDetail 0) .DetCount}}<button type="button" class="toggle-det" data-parent="{{.id}}">显示副图({{.DetCount}} 张)</button>{{end}}
|
||
{{if eq .isDetail 1}}<form data-only="detail" method="post" action="/admin/articles/{{$kind}}/{{$rid}}/images/{{.id}}/detach"><button type="submit">拆出</button></form>{{end}}
|
||
</div>
|
||
</div>{{end}}
|
||
</div>
|
||
<div class="batchbar">
|
||
<button type="submit" form="batch">合并选中为一组</button>
|
||
<span class="muted">勾选同一人的图,并在其中一张主图上点「主图」</span>
|
||
</div>
|
||
{{end}}
|