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:
@@ -6,11 +6,12 @@
|
||||
| [brand-directions.html](brand-directions.html) | 三个视觉方向对比(A 校样 / B 卷宗 / C 检验单) | 已选 A |
|
||||
| [logo/](logo/) | Logo 正式资产、使用规范、构建脚本、历史版本 | v5.1 定稿 |
|
||||
| [vi/](vi/) | VI 视觉识别规范 PDF 与 HTML 源文件 | v1.1 |
|
||||
| [design-system.md](design-system.md) | 产品界面设计规范:原则、颜色与对比度、字体字阶、布局、17 个组件、交互模式、文案规范 | v0.1(U1–U4 已确认) |
|
||||
| [system/tokens.css](system/tokens.css) | 设计变量(原型和前端直接引用) | v0.1 |
|
||||
| [design-system.md](design-system.md) | 产品界面设计规范:原则、颜色与对比度、字体字阶、布局、17 个组件、交互模式、文案规范 | v0.2(U1–U5 已确认) |
|
||||
| [system/tokens.css](system/tokens.css) | 设计变量(原型和前端直接引用) | v0.2 |
|
||||
| [system/components.css](system/components.css) | 组件样式参考实现(预览页和原型共用) | v0.1 |
|
||||
| [system/components.html](system/components.html) | 组件预览页(用样本条款演示) | v0.1 |
|
||||
| [ui-demo/](ui-demo/index.html) | UI demo:控件总览、表单、卡片、列表与表格,以及工作台、读标两个组合页;附新增组件清单和 UI 精修待定问题 Q1–Q12 | 供 UI 精修评审 |
|
||||
| [ui-demo/](ui-demo/index.html) | UI demo:控件总览、表单、卡片、列表与表格,以及工作台、读标两个组合页;附新增组件清单和 UI 精修待定问题 Q1–Q12 | v0.1 结构,待按 v0.2 重刷 |
|
||||
| [ui-demo/explore/](ui-demo/explore/index.html) | 工作台风格探索:六轮方案;**D5 是定稿样张**(U5) | 定稿 |
|
||||
|
||||
## 速查
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 标博士 · 产品界面设计规范(v0.1 · 2026-11-02)
|
||||
# 标博士 · 产品界面设计规范(v0.2 · 2026-11-03)
|
||||
|
||||
> 范围:MVP 的 Web 界面(读标 M2、查标 M3、项目工作台与额度 M6)和检查报告 PDF。
|
||||
> 依据:视觉方向 A「校样」(决策 B1)、VI 手册 v1.1、品牌策略的语气规则、`docs/analysis/03-functional-requirements.md`。
|
||||
@@ -9,6 +9,17 @@
|
||||
> - `system/components.html`:组件预览页,用真实样本条款演示(起本地服务后打开,见第 13 节)。
|
||||
>
|
||||
> 第 12 节的 U1–U4 已于 2026-11-02 全部按建议确认(`docs/decisions.md`)。
|
||||
>
|
||||
> **v0.2(2026-11-03,决策 U5)**:UI 精修时,用户认为 v0.1 风格过于陈旧(处处描边框、2px 圆角、灰色凹陷表头)。经过六轮工作台方案,定稿为 `ui-demo/explore/workbench-d5.html`,并据此修订:
|
||||
> - 第 3.1 节:页面底色;
|
||||
> - 第 3.4 节:渐变;
|
||||
> - 第 5 节:圆角、层次、页面框架;
|
||||
> - 第 6 节:图标;
|
||||
> - 第 7.1 节:按钮;
|
||||
> - 第 7.9 节:项目行;
|
||||
> - 第 9.5 节:文案精简。
|
||||
>
|
||||
> 颜色语义、字体、问题级别的形状规则、产品红线都**没有变**。其余 demo 页和原型还是 v0.1 的样式,待按本版重刷。
|
||||
|
||||
---
|
||||
|
||||
@@ -45,13 +56,16 @@
|
||||
|
||||
| 名称 | 变量 | HEX | 用途 |
|
||||
|---|---|---|---|
|
||||
| 校样纸 | `--c-paper` | #F5F6F3 | 页面底色(冷灰白,不是米色) |
|
||||
| 校样纸 | `--c-paper` | #F5F6F3 | 品牌色板保留(VI、报告用);**v0.2 起不再作页面底色** |
|
||||
| 白 | `--c-white` | #FFFFFF | 卡片、表格、输入框、原文页面 |
|
||||
| 凹陷灰 | `--c-sunken` | #ECEEEA | 表头、「提示」级底色、只读区 |
|
||||
| 凹陷灰 | `--c-sunken` | #ECEEEA | v0.1 的表头、「提示」级底色;v0.2 起界面改用 `--c-n100` |
|
||||
| 墨 | `--c-ink` | #1D2733 | 正文、标题、主按钮、人工确认后的内容、焦点环 |
|
||||
| 辅助灰 | `--c-sub` | #5C6670 | 次要文字、说明、「提示」级 |
|
||||
| 禁用灰 | `--c-muted` | #7A838C | **只用于**禁用文字和控件边框 |
|
||||
| 分隔线 | `--c-rule` | #D9DDD8 | 装饰性分隔线 |
|
||||
| 分隔线 | `--c-rule` | #D9DDD8 | v0.1 的分隔线;v0.2 起界面改用 `--c-nline` |
|
||||
| 中性浅灰 50 | `--c-n50` | #F8F8F9 | **页面底色**、行 / 格悬停(墨 3% 混白) |
|
||||
| 中性浅灰 100 | `--c-n100` | #F1F2F3 | 中性标签底、文字按钮悬停、进度槽(墨 6%) |
|
||||
| 发丝线 | `--c-nline` | #E6E7E9 | 卡片边框、行间分隔线(墨 11%) |
|
||||
| 蓝铅笔 | `--c-pencil` | #2F5BD3 | AI 标注、出处、跳转原文的链接 |
|
||||
| 蓝铅笔浅 | `--c-pencil-soft` | #E7EDFB | 原句高亮 |
|
||||
| 致命红 | `--c-fatal` | #C4122F | **只用于「致命」级问题** |
|
||||
@@ -61,6 +75,17 @@
|
||||
|
||||
每个功能色都有一个浅色底(`-soft`),只用作背景,文字仍用对应的深色。
|
||||
|
||||
**v0.2 的浅灰都从墨色派生**:原来的校样纸、凹陷灰、分隔线偏绿,页面整体显得发灰发闷;改为墨色按比例混白,更亮、更中性。
|
||||
|
||||
**侧栏的颜色**(v0.2,`--side-*`):
|
||||
- 底色:墨色竖向渐变,下端是墨混 18% 蓝铅笔得到的深墨蓝 #203050。蓝铅笔混在深色里看上去是藏青,只作底色,不承担「AI」语义。
|
||||
- 文字和选中态:都用白色按比例叠加。
|
||||
|
||||
**套餐标签的颜色**(v0.2,`--plan-*`,例外):
|
||||
- 只用于账户区的套餐标签:免费版是中性灰,专业版是浅金色底加金边,企业版是金色实底加墨色字。
|
||||
- 金色从赭色派生,靠深浅区分档次。
|
||||
- 不得用在其他地方,以免和表示提醒的赭色混淆。
|
||||
|
||||
**不进界面的颜色**:印章红 #D9291C 和藏青 #1C2554 只属于 logo(VI 手册第 03 章)。
|
||||
|
||||
### 3.2 对比度实测
|
||||
@@ -94,7 +119,7 @@
|
||||
- 不用红色做按钮(包括删除按钮)、表单错误、系统错误、倒计时、额度用完提示。删除这类操作靠二次确认,不靠颜色吓人。
|
||||
- 不用定稿绿表示「通过 / 合格」。绿色只表示「有人确认过」,不表示结论好。
|
||||
- 不用蓝铅笔做普通导航链接、主按钮。蓝色只属于系统说的话和出处。普通链接用墨色加下划线。
|
||||
- 不用渐变、不用大面积彩色底。一屏之内彩色面积越少,致命红越显眼。
|
||||
- 不用渐变、不用大面积彩色底。一屏之内彩色面积越少,致命红越显眼。**唯一的例外**是侧栏的墨色竖向渐变(v0.2):只在墨色和深墨蓝之间过渡,不出现在内容区。
|
||||
|
||||
---
|
||||
|
||||
@@ -142,8 +167,15 @@
|
||||
|
||||
- 间距以 4px 为基准:4 / 8 / 12 / 16 / 20 / 24 / 32 / 40 / 48 / 64(`--sp-*`)。
|
||||
- 组件内部用 4–12,组件之间 16–24,区块之间 32–48。
|
||||
- 圆角:控件和卡片 2px(纸张感,接近直角),对话框和浮层 4px。不用大圆角和胶囊形。
|
||||
- 层次靠分隔线和底色区分,不靠阴影。阴影只用于浮层(下拉、对话框、提示)。
|
||||
- **圆角三档**(v0.2):
|
||||
- 标签 6px(`--radius-tag`);
|
||||
- 按钮、输入框、菜单项 8px(`--radius-ctl`);
|
||||
- 卡片、对话框、浮层、内容区左上角 12px(`--radius-card`)。
|
||||
- 不用胶囊形;圆形只用于头像和状态小圆点。
|
||||
- **层次**(v0.2):浅灰页面上放白卡片,卡片是 1px 发丝线边框,**全页只有这一种容器**,卡片里不再套面板。
|
||||
- 表头不用底色和粗线,只用 12px 辅助灰列名;行与行之间用发丝线分隔。
|
||||
- 阴影只用于浮层(下拉、对话框、提示),以及滚动后的顶栏。
|
||||
- **整齐**:一种中性标签样式;卡片间距统一 16px;卡片内左右边距统一 20px。
|
||||
|
||||
### 5.2 平台与尺寸
|
||||
|
||||
@@ -154,19 +186,37 @@
|
||||
### 5.3 页面框架
|
||||
|
||||
```
|
||||
┌──────────┬──────────────────────────────────────────────┐
|
||||
│ 侧边栏 │ 顶栏:面包屑 · 项目名 · 截止倒计时 · 额度 · 账号 │
|
||||
│ 216px ├──────────────────────────────────────────────┤
|
||||
│ 工作台 │ │
|
||||
│ 项目 │ 页面内容(最大宽度 1200px,三种主布局见下) │
|
||||
│ 资质库 │ │
|
||||
│ 额度 │ │
|
||||
└──────────┴──────────────────────────────────────────────┘
|
||||
┌──────┬────────────────────────────────────────────────────┐
|
||||
│ 侧栏 │ 顶栏(透明):公司 / 页面 搜索 │ 额度 · 通知 │
|
||||
│ 96px ├────────────────────────────────────────────────────┤
|
||||
│ logo │╭ 内容区(浅灰底,左上角 12px 圆角压在侧栏深色上) │
|
||||
│ 菜单 ││ │
|
||||
│ … ││ 页面内容(三种主布局见下) │
|
||||
│ 头像 ││ │
|
||||
└──────┴────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**侧栏**(v0.2):
|
||||
- 宽度固定 96px,只有一种状态,**不折叠**。
|
||||
- 底色是墨色竖向渐变,底部有一片淡点阵,向上渐隐。
|
||||
- 顶部是竖版反白 logo(上印章、下字标),在侧栏内居中,宽约 60px。
|
||||
- 菜单项上图标、下文字:图标 22px,文字 12px,项高 62px。
|
||||
- 当前项:圆角矩形的半透明浅色底,白色加粗文字。悬停用同一形状,底色更淡。
|
||||
- 分组之间只用一条细分隔线,不写分组标题。
|
||||
- 底部:44px 头像、姓名,以及套餐标签(见 3.1)。
|
||||
|
||||
**顶栏**(v0.2):
|
||||
- 高 56px。左侧是「公司 / 页面」;右侧依次是搜索框(宽 280px,聚焦时变宽,Ctrl K 聚焦)、额度、通知。
|
||||
- 页面在顶部时,顶栏透明,和内容区底色连成一片;滚动后变为半透明白,加毛玻璃和一条发丝线。
|
||||
- 顶栏不用深色,避免和侧栏连成一个「门」字形边框。
|
||||
|
||||
**内容区**:
|
||||
- 浅灰底 `--bg-page`,左上角 12px 圆角,压在侧栏的深色上。
|
||||
- 页面左右内边距 32px,1400px 以下为 24px。
|
||||
|
||||
| 布局 | 页面 | 结构 |
|
||||
|---|---|---|
|
||||
| 列表 | 工作台、资质库、额度流水 | 筛选栏 + 表格,单栏 |
|
||||
| 列表 | 工作台、资质库、额度流水 | 页头 + 统计 + 白卡片里的列表;工作台右侧另有 296px 信息栏 |
|
||||
| 对照 | 读标(清单)、查标(问题列表) | 左:分类导航 200px;中:列表;右:**原文查看器**,宽度可拖动(默认 45%) |
|
||||
| 批注 | 查标结果的默认视图 | 问题列表 + 右侧 280px 页边批注栏(出处);窄于 1440 时批注收进问题下方 |
|
||||
|
||||
@@ -180,6 +230,10 @@
|
||||
- 图标只做辅助,必须和文字一起出现;只有图标的按钮要有 `aria-label` 和悬停提示。
|
||||
- ★ ▲ 是招标文件里的标注符号,按文字显示,不换成图标。
|
||||
- 不用笑脸、奖杯、盾牌对勾这类暗示「保证」的图形。
|
||||
- **只用功能图标,不用装饰图标**(v0.2,用户试过后否决):
|
||||
- 统计卡、区块标题、列表行首都不加图标;
|
||||
- 图标只出现在导航、按钮、出处、提醒、搜索、通知这类有操作含义的地方。
|
||||
- 侧栏菜单图标:工作台 `house`、资质库 `award`、额度 `wallet`。
|
||||
|
||||
---
|
||||
|
||||
@@ -195,7 +249,7 @@
|
||||
| 次按钮 | 白底墨字,控件边框 | 其他操作,包括问题处理三按钮 |
|
||||
| 文字按钮 | 墨字,无边框,悬停出底色 | 表格行内、次要操作 |
|
||||
|
||||
- 高度 32(默认)/ 28(表格行内)/ 40(登录页、空状态)。左右内边距 12 / 8 / 16。
|
||||
- 高度 36(默认)/ 32(表格行内、筛选标签)/ 40(登录页、空状态),圆角 8(v0.2)。左右内边距 14 / 10 / 16。
|
||||
- **没有红色的危险按钮**。删除项目、删除文件用次按钮 + 确认对话框;删除项目要输入项目名才能确认。
|
||||
- 会扣点的按钮,旁边直接写扣多少、还剩多少(见 8.3),不放在弹窗里才说。
|
||||
- 状态:悬停(主按钮变浅 8%,次按钮出 `--bg-hover`)、按下、禁用(禁用灰字、无边框底色)、加载中(文字换成「正在…」,按钮宽度不变)。
|
||||
@@ -209,7 +263,7 @@
|
||||
[提示] 无边框,凹陷灰底,辅助灰字
|
||||
```
|
||||
|
||||
- 字号 12,字重 600,高 20,左右内边距 6,圆角 2。
|
||||
- 字号 12,字重 600,高 20,左右内边距 6,圆角 6(v0.2)。
|
||||
- 级别由规则决定(FR-M3-03 的级别表),用户不能改级别;不同意可以「反馈:判断有误」。
|
||||
- 计数徽标也用同一套形状:「致命 2」实心、「重要 5」描边、「提示 11」无框。
|
||||
|
||||
@@ -299,9 +353,11 @@
|
||||
| 项目 | 项目名(16/24 600)+ 采购人(辅助灰) | |
|
||||
| 所投包 | 「采购包 2」或「全部」 | |
|
||||
| 截止 | 倒计时 | 见下 |
|
||||
| 状态 | 跟进中 / 编制中 / 已检查 / 已递交 / 已归档 | 中性描边标签,不用彩色 |
|
||||
| 状态 | 跟进中 / 编制中 / 已检查 / 已递交 / 已归档 | 中性标签:浅灰实底(`--c-n100`),无描边,不用彩色(v0.2) |
|
||||
| 就绪 | 只在「已检查」显示 | 见下 |
|
||||
| 致命 | 待处理的致命问题数 | 实心标签「致命 2」;为 0 时显示「—」 |
|
||||
| 致命 | 待处理的致命问题数 | 实心标签「致命 2」;为 0 时显示「—」;本列居中 |
|
||||
| 下一步 | 当前最该做的一个动作(确认清单、导出检查报告、处理致命问题……) | 文字链加 ›,表头和内容**左对齐**;行悬停时箭头右移 3px,不加边框和底色 |
|
||||
| 更多 | 「⋯」按钮 | 单独一列 32px,不设表头;常驻显示,平时是辅助灰,行悬停时变墨色。会扣点的操作在菜单里写明扣多少、还剩多少 |
|
||||
|
||||
**倒计时**(以投标截止时间为准):
|
||||
|
||||
@@ -454,6 +510,18 @@
|
||||
| 改法 | 「请在技术偏离表第 12 行补充响应。」 |
|
||||
| 签章 | 「投标函第 2 页的签章位置未发现盖章。本检查只看有无,不鉴定真伪。」 |
|
||||
|
||||
### 9.5 少写说明(v0.2)
|
||||
|
||||
用户反馈:「文案多了反而会增加 AI 感。」界面上只留做决定需要的信息:
|
||||
|
||||
- 统计卡只写标题和数字,不加「在 2 个项目里」这类小字。
|
||||
- 不重复:页头、提醒条、统计卡说的是同一件事时,只留一处。
|
||||
- 解释性的话放进悬停提示,例如「就绪」的说明。
|
||||
- 产品红线要求明示的不能删:
|
||||
- 扣点按钮旁的「扣 1 次 · 剩 37 次」(8.3);
|
||||
- 签章的「只看有无,不鉴定真伪」(红线 7);
|
||||
- 报告首页的检查范围和免责声明。
|
||||
|
||||
---
|
||||
|
||||
## 10. 无障碍检查清单
|
||||
@@ -493,7 +561,17 @@
|
||||
| U3 | 图标用 Lucide(ISC 许可) | 采纳 | 线性风格和校样方向一致,图标全,可商用 |
|
||||
| U4 | 「致命」级问题点「确认无误」时,也要求写一句说明(目前需求只要求「忽略」填原因) | 采纳 | 对致命问题说「没问题」,等于认为系统判断错了:留下说明既便于审计,也是最有价值的误报反馈。会改动 FR-M3-07 |
|
||||
|
||||
组件库(TDesign / Ant Design / Arco 等)由技术架构阶段选定;本规范的正文 14/22、4px 间距与这些组件库一致,选定后把 `tokens.css` 映射到它的主题配置即可。
|
||||
**U5(2026-11-03)**:UI 风格定稿为 `ui-demo/explore/workbench-d5.html`。要点:
|
||||
- 白色主调,浅灰从墨色派生;
|
||||
- 圆角三档;
|
||||
- 墨色渐变窄侧栏,上图标、下文字,不折叠;
|
||||
- 透明顶栏;
|
||||
- 只用功能图标;
|
||||
- 少写说明。
|
||||
|
||||
详见 `docs/decisions.md` U5。
|
||||
|
||||
前端不用带样式的 UI 框架(AR10):组件样式直接引用 `tokens.css`,复杂交互用无样式组件库 Reka UI 打底。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/* 标博士 · 界面设计变量(design tokens)v0.1
|
||||
* 规范说明见 docs/design/design-system.md;预览见同目录 components.html。
|
||||
* 只放变量,不放组件样式。技术架构选定组件库后,把这些值映射到组件库的主题配置。
|
||||
* 对比度一律按 WCAG 2.x 公式计算,底色为校样纸 #F5F6F3(括号内为白底)。
|
||||
/* 标博士 · 界面设计变量(design tokens)v0.2 · 2026-11-03
|
||||
* 规范说明见 docs/design/design-system.md;定稿样张见 docs/design/ui-demo/explore/workbench-d5.html。
|
||||
* 只放变量,不放组件样式。前端不用带样式的 UI 框架(AR10),组件样式直接引用这些变量。
|
||||
* 对比度一律按 WCAG 2.x 公式计算。v0.1 的注释数值以校样纸 #F5F6F3 为底,括号内为白底;
|
||||
* v0.2 起页面底色改为 --c-n50 #F8F8F9(比校样纸更亮),对比度介于两者之间。
|
||||
* v0.2(决策 U5):页面改为白色主调 + 墨色派生的中性浅灰;圆角三档;控件高 36;墨色渐变窄侧栏。
|
||||
*/
|
||||
:root {
|
||||
/* ---------- 基础色板:校样色系 ---------- */
|
||||
@@ -23,18 +25,40 @@
|
||||
--c-ochre-soft: #FBF0DC;
|
||||
--c-green: #2E6B4F; /* 定稿绿:人工确认署名 5.8 (6.3) */
|
||||
--c-green-soft: #E5F0EA;
|
||||
|
||||
/* 中性浅灰(v0.2):都由墨 #1D2733 按比例混白得到,不偏绿,不引入新色相 */
|
||||
--c-n50: #F8F8F9; /* 墨 3%:页面底色、行 / 格悬停 */
|
||||
--c-n100: #F1F2F3; /* 墨 6%:中性标签底、文字按钮悬停、进度槽 */
|
||||
--c-nline: #E6E7E9; /* 墨 11%:卡片边框、发丝分隔线 */
|
||||
|
||||
/* 侧栏(v0.2):墨色竖向渐变,下端混 18% 蓝铅笔得到深墨蓝;只作底色,不承担 AI 语义 */
|
||||
--side-top: var(--c-ink);
|
||||
--side-bottom: #203050;
|
||||
--side-fg: #C4C7CA; /* 白 74% 混墨:未选中菜单 约 9:1 */
|
||||
--side-fg-strong: var(--c-white);
|
||||
--side-muted: #A0A4A9; /* 白 58% 混墨:侧栏小字 约 6:1 */
|
||||
--side-hover: rgba(255, 255, 255, .07);
|
||||
--side-active: rgba(255, 255, 255, .14);
|
||||
--side-line: rgba(255, 255, 255, .12);
|
||||
|
||||
/* 套餐标签(v0.2):只用于账户区的套餐标签,从赭色派生的金色阶梯;靠深浅区分档次 */
|
||||
--plan-free-bg: rgba(255, 255, 255, .12);
|
||||
--plan-pro-bg: rgba(166, 99, 0, .42);
|
||||
--plan-pro-fg: #F4ECE0;
|
||||
--plan-pro-line: rgba(177, 119, 32, .8);
|
||||
--plan-ent-bg: #CEA973; /* 企业版:金色实底,墨色字 */
|
||||
/* 印章红 #D9291C、藏青 #1C2554 只属于 logo,不进界面变量 */
|
||||
|
||||
/* ---------- 语义色 ---------- */
|
||||
--bg-page: var(--c-paper);
|
||||
--bg-page: var(--c-n50); /* v0.2:原为校样纸 --c-paper */
|
||||
--bg-surface: var(--c-white);
|
||||
--bg-sunken: var(--c-sunken);
|
||||
--bg-hover: var(--c-hover);
|
||||
--bg-sunken: var(--c-n100); /* v0.2:原为 --c-sunken */
|
||||
--bg-hover: var(--c-n50); /* v0.2:原为 --c-hover */
|
||||
--text-primary: var(--c-ink);
|
||||
--text-secondary: var(--c-sub);
|
||||
--text-disabled: var(--c-muted);
|
||||
--text-on-dark: var(--c-white);
|
||||
--border-subtle: var(--c-rule);
|
||||
--border-subtle: var(--c-nline); /* v0.2:原为 --c-rule */
|
||||
--border-control: var(--c-muted); /* 输入框等控件边框,≥3:1(WCAG 1.4.11) */
|
||||
--border-strong: var(--c-ink);
|
||||
--focus-ring: var(--c-ink);
|
||||
@@ -80,17 +104,22 @@
|
||||
--sp-6: 24px; --sp-8: 32px; --sp-10: 40px; --sp-12: 48px; --sp-16: 64px;
|
||||
|
||||
/* ---------- 圆角、线、阴影 ---------- */
|
||||
--radius-sm: 2px; /* 按钮、标签、输入框、卡片 */
|
||||
--radius-md: 4px; /* 对话框、浮层 */
|
||||
--radius-tag: 6px; /* 标签、问题级别标签(v0.2) */
|
||||
--radius-ctl: 8px; /* 按钮、输入框、菜单项、下拉(v0.2) */
|
||||
--radius-card: 12px; /* 卡片、对话框、浮层、内容区左上角(v0.2) */
|
||||
--radius-sm: var(--radius-ctl); /* 兼容 v0.1 的写法,新代码用上面三个 */
|
||||
--radius-md: var(--radius-card);
|
||||
--line: 1px;
|
||||
--line-sev: 1.5px; /* 「重要」标签描边 */
|
||||
--shadow-overlay: 0 4px 16px rgba(29, 39, 51, .14), 0 1px 3px rgba(29, 39, 51, .10);
|
||||
|
||||
/* ---------- 尺寸 ---------- */
|
||||
--control-h: 32px; /* 默认按钮、输入框 */
|
||||
--control-h-sm: 28px; /* 表格行内 */
|
||||
--control-h: 36px; /* 默认按钮、输入框(v0.2:原 32) */
|
||||
--control-h-sm: 32px; /* 表格行内、筛选标签(v0.2:原 28) */
|
||||
--control-h-lg: 40px; /* 登录、空状态主操作 */
|
||||
--sidebar-w: 216px;
|
||||
--rail-w: 96px; /* 侧栏(v0.2):单一状态,上图标下文字,不折叠 */
|
||||
--topbar-h: 56px; /* 顶栏:透明,滚动后半透明白 + 毛玻璃 */
|
||||
--sidebar-w: 216px; /* v0.1 宽侧栏,仅旧原型使用,重刷后删除 */
|
||||
--margin-note-w: 280px; /* 页边批注栏 */
|
||||
--content-max: 1200px;
|
||||
|
||||
|
||||
59
docs/design/ui-demo/explore/index.html
Normal file
59
docs/design/ui-demo/explore/index.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>工作台风格方案</title>
|
||||
<link rel="icon" href="../../logo/favicon.svg">
|
||||
<link rel="stylesheet" href="../../system/tokens.css">
|
||||
<link rel="stylesheet" href="../../system/fonts-local.css">
|
||||
<style>
|
||||
body{margin:0;background:var(--bg-page);color:var(--text-primary);font:var(--fs-body)/var(--lh-body) var(--font-sans)}
|
||||
main{max-width:960px;margin:0 auto;padding:var(--sp-12) var(--sp-6)}
|
||||
h1{font:var(--fw-bold) 32px/40px var(--font-serif);margin:0 0 var(--sp-2)}
|
||||
.lead{color:var(--text-secondary);margin:0 0 var(--sp-8)}
|
||||
.v{display:grid;grid-template-columns:120px minmax(0,1fr) auto;gap:var(--sp-4);align-items:baseline;padding:var(--sp-5) 0;border-top:1px solid var(--border-subtle)}
|
||||
.v:last-of-type{border-bottom:1px solid var(--border-subtle)}
|
||||
.v b{font:var(--fw-bold) 20px/28px var(--font-serif)}
|
||||
.v p{margin:0}
|
||||
.v .sub{color:var(--text-secondary);font-size:var(--fs-caption);line-height:var(--lh-caption);margin-top:var(--sp-1)}
|
||||
.v a{display:inline-flex;align-items:center;height:36px;padding:0 var(--sp-4);border-radius:6px;background:var(--c-ink);color:var(--text-on-dark);text-decoration:none;white-space:nowrap}
|
||||
.v a.ghost{background:var(--bg-sunken);color:var(--text-primary)}
|
||||
.v a:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}
|
||||
.round{font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans);margin:var(--sp-10) 0 var(--sp-2)}
|
||||
.note{margin-top:var(--sp-6);color:var(--text-secondary);font-size:var(--fs-caption);line-height:var(--lh-caption)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>工作台风格方案</h1>
|
||||
<p class="lead">同一份内容、同一套颜色、字体和图标,逐轮调整版式和分层方式。<b>D5 已定稿</b>,规则写入设计规范 v0.2 和 tokens.css v0.2。</p>
|
||||
|
||||
<h2 class="round">第六轮:窄侧栏,上图标下文字(2026-11-03 定稿,决策 U5)</h2>
|
||||
<div class="v"><b>D5</b><div><p>侧栏只有一种状态,宽 96px,不再折叠。顶部是竖版 logo(上印章、下字标);菜单改为上图标、下文字,图标换成更精致的 Lucide 线性图标(工作台 house、资质库 award、额度 wallet)。当前项是圆角矩形的半透明浅色底。底部是大头像、名称、套餐和有效期。</p><p class="sub">其余(渐变侧栏、点阵、透明顶栏、精简文案、下一步悬停)同 D4。</p></div><a href="workbench-d5.html">打开</a></div>
|
||||
|
||||
<h2 class="round">第五轮:深色侧栏与内容区融合</h2>
|
||||
<div class="v"><b>D4</b><div><p>侧栏改为双色竖向渐变(墨色到深墨蓝),底部有一片淡淡的点阵;内容区左上角是 12px 圆角,像一张纸铺在深色桌面上;当前菜单项是浅色半透明底,加侧栏左缘一条 3px 白色竖条(?sel=bar 只有竖条,?sel=solid 白色实底)。logo 放大并在侧栏里居中;「下一步」悬停时只让箭头右移;精简了说明性文案。D3 加的装饰图标已全部去掉,只保留原有的功能图标;顶栏和问候语保留 D3 的改法。</p><p class="sub">网址加 ?v=flat 可以看 D3 的平铺深色侧栏作对照;折叠后效果同样成立。</p></div><a href="workbench-d4.html">打开</a></div>
|
||||
|
||||
<h2 class="round">第四轮:顶栏优化,加入图标</h2>
|
||||
<div class="v"><b>D3</b><div><p>顶栏不再是一条白带:平时透明,融进页面底色;往下滚动后变成半透明白(毛玻璃)加一条细线。搜索框靠右,聚焦时变宽。统计、区块标题、项目行、下一步按钮、最近处理都配了图标;图标底块大多是中性浅灰,只有带含义的才着色(截止赭、致命红、待确认的 AI 条目蓝)。页头加了问候语。</p><p class="sub">侧栏同 D2,可以加 ?side=light 看浅灰侧栏。</p></div><a href="workbench-d3.html">打开</a></div>
|
||||
|
||||
<h2 class="round">第三轮:在 D 的基础上,侧边栏换底色并支持折叠</h2>
|
||||
<div class="v"><b>D2 墨色侧栏</b><div><p>侧边栏改为墨色底,logo 用反白版。底部「收起侧栏」按钮(或 Ctrl \)可以折叠到只留图标,折叠后 logo 换成印章,悬停图标有提示;折叠状态会记住。</p><p class="sub">另有浅灰侧栏对照;也可以在网址后加 ?collapsed=1 直接看折叠状态。</p></div><a href="workbench-d2.html">打开</a></div>
|
||||
<div class="v"><b>D2 浅灰侧栏</b><div><p>同一个页面,侧边栏用从墨色派生的浅灰底,当前项是白色块。比墨色版温和,但和白色内容区的区分弱一些。</p><p class="sub">网址参数 ?side=light。</p></div><a class="ghost" href="workbench-d2.html?side=light">打开</a></div>
|
||||
|
||||
<h2 class="round">第二轮(2026-11-03):白色为主、有交互感、整齐</h2>
|
||||
<div class="v"><b>D 明亮工作区</b><div><p>结构和现状接近,整页只用一种白卡片加发丝线。统计格可以点击筛选;每个项目行有「下一步」,悬停时变成按钮,并出现「更多」菜单(会扣点的操作写明扣多少)。</p><p class="sub">圆角 6 / 8 / 12;浅底改为从墨色派生的冷灰;状态标签改为浅灰实底。</p></div><a href="workbench-d.html">打开</a></div>
|
||||
<div class="v"><b>E 下一步</b><div><p>页面最上方是按紧急程度排好的待办队列:今天截止 → 有致命问题 → 7 天内 → 其他,每条都带操作按钮,致命那条可以展开问题。项目表放在下面。</p><p class="sub">圆角 4 / 8 / 12;只有待办卡悬停时出现阴影;取消统计卡和单独的提醒条。</p></div><a href="workbench-e.html">打开</a></div>
|
||||
<div class="v"><b>F 列表 + 详情</b><div><p>左边是项目列表,点击或用 ↑ / ↓ 选中,右边的详情立即切换:状态步骤、倒计时、下一步(带扣点说明)、致命问题、清单确认进度、最近处理。</p><p class="sub">页面全白;圆角 4 / 8 / 12;统计改为页头下的一行;额度移到顶栏。</p></div><a href="workbench-f.html">打开</a></div>
|
||||
|
||||
<h2 class="round">第一轮:反馈是 A 像只读报告、B 有点乱、C 排除,底色太绿灰</h2>
|
||||
<div class="v"><b>现状</b><div><p>描边框、2px 圆角、灰色表头、四张统计卡。</p><p class="sub">用作对照。</p></div><a class="ghost" href="../page-workbench.html">打开</a></div>
|
||||
<div class="v"><b>A 留白文档</b><div><p>主列是一张白纸,几乎不用框线,靠字号、字重和留白分层。四张统计卡换成一句嵌大数字的概要句,数字可以点击筛选。</p><p class="sub">圆角 6 / 8 / 12;纸面极轻阴影;标题加大到 32;状态改为纯文字。</p></div><a href="workbench-a.html">打开</a></div>
|
||||
<div class="v"><b>B 工作面板</b><div><p>信息密度不降,层次靠底色分区和圆角:校样纸色面板里放白色列表行。每个项目行带一条四段状态轨。顶栏有全局搜索。</p><p class="sub">圆角 4 / 6 / 8 / 10;不用描边和阴影;页面底色改白;状态标签改实底。</p></div><a href="workbench-b.html">打开</a></div>
|
||||
<div class="v"><b>C 截止线(已排除)</b><div><p>以时间为主轴:顶部墨色时间带标出未来 30 天的截止点,今天截止的提醒和统计都并进时间带。项目行左侧是大号日期块。</p><p class="sub">圆角 4 / 6 / 10 / 12;新增大面积墨底;项目名用宋体 17。</p></div><a href="workbench-c.html">打开</a></div>
|
||||
|
||||
<p class="note">每个方案文件顶部的 HTML 注释里,列出了它相对 design-system.md 改了哪些规则。</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
436
docs/design/ui-demo/explore/workbench-a.html
Normal file
436
docs/design/ui-demo/explore/workbench-a.html
Normal file
@@ -0,0 +1,436 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
工作台 · 风格探索方案 A「留白文档」
|
||||
内容、数据、交互沿用 ../page-workbench.html;颜色只用 tokens.css 的色值和语义。
|
||||
|
||||
本方案相对 design-system.md 改了这些规则:
|
||||
1. 圆角(§5.1 控件和卡片 2px、浮层 4px):控件 6px,主列白纸面 12px,侧栏当前项、提醒条、表格行悬停 8px,问题级别标签 4px。
|
||||
2. 阴影(§5.1 只用于浮层):主列白纸面加一层极轻阴影,只为把白纸从校样纸上托起来;右栏、提醒条、标签都不用阴影。
|
||||
3. 分隔方式(§5.1 层次靠分隔线和底色):表头去掉凹陷灰底和加粗下边线,只剩小号辅助灰字;表格不画竖线,行间只留一条极浅分隔线;
|
||||
侧栏不画右边线、顶栏不画下边线;右栏三组内容不加卡片框,只用间距和小标题分组。
|
||||
4. 字阶(§4.2):H1 24/32 → 32/40;区块标题改用宋体 20/28;项目名保持 16/24;新增「概要句」22/52,句中数字 Barlow 44;
|
||||
正文仍为 14/22,最小 12px,字重只用 400 / 600。
|
||||
5. 统计卡(§7.9 / 原型的四张统计卡):换成一句概要句,句中数字可点,点了就筛选下方项目列表。
|
||||
6. 控件(§7.1):按钮高 32 → 36;分段筛选改为文字标签页,选中用 2px 墨色下划线;搜索框去掉描边,只用校样纸浅底
|
||||
(放宽了 §3.2「需要看清边界的控件用 --border-control」:靠浅底、放大镜图标和占位符说明这是输入框);提醒条里的次按钮改为无描边白底。
|
||||
7. 状态 / 就绪(§7.9 中性描边标签):改为纯文字;「就绪」用虚下划线提示可悬停查看说明。问题级别标签的形状规则不变(致命实心、重要描边、提示无框)。
|
||||
8. 提醒条(§7.12):去掉外框,赭浅底 + 左侧 3px 赭色竖条。
|
||||
9. 截止时间:日期一行(数字用 Barlow)+ 剩余时间一行,整列右对齐放在最后一列;颜色规则不变(≤72 小时赭小字,≤24 小时加粗,已截止辅助灰)。
|
||||
-->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>工作台 · 标博士</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<link rel="stylesheet" href="../../system/tokens.css">
|
||||
<link rel="stylesheet" href="../../system/fonts-local.css">
|
||||
<style>
|
||||
/* ---------- 本方案派生的变量(全部由 tokens.css 现有颜色 color-mix 得到,不引入新色相) ---------- */
|
||||
:root {
|
||||
--a-sheet-shadow: 0 1px 2px color-mix(in srgb, var(--c-ink) 5%, transparent),
|
||||
0 12px 32px -16px color-mix(in srgb, var(--c-ink) 12%, transparent); /* 白纸面的极轻阴影:墨 5% / 12% */
|
||||
--a-row-rule: color-mix(in srgb, var(--c-rule) 60%, var(--c-white)); /* 表格行间分隔:分隔线 60% 混白,比 --c-rule 更浅 */
|
||||
--a-nav-hover: color-mix(in srgb, var(--c-white) 55%, var(--c-paper)); /* 侧栏悬停:白与校样纸之间 */
|
||||
--a-underline: color-mix(in srgb, var(--c-ink) 22%, var(--c-white)); /* 概要句数字的常态下划线:墨 22% */
|
||||
--a-radius-ctl: 6px; /* 按钮、输入框、图标按钮 */
|
||||
--a-radius-block: 8px; /* 侧栏当前项、提醒条、行悬停 */
|
||||
--a-radius-sheet: 12px; /* 主列白纸面 */
|
||||
--a-ctl-h: 36px;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html { -webkit-text-size-adjust: 100%; }
|
||||
body {
|
||||
margin: 0; min-width: 1280px;
|
||||
background: var(--bg-page); color: var(--text-primary);
|
||||
font: var(--fw-regular) var(--fs-body)/var(--lh-body) var(--font-sans);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
a { color: inherit; }
|
||||
button { font: inherit; color: inherit; }
|
||||
.fig { font-family: var(--font-figures); font-variant-numeric: tabular-nums; }
|
||||
.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
||||
.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; }
|
||||
:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: 4px; }
|
||||
.lnk { color: var(--text-primary); text-decoration: underline; text-decoration-color: var(--a-underline); text-underline-offset: 3px; }
|
||||
.lnk:hover { text-decoration-color: currentColor; }
|
||||
|
||||
/* ---------- 框架 ---------- */
|
||||
.app { display: grid; grid-template-columns: var(--sidebar-w) minmax(0, 1fr); min-height: 100vh; }
|
||||
.side { position: sticky; top: 0; height: 100vh; display: flex; flex-direction: column; padding: var(--sp-6) var(--sp-4) var(--sp-6) var(--sp-5); }
|
||||
.brand { display: inline-block; padding: 0 var(--sp-2); margin-bottom: var(--sp-10); }
|
||||
.brand img { display: block; height: 34px; width: auto; }
|
||||
.nav { display: flex; flex-direction: column; gap: 2px; }
|
||||
.nav a {
|
||||
display: flex; align-items: center; gap: 10px; height: 40px; padding: 0 var(--sp-3);
|
||||
border-radius: var(--a-radius-block); color: var(--text-secondary); text-decoration: none;
|
||||
}
|
||||
.nav a:hover { background: var(--a-nav-hover); color: var(--text-primary); }
|
||||
.nav a[aria-current="page"] { background: var(--bg-surface); color: var(--text-primary); font-weight: var(--fw-bold); }
|
||||
.nav .demo { margin-top: var(--sp-6); font-size: var(--fs-caption); line-height: var(--lh-caption); height: 32px; }
|
||||
.side-foot { margin-top: auto; padding: 0 var(--sp-3); 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); font-weight: var(--fw-bold); color: var(--text-primary); }
|
||||
|
||||
.main { padding: 0 var(--sp-10) var(--sp-16) var(--sp-6); min-width: 0; }
|
||||
.top { display: flex; justify-content: flex-end; align-items: center; gap: var(--sp-1); height: 64px; }
|
||||
.quota-chip { display: inline-flex; align-items: center; gap: 6px; height: var(--a-ctl-h); padding: 0 var(--sp-3); border-radius: var(--a-radius-ctl); color: var(--text-secondary); text-decoration: none; }
|
||||
.quota-chip .fig { font-size: 16px; font-weight: var(--fw-bold); color: var(--text-primary); margin-right: 6px; }
|
||||
.quota-chip:hover, .icon-btn:hover { background: var(--a-nav-hover); }
|
||||
.icon-btn { position: relative; display: inline-grid; place-items: center; width: var(--a-ctl-h); height: var(--a-ctl-h); border: 0; border-radius: var(--a-radius-ctl); background: none; color: var(--text-secondary); cursor: pointer; }
|
||||
.icon-btn:hover { color: var(--text-primary); }
|
||||
.icon-btn .dot { position: absolute; top: 8px; right: 8px; width: 7px; height: 7px; border-radius: 50%; background: var(--warn); box-shadow: 0 0 0 2px var(--bg-page); }
|
||||
|
||||
.layout { display: grid; grid-template-columns: minmax(0, 1fr) clamp(256px, 20.5vw, 296px); gap: clamp(32px, 2.8vw, 48px); align-items: start; max-width: 1240px; }
|
||||
|
||||
/* ---------- 白纸面 ---------- */
|
||||
.sheet { background: var(--bg-surface); border-radius: var(--a-radius-sheet); box-shadow: var(--a-sheet-shadow); padding: var(--sp-10) clamp(36px, 3.3vw, 52px) var(--sp-12); }
|
||||
.head { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--sp-6); }
|
||||
.head h1 { margin: 0; font: var(--fw-bold) 32px/40px var(--font-serif); letter-spacing: .02em; }
|
||||
.head .date { margin-left: var(--sp-4); color: var(--text-secondary); font-family: var(--font-sans); font-size: var(--fs-body); font-weight: var(--fw-regular); letter-spacing: 0; }
|
||||
.head .actions { display: flex; align-items: center; gap: var(--sp-4); flex: none; }
|
||||
.cost { font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; justify-content: center; gap: 6px; height: var(--a-ctl-h); padding: 0 var(--sp-4);
|
||||
border: 0; border-radius: var(--a-radius-ctl); font-weight: var(--fw-bold); text-decoration: none; cursor: pointer; white-space: nowrap;
|
||||
}
|
||||
.btn.primary { background: var(--c-ink); color: var(--text-on-dark); }
|
||||
.btn.primary:hover { background: color-mix(in srgb, var(--c-ink) 92%, var(--c-white)); }
|
||||
.btn.quiet { height: 32px; padding: 0 var(--sp-3); background: var(--bg-surface); color: var(--text-primary); }
|
||||
.btn.quiet:hover { background: color-mix(in srgb, var(--c-white) 60%, var(--warn-soft)); }
|
||||
|
||||
/* 概要句:本页唯一的「大声」元素 */
|
||||
.lede { margin: var(--sp-8) 0 0; max-width: 34em; font-size: 22px; line-height: 52px; color: var(--text-primary); }
|
||||
.lede .half { display: block; white-space: nowrap; } /* 两半句在分号处固定断行 */
|
||||
.n {
|
||||
padding: 0 3px; margin: 0 1px; border: 0; background: none; cursor: pointer;
|
||||
font: var(--fw-bold) 44px/1 var(--font-figures); font-variant-numeric: tabular-nums; color: var(--text-primary);
|
||||
text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 7px; text-decoration-color: var(--a-underline);
|
||||
vertical-align: -3px;
|
||||
}
|
||||
.n:hover { text-decoration-color: currentColor; }
|
||||
.n[aria-pressed="true"] { text-decoration-color: currentColor; text-decoration-thickness: 3px; }
|
||||
.n.due { color: var(--c-ochre); } /* ≥18px,可用 --c-ochre */
|
||||
.n.fatal { color: var(--sev-fatal); }
|
||||
.lede-sub { margin: var(--sp-2) 0 0; color: var(--text-secondary); }
|
||||
|
||||
/* 提醒条 */
|
||||
.alert { position: relative; display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-8); padding: var(--sp-3) var(--sp-4) var(--sp-3) var(--sp-5); border-radius: var(--a-radius-block); background: var(--warn-soft); overflow: hidden; }
|
||||
.alert::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--warn); }
|
||||
.alert > .ic { color: var(--warn); }
|
||||
.alert p { margin: 0; flex: 1; min-width: 0; }
|
||||
.alert b { color: var(--warn-text); font-weight: var(--fw-bold); }
|
||||
|
||||
/* 筛选 + 搜索 */
|
||||
.bar { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-6); margin-top: var(--sp-10); }
|
||||
.tabs { display: flex; gap: var(--sp-6); }
|
||||
.tabs button {
|
||||
height: var(--a-ctl-h); padding: 0 2px; border: 0; background: none; cursor: pointer;
|
||||
font-size: 15px; color: var(--text-secondary); box-shadow: inset 0 -2px 0 transparent;
|
||||
}
|
||||
.tabs button:hover { color: var(--text-primary); }
|
||||
.tabs button[aria-pressed="true"] { color: var(--text-primary); font-weight: var(--fw-bold); box-shadow: inset 0 -2px 0 var(--c-ink); }
|
||||
.tabs .fig { margin-left: 4px; font-size: 15px; }
|
||||
.search { position: relative; display: block; width: 248px; }
|
||||
.search .ic { position: absolute; left: 12px; top: 10px; color: var(--text-secondary); pointer-events: none; }
|
||||
.search input {
|
||||
width: 100%; height: var(--a-ctl-h); padding: 0 var(--sp-3) 0 36px; border: 0; border-radius: var(--a-radius-ctl);
|
||||
background: var(--bg-page); font: inherit; color: var(--text-primary);
|
||||
}
|
||||
.search input::placeholder { color: var(--text-secondary); }
|
||||
.search input:focus-visible { outline-offset: 0; border-radius: var(--a-radius-ctl); background: var(--bg-surface); }
|
||||
.filter-note { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-3); color: var(--text-secondary); }
|
||||
.filter-note[hidden] { display: none; }
|
||||
.filter-note b { color: var(--text-primary); font-weight: var(--fw-bold); }
|
||||
.text-btn { display: inline-flex; align-items: center; gap: 4px; height: 28px; padding: 0 var(--sp-2); border: 0; border-radius: var(--a-radius-ctl); background: none; cursor: pointer; color: var(--text-primary); }
|
||||
.text-btn:hover { background: var(--bg-hover); }
|
||||
|
||||
/* 项目列表 */
|
||||
.projects { width: calc(100% + 24px); margin: var(--sp-5) -12px 0; border-collapse: separate; border-spacing: 0; table-layout: fixed; }
|
||||
.projects th { padding: 0 12px var(--sp-2); text-align: left; font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-regular); color: var(--text-secondary); white-space: nowrap; }
|
||||
.projects td { padding: var(--sp-3) 12px; vertical-align: middle; border-top: 1px solid var(--a-row-rule); }
|
||||
.projects tbody tr:first-child td { border-top-color: transparent; }
|
||||
.projects tbody tr.row { cursor: pointer; }
|
||||
.projects tbody tr.row:hover td { background: var(--bg-hover); }
|
||||
.projects tbody tr.row:hover td:first-child { border-radius: var(--a-radius-block) 0 0 var(--a-radius-block); }
|
||||
.projects tbody tr.row:hover td:last-child { border-radius: 0 var(--a-radius-block) var(--a-radius-block) 0; }
|
||||
.projects tbody tr.row:hover td, .projects tbody tr.row:hover + tr td { border-top-color: transparent; }
|
||||
.projects .r { text-align: right; }
|
||||
.c-st { width: 72px; } .c-rd { width: 72px; } .c-ft { width: 72px; } .c-due { width: 128px; }
|
||||
.projects td:nth-child(2), .projects td:nth-child(3) { white-space: nowrap; }
|
||||
.projects th .ic { vertical-align: -3px; margin-left: 2px; }
|
||||
.pname { font-size: var(--fs-title); line-height: var(--lh-title); font-weight: var(--fw-bold); text-decoration: none; }
|
||||
.row:hover .pname { text-decoration: underline; text-underline-offset: 3px; }
|
||||
.psub { color: var(--text-secondary); }
|
||||
.psub span + span { margin-left: var(--sp-3); }
|
||||
.due { white-space: nowrap; }
|
||||
.due .d1 { display: block; font-size: 15px; }
|
||||
.due .d1 .fig { font-size: 16px; }
|
||||
.due .d2 { display: block; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
|
||||
.due.today .d1, .due.today .d2 { color: var(--warn-text); font-weight: var(--fw-bold); }
|
||||
.due.soon .d1, .due.soon .d2 { color: var(--warn-text); }
|
||||
.due.past .d1 { color: var(--text-secondary); }
|
||||
.dash { color: var(--text-secondary); }
|
||||
.ready { text-decoration: underline dotted var(--text-secondary); text-underline-offset: 4px; cursor: help; position: relative; }
|
||||
.ready:hover::after, .ready:focus-visible::after {
|
||||
content: attr(data-tip); position: absolute; left: -8px; top: calc(100% + 8px); z-index: 5; width: 232px; padding: var(--sp-2) var(--sp-3);
|
||||
border-radius: var(--a-radius-ctl); background: var(--c-ink); color: var(--text-on-dark); font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-regular);
|
||||
box-shadow: var(--shadow-overlay); white-space: normal; text-align: left;
|
||||
}
|
||||
.empty { padding: var(--sp-10) 12px; text-align: center; color: var(--text-secondary); }
|
||||
.empty b { display: block; margin-bottom: var(--sp-1); font-size: var(--fs-title); line-height: var(--lh-title); color: var(--text-primary); font-weight: var(--fw-bold); }
|
||||
.note { margin: var(--sp-4) 0 0; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
|
||||
|
||||
/* 问题级别标签(形状规则不变) */
|
||||
.sev { display: inline-flex; align-items: center; gap: 4px; height: 20px; padding: 0 6px; border-radius: 4px; font-size: var(--fs-caption); line-height: 20px; font-weight: var(--fw-bold); white-space: nowrap; }
|
||||
.sev.fatal { background: var(--sev-fatal); color: var(--text-on-dark); }
|
||||
.sev .fig { font-size: 13px; }
|
||||
|
||||
/* 待处理的致命问题 */
|
||||
.sec-h { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-12); }
|
||||
.sec-h h2 { margin: 0; font: var(--fw-bold) var(--fs-h2)/var(--lh-h2) var(--font-serif); }
|
||||
.sec-h .more { margin-left: auto; }
|
||||
.issues { list-style: none; margin: var(--sp-4) 0 0; padding: 0; }
|
||||
.issues li { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; column-gap: var(--sp-4); align-items: baseline; padding: var(--sp-3) 0; }
|
||||
.issues li + li { margin-top: var(--sp-2); }
|
||||
.issues .sev { position: relative; top: -2px; }
|
||||
.issues b { display: block; font-size: 15px; line-height: 24px; font-weight: var(--fw-bold); }
|
||||
.issues p { margin: 2px 0 0; color: var(--text-secondary); }
|
||||
.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; }
|
||||
.cite .fig { font-size: 15px; }
|
||||
|
||||
/* ---------- 右栏:直接放在校样纸上 ---------- */
|
||||
.rail { padding-top: var(--sp-10); }
|
||||
.rail section + section { margin-top: var(--sp-12); }
|
||||
.rail h3 { display: flex; align-items: baseline; justify-content: space-between; margin: 0 0 var(--sp-4); font-size: var(--fs-body); line-height: var(--lh-body); font-weight: var(--fw-bold); }
|
||||
.rail h3 small { font-size: var(--fs-caption); font-weight: var(--fw-regular); color: var(--text-secondary); }
|
||||
.rail h3 small .fig { font-size: 13px; }
|
||||
.task-h { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-2); }
|
||||
.task-h span, .task-p { font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
|
||||
.progress { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-2); }
|
||||
.track { flex: 1; height: 4px; border-radius: 2px; background: var(--c-sunken); overflow: hidden; }
|
||||
.track i { display: block; height: 100%; background: var(--c-ink); border-radius: 2px; }
|
||||
.progress .fig { font-size: 15px; font-weight: var(--fw-bold); min-width: 34px; text-align: right; }
|
||||
.queue { display: flex; gap: var(--sp-2); margin-top: var(--sp-4); color: var(--text-secondary); }
|
||||
.queue .ic { margin-top: 3px; }
|
||||
.rail .hint { margin: var(--sp-3) 0 0; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
|
||||
.quota { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-6); }
|
||||
.quota small { display: block; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
|
||||
.quota .big { font: var(--fw-bold) var(--fs-figure)/var(--lh-figure) var(--font-figures); font-variant-numeric: tabular-nums; }
|
||||
.quota .unit { margin-left: 4px; font-family: var(--font-figures); font-size: 15px; color: var(--text-secondary); }
|
||||
.quota .track { margin-top: var(--sp-2); }
|
||||
.rail .links { display: flex; gap: var(--sp-5); margin-top: var(--sp-4); font-size: var(--fs-caption); line-height: var(--lh-caption); }
|
||||
.log { list-style: none; margin: 0; padding: 0; }
|
||||
.log li { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-3); padding: 6px 0; }
|
||||
.log .t { font-family: var(--font-figures); font-variant-numeric: tabular-nums; font-size: 13px; color: var(--text-secondary); white-space: nowrap; }
|
||||
.log .who { font-weight: var(--fw-bold); }
|
||||
.log .ok { color: var(--confirmed); white-space: nowrap; }
|
||||
|
||||
/* 轻提示 */
|
||||
.toasts { position: fixed; left: 50%; bottom: var(--sp-8); transform: translateX(-50%); display: grid; gap: var(--sp-2); z-index: 20; }
|
||||
.toast { padding: 10px var(--sp-4); border-radius: var(--a-radius-block); background: var(--c-ink); color: var(--text-on-dark); box-shadow: var(--shadow-overlay); }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<aside class="side">
|
||||
<a class="brand" href="../page-workbench.html"><img src="../../logo/svg/logo-horizontal.svg" alt="标博士"></a>
|
||||
<nav class="nav" aria-label="主导航">
|
||||
<a href="#" aria-current="page"><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><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"/></svg>工作台</a>
|
||||
<a href="#" data-demo><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 9h10M7 13h6"/><circle cx="16" cy="15" r="2"/></svg>资质库</a>
|
||||
<a href="#" data-demo><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/></svg>额度</a>
|
||||
<a class="demo" href="../index.html"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><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"/></svg>UI demo 目录</a>
|
||||
</nav>
|
||||
<div class="side-foot"><b>王敏</b>南京某信息技术有限公司<br>专业版,至 2027-10-31</div>
|
||||
</aside>
|
||||
|
||||
<main class="main">
|
||||
<header class="top">
|
||||
<a class="quota-chip" href="#quota" title="本期剩余额度">解析<span class="fig">37</span>检查<span class="fig">12</span></a>
|
||||
<button class="icon-btn" aria-label="通知(有 1 条未读)" title="通知" data-notice><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><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"/></svg><span class="dot"></span></button>
|
||||
<button class="icon-btn" aria-label="快捷键说明" title="快捷键说明(?)" data-keys><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.01M10 8h.01M14 8h.01M18 8h.01M8 12h.01M12 12h.01M16 12h.01M7 16h10"/></svg></button>
|
||||
</header>
|
||||
|
||||
<div class="layout">
|
||||
<article class="sheet">
|
||||
<div class="head">
|
||||
<h1>工作台<span class="date">11月3日 周二</span></h1>
|
||||
<div class="actions">
|
||||
<span class="cost">扣 1 次解析,剩 <span class="fig">37</span> 次</span>
|
||||
<a class="btn primary" href="../forms.html#s-new"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14"/><path d="M12 5v14"/></svg>新建项目</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="lede" data-lede>
|
||||
<span class="half"><button class="n" data-f="active" aria-pressed="false"><span data-to="4">4</span><span class="vh">(只看进行中的项目)</span></button> 个项目进行中,<button class="n due" data-f="due7" aria-pressed="false"><span data-to="2">2</span><span class="vh">(只看 7 天内截止的项目)</span></button> 个 7 天内截止;</span>
|
||||
<span class="half"><button class="n fatal" data-f="fatal" aria-pressed="false"><span data-to="3">3</span><span class="vh">(只看有致命问题的项目)</span></button> 个致命问题待处理,<button class="n" data-f="pend" aria-pressed="false"><span data-to="4">4</span><span class="vh">(只看有待确认清单条目的项目)</span></button> 条清单待确认。</span>
|
||||
</p>
|
||||
<p class="lede-sub">另有 1 个已递交、1 个已归档。致命问题都在 1 个项目里,待确认的清单条目在 2 个项目里。</p>
|
||||
|
||||
<div class="alert" role="note">
|
||||
<svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
|
||||
<p><b>某县数据中心机房改造项目今天 17:00 截止。</b>清单还是草稿,还没有做过检查。</p>
|
||||
<a class="btn quiet" href="../page-reading.html">去确认清单</a>
|
||||
</div>
|
||||
|
||||
<div class="bar">
|
||||
<div class="tabs" role="group" aria-label="按状态筛选" data-tabs>
|
||||
<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>
|
||||
<label class="search"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg><input type="search" placeholder="搜索项目、采购人、编号" aria-label="搜索项目" data-q></label>
|
||||
</div>
|
||||
<div class="filter-note" data-fnote hidden><span>正在看:<b data-fname></b></span><button class="text-btn" data-clear><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>清除筛选</button></div>
|
||||
|
||||
<table class="projects">
|
||||
<thead><tr><th scope="col">项目</th><th scope="col" class="c-st">状态</th><th scope="col" class="c-rd">就绪</th><th scope="col" class="c-ft">致命</th><th scope="col" class="c-due r" aria-sort="ascending">投标截止<svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14"/><path d="m19 12-7 7-7-7"/></svg></th></tr></thead>
|
||||
<tbody data-rows></tbody>
|
||||
</table>
|
||||
<p class="note">按投标截止时间排序,截止临近的排在前面。<span id="ready-note">「就绪」只表示致命问题都已处理,不代表投标文件没有问题。</span></p>
|
||||
|
||||
<section id="fatal" aria-labelledby="fatal-h">
|
||||
<div class="sec-h"><h2 id="fatal-h">待处理的致命问题</h2><span class="sev fatal">致命<span class="fig">3</span></span><a class="more lnk" href="#" data-demo>去查标页处理</a></div>
|
||||
<ul class="issues">
|
||||
<li><span class="sev fatal">致命</span><div><b>报价超过采购包 1 的最高限价</b><p>智慧大厅建设项目。开标一览表 287.60 万元,限价 286.00 万元。</p></div><a class="cite" href="#" data-cite>报价册 · 第 <span class="fig">1</span> 页</a></li>
|
||||
<li><span class="sev fatal">致命</span><div><b>★ 条款疑似不满足:应用服务器内存</b><p>智慧大厅建设项目。响应 256G,要求 ≥ 512G。上次标记为已修改,问题仍在。</p></div><a class="cite" href="#" data-cite>技术册 · 第 <span class="fig">12</span> 页</a></li>
|
||||
<li><span class="sev fatal">致命</span><div><b>授权委托书的单位签章位置未发现盖章</b><p>智慧大厅建设项目。只看有无,不鉴定真伪。</p></div><a class="cite" href="#" data-cite>商务册 · 第 <span class="fig">5</span> 页</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
<aside class="rail" aria-label="任务、额度与最近处理">
|
||||
<section>
|
||||
<h3>正在进行的任务</h3>
|
||||
<div class="task-h"><b>解析补遗 2</b><span>提取清单</span></div>
|
||||
<div class="task-p">某市医保信息系统等保测评服务</div>
|
||||
<div class="progress"><div class="track" 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"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg><span>检查排队中:智慧社区平台运维,前面还有 1 个任务</span></div>
|
||||
<p class="hint">可以离开本页,完成后会发站内通知。</p>
|
||||
</section>
|
||||
|
||||
<section id="quota">
|
||||
<h3>本期额度<small>至 <span class="fig">2027-10-31</span></small></h3>
|
||||
<div class="quota">
|
||||
<div><small>解析剩余</small><span class="big">37</span><span class="unit">/ 50</span><div class="track" 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="track" aria-hidden="true"><i style="width:40%"></i></div></div>
|
||||
</div>
|
||||
<div class="links"><a class="lnk" href="#" data-demo>扣点规则</a><a class="lnk" href="#" data-demo>查看流水</a></div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>最近处理<a class="lnk" href="#" data-demo style="font-size:var(--fs-caption);font-weight:400">全部记录</a></h3>
|
||||
<ul class="log">
|
||||
<li><span><span class="who">王敏</span> <span class="ok">✓ 已修改</span>「授权委托书签章」</span><span class="t">14:26</span></li>
|
||||
<li><span><span class="who">王敏</span> 确认了清单 v2:智慧大厅</span><span class="t">11-02</span></li>
|
||||
<li><span><span class="who">系统</span> 完成第 2 次检查,致命 3</span><span class="t">11-02</span></li>
|
||||
<li><span><span class="who">系统</span> 并入补遗 1,更新 2 条</span><span class="t">11-02</span></li>
|
||||
<li><span><span class="who">李工</span> 忽略「其他项目名称」:业绩介绍</span><span class="t">11-01</span></li>
|
||||
</ul>
|
||||
</section>
|
||||
</aside>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const $ = (s, el = document) => el.querySelector(s);
|
||||
const $$ = (s, el = document) => [...el.querySelectorAll(s)];
|
||||
const fig = (s) => String(s).replace(/(\d[\d:.]*)/g, '<span class="fig">$1</span>');
|
||||
|
||||
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;pend = 有待确认的清单条目(4 条在 2 个项目里) */
|
||||
const P = [
|
||||
{ name: "某县数据中心机房改造项目", buyer: "某县大数据管理局", lot: "标段 1", h: 6, d1: "今天 17:00", d2: "剩 6 小时", cls: "today", st: "编制中", ready: "", fatal: 0, pend: 1, href: "../page-reading.html" },
|
||||
{ name: "某区智慧社区平台运维服务", buyer: "某区民政局", lot: "全部", h: 53, d1: "11月5日 16:00", d2: "剩 2 天 5 小时", cls: "soon", st: "已检查", ready: "就绪", fatal: 0 },
|
||||
{ name: "某市政务服务中心智慧大厅建设项目", buyer: "某市政务服务管理办公室", lot: "采购包 1", h: 408, d1: "11月20日 09:30", d2: "剩 17 天", cls: "", st: "已检查", ready: "未就绪", fatal: 3, href: "../page-reading.html" },
|
||||
{ name: "某市医保信息系统等保测评服务", buyer: "某市医疗保障局", lot: "全部", h: 600, d1: "11月28日 10:00", d2: "剩 25 天", cls: "", st: "跟进中", ready: "", fatal: 0, pend: 1 },
|
||||
{ name: "某区智慧校园一期", buyer: "某区教育局", lot: "全部", h: -1, d1: "已截止", d2: "", cls: "past", st: "已递交", ready: "", fatal: 0 },
|
||||
{ name: "某市不动产登记信息平台升级", buyer: "某市自然资源和规划局", lot: "采购包 2", h: -2, d1: "已截止", d2: "", cls: "past", st: "已归档", ready: "", fatal: 0 }
|
||||
];
|
||||
const DONE = ["已递交", "已归档"];
|
||||
const LEDE = {
|
||||
active: ["进行中的项目", (p) => !DONE.includes(p.st)],
|
||||
due7: ["7 天内截止的项目", (p) => p.h >= 0 && p.h <= 168],
|
||||
fatal: ["有致命问题待处理的项目", (p) => p.fatal > 0],
|
||||
pend: ["有清单条目待确认的项目", (p) => p.pend > 0]
|
||||
};
|
||||
let seg = "active", lede = "", q = "";
|
||||
|
||||
function match(p) {
|
||||
const bySeg = lede ? LEDE[lede][1](p) : seg === "all" || (seg === "active" ? !DONE.includes(p.st) : p.st === seg);
|
||||
return bySeg && (!q || (p.name + p.buyer).includes(q));
|
||||
}
|
||||
function render() {
|
||||
const rows = P.filter(match);
|
||||
$("[data-rows]").innerHTML = rows.map((p, i) => `<tr class="row" data-i="${P.indexOf(p)}">
|
||||
<td><a class="pname" href="${p.href || "#"}">${p.name}</a><div class="psub"><span>${p.buyer}</span><span>${fig(p.lot)}</span></div></td>
|
||||
<td>${p.st}</td>
|
||||
<td>${p.ready === "就绪" ? '<span class="ready" tabindex="0" aria-describedby="ready-note" data-tip="只表示致命问题都已处理,不代表投标文件没有问题。">就绪</span>' : p.ready || '<span class="dash" aria-label="无">—</span>'}</td>
|
||||
<td>${p.fatal ? `<span class="sev fatal">致命<span class="fig">${p.fatal}</span></span>` : '<span class="dash" aria-label="无">—</span>'}</td>
|
||||
<td class="r due ${p.cls}"><span class="d1">${fig(p.d1)}</span>${p.d2 ? `<span class="d2">${p.d2}</span>` : ""}</td></tr>`).join("")
|
||||
|| `<tr><td colspan="5"><div class="empty"><b>没有符合条件的项目</b>换个关键词,或切到「全部」看看。</div></td></tr>`;
|
||||
}
|
||||
function setSeg(v) {
|
||||
seg = v; lede = "";
|
||||
$$("[data-tabs] button").forEach((b) => b.setAttribute("aria-pressed", b.dataset.v === v));
|
||||
$$(".n").forEach((b) => b.setAttribute("aria-pressed", "false"));
|
||||
$("[data-fnote]").hidden = true;
|
||||
render();
|
||||
}
|
||||
function setLede(f) {
|
||||
if (lede === f) { setSeg("active"); return; }
|
||||
lede = f;
|
||||
$$("[data-tabs] button").forEach((b) => b.setAttribute("aria-pressed", "false"));
|
||||
$$(".n").forEach((b) => b.setAttribute("aria-pressed", b.dataset.f === f));
|
||||
$("[data-fname]").textContent = LEDE[f][0];
|
||||
$("[data-fnote]").hidden = false;
|
||||
render();
|
||||
}
|
||||
|
||||
$("[data-tabs]").addEventListener("click", (e) => { const b = e.target.closest("button"); if (b) setSeg(b.dataset.v); });
|
||||
$$(".n").forEach((b) => b.addEventListener("click", () => setLede(b.dataset.f)));
|
||||
$("[data-clear]").addEventListener("click", () => { setSeg("active"); $("[data-tabs] button").focus(); });
|
||||
$("[data-q]").addEventListener("input", (e) => { q = e.target.value.trim(); render(); });
|
||||
$("[data-rows]").addEventListener("click", (e) => {
|
||||
const r = e.target.closest("tr.row"); if (!r) return;
|
||||
const p = P[r.dataset.i];
|
||||
if (!p.href) { e.preventDefault(); toast("(演示)只做了「读标」组合页"); return; }
|
||||
if (!e.target.closest("a")) location.href = p.href;
|
||||
});
|
||||
document.addEventListener("click", (e) => {
|
||||
const a = e.target.closest("[data-demo], [data-cite]"); if (!a) return;
|
||||
e.preventDefault();
|
||||
toast(a.matches("[data-cite]") ? "(演示)出处会在原文查看器里打开" : "(演示)这一页还没做");
|
||||
});
|
||||
$("[data-notice]").onclick = () => toast("解析完成:补遗 1 已并入清单(新增 2 条,更新 1 条)。");
|
||||
$("[data-keys]").onclick = () => toast("(演示)快捷键:J / K 逐条,1 / 2 / 3 处理,? 显示说明");
|
||||
|
||||
render();
|
||||
|
||||
/* 唯一的动效:概要句数字载入时从 0 数到实际值一次 */
|
||||
if (!matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
||||
const els = $$("[data-to]"), t0 = performance.now(), dur = 700;
|
||||
const step = (t) => {
|
||||
const k = Math.min(1, (t - t0) / dur), e = 1 - Math.pow(1 - k, 3);
|
||||
els.forEach((el) => { el.textContent = Math.round(+el.dataset.to * e); });
|
||||
if (k < 1) requestAnimationFrame(step);
|
||||
};
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
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>
|
||||
481
docs/design/ui-demo/explore/workbench-c.html
Normal file
481
docs/design/ui-demo/explore/workbench-c.html
Normal file
@@ -0,0 +1,481 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<!--
|
||||
工作台 · 风格方案 C「截止线」(探索稿,不是定稿)
|
||||
|
||||
思路:工作台要回答的是「下一个截止的是谁、它准备好了没有」,所以页面以时间为主轴。
|
||||
顶部一条墨色时间带画出未来 30 天的截止点,项目列表每行以日期块开头。
|
||||
|
||||
本方案相对 design-system.md 改了这些规则:
|
||||
1. 圆角(§5.1):控件、标签 2px → 控件 6px、级别标签 4px、面板 10px、时间带 12px。仍不用胶囊形。
|
||||
2. 按钮高度(§7.1):默认 32 → 36。次按钮不画描边,用凹陷底色;分段筛选不画外框,选中项墨底白字。
|
||||
3. 层次(§5.1「靠分隔线和底色」):区块之间改为只靠间距和白色面板,不用描边和阴影;表格去掉凹陷表头和竖线,行间用极淡分隔线。
|
||||
4. 颜色(§3.4「不用大面积彩色底」):新增一块大面积墨色底(时间带)。墨不是彩色,但这是规范里没有的用法。
|
||||
时间带上的文字用白 / 校样纸,次要文字用派生的浅灰(对比 ≥ 7:1);赭色只做图形和带白字的色块(白字压赭 4.8:1),不做小字;
|
||||
致命在深色底上一律用「红底白字」实心标签。
|
||||
5. 字阶(§4.2):工作台标题 24 → 28/36;项目名 16 宋体 → 17/26 宋体(规范 §2 说宋体只用于 ≥20px,此处是例外,需在 Windows 上实测);
|
||||
日期块日数字 Barlow 28;时间带小结数字 Barlow 24。
|
||||
6. 状态(§7.9):状态不再用中性描边标签,改成文字 + 5 格流程刻度(跟进中 → 已归档);「就绪」改为文字 + 墨点,不再描边。
|
||||
7. 统计(§4.2 Figure 级):去掉四张统计卡,并入时间带底部一行小结。
|
||||
8. 提醒条(§7.12):「今天截止」提醒并入时间带头部,不再单独用赭浅底提醒条。
|
||||
9. 动效:时间带标记载入时依次落位一次(只此一处),减少动态效果时关闭。
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>工作台 · 标博士</title>
|
||||
<link rel="icon" href="../../logo/svg/favicon-16.svg">
|
||||
<link rel="stylesheet" href="../../system/tokens.css">
|
||||
<link rel="stylesheet" href="../../system/fonts-local.css">
|
||||
<style>
|
||||
/* ---------- 本页派生变量:全部从 tokens.css 已有色值混出,不引入新色相 ---------- */
|
||||
:root {
|
||||
--band: var(--c-ink); /* 时间带底色:墨 */
|
||||
--band-raise: color-mix(in srgb, var(--c-white) 11%, var(--c-ink)); /* 时间带上的普通截止标记底 */
|
||||
--on-band: var(--c-paper); /* 时间带正文:校样纸,约 13:1 */
|
||||
--on-band-sub: color-mix(in srgb, var(--c-paper) 72%, var(--c-ink)); /* 时间带次要文字,约 7.8:1 */
|
||||
--band-axis: color-mix(in srgb, var(--c-paper) 48%, var(--c-ink)); /* 时间轴主线(图形,约 4:1) */
|
||||
--band-tick: color-mix(in srgb, var(--c-paper) 24%, var(--c-ink)); /* 日刻度、分隔(装饰) */
|
||||
--band-today: color-mix(in srgb, var(--c-paper) 9%, var(--c-ink)); /* 「今天」那一格的底 */
|
||||
--row-rule: color-mix(in srgb, var(--c-rule) 55%, var(--c-white)); /* 列表行之间的极淡分隔线(装饰) */
|
||||
--btn-quiet: var(--c-sunken); /* 次按钮底色(沿用凹陷灰) */
|
||||
|
||||
--r-ctl: 6px; /* 控件 */
|
||||
--r-tag: 4px; /* 级别标签、状态小标签 */
|
||||
--r-panel: 10px; /* 白色面板 */
|
||||
--r-band: 12px; /* 时间带 */
|
||||
--ctl-h: 36px;
|
||||
--rail-w: clamp(296px, 24vw, 336px);
|
||||
}
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
html { -webkit-text-size-adjust: 100%; }
|
||||
body {
|
||||
margin: 0; min-width: 1280px; background: var(--bg-page); color: var(--text-primary);
|
||||
font: var(--fw-regular) var(--fs-body)/var(--lh-body) var(--font-sans);
|
||||
display: grid; grid-template-columns: var(--sidebar-w) minmax(0, 1fr); min-height: 100vh;
|
||||
}
|
||||
h1, h2, h3, p, ul { margin: 0; }
|
||||
ul { padding: 0; list-style: none; }
|
||||
button, input { font: inherit; color: inherit; }
|
||||
a { color: inherit; }
|
||||
.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; }
|
||||
.sr { 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; }
|
||||
.band :focus-visible { outline-color: var(--c-white); }
|
||||
|
||||
/* ---------- 侧边栏 ---------- */
|
||||
.sidebar { position: sticky; top: 0; height: 100vh; display: flex; flex-direction: column; padding: var(--sp-6) var(--sp-4) var(--sp-5); }
|
||||
.sidebar .brand { display: block; padding: 0 var(--sp-2) var(--sp-8); }
|
||||
.sidebar .brand img { height: 30px; display: block; }
|
||||
.nav { display: flex; flex-direction: column; gap: var(--sp-1); }
|
||||
.nav a { display: flex; align-items: center; gap: var(--sp-3); height: 40px; padding: 0 var(--sp-3); border-radius: var(--r-ctl); text-decoration: none; color: var(--text-secondary); }
|
||||
.nav a:hover { background: var(--bg-hover); color: var(--text-primary); }
|
||||
.nav a[aria-current] { background: var(--bg-surface); color: var(--text-primary); font-weight: var(--fw-bold); }
|
||||
.sidebar-foot { margin-top: auto; padding: var(--sp-3); font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
|
||||
.sidebar-foot b { display: block; color: var(--text-primary); font-size: var(--fs-body); line-height: var(--lh-body); }
|
||||
|
||||
/* ---------- 主区 ---------- */
|
||||
main { padding: var(--sp-8) var(--sp-10) var(--sp-16); min-width: 0; }
|
||||
.page-h { display: flex; align-items: flex-end; gap: var(--sp-4); margin-bottom: var(--sp-6); }
|
||||
.page-h h1 { font: var(--fw-bold) 28px/36px var(--font-serif); letter-spacing: .02em; }
|
||||
.page-h p { margin-top: var(--sp-1); color: var(--text-secondary); }
|
||||
.page-h .acts { margin-left: auto; display: flex; align-items: center; gap: var(--sp-3); }
|
||||
.cost { font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
|
||||
.cost .fig { color: var(--text-primary); font-size: 14px; }
|
||||
|
||||
.btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2); height: var(--ctl-h); padding: 0 var(--sp-4); border: 0; border-radius: var(--r-ctl); background: var(--btn-quiet); color: var(--text-primary); font-weight: var(--fw-bold); text-decoration: none; cursor: pointer; white-space: nowrap; transition: background var(--dur-fast) var(--ease); }
|
||||
.btn:hover { background: color-mix(in srgb, var(--c-ink) 8%, var(--c-sunken)); }
|
||||
.btn.primary { background: var(--c-ink); color: var(--c-white); }
|
||||
.btn.primary:hover { background: color-mix(in srgb, var(--c-white) 8%, var(--c-ink)); }
|
||||
.btn.on-band { background: var(--c-paper); color: var(--c-ink); }
|
||||
.btn.on-band:hover { background: var(--c-white); }
|
||||
.icon-btn { display: inline-grid; place-items: center; width: var(--ctl-h); height: var(--ctl-h); border: 0; border-radius: var(--r-ctl); background: transparent; color: var(--text-primary); cursor: pointer; position: relative; }
|
||||
.icon-btn:hover { background: var(--bg-hover); }
|
||||
.icon-btn .dot { position: absolute; top: 8px; right: 8px; width: 7px; height: 7px; border-radius: 50%; background: var(--c-ochre); box-shadow: 0 0 0 2px var(--bg-page); }
|
||||
|
||||
/* 级别标签:致命实心 / 重要描边 / 提示无框(形状规则不变,只是圆角 4) */
|
||||
.sev { display: inline-flex; align-items: center; gap: 4px; height: 20px; padding: 0 6px; border-radius: var(--r-tag); font-size: 12px; line-height: 20px; font-weight: var(--fw-bold); white-space: nowrap; }
|
||||
.sev.fatal { background: var(--sev-fatal); color: var(--c-white); }
|
||||
.sev .fig { font-size: 13px; }
|
||||
|
||||
/* ---------- 时间带 ---------- */
|
||||
.band { background: var(--band); color: var(--on-band); border-radius: var(--r-band); padding: var(--sp-6) var(--sp-8) 0; }
|
||||
.band-h { display: flex; align-items: center; gap: var(--sp-4); }
|
||||
.band-h .clock { width: 20px; height: 20px; color: var(--c-ochre); flex: none; }
|
||||
.band-h .msg b { display: block; font-size: var(--fs-title); line-height: var(--lh-title); font-weight: var(--fw-bold); color: var(--c-white); }
|
||||
.band-h .msg span { color: var(--on-band-sub); }
|
||||
.band-h .btn { margin-left: auto; }
|
||||
|
||||
.tl { position: relative; height: 212px; margin-top: var(--sp-6); }
|
||||
.tl-today { position: absolute; top: 0; bottom: 36px; background: var(--band-today); border-radius: 4px 4px 0 0; }
|
||||
.tl-axis { position: absolute; left: 0; right: 0; bottom: 36px; height: 1px; background: var(--band-axis); }
|
||||
.tl-72 { position: absolute; left: 0; bottom: 35px; height: 3px; background: var(--c-ochre); border-radius: 0 2px 2px 0; }
|
||||
.tl-tick { position: absolute; bottom: 36px; width: 1px; height: 6px; background: var(--band-tick); }
|
||||
.tl-tick.wk { height: 10px; background: var(--band-axis); }
|
||||
.tl-lab { position: absolute; bottom: 8px; font-size: 12px; line-height: 20px; color: var(--on-band-sub); white-space: nowrap; transform: translateX(-50%); }
|
||||
.tl-lab.today { transform: none; color: var(--c-white); font-weight: var(--fw-bold); }
|
||||
.tl-lab.r { transform: translateX(-100%); }
|
||||
|
||||
.mk { position: absolute; display: block; text-decoration: none; color: var(--c-white); }
|
||||
.mk .flag { display: block; padding: 6px 10px 6px 10px; border-radius: 6px; background: var(--band-raise); white-space: nowrap; }
|
||||
.mk .flag b { display: block; font-size: 13px; line-height: 20px; font-weight: var(--fw-bold); }
|
||||
.mk .flag small { display: block; font-size: 12px; line-height: 18px; color: var(--on-band-sub); }
|
||||
.mk.soon .flag { background: var(--c-ochre); } /* 72 小时内:赭色块 + 白字(4.8:1) */
|
||||
.mk.soon .flag small { color: var(--c-white); }
|
||||
.mk .hang { display: flex; gap: 6px; margin-top: 4px; }
|
||||
.mk .ready { display: inline-flex; align-items: center; height: 20px; padding: 0 6px; border-radius: var(--r-tag); box-shadow: inset 0 0 0 1px var(--on-band-sub); font-size: 12px; font-weight: var(--fw-bold); color: var(--on-band); }
|
||||
.mk:hover .flag { box-shadow: 0 0 0 1px var(--c-paper); }
|
||||
.mk.flip .hang { justify-content: flex-end; }
|
||||
.stem { position: absolute; width: 1px; background: var(--band-axis); }
|
||||
.stem.soon { width: 2px; background: var(--c-ochre); }
|
||||
.pin { position: absolute; width: 9px; height: 9px; margin: -4px 0 0 -4px; border-radius: 50%; background: var(--c-paper); }
|
||||
.pin.soon { background: var(--c-ochre); box-shadow: 0 0 0 2px var(--band); }
|
||||
|
||||
.band-sum { display: flex; gap: var(--sp-8); padding: var(--sp-4) 0 var(--sp-5); margin-top: var(--sp-2); border-top: 1px solid var(--band-tick); }
|
||||
.band-sum > div { display: flex; align-items: center; gap: var(--sp-3); white-space: nowrap; }
|
||||
.band-sum .fig { font-size: 30px; line-height: 36px; color: var(--c-white); }
|
||||
.band-sum span { display: block; color: var(--on-band); line-height: 20px; }
|
||||
.band-sum small { display: block; font-size: 12px; line-height: 18px; color: var(--on-band-sub); }
|
||||
.band-sum .key { margin-left: auto; align-self: center; font-size: 12px; color: var(--on-band-sub); white-space: nowrap; display: flex; align-items: center; gap: var(--sp-2); }
|
||||
.band-sum .key i { width: 16px; height: 3px; border-radius: 2px; background: var(--c-ochre); display: inline-block; }
|
||||
|
||||
/* ---------- 下半部分 ---------- */
|
||||
.wb { display: grid; grid-template-columns: minmax(0, 1fr) var(--rail-w); gap: var(--sp-8); align-items: start; margin-top: var(--sp-8); }
|
||||
.toolbar { display: flex; align-items: center; gap: var(--sp-4); margin-bottom: var(--sp-3); }
|
||||
.toolbar h2 { font: var(--fw-bold) 20px/28px var(--font-serif); margin-right: var(--sp-2); }
|
||||
.seg { display: flex; gap: 2px; }
|
||||
.seg button { height: 32px; padding: 0 var(--sp-3); border: 0; border-radius: var(--r-ctl); background: transparent; color: var(--text-secondary); cursor: pointer; display: inline-flex; align-items: center; gap: 6px; }
|
||||
.seg button:hover { background: var(--bg-hover); color: var(--text-primary); }
|
||||
.seg button[aria-pressed="true"] { background: var(--c-ink); color: var(--c-white); font-weight: var(--fw-bold); }
|
||||
.seg .fig { font-weight: var(--fw-bold); }
|
||||
.search { margin-left: auto; position: relative; width: 248px; }
|
||||
.search .ic { position: absolute; left: 12px; top: 10px; color: var(--text-secondary); pointer-events: none; }
|
||||
.search input { width: 100%; height: var(--ctl-h); padding: 0 12px 0 36px; border: 1px solid var(--border-control); border-radius: var(--r-ctl); background: var(--bg-surface); }
|
||||
.search input::placeholder { color: var(--text-secondary); }
|
||||
.search input:focus-visible { outline-offset: 1px; }
|
||||
|
||||
.list { container: list / inline-size; background: var(--bg-surface); border-radius: var(--r-panel); padding: var(--sp-2) 0; }
|
||||
.row { display: grid; grid-template-columns: 84px minmax(0, 1fr) 96px 80px; gap: var(--sp-4); align-items: center; padding: var(--sp-4) var(--sp-6) var(--sp-4) var(--sp-5); text-decoration: none; color: inherit; position: relative; cursor: pointer; }
|
||||
.row + .row::before { content: ""; position: absolute; top: 0; left: calc(var(--sp-5) + 84px + var(--sp-4)); right: var(--sp-6); height: 1px; background: var(--row-rule); }
|
||||
.row:hover { background: var(--bg-hover); }
|
||||
.row:hover::before, .row:hover + .row::before { opacity: 0; }
|
||||
.row:focus-visible { outline-offset: -2px; border-radius: var(--r-ctl); }
|
||||
.date { text-align: center; line-height: 1; }
|
||||
.date .m { display: block; font-size: 12px; line-height: 16px; color: var(--text-secondary); }
|
||||
.date .d { display: block; font-size: 28px; line-height: 32px; margin: 1px 0 2px; }
|
||||
.date .cd { display: block; font-size: 12px; line-height: 16px; color: var(--text-secondary); white-space: nowrap; }
|
||||
.row.today .date .d { color: var(--c-ochre); }
|
||||
.row.today .date .cd, .row.soon .date .cd { color: var(--c-ochre-ink); }
|
||||
.row.today .date .cd { font-weight: var(--fw-bold); }
|
||||
.row.past .date .d { color: var(--text-secondary); }
|
||||
.row .name { display: block; font: var(--fw-bold) 17px/26px var(--font-serif); color: var(--text-primary); text-wrap: pretty; }
|
||||
.row .sub { font-size: 13px; line-height: 20px; color: var(--text-secondary); margin-top: 2px; }
|
||||
.row .sub .at { display: none; }
|
||||
@container list (min-width: 720px) { .row .sub .at { display: inline; } }
|
||||
.row .when { font-family: var(--font-figures); font-variant-numeric: tabular-nums; font-weight: var(--fw-bold); }
|
||||
.stage { font-size: 13px; line-height: 20px; }
|
||||
.pips { display: flex; gap: 3px; margin-top: 6px; }
|
||||
.pips i { width: 14px; height: 3px; border-radius: 2px; background: var(--c-rule); }
|
||||
.pips i.on { background: var(--c-ink); }
|
||||
.ready { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; font-size: 13px; line-height: 20px; }
|
||||
.ready .ok { display: inline-flex; align-items: center; gap: 6px; font-weight: var(--fw-bold); }
|
||||
.ready .ok::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--c-ink); }
|
||||
.ready .no { color: var(--text-secondary); }
|
||||
.empty { padding: var(--sp-10) var(--sp-6); text-align: center; }
|
||||
.empty b { display: block; font-size: var(--fs-title); }
|
||||
.empty p { color: var(--text-secondary); margin-top: var(--sp-1); }
|
||||
.note { margin-top: var(--sp-3); font-size: 12px; line-height: 20px; color: var(--text-secondary); padding-left: var(--sp-5); }
|
||||
|
||||
/* 右侧栏:白色面板,只靠间距分隔 */
|
||||
.rail { display: flex; flex-direction: column; gap: var(--sp-4); }
|
||||
.panel { background: var(--bg-surface); border-radius: var(--r-panel); padding: var(--sp-5); }
|
||||
.panel-h { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-4); }
|
||||
.panel-h h3 { font-size: var(--fs-title); line-height: var(--lh-title); font-weight: var(--fw-bold); }
|
||||
.panel-h .more { margin-left: auto; font-size: 12px; line-height: 20px; color: var(--text-secondary); }
|
||||
.lnk { color: var(--text-primary); text-decoration: underline; text-decoration-color: var(--c-muted); text-underline-offset: 3px; font-size: 12px; }
|
||||
.lnk:hover { text-decoration-color: var(--c-ink); }
|
||||
.cite { display: inline-flex; align-items: center; gap: 4px; color: var(--ai); text-decoration: none; font-size: 12px; line-height: 20px; white-space: nowrap; }
|
||||
.cite:hover { color: var(--ai-strong); text-decoration: underline; text-underline-offset: 3px; }
|
||||
.cite .fig { font-size: 13px; }
|
||||
|
||||
.todo li { position: relative; padding: var(--sp-2) 0 var(--sp-2) var(--sp-4); }
|
||||
.todo li + li { margin-top: var(--sp-3); }
|
||||
.todo li::before { content: ""; position: absolute; left: 0; top: var(--sp-2); bottom: var(--sp-2); width: 3px; border-radius: 2px; background: var(--sev-fatal); }
|
||||
.todo b { display: block; font-weight: var(--fw-bold); }
|
||||
.todo p { font-size: 12px; line-height: 20px; color: var(--text-secondary); margin-top: 2px; }
|
||||
.todo .cite { margin-top: 2px; }
|
||||
|
||||
.quota { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-5); }
|
||||
.quota small { display: block; font-size: 12px; line-height: 20px; color: var(--text-secondary); }
|
||||
.quota .big { font-size: 30px; line-height: 36px; }
|
||||
.quota .unit { font-family: var(--font-figures); font-variant-numeric: tabular-nums; color: var(--text-secondary); margin-left: 4px; }
|
||||
.bar { height: 4px; border-radius: 2px; background: var(--c-sunken); margin-top: var(--sp-2); overflow: hidden; }
|
||||
.bar i { display: block; height: 100%; background: var(--c-ink); border-radius: 2px; }
|
||||
.panel-f { display: flex; gap: var(--sp-4); margin-top: var(--sp-4); }
|
||||
|
||||
.task-h { display: flex; align-items: baseline; gap: var(--sp-2); }
|
||||
.task-h b { font-weight: var(--fw-bold); }
|
||||
.task-h span { margin-left: auto; font-size: 12px; color: var(--text-secondary); }
|
||||
.task .cap { font-size: 12px; line-height: 20px; color: var(--text-secondary); }
|
||||
.prog { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-2); }
|
||||
.prog .bar { flex: 1; margin: 0; }
|
||||
.prog .fig { font-size: 14px; }
|
||||
.queued { display: flex; gap: var(--sp-2); margin-top: var(--sp-4); font-size: 12px; line-height: 20px; color: var(--text-secondary); }
|
||||
.queued .ic { margin-top: 2px; }
|
||||
.task .leave { margin-top: var(--sp-3); font-size: 12px; line-height: 20px; color: var(--text-secondary); }
|
||||
|
||||
.log li { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-3); padding: 6px 0; font-size: 13px; line-height: 20px; }
|
||||
.log .who { font-weight: var(--fw-bold); }
|
||||
.log .ok { color: var(--confirmed); white-space: nowrap; }
|
||||
.log time { font-family: var(--font-figures); font-variant-numeric: tabular-nums; color: var(--text-secondary); font-size: 13px; }
|
||||
|
||||
/* 轻提示 */
|
||||
.toasts { position: fixed; left: 50%; bottom: var(--sp-8); transform: translateX(-50%); display: flex; flex-direction: column; gap: var(--sp-2); z-index: 10; }
|
||||
.toast { background: var(--c-ink); color: var(--c-white); padding: var(--sp-3) var(--sp-4); border-radius: var(--r-ctl); box-shadow: var(--shadow-overlay); }
|
||||
|
||||
/* 唯一的动效:时间带标记依次落位 */
|
||||
@keyframes drop { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: none; } }
|
||||
@keyframes grow { from { transform: scaleY(0); } to { transform: none; } }
|
||||
.tl.animate .mk { animation: drop 420ms var(--ease) both; animation-delay: calc(var(--i) * 110ms + 120ms); }
|
||||
.tl.animate .stem { transform-origin: bottom; animation: grow 320ms var(--ease) both; animation-delay: calc(var(--i) * 110ms); }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.tl.animate .mk, .tl.animate .stem { animation: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
|
||||
<symbol id="i-grid" viewBox="0 0 24 24"><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"/></symbol>
|
||||
<symbol id="i-cert" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 9h10M7 13h6"/><circle cx="16" cy="15" r="2"/></symbol>
|
||||
<symbol id="i-gauge" viewBox="0 0 24 24"><path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/></symbol>
|
||||
<symbol id="i-bell" viewBox="0 0 24 24"><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"/></symbol>
|
||||
<symbol id="i-plus" viewBox="0 0 24 24"><path d="M5 12h14"/><path d="M12 5v14"/></symbol>
|
||||
<symbol id="i-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></symbol>
|
||||
<symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></symbol>
|
||||
<symbol id="i-file" viewBox="0 0 24 24"><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"/></symbol>
|
||||
</svg>
|
||||
|
||||
<aside class="sidebar">
|
||||
<a class="brand" href="#"><img src="../../logo/svg/logo-horizontal.svg" alt="标博士"></a>
|
||||
<nav class="nav" aria-label="主导航">
|
||||
<a href="#" aria-current="page"><svg class="ic lg"><use href="#i-grid"/></svg>工作台</a>
|
||||
<a href="#" data-demo><svg class="ic lg"><use href="#i-cert"/></svg>资质库</a>
|
||||
<a href="#" data-demo><svg class="ic lg"><use href="#i-gauge"/></svg>额度</a>
|
||||
</nav>
|
||||
<div class="sidebar-foot"><b>王敏</b>南京某信息技术有限公司<br>专业版,至 2027-10-31</div>
|
||||
</aside>
|
||||
|
||||
<main>
|
||||
<header class="page-h">
|
||||
<div>
|
||||
<h1>工作台</h1>
|
||||
<p>11月3日 周二。按投标截止时间排序,截止临近的排在前面。</p>
|
||||
</div>
|
||||
<div class="acts">
|
||||
<span class="cost">扣 1 次解析,剩 <span class="fig">37</span> 次</span>
|
||||
<button class="btn primary" data-new><svg class="ic"><use href="#i-plus"/></svg>新建项目</button>
|
||||
<button class="icon-btn" aria-label="通知" title="通知" data-notice><svg class="ic lg"><use href="#i-bell"/></svg><span class="dot"></span></button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="band" aria-labelledby="band-t">
|
||||
<h2 class="sr" id="band-t">未来 30 天的投标截止</h2>
|
||||
<div class="band-h">
|
||||
<svg class="ic clock" aria-hidden="true"><use href="#i-clock"/></svg>
|
||||
<div class="msg"><b>某县数据中心机房改造项目今天 17:00 截止。</b><span>清单还是草稿,还没有做过检查。</span></div>
|
||||
<a class="btn on-band" href="../page-reading.html">去确认清单</a>
|
||||
</div>
|
||||
|
||||
<div class="tl" data-tl aria-label="截止时间轴" role="group"></div>
|
||||
|
||||
<div class="band-sum">
|
||||
<div><span class="fig">4</span><p><span>进行中的项目</span><small>已递交 1,已归档 1</small></p></div>
|
||||
<div><span class="fig">2</span><p><span>7 天内截止</span><small>最近:今天 17:00</small></p></div>
|
||||
<div><span class="fig">3</span><p><span>待处理的致命问题</span><small>在 1 个项目里</small></p></div>
|
||||
<div><span class="fig">4</span><p><span>待确认的清单条目</span><small>在 2 个项目里</small></p></div>
|
||||
<span class="key"><i></i>72 小时内截止</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="wb">
|
||||
<section aria-labelledby="list-t">
|
||||
<div class="toolbar">
|
||||
<h2 id="list-t">项目</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>
|
||||
<label class="search"><svg class="ic" aria-hidden="true"><use href="#i-search"/></svg><input type="search" placeholder="搜索项目、采购人、编号" aria-label="搜索项目" data-q></label>
|
||||
</div>
|
||||
<div class="list" data-rows></div>
|
||||
<p class="note" id="ready-note">「就绪」只表示致命问题都已处理,不代表投标文件没有问题。</p>
|
||||
</section>
|
||||
|
||||
<aside class="rail" aria-label="待办与额度">
|
||||
<section class="panel" aria-labelledby="todo-t">
|
||||
<div class="panel-h"><h3 id="todo-t">待处理的致命问题</h3><span class="sev fatal">致命 <span class="fig">3</span></span><span class="more"><a class="lnk" href="#" data-demo>去查标页处理</a></span></div>
|
||||
<ul class="todo">
|
||||
<li><b>报价超过采购包 1 的最高限价</b><p>智慧大厅建设项目:开标一览表 287.60 万元,限价 286.00 万元</p><a class="cite" href="#" data-demo><svg class="ic"><use href="#i-file"/></svg>报价册 · 第 <span class="fig">1</span> 页</a></li>
|
||||
<li><b>★ 条款疑似不满足:应用服务器内存</b><p>智慧大厅建设项目:响应 256G,要求 ≥ 512G。上次标记为已修改,问题仍在</p><a class="cite" href="#" data-demo><svg class="ic"><use href="#i-file"/></svg>技术册 · 第 <span class="fig">12</span> 页</a></li>
|
||||
<li><b>授权委托书的单位签章位置未发现盖章</b><p>智慧大厅建设项目:只看有无,不鉴定真伪</p><a class="cite" href="#" data-demo><svg class="ic"><use href="#i-file"/></svg>商务册 · 第 <span class="fig">5</span> 页</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="panel" aria-labelledby="quota-t">
|
||||
<div class="panel-h"><h3 id="quota-t">本期额度</h3><span class="more">至 2027-10-31</span></div>
|
||||
<div class="quota">
|
||||
<div><small>解析剩余</small><span class="fig big">37</span><span class="unit">/ 50</span><div class="bar" role="img" aria-label="已用 13 次,共 50 次"><i style="width:26%"></i></div></div>
|
||||
<div><small>检查剩余</small><span class="fig big">12</span><span class="unit">/ 20</span><div class="bar" role="img" aria-label="已用 8 次,共 20 次"><i style="width:40%"></i></div></div>
|
||||
</div>
|
||||
<div class="panel-f"><a class="lnk" href="#" data-demo>扣点规则</a><a class="lnk" href="#" data-demo>查看流水</a></div>
|
||||
</section>
|
||||
|
||||
<section class="panel task" aria-labelledby="task-t">
|
||||
<div class="panel-h"><h3 id="task-t">正在进行的任务</h3></div>
|
||||
<div class="task-h"><b>解析补遗 2</b><span>提取清单</span></div>
|
||||
<div class="cap">某市医保信息系统等保测评服务</div>
|
||||
<div class="prog"><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="queued"><svg class="ic" aria-hidden="true"><use href="#i-clock"/></svg><span>检查排队中:智慧社区平台运维,前面还有 1 个任务</span></div>
|
||||
<p class="leave">可以离开本页,完成后会发站内通知。</p>
|
||||
</section>
|
||||
|
||||
<section class="panel" aria-labelledby="log-t">
|
||||
<div class="panel-h"><h3 id="log-t">最近处理</h3><span class="more"><a class="lnk" href="#" data-demo>全部记录</a></span></div>
|
||||
<ul class="log">
|
||||
<li><span><span class="who">王敏</span> <span class="ok">✓ 已修改</span>「授权委托书签章」</span><time>14:26</time></li>
|
||||
<li><span><span class="who">王敏</span> 确认了清单 v2:智慧大厅</span><time>11-02</time></li>
|
||||
<li><span><span class="who">系统</span> 完成第 2 次检查,致命 3</span><time>11-02</time></li>
|
||||
<li><span><span class="who">系统</span> 并入补遗 1,更新 2 条</span><time>11-02</time></li>
|
||||
<li><span><span class="who">李工</span> 忽略「其他项目名称」:业绩介绍</span><time>11-01</time></li>
|
||||
</ul>
|
||||
</section>
|
||||
</aside>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const $ = (s, el = document) => el.querySelector(s);
|
||||
const $$ = (s, el = document) => [...el.querySelectorAll(s)];
|
||||
const STAGES = ["跟进中", "编制中", "已检查", "已递交", "已归档"];
|
||||
/* 演示数据:现在 = 11月3日 11:00;h = 距投标截止的小时数 */
|
||||
const P = [
|
||||
{ id: "p1", name: "某县数据中心机房改造项目", short: "数据中心机房改造", buyer: "某县大数据管理局", lot: "标段 1", h: 6, m: 11, d: 3, cd: "今天 17:00 截止", st: "编制中", ready: "", fatal: 0, href: "../page-reading.html" },
|
||||
{ id: "p2", name: "某区智慧社区平台运维服务", short: "智慧社区平台运维", buyer: "某区民政局", lot: "全部", h: 53, m: 11, d: 5, cd: "剩 2 天 5 小时", st: "已检查", ready: "就绪", fatal: 0 },
|
||||
{ id: "p3", name: "某市政务服务中心智慧大厅建设项目", short: "智慧大厅建设", buyer: "某市政务服务管理办公室", lot: "采购包 1", h: 408, m: 11, d: 20, cd: "09:30 · 剩 17 天", st: "已检查", ready: "未就绪", fatal: 3, href: "../page-reading.html" },
|
||||
{ id: "p4", name: "某市医保信息系统等保测评服务", short: "医保等保测评", buyer: "某市医疗保障局", lot: "全部", h: 600, m: 11, d: 28, cd: "10:00 · 剩 25 天", st: "跟进中", ready: "", fatal: 0 },
|
||||
{ id: "p5", name: "某区智慧校园一期", short: "智慧校园一期", buyer: "某区教育局", lot: "全部", h: -1, m: 10, d: 30, cd: "已截止", st: "已递交", ready: "", fatal: 0 },
|
||||
{ id: "p6", name: "某市不动产登记信息平台升级", short: "不动产登记平台", buyer: "某市自然资源和规划局", lot: "采购包 2", h: -2, m: 10, d: 21, cd: "已截止", st: "已归档", ready: "", fatal: 0 }
|
||||
];
|
||||
const SPAN = 30 * 24; // 时间轴:未来 30 天
|
||||
const TO_MIDNIGHT = 13; // 现在到 11月4日 0:00 的小时数
|
||||
|
||||
/* ---------- 时间轴 ---------- */
|
||||
const tl = $("[data-tl]");
|
||||
function drawTimeline(animate) {
|
||||
const W = tl.clientWidth, AXIS = tl.clientHeight - 36;
|
||||
const x = (h) => Math.round(h / SPAN * W);
|
||||
let html = `<div class="tl-today" style="left:0;width:${x(TO_MIDNIGHT)}px"></div><div class="tl-axis"></div><div class="tl-72" style="width:${x(72)}px"></div>`;
|
||||
for (let k = 0; TO_MIDNIGHT + 24 * k < SPAN; k++) {
|
||||
const hx = x(TO_MIDNIGHT + 24 * k), wk = k % 7 === 6;
|
||||
html += `<span class="tl-tick ${wk ? "wk" : ""}" style="left:${hx}px"></span>`;
|
||||
if (wk) { const dt = new Date(2026, 10, 4 + k); html += `<span class="tl-lab" style="left:${hx}px">${dt.getMonth() + 1}月${dt.getDate()}日</span>`; }
|
||||
}
|
||||
html += `<span class="tl-lab today" style="left:0">今天</span>`;
|
||||
tl.innerHTML = html;
|
||||
|
||||
// 标记:先放进去量宽度,再分泳道,避免文字互相压住
|
||||
const live = P.filter((p) => p.h > 0 && p.h <= SPAN).sort((a, b) => a.h - b.h);
|
||||
const lanes = [0, 76]; // 两条泳道的顶部位置
|
||||
const laneEnd = [-Infinity, -Infinity];
|
||||
live.forEach((p, i) => {
|
||||
const soon = p.h <= 72, px = x(p.h);
|
||||
const a = document.createElement("a");
|
||||
a.className = `mk ${soon ? "soon" : ""}`;
|
||||
a.href = `#${p.id}`;
|
||||
a.style.setProperty("--i", i);
|
||||
const due = soon ? p.cd : `${p.m}月${p.d}日 ${p.cd.split(" · ")[0]}`;
|
||||
a.setAttribute("aria-label", `${p.name},${p.h <= 72 ? p.cd : `${p.m}月${p.d}日 ${p.cd.replace(" · ", ",")}`}${p.fatal ? `,致命问题 ${p.fatal} 项待处理` : ""}${p.ready === "就绪" ? ",就绪" : ""}`);
|
||||
const hang = [p.fatal ? `<span class="sev fatal">致命 <span class="fig">${p.fatal}</span></span>` : "", p.ready === "就绪" ? `<span class="ready">就绪</span>` : ""].join("");
|
||||
a.innerHTML = `<span class="flag"><b>${p.short}</b><small>${due}</small></span>${hang ? `<span class="hang">${hang}</span>` : ""}`;
|
||||
tl.appendChild(a);
|
||||
const w = a.offsetWidth;
|
||||
const flip = px + w > W;
|
||||
const left = flip ? px - w : px, right = flip ? px : px + w;
|
||||
let lane = laneEnd.findIndex((e) => e + 12 < left);
|
||||
if (lane < 0) lane = 1;
|
||||
laneEnd[lane] = right;
|
||||
if (flip) a.classList.add("flip");
|
||||
a.style.left = `${left}px`;
|
||||
a.style.top = `${lanes[lane]}px`;
|
||||
const stemTop = lanes[lane] + (a.querySelector(".flag").offsetHeight);
|
||||
const stem = document.createElement("span");
|
||||
stem.className = `stem ${soon ? "soon" : ""}`;
|
||||
stem.style.cssText = `left:${px - (soon ? 1 : 0)}px;top:${stemTop}px;height:${AXIS - stemTop}px;--i:${i}`;
|
||||
const pin = document.createElement("span");
|
||||
pin.className = `pin ${soon ? "soon" : ""}`;
|
||||
pin.style.cssText = `left:${px}px;top:${AXIS}px`;
|
||||
tl.insertBefore(stem, a); tl.appendChild(pin);
|
||||
});
|
||||
tl.classList.toggle("animate", !!animate);
|
||||
}
|
||||
|
||||
/* ---------- 项目列表 ---------- */
|
||||
let seg = "active", q = "";
|
||||
function render() {
|
||||
const rows = P.filter((p) => (seg === "all" || (seg === "active" ? !["已递交", "已归档"].includes(p.st) : p.st === seg)) && (!q || (p.name + p.buyer).includes(q)));
|
||||
$("[data-rows]").innerHTML = rows.map((p) => {
|
||||
const cls = p.h < 0 ? "past" : p.h <= 24 ? "today" : p.h <= 72 ? "soon" : "";
|
||||
const si = STAGES.indexOf(p.st);
|
||||
const ready = p.fatal
|
||||
? `<span class="sev fatal">致命 <span class="fig">${p.fatal}</span></span><span class="no">未就绪</span>`
|
||||
: p.ready === "就绪" ? `<span class="ok" title="只表示致命问题都已处理,不代表投标文件没有问题。" aria-describedby="ready-note">就绪</span>` : "";
|
||||
const cd = p.h > 72 ? `<span class="when">${p.cd.split(" · ")[1]}</span>` : p.cd;
|
||||
return `<a class="row ${cls}" id="${p.id}" href="${p.href || "#"}" ${p.href ? "" : "data-demo"}>
|
||||
<span class="date" title="${p.h > 0 ? `投标截止 ${p.m}月${p.d}日${p.h > 72 ? " " + p.cd.split(" · ")[0] : ""}` : "已截止"}"><span class="m">${p.m}月</span><span class="d fig">${p.d}</span><span class="cd">${cd}</span></span>
|
||||
<span><span class="name">${p.name}</span><span class="sub" style="display:block">${p.buyer},${p.lot}${p.h > 72 ? `<span class="at">,${p.m}月${p.d}日 ${p.cd.split(" · ")[0]} 截止</span>` : ""}</span></span>
|
||||
<span class="stage">${p.st}<span class="pips" aria-hidden="true">${STAGES.map((_, i) => `<i class="${i <= si ? "on" : ""}"></i>`).join("")}</span></span>
|
||||
<span class="ready">${ready}</span></a>`;
|
||||
}).join("") || `<div class="empty"><b>没有符合条件的项目</b><p>换个关键词,或切到「全部」看看。</p></div>`;
|
||||
}
|
||||
|
||||
/* ---------- 轻提示 ---------- */
|
||||
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);
|
||||
}
|
||||
|
||||
$("[data-seg]").addEventListener("click", (e) => {
|
||||
const b = e.target.closest("button"); if (!b) return;
|
||||
$$("button", e.currentTarget).forEach((x) => x.setAttribute("aria-pressed", x === b));
|
||||
seg = b.dataset.v; render();
|
||||
});
|
||||
$("[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("(演示)只做了「读标」组合页"); return; }
|
||||
const mk = e.target.closest(".mk");
|
||||
if (mk) {
|
||||
e.preventDefault();
|
||||
const id = mk.getAttribute("href").slice(1);
|
||||
if (!$("#" + id)) { seg = "all"; $$("[data-seg] button").forEach((x) => x.setAttribute("aria-pressed", x.dataset.v === "all")); q = ""; $("[data-q]").value = ""; render(); }
|
||||
const r = $("#" + id); r.scrollIntoView({ behavior: matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth", block: "center" }); r.focus({ preventScroll: true });
|
||||
}
|
||||
});
|
||||
$("[data-new]").onclick = () => toast("(演示)新建项目");
|
||||
$("[data-notice]").onclick = () => toast("解析完成:补遗 1 已并入清单(新增 2 条,更新 1 条)。");
|
||||
|
||||
render();
|
||||
drawTimeline(true);
|
||||
let rt; addEventListener("resize", () => { clearTimeout(rt); rt = setTimeout(() => drawTimeline(false), 80); });
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
545
docs/design/ui-demo/explore/workbench-d.html
Normal file
545
docs/design/ui-demo/explore/workbench-d.html
Normal file
@@ -0,0 +1,545 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
工作台 · 风格方案 D「明亮工作区」(探索稿,不是定稿)
|
||||
数据、文案、交互沿用 ../page-workbench.html 与 ../demo.js。
|
||||
|
||||
本方案相对 design-system.md 改了哪些规则:
|
||||
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>工作台 · 方案 D 明亮工作区</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;
|
||||
--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(--sidebar-w) minmax(0,1fr);min-height:100vh}
|
||||
.side{background:var(--c-white);border-right:1px solid var(--n-line)}
|
||||
.side-in{position:sticky;top:0;height:100vh;display:flex;flex-direction:column;padding:0 12px 16px}
|
||||
.brand{display:flex;align-items:center;height:var(--top-h);padding:0 8px;margin-bottom:12px}
|
||||
.brand img{height:26px;display:block}
|
||||
.nav{display:flex;flex-direction:column;gap:2px}
|
||||
.nav a{display:flex;align-items:center;gap:10px;height:var(--btn-h);padding:0 10px;border-radius:var(--r-ctl);text-decoration:none;color:var(--text-secondary);transition:background-color var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)}
|
||||
.nav a:hover{background:var(--n-50);color:var(--text-primary)}
|
||||
.nav a[aria-current]{background:var(--n-100);color:var(--text-primary);font-weight:var(--fw-bold)}
|
||||
.nav .grp{margin:20px 10px 6px;font-size:var(--fs-caption);color:var(--text-secondary)}
|
||||
.side-foot{margin-top:auto;padding:12px 10px 0;border-top:1px solid var(--n-line);font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)}
|
||||
.side-foot b{display:block;color:var(--text-primary);font-size:var(--fs-body);line-height:var(--lh-body);font-weight:var(--fw-bold)}
|
||||
|
||||
.top{position:sticky;top:0;z-index:5;display:grid;grid-template-columns:1fr minmax(0,400px) 1fr;align-items:center;gap:16px;height:var(--top-h);padding:0 var(--pad);background:var(--c-white);border-bottom:1px solid var(--n-line)}
|
||||
.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:28px var(--pad) 48px}
|
||||
|
||||
/* ---------- 页头 ---------- */
|
||||
.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)}
|
||||
.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 > .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}
|
||||
.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,.row:focus-within .nx{background:var(--c-white);border-color:var(--ctl-border)}
|
||||
.row:hover .nx span,.row:focus-within .nx span{text-decoration-color:transparent}
|
||||
.row .nx:hover{border-color:var(--c-ink)}
|
||||
.row .nx:hover .ic{transform:translateX(2px);color:var(--text-primary)}
|
||||
.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{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)}
|
||||
.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-d.html"><img src="../../logo/svg/logo-horizontal.svg" alt="标博士"></a>
|
||||
<nav class="nav" aria-label="主导航">
|
||||
<a href="workbench-d.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>专业版 · 至 2027-10-31</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>
|
||||
<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><p>已递交 1 · 已归档 1</p><i data-ic="filter"></i></button>
|
||||
<button class="stat" data-f="due7" aria-pressed="false"><small>7 天内截止</small><span class="big">2</span><p class="due">最近:今天 17:00</p><i data-ic="filter"></i></button>
|
||||
<button class="stat" data-f="fatal" aria-pressed="false"><small>待处理的致命问题</small><span class="big fatal">3</span><p>在 1 个项目里</p><i data-ic="filter"></i></button>
|
||||
<button class="stat" data-f="pend" aria-pressed="false"><small>待确认的清单条目</small><span class="big">4</span><p>在 2 个项目里</p><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>
|
||||
<p class="cf cap sec">「就绪」只表示致命问题都已处理,不代表投标文件没有问题。</p>
|
||||
</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>
|
||||
<p class="cap sec" style="margin-top:8px">可以离开本页,完成后会发站内通知。</p>
|
||||
</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"/>'
|
||||
};
|
||||
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);
|
||||
}
|
||||
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><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 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>
|
||||
599
docs/design/ui-demo/explore/workbench-d2.html
Normal file
599
docs/design/ui-demo/explore/workbench-d2.html
Normal file
@@ -0,0 +1,599 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
工作台 · 风格方案 D「明亮工作区」(探索稿,不是定稿)
|
||||
数据、文案、交互沿用 ../page-workbench.html 与 ../demo.js。
|
||||
|
||||
本方案相对 design-system.md 改了哪些规则:
|
||||
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>工作台 · 方案 D 明亮工作区</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 8%, var(--c-ink));
|
||||
--side-active: color-mix(in srgb, white 14%, var(--c-ink));
|
||||
--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}
|
||||
/* 浅灰侧栏对照(?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:28px;width:28px}
|
||||
.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;height:var(--top-h);padding:0 8px;margin-bottom:12px;flex:none}
|
||||
.brand:focus-visible{outline:2px solid var(--side-fg-strong);outline-offset:-2px;border-radius:var(--r-ctl)}
|
||||
.brand img{height:26px;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)}
|
||||
.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:grid;grid-template-columns:1fr minmax(0,400px) 1fr;align-items:center;gap:16px;height:var(--top-h);padding:0 var(--pad);background:var(--c-white);border-bottom:1px solid var(--n-line)}
|
||||
.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:28px var(--pad) 48px}
|
||||
|
||||
/* ---------- 页头 ---------- */
|
||||
.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)}
|
||||
.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 > .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}
|
||||
.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,.row:focus-within .nx{background:var(--c-white);border-color:var(--ctl-border)}
|
||||
.row:hover .nx span,.row:focus-within .nx span{text-decoration-color:transparent}
|
||||
.row .nx:hover{border-color:var(--c-ink)}
|
||||
.row .nx:hover .ic{transform:translateX(2px);color:var(--text-primary)}
|
||||
.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{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)}
|
||||
.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-d2.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-d2.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>
|
||||
<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><p>已递交 1 · 已归档 1</p><i data-ic="filter"></i></button>
|
||||
<button class="stat" data-f="due7" aria-pressed="false"><small>7 天内截止</small><span class="big">2</span><p class="due">最近:今天 17:00</p><i data-ic="filter"></i></button>
|
||||
<button class="stat" data-f="fatal" aria-pressed="false"><small>待处理的致命问题</small><span class="big fatal">3</span><p>在 1 个项目里</p><i data-ic="filter"></i></button>
|
||||
<button class="stat" data-f="pend" aria-pressed="false"><small>待确认的清单条目</small><span class="big">4</span><p>在 2 个项目里</p><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>
|
||||
<p class="cf cap sec">「就绪」只表示致命问题都已处理,不代表投标文件没有问题。</p>
|
||||
</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>
|
||||
<p class="cap sec" style="margin-top:8px">可以离开本页,完成后会发站内通知。</p>
|
||||
</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"/>',
|
||||
"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); });
|
||||
|
||||
/* 侧栏:主题(?side=light 为浅灰对照)与折叠(记在本地,读写失败不影响页面) */
|
||||
const app = $(".app");
|
||||
if (new URLSearchParams(location.search).get("side") === "light") app.dataset.side = "light";
|
||||
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><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 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>
|
||||
647
docs/design/ui-demo/explore/workbench-d3.html
Normal file
647
docs/design/ui-demo/explore/workbench-d3.html
Normal file
@@ -0,0 +1,647 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
工作台 · 风格方案 D「明亮工作区」(探索稿,不是定稿)
|
||||
数据、文案、交互沿用 ../page-workbench.html 与 ../demo.js。
|
||||
|
||||
本方案相对 design-system.md 改了哪些规则:
|
||||
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>工作台 · 方案 D3</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 8%, var(--c-ink));
|
||||
--side-active: color-mix(in srgb, white 14%, var(--c-ink));
|
||||
--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}
|
||||
/* 浅灰侧栏对照(?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:28px;width:28px}
|
||||
.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;height:var(--top-h);padding:0 8px;margin-bottom:12px;flex:none}
|
||||
.brand:focus-visible{outline:2px solid var(--side-fg-strong);outline-offset:-2px;border-radius:var(--r-ctl)}
|
||||
.brand img{height:26px;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)}
|
||||
.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}
|
||||
.tile{flex:none;display:inline-grid;place-items:center;width:36px;height:36px;border-radius:10px;background:var(--n-100);color:var(--text-primary)}
|
||||
.tile.sm{width:28px;height:28px;border-radius:8px}
|
||||
.tile.warn{background:var(--warn-soft);color:var(--c-ochre)}
|
||||
.tile.fatal{background:var(--sev-fatal-soft);color:var(--sev-fatal)}
|
||||
.tile.ai{background:var(--ai-soft);color:var(--ai)}
|
||||
.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-columns:auto minmax(0,1fr);grid-template-rows:auto auto auto;column-gap:16px;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 > .tile{grid-row:1 / span 3;align-self:start;margin-top:2px}
|
||||
.stat > small,.stat > .big,.stat > p{grid-column:2}
|
||||
.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 > .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}
|
||||
.row.arch .tile{color:var(--text-secondary)}
|
||||
.nx .ic.lead{color:var(--text-secondary)}
|
||||
.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,.row:focus-within .nx{background:var(--c-white);border-color:var(--ctl-border)}
|
||||
.row:hover .nx span,.row:focus-within .nx span{text-decoration-color:transparent}
|
||||
.row .nx:hover{border-color:var(--c-ink)}
|
||||
.row .nx:hover .ic{transform:translateX(2px);color:var(--text-primary)}
|
||||
.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:center;gap:10px}
|
||||
.task-h + .cap{padding-left:38px}
|
||||
.rbody .pbar{padding-left:38px}
|
||||
.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:auto minmax(0,1fr) auto;align-items:start;gap:10px;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-d3.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-d3.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"><i data-ic="building"></i><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 次"><i data-ic="gauge"></i>解析<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日 周二,有 <b class="warn-t">1 个项目今天截止</b>。</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">
|
||||
<span class="tile warn"><i data-ic="clock" class="lg"></i></span>
|
||||
<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"><span class="tile"><i data-ic="folders" class="lg"></i></span><small>进行中的项目</small><span class="big">4</span><p>已递交 1 · 已归档 1</p><i data-ic="filter"></i></button>
|
||||
<button class="stat" data-f="due7" aria-pressed="false"><span class="tile warn"><i data-ic="calendar-clock" class="lg"></i></span><small>7 天内截止</small><span class="big">2</span><p class="due">最近:今天 17:00</p><i data-ic="filter"></i></button>
|
||||
<button class="stat" data-f="fatal" aria-pressed="false"><span class="tile fatal"><i data-ic="octagon-alert" class="lg"></i></span><small>待处理的致命问题</small><span class="big fatal">3</span><p>在 1 个项目里</p><i data-ic="filter"></i></button>
|
||||
<button class="stat" data-f="pend" aria-pressed="false"><span class="tile ai"><i data-ic="list" class="lg"></i></span><small>待确认的清单条目</small><span class="big">4</span><p>在 2 个项目里</p><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"><i data-ic="folders"></i>项目</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>
|
||||
<p class="cf cap sec">「就绪」只表示致命问题都已处理,不代表投标文件没有问题。</p>
|
||||
</section>
|
||||
|
||||
<section class="card" aria-labelledby="h-todo">
|
||||
<div class="ch">
|
||||
<h2 id="h-todo"><i data-ic="octagon-alert"></i>待处理的致命问题</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"><i data-ic="activity"></i>正在进行的任务</h3></div>
|
||||
<div class="rbody">
|
||||
<div class="task-h"><span class="tile sm ai"><i data-ic="loader"></i></span><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="cap sec" style="margin-top:8px">可以离开本页,完成后会发站内通知。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card" aria-labelledby="h-quota">
|
||||
<div class="ch"><h3 id="h-quota"><i data-ic="gauge"></i>本期额度</h3><span class="more cap sec">至 2027-10-31</span></div>
|
||||
<div class="rbody">
|
||||
<div class="q2">
|
||||
<div><small><i data-ic="file-search"></i>解析剩余</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><i data-ic="scan"></i>检查剩余</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"><i data-ic="history"></i>最近处理</h3><a class="more lnk cap" href="#" data-demo>全部记录</a></div>
|
||||
<div class="rbody">
|
||||
<ul class="log">
|
||||
<li><span class="av">王</span><span><span class="who">王敏</span> <span class="ok">✓ 已修改</span>「授权委托书签章」</span><span>14:26</span></li>
|
||||
<li><span class="av">王</span><span><span class="who">王敏</span> 确认了清单 v2 · 智慧大厅</span><span>11-02</span></li>
|
||||
<li><span class="av sys"><i data-ic="cpu"></i></span><span><span class="who">系统</span> 完成第 2 次检查 · 致命 3</span><span>11-02</span></li>
|
||||
<li><span class="av sys"><i data-ic="cpu"></i></span><span><span class="who">系统</span> 并入补遗 1 · 更新 2 条</span><span>11-02</span></li>
|
||||
<li><span class="av">李</span><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";
|
||||
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 = "";
|
||||
|
||||
const ST_IC = { "跟进中": "bookmark", "编制中": "pen", "已检查": "scan", "已递交": "send", "已归档": "archive" };
|
||||
const NX_IC = { "确认清单": "list", "导出检查报告": "download", "处理致命问题": "wrench", "上传投标文件": "upload", "查看检查报告": "file", "发起检查": "scan", "查看解析进度": "loader", "归档项目": "archive" };
|
||||
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="tile sm" title="${p.st}">${icon(ST_IC[p.st] || "folders")}</span><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>${icon(NX_IC[p.next] || "chev-right", "lead")}<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>
|
||||
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>
|
||||
658
docs/design/ui-demo/explore/workbench-d5.html
Normal file
658
docs/design/ui-demo/explore/workbench-d5.html
Normal file
@@ -0,0 +1,658 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
工作台 · 风格方案 D「明亮工作区」(探索稿,不是定稿)
|
||||
数据、文案、交互沿用 ../page-workbench.html 与 ../demo.js。
|
||||
|
||||
本方案相对 design-system.md 改了哪些规则:
|
||||
0e. (d5)侧栏改为单一状态的窄栏(96px,不再折叠):竖版 logo;菜单上图标、下文字;当前项用圆角矩形的半透明浅色底 + 白色加粗文字,不再连进内容区。图标换为 Lucide house / award / wallet。
|
||||
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>工作台 · 方案 D5</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 14%, 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 140px 32px; /* 项目行栅格 */
|
||||
--col-gap: 16px;
|
||||
}
|
||||
@media (max-width: 1399px){
|
||||
:root{ --pad: 24px; --rail-w: 256px; --cols: minmax(0,1fr) 108px 108px 56px 132px 32px; --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)}
|
||||
.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-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)}
|
||||
/* 折叠:只留图标;文字保留给读屏,悬停出提示 */
|
||||
.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}
|
||||
.row .more-cell{justify-self:end}
|
||||
.cols .c,.row .c{justify-self:center;text-align:center}
|
||||
.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-start;min-width:0}
|
||||
.nx{position:relative;z-index:1;display:inline-flex;align-items:center;gap:4px;height:var(--btn-h-sm);padding:0;border:0;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;color:var(--text-secondary);transition:color var(--dur-fast) var(--ease),background-color var(--dur-fast) var(--ease)}
|
||||
.row:hover .ibtn,.row:focus-within .ibtn,.row .ibtn[aria-expanded="true"]{color:var(--text-primary)}
|
||||
.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)}
|
||||
|
||||
/* ---------- d5:单一状态窄侧栏 ---------- */
|
||||
.app{--side-w:96px}
|
||||
.side .side-in{padding:0 0 16px 10px;overflow:visible}
|
||||
.side .brand{height:auto;justify-content:center;padding:20px 10px 20px 0;margin:0}
|
||||
.side .brand img{width:60px;height:auto}
|
||||
.side .nav{gap:4px}
|
||||
.side .nav a{flex-direction:column;justify-content:center;gap:4px;height:62px;padding:0;margin-right:10px;border-radius:10px;font-size:var(--fs-caption);line-height:16px;color:var(--side-fg);font-weight:var(--fw-regular);text-align:center}
|
||||
.side .nav a .ic{width:22px;height:22px}
|
||||
.side .nav a:hover{background:var(--side-hover);color:var(--side-fg-strong)}
|
||||
.side .nav a[aria-current]{background:var(--side-active);color:var(--side-fg-strong);font-weight:var(--fw-bold)}
|
||||
.side .nav a[aria-current]::before{display:none}
|
||||
.side .nav .grp{display:block;height:1px;margin:10px 22px 10px 12px;background:var(--side-line)}
|
||||
.side .side-foot{flex-direction:column;justify-content:center;gap:0;margin-top:auto;padding:16px 10px 4px 0;border-top:0;color:var(--side-muted);text-align:center;white-space:normal}
|
||||
.side .side-foot .avatar{width:44px;height:44px;font-size:var(--fs-title);margin-bottom:8px;background:color-mix(in srgb, white 16%, transparent);box-shadow:0 0 0 1px color-mix(in srgb, white 22%, transparent)}
|
||||
.side .side-foot .uname{color:var(--side-fg-strong);font-size:var(--fs-body);line-height:var(--lh-body);font-weight:var(--fw-bold)}
|
||||
.side .side-foot .plan{margin-top:4px;padding:0 8px;border-radius:4px;font-size:var(--fs-caption);line-height:20px;font-weight:var(--fw-bold)}
|
||||
/* 套餐标签:只在账户区使用的「金色」阶梯,从赭色派生,不引入新色相;
|
||||
免费版中性、专业版浅金底、企业版金色实底,靠深浅区分,不靠多种颜色。?plan=free / ent 预览 */
|
||||
.plan[data-plan="free"]{background:color-mix(in srgb, white 12%, transparent);color:var(--side-fg)}
|
||||
.plan[data-plan="pro"]{background:color-mix(in srgb, var(--c-ochre) 42%, transparent);color:color-mix(in srgb, var(--c-ochre) 12%, white);box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--c-ochre) 70%, white 10%)}
|
||||
.plan[data-plan="ent"]{background:color-mix(in srgb, var(--c-ochre) 55%, white);color:var(--c-ink)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<aside class="side"><div class="side-in">
|
||||
<a class="brand" href="workbench-d5.html" aria-label="标博士"><img src="../../logo/svg/logo-vertical-reverse.svg" alt=""></a>
|
||||
<nav class="nav" aria-label="主导航">
|
||||
<a href="workbench-d5.html" aria-current="page"><i data-ic="house"></i><span>工作台</span></a>
|
||||
<a href="#" data-demo><i data-ic="award"></i><span>资质库</span></a>
|
||||
<a href="#" data-demo><i data-ic="wallet"></i><span>额度</span></a>
|
||||
<span class="grp" role="separator"></span>
|
||||
<a href="index.html"><i data-ic="book"></i><span>方案目录</span></a>
|
||||
</nav>
|
||||
<div class="side-foot"><span class="avatar" aria-hidden="true">王</span><b class="uname">王敏</b><span class="plan" data-plan="pro">专业版</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="c">致命</span><span>下一步</span><span></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"/>',
|
||||
house: '<path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"/><path d="M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>',
|
||||
award: '<path d="m15.477 12.89 1.515 8.526a.5.5 0 0 1-.81.47l-3.58-2.687a1 1 0 0 0-1.197 0l-3.586 2.686a.5.5 0 0 1-.81-.469l1.514-8.526"/><circle cx="12" cy="8" r="6"/>',
|
||||
wallet: '<path d="M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1"/><path d="M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4"/>',
|
||||
"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 plan = new URLSearchParams(location.search).get("plan"); const PLAN = { free: "免费版", pro: "专业版", ent: "企业版" };
|
||||
if (PLAN[plan]) { const el = $(".plan"); el.dataset.plan = plan; el.textContent = PLAN[plan]; }
|
||||
|
||||
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="c">${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></span><span class="more-cell"><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>
|
||||
544
docs/design/ui-demo/explore/workbench-e.html
Normal file
544
docs/design/ui-demo/explore/workbench-e.html
Normal file
@@ -0,0 +1,544 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
工作台 · 风格探索第二轮 方案 E「下一步」
|
||||
概念:工作台是「今天该做什么」的行动清单,不是报表。最上方是按紧急程度排好的待办队列,每条都能直接动手;项目全貌放在下面。
|
||||
内容、数据沿用 ../page-workbench.html;颜色只用 tokens.css 的色值和语义,浅底与分隔线全部由墨色派生。
|
||||
|
||||
本方案相对 design-system.md 改了这些规则:
|
||||
1. 底色与分隔线(§3.1 / §3.2):页面底色 --c-paper 改为 --n-50(墨 3% 混白);分隔线 --c-rule 改为 --n-line(墨 11% 混白);
|
||||
行悬停 --c-hover 改为 --n-50;进度条轨道、侧栏当前项用 --n-100(墨 6%)。不再使用 --c-paper / --c-sunken / --c-hover / --c-rule。
|
||||
2. 圆角(§5.1 控件和卡片 2px、浮层 4px):卡片 12px;按钮、输入框、侧栏当前项、轻提示 8px;问题级别标签 4px。
|
||||
3. 阴影(§5.1 只用于浮层):只有可点击的待办卡在悬停 / 键盘聚焦时出现一层极轻阴影并加深边框,作为「可以点」的提示;静止时所有卡片都不带阴影。
|
||||
4. 按钮(§7.1):默认高度 32 → 36;次按钮白底 + 1px --border-control;表格行内按钮 28 不变。
|
||||
5. 统计卡(原型四张统计卡):取消。数字并入区块标题(待办 4、项目 4)和待办标题右侧的一行概要。
|
||||
6. 提醒条(§7.12):取消,并入待办队列。今天截止的那条用左侧 3px 赭色竖条 + 赭色时间文字。
|
||||
7. 筛选(§7.1 分段控件):改为文字标签页,选中项墨色 2px 下划线。
|
||||
8. 表格(§7.8):表头去掉凹陷底色和加粗下边线,只留小号辅助灰字;行间发丝线;「状态」「就绪」「致命」合并为一列;
|
||||
状态改为纯文字,全页只保留问题级别一种标签(形状规则不变:致命实心、重要描边、提示无框)。新增「下一步」列,每行一个可直接点的操作。
|
||||
9. 页面框架(§5.3):去掉顶栏,通知、快捷键和新建项目并入页头一行;侧栏白底,与内容之间一条 --n-line 发丝线;额度只在右栏出现一次。
|
||||
10. 区块标题:16/24 黑体 600(宋体只用于 H1「工作台」)。
|
||||
-->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>工作台 · 标博士</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); /* 卡片边框、发丝线 */
|
||||
--n-line-strong: color-mix(in srgb, var(--c-ink) 24%, white); /* 待办卡悬停边框 */
|
||||
--e-lift: 0 1px 2px color-mix(in srgb, var(--c-ink) 6%, transparent),
|
||||
0 6px 16px -6px color-mix(in srgb, var(--c-ink) 14%, transparent);
|
||||
--e-r-card: 12px;
|
||||
--e-r-ctl: 8px;
|
||||
--e-h: 36px;
|
||||
--e-h-sm: 28px;
|
||||
--e-gap: 16px; /* 卡片之间、卡片内分组之间的统一间距 */
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html { -webkit-text-size-adjust: 100%; }
|
||||
body {
|
||||
margin: 0; min-width: 1280px;
|
||||
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;
|
||||
}
|
||||
a { color: inherit; }
|
||||
button { font: inherit; color: inherit; }
|
||||
h1, h2, h3, p { margin: 0; }
|
||||
.fig { font-family: var(--font-figures); font-variant-numeric: tabular-nums; }
|
||||
.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
||||
.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; }
|
||||
:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: 4px; }
|
||||
.sub { color: var(--text-secondary); }
|
||||
.cap { font-size: var(--fs-caption); line-height: var(--lh-caption); }
|
||||
.lnk { color: var(--text-primary); text-decoration: underline; text-decoration-color: var(--n-line-strong); text-underline-offset: 3px; cursor: pointer; }
|
||||
.lnk:hover { text-decoration-color: currentColor; }
|
||||
|
||||
/* ---------- 按钮:一种主按钮、一种次按钮、一种图标按钮 ---------- */
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
||||
height: var(--e-h); padding: 0 var(--sp-4); border: 1px solid transparent; border-radius: var(--e-r-ctl);
|
||||
font-weight: var(--fw-bold); text-decoration: none; white-space: nowrap; cursor: pointer;
|
||||
transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
|
||||
}
|
||||
.btn.primary { background: var(--c-ink); color: var(--text-on-dark); }
|
||||
.btn.primary:hover { background: color-mix(in srgb, var(--c-ink) 86%, white); }
|
||||
.btn.secondary { background: var(--c-white); border-color: var(--border-control); color: var(--text-primary); }
|
||||
.btn.secondary:hover { background: var(--n-50); border-color: var(--c-ink); }
|
||||
.btn.sm { height: var(--e-h-sm); padding: 0 var(--sp-3); font-weight: var(--fw-regular); }
|
||||
.icon-btn {
|
||||
position: relative; display: inline-grid; place-items: center; width: var(--e-h); height: var(--e-h);
|
||||
border: 0; border-radius: var(--e-r-ctl); background: none; color: var(--text-secondary); cursor: pointer;
|
||||
}
|
||||
.icon-btn:hover { background: var(--n-100); color: var(--text-primary); }
|
||||
.icon-btn.sm { width: var(--e-h-sm); height: var(--e-h-sm); }
|
||||
.icon-btn .dot { position: absolute; top: 8px; right: 8px; width: 7px; height: 7px; border-radius: 50%; background: var(--warn); box-shadow: 0 0 0 2px var(--n-50); }
|
||||
|
||||
/* ---------- 唯一的标签:问题级别 ---------- */
|
||||
.sev { display: inline-flex; align-items: center; gap: 4px; height: 20px; padding: 0 6px; border-radius: 4px; font-size: var(--fs-caption); line-height: 20px; font-weight: var(--fw-bold); white-space: nowrap; }
|
||||
.sev.fatal { background: var(--sev-fatal); color: var(--text-on-dark); }
|
||||
.sev .fig { font-size: 13px; }
|
||||
|
||||
/* ---------- 框架 ---------- */
|
||||
.app { display: grid; grid-template-columns: var(--sidebar-w) minmax(0, 1fr); min-height: 100vh; }
|
||||
.side {
|
||||
position: sticky; top: 0; height: 100vh; display: flex; flex-direction: column;
|
||||
padding: var(--sp-6) var(--sp-3); background: var(--c-white); border-right: 1px solid var(--n-line);
|
||||
}
|
||||
.brand { display: inline-block; padding: 0 var(--sp-3); margin-bottom: var(--sp-8); }
|
||||
.brand img { display: block; height: 32px; width: auto; }
|
||||
.nav { display: flex; flex-direction: column; gap: 2px; }
|
||||
.nav a { display: flex; align-items: center; gap: 10px; height: 40px; padding: 0 var(--sp-3); border-radius: var(--e-r-ctl); color: var(--text-secondary); text-decoration: none; }
|
||||
.nav a:hover { background: var(--n-50); color: var(--text-primary); }
|
||||
.nav a[aria-current="page"] { background: var(--n-100); color: var(--text-primary); font-weight: var(--fw-bold); }
|
||||
.nav .demo { margin-top: var(--sp-6); height: 32px; font-size: var(--fs-caption); }
|
||||
.side-foot { margin-top: auto; padding: var(--sp-4) var(--sp-3) 0; border-top: 1px solid var(--n-line); 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); font-weight: var(--fw-bold); color: var(--text-primary); }
|
||||
|
||||
.main { min-width: 0; padding: var(--sp-6) var(--sp-6) var(--sp-16); }
|
||||
.wrap { max-width: 1320px; }
|
||||
|
||||
/* 页头 */
|
||||
.head { display: flex; align-items: center; gap: var(--sp-4); min-height: 48px; }
|
||||
.head h1 { font: var(--fw-bold) 28px/36px var(--font-serif); letter-spacing: .02em; }
|
||||
.head .date { color: var(--text-secondary); padding-top: 6px; }
|
||||
.head .tools { margin-left: auto; display: flex; align-items: center; gap: var(--sp-1); }
|
||||
.head .cost { margin: 0 var(--sp-3) 0 var(--sp-4); color: var(--text-secondary); }
|
||||
.head .cost .fig { color: var(--text-primary); font-weight: var(--fw-bold); font-size: 13px; }
|
||||
|
||||
.layout { display: grid; grid-template-columns: minmax(0, 1fr) clamp(272px, 20.5vw, 304px); gap: var(--sp-6); align-items: start; margin-top: var(--sp-6); }
|
||||
|
||||
/* 区块标题:标题 + 小号数字 + 右侧补充 */
|
||||
.sec-h { display: flex; align-items: baseline; gap: var(--sp-2); margin-bottom: var(--sp-3); min-height: 28px; }
|
||||
.sec-h h2 { font-size: var(--fs-title); line-height: var(--lh-title); font-weight: var(--fw-bold); }
|
||||
.sec-h .count { font-family: var(--font-figures); font-variant-numeric: tabular-nums; font-size: 16px; color: var(--text-secondary); }
|
||||
.sec-h .aside { margin-left: auto; display: flex; gap: var(--sp-4); color: var(--text-secondary); font-size: var(--fs-caption); line-height: var(--lh-caption); }
|
||||
.sec-h .aside .fig { font-size: 14px; font-weight: var(--fw-bold); color: var(--text-primary); margin: 0 2px; }
|
||||
.sec-h .aside .fig.ochre { color: var(--warn-text); }
|
||||
.sec-h .aside .fig.fatal { color: var(--sev-fatal); }
|
||||
|
||||
/* ---------- 唯一的容器:白卡片 ---------- */
|
||||
.card { background: var(--c-white); border: 1px solid var(--n-line); border-radius: var(--e-r-card); }
|
||||
|
||||
/* ---------- 待办队列(主角) ---------- */
|
||||
.queue { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
|
||||
.todo {
|
||||
position: relative; overflow: hidden; cursor: pointer;
|
||||
transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-base) var(--ease), transform var(--dur-base) var(--ease),
|
||||
opacity 220ms var(--ease), height 260ms var(--ease) 120ms, margin 260ms var(--ease) 120ms;
|
||||
}
|
||||
.todo:hover, .todo:focus-within { border-color: var(--n-line-strong); box-shadow: var(--e-lift); transform: translateY(-1px); }
|
||||
.todo.today::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--warn); }
|
||||
.todo-row {
|
||||
display: grid; align-items: center; column-gap: var(--sp-4);
|
||||
grid-template-columns: 20px minmax(0, 1fr) 136px 160px 28px;
|
||||
padding: var(--sp-4) var(--sp-3) var(--sp-4) var(--sp-5);
|
||||
}
|
||||
.todo .kind { align-self: start; margin-top: 2px; color: var(--text-secondary); }
|
||||
.todo.today .kind { color: var(--warn); }
|
||||
.todo.fatal .kind { color: var(--sev-fatal); }
|
||||
.todo h3 { font-size: 15px; line-height: 24px; font-weight: var(--fw-bold); }
|
||||
.todo h3 .proj { font-weight: var(--fw-bold); }
|
||||
.todo .why { margin-top: 2px; color: var(--text-secondary); }
|
||||
.todo .why b { font-weight: var(--fw-bold); color: var(--warn-text); }
|
||||
.todo .why .sev { margin-right: 4px; position: relative; top: -1px; }
|
||||
.todo .meta { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; white-space: nowrap; }
|
||||
.todo .meta .d1 { font-size: 14px; line-height: 22px; }
|
||||
.todo .meta .d1 .fig { font-size: 15px; }
|
||||
.todo .meta .d2 { font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
|
||||
.todo.today .meta .d1, .todo.today .meta .d2 { color: var(--warn-text); font-weight: var(--fw-bold); }
|
||||
.todo.soon .meta .d1, .todo.soon .meta .d2 { color: var(--warn-text); }
|
||||
.todo .go { width: 100%; }
|
||||
@media (max-width: 1400px) { .todo-row { column-gap: var(--sp-3); grid-template-columns: 20px minmax(0, 1fr) 120px 148px 28px; } .todo .go { padding: 0 var(--sp-3); } }
|
||||
.todo .snooze { opacity: 0; transition: opacity var(--dur-fast) var(--ease); }
|
||||
.todo:hover .snooze, .todo:focus-within .snooze, .todo .snooze:focus-visible { opacity: 1; }
|
||||
|
||||
/* 致命问题展开 */
|
||||
.toggle { display: inline-flex; align-items: center; gap: 4px; margin-top: var(--sp-2); padding: 0 6px 0 0; height: 24px; border: 0; background: none; color: var(--text-primary); cursor: pointer; text-decoration: underline; text-decoration-color: var(--n-line-strong); text-underline-offset: 3px; }
|
||||
.toggle:hover { text-decoration-color: currentColor; }
|
||||
.toggle .ic { transition: transform var(--dur-base) var(--ease); }
|
||||
.toggle[aria-expanded="true"] .ic { transform: rotate(180deg); }
|
||||
.issues { list-style: none; margin: 0; padding: 0 var(--sp-5) var(--sp-2) calc(var(--sp-5) + 20px + var(--sp-4)); cursor: default; }
|
||||
.issues[hidden] { display: none; }
|
||||
.issues li { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; column-gap: var(--sp-3); align-items: baseline; padding: var(--sp-3) 0; border-top: 1px solid var(--n-line); }
|
||||
.issues .sev { position: relative; top: -1px; }
|
||||
.issues b { display: block; font-weight: var(--fw-bold); }
|
||||
.issues p { color: var(--text-secondary); font-size: var(--fs-caption); line-height: var(--lh-caption); margin-top: 2px; }
|
||||
.cite { display: inline-flex; align-items: center; gap: 4px; color: var(--ai); text-decoration: none; white-space: nowrap; font-size: var(--fs-caption); line-height: var(--lh-caption); }
|
||||
.cite:hover { color: var(--ai-strong); text-decoration: underline; text-underline-offset: 3px; }
|
||||
.cite .fig { font-size: 13px; }
|
||||
.issues .foot { display: block; border-top: 1px solid var(--n-line); padding: var(--sp-3) 0 var(--sp-1); color: var(--text-secondary); font-size: var(--fs-caption); line-height: var(--lh-caption); }
|
||||
|
||||
.queue-done { padding: var(--sp-6) var(--sp-5); text-align: center; color: var(--text-secondary); }
|
||||
.queue-done b { display: block; color: var(--text-primary); font-weight: var(--fw-bold); }
|
||||
|
||||
/* ---------- 项目列表 ---------- */
|
||||
.projects-sec { margin-top: var(--sp-10); }
|
||||
.list-card { overflow: hidden; }
|
||||
.bar { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--sp-4); padding: var(--sp-2) var(--sp-5) 0; border-bottom: 1px solid var(--n-line); }
|
||||
.tabs { display: flex; gap: var(--sp-5); }
|
||||
.tabs button {
|
||||
height: 44px; padding: 0 2px; border: 0; background: none; cursor: pointer; color: var(--text-secondary);
|
||||
box-shadow: inset 0 -2px 0 transparent; transition: color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
|
||||
}
|
||||
.tabs button:hover { color: var(--text-primary); box-shadow: inset 0 -2px 0 var(--n-line-strong); }
|
||||
.tabs button[aria-selected="true"] { color: var(--text-primary); font-weight: var(--fw-bold); box-shadow: inset 0 -2px 0 var(--c-ink); }
|
||||
.tabs .fig { margin-left: 4px; }
|
||||
.search { position: relative; display: block; width: 240px; margin-bottom: var(--sp-2); }
|
||||
.search .ic { position: absolute; left: 10px; top: 8px; color: var(--text-secondary); pointer-events: none; }
|
||||
.search input {
|
||||
width: 100%; height: 32px; padding: 0 var(--sp-3) 0 34px; border: 1px solid var(--border-control); border-radius: var(--e-r-ctl);
|
||||
background: var(--c-white); font: inherit; color: var(--text-primary);
|
||||
}
|
||||
.search input::placeholder { color: var(--text-secondary); }
|
||||
.search input:hover { border-color: var(--c-ink); }
|
||||
.search input:focus-visible { outline-offset: 1px; border-color: var(--c-ink); }
|
||||
|
||||
.projects { width: 100%; border-collapse: collapse; table-layout: fixed; }
|
||||
.projects th { padding: var(--sp-3) 10px var(--sp-2); text-align: left; font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-regular); color: var(--text-secondary); white-space: nowrap; }
|
||||
.projects th:first-child, .projects td:first-child { padding-left: var(--sp-5); }
|
||||
.projects th:last-child, .projects td:last-child { padding-right: var(--sp-4); }
|
||||
.projects td { padding: var(--sp-3) 10px; border-top: 1px solid var(--n-line); vertical-align: middle; }
|
||||
.projects tbody tr.row { cursor: pointer; transition: background var(--dur-fast) var(--ease); }
|
||||
.projects tbody tr.row:hover, .projects tbody tr.row:focus-within { background: var(--n-50); }
|
||||
.c-st { width: 104px; } .c-due { width: 124px; } .c-next { width: 172px; }
|
||||
.pname { font-weight: var(--fw-bold); text-decoration: none; }
|
||||
.row:hover .pname { text-decoration: underline; text-decoration-color: var(--n-line-strong); text-underline-offset: 3px; }
|
||||
.psub { display: flex; gap: var(--sp-3); color: var(--text-secondary); font-size: var(--fs-caption); line-height: var(--lh-caption); }
|
||||
.st { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }
|
||||
.st .r2 { font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
|
||||
.ready { text-decoration: underline dotted var(--text-secondary); text-underline-offset: 3px; cursor: help; }
|
||||
.due { white-space: nowrap; }
|
||||
.due .d1 { display: block; }
|
||||
.due .d2 { display: block; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
|
||||
.due.today .d1, .due.today .d2 { color: var(--warn-text); font-weight: var(--fw-bold); }
|
||||
.due.soon .d1, .due.soon .d2 { color: var(--warn-text); }
|
||||
.due.past .d1 { color: var(--text-secondary); }
|
||||
.next { display: flex; align-items: center; justify-content: flex-end; gap: 4px; }
|
||||
.next .more { opacity: 0; transition: opacity var(--dur-fast) var(--ease); }
|
||||
.row:hover .more, .row:focus-within .more { opacity: 1; }
|
||||
.row:hover .btn.secondary { border-color: var(--c-ink); }
|
||||
.empty { padding: var(--sp-10) var(--sp-5); text-align: center; color: var(--text-secondary); }
|
||||
.empty b { display: block; margin-bottom: var(--sp-1); color: var(--text-primary); font-weight: var(--fw-bold); }
|
||||
.list-note { padding: var(--sp-3) var(--sp-5); border-top: 1px solid var(--n-line); color: var(--text-secondary); font-size: var(--fs-caption); line-height: var(--lh-caption); }
|
||||
|
||||
/* ---------- 右栏:同一种白卡片 ---------- */
|
||||
.rail { display: grid; gap: var(--e-gap); padding-top: calc(28px + var(--sp-3)); }
|
||||
.rail .card { padding: var(--sp-4) var(--sp-5) var(--sp-5); }
|
||||
.rail h3 { display: flex; align-items: baseline; gap: var(--sp-2); margin-bottom: var(--sp-3); font-size: var(--fs-title); line-height: var(--lh-title); font-weight: var(--fw-bold); }
|
||||
.rail h3 .r { margin-left: auto; font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-regular); color: var(--text-secondary); }
|
||||
.quota { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-5); }
|
||||
.quota small { display: block; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
|
||||
.quota .big { font: var(--fw-bold) var(--fs-figure)/var(--lh-figure) var(--font-figures); font-variant-numeric: tabular-nums; }
|
||||
.quota .unit { margin-left: 4px; font-family: var(--font-figures); font-size: 15px; color: var(--text-secondary); }
|
||||
.track { height: 4px; border-radius: 2px; background: var(--n-100); overflow: hidden; margin-top: var(--sp-2); }
|
||||
.track i { display: block; height: 100%; background: var(--c-ink); border-radius: 2px; }
|
||||
.links { display: flex; gap: var(--sp-5); margin-top: var(--sp-4); padding-top: var(--sp-3); border-top: 1px solid var(--n-line); font-size: var(--fs-caption); line-height: var(--lh-caption); }
|
||||
.task-h { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-2); }
|
||||
.task-h span { font-size: var(--fs-caption); color: var(--text-secondary); }
|
||||
.progress { display: flex; align-items: center; gap: var(--sp-3); }
|
||||
.progress .track { flex: 1; margin: 0; }
|
||||
.progress .fig { font-size: 15px; font-weight: var(--fw-bold); min-width: 32px; text-align: right; }
|
||||
.task + .task { margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px solid var(--n-line); }
|
||||
.task .wait { display: flex; gap: var(--sp-2); color: var(--text-secondary); }
|
||||
.task .wait .ic { margin-top: 3px; }
|
||||
.rail .hint { margin-top: var(--sp-3); font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
|
||||
.log { list-style: none; margin: 0; padding: 0; }
|
||||
.log li { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-3); padding: 6px 0; font-size: 13px; line-height: 20px; }
|
||||
.log li + li { border-top: 1px solid var(--n-line); }
|
||||
.log .t { font-family: var(--font-figures); font-variant-numeric: tabular-nums; color: var(--text-secondary); white-space: nowrap; }
|
||||
.log .who { font-weight: var(--fw-bold); }
|
||||
.log .ok { color: var(--confirmed); white-space: nowrap; }
|
||||
|
||||
/* 轻提示 */
|
||||
.toasts { position: fixed; left: 50%; bottom: var(--sp-8); transform: translateX(-50%); display: grid; gap: var(--sp-2); justify-items: center; z-index: 20; }
|
||||
.toast { display: flex; align-items: center; gap: var(--sp-3); min-height: 40px; padding: var(--sp-2) var(--sp-4); border-radius: var(--e-r-ctl); background: var(--c-ink); color: var(--text-on-dark); box-shadow: var(--shadow-overlay); white-space: nowrap; }
|
||||
.toast button { padding: 0; border: 0; background: none; color: var(--text-on-dark); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
|
||||
.toast button:focus-visible { outline-color: var(--c-white); }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } .todo:hover, .todo:focus-within { transform: none; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<aside class="side">
|
||||
<a class="brand" href="../page-workbench.html"><img src="../../logo/svg/logo-horizontal.svg" alt="标博士"></a>
|
||||
<nav class="nav" aria-label="主导航">
|
||||
<a href="#" aria-current="page"><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><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"/></svg>工作台</a>
|
||||
<a href="#" data-demo><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 9h10M7 13h6"/><circle cx="16" cy="15" r="2"/></svg>资质库</a>
|
||||
<a href="#quota"><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/></svg>额度</a>
|
||||
<a class="demo" href="index.html"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><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"/></svg>风格探索目录</a>
|
||||
</nav>
|
||||
<div class="side-foot"><b>王敏</b>南京某信息技术有限公司<br>专业版,至 2027-10-31</div>
|
||||
</aside>
|
||||
|
||||
<main class="main">
|
||||
<div class="wrap">
|
||||
<header class="head">
|
||||
<h1>工作台</h1>
|
||||
<span class="date">11月3日 周二</span>
|
||||
<div class="tools">
|
||||
<button class="icon-btn" aria-label="通知(有 1 条未读)" title="通知" data-notice><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><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"/></svg><span class="dot"></span></button>
|
||||
<button class="icon-btn" aria-label="快捷键说明" title="快捷键说明(?)" data-keys><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.01M10 8h.01M14 8h.01M18 8h.01M8 12h.01M12 12h.01M16 12h.01M7 16h10"/></svg></button>
|
||||
<span class="cost cap">扣 1 次解析,剩 <span class="fig">37</span> 次</span>
|
||||
<a class="btn primary" href="../forms.html#s-new"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14"/><path d="M12 5v14"/></svg>新建项目</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="layout">
|
||||
<div>
|
||||
<!-- 待办队列 -->
|
||||
<section aria-labelledby="todo-h">
|
||||
<div class="sec-h">
|
||||
<h2 id="todo-h">待办</h2><span class="count" data-todo-count>4</span>
|
||||
<div class="aside"><span>7 天内截止<span class="fig ochre">2</span>个项目</span><span>致命问题<span class="fig fatal">3</span>个</span><span>清单待确认<span class="fig">4</span>条,在 2 个项目里</span></div>
|
||||
</div>
|
||||
<ol class="queue" data-queue>
|
||||
<li class="card todo today" data-go="../page-reading.html">
|
||||
<div class="todo-row">
|
||||
<svg class="ic lg kind" viewBox="0 0 24 24" aria-hidden="true"><path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8M13 12h8M13 18h8"/></svg>
|
||||
<div><h3>确认清单:<span class="proj">某县数据中心机房改造项目</span></h3><p class="why"><b>今天 17:00 截止</b>,清单还是草稿,还没做过检查。确认后上传投标文件就能发起检查。</p></div>
|
||||
<div class="meta"><span class="d1">今天 <span class="fig">17:00</span></span><span class="d2">剩 6 小时</span></div>
|
||||
<a class="btn primary go" href="../page-reading.html">确认清单</a>
|
||||
<button class="icon-btn sm snooze" aria-label="稍后提醒:确认清单" title="稍后提醒" data-snooze><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M10.1 2.18a9.93 9.93 0 0 1 3.8 0"/><path d="M17.6 3.71a9.95 9.95 0 0 1 2.69 2.7"/><path d="M21.82 10.1a9.93 9.93 0 0 1 0 3.8"/><path d="M20.29 17.6a9.95 9.95 0 0 1-2.7 2.69"/><path d="M13.9 21.82a9.94 9.94 0 0 1-3.8 0"/><path d="M6.4 20.29a9.95 9.95 0 0 1-2.69-2.7"/><path d="M2.18 13.9a9.93 9.93 0 0 1 0-3.8"/><path d="M3.71 6.4a9.95 9.95 0 0 1 2.7-2.69"/><path d="M12 7v5l3 2"/></svg></button>
|
||||
</div>
|
||||
</li>
|
||||
<li class="card todo fatal" data-go="../page-reading.html">
|
||||
<div class="todo-row">
|
||||
<svg class="ic lg kind" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 16h.01"/><path d="M12 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"/></svg>
|
||||
<div><h3>致命问题:<span class="proj">某市政务服务中心智慧大厅建设项目</span></h3><p class="why"><span class="sev fatal">致命<span class="fig">3</span></span> 第 2 次检查发现 3 个致命问题。没处理完也能导出,项目会保持「未就绪」。</p>
|
||||
<button class="toggle cap" aria-expanded="false" aria-controls="fatal-list" data-toggle>看看是哪 3 个<svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button></div>
|
||||
<div class="meta"><span class="d1"><span class="fig">11</span>月<span class="fig">20</span>日 <span class="fig">09:30</span></span><span class="d2">剩 17 天</span></div>
|
||||
<a class="btn primary go" href="../page-reading.html">处理 3 个致命问题</a>
|
||||
<button class="icon-btn sm snooze" aria-label="稍后提醒:致命问题" title="稍后提醒" data-snooze><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M10.1 2.18a9.93 9.93 0 0 1 3.8 0"/><path d="M17.6 3.71a9.95 9.95 0 0 1 2.69 2.7"/><path d="M21.82 10.1a9.93 9.93 0 0 1 0 3.8"/><path d="M20.29 17.6a9.95 9.95 0 0 1-2.7 2.69"/><path d="M13.9 21.82a9.94 9.94 0 0 1-3.8 0"/><path d="M6.4 20.29a9.95 9.95 0 0 1-2.69-2.7"/><path d="M2.18 13.9a9.93 9.93 0 0 1 0-3.8"/><path d="M3.71 6.4a9.95 9.95 0 0 1 2.7-2.69"/><path d="M12 7v5l3 2"/></svg></button>
|
||||
</div>
|
||||
<ul class="issues" id="fatal-list" hidden>
|
||||
<li><span class="sev fatal">致命</span><div><b>报价超过采购包 1 的最高限价</b><p>开标一览表 287.60 万元,限价 286.00 万元</p></div><a class="cite" href="#" data-cite>报价册 第 <span class="fig">1</span> 页</a></li>
|
||||
<li><span class="sev fatal">致命</span><div><b>★ 条款疑似不满足:应用服务器内存</b><p>响应 256G,要求 ≥ 512G。上次标记为已修改,问题仍在</p></div><a class="cite" href="#" data-cite>技术册 第 <span class="fig">12</span> 页</a></li>
|
||||
<li><span class="sev fatal">致命</span><div><b>授权委托书的单位签章位置未发现盖章</b><p>只看有无,不鉴定真伪</p></div><a class="cite" href="#" data-cite>商务册 第 <span class="fig">5</span> 页</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="card todo soon" data-go="">
|
||||
<div class="todo-row">
|
||||
<svg class="ic lg kind" viewBox="0 0 24 24" aria-hidden="true"><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="m9 15 2 2 4-4"/></svg>
|
||||
<div><h3>递交前复核:<span class="proj">某区智慧社区平台运维服务</span></h3><p class="why">已就绪。新上传的版本在排队检查,前面还有 1 个任务,出结果后会通知你。</p></div>
|
||||
<div class="meta"><span class="d1"><span class="fig">11</span>月<span class="fig">5</span>日 <span class="fig">16:00</span></span><span class="d2">剩 2 天 5 小时</span></div>
|
||||
<button class="btn secondary go" data-demo-go="(演示)会打开该项目上一次的检查结果">查看检查结果</button>
|
||||
<button class="icon-btn sm snooze" aria-label="稍后提醒:递交前复核" title="稍后提醒" data-snooze><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M10.1 2.18a9.93 9.93 0 0 1 3.8 0"/><path d="M17.6 3.71a9.95 9.95 0 0 1 2.69 2.7"/><path d="M21.82 10.1a9.93 9.93 0 0 1 0 3.8"/><path d="M20.29 17.6a9.95 9.95 0 0 1-2.7 2.69"/><path d="M13.9 21.82a9.94 9.94 0 0 1-3.8 0"/><path d="M6.4 20.29a9.95 9.95 0 0 1-2.69-2.7"/><path d="M2.18 13.9a9.93 9.93 0 0 1 0-3.8"/><path d="M3.71 6.4a9.95 9.95 0 0 1 2.7-2.69"/><path d="M12 7v5l3 2"/></svg></button>
|
||||
</div>
|
||||
</li>
|
||||
<li class="card todo" data-go="">
|
||||
<div class="todo-row">
|
||||
<svg class="ic lg kind" viewBox="0 0 24 24" aria-hidden="true"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>
|
||||
<div><h3>补遗解析中:<span class="proj">某市医保信息系统等保测评服务</span></h3><p class="why">补遗 2 正在提取清单,已完成 62%。完成后需要确认补遗带来的变更。</p></div>
|
||||
<div class="meta"><span class="d1"><span class="fig">11</span>月<span class="fig">28</span>日 <span class="fig">10:00</span></span><span class="d2">剩 25 天</span></div>
|
||||
<button class="btn secondary go" data-demo-go="(演示)会打开解析进度:补遗 2,提取清单 62%">查看解析进度</button>
|
||||
<button class="icon-btn sm snooze" aria-label="稍后提醒:补遗解析中" title="稍后提醒" data-snooze><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M10.1 2.18a9.93 9.93 0 0 1 3.8 0"/><path d="M17.6 3.71a9.95 9.95 0 0 1 2.69 2.7"/><path d="M21.82 10.1a9.93 9.93 0 0 1 0 3.8"/><path d="M20.29 17.6a9.95 9.95 0 0 1-2.7 2.69"/><path d="M13.9 21.82a9.94 9.94 0 0 1-3.8 0"/><path d="M6.4 20.29a9.95 9.95 0 0 1-2.69-2.7"/><path d="M2.18 13.9a9.93 9.93 0 0 1 0-3.8"/><path d="M3.71 6.4a9.95 9.95 0 0 1 2.7-2.69"/><path d="M12 7v5l3 2"/></svg></button>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- 项目列表 -->
|
||||
<section class="projects-sec" aria-labelledby="proj-h">
|
||||
<div class="sec-h"><h2 id="proj-h">项目</h2><span class="count" data-proj-count>4</span></div>
|
||||
<div class="card list-card">
|
||||
<div class="bar">
|
||||
<div class="tabs" role="tablist" aria-label="按状态筛选" data-tabs>
|
||||
<button role="tab" aria-selected="true" data-v="active">进行中<span class="fig">4</span></button>
|
||||
<button role="tab" aria-selected="false" data-v="all" tabindex="-1">全部<span class="fig">6</span></button>
|
||||
<button role="tab" aria-selected="false" data-v="已递交" tabindex="-1">已递交<span class="fig">1</span></button>
|
||||
<button role="tab" aria-selected="false" data-v="已归档" tabindex="-1">已归档<span class="fig">1</span></button>
|
||||
</div>
|
||||
<label class="search"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg><input type="search" placeholder="搜索项目、采购人、编号" aria-label="搜索项目" data-q></label>
|
||||
</div>
|
||||
<table class="projects">
|
||||
<thead><tr><th scope="col">项目</th><th scope="col" class="c-st">状态</th><th scope="col" class="c-due" aria-sort="ascending">投标截止</th><th scope="col" class="c-next"><span class="vh">下一步</span></th></tr></thead>
|
||||
<tbody data-rows></tbody>
|
||||
</table>
|
||||
<p class="list-note" id="ready-note">按投标截止时间排序。「就绪」只表示致命问题都已处理,不代表投标文件没有问题。</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- 右栏 -->
|
||||
<aside class="rail" aria-label="额度、任务与最近处理">
|
||||
<section class="card" id="quota">
|
||||
<h3>本期额度<span class="r">至 <span class="fig">2027-10-31</span></span></h3>
|
||||
<div class="quota">
|
||||
<div><small>解析剩余</small><span class="big">37</span><span class="unit">/ 50</span><div class="track" aria-hidden="true"><i style="width:74%"></i></div></div>
|
||||
<div><small>检查剩余</small><span class="big">12</span><span class="unit">/ 20</span><div class="track" aria-hidden="true"><i style="width:60%"></i></div></div>
|
||||
</div>
|
||||
<div class="links"><a class="lnk" href="#" data-demo>扣点规则</a><a class="lnk" href="#" data-demo>查看流水</a></div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h3>正在进行的任务<span class="count fig sub">2</span></h3>
|
||||
<div class="task">
|
||||
<div class="task-h"><b>解析补遗 2</b><span>提取清单</span></div>
|
||||
<div class="cap sub">某市医保信息系统等保测评服务</div>
|
||||
<div class="progress"><div class="track" 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>
|
||||
<div class="task">
|
||||
<div class="wait"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg><span>检查排队中:某区智慧社区平台运维服务,前面还有 1 个任务</span></div>
|
||||
</div>
|
||||
<p class="hint">可以离开本页,完成后会发站内通知。</p>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h3>最近处理<a class="r lnk" href="#" data-demo>全部记录</a></h3>
|
||||
<ul class="log">
|
||||
<li><span><span class="who">王敏</span> <span class="ok">✓ 已修改</span>「授权委托书签章」</span><span class="t">14:26</span></li>
|
||||
<li><span><span class="who">王敏</span> 确认了清单 v2:智慧大厅</span><span class="t">11-02</span></li>
|
||||
<li><span><span class="who">系统</span> 完成第 2 次检查,致命 3</span><span class="t">11-02</span></li>
|
||||
<li><span><span class="who">系统</span> 并入补遗 1,更新 2 条</span><span class="t">11-02</span></li>
|
||||
<li><span><span class="who">李工</span> 忽略「其他项目名称」:业绩介绍</span><span class="t">11-01</span></li>
|
||||
</ul>
|
||||
</section>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const $ = (s, el = document) => el.querySelector(s);
|
||||
const $$ = (s, el = document) => [...el.querySelectorAll(s)];
|
||||
const fig = (s) => String(s).replace(/(\d[\d:.]*)/g, '<span class="fig">$1</span>');
|
||||
|
||||
function toast(msg, undo) {
|
||||
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.innerHTML = `<span></span>${undo ? "<button type=\"button\">撤销</button>" : ""}`;
|
||||
t.firstChild.textContent = msg;
|
||||
if (undo) t.querySelector("button").onclick = () => { t.remove(); undo(); };
|
||||
box.appendChild(t);
|
||||
setTimeout(() => t.remove(), 5000);
|
||||
}
|
||||
|
||||
/* ---------- 待办队列 ---------- */
|
||||
const queue = $("[data-queue]");
|
||||
const countTodo = () => { $("[data-todo-count]").textContent = $$(".todo:not(.leaving)", queue).length; };
|
||||
|
||||
queue.addEventListener("click", (e) => {
|
||||
const card = e.target.closest(".todo"); if (!card) return;
|
||||
if (e.target.closest("[data-toggle]")) {
|
||||
const b = e.target.closest("[data-toggle]"), open = b.getAttribute("aria-expanded") !== "true";
|
||||
b.setAttribute("aria-expanded", open);
|
||||
b.firstChild.textContent = open ? "收起" : "看看是哪 3 个";
|
||||
$("#" + b.getAttribute("aria-controls")).hidden = !open;
|
||||
return;
|
||||
}
|
||||
if (e.target.closest("[data-cite]")) { e.preventDefault(); toast("(演示)出处会在原文查看器里打开"); return; }
|
||||
if (e.target.closest(".issues")) return;
|
||||
if (e.target.closest("[data-snooze]")) { snooze(card); return; }
|
||||
const demo = card.querySelector("[data-demo-go]");
|
||||
if (!card.dataset.go) { e.preventDefault(); toast(demo ? demo.dataset.demoGo : "(演示)这一页还没做"); return; }
|
||||
if (!e.target.closest("a")) location.href = card.dataset.go;
|
||||
});
|
||||
|
||||
/* 唯一的演示动效:推迟一条待办后,该条淡出、队列上移 */
|
||||
function snooze(card) {
|
||||
const title = card.querySelector("h3").textContent;
|
||||
const h = card.offsetHeight;
|
||||
card.style.height = h + "px";
|
||||
card.classList.add("leaving");
|
||||
card.offsetHeight; // reflow
|
||||
card.style.opacity = "0";
|
||||
card.style.height = "0px";
|
||||
card.style.marginBottom = "-12px";
|
||||
card.style.borderWidth = "0";
|
||||
countTodo();
|
||||
const done = () => { card.hidden = true; card.removeEventListener("transitionend", onEnd); emptyCheck(); };
|
||||
const onEnd = (ev) => { if (ev.propertyName === "height") done(); };
|
||||
card.addEventListener("transitionend", onEnd);
|
||||
if (matchMedia("(prefers-reduced-motion: reduce)").matches) done();
|
||||
toast(`「${title}」已推迟,明天 9:00 再提醒`, () => {
|
||||
card.hidden = false; card.classList.remove("leaving");
|
||||
card.style.height = h + "px"; card.style.borderWidth = ""; card.style.marginBottom = "";
|
||||
card.offsetHeight; card.style.opacity = "";
|
||||
setTimeout(() => { card.style.height = ""; }, 400);
|
||||
countTodo(); emptyCheck();
|
||||
});
|
||||
}
|
||||
function emptyCheck() {
|
||||
let e = $(".queue-done");
|
||||
const left = $$(".todo:not(.leaving)", queue).length;
|
||||
if (!left && !e) { queue.insertAdjacentHTML("afterend", '<div class="card queue-done"><b>今天的待办都安排好了</b>被推迟的事项明天 9:00 会回到这里。</div>'); }
|
||||
if (left && e) e.remove();
|
||||
}
|
||||
|
||||
/* ---------- 项目列表 ---------- */
|
||||
const P = [
|
||||
{ name: "某县数据中心机房改造项目", buyer: "某县大数据管理局", lot: "标段 1", d1: "今天 17:00", d2: "剩 6 小时", cls: "today", st: "编制中", ready: "清单草稿", fatal: 0, next: "确认清单", href: "../page-reading.html" },
|
||||
{ name: "某区智慧社区平台运维服务", buyer: "某区民政局", lot: "全部", d1: "11月5日 16:00", d2: "剩 2 天 5 小时", cls: "soon", st: "已检查", ready: "就绪", fatal: 0, next: "查看检查结果" },
|
||||
{ name: "某市政务服务中心智慧大厅建设项目", buyer: "某市政务服务管理办公室", lot: "采购包 1", d1: "11月20日 09:30", d2: "剩 17 天", cls: "", st: "已检查", ready: "未就绪", fatal: 3, next: "处理致命问题", href: "../page-reading.html" },
|
||||
{ name: "某市医保信息系统等保测评服务", buyer: "某市医疗保障局", lot: "全部", d1: "11月28日 10:00", d2: "剩 25 天", cls: "", st: "跟进中", ready: "补遗解析中", fatal: 0, next: "查看解析进度" },
|
||||
{ name: "某区智慧校园一期", buyer: "某区教育局", lot: "全部", d1: "已截止", d2: "", cls: "past", st: "已递交", ready: "", fatal: 0, next: "查看检查报告" },
|
||||
{ name: "某市不动产登记信息平台升级", buyer: "某市自然资源和规划局", lot: "采购包 2", d1: "已截止", d2: "", cls: "past", st: "已归档", ready: "", fatal: 0, next: "查看项目" }
|
||||
];
|
||||
const DONE = ["已递交", "已归档"];
|
||||
let seg = "active", q = "";
|
||||
|
||||
function status(p) {
|
||||
let r2 = "";
|
||||
if (p.fatal) r2 = `<span class="sev fatal">致命<span class="fig">${p.fatal}</span></span>`;
|
||||
else if (p.ready === "就绪") r2 = '<span class="r2"><span class="ready" tabindex="0" aria-describedby="ready-note" title="只表示致命问题都已处理,不代表投标文件没有问题。">就绪</span></span>';
|
||||
else if (p.ready) r2 = `<span class="r2">${p.ready}</span>`;
|
||||
return `<div class="st"><span>${p.st}</span>${r2}</div>`;
|
||||
}
|
||||
function render() {
|
||||
const rows = P.filter((p) => (seg === "all" || (seg === "active" ? !DONE.includes(p.st) : p.st === seg)) && (!q || (p.name + p.buyer).includes(q)));
|
||||
$("[data-proj-count]").textContent = rows.length;
|
||||
$("[data-rows]").innerHTML = rows.map((p) => `<tr class="row" data-i="${P.indexOf(p)}">
|
||||
<td><a class="pname" href="${p.href || "#"}">${p.name}</a><div class="psub"><span>${p.buyer}</span><span>${fig(p.lot)}</span></div></td>
|
||||
<td>${status(p)}</td>
|
||||
<td class="due ${p.cls}"><span class="d1">${fig(p.d1)}</span>${p.d2 ? `<span class="d2">${p.d2}</span>` : ""}</td>
|
||||
<td><div class="next"><button class="btn sm secondary" data-next>${p.next}</button><button class="icon-btn sm more" aria-label="更多操作:${p.name}" title="更多操作" data-more><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg></button></div></td></tr>`).join("")
|
||||
|| `<tr><td colspan="4"><div class="empty"><b>没有符合条件的项目</b>换个关键词,或切到「全部」看看。</div></td></tr>`;
|
||||
}
|
||||
function setSeg(btn) {
|
||||
seg = btn.dataset.v;
|
||||
$$("[data-tabs] button").forEach((b) => { const on = b === btn; b.setAttribute("aria-selected", on); b.tabIndex = on ? 0 : -1; });
|
||||
render();
|
||||
}
|
||||
$("[data-tabs]").addEventListener("click", (e) => { const b = e.target.closest("button"); if (b) setSeg(b); });
|
||||
$("[data-tabs]").addEventListener("keydown", (e) => {
|
||||
if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
|
||||
const bs = $$("[data-tabs] button"), i = bs.indexOf(document.activeElement);
|
||||
const n = bs[(i + (e.key === "ArrowRight" ? 1 : -1) + bs.length) % bs.length];
|
||||
n.focus(); setSeg(n);
|
||||
});
|
||||
$("[data-q]").addEventListener("input", (e) => { q = e.target.value.trim(); render(); });
|
||||
$("[data-rows]").addEventListener("click", (e) => {
|
||||
const r = e.target.closest("tr.row"); if (!r) return;
|
||||
const p = P[r.dataset.i];
|
||||
if (e.target.closest("[data-more]")) { toast("(演示)更多操作:改状态、归档、删除项目"); return; }
|
||||
if (e.target.closest("[data-next]") && p.href) { location.href = p.href; return; }
|
||||
if (!p.href) { e.preventDefault(); toast(`(演示)会打开「${p.name}」:${p.next}`); return; }
|
||||
if (!e.target.closest("a")) location.href = p.href;
|
||||
});
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
const a = e.target.closest("[data-demo]"); if (!a) return;
|
||||
e.preventDefault(); toast("(演示)这一页还没做");
|
||||
});
|
||||
$("[data-notice]").onclick = () => toast("解析完成:补遗 1 已并入清单(新增 2 条,更新 1 条)。");
|
||||
$("[data-keys]").onclick = () => toast("(演示)快捷键:J / K 逐条,1 / 2 / 3 处理,? 显示说明");
|
||||
|
||||
render();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
638
docs/design/ui-demo/explore/workbench-f.html
Normal file
638
docs/design/ui-demo/explore/workbench-f.html
Normal file
@@ -0,0 +1,638 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
工作台 · 风格探索方案 F「列表 + 详情」
|
||||
主从布局:左边项目列表,点一个项目,右边详情卡立即换成它,不离开页面。和读标 / 查标的「清单 + 原文并排」是同一种结构。
|
||||
内容、数据沿用 ../page-workbench.html 和 ../demo.js;颜色只用 tokens.css 的色值和语义。
|
||||
|
||||
本方案相对 design-system.md 改了这些规则:
|
||||
1. 底色与分隔(§3.1 校样纸底、--c-rule 分隔线):页面、侧栏、顶栏一律白底;浅底和分隔线改用从墨色派生的中性冷灰
|
||||
--n-50 / --n-100 / --n-line(墨 3% / 6% / 11% 混白)。不再使用 --c-paper、--c-sunken、--c-hover、--c-rule。
|
||||
2. 容器(§5.1 层次靠分隔线和底色):整页只有两种容器——页面本身和一种白卡片(圆角 12,1px --n-line,无阴影),
|
||||
白卡片只用于右侧的项目详情。左侧列表、额度、任务都直接放在页面上,不套面板。
|
||||
3. 圆角(§5.1 控件和卡片 2px、浮层 4px):按钮、输入框、列表项、提醒条 8px;标签 4px;详情卡 12px。
|
||||
4. 控件(§7.1):按钮高 32 → 36(行内小按钮 28)。主按钮墨底白字;次按钮白底 + 1px --border-control(≥3:1)。
|
||||
筛选从分段控件改为文字标签页,选中用 2px 墨色下划线。
|
||||
5. 统计(原型的四张统计卡):去掉统计卡,改成页头下一行内联统计,小号文字 + Barlow 数字,每项都是可点击的筛选。
|
||||
6. 项目列表(§7.9 项目行):表格改为列表项。项目名 14/22 600(原 16/24);状态改为副行里的纯文字,
|
||||
详细状态放到详情卡的状态步骤条里。选中项 = --n-50 底 + 左侧 3px 墨色竖条;支持 ↑ / ↓ 切换、Enter 打开。
|
||||
7. 最近处理、待处理的致命问题、正在进行的任务:从全局区块改为跟着选中项目显示在详情卡里;
|
||||
额度移到顶栏(小进度条 + 数字),正在进行的任务在列表下方留一个紧凑区块,点了就选中对应项目。
|
||||
8. 提醒条(§7.12):去掉外框和左竖条,只用赭浅底,圆角 8。
|
||||
9. 截止倒计时:详情卡里用 Barlow 36 大号数字;颜色规则不变(≤72 小时赭,≤24 小时赭加粗,已截止辅助灰)。
|
||||
10. 动效(§10):只有详情切换时一次 120ms 交叉淡入(reduced-motion 下关闭),其余只有悬停 / 按下反馈。
|
||||
问题级别的形状规则、颜色语义、字体分工、最小字号 12、字重 400 / 600 都不变。
|
||||
-->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>工作台 · 标博士</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); /* 发丝线、详情卡边框 */
|
||||
--ink-press: color-mix(in srgb, var(--c-ink) 82%, black);
|
||||
--r-ctl: 8px;
|
||||
--r-tag: 4px;
|
||||
--r-card: 12px;
|
||||
--h-btn: 36px;
|
||||
--h-btn-sm: 28px;
|
||||
--top-h: 56px;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html { -webkit-text-size-adjust: 100%; }
|
||||
body {
|
||||
margin: 0; min-width: 1280px; background: var(--c-white); color: var(--text-primary);
|
||||
font: var(--fw-regular) var(--fs-body)/var(--lh-body) var(--font-sans);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
a { color: inherit; }
|
||||
button, input { font: inherit; color: inherit; }
|
||||
button { cursor: pointer; }
|
||||
p { margin: 0; }
|
||||
.fig { font-family: var(--font-figures); font-variant-numeric: tabular-nums; font-weight: var(--fw-bold); }
|
||||
.cap { font-size: var(--fs-caption); line-height: var(--lh-caption); }
|
||||
.sub { color: var(--text-secondary); }
|
||||
.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
||||
.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; }
|
||||
:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
|
||||
.lnk { color: var(--text-primary); text-decoration: underline; text-decoration-color: var(--border-control); text-underline-offset: 3px; cursor: pointer; background: none; border: 0; padding: 0; }
|
||||
.lnk:hover { text-decoration-color: currentColor; }
|
||||
|
||||
/* ---------- 按钮:主按钮墨底白字;次按钮白底 + 控件边框 ---------- */
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
||||
height: var(--h-btn); padding: 0 16px; border-radius: var(--r-ctl);
|
||||
border: 1px solid var(--border-control); background: var(--c-white); color: var(--text-primary);
|
||||
font-weight: var(--fw-bold); white-space: nowrap; text-decoration: none;
|
||||
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(--text-on-dark); }
|
||||
.btn.primary:hover { background: var(--ink-press); border-color: var(--ink-press); }
|
||||
.btn.sm { height: var(--h-btn-sm); padding: 0 12px; font-weight: var(--fw-regular); }
|
||||
.icon-btn { display: inline-grid; place-items: center; width: 36px; height: 36px; border: 0; border-radius: var(--r-ctl); background: transparent; color: var(--text-secondary); position: relative; }
|
||||
.icon-btn:hover { background: var(--n-50); color: var(--text-primary); }
|
||||
.cost { font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); white-space: nowrap; }
|
||||
.cost .fig { color: var(--text-primary); }
|
||||
|
||||
/* ---------- 标签:一种几何,按级别规则填充 ---------- */
|
||||
.tag { display: inline-flex; align-items: center; gap: 3px; height: 20px; padding: 0 6px; border-radius: var(--r-tag); font-size: var(--fs-caption); line-height: 20px; font-weight: var(--fw-bold); white-space: nowrap; flex: none; }
|
||||
.tag.fatal { background: var(--sev-fatal); color: var(--c-white); }
|
||||
.tag.ready { box-shadow: inset 0 0 0 1px var(--c-ink); color: var(--c-ink); }
|
||||
|
||||
/* ---------- 框架:白色侧栏 + 发丝线 ---------- */
|
||||
.side { position: fixed; inset: 0 auto 0 0; width: var(--sidebar-w); background: var(--c-white); border-right: 1px solid var(--n-line); display: flex; flex-direction: column; padding: 18px 12px 20px; }
|
||||
.side .brand { display: block; padding: 0 8px 26px; }
|
||||
.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(--r-ctl); text-decoration: none; color: var(--text-secondary); }
|
||||
.nav a:hover { background: var(--n-50); color: var(--text-primary); }
|
||||
.nav a[aria-current="page"] { background: var(--n-100); 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: 0 10px; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
|
||||
.side-foot b { display: block; color: var(--text-primary); font-size: var(--fs-body); line-height: var(--lh-body); }
|
||||
|
||||
.main { margin-left: var(--sidebar-w); min-width: 0; }
|
||||
.top { position: sticky; top: 0; z-index: 5; height: var(--top-h); display: flex; align-items: center; gap: 16px; padding: 0 24px 0 32px; background: var(--c-white); border-bottom: 1px solid var(--n-line); }
|
||||
.top .today { color: var(--text-secondary); }
|
||||
.top .right { margin-left: auto; display: flex; align-items: center; gap: 4px; }
|
||||
.quota { display: flex; align-items: center; gap: 18px; height: 36px; padding: 0 12px; margin-right: 8px; border-radius: var(--r-ctl); text-decoration: none; }
|
||||
.quota:hover { background: var(--n-50); }
|
||||
.quota .q { display: flex; align-items: center; gap: 8px; }
|
||||
.quota .q > span:first-child { color: var(--text-secondary); }
|
||||
.meter { width: 56px; height: 4px; border-radius: 2px; background: var(--n-100); overflow: hidden; }
|
||||
.meter i { display: block; height: 100%; background: var(--c-ink); border-radius: 2px; }
|
||||
.meter.warn i { background: var(--warn); }
|
||||
.quota .fig { font-size: 15px; }
|
||||
.quota .of { color: var(--text-secondary); font-family: var(--font-figures); font-variant-numeric: tabular-nums; font-size: var(--fs-caption); }
|
||||
.icon-btn .dot { position: absolute; top: 8px; right: 9px; width: 7px; height: 7px; border-radius: 50%; background: var(--warn); box-shadow: 0 0 0 2px var(--c-white); }
|
||||
|
||||
.content { padding: 28px 32px 48px; }
|
||||
|
||||
/* ---------- 页头 ---------- */
|
||||
.head { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; }
|
||||
h1 { margin: 0; font: var(--fw-bold) 28px/36px var(--font-serif); letter-spacing: .02em; }
|
||||
.stats { display: flex; flex-wrap: wrap; gap: 4px 6px; margin: 6px 0 0 -8px; }
|
||||
.stat { display: inline-flex; align-items: baseline; gap: 4px; height: 32px; padding: 0 8px; border: 0; border-radius: 6px; background: transparent; color: var(--text-secondary); }
|
||||
.stat .fig { font-size: 20px; line-height: 32px; color: var(--text-primary); }
|
||||
.stat .lb { text-decoration: underline; text-decoration-color: var(--n-line); text-underline-offset: 4px; }
|
||||
.stat:hover { background: var(--n-50); color: var(--text-primary); }
|
||||
.stat:hover .lb { text-decoration-color: var(--border-control); }
|
||||
.stat[aria-pressed="true"] { background: var(--n-100); color: var(--text-primary); }
|
||||
.stat[aria-pressed="true"] .lb { text-decoration-color: var(--c-ink); }
|
||||
.head .acts { display: flex; align-items: center; gap: 12px; padding-bottom: 2px; }
|
||||
|
||||
.remind { display: flex; align-items: center; gap: 10px; margin-top: 18px; padding: 8px 8px 8px 14px; border-radius: var(--r-ctl); background: var(--warn-soft); color: var(--warn-text); }
|
||||
.remind .ic { color: var(--warn); }
|
||||
.remind p { flex: 1; min-width: 0; }
|
||||
.remind .btn { border-color: color-mix(in srgb, var(--warn) 55%, white); color: var(--warn-text); }
|
||||
.remind .btn:hover { border-color: var(--warn); background: var(--c-white); }
|
||||
|
||||
/* ---------- 主从两栏 ---------- */
|
||||
.split { display: grid; grid-template-columns: minmax(0, 44fr) minmax(0, 56fr); gap: 32px; align-items: start; margin-top: 24px; }
|
||||
|
||||
/* 左栏:标签页 + 搜索 */
|
||||
.lbar { display: flex; align-items: flex-end; gap: 16px; border-bottom: 1px solid var(--n-line); }
|
||||
.tabs { display: flex; gap: 20px; flex: none; }
|
||||
.tabs button { position: relative; height: 40px; padding: 0 1px; border: 0; background: none; color: var(--text-secondary); white-space: nowrap; }
|
||||
.tabs button .fig { margin-left: 3px; font-weight: var(--fw-regular); }
|
||||
.tabs button:hover { color: var(--text-primary); }
|
||||
.tabs button[aria-selected="true"] { color: var(--text-primary); font-weight: var(--fw-bold); }
|
||||
.tabs button[aria-selected="true"]::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; border-radius: 1px; background: var(--c-ink); }
|
||||
.search { flex: 1; min-width: 132px; position: relative; margin-bottom: 6px; }
|
||||
.search .ic { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--text-secondary); pointer-events: none; }
|
||||
.search input { width: 100%; height: 32px; padding: 0 10px 0 32px; border: 1px solid var(--border-control); border-radius: var(--r-ctl); background: var(--c-white); }
|
||||
.search input::placeholder { color: var(--text-secondary); }
|
||||
.search input:hover { border-color: var(--c-ink); }
|
||||
.search input:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; border-color: var(--c-ink); }
|
||||
.qf { display: flex; align-items: center; gap: 12px; padding: 10px 0 0; color: var(--text-secondary); }
|
||||
.qf[hidden] { display: none; }
|
||||
|
||||
/* 左栏:列表项 */
|
||||
.plist { margin: 8px 0 0 -16px; padding: 0; list-style: none; outline: none; }
|
||||
.pitem {
|
||||
position: relative; display: grid; grid-template-columns: minmax(0, 1fr) auto 28px; gap: 2px 12px; align-items: center;
|
||||
padding: 12px 6px 12px 16px; border-radius: var(--r-ctl); cursor: pointer;
|
||||
transition: background-color var(--dur-fast) var(--ease);
|
||||
}
|
||||
.pitem + .pitem { margin-top: 2px; }
|
||||
.pitem:hover, .pitem[aria-selected="true"] { background: var(--n-50); }
|
||||
.pitem[aria-selected="true"]::before { content: ""; position: absolute; left: 0; top: 10px; bottom: 10px; width: 3px; border-radius: 2px; background: var(--c-ink); }
|
||||
.pitem:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
|
||||
.pitem .nm { font-weight: var(--fw-bold); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.pitem .meta { grid-column: 1; display: flex; align-items: center; gap: 10px; min-width: 0; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
|
||||
.pitem .meta .by { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
||||
.pitem .meta span { flex: none; }
|
||||
.pitem .meta .by { flex: 0 1 auto; }
|
||||
.pitem .busy { display: inline-flex; align-items: center; gap: 4px; color: var(--text-primary); }
|
||||
.pitem .due { grid-column: 2; grid-row: 1; text-align: right; white-space: nowrap; font-family: var(--font-figures); font-variant-numeric: tabular-nums; font-weight: var(--fw-bold); font-size: 15px; }
|
||||
.pitem .left { grid-column: 2; grid-row: 2; text-align: right; white-space: nowrap; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
|
||||
.pitem .go { grid-column: 3; grid-row: 1 / span 2; width: 28px; height: 28px; display: grid; place-items: center; border: 0; border-radius: 6px; background: transparent; color: var(--text-secondary); opacity: 0; }
|
||||
.pitem:hover .go, .pitem[aria-selected="true"] .go, .pitem .go:focus-visible { opacity: 1; }
|
||||
.pitem .go:hover { background: var(--c-white); color: var(--text-primary); box-shadow: inset 0 0 0 1px var(--border-control); }
|
||||
.t-soon { color: var(--warn-text); }
|
||||
.t-today { color: var(--warn-text); }
|
||||
.pitem .left.t-soon, .pitem .left.t-today { color: var(--warn-text); font-weight: var(--fw-bold); }
|
||||
.t-past { color: var(--text-secondary); }
|
||||
.empty { padding: 32px 16px; text-align: center; color: var(--text-secondary); }
|
||||
.empty b { display: block; color: var(--text-primary); margin-bottom: 4px; }
|
||||
.lhint { margin-top: 10px; color: var(--text-secondary); }
|
||||
.kbd { display: inline-block; min-width: 20px; height: 20px; padding: 0 5px; border-radius: 4px; box-shadow: inset 0 0 0 1px var(--n-line); background: var(--c-white); text-align: center; font-size: var(--fs-caption); line-height: 20px; color: var(--text-primary); }
|
||||
|
||||
/* 左栏底部:正在进行的任务 */
|
||||
.tasks { margin-top: 24px; padding-top: 16px; border-top: 1px solid var(--n-line); }
|
||||
.tasks h2, .sec h3 { margin: 0; font-size: var(--fs-body); line-height: var(--lh-body); font-weight: var(--fw-bold); }
|
||||
.tasks h2 { display: flex; align-items: baseline; gap: 8px; }
|
||||
.task { display: grid; grid-template-columns: 20px minmax(0, 1fr) auto; gap: 0 10px; align-items: center; width: calc(100% + 16px); margin: 6px 0 0 -16px; padding: 8px 10px 8px 16px; border: 0; border-radius: var(--r-ctl); background: none; text-align: left; }
|
||||
.task:hover { background: var(--n-50); }
|
||||
.task .ic { color: var(--text-secondary); }
|
||||
.task .t2 { grid-column: 2 / span 2; }
|
||||
.bar { height: 4px; border-radius: 2px; background: var(--n-100); overflow: hidden; }
|
||||
.bar i { display: block; height: 100%; background: var(--c-ink); border-radius: 2px; }
|
||||
.task .bar { grid-column: 2 / span 2; margin-top: 6px; }
|
||||
|
||||
/* ---------- 右栏:详情卡(唯一的卡片) ---------- */
|
||||
.detail { position: sticky; top: calc(var(--top-h) + 16px); border: 1px solid var(--n-line); border-radius: var(--r-card); background: var(--c-white); }
|
||||
.sec { padding: 20px 24px; }
|
||||
.sec + .sec { border-top: 1px solid var(--n-line); }
|
||||
.dh { display: flex; align-items: flex-start; gap: 16px; padding-bottom: 18px; }
|
||||
.dh h2 { margin: 0; font: var(--fw-bold) var(--fs-h2)/var(--lh-h2) var(--font-serif); }
|
||||
.dh .who { margin-top: 2px; display: flex; gap: 12px; color: var(--text-secondary); }
|
||||
.dh .btn { margin-left: auto; flex: none; }
|
||||
|
||||
/* 状态步骤条 */
|
||||
.steps { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin: 18px 0 0; padding: 0; list-style: none; }
|
||||
.steps li { position: relative; padding-top: 18px; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); }
|
||||
.steps li::before { content: ""; position: absolute; left: 0; top: 4px; width: 10px; height: 10px; border-radius: 50%; box-sizing: border-box; border: 1.5px solid var(--border-control); background: var(--c-white); z-index: 1; }
|
||||
.steps li::after { content: ""; position: absolute; left: 14px; right: 4px; top: 8.5px; height: 1.5px; background: var(--n-line); }
|
||||
.steps li:last-child::after { display: none; }
|
||||
.steps li.done { color: var(--text-primary); }
|
||||
.steps li.done::before { background: var(--c-ink); border-color: var(--c-ink); }
|
||||
.steps li.done::after { background: var(--c-ink); }
|
||||
.steps li.cur { color: var(--text-primary); font-weight: var(--fw-bold); }
|
||||
.steps li.cur::before { background: var(--c-ink); border-color: var(--c-ink); box-shadow: 0 0 0 3px var(--n-100); }
|
||||
.steps li .when { display: block; font-weight: var(--fw-regular); color: var(--text-secondary); font-family: var(--font-figures); font-variant-numeric: tabular-nums; }
|
||||
|
||||
/* 截止 + 下一步 */
|
||||
.dn { display: grid; grid-template-columns: minmax(150px, 34%) minmax(0, 1fr); gap: 24px; }
|
||||
.lbl { font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-secondary); margin-bottom: 4px; }
|
||||
.cd { display: flex; align-items: baseline; gap: 4px; flex-wrap: wrap; }
|
||||
.cd .n { font-family: var(--font-figures); font-variant-numeric: tabular-nums; font-weight: var(--fw-bold); font-size: 44px; line-height: 48px; }
|
||||
.cd .u { margin-right: 6px; }
|
||||
.cd.t-today .u, .cd.t-soon .u { color: var(--warn-text); }
|
||||
.cd .past { font-size: 20px; line-height: 48px; font-weight: var(--fw-bold); color: var(--text-secondary); }
|
||||
.cd-at { margin-top: 2px; color: var(--text-secondary); }
|
||||
.cd-at .fig { font-weight: var(--fw-regular); }
|
||||
.t-today .n, .t-soon .n { color: var(--warn); }
|
||||
.situ { margin-bottom: 12px; }
|
||||
.situ .tag { margin-right: 6px; vertical-align: 1px; }
|
||||
.acts-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; }
|
||||
.note2 { margin-top: 10px; color: var(--text-secondary); display: flex; gap: 6px; align-items: flex-start; }
|
||||
.note2 .ic { margin-top: 2px; }
|
||||
|
||||
/* 详情:致命问题 */
|
||||
.sec-h { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
|
||||
.sec-h .more { margin-left: auto; }
|
||||
.issues { list-style: none; margin: 0 -12px; padding: 0; }
|
||||
.issue { position: relative; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 0 10px; align-items: start; padding: 10px 12px; border-radius: var(--r-ctl); }
|
||||
.issue + .issue { margin-top: 2px; }
|
||||
.issue:hover { background: var(--n-50); }
|
||||
.issue .tag { margin-top: 1px; }
|
||||
.issue b { display: block; font-weight: var(--fw-bold); }
|
||||
.issue .d { color: var(--text-secondary); }
|
||||
.issue .src { display: inline-flex; align-items: center; gap: 4px; margin-top: 4px; color: var(--ai); text-decoration: none; border-radius: 4px; }
|
||||
.issue .src .ic { width: 14px; height: 14px; }
|
||||
.issue .src:hover { color: var(--ai-strong); text-decoration: underline; text-underline-offset: 3px; }
|
||||
.issue .src .fig { font-weight: var(--fw-regular); }
|
||||
.issue .btn { opacity: 0; align-self: center; }
|
||||
.issue:hover .btn, .issue .btn:focus-visible { opacity: 1; }
|
||||
.none { color: var(--text-secondary); }
|
||||
|
||||
/* 详情:清单进度 + 最近处理 */
|
||||
.two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 24px; }
|
||||
.prog { display: flex; align-items: baseline; gap: 6px; margin: 6px 0 8px; }
|
||||
.prog .n { font-family: var(--font-figures); font-variant-numeric: tabular-nums; font-weight: var(--fw-bold); font-size: 20px; line-height: 28px; }
|
||||
.signed { color: var(--confirmed); }
|
||||
.log { list-style: none; margin: 6px 0 0; padding: 0; }
|
||||
.log li { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; padding: 5px 0; font-size: var(--fs-caption); line-height: var(--lh-caption); }
|
||||
.log li + li { border-top: 1px solid var(--n-line); }
|
||||
.log .w { font-weight: var(--fw-bold); }
|
||||
.log .ok { color: var(--confirmed); }
|
||||
.log time { color: var(--text-secondary); font-family: var(--font-figures); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
||||
.dempty { padding: 64px 24px; text-align: center; color: var(--text-secondary); }
|
||||
|
||||
/* ---------- 轻提示与悬停说明 ---------- */
|
||||
.toasts { position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%); z-index: 20; 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(--text-on-dark); box-shadow: var(--shadow-overlay); }
|
||||
[data-tip] { position: relative; }
|
||||
[data-tip]:hover::after, [data-tip]:focus-visible::after {
|
||||
content: attr(data-tip); position: absolute; z-index: 10; top: calc(100% + 6px); left: 50%; transform: translateX(-50%);
|
||||
width: max-content; max-width: 260px; padding: 6px 10px; border-radius: 6px; background: var(--c-ink); color: var(--text-on-dark);
|
||||
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-l:hover::after, [data-tip].tip-l:focus-visible::after { left: auto; right: 0; transform: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<aside class="side">
|
||||
<a class="brand" href="workbench-f.html"><img src="../../logo/svg/logo-horizontal.svg" alt="标博士"></a>
|
||||
<nav class="nav" aria-label="主导航">
|
||||
<a href="workbench-f.html" aria-current="page"><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><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"/></svg>工作台</a>
|
||||
<a href="#" data-demo="资质库"><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 9h10M7 13h6"/><circle cx="16" cy="15" r="2"/></svg>资质库</a>
|
||||
<a href="#" data-demo="额度"><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/></svg>额度</a>
|
||||
<div class="grp">UI demo</div>
|
||||
<a href="index.html"><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><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"/></svg>方案目录</a>
|
||||
</nav>
|
||||
<div class="side-foot"><b>王敏</b>南京某信息技术有限公司<br>专业版,至 2027-10-31</div>
|
||||
</aside>
|
||||
|
||||
<div class="main">
|
||||
<header class="top">
|
||||
<span class="today">11月3日 周二</span>
|
||||
<div class="right">
|
||||
<a class="quota tip-l" href="#" data-demo="额度" data-tip="本期剩余额度,至 2027-10-31。点开查看扣点规则和流水。">
|
||||
<span class="q"><span>解析</span><span class="meter" aria-hidden="true"><i style="width:26%"></i></span><span><span class="fig">37</span><span class="of"> / 50</span></span></span>
|
||||
<span class="q"><span>检查</span><span class="meter" aria-hidden="true"><i style="width:40%"></i></span><span><span class="fig">12</span><span class="of"> / 20</span></span></span>
|
||||
</a>
|
||||
<button class="icon-btn tip-l" aria-label="通知" data-tip="通知" data-notice><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><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"/></svg><span class="dot"></span></button>
|
||||
<button class="icon-btn tip-l" aria-label="快捷键说明" data-tip="列表里按 ↑ ↓ 切换项目,Enter 打开" data-keys><svg class="ic lg" viewBox="0 0 24 24" aria-hidden="true"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.01M10 8h.01M14 8h.01M18 8h.01M8 12h.01M12 12h.01M16 12h.01M7 16h10"/></svg></button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="content">
|
||||
<div class="head">
|
||||
<div>
|
||||
<h1>工作台</h1>
|
||||
<div class="stats" role="group" aria-label="快速筛选">
|
||||
<button class="stat" data-stat="active" aria-pressed="false"><span class="fig">4</span><span class="lb">个进行中</span></button>
|
||||
<button class="stat" data-stat="due7" aria-pressed="false"><span class="fig">2</span><span class="lb">个 7 天内截止</span></button>
|
||||
<button class="stat" data-stat="fatal" aria-pressed="false"><span class="fig">3</span><span class="lb">个致命问题待处理</span></button>
|
||||
<button class="stat" data-stat="pend" aria-pressed="false"><span class="fig">4</span><span class="lb">条清单待确认</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="acts"><span class="cost">扣 <span class="fig">1</span> 次解析,剩 <span class="fig">37</span> 次</span><button class="btn primary" data-new><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14"/><path d="M12 5v14"/></svg>新建项目</button></div>
|
||||
</div>
|
||||
|
||||
<div class="remind" role="status">
|
||||
<svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
|
||||
<p><b>某县数据中心机房改造项目今天 17:00 截止。</b>清单还是草稿,还没有做过检查。</p>
|
||||
<button class="btn sm" data-pick="room">查看项目</button>
|
||||
</div>
|
||||
|
||||
<div class="split">
|
||||
<section aria-label="项目列表">
|
||||
<div class="lbar">
|
||||
<div class="tabs" role="tablist" aria-label="按状态筛选">
|
||||
<button role="tab" data-tab="active" aria-selected="true">进行中<span class="fig">4</span></button>
|
||||
<button role="tab" data-tab="all" aria-selected="false" tabindex="-1">全部<span class="fig">6</span></button>
|
||||
<button role="tab" data-tab="已递交" aria-selected="false" tabindex="-1">已递交<span class="fig">1</span></button>
|
||||
<button role="tab" data-tab="已归档" aria-selected="false" tabindex="-1">已归档<span class="fig">1</span></button>
|
||||
</div>
|
||||
<label class="search"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg><input type="search" placeholder="搜索项目" aria-label="搜索项目名称或采购人" title="可以搜项目名称或采购人" data-q></label>
|
||||
</div>
|
||||
<div class="qf cap" data-qf hidden><span data-qf-text></span><button class="lnk cap" data-qf-clear>清除筛选</button></div>
|
||||
<ul class="plist" role="listbox" aria-label="项目" data-list></ul>
|
||||
<p class="lhint cap"><span class="kbd">↑</span> <span class="kbd">↓</span> 切换项目,<span class="kbd">Enter</span> 打开</p>
|
||||
|
||||
<div class="tasks">
|
||||
<h2>正在进行<span class="cap sub" style="font-weight:400">可以离开本页,完成后会发站内通知</span></h2>
|
||||
<button class="task" data-pick="med">
|
||||
<svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M21 12a9 9 0 1 1-6.22-8.56"/></svg>
|
||||
<span><b>解析补遗 2</b><span class="sub cap"> 提取清单</span></span><span class="fig">62%</span>
|
||||
<span class="t2 cap sub">某市医保信息系统等保测评服务</span>
|
||||
<span class="bar"><i style="width:62%"></i></span>
|
||||
</button>
|
||||
<button class="task" data-pick="comm">
|
||||
<svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
|
||||
<span><b>检查排队中</b><span class="sub cap"> 前面还有 1 个任务</span></span><span></span>
|
||||
<span class="t2 cap sub">某区智慧社区平台运维服务</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="detail" aria-live="polite" aria-label="项目详情" data-detail></section>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const $ = (s, el = document) => el.querySelector(s);
|
||||
const $$ = (s, el = document) => [...el.querySelectorAll(s)];
|
||||
const IC = {
|
||||
open: '<path d="M7 7h10v10"/><path d="M7 17 17 7"/>',
|
||||
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"/>',
|
||||
spin: '<path d="M21 12a9 9 0 1 1-6.22-8.56"/>',
|
||||
clock: '<circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/>',
|
||||
info: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/>'
|
||||
};
|
||||
const ic = (n, cls = "") => `<svg class="ic ${cls}" viewBox="0 0 24 24" aria-hidden="true">${IC[n]}</svg>`;
|
||||
const fig = (n) => `<span class="fig">${n}</span>`;
|
||||
|
||||
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(), 3200);
|
||||
}
|
||||
|
||||
/* 状态序号:跟进中 0 → 编制中 1 → 已检查 2 → 已递交 3;已归档 = 全部完成 */
|
||||
const STEPS = ["跟进中", "编制中", "已检查", "已递交"];
|
||||
const CHECK_NOTE = `扣 ${fig(1)} 次检查,剩 ${fig(12)} 次`;
|
||||
|
||||
const P = [
|
||||
{ id: "room", name: "某县数据中心机房改造项目", buyer: "某县大数据管理局", lot: "标段 1", h: 6, st: "编制中", fatal: 0, pend: 3,
|
||||
due: "今天 17:00", left: "剩 6 小时", tone: "t-today",
|
||||
cd: [["6", "小时"]], at: `今天 ${fig("17:00")} 截止`,
|
||||
stepAt: ["10-28", "11-01", "", ""],
|
||||
situ: `清单还是草稿,还有 ${fig(3)} 条待确认。`,
|
||||
acts: [{ t: "确认清单", p: 1, href: "../page-reading.html" }, { t: "发起检查", note: CHECK_NOTE }],
|
||||
note: `${ic("info")}<span>投标文件已上传。先确认清单再检查,检查会用到这些条目。</span>`,
|
||||
issuesEmpty: "还没有做过检查。检查后,致命问题会列在这里。",
|
||||
list: { done: 39, total: 42, line: `还有 ${fig(3)} 条待确认` },
|
||||
log: [["李工", "上传投标文件(3 册)", "09:40"], ["李工", "确认了 39 条清单条目", "11-02"], ["系统", "解析完成,提取清单 42 条", "11-01"], ["李工", "新建项目,上传招标文件", "11-01"]] },
|
||||
|
||||
{ id: "comm", name: "某区智慧社区平台运维服务", buyer: "某区民政局", lot: "全部", h: 53, st: "已检查", ready: true, fatal: 0, pend: 0,
|
||||
due: "11月5日 16:00", left: "剩 2 天 5 小时", tone: "t-soon", busy: "检查排队中",
|
||||
cd: [["2", "天"], ["5", "小时"]], at: `11月5日 ${fig("16:00")} 截止`,
|
||||
stepAt: ["10-20", "10-24", "11-01", ""],
|
||||
situ: `<span class="tag ready" data-tip="只表示致命问题都已处理,不代表投标文件没有问题。" tabindex="0">就绪</span>第 1 次检查的致命问题都已处理。`,
|
||||
acts: [{ t: "标记已递交", p: 1 }, { t: "查看检查结果", href: "../page-reading.html" }],
|
||||
note: `${ic("clock")}<span>修改后的投标文件正在排队复查,前面还有 ${fig(1)} 个任务。复查完成前也可以标记递交。</span>`,
|
||||
issuesEmpty: "第 1 次检查的致命问题都已处理。复查结果出来后会更新。",
|
||||
list: { done: 38, total: 38, sign: "李工 确认了清单", when: "10-24" },
|
||||
log: [["系统", "第 2 次检查排队中", "09:53"], ["李工", "上传投标文件 v3", "09:52"], ["李工", '<span class="ok">✓ 已修改</span>「业绩证明材料页码」', "11-02"], ["系统", "完成第 1 次检查,致命 1", "11-01"]] },
|
||||
|
||||
{ id: "hall", name: "某市政务服务中心智慧大厅建设项目", buyer: "某市政务服务管理办公室", lot: "采购包 1", h: 408, st: "已检查", ready: false, fatal: 3, pend: 0,
|
||||
due: "11月20日 09:30", left: "剩 17 天", tone: "",
|
||||
cd: [["17", "天"]], at: `11月20日 ${fig("09:30")} 截止`,
|
||||
stepAt: ["10-15", "10-22", "11-02", ""],
|
||||
situ: `未就绪:${fig(3)} 项致命问题待处理。`,
|
||||
acts: [{ t: "处理致命问题", p: 1, href: "../page-reading.html" }, { t: "导出检查报告" }],
|
||||
note: `${ic("info")}<span>致命问题没处理完也可以导出,报告会如实列出这 ${fig(3)} 项。</span>`,
|
||||
issues: [
|
||||
["报价超过采购包 1 的最高限价", "开标一览表 287.60 万元,限价 286.00 万元", "报价册", 1],
|
||||
["★ 条款疑似不满足:应用服务器内存", "响应 256G,要求 ≥ 512G。上次标记为已修改,问题仍在", "技术册", 12],
|
||||
["授权委托书的单位签章位置未发现盖章", "只看有无,不鉴定真伪", "商务册", 5]
|
||||
],
|
||||
list: { done: 46, total: 46, sign: "王敏 确认了清单 v2", when: "11-02" },
|
||||
log: [["王敏", '<span class="ok">✓ 已修改</span>「授权委托书签章」', "14:26"], ["王敏", "确认了清单 v2", "11-02"], ["系统", "完成第 2 次检查,致命 3", "11-02"], ["系统", "并入补遗 1,更新 2 条", "11-02"], ["李工", "忽略「其他项目名称」:业绩介绍", "11-01"]] },
|
||||
|
||||
{ id: "med", name: "某市医保信息系统等保测评服务", buyer: "某市医疗保障局", lot: "全部", h: 600, st: "跟进中", fatal: 0, pend: 1,
|
||||
due: "11月28日 10:00", left: "剩 25 天", tone: "", busy: "解析中 62%",
|
||||
cd: [["25", "天"]], at: `11月28日 ${fig("10:00")} 截止`,
|
||||
stepAt: ["10-29", "", "", ""],
|
||||
situ: `清单还有 ${fig(1)} 条待确认;还没有上传投标文件。`,
|
||||
acts: [{ t: "确认清单", p: 1, href: "../page-reading.html" }, { t: "上传投标文件" }],
|
||||
note: `${ic("spin")}<span>补遗 2 正在解析(${fig("62%")}),完成后清单可能新增或更新条目。</span>`,
|
||||
issuesEmpty: "还没有做过检查。上传投标文件并检查后,致命问题会列在这里。",
|
||||
list: { done: 35, total: 36, line: `还有 ${fig(1)} 条待确认` },
|
||||
log: [["李工", "上传补遗 2", "10:12"], ["李工", "确认了 35 条清单条目", "10-30"], ["系统", "解析完成,提取清单 36 条", "10-29"], ["李工", "新建项目,上传招标文件", "10-29"]] },
|
||||
|
||||
{ id: "camp", name: "某区智慧校园一期", buyer: "某区教育局", lot: "全部", h: -1, st: "已递交", fatal: 0, pend: 0,
|
||||
due: "11月1日 09:30", left: "已截止", tone: "t-past",
|
||||
past: true, at: `11月1日 ${fig("09:30")} 截止`,
|
||||
stepAt: ["10-08", "10-12", "10-29", "10-31"],
|
||||
situ: "已递交,等开标结果。",
|
||||
acts: [{ t: "归档项目", p: 1 }, { t: "导出检查报告" }],
|
||||
note: "",
|
||||
issuesEmpty: "最后一次检查(10-29)的致命问题都已处理。",
|
||||
list: { done: 40, total: 40, sign: "王敏 确认了清单", when: "10-12" },
|
||||
log: [["李工", "标记已递交", "10-31"], ["王敏", "导出检查报告", "10-30"], ["系统", "完成第 2 次检查,致命 0", "10-29"]] },
|
||||
|
||||
{ id: "land", name: "某市不动产登记信息平台升级", buyer: "某市自然资源和规划局", lot: "采购包 2", h: -2, st: "已归档", fatal: 0, pend: 0,
|
||||
due: "10月20日 09:00", left: "已截止", tone: "t-past",
|
||||
past: true, at: `10月20日 ${fig("09:00")} 截止`,
|
||||
stepAt: ["09-21", "09-26", "10-16", "10-19"],
|
||||
situ: "已归档。归档的项目只读,资料可以复用到新项目。",
|
||||
acts: [{ t: "查看检查报告", p: 1 }, { t: "取消归档" }],
|
||||
note: "",
|
||||
issuesEmpty: "最后一次检查(10-16)的致命问题都已处理。",
|
||||
list: { done: 51, total: 51, sign: "王敏 确认了清单", when: "09-26" },
|
||||
log: [["王敏", "归档项目", "10-28"], ["李工", "标记已递交", "10-19"], ["系统", "完成第 1 次检查,致命 0", "10-16"]] }
|
||||
];
|
||||
|
||||
const QF = {
|
||||
active: { t: "进行中的项目" },
|
||||
due7: { t: "只看 7 天内截止的项目", f: (p) => p.h >= 0 && p.h <= 168 },
|
||||
fatal: { t: "只看有待处理致命问题的项目", f: (p) => p.fatal > 0 },
|
||||
pend: { t: "只看有待确认清单条目的项目", f: (p) => p.pend > 0 }
|
||||
};
|
||||
let tab = "active", quick = "", q = "";
|
||||
const hashSel = (location.hash.match(/p=(\w+)/) || [])[1];
|
||||
let sel = P.some((p) => p.id === hashSel) ? hashSel : "room";
|
||||
|
||||
const isActive = (p) => !["已递交", "已归档"].includes(p.st);
|
||||
function visible() {
|
||||
return P.filter((p) => {
|
||||
if (quick && QF[quick].f) { if (!QF[quick].f(p)) return false; }
|
||||
else if (!(tab === "all" || (tab === "active" ? isActive(p) : p.st === tab))) return false;
|
||||
return !q || (p.name + p.buyer).includes(q);
|
||||
});
|
||||
}
|
||||
|
||||
function renderList() {
|
||||
const rows = visible();
|
||||
if (rows.length && !rows.some((p) => p.id === sel)) sel = rows[0].id;
|
||||
$("[data-list]").innerHTML = rows.map((p) => `
|
||||
<li class="pitem" role="option" id="pi-${p.id}" data-id="${p.id}" aria-selected="${p.id === sel}" tabindex="${p.id === sel ? 0 : -1}">
|
||||
<span class="nm" title="${p.name}">${p.name}</span>
|
||||
<span class="due ${p.tone}">${p.due}</span>
|
||||
<span class="meta"><span class="by" title="${p.buyer}">${p.buyer}</span><span>${p.lot}</span><span>${p.st}</span>${p.busy ? `<span class="busy">${ic(p.busy.startsWith("解析") ? "spin" : "clock")}${p.busy}</span>` : ""}${p.fatal ? `<span class="tag fatal">致命 ${fig(p.fatal)}</span>` : ""}</span>
|
||||
<span class="left ${p.tone}">${p.left}</span>
|
||||
<button class="go" tabindex="-1" aria-label="打开项目" data-open="${p.id}">${ic("open")}</button>
|
||||
</li>`).join("") || `<li class="empty"><b>没有符合条件的项目</b>换个关键词,或切到「全部」看看。</li>`;
|
||||
const qf = $("[data-qf]");
|
||||
qf.hidden = !(quick && QF[quick].f);
|
||||
if (!qf.hidden) $("[data-qf-text]").textContent = `${QF[quick].t},共 ${rows.length} 个`;
|
||||
$$("[data-stat]").forEach((b) => b.setAttribute("aria-pressed", b.dataset.stat === quick));
|
||||
return rows;
|
||||
}
|
||||
|
||||
function renderDetail(fade) {
|
||||
const box = $("[data-detail]");
|
||||
const p = P.find((x) => x.id === sel);
|
||||
if (!p || !visible().some((x) => x.id === sel)) { box.innerHTML = `<div class="dempty">在左侧选一个项目,详情会显示在这里。</div>`; return; }
|
||||
const si = p.st === "已归档" ? 4 : STEPS.indexOf(p.st);
|
||||
const steps = STEPS.map((s, i) => `<li class="${i < si ? "done" : i === si ? "cur" : ""}"${i === si ? ' aria-current="step"' : ""}>${s}<span class="when">${p.stepAt[i] || " "}</span></li>`).join("");
|
||||
const cd = p.past ? `<div class="cd"><span class="past">已截止</span></div>`
|
||||
: `<div class="cd ${p.tone}">${p.cd.map(([n, u]) => `<span class="n">${n}</span><span class="u">${u}</span>`).join("")}</div>`;
|
||||
const acts = p.acts.map((a) => `<button class="btn ${a.p ? "primary" : ""}" data-act="${a.t}"${a.href ? ` data-href="${a.href}"` : ""}>${a.t}</button>${a.note ? `<span class="cost">${a.note}</span>` : ""}`).join("");
|
||||
const issues = p.issues
|
||||
? `<ul class="issues">${p.issues.map(([t, d, book, pg]) => `<li class="issue">
|
||||
<span class="tag fatal">致命</span>
|
||||
<div><b>${t}</b><span class="d cap">${d}</span><br><a class="src cap" href="#" data-src="${book} 第 ${pg} 页">${ic("file")}${book} 第 ${fig(pg)} 页</a></div>
|
||||
<button class="btn sm" data-href="../page-reading.html">去处理</button></li>`).join("")}</ul>`
|
||||
: `<p class="none">${p.issuesEmpty}</p>`;
|
||||
const pct = Math.round((p.list.done / p.list.total) * 100);
|
||||
const listLine = p.list.sign ? `<span class="signed">✓ ${p.list.sign}</span> <span class="sub fig" style="font-weight:400">${p.list.when}</span>` : `<span class="t-today">${p.list.line}</span>`;
|
||||
|
||||
box.innerHTML = `
|
||||
<div class="sec">
|
||||
<div class="dh">
|
||||
<div style="min-width:0"><h2>${p.name}</h2><div class="who cap"><span>${p.buyer}</span><span>${p.lot}</span></div></div>
|
||||
<button class="btn" data-open="${p.id}">${ic("open")}打开项目</button>
|
||||
</div>
|
||||
<ol class="steps" aria-label="项目状态">${steps}</ol>
|
||||
</div>
|
||||
<div class="sec dn">
|
||||
<div><div class="lbl">投标截止</div>${cd}<div class="cd-at cap">${p.at}</div></div>
|
||||
<div><div class="lbl">下一步</div><p class="situ">${p.situ}</p><div class="acts-row">${acts}</div>${p.note ? `<p class="note2 cap">${p.note}</p>` : ""}</div>
|
||||
</div>
|
||||
<div class="sec">
|
||||
<div class="sec-h"><h3>待处理的致命问题</h3>${p.fatal ? `<span class="tag fatal">致命 ${fig(p.fatal)}</span><a class="lnk cap more" href="../page-reading.html">去查标页处理</a>` : ""}</div>
|
||||
${issues}
|
||||
</div>
|
||||
<div class="sec two">
|
||||
<div><h3>清单确认</h3>
|
||||
<div class="prog"><span class="n">${p.list.done}</span><span class="sub">/ ${fig(p.list.total)} 条</span></div>
|
||||
<div class="bar" role="progressbar" aria-valuenow="${pct}" aria-valuemin="0" aria-valuemax="100" aria-label="清单确认进度"><i style="width:${pct}%"></i></div>
|
||||
<p class="cap" style="margin-top:8px">${listLine}</p>
|
||||
</div>
|
||||
<div><div class="sec-h" style="margin-bottom:0"><h3>最近处理</h3><a class="lnk cap more" href="#" data-demo="全部记录">全部记录</a></div>
|
||||
<ul class="log">${p.log.map(([w, t, tm]) => `<li><span><span class="w">${w}</span> ${t}</span><time>${tm}</time></li>`).join("")}</ul>
|
||||
</div>
|
||||
</div>`;
|
||||
if (fade && !matchMedia("(prefers-reduced-motion: reduce)").matches) box.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 120, easing: "ease-out" });
|
||||
}
|
||||
|
||||
function select(id, { focus = false, fade = true } = {}) {
|
||||
if (id === sel && !focus) return;
|
||||
const changed = id !== sel;
|
||||
sel = id;
|
||||
$$(".pitem").forEach((li) => { const on = li.dataset.id === id; li.setAttribute("aria-selected", on); li.tabIndex = on ? 0 : -1; });
|
||||
const li = $(`#pi-${id}`);
|
||||
if (li && focus) { li.focus(); li.scrollIntoView({ block: "nearest" }); }
|
||||
if (changed) renderDetail(fade);
|
||||
}
|
||||
function openProject(id) {
|
||||
const p = P.find((x) => x.id === id);
|
||||
if (id === "room" || id === "hall") location.href = "../page-reading.html";
|
||||
else toast(`(演示)打开「${p.name}」:只做了「读标」组合页`);
|
||||
}
|
||||
function rerender() { renderList(); renderDetail(true); }
|
||||
|
||||
/* 列表:点击选中、↑↓ 切换、Enter 打开 */
|
||||
const list = $("[data-list]");
|
||||
list.addEventListener("click", (e) => {
|
||||
const go = e.target.closest("[data-open]");
|
||||
if (go) { e.stopPropagation(); return openProject(go.dataset.open); }
|
||||
const li = e.target.closest(".pitem"); if (li) select(li.dataset.id);
|
||||
});
|
||||
list.addEventListener("dblclick", (e) => { const li = e.target.closest(".pitem"); if (li) openProject(li.dataset.id); });
|
||||
list.addEventListener("keydown", (e) => {
|
||||
const items = $$(".pitem"); const i = items.findIndex((x) => x.dataset.id === sel);
|
||||
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
const j = Math.max(0, Math.min(items.length - 1, i + (e.key === "ArrowDown" ? 1 : -1)));
|
||||
if (items[j]) select(items[j].dataset.id, { focus: true });
|
||||
} else if (e.key === "Home" || e.key === "End") {
|
||||
e.preventDefault(); const it = items[e.key === "Home" ? 0 : items.length - 1]; if (it) select(it.dataset.id, { focus: true });
|
||||
} else if (e.key === "Enter") { e.preventDefault(); openProject(sel); }
|
||||
});
|
||||
|
||||
/* 标签页、统计筛选、搜索 */
|
||||
function setTab(v) {
|
||||
tab = v; quick = "";
|
||||
$$("[data-tab]").forEach((b) => { const on = b.dataset.tab === v; b.setAttribute("aria-selected", on); b.tabIndex = on ? 0 : -1; });
|
||||
rerender();
|
||||
}
|
||||
$$("[data-tab]").forEach((b) => b.addEventListener("click", () => setTab(b.dataset.tab)));
|
||||
$(".tabs").addEventListener("keydown", (e) => {
|
||||
const tabs = $$("[data-tab]"); const i = tabs.indexOf(document.activeElement); if (i < 0) return;
|
||||
const d = e.key === "ArrowRight" ? 1 : e.key === "ArrowLeft" ? -1 : 0; if (!d) return;
|
||||
e.preventDefault(); const t = tabs[(i + d + tabs.length) % tabs.length]; t.focus(); setTab(t.dataset.tab);
|
||||
});
|
||||
$$("[data-stat]").forEach((b) => b.addEventListener("click", () => {
|
||||
const v = b.dataset.stat;
|
||||
if (v === "active") { setTab("active"); return; }
|
||||
quick = quick === v ? "" : v;
|
||||
$$("[data-tab]").forEach((t) => t.setAttribute("aria-selected", !quick && t.dataset.tab === tab));
|
||||
rerender();
|
||||
}));
|
||||
$("[data-qf-clear]").addEventListener("click", () => setTab(tab));
|
||||
$("[data-q]").addEventListener("input", (e) => { q = e.target.value.trim(); rerender(); });
|
||||
|
||||
/* 让被选中的项目可见:提醒条、任务 */
|
||||
$$("[data-pick]").forEach((b) => b.addEventListener("click", () => {
|
||||
const id = b.dataset.pick, p = P.find((x) => x.id === id);
|
||||
if (!visible().some((x) => x.id === id)) { tab = isActive(p) ? "active" : "all"; quick = ""; q = ""; $("[data-q]").value = ""; $$("[data-tab]").forEach((t) => t.setAttribute("aria-selected", t.dataset.tab === tab)); renderList(); }
|
||||
select(id, { focus: true });
|
||||
}));
|
||||
|
||||
/* 详情卡里的操作 */
|
||||
$("[data-detail]").addEventListener("click", (e) => {
|
||||
const o = e.target.closest("[data-open]"); if (o) return openProject(o.dataset.open);
|
||||
const s = e.target.closest("[data-src]"); if (s) { e.preventDefault(); return toast(`(演示)在原文查看器里打开:${s.dataset.src}`); }
|
||||
const d = e.target.closest("[data-demo]"); if (d) { e.preventDefault(); return toast(`(演示)打开${d.dataset.demo}`); }
|
||||
const b = e.target.closest("[data-act], [data-href]"); if (!b) return;
|
||||
if (b.dataset.href) { location.href = b.dataset.href; return; }
|
||||
const p = P.find((x) => x.id === sel), a = b.dataset.act;
|
||||
const msg = {
|
||||
"发起检查": "(演示)发起检查:扣 1 次检查,剩 12 次。可以离开本页,完成后会发站内通知。",
|
||||
"标记已递交": "(演示)已标记为「已递交」,可以撤销。",
|
||||
"导出检查报告": p.fatal ? `(演示)导出检查报告:还有 ${p.fatal} 项致命问题待处理,报告会如实列出。` : "(演示)导出检查报告",
|
||||
"上传投标文件": "(演示)打开上传投标文件对话框",
|
||||
"归档项目": "(演示)已归档,可以撤销。"
|
||||
}[a] || `(演示)${a}`;
|
||||
toast(msg);
|
||||
});
|
||||
$$("[data-demo]").forEach((a) => { if (!a.closest("[data-detail]")) a.addEventListener("click", (e) => { e.preventDefault(); toast(`(演示)打开${a.dataset.demo}`); }); });
|
||||
$("[data-new]").onclick = () => toast("(演示)新建项目:上传招标文件,扣 1 次解析,剩 37 次。解析失败不扣额度。");
|
||||
$("[data-notice]").onclick = () => toast("解析完成:补遗 1 已并入清单(新增 2 条,更新 1 条)。");
|
||||
$("[data-keys]").onclick = () => { const li = $(`#pi-${sel}`); if (li) li.focus(); toast("列表里按 ↑ ↓ 切换项目,Enter 打开。"); };
|
||||
|
||||
if (hashSel && !visible().some((p) => p.id === sel)) { tab = "all"; $$("[data-tab]").forEach((t) => { const on = t.dataset.tab === tab; t.setAttribute("aria-selected", on); t.tabIndex = on ? 0 : -1; }); }
|
||||
renderList(); renderDetail(false);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user