- 整段色块计数改为按当前格所在周期:cycleDone → 1/1,否则 0/1; 不再把全局 used/total 重复到每一格。 - 抽出 blockCellText / blockCellPercent,工作台、大屏、客户自助页共用同一口径; 通行证保持 0/1 / 1/1,深渊按所在周期 1/1 / 0/1。 - 大屏恢复通行证 / 深渊页签;screenCalSubs 补上缺失的 start(之前为零值, 导致大屏周期判定和色阶都算错),大屏格子现在显示与工作台一致的 1/1、0/1 以及 34%→4% 的周期色阶。 - 大屏色块样式:has-block / scr-block,亮暗两套都可读。 - 版本 1.11.5;README 的深渊说明同步,「工作台、大屏、客户自助页同一口径」。 验证:测试数据第一周期完成、第二周期未完成,工作台、 大屏、客户自助页均显示 20–26 为 1/1、27–31 为 0/1;周期浓度 34%/24%/14%/4%;gofmt / go vet / go test 全绿。
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
┌───────────────────────────────────────────────┐
|
||||
│ 代 打 台 · 代打管理平台 │
|
||||
└───────────────────────────────────────────────┘
|
||||
版本 1.11.4 配置文件 daili.ini
|
||||
版本 1.11.5 配置文件 daili.ini
|
||||
数据库 D:\代打台\daili.db
|
||||
访问地址:
|
||||
本机 http://127.0.0.1:17777
|
||||
@@ -167,7 +167,7 @@
|
||||
## 4.3 通行证、深渊与按次有效期
|
||||
|
||||
- **通行证**(原「包赛季」):套餐上直接填通行证开始日/结束日。客户开通后,该套餐的独立日历会把整段有效期连续着色(用套餐色签的淡化色),每一格显示同一个「已完成 / 总单数」(0/1 或 1/1)。有效期内任意一天点「完成通行证」即整段完成,完成后色块保留并显示 1/1。
|
||||
- **深渊**:套餐填「周期天数 N」与「周期数 X」。开通时从开始日连续排出 X 个 N 天周期;独立日历按周期连续着色,颜色从约 34% 依次减淡到约 4%,每一格显示同一个「已完成周期 / 总周期数」。周期内任意一天完成即算该周期完成(同周期重复完成不会重复计数),撤销该天打卡会退回该周期。
|
||||
- **深渊**:套餐填「周期天数 N」与「周期数 X」。开通时从开始日连续排出 X 个 N 天周期;独立日历按周期连续着色,颜色从约 34% 依次减淡到约 4%。每一格显示**该周期自己**的完成情况:`1/1` 或 `0/1`,不再重复全局的 `x/X`;工作台、大屏、客户自助页同一口径。周期内任意一天完成即算该周期完成(同周期重复完成不会重复计数),撤销该天打卡会退回该周期。
|
||||
- **通行证过期不可选**:今天已过通行证结束日的套餐,在客户开通下拉里会置灰并标「通行证已结束」,服务端也会拒绝开通。
|
||||
- **按次默认有效期**:按次套餐可设「默认有效期(天)」,开单时自动算出到期日(开始日 + 天数 − 1),仍可手动改。
|
||||
- **删除订阅**:已结束的订阅卡片上,在「重新启用」旁新增「删除」,二次确认后彻底删除该订阅及其打卡/追加记录。
|
||||
|
||||
@@ -446,15 +446,14 @@ func (s *Server) handleCalendar(w http.ResponseWriter, r *http.Request) {
|
||||
if cs.isBlock() {
|
||||
if cs.blockCovers(ds) && cell.BlockText == "" {
|
||||
cell.BlockColor = cs.sub.PackageColor
|
||||
cell.BlockDone = cs.blockDone()
|
||||
cell.BlockTotal = cs.blockTotal()
|
||||
cell.BlockText = fmt.Sprintf("%d/%d", cell.BlockDone, cell.BlockTotal)
|
||||
cell.BlockTitle = cs.sub.PackageName + " · " + cell.BlockText
|
||||
cell.BlockPercent = 18
|
||||
if cs.sub.Abyss && cs.sub.CycleDays > 0 {
|
||||
idx := domain.CycleIndex(cs.start, day, cs.sub.CycleDays)
|
||||
cell.BlockPercent = abyssBlockPercent(idx, cs.sub.TotalCount)
|
||||
cell.BlockText = blockCellText(cs, ds)
|
||||
cell.BlockDone = 0
|
||||
if cell.BlockText == "1/1" {
|
||||
cell.BlockDone = 1
|
||||
}
|
||||
cell.BlockTotal = 1
|
||||
cell.BlockTitle = cs.sub.PackageName + " · " + cell.BlockText
|
||||
cell.BlockPercent = blockCellPercent(cs, day)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -664,6 +663,30 @@ func assigneeStrip(tickets []Ticket) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// blockCellText 整段型订阅在 date 所在周期的完成文本:
|
||||
// 通行证 0/1 或 1/1;深渊按 date 所在的那一个周期 0/1 或 1/1,不显示全局 x/N。
|
||||
func blockCellText(cs calSub, date string) string {
|
||||
if cs.sub.Abyss {
|
||||
if cs.cycleDone(date) {
|
||||
return "1/1"
|
||||
}
|
||||
return "0/1"
|
||||
}
|
||||
if cs.sub.SeasonDone != "" {
|
||||
return "1/1"
|
||||
}
|
||||
return "0/1"
|
||||
}
|
||||
|
||||
// blockCellPercent 整段型色块浓度:通行证 18%;深渊按所在周期 34%→4%。
|
||||
func blockCellPercent(cs calSub, day time.Time) int {
|
||||
if cs.sub.Abyss && cs.sub.CycleDays > 0 {
|
||||
idx := domain.CycleIndex(cs.start, day, cs.sub.CycleDays)
|
||||
return abyssBlockPercent(idx, cs.sub.TotalCount)
|
||||
}
|
||||
return 18
|
||||
}
|
||||
|
||||
// abyssBlockPercent 深渊色块浓度:第一个周期 34%,之后每个周期依次减淡,
|
||||
// 最后一个周期 4%(周期数大于 1 时线性过渡);跨度比 v1.11.3 的 22%→10% 明显更大。
|
||||
func abyssBlockPercent(idx, total int) int {
|
||||
|
||||
@@ -295,13 +295,9 @@ func (s *Server) buildPortal(c store.Customer, tail, name, month string) *portal
|
||||
if cs.isBlock() {
|
||||
if cs.blockCovers(ds) && pd.BlockText == "" {
|
||||
pd.BlockColor = sub.PackageColor
|
||||
pd.BlockText = fmt.Sprintf("%d/%d", cs.blockDone(), cs.blockTotal())
|
||||
pd.BlockText = blockCellText(cs, ds)
|
||||
pd.BlockTitle = sub.PackageName + " · " + pd.BlockText
|
||||
pd.BlockPercent = 18
|
||||
if sub.Abyss && sub.CycleDays > 0 {
|
||||
idx := domain.CycleIndex(cs.start, d, sub.CycleDays)
|
||||
pd.BlockPercent = abyssBlockPercent(idx, sub.TotalCount)
|
||||
}
|
||||
pd.BlockPercent = blockCellPercent(cs, d)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -21,6 +21,11 @@ type screenDay struct {
|
||||
Done int
|
||||
Urgent int
|
||||
Heat int
|
||||
// 通行证 / 深渊整段色块:BlockText 是该周期的 1/1 或 0/1
|
||||
BlockColor string
|
||||
BlockText string
|
||||
BlockTitle string
|
||||
BlockPercent int
|
||||
// Strip 打手色竖条(去重)
|
||||
Strip []string
|
||||
}
|
||||
@@ -66,7 +71,13 @@ func (s *Server) screenCalSubs(from, to string, pkgID int64, tag string) ([]calS
|
||||
warn := s.warnDays()
|
||||
list := make([]calSub, 0, len(subs))
|
||||
for _, sub := range subs {
|
||||
cs := calSub{sub: sub, pauses: pauses[sub.ID], checkins: checkins[sub.ID], eval: sub.Eval(today, warn)}
|
||||
cs := calSub{
|
||||
sub: sub,
|
||||
start: domain.ParseDateOr(sub.StartDate, today),
|
||||
pauses: pauses[sub.ID],
|
||||
checkins: checkins[sub.ID],
|
||||
eval: sub.Eval(today, warn),
|
||||
}
|
||||
if sub.EndDate != "" {
|
||||
cs.hasEnd = true
|
||||
cs.end = domain.ParseDateOr(sub.EndDate, today)
|
||||
@@ -138,15 +149,30 @@ func (s *Server) handleScreen(w http.ResponseWriter, r *http.Request) {
|
||||
count, doneN, urgentN := 0, 0, 0
|
||||
strip := []string{}
|
||||
seenColor := map[string]bool{}
|
||||
sd := screenDay{
|
||||
Date: ds,
|
||||
Day: d.Day(),
|
||||
InMonth: d.Month() == today.Month(),
|
||||
IsToday: ds == domain.Date(today),
|
||||
IsPast: d.Before(today),
|
||||
}
|
||||
for _, cs := range list {
|
||||
if heatMode == "due" {
|
||||
if cs.sub.EndDate == ds {
|
||||
count++
|
||||
}
|
||||
}
|
||||
// 通行证 / 深渊是整段型:不计入每日待完成热力,但用整段色块显示周期状态。
|
||||
if cs.isBlock() {
|
||||
if cs.blockCovers(ds) && sd.BlockText == "" {
|
||||
sd.BlockColor = cs.sub.PackageColor
|
||||
sd.BlockText = blockCellText(cs, ds)
|
||||
sd.BlockTitle = cs.sub.PackageName + " · " + sd.BlockText
|
||||
sd.BlockPercent = blockCellPercent(cs, d)
|
||||
}
|
||||
continue
|
||||
}
|
||||
// 通行证 / 深渊是整段型,不计入「每日待完成」热力
|
||||
if cs.isBlock() {
|
||||
if heatMode == "due" {
|
||||
continue
|
||||
}
|
||||
if !cs.covers(ds) {
|
||||
@@ -173,18 +199,12 @@ func (s *Server) handleScreen(w http.ResponseWriter, r *http.Request) {
|
||||
if d.Month() == today.Month() {
|
||||
data.HeatTotal += count
|
||||
}
|
||||
week = append(week, screenDay{
|
||||
Date: ds,
|
||||
Day: d.Day(),
|
||||
InMonth: d.Month() == today.Month(),
|
||||
IsToday: ds == domain.Date(today),
|
||||
IsPast: d.Before(today),
|
||||
Count: count + doneN,
|
||||
Done: doneN,
|
||||
Urgent: urgentN,
|
||||
Heat: heatLevel(count),
|
||||
Strip: strip,
|
||||
})
|
||||
sd.Count = count + doneN
|
||||
sd.Done = doneN
|
||||
sd.Urgent = urgentN
|
||||
sd.Heat = heatLevel(count)
|
||||
sd.Strip = strip
|
||||
week = append(week, sd)
|
||||
}
|
||||
data.Weeks = append(data.Weeks, week)
|
||||
}
|
||||
@@ -214,16 +234,9 @@ func (s *Server) handleScreen(w http.ResponseWriter, r *http.Request) {
|
||||
data.Recent = adj
|
||||
}
|
||||
|
||||
// 页签:按标签合并 + 独立显示另开;通行证/深渊不计每日热力,不放大屏页签
|
||||
// 页签:按标签合并 + 独立显示另开;通行证 / 深渊也放进大屏,用整段色块显示周期状态
|
||||
pkgs, _ := s.db.ListPackages(true)
|
||||
screenPkgs := make([]store.Package, 0, len(pkgs))
|
||||
for _, p := range pkgs {
|
||||
if p.Season || p.Abyss {
|
||||
continue
|
||||
}
|
||||
screenPkgs = append(screenPkgs, p)
|
||||
}
|
||||
data.Tabs, data.HasTabs = buildCalendarTabs(screenPkgs, nil, 0)
|
||||
data.Tabs, data.HasTabs = buildCalendarTabs(pkgs, nil, 0)
|
||||
for i := range data.Tabs {
|
||||
t := &data.Tabs[i]
|
||||
href := "/screen?heat=" + heatMode
|
||||
|
||||
@@ -210,6 +210,18 @@ body.screen-body::before {
|
||||
.heat-cell.h4 { background: color-mix(in srgb, var(--scr-accent) 52%, var(--scr-panel-2)); }
|
||||
.heat-cell.h3 span, .heat-cell.h4 span { color: var(--scr-text); }
|
||||
.heat-cell.h3 b, .heat-cell.h4 b { color: var(--scr-text); }
|
||||
.heat-cell.has-block {
|
||||
background: color-mix(in srgb, var(--block) var(--block-pct, 18%), var(--scr-panel-2));
|
||||
border-color: color-mix(in srgb, var(--block) 30%, var(--scr-line));
|
||||
}
|
||||
.heat-cell b.scr-block {
|
||||
display: inline-flex; align-items: center;
|
||||
padding: 1px 5px; border-radius: 4px;
|
||||
background: color-mix(in srgb, var(--block) 20%, var(--scr-panel));
|
||||
border: 1px solid color-mix(in srgb, var(--block) 32%, transparent);
|
||||
color: var(--scr-text);
|
||||
font-size: 12px; font-weight: 700;
|
||||
}
|
||||
|
||||
.cell-urgent {
|
||||
position: absolute; top: 3px; right: 3px;
|
||||
|
||||
@@ -51,12 +51,12 @@
|
||||
{{range .D.Weeks}}
|
||||
<div class="heat-row">
|
||||
{{range .}}
|
||||
<div class="heat-cell h{{.Heat}}{{if not .InMonth}} out{{end}}{{if .IsToday}} today{{end}}{{if .IsPast}} past{{end}}"
|
||||
data-date="{{.Date}}" title="{{.Date}}">
|
||||
<div class="heat-cell h{{.Heat}}{{if not .InMonth}} out{{end}}{{if .IsToday}} today{{end}}{{if .IsPast}} past{{end}}{{if .BlockColor}} has-block{{end}}"
|
||||
data-date="{{.Date}}" title="{{if .BlockTitle}}{{.BlockTitle}} · {{end}}{{.Date}}"{{if .BlockColor}} style="--block:{{.BlockColor}};--block-pct:{{.BlockPercent}}%"{{end}}>
|
||||
{{if .Strip}}<span class="asg-strip sm" aria-hidden="true">{{range .Strip}}<i style="background:{{.}}"></i>{{end}}</span>{{end}}
|
||||
<span>{{.Day}}</span>
|
||||
{{if gt .Urgent 0}}<u class="cell-urgent"></u>{{end}}
|
||||
{{if .Count}}<b>{{.Done}}/{{.Count}}</b>{{end}}
|
||||
{{if and .BlockText (not .Count)}}<b class="scr-block">{{.BlockText}}</b>{{else if .Count}}<b>{{.Done}}/{{.Count}}</b>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user