diff --git a/README.md b/README.md index 43121a9..fc17369 100644 --- a/README.md +++ b/README.md @@ -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),仍可手动改。 - **删除订阅**:已结束的订阅卡片上,在「重新启用」旁新增「删除」,二次确认后彻底删除该订阅及其打卡/追加记录。 diff --git a/internal/web/handlers_calendar.go b/internal/web/handlers_calendar.go index 2e0cf34..b834809 100644 --- a/internal/web/handlers_calendar.go +++ b/internal/web/handlers_calendar.go @@ -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 { diff --git a/internal/web/handlers_portal.go b/internal/web/handlers_portal.go index bcd321b..005136f 100644 --- a/internal/web/handlers_portal.go +++ b/internal/web/handlers_portal.go @@ -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 } diff --git a/internal/web/handlers_screen.go b/internal/web/handlers_screen.go index 8962be1..6bca6c4 100644 --- a/internal/web/handlers_screen.go +++ b/internal/web/handlers_screen.go @@ -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 diff --git a/internal/web/static/screen.css b/internal/web/static/screen.css index 1a10735..d876739 100644 --- a/internal/web/static/screen.css +++ b/internal/web/static/screen.css @@ -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; diff --git a/internal/web/templates/pages/screen.html b/internal/web/templates/pages/screen.html index f6df175..b2e78ed 100644 --- a/internal/web/templates/pages/screen.html +++ b/internal/web/templates/pages/screen.html @@ -51,12 +51,12 @@ {{range .D.Weeks}}
{{range .}} -
+
{{if .Strip}}{{end}} {{.Day}} {{if gt .Urgent 0}}{{end}} - {{if .Count}}{{.Done}}/{{.Count}}{{end}} + {{if and .BlockText (not .Count)}}{{.BlockText}}{{else if .Count}}{{.Done}}/{{.Count}}{{end}}
{{end}}
diff --git a/main.go b/main.go index 44e598f..22eed7b 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,7 @@ import ( "daili/internal/web" ) -const version = "1.11.4" +const version = "1.11.5" func main() { cfgFlag := flag.String("config", "", "配置文件路径(默认:exe 同级的 "+config.FileName+")")