Files
subtrack/internal/web/templates/_partials.html
T
subtrack a3fd817edc feat(v1.8): 包赛季模式、按次默认有效期、订阅删除
1) 包赛季(第三种计费方式)
- 套餐表单新增「包赛季」,套餐上直接填赛季开始日/结束日(必填,结束日不能早于开始日)
- 客户开通时自动带出赛季起止,不用再手填日期
- 日历上用横向色带标出整段赛季;赛季内任意一天点「完成」即整段完成
- 完成后不再进日历/待打,客户详情显示「赛季已完成 + 完成日期」
- 已过赛季结束日的套餐在客户开通下拉里置灰标「赛季已结束」,服务端同样拒绝开通

2) 按次默认有效期
- 套餐新增「按次默认有效期(天)」,开单时自动算出到期日(开始日 + 天数 − 1),仍可改

3) 订阅删除
- 已结束的订阅卡片在「重新启用」旁新增「删除」,二次确认后彻底删除该订阅及其打卡/追加/加急记录

4) 客户月历、日历与大屏同步识别赛季(色带、完成状态)

数据库兼容性:
- 仅新增列:packages.season / default_validity_days / season_start / season_end,subscriptions.season / season_done
- 没有改动原有 unit 的 CHECK 约束、没有重建表;老库启动自动 ALTER,可重复迁移

测试:
- 功能验证 13 项:赛季套餐创建、过期套餐不可选、开通带出日期、日历色带、完成整段后隐藏、按次自动到期、删除订阅
- 119 项验收回归全绿;go test ./... 通过;无头 Chrome 实拍 3 张(日历色带 / 客户赛季卡片 / 套餐表单)
- 版本号 1.7.0 → 1.8.0
2026-10-19 19:26:47 +08:00

151 lines
6.8 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 "state-chip"}}<span class="chip chip-{{.StateClass}}">{{.StateLabel}}</span>{{end}}
{{define "pager"}}
{{if gt .Pages 1}}
<nav class="pager">
{{if .Prev}}<a class="btn btn-sm" href="{{.Prev}}">← 上一页</a>{{else}}<span class="btn btn-sm disabled">← 上一页</span>{{end}}
<span class="pager-info">{{.Page}} / {{.Pages}} 页 · 共 {{.Total}} 条</span>
{{if .Next}}<a class="btn btn-sm" href="{{.Next}}">下一页 →</a>{{else}}<span class="btn btn-sm disabled">下一页 →</span>{{end}}
</nav>
{{end}}
{{end}}
{{define "empty"}}
<div class="empty">
<div class="empty-mark">票</div>
<p>{{.Text}}</p>
{{if .Action}}<a class="btn btn-primary" href="{{.Action}}">{{.ActionText}}</a>{{end}}
</div>
{{end}}
{{define "sub-card"}}
<article class="subcard" id="sub-{{.ID}}" style="--tag:{{.PackageColor}}">
<div class="subcard-head">
<span class="tag"></span>
<h3>{{.PackageName}}</h3>
{{template "state-chip" .}}
<span class="unit">{{.UnitLabel}}</span>
</div>
<div class="subcard-body">
<div class="subcard-big">{{.RemainText}}</div>
<div class="subcard-meta">{{.PeriodText}}</div>
{{if .DueText}}<div class="due due-{{.Eval.Level}}">{{.DueText}}</div>{{end}}
<div class="bar"><i style="width:{{.Progress}}%"></i></div>
</div>
<div class="subcard-foot">
{{if ne .Status "closed"}}
{{if .CanCheckinToday}}
<form method="post" action="/subscriptions/{{.ID}}/checkin">
<input type="hidden" name="date" value="{{.Today}}">
<input type="hidden" name="status" value="done">
<button class="btn btn-sm">今日打卡</button>
</form>
{{end}}
{{if .Season}}
{{if eq .SeasonDone ""}}
<form method="post" action="/subscriptions/{{.ID}}/season-done" data-confirm="标记后整段赛季完成,确认?">
<input type="hidden" name="date" value="{{.Today}}">
<button class="btn btn-primary btn-sm">完成赛季</button>
</form>
{{else}}
<span class="chip chip-active">赛季已完成 {{.SeasonDone}}</span>
{{end}}
{{else}}
<a class="btn btn-primary btn-sm" href="?extend={{.ID}}#extend">{{if eq .Unit "day"}}追加时长{{else}}追加次数{{end}}</a>
{{end}}
{{if eq .Status "active"}}
<form method="post" action="/subscriptions/{{.ID}}/pause" data-confirm="暂停后剩余时长冻结,确认暂停?">
<button class="btn btn-sm">暂停</button>
</form>
{{else if eq .Status "paused"}}
<form method="post" action="/subscriptions/{{.ID}}/resume">
<button class="btn btn-sm">恢复</button>
</form>
{{end}}
<form method="post" action="/subscriptions/{{.ID}}/close" data-confirm="结束后该订阅不再出现在待打清单,确认结束?">
<button class="btn btn-sm btn-ghost">结束</button>
</form>
{{else}}
<form method="post" action="/subscriptions/{{.ID}}/reopen">
<button class="btn btn-sm">重新启用</button>
</form>
<form method="post" action="/subscriptions/{{.ID}}/delete" data-confirm="将永久删除该订阅及其打卡/追加记录,无法恢复。确定删除?">
<input type="hidden" name="confirm" value="delete">
<button class="btn btn-sm btn-ghost" style="color:var(--danger)">删除</button>
</form>
{{end}}
</div>
</article>
{{end}}
{{define "extend-panel"}}
<section class="extend" id="extend" style="--tag:{{.Sub.PackageColor}}">
<header class="extend-head">
<div>
<h3>给 {{.Sub.CustomerTail}} {{.Sub.CustomerName}} 追加{{if .IsDay}}时长{{else}}次数{{end}}</h3>
<p class="muted">{{.Sub.PackageName}} · {{.View.RemainText}} · {{.View.PeriodText}}</p>
</div>
<a class="extend-close" href="{{.Back}}" aria-label="关闭">×</a>
</header>
{{if .IsDay}}
<form method="post" action="/subscriptions/{{.Sub.ID}}/extend" class="extend-form"
data-extend data-base="{{.Base}}" data-old-end="{{.OldEnd}}" data-today="{{.Today}}">
<input type="hidden" name="back" value="{{.Back}}">
<input type="hidden" name="new_end_date" value="{{.Suggested}}" data-end-input>
<div class="extend-preview">
<div><span>计算起点</span><b data-base-label>{{.Base}}</b></div>
<div><span>原结束日</span><b>{{.OldEnd}}</b></div>
<div><span>新结束日</span><b data-end-label>{{.Suggested}}</b></div>
<div class="add"><span>本次追加</span><b><i data-days>{{.SuggestedDays}}</i> 天</b></div>
<div><span>追加后剩余</span><b><i data-remain>—</i> 天</b></div>
</div>
<p class="extend-hint">在下面的日历上点选新的终止日期;未到期的订阅从原结束日次日起算,已到期的从今天起算。</p>
<div class="minis">
{{range .Months}}
<div class="minimonth">
<div class="minimonth-title">{{.Title}}</div>
<div class="minimonth-grid">
<span class="dow">一</span><span class="dow">二</span><span class="dow">三</span><span class="dow">四</span><span class="dow">五</span><span class="dow">六</span><span class="dow">日</span>
{{range .Weeks}}{{range .}}
<button type="button" class="miniday{{if not .InMonth}} out{{end}}{{if .IsOldEnd}} old{{end}}{{if .IsToday}} today{{end}}" data-date="{{.Date}}"{{if .Disabled}} disabled{{end}}>{{.Day}}</button>
{{end}}{{end}}
</div>
</div>
{{end}}
</div>
<label class="field"><span>备注</span>
<input type="text" name="note" maxlength="120" placeholder="例如:微信转账 30 天 / 补送 3 天">
</label>
<div class="actions">
<button type="submit" class="btn btn-primary">确认追加</button>
<a class="btn" href="{{.Back}}">取消</a>
</div>
</form>
{{else}}
<form method="post" action="/subscriptions/{{.Sub.ID}}/extend" class="extend-form">
<input type="hidden" name="back" value="{{.Back}}">
<div class="extend-preview">
<div><span>当前剩余</span><b>{{.View.RemainText}}</b></div>
<div><span>已用 / 总数</span><b>{{.Sub.UsedCount}} / {{.Sub.TotalCount}}</b></div>
{{if .Sub.EndDate}}<div><span>有效期至</span><b>{{.Sub.EndDate}}</b></div>{{end}}
</div>
<div class="grid-2">
<label class="field"><span>追加次数</span>
<input type="number" name="add_count" min="1" max="10000" value="{{.DefaultAmount}}" required>
</label>
<label class="field"><span>新的有效期(留空不改)</span>
<input type="date" name="new_end_opt">
</label>
</div>
<label class="field"><span>备注</span>
<input type="text" name="note" maxlength="120" placeholder="例如:加购 10 次副本">
</label>
<div class="actions">
<button type="submit" class="btn btn-primary">确认追加</button>
<a class="btn" href="{{.Back}}">取消</a>
</div>
</form>
{{end}}
</section>
{{end}}