fix(v1.11.3): 客户自助查询 /customer 全量修复
build / build-and-test (push) Successful in 3m18s

- 路由:从 screen_public 拆出 portal_public(默认 true),关掉大屏不再连带 404;
  设置页显示客户自助状态;/customer 与 /screen 各自独立。
- 查询:尾号只接受 4–8 位数字,服务端精确匹配,去掉 LIKE 模糊回退;按 IP
  40 次/分钟限流;页面 noindex。
- 语义:修正「今日待完成」把已完成数当待完成数的问题;0/0 改为「今日无排单」;
  无订阅走空态;删掉进度条和重复的今日文案,只留一个状态。
- 订阅:复用管理端 SubView / calSub;通行证 1/1、深渊 x/y 周期;过期、暂停、
  按次用尽、跳过都有独立口径;「最近到期」只取未来日期。
- 日历:与主日历一致,只在本月格显示任务;通行证/深渊整段色块(深渊按周期
  减淡),日常任务用空心/实心点;每格带读屏描述;today 加「今天」文字标记。
- 错误:只渲染一次并放回内容容器,字段加 aria-invalid;删掉顶部重复横幅。
- 多客户:显示掩码姓名 + 卡内补姓名表单;姓名回填;翻月用 url.Values 编码;
  1 位尾号不触发查询;只有空态/补姓名时 autofocus。
- UI:品牌头、可折叠「查询其他尾号」、订阅全宽列表、状态统一用现有 chip、
  月份按钮 44px、去掉卡中卡 / opacity / 满屏 0/1。
- 版本 1.11.3;README 增加 4.4 与 portal_public 配置说明。

验证:gofmt / go vet / go test 全绿;portal_public=true 时 /customer 200、
false 时 404;screen_public=false + portal_public=true 时 /customer 200、
/screen 302;尾号 2 与 % 被拒;2222 多客户掩码;9900 日常点、3321 通行证
1/1、6677 深渊 1/4 + 已到期订阅;明暗 1440 与 390 截图无对比度/溢出问题。
This commit is contained in:
2026-10-20 03:39:41 +08:00
parent 7371b86a97
commit ddc7988f34
11 changed files with 539 additions and 197 deletions
+114 -15
View File
@@ -1259,24 +1259,123 @@ fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
/* ---------- 客户自助查询 /customer ---------- */
.portal-wrap { max-width: 980px; margin: 0 auto; padding: 24px 16px 48px; }
.portal-head h1 { color: var(--app-text) !important; -webkit-text-fill-color: currentColor; background: none !important; font-size: 1.6rem; font-weight: 800; margin: 0 0 4px; background: linear-gradient(135deg, var(--app-accent-strong), var(--app-accent)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.portal-form { border-radius: 1rem; }
.portal-progress .bar { height: 8px; border-radius: 999px; background: var(--app-surface-3); overflow: hidden; margin-bottom: 6px; }
.portal-progress .bar i { display: block; height: 100%; background: linear-gradient(135deg, var(--app-accent-strong), var(--app-accent)); border-radius: 999px; }
.pcal { border: 1px solid var(--app-border); border-radius: .85rem; overflow: hidden; }
.pcal-dow { display: grid; grid-template-columns: repeat(7, 1fr); background: var(--app-surface-3); }
.pcal-dow span { text-align: center; padding: 6px 0; font-size: .72rem; color: var(--app-text-3); }
.portal-wrap { max-width: 980px; margin: 0 auto; padding: 24px 16px 56px; }
.portal-head { margin-bottom: 18px; }
.portal-brand {
display: inline-flex; align-items: center; gap: 8px;
margin-bottom: 12px; color: var(--app-text); text-decoration: none;
font-weight: 700; font-size: .9375rem;
}
.portal-brand:hover { color: var(--app-text); }
.portal-brand .brand-mark { width: 26px; height: 26px; font-size: .8125rem; }
.portal-head h1 { font-size: 1.6rem; font-weight: 800; margin: 0 0 4px; color: var(--app-text); }
.portal-head p { margin: 0; }
.portal-error { margin: 0 0 14px; }
.portal-search {
border: 1px solid var(--app-border); border-radius: var(--app-radius);
background: var(--app-surface); box-shadow: var(--app-shadow-sm);
overflow: hidden;
}
.portal-search-head {
display: flex; align-items: center; gap: 8px;
padding: 13px 16px; cursor: pointer; list-style: none;
font-weight: 600; color: var(--app-text);
}
.portal-search-head::-webkit-details-marker { display: none; }
.portal-search-head::after { content: "▾"; margin-left: auto; color: var(--app-text-3); font-size: .75rem; }
.portal-search[open] .portal-search-head { border-bottom: 1px solid var(--app-border); }
.portal-search[open] .portal-search-head::after { transform: rotate(180deg); }
.portal-form-body { padding: 14px 16px 16px; }
.portal-form-body .form-label { font-size: .8125rem; color: var(--app-text-2); margin-bottom: 5px; }
.portal-client { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 12px; }
.portal-client-main { min-width: 0; }
.portal-client-name { font-size: 1.5rem; font-weight: 800; margin: 0 0 6px; }
.portal-client-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.portal-client-status { margin-left: auto; }
.portal-client-status .chip { font-size: .8125rem; }
.portal-section-head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.portal-section-head h2 { font-size: 1rem; font-weight: 700; margin: 0; }
.portal-section-count {
display: inline-flex; align-items: center; justify-content: center;
min-width: 20px; height: 20px; padding: 0 6px; border-radius: 999px;
background: var(--app-surface-3); color: var(--app-text-2);
font-size: .75rem; font-weight: 600;
}
.portal-sub-list { margin-top: 2px; }
.portal-sub { padding: 12px 0; border-top: 1px solid var(--app-border); }
.portal-sub:first-child { border-top: 0; }
.portal-sub-main { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.portal-sub-main strong { font-size: .9375rem; }
.portal-sub-meta {
display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px;
margin-top: 4px; font-size: .8125rem; color: var(--app-text-2);
}
.portal-sub-meta .muted { color: var(--app-text-3); }
.portal-ask-names { font-size: .875rem; color: var(--app-text-2); }
.portal-ask-form { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.portal-ask-form .form-control { flex: 1 1 200px; width: auto; }
.pcal-nav { display: flex; align-items: center; gap: 6px; margin-left: auto; }
.pcal-nav b { font-size: .875rem; min-width: 92px; text-align: center; }
.pcal-nav-btn {
width: 44px; height: 44px; padding: 0;
display: inline-flex; align-items: center; justify-content: center;
font-size: 1.125rem; line-height: 1;
}
.pcal { margin-top: 10px; max-width: 880px; margin-left: auto; margin-right: auto; }
.pcal-dow { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); background: var(--app-surface-2); border-bottom: 1px solid var(--app-border); }
.pcal-dow span { padding: 6px 0; text-align: center; font-size: .72rem; color: var(--app-text-3); }
.pcal-row { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.pcal-cell { min-height: 54px; padding: 6px 8px; border-top: 1px solid var(--app-border); border-right: 1px solid var(--app-border); display: flex; flex-direction: column; gap: 2px; }
.pcal-cell {
position: relative;
display: flex; flex-direction: column; gap: 2px;
min-width: 0; min-height: 56px; padding: 6px 8px;
border-top: 1px solid var(--app-border); border-right: 1px solid var(--app-border);
background: var(--app-surface);
}
.pcal-cell:nth-child(7) { border-right: 0; }
.pcal-cell b { font-size: .82rem; color: var(--app-text); }
.pcal-cell span { font-size: .72rem; color: var(--app-accent); font-weight: 700; }
.pcal-cell.out { opacity: .4; }
.pcal-cell.today { background: var(--app-accent-soft); box-shadow: inset 0 0 0 2px var(--app-accent); }
.pcal-cell b { font-size: .8125rem; color: var(--app-text); }
.pcal-cell.out { background: var(--app-surface-2); }
.pcal-cell.out b, .pcal-cell.out span, .pcal-cell.out .pcal-count { color: var(--app-text-3); }
.pcal-cell.today { background: var(--app-accent-soft); box-shadow: inset 0 0 0 1.5px var(--app-accent); }
.pcal-today {
position: absolute; top: 5px; right: 6px;
font-style: normal; font-size: .5625rem; font-weight: 700;
color: var(--app-accent-ink); letter-spacing: 0;
}
.pcal-cell.has-block {
background: color-mix(in srgb, var(--block) var(--block-pct, 18%), var(--app-surface));
border-color: color-mix(in srgb, var(--block) 22%, var(--app-border));
}
.pcal-block {
display: inline-flex; align-items: center; gap: 4px; align-self: flex-start;
padding: 1px 6px; border-radius: 999px;
font-size: .6875rem; font-weight: 700; white-space: nowrap;
color: var(--app-text);
background: color-mix(in srgb, var(--block) 16%, var(--app-surface));
border: 1px solid color-mix(in srgb, var(--block) 28%, transparent);
}
.pcal-block::before { content: ""; width: 5px; height: 5px; border-radius: 50%; background: var(--block); }
.pcal-dot { width: 7px; height: 7px; margin-top: auto; border-radius: 50%; border: 1.5px solid var(--app-accent); }
.pcal-dot.done { background: var(--app-accent); border-color: var(--app-accent); }
.pcal-count { margin-top: auto; font-size: .75rem; font-weight: 700; color: var(--app-accent); }
@media (max-width: 575px) {
.portal-wrap { padding: 16px 10px 32px; }
.pcal-cell { min-height: 46px; padding: 4px 5px; }
.portal-wrap { padding: 16px 10px 40px; }
.portal-head h1 { font-size: 1.45rem; }
.portal-client-name { font-size: 1.3rem; }
.portal-client-status { margin-left: 0; }
.pcal-cell { min-height: 50px; padding: 4px 5px; }
.pcal-today { top: 3px; right: 4px; font-size: .5rem; }
.pcal-block { font-size: .625rem; padding: 1px 4px; }
.pcal-nav b { min-width: 74px; }
}
.tagchip, .pkg-tag {