fix(v1.11.5): 深渊日历按单周期显示 1/1 或 0/1,工作台与大屏同步
build / build-and-test (push) Successful in 3m26s

- 整段色块计数改为按当前格所在周期: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:
2026-10-20 04:06:39 +08:00
parent 2e2d7d4bdd
commit 0392f44cf1
7 changed files with 88 additions and 44 deletions
+31 -8
View File
@@ -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 {
+2 -6
View File
@@ -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
}
+37 -24
View File
@@ -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
+12
View File
@@ -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;
+3 -3
View File
@@ -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>