fix(v1.11.3): 客户自助查询 /customer 全量修复
build / build-and-test (push) Successful in 3m18s

- 路由:从 screen_public 拆出 portal_public(默认 true),关掉大屏不再连带 404;
  设置页显示客户自助状态;/customer 与 /screen 各自独立。
- 查询:尾号只接受 4–8 位数字,服务端精确匹配,去掉 LIKE 模糊回退;按 IP
  40 次/分钟限流;页面 noindex。
- 语义:修正「今日待完成」把已完成数当待完成数的问题;0/0 改为「今日无排单」;
  无订阅走空态;删掉进度条和重复的今日文案,只留一个状态。
- 订阅:复用管理端 SubView / calSub;通行证 1/1、深渊 x/y 周期;过期、暂停、
  按次用尽、跳过都有独立口径;「最近到期」只取未来日期。
- 日历:与主日历一致,只在本月格显示任务;通行证/深渊整段色块(深渊按周期
  减淡),日常任务用空心/实心点;每格带读屏描述;today 加「今天」文字标记。
- 错误:只渲染一次并放回内容容器,字段加 aria-invalid;删掉顶部重复横幅。
- 多客户:显示掩码姓名 + 卡内补姓名表单;姓名回填;翻月用 url.Values 编码;
  1 位尾号不触发查询;只有空态/补姓名时 autofocus。
- UI:品牌头、可折叠「查询其他尾号」、订阅全宽列表、状态统一用现有 chip、
  月份按钮 44px、去掉卡中卡 / opacity / 满屏 0/1。
- 版本 1.11.3;README 增加 4.4 与 portal_public 配置说明。

验证:gofmt / go vet / go test 全绿;portal_public=true 时 /customer 200、
false 时 404;screen_public=false + portal_public=true 时 /customer 200、
/screen 302;尾号 2 与 % 被拒;2222 多客户掩码;9900 日常点、3321 通行证
1/1、6677 深渊 1/4 + 已到期订阅;明暗 1440 与 390 截图无对比度/溢出问题。
This commit is contained in:
2026-10-20 03:39:41 +08:00
parent 7371b86a97
commit ddc7988f34
11 changed files with 539 additions and 197 deletions
+90 -65
View File
@@ -1,98 +1,123 @@
{{define "content"}}
<div class="portal-wrap">
<header class="portal-head">
<div>
<h1>排单查询</h1>
<p class="muted">输入手机尾号查看自己的排单与完成情况{{if not .D}};同一尾号有多位客户时需要再填姓名{{end}}</p>
</div>
<a class="portal-brand" href="/customer"><span class="brand-mark" aria-hidden="true">代</span><span>代打台</span></a>
<h1>排单查询</h1>
<p class="muted">输入手机尾号查看自己的排单与完成情况。</p>
</header>
<form class="portal-form card" method="get" action="/customer">
<div class="card-body">
<div class="row g-2 align-items-end">
<div class="col-12 col-sm-4">
<label class="form-label" for="tail">手机尾号</label>
<input class="form-control" id="tail" name="tail" value="{{.Query}}" inputmode="numeric" maxlength="8" placeholder="例如 2300" required autofocus>
</div>
<div class="col-12 col-sm-4">
<label class="form-label" for="name">客户姓名(可选)</label>
<input class="form-control" id="name" name="name" maxlength="32" placeholder="尾号重复时填写">
</div>
<div class="col-12 col-sm-4">
<button class="btn btn-primary w-100" type="submit">查询</button>
</div>
</div>
<p class="muted small mb-0 mt-2">只显示排单与完成情况,不含备注与价格。如有疑问请联系代打。</p>
</div>
</form>
{{if .InlineError}}<div class="notice notice-danger portal-error" id="portal-error" role="alert">{{.InlineError}}</div>{{end}}
{{if .Error}}<div class="alert alert-warning mt-3" role="alert">{{.Error}}</div>{{end}}
<details class="portal-search" {{if or (not .Query) .InlineError}}open{{end}}>
<summary class="portal-search-head">{{if .InlineError}}修改查询{{else if .Query}}查询其他尾号{{else}}输入手机尾号查询{{end}}</summary>
<form class="portal-form" method="get" action="/customer">
<div class="portal-form-body">
<div class="row g-2 align-items-end">
<div class="col-12 col-sm-4">
<label class="form-label" for="tail">手机尾号(4–8 位)</label>
<input class="form-control" id="tail" name="tail" value="{{.Query}}" inputmode="numeric" pattern="[0-9]{4,8}" minlength="4" maxlength="8" placeholder="例如 2300" required{{if not .Query}} autofocus{{end}}{{if eq .InvalidField "tail"}} aria-invalid="true" aria-describedby="portal-error"{{end}}>
</div>
<div class="col-12 col-sm-4">
<label class="form-label" for="name">客户姓名(可选)</label>
<input class="form-control" id="name" name="name" value="{{.Name}}" maxlength="32" placeholder="尾号重复时填写"{{if eq .InvalidField "name"}} aria-invalid="true" aria-describedby="portal-error"{{end}}>
</div>
<div class="col-12 col-sm-4">
<button class="btn btn-primary w-100" type="submit">查询</button>
</div>
</div>
<p class="muted small mb-0 mt-2">只显示排单与完成情况,不含备注与价格。</p>
</div>
</form>
</details>
{{with .D}}
{{if .Names}}
<div class="card mt-3"><div class="card-body">
<h2 class="h5">找到 {{.Count}} 位客户</h2>
<p class="muted mb-2">同一尾号有多位客户,请在上方补填姓名(支持模糊匹配)后重新查询。</p>
</div></div>
{{else}}
{{if .Masked}}
<section class="portal-result">
<div class="card mt-3"><div class="card-body">
<div class="d-flex flex-wrap align-items-center gap-2">
<h2 class="h4 mb-0">{{.Tail}} · {{.Name}}</h2>
{{if .Game}}<span class="tagchip">{{.Game}}</span>{{end}}
<span class="tagchip">{{.ActiveSubs}} 个订阅</span>
{{if .Expiring}}<span class="tagchip">最近到期 {{.Expiring}}</span>{{end}}
<span class="ms-auto">
{{if .AllDone}}<span class="badge text-bg-success">今日已清完</span>
{{else}}<span class="badge text-bg-warning">今日待完成 {{.TodayDone}} / {{.TodayTotal}}</span>{{end}}
</span>
</div>
<div class="portal-progress mt-3">
<div class="bar"><i style="width:{{.TodayPct}}%"></i></div>
<span class="muted small">今日 {{.TodayDone}} / {{.TodayTotal}} 单完成</span>
<h2 class="h5">找到 {{.Count}} 位客户</h2>
<p class="muted mb-2">尾号 {{.Tail}} 对应多位客户,请补一个姓名后查询。</p>
{{if .Masked}}<p class="portal-ask-names">可能是:{{range $i, $n := .Masked}}{{if $i}}、{{end}}{{$n}}{{end}}</p>{{end}}
<form class="portal-ask-form" method="get" action="/customer">
<input type="hidden" name="tail" value="{{.Tail}}">
<label class="visually-hidden" for="ask-name">姓名</label>
<input class="form-control" id="ask-name" name="name" maxlength="32" placeholder="输入姓名(可只填一部分)" required autofocus>
<button class="btn btn-primary" type="submit">查询</button>
</form>
</div></div>
</section>
{{else}}
<section class="portal-result">
{{if .Subs}}
<div class="card mt-3"><div class="card-body">
<div class="portal-client">
<div class="portal-client-main">
<h2 class="portal-client-name">{{.Tail}} · {{.Name}}</h2>
<div class="portal-client-meta">
{{if .Game}}<span class="tagchip">{{.Game}}</span>{{end}}
{{if .Expiring}}<span class="tagchip">最近到期 {{.Expiring}}</span>{{end}}
</div>
</div>
<div class="portal-client-status">
{{if eq .TodayState "done"}}<span class="chip chip-active">今日已清完</span>
{{else if eq .TodayState "pending"}}<span class="chip chip-plain">今日待完成 {{.TodayPending}} / {{.TodayTotal}}</span>
{{else}}<span class="chip chip-plain">今日无排单</span>{{end}}
</div>
</div>
</div></div>
{{if .Subs}}
<div class="row g-3 mt-1">
{{range .Subs}}
<div class="col-12 col-md-6 col-xl-4">
<div class="card h-100"><div class="card-body">
<div class="d-flex align-items-center gap-2 mb-1">
<strong>{{.Name}}</strong>
<div class="card mt-3"><div class="card-body">
<div class="portal-section-head"><h2>订阅</h2><span class="portal-section-count">{{.SubCount}}</span></div>
<div class="portal-sub-list">
{{range .Subs}}
<article class="portal-sub">
<div class="portal-sub-main">
<strong>{{.PackageName}}</strong>
<span class="tagchip">{{.UnitLabel}}</span>
<span class="ms-auto">{{if .TodayDone}}<span class="badge text-bg-success">今日已完成</span>{{else}}<span class="badge text-bg-secondary">今日未完成</span>{{end}}</span>
{{if .TodayLabel}}<span class="chip chip-{{.TodayClass}}">{{.TodayLabel}}</span>{{else}}{{template "state-chip" .}}{{end}}
</div>
<div class="muted small">{{.Period}}</div>
</div></div>
<div class="portal-sub-meta">
<span>{{.RemainText}}</span>
<span class="muted">{{.PeriodText}}</span>
</div>
</article>
{{end}}
</div>
{{end}}
</div>
{{end}}
</div></div>
<div class="card mt-3"><div class="card-body">
<div class="d-flex align-items-center mb-2">
<h3 class="h6 mb-0">当月排单</h3>
<div class="ms-auto d-flex align-items-center gap-2">
<a class="btn btn-sm btn-outline-secondary" href="{{.Prev}}">‹</a>
<div class="portal-section-head">
<h2>当月排单</h2>
<div class="pcal-nav">
<a class="btn btn-soft pcal-nav-btn" href="{{.Prev}}" aria-label="上个月">‹</a>
<b>{{.MonthTitle}}</b>
<a class="btn btn-sm btn-outline-secondary" href="{{.Next}}">›</a>
<a class="btn btn-soft pcal-nav-btn" href="{{.Next}}" aria-label="下个月">›</a>
</div>
</div>
<div class="pcal">
<div class="pcal-dow"><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span><span>日</span></div>
<div class="pcal" role="grid" aria-label="{{.MonthTitle}}排单">
<div class="pcal-dow" role="row">
<span role="columnheader">一</span><span role="columnheader">二</span><span role="columnheader">三</span><span role="columnheader">四</span><span role="columnheader">五</span><span role="columnheader">六</span><span role="columnheader">日</span>
</div>
{{range .Weeks}}
<div class="pcal-row">
<div class="pcal-row" role="row">
{{range .}}
<div class="pcal-cell{{if not .InMonth}} out{{end}}{{if .Today}} today{{end}}">
<div class="pcal-cell{{if not .InMonth}} out{{end}}{{if .Today}} today{{end}}{{if .BlockColor}} has-block{{end}}" role="gridcell" aria-label="{{.Label}}"{{if .BlockColor}} style="--block:{{.BlockColor}};--block-pct:{{.BlockPercent}}%"{{end}}>
<b>{{.Day}}</b>
{{if and .InMonth .Total}}<span>{{.Done}}/{{.Total}}</span>{{end}}
{{if .Today}}<i class="pcal-today">今天</i>{{end}}
{{if .BlockText}}<span class="pcal-block" title="{{.BlockTitle}}">{{.BlockText}}</span>{{end}}
{{if .Total}}{{if eq .Total 1}}<span class="pcal-dot{{if .Done}} done{{end}}" title="{{.Done}}/{{.Total}} 单"></span>{{else}}<span class="pcal-count">{{.Done}}/{{.Total}}</span>{{end}}{{end}}
</div>
{{end}}
</div>
{{end}}
</div>
</div></div>
{{else}}
<div class="card mt-3"><div class="card-body">
<h2 class="h4 mb-1">{{.Tail}} · {{.Name}}</h2>
<div class="portal-client-meta">{{if .Game}}<span class="tagchip">{{.Game}}</span>{{end}}</div>
<p class="muted mb-0 mt-3">还没有进行中的订阅。如需开通,请联系你的代打。</p>
</div></div>
{{end}}
</section>
{{end}}
{{end}}
@@ -89,6 +89,7 @@
<div><dt>数据库</dt><dd class="mono small">{{.D.DBPath}}</dd></div>
<div><dt>备份</dt><dd class="mono small">{{.D.BackupDir}} · 保留 {{.D.BackupKeep}} 份 · {{if .D.BackupEnabled}}自动备份已开启{{else}}自动备份已关闭{{end}}</dd></div>
<div><dt>大屏</dt><dd class="mono small">/screen {{if .D.ScreenPublic}}免登录{{else}}需登录{{end}}</dd></div>
<div><dt>客户自助</dt><dd class="mono small">/customer {{if .D.PortalPublic}}免登录{{else}}已关闭{{end}}</dd></div>
<div><dt>局域网地址</dt><dd class="mono small">http://{{if .D.Addrs}}{{index .D.Addrs 0}}{{else}}127.0.0.1{{end}}:{{.D.Port}}/screen</dd></div>
<div><dt>已运行</dt><dd class="mono">{{.D.Uptime}}</dd></div>
<div><dt>数据量</dt><dd>{{.D.CustomerCount}} 位客户 · {{.D.PackageCount}} 个套餐 · {{.D.LogCount}} 条日志</dd></div>