From 8b9c347649282188355b19ec1407c616983552df Mon Sep 17 00:00:00 2001 From: chun_qiu Date: Tue, 20 Oct 2026 01:46:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(v1.10.0):=20=E9=87=8D=E5=BB=BA=20Bootstrap?= =?UTF-8?q?=20=E4=B8=BB=E9=A2=98=E5=B1=82=EF=BC=8C=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E9=81=97=E7=95=99=E7=9A=84=E6=98=8E=E6=9A=97?= =?UTF-8?q?/=E7=BC=93=E5=AD=98/=E5=B8=83=E5=B1=80=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 背景:v1.9.0 把界面迁到 Bootstrap 5 时保留了旧自研 CSS,v1.9.2 又叠加了一层 「墨绿渐变」覆盖层,形成三套令牌(--paper/--ink、prefers-color-scheme、 data-bs-theme)并存。系统偏好与 Cookie 不一致时,页面会出现半亮半暗、表单 标签/抽屉标题/订阅页签不可见、输入框变黑块等问题。 本次改动: - 样式加载顺序改为 bootstrap → app.css;删除旧纸墨令牌与 v1.9.2 覆盖层, app.css 重写为单一主题层(:root/[data-bs-theme=light] 与 [data-bs-theme=dark] 两套语义令牌)。组件只读 --app-*,解决 --ink-2 同时被当作「次级文字色」和 「卡片底色」的语义冲突。 - 主题只认 :服务端按 Cookie 直出;没有 Cookie 时由 内联脚本在首屏按系统偏好补齐,修好「跟随系统」并避免闪白。大屏同理。 - 修复 Bootstrap 覆盖自研设计的问题:正文 14px + 中文字体栈、22/16/14px 标题、 按钮/输入框/焦点环/卡片回到设计值;普通按钮新增 .btn-soft,不再变透明文字。 - 布局修复:客户/套餐表单的 .grid-2 不再塞 3 个字段;包详情页把筛选独立成 GET 表单(在搜索框回车不再误触批量开通);客户详情操作区与 meta 重排; 抽屉时钟改为固定 46px 并用它对齐抽屉头的 sticky 偏移;追加面板在抽屉里 两列月历、不再 1100px 竖排;手机端日历数字不再换行溢出,移动端表格隐藏 低优先级列,不再被裁切。 - 大屏:KPI/面板去掉 Bootstrap utility,统一由 screen.css 控制;TOP/今日待打 改为固定列宽 + grid-column 对齐;浅色主题的链接、热力、图例对比度补齐; 热力格不再整体降透明度;当日浮层可滚动并支持点击/键盘打开。 - 配色与可读性:普通文本对比度全部达标(截图 lint 由 65 处降到 0);日历 外月日期改为浅底深字,不再整格 opacity;archived 行改为中性底色而非降透明。 - 缓存:新增内嵌静态资源内容指纹 AssetVersion(12 位 sha256),所有 /static 链接带 ?v=;带指纹的响应 public, max-age=31536000, immutable,不带指纹的 回源 no-cache;HTML 继续 no-store。改样式重新编译即自动破缓存,不再依赖 手改版本号(仍保留 v1.10.0 作为产品版本)。 - 认证页重做:登录/初始化页改用 .auth 卡片(品牌、主题切换、绿色焦点环), 去掉 min-height:100vh 叠加 main padding 造成的 40px 纵向滚动。 - JS:修 toISOString 计算自然日差一天的时区 bug;主题切换读 data-bs-theme 并同步 aria-pressed;全选支持半选;大屏切主题同时更新 data-bs-theme; 提示条改为固定定位、淡出不再顶动页面。 - README 更新主题与缓存说明;版本号 1.9.5 → 1.10.0。 验证:gofmt / go vet / go test 全绿;1440、1100、390 三档,明暗两套,工作台/ 客户/套餐/订阅/设置/日志/大屏/登录全部截图走查,无横向溢出与控制台错误。 --- README.md | 6 +- internal/web/handlers_screen.go | 54 +- internal/web/server.go | 75 +- internal/web/static/app.css | 2337 ++++++++--------- internal/web/static/app.js | 124 +- internal/web/static/screen.css | 587 ++--- internal/web/static/screen.js | 64 +- internal/web/templates/_layout.html | 60 +- internal/web/templates/_partials.html | 24 +- internal/web/templates/_screen_layout.html | 13 +- internal/web/templates/pages/calendar.html | 36 +- .../web/templates/pages/customer_detail.html | 52 +- .../web/templates/pages/customer_form.html | 31 +- internal/web/templates/pages/customers.html | 18 +- internal/web/templates/pages/login.html | 39 +- internal/web/templates/pages/logs.html | 10 +- .../web/templates/pages/package_detail.html | 25 +- .../web/templates/pages/package_form.html | 18 +- internal/web/templates/pages/packages.html | 12 +- internal/web/templates/pages/screen.html | 28 +- internal/web/templates/pages/settings.html | 26 +- internal/web/templates/pages/setup.html | 51 +- .../web/templates/pages/subscriptions.html | 26 +- main.go | 2 +- 24 files changed, 1733 insertions(+), 1985 deletions(-) diff --git a/README.md b/README.md index eb4e6e9..2da9a03 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ ┌───────────────────────────────────────────────┐ │ 代 打 台 · 代打管理平台 │ └───────────────────────────────────────────────┘ - 版本 1.1.0 配置文件 daili.ini + 版本 1.10.0 配置文件 daili.ini 数据库 D:\代打台\daili.db 访问地址: 本机 http://127.0.0.1:17777 @@ -149,8 +149,8 @@ - 日历格左侧竖条 = 当天涉及的所有打手颜色(几种人几条,等分显示); - 「当日待完成」每条前面有打手色条 + 名字,未指派的显示灰色「未指派」; - 大屏的今日待打、悬停浮层、热力格竖条同样带颜色。 -- **主题**:顶栏右上角 ☾ / ☀ 切换亮 / 暗,偏好记在浏览器里;不选时跟随系统。 -- **升级不缓存旧界面**:CSS/JS 链接带版本号(`app.css?v=1.6.0`),换新版程序后浏览器会自动取新文件,不用手动清缓存。 +- **主题**:顶栏右上角 ☾ / ☀ 切换亮 / 暗,偏好记在浏览器里;没选过时由页面在首屏按系统偏好自动决定。整套界面只认 `` 一个开关,不会出现「半亮半暗」。 +- **升级不缓存旧界面**:CSS/JS 链接带**内容指纹**(`app.css?v=<12 位哈希>`)。资源可以长缓存(一年、immutable),但样式或脚本一改、重新编译,URL 就会变,浏览器自动取新文件,不用手动清缓存;HTML 仍然 `no-store`。 --- diff --git a/internal/web/handlers_screen.go b/internal/web/handlers_screen.go index 8064879..74aa335 100644 --- a/internal/web/handlers_screen.go +++ b/internal/web/handlers_screen.go @@ -30,20 +30,21 @@ type screenTask struct { } type screenData struct { - Stats *store.Stats - MonthTitle string - Year, Month int - Weeks [][]screenDay - Weekdays []string - Top []SubView - DueToday []SubView - TodayList []screenTask - Recent []store.Adjustment - TodayTasks int - TodayDone int - TodayUrgent int - Refresh int - Version string + Stats *store.Stats + MonthTitle string + Year, Month int + Weeks [][]screenDay + Weekdays []string + Top []SubView + DueToday []SubView + TodayList []screenTask + Recent []store.Adjustment + TodayTasks int + TodayDone int + TodayUrgent int + Refresh int + Version string + AssetVersion string HeatMode string // task = 每日待完成;due = 到期 Tag string @@ -91,18 +92,19 @@ func (s *Server) handleScreen(w http.ResponseWriter, r *http.Request) { return } data := &screenData{ - Stats: stats, - MonthTitle: fmt.Sprintf("%d 年 %d 月", today.Year(), int(today.Month())), - Year: today.Year(), - Month: int(today.Month()), - Weekdays: []string{"一", "二", "三", "四", "五", "六", "日"}, - Top: subViews(stats.Top, today, warn), - Refresh: s.screenRefresh(), - Version: s.cfg.Version, - HeatMode: heatMode, - PkgID: pkgID, - Tag: tag, - FilterQuery: "pkg=" + itoa(pkgID), + Stats: stats, + MonthTitle: fmt.Sprintf("%d 年 %d 月", today.Year(), int(today.Month())), + Year: today.Year(), + Month: int(today.Month()), + Weekdays: []string{"一", "二", "三", "四", "五", "六", "日"}, + Top: subViews(stats.Top, today, warn), + Refresh: s.screenRefresh(), + Version: s.cfg.Version, + AssetVersion: s.assetVersion, + HeatMode: heatMode, + PkgID: pkgID, + Tag: tag, + FilterQuery: "pkg=" + itoa(pkgID), } // 今日到期(可按套餐过滤) diff --git a/internal/web/server.go b/internal/web/server.go index d534f7a..c95ab0d 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -5,6 +5,7 @@ import ( "bytes" "context" "crypto/rand" + "crypto/sha256" "embed" "encoding/hex" "fmt" @@ -50,11 +51,12 @@ type Config struct { // Server 是应用服务。 type Server struct { - db *store.DB - cfg Config - tpl *renderer - handler http.Handler - started time.Time + db *store.DB + cfg Config + tpl *renderer + handler http.Handler + started time.Time + assetVersion string } type ctxKey int @@ -67,11 +69,33 @@ func New(db *store.DB, cfg Config) (*Server, error) { if err != nil { return nil, err } - s := &Server{db: db, cfg: cfg, tpl: r, started: time.Now()} + s := &Server{db: db, cfg: cfg, tpl: r, started: time.Now(), assetVersion: staticAssetVersion()} s.handler = s.routes() return s, nil } +// staticAssetVersion 计算内嵌静态资源的内容指纹。 +// 资源 URL 带上它之后可以放心长缓存;改了 CSS/JS 重新编译就会自动换 URL。 +func staticAssetVersion() string { + h := sha256.New() + if err := fs.WalkDir(staticFS, "static", func(path string, d fs.DirEntry, err error) error { + if err != nil || d.IsDir() { + return nil + } + data, rerr := fs.ReadFile(staticFS, path) + if rerr != nil { + return nil + } + h.Write([]byte(path)) + h.Write([]byte{0}) + h.Write(data) + return nil + }); err != nil { + return "dev" + } + return hex.EncodeToString(h.Sum(nil))[:12] +} + // Handler 返回 HTTP 处理器。 func (s *Server) Handler() http.Handler { return s.handler } @@ -82,7 +106,7 @@ func (s *Server) routes() http.Handler { if err != nil { panic(err) } - mux.Handle("GET /static/", cacheControl(http.StripPrefix("/static/", http.FileServerFS(sub)))) + mux.Handle("GET /static/", s.staticFiles(http.StripPrefix("/static/", http.FileServerFS(sub)))) mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") _, _ = w.Write([]byte("ok")) @@ -153,9 +177,16 @@ func (s *Server) routes() http.Handler { return s.noCacheHTML(mux) } -func cacheControl(h http.Handler) http.Handler { +// staticFiles 给内嵌静态资源加缓存策略: +// 只有带当前内容指纹(?v=)的 URL 才允许一年不可变缓存,其它一律回源。 +func (s *Server) staticFiles(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Cache-Control", "public, max-age=300") + w.Header().Set("X-Content-Type-Options", "nosniff") + if r.URL.Query().Get("v") == s.assetVersion { + w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") + } else { + w.Header().Set("Cache-Control", "no-cache") + } h.ServeHTTP(w, r) }) } @@ -337,14 +368,16 @@ type page struct { TodayStr string WarnDays int Version string - Query string - Next string - DueToday int - Due3 int - Due7 int - Addr string - Theme string // "" = 跟随系统;light / dark - D any + // AssetVersion 是内嵌静态资源的内容指纹,用于 /static/*.css?v=... 的长缓存 + AssetVersion string + Query string + Next string + DueToday int + Due3 int + Due7 int + Addr string + Theme string // "" = 跟随系统;light / dark + D any } func (s *Server) warnDays() int { @@ -374,9 +407,11 @@ func (s *Server) newPage(r *http.Request, title, nav string) *page { TodayStr: domain.Date(today), WarnDays: s.warnDays(), Version: s.cfg.Version, - Query: r.URL.Query().Get("q"), - Next: r.URL.Query().Get("next"), - Theme: themeFromRequest(r), + + AssetVersion: s.assetVersion, + Query: r.URL.Query().Get("q"), + Next: r.URL.Query().Get("next"), + Theme: themeFromRequest(r), } if u != nil { if due, due3, due7, err := s.db.Badges(today); err == nil { diff --git a/internal/web/static/app.css b/internal/web/static/app.css index 36fe8e4..13a9467 100644 --- a/internal/web/static/app.css +++ b/internal/web/static/app.css @@ -1,1444 +1,1211 @@ /* ============================================================ - 代打台 · 管理端 - 主题::root 为亮色;暗色由系统偏好或 [data-theme] 覆盖 - 设计语汇:纸感底色 + 墨色文字 + 松墨绿主色;色签(ticket)是唯一强装饰 + 代打台 · 管理端样式(v1.10) + ------------------------------------------------------------ + Bootstrap 5.3 负责栅格/重置/组件行为,本文件只做「主题 + 业务组件」。 + 加载顺序:bootstrap.min.css 在前,app.css 在后(见 _layout.html)。 + 主题唯一来源:。 + 设计语汇:纸感中性底 + 松墨绿强调 + 票券式色签;不使用渐变文字。 ============================================================ */ -:root { - --paper: #f7f6f2; - --surface: #ffffff; - --surface-2: #fcfcfa; - --ink: #1a1c21; - --ink-2: #43474f; - --muted: #7a7e86; - --line: #e3e0d6; - --line-2: #efede6; - --accent: #2f5d50; - --accent-ink: #22453b; - --accent-soft: #e7efea; - --danger: #b4231f; - --danger-soft: #fbeceb; - --warn: #a85e12; - --warn-soft: #fbf1e3; - --info: #41556b; - --info-soft: #eaeef2; - --hover: #f2f7f4; - --shadow: 0 12px 32px -26px rgba(26, 28, 33, 0.8); - --radius: 3px; - --mono: "Bahnschrift", "Consolas", "SF Mono", ui-monospace, monospace; - --sans: "Microsoft YaHei UI", "PingFang SC", "Hiragino Sans GB", system-ui, -apple-system, "Segoe UI", sans-serif; +/* ---------- 1. 主题令牌 ---------- */ + +:root, +[data-bs-theme="light"] { + --app-bg: #f5f6f2; + --app-surface: #ffffff; + --app-surface-2: #f8faf7; + --app-surface-3: #eef3ee; + --app-border: #e2e7df; + --app-border-strong: #cbd5cc; + --app-text: #16211c; + --app-text-2: #45564d; + --app-text-3: #55675d; + --app-accent: #1f6b52; + --app-accent-strong: #175844; + --app-accent-solid: #1f6b52; + --app-accent-solid-hover: #175844; + --app-accent-soft: #e6f1eb; + --app-accent-ink: #124434; + --app-danger: #b3261e; + --app-danger-soft: #fbeae8; + --app-warn: #8a5200; + --app-warn-soft: #fdf3e3; + --app-info: #3c556e; + --app-info-soft: #eaf0f6; + --app-ring: rgba(31, 107, 82, 0.18); + --app-shadow-sm: 0 1px 2px rgba(16, 32, 24, 0.06); + --app-shadow: 0 18px 40px -28px rgba(16, 32, 24, 0.45); + --app-radius: 12px; + --app-radius-sm: 8px; + --app-sans: "Microsoft YaHei UI", "PingFang SC", "Hiragino Sans GB", "Noto Sans SC", + system-ui, -apple-system, "Segoe UI", sans-serif; + --app-mono: "Cascadia Mono", "Consolas", "SFMono-Regular", ui-monospace, monospace; + --app-nav-h: 58px; + + /* Bootstrap 基色映射 */ + --bs-body-bg: var(--app-bg); + --bs-body-color: var(--app-text); + --bs-body-font-family: var(--app-sans); + --bs-body-font-size: 0.875rem; + --bs-body-line-height: 1.55; + --bs-border-color: var(--app-border); + --bs-border-color-translucent: rgba(22, 33, 28, 0.12); + --bs-secondary-color: var(--app-text-3); + --bs-tertiary-bg: var(--app-surface-2); + --bs-secondary-bg: var(--app-surface-3); + --bs-emphasis-color: var(--app-text); + --bs-heading-color: var(--app-text); + --bs-link-color: var(--app-accent); + --bs-link-hover-color: var(--app-accent-strong); + --bs-primary: #1f6b52; + --bs-primary-rgb: 31, 107, 82; + --bs-success: #1f6b52; + --bs-success-rgb: 31, 107, 82; + --bs-border-radius: var(--app-radius); + --bs-border-radius-sm: var(--app-radius-sm); + --bs-border-radius-lg: 16px; + --bs-focus-ring-color: var(--app-ring); + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833,37,41,0.7%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); } -/* 暗色令牌:系统偏好 + 手动选择共用 */ -@media (prefers-color-scheme: dark) { - :root:not([data-theme="light"]) { - --paper: #111317; - --surface: #181b20; - --surface-2: #1e2228; - --ink: #e8eaee; - --ink-2: #c2c7cf; - --muted: #8b929c; - --line: #2b3038; - --line-2: #23272e; - --accent: #6bb8a3; - --accent-ink: #8fd0bd; - --accent-soft: rgba(107, 184, 163, 0.16); - --danger: #e8776f; - --danger-soft: rgba(232, 119, 111, 0.16); - --warn: #e0a862; - --warn-soft: rgba(224, 168, 98, 0.16); - --info: #93a7bd; - --info-soft: rgba(147, 167, 189, 0.16); - --hover: #1f242b; - --shadow: 0 12px 32px -22px rgba(0, 0, 0, 0.9); - } +[data-bs-theme="dark"] { + --app-bg: #0c1310; + --app-surface: #131b17; + --app-surface-2: #17211c; + --app-surface-3: #1e2b24; + --app-border: #26362e; + --app-border-strong: #35493e; + --app-text: #e6f0ea; + --app-text-2: #b9ccc1; + --app-text-3: #8ea69a; + --app-accent: #5cc4a3; + --app-accent-strong: #7bd7b8; + --app-accent-solid: #2b7d61; + --app-accent-solid-hover: #339374; + --app-accent-soft: rgba(92, 196, 163, 0.14); + --app-accent-ink: #a9ecd6; + --app-danger: #f08a82; + --app-danger-soft: rgba(240, 138, 130, 0.14); + --app-warn: #e0b072; + --app-warn-soft: rgba(224, 176, 114, 0.14); + --app-info: #9db4cc; + --app-info-soft: rgba(157, 180, 204, 0.14); + --app-ring: rgba(92, 196, 163, 0.24); + --app-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4); + --app-shadow: 0 24px 48px -28px rgba(0, 0, 0, 0.8); + --app-nav-h: 58px; + + --bs-body-bg: var(--app-bg); + --bs-body-color: var(--app-text); + --bs-body-font-family: var(--app-sans); + --bs-body-font-size: 0.875rem; + --bs-body-line-height: 1.55; + --bs-border-color: var(--app-border); + --bs-border-color-translucent: rgba(230, 240, 234, 0.12); + --bs-secondary-color: var(--app-text-3); + --bs-tertiary-bg: var(--app-surface-2); + --bs-secondary-bg: var(--app-surface-3); + --bs-emphasis-color: var(--app-text); + --bs-heading-color: var(--app-text); + --bs-link-color: var(--app-accent); + --bs-link-hover-color: var(--app-accent-strong); + --bs-primary: #2b7d61; + --bs-primary-rgb: 43, 125, 97; + --bs-success: #2b7d61; + --bs-success-rgb: 43, 125, 97; + --bs-border-radius: var(--app-radius); + --bs-border-radius-sm: var(--app-radius-sm); + --bs-border-radius-lg: 16px; + --bs-focus-ring-color: var(--app-ring); + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28230,240,234,0.8%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); } -:root[data-theme="dark"] { - --paper: #111317; - --surface: #181b20; - --surface-2: #1e2228; - --ink: #e8eaee; - --ink-2: #c2c7cf; - --muted: #8b929c; - --line: #2b3038; - --line-2: #23272e; - --accent: #6bb8a3; - --accent-ink: #8fd0bd; - --accent-soft: rgba(107, 184, 163, 0.16); - --danger: #e8776f; - --danger-soft: rgba(232, 119, 111, 0.16); - --warn: #e0a862; - --warn-soft: rgba(224, 168, 98, 0.16); - --info: #93a7bd; - --info-soft: rgba(147, 167, 189, 0.16); - --hover: #1f242b; - --shadow: 0 12px 32px -22px rgba(0, 0, 0, 0.9); -} +/* ---------- 2. 基础 ---------- */ * { box-sizing: border-box; } [hidden] { display: none !important; } -html, body { margin: 0; padding: 0; } +html { -webkit-text-size-adjust: 100%; } body { - background: var(--paper); - color: var(--ink); - font-family: var(--sans); - font-size: 14px; + min-height: 100vh; + background-color: var(--app-bg); + background-image: + radial-gradient(1100px 420px at 6% -12%, color-mix(in srgb, var(--app-accent) 9%, transparent), transparent 62%), + radial-gradient(900px 380px at 100% 0%, color-mix(in srgb, var(--app-accent) 5%, transparent), transparent 58%); + background-attachment: fixed; + color: var(--app-text); + font-family: var(--app-sans); + font-size: 0.875rem; line-height: 1.55; -webkit-font-smoothing: antialiased; } -a { color: var(--accent); text-decoration: none; } -a:hover { text-decoration: underline; text-underline-offset: 2px; } +h1, h2, h3, h4, h5, h6 { margin: 0; font-weight: 700; letter-spacing: -0.01em; color: var(--app-text); } +h1 { font-size: 1.375rem; } +h2 { font-size: 1rem; } +h3 { font-size: 0.875rem; } +h4 { font-size: 0.8125rem; } -h1, h2, h3, h4 { margin: 0; font-weight: 700; letter-spacing: -0.01em; } -h1 { font-size: 22px; } -h2 { font-size: 15px; } -h3 { font-size: 13px; } -h4 { font-size: 13px; } +a { color: var(--app-accent); text-decoration: none; } +a:hover { color: var(--app-accent-strong); text-decoration: underline; text-underline-offset: 2px; } -.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; letter-spacing: 0.02em; } -.small { font-size: 12px; } -.muted { color: var(--muted); } +::selection { background: color-mix(in srgb, var(--app-accent) 28%, transparent); } + +:focus-visible { outline: 2px solid var(--app-accent); outline-offset: 2px; } + +.mono { font-family: var(--app-mono); font-variant-numeric: tabular-nums; letter-spacing: 0.01em; } +.small { font-size: 0.75rem; } +.muted { color: var(--app-text-3); } .right { text-align: right; } -.text-urgent { color: var(--danger); } +.text-urgent { color: var(--app-danger); } -/* ---------- 顶栏 ---------- */ +* { scrollbar-width: thin; scrollbar-color: var(--app-border-strong) transparent; } +::-webkit-scrollbar { width: 10px; height: 10px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--app-border-strong); border-radius: 999px; border: 3px solid transparent; background-clip: padding-box; } +::-webkit-scrollbar-thumb:hover { background: var(--app-text-3); background-clip: padding-box; } -.topbar { - position: sticky; top: 0; z-index: 40; - display: flex; align-items: center; gap: 14px; - padding: 0 20px; height: 56px; - background: color-mix(in srgb, var(--paper) 92%, transparent); - backdrop-filter: blur(6px); - border-bottom: 1px solid var(--line); -} +/* ---------- 3. 布局骨架 ---------- */ -.brand { display: flex; align-items: center; gap: 8px; color: var(--ink); text-decoration: none; } -.brand:hover { text-decoration: none; } -.brand-mark { - display: grid; place-items: center; - width: 26px; height: 26px; - background: var(--accent); color: var(--paper); - font-size: 14px; font-weight: 700; - border-radius: 2px; - box-shadow: 2px 2px 0 color-mix(in srgb, var(--accent) 22%, transparent); -} -.brand-name { font-weight: 700; letter-spacing: 0.06em; } +.app-main { padding: 20px 24px 44px; } +.auth-main { padding: 0; } -.search { position: relative; flex: 1 1 260px; max-width: 380px; } -.search input { - width: 100%; height: 34px; - padding: 0 34px 0 12px; - border: 1px solid var(--line); - border-radius: var(--radius); - background: var(--surface); - font: inherit; color: var(--ink); -} -.search input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); } -.search kbd { - position: absolute; right: 8px; top: 7px; - font-family: var(--mono); font-size: 11px; - color: var(--muted); - border: 1px solid var(--line); border-bottom-width: 2px; - border-radius: 3px; padding: 0 5px; background: var(--paper); -} - -.nav { display: flex; gap: 2px; } -.nav a { - padding: 6px 10px; border-radius: var(--radius); - color: var(--ink-2); font-size: 13px; -} -.nav a:hover { background: var(--surface); text-decoration: none; } -.nav a.on { background: var(--accent); color: var(--paper); } -.nav a.nav-screen { color: var(--accent); } - -.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 8px; } - -.pill { - display: inline-flex; align-items: center; gap: 5px; - height: 28px; padding: 0 10px; - border: 1px solid var(--line); border-radius: 999px; - background: var(--surface); - font-size: 12px; color: var(--ink-2); white-space: nowrap; -} -.pill b { font-family: var(--mono); font-size: 13px; } -.pill:hover { text-decoration: none; border-color: var(--accent); } -.pill-due b { color: var(--danger); } -.pill-warn b { color: var(--warn); } - -.theme-toggle { - display: inline-grid; place-items: center; - width: 30px; height: 28px; padding: 0; - border: 1px solid var(--line); border-radius: 999px; - background: var(--surface); color: var(--ink-2); - cursor: pointer; font-size: 14px; line-height: 1; -} -.theme-toggle:hover { border-color: var(--accent); color: var(--accent); } -.theme-toggle .ic-sun { display: none; } -:root[data-theme="dark"] .theme-toggle .ic-sun { display: inline; } -:root[data-theme="dark"] .theme-toggle .ic-moon { display: none; } -@media (prefers-color-scheme: dark) { - :root:not([data-theme="light"]) .theme-toggle .ic-sun { display: inline; } - :root:not([data-theme="light"]) .theme-toggle .ic-moon { display: none; } -} - -.auth-theme { position: fixed; top: 14px; right: 14px; z-index: 50; background: var(--surface); } - -.me { display: flex; align-items: center; gap: 8px; font-size: 13px; } -.me a { color: var(--ink-2); } -.link-btn { - border: 0; background: none; padding: 0; cursor: pointer; - color: var(--muted); font: inherit; font-size: 12px; -} -.link-btn:hover { color: var(--danger); } - -/* ---------- 提示条 ---------- */ - -.flash { - position: fixed; z-index: 90; - left: 50%; top: 68px; transform: translateX(-50%); - max-width: min(680px, calc(100vw - 32px)); - padding: 9px 16px; - background: var(--accent); color: var(--paper); - border-radius: var(--radius); - box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18); - font-size: 13px; - animation: flash-in 0.22s ease-out; -} -.flash-error { background: var(--danger); } -.flash.hide { opacity: 0; transition: opacity 0.4s ease; } -@keyframes flash-in { from { opacity: 0; transform: translate(-50%, -6px); } } - -/* ---------- 布局 ---------- */ - -main { padding: 0 20px 40px; } -main.with-top { padding-top: 22px; } - -.page { max-width: 1280px; margin: 0 auto; } -.page.narrow { max-width: 860px; } +.page { max-width: 1320px; margin: 0 auto; } +.page.narrow { max-width: 880px; } .page-head { display: flex; align-items: flex-end; justify-content: space-between; - gap: 16px; flex-wrap: wrap; margin-bottom: 16px; + gap: 16px; flex-wrap: wrap; margin-bottom: 18px; } -.page-head p { margin: 4px 0 0; font-size: 12px; } -.page-actions { display: flex; gap: 8px; flex-wrap: wrap; } +.page-head h1 { margin-bottom: 2px; } +.page-head p { margin: 0; font-size: 0.75rem; color: var(--app-text-3); } +.page-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; } -.foot { - max-width: 1280px; margin: 0 auto; padding: 18px 20px 28px; - font-size: 12px; color: var(--muted); - border-top: 1px solid var(--line); +.section { margin-top: 24px; } +.section-head { + display: flex; align-items: baseline; justify-content: space-between; + gap: 12px; flex-wrap: wrap; margin-bottom: 10px; } +.section-head h2 { font-size: 0.9375rem; } +.section-head .muted, .section-head .small { font-size: 0.75rem; } +.section.card h2 { margin-bottom: 14px; } + +.app-foot { + max-width: 1320px; margin: 0 auto; padding: 18px 24px 28px; + font-size: 0.75rem; color: var(--app-text-3); + border-top: 1px solid var(--app-border); + display: flex; gap: 8px 14px; flex-wrap: wrap; +} +.app-foot a { color: var(--app-text-3); } +.app-foot a:hover { color: var(--app-accent); } +.app-foot .foot-version { font-family: var(--app-mono); } + +/* ---------- 4. 顶栏 ---------- */ + +.app-navbar { + min-height: var(--app-nav-h); + padding-top: 0.35rem; padding-bottom: 0.35rem; + background: color-mix(in srgb, var(--app-surface) 86%, transparent); + border-bottom: 1px solid var(--app-border); + backdrop-filter: blur(12px); + z-index: 1030; +} +.app-navbar .container-fluid { gap: 0.5rem; } +.app-navbar .navbar-collapse { gap: 0.5rem; } + +.navbar-brand { + display: inline-flex; align-items: center; gap: 8px; + color: var(--app-text) !important; + font-weight: 800; font-size: 1rem; letter-spacing: 0.04em; + margin-right: 0.5rem; padding: 0; +} +.navbar-brand:hover { color: var(--app-text) !important; text-decoration: none; } +.brand-mark { + width: 28px; height: 28px; flex: 0 0 auto; + display: inline-grid; place-items: center; + background: var(--app-accent-solid); color: #fff; + border-radius: 9px; font-size: 0.875rem; font-weight: 800; + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.14); +} + +.app-navbar .navbar-nav { gap: 2px; } +.app-navbar .nav-link { + padding: 0.32rem 0.7rem; border-radius: var(--app-radius-sm); + color: var(--app-text-2); font-size: 0.8125rem; font-weight: 500; +} +.app-navbar .nav-link:hover { background: var(--app-surface-3); color: var(--app-text); } +.app-navbar .nav-link.active { + background: var(--app-accent-soft); color: var(--app-accent-ink); font-weight: 600; +} +.app-navbar .nav-link.nav-screen { color: var(--app-accent); } + +.nav-search { position: relative; width: 240px; flex: 0 1 auto; margin: 0 0.5rem; } +.nav-search .form-control { height: 32px; padding-right: 30px; } +.nav-search kbd { + position: absolute; right: 8px; top: 50%; transform: translateY(-50%); + padding: 0 5px; border: 1px solid var(--app-border); border-radius: 5px; + background: var(--app-surface-2); color: var(--app-text-3); + font-family: var(--app-mono); font-size: 0.625rem; line-height: 1.5; + pointer-events: none; +} + +.nav-side { display: flex; align-items: center; gap: 6px; margin-left: auto; } + +.badge-due, .badge-warn, .badge-muted { + display: inline-flex; align-items: center; gap: 5px; + height: 26px; padding: 0 0.55rem; + border-radius: 999px; border: 1px solid transparent; + font-size: 0.6875rem; font-weight: 600; white-space: nowrap; + text-decoration: none; +} +.badge-due { background: var(--app-danger-soft); color: var(--app-danger); border-color: color-mix(in srgb, var(--app-danger) 26%, transparent); } +.badge-warn { background: var(--app-warn-soft); color: var(--app-warn); border-color: color-mix(in srgb, var(--app-warn) 26%, transparent); } +.badge-muted { background: var(--app-surface-2); color: var(--app-text-2); border-color: var(--app-border); } +.badge-due b, .badge-warn b, .badge-muted b { font-family: var(--app-mono); font-size: 0.75rem; } +.badge-due:hover, .badge-warn:hover, .badge-muted:hover { text-decoration: none; filter: brightness(0.98); } + +.theme-toggle { + width: 30px; height: 30px; flex: 0 0 auto; + display: inline-grid; place-items: center; padding: 0; + border: 1px solid var(--app-border); border-radius: 999px; + background: var(--app-surface); color: var(--app-text-2); + font-size: 0.875rem; line-height: 1; cursor: pointer; +} +.theme-toggle:hover { border-color: var(--app-accent); color: var(--app-accent); } +.theme-toggle .ic-sun { display: none; } +[data-bs-theme="dark"] .theme-toggle .ic-sun { display: inline; } +[data-bs-theme="dark"] .theme-toggle .ic-moon { display: none; } + +.navbar-toggler { border-color: var(--app-border); padding: 0.3rem 0.5rem; } +.navbar-toggler:focus { box-shadow: 0 0 0 3px var(--app-ring); } +.navbar-toggler-icon { width: 1.1em; height: 1.1em; } + +.dropdown-menu { + --bs-dropdown-bg: var(--app-surface); + --bs-dropdown-border-color: var(--app-border); + --bs-dropdown-border-radius: var(--app-radius-sm); + --bs-dropdown-link-color: var(--app-text-2); + --bs-dropdown-link-hover-bg: var(--app-surface-3); + --bs-dropdown-link-hover-color: var(--app-text); + --bs-dropdown-link-active-bg: var(--app-accent-soft); + --bs-dropdown-link-active-color: var(--app-accent-ink); + padding: 6px; box-shadow: var(--app-shadow); + font-size: 0.8125rem; +} +.dropdown-item { border-radius: 6px; padding: 0.4rem 0.6rem; } +.dropdown-divider { border-color: var(--app-border); margin: 4px 0; } + +/* 登录页右上角的主题按钮 */ +.auth-theme { position: fixed; top: 16px; right: 16px; z-index: 20; background: var(--app-surface); box-shadow: var(--app-shadow-sm); } + +/* ---------- 5. 提示条 ---------- */ + +.flash { + position: fixed; z-index: 1080; left: 50%; top: 74px; + transform: translateX(-50%); + display: flex; align-items: center; gap: 8px; + max-width: min(560px, calc(100vw - 32px)); + padding: 10px 16px; + background: var(--app-accent-solid); color: #fff; + border-radius: 12px; font-size: 0.8125rem; font-weight: 600; + box-shadow: 0 16px 36px -18px rgba(0, 0, 0, 0.5); + transition: opacity 0.35s ease, transform 0.35s ease; +} +.flash::before { content: "✓"; font-weight: 800; opacity: 0.9; } +.flash.hide { opacity: 0; transform: translate(-50%, -8px); } .notice { - margin: 0 0 14px; padding: 10px 14px; - background: var(--warn-soft); border-left: 3px solid var(--warn); - font-size: 13px; border-radius: 0 var(--radius) var(--radius) 0; + margin: 0 auto 16px; max-width: 1320px; + padding: 10px 14px; + background: var(--app-warn-soft); color: var(--app-warn); + border-left: 3px solid var(--app-warn); + border-radius: 0 var(--app-radius-sm) var(--app-radius-sm) 0; + font-size: 0.8125rem; } -.notice.note { background: var(--accent-soft); border-left-color: var(--accent); white-space: pre-wrap; } +.notice.note { background: var(--app-accent-soft); color: var(--app-accent-ink); border-left-color: var(--app-accent); white-space: pre-wrap; } +.notice-danger { background: var(--app-danger-soft); color: var(--app-danger); border-left-color: var(--app-danger); } +.notice a { color: inherit; text-decoration: underline; } -/* ---------- 按钮与表单 ---------- */ +/* ---------- 6. 按钮 ---------- */ .btn { - display: inline-flex; align-items: center; justify-content: center; gap: 6px; - height: 34px; padding: 0 14px; - border: 1px solid var(--line); border-radius: var(--radius); - background: var(--surface); color: var(--ink); - font: inherit; font-size: 13px; cursor: pointer; - white-space: nowrap; text-decoration: none; + --bs-btn-font-size: 0.8125rem; + --bs-btn-font-weight: 600; + --bs-btn-padding-y: 0.4rem; + --bs-btn-padding-x: 0.85rem; + --bs-btn-border-radius: var(--app-radius-sm); + --bs-btn-disabled-opacity: 0.45; + white-space: nowrap; } -.btn:hover { border-color: var(--ink-2); text-decoration: none; } -.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } -.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--paper); } -.btn-primary:hover { background: var(--accent-ink); border-color: var(--accent-ink); } -.btn-ghost { background: transparent; border-color: transparent; color: var(--ink-2); } -.btn-ghost:hover { background: var(--surface); border-color: var(--line); } -.btn-urgent { background: var(--danger-soft); border-color: var(--danger); color: var(--danger); } -.btn-sm { height: 28px; padding: 0 10px; font-size: 12px; } -.btn-square { width: 34px; padding: 0; font-size: 18px; line-height: 1; } +.btn-sm { + --bs-btn-font-size: 0.75rem; + --bs-btn-padding-y: 0.26rem; + --bs-btn-padding-x: 0.6rem; + --bs-btn-border-radius: 7px; +} +.btn-square { width: 34px; padding-inline: 0; font-size: 1.125rem; line-height: 1; } .btn-block { width: 100%; } -.btn.disabled { opacity: 0.4; pointer-events: none; } +.btn.disabled, .btn:disabled { pointer-events: none; } -form { margin: 0; } +.btn-soft { + --bs-btn-color: var(--app-text); + --bs-btn-bg: var(--app-surface); + --bs-btn-border-color: var(--app-border); + --bs-btn-hover-color: var(--app-text); + --bs-btn-hover-bg: var(--app-surface-3); + --bs-btn-hover-border-color: var(--app-border-strong); + --bs-btn-active-color: var(--app-text); + --bs-btn-active-bg: var(--app-surface-3); + --bs-btn-active-border-color: var(--app-border-strong); + --bs-btn-disabled-color: var(--app-text-3); + --bs-btn-disabled-bg: var(--app-surface-2); + --bs-btn-disabled-border-color: var(--app-border); +} + +.btn-primary, .btn-success { + --bs-btn-color: #fff; + --bs-btn-bg: var(--app-accent-solid); + --bs-btn-border-color: var(--app-accent-solid); + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: var(--app-accent-solid-hover); + --bs-btn-hover-border-color: var(--app-accent-solid-hover); + --bs-btn-active-color: #fff; + --bs-btn-active-bg: var(--app-accent-solid-hover); + --bs-btn-active-border-color: var(--app-accent-solid-hover); + box-shadow: var(--app-shadow-sm); +} + +.btn-outline-secondary { + --bs-btn-color: var(--app-text-2); + --bs-btn-border-color: var(--app-border); + --bs-btn-hover-color: var(--app-text); + --bs-btn-hover-bg: var(--app-surface-3); + --bs-btn-hover-border-color: var(--app-border-strong); + --bs-btn-active-bg: var(--app-surface-3); + --bs-btn-active-border-color: var(--app-border-strong); +} + +.btn-ghost { + --bs-btn-color: var(--app-text-2); + --bs-btn-bg: transparent; + --bs-btn-border-color: transparent; + --bs-btn-hover-color: var(--app-text); + --bs-btn-hover-bg: var(--app-surface-3); + --bs-btn-hover-border-color: transparent; + --bs-btn-active-bg: var(--app-surface-3); + --bs-btn-active-border-color: transparent; +} + +.btn-urgent { + --bs-btn-color: var(--app-danger); + --bs-btn-bg: var(--app-danger-soft); + --bs-btn-border-color: color-mix(in srgb, var(--app-danger) 30%, transparent); + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: var(--app-danger); + --bs-btn-hover-border-color: var(--app-danger); +} + +.btn-link { --bs-btn-color: var(--app-accent); text-decoration: none; } + +/* ---------- 7. 表单 ---------- */ + +.form { margin: 0; } fieldset { border: 0; margin: 0; padding: 0; min-width: 0; } -.field { display: flex; flex-direction: column; gap: 5px; margin-bottom: 14px; } -.field > span { font-size: 12px; color: var(--ink-2); font-weight: 600; } -.field > span i { color: var(--danger); font-style: normal; } -.field input[type="text"], .field input[type="password"], .field input[type="number"], -.field input[type="date"], .field input[type="search"], .field select, .field textarea { - width: 100%; min-height: 34px; padding: 6px 10px; - border: 1px solid var(--line); border-radius: var(--radius); - background: var(--surface); font: inherit; color: var(--ink); +.form-control, .form-select { + background-color: var(--app-surface); + border-color: var(--app-border); + color: var(--app-text); + border-radius: var(--app-radius-sm); + font-size: 0.875rem; } -.field textarea { resize: vertical; line-height: 1.6; } +.form-control::placeholder { color: var(--app-text-3); opacity: 1; } +.form-control:focus, .form-select:focus { + background-color: var(--app-surface); + border-color: var(--app-accent); + color: var(--app-text); + box-shadow: 0 0 0 3px var(--app-ring); +} +.form-control-sm, .form-select-sm { font-size: 0.8125rem; border-radius: 7px; } +.form-select { padding-right: 2rem; } +.form-label { font-size: 0.75rem; font-weight: 600; color: var(--app-text-2); margin-bottom: 4px; } + +.field { display: flex; flex-direction: column; gap: 5px; margin-bottom: 14px; min-width: 0; } +.field > span { + display: flex; align-items: center; gap: 4px; + font-size: 0.75rem; font-weight: 600; color: var(--app-text-2); +} +.field > span i { color: var(--app-danger); font-style: normal; } +.field > small { font-size: 0.6875rem; color: var(--app-text-3); } + +.field input:not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="submit"]):not([type="button"]), +.field select, +.field textarea { + width: 100%; min-height: 36px; padding: 0.4rem 0.65rem; + border: 1px solid var(--app-border); border-radius: var(--app-radius-sm); + background: var(--app-surface); color: var(--app-text); + font: inherit; font-size: 0.875rem; + transition: border-color 0.12s ease, box-shadow 0.12s ease; +} +.field textarea { min-height: 78px; resize: vertical; line-height: 1.55; } +.field select { padding-right: 1.9rem; } .field input:focus, .field select:focus, .field textarea:focus { - outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); + outline: none; border-color: var(--app-accent); box-shadow: 0 0 0 3px var(--app-ring); +} +.field input[type="color"] { + width: 100%; height: 36px; padding: 3px; + border: 1px solid var(--app-border); border-radius: var(--app-radius-sm); + background: var(--app-surface); cursor: pointer; +} +.field-narrow { max-width: 340px; } + +.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; } +.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0 16px; } +.grid-auto { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0 16px; } +.stack { display: grid; gap: 14px; } +.stack .field { margin-bottom: 0; } + +.input-suffix { position: relative; } +.input-suffix input { padding-right: 42px; } +.input-suffix b { + position: absolute; right: 12px; top: 50%; transform: translateY(-50%); + font-size: 0.75rem; font-weight: 500; color: var(--app-text-3); } -.field input[type="color"] { width: 100%; height: 34px; padding: 2px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); cursor: pointer; } .check-line { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; margin-bottom: 14px; - border: 1px solid var(--line); border-radius: var(--radius); - background: var(--surface); cursor: pointer; + border: 1px solid var(--app-border); border-radius: var(--app-radius-sm); + background: var(--app-surface); cursor: pointer; } -.check-line input { margin-top: 3px; accent-color: var(--accent); } -.check-line span { font-size: 13px; font-weight: 600; color: var(--ink-2); } -.check-line small { display: block; font-weight: 400; color: var(--muted); font-size: 12px; margin-top: 2px; } +.check-line:hover { border-color: var(--app-border-strong); } +.check-line input { margin-top: 3px; accent-color: var(--app-accent); } +.check-line > span { font-size: 0.8125rem; font-weight: 600; color: var(--app-text); } +.check-line small { display: block; margin-top: 2px; font-size: 0.75rem; font-weight: 400; color: var(--app-text-3); line-height: 1.5; } .radios { display: grid; gap: 8px; } .radios label { display: grid; grid-template-columns: 18px 1fr; grid-template-rows: auto auto; column-gap: 10px; align-items: center; - padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius); - background: var(--surface); cursor: pointer; + padding: 10px 12px; border: 1px solid var(--app-border); border-radius: var(--app-radius-sm); + background: var(--app-surface); cursor: pointer; } -.radios label:hover { border-color: var(--accent); } -.radios input { grid-row: span 2; margin: 0; accent-color: var(--accent); } -.radios b { font-size: 13px; } -.radios small { grid-column: 2; color: var(--muted); font-size: 12px; } +.radios label:hover { border-color: var(--app-accent); } +.radios input { grid-row: span 2; margin: 0; accent-color: var(--app-accent); } +.radios b { font-size: 0.8125rem; color: var(--app-text); } +.radios small { grid-column: 2; color: var(--app-text-3); font-size: 0.75rem; line-height: 1.5; } -.input-suffix { position: relative; } -.input-suffix input { padding-right: 40px; } -.input-suffix b { - position: absolute; right: 12px; top: 50%; transform: translateY(-50%); - font-size: 12px; color: var(--muted); font-weight: 400; -} - -.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; } -.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0 14px; } - -.actions { display: flex; gap: 8px; align-items: center; margin-top: 6px; } -.actions.wrap { flex-wrap: wrap; } - -.inline-form { - display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; - margin-top: 14px; padding: 12px; - background: var(--surface); border: 1px dashed var(--line); border-radius: var(--radius); -} -.inline-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--ink-2); } -.inline-field input, .inline-field select { - height: 32px; padding: 0 8px; min-width: 130px; - border: 1px solid var(--line); border-radius: var(--radius); - background: var(--surface); font: inherit; font-size: 13px; color: var(--ink); -} -.inline-form select[name="package_id"] { min-width: 220px; } - -.form.card, .section.card { - background: var(--surface); - border: 1px solid var(--line); - border-radius: var(--radius); - padding: 20px; -} - -.filterbar { display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; } +.filterbar { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; align-items: center; } .filterbar input[type="search"], .filterbar select { height: 34px; padding: 0 10px; - border: 1px solid var(--line); border-radius: var(--radius); - background: var(--surface); font: inherit; color: var(--ink); + border: 1px solid var(--app-border); border-radius: var(--app-radius-sm); + background: var(--app-surface); color: var(--app-text); + font: inherit; font-size: 0.8125rem; } .filterbar input[type="search"] { flex: 1 1 260px; max-width: 420px; } +.filterbar input[type="search"]:focus, .filterbar select:focus { + outline: none; border-color: var(--app-accent); box-shadow: 0 0 0 3px var(--app-ring); +} +.filterbar .muted { margin-left: auto; } -/* ---------- 认证页 ---------- */ +.inline-form { + display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; + margin-top: 16px; padding: 14px; + background: var(--app-surface); border: 1px dashed var(--app-border-strong); + border-radius: var(--app-radius-sm); +} +.inline-field { display: flex; flex-direction: column; gap: 4px; font-size: 0.75rem; color: var(--app-text-2); font-weight: 600; } +.inline-field input, .inline-field select { + height: 34px; min-width: 130px; padding: 0 9px; + border: 1px solid var(--app-border); border-radius: 7px; + background: var(--app-surface); color: var(--app-text); + font: inherit; font-size: 0.8125rem; font-weight: 400; +} +.inline-field select { padding-right: 1.7rem; } +.inline-field input:focus, .inline-field select:focus { + outline: none; border-color: var(--app-accent); box-shadow: 0 0 0 3px var(--app-ring); +} +.inline-form select[name="package_id"] { min-width: 220px; } +.inline-form > .form-select { flex: 1 1 260px; width: auto; max-width: 380px; } -.auth { min-height: 100vh; display: grid; place-items: center; padding: 32px 16px; } -.auth-card { - width: 100%; max-width: 380px; - background: var(--surface); border: 1px solid var(--line); - border-top: 3px solid var(--accent); - padding: 28px; - box-shadow: var(--shadow); -} -.auth-brand { display: flex; align-items: center; gap: 8px; font-weight: 700; letter-spacing: 0.06em; margin-bottom: 18px; } -.auth-card h1 { font-size: 20px; margin-bottom: 4px; } -.auth-card > p { font-size: 12px; margin: 0 0 18px; } -.auth-foot { margin-top: 16px !important; text-align: center; } -.form .btn-block { margin-top: 4px; } +.actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 6px; } +.actions.wrap { flex-wrap: wrap; } +.actions form { margin: 0; display: inline-flex; } -/* ---------- 套餐标签(日历/大屏共用) ---------- */ - -.pkg-tabs { - display: flex; gap: 6px; margin-bottom: 14px; flex-wrap: wrap; - padding-bottom: 8px; border-bottom: 1px solid var(--line); -} -.pkg-tabs a { - display: inline-flex; align-items: center; gap: 6px; - height: 30px; padding: 0 12px; - border: 1px solid var(--line); border-radius: 999px; - background: var(--surface); color: var(--ink-2); - font-size: 13px; text-decoration: none; -} -.pkg-tabs a i { - width: 8px; height: 8px; border-radius: 50%; - background: var(--tab, var(--accent)); display: inline-block; -} -.pkg-tabs a em { - font-family: var(--mono); font-style: normal; font-size: 11px; - color: var(--muted); background: var(--paper); - border-radius: 999px; padding: 0 5px; -} -.pkg-tabs a:hover { border-color: var(--accent); text-decoration: none; } -.pkg-tabs a.on { background: var(--accent); border-color: var(--accent); color: var(--paper); } -.pkg-tabs a.on em { background: color-mix(in srgb, var(--paper) 25%, transparent); color: var(--paper); } - -/* ---------- 日历 ---------- */ - -.cal-page { max-width: 1400px; margin: 0 auto; } - -.cal-head { - display: flex; align-items: center; justify-content: space-between; - gap: 16px; flex-wrap: wrap; margin-bottom: 14px; -} -.cal-title { display: flex; align-items: center; gap: 10px; } -.cal-title h1 { font-size: 20px; font-family: var(--mono); letter-spacing: 0.02em; } -.cal-stats { display: flex; align-items: center; gap: 16px; font-size: 12px; color: var(--muted); flex-wrap: wrap; } -.cal-stats em { - font-family: var(--mono); font-style: normal; font-size: 16px; - color: var(--ink); margin-right: 4px; -} -.cal-stats .text-urgent em { color: var(--danger); } - -.cal-body { display: grid; grid-template-columns: minmax(0, 1fr) 372px; gap: 16px; align-items: start; } - -.cal-grid { - border: 1px solid var(--line); - border-radius: var(--radius); - background: var(--surface); - overflow: hidden; -} -.cal-dow { - display: grid; grid-template-columns: repeat(7, 1fr); - background: var(--paper); - border-bottom: 1px solid var(--line); -} -.cal-dow span { - padding: 7px 0; text-align: center; - font-size: 12px; color: var(--muted); -} -.cal-week { display: grid; grid-template-columns: repeat(7, 1fr); } - -.cal-cell { - position: relative; - display: flex; flex-direction: column; gap: 3px; - min-height: 112px; padding: 6px 7px 8px; - border-right: 1px solid var(--line-2); - border-bottom: 1px solid var(--line-2); - color: var(--ink); text-decoration: none; - transition: background 0.12s ease; -} -.cal-cell:nth-child(7n) { border-right: 0; } -.cal-cell:hover { background: var(--hover); text-decoration: none; } -.cal-cell.out { opacity: 0.4; } -.cal-cell.weekend .dnum { color: var(--muted); } -.cal-cell.sel { box-shadow: inset 0 0 0 2px var(--accent); z-index: 2; } -.cal-cell.today .dnum { - background: var(--accent); color: var(--paper); - border-radius: 50%; width: 22px; height: 22px; - display: inline-grid; place-items: center; -} -.cal-cell.has-due::after { - content: ""; position: absolute; left: 6px; right: 6px; bottom: 3px; - border-bottom: 1px dashed color-mix(in srgb, var(--danger) 45%, transparent); -} - -.cal-cell.heat-1 { background: color-mix(in srgb, var(--accent) 5%, transparent); } -.cal-cell.heat-2 { background: color-mix(in srgb, var(--accent) 9%, transparent); } -.cal-cell.heat-3 { background: color-mix(in srgb, var(--accent) 14%, transparent); } -.cal-cell.heat-4 { background: color-mix(in srgb, var(--accent) 20%, transparent); } -.cal-cell.heat-0:hover, .cal-cell.heat-1:hover { background: var(--hover); } - -.cell-top { display: flex; align-items: center; justify-content: space-between; gap: 4px; } -.cell-badges { display: inline-flex; align-items: center; gap: 4px; } -.dnum { font-family: var(--mono); font-size: 13px; font-weight: 700; line-height: 1.4; } -.urgent-dot { - width: 7px; height: 7px; border-radius: 50%; - background: var(--danger); display: inline-block; - box-shadow: 0 0 0 2px color-mix(in srgb, var(--danger) 22%, transparent); -} -.due-badge { - display: inline-flex; align-items: baseline; gap: 2px; - font-size: 10px; padding: 0 5px; line-height: 16px; - background: var(--danger); color: #fff; border-radius: 2px; - white-space: nowrap; -} -.due-badge em { font-style: normal; opacity: 0.85; } -.cell-load { font-family: var(--mono); font-size: 10px; color: var(--accent); } - -.cell-tickets { display: flex; flex-direction: column; gap: 2px; margin-top: auto; overflow: hidden; } - -.ticket { - display: inline-flex; align-items: baseline; gap: 3px; - max-width: 100%; min-width: 0; - padding: 1px 5px; - font-size: 10.5px; line-height: 16px; - border-left: 3px solid var(--tag, var(--accent)); - background: color-mix(in srgb, var(--tag, var(--accent)) 13%, var(--surface)); - border-radius: 1px 2px 2px 1px; - white-space: nowrap; - color: var(--ink-2); -} -.ticket b { font-family: var(--mono); font-size: 11px; color: var(--ink); flex: 0 0 auto; } -.ticket .tk-name { overflow: hidden; text-overflow: ellipsis; } -.ticket i { font-style: normal; font-size: 9.5px; color: var(--muted); flex: 0 0 auto; } -.ticket.done { opacity: 0.55; } -.ticket.done::after { content: "✓"; color: var(--accent); font-size: 10px; } -.ticket.skipped { opacity: 0.4; text-decoration: line-through; } -.ticket.more { border-left-color: var(--line); background: var(--paper); color: var(--muted); } -.ticket.urgent { border-left-color: var(--danger); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--danger) 35%, transparent); } - -/* ---------- 当日抽屉 ---------- */ - -.drawer { - position: sticky; top: 72px; - border: 1px solid var(--line); border-radius: var(--radius); - background: var(--surface); - max-height: calc(100vh - 100px); - overflow-y: auto; -} -.drawer-head { - position: sticky; top: 0; z-index: 2; - display: flex; align-items: center; justify-content: space-between; gap: 8px; - padding: 12px 14px; - background: var(--paper); border-bottom: 1px solid var(--line); -} -.drawer-head h2 { font-size: 14px; font-family: var(--mono); } -.drawer-tags { display: flex; gap: 4px; } -.drawer-block { padding: 12px 14px; border-bottom: 1px solid var(--line-2); } -.drawer-block:last-child { border-bottom: 0; } -.drawer-block h3 { - display: flex; align-items: center; gap: 6px; - font-size: 12px; color: var(--ink-2); margin-bottom: 8px; - letter-spacing: 0.02em; -} -.drawer-block h3 em { - font-family: var(--mono); font-style: normal; font-size: 11px; - background: var(--paper); border: 1px solid var(--line); - border-radius: 999px; padding: 0 6px; color: var(--muted); -} -.drawer-block p { margin: 0; font-size: 12px; } - -/* 快速完成 */ -.quick-form { display: flex; gap: 6px; margin-bottom: 4px; } -.quick-form input[type="text"] { - flex: 1 1 auto; min-width: 0; height: 30px; padding: 0 9px; - border: 1px solid var(--line); border-radius: var(--radius); - background: var(--surface); font: inherit; font-size: 13px; font-family: var(--mono); - color: var(--ink); -} -.quick-form input[type="text"]:focus { - outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); -} -.quick-hint { margin: 0 0 10px !important; font-size: 11px; line-height: 1.4; } - -/* 选择面板 */ -.picker { - margin: 8px 0 12px; padding: 10px; - border: 1px solid var(--accent); - border-radius: var(--radius); - background: var(--surface-2); -} -.picker-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; } -.picker-head h4 { font-size: 13px; } -.pick-list { list-style: none; margin: 0 0 8px; padding: 0; } -.pick-list li { border-bottom: 1px dashed var(--line-2); } -.pick-list li:last-child { border-bottom: 0; } -.pick-list label { - display: flex; align-items: center; gap: 7px; - padding: 6px 0; cursor: pointer; font-size: 12px; -} -.pick-list input[type="checkbox"] { accent-color: var(--accent); } -.pick-pkg { color: var(--ink-2); } -.pick-remain { margin-left: auto; font-family: var(--mono); color: var(--muted); font-size: 11px; } - -.due-list, .task-list, .mini-due, .checkin-list { list-style: none; margin: 0; padding: 0; } - -.due-list li { - display: flex; align-items: center; justify-content: space-between; gap: 8px; - padding: 6px 0; border-bottom: 1px dashed var(--line-2); -} -.due-list li:last-child { border-bottom: 0; } -.due-main { display: flex; align-items: center; gap: 7px; min-width: 0; color: var(--ink); } -.due-main .pkg, .task-meta .pkg { font-size: 11px; color: var(--muted); } -.due-main .rem { font-family: var(--mono); font-size: 11px; color: var(--danger); } - -.task { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; padding: 8px 0; border-bottom: 1px dashed var(--line-2); } -.task:last-child { border-bottom: 0; } -.task.urgent { border-left: 3px solid var(--danger); padding-left: 8px; margin-left: -8px; border-radius: 2px; background: color-mix(in srgb, var(--danger) 5%, transparent); } -.task-main { min-width: 0; flex: 1 1 auto; } -.task-title { display: flex; align-items: center; gap: 6px; color: var(--ink); flex-wrap: wrap; } -.task-meta { display: flex; gap: 6px; font-size: 11px; color: var(--muted); margin-top: 2px; } -.task-note { font-size: 11px; color: var(--ink-2); margin-top: 2px; } -.task-actions { display: flex; gap: 4px; align-items: center; flex-shrink: 0; flex-wrap: wrap; justify-content: flex-end; max-width: 190px; } -.task.done .task-title, .task.skipped .task-title { opacity: 0.5; } -.task.skipped .task-title .ticket { text-decoration: line-through; } - -.tag-urgent { - display: inline-flex; align-items: center; - height: 17px; padding: 0 5px; - font-size: 10px; line-height: 1; - color: #fff; background: var(--danger); border-radius: 2px; - letter-spacing: 0.02em; white-space: nowrap; -} - -.mini-due li { display: grid; grid-template-columns: 1fr auto auto; gap: 8px; align-items: center; padding: 4px 0; font-size: 12px; } -.sm-date { font-family: var(--mono); font-size: 11px; color: var(--muted); } - -/* ---------- 状态标签 ---------- */ +/* ---------- 8. 徽标 / 状态 ---------- */ .chip { display: inline-flex; align-items: center; height: 20px; padding: 0 7px; - font-size: 11px; line-height: 1; - border-radius: 2px; border: 1px solid transparent; - white-space: nowrap; + border-radius: 999px; border: 1px solid transparent; + font-size: 0.6875rem; font-weight: 600; line-height: 1; white-space: nowrap; } -.chip-active { background: var(--accent-soft); color: var(--accent-ink); border-color: color-mix(in srgb, var(--accent) 25%, transparent); } -.chip-pending { background: var(--info-soft); color: var(--info); border-color: color-mix(in srgb, var(--info) 25%, transparent); } -.chip-paused { background: var(--info-soft); color: var(--info); border-color: color-mix(in srgb, var(--info) 25%, transparent); } -.chip-expired { background: var(--danger-soft); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 25%, transparent); } -.chip-usedup { background: var(--warn-soft); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 25%, transparent); } -.chip-closed { background: var(--paper); color: var(--muted); border-color: var(--line); } -.chip-plain { background: var(--paper); color: var(--ink-2); border-color: var(--line); } +.chip-active { background: var(--app-accent-soft); color: var(--app-accent-ink); border-color: color-mix(in srgb, var(--app-accent) 26%, transparent); } +.chip-paused { background: var(--app-info-soft); color: var(--app-info); border-color: color-mix(in srgb, var(--app-info) 26%, transparent); } +.chip-expired { background: var(--app-danger-soft); color: var(--app-danger); border-color: color-mix(in srgb, var(--app-danger) 26%, transparent); } +.chip-usedup { background: var(--app-warn-soft); color: var(--app-warn); border-color: color-mix(in srgb, var(--app-warn) 26%, transparent); } +.chip-closed { background: var(--app-surface-2); color: var(--app-text-3); border-color: var(--app-border); } +.chip-plain { background: var(--app-surface-2); color: var(--app-text-2); border-color: var(--app-border); } .tagchip { display: inline-block; margin-right: 4px; - padding: 0 6px; height: 19px; line-height: 19px; - font-size: 11px; color: var(--ink-2); - background: var(--paper); border: 1px solid var(--line); border-radius: 2px; + padding: 0 7px; height: 20px; line-height: 18px; + font-size: 0.6875rem; font-weight: 600; color: var(--app-text-2); + background: var(--app-surface-2); border: 1px solid var(--app-border); + border-radius: 999px; } -.due { font-size: 11px; } -.due-today { color: var(--danger); font-weight: 700; } -.due-warn { color: var(--warn); } -.due-expired { color: var(--muted); } -.due-ok, .due-none { color: var(--muted); } -.plus { color: var(--accent); font-family: var(--mono); } +.tag-urgent { + display: inline-flex; align-items: center; + height: 18px; padding: 0 6px; + font-size: 0.625rem; font-weight: 700; line-height: 1; + color: var(--app-danger); background: var(--app-danger-soft); + border: 1px solid color-mix(in srgb, var(--app-danger) 28%, transparent); + border-radius: 999px; letter-spacing: 0.02em; white-space: nowrap; +} -.dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 5px; vertical-align: 1px; } +.due { font-size: 0.6875rem; font-weight: 600; } +.due-today { color: var(--app-danger); } +.due-warn { color: var(--app-warn); } +.due-expired, .due-ok, .due-none { color: var(--app-text-3); font-weight: 500; } +.plus { color: var(--app-accent); font-family: var(--app-mono); } + +.dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 6px; vertical-align: 1px; } .dot.big { width: 12px; height: 12px; margin: 0; } -/* ---------- 表格 ---------- */ +.asg { display: inline-flex; align-items: center; gap: 4px; font-size: 0.6875rem; color: var(--app-text-2); white-space: nowrap; } +.asg i { width: 8px; height: 8px; border-radius: 2px; display: inline-block; flex: 0 0 auto; } +.asg.none { color: var(--app-text-3); } +.asg.none i { opacity: 0.55; } -.table-wrap { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); overflow-x: auto; } -.table { width: 100%; border-collapse: collapse; font-size: 13px; } +/* ---------- 9. 表格 ---------- */ + +.table-wrap { + background: var(--app-surface); + border: 1px solid var(--app-border); + border-radius: var(--app-radius); + overflow-x: auto; + box-shadow: var(--app-shadow-sm); +} +.table { + width: 100%; margin: 0; border-collapse: collapse; + --bs-table-bg: transparent; + --bs-table-color: var(--app-text); + --bs-table-border-color: var(--app-border); + --bs-table-hover-bg: var(--app-surface-2); + --bs-table-hover-color: var(--app-text); + font-size: 0.8125rem; +} .table th { - text-align: left; padding: 9px 12px; - font-size: 11px; font-weight: 600; color: var(--muted); - letter-spacing: 0.04em; - background: var(--paper); - border-bottom: 1px solid var(--line); - white-space: nowrap; + padding: 9px 12px; + background: var(--app-surface-2); + border-bottom: 1px solid var(--app-border); + font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.04em; + color: var(--app-text-3); text-align: left; white-space: nowrap; +} +.table td { + padding: 9px 12px; + border-bottom: 1px solid var(--app-border); + color: var(--app-text); vertical-align: middle; } -.table td { padding: 9px 12px; border-bottom: 1px solid var(--line-2); vertical-align: middle; } .table tbody tr:last-child td { border-bottom: 0; } -.table tbody tr:hover { background: var(--hover); } -.table.tight td, .table.tight th { padding: 7px 10px; } +.table tbody tr:hover { background: var(--app-surface-2); } +.table.tight th, .table.tight td { padding: 7px 10px; } .table .col-dot { width: 28px; } -.table tr.archived { opacity: 0.6; } -.actions-cell { display: flex; gap: 4px; align-items: center; flex-wrap: wrap; } +.table tr.archived { background: var(--app-surface-2); } +.table tr.archived td { color: var(--app-text-3); } +.table tr.archived td a { color: var(--app-text-3); } +.table tr.archived:hover { background: var(--app-surface-3); } +.table td .muted, .table td small { color: var(--app-text-3); } +.table td:last-child { white-space: nowrap; } -.tail { - display: inline-block; font-family: var(--mono); font-size: 15px; font-weight: 700; - color: var(--accent-ink); letter-spacing: 0.04em; -} -.name { font-weight: 600; color: var(--ink); } -.summary { font-size: 12px; color: var(--ink-2); } +.actions-cell { display: flex; gap: 5px; align-items: center; flex-wrap: nowrap; } +.actions-cell form { margin: 0; display: inline-flex; } -.pager { display: flex; align-items: center; justify-content: center; gap: 12px; margin-top: 16px; } -.pager-info { font-size: 12px; color: var(--muted); font-family: var(--mono); } +.tail { font-family: var(--app-mono); font-size: 0.9375rem; font-weight: 700; color: var(--app-accent-ink); letter-spacing: 0.03em; } +.name { font-weight: 600; color: var(--app-text); } +.summary { font-size: 0.75rem; color: var(--app-text-2); } +.summary .due-ok { color: var(--app-accent); } +.summary .due-soon { color: var(--app-warn); } +.summary .due-late { color: var(--app-danger); } + +.pager { display: flex; align-items: center; justify-content: center; gap: 12px; margin-top: 18px; flex-wrap: wrap; } +.pager-info { font-size: 0.75rem; color: var(--app-text-3); font-family: var(--app-mono); } .empty { display: flex; flex-direction: column; align-items: center; gap: 12px; - padding: 56px 20px; text-align: center; - border: 1px dashed var(--line); border-radius: var(--radius); - background: color-mix(in srgb, var(--surface) 60%, transparent); + padding: 48px 20px; text-align: center; + border: 1px dashed var(--app-border-strong); border-radius: var(--app-radius); + background: color-mix(in srgb, var(--app-surface) 70%, transparent); } -.empty p { margin: 0; color: var(--muted); font-size: 13px; } +.empty p { margin: 0; color: var(--app-text-3); font-size: 0.8125rem; } .empty-mark { width: 46px; height: 30px; display: grid; place-items: center; - border: 1px solid var(--line); border-left: 4px solid var(--accent); - background: var(--surface); color: var(--muted); font-size: 12px; - transform: rotate(-4deg); + border: 1px solid var(--app-border); border-left: 4px solid var(--app-accent); + background: var(--app-surface); color: var(--app-text-3); font-size: 0.75rem; + border-radius: 3px; transform: rotate(-4deg); } -/* ---------- 客户详情 ---------- */ +/* ---------- 10. 客户详情 ---------- */ -.cust-head { display: grid; grid-template-columns: auto 1fr auto; gap: 20px; align-items: start; margin-bottom: 16px; } -.cust-id { display: flex; align-items: center; gap: 14px; } +.cust-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 18px; align-items: start; margin-bottom: 18px; } +.cust-id { display: flex; align-items: center; gap: 16px; min-width: 0; } .cust-tail { - font-family: var(--mono); font-size: 30px; font-weight: 700; line-height: 1; - padding: 8px 12px; - background: var(--surface); border: 1px solid var(--line); - border-left: 4px solid var(--accent); - letter-spacing: 0.06em; + flex: 0 0 auto; + font-family: var(--app-mono); font-size: 1.75rem; font-weight: 700; line-height: 1; + padding: 10px 14px; + background: var(--app-surface); border: 1px solid var(--app-border); + border-left: 4px solid var(--app-accent); border-radius: var(--app-radius-sm); + color: var(--app-accent-ink); letter-spacing: 0.06em; + box-shadow: var(--app-shadow-sm); } -.cust-tags { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 4px; } -.cust-meta { display: flex; flex-wrap: wrap; gap: 4px 20px; margin: 0; } -.cust-meta dt { font-size: 11px; color: var(--muted); } -.cust-meta dd { margin: 0; font-size: 12px; color: var(--ink-2); } +.cust-id-main { min-width: 0; } +.cust-id-main h1 { font-size: 1.5rem; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } +.cust-tags { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 6px; } -.section { margin-top: 22px; } -.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 10px; flex-wrap: wrap; } -.section-head h2 { font-size: 14px; } -.section-head .muted { font-size: 11px; } -.section.card h2 { margin-bottom: 12px; } +.cust-meta { + grid-column: 1 / -1; + display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); + gap: 12px 20px; margin: 0; +} +.cust-meta > div { min-width: 0; } +.cust-meta dt { font-size: 0.6875rem; font-weight: 600; color: var(--app-text-3); letter-spacing: 0.03em; margin-bottom: 2px; } +.cust-meta dd { + margin: 0; font-size: 0.8125rem; color: var(--app-text); + display: flex; align-items: center; gap: 8px; flex-wrap: wrap; + word-break: break-word; +} +.cust-head .page-actions { grid-column: 2; grid-row: 1; justify-content: flex-end; } -.subgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(268px, 1fr)); gap: 12px; } +.assign-form { display: inline-flex; align-items: center; gap: 6px; } +.assign-form select { + height: 30px; padding: 0 26px 0 8px; max-width: 150px; + border: 1px solid var(--app-border); border-radius: 7px; + background: var(--app-surface); color: var(--app-text); + font: inherit; font-size: 0.75rem; +} +.assign-form select:focus { outline: none; border-color: var(--app-accent); box-shadow: 0 0 0 3px var(--app-ring); } + +.subgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; } .subcard { - background: var(--surface); - border: 1px solid var(--line); - border-left: 4px solid var(--tag, var(--accent)); - border-radius: var(--radius); + --tag: var(--app-accent); + background: var(--app-surface); + border: 1px solid var(--app-border); + border-left: 4px solid var(--tag); + border-radius: var(--app-radius); display: flex; flex-direction: column; + box-shadow: var(--app-shadow-sm); } -.subcard-head { display: flex; align-items: center; gap: 6px; padding: 10px 12px 6px; } -.subcard-head h3 { font-size: 13px; flex: 1 1 auto; } -.subcard-head .unit { font-size: 10px; color: var(--muted); border: 1px solid var(--line); border-radius: 2px; padding: 0 4px; } -.subcard-body { padding: 0 12px 10px; } -.subcard-big { font-family: var(--mono); font-size: 20px; font-weight: 700; letter-spacing: 0.01em; } -.subcard-meta { font-family: var(--mono); font-size: 11px; color: var(--muted); margin-top: 2px; } -.bar { height: 5px; background: var(--line-2); border-radius: 999px; overflow: hidden; margin-top: 8px; } -.bar i { display: block; height: 100%; background: var(--tag, var(--accent)); } +.subcard-head { display: flex; align-items: center; gap: 6px; padding: 12px 14px 6px; } +.subcard-head > .tag { width: 8px; height: 8px; border-radius: 3px; background: var(--tag); flex: 0 0 auto; } +.subcard-head h3 { font-size: 0.8125rem; flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.subcard-head .unit { font-size: 0.625rem; color: var(--app-text-3); border: 1px solid var(--app-border); border-radius: 999px; padding: 0 6px; } +.subcard-body { padding: 0 14px 12px; } +.subcard-big { font-family: var(--app-mono); font-size: 1.375rem; font-weight: 700; letter-spacing: 0.01em; color: var(--app-text); } +.subcard-meta { font-family: var(--app-mono); font-size: 0.6875rem; color: var(--app-text-3); margin-top: 2px; } +.subcard-body .due { margin-top: 4px; } +.bar { height: 6px; background: var(--app-surface-3); border-radius: 999px; overflow: hidden; margin-top: 10px; } +.bar i { display: block; height: 100%; background: var(--tag); border-radius: inherit; } .subcard-foot { - display: flex; gap: 4px; flex-wrap: wrap; - padding: 8px 12px; border-top: 1px dashed var(--line); - background: var(--surface-2); + display: flex; gap: 6px; flex-wrap: wrap; align-items: center; + padding: 10px 12px; border-top: 1px dashed var(--app-border); + background: var(--app-surface-2); border-radius: 0 0 var(--app-radius) 0; } +.subcard-foot form { margin: 0; display: inline-flex; } +.subcard.is-closed { opacity: 0.6; filter: saturate(0.55); } +.subcard.is-closed:hover { opacity: 0.85; } -.timeline { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); padding: 16px 16px 10px; } -.tl-axis { position: relative; height: 18px; margin-left: 92px; margin-right: 150px; border-bottom: 1px solid var(--line-2); } -.tl-tick { position: absolute; top: 0; font-family: var(--mono); font-size: 10px; color: var(--muted); transform: translateX(-50%); } -.tl-row { display: grid; grid-template-columns: 84px 1fr 142px; gap: 8px; align-items: center; padding: 5px 0; } -.tl-name { font-size: 12px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.tl-track { position: relative; height: 20px; background: var(--line-2); border-radius: 2px; } -.tl-bar { position: absolute; top: 4px; height: 12px; background: var(--tag); border-radius: 2px; } +.timeline { background: var(--app-surface); border: 1px solid var(--app-border); border-radius: var(--app-radius); padding: 16px 16px 12px; box-shadow: var(--app-shadow-sm); } +.tl-axis { position: relative; height: 18px; margin-left: 120px; margin-right: 152px; border-bottom: 1px solid var(--app-border); } +.tl-tick { position: absolute; top: 0; font-family: var(--app-mono); font-size: 0.625rem; color: var(--app-text-3); transform: translateX(-50%); } +.tl-row { display: grid; grid-template-columns: 110px 1fr 142px; gap: 10px; align-items: center; padding: 6px 0; } +.tl-name { font-size: 0.75rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--app-text); } +.tl-track { position: relative; height: 20px; background: var(--app-surface-3); border-radius: 4px; } +.tl-bar { position: absolute; top: 4px; height: 12px; background: var(--tag, var(--app-accent)); border-radius: 3px; } .tl-bar.st-paused { opacity: 0.45; } -.tl-bar.st-closed { background: var(--muted); opacity: 0.6; } +.tl-bar.st-closed { background: var(--app-text-3); opacity: 0.5; } .tl-pause { position: absolute; top: 4px; height: 12px; background: repeating-linear-gradient(135deg, - color-mix(in srgb, var(--muted) 45%, transparent) 0 4px, - color-mix(in srgb, var(--muted) 18%, transparent) 4px 8px); - border-radius: 1px; + color-mix(in srgb, var(--app-text-3) 45%, transparent) 0 4px, + color-mix(in srgb, var(--app-text-3) 15%, transparent) 4px 8px); + border-radius: 2px; } -.tl-due { position: absolute; top: 1px; bottom: 1px; width: 2px; background: var(--danger); } -.tl-legend { font-family: var(--mono); font-size: 10px; color: var(--muted); text-align: right; } +.tl-due { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--app-danger); } +.tl-legend { font-family: var(--app-mono); font-size: 0.625rem; color: var(--app-text-3); text-align: right; } -.checkin-list li { display: flex; align-items: center; gap: 8px; padding: 5px 0; border-bottom: 1px dashed var(--line-2); font-size: 12px; } +.checkin-list { list-style: none; margin: 0; padding: 0; } +.checkin-list li { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px dashed var(--app-border); font-size: 0.75rem; } .checkin-list li:last-child { border-bottom: 0; } -/* ---------- 追加面板 ---------- */ +/* ---------- 11. 日历 ---------- */ -.extend { - margin-top: 18px; - border: 1px solid var(--accent); - border-radius: var(--radius); - background: var(--surface); - box-shadow: var(--shadow); - scroll-margin-top: 80px; +.cal-page { max-width: 1440px; margin: 0 auto; } + +.cal-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 16px; } +.cal-title { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } +.cal-title h1 { font-size: 1.25rem; font-family: var(--app-mono); font-weight: 700; letter-spacing: 0.02em; margin: 0; } +.cal-stats { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; } +.cal-stats > span { display: inline-flex; align-items: baseline; gap: 5px; font-size: 0.75rem; color: var(--app-text-3); } +.cal-stats em { font-family: var(--app-mono); font-style: normal; font-size: 1.0625rem; font-weight: 700; color: var(--app-text); } +.cal-stats .text-urgent em { color: var(--app-danger); } + +.cal-body { display: grid; grid-template-columns: minmax(0, 1fr) 380px; gap: 16px; align-items: start; } + +.cal-grid { + background: var(--app-surface); + border: 1px solid var(--app-border); + border-radius: var(--app-radius); + overflow: hidden; + box-shadow: var(--app-shadow-sm); } -.drawer .extend { margin: 12px 14px; } -.extend-head { - display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; - padding: 12px 14px; - background: var(--accent-soft); - border-bottom: 1px solid color-mix(in srgb, var(--accent) 25%, transparent); +.cal-dow { display: grid; grid-template-columns: repeat(7, 1fr); background: var(--app-surface-2); border-bottom: 1px solid var(--app-border); } +.cal-dow span { padding: 8px 0; text-align: center; font-size: 0.75rem; color: var(--app-text-3); } +.cal-week { display: grid; grid-template-columns: repeat(7, 1fr); } + +.cal-cell { + position: relative; + display: flex; flex-direction: column; gap: 4px; + min-height: 112px; padding: 8px 9px 10px 12px; + border-right: 1px solid var(--app-border); + border-bottom: 1px solid var(--app-border); + color: var(--app-text); text-decoration: none; + transition: background 0.12s ease; } -.extend-head h3 { font-size: 14px; } -.extend-head p { margin: 2px 0 0; font-size: 11px; } -.extend-close { font-size: 20px; line-height: 1; color: var(--ink-2); padding: 0 4px; } -.extend-form { padding: 14px; } - -.extend-preview { - display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; - padding: 10px; margin-bottom: 10px; - background: var(--paper); border: 1px dashed var(--line); border-radius: var(--radius); +.cal-cell:nth-child(7n) { border-right: 0; } +.cal-week:last-child .cal-cell { border-bottom: 0; } +.cal-cell:hover { background: var(--app-surface-2); } +.cal-cell.out { background: var(--app-surface); } +.cal-cell.out .dnum { color: var(--app-text-3); font-weight: 600; } +.cal-cell.out .cell-count b { color: var(--app-text-3); } +.cal-cell.out .cell-count span { color: var(--app-text-3); } +.cal-cell.weekend .dnum { color: var(--app-text-2); } +.cal-cell.sel { box-shadow: inset 0 0 0 2px var(--app-accent); z-index: 2; } +.cal-cell.today .dnum { + background: var(--app-accent-solid); color: #fff; + border-radius: 50%; width: 22px; height: 22px; + display: inline-grid; place-items: center; } -.extend-preview div { display: flex; flex-direction: column; gap: 1px; } -.extend-preview span { font-size: 10px; color: var(--muted); } -.extend-preview b { font-family: var(--mono); font-size: 13px; font-weight: 600; } -.extend-preview b i { font-style: normal; font-size: 18px; color: var(--accent); } -.extend-preview .add b { color: var(--accent); } -.extend-hint { margin: 0 0 10px; font-size: 11px; color: var(--muted); } +.cal-cell.heat-1 { background: color-mix(in srgb, var(--app-accent) 5%, transparent); } +.cal-cell.heat-2 { background: color-mix(in srgb, var(--app-accent) 9%, transparent); } +.cal-cell.heat-3 { background: color-mix(in srgb, var(--app-accent) 13%, transparent); } +.cal-cell.heat-4 { background: color-mix(in srgb, var(--app-accent) 18%, transparent); } +.cal-cell.heat-1:hover, .cal-cell.heat-2:hover, .cal-cell.heat-3:hover, .cal-cell.heat-4:hover { background: var(--app-surface-2); } -.minis { display: grid; grid-template-columns: repeat(auto-fit, minmax(196px, 1fr)); gap: 12px; margin-bottom: 12px; } -.minimonth { border: 1px solid var(--line); border-radius: var(--radius); padding: 8px; } -.minimonth-title { font-size: 11px; color: var(--ink-2); font-weight: 600; margin-bottom: 6px; text-align: center; } -.minimonth-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; } -.minimonth-grid .dow { text-align: center; font-size: 10px; color: var(--muted); } -.miniday { - height: 28px; border: 1px solid transparent; border-radius: 2px; - background: transparent; font: inherit; font-size: 11px; font-family: var(--mono); - color: var(--ink-2); cursor: pointer; padding: 0; +.cell-top { display: flex; align-items: center; justify-content: space-between; gap: 4px; min-width: 0; } +.cell-badges { display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto; } +.dnum { font-family: var(--app-mono); font-size: 0.8125rem; font-weight: 700; line-height: 1.4; color: var(--app-text); } +.urgent-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--app-danger); display: inline-block; box-shadow: 0 0 0 2px color-mix(in srgb, var(--app-danger) 20%, transparent); } +.due-badge { + display: inline-flex; align-items: baseline; gap: 2px; + height: 16px; padding: 0 5px; + background: var(--app-danger-soft); color: var(--app-danger); + border: 1px solid color-mix(in srgb, var(--app-danger) 24%, transparent); + border-radius: 999px; + font-size: 0.625rem; font-weight: 700; line-height: 1; white-space: nowrap; } -.miniday:hover:not(:disabled) { background: var(--accent-soft); border-color: color-mix(in srgb, var(--accent) 30%, transparent); } -.miniday.out { color: color-mix(in srgb, var(--muted) 55%, transparent); } -.miniday.old { background: var(--warn-soft); border-color: color-mix(in srgb, var(--warn) 35%, transparent); font-weight: 700; } -.miniday.today { outline: 1px solid var(--accent); } -.miniday.picked { background: var(--accent); color: var(--paper); border-color: var(--accent); font-weight: 700; } -.miniday:disabled { color: color-mix(in srgb, var(--muted) 45%, transparent); cursor: not-allowed; } +.due-badge em { font-style: normal; } +.cell-load { font-family: var(--app-mono); font-size: 0.625rem; color: var(--app-accent); } -/* ---------- 设置页杂项 ---------- */ +.cell-count { margin-top: auto; display: flex; align-items: baseline; gap: 3px; min-width: 0; } +.cell-count b { font-family: var(--app-mono); font-size: 1.375rem; font-weight: 700; line-height: 1; color: var(--app-accent); letter-spacing: -0.02em; } +.cell-count span { font-size: 0.75rem; color: var(--app-text-3); white-space: nowrap; } +.cal-cell.out .cell-count b { color: var(--app-text-3); } -.kv { display: grid; gap: 6px; margin: 0 0 14px; } -.kv > div { display: grid; grid-template-columns: 88px 1fr; gap: 10px; align-items: baseline; } -.kv dt { font-size: 11px; color: var(--muted); } -.kv dd { margin: 0; font-size: 12px; word-break: break-all; } -.backups { margin-top: 12px; font-size: 12px; } -.backups summary { cursor: pointer; color: var(--ink-2); } -.backups ul { margin: 8px 0 0; padding-left: 18px; color: var(--muted); } - -.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--line); margin-bottom: 14px; flex-wrap: wrap; } -.tabs a { - padding: 7px 12px; font-size: 13px; color: var(--ink-2); - border-bottom: 2px solid transparent; margin-bottom: -1px; -} -.tabs a:hover { text-decoration: none; color: var(--ink); } -.tabs a.on { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; } - -/* ---------- 响应式 ---------- */ - -@media (max-width: 1180px) { - .cal-body { grid-template-columns: 1fr; } - .drawer { position: static; max-height: none; } - .cust-head { grid-template-columns: 1fr; } -} - -@media (max-width: 860px) { - .topbar { height: auto; flex-wrap: wrap; padding: 8px 12px; gap: 8px; } - .search { order: 3; flex-basis: 100%; max-width: none; } - .nav { order: 4; flex-basis: 100%; overflow-x: auto; } - .topbar-right { order: 2; margin-left: auto; } - main { padding: 0 12px 32px; } - .cal-grid { overflow-x: auto; } - .cal-week, .cal-dow { min-width: 640px; } - .cal-cell { min-height: 96px; } - .grid-2, .grid-3 { grid-template-columns: 1fr; } - .cust-tail { font-size: 24px; } - .tl-row { grid-template-columns: 70px 1fr; } - .tl-legend { display: none; } - .tl-axis { margin-right: 0; } - .extend-preview { grid-template-columns: 1fr 1fr; } -} - -@media (max-width: 560px) { - .pill { display: none; } - .cal-week, .cal-dow { min-width: 0; } - .cal-cell { min-height: 64px; padding: 3px; } - .dnum { font-size: 12px; } - .due-badge { font-size: 9px; padding: 0 4px; line-height: 14px; } - .due-badge em { display: none; } - .cell-load { display: none; } - .cell-tickets { display: none; } - .task { flex-direction: column; } - .task-actions { max-width: none; justify-content: flex-start; } -} - -@media (prefers-reduced-motion: reduce) { - * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } -} - - -/* ---------- 打手颜色 ---------- */ - -/* 日历格左侧竖条:同一天有几种打手就分几段 */ .asg-strip { position: absolute; left: 0; top: 0; bottom: 0; display: flex; flex-direction: column; - width: 5px; overflow: hidden; - border-radius: 2px 0 0 2px; - z-index: 1; + width: 4px; overflow: hidden; border-radius: 2px 0 0 2px; z-index: 1; } -.asg-strip i { display: block; flex: 1 1 0; min-height: 3px; background: var(--muted); } +.asg-strip i { display: block; flex: 1 1 0; min-height: 3px; background: var(--app-text-3); } .asg-strip.sm { width: 3px; } -.cal-cell { padding-left: 12px; } - -/* 打手标签:小圆点 + 名字 */ -.asg { - display: inline-flex; align-items: center; gap: 4px; - font-size: 11px; color: var(--ink-2); white-space: nowrap; -} -.asg i { - width: 8px; height: 8px; border-radius: 2px; - display: inline-block; flex: 0 0 auto; -} -.asg.none { color: var(--muted); } -.asg.none i { background: var(--muted); opacity: 0.55; } - -/* 待办行左侧打手色(加急时红底仍在,色条让位给背景) */ -.task { border-left: 3px solid var(--asg, transparent); padding-left: 8px; margin-left: -3px; } -.task.urgent { border-left-color: var(--danger); } - -/* 设置页颜色表单 */ -.color-form { display: flex; align-items: center; gap: 6px; } -.color-form input[type="color"] { - width: 34px; height: 26px; padding: 1px; - border: 1px solid var(--line); border-radius: var(--radius); - background: var(--surface); cursor: pointer; -} - -/* 客户详情里的快速指派 */ -.assign-form { display: inline-flex; align-items: center; gap: 6px; margin-left: 8px; } -.assign-form select { - height: 26px; padding: 0 6px; font-size: 12px; - border: 1px solid var(--line); border-radius: var(--radius); - background: var(--surface); color: var(--ink); -} -.cust-meta dd { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; } - -@media (max-width: 560px) { - .asg { font-size: 10px; } - .assign-form { margin-left: 0; } -} - - -/* ---------- 日历格子:大字待完成/总数 ---------- */ - -.cal-cell { justify-content: flex-start; } -.cell-count { - flex: 1 1 auto; - display: flex; align-items: baseline; justify-content: center; gap: 4px; - padding-top: 4px; - font-family: var(--mono); - color: var(--muted); -} -.cell-count b { font-size: 26px; line-height: 1; font-weight: 700; color: var(--ink); } -.cell-count span { font-size: 12px; color: var(--muted); } -.cell-count b:first-child { color: var(--accent); } -.cal-cell.today .cell-count b { color: var(--accent-ink); } -.cal-cell.out .cell-count b { color: var(--muted); } - -/* ---------- 抽屉时钟 ---------- */ - -.drawer-clock { - position: sticky; top: 0; z-index: 3; - display: flex; align-items: baseline; justify-content: space-between; gap: 8px; - padding: 10px 14px 6px; - background: var(--surface); - border-bottom: 1px solid var(--line-2); -} -.drawer-clock b { - font-family: var(--mono); font-size: 22px; letter-spacing: 0.06em; - font-variant-numeric: tabular-nums; -} -.drawer-clock span { font-size: 11px; color: var(--muted); font-family: var(--mono); } -.drawer-head { top: 44px; } - -/* ---------- 数字小键盘 ---------- */ - -.keypad { - display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; - margin: 8px 0 12px; -} -.keypad button { - height: 44px; - border: 1px solid var(--line); border-radius: var(--radius); - background: var(--surface); color: var(--ink); - font-family: var(--mono); font-size: 17px; font-weight: 600; - cursor: pointer; user-select: none; - -webkit-tap-highlight-color: transparent; -} -.keypad button:hover { border-color: var(--accent); } -.keypad button:active { background: var(--accent-soft); } -.keypad .key-back { color: var(--ink-2); font-size: 15px; } -.keypad .key-ok { background: var(--accent); border-color: var(--accent); color: var(--paper); } -.keypad .key-ok:hover { background: var(--accent-ink); border-color: var(--accent-ink); } - -/* ---------- 任务行“更多”菜单 ---------- */ - -.more-actions { position: relative; } -.more-actions > summary { - list-style: none; cursor: pointer; - width: 28px; padding: 0; justify-content: center; - font-size: 16px; line-height: 1; -} -.more-actions > summary::-webkit-details-marker { display: none; } -.more-actions[open] > summary { background: var(--accent-soft); border-color: var(--accent); } -.more-menu { - position: absolute; right: 0; top: calc(100% + 4px); z-index: 20; - display: flex; flex-direction: column; gap: 2px; - min-width: 124px; padding: 5px; - background: var(--surface); - border: 1px solid var(--line); - border-radius: var(--radius); - box-shadow: var(--shadow); -} -.more-menu .btn { - justify-content: flex-start; width: 100%; - height: 30px; border-color: transparent; background: transparent; -} -.more-menu .btn:hover { background: var(--accent-soft); border-color: transparent; } - - -/* ---------- v1.6 精修 ---------- */ - -/* 小键盘:3 列 4 行,底行 ⌫ 0 ✓ */ -.keypad { grid-template-columns: repeat(3, 1fr); gap: 8px; } -.keypad .key { - height: 52px; - font-size: 20px; font-weight: 600; - border-radius: 10px; - background: var(--surface); - border: 1px solid var(--line); - box-shadow: 0 1px 2px color-mix(in srgb, var(--ink) 6%, transparent); - transition: background .12s ease, transform .06s ease; -} -.keypad .key:hover { border-color: var(--accent); color: var(--accent); } -.keypad .key:active { transform: translateY(1px); background: var(--accent-soft); } -.keypad .key-fn { font-size: 17px; color: var(--ink-2); background: var(--surface-2); } -.keypad .key-ok { - font-size: 22px; - background: var(--accent); border-color: var(--accent); color: var(--paper); - box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 35%, transparent); -} -.keypad .key-ok:hover { background: var(--accent-ink); border-color: var(--accent-ink); color: var(--paper); } - -/* 格子计数:靠左、垂直居中、主题绿 */ -.cell-count { - justify-content: flex-start; align-items: center; - gap: 5px; padding: 0 0 0 1px; -} -.cell-count b { - font-size: 34px; font-weight: 800; line-height: 1; - color: var(--accent); letter-spacing: -0.02em; -} -.cell-count span { font-size: 15px; color: var(--muted); } - -/* 抽屉时钟:放大、水平垂直居中 */ -.drawer-clock { - flex-direction: column; align-items: center; justify-content: center; - gap: 2px; padding: 14px 14px 10px; -} -.drawer-clock b { font-size: 30px; font-weight: 700; letter-spacing: 0.08em; } -.drawer-clock span { font-size: 11px; letter-spacing: 0.14em; } -.drawer-head { top: 76px; } - -/* 已完成:每条折叠成一行 */ -.task.task-done { padding: 6px 0; } -.done-fold > summary { - display: flex; align-items: center; gap: 6px; - list-style: none; cursor: pointer; padding: 1px 0; - min-width: 0; -} -.done-fold > summary::-webkit-details-marker { display: none; } -.done-fold > summary .pkg { font-size: 11px; color: var(--muted); } -.done-fold > summary .fold-caret { - margin-left: auto; - width: 0; height: 0; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-top: 6px solid var(--muted); - transition: transform 0.15s ease; -} -.done-fold > summary:hover .fold-caret { border-top-color: var(--accent); } -.done-fold > summary:hover .pkg { color: var(--ink-2); } -.done-fold[open] > summary .fold-caret { transform: rotate(180deg); } -.done-detail { - margin: 6px 0 2px; padding-left: 9px; - border-left: 1px dashed var(--line); -} - - -/* ---------- v1.7:客户详情月历 ---------- */ - -.minical-nav { display: flex; align-items: center; gap: 8px; } -.minical-nav b { font-family: var(--mono); font-size: 13px; } - -.minical { - border: 1px solid var(--line); - border-radius: var(--radius); - background: var(--surface); - overflow: hidden; - max-width: 560px; -} -.minical-dow { - display: grid; grid-template-columns: repeat(7, 1fr); - background: var(--paper); border-bottom: 1px solid var(--line); -} -.minical-dow span { padding: 5px 0; text-align: center; font-size: 11px; color: var(--muted); } -.minical-row { display: grid; grid-template-columns: repeat(7, 1fr); grid-auto-rows: 54px; } - -.minical-cell { - position: relative; - display: flex; flex-direction: column; align-items: center; justify-content: center; - gap: 1px; min-height: 0; - border-right: 1px solid var(--line-2); - border-bottom: 1px solid var(--line-2); - color: var(--ink); text-decoration: none; - font-size: 12px; -} -.minical-cell:nth-child(7n) { border-right: 0; } -.minical-cell:hover { background: var(--hover); text-decoration: none; } -.minical-cell.out { opacity: 0.35; } -.minical-cell.today { box-shadow: inset 0 0 0 2px var(--accent); } -.minical-cell.no-task { color: var(--muted); } -.minical-cell .mc-day { font-family: var(--mono); font-size: 12px; font-weight: 600; } -.minical-cell .mc-count { - font-family: var(--mono); font-size: 10px; - color: var(--accent); line-height: 1; -} -.minical-cell.all-done .mc-count { color: var(--muted); } -.minical-cell.all-done .mc-day { color: var(--muted); } -.minical-cell.today .mc-day { color: var(--accent); } - -/* 触控模式按钮 */ -.keypad-toggle { min-width: 40px; padding: 0 8px; font-size: 12px; } -.keypad-toggle.on { - background: var(--accent); border-color: var(--accent); color: var(--paper); -} - - -/* ---------- v1.8:包赛季色带 ---------- */ - -.cal-cell { padding-bottom: 10px; } .season-band { - position: absolute; - left: 0; right: 0; bottom: 2px; - height: 5px; - background: var(--sc, var(--accent)); - opacity: 0.55; + position: absolute; left: 0; right: 0; bottom: 0; + height: 4px; background: var(--sc, var(--app-accent)); opacity: 0.6; pointer-events: none; } -.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; } +.season-band.start { border-top-left-radius: 3px; border-bottom-left-radius: 3px; left: 3px; } +.season-band.end { border-top-right-radius: 3px; border-bottom-right-radius: 3px; right: 3px; } - -/* ---------- v1.8.1:已结束订阅置灰 ---------- */ - -.subcard.is-closed { - opacity: 0.55; - filter: saturate(0.5); - background: var(--surface-2); +.pkg-tabs { display: flex; gap: 6px; margin-bottom: 16px; flex-wrap: wrap; padding-bottom: 10px; border-bottom: 1px solid var(--app-border); } +.pkg-tabs a { + display: inline-flex; align-items: center; gap: 6px; + height: 30px; padding: 0 12px; + border: 1px solid var(--app-border); border-radius: 999px; + background: var(--app-surface); color: var(--app-text-2); + font-size: 0.8125rem; text-decoration: none; } -.subcard.is-closed:hover { opacity: 0.8; } +.pkg-tabs a i { width: 8px; height: 8px; border-radius: 50%; background: var(--tab, var(--app-accent)); display: inline-block; } +.pkg-tabs a em { font-family: var(--app-mono); font-style: normal; font-size: 0.6875rem; color: var(--app-text-3); background: var(--app-surface-2); border-radius: 999px; padding: 0 6px; } +.pkg-tabs a:hover { border-color: var(--app-accent); color: var(--app-text); text-decoration: none; } +.pkg-tabs a.on { background: var(--app-accent-solid); border-color: var(--app-accent-solid); color: #fff; } +.pkg-tabs a.on em { background: rgba(255, 255, 255, 0.2); color: #fff; } +.pkg-tabs a.on i { background: #fff; } +/* ---------- 12. 当日抽屉 ---------- */ -/* ---------- v1.9 bootstrap 收敛 ---------- */ -.list-group-item { background: transparent; } -.task.list-group-item { border-bottom: 1px dashed var(--line-2); } -.task.list-group-item:last-child { border-bottom: 0; } -.quick-form .form-control { max-width: 220px; } - - -/* ---------- v1.9.1:自绘组件对齐 Bootstrap ---------- */ -.cal-grid, .cal-cell { border-color: var(--bs-border-color); } -.cal-cell:hover { background: var(--bs-tertiary-bg); } -.cal-cell.sel { box-shadow: inset 0 0 0 2px var(--bs-primary); } -.cal-cell.today .dnum { background: var(--bs-success); color: #fff; } -.cell-count b { color: var(--bs-success); } -.cal-cell.heat-1 { background: rgba(var(--bs-success-rgb), 0.06); } -.cal-cell.heat-2 { background: rgba(var(--bs-success-rgb), 0.11); } -.cal-cell.heat-3 { background: rgba(var(--bs-success-rgb), 0.16); } -.cal-cell.heat-4 { background: rgba(var(--bs-success-rgb), 0.22); } -.keypad { gap: 8px; } -.keypad .key { height: 50px; font-size: 18px; font-weight: 600; } -.keypad .key-ok { color: #fff; } -.season-band { opacity: 0.6; } - - -/* ============================================================ - v1.9.2 · 墨绿渐变设计系统(覆盖层,保留全部组件类名) - 明暗两套均由 [data-theme] / [data-bs-theme] 双属性驱动 - ============================================================ */ - -:root, :root[data-theme="light"], :root[data-bs-theme="light"] { - --ink-0: #f5f8f6; - --ink-1: #eaf1ed; - --ink-2: #ffffff; - --ink-line: #d7e3dc; - --ink-ink: #10241d; - --ink-ink-2: #2d4a3f; - --ink-muted: #6a8579; - --ink-green: #16543f; - --ink-green-2: #1d7a58; - --ink-green-3: #2fa37a; - --ink-glow: rgba(47, 163, 122, 0.18); - --ink-grad: linear-gradient(135deg, #16543f 0%, #1d7a58 45%, #2fa37a 100%); - --ink-grad-soft: linear-gradient(135deg, rgba(22,84,63,.08), rgba(47,163,122,.04)); - --bs-body-bg: #f5f8f6; - --bs-body-color: #10241d; - --bs-border-color: #d7e3dc; - --bs-secondary-bg: #eaf1ed; - --bs-tertiary-bg: #e2ece6; - --bs-primary: #16543f; - --bs-primary-rgb: 22, 84, 63; - --bs-success: #1d7a58; - --bs-success-rgb: 29, 122, 88; - --bs-link-color: #16543f; - --bs-border-radius: .65rem; +.drawer { + position: sticky; top: 76px; + background: var(--app-surface); + border: 1px solid var(--app-border); + border-radius: var(--app-radius); + max-height: calc(100vh - 96px); + overflow-y: auto; + overscroll-behavior: contain; + box-shadow: var(--app-shadow-sm); } - -:root[data-theme="dark"], :root[data-bs-theme="dark"] { - --ink-0: #0b1512; - --ink-1: #101d18; - --ink-2: #14241e; - --ink-line: #1f352c; - --ink-ink: #e6f2ec; - --ink-ink-2: #b9d2c6; - --ink-muted: #7f9c8f; - --ink-green: #2fa37a; - --ink-green-2: #3dbd90; - --ink-green-3: #63d6ad; - --ink-glow: rgba(63, 214, 173, 0.22); - --ink-grad: linear-gradient(135deg, #0d3a2b 0%, #145841 45%, #1d7a58 100%); - --ink-grad-soft: linear-gradient(135deg, rgba(29,122,88,.18), rgba(63,214,173,.06)); - --bs-body-bg: #0b1512; - --bs-body-color: #e6f2ec; - --bs-border-color: #1f352c; - --bs-secondary-bg: #101d18; - --bs-tertiary-bg: #16261f; - --bs-primary: #3dbd90; - --bs-primary-rgb: 61, 189, 144; - --bs-success: #3dbd90; - --bs-success-rgb: 61, 189, 144; - --bs-link-color: #63d6ad; +.drawer-clock { + position: sticky; top: 0; z-index: 3; + display: flex; align-items: center; justify-content: space-between; gap: 8px; + height: 46px; padding: 0 14px; + background: var(--app-surface-2); + border-bottom: 1px solid var(--app-border); } +.drawer-clock b { font-family: var(--app-mono); font-size: 1.125rem; font-weight: 700; letter-spacing: 0.06em; color: var(--app-text); font-variant-numeric: tabular-nums; } +.drawer-clock span { font-family: var(--app-mono); font-size: 0.6875rem; color: var(--app-text-3); } -body { - background: - radial-gradient(1200px 520px at 12% -10%, rgba(47,163,122,.16), transparent 60%), - radial-gradient(900px 420px at 100% 0%, rgba(22,84,63,.14), transparent 55%), - var(--ink-0) !important; - color: var(--ink-ink) !important; - font-family: "Inter", "PingFang SC", "Microsoft YaHei UI", system-ui, sans-serif; - -webkit-font-smoothing: antialiased; +.drawer-head { + position: sticky; top: 46px; z-index: 2; + display: flex; align-items: center; justify-content: space-between; gap: 8px; + padding: 10px 14px; + background: var(--app-surface); + border-bottom: 1px solid var(--app-border); } +.drawer-head h2 { font-size: 0.875rem; font-family: var(--app-mono); margin: 0; } +.drawer-tags { display: flex; gap: 4px; flex-wrap: wrap; justify-content: flex-end; } -a { color: var(--bs-link-color); text-decoration: none; } -a:hover { color: var(--ink-green-3); } - -/* 顶栏 */ -.navbar { - background: linear-gradient(90deg, rgba(22,84,63,.10), rgba(47,163,122,.05)) !important; - backdrop-filter: blur(10px); - border-bottom: 1px solid var(--ink-line) !important; +.drawer-block { padding: 14px; border-bottom: 1px solid var(--app-border); } +.drawer-block:last-child { border-bottom: 0; } +.drawer-block h3 { + display: flex; align-items: center; gap: 6px; flex-wrap: wrap; + margin: 0 0 10px; + font-size: 0.75rem; font-weight: 600; letter-spacing: 0.03em; color: var(--app-text-3); } -.navbar-brand { - background: var(--ink-grad); - -webkit-background-clip: text; background-clip: text; color: transparent !important; - font-weight: 800; letter-spacing: .04em; +.drawer-block h3 em { + font-family: var(--app-mono); font-style: normal; font-size: 0.6875rem; + background: var(--app-surface-2); border: 1px solid var(--app-border); + border-radius: 999px; padding: 0 7px; color: var(--app-text-2); } -.navbar .nav-link { color: var(--ink-ink-2) !important; border-radius: .5rem; padding: .35rem .7rem !important; } -.navbar .nav-link:hover { background: var(--ink-grad-soft); color: var(--ink-ink) !important; } -.navbar .nav-link.active { - color: #fff !important; - background: var(--ink-grad); - box-shadow: 0 6px 18px -8px var(--ink-glow); +.drawer-block h3 > span.muted { margin-left: auto; font-size: 0.6875rem; font-weight: 400; } +.drawer-block p { margin: 0; font-size: 0.75rem; color: var(--app-text-3); } + +.quick-form { display: flex; gap: 6px; margin-bottom: 6px; align-items: center; } +.quick-form input[type="text"], .quick-form .form-control { + flex: 1 1 auto; min-width: 0; height: 32px; padding: 0 9px; + border: 1px solid var(--app-border); border-radius: 7px; + background: var(--app-surface); color: var(--app-text); + font: inherit; font-size: 0.8125rem; font-family: var(--app-mono); } -.navbar .form-control { - background: var(--ink-2); border-color: var(--ink-line); color: var(--ink-ink); +.quick-form input:focus { + outline: none; border-color: var(--app-accent); box-shadow: 0 0 0 3px var(--app-ring); } -.navbar .form-control:focus { border-color: var(--ink-green-3); box-shadow: 0 0 0 .2rem var(--ink-glow); } +.quick-hint { margin: 0 0 10px !important; font-size: 0.6875rem; line-height: 1.45; } -/* 徽标 */ -.badge.text-bg-danger { background: linear-gradient(135deg, #8f2f2a, #c4453d) !important; } -.badge.text-bg-warning { background: linear-gradient(135deg, #8a5a14, #c98a2c) !important; color: #fff !important; } -.badge.text-bg-secondary { background: var(--ink-1) !important; color: var(--ink-ink-2) !important; border: 1px solid var(--ink-line); } +.picker { margin: 8px 0 12px; padding: 10px; border: 1px solid var(--app-accent); border-radius: var(--app-radius-sm); background: var(--app-surface-2); } +.picker-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; } +.picker-head h4 { font-size: 0.8125rem; } +.pick-list { list-style: none; margin: 0 0 8px; padding: 0; } +.pick-list li { border-bottom: 1px dashed var(--app-border); } +.pick-list li:last-child { border-bottom: 0; } +.pick-list label { display: flex; align-items: center; gap: 7px; padding: 6px 0; cursor: pointer; font-size: 0.75rem; } +.pick-list input[type="checkbox"] { accent-color: var(--app-accent); } +.pick-pkg { color: var(--app-text-2); } +.pick-remain { margin-left: auto; font-family: var(--app-mono); color: var(--app-text-3); font-size: 0.6875rem; } -/* 按钮 */ -.btn { border-radius: .55rem; font-weight: 600; transition: transform .08s ease, box-shadow .15s ease, background .15s ease; } -.btn:active { transform: translateY(1px); } -.btn-primary, .btn-success { - background: var(--ink-grad) !important; border: 0 !important; color: #fff !important; - box-shadow: 0 8px 20px -12px var(--ink-glow); -} -.btn-primary:hover, .btn-success:hover { filter: brightness(1.08); } -.btn-outline-secondary { border-color: var(--ink-line); color: var(--ink-ink-2); } -.btn-outline-secondary:hover { background: var(--ink-grad-soft); border-color: var(--ink-green-3); color: var(--ink-ink); } -.btn-ghost { background: transparent; border: 1px solid transparent; color: var(--ink-ink-2); } -.btn-ghost:hover { background: var(--ink-1); border-color: var(--ink-line); } -.btn-danger, .btn-urgent { background: linear-gradient(135deg, #8f2f2a, #c4453d) !important; border: 0; color: #fff; } +.due-list, .task-list, .mini-due, .checkin-list { list-style: none; margin: 0; padding: 0; } +.due-list li { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 6px 0; border-bottom: 1px dashed var(--app-border); } +.due-list li:last-child { border-bottom: 0; } +.due-main { display: flex; align-items: center; gap: 7px; min-width: 0; color: var(--app-text); } +.due-main .pkg, .task-meta .pkg { font-size: 0.6875rem; color: var(--app-text-3); } +.due-main .rem { font-family: var(--app-mono); font-size: 0.6875rem; color: var(--app-danger); } -/* 卡片与面板 */ -.card, .panel, .table-wrap, .drawer, .section.card, .form.card, .minical { - background: var(--ink-2) !important; - border: 1px solid var(--ink-line) !important; - border-radius: 1rem !important; - box-shadow: 0 18px 40px -32px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.04); -} -.card-header, .panel-head { background: var(--ink-grad-soft) !important; border-color: var(--ink-line) !important; } - -/* 表格 */ -.table { --bs-table-bg: transparent; --bs-table-color: var(--ink-ink); margin: 0; } -.table thead th { background: var(--ink-1) !important; color: var(--ink-muted) !important; font-size: .75rem; letter-spacing: .06em; text-transform: uppercase; border-color: var(--ink-line) !important; } -.table td { border-color: var(--ink-line) !important; vertical-align: middle; } -.table tbody tr:hover { background: var(--ink-grad-soft); } - -/* 表单 */ -.form-control, .form-select { - background: var(--ink-2); border-color: var(--ink-line); color: var(--ink-ink); border-radius: .55rem; -} -.form-control:focus, .form-select:focus { border-color: var(--ink-green-3); box-shadow: 0 0 0 .2rem var(--ink-glow); background: var(--ink-2); color: var(--ink-ink); } -.form-label, label > span { color: var(--ink-ink-2); font-weight: 600; font-size: .8rem; } - -/* 日历 */ -.cal-grid { border-radius: 1rem; overflow: hidden; background: var(--ink-2); } -.cal-dow { background: var(--ink-1); border-color: var(--ink-line); } -.cal-cell { border-color: var(--ink-line); color: var(--ink-ink); } -.cal-cell:hover { background: var(--ink-grad-soft); } -.cal-cell.out { opacity: .35; } -.cal-cell.sel { box-shadow: inset 0 0 0 2px var(--ink-green-3); } -.cal-cell.today .dnum { background: var(--ink-grad); color: #fff; box-shadow: 0 6px 16px -8px var(--ink-glow); } -.cal-cell.heat-1 { background: rgba(47,163,122,.07); } -.cal-cell.heat-2 { background: rgba(47,163,122,.12); } -.cal-cell.heat-3 { background: rgba(47,163,122,.18); } -.cal-cell.heat-4 { background: rgba(47,163,122,.26); } -.cell-count b { color: var(--ink-green-3); font-weight: 800; } -.cell-count span { color: var(--ink-muted); } - -/* 抽屉 */ -.drawer { backdrop-filter: blur(6px); } -.drawer-head { background: var(--ink-grad-soft) !important; border-color: var(--ink-line) !important; } -.drawer-clock b { background: var(--ink-grad); -webkit-background-clip: text; background-clip: text; color: transparent; } -.task.list-group-item { border-color: var(--ink-line) !important; } -.task.urgent { background: linear-gradient(90deg, rgba(196,69,61,.14), transparent); } -.ticket { border-radius: .35rem; } - -/* 小键盘 */ -.keypad .key { background: var(--ink-2); border-color: var(--ink-line); color: var(--ink-ink); height: 52px; border-radius: .75rem; } -.keypad .key:hover { border-color: var(--ink-green-3); color: var(--ink-green-3); transform: translateY(-1px); } -.keypad .key-ok { background: var(--ink-grad); border: 0; color: #fff; box-shadow: 0 10px 24px -12px var(--ink-glow); } - -/* 标签页 */ -.pkg-tabs a, .scr-tabs a { background: var(--ink-2); border-color: var(--ink-line); color: var(--ink-ink-2); border-radius: 999px; } -.pkg-tabs a.on, .scr-tabs a.on { background: var(--ink-grad); border-color: transparent; color: #fff; } - -/* 提示与弹层 */ -.alert { border-radius: .75rem; border: 1px solid var(--ink-line); } -.flash { background: var(--ink-grad) !important; color: #fff; border-radius: .75rem; } -.dropdown-menu { background: var(--ink-2); border-color: var(--ink-line); border-radius: .75rem; box-shadow: 0 20px 40px -24px rgba(0,0,0,.6); } -.dropdown-item { color: var(--ink-ink-2); } -.dropdown-item:hover { background: var(--ink-grad-soft); color: var(--ink-ink); } - -/* 滚动条 */ -* { scrollbar-color: var(--ink-green-3) transparent; scrollbar-width: thin; } -::-webkit-scrollbar { width: 10px; height: 10px; } -::-webkit-scrollbar-thumb { background: var(--ink-green-2); border-radius: 999px; border: 2px solid var(--ink-0); } - -/* 大屏同步 */ -.scr { background: transparent; } -.scr-brand .mark { background: var(--ink-grad); } -.scr-kpis .kpi { background: var(--ink-2); border-color: var(--ink-line); border-radius: 1rem; } -.kpi b { background: var(--ink-grad); -webkit-background-clip: text; background-clip: text; color: transparent; } -.heat-cell { border-color: var(--ink-line); background: var(--ink-1); } -.heat-cell.h1 { background: rgba(47,163,122,.14); } -.heat-cell.h2 { background: rgba(47,163,122,.26); } -.heat-cell.h3 { background: rgba(47,163,122,.40); } -.heat-cell.h4 { background: rgba(47,163,122,.58); } - - -/* ---------- v1.9.3 fixes:布局与对比度 ---------- */ - -/* 日历头部:左右两组垂直居中 */ -.cal-head, .cal-toolbar, .month-nav { - display: flex; align-items: center; gap: 10px; - flex-wrap: wrap; min-height: 40px; -} -.cal-head h1, .month-nav h1, .month-nav b { margin: 0; line-height: 1.2; display: inline-flex; align-items: center; } -.cal-head .btn, .month-nav .btn, .cal-toolbar .btn { align-self: center; } - -/* 任务卡:正文与操作按钮分两列,按钮横排、不换行 */ .task { - display: flex !important; align-items: flex-start; gap: 10px; - flex-wrap: nowrap; padding-block: 10px; + display: flex !important; align-items: flex-start; gap: 10px; flex-wrap: nowrap; + padding: 9px 0; border-bottom: 1px dashed var(--app-border); + border-left: 3px solid var(--asg, transparent); + padding-left: 8px; margin-left: -3px; +} +.task:last-child { border-bottom: 0; } +.task.urgent { + border-left-color: var(--app-danger); + background: linear-gradient(90deg, color-mix(in srgb, var(--app-danger) 7%, transparent), transparent); + border-radius: 0 6px 6px 0; } .task-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; } -.task-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; margin-left: auto; } +.task-title { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; color: var(--app-text); font-weight: 600; text-decoration: none; min-width: 0; } +.task-title:hover { color: var(--app-accent); text-decoration: none; } +.task-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 0.6875rem; color: var(--app-text-3); } +.task-note { font-size: 0.6875rem; color: var(--app-text-2); margin-top: 1px; } +.task-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 4px; margin-left: auto; } .task-actions form { display: inline-flex; margin: 0; } -.task-actions .btn, .task .btn { white-space: nowrap; padding-block: .2rem; } -.task-title { - color: var(--ink-ink) !important; font-weight: 650; text-decoration: none; - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; -} -.task-title:hover { color: var(--ink-green-3) !important; } -.task-meta, .task-sub, .task-note, .task .muted { - color: var(--ink-muted) !important; font-size: .78rem; -} -.task.urgent .task-title { color: #f08a82 !important; } +.task.task-done { padding: 6px 0; } +.task.done .task-title, .task.skipped .task-title { opacity: 0.55; } -/* 标签/徽记:浅底深字,任何主题都读得清 */ -.tagchip, .chip, .pkg-tag, .tag { - display: inline-flex; align-items: center; gap: 4px; - padding: 1px 8px; border-radius: 999px; - background: rgba(47,163,122,.16) !important; - border: 1px solid rgba(47,163,122,.38) !important; - color: var(--ink-ink) !important; - font-size: .72rem; font-weight: 600; line-height: 1.7; +.ticket { + display: inline-flex; align-items: baseline; gap: 3px; + max-width: 100%; min-width: 0; + padding: 1px 6px; + font-size: 0.6875rem; line-height: 1.5; + border-left: 3px solid var(--tag, var(--app-accent)); + background: color-mix(in srgb, var(--tag, var(--app-accent)) 12%, var(--app-surface)); + border-radius: 2px 6px 6px 2px; + white-space: nowrap; color: var(--app-text-2); } -[data-theme="dark"] .tagchip, [data-theme="dark"] .chip, -[data-bs-theme="dark"] .tagchip, [data-bs-theme="dark"] .chip { - background: rgba(99,214,173,.18) !important; - border-color: rgba(99,214,173,.42) !important; - color: #d8f5e9 !important; -} -[data-theme="dark"] .tagchip, [data-bs-theme="dark"] .tagchip { color: #bfe9d8 !important; } +.ticket b { font-family: var(--app-mono); font-size: 0.75rem; color: var(--app-text); flex: 0 0 auto; } +.ticket .tk-name { overflow: hidden; text-overflow: ellipsis; } +.ticket i { font-style: normal; font-size: 0.625rem; color: var(--app-text-3); flex: 0 0 auto; } +.ticket.done { opacity: 0.6; } +.ticket.done::after { content: "✓"; color: var(--app-accent); font-size: 0.625rem; } +.ticket.skipped { opacity: 0.45; text-decoration: line-through; } +.ticket.more { border-left-color: var(--app-border); background: var(--app-surface-2); color: var(--app-text-3); } +.ticket.urgent { border-left-color: var(--app-danger); } -/* 套餐表格行操作按钮 */ -.actions, .row-actions, td .actions { - display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; -} -.actions form, .row-actions form { display: inline-flex; margin: 0; } -.actions .btn, .row-actions .btn, td .btn { white-space: nowrap; padding-block: .2rem; } +.mini-due li { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 8px; align-items: center; padding: 4px 0; font-size: 0.75rem; } +.sm-date { font-family: var(--app-mono); font-size: 0.6875rem; color: var(--app-text-3); } -/* 订阅卡动作行 */ -.subcard-foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } -.subcard-foot form { display: inline-flex; margin: 0; } +/* 完成折叠 */ +.done-fold > summary { display: flex; align-items: center; gap: 6px; list-style: none; cursor: pointer; padding: 1px 0; min-width: 0; } +.done-fold > summary::-webkit-details-marker { display: none; } +.done-fold > summary .pkg { font-size: 0.6875rem; color: var(--app-text-3); } +.done-fold > summary .fold-caret { + margin-left: auto; width: 0; height: 0; + border-left: 5px solid transparent; border-right: 5px solid transparent; + border-top: 6px solid var(--app-text-3); transition: transform 0.15s ease; +} +.done-fold > summary:hover .fold-caret { border-top-color: var(--app-accent); } +.done-fold[open] > summary .fold-caret { transform: rotate(180deg); } +.done-detail { margin: 6px 0 2px; padding-left: 9px; border-left: 1px dashed var(--app-border); } -/* 表格正文对比度 */ -.table td, .table td a, .table td strong { color: var(--ink-ink); } -.table td .muted, .table td small { color: var(--ink-muted) !important; } +/* ---------- 13. 追加面板 ---------- */ -/* v1.9.3 对比度补丁 */ -.badge.text-bg-success { background: #1d7a58 !important; color: #f2fffa !important; } -.table thead th { color: #4f6a5e !important; } -[data-bs-theme="dark"] .table thead th, [data-theme="dark"] .table thead th { color: var(--ink-muted) !important; } -.task-meta, .task-sub, .task-note { color: #55705f !important; } -[data-bs-theme="dark"] .task-meta, [data-theme="dark"] .task-meta, -[data-bs-theme="dark"] .task-sub, [data-theme="dark"] .task-sub { color: #9dbbac !important; } -/* ---------- v1.9.5:修复 asg / ticket / summary 文字反色 ---------- */ -.asg, .asg-name, .asg b, .asg span, -.ticket, .ticket b, .ticket span, .ticket em, -.summary, .summary b, .summary span, .summary em, -.summary .due, .summary .due-ok, .summary .due-soon { - color: var(--ink-ink) !important; +.extend { + margin-top: 18px; + background: var(--app-surface); + border: 1px solid var(--app-accent); + border-radius: var(--app-radius); + box-shadow: var(--app-shadow); + scroll-margin-top: 84px; + overflow: hidden; } -.asg i, .asg .dot { border-color: var(--ink-line) !important; } -.summary .due-ok { color: var(--bs-success) !important; font-weight: 700; } -.summary .due-soon { color: #b8792a !important; } -.summary .due-late { color: #c4453d !important; } -.ticket { background: var(--ink-grad-soft) !important; border: 1px solid var(--ink-line); } -[data-theme="dark"] .asg, [data-theme="dark"] .asg-name, [data-theme="dark"] .asg b, [data-theme="dark"] .asg span, -[data-theme="dark"] .ticket, [data-theme="dark"] .ticket b, [data-theme="dark"] .ticket span, [data-theme="dark"] .ticket em, -[data-theme="dark"] .summary, [data-theme="dark"] .summary b, [data-theme="dark"] .summary span, [data-theme="dark"] .summary em, -[data-bs-theme="dark"] .asg, [data-bs-theme="dark"] .asg-name, [data-bs-theme="dark"] .asg b, [data-bs-theme="dark"] .asg span, -[data-bs-theme="dark"] .ticket, [data-bs-theme="dark"] .ticket b, [data-bs-theme="dark"] .ticket span, [data-bs-theme="dark"] .ticket em, -[data-bs-theme="dark"] .summary, [data-bs-theme="dark"] .summary b, [data-bs-theme="dark"] .summary span, [data-bs-theme="dark"] .summary em { - color: #dff5ea !important; +.drawer .extend { margin: 12px; } +.extend-head { + display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; + padding: 12px 14px; + background: var(--app-accent-soft); + border-bottom: 1px solid color-mix(in srgb, var(--app-accent) 24%, transparent); } -[data-theme="dark"] .summary .due-ok, [data-bs-theme="dark"] .summary .due-ok { color: #63d6ad !important; } -/* ---------- v1.9.5:表格操作列高度与行高对齐 ---------- */ -.table > tbody > tr > td { padding-block: .55rem; vertical-align: middle; } -.table td .actions, td .actions, .actions { - display: inline-flex !important; - align-items: center; - gap: 6px; - flex-wrap: nowrap; - vertical-align: middle; - line-height: 1; +.extend-head h3 { font-size: 0.875rem; } +.extend-head p { margin: 2px 0 0; font-size: 0.6875rem; color: var(--app-text-2); } +.extend-close { font-size: 1.25rem; line-height: 1; color: var(--app-text-2); padding: 0 4px; } +.extend-close:hover { color: var(--app-danger); text-decoration: none; } +.extend-form { padding: 14px; } + +.extend-preview { + display: grid; grid-template-columns: repeat(auto-fit, minmax(124px, 1fr)); gap: 8px; + padding: 10px; margin-bottom: 10px; + background: var(--app-surface-2); border: 1px dashed var(--app-border-strong); + border-radius: var(--app-radius-sm); } -.actions > * , .actions .btn, .actions a, .actions form { margin: 0 !important; } -.actions .btn, td .actions .btn { - padding: .18rem .55rem; - line-height: 1.35; - font-size: .8rem; - vertical-align: middle; +.extend-preview div { display: flex; flex-direction: column; gap: 1px; min-width: 0; } +.extend-preview span { font-size: 0.625rem; color: var(--app-text-3); } +.extend-preview b { font-family: var(--app-mono); font-size: 0.8125rem; font-weight: 600; color: var(--app-text); } +.extend-preview b i { font-style: normal; font-size: 1.125rem; color: var(--app-accent); } +.extend-preview .add b { color: var(--app-accent); } +.extend-hint { margin: 0 0 10px; font-size: 0.6875rem; color: var(--app-text-3); line-height: 1.5; } + +.minis { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 12px; } +.drawer .minis { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; } +.minimonth { border: 1px solid var(--app-border); border-radius: var(--app-radius-sm); padding: 8px; background: var(--app-surface-2); } +.minimonth-title { font-size: 0.6875rem; font-weight: 600; color: var(--app-text-2); margin-bottom: 6px; text-align: center; } +.minimonth-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; } +.minimonth-grid .dow { text-align: center; font-size: 0.625rem; color: var(--app-text-3); } +.miniday { + height: 28px; padding: 0; border: 1px solid transparent; border-radius: 5px; + background: transparent; color: var(--app-text-2); + font: inherit; font-size: 0.6875rem; font-family: var(--app-mono); cursor: pointer; +} +.drawer .miniday { height: 25px; font-size: 0.625rem; } +.miniday:hover:not(:disabled) { background: var(--app-accent-soft); border-color: color-mix(in srgb, var(--app-accent) 30%, transparent); } +.miniday.out { color: color-mix(in srgb, var(--app-text-3) 70%, transparent); } +.miniday.old { background: var(--app-warn-soft); border-color: color-mix(in srgb, var(--app-warn) 32%, transparent); color: var(--app-warn); font-weight: 700; } +.miniday.today { outline: 1px solid var(--app-accent); outline-offset: -1px; } +.miniday.picked { background: var(--app-accent-solid); border-color: var(--app-accent-solid); color: #fff; font-weight: 700; } +.miniday:disabled { color: color-mix(in srgb, var(--app-text-3) 45%, transparent); cursor: not-allowed; } + +/* ---------- 14. 客户月历 ---------- */ + +.minical { + width: 100%; + background: var(--app-surface); + border: 1px solid var(--app-border); + border-radius: var(--app-radius); + overflow: hidden; + box-shadow: var(--app-shadow-sm); +} +.minical-dow { display: grid; grid-template-columns: repeat(7, 1fr); background: var(--app-surface-2); border-bottom: 1px solid var(--app-border); } +.minical-dow span { padding: 6px 0; text-align: center; font-size: 0.6875rem; color: var(--app-text-3); } +.minical-row { display: grid; grid-template-columns: repeat(7, 1fr); grid-auto-rows: 58px; } +.minical-cell { + position: relative; + display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px; + min-height: 0; border-right: 1px solid var(--app-border); border-bottom: 1px solid var(--app-border); + color: var(--app-text); text-decoration: none; font-size: 0.75rem; +} +.minical-cell:nth-child(7n) { border-right: 0; } +.minical-row:last-child .minical-cell { border-bottom: 0; } +.minical-cell:hover { background: var(--app-surface-2); text-decoration: none; } +.minical-cell.out { background: var(--app-surface-2); } +.minical-cell.out .mc-day, .minical-cell.out .mc-count { color: var(--app-text-3); } +.minical-cell.today { box-shadow: inset 0 0 0 2px var(--app-accent); } +.minical-cell.no-task { color: var(--app-text-3); } +.minical-cell .mc-day { font-family: var(--app-mono); font-size: 0.75rem; font-weight: 600; } +.minical-cell .mc-count { font-family: var(--app-mono); font-size: 0.625rem; line-height: 1; color: var(--app-accent); } +.minical-cell.all-done .mc-count, .minical-cell.all-done .mc-day { color: var(--app-text-3); } +.minical-cell.today .mc-day { color: var(--app-accent-ink); font-weight: 700; } +.minical-nav { display: flex; align-items: center; gap: 8px; } +.minical-nav b { font-family: var(--app-mono); font-size: 0.8125rem; } + +/* ---------- 15. 设置页杂项 ---------- */ + +.kv { display: grid; gap: 8px; margin: 0 0 16px; } +.kv > div { display: grid; grid-template-columns: 92px 1fr; gap: 12px; align-items: baseline; } +.kv dt { font-size: 0.6875rem; font-weight: 600; color: var(--app-text-3); } +.kv dd { margin: 0; font-size: 0.75rem; color: var(--app-text-2); word-break: break-all; } +.backups { margin-top: 14px; font-size: 0.75rem; } +.backups summary { cursor: pointer; color: var(--app-text-2); } +.backups ul { margin: 8px 0 0; padding-left: 18px; color: var(--app-text-3); } + +.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--app-border); margin-bottom: 16px; flex-wrap: wrap; } +.tabs a { padding: 7px 12px; font-size: 0.8125rem; color: var(--app-text-2); border-bottom: 2px solid transparent; margin-bottom: -1px; } +.tabs a:hover { color: var(--app-text); text-decoration: none; } +.tabs a.on { color: var(--app-accent-ink); border-bottom-color: var(--app-accent); font-weight: 600; } + +.color-form { display: flex; align-items: center; gap: 6px; } +.color-form input[type="color"] { width: 34px; height: 26px; padding: 1px; border: 1px solid var(--app-border); border-radius: 6px; background: var(--app-surface); cursor: pointer; } + +/* 卡片 */ +.card, .section.card, .form.card { + --bs-card-bg: var(--app-surface); + --bs-card-border-color: var(--app-border); + --bs-card-border-radius: var(--app-radius); + --bs-card-inner-border-radius: calc(var(--app-radius) - 1px); + --bs-card-cap-bg: var(--app-surface-2); + --bs-card-color: var(--app-text); + box-shadow: var(--app-shadow-sm); +} +.section.card, .form.card { padding: 20px; } +.card-header { border-bottom-color: var(--app-border); } +.section.card .table-wrap { box-shadow: none; border-radius: var(--app-radius-sm); } + +/* ---------- 16. 认证页 ---------- */ + +.auth { min-height: 100vh; display: grid; place-items: center; padding: 40px 16px; } +.auth-card { + width: 100%; max-width: 400px; + background: var(--app-surface); + border: 1px solid var(--app-border); + border-radius: 18px; + padding: 28px; + box-shadow: var(--app-shadow); +} +.auth-brand { display: flex; align-items: center; gap: 8px; font-weight: 800; letter-spacing: 0.06em; margin-bottom: 22px; } +.auth-brand .brand-mark { width: 30px; height: 30px; } +.auth-card h1 { font-size: 1.25rem; margin-bottom: 4px; } +.auth-card > p { margin: 0 0 18px; font-size: 0.75rem; color: var(--app-text-3); } +.auth-foot { margin: 16px 0 0; text-align: center; font-size: 0.6875rem; color: var(--app-text-3); } +.auth-card .btn-block { margin-top: 4px; } + +/* ---------- 17. 小屏适配 ---------- */ + +@media (max-width: 1180px) { + .cal-body { grid-template-columns: minmax(0, 1fr); } + .drawer { position: static; max-height: none; } + .drawer-clock, .drawer-head { position: static; } + .cust-head { grid-template-columns: minmax(0, 1fr); } + .cust-head .page-actions { grid-column: auto; grid-row: auto; justify-content: flex-start; } +} + +@media (max-width: 1199.98px) { + .app-navbar .navbar-collapse { padding-top: 0.5rem; } + .nav-search { width: 100%; margin: 6px 0; } + .nav-side { width: 100%; margin: 6px 0 2px; flex-wrap: wrap; } +} + +@media (max-width: 992px) { + .app-main { padding: 16px 16px 36px; } + .cal-stats { gap: 12px; } +} + +@media (max-width: 860px) { + .grid-2, .grid-3 { grid-template-columns: 1fr; } + .cal-grid { overflow-x: auto; } + .cal-week, .cal-dow { min-width: 640px; } + .cal-cell { min-height: 96px; } + .cust-tail { font-size: 1.375rem; } + .tl-row { grid-template-columns: 76px 1fr; } + .tl-legend { display: none; } + .tl-axis { margin-right: 0; } + .extend-preview { grid-template-columns: 1fr 1fr; } + .page-head { align-items: flex-start; } + .app-foot { padding: 16px; } +} + +@media (max-width: 640px) { + .app-main { padding: 12px 12px 32px; } + .page-head h1, h1 { font-size: 1.25rem; } + .cal-head { gap: 10px; } + .cal-title h1 { font-size: 1rem; } + .cal-stats { gap: 10px; font-size: 0.6875rem; } + .cal-stats em { font-size: 0.9375rem; } + .cal-week, .cal-dow { min-width: 0; } + .cal-cell { min-height: 64px; padding: 4px 5px 7px 8px; gap: 2px; } + .dnum { font-size: 0.6875rem; } + .due-badge { height: 14px; padding: 0 4px; font-size: 0.5625rem; } + .due-badge em { display: none; } + .cell-load { display: none; } + .cell-count b { font-size: 1rem; } + .cell-count span { font-size: 0.625rem; } + .cal-cell.today .dnum { width: 18px; height: 18px; } + .table { min-width: 0; } + /* 手机上隐藏低优先级列,避免横向滚动 */ + .table-customers th:nth-child(3), .table-customers td:nth-child(3), + .table-customers th:nth-child(5), .table-customers td:nth-child(5) { display: none; } + .table-subs th:nth-child(5), .table-subs td:nth-child(5) { display: none; } + .table-packages th:nth-child(1), .table-packages td:nth-child(1), + .table-packages th:nth-child(4), .table-packages td:nth-child(4), + .table-packages th:nth-child(5), .table-packages td:nth-child(5), + .table-packages th:nth-child(6), .table-packages td:nth-child(6) { display: none; } + .table-logs th:nth-child(4), .table-logs td:nth-child(4), + .table-logs th:nth-child(6), .table-logs td:nth-child(6) { display: none; } + .table-adjust th:nth-child(4), .table-adjust td:nth-child(4), + .table-adjust th:nth-child(5), .table-adjust td:nth-child(5), + .table-adjust th:nth-child(7), .table-adjust td:nth-child(7), + .table-adjust th:nth-child(8), .table-adjust td:nth-child(8) { display: none; } + .table-users th:nth-child(5), .table-users td:nth-child(5), + .table-users th:nth-child(6), .table-users td:nth-child(6), + .table-users th:nth-child(7), .table-users td:nth-child(7) { display: none; } + .table-wrap { -webkit-overflow-scrolling: touch; } + .task { flex-wrap: wrap; } + .task-actions { width: 100%; justify-content: flex-end; margin-left: 0; } + .drawer-block { padding: 12px; } + .minis { grid-template-columns: 1fr; } + .drawer .minis { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .extend-preview { grid-template-columns: 1fr 1fr; } + .cust-id { gap: 10px; } + .cust-tail { font-size: 1.25rem; padding: 8px 10px; } + .cust-id-main h1 { font-size: 1.25rem; } + .cust-meta { grid-template-columns: 1fr 1fr; gap: 10px 14px; } + .inline-form { align-items: stretch; } + .inline-form .inline-field, .inline-form .btn { width: 100%; } + .flash { top: 64px; } + .auth { padding: 24px 14px; } + .auth-card { padding: 22px; } +} + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } } -.table td:last-child { white-space: nowrap; } \ No newline at end of file diff --git a/internal/web/static/app.js b/internal/web/static/app.js index a244ec5..c02d833 100644 --- a/internal/web/static/app.js +++ b/internal/web/static/app.js @@ -2,15 +2,22 @@ (function () { 'use strict'; - /* 提示条自动淡出 */ + /* ---------- 工具:本地日期,禁止用 toISOString 做“自然日”计算 ---------- */ + function pad2(n) { return n < 10 ? '0' + n : '' + n; } + function parseDay(s) { return new Date(s + 'T00:00:00'); } + function fmtDay(d) { return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate()); } + function addDays(s, n) { var d = parseDay(s); d.setDate(d.getDate() + n); return fmtDay(d); } + function diffDays(a, b) { return Math.round((b.getTime() - a.getTime()) / 86400000); } + + /* ---------- 提示条:自动淡出(.flash 固定定位,不会顶动页面) ---------- */ document.querySelectorAll('[data-flash]').forEach(function (el) { setTimeout(function () { el.classList.add('hide'); - setTimeout(function () { el.remove(); }, 450); + setTimeout(function () { el.remove(); }, 400); }, 4200); }); - /* 按 / 聚焦搜索框 */ + /* ---------- 按 / 聚焦搜索框 ---------- */ document.addEventListener('keydown', function (e) { if (e.key !== '/' || e.metaKey || e.ctrlKey || e.altKey) return; var tag = (document.activeElement && document.activeElement.tagName) || ''; @@ -19,17 +26,14 @@ if (s) { e.preventDefault(); s.focus(); s.select(); } }); - /* 危险操作确认 */ + /* ---------- 危险操作确认 ---------- */ document.querySelectorAll('form[data-confirm]').forEach(function (f) { f.addEventListener('submit', function (e) { if (!window.confirm(f.getAttribute('data-confirm'))) e.preventDefault(); }); }); - /* 追加时长面板:点日历挑终止日 + 实时反算 */ - function parseDay(s) { return new Date(s + 'T00:00:00'); } - function diffDays(a, b) { return Math.round((b.getTime() - a.getTime()) / 86400000); } - + /* ---------- 追加时长面板:点日历挑终止日 + 实时反算 ---------- */ document.querySelectorAll('form[data-extend]').forEach(function (form) { var base = parseDay(form.getAttribute('data-base')); var today = parseDay(form.getAttribute('data-today')); @@ -63,7 +67,7 @@ if (input.value) paint(input.value); }); - /* 新订阅表单:按套餐类型切换「天数 / 次数 / 终止日」 */ + /* ---------- 新订阅表单:按套餐类型切换「天数 / 次数 / 终止日」 ---------- */ document.querySelectorAll('form[data-sub-form]').forEach(function (form) { var sel = form.querySelector('[data-pkg-select]'); var daysWrap = form.querySelector('[data-days-wrap]'); @@ -71,14 +75,23 @@ var endWrap = form.querySelector('[data-end-wrap]'); var startWrap = form.querySelector('[data-start-wrap]'); var startInput = form.querySelector('input[name="start_date"]'); + var endInput = endWrap && endWrap.querySelector('input'); if (!sel) return; + /* 只覆盖“上一次自动填的”终止日,不碰用户手填的值 */ + function setAutoEnd(v) { + if (!endInput || !v) return; + if (!endInput.value || endInput.dataset.auto === endInput.value) { + endInput.value = v; + endInput.dataset.auto = v; + } + } + function sync() { var opt = sel.options[sel.selectedIndex]; var unit = opt && opt.getAttribute('data-unit'); var amount = opt && opt.getAttribute('data-amount'); var validity = opt && parseInt(opt.getAttribute('data-validity') || '0', 10); - var endInput = endWrap && endWrap.querySelector('input'); if (unit === 'season') { if (daysWrap) daysWrap.hidden = true; @@ -96,11 +109,7 @@ if (daysWrap) daysWrap.hidden = true; if (countWrap) { countWrap.hidden = false; if (amount) countWrap.querySelector('input').value = amount; } if (endWrap) endWrap.hidden = false; - if (validity > 0 && endInput && !endInput.value && startInput && startInput.value) { - var d = new Date(startInput.value + 'T00:00:00'); - d.setDate(d.getDate() + validity - 1); - endInput.value = d.toISOString().slice(0, 10); - } + if (validity > 0 && startInput && startInput.value) setAutoEnd(addDays(startInput.value, validity - 1)); } else if (unit === 'day') { if (daysWrap) { daysWrap.hidden = false; if (amount) daysWrap.querySelector('input').value = amount; } if (countWrap) countWrap.hidden = true; @@ -111,11 +120,21 @@ if (endWrap) endWrap.hidden = true; } } + sel.addEventListener('change', sync); + if (startInput) { + startInput.addEventListener('change', function () { + var opt = sel.options[sel.selectedIndex]; + var validity = opt && parseInt(opt.getAttribute('data-validity') || '0', 10); + if (opt && opt.getAttribute('data-unit') === 'count' && validity > 0 && startInput.value) { + setAutoEnd(addDays(startInput.value, validity - 1)); + } + }); + } sync(); }); - /* 套餐表单:切换默认量单位文案 */ + /* ---------- 套餐表单:切换默认量单位文案 ---------- */ document.querySelectorAll('form[data-package-form]').forEach(function (form) { var label = form.querySelector('[data-unit-label]'); var radios = form.querySelectorAll('input[name="unit"]'); @@ -134,31 +153,59 @@ radios.forEach(function (r) { r.addEventListener('change', sync); }); sync(); }); + + /* ---------- 批量开通:表头全选 + 半选状态 ---------- */ + document.querySelectorAll('[data-select-all]').forEach(function (box) { + var form = box.closest('form') || document; + var boxes = form.querySelectorAll('input[name="customer_ids"]'); + + function refresh() { + var total = 0, checked = 0; + boxes.forEach(function (c) { + if (c.disabled) return; + total++; + if (c.checked) checked++; + }); + box.checked = total > 0 && checked === total; + box.indeterminate = checked > 0 && checked < total; + } + + box.addEventListener('change', function () { + boxes.forEach(function (c) { if (!c.disabled) c.checked = box.checked; }); + box.indeterminate = false; + }); + boxes.forEach(function (c) { c.addEventListener('change', refresh); }); + refresh(); + }); })(); /* ---------- 亮色 / 暗色切换 ---------- - 偏好写 cookie,服务端首屏直接带 data-theme,不闪。 - 没有 cookie 时跟随系统(由 CSS 的 prefers-color-scheme 决定)。 */ + 唯一状态源是 :服务端按 Cookie 直出, + 没有 Cookie 时由 里的内联脚本按系统偏好补上。 */ (function () { 'use strict'; var KEY = 'daili_theme'; + var root = document.documentElement; var btns = document.querySelectorAll('[data-theme-toggle]'); if (!btns.length) return; - function effective() { - var attr = document.documentElement.getAttribute('data-theme'); - if (attr === 'light' || attr === 'dark') return attr; - return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + function current() { + return root.getAttribute('data-bs-theme') === 'dark' ? 'dark' : 'light'; + } + function paint() { + var dark = current() === 'dark'; + btns.forEach(function (b) { b.setAttribute('aria-pressed', dark ? 'true' : 'false'); }); } - Array.prototype.forEach.call(btns, function (btn) { + btns.forEach(function (btn) { btn.addEventListener('click', function () { - var next = effective() === 'dark' ? 'light' : 'dark'; - document.documentElement.setAttribute('data-theme', next); - document.documentElement.setAttribute('data-bs-theme', next); + var next = current() === 'dark' ? 'light' : 'dark'; + root.setAttribute('data-bs-theme', next); document.cookie = KEY + '=' + next + '; path=/; max-age=31536000; SameSite=Lax'; + paint(); }); }); + paint(); })(); /* ---------- 触摸小键盘 + 抽屉时钟 + 更多菜单 ---------- */ @@ -171,14 +218,14 @@ var d = new Date(); var pad = function (n) { return n < 10 ? '0' + n : '' + n; }; var t = pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds()); - Array.prototype.forEach.call(clocks, function (el) { el.textContent = t; }); + clocks.forEach(function (el) { el.textContent = t; }); } if (clocks.length) { tick(); setInterval(tick, 1000); } /* 小键盘:输入手机尾号 关键点:鼠标按下时阻止默认行为,否则按钮会先抢走输入框焦点; 每次按键后再把焦点和光标送回输入框末尾。 */ - Array.prototype.forEach.call(document.querySelectorAll('[data-keypad]'), function (pad) { + document.querySelectorAll('[data-keypad]').forEach(function (pad) { // 键盘是表单的兄弟节点(不在
内),所以先找输入框,再用 input.form 反查表单 var scope = pad.closest('.drawer-block') || pad.parentElement || document; var input = scope.querySelector('input[name="tail"]'); @@ -222,33 +269,20 @@ /* 更多菜单:点开一个时收起其它,点空白处关闭 */ document.addEventListener('click', function (e) { - var opened = document.querySelectorAll('.more-actions[open]'); - Array.prototype.forEach.call(opened, function (d) { + document.querySelectorAll('.more-actions[open]').forEach(function (d) { if (!d.contains(e.target)) { d.removeAttribute('open'); } }); }); })(); - -/* ---------- 全选 + 触控/PC 小键盘模式 ---------- */ +/* ---------- 触控/PC 小键盘模式 ---------- */ (function () { 'use strict'; - /* 批量开通:表头全选 */ - Array.prototype.forEach.call(document.querySelectorAll('[data-select-all]'), function (box) { - box.addEventListener('change', function () { - var form = box.closest('form') || document; - Array.prototype.forEach.call(form.querySelectorAll('input[name="customer_ids"]'), function (c) { - if (!c.disabled) { c.checked = box.checked; } - }); - }); - }); - - /* 触控 / PC 模式:触控时显示键盘并禁用输入框(不唤起系统输入法) */ var KEY = 'daili_keypad_mode'; function applyMode(touch) { - Array.prototype.forEach.call(document.querySelectorAll('[data-keypad]'), function (pad) { + document.querySelectorAll('[data-keypad]').forEach(function (pad) { pad.hidden = !touch; var block = pad.closest('.drawer-block') || document; var input = block.querySelector('input[name="tail"]'); @@ -270,7 +304,7 @@ try { touchMode = localStorage.getItem(KEY) === 'touch'; } catch (err) { } applyMode(touchMode); - Array.prototype.forEach.call(document.querySelectorAll('[data-keypad-toggle]'), function (btn) { + document.querySelectorAll('[data-keypad-toggle]').forEach(function (btn) { btn.addEventListener('click', function () { touchMode = !touchMode; try { localStorage.setItem(KEY, touchMode ? 'touch' : 'pc'); } catch (err) { } diff --git a/internal/web/static/screen.css b/internal/web/static/screen.css index 8ba691d..dcd431d 100644 --- a/internal/web/static/screen.css +++ b/internal/web/static/screen.css @@ -1,82 +1,70 @@ /* ============================================================ - 代打台 · 可视化大屏(记分板) - 默认深色;亮色由系统偏好或 [data-theme="light"] 覆盖 + 代打台 · 可视化大屏(v1.10) + ------------------------------------------------------------ + 只认 ;默认由模板给 dark。 + Bootstrap 只用于重置与栅格,视觉全部在本文件内闭环。 ============================================================ */ -:root { - --bg: #0c0f12; - --panel: #12161b; - --panel-2: #171c22; - --line: #232a32; - --ink: #e9edf1; - --ink-2: #aab4bf; - --muted: #6f7b87; - --accent: #58c1a3; - --accent-2: #8bd9c4; - --danger: #ef6b62; - --warn: #e6ac55; - --ok: #58c1a3; - --glow: 0 0 24px rgba(88, 193, 163, 0.18); +:root, +[data-bs-theme="light"] { + --scr-bg: #eef3ee; + --scr-panel: #ffffff; + --scr-panel-2: #f7faf7; + --scr-line: #dbe5dc; + --scr-text: #0f241d; + --scr-text-2: #33493f; + --scr-muted: #5f7468; + --scr-accent: #1f6b52; + --scr-accent-2: #2f8a69; + --scr-accent-solid: #1f6b52; + --scr-danger: #b3261e; + --scr-warn: #8a5200; + --scr-glow: 0 0 18px rgba(31, 107, 82, 0.12); + --scr-shadow: 0 14px 32px -22px rgba(16, 32, 24, 0.4); } -@media (prefers-color-scheme: light) { - :root:not([data-theme="dark"]) { - --bg: #f4f2ec; - --panel: #ffffff; - --panel-2: #faf9f5; - --line: #e0ddd2; - --ink: #1b1e23; - --ink-2: #4a4f57; - --muted: #8a8f97; - --accent: #2f7d68; - --accent-2: #245c4d; - --danger: #b3302a; - --warn: #a0600f; - --ok: #2f7d68; - --glow: 0 0 18px rgba(47, 125, 104, 0.12); - } -} - -:root[data-theme="light"] { - --bg: #f4f2ec; - --panel: #ffffff; - --panel-2: #faf9f5; - --line: #e0ddd2; - --ink: #1b1e23; - --ink-2: #4a4f57; - --muted: #8a8f97; - --accent: #2f7d68; - --accent-2: #245c4d; - --danger: #b3302a; - --warn: #a0600f; - --ok: #2f7d68; - --glow: 0 0 18px rgba(47, 125, 104, 0.12); +[data-bs-theme="dark"] { + --scr-bg: #0a120f; + --scr-panel: #111a16; + --scr-panel-2: #16211c; + --scr-line: #24352d; + --scr-text: #e8f2ec; + --scr-text-2: #b5c9bd; + --scr-muted: #87a094; + --scr-accent: #5cc4a3; + --scr-accent-2: #7bd7b8; + --scr-accent-solid: #2b7d61; + --scr-danger: #f08a82; + --scr-warn: #e0b072; + --scr-glow: 0 0 24px rgba(92, 196, 163, 0.16); + --scr-shadow: 0 18px 40px -24px rgba(0, 0, 0, 0.8); } * { box-sizing: border-box; } html, body { height: 100%; margin: 0; } -body { +body.screen-body { background: - radial-gradient(1200px 600px at 15% -10%, rgba(47,163,122,.18), transparent 60%), - radial-gradient(900px 500px at 100% 0%, rgba(22,84,63,.22), transparent 55%), - var(--bg); - color: var(--ink); - font-family: "Bahnschrift", "Microsoft YaHei UI", "PingFang SC", system-ui, sans-serif; + radial-gradient(1200px 600px at 12% -10%, color-mix(in srgb, var(--scr-accent) 14%, transparent), transparent 60%), + radial-gradient(900px 500px at 100% 0%, color-mix(in srgb, var(--scr-accent) 8%, transparent), transparent 55%), + var(--scr-bg); + color: var(--scr-text); + font-family: "Microsoft YaHei UI", "PingFang SC", "Hiragino Sans GB", "Noto Sans SC", + system-ui, -apple-system, "Segoe UI", sans-serif; font-size: 14px; + line-height: 1.45; overflow: hidden; + -webkit-font-smoothing: antialiased; } -/* 背景:极淡的网格与光晕 */ -body::before { +body.screen-body::before { content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none; background: - radial-gradient(1200px 600px at 20% -10%, color-mix(in srgb, var(--accent) 8%, transparent), transparent 60%), - linear-gradient(var(--line) 1px, transparent 1px) 0 0 / 100% 40px, - linear-gradient(90deg, var(--line) 1px, transparent 1px) 0 0 / 40px 100%; - opacity: 0.5; + linear-gradient(var(--scr-line) 1px, transparent 1px) 0 0 / 100% 44px, + linear-gradient(90deg, var(--scr-line) 1px, transparent 1px) 0 0 / 44px 100%; + opacity: 0.35; } .scr { @@ -85,69 +73,65 @@ body::before { grid-template-rows: auto auto minmax(0, 1fr) auto auto auto; gap: 10px; height: 100vh; + height: 100dvh; padding: 12px 16px 8px; } /* ---------- 头部 ---------- */ -.scr-head { display: flex; align-items: center; gap: 16px; min-height: 40px; } +.scr-head { display: flex; align-items: center; gap: 14px; min-height: 40px; flex-wrap: wrap; padding-bottom: 8px; border-bottom: 1px solid var(--scr-line); } -.scr-brand { display: flex; align-items: baseline; gap: 6px; font-size: 17px; font-weight: 700; letter-spacing: 0.08em; } +.scr-brand { display: flex; align-items: center; gap: 8px; font-size: 16px; font-weight: 800; letter-spacing: 0.06em; color: var(--scr-text); } .scr-brand .mark { display: inline-grid; place-items: center; - width: 24px; height: 24px; margin-right: 4px; - background: var(--accent); color: var(--bg); - font-size: 13px; border-radius: 2px; - align-self: center; + width: 26px; height: 26px; + background: var(--scr-accent-solid); color: #fff; + font-size: 13px; font-weight: 800; border-radius: 8px; } -.scr-brand em { font-style: normal; font-size: 11px; color: var(--muted); letter-spacing: 0.2em; } +.scr-brand em { font-style: normal; font-size: 11px; font-weight: 500; color: var(--scr-muted); letter-spacing: 0.16em; } .scr-tabs { display: flex; gap: 4px; flex-wrap: wrap; } .scr-tabs a { display: inline-flex; align-items: center; gap: 5px; height: 28px; padding: 0 11px; - font-size: 12px; color: var(--ink-2); text-decoration: none; - border: 1px solid var(--line); border-radius: 999px; - background: var(--panel); + font-size: 12px; color: var(--scr-text-2); text-decoration: none; + border: 1px solid var(--scr-line); border-radius: 999px; + background: var(--scr-panel); } -.scr-tabs a::before { - content: ""; width: 6px; height: 6px; border-radius: 50%; - background: var(--tab, var(--accent)); -} -.scr-tabs a.on { background: var(--accent); border-color: var(--accent); color: var(--bg); } -.scr-tabs a.on::before { background: var(--bg); } +.scr-tabs a::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--tab, var(--scr-accent)); } +.scr-tabs a:hover { border-color: var(--scr-accent); color: var(--scr-text); text-decoration: none; } +.scr-tabs a.on { background: var(--scr-accent-solid); border-color: var(--scr-accent-solid); color: #fff; } +.scr-tabs a.on::before { background: #fff; } .scr-tools { display: flex; align-items: center; gap: 8px; } .scr-switch { height: 28px; padding: 0 10px; display: inline-flex; align-items: center; - font-size: 12px; color: var(--accent); text-decoration: none; - border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--line)); - border-radius: 999px; background: var(--panel); + font-size: 12px; color: var(--scr-accent); text-decoration: none; + border: 1px solid color-mix(in srgb, var(--scr-accent) 40%, var(--scr-line)); + border-radius: 999px; background: var(--scr-panel); } -.scr-switch:hover { background: color-mix(in srgb, var(--accent) 12%, var(--panel)); } +.scr-switch:hover { background: color-mix(in srgb, var(--scr-accent) 12%, var(--scr-panel)); text-decoration: none; } .scr-theme { width: 28px; height: 28px; padding: 0; display: inline-grid; place-items: center; - border: 1px solid var(--line); border-radius: 999px; - background: var(--panel); color: var(--ink-2); + border: 1px solid var(--scr-line); border-radius: 999px; + background: var(--scr-panel); color: var(--scr-text-2); font-size: 13px; cursor: pointer; } +.scr-theme:hover { border-color: var(--scr-accent); color: var(--scr-accent); } .scr-theme .ic-sun { display: none; } -:root[data-theme="light"] .scr-theme .ic-sun { display: inline; } -:root[data-theme="light"] .scr-theme .ic-moon { display: none; } -@media (prefers-color-scheme: light) { - :root:not([data-theme="dark"]) .scr-theme .ic-sun { display: inline; } - :root:not([data-theme="dark"]) .scr-theme .ic-moon { display: none; } -} +[data-bs-theme="dark"] .scr-theme .ic-sun { display: inline; } +[data-bs-theme="dark"] .scr-theme .ic-moon { display: none; } .scr-clock { margin-left: auto; text-align: right; line-height: 1.1; } -.scr-clock b { font-family: var(--mono, "Bahnschrift", monospace); font-size: 22px; letter-spacing: 0.04em; font-variant-numeric: tabular-nums; } -.scr-clock span { display: block; font-size: 11px; color: var(--muted); } +.scr-clock b { font-family: "Cascadia Mono", "Consolas", ui-monospace, monospace; font-size: 22px; letter-spacing: 0.04em; font-variant-numeric: tabular-nums; color: var(--scr-text); } +.scr-clock span { display: block; font-size: 11px; color: var(--scr-muted); } -.scr-links { font-size: 11px; color: var(--muted); } -.scr-links a { color: var(--accent); text-decoration: none; } +.scr-links { font-size: 11px; color: var(--scr-muted); } +.scr-links a { color: var(--scr-accent); text-decoration: none; } +.scr-links a:hover { text-decoration: underline; } /* ---------- KPI ---------- */ @@ -155,23 +139,24 @@ body::before { .kpi { position: relative; display: flex; align-items: baseline; gap: 6px; - padding: 10px 14px; - background: var(--panel); - border: 1px solid var(--line); - border-radius: 3px; + padding: 10px 14px 10px 18px; + background: var(--scr-panel); + border: 1px solid var(--scr-line); + border-radius: 10px; overflow: hidden; + box-shadow: var(--scr-shadow); } .kpi::after { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; - background: var(--accent); + background: var(--scr-accent); } -.kpi span { font-size: 12px; color: var(--muted); margin-right: auto; } -.kpi b { font-family: "Bahnschrift", monospace; font-size: 30px; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; } -.kpi i { font-style: normal; font-size: 11px; color: var(--muted); } -.kpi-hot::after { background: var(--danger); } -.kpi-hot b { color: var(--danger); } -.kpi-warm::after { background: var(--warn); } -.kpi-warm b { color: var(--warn); } +.kpi span { font-size: 12px; color: var(--scr-muted); margin-right: auto; } +.kpi b { font-family: "Cascadia Mono", "Consolas", ui-monospace, monospace; font-size: 30px; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; color: var(--scr-text); } +.kpi i { font-style: normal; font-size: 11px; color: var(--scr-muted); } +.kpi-hot::after { background: var(--scr-danger); } +.kpi-hot b { color: var(--scr-danger); } +.kpi-warm::after { background: var(--scr-warn); } +.kpi-warm b { color: var(--scr-warn); } /* ---------- 主区 ---------- */ @@ -179,140 +164,146 @@ body::before { .panel { display: flex; flex-direction: column; min-height: 0; - background: var(--panel); - border: 1px solid var(--line); - border-radius: 3px; + background: var(--scr-panel); + border: 1px solid var(--scr-line); + border-radius: 10px; overflow: hidden; + box-shadow: var(--scr-shadow); } .panel-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; padding: 9px 14px; - border-bottom: 1px solid var(--line); - background: var(--panel-2); + border-bottom: 1px solid var(--scr-line); + background: var(--scr-panel-2); } -.panel-head h2 { margin: 0; font-size: 13px; letter-spacing: 0.08em; font-weight: 700; } -.panel-head span { font-size: 11px; color: var(--muted); } +.panel-head h2 { margin: 0; font-size: 13px; font-weight: 700; letter-spacing: 0.06em; color: var(--scr-text); } +.panel-head span { font-size: 11px; color: var(--scr-muted); } /* 热力 */ -.heat-grid { flex: 1 1 auto; display: flex; flex-direction: column; justify-content: center; padding: 8px 12px; min-height: 0; } -.heat-dow { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; margin-bottom: 4px; } -.heat-dow span { text-align: center; font-size: 10px; color: var(--muted); } -.heat-row { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; margin-bottom: 4px; } +.heat-grid { flex: 1 1 auto; display: flex; flex-direction: column; justify-content: center; padding: 8px 12px; min-height: 0; min-width: 0; overflow: hidden; } +.heat-dow { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 4px; margin-bottom: 4px; } +.heat-dow span { text-align: center; font-size: 10px; color: var(--scr-muted); } +.heat-row { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 4px; margin-bottom: 4px; align-items: stretch; } .heat-cell { position: relative; display: flex; align-items: center; justify-content: space-between; - min-height: 0; aspect-ratio: 2.05 / 1; - padding: 4px 7px; - border: 1px solid var(--line); - border-radius: 2px; - background: var(--panel-2); - font-size: 11px; color: var(--muted); + min-height: 34px; min-width: 0; + padding: 3px 7px 3px 9px; + border: 1px solid var(--scr-line); border-radius: 6px; + background: var(--scr-panel-2); + font-size: 11px; color: var(--scr-muted); + overflow: hidden; transition: transform 0.1s ease, border-color 0.1s ease; } -.heat-cell span { font-family: "Bahnschrift", monospace; font-size: 11px; } -.heat-cell b { font-family: "Bahnschrift", monospace; font-size: 12px; color: var(--ink-2); } -.heat-cell.out { opacity: 0.28; } -.heat-cell.past { opacity: 0.66; } -.heat-cell.today { outline: 1px solid var(--accent); outline-offset: -1px; } -.heat-cell.today span { color: var(--accent); font-weight: 700; } -.heat-cell:hover { transform: translateY(-1px); border-color: var(--accent); z-index: 3; } +.heat-cell span { font-family: "Cascadia Mono", "Consolas", ui-monospace, monospace; font-size: 11px; color: var(--scr-text-2); } +.heat-cell b { font-family: "Cascadia Mono", "Consolas", ui-monospace, monospace; font-size: 12px; font-weight: 700; color: var(--scr-text); white-space: nowrap; } +.heat-cell.out { background: var(--scr-panel-2); border-color: var(--scr-line); } +.heat-cell.out span, .heat-cell.out b { color: var(--scr-muted); } +.heat-cell.past { opacity: 1; } +.heat-cell.today { outline: 1px solid var(--scr-accent); outline-offset: -1px; } +.heat-cell.today span { color: var(--scr-accent); font-weight: 700; } +.heat-cell:hover { transform: translateY(-1px); border-color: var(--scr-accent); z-index: 3; } -.heat-cell.h1 { background: color-mix(in srgb, var(--accent) 12%, var(--panel-2)); border-color: color-mix(in srgb, var(--accent) 22%, var(--line)); } -.heat-cell.h2 { background: color-mix(in srgb, var(--accent) 24%, var(--panel-2)); } -.heat-cell.h3 { background: color-mix(in srgb, var(--accent) 38%, var(--panel-2)); } -.heat-cell.h4 { background: color-mix(in srgb, var(--accent) 56%, var(--panel-2)); } -.heat-cell.h3 b, .heat-cell.h4 b { color: var(--bg); } -.heat-cell.h3 span, .heat-cell.h4 span { color: color-mix(in srgb, var(--bg) 75%, var(--accent)); } +.heat-cell.h1 { background: color-mix(in srgb, var(--scr-accent) 10%, var(--scr-panel-2)); } +.heat-cell.h2 { background: color-mix(in srgb, var(--scr-accent) 22%, var(--scr-panel-2)); } +.heat-cell.h3 { background: color-mix(in srgb, var(--scr-accent) 36%, var(--scr-panel-2)); } +.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); } .cell-urgent { position: absolute; top: 3px; right: 3px; width: 5px; height: 5px; border-radius: 50%; - background: var(--danger); - box-shadow: 0 0 6px color-mix(in srgb, var(--danger) 70%, transparent); + background: var(--scr-danger); + box-shadow: 0 0 6px color-mix(in srgb, var(--scr-danger) 70%, transparent); } -.heat-legend { - display: flex; align-items: center; gap: 4px; - padding: 6px 14px 10px; - font-size: 10px; color: var(--muted); -} +.heat-legend { display: flex; align-items: center; gap: 4px; padding: 6px 14px 10px; font-size: 10px; color: var(--scr-muted); } .heat-legend span { margin-right: 6px; } -.heat-legend i { - width: 14px; height: 9px; border-radius: 1px; - border: 1px solid var(--line); - display: inline-block; -} -.heat-legend i.h0 { background: var(--panel-2); } -.heat-legend i.h1 { background: color-mix(in srgb, var(--accent) 12%, var(--panel-2)); } -.heat-legend i.h2 { background: color-mix(in srgb, var(--accent) 24%, var(--panel-2)); } -.heat-legend i.h3 { background: color-mix(in srgb, var(--accent) 38%, var(--panel-2)); } -.heat-legend i.h4 { background: color-mix(in srgb, var(--accent) 56%, var(--panel-2)); } -.heat-legend .legend-hint { margin-left: auto; font-style: normal; opacity: 0.7; } +.heat-legend i { width: 14px; height: 9px; border-radius: 2px; border: 1px solid var(--scr-line); display: inline-block; } +.heat-legend i.h0 { background: var(--scr-panel-2); } +.heat-legend i.h1 { background: color-mix(in srgb, var(--scr-accent) 10%, var(--scr-panel-2)); } +.heat-legend i.h2 { background: color-mix(in srgb, var(--scr-accent) 22%, var(--scr-panel-2)); } +.heat-legend i.h3 { background: color-mix(in srgb, var(--scr-accent) 36%, var(--scr-panel-2)); } +.heat-legend i.h4 { background: color-mix(in srgb, var(--scr-accent) 52%, var(--scr-panel-2)); } +.heat-legend .legend-hint { margin-left: auto; font-style: normal; opacity: 0.8; } /* TOP 10 */ -.top-list { list-style: none; margin: 0; padding: 6px 10px; overflow: hidden; flex: 1 1 auto; } +.top-list { list-style: none; margin: 0; padding: 6px 10px; overflow: auto; flex: 1 1 auto; min-height: 0; } .top-item { display: grid; - grid-template-columns: 22px 54px minmax(0, 1fr) auto auto 56px; + grid-template-columns: 24px 56px minmax(0, 1fr) minmax(0, 118px) 78px 64px; gap: 8px; align-items: center; padding: 5px 4px; - border-bottom: 1px dashed var(--line); + border-bottom: 1px dashed var(--scr-line); font-size: 12px; } .top-item:last-child { border-bottom: 0; } -.top-item .rank { font-family: "Bahnschrift", monospace; font-size: 11px; color: var(--muted); } -.top-item:nth-child(-n+3) .rank { color: var(--accent); font-weight: 700; } -.top-tail { font-family: "Bahnschrift", monospace; font-size: 14px; font-weight: 700; letter-spacing: 0.03em; } -.top-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-2); } +.top-item .rank { grid-column: 1; font-family: "Cascadia Mono", "Consolas", ui-monospace, monospace; font-size: 11px; color: var(--scr-muted); } +.top-item:nth-child(-n+3) .rank { color: var(--scr-accent); font-weight: 700; } +.top-tail { grid-column: 2; font-family: "Cascadia Mono", "Consolas", ui-monospace, monospace; font-size: 14px; font-weight: 700; letter-spacing: 0.03em; color: var(--scr-text); } +.top-name { grid-column: 3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--scr-text-2); } .top-pkg { - font-size: 10px; padding: 1px 6px; border-radius: 2px; - border-left: 2px solid var(--tag, var(--accent)); - background: color-mix(in srgb, var(--tag, var(--accent)) 14%, transparent); - color: var(--ink-2); white-space: nowrap; + grid-column: 4; + font-size: 10px; padding: 1px 6px; border-radius: 999px; + border-left: 2px solid var(--tag, var(--scr-accent)); + background: color-mix(in srgb, var(--tag, var(--scr-accent)) 14%, transparent); + color: var(--scr-text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.top-remain { font-family: "Bahnschrift", monospace; font-size: 11px; white-space: nowrap; } -.top-item.lv-today .top-remain { color: var(--danger); font-weight: 700; } -.top-item.lv-warn .top-remain { color: var(--warn); } -.top-bar { height: 4px; background: var(--line); border-radius: 999px; overflow: hidden; } -.top-bar i { display: block; height: 100%; background: var(--accent); } -.top-item.lv-today .top-bar i { background: var(--danger); } -.top-item.lv-warn .top-bar i { background: var(--warn); } +.top-remain { grid-column: 5; font-family: "Cascadia Mono", "Consolas", ui-monospace, monospace; font-size: 11px; color: var(--scr-text-2); white-space: nowrap; } +.top-bar { grid-column: 6; height: 4px; background: var(--scr-line); border-radius: 999px; overflow: hidden; } +.top-bar i { display: block; height: 100%; background: var(--scr-accent); } +.top-item.lv-today .top-remain { color: var(--scr-danger); font-weight: 700; } +.top-item.lv-warn .top-remain { color: var(--scr-warn); } +.top-item.lv-today .top-bar i { background: var(--scr-danger); } +.top-item.lv-warn .top-bar i { background: var(--scr-warn); } /* ---------- 底部两块 ---------- */ -.scr-bottom { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; min-height: 150px; max-height: 30vh; } -.today-list { list-style: none; margin: 0; padding: 4px 10px 8px; overflow: auto; } +.scr-bottom { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; min-height: 168px; max-height: 30vh; } +.today-list { list-style: none; margin: 0; padding: 4px 10px 8px; overflow: auto; min-height: 0; } .today-list li { - display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; + display: grid; + grid-template-columns: auto minmax(0, 1fr) 86px 52px 76px; gap: 8px; align-items: center; padding: 5px 6px; - border-bottom: 1px dashed var(--line); + border-bottom: 1px dashed var(--scr-line); font-size: 12px; } .today-list li:last-child { border-bottom: 0; } .today-list li.urgent { - background: color-mix(in srgb, var(--danger) 9%, transparent); - border-left: 2px solid var(--danger); + background: color-mix(in srgb, var(--scr-danger) 8%, transparent); + border-left: 2px solid var(--scr-danger); padding-left: 8px; } +.today-list .tchip { grid-column: 1; } +.today-list .pkg { grid-column: 2; font-size: 10px; color: var(--scr-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.today-list .asg { grid-column: 3; } +.today-list .tag-urgent { grid-column: 4; justify-self: start; } +.today-list .rem { grid-column: 5; font-family: "Cascadia Mono", "Consolas", ui-monospace, monospace; font-size: 11px; color: var(--scr-text-2); white-space: nowrap; text-align: right; } + .tchip { display: inline-flex; align-items: baseline; gap: 3px; padding: 1px 6px; - border-left: 2px solid var(--tag, var(--accent)); - background: color-mix(in srgb, var(--tag, var(--accent)) 14%, transparent); - border-radius: 1px; white-space: nowrap; + border-left: 2px solid var(--tag, var(--scr-accent)); + background: color-mix(in srgb, var(--tag, var(--scr-accent)) 14%, transparent); + border-radius: 2px 6px 6px 2px; white-space: nowrap; + color: var(--scr-text-2); } -.tchip b { font-family: "Bahnschrift", monospace; font-size: 13px; } -.today-list .pkg { font-size: 10px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.today-list .rem { font-family: "Bahnschrift", monospace; font-size: 11px; color: var(--ink-2); white-space: nowrap; } +.tchip b { font-family: "Cascadia Mono", "Consolas", ui-monospace, monospace; font-size: 13px; color: var(--scr-text); } .tag-urgent { - font-size: 9px; line-height: 1; padding: 2px 4px; - background: var(--danger); color: #fff; border-radius: 2px; + display: inline-flex; align-items: center; + height: 18px; padding: 0 6px; + font-size: 10px; font-weight: 700; line-height: 1; + background: color-mix(in srgb, var(--scr-danger) 18%, var(--scr-panel)); + color: var(--scr-danger); + border: 1px solid color-mix(in srgb, var(--scr-danger) 32%, transparent); + border-radius: 999px; white-space: nowrap; } -.scr-empty, .scr-empty-inline { color: var(--muted); font-size: 12px; padding: 16px; text-align: center; margin: 0; } +.scr-empty, .scr-empty-inline { color: var(--scr-muted); font-size: 12px; padding: 16px; text-align: center; margin: 0; } .scr-empty-inline { padding: 0; text-align: left; } /* ---------- 最近追加 ---------- */ @@ -320,18 +311,18 @@ body::before { .scr-recent { display: flex; align-items: center; gap: 10px; padding: 5px 12px; - background: var(--panel); - border: 1px solid var(--line); border-radius: 3px; - font-size: 11px; color: var(--muted); + background: var(--scr-panel); + border: 1px solid var(--scr-line); border-radius: 10px; + font-size: 11px; color: var(--scr-muted); overflow: hidden; } -.recent-label { flex: 0 0 auto; letter-spacing: 0.12em; color: var(--accent); font-size: 10px; } +.recent-label { flex: 0 0 auto; letter-spacing: 0.12em; color: var(--scr-accent); font-size: 10px; } .recent-track { display: flex; gap: 18px; overflow: hidden; white-space: nowrap; } -.recent-item { color: var(--ink-2); } -.recent-item b { font-family: "Bahnschrift", monospace; font-size: 12px; color: var(--ink); margin-right: 3px; } -.recent-item em { font-style: normal; color: var(--accent); margin-left: 3px; } +.recent-item { color: var(--scr-text-2); } +.recent-item b { font-family: "Cascadia Mono", "Consolas", ui-monospace, monospace; font-size: 12px; color: var(--scr-text); margin-right: 3px; } +.recent-item em { font-style: normal; color: var(--scr-accent); margin-left: 3px; } -.scr-foot { text-align: center; font-size: 10px; color: var(--muted); letter-spacing: 0.06em; } +.scr-foot { text-align: center; font-size: 10px; color: var(--scr-muted); letter-spacing: 0.06em; } /* ---------- 当日明细浮层 ---------- */ @@ -339,186 +330,72 @@ body::before { position: fixed; z-index: 60; width: 340px; max-height: 62vh; overflow: auto; padding: 10px 12px; - background: var(--panel); - border: 1px solid var(--accent); - border-radius: 3px; - box-shadow: 0 18px 40px -18px rgba(0, 0, 0, 0.7), var(--glow); + background: var(--scr-panel); + border: 1px solid var(--scr-accent); + border-radius: 10px; + box-shadow: 0 18px 40px -18px rgba(0, 0, 0, 0.55), var(--scr-glow); font-size: 12px; - pointer-events: none; + pointer-events: auto; + overscroll-behavior: contain; } .day-pop[hidden] { display: none; } -.day-pop h4 { - margin: 0 0 6px; display: flex; align-items: baseline; justify-content: space-between; - font-size: 12px; color: var(--ink); -} -.day-pop h4 small { font-weight: 400; color: var(--muted); font-size: 10px; } -.day-pop .pop-meta { display: flex; gap: 10px; margin-bottom: 6px; font-size: 10px; color: var(--muted); } -.day-pop .pop-meta b { color: var(--ink-2); font-family: "Bahnschrift", monospace; } +.day-pop h4 { margin: 0 0 6px; display: flex; align-items: baseline; justify-content: space-between; font-size: 12px; color: var(--scr-text); } +.day-pop h4 small { font-weight: 400; color: var(--scr-muted); font-size: 10px; } +.day-pop .pop-meta { display: flex; gap: 10px; margin-bottom: 6px; font-size: 10px; color: var(--scr-muted); } +.day-pop .pop-meta b { color: var(--scr-text-2); font-family: "Cascadia Mono", "Consolas", ui-monospace, monospace; } .day-pop ul { list-style: none; margin: 0; padding: 0; } -.day-pop li { - display: grid; grid-template-columns: auto minmax(0, 1fr) auto; - gap: 6px; align-items: center; - padding: 3px 0; border-bottom: 1px dashed var(--line); -} +.day-pop li { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 6px; align-items: center; padding: 3px 0; border-bottom: 1px dashed var(--scr-line); } .day-pop li:last-child { border-bottom: 0; } -.day-pop li.urgent { color: var(--danger); } +.day-pop li.urgent { color: var(--scr-danger); } .day-pop li.done { opacity: 0.45; text-decoration: line-through; } -.day-pop .pop-tail { font-family: "Bahnschrift", monospace; font-size: 12px; font-weight: 700; } -.day-pop .pop-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-2); } -.day-pop .pop-rem { font-size: 10px; color: var(--muted); white-space: nowrap; } -.day-pop .pop-more { margin-top: 6px; font-size: 10px; color: var(--muted); text-align: right; } -.day-pop .pop-empty { color: var(--muted); text-align: center; padding: 8px 0; } +.day-pop .pop-tail { font-family: "Cascadia Mono", "Consolas", ui-monospace, monospace; font-size: 12px; font-weight: 700; } +.day-pop .pop-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--scr-text-2); } +.day-pop .pop-rem { font-size: 10px; color: var(--scr-muted); white-space: nowrap; } +.day-pop .pop-more { margin-top: 6px; font-size: 10px; color: var(--scr-muted); text-align: right; } +.day-pop .pop-empty { color: var(--scr-muted); text-align: center; padding: 8px 0; } -/* ---------- 小屏适配(笔记本投屏也常见) ---------- */ - -@media (max-width: 1200px) { - .scr-main { grid-template-columns: 1fr 1fr; } - .scr-kpis .kpi b { font-size: 24px; } - .top-item { grid-template-columns: 20px 48px minmax(0, 1fr) auto 48px; } - .top-item .top-pkg { display: none; } -} - -@media (max-width: 860px) { - body { overflow: auto; } - .scr { height: auto; } - .scr-main, .scr-bottom { grid-template-columns: 1fr; } - .scr-tabs, .scr-links { display: none; } -} - - -/* ---------- 打手颜色(大屏) ---------- */ +/* ---------- 打手颜色 ---------- */ .asg-strip { position: absolute; left: 0; top: 0; bottom: 0; display: flex; flex-direction: column; - width: 3px; overflow: hidden; + width: 3px; overflow: hidden; border-radius: 6px 0 0 6px; } .asg-strip i { display: block; flex: 1 1 0; min-height: 3px; } +.asg-strip.sm { width: 3px; } -.heat-cell { padding-left: 9px; } - -.asg { - display: inline-flex; align-items: center; gap: 4px; - font-size: 10px; color: var(--ink-2); white-space: nowrap; - overflow: hidden; text-overflow: ellipsis; -} -.asg i { width: 7px; height: 7px; border-radius: 1px; flex: 0 0 auto; } -.asg.none { color: var(--muted); } +.asg { display: inline-flex; align-items: center; gap: 4px; font-size: 10px; color: var(--scr-text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.asg i { width: 7px; height: 7px; border-radius: 2px; flex: 0 0 auto; } +.asg.none { color: var(--scr-muted); } .asg.none i { opacity: 0.5; } -.today-list li { grid-template-columns: auto minmax(0, 1fr) auto auto auto; } +.pop-who { display: inline-block; width: 7px; height: 7px; border-radius: 2px; margin-right: 4px; vertical-align: 0; } -.pop-who { - display: inline-block; width: 7px; height: 7px; - border-radius: 1px; margin-right: 4px; vertical-align: 0; +/* ---------- 小屏 / 投屏适配 ---------- */ + +@media (max-width: 1200px) { + .scr-main { grid-template-columns: 1fr 1fr; } + .scr-kpis .kpi b { font-size: 24px; } + .top-item { grid-template-columns: 24px 52px minmax(0, 1fr) 78px 56px; } + .top-item .top-pkg { display: none; } + .top-remain { grid-column: 4; } + .top-bar { grid-column: 5; } + .today-list li { grid-template-columns: auto minmax(0, 1fr) 52px 76px; } + .today-list .asg { display: none; } + .today-list .tag-urgent { grid-column: 3; } + .today-list .rem { grid-column: 4; } } +@media (max-width: 860px) { + body.screen-body { overflow: auto; } + .scr { height: auto; } + .scr-main, .scr-bottom { grid-template-columns: 1fr; } + .scr-bottom { max-height: none; } + .scr-tabs, .scr-links { display: none; } + .scr-clock { margin-left: 0; } + .scr-kpis { grid-template-columns: 1fr 1fr; } +} -/* ---------- v1.9.1:大屏对齐 Bootstrap ---------- */ -.heat-cell { border-color: var(--bs-border-color); } -.heat-cell.h1 { background: rgba(var(--bs-success-rgb), 0.14); } -.heat-cell.h2 { background: rgba(var(--bs-success-rgb), 0.26); } -.heat-cell.h3 { background: rgba(var(--bs-success-rgb), 0.40); } -.heat-cell.h4 { background: rgba(var(--bs-success-rgb), 0.58); } -.scr-kpis .kpi { --bs-card-border-color: var(--bs-border-color); } - -/* v1.9.2 ink */ -.scr-tabs a.on { background: linear-gradient(135deg,#16543f,#2fa37a); border-color: transparent; } - -/* v1.9.4:大屏弱化文字提亮,提升可读性 */ -.scr { --muted: #9fb8ab; --ink-2: #b9d2c6; } -.scr-links, .scr-foot, .scr-head .sub, .scr-head em, -.kpi small, .kpi .unit, .panel-sub, -.top-item .meta, .top-item .sub, .due-item .sub, -.heat-cell .lbl, .scr-note { color: #a9c3b6 !important; } -.scr a { color: #8fe0bf !important; } -.cal-dow span, .scr-cal .dow { color: #a9c3b6 !important; } -.kpi b, .kpi strong { color: #eafff6 !important; } -.heat-cell.h1 { color: #dff5ea; } -/* v1.9.4:非本月日期再提亮一档 */ -.heat-cell.out, .scr-cal .out { color: #a9c3b6 !important; opacity: .85; } -/* v1.9.5:热力日历对齐修复 + 文字反色 */ -.panel-head { position: relative; z-index: 2; } -.heat-grid { overflow: hidden; min-width: 0; } -.heat-dow, .heat-row { - display: grid !important; - grid-template-columns: repeat(7, minmax(0, 1fr)) !important; - grid-auto-flow: row; - gap: 4px; - width: 100%; - min-width: 0; +@media (prefers-reduced-motion: reduce) { + .heat-cell { transition: none; } } -.heat-row { align-items: stretch; } -.heat-cell { - width: 100% !important; - min-width: 0; - aspect-ratio: auto !important; - min-height: 34px; - padding: 2px 6px; - overflow: hidden; -} -.heat-cell span, .heat-cell b, .heat-cell em { - color: var(--ink-ink) !important; - font-family: "Bahnschrift", monospace; - white-space: nowrap; -} -.heat-cell b { color: #63d6ad !important; font-weight: 700; } -.heat-cell.out { opacity: .45 !important; } -.heat-cell.past { opacity: .75 !important; } -.heat-legend { margin-top: 6px; color: var(--muted); } -/* ---------- v1.9.5:大屏浅色主题配色修复 ---------- */ -:root[data-theme="light"] .scr, -:root[data-bs-theme="light"] .scr { - --bg: #f2f7f4; - --surface: #ffffff; - --surface-2: #eaf1ed; - --line: #d7e3dc; - --line-2: #c6d8cf; - --muted: #55705f; - --ink: #0f241d; - --ink-2: #27453a; - --accent: #16543f; - --accent-2: #1d7a58; - color: #0f241d; -} -:root[data-theme="light"] .scr a, -:root[data-bs-theme="light"] .scr a { color: #16543f; } -:root[data-theme="light"] .scr em, -:root[data-theme="light"] .scr small, -:root[data-theme="light"] .scr .sub, -:root[data-theme="light"] .scr .meta, -:root[data-theme="light"] .scr .unit, -:root[data-theme="light"] .scr .panel-sub, -:root[data-theme="light"] .scr .scr-links, -:root[data-theme="light"] .scr .scr-foot { color: #55705f !important; } -:root[data-theme="light"] .scr-tags a, -:root[data-theme="light"] .scr-tabs a, -:root[data-theme="light"] .scr-switch, -:root[data-theme="light"] .scr-head .sub { color: #27453a !important; background: #ffffff; border: 1px solid #d7e3dc; } -:root[data-theme="light"] .scr-tags a.on, -:root[data-theme="light"] .scr-tabs a.on { color: #ffffff !important; background: linear-gradient(135deg,#16543f,#2fa37a); border-color: transparent; } -:root[data-theme="light"] .kpi, -:root[data-theme="light"] .kpi b, -:root[data-theme="light"] .kpi strong, -:root[data-theme="light"] .kpi small, -:root[data-theme="light"] .kpi .unit { color: #0f241d !important; } -:root[data-theme="light"] .kpi b, -:root[data-theme="light"] .kpi strong { color: #16543f !important; } -:root[data-theme="light"] .heat-cell, -:root[data-theme="light"] .heat-cell span, -:root[data-theme="light"] .heat-cell b { color: #0f241d !important; } -:root[data-theme="light"] .heat-cell b { color: #16543f !important; } -:root[data-theme="light"] .heat-cell.out { opacity: .55 !important; } -:root[data-theme="light"] .heat-cell .note, -:root[data-theme="light"] .heat-legend { color: #55705f !important; } -:root[data-theme="light"] .top-item, -:root[data-theme="light"] .top-item span, -:root[data-theme="light"] .top-item b, -:root[data-theme="light"] .due-item, -:root[data-theme="light"] .today-item { color: #0f241d !important; } -:root[data-theme="light"] .top-item .pkg-tag, -:root[data-theme="light"] .top-item em, -:root[data-theme="light"] .today-item em { color: #1d7a58 !important; } -:root[data-theme="light"] .panel-head h2 { color: #0f241d; } -:root[data-theme="light"] .heat-dow span { color: #55705f !important; } -:root[data-theme="light"] .scr-note { color: #55705f !important; } \ No newline at end of file diff --git a/internal/web/static/screen.js b/internal/web/static/screen.js index 72393c7..6921076 100644 --- a/internal/web/static/screen.js +++ b/internal/web/static/screen.js @@ -1,4 +1,4 @@ -/* 大屏:时钟 + 全屏 + 主题 + 日历悬停明细 */ +/* 大屏:时钟 + 全屏 + 主题 + 日历悬停/点按明细 */ (function () { 'use strict'; @@ -25,32 +25,42 @@ document.documentElement.requestFullscreen(); } } + if (e.key === 'Escape') { hide(); } }); - /* ---------- 主题切换 ---------- */ + /* ---------- 主题切换:唯一状态源 data-bs-theme ---------- */ var THEME_KEY = 'daili_theme'; - function effectiveTheme() { - var attr = document.documentElement.getAttribute('data-theme'); - if (attr === 'light' || attr === 'dark') return attr; - return window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'; + var root = document.documentElement; + var themeBtns = document.querySelectorAll('[data-theme-toggle]'); + + function currentTheme() { + return root.getAttribute('data-bs-theme') === 'light' ? 'light' : 'dark'; } - document.querySelectorAll('[data-theme-toggle]').forEach(function (btn) { + function paintTheme() { + var dark = currentTheme() === 'dark'; + themeBtns.forEach(function (b) { b.setAttribute('aria-pressed', dark ? 'true' : 'false'); }); + } + + themeBtns.forEach(function (btn) { btn.addEventListener('click', function () { - var next = effectiveTheme() === 'dark' ? 'light' : 'dark'; - document.documentElement.setAttribute('data-theme', next); + var next = currentTheme() === 'dark' ? 'light' : 'dark'; + root.setAttribute('data-bs-theme', next); document.cookie = THEME_KEY + '=' + next + '; path=/; max-age=31536000; SameSite=Lax'; + paintTheme(); }); }); + paintTheme(); - /* ---------- 日历格子悬停明细 ---------- */ - var root = document.querySelector('.scr'); + /* ---------- 日历格子明细 ---------- */ + var scr = document.querySelector('.scr'); var pop = document.getElementById('day-pop'); - if (!root || !pop) return; + if (!scr || !pop) return; - var api = root.getAttribute('data-api') || '/api/screen/day'; - var pkg = root.getAttribute('data-pkg') || '0'; + var api = scr.getAttribute('data-api') || '/api/screen/day'; + var pkg = scr.getAttribute('data-pkg') || '0'; var cache = {}; var hideTimer = null; + var pinned = false; function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { @@ -77,7 +87,7 @@ var d = new Date(data.date + 'T00:00:00'); var label = data.date + ' 周' + '日一二三四五六'.charAt(d.getDay()); var html = '

' + esc(label) + '' + data.total + ' 单 · 已完成 ' + data.done + - (data.urgent ? ' · 加急 ' + data.urgent + '' : '') + '

'; + (data.urgent ? ' · 加急 ' + data.urgent + '' : '') + ''; if (!data.items || !data.items.length) { html += '
这天没有排单
'; } else { @@ -121,12 +131,32 @@ }); } - function hideSoon() { - hideTimer = setTimeout(function () { pop.hidden = true; }, 160); + function hide() { + clearTimeout(hideTimer); + pinned = false; + pop.hidden = true; } + function hideSoon() { + if (pinned) return; + clearTimeout(hideTimer); + hideTimer = setTimeout(function () { pop.hidden = true; }, 260); + } + function keepOpen() { clearTimeout(hideTimer); } document.querySelectorAll('.heat-cell[data-date]').forEach(function (cell) { cell.addEventListener('mouseenter', function () { show(cell); }); cell.addEventListener('mouseleave', hideSoon); + cell.addEventListener('focus', function () { show(cell); }); + cell.addEventListener('click', function (e) { + e.preventDefault(); + var openForCell = !pop.hidden && pop.dataset.date === cell.getAttribute('data-date'); + if (openForCell) { hide(); return; } + pinned = true; + show(cell); + pop.dataset.date = cell.getAttribute('data-date'); + }); }); + + pop.addEventListener('mouseenter', keepOpen); + pop.addEventListener('mouseleave', function () { if (!pinned) hideSoon(); }); })(); diff --git a/internal/web/templates/_layout.html b/internal/web/templates/_layout.html index 4de65a7..b272a3d 100644 --- a/internal/web/templates/_layout.html +++ b/internal/web/templates/_layout.html @@ -1,37 +1,48 @@ {{define "layout"}} - + + {{.Title}} · 代打台 - - - + + + + - + {{if .User}} -
- - + - +
{{if .D.List}}
- +
- + {{range .D.List}} @@ -49,14 +49,14 @@ @@ -69,7 +69,7 @@
票

这个筛选下没有订阅。

- 看全部 + 看全部
{{end}} diff --git a/main.go b/main.go index ab48240..13ad030 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,7 @@ import ( "daili/internal/web" ) -const version = "1.9.5" +const version = "1.10.0" func main() { cfgFlag := flag.String("config", "", "配置文件路径(默认:exe 同级的 "+config.FileName+")")
客户套餐状态剩余时间操作
客户套餐状态剩余时间操作
{{.PeriodText}} {{if ne .Status "closed"}} - 追加 + 追加 {{if eq .Status "active"}} -
+
{{else if eq .Status "paused"}} -
+
{{end}} {{else}} -
+
{{end}}