Files
subtrack/internal/web/templates/pages/calendar.html
T
subtrack 1f7d0f0cc4
build / build-and-test (push) Successful in 3m19s
feat(v1.5): 触摸键盘、格子大字计数、客户停用/删除、按钮收纳、7 天徽标、抽屉时钟
1) 工作台速录
- 手机尾号输入框下方新增 3×4 触摸小键盘(1-9、0、⌫、✓),点 ✓ 直接提交;实体键盘仍可输入
- 抽屉顶部新增秒级时钟(HH:MM:SS),位于日期标题上方

2) 日历格子
- 移除排单缩略信息(客户名/套餐色签/“x/y 单”小字)
- 保留左侧打手颜色竖条,中间改为大字「待完成 / 总数」,例如 1/2 = 还剩 1 单、共 2 单
- 大屏格子按此前约定保持原样

3) 客户停用 / 恢复 / 彻底删除
- customers 新增 archived 列(0/1);停用后默认从客户列表、日历、待办、大屏、到期提醒、徽标中隐藏,订阅与打卡记录原样保留
- 恢复后立即回到日历;列表右上角可切换「显示已停用」,已停用行带标记
- 彻底删除为二次确认操作,依赖外键级联清理订阅、打卡、暂停、追加记录与加急标记
- 停用/恢复/删除都会写入操作日志

4) 按钮收纳
- 任务行只露出「完成」,跳过 / 加急(取消加急)/ 追加收进「⋯」菜单;已完成行只露「撤销打卡」等,点空白处自动收起
- 加急、跳过等原有能力不变,仅入口折叠

5) 顶栏
- 在「今日到期」「3 天内到期」右侧新增「7 天内到期」徽标,固定统计未来 7 天

6) 视觉
- 移除管理端背景的横线装饰,改为纯底色;大屏网格背景保留

数据库兼容性:
- 只新增 customers.archived 一列,老库启动时自动 ALTER,可重复迁移;原有表结构与接口不变

测试:
- 新增单测 TestCustomerArchiveFilters:停用后日历/到期/徽标/搜索/列表全部过滤、恢复回日历、彻底删除级联清理订阅与打卡
- 功能验证 20 项(键盘、格子计数、折叠菜单、7 天徽标、时钟、停用/恢复/删除、背景)全部通过
- 原有 119 项验收回归全绿;go vet / go test ./... 通过;版本号 1.5.0
2026-10-19 02:12:25 +08:00

235 lines
11 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="cal-page">
<header class="cal-head">
<div class="cal-title">
<a class="btn btn-ghost btn-square" href="/calendar?y={{.D.PrevY}}&m={{.D.PrevM}}&pkg={{.D.PkgID}}" aria-label="上个月">‹</a>
<h1>{{.D.Title}}</h1>
<a class="btn btn-ghost btn-square" href="/calendar?y={{.D.NextY}}&m={{.D.NextM}}&pkg={{.D.PkgID}}" aria-label="下个月">›</a>
<a class="btn btn-sm" href="/calendar?pkg={{.D.PkgID}}">回到今天</a>
</div>
<div class="cal-stats">
<span><em>{{.D.MonthDue}}</em>本月到期</span>
<span><em>{{.D.MonthTasks}}</em>本月待打</span>
<span><em>{{.D.MonthDone}}</em>已完成</span>
{{if gt .D.MonthUrgent 0}}<span class="text-urgent"><em>{{.D.MonthUrgent}}</em>加急</span>{{end}}
<a class="btn btn-primary btn-sm" href="/customers/new">+ 新建客户</a>
</div>
</header>
{{if .D.HasTabs}}
<nav class="pkg-tabs">
{{range .D.Tabs}}
<a class="{{if eq .ID $.D.PkgID}}on{{end}}" style="--tab:{{.Color}}"
href="/calendar?y={{$.D.Year}}&m={{$.D.Month}}&d={{$.D.Selected}}&pkg={{.ID}}">
<i></i>{{.Name}}{{if gt .Count 0}}<em>{{.Count}}</em>{{end}}
</a>
{{end}}
</nav>
{{end}}
{{if .D.NoPackages}}
<div class="notice">还没有服务套餐,先去 <a href="/packages/new">新建套餐</a>,再给客户开通。</div>
{{else if .D.NoCustomers}}
<div class="notice">还没有客户,<a href="/customers/new">新建第一位客户</a>。</div>
{{end}}
<div class="cal-body">
<div class="cal-grid" role="grid">
<div class="cal-dow" role="row">
{{range .D.Weekdays}}<span>{{.}}</span>{{end}}
</div>
{{range .D.Weeks}}
<div class="cal-week" role="row">
{{range .}}
<a class="cal-cell heat-{{.Heat}}{{if not .InMonth}} out{{end}}{{if .IsToday}} today{{end}}{{if .IsSelected}} sel{{end}}{{if .IsWeekend}} weekend{{end}}{{if .DueCount}} has-due{{end}}"
href="/calendar?y={{$.D.Year}}&m={{$.D.Month}}&d={{.Date}}&pkg={{$.D.PkgID}}" role="gridcell">
{{if .Strip}}<span class="asg-strip" aria-hidden="true">{{range .Strip}}<i style="background:{{.}}"></i>{{end}}</span>{{end}}
<div class="cell-top">
<span class="dnum">{{.Day}}</span>
<span class="cell-badges">
{{if gt .UrgentCnt 0}}<span class="urgent-dot" title="{{.UrgentCnt}} 项加急"></span>{{end}}
{{if .DueCount}}<span class="due-badge"><em>到期</em>{{.DueCount}}</span>{{end}}
</span>
</div>
{{if .TaskCount}}
<div class="cell-count">
<b>{{.PendingCount}}</b><span>/ {{.TaskCount}}</span>
</div>
{{end}}
</a>
{{end}}
</div>
{{end}}
</div>
<aside class="drawer">
<div class="drawer-clock"><b data-clock>--:--:--</b><span>{{.D.TodayStr}}</span></div>
<header class="drawer-head">
<h2>{{.D.SelectedLabel}}</h2>
<div class="drawer-tags">
{{if .D.SelectedIsToday}}<span class="chip chip-active">今天</span>{{end}}
{{if .D.SelectedIsPast}}<span class="chip chip-closed">已过去</span>{{end}}
<span class="chip chip-plain">{{.D.TaskPending}} 待完成</span>
</div>
</header>
<section class="drawer-block">
<h3>当日待完成 <em>{{.D.TaskPending}}</em><span class="muted small">/ {{len .D.Tasks}} 单</span></h3>
<form class="quick-form" method="post" action="/checkins/quick">
<input type="hidden" name="date" value="{{.D.Selected}}">
<input type="hidden" name="pkg" value="{{.D.PkgID}}">
<input type="hidden" name="back" value="{{.D.Back}}">
<input type="text" name="tail" placeholder="输入手机尾号,回车即可完成" maxlength="8" autocomplete="off" inputmode="numeric" aria-label="手机尾号快速完成">
<button class="btn btn-sm btn-primary" type="submit">完成</button>
</form>
<p class="muted small quick-hint">唯一匹配直接完成;同尾号多人 / 一单多服务会列出让你勾选。</p>
<div class="keypad" data-keypad aria-label="数字小键盘">
<button type="button" data-key="1">1</button>
<button type="button" data-key="2">2</button>
<button type="button" data-key="3">3</button>
<button type="button" data-key="4">4</button>
<button type="button" data-key="5">5</button>
<button type="button" data-key="6">6</button>
<button type="button" data-key="7">7</button>
<button type="button" data-key="8">8</button>
<button type="button" data-key="9">9</button>
<button type="button" data-key="0">0</button>
<button type="button" data-key="back" class="key-back" aria-label="退格">⌫</button>
<button type="button" data-key="ok" class="key-ok" aria-label="确认完成">✓</button>
</div>
{{if .D.PickTail}}
<section class="picker">
<header class="picker-head">
<h4>尾号 {{.D.PickTail}} 的待完成项</h4>
<a class="btn btn-sm btn-ghost" href="{{.D.Back}}">收起</a>
</header>
{{if .D.PickRows}}
<form method="post" action="/checkins/batch">
<input type="hidden" name="date" value="{{.D.Selected}}">
<input type="hidden" name="back" value="{{.D.Back}}">
<ul class="pick-list">
{{range .D.PickRows}}
<li>
<label>
<input type="checkbox" name="sub_ids" value="{{.ID}}"{{if not .Done}}{{if not .Skipped}} checked{{end}}{{end}}>
<span class="ticket" style="--tag:{{.PackageColor}}"><b>{{.CustomerTail}}</b>{{.CustomerName}}</span>
<span class="pick-pkg">{{.PackageName}}</span>
<span class="pick-remain">{{.RemainShort}}</span>
{{if .Done}}<span class="chip chip-active">已完成</span>{{end}}
{{if .Skipped}}<span class="chip chip-paused">已跳过</span>{{end}}
</label>
</li>
{{end}}
</ul>
<div class="actions">
<button class="btn btn-primary btn-sm" type="submit">确认完成选中项</button>
<a class="btn btn-sm" href="{{.D.Back}}">取消</a>
</div>
</form>
{{else}}
<p class="muted">{{.D.PickMsg}}</p>
{{end}}
</section>
{{end}}
{{if .D.Tasks}}
<ul class="task-list">
{{range .D.Tasks}}
<li class="task{{if .Done}} done{{end}}{{if .Skipped}} skipped{{end}}{{if .Urgent}} urgent{{end}}" style="--asg:{{.AssigneeHex}}">
<div class="task-main">
<a class="task-title" href="/customers/{{.CustomerID}}">
<span class="ticket" style="--tag:{{.PackageColor}}"><b>{{.CustomerTail}}</b>{{.CustomerName}}</span>
<span class="pkg">{{.PackageName}}</span>
{{if .Urgent}}<span class="tag-urgent">加急</span>{{end}}
</a>
<div class="task-meta">
<span class="asg{{if .AssigneeNone}} none{{end}}"><i style="background:{{.AssigneeHex}}"></i>{{.AssigneeLabel}}</span>
<span>{{.RemainText}}</span>
{{if .DueText}}<span class="due due-{{.Eval.Level}}">{{.DueText}}</span>{{end}}
</div>
{{if .Note}}<div class="task-note">{{.Note}}{{if .Operator}} · {{.Operator}}{{end}}</div>{{end}}
</div>
<div class="task-actions">
{{if or .Done .Skipped}}
<span class="chip {{if .Done}}chip-active{{else}}chip-paused{{end}}">{{if .Done}}已完成{{else}}已跳过{{end}}</span>
{{else}}
<form method="post" action="/subscriptions/{{.ID}}/checkin">
<input type="hidden" name="date" value="{{$.D.Selected}}">
<input type="hidden" name="status" value="done">
<input type="hidden" name="back" value="{{$.D.Back}}">
<button class="btn btn-sm btn-primary">完成</button>
</form>
{{end}}
<details class="more-actions">
<summary class="btn btn-sm btn-ghost" title="更多操作" aria-label="更多操作">⋯</summary>
<div class="more-menu">
{{if or .Done .Skipped}}
<form method="post" action="/checkins/{{.CheckinID}}/delete">
<input type="hidden" name="back" value="{{$.D.Back}}">
<button class="btn btn-sm btn-ghost" type="submit">撤销打卡</button>
</form>
{{else}}
<form method="post" action="/subscriptions/{{.ID}}/checkin">
<input type="hidden" name="date" value="{{$.D.Selected}}">
<input type="hidden" name="status" value="skipped">
<input type="hidden" name="back" value="{{$.D.Back}}">
<button class="btn btn-sm btn-ghost" type="submit">跳过这次</button>
</form>
{{end}}
<form method="post" action="/tasks/urgent">
<input type="hidden" name="sub_id" value="{{.ID}}">
<input type="hidden" name="date" value="{{$.D.Selected}}">
<input type="hidden" name="urgent" value="{{if .Urgent}}0{{else}}1{{end}}">
<input type="hidden" name="back" value="{{$.D.Back}}">
<button class="btn btn-sm {{if .Urgent}}btn-urgent{{else}}btn-ghost{{end}}" type="submit">{{if .Urgent}}取消加急{{else}}标记加急{{end}}</button>
</form>
<a class="btn btn-sm" href="/calendar?y={{$.D.Year}}&m={{$.D.Month}}&d={{$.D.Selected}}&pkg={{$.D.PkgID}}&extend={{.ID}}#extend">追加时长</a>
</div>
</details>
</div>
</li>
{{end}}
</ul>
{{else}}<p class="muted">这天没有待打任务。</p>{{end}}
</section>
{{if .D.Extend}}{{template "extend-panel" .D.Extend}}{{end}}
<section class="drawer-block">
<h3>这天到期 <em>{{len .D.Due}}</em></h3>
{{if .D.Due}}
<ul class="due-list">
{{range .D.Due}}
<li>
<a class="due-main" href="/customers/{{.CustomerID}}">
<span class="ticket" style="--tag:{{.PackageColor}}"><b>{{.CustomerTail}}</b>{{.CustomerName}}</span>
<span class="pkg">{{.PackageName}}</span>
<span class="rem">{{.RemainShort}}</span>
</a>
<a class="btn btn-sm" href="/calendar?y={{$.D.Year}}&m={{$.D.Month}}&d={{$.D.Selected}}&pkg={{$.D.PkgID}}&extend={{.ID}}#extend">追加</a>
</li>
{{end}}
</ul>
{{else}}<p class="muted">没有到期订阅。</p>{{end}}
</section>
<section class="drawer-block">
<h3>近 7 天到期</h3>
{{if .D.OtherDue}}
<ul class="mini-due">
{{range .D.OtherDue}}
<li>
<a href="/customers/{{.CustomerID}}"><span class="ticket" style="--tag:{{.PackageColor}}"><b>{{.CustomerTail}}</b>{{.CustomerName}}</span></a>
<span class="muted">{{.PackageName}}</span>
<span class="sm-date">{{.EndDate}}</span>
</li>
{{end}}
</ul>
{{else}}<p class="muted">7 天内没有其它到期。</p>{{end}}
</section>
</aside>
</div>
</div>
{{end}}