This commit is contained in:
toom1996
2026-10-18 17:42:10 +08:00
parent 1b06bd5bda
commit 1de1ec46a6
58 changed files with 5281 additions and 19191 deletions

View File

@ -0,0 +1,39 @@
{{define "title"}}新建文章 · 管理后台{{end}}
{{define "barLabel"}}管理后台 · 新建文章{{end}}
{{define "barRight"}}<a href="/admin/articles">← 返回列表</a>{{end}}
{{define "content"}}
{{template "bar" .}}
<div class="main">
<h1>新建文章</h1>
<p class="muted">走秀与街拍二选一:走秀填品牌/季节/系列,街拍填城市/年份;图片一步到位上传,创建后状态为「待审核」。</p>
{{if .Error}}<p class="badge rejected">{{.Error}}</p>{{end}}
<form method="post" action="/admin/articles" enctype="multipart/form-data">
<table>
<tbody>
<tr><th>类型</th><td>
<select name="kind">
<option value="runway"{{if ne .Kind "street"}} selected{{end}}>走秀</option>
<option value="street"{{if eq .Kind "street"}} selected{{end}}>街拍</option>
</select>
</td></tr>
<tr><th>品牌英文名</th><td><input name="brand_name" value="{{.BrandName}}" placeholder="如 Gucci(走秀必填,须与品牌管理里的英文名完全一致)"></td></tr>
<tr><th>标题(英文)</th><td><input name="title_en" value="{{.TitleEn}}"></td></tr>
<tr><th>标题(中文)</th><td><input name="title_cn" value="{{.TitleCn}}"></td></tr>
<tr><th>年份</th><td><input name="year" value="{{.Year}}" size="6" placeholder="如 2027"></td></tr>
<tr><th>季节</th><td>
<select name="season">{{range .Seasons}}<option value="{{.}}"{{if eq . $.Season}} selected{{end}}>{{.}}</option>{{end}}</select>
</td></tr>
<tr><th>系列</th><td>
<select name="collection_type">{{range .Collections}}<option value="{{.}}"{{if eq . $.CollectionType}} selected{{end}}>{{.}}</option>{{end}}</select>
</td></tr>
<tr><th>城市</th><td><input name="city" value="{{.City}}" placeholder="街拍必填,如 Paris"></td></tr>
<tr><th>图片</th><td>
<input type="file" name="images" multiple required accept="image/jpeg,image/png,image/webp">
<div class="muted">至少 1 张;JPG / PNG / WEBP;单张 ≤ 10MB。按选择顺序作为 Look 1..N,第一张作封面;同内容图片会自动去重。</div>
</td></tr>
</tbody>
</table>
<p><button type="submit" class="op op-primary">创建</button> <a href="/admin/articles">取消</a></p>
</form>
</div>
{{end}}

View File

@ -1,6 +1,6 @@
{{define "title"}}文章管理 · 管理后台{{end}}
{{define "barLabel"}}管理后台 · 文章管理{{end}}
{{define "barRight"}}<a href="/admin/logout">退出</a>{{end}}
{{define "barRight"}}<a href="/admin/articles/new">+ 新建</a> · <a href="/admin/logout">退出</a>{{end}}
{{define "content"}}
{{template "bar" .}}
<div class="main">
@ -21,7 +21,7 @@
<table>
<thead><tr>
<th style="width:30px"><input type="checkbox" id="sel-all" aria-label="全选本页"></th>
<th>类型</th><th>ID</th><th>标题</th><th>副标题</th><th>图数</th><th>原图总大小</th><th>重复率</th><th>状态</th><th>操作</th>
<th>类型</th><th>ID</th><th>标题</th><th>副标题</th><th>图数(主+副)</th>{{if .ShowDup}}<th>跨图集重复</th>{{end}}<th>原图总大小</th><th>状态</th><th>操作</th>
</tr></thead>
<tbody>
{{range .Items}}<tr data-kind="{{.Kind}}" data-id="{{.ID}}">
@ -30,8 +30,7 @@
<td><code>{{.ID}}</code></td>
<td>{{.Title}}</td>
<td class="muted">{{.Subtitle}}</td>
<td>{{.ImageCount}}</td><td>{{formatBytes .ImageTotalSize}}</td>
<td>{{if .DupCount}}<span class="badge dup">{{printf "%.0f" .DupPct}}%({{.DupCount}})</span>{{else}}<span class="muted">—</span>{{end}}</td>
<td>{{.ImageTotal}}{{if .ImageDetail}}<span class="muted" title="真实图片行数:主图 {{.ImageMain}} 张 + 细节图 {{.ImageDetail}} 张。公开卡片角标用的是图集表的 image_count,它只记主图(当前 {{.ImageCount}}),所以两者对不上是正常的。"> 主{{.ImageMain}}+副{{.ImageDetail}}</span>{{end}}</td>{{if $.ShowDup}}<td>{{if .DupImages}}<a class="badge dup" href="/admin/{{if eq .Kind "runway"}}runways{{else}}street-snaps{{end}}/{{.UID}}/edit#img-{{.DupFirstImageID}}" title="本图集 {{.DupTotal}} 张图里有 {{.DupImages}} 张在其他图集里出现过({{printf "%.1f" .DupRate}}%)。点开直接跳到其中第一张。">{{.DupImages}} 张 · {{printf "%.0f" .DupRate}}%</a>{{else}}<span class="muted">—</span>{{end}}</td>{{end}}<td>{{formatBytes .ImageTotalSize}}</td>
<td><span class="badge {{.Status}}">{{.Status}}</span></td>
<td>
<div class="ops">

View File

@ -7,43 +7,22 @@
<h1>品牌列表</h1>
<form class="toolbar" method="get" action="/admin/brands">
<input name="keyword" placeholder="搜索品牌名…" value="{{.Keyword}}">
{{if .All}}<input type="hidden" name="all" value="1">{{end}}
<button type="submit">搜索</button>
{{if .Keyword}}<a href="/admin/brands">清除</a>{{end}}
{{if .Keyword}}<a href="/admin/brands{{if .All}}?all=1{{end}}">清除</a>{{end}}
<span class="spacer"></span>
{{if .All}}<a class="view" href="/admin/brands?keyword={{.Keyword}}">只看有图集的品牌</a>{{else}}<a class="view" href="/admin/brands?keyword={{.Keyword}}&all=1">显示全部品牌(含无图集)</a>{{end}}
</form>
<p class="muted">默认只显示「已有图集」的品牌;品牌目录共 2 万余条,其余请用上面的搜索或开关查看。</p>
<div class="idx">
<a href="/admin/brands?keyword={{.Keyword}}" class="{{if eq .Letter ""}}on{{end}}">全部</a>
<a href="/admin/brands?letter=OTHER&keyword={{.Keyword}}" class="{{if eq .Letter "OTHER"}}on{{end}}">中文</a>
<a href="/admin/brands?letter=A&keyword={{.Keyword}}" class="{{if eq .Letter "A"}}on{{end}}">A</a>
<a href="/admin/brands?letter=B&keyword={{.Keyword}}" class="{{if eq .Letter "B"}}on{{end}}">B</a>
<a href="/admin/brands?letter=C&keyword={{.Keyword}}" class="{{if eq .Letter "C"}}on{{end}}">C</a>
<a href="/admin/brands?letter=D&keyword={{.Keyword}}" class="{{if eq .Letter "D"}}on{{end}}">D</a>
<a href="/admin/brands?letter=E&keyword={{.Keyword}}" class="{{if eq .Letter "E"}}on{{end}}">E</a>
<a href="/admin/brands?letter=F&keyword={{.Keyword}}" class="{{if eq .Letter "F"}}on{{end}}">F</a>
<a href="/admin/brands?letter=G&keyword={{.Keyword}}" class="{{if eq .Letter "G"}}on{{end}}">G</a>
<a href="/admin/brands?letter=H&keyword={{.Keyword}}" class="{{if eq .Letter "H"}}on{{end}}">H</a>
<a href="/admin/brands?letter=I&keyword={{.Keyword}}" class="{{if eq .Letter "I"}}on{{end}}">I</a>
<a href="/admin/brands?letter=J&keyword={{.Keyword}}" class="{{if eq .Letter "J"}}on{{end}}">J</a>
<a href="/admin/brands?letter=K&keyword={{.Keyword}}" class="{{if eq .Letter "K"}}on{{end}}">K</a>
<a href="/admin/brands?letter=L&keyword={{.Keyword}}" class="{{if eq .Letter "L"}}on{{end}}">L</a>
<a href="/admin/brands?letter=M&keyword={{.Keyword}}" class="{{if eq .Letter "M"}}on{{end}}">M</a>
<a href="/admin/brands?letter=N&keyword={{.Keyword}}" class="{{if eq .Letter "N"}}on{{end}}">N</a>
<a href="/admin/brands?letter=O&keyword={{.Keyword}}" class="{{if eq .Letter "O"}}on{{end}}">O</a>
<a href="/admin/brands?letter=P&keyword={{.Keyword}}" class="{{if eq .Letter "P"}}on{{end}}">P</a>
<a href="/admin/brands?letter=Q&keyword={{.Keyword}}" class="{{if eq .Letter "Q"}}on{{end}}">Q</a>
<a href="/admin/brands?letter=R&keyword={{.Keyword}}" class="{{if eq .Letter "R"}}on{{end}}">R</a>
<a href="/admin/brands?letter=S&keyword={{.Keyword}}" class="{{if eq .Letter "S"}}on{{end}}">S</a>
<a href="/admin/brands?letter=T&keyword={{.Keyword}}" class="{{if eq .Letter "T"}}on{{end}}">T</a>
<a href="/admin/brands?letter=U&keyword={{.Keyword}}" class="{{if eq .Letter "U"}}on{{end}}">U</a>
<a href="/admin/brands?letter=V&keyword={{.Keyword}}" class="{{if eq .Letter "V"}}on{{end}}">V</a>
<a href="/admin/brands?letter=W&keyword={{.Keyword}}" class="{{if eq .Letter "W"}}on{{end}}">W</a>
<a href="/admin/brands?letter=X&keyword={{.Keyword}}" class="{{if eq .Letter "X"}}on{{end}}">X</a>
<a href="/admin/brands?letter=Y&keyword={{.Keyword}}" class="{{if eq .Letter "Y"}}on{{end}}">Y</a>
<a href="/admin/brands?letter=Z&keyword={{.Keyword}}" class="{{if eq .Letter "Z"}}on{{end}}">Z</a>
<a href="/admin/brands?keyword={{.Keyword}}{{if .All}}&all=1{{end}}" class="{{if eq .Letter ""}}on{{end}}">全部</a>
<a href="/admin/brands?letter=OTHER&keyword={{.Keyword}}{{if .All}}&all=1{{end}}" class="{{if eq .Letter "OTHER"}}on{{end}}">中文</a>
{{range $l := .Letters}}<a href="/admin/brands?letter={{$l}}&keyword={{$.Keyword}}{{if $.All}}&all=1{{end}}" class="{{if eq $l $.Letter}}on{{end}}">{{$l}}</a>{{end}}
</div>
<table>
<thead><tr><th>编码 ID</th><th>英文名</th><th>中文名</th></tr></thead>
<thead><tr><th>编码 ID</th><th>英文名</th><th>中文名</th><th>操作</th></tr></thead>
<tbody>
{{range .Items}}<tr><td><code>{{.UID}}</code></td><td>{{.NameEn}}</td><td>{{.NameCn}}</td><td><a class="view" href="/admin/brands/{{.UID}}">编辑</a></td></tr>{{end}}
{{range .Items}}<tr><td><code>{{.UID}}</code></td><td>{{.NameEn}}</td><td>{{.NameCn}}</td><td><a class="view" href="/admin/brands/{{.UID}}">编辑</a><form class="inline" method="post" action="/admin/brands/{{.UID}}/delete" onsubmit="return confirm('确认删除品牌「{{.NameEn}}」?该品牌下若还有图集会拒绝删除。')"><button type="submit" class="op op-warn">删除</button></form></td></tr>{{end}}
</tbody>
</table>
{{template "pager" .Pager}}

View File

@ -46,22 +46,20 @@
.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}}{{if .DupCount}} · 重复率 <span class="badge dup">{{printf "%.0f" .DupPct}}%({{.DupCount}} 张)</span>{{end}})</h2>
<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}}{{$dupSources := .DupSources}}
{{$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}}" data-img-id="{{.id}}"{{if eq .isDetail 1}} data-parent="{{.parent}}"{{end}}>
<button class="thumb" type="button" data-zoom><img src="{{imgURL .image}}" loading="lazy"></button>
{{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>
{{if eq .isDuplicate 1}}<span class="badge dup">重复</span>
{{with index $dupSources .dupOf}}{{if .srcTitle}}<details class="dup-src"><summary>查看重复来源</summary><div class="dup-src-body"><img src="{{imgURL .srcImage}}" loading="lazy" alt="源图缩略图"><div><div class="dup-title">属于:<b>{{.srcTitle}}</b>{{if .srcBrand}} · {{.srcBrand}}{{end}}</div><a class="dup-link" href="/admin/{{if eq .srcKind "runway"}}runways{{else}}street-snaps{{end}}/{{.srcUID}}/edit" target="_blank">打开所属{{if eq .srcKind "runway"}}走秀{{else}}街拍{{end}} →</a></div></div></details>{{end}}{{end}}
{{end}}
{{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>