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:
654
docs/design/ui-demo/explore/workbench-d4.html
Normal file
654
docs/design/ui-demo/explore/workbench-d4.html
Normal file
@@ -0,0 +1,654 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
工作台 · 风格方案 D「明亮工作区」(探索稿,不是定稿)
|
||||
数据、文案、交互沿用 ../page-workbench.html 与 ../demo.js。
|
||||
|
||||
本方案相对 design-system.md 改了哪些规则:
|
||||
0d. (d4 修订)按用户意见去掉 d3 加的装饰图标(统计、区块标题、下一步、最近处理、顶栏),只保留原有的功能图标;问候语保留。
|
||||
0c. (d4)侧栏与内容区融合:侧栏双色竖向渐变(墨 → 墨混 18% 蓝铅笔的深墨蓝,只作底色、不承担 AI 语义)+ 底部淡点阵;内容区左上角 12px 圆角压在深色上;当前菜单项:浅色半透明底 + 侧栏左缘 3px 白色竖条(?sel=bar 只有竖条,?sel=solid 白色实底),不再连进内容区(折叠动画会穿帮)。项目行去掉状态图标列。?v=flat 看 D3 的平铺侧栏对照。
|
||||
0b. (d3)顶栏:透明落在页面底色上、滚动后变为半透明白 + 毛玻璃 + 发丝线,和墨色侧栏区分;搜索框靠右。图标:统计、区块标题、项目行、下一步、最近处理都加 Lucide 图标;图标底块只用中性浅灰,彩色只给有语义的(致命红、赭、蓝铅笔)。页头加问候语。
|
||||
0. (d2)侧边栏改为墨色底(--c-ink),logo 用反白版;可折叠到 64px 只留图标,折叠状态记在本地。?side=light 可切换为浅灰侧栏对照。
|
||||
1. §3.1 页面底色:校样纸 #F5F6F3 → --n-50(墨 3% 混白的中性冷灰);侧边栏、顶栏、卡片纯白。
|
||||
偏绿的 --c-paper / --c-sunken / --c-hover / --c-rule 一律不用,浅底和分隔线只用下面三个从墨派生的值。
|
||||
2. §5.1 层次:只有两种容器——页面(--n-50)和白卡片(圆角 12、1px --n-line、无阴影)。卡片里不再套面板。
|
||||
可点击的整张卡片(本页没有)和浮层才用阴影;行、格的悬停只出 --n-50 底色。
|
||||
3. §5.1 圆角:2px 一档 → 卡片 12、按钮 / 输入框 / 菜单 8、标签 6。
|
||||
4. §7.1 按钮:高 32 → 36(主 / 次);表格行内 32。主按钮墨底白字;次按钮白底 + --c-muted 控件边框(3.9:1);
|
||||
文字按钮悬停出 --n-100 底。普通链接仍是墨色 + 下划线,蓝铅笔只给出处。
|
||||
5. §7.7 表头:凹陷灰底 + 粗底线 → 无底色,12px 辅助灰列名,无线。行高 64,行间 --n-line 发丝线。
|
||||
6. §7.9 项目行:
|
||||
- 「状态」中性描边标签 → --n-100 实底、圆角 6、无描边;全页只有这一种中性标签。
|
||||
- 「就绪」从描边标签改为墨色普通字 + 虚线下划线(悬停说明不变),和「未就绪」并排写在状态标签后面,省掉一列。
|
||||
- 「所投包」并入项目名下方的辅助灰行。
|
||||
- 新增「下一步」列:默认是带箭头的文字链,整行悬停 / 键盘聚焦时变成次按钮,同时浮现「更多」图标按钮。
|
||||
会扣点的操作(发起检查)放在「更多」里,并写明扣多少、还剩多少;有致命问题时「导出检查报告」仍可用。
|
||||
7. 工作台统计:四张卡片 → 一张卡片四格,竖向发丝线分隔,数字 30 → 28(Barlow 600)。
|
||||
每格可点击筛选项目列表,悬停出 --n-50 底,选中格底部 2px 墨色边条。
|
||||
8. §5.3 顶栏:面包屑位置改为企业主体名;中间加全局搜索(白底 + 控件边框、圆角 8、Ctrl K),本页用它筛选项目列表。
|
||||
9. §7.12 截止提醒条:赭色浅底条 → 同规格白卡片,赭色图标 + 赭色时间字。
|
||||
10. §7.4「致命」行左侧 3px 竖线:工作台的待处理列表不用竖线,靠左侧实心「致命」标签扫读。
|
||||
未改:颜色语义、问题级别形状、字体分工、字阶(正文 14 / 标题 16 / 页标题 24)、4px 间距基准、Lucide 图标。
|
||||
-->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>工作台 · 方案 D4</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<link rel="stylesheet" href="../../system/tokens.css">
|
||||
<link rel="stylesheet" href="../../system/fonts-local.css">
|
||||
<style>
|
||||
/* ---------- 本方案派生变量:浅底和分隔线只从墨派生,不引入新色相 ---------- */
|
||||
:root{
|
||||
--n-50: color-mix(in srgb, var(--c-ink) 3%, white); /* 页面底、行 / 格悬停 */
|
||||
--n-100: color-mix(in srgb, var(--c-ink) 6%, white); /* 标签底、文字按钮悬停、进度槽 */
|
||||
--n-line: color-mix(in srgb, var(--c-ink) 11%, white); /* 发丝线、卡片边 */
|
||||
--ctl-border: var(--c-muted); /* 控件边框 3.9:1 */
|
||||
--primary-hover: color-mix(in srgb, var(--c-ink) 88%, white);
|
||||
|
||||
--r-card: 12px; --r-ctl: 8px; --r-tag: 6px;
|
||||
--btn-h: 36px; --btn-h-sm: 32px;
|
||||
--top-h: 56px;
|
||||
--pad: 32px; /* 页面左右内边距 */
|
||||
--gap: 16px; /* 卡片之间、主列与右栏之间:全页一个节奏 */
|
||||
--card-x: 20px; /* 卡片内左右边距:所有卡片统一 */
|
||||
--rail-w: 296px;
|
||||
--side-w: var(--sidebar-w); --side-w-min: 64px;
|
||||
/* 侧栏:墨色底。文字和底色都从墨、白派生,不引入新色相 */
|
||||
--side-bg: var(--c-ink);
|
||||
--side-fg: color-mix(in srgb, white 74%, var(--c-ink)); /* 未选中项 约 9:1 */
|
||||
--side-fg-strong: var(--c-white);
|
||||
--side-hover: color-mix(in srgb, white 7%, transparent); /* 半透明:在渐变上各处一致 */
|
||||
--side-active: color-mix(in srgb, white 12%, transparent);
|
||||
--side-line: color-mix(in srgb, white 12%, var(--c-ink));
|
||||
--side-muted: color-mix(in srgb, white 58%, var(--c-ink)); /* 分组小字、页脚 约 6:1 */
|
||||
--cols: minmax(0,1fr) 120px 120px 64px 168px; /* 项目行栅格 */
|
||||
--col-gap: 16px;
|
||||
}
|
||||
@media (max-width: 1399px){
|
||||
:root{ --pad: 24px; --rail-w: 256px; --cols: minmax(0,1fr) 108px 108px 56px 160px; --col-gap: 12px; }
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0}
|
||||
body{background:var(--n-50);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{font:inherit;color:inherit}
|
||||
.fig{font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-weight:var(--fw-bold)}
|
||||
.sec{color:var(--text-secondary)}
|
||||
.cap{font-size:var(--fs-caption);line-height:var(--lh-caption)}
|
||||
.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}
|
||||
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
|
||||
:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px;border-radius:var(--r-ctl)}
|
||||
|
||||
/* ---------- 链接与按钮 ---------- */
|
||||
.lnk{color:var(--text-primary);text-decoration:underline;text-decoration-color:color-mix(in srgb, var(--c-ink) 30%, white);text-underline-offset:3px;transition:text-decoration-color var(--dur-fast) var(--ease)}
|
||||
.lnk:hover{text-decoration-color:var(--c-ink)}
|
||||
.cite{display:inline-flex;align-items:center;gap:4px;color:var(--ai);text-decoration:none;white-space:nowrap;border-radius:var(--r-ctl);padding:2px 6px;margin:-2px -6px;transition:background-color var(--dur-fast) var(--ease)}
|
||||
.cite:hover{background:var(--ai-soft);color:var(--ai-strong)}
|
||||
|
||||
.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:var(--btn-h);padding:0 14px;border-radius:var(--r-ctl);border:1px solid var(--ctl-border);background:var(--c-white);color:var(--text-primary);font-size:var(--fs-body);line-height:1;white-space:nowrap;text-decoration:none;cursor:pointer;transition:background-color var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)}
|
||||
.btn:hover{background:var(--n-50);border-color:var(--c-ink)}
|
||||
.btn:active{background:var(--n-100)}
|
||||
.btn.primary{background:var(--c-ink);border-color:var(--c-ink);color:var(--c-white);font-weight:var(--fw-bold)}
|
||||
.btn.primary:hover{background:var(--primary-hover)}
|
||||
.btn.sm{height:var(--btn-h-sm);padding:0 12px}
|
||||
.tbtn{display:inline-flex;align-items:center;gap:6px;height:var(--btn-h-sm);padding:0 10px;border:0;border-radius:var(--r-ctl);background:transparent;cursor:pointer;white-space:nowrap;text-decoration:none;transition:background-color var(--dur-fast) var(--ease)}
|
||||
.tbtn:hover{background:var(--n-100)}
|
||||
.ibtn{position:relative;display:inline-grid;place-items:center;width:var(--btn-h);height:var(--btn-h);border:0;border-radius:var(--r-ctl);background:transparent;color:var(--text-primary);cursor:pointer;transition:background-color var(--dur-fast) var(--ease)}
|
||||
.ibtn:hover{background:var(--n-100)}
|
||||
.ibtn.sm{width:var(--btn-h-sm);height:var(--btn-h-sm)}
|
||||
|
||||
/* ---------- 标签:中性一种 + 问题级别 ---------- */
|
||||
.tag{display:inline-flex;align-items:center;height:22px;padding:0 8px;border-radius:var(--r-tag);background:var(--n-100);color:var(--text-primary);font-size:var(--fs-caption);white-space:nowrap}
|
||||
.sev{display:inline-flex;align-items:center;gap:4px;height:22px;padding:0 8px;border-radius:var(--r-tag);font-size:var(--fs-caption);font-weight:var(--fw-bold);white-space:nowrap}
|
||||
.sev.fatal{background:var(--sev-fatal);color:var(--c-white)}
|
||||
.sev .fig{font-weight:var(--fw-bold)}
|
||||
.dash{color:var(--text-secondary)}
|
||||
|
||||
/* ---------- 应用框架 ---------- */
|
||||
.app{display:grid;grid-template-columns:var(--side-w) minmax(0,1fr);min-height:100vh;transition:grid-template-columns var(--dur-base) var(--ease)}
|
||||
.app[data-collapsed]{--side-w:var(--side-w-min)}
|
||||
.side{background:var(--side-bg);color:var(--side-fg);min-width:0}
|
||||
/* ---- d4:侧栏与内容区融合 ---- */
|
||||
.app:not([data-side="light"]):not([data-v="flat"]){
|
||||
--side-deep: color-mix(in srgb, var(--c-ink) 82%, var(--c-pencil)); /* 渐变下端:深墨蓝 */
|
||||
background:linear-gradient(180deg, var(--c-ink) 0%, var(--side-deep) 100%) fixed;
|
||||
}
|
||||
.app:not([data-side="light"]):not([data-v="flat"]) .side{background:transparent;position:relative}
|
||||
.app:not([data-side="light"]):not([data-v="flat"]) .side-in::after{content:"";position:absolute;left:0;right:0;bottom:0;height:260px;pointer-events:none;
|
||||
background-image:radial-gradient(color-mix(in srgb, white 14%, transparent) 1px, transparent 1.2px);background-size:14px 14px;
|
||||
-webkit-mask-image:linear-gradient(to top, black, transparent);mask-image:linear-gradient(to top, black, transparent)}
|
||||
.app:not([data-side="light"]):not([data-v="flat"]) .side-in > *{position:relative;z-index:1}
|
||||
.app:not([data-side="light"]):not([data-v="flat"]) .main{background:var(--n-50);border-top-left-radius:12px;min-height:100vh}
|
||||
.app:not([data-side="light"]):not([data-v="flat"]) .top{border-top-left-radius:12px}
|
||||
/* 浅灰侧栏对照(?side=light) */
|
||||
.app[data-side="light"]{--side-bg:color-mix(in srgb, var(--c-ink) 9%, white);--side-fg:var(--text-secondary);--side-fg-strong:var(--text-primary);--side-hover:color-mix(in srgb, var(--c-ink) 14%, white);--side-active:var(--c-white);--side-line:var(--n-line);--side-muted:var(--text-secondary)}
|
||||
.app[data-side="light"] .side{border-right:1px solid var(--n-line)}
|
||||
.app[data-side="light"] .nav a[aria-current]{box-shadow:0 0 0 1px var(--n-line)}
|
||||
.brand .mini{display:none;height:32px;width:32px}
|
||||
.app[data-collapsed] .brand .full{display:none}
|
||||
.app[data-collapsed] .brand .mini{display:block}
|
||||
.app[data-side="light"] .brand .full.rev,.app:not([data-side="light"]) .brand .full.std{display:none}
|
||||
.side-in{position:sticky;top:0;height:100vh;display:flex;flex-direction:column;padding:0 12px 12px;overflow:hidden}
|
||||
.brand{display:flex;align-items:center;justify-content:center;height:72px;padding:0;margin:0 0 8px;flex:none}
|
||||
.brand:focus-visible{outline:2px solid var(--side-fg-strong);outline-offset:-2px;border-radius:var(--r-ctl)}
|
||||
.brand img{height:34px;display:block}
|
||||
.nav{display:flex;flex-direction:column;gap:2px}
|
||||
.nav a,.side-btn{position:relative;display:flex;align-items:center;gap:10px;height:var(--btn-h);padding:0 10px;border-radius:var(--r-ctl);text-decoration:none;color:var(--side-fg);white-space:nowrap;transition:background-color var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)}
|
||||
.nav a .ic,.side-btn .ic{flex:none}
|
||||
.nav a:hover,.side-btn:hover{background:var(--side-hover);color:var(--side-fg-strong)}
|
||||
.nav a[aria-current]{background:var(--side-active);color:var(--side-fg-strong);font-weight:var(--fw-bold)}
|
||||
/* 选中样式(d4 修订):选中效果全部留在侧栏内,折叠动画中不会露出。?sel=bar / solid 切换对照 */
|
||||
.nav a[aria-current]::before{content:"";position:absolute;left:-12px;top:8px;bottom:8px;width:3px;border-radius:0 3px 3px 0;background:var(--side-fg-strong)}
|
||||
.app[data-sel="bar"] .nav a[aria-current]{background:transparent}
|
||||
.app[data-sel="bar"] .nav a[aria-current]:hover{background:var(--side-hover)}
|
||||
.app[data-sel="solid"] .nav a[aria-current]{background:var(--c-white);color:var(--text-primary)}
|
||||
.app[data-sel="solid"] .nav a[aria-current]::before{display:none}
|
||||
.app[data-side="light"] .nav a[aria-current]::before{background:var(--c-ink)}
|
||||
.nav a:focus-visible,.side-btn:focus-visible{outline:2px solid var(--side-fg-strong);outline-offset:-2px}
|
||||
.nav .grp{margin:20px 10px 6px;height:20px;font-size:var(--fs-caption);line-height:20px;color:var(--side-muted);white-space:nowrap}
|
||||
.side-btn{width:100%;border:0;background:transparent;font:inherit;cursor:pointer;margin-top:auto}
|
||||
.side-foot{display:flex;align-items:center;gap:10px;margin-top:8px;padding:12px 6px 0;border-top:1px solid var(--side-line);font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--side-muted);white-space:nowrap}
|
||||
.side-foot b{display:block;color:var(--side-fg-strong);font-size:var(--fs-body);line-height:var(--lh-body);font-weight:var(--fw-bold)}
|
||||
.avatar{flex:none;display:inline-grid;place-items:center;width:32px;height:32px;border-radius:50%;background:var(--side-active);color:var(--side-fg-strong);font-weight:var(--fw-bold);font-size:var(--fs-body)}
|
||||
.app[data-side="light"] .avatar{background:var(--c-ink);color:var(--c-white)}
|
||||
/* 折叠:只留图标;文字保留给读屏,悬停出提示 */
|
||||
.app[data-collapsed] .lbl,.app[data-collapsed] .side-foot .who{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
|
||||
.app[data-collapsed] .nav a,.app[data-collapsed] .side-btn{justify-content:center;padding:0;width:40px}
|
||||
.app[data-collapsed] .nav .grp{margin:20px 4px 6px;font-size:0;border-top:1px solid var(--side-line);height:0}
|
||||
.app[data-collapsed] .side-foot{justify-content:center;padding:12px 0 0}
|
||||
.app[data-collapsed] .brand{justify-content:center;padding:0}
|
||||
.app[data-collapsed] [data-stip]:hover::after,.app[data-collapsed] [data-stip]:focus-visible::after{content:attr(data-stip);position:absolute;z-index:40;left:calc(100% + 12px);top:50%;transform:translateY(-50%);width:max-content;padding:6px 10px;border-radius:var(--r-ctl);background:var(--c-ink);color:var(--c-white);font-size:var(--fs-caption);line-height:var(--lh-caption);font-weight:var(--fw-regular);box-shadow:var(--shadow-overlay);pointer-events:none}
|
||||
.side-in:has([data-stip]:hover){overflow:visible}
|
||||
|
||||
.top{position:sticky;top:0;z-index:5;display:flex;align-items:center;gap:12px;height:var(--top-h);padding:0 var(--pad);background:transparent;border-bottom:1px solid transparent;transition:background-color var(--dur-base) var(--ease),border-color var(--dur-base) var(--ease)}
|
||||
.top.scrolled{background:color-mix(in srgb, var(--c-white) 82%, transparent);-webkit-backdrop-filter:blur(14px) saturate(1.2);backdrop-filter:blur(14px) saturate(1.2);border-bottom-color:var(--n-line)}
|
||||
.top .search{margin-left:auto;width:280px;transition:width var(--dur-base) var(--ease),border-color var(--dur-fast) var(--ease)}
|
||||
.top .search:focus-within{width:360px}
|
||||
.top .vsep{width:1px;height:20px;background:var(--n-line);margin:0 4px}
|
||||
.org .ic{color:var(--text-secondary)}
|
||||
.org{justify-self:start;display:inline-flex;align-items:center;gap:6px;color:var(--text-secondary)}
|
||||
.org b{color:var(--text-primary);font-weight:var(--fw-bold)}
|
||||
.search{position:relative;display:flex;align-items:center;height:var(--btn-h);border:1px solid var(--ctl-border);border-radius:var(--r-ctl);background:var(--c-white);transition:border-color var(--dur-fast) var(--ease)}
|
||||
.search:hover{border-color:var(--c-ink)}
|
||||
.search:focus-within{border-color:var(--c-ink);box-shadow:0 0 0 1px var(--c-ink)}
|
||||
.search > .ic{position:absolute;left:11px;color:var(--text-secondary);pointer-events:none}
|
||||
.search input{flex:1;min-width:0;height:100%;border:0;background:transparent;padding:0 8px 0 34px;font:inherit;color:var(--text-primary);outline:0}
|
||||
.search input::placeholder{color:var(--text-secondary)}
|
||||
.search input::-webkit-search-cancel-button{display:none}
|
||||
.kbd{flex:none;margin-right:6px;padding:0 6px;height:22px;display:inline-flex;align-items:center;border-radius:var(--r-tag);background:var(--n-100);color:var(--text-secondary);font-size:var(--fs-caption);font-family:var(--font-figures);font-weight:var(--fw-bold)}
|
||||
.top-r{justify-self:end;display:flex;align-items:center;gap:4px}
|
||||
.qchip{display:inline-flex;align-items:center;gap:6px;height:var(--btn-h-sm);padding:0 10px;color:var(--text-secondary)}
|
||||
.qchip .fig{color:var(--text-primary);font-size:15px}
|
||||
.qchip .sep{width:1px;height:12px;background:var(--n-line);margin:0 4px}
|
||||
.dot{position:absolute;top:8px;right:8px;width:7px;height:7px;border-radius:50%;background:var(--c-ink);box-shadow:0 0 0 2px var(--c-white)}
|
||||
|
||||
.content{padding:20px var(--pad) 48px}
|
||||
.ch h2,.ch h3{display:inline-flex;align-items:center;gap:8px}
|
||||
.ch h2 > .ic,.ch h3 > .ic{color:var(--text-secondary)}
|
||||
|
||||
/* ---------- 页头 ---------- */
|
||||
.ph{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;margin-bottom:20px}
|
||||
.ph h1{font-family:var(--font-serif);font-weight:var(--fw-bold);font-size:var(--fs-h1);line-height:var(--lh-h1)}
|
||||
.ph p{margin-top:2px;color:var(--text-secondary)}
|
||||
.warn-t{color:var(--warn-text);font-weight:var(--fw-bold)}
|
||||
.ph .act{display:flex;align-items:center;gap:12px}
|
||||
.cost{font-size:var(--fs-caption);color:var(--text-secondary);white-space:nowrap}
|
||||
.cost .fig{color:var(--text-primary)}
|
||||
|
||||
/* ---------- 唯一的容器:白卡片 ---------- */
|
||||
.card{background:var(--c-white);border:1px solid var(--n-line);border-radius:var(--r-card)}
|
||||
.ch{display:flex;align-items:center;gap:12px;min-height:56px;padding:12px var(--card-x)}
|
||||
.ch h2,.ch h3{font-size:var(--fs-title);line-height:var(--lh-title);font-weight:var(--fw-bold)}
|
||||
.ch .more{margin-left:auto}
|
||||
.cf{padding:12px var(--card-x);border-top:1px solid var(--n-line)}
|
||||
.stack > * + *{margin-top:var(--gap)}
|
||||
|
||||
/* 截止提醒 */
|
||||
.alert{display:flex;align-items:center;gap:14px;padding:14px var(--card-x);margin-bottom:var(--gap)}
|
||||
.alert > .ic{color:var(--c-ochre)}
|
||||
.alert p{flex:1}
|
||||
.alert .when{color:var(--warn-text);font-weight:var(--fw-bold)}
|
||||
|
||||
/* 统计:一张卡片四格 */
|
||||
.stats{display:grid;grid-template-columns:repeat(4,1fr);margin-bottom:var(--gap);overflow:hidden}
|
||||
.stat{position:relative;display:grid;grid-template-rows:auto auto auto;justify-items:start;gap:2px;padding:16px var(--card-x) 16px;border:0;border-left:1px solid var(--n-line);background:transparent;text-align:left;cursor:pointer;transition:background-color var(--dur-fast) var(--ease)}
|
||||
.stat:first-child{border-left:0}
|
||||
.stat:hover{background:var(--n-50)}
|
||||
.stat::after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--c-ink);transform:scaleY(0);transform-origin:bottom;transition:transform var(--dur-fast) var(--ease)}
|
||||
.stat[aria-pressed="true"]::after{transform:scaleY(1)}
|
||||
.stat[aria-pressed="true"] small{color:var(--text-primary);font-weight:var(--fw-bold)}
|
||||
.stat:focus-visible{outline-offset:-2px;border-radius:0}
|
||||
.stat small{font-size:var(--fs-body);color:var(--text-secondary)}
|
||||
.stat .big{font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-weight:var(--fw-bold);font-size:28px;line-height:36px}
|
||||
.stat .big.fatal{color:var(--sev-fatal)}
|
||||
.stat .big.due{color:var(--warn-text)}
|
||||
.stat > .ic{position:absolute;top:18px;right:var(--card-x);color:var(--text-secondary);opacity:0;transition:opacity var(--dur-fast) var(--ease)}
|
||||
.stat:hover > .ic,.stat:focus-visible > .ic,.stat[aria-pressed="true"] > .ic{opacity:1}
|
||||
.stat[aria-pressed="true"] > .ic{color:var(--text-primary)}
|
||||
.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(--rail-w);gap:var(--gap);align-items:start}
|
||||
|
||||
/* 项目卡片头:筛选 */
|
||||
.filters{display:flex;align-items:center;gap:2px;margin-left:8px}
|
||||
.filters .tbtn{color:var(--text-secondary)}
|
||||
.filters .tbtn .fig{font-size:15px}
|
||||
.filters .tbtn[aria-pressed="true"]{background:var(--n-100);color:var(--text-primary);font-weight:var(--fw-bold)}
|
||||
.clear{margin-left:auto;color:var(--text-primary)}
|
||||
|
||||
/* 项目行 */
|
||||
.grid-cols{display:grid;grid-template-columns:var(--cols);column-gap:var(--col-gap);align-items:center;padding:0 var(--card-x)}
|
||||
.cols{padding-bottom:8px;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
|
||||
.cols .r,.row .r{justify-self:end;text-align:right}
|
||||
.cols .nx-h{padding-right:48px}
|
||||
.row{position:relative;min-height:64px;padding-top:10px;padding-bottom:10px;border-top:1px solid var(--n-line);cursor:pointer;transition:background-color var(--dur-fast) var(--ease)}
|
||||
.row:hover,.row:focus-within{background:var(--n-50)}
|
||||
.row:has(.pname:focus-visible){outline:2px solid var(--focus-ring);outline-offset:-2px}
|
||||
.pcell{display:flex;align-items:center;gap:10px;min-width:0}
|
||||
.pcell .pt{min-width:0}
|
||||
.pname{display:block;font-weight:var(--fw-bold);text-decoration:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;outline:0}
|
||||
.pname::after{content:"";position:absolute;inset:0}
|
||||
.psub{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.due b{display:block;font-weight:var(--fw-regular);white-space:nowrap}
|
||||
.due small{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary);white-space:nowrap}
|
||||
.due.today b{color:var(--warn-text);font-weight:var(--fw-bold)}
|
||||
.due.soon b{color:var(--warn-text)}
|
||||
.due.past b{color:var(--text-secondary)}
|
||||
.st{display:flex;align-items:center;gap:8px;white-space:nowrap}
|
||||
.ready{position:relative;z-index:1;text-decoration:underline dashed;text-decoration-color:color-mix(in srgb, var(--c-ink) 40%, white);text-underline-offset:4px;cursor:help}
|
||||
.row.arch .pname,.row.arch .due b{color:var(--text-secondary);font-weight:var(--fw-regular)}
|
||||
|
||||
/* 下一步:默认文字链,行悬停 / 聚焦变次按钮,同时浮现「更多」 */
|
||||
.nx-cell{display:flex;align-items:center;justify-content:flex-end;gap:4px}
|
||||
.nx{position:relative;z-index:1;display:inline-flex;align-items:center;gap:4px;height:var(--btn-h-sm);padding:0 10px 0 12px;border:1px solid transparent;border-radius:var(--r-ctl);background:transparent;color:var(--text-primary);white-space:nowrap;cursor:pointer;transition:background-color var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)}
|
||||
.nx span{text-decoration:underline;text-decoration-color:color-mix(in srgb, var(--c-ink) 30%, white);text-underline-offset:3px}
|
||||
.nx .ic{color:var(--text-secondary);transition:transform var(--dur-fast) var(--ease)}
|
||||
.row:hover .nx .ic,.row:focus-within .nx .ic{transform:translateX(3px);color:var(--text-primary)}
|
||||
.row .nx:hover span{text-decoration-color:var(--c-ink)}
|
||||
.row .ibtn{position:relative;z-index:1;opacity:0;transition:opacity var(--dur-fast) var(--ease),background-color var(--dur-fast) var(--ease)}
|
||||
.row:hover .ibtn,.row:focus-within .ibtn,.row .ibtn[aria-expanded="true"]{opacity:1}
|
||||
.empty{padding:32px var(--card-x);border-top:1px solid var(--n-line);text-align:center}
|
||||
.empty b{display:block;font-weight:var(--fw-bold)}
|
||||
.empty p{color:var(--text-secondary);margin-top:4px}
|
||||
|
||||
/* 待处理的致命问题 */
|
||||
.todo{display:grid;grid-template-columns:auto minmax(0,1fr) auto auto;column-gap:16px;align-items:center;padding:12px var(--card-x);border-top:1px solid var(--n-line)}
|
||||
.todo b{display:block;font-weight:var(--fw-bold)}
|
||||
.todo .psub{white-space:normal}
|
||||
.todo .cite{margin-right:2px}
|
||||
|
||||
/* 右栏 */
|
||||
.rail .ch{min-height:52px;padding-bottom:4px}
|
||||
.rbody{padding:4px var(--card-x) 16px}
|
||||
.task-h{display:flex;align-items:baseline;gap:8px}
|
||||
.task-h b{font-weight:var(--fw-bold)}
|
||||
.task-h .cap{margin-left:auto}
|
||||
.pbar{display:flex;align-items:center;gap:10px;margin-top:10px}
|
||||
.bar{flex:1;height:6px;border-radius:3px;background:var(--n-100);overflow:hidden}
|
||||
.bar i{display:block;height:100%;background:var(--c-ink);border-radius:3px}
|
||||
.queue{display:flex;gap:8px;align-items:flex-start;margin-top:14px;padding-top:12px;border-top:1px solid var(--n-line)}
|
||||
.queue .ic{margin-top:2px;color:var(--text-secondary)}
|
||||
.q2{display:grid;grid-template-columns:1fr 1fr;gap:20px}
|
||||
.q2 small .ic{vertical-align:-3px;margin-right:4px;color:var(--text-secondary)}
|
||||
.q2 small{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
|
||||
.q2 .big{font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-weight:var(--fw-bold);font-size:28px;line-height:36px}
|
||||
.q2 .unit{margin-left:4px;color:var(--text-secondary);font-family:var(--font-figures);font-variant-numeric:tabular-nums}
|
||||
.q2 .bar{margin-top:6px}
|
||||
.links{display:flex;gap:16px}
|
||||
.log li{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;padding:8px 0;border-top:1px solid var(--n-line);font-size:var(--fs-caption);line-height:var(--lh-caption)}
|
||||
.log li:first-child{border-top:0;padding-top:0}
|
||||
.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)}
|
||||
.av{display:inline-grid;place-items:center;width:22px;height:22px;border-radius:50%;background:var(--n-100);color:var(--text-primary);font-size:var(--fs-caption);font-weight:var(--fw-bold);line-height:1}
|
||||
.av .ic{width:14px;height:14px;color:var(--text-secondary)}
|
||||
.log .ok{color:var(--confirmed);white-space:nowrap}
|
||||
|
||||
/* ---------- 浮层:菜单、提示、轻提示 ---------- */
|
||||
.menu{position:fixed;z-index:20;min-width:248px;padding:6px;background:var(--c-white);border:1px solid var(--n-line);border-radius:var(--r-ctl);box-shadow:var(--shadow-overlay)}
|
||||
.menu button{display:block;width:100%;padding:7px 10px;border:0;border-radius:6px;background:transparent;text-align:left;cursor:pointer}
|
||||
.menu button:hover,.menu button:focus-visible{background:var(--n-100);outline:0}
|
||||
.menu button small{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
|
||||
.menu button small .fig{color:var(--text-primary)}
|
||||
.menu hr{border:0;border-top:1px solid var(--n-line);margin:6px 0}
|
||||
[data-tip]{position:relative}
|
||||
[data-tip]:hover::before,[data-tip]:focus-visible::before{content:attr(data-tip);position:absolute;z-index:30;top:calc(100% + 8px);left:50%;transform:translateX(-50%);width:max-content;max-width:260px;padding:6px 10px;border-radius:var(--r-ctl);background:var(--c-ink);color:var(--c-white);font-size:var(--fs-caption);line-height:var(--lh-caption);font-weight:var(--fw-regular);white-space:normal;text-align:left;pointer-events:none}
|
||||
[data-tip].tip-r:hover::before,[data-tip].tip-r:focus-visible::before{left:auto;right:0;transform:none}
|
||||
.toasts{position:fixed;left:50%;bottom:28px;transform:translateX(-50%);z-index:40;display:flex;flex-direction:column;gap:8px;align-items:center}
|
||||
.toast{padding:10px 16px;border-radius:var(--r-ctl);background:var(--c-ink);color:var(--c-white);box-shadow:var(--shadow-overlay)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<aside class="side"><div class="side-in">
|
||||
<a class="brand" href="workbench-d4.html" aria-label="标博士"><img class="full rev" src="../../logo/svg/logo-horizontal-reverse.svg" alt=""><img class="full std" src="../../logo/svg/logo-horizontal.svg" alt=""><img class="mini" src="../../logo/svg/seal-small.svg" alt=""></a>
|
||||
<nav class="nav" aria-label="主导航">
|
||||
<a href="workbench-d4.html" aria-current="page" data-stip="工作台"><i data-ic="grid" class="lg"></i><span class="lbl">工作台</span></a>
|
||||
<a href="#" data-demo data-stip="资质库"><i data-ic="cert" class="lg"></i><span class="lbl">资质库</span></a>
|
||||
<a href="#" data-demo data-stip="额度"><i data-ic="gauge" class="lg"></i><span class="lbl">额度</span></a>
|
||||
<div class="grp">UI demo</div>
|
||||
<a href="index.html" data-stip="方案目录"><i data-ic="book" class="lg"></i><span class="lbl">方案目录</span></a>
|
||||
</nav>
|
||||
<button class="side-btn" type="button" data-collapse aria-expanded="true" aria-keyshortcuts="Control+Backslash" data-stip="展开侧栏(Ctrl \)"><i data-ic="panel-close" class="lg"></i><span class="lbl">收起侧栏</span></button>
|
||||
<div class="side-foot" data-stip="王敏 · 专业版"><span class="avatar" aria-hidden="true">王</span><span class="who"><b>王敏</b>专业版 · 至 2027-10-31</span></div>
|
||||
</div></aside>
|
||||
|
||||
<div class="main">
|
||||
<header class="top">
|
||||
<span class="org"><b>南京某信息技术有限公司</b><span>/</span>工作台</span>
|
||||
<form class="search" role="search" data-gsearch>
|
||||
<i data-ic="search"></i>
|
||||
<input type="search" placeholder="搜索项目、采购人" aria-label="搜索项目、采购人" aria-keyshortcuts="Control+K" data-q>
|
||||
<span class="kbd" aria-hidden="true">Ctrl K</span>
|
||||
</form>
|
||||
<span class="vsep" aria-hidden="true"></span>
|
||||
<div class="top-r">
|
||||
<a class="qchip tbtn" href="#" data-demo data-tip="本期剩余额度:解析 37 / 50 次,检查 12 / 20 次" aria-label="本期剩余额度:解析 37 次,检查 12 次">解析<span class="fig">37</span><span class="sep"></span>检查<span class="fig">12</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>下午好,王敏。今天是 11月3日,周二。</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="card alert" role="note">
|
||||
<i data-ic="clock" class="lg"></i>
|
||||
<p><b>某县数据中心机房改造项目</b> <span class="when">今天 17:00 截止</span>,清单还没确认。</p>
|
||||
<a class="btn sm" href="../page-reading.html"><i data-ic="list"></i>确认清单</a>
|
||||
</div>
|
||||
|
||||
<section class="card stats" aria-label="项目统计,点击可筛选项目列表" data-stats>
|
||||
<button class="stat" data-f="active" aria-pressed="true"><small>进行中的项目</small><span class="big">4</span><i data-ic="filter"></i></button>
|
||||
<button class="stat" data-f="due7" aria-pressed="false"><small>7 天内截止</small><span class="big due">2</span><i data-ic="filter"></i></button>
|
||||
<button class="stat" data-f="fatal" aria-pressed="false"><small>待处理的致命问题</small><span class="big fatal">3</span><i data-ic="filter"></i></button>
|
||||
<button class="stat" data-f="pend" aria-pressed="false"><small>待确认的清单条目</small><span class="big">4</span><i data-ic="filter"></i></button>
|
||||
</section>
|
||||
|
||||
<div class="wb">
|
||||
<div class="stack">
|
||||
<section class="card" aria-labelledby="h-proj">
|
||||
<div class="ch">
|
||||
<h2 id="h-proj">项目</h2>
|
||||
<div class="filters" role="group" aria-label="按状态筛选" data-seg>
|
||||
<button class="tbtn" aria-pressed="true" data-v="active">进行中 <span class="fig">4</span></button>
|
||||
<button class="tbtn" aria-pressed="false" data-v="all">全部 <span class="fig">6</span></button>
|
||||
<button class="tbtn" aria-pressed="false" data-v="已递交">已递交</button>
|
||||
<button class="tbtn" aria-pressed="false" data-v="已归档">已归档</button>
|
||||
</div>
|
||||
<span class="more" data-chip></span>
|
||||
</div>
|
||||
<div class="cols grid-cols" aria-hidden="true"><span>项目</span><span>投标截止</span><span>状态</span><span class="r">致命</span><span class="r nx-h">下一步</span></div>
|
||||
<ul data-rows aria-label="项目列表"></ul>
|
||||
</section>
|
||||
|
||||
<section class="card" aria-labelledby="h-todo">
|
||||
<div class="ch">
|
||||
<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>
|
||||
<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><a class="btn sm" href="../page-reading.html">去处理</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><a class="btn sm" href="../page-reading.html">去处理</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><a class="btn sm" href="../page-reading.html">去处理</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="stack rail">
|
||||
<section class="card" aria-labelledby="h-task">
|
||||
<div class="ch"><h3 id="h-task">正在进行的任务</h3></div>
|
||||
<div class="rbody">
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card" aria-labelledby="h-quota">
|
||||
<div class="ch"><h3 id="h-quota">本期额度</h3><span class="more cap sec">至 2027-10-31</span></div>
|
||||
<div class="rbody">
|
||||
<div class="q2">
|
||||
<div><small>解析剩余</small><span class="big">37</span><span class="unit">/ 50</span><div class="bar" 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="bar" aria-hidden="true"><i style="width:40%"></i></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cf links cap"><a class="lnk" href="#" data-demo>扣点规则</a><a class="lnk" href="#" data-demo>查看流水</a></div>
|
||||
</section>
|
||||
|
||||
<section class="card" aria-labelledby="h-log">
|
||||
<div class="ch"><h3 id="h-log">最近处理</h3><a class="more lnk cap" href="#" data-demo>全部记录</a></div>
|
||||
<div class="rbody">
|
||||
<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>
|
||||
</div>
|
||||
</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"/>',
|
||||
list: '<path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8M13 12h8M13 18h8"/>',
|
||||
"chev-right": '<path d="m9 18 6-6-6-6"/>',
|
||||
more: '<circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/>',
|
||||
filter: '<path d="M22 3H2l8 9.46V19l4 2v-8.54L22 3z"/>',
|
||||
x: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
|
||||
building: '<path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z"/><path d="M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2"/><path d="M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2"/><path d="M10 6h4M10 10h4M10 14h4M10 18h4"/>',
|
||||
folders: '<path d="M20 17a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3.9a2 2 0 0 1-1.69-.9l-.81-1.2a2 2 0 0 0-1.67-.9H8a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2Z"/><path d="M2 8v11a2 2 0 0 0 2 2h14"/>',
|
||||
"calendar-clock": '<path d="M21 7.5V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h3.5"/><path d="M16 2v4M8 2v4M3 10h5"/><path d="M17.5 17.5 16 16.3V14"/><circle cx="16" cy="16" r="6"/>',
|
||||
"octagon-alert": '<path d="M12 16h.01M12 8v4"/><path d="M15.312 2a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586l-4.688-4.688A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2z"/>',
|
||||
activity: '<path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"/>',
|
||||
history: '<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M12 7v5l4 2"/>',
|
||||
loader: '<path d="M21 12a9 9 0 1 1-6.219-8.56"/>',
|
||||
"file-search": '<path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M4.268 21a2 2 0 0 0 1.727 1H18a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v3"/><path d="m9 18-1.5-1.5"/><circle cx="5" cy="14" r="3"/>',
|
||||
scan: '<path d="M3 7V5a2 2 0 0 1 2-2h2M17 3h2a2 2 0 0 1 2 2v2M21 17v2a2 2 0 0 1-2 2h-2M7 21H5a2 2 0 0 1-2-2v-2"/><circle cx="12" cy="12" r="3"/><path d="m16 16-1.9-1.9"/>',
|
||||
bookmark: '<path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z"/>',
|
||||
pen: '<path d="M12 20h9"/><path d="M16.376 3.622a1 1 0 0 1 3.002 3.002L7.368 18.635a2 2 0 0 1-.855.506l-2.872.838a.5.5 0 0 1-.62-.62l.838-2.872a2 2 0 0 1 .506-.854z"/>',
|
||||
send: '<path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/>',
|
||||
archive: '<rect width="20" height="5" x="2" y="3" rx="1"/><path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8"/><path d="M10 12h4"/>',
|
||||
download: '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/><path d="M12 15V3"/>',
|
||||
upload: '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m17 8-5-5-5 5"/><path d="M12 3v12"/>',
|
||||
wrench: '<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>',
|
||||
cpu: '<rect width="16" height="16" x="4" y="4" rx="2"/><rect width="6" height="6" x="9" y="9" rx="1"/><path d="M15 2v2M15 20v2M2 15h2M2 9h2M20 15h2M20 9h2M9 2v2M9 20v2"/>',
|
||||
"panel-close": '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="m16 15-3-3 3-3"/>',
|
||||
"panel-open": '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="m14 9 3 3-3 3"/>'
|
||||
};
|
||||
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); });
|
||||
|
||||
/* 顶栏:滚动后变为半透明白 + 毛玻璃 */
|
||||
const topEl = $(".top"); const onScroll = () => topEl.classList.toggle("scrolled", scrollY > 4); addEventListener("scroll", onScroll, { passive: true }); onScroll();
|
||||
|
||||
/* 侧栏:主题(?side=light 为浅灰对照)与折叠(记在本地,读写失败不影响页面) */
|
||||
const app = $(".app");
|
||||
if (new URLSearchParams(location.search).get("side") === "light") app.dataset.side = "light";
|
||||
if (new URLSearchParams(location.search).get("v") === "flat") app.dataset.v = "flat";
|
||||
const sel = new URLSearchParams(location.search).get("sel"); if (sel === "bar" || sel === "solid") app.dataset.sel = sel;
|
||||
const colBtn = $("[data-collapse]");
|
||||
function setCollapsed(on, save) {
|
||||
app.toggleAttribute("data-collapsed", on);
|
||||
colBtn.setAttribute("aria-expanded", String(!on));
|
||||
$(".lbl", colBtn).textContent = on ? "展开侧栏" : "收起侧栏";
|
||||
colBtn.dataset.stip = on ? "展开侧栏(Ctrl \\)" : "收起侧栏(Ctrl \\)";
|
||||
$(".ic", colBtn).outerHTML = icon(on ? "panel-open" : "panel-close", "lg");
|
||||
if (save) { try { localStorage.setItem("bd-side-collapsed", on ? "1" : "0"); } catch (e) {} }
|
||||
}
|
||||
let saved = null; try { saved = localStorage.getItem("bd-side-collapsed"); } catch (e) {}
|
||||
const forced = new URLSearchParams(location.search).get("collapsed");
|
||||
setCollapsed(forced ? forced === "1" : saved === "1", false);
|
||||
colBtn.addEventListener("click", () => setCollapsed(!app.hasAttribute("data-collapsed"), true));
|
||||
document.addEventListener("keydown", (e) => { if (e.ctrlKey && e.key === "\\") { e.preventDefault(); setCollapsed(!app.hasAttribute("data-collapsed"), true); } });
|
||||
|
||||
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);
|
||||
}
|
||||
const DEMO = "(演示)这里只做了工作台,可以打开「某县数据中心」或「智慧大厅」看读标页";
|
||||
|
||||
/* 数据沿用 page-workbench.html;pend 为待确认清单条目数(对应统计「在 2 个项目里」)。
|
||||
next 为「下一步」,menu 为「更多」里的操作;cost 表示会扣点。 */
|
||||
const CHECK_COST = { label: "扣 1 次检查 · 剩 ", left: 12, unit: " 次" };
|
||||
const P = [
|
||||
{ name: "某县数据中心机房改造项目", buyer: "某县大数据管理局", lot: "标段 1", h: 6, due: ["今天 17:00 截止", "剩 6 小时"], cls: "today", st: "编制中", ready: "", fatal: 0, pend: 3, href: "../page-reading.html",
|
||||
next: "确认清单", menu: [["上传投标文件", ""], ["发起检查", "cost"], ["导出清单", ""]] },
|
||||
{ name: "某区智慧社区平台运维服务", buyer: "某区民政局", lot: "全部", h: 53, due: ["剩 2 天 5 小时", "11月5日 16:00"], cls: "soon", st: "已检查", ready: "就绪", fatal: 0, pend: 0,
|
||||
next: "导出检查报告", menu: [["查看检查结果", ""], ["标记为已递交", ""]] },
|
||||
{ name: "某市政务服务中心智慧大厅建设项目", buyer: "某市政务服务管理办公室", lot: "采购包 1", h: 408, due: ["剩 17 天", "11月20日 09:30"], cls: "", st: "已检查", ready: "未就绪", fatal: 3, pend: 0, href: "../page-reading.html",
|
||||
next: "处理致命问题", menu: [["重新检查", "cost"], ["导出检查报告", "致命问题未处理也可以导出"], ["标记为已递交", ""]] },
|
||||
{ name: "某市医保信息系统等保测评服务", buyer: "某市医疗保障局", lot: "全部", h: 600, due: ["剩 25 天", "11月28日 10:00"], cls: "", st: "跟进中", ready: "", fatal: 0, pend: 1,
|
||||
next: "确认清单", menu: [["查看解析进度", "补遗 2 解析中 62%"], ["上传补遗", ""]] },
|
||||
{ name: "某区智慧校园一期", buyer: "某区教育局", lot: "全部", h: -1, due: ["已截止", "已递交"], cls: "past", st: "已递交", ready: "", fatal: 0, pend: 0,
|
||||
next: "归档项目", menu: [["导出检查报告", ""]] },
|
||||
{ name: "某市不动产登记信息平台升级", buyer: "某市自然资源和规划局", lot: "采购包 2", h: -2, due: ["已截止", "已归档"], cls: "past", st: "已归档", ready: "", fatal: 0, pend: 0,
|
||||
next: "查看项目", menu: [["恢复为进行中", ""]] }
|
||||
];
|
||||
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 status(p) {
|
||||
let r = "";
|
||||
if (p.ready === "就绪") r = '<span class="ready" tabindex="0" data-tip="只表示致命问题都已处理,不代表投标文件没有问题。">就绪</span>';
|
||||
else if (p.ready) r = `<span>${p.ready}</span>`;
|
||||
return `<span class="st"><span class="tag">${p.st}</span>${r}</span>`;
|
||||
}
|
||||
|
||||
function render() {
|
||||
const rows = P.map((p, i) => [p, i]).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, i]) => `<li class="row grid-cols ${p.st === "已归档" ? "arch" : ""}" data-i="${i}">
|
||||
<span class="pcell"><span class="pt"><a class="pname" href="${p.href || "#"}" title="${p.name}" ${p.href ? "" : "data-demo"}>${p.name}</a><span class="psub">${p.buyer} · ${p.lot}</span></span></span>
|
||||
<span class="due ${p.cls}"><b>${p.due[0]}</b><small>${p.due[1]}</small></span>
|
||||
${status(p)}
|
||||
<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="nx-cell"><button class="nx" data-next><span>${p.next}</span>${icon("chev-right")}</button><button class="ibtn sm" aria-label="更多操作:${p.name}" aria-haspopup="menu" aria-expanded="false" data-menu>${icon("more")}</button></span>
|
||||
</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 ? `<button class="tbtn clear" data-clear>${STAT[stat].label}${icon("x")}<span class="vh">,清除筛选</span></button>` : "";
|
||||
}
|
||||
|
||||
/* 「更多」菜单 */
|
||||
let menuEl = null, menuBtn = null;
|
||||
function closeMenu(focusBack) {
|
||||
if (!menuEl) return;
|
||||
menuEl.remove(); menuEl = null;
|
||||
if (menuBtn) { menuBtn.setAttribute("aria-expanded", "false"); if (focusBack) menuBtn.focus(); }
|
||||
menuBtn = null;
|
||||
}
|
||||
function openMenu(btn, p) {
|
||||
closeMenu();
|
||||
menuBtn = btn; btn.setAttribute("aria-expanded", "true");
|
||||
menuEl = document.createElement("div");
|
||||
menuEl.className = "menu"; menuEl.setAttribute("role", "menu");
|
||||
const items = [["打开项目", ""], ...p.menu].map(([t, s]) => {
|
||||
const sub = s === "cost" ? `${CHECK_COST.label}<span class="fig">${CHECK_COST.left}</span>${CHECK_COST.unit}` : s;
|
||||
return `<button role="menuitem" data-act="${t}">${t}${sub ? `<small>${sub}</small>` : ""}</button>`;
|
||||
});
|
||||
items.splice(1, 0, "<hr>");
|
||||
menuEl.innerHTML = items.join("");
|
||||
document.body.appendChild(menuEl);
|
||||
const r = btn.getBoundingClientRect();
|
||||
menuEl.style.top = `${r.bottom + 4}px`;
|
||||
menuEl.style.left = `${r.right - menuEl.offsetWidth}px`;
|
||||
menuEl.addEventListener("click", (e) => {
|
||||
const it = e.target.closest("[data-act]"); if (!it) return;
|
||||
const a = it.dataset.act; closeMenu(true);
|
||||
if (a === "打开项目" && p.href) location.href = p.href;
|
||||
else toast(`(演示)${a}:${p.name}`);
|
||||
});
|
||||
menuEl.addEventListener("keydown", (e) => {
|
||||
const its = $$("[role=menuitem]", menuEl), k = its.indexOf(document.activeElement);
|
||||
if (e.key === "ArrowDown") { e.preventDefault(); its[(k + 1) % its.length].focus(); }
|
||||
if (e.key === "ArrowUp") { e.preventDefault(); its[(k - 1 + its.length) % its.length].focus(); }
|
||||
});
|
||||
$("[role=menuitem]", menuEl).focus();
|
||||
}
|
||||
|
||||
$("[data-rows]").addEventListener("click", (e) => {
|
||||
const row = e.target.closest(".row"); if (!row) return;
|
||||
const p = P[+row.dataset.i];
|
||||
const m = e.target.closest("[data-menu]");
|
||||
if (m) { e.preventDefault(); e.stopPropagation(); if (menuBtn === m) closeMenu(); else openMenu(m, p); return; }
|
||||
if (e.target.closest(".ready")) return;
|
||||
if (e.target.closest("[data-next]")) {
|
||||
if (p.href) location.href = p.href;
|
||||
else toast(`(演示)${p.next}:${p.name}`);
|
||||
return;
|
||||
}
|
||||
if (!e.target.closest(".pname")) { if (p.href) location.href = p.href; else toast(DEMO); }
|
||||
});
|
||||
document.addEventListener("click", (e) => { if (menuEl && !menuEl.contains(e.target)) closeMenu(); });
|
||||
window.addEventListener("scroll", () => closeMenu(), { passive: true });
|
||||
window.addEventListener("resize", () => closeMenu());
|
||||
|
||||
$("[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(); });
|
||||
$("[data-gsearch]").addEventListener("submit", (e) => e.preventDefault());
|
||||
document.addEventListener("click", (e) => { const d = e.target.closest("[data-demo]"); if (d) { e.preventDefault(); toast(DEMO); } });
|
||||
$("[data-notice]").onclick = () => toast("解析完成:补遗 1 已并入清单(新增 2 条,更新 1 条)。");
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") { e.preventDefault(); $("[data-q]").focus(); }
|
||||
if (e.key === "Escape") {
|
||||
if (menuEl) closeMenu(true);
|
||||
else if (document.activeElement === $("[data-q]")) $("[data-q]").blur();
|
||||
}
|
||||
});
|
||||
render();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user