docs(design): UI 风格定稿(U5),设计规范与 tokens 升到 v0.2
- ui-demo/explore/:工作台风格的六轮方案(A–F、D2–D5),D5 为定稿样张 - tokens.css v0.2:墨色派生的中性浅灰作页面底色;圆角三档(6 / 8 / 12);控件高 36;侧栏渐变与套餐标签变量;旧变量保留兼容 - design-system.md v0.2:页面框架(96px 墨色渐变侧栏、透明顶栏、内容区圆角)、只用功能图标、项目行「下一步 / 更多」列、少写说明 - decisions.md 记录 U5;AGENTS.md 与 design/README 同步 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
469
docs/design/ui-demo/explore/workbench-b.html
Normal file
469
docs/design/ui-demo/explore/workbench-b.html
Normal file
@@ -0,0 +1,469 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
工作台 · 风格方案 B「工作面板」(探索稿,不是定稿)
|
||||
数据、文案、交互沿用 ../page-workbench.html。
|
||||
|
||||
本方案相对 design-system.md 改了哪些规则:
|
||||
1. §5.1 圆角:2px 一档 → 分层圆角。面板 10、列表行 8、按钮 / 输入框 / 分段控件 6、标签 4。仍不用胶囊形。
|
||||
2. §5.1 层次:「靠分隔线和底色」→ 只靠底色。画布白、侧边栏和面板用校样纸色,面板里的行是白底,行与行留 3px 缝,不画分隔线。
|
||||
3. §3.1 页面底色:校样纸 → 白;校样纸改作侧边栏和面板底色。
|
||||
4. §7.1 次按钮:白底 + 控件边框 → 浅墨实底、无边框。只有输入框保留控件边框(WCAG 1.4.11)。
|
||||
5. §7.9 状态标签:中性描边 → 浅灰实底、无描边;「就绪」同样改为实底。
|
||||
6. §7.9 项目行:「状态」列改为「进度」列,用四段状态轨(跟进中 → 编制中 → 已检查 → 已递交)加当前状态标签;已归档整条变浅。
|
||||
7. §7.9 倒计时:一行写完 → 两行,上行写剩余时间(≤ 72 小时赭色),下行写截止时刻。
|
||||
8. §7.7 表头:凹陷灰底 + 2px 墨色底线 → 去掉,只留辅助灰列名。
|
||||
9. 工作台统计:四张一样的卡片 → 一个面板里四列,数字 32px,每列可点击筛选;致命数字用致命红,截止数字用赭。
|
||||
10. §4.2 字阶:H1 24/32 → 28/36;统计数字 30/36 → 32/40。其余不变。
|
||||
11. §7.2 级别标签:圆角 2 → 4,形状规则(致命实心 / 重要描边 / 提示无框)不变。
|
||||
12. 顶栏:面包屑 → 居中的全局搜索(Ctrl K);额度从文字改为小进度条 + 数字。
|
||||
新增的派生色全部用 color-mix() 从 tokens.css 的现有色得到,集中声明在 <style> 顶部,没有新色相。
|
||||
-->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>工作台 · 方案 B 工作面板</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<link rel="stylesheet" href="../../system/tokens.css">
|
||||
<link rel="stylesheet" href="../../system/fonts-local.css">
|
||||
<style>
|
||||
/* ---------- 本方案派生变量(全部从 tokens.css 现有色派生,不引入新色相) ---------- */
|
||||
:root{
|
||||
--wb-canvas: var(--c-white); /* 应用画布 */
|
||||
--wb-side: color-mix(in srgb, var(--c-paper) 55%, var(--c-sunken)); /* 侧边栏:比校样纸略深 */
|
||||
--wb-panel: var(--c-paper); /* 内容分区面板 */
|
||||
--wb-fill: color-mix(in srgb, var(--c-ink) 6%, var(--c-white)); /* 次按钮、图标按钮悬停 */
|
||||
--wb-fill-hover: color-mix(in srgb, var(--c-ink) 11%, var(--c-white)); /* 次按钮悬停 */
|
||||
--wb-track: color-mix(in srgb, var(--c-ink) 7%, var(--c-paper)); /* 分段控件底、面板内的凹槽 */
|
||||
--wb-row-hover: color-mix(in srgb, var(--c-ink) 5%, var(--c-white)); /* 列表行悬停 */
|
||||
--wb-stat-hover: color-mix(in srgb, var(--c-white) 55%, var(--c-paper));/* 统计列悬停 */
|
||||
--wb-tag: var(--c-sunken); /* 状态标签实底 */
|
||||
--wb-rail-off: color-mix(in srgb, var(--c-ink) 12%, var(--c-white)); /* 状态轨:未走到的段 */
|
||||
--wb-rail-on: var(--c-ink); /* 状态轨:已走过 / 当前段 */
|
||||
--wb-rail-arch: color-mix(in srgb, var(--c-ink) 32%, var(--c-white)); /* 状态轨:已归档整条变浅 */
|
||||
--wb-primary-hover: color-mix(in srgb, var(--c-ink) 88%, var(--c-white));/* 主按钮悬停:变浅 */
|
||||
|
||||
--wb-r-panel: 10px; --wb-r-row: 8px; --wb-r-ctl: 6px; --wb-r-tag: 4px;
|
||||
--wb-fs-h1: 28px; --wb-lh-h1: 36px;
|
||||
--wb-fs-stat: 32px; --wb-lh-stat: 40px;
|
||||
--wb-top-h: 60px;
|
||||
--wb-rail-w: 296px;
|
||||
--wb-track-w: 156px;
|
||||
}
|
||||
@media (max-width: 1399px){ :root{ --wb-rail-w: 260px; --wb-track-w: 132px; } }
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0}
|
||||
body{background:var(--wb-canvas);color:var(--text-primary);font:var(--fw-regular) var(--fs-body)/var(--lh-body) var(--font-sans);-webkit-font-smoothing:antialiased;min-width:1280px}
|
||||
h1,h2,h3,p,ul{margin:0}
|
||||
ul{padding:0;list-style:none}
|
||||
a{color:inherit}
|
||||
button,input{font:inherit;color:inherit}
|
||||
:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}
|
||||
.fig{font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-weight:var(--fw-bold)}
|
||||
.ic{width:16px;height:16px;flex:none;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
|
||||
.ic.lg{width:20px;height:20px}
|
||||
.sec{color:var(--text-secondary)}
|
||||
.cap{font-size:var(--fs-caption);line-height:var(--lh-caption)}
|
||||
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
|
||||
|
||||
/* ---------- 框架 ---------- */
|
||||
.app{display:grid;grid-template-columns:var(--sidebar-w) minmax(0,1fr);min-height:100vh}
|
||||
.side{background:var(--wb-side)}
|
||||
.side-in{display:flex;flex-direction:column;padding:20px 12px 16px;position:sticky;top:0;height:100vh}
|
||||
.side .brand{display:block;padding:0 8px 24px}
|
||||
.side .brand img{height:30px;display:block}
|
||||
.nav{display:flex;flex-direction:column;gap:2px}
|
||||
.nav a{display:flex;align-items:center;gap:10px;height:36px;padding:0 10px;border-radius:var(--wb-r-ctl);text-decoration:none;color:var(--text-secondary)}
|
||||
.nav a:hover{background:var(--wb-track);color:var(--text-primary)}
|
||||
.nav a[aria-current="page"]{background:var(--c-white);color:var(--text-primary);font-weight:var(--fw-bold)}
|
||||
.nav .grp{margin:20px 10px 6px;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
|
||||
.side-foot{margin-top:auto;padding:12px 10px 0;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
|
||||
.side-foot b{display:block;font-size:var(--fs-body);line-height:var(--lh-body);color:var(--text-primary);font-weight:var(--fw-bold)}
|
||||
|
||||
.main{min-width:0;display:flex;flex-direction:column}
|
||||
.top{height:var(--wb-top-h);display:grid;grid-template-columns:minmax(max-content,1fr) minmax(280px,540px) minmax(max-content,1fr);align-items:center;gap:24px;padding:0 24px 0 32px;position:sticky;top:0;z-index:20;background:var(--wb-canvas)}
|
||||
.top .date{color:var(--text-secondary);white-space:nowrap}
|
||||
.gsearch{position:relative;display:flex;align-items:center}
|
||||
.gsearch .ic{position:absolute;left:12px;color:var(--text-secondary);pointer-events:none}
|
||||
.gsearch input{width:100%;height:36px;padding:0 64px 0 38px;border:1px solid var(--border-control);border-radius:var(--wb-r-ctl);background:var(--c-white)}
|
||||
.gsearch input::placeholder{color:var(--text-secondary)}
|
||||
.gsearch input:focus-visible{outline:2px solid var(--focus-ring);outline-offset:1px;border-color:var(--c-ink)}
|
||||
.kbd{display:inline-flex;align-items:center;height:20px;padding:0 6px;border-radius:var(--wb-r-tag);background:var(--wb-tag);color:var(--text-secondary);font:var(--fw-regular) var(--fs-caption)/1 var(--font-sans)}
|
||||
.gsearch .kbd{position:absolute;right:8px;pointer-events:none}
|
||||
.top-r{display:flex;align-items:center;justify-content:flex-end;gap:4px}
|
||||
.qchip{display:flex;align-items:center;gap:16px;height:36px;padding:0 10px;border-radius:var(--wb-r-ctl);text-decoration:none}
|
||||
.qchip:hover{background:var(--wb-fill)}
|
||||
.qchip .q{display:flex;align-items:center;gap:8px;color:var(--text-secondary);white-space:nowrap}
|
||||
.qchip .q .fig{color:var(--text-primary);font-size:16px;min-width:18px}
|
||||
.mbar{width:40px;height:4px;border-radius:2px;background:var(--wb-rail-off);overflow:hidden}
|
||||
.mbar i{display:block;height:100%;background:var(--c-ink)}
|
||||
.ibtn{position:relative;display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border:0;border-radius:var(--wb-r-ctl);background:none;cursor:pointer;color:var(--text-primary)}
|
||||
.ibtn:hover{background:var(--wb-fill)}
|
||||
.ibtn .dot{position:absolute;top:8px;right:9px;width:6px;height:6px;border-radius:50%;background:var(--c-ink);box-shadow:0 0 0 2px var(--wb-canvas)}
|
||||
|
||||
.content{width:100%;max-width:calc(var(--content-max) + 64px);padding:12px 32px 48px}
|
||||
|
||||
/* ---------- 页头 ---------- */
|
||||
.ph{display:flex;align-items:flex-end;gap:24px;margin-bottom:20px}
|
||||
.ph h1{font:var(--fw-bold) var(--wb-fs-h1)/var(--wb-lh-h1) var(--font-serif);letter-spacing:.02em}
|
||||
.ph p{margin-top:4px;color:var(--text-secondary)}
|
||||
.ph .act{margin-left:auto;display:flex;align-items:center;gap:12px}
|
||||
|
||||
/* ---------- 按钮 ---------- */
|
||||
.btn{display:inline-flex;align-items:center;gap:6px;height:34px;padding:0 14px;border:0;border-radius:var(--wb-r-ctl);background:var(--wb-fill);color:var(--text-primary);text-decoration:none;white-space:nowrap;cursor:pointer;transition:background var(--dur-fast) var(--ease)}
|
||||
.btn:hover{background:var(--wb-fill-hover)}
|
||||
.btn.primary{background:var(--c-ink);color:var(--text-on-dark);font-weight:var(--fw-bold)}
|
||||
.btn.primary:hover{background:var(--wb-primary-hover)}
|
||||
.btn.on-tint{background:var(--c-white)}
|
||||
.btn.on-tint:hover{background:var(--wb-fill)}
|
||||
.cost{color:var(--text-secondary);font-size:var(--fs-caption)}
|
||||
.cost .fig{color:var(--text-primary);font-size:14px}
|
||||
.lnk{color:var(--text-primary);text-underline-offset:3px}
|
||||
.lnk:hover{text-decoration-thickness:2px}
|
||||
|
||||
/* ---------- 提醒 ---------- */
|
||||
.alert{display:flex;align-items:center;gap:12px;padding:12px 12px 12px 16px;border-radius:var(--wb-r-panel);background:var(--warn-soft);margin-bottom:16px}
|
||||
.alert > .ic{color:var(--warn)}
|
||||
.alert p{flex:1;min-width:0}
|
||||
.alert b{font-weight:var(--fw-bold)}
|
||||
|
||||
/* ---------- 面板 ---------- */
|
||||
.panel{background:var(--wb-panel);border-radius:var(--wb-r-panel);padding:6px}
|
||||
.panel-h{display:flex;align-items:center;gap:12px;min-height:44px;padding:4px 10px 6px 12px}
|
||||
.panel-h h2,.panel-h h3{font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans)}
|
||||
.panel-h .more{margin-left:auto}
|
||||
.panel-f{padding:8px 12px 6px}
|
||||
|
||||
/* ---------- 统计 ---------- */
|
||||
.stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin-bottom:16px}
|
||||
.stat{display:block;text-align:left;border:0;background:none;border-radius:var(--wb-r-row);padding:12px 16px 14px;cursor:pointer;transition:background var(--dur-fast) var(--ease)}
|
||||
.stat:hover{background:var(--wb-stat-hover)}
|
||||
.stat[aria-pressed="true"]{background:var(--c-white)}
|
||||
.stat small{display:flex;align-items:center;gap:6px;font-size:var(--fs-body);line-height:var(--lh-body);color:var(--text-secondary)}
|
||||
.stat .big{display:block;margin:6px 0 2px;font:var(--fw-bold) var(--wb-fs-stat)/var(--wb-lh-stat) var(--font-figures);font-variant-numeric:tabular-nums;color:var(--text-primary)}
|
||||
.stat .big.fatal{color:var(--sev-fatal)}
|
||||
.stat .big.due{color:var(--warn)}
|
||||
.stat p{font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
|
||||
.stat p.due{color:var(--warn-text);font-weight:var(--fw-bold)}
|
||||
|
||||
/* ---------- 主体两栏 ---------- */
|
||||
.wb{display:grid;grid-template-columns:minmax(0,1fr) var(--wb-rail-w);gap:16px;align-items:start}
|
||||
.col{display:flex;flex-direction:column;gap:16px;min-width:0}
|
||||
|
||||
/* 分段控件:整体浅底,选中项白底,无描边 */
|
||||
.seg{display:inline-flex;gap:2px;padding:3px;border-radius:calc(var(--wb-r-ctl) + 3px);background:var(--wb-track)}
|
||||
.seg button{display:flex;align-items:center;gap:6px;height:28px;padding:0 12px;border:0;border-radius:var(--wb-r-ctl);background:none;color:var(--text-secondary);cursor:pointer;white-space:nowrap;transition:background var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)}
|
||||
.seg button:hover{color:var(--text-primary)}
|
||||
.seg button[aria-pressed="true"]{background:var(--c-white);color:var(--text-primary);font-weight:var(--fw-bold)}
|
||||
.seg .fig{font-weight:var(--fw-bold)}
|
||||
.chip{display:inline-flex;align-items:center;gap:4px;height:28px;padding:0 6px 0 10px;border:0;border-radius:var(--wb-r-ctl);background:var(--c-ink);color:var(--text-on-dark);cursor:pointer;white-space:nowrap}
|
||||
.chip:hover{background:var(--wb-primary-hover)}
|
||||
.lsearch{position:relative;margin-left:auto;display:flex;align-items:center}
|
||||
.lsearch .ic{position:absolute;left:10px;color:var(--text-secondary);pointer-events:none}
|
||||
.lsearch input{width:220px;height:34px;padding:0 10px 0 32px;border:1px solid var(--border-control);border-radius:var(--wb-r-ctl);background:var(--c-white)}
|
||||
.lsearch input::placeholder{color:var(--text-secondary)}
|
||||
input[type=search]::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}
|
||||
|
||||
/* 嵌入式列表 */
|
||||
.grid-cols{display:grid;grid-template-columns:minmax(0,1fr) 120px var(--wb-track-w) 56px 64px 16px;gap:16px;align-items:center}
|
||||
@media (max-width: 1399px){ .grid-cols{grid-template-columns:minmax(0,1fr) 112px var(--wb-track-w) 52px 60px 16px;gap:12px} }
|
||||
.cols{padding:6px 14px 6px 16px;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
|
||||
.cols .r,.row .r{text-align:right;justify-self:end}
|
||||
.rows{display:flex;flex-direction:column;gap:3px}
|
||||
.row{padding:12px 14px 12px 16px;min-height:64px;background:var(--c-white);border-radius:var(--wb-r-row);text-decoration:none;color:inherit;cursor:pointer;transition:background var(--dur-fast) var(--ease)}
|
||||
.row:hover{background:var(--wb-row-hover)}
|
||||
.row:focus-visible{outline-offset:-2px}
|
||||
.row .go{color:var(--text-secondary);opacity:0;transition:opacity var(--dur-fast) var(--ease)}
|
||||
.row:hover .go,.row:focus-visible .go{opacity:1}
|
||||
.pname{display:block;font-weight:var(--fw-bold);font-size:15px;line-height:22px}
|
||||
.psub{display:block;margin-top:2px;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
|
||||
.due b{display:block;font-weight:var(--fw-regular)}
|
||||
.due b .fig{font-size:15px}
|
||||
.due small{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
|
||||
.due.today b,.due.soon b{color:var(--warn-text)}
|
||||
.due.today b{font-weight:var(--fw-bold)}
|
||||
.due.past b{color:var(--text-secondary)}
|
||||
|
||||
/* 状态轨:四段 = 跟进中 → 编制中 → 已检查 → 已递交 */
|
||||
.track{position:relative;width:var(--wb-track-w);padding-bottom:22px}
|
||||
.track .segs{display:grid;grid-template-columns:repeat(4,1fr);gap:3px}
|
||||
.track .segs i{height:6px;border-radius:2px;background:var(--wb-rail-off)}
|
||||
.track .segs i.on{background:var(--wb-rail-on)}
|
||||
.track .lab{position:absolute;top:12px;white-space:nowrap}
|
||||
.track.arch .segs i{background:var(--wb-rail-arch)}
|
||||
.tag{display:inline-flex;align-items:center;height:20px;padding:0 6px;border-radius:var(--wb-r-tag);background:var(--wb-tag);color:var(--text-primary);font-size:var(--fs-caption);line-height:1;white-space:nowrap}
|
||||
.track.arch .tag{color:var(--text-secondary)}
|
||||
|
||||
/* 问题级别标签:致命实心 / 重要描边 / 提示无框 */
|
||||
.sev{display:inline-flex;align-items:center;gap:4px;height:20px;padding:0 6px;border-radius:var(--wb-r-tag);font-size:var(--fs-caption);line-height:1;font-weight:var(--fw-bold);white-space:nowrap}
|
||||
.sev.fatal{background:var(--sev-fatal);color:var(--text-on-dark)}
|
||||
.sev .fig{font-size:13px}
|
||||
.dash{color:var(--text-secondary)}
|
||||
.empty{padding:28px 16px;background:var(--c-white);border-radius:var(--wb-r-row);text-align:center}
|
||||
.empty b{display:block;font-weight:var(--fw-bold)}
|
||||
.empty p{margin-top:4px;color:var(--text-secondary)}
|
||||
|
||||
/* 待处理的致命问题 */
|
||||
.todo{position:relative;display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:12px;align-items:baseline;padding:12px 16px 12px 20px;background:var(--c-white);border-radius:var(--wb-r-row)}
|
||||
.todo::before{content:"";position:absolute;left:7px;top:12px;bottom:12px;width:3px;border-radius:2px;background:var(--sev-fatal)}
|
||||
.todo b{display:block;font-weight:var(--fw-bold)}
|
||||
.cite{display:inline-flex;align-items:center;gap:6px;color:var(--ai);text-decoration:none;white-space:nowrap}
|
||||
.cite:hover{color:var(--ai-strong);text-decoration:underline;text-underline-offset:3px}
|
||||
|
||||
/* 右栏 */
|
||||
.rail .panel{padding:6px 6px 8px}
|
||||
.rail .body{padding:4px 12px 4px}
|
||||
.task-h{display:flex;align-items:baseline;gap:8px}
|
||||
.task-h b{font-weight:var(--fw-bold)}
|
||||
.task-h span{margin-left:auto}
|
||||
.pbar{display:flex;align-items:center;gap:10px;margin-top:8px}
|
||||
.pbar .bar{flex:1;height:6px;border-radius:3px;background:var(--wb-rail-off);overflow:hidden}
|
||||
.pbar .bar i{display:block;height:100%;background:var(--c-ink);border-radius:3px}
|
||||
.pbar .fig{font-size:15px;min-width:34px;text-align:right}
|
||||
.queue{display:flex;gap:8px;align-items:flex-start;margin-top:12px;padding:10px 12px;border-radius:var(--wb-r-row);background:var(--c-white)}
|
||||
.queue .ic{margin-top:2px;color:var(--text-secondary)}
|
||||
.rnote{margin-top:10px}
|
||||
.q2{display:grid;grid-template-columns:1fr 1fr;gap:6px}
|
||||
.q2 > div{padding:10px 12px 12px;background:var(--c-white);border-radius:var(--wb-r-row)}
|
||||
.q2 small{display:block;color:var(--text-secondary)}
|
||||
.q2 .big{font:var(--fw-bold) 26px/34px var(--font-figures);font-variant-numeric:tabular-nums}
|
||||
.q2 .unit{margin-left:4px;color:var(--text-secondary);font-family:var(--font-figures);font-size:14px}
|
||||
.q2 .mbar{width:100%;margin-top:6px}
|
||||
.links{display:flex;justify-content:space-between;padding:10px 12px 2px}
|
||||
.log{display:flex;flex-direction:column;gap:2px}
|
||||
.log li{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;padding:6px 12px;border-radius:var(--wb-r-row);font-size:var(--fs-caption);line-height:var(--lh-caption)}
|
||||
.log li > span:last-child{color:var(--text-secondary);font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-size:13px}
|
||||
.log .who{font-weight:var(--fw-bold)}
|
||||
.log .ok{color:var(--confirmed);white-space:nowrap}
|
||||
|
||||
/* 悬停说明(浮层,可用阴影) */
|
||||
[data-tip]{position:relative}
|
||||
[data-tip]:hover::after,[data-tip]:focus-visible::after{content:attr(data-tip);position:absolute;z-index:30;top:calc(100% + 6px);left:0;width:max-content;max-width:260px;padding:6px 10px;border-radius:var(--wb-r-ctl);background:var(--c-ink);color:var(--text-on-dark);font:var(--fw-regular) var(--fs-caption)/var(--lh-caption) var(--font-sans);box-shadow:var(--shadow-overlay);pointer-events:none;white-space:normal}
|
||||
[data-tip].tip-r:hover::after,[data-tip].tip-r:focus-visible::after{left:auto;right:0}
|
||||
|
||||
.toasts{position:fixed;left:50%;bottom:28px;transform:translateX(-50%);z-index:60;display:flex;flex-direction:column;gap:8px}
|
||||
.toast{padding:10px 16px;border-radius:var(--wb-r-ctl);background:var(--c-ink);color:var(--text-on-dark);box-shadow:var(--shadow-overlay)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<aside class="side"><div class="side-in">
|
||||
<a class="brand" href="workbench-b.html"><img src="../../logo/svg/logo-horizontal.svg" alt="标博士"></a>
|
||||
<nav class="nav" aria-label="主导航">
|
||||
<a href="workbench-b.html" aria-current="page"><i data-ic="grid" class="lg"></i>工作台</a>
|
||||
<a href="#" data-demo><i data-ic="cert" class="lg"></i>资质库</a>
|
||||
<a href="#" data-demo><i data-ic="gauge" class="lg"></i>额度</a>
|
||||
<div class="grp">UI demo</div>
|
||||
<a href="../index.html"><i data-ic="book" class="lg"></i>demo 目录</a>
|
||||
</nav>
|
||||
<div class="side-foot"><b>王敏</b>南京某信息技术有限公司<br>专业版 · 至 2027-10-31</div>
|
||||
</div></aside>
|
||||
|
||||
<div class="main">
|
||||
<header class="top">
|
||||
<span class="date">11月3日 周二</span>
|
||||
<form class="gsearch" role="search" data-gsearch>
|
||||
<i data-ic="search"></i>
|
||||
<input type="search" placeholder="搜索项目、文件、条款" aria-label="全局搜索:项目、文件、条款" aria-keyshortcuts="Control+K" data-gq>
|
||||
<span class="kbd" aria-hidden="true">Ctrl K</span>
|
||||
</form>
|
||||
<div class="top-r">
|
||||
<a class="qchip" href="#" data-demo data-tip="本期剩余额度:解析 37 / 50 次,检查 12 / 20 次" aria-label="本期剩余额度:解析 37 次,检查 12 次">
|
||||
<span class="q">解析<span class="mbar" aria-hidden="true"><i style="width:26%"></i></span><span class="fig">37</span></span>
|
||||
<span class="q">检查<span class="mbar" aria-hidden="true"><i style="width:40%"></i></span><span class="fig">12</span></span>
|
||||
</a>
|
||||
<button class="ibtn tip-r" aria-label="通知(1 条未读)" data-tip="通知" data-notice><i data-ic="bell" class="lg"></i><span class="dot"></span></button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="content">
|
||||
<div class="ph">
|
||||
<div><h1>工作台</h1><p>按投标截止时间排序,截止临近的排在前面。</p></div>
|
||||
<div class="act">
|
||||
<span class="cost">扣 1 次解析 · 剩 <span class="fig">37</span> 次</span>
|
||||
<a class="btn primary" href="../forms.html#s-new"><i data-ic="plus"></i>新建项目</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert" role="note">
|
||||
<i data-ic="clock" class="lg"></i>
|
||||
<p><b>某县数据中心机房改造项目今天 17:00 截止。</b>清单还是草稿,还没有做过检查。</p>
|
||||
<a class="btn on-tint" href="../page-reading.html">去确认清单</a>
|
||||
</div>
|
||||
|
||||
<section class="panel stats" aria-label="项目统计,点击可筛选下方项目列表" data-stats>
|
||||
<button class="stat" data-f="active" aria-pressed="false"><small>进行中的项目</small><span class="big">4</span><p>已递交 1 · 已归档 1</p></button>
|
||||
<button class="stat" data-f="due7" aria-pressed="false"><small><i data-ic="clock"></i>7 天内截止</small><span class="big due">2</span><p class="due">最近:今天 17:00</p></button>
|
||||
<button class="stat" data-f="fatal" aria-pressed="false"><small>待处理的致命问题</small><span class="big fatal">3</span><p>在 1 个项目里</p></button>
|
||||
<button class="stat" data-f="pend" aria-pressed="false"><small>待确认的清单条目</small><span class="big">4</span><p>在 2 个项目里</p></button>
|
||||
</section>
|
||||
|
||||
<div class="wb">
|
||||
<div class="col">
|
||||
<section class="panel" aria-labelledby="h-proj">
|
||||
<div class="panel-h">
|
||||
<h2 id="h-proj">项目</h2>
|
||||
<div class="seg" role="group" aria-label="按状态筛选" data-seg>
|
||||
<button aria-pressed="true" data-v="active">进行中 <span class="fig">4</span></button>
|
||||
<button aria-pressed="false" data-v="all">全部 <span class="fig">6</span></button>
|
||||
<button aria-pressed="false" data-v="已递交">已递交</button>
|
||||
<button aria-pressed="false" data-v="已归档">已归档</button>
|
||||
</div>
|
||||
<span data-chip></span>
|
||||
<label class="lsearch"><i data-ic="search"></i><input type="search" placeholder="搜索项目、采购人" aria-label="在项目列表中搜索" data-q></label>
|
||||
</div>
|
||||
<div class="cols grid-cols" aria-hidden="true"><span>项目</span><span>投标截止</span><span>进度</span><span>就绪</span><span class="r">致命</span><span></span></div>
|
||||
<ul class="rows" data-rows aria-label="项目列表"></ul>
|
||||
<p class="panel-f cap sec">「就绪」只表示致命问题都已处理,不代表投标文件没有问题。</p>
|
||||
</section>
|
||||
|
||||
<section class="panel" aria-labelledby="h-todo">
|
||||
<div class="panel-h">
|
||||
<h2 id="h-todo">待处理的致命问题</h2>
|
||||
<span class="sev fatal">致命<span class="fig">3</span></span>
|
||||
<a class="more lnk cap" href="#" data-demo>去查标页处理</a>
|
||||
</div>
|
||||
<ul class="rows">
|
||||
<li class="todo"><span class="sev fatal">致命</span><div><b>报价超过采购包 1 的最高限价</b><span class="psub">智慧大厅建设项目 · 开标一览表 287.60 万元,限价 286.00 万元</span></div><a class="cite" href="#" data-demo><i data-ic="file"></i>报价册 · 第 <span class="fig">1</span> 页</a></li>
|
||||
<li class="todo"><span class="sev fatal">致命</span><div><b>★ 条款疑似不满足:应用服务器内存</b><span class="psub">智慧大厅建设项目 · 响应 256G,要求 ≥ 512G · 上次标记为已修改,问题仍在</span></div><a class="cite" href="#" data-demo><i data-ic="file"></i>技术册 · 第 <span class="fig">12</span> 页</a></li>
|
||||
<li class="todo"><span class="sev fatal">致命</span><div><b>授权委托书的单位签章位置未发现盖章</b><span class="psub">智慧大厅建设项目 · 只看有无,不鉴定真伪</span></div><a class="cite" href="#" data-demo><i data-ic="file"></i>商务册 · 第 <span class="fig">5</span> 页</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="col rail">
|
||||
<section class="panel" aria-labelledby="h-task">
|
||||
<div class="panel-h"><h3 id="h-task">正在进行的任务</h3></div>
|
||||
<div class="body">
|
||||
<div class="task-h"><b>解析补遗 2</b><span class="cap sec">提取清单</span></div>
|
||||
<div class="cap sec">某市医保信息系统等保测评服务</div>
|
||||
<div class="pbar"><div class="bar" role="progressbar" aria-valuenow="62" aria-valuemin="0" aria-valuemax="100" aria-label="解析补遗 2 的进度"><i style="width:62%"></i></div><span class="fig">62%</span></div>
|
||||
<div class="queue"><i data-ic="clock"></i><span class="cap sec">检查排队中 · 智慧社区平台运维 · 前面还有 1 个任务</span></div>
|
||||
<p class="rnote cap sec">可以离开本页,完成后会发站内通知。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel" aria-labelledby="h-quota">
|
||||
<div class="panel-h"><h3 id="h-quota">本期额度</h3><span class="more cap sec">至 2027-10-31</span></div>
|
||||
<div class="q2">
|
||||
<div><small>解析剩余</small><span class="big">37</span><span class="unit">/ 50</span><div class="mbar" aria-hidden="true"><i style="width:26%"></i></div></div>
|
||||
<div><small>检查剩余</small><span class="big">12</span><span class="unit">/ 20</span><div class="mbar" aria-hidden="true"><i style="width:40%"></i></div></div>
|
||||
</div>
|
||||
<div class="links"><a class="lnk cap" href="#" data-demo>扣点规则</a><a class="lnk cap" href="#" data-demo>查看流水</a></div>
|
||||
</section>
|
||||
|
||||
<section class="panel" aria-labelledby="h-log">
|
||||
<div class="panel-h"><h3 id="h-log">最近处理</h3><a class="more lnk cap" href="#" data-demo>全部记录</a></div>
|
||||
<ul class="log">
|
||||
<li><span><span class="who">王敏</span> <span class="ok">✓ 已修改</span>「授权委托书签章」</span><span>14:26</span></li>
|
||||
<li><span><span class="who">王敏</span> 确认了清单 v2 · 智慧大厅</span><span>11-02</span></li>
|
||||
<li><span><span class="who">系统</span> 完成第 2 次检查 · 致命 3</span><span>11-02</span></li>
|
||||
<li><span><span class="who">系统</span> 并入补遗 1 · 更新 2 条</span><span>11-02</span></li>
|
||||
<li><span><span class="who">李工</span> 忽略「其他项目名称」:业绩介绍</span><span>11-01</span></li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const $ = (s, el = document) => el.querySelector(s);
|
||||
const $$ = (s, el = document) => [...el.querySelectorAll(s)];
|
||||
/* Lucide 图标(ISC 许可)路径 */
|
||||
const ICONS = {
|
||||
grid: '<rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/>',
|
||||
cert: '<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 9h10M7 13h6"/><circle cx="16" cy="15" r="2"/>',
|
||||
gauge: '<path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/>',
|
||||
bell: '<path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/>',
|
||||
book: '<path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/>',
|
||||
search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
|
||||
plus: '<path d="M5 12h14"/><path d="M12 5v14"/>',
|
||||
clock: '<circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/>',
|
||||
file: '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8M16 13H8M16 17H8"/>',
|
||||
"chev-right": '<path d="m9 18 6-6-6-6"/>',
|
||||
x: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>'
|
||||
};
|
||||
const icon = (n, cls = "") => `<svg class="ic ${cls}" viewBox="0 0 24 24" aria-hidden="true">${ICONS[n] || ""}</svg>`;
|
||||
$$("i[data-ic]").forEach((el) => { el.outerHTML = icon(el.dataset.ic, el.className); });
|
||||
|
||||
function toast(msg) {
|
||||
let box = $(".toasts");
|
||||
if (!box) { box = document.createElement("div"); box.className = "toasts"; box.setAttribute("role", "status"); box.setAttribute("aria-live", "polite"); document.body.appendChild(box); }
|
||||
const t = document.createElement("div"); t.className = "toast"; t.textContent = msg; box.appendChild(t);
|
||||
setTimeout(() => t.remove(), 4000);
|
||||
}
|
||||
|
||||
/* 数据沿用 page-workbench.html;due 拆成两行,pend 为待确认清单条目数(对应统计「在 2 个项目里」) */
|
||||
const STEPS = ["跟进中", "编制中", "已检查", "已递交"];
|
||||
const P = [
|
||||
{ name: "某县数据中心机房改造项目", buyer: "某县大数据管理局", lot: "标段 1", h: 6, due: ["今天 <span class=fig>17:00</span> 截止", "剩 <span class=fig>6</span> 小时"], cls: "today", st: "编制中", ready: "", fatal: 0, pend: 3, href: "../page-reading.html" },
|
||||
{ name: "某区智慧社区平台运维服务", buyer: "某区民政局", lot: "全部", h: 53, due: ["剩 <span class=fig>2</span> 天 <span class=fig>5</span> 小时", "11月5日 16:00"], cls: "soon", st: "已检查", ready: "就绪", fatal: 0, pend: 0 },
|
||||
{ name: "某市政务服务中心智慧大厅建设项目", buyer: "某市政务服务管理办公室", lot: "采购包 1", h: 408, due: ["剩 <span class=fig>17</span> 天", "11月20日 09:30"], cls: "", st: "已检查", ready: "未就绪", fatal: 3, pend: 0, href: "../page-reading.html" },
|
||||
{ name: "某市医保信息系统等保测评服务", buyer: "某市医疗保障局", lot: "全部", h: 600, due: ["剩 <span class=fig>25</span> 天", "11月28日 10:00"], cls: "", st: "跟进中", ready: "", fatal: 0, pend: 1 },
|
||||
{ name: "某区智慧校园一期", buyer: "某区教育局", lot: "全部", h: -1, due: ["已截止", ""], cls: "past", st: "已递交", ready: "", fatal: 0, pend: 0 },
|
||||
{ name: "某市不动产登记信息平台升级", buyer: "某市自然资源和规划局", lot: "采购包 2", h: -2, due: ["已截止", ""], cls: "past", st: "已归档", ready: "", fatal: 0, pend: 0 }
|
||||
];
|
||||
const STAT = {
|
||||
active: { label: "进行中", fn: (p) => !["已递交", "已归档"].includes(p.st) },
|
||||
due7: { label: "7 天内截止", fn: (p) => p.h >= 0 && p.h <= 168 },
|
||||
fatal: { label: "有致命问题", fn: (p) => p.fatal > 0 },
|
||||
pend: { label: "有待确认条目", fn: (p) => p.pend > 0 }
|
||||
};
|
||||
let seg = "active", stat = "", q = "";
|
||||
|
||||
function track(p) {
|
||||
const arch = p.st === "已归档";
|
||||
const idx = arch ? 3 : STEPS.indexOf(p.st);
|
||||
const segs = STEPS.map((s, i) => `<i class="${i <= idx ? "on" : ""}"></i>`).join("");
|
||||
/* 当前状态标签对齐到它那一段的起点;最后一段右对齐 */
|
||||
const pos = arch || idx === 3 ? "right:0" : `left:calc(${idx} * (100% + 3px) / 4)`;
|
||||
const sr = arch ? "进度:已归档(四步都已走完)" : `进度:${p.st},第 ${idx + 1} 步,共 4 步(${STEPS.join("、")})`;
|
||||
return `<span class="track ${arch ? "arch" : ""}"><span class="vh">${sr}</span><span class="segs" aria-hidden="true">${segs}</span><span class="lab" style="${pos}" aria-hidden="true"><span class="tag">${p.st}</span></span></span>`;
|
||||
}
|
||||
|
||||
function render() {
|
||||
const rows = P.filter((p) => {
|
||||
if (stat) { if (!STAT[stat].fn(p)) return false; }
|
||||
else if (seg !== "all" && !(seg === "active" ? STAT.active.fn(p) : p.st === seg)) return false;
|
||||
return !q || (p.name + p.buyer).includes(q);
|
||||
});
|
||||
$("[data-rows]").innerHTML = rows.map((p) => `<li><a class="row grid-cols" href="${p.href || "#"}" ${p.href ? "" : "data-demo"}>
|
||||
<span><span class="pname">${p.name}</span><span class="psub">${p.buyer} · ${p.lot}</span></span>
|
||||
<span class="due ${p.cls}"><b>${p.due[0]}</b>${p.due[1] ? `<small>${p.due[1]}</small>` : ""}</span>
|
||||
${track(p)}
|
||||
<span>${p.ready === "就绪" ? '<span class="tag" data-tip="只表示致命问题都已处理,不代表投标文件没有问题。">就绪</span>' : p.ready ? p.ready : '<span class="dash" aria-label="不适用">—</span>'}</span>
|
||||
<span class="r">${p.fatal ? `<span class="sev fatal">致命<span class="fig">${p.fatal}</span></span>` : '<span class="dash" aria-label="无">—</span>'}</span>
|
||||
<span class="go">${icon("chev-right")}</span></a></li>`).join("")
|
||||
|| `<li class="empty"><b>没有符合条件的项目</b><p>换个关键词,或切到「全部」看看。</p></li>`;
|
||||
$$("[data-seg] button").forEach((b) => b.setAttribute("aria-pressed", !stat && b.dataset.v === seg));
|
||||
$$("[data-stats] .stat").forEach((b) => b.setAttribute("aria-pressed", b.dataset.f === stat || (!stat && b.dataset.f === "active" && seg === "active")));
|
||||
$("[data-chip]").innerHTML = stat && stat !== "active" ? `<button class="chip" data-clear aria-label="清除筛选:${STAT[stat].label}">${STAT[stat].label}${icon("x")}</button>` : "";
|
||||
}
|
||||
|
||||
$("[data-seg]").addEventListener("click", (e) => { const b = e.target.closest("button"); if (!b) return; seg = b.dataset.v; stat = ""; render(); });
|
||||
$("[data-stats]").addEventListener("click", (e) => {
|
||||
const b = e.target.closest(".stat"); if (!b) return;
|
||||
const f = b.dataset.f;
|
||||
if (f === "active") { seg = "active"; stat = ""; }
|
||||
else if (stat === f) { stat = ""; seg = "active"; }
|
||||
else stat = f;
|
||||
render();
|
||||
});
|
||||
$("[data-chip]").addEventListener("click", (e) => { if (e.target.closest("[data-clear]")) { stat = ""; seg = "active"; render(); $("[data-seg] button[aria-pressed=true]").focus(); } });
|
||||
$("[data-q]").addEventListener("input", (e) => { q = e.target.value.trim(); render(); });
|
||||
document.addEventListener("click", (e) => { const d = e.target.closest("[data-demo]"); if (d) { e.preventDefault(); toast("(演示)这里只做了工作台,可以打开「某县数据中心」或「智慧大厅」看读标页"); } });
|
||||
$("[data-notice]").onclick = () => toast("解析完成:补遗 1 已并入清单(新增 2 条,更新 1 条)。");
|
||||
$("[data-gsearch]").addEventListener("submit", (e) => { e.preventDefault(); toast("(演示)全局搜索还没有接入,项目可以在下方列表里搜索"); });
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") { e.preventDefault(); $("[data-gq]").focus(); }
|
||||
if (e.key === "Escape" && document.activeElement === $("[data-gq]")) $("[data-gq]").blur();
|
||||
});
|
||||
render();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user