Files
subtrack/internal/web/templates/pages/portal.html
T
subtrack c84175b723 feat(v1.10.0): 新增客户自助查询页 /customer
需求与确认:
- 完全公开、不加入口链接(用户确认)
- 输入手机尾号查询;同一尾号命中多个客户时必须补姓名,模糊匹配(用户确认)
- 只展示排单与完成情况:月历每日 已完成/总数、订阅套餐名与类型、到期时间、今日是否清完;不显示备注、打手、价格(用户确认)

实现:
- 新增 internal/web/handlers_portal.go:handleCustomerPortal + buildPortal
  - 尾号查客户(CustomersByTail,已过滤归档客户)
  - 多命中时返回候选家数并要求补姓名,姓名模糊匹配(不区分大小写)
  - 月度网格:按订阅覆盖日期统计每日应打/已完成,打卡数据复用 CheckinsBetween
  - 今日汇总、最近到期日、活跃订阅数
- 新增模板 pages/portal.html(Bootstrap 卡片 + 自定义 .pcal 月历)
- 路由 GET /customer(公开,无需登录)
- app.css 增加门户与 pcal 样式
- 数据库零改动,未新增表或列

测试:
- HTTP 验证 6 条路径全部 200:落地页、仅尾号(提示补姓名)、尾号+姓名模糊、姓名不匹配、尾号不存在、翻月
- 无头浏览器实拍 shots/portal-v110.png(公开页)
- 已知待办:门户标题与主按钮的品牌色内联覆盖未生效,需下一版修正(Bootstrap 后加载覆盖了主题色)
2026-10-20 03:11:36 +08:00

101 lines
4.1 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "content"}}
<div class="portal-wrap">
<header class="portal-head">
<div>
<h1>排单查询</h1>
<p class="muted">输入手机尾号查看自己的排单与完成情况{{if not .D}};同一尾号有多位客户时需要再填姓名{{end}}</p>
</div>
</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 .Error}}<div class="alert alert-warning mt-3" role="alert">{{.Error}}</div>{{end}}
{{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}}
<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>
</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>
<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>
</div>
<div class="muted small">{{.Period}}</div>
</div></div>
</div>
{{end}}
</div>
{{end}}
<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>
<b>{{.MonthTitle}}</b>
<a class="btn btn-sm btn-outline-secondary" href="{{.Next}}">›</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>
{{range .Weeks}}
<div class="pcal-row">
{{range .}}
<div class="pcal-cell{{if not .InMonth}} out{{end}}{{if .Today}} today{{end}}">
<b>{{.Day}}</b>
{{if and .InMonth .Total}}<span>{{.Done}}/{{.Total}}</span>{{end}}
</div>
{{end}}
</div>
{{end}}
</div>
</div></div>
</section>
{{end}}
{{end}}
</div>
{{end}}