fix(v1.8.1): 按次显示已完成/总次数、隐藏已开通套餐、已结束订阅置灰置底

1) 按次计费显示口径
- 订阅卡片、列表、大屏 TOP 都从「剩 x / y 次」改为「已完成 x / y 次」
- 进度条改为按已用次数计算

2) 开通订阅下拉过滤
- 客户详情「开通订阅」下拉自动隐藏该客户已开通的套餐,延期只能走「追加」入口,避免新开叠加
- 若客户已开通全部套餐,下拉只剩占位项

3) 已结束订阅置灰置底
- 客户详情的订阅卡片按「进行中 → 已结束」排序,已结束排到最后
- 已结束卡片降低透明度、降低饱和度,鼠标悬停恢复部分可见

数据库:无变更
测试:
- 功能验证 5 项:按次文案、已开通套餐不在下拉、未开通仍可选、已结束卡片置灰、已结束排在最后
- 119 项验收回归全绿(8 条按次断言随之更新为新口径)
- 无头 Chrome 实拍客户详情页;版本号 1.8.1
This commit is contained in:
subtrack
2026-10-19 19:53:50 +08:00
parent a3fd817edc
commit bcce6bb830
6 changed files with 45 additions and 14 deletions
+22 -2
View File
@@ -323,12 +323,32 @@ func (s *Server) handleCustomerDetail(w http.ResponseWriter, r *http.Request) {
pauses, _ := s.db.PausesForSubs(ids)
users, _ := s.db.ActiveUsers()
// 已开通的套餐不再出现在「开通订阅」下拉里,延期走追加
owned := map[int64]bool{}
for _, sub := range subs {
owned[sub.PackageID] = true
}
available := make([]store.Package, 0, len(pkgs))
for _, pkg := range pkgs {
if !owned[pkg.ID] {
available = append(available, pkg)
}
}
sorted := append([]store.Subscription{}, subs...)
sort.SliceStable(sorted, func(i, j int) bool {
ai := sorted[i].Status == domain.StatusClosed
aj := sorted[j].Status == domain.StatusClosed
if ai != aj {
return !ai
}
return sorted[i].ID < sorted[j].ID
})
data := &customerDetailData{
Users: users,
CustCal: s.buildCustomerCal(c, r.URL.Query().Get("cm"), today),
Customer: c,
Subs: subViews(subs, today, warn),
Packages: pkgs,
Subs: subViews(sorted, today, warn),
Packages: available,
Timeline: buildTimeline(subs, pauses, today),
AdjSubName: map[int64]string{},
AdjSubColor: map[int64]string{},
+10
View File
@@ -1109,3 +1109,13 @@ fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
.season-band.start { border-top-left-radius: 3px; border-bottom-left-radius: 3px; left: 2px; }
.season-band.end { border-top-right-radius: 3px; border-bottom-right-radius: 3px; right: 2px; }
.cal-cell.has-due .season-band { bottom: 4px; }
/* ---------- v1.8.1:已结束订阅置灰 ---------- */
.subcard.is-closed {
opacity: 0.55;
filter: saturate(0.5);
background: var(--surface-2);
}
.subcard.is-closed:hover { opacity: 0.8; }
+1 -1
View File
@@ -19,7 +19,7 @@
{{end}}
{{define "sub-card"}}
<article class="subcard" id="sub-{{.ID}}" style="--tag:{{.PackageColor}}">
<article class="subcard{{if eq .Status "closed"}} is-closed{{end}}" id="sub-{{.ID}}" style="--tag:{{.PackageColor}}">
<div class="subcard-head">
<span class="tag"></span>
<h3>{{.PackageName}}</h3>
+3 -2
View File
@@ -103,8 +103,8 @@ func newSubView(s store.Subscription, today time.Time, warnDays int) SubView {
}
} else {
v.UnitLabel = "按次"
v.RemainText = fmt.Sprintf("剩 %d / %d 次", e.RemainingCount, s.TotalCount)
v.RemainShort = fmt.Sprintf("%d 次", e.RemainingCount)
v.RemainText = fmt.Sprintf("已完成 %d / %d 次", s.UsedCount, s.TotalCount)
v.RemainShort = fmt.Sprintf("%d/%d 次", s.UsedCount, s.TotalCount)
v.PeriodText = s.StartDate + " 起"
if s.EndDate != "" {
v.PeriodText += " · 有效期至 " + s.EndDate
@@ -112,6 +112,7 @@ func newSubView(s store.Subscription, today time.Time, warnDays int) SubView {
v.PeriodText += " · 无有效期"
}
if s.TotalCount > 0 {
v.Progress = clampPct(s.UsedCount * 100 / s.TotalCount)
v.Progress = clampPct(e.RemainingCount * 100 / s.TotalCount)
}
}