diff --git a/AGENTS.md b/AGENTS.md index d13c8c2..cd6e5ff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -151,4 +151,5 @@ samples/ - 2026-11-03:种子用户走查结束,没有问题。进入 UI 精修前先做 UI demo(`docs/design/ui-demo/`,6 个页面):控件、表单、卡片、列表,以及工作台、读标两个组合页;新增组件清单和待定问题 Q1–Q12 见其 index。 - 2026-11-03:技术验证 S1–S4 完成(`docs/architecture/tech-validation.md`,代码在 `spikes/`),四项都可行。要点:Word 转 PDF 要修正 WPS 字体字符集并改用行高调整过的字体;格式识别不需要 Docling;表格内切句要改进;DeepSeek 抽取要加第三道依据校验和规则兜底,单项抽取每份约 0.15 元。FR-M2-05 和 FR-M2-06 的目标值按建议确认(TV1)。 - 2026-11-03:UI 风格定稿(U5)。工作台经过六轮方案(`docs/design/ui-demo/explore/`),定为 D5:白色主调、墨色渐变窄侧栏、透明顶栏、圆角三档、只用功能图标、少写说明。设计规范和 `tokens.css` 升到 v0.2。 -- 下一步:按 v0.2 重刷其余 UI demo 页和原型,同时回答 Q1–Q12。 +- 2026-11-03:UI demo 全部按 v0.2 重刷(控件、表单、卡片、列表、工作台、读标)。Q1–Q12 全部按建议确认,写入设计规范;各页的共用问题收回到 `components.css`、`demo.css`、`demo.js`。Q13(流水记 0 点的「免扣」行)、U 键(撤销当前条)按建议确认。 +- 下一步:按 v0.2 重刷 MVP 原型(`docs/prototype/`),包括侧栏框架和待确认区。 diff --git a/docs/analysis/03-functional-requirements.md b/docs/analysis/03-functional-requirements.md index dedb917..ae2cf60 100644 --- a/docs/analysis/03-functional-requirements.md +++ b/docs/analysis/03-functional-requirements.md @@ -213,7 +213,8 @@ PDF 报告内容: ### FR-M6-03 额度计量 · P0 -- 额度账户按企业主体建;流水类型:发放 / 预扣 / 结算 / 退回 / 过期 / 人工调整(必须填原因)。 +- 额度账户按企业主体建;流水类型:发放 / 预扣 / 结算 / 退回 / 过期 / 人工调整(必须填原因)/ 免扣。 +- **免扣**(决策 Q13):24 小时内复查不扣点(FR-M3-02、决策 P1)时,也记一行流水,类型「免扣(24 小时内复查)」,数量 0,关联到这次检查,让用户能核对复查确实没扣点。 - 额度页:本期已用、剩余、按功能分布,流水可以查到具体是哪个项目、哪个任务。 - 用到 80%、100% 时站内提醒,同一阈值每个周期只提醒一次。 diff --git a/docs/analysis/04-domain-model.md b/docs/analysis/04-domain-model.md index 13debda..b4fb1e2 100644 --- a/docs/analysis/04-domain-model.md +++ b/docs/analysis/04-domain-model.md @@ -43,7 +43,7 @@ erDiagram | **Tenant** 企业主体 | 名称、统一社会信用代码(可空)、当前套餐 | 额度和数据的归属单位(PRD 4.3:额度绑定企业主体) | | **User** 用户 | 手机号、所属租户、角色 | MVP 一个租户一个成员;角色字段先留着,给 V1 用 | | **Subscription** 订阅 | 套餐、起止日期、来源(注册赠送 / 人工开通 / 线上支付) | | -| **QuotaAccount / QuotaEntry** 额度 | 单位(解析 / 检查 / 生成 / 查重)、类型(发放 / 预扣 / 结算 / 退回 / 过期 / 调整)、数量、关联任务、原因 | 只追加、不修改;余额由流水算出来 | +| **QuotaAccount / QuotaEntry** 额度 | 单位(解析 / 检查 / 生成 / 查重)、类型(发放 / 预扣 / 结算 / 退回 / 过期 / 调整 / 免扣)、数量、关联任务、原因 | 只追加、不修改;余额由流水算出来 | | **Qualification** 资质 | 名称、类型、等级、证书号、发证机构、有效期、附件 | MVP 最小资质库 | | **Project** 投标项目 | 名称、编号、采购人、代理机构、采购方式、**法律体系**、截止时间、状态、所投包 | 基本信息来自 FR-M2-03,用户可以改 | | **Lot** 包 / 标段 | 包号、名称、预算、最高限价、是否投标 | 没有分包时只有一个默认包 | diff --git a/docs/decisions.md b/docs/decisions.md index 5d98fe8..0604197 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -4,6 +4,30 @@ --- +## Q13、U 键 · 2026-11-03 · 按建议 + +- **Q13 额度流水记录「免扣」行**:24 小时内复查不扣点时,也记一行流水,类型「免扣(24 小时内复查)」,数量 0,关联到这次检查。理由:红线 9 要求扣点规则可见、可查,用户能自己核对复查确实没扣点。影响:FR-M6-03 和 04 领域模型补「免扣」类型。 +- **快捷键 U 撤销「当前条」**:和规范 §8.2 一致,不再是「撤销最近一次」。为此调整了交互:本次处理过的问题留在原位,显示署名行;处理完自动跳到下一条,按 K 可以回到刚处理的那条再按 U。页面加载前已处理的仍然折叠在「已处理」里。 + +## Q1–Q12 · 2026-11-03 · UI demo 的待定问题:全部按建议 + +来源:`docs/design/ui-demo/index.html`。按设计规范 v0.2 重刷 demo 时一并落实,写入 design-system.md 对应章节。 + +| # | 决策 | +|---|---| +| Q1 | 额度流水的「+」用墨色,靠「+ / −」符号区分;绿色只表示「有人确认过」 | +| Q2 | AI 产出的正文始终墨色;AI 身份靠「AI」小标签和左侧蓝铅笔虚线,确认后换成署名行 | +| Q3 | 选中统一为浅灰底加左侧 3px 墨色竖条;蓝色只留给原文高亮和出处链接 | +| Q4 | 开关:圆角 6 的矩形轨道加「开 / 关」文字,不用胶囊形。分页:当前页浅灰实底加粗。下拉:选中项加粗加墨色 ✓。日期选择:不可选日期只变灰,不加删除线 | +| Q5 | 参数对照表保留「级别」列,色弱用户不能只靠颜色判断 | +| Q6 | tokens.css 补悬停、按下、遮罩等颜色变量,清掉裸色值 | +| Q7 | 数字字阶:28(统计)/ 15 / 13 | +| Q8 | 骨架屏静止,不做明暗起伏 | +| Q9 | 顶栏额度用到 ≥ 80% 时,数字变赭色加粗;用完时悬停说明续费方式;不用红 | +| Q10 | 日期输入格式「2026-11-20 09:30」(24 小时制,配日期选择器);显示格式「11月20日 09:30」 | +| Q11 | MVP 不做项目卡片视图,工作台只用列表 | +| Q12 | 「一屏一个主按钮」按上下文算:页面级一个,对话框可以有自己的主按钮;陈列页不受限 | + ## U5 · 2026-11-03 · UI 风格定稿:白色主调 + 墨色渐变窄侧栏 - **背景**:种子用户走查结束,进入 UI 精修。用户看了 UI demo,认为细节没问题,但整体风格「像 Windows 2000」:处处描边框、2px 圆角、灰色凹陷表头、四张一样的统计卡。 diff --git a/docs/design/design-system.md b/docs/design/design-system.md index efcc1f4..79bd4b8 100644 --- a/docs/design/design-system.md +++ b/docs/design/design-system.md @@ -20,6 +20,10 @@ > - 第 9.5 节:文案精简。 > > 颜色语义、字体、问题级别的形状规则、产品红线都**没有变**。其余 demo 页和原型还是 v0.1 的样式,待按本版重刷。 +> +> **v0.2 补充(2026-11-03,Q1–Q12 已确认)**:UI demo 做的过程中发现的 12 个规范缺口,答案写进了对应章节,每处标「(v0.2,Q?,已确认 2026-11-03)」:§3.1(交互色变量,Q6)、§3.4(绿色,Q1)、§4.2(数字字号,Q7)、§7.1(主按钮,Q12)、§7.7(级别列,Q5)、§7.9(不做卡片视图,Q11)、§7.10(顶栏额度,Q9)、§7.15(骨架屏,Q8)、§7.16(AI 标识,Q2)、§7.18–7.22(选中态 Q3;开关、分页、下拉选择、日期选择 Q4)、§9.3(日期输入格式,Q10)。`tokens.css`、`components.css` 和 `ui-demo/demo.css` 已按本版改写;汇总表见 `ui-demo/index.html` 第 03 节。 +> +> **v0.2 收尾(2026-11-03)**:UI demo 六个页面重刷完后,把各页的临时做法收回共用层,并补写以下规则,每处标「(v0.2)」:§4.2(`.fig` 默认字号)、§5.3(对照布局尺寸)、§7.4(致命行不加红竖线、说明在行内展开)、§7.5(中性实底标签)、§7.6(待确认分组)、§7.8(原句高亮、原文排版变量)、§7.9(统计数字颜色)、§7.10(续费文案)、§7.18(当前条与勾选条)、§7.22(日历留空)、§8.2(快捷键按页列出)、§10(高亮的非颜色提示)。流水「免扣」行是否保留待确认(Q13,见 `ui-demo/index.html` 第 03 节)。 --- @@ -72,9 +76,18 @@ | 赭 | `--c-ochre` | #A66300 | 「重要」级边框和图标、系统提醒、表单错误、截止临近 | | 赭(小字) | `--c-ochre-ink` | #8A5200 | 18px 以下的赭色文字 | | 定稿绿 | `--c-green` | #2E6B4F | 人工确认的署名和 ✓ | +| 墨 · 悬停 | `--c-ink-hover` | #38414B | 主按钮等墨底元素的悬停(墨 88% 混白)(v0.2,Q6,已确认 2026-11-03) | +| 墨 · 按下 | `--c-ink-active` | #171F29 | 墨底元素按下(墨 80% 混黑)(v0.2,Q6,已确认 2026-11-03) | +| 中性浅灰 300 / 400 | `--c-n300` / `--c-n400` | #BBBEC2 / #A5A9AD | 普通链接的下划线 / 带悬停说明文字的虚线下划线(墨 30% / 40%)(v0.2,Q6,已确认 2026-11-03) | 每个功能色都有一个浅色底(`-soft`),只用作背景,文字仍用对应的深色。 +**交互与浮层的语义变量**(v0.2,Q6,已确认 2026-11-03):组件里**不写裸色值**,悬停、按下、遮罩一律用 `tokens.css` 的语义变量: +- `--primary-hover` / `--primary-active`(指向上表的墨 · 悬停 / 按下); +- `--link-underline` / `--hint-underline`(普通链接 / 有悬停说明的文字); +- `--scrim`(对话框遮罩,墨 32%)、`--topbar-glass`(滚动后的顶栏)、`--on-dark-line`(墨底浮层里的分隔线)、`--shadow-sheet`(原文查看器的纸页)。 +- 原来的 `#34404D`(主按钮悬停)、原文页的 `#222` `#888` `#999` `#EEE` 都已换成变量。 + **v0.2 的浅灰都从墨色派生**:原来的校样纸、凹陷灰、分隔线偏绿,页面整体显得发灰发闷;改为墨色按比例混白,更亮、更中性。 **侧栏的颜色**(v0.2,`--side-*`): @@ -118,6 +131,7 @@ - 不用红色做按钮(包括删除按钮)、表单错误、系统错误、倒计时、额度用完提示。删除这类操作靠二次确认,不靠颜色吓人。 - 不用定稿绿表示「通过 / 合格」。绿色只表示「有人确认过」,不表示结论好。 + - 额度流水的「+」(发放、退回)也不用绿色:和「−」一样用墨色,靠符号区分(v0.2,Q1,已确认 2026-11-03)。 - 不用蓝铅笔做普通导航链接、主按钮。蓝色只属于系统说的话和出处。普通链接用墨色加下划线。 - 不用渐变、不用大面积彩色底。一屏之内彩色面积越少,致命红越显眼。**唯一的例外**是侧栏的墨色竖向渐变(v0.2):只在墨色和深墨蓝之间过渡,不出现在内容区。 @@ -147,9 +161,13 @@ | Body | 14 / 22 | 黑体 400 | 界面正文、表格 | | Read | 14 / 24 | 黑体 400 | 招标原文、投标原文的引用 | | Caption | 12 / 20 | 黑体 400 | 时间戳、脚注、表单说明;**最小字号**,正文不用 | -| Figure | 30 / 36 | Barlow 600 | 工作台统计数字、额度剩余 | +| Figure | 28 / 36 | Barlow 600 | 工作台统计数字、额度剩余(`--fs-figure`,v0.2 由 30 改为 28) | +| Figure S | 15 / 22 | Barlow 500 / 600 | 正文行里独立出现的数字:表格数值、日期、页码、筛选计数、顶栏额度(`--fs-figure-sm`)(v0.2,Q7,已确认 2026-11-03) | +| Figure XS | 13 / 20 | Barlow 500 / 600 | 12px 小字行里的数字:标签计数、出处页码、扣点说明、时间戳(`--fs-figure-xs`)(v0.2,Q7,已确认 2026-11-03) | -- 字重只用 400 和 600 两档(黑体在 Windows 上 500 和 400 几乎看不出区别)。 +- 字重只用 400 和 600 两档(黑体在 Windows 上 500 和 400 几乎看不出区别)。Barlow 数字例外,可用 500。 +- Barlow 的字面比黑体小一号,所以和 14px 正文同行时用 15px、和 12px 小字同行时用 13px,看上去才一样大;这两档只给数字用,不算进「同一屏不超过 5 个字号」。 +- 默认值做进共用层(v0.2):`.fig` 默认 15(`--fs-figure-sm`);放在 12px 小字里(标签、扣点说明、出处、表头、说明文字等)自动变 13(`--fs-figure-xs`);统计大数字用 `.big`(`--fs-figure` 28)。页面里不必再逐处写字号。 - 强调用加粗,不用斜体(中文没有真斜体)、不用彩色。 ### 4.3 数字与中英文混排 @@ -217,11 +235,18 @@ | 布局 | 页面 | 结构 | |---|---|---| | 列表 | 工作台、资质库、额度流水 | 页头 + 统计 + 白卡片里的列表;工作台右侧另有 296px 信息栏 | -| 对照 | 读标(清单)、查标(问题列表) | 左:分类导航 200px;中:列表;右:**原文查看器**,宽度可拖动(默认 45%) | +| 对照 | 读标(清单)、查标(问题列表) | 左:分类导航和列表在同一张白卡片里;右:**原文查看器**,宽度可拖动(尺寸见下) | | 批注 | 查标结果的默认视图 | 问题列表 + 右侧 280px 页边批注栏(出处);窄于 1440 时批注收进问题下方 | 点出处时,对照布局的右栏直接翻到对应页并高亮;批注布局下从右侧滑出原文查看器,不跳页面。 +**对照布局的尺寸**(v0.2): +- 分类导航 176px(1400 以下 152px),当前分类用 `--c-n100` 实底 + 加粗; +- 列表最小 560px,其余宽度都给列表; +- 原文查看器默认占内容区宽度的 45%,最小 400px;中间 16px 是拖动条(平时不可见,悬停出浅灰细线),可以拖动或聚焦后用 ← → 调整,列表始终不窄于 560px; +- 1280 宽时(内容区约 1136px):列表 ≥ 560、查看器 ≤ 560,不横向滚动; +- 对照布局撑满顶栏和页头以下的高度,分类导航、列表、查看器各自滚动。 + --- ## 6. 图标 @@ -245,14 +270,18 @@ | 类型 | 样式 | 用于 | |---|---|---| -| 主按钮 | 墨底白字 | 一屏只有一个:「发起检查」「导出报告」「确认清单」 | +| 主按钮 | 墨底白字,加粗 | 一屏只有一个(怎么算见下):「发起检查」「导出报告」「确认清单」 | | 次按钮 | 白底墨字,控件边框 | 其他操作,包括问题处理三按钮 | | 文字按钮 | 墨字,无边框,悬停出底色 | 表格行内、次要操作 | - 高度 36(默认)/ 32(表格行内、筛选标签)/ 40(登录页、空状态),圆角 8(v0.2)。左右内边距 14 / 10 / 16。 - **没有红色的危险按钮**。删除项目、删除文件用次按钮 + 确认对话框;删除项目要输入项目名才能确认。 - 会扣点的按钮,旁边直接写扣多少、还剩多少(见 8.3),不放在弹窗里才说。 -- 状态:悬停(主按钮变浅 8%,次按钮出 `--bg-hover`)、按下、禁用(禁用灰字、无边框底色)、加载中(文字换成「正在…」,按钮宽度不变)。 +- 状态:悬停(主按钮变为 `--c-ink-hover`,次按钮出 `--bg-hover`、边框变墨)、按下(主按钮 `--c-ink-active`,次按钮 `--c-n100`)、禁用(禁用灰字、`--c-n100` 底、无边框)、加载中(文字换成「正在…」,按钮宽度不变)(v0.2,Q6,已确认 2026-11-03)。 +- **「一屏一个主按钮」按上下文算**(v0.2,Q12,已确认 2026-11-03): + - 页面级只有一个主按钮(通常在页头右侧); + - 对话框是独立的上下文,可以有自己的主按钮(「删除项目」「确认无误」),不和页面的主按钮冲突; + - 组件陈列页(UI demo、组件预览)为了对比会同时出现多个,不受此限。 - 按钮文字是动词短语,和结果状态用同一个词(品牌策略第 4 节)。 ### 7.2 问题级别标签 @@ -260,10 +289,10 @@ ``` [致命] 实心,白字压致命红 [重要] 1.5px 赭色描边,赭色小字 -[提示] 无边框,凹陷灰底,辅助灰字 +[提示] 无边框,--c-n100 浅灰底,辅助灰字 ``` -- 字号 12,字重 600,高 20,左右内边距 6,圆角 6(v0.2)。 +- 字号 12,字重 600,高 22,左右内边距 8,圆角 6(v0.2,与中性标签同高,以 d5 为准)。 - 级别由规则决定(FR-M3-03 的级别表),用户不能改级别;不同意可以「反馈:判断有误」。 - 计数徽标也用同一套形状:「致命 2」实心、「重要 5」描边、「提示 11」无框。 @@ -298,10 +327,13 @@ - 标题写结论,不写规则名:「报价超过采购包 2 的最高限价」,不写「报价限价规则命中」。 - 复查标记放在标题行右侧:「新增」「沿用」「本次已解决」(FR-M3-08),用辅助灰小字,不用颜色。 - 处理后:按钮收起,换成署名行「✓ 王敏 11月2日 14:20 已修改」(绿),可「撤销」。「已忽略」不是确认,署名行用辅助灰和「–」,后面带原因。 -- 「致命」行左侧加 3px 致命红竖线,方便在长列表里扫读;「重要」「提示」不加。 +- ~~「致命」行左侧加 3px 致命红竖线~~(v0.2 取消):和工作台一致,级别只靠实心「致命」标签扫读;行左侧的 3px 竖条只表示「当前条」(墨色,§7.18),和级别无关,两者不再冲突。 - 已处理的问题默认折叠到列表底部「已处理(N)」,避免干扰。 - 每行都有「判断有误?」文字链接,打开反馈表单(FR-M3-07 / J5)。 - 「致命」级点「确认无误」时,展开一个必填的说明输入框(U4),说明同时作为误报反馈提交。 + - 说明框**在问题行内展开**,替换处理按钮,不用对话框(v0.2);「忽略并注明原因」也一样在行内展开(原因可以不处理也能撤销,按 §7.12 不用对话框打断)。 + - 行内区域无底色,不在卡片里再套面板;至少 5 个字,有字数计数,Esc 取消。 + - 提交后收起,换成署名行;说明 / 原因写在署名行下方的辅助灰小字里。 **三个处理动作的含义**(按钮下方悬停提示要写清楚,避免误解): @@ -313,35 +345,39 @@ ### 7.5 清单项(读标) -- 一行:重要性符号(★ / ▲ / 空)→ 标题 → 类别标签(可多个,灰色描边小标签)→ 所属包 → 来源 → 出处。 -- 来源标注:「AI 提取」(蓝铅笔)、「人工添加」(墨)、「补遗 2」(墨)。 +- 一行:重要性符号(★ / ▲ / 空)→ 标题 → 类别标签(可多个)→ 所属包 → 来源 → 出处。 +- 类别、包、来源都用**中性实底标签**(`--c-n100` 底、无描边、墨色字,与状态标签同一种)(v0.2)。 +- 来源标注:AI 提取的用「AI」标识(蓝铅笔描边小标签,§7.16);「人工添加」「补遗 2」用中性实底标签(v0.2)。 - 被用户改过的条目显示「已修改 · 查看记录」,打开修改历史(谁、何时、改前、改后)。 - 「已被补遗 N 更新」的旧值加删除线,新值旁边写来源,用户确认后去掉提示(FR-M2-10)。 -- 清单整体状态显示在页头:「草稿」(辅助灰描边)/「已确认 · 王敏 11月2日」(绿色署名)。 +- 清单整体状态显示在页头:「草稿 v3」(中性实底标签,v0.2)/「已确认 v3」标签 + 绿色署名「✓ 王敏 11月2日 14:20」。 ### 7.6 待确认区 -- 虚线蓝铅笔边框的独立区块,标题「待确认(N)」,说明一句:「这些结果没有找到可靠出处,或系统把握不大。确认之前,不参与检查。」 -- 每条三个动作:「补上出处后采纳」「修改后采纳」「丢弃」。没有出处也要采纳时,标注「人工确认、无出处」。 -- 待确认区不和正式清单混排,放在清单顶部,有条目时默认展开。 +- 是清单顶部的一个**分组**,和「资格条件」等分组同样排版,标题用蓝铅笔色「待确认 N」,后面一句辅助灰说明:「没找到可靠出处,或把握不大。确认前不参与检查。」**不再用虚线框**(v0.2,避免卡片里再套框)。 +- 条目是未确认的 AI 产出:「AI」标识 + 左侧 2px 蓝铅笔虚线(§7.16)。 +- 每条三个动作:「补上出处后采纳」「修改后采纳」「丢弃」(有可靠出处时第一个动作是「采纳」)。没有出处也要采纳时,标注「人工确认、无出处」。 +- 不和正式清单混排,放在清单顶部,有条目时默认展开;分类导航里另有「待确认」一项(蓝铅笔色)可以单独查看。 ### 7.7 参数对照表 技术参数的检查结果用表格(借鉴方向 C「检验单」的写法): -| 参数项 | 要求 | 投标响应 | 判断 | 出处 | -|---|---|---|---|---| -| ★ 内存 | ≥ 512G DDR4 | 256G DDR4 | **疑似不满足** | 第 41 页 ↔ 技术册 第 12 页 | +| 参数项 | 要求 | 投标响应 | 判断 | 级别 | 出处 | +|---|---|---|---|---|---| +| ★ 内存 | ≥ 512G DDR4 | 256G DDR4 | **疑似不满足** | [致命] | 第 41 页 ↔ 技术册 第 12 页 | - 要求和响应列用 Barlow + `tabular-nums`,数值左对齐(参数是「数 + 单位 + 说明」,右对齐反而难读)。 - 「判断」列只用四个词:**满足 / 疑似不满足 / 未找到响应 / 需人工判断**。不用「合格 / 不合格 / 通过」。 - 「疑似不满足」用所在行的问题级别标签颜色;「满足」用墨色普通字,不用绿色(绿色只表示人工确认)。 -- 表头凹陷灰底、2px 墨色底线;行间 1px 分隔线;行高 40。 +- **保留「级别」列**(v0.2,Q5,已确认 2026-11-03):判断词的颜色对色弱不够,级别标签有实心 / 描边 / 无框的形状兜底;「满足」「需人工判断」的行级别列写「—」。 +- 表头无底色、无粗线,12px 辅助灰列名;行间发丝线;行高 44;放在白卡片里,首尾列与卡片左右边距(20)对齐(v0.2,同 §5.1)。 ### 7.8 原文查看器 - 白色页面,按规范化 PDF 显示(出处页码以它为准)。 -- 高亮:原句用蓝铅笔浅底 + 1px 蓝铅笔描边框;当前选中的出处描边加粗为 2px,并自动滚动到可见区域。 +- 高亮:原句用蓝铅笔浅底 + 1px 蓝铅笔底边;当前出处的底边加粗为 2px,并自动滚动到可见区域(v0.2:不再用描边框——描边框在换行处断开,底边线跨行时每一行都有,读起来连续)。 +- 纸面字体模拟招标文件的打印效果:`--font-doc`(宋体)、`--fs-doc` 13px、`--lh-doc` 24px,不跟界面字阶(v0.2)。 - 顶部:文件名、文件类型(招标文件 / 补遗 N / 投标分册)、页码跳转「第 41 / 186 页」、上一处 / 下一处高亮。 - OCR 页面显示「扫描页 · 文字由 OCR 识别」,高亮按 OCR 坐标。 - 原文只读,不允许在查看器里改文字。 @@ -368,13 +404,20 @@ | ≤ 24 小时 | 「今天 09:30 截止」/「剩 6 小时」 | 赭小字,加粗 | | 已过 | 「已截止」 | 辅助灰 | -**就绪状态**:「未就绪」(墨色普通字,致命数量看「致命」列)/「就绪」(墨色描边标签)。项目详情页的页头合在一起写:「未就绪 · 2 项致命问题待处理」。「就绪」悬停说明:「只表示致命问题都已处理,不代表投标文件没有问题。」 +**就绪状态**:「未就绪」(墨色普通字,致命数量看「致命」列)/「就绪」(墨色普通字 + 虚线下划线,v0.2 以 d5 为准);两者都写在状态标签后面,不单独占一列。项目详情页的页头合在一起写:「未就绪 · 2 项致命问题待处理」。「就绪」悬停说明:「只表示致命问题都已处理,不代表投标文件没有问题。」 + +**统计卡的数字颜色**(v0.2):「待处理的致命问题」用致命红,「7 天内截止」用赭(`--warn-text`),其余(进行中、待确认的清单条目等)用墨色。数字 Barlow 28,只写标题和数字(§9.5)。 + +**不做项目卡片视图**(v0.2,Q11,已确认 2026-11-03):MVP 的工作台只用列表(项目行)。投标专员要按截止时间扫读、比对多个项目,列表密度更合适;不提供列表 / 卡片切换。 ### 7.10 额度 -- 顶栏常驻:「解析 37 · 检查 12」(Barlow),点开额度页。 -- 额度条:已用部分墨色,80% 起变赭色并出现一行提醒,100% 时提醒「本期检查额度已用完,续费或下期发放后可继续」。**不用红色**。 -- 流水表:时间、类型(发放 / 预扣 / 结算 / 退回 / 过期 / 人工调整)、数量(+ / −,Barlow 等宽)、关联项目和任务、原因。退回的记录和对应的预扣记录相邻显示。 +- 顶栏常驻:「解析 37 · 检查 12」(Barlow 15),点开额度页;悬停说明本期剩余 / 总数。 + - 某项用到 ≥ 80% 时,该项数字变赭色(`--warn-text`)加粗;用完时悬停说明续费方式(「本期检查额度已用完,可在额度页提交续费申请,或等下期发放」,v0.2)。**不用红**(v0.2,Q9,已确认 2026-11-03)。 +- 额度条:已用部分墨色,80% 起变赭色并出现一行提醒,100% 时提醒「本期检查额度已用完,可在额度页提交续费申请,或等下期发放」,并给「提交续费申请」按钮。**不用红色**。 + - 续费按决策 G8:专业版在线申请 → 对公转账 → 运营开通;文案写「在额度页提交续费申请」,**不写「联系客服」**(v0.2)。 +- 流水表:时间、类型(发放 / 预扣 / 结算 / 退回 / 过期 / 人工调整 / 免扣),其中「免扣(24 小时内复查)」数量记 0(v0.2,Q13);数量(+ / −,Barlow 等宽)、关联项目和任务、原因。退回的记录和对应的预扣记录相邻显示。 + - 「+」和「−」都用墨色,只靠符号区分,不用绿色(v0.2,Q1,已确认 2026-11-03)。 ### 7.11 异步任务进度(解析 / 检查) @@ -386,9 +429,11 @@ | 类型 | 样式 | 例子 | |---|---|---| -| 信息 | 白底,墨色左边线 | 「清单未确认,检查结果以当前清单为准。」 | -| 提醒 | 赭浅底,赭色左边线,赭小字标题 | 额度 80%、截止 ≤ 72 小时、解析失败、上传文件超限 | -| 完成 | 白底,绿色 ✓ | 「报告已导出」 | +| 信息 | 白卡片,墨色图标 | 「清单未确认,检查结果以当前清单为准。」 | +| 提醒 | 白卡片,赭色图标 + 赭小字关键字(如截止时间) | 额度 80%、截止 ≤ 72 小时、解析失败、上传文件超限 | +| 完成 | 白卡片,绿色 ✓ | 「报告已导出」 | + +- v0.2(U5,以 d5 为准):页内提醒和卡片同规格(白底、1px 发丝线、圆角 12),不再用彩色浅底和左边线;颜色只出现在图标和关键字上。 - 页内提醒(Alert)放在相关内容上方,不自动消失;轻提示(Toast)只用于操作结果,4 秒后消失,可悬停暂停。 - 不用弹窗打断。只有不可撤销的操作(删除、标记已递交)才用确认对话框。 @@ -402,7 +447,7 @@ ### 7.14 对话框 -- 宽 480(确认)/ 640(表单),圆角 4,浮层阴影。 +- 宽 480(确认)/ 640(表单),圆角 12(v0.2,同 §5.1),浮层阴影,遮罩 `--scrim`。 - 标题写要做的事:「删除项目「智慧规划资源 2025 标段三」?」;正文写后果;按钮「删除项目」「取消」,主操作在右。 - 删除项目要输入项目名才能点删除;说明删除后 30 天内可在回收站恢复(以 G10 定稿为准)。 @@ -410,12 +455,16 @@ - 空状态写下一步做什么,带一个主按钮:「还没有招标文件。上传后,标博士会逐条提取要求。」 - 加载用骨架屏,不用转圈占满屏。 + - 骨架屏**静止**:`--c-n100` 浅灰块,不做明暗起伏或扫光(符合「不闪烁」原则)(v0.2,Q8,已确认 2026-11-03)。 - 系统错误:「出了点问题,请稍后重试。本次操作未扣额度。」带错误编号,便于客服查询。 ### 7.16 AI 标识 - 系统生成的文字(摘要、说明、初稿)前面加「AI 摘要」「AI 初稿」标签:蓝铅笔 1px 描边、蓝铅笔字。 -- 人修改或确认后,标签换成署名行,文字变为墨色。 +- **AI 产出的正文始终是墨色**(v0.2,Q2,已确认 2026-11-03):大段蓝字阅读负担重。AI 身份只靠两样东西表示: + - 「AI」小标签; + - 正文左侧 2px 蓝铅笔虚线。 +- 人修改或确认后:去掉虚线(左内边距保留,正文不跳动),标签换成署名行(绿 ✓ + 姓名 + 时间 + 动作)。 ### 7.17 检查报告 PDF @@ -426,9 +475,48 @@ - 页脚:页码「3 / 12」+「本报告为辅助检查结果,不构成对投标结果的保证」。 - 全文不出现「通过」「合格」「保证」。 ---- +### 7.18 选中态(v0.2,Q3,已确认 2026-11-03) + +- 列表项、表格行被选中(读标清单的当前条、勾选的表格行、问题列表的当前问题):`--c-n50` 底 + 左侧 3px 墨色竖条。读标清单和表格一样,不再用蓝色浅底。 +- 键盘焦点另加 2px 墨色内框,和选中可以同时出现。 +- **当前条与勾选条要区分**(v0.2): + - 当前条(点击或 J / K 定位到的那一条,原文查看器跟着它走)= `--c-n50` 底 + 3px 墨色竖条;同一列表只有一条; + - 勾选条(批量处理的复选框)只有复选框是选中状态,**不加底色和竖条**;可以有多条,也可以和当前条重合; + - 上面「勾选的表格行」按此修正:勾选不再出底色和竖条。 +- **竖条和级别的关系**(v0.2):竖条只表示「当前」,永远是墨色;问题级别只看实心 / 描边 / 无框的级别标签,「致命」行不再另加红竖线(§7.4),所以选中致命行时不会出现红、墨两条竖线。 +- 蓝色只留给原文高亮和出处链接:清单选中某条时,右侧原文查看器里对应的原句用蓝铅笔浅底高亮(§7.8),两边靠位置对应,不靠同色。 +- 导航和筛选的「当前项」(分类导航、筛选按钮、分页当前页)不是选中,用 `--c-n100` 实底 + 加粗。 + +### 7.19 开关(v0.2,Q4,已确认 2026-11-03) + +- 矩形轨道(34 × 20,圆角 6)+ 方块滑钮(14 × 14,圆角 4),**不用胶囊形**(§5.1)。 +- 关:白底、控件边框、禁用灰滑钮;开:墨色轨道、白色滑钮。 +- 旁边写「开 / 关」(12px),不只靠滑钮位置表示状态。 +- 只用于立即生效的设置(如「到期前 30 天提醒」);需要提交的表单项用复选框。 + +### 7.20 分页(v0.2,Q4,已确认 2026-11-03) + +- 左侧「共 N 条」,右侧页码 + 上一页 / 下一页 + 跳转输入框;页码用 Barlow 15。 +- 当前页:`--c-n100` 实底 + 加粗,无描边,和筛选按钮的按下态一致;悬停也出 `--c-n100`。 +- 高 32,圆角 8。只在超过一页时显示。 + +### 7.21 下拉选择与菜单(v0.2,Q4,已确认 2026-11-03) + +- 触发框和输入框同规格(高 36、圆角 8、控件边框),右侧 chevron,展开时旋转。 +- 浮层:白底、1px 发丝线、圆角 12、浮层阴影,内边距 6;选项高 34、圆角 6,悬停 / 键盘移动出 `--c-n100`。 +- **选中项加粗,右侧墨色 ✓**;不用蓝色、不用背景色表示选中。 +- 不可选的项用禁用灰,并写明原因(如「不适用于本项目的采购方式」)。 +- 操作菜单(列表行的「更多」)同样式;会扣点的项在第二行写「扣 1 次检查 · 剩 12 次」(§8.3)。 + +### 7.22 日期选择(v0.2,Q4,已确认 2026-11-03) + +- 周一开始;日期数字用 Barlow 15。 +- 今天:1px 控件边框;选中:墨底白字;本项目的关键日期:下方墨色小圆点。 +- **不可选的日期只变禁用灰,不加删除线**;不可选的格子没有悬停效果(v0.2)。 +- 非本月的日期**不显示**,格子留空(v0.2):否则上下月的灰色日期和不可选日期很难区分。 +- 日期输入框:可以直接键入,接受「2026/11/20 9:30」「2026.11.20 9:30」「2026年11月20日」等常见写法,离开输入框时整理成「2026-11-20 09:30」;下方小字显示界面写法「11月20日 09:30 · 剩 17 天」(v0.2)。 +- 带时间的字段(如投标截止):日期选择器底部有时间输入框,24 小时制;输入框格式见 §9.3。 -## 8. 交互模式 ### 8.1 AI 产出 → 人工确认 → 留痕 @@ -448,10 +536,12 @@ | J / K | 下一条 / 上一条 | | O 或 Enter | 在原文查看器里打开当前出处 | | 1 / 2 / 3 | 确认无误 / 已修改 / 忽略(3 会打开原因输入框) | -| U | 撤销当前条的处理 | +| U | 撤销当前条的处理。本次处理过的问题留在原位,显示署名行,可以用 J / K 回到它再按 U;页面加载前已处理的折叠在「已处理」里(v0.2) | | ? | 显示快捷键说明 | -批量处理只开放给「提示」级(FR-M3-07):勾选后「批量确认无误」。 +批量处理只开放给「提示」级(FR-M3-07):勾选后「批量确认无误」;键盘 X 勾选当前条。 + +快捷键说明(? 打开)**按页面只列能用的键**(v0.2):读标页是 J / K、Enter、?;查标的问题列表是 J / K、O / Enter、1 / 2 / 3、X、U、?。 ### 8.3 扣点前先告知 @@ -497,6 +587,7 @@ | 页码 | 「第 41 页」;带文件时「招标文件 · 第 41 页」「技术册 · 第 12 页」 | | 金额 | 沿用招标文件的单位,保留两位小数:「132.00 万元」;不换算单位 | | 日期时间 | 界面:「11月2日 14:20」(跨年时带年份);报告和留痕:「2026-11-02 14:20」 | +| 日期输入 | 输入框里写「2026-11-20 09:30」(年-月-日 + 24 小时制时间),配日期选择器(§7.22),也可以直接键入;提交后在界面上显示为「11月20日 09:30」(v0.2,Q10,已确认 2026-11-03) | | 数量 | 「致命 2 项」「共 186 页」 | | 标点 | 中文用全角标点,引用原文照录 | @@ -530,7 +621,7 @@ - [ ] 级别、状态不只靠颜色 —— 见 3.3 - [ ] 所有操作可用键盘完成;焦点环 2px 墨色 + 2px 间隙,任何背景上都看得见 - [ ] 只有图标的按钮有 `aria-label` -- [ ] 原文高亮除颜色外还有描边框 +- [ ] 原文高亮除颜色外还有底边线(v0.2,原为描边框) - [ ] 尊重「减少动态效果」系统设置;动效时长 120–200ms,不闪烁 - [ ] 浏览器缩放到 200% 时内容不丢失(可以横向滚动) @@ -571,6 +662,8 @@ 详见 `docs/decisions.md` U5。 +**Q1–Q12(2026-11-03)**:UI demo 发现的规范缺口,全部按建议确认,已写进第 3、4、7、9 节(见文件头的 v0.2 补充),汇总表在 `ui-demo/index.html` 第 03 节。 + 前端不用带样式的 UI 框架(AR10):组件样式直接引用 `tokens.css`,复杂交互用无样式组件库 Reka UI 打底。 --- diff --git a/docs/design/system/components.css b/docs/design/system/components.css index 9a5ad3a..19cda4e 100644 --- a/docs/design/system/components.css +++ b/docs/design/system/components.css @@ -1,183 +1,244 @@ -/* 标博士 · 组件样式 v0.1 +/* 标博士 · 组件样式 v0.2 · 2026-11-03 * 依赖 tokens.css。规范见 docs/design/design-system.md §7;组件预览 components.html 和原型 docs/prototype/ 共用本文件。 - * 原型阶段的参考实现:技术架构选定组件库后,按这里的视觉去配置组件库主题,不必照搬选择器。 */ + * 原型阶段的参考实现:前端不用带样式的 UI 框架(AR10),正式实现按这里的视觉写组件样式,不必照搬选择器。 + * v0.2(决策 U5,定稿样张 ui-demo/explore/workbench-d5.html): + * - 只有一种容器:白卡片,1px 发丝线,圆角 12,无阴影;卡片里不再套面板; + * - 表头无底色、无粗线,12px 辅助灰列名;行间发丝线;行悬停 --c-n50; + * - 圆角三档:标签 6、控件 8、卡片 / 对话框 12;按钮高 36 / 32 / 40; + * - 中性标签只有一种:--c-n100 实底、无描边;问题级别标签形状不变(致命实心、重要描边、提示无框); + * - Q1–Q12(2026-11-03 已确认):流水「+」用墨色(Q1);AI 正文始终墨色(Q2);选中 = --c-n50 底 + 左侧 3px 墨色竖条(Q3); + * 不写裸色值(Q6);数字字号三档(Q7)。 + * - 收尾(2026-11-03):.fig 默认字号进共用层;致命问题行不再加红竖线(级别只靠实心「致命」标签); + * 原文纸面用 --font-doc / --fs-doc / --lh-doc,原句高亮改为「蓝铅笔浅底 + 蓝色底边」。 */ /* ---- 基础 ---- */ *{box-sizing:border-box} body{margin:0;background:var(--bg-page);color:var(--text-primary);font:var(--fs-body)/var(--lh-body) var(--font-sans);-webkit-font-smoothing:antialiased} :focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px} -.card{background:var(--bg-surface);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-sm);padding:var(--sp-5) var(--sp-6)} +[hidden]{display:none!important} .row{display:flex;flex-wrap:wrap;gap:var(--sp-3);align-items:center} .grid2{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-6)} -.fig{font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-weight:600} +.fig{font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-weight:var(--fw-bold);font-size:var(--fs-figure-sm)} +/* 数字字号(Q7):正文里独立出现的数字默认 15(--fs-figure-sm);12px 小字里 13(--fs-figure-xs);统计大数字用 .big(--fs-figure 28) */ +:is(small,.cap,.note,.cost,.flag,.sev,.tag,.chip,.msg,.cite-src,.proj-sub,.psub) .fig,table.data th .fig,table.data a .fig{font-size:var(--fs-figure-xs)} .note{font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary);margin:var(--sp-3) 0 0} +/* ---- 卡片:全页唯一的容器 ---- + * 整块卡片:.card(自带内边距)。 + * 分区卡片:.card.flush 不带内边距,里面用 .card-h(标题行)/ .card-b(正文)/ .card-f(底栏,带发丝线),左右边距统一 --card-x。 */ +.card{background:var(--bg-surface);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-card);padding:var(--card-y) var(--card-x)} +.card.flush{padding:0} +.card.flush > .card-h{display:flex;align-items:center;gap:var(--sp-3);min-height:var(--card-h-min);margin:0;padding:var(--sp-3) var(--card-x)} +.card-h h2,.card-h h3{margin:0;font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans)} +.card-b{padding:var(--sp-1) var(--card-x) var(--sp-4)} +.card-f{display:flex;align-items:center;gap:var(--sp-4);padding:var(--sp-3) var(--card-x);border-top:var(--line) solid var(--border-subtle)} -/* ---- 级别标签 ---- */ -.sev{display:inline-flex;align-items:center;height:20px;padding:0 6px;border-radius:var(--radius-sm);font:var(--fw-bold) var(--fs-caption)/1 var(--font-sans);white-space:nowrap} -.sev .fig{margin-left:4px;font-size:13px} +/* ---- 级别标签与中性标签 ---- */ +.sev{display:inline-flex;align-items:center;gap:var(--sp-1);height:var(--tag-h);padding:0 var(--sp-2);border-radius:var(--radius-tag);font:var(--fw-bold) var(--fs-caption)/1 var(--font-sans);white-space:nowrap} +.sev .fig{font-size:var(--fs-figure-xs)} .sev.fatal{background:var(--sev-fatal);color:var(--text-on-dark)} .sev.major{border:var(--line-sev) solid var(--sev-major);color:var(--sev-major-text)} .sev.hint{background:var(--sev-hint-soft);color:var(--sev-hint)} -.tag{display:inline-flex;align-items:center;height:20px;padding:0 6px;border:1px solid var(--border-subtle);border-radius:var(--radius-sm);font-size:var(--fs-caption);color:var(--text-secondary);white-space:nowrap} -.tag.status{border-color:var(--border-control);color:var(--text-primary)} -.tag.ai{border-color:var(--ai);color:var(--ai)} +/* 中性标签(状态、类别、包):只有这一种,--c-n100 实底、无描边、墨色字 */ +.tag{display:inline-flex;align-items:center;gap:var(--sp-1);height:var(--tag-h);padding:0 var(--sp-2);border-radius:var(--radius-tag);background:var(--c-n100);font-size:var(--fs-caption);line-height:1;color:var(--text-primary);white-space:nowrap} +.tag.status{background:var(--c-n100)} +/* AI 标识(Q2):蓝铅笔 1px 描边 + 蓝铅笔字;AI 产出的正文本身仍是墨色 */ +.tag.ai{background:none;box-shadow:inset 0 0 0 1px var(--ai);color:var(--ai)} .flag{font-size:var(--fs-caption);color:var(--text-secondary)} -/* ---- 按钮 ---- */ -.btn{display:inline-flex;align-items:center;gap:6px;height:var(--control-h);padding:0 12px;border-radius:var(--radius-sm);border:1px solid var(--border-control);background:var(--bg-surface);color:var(--text-primary);font:var(--fs-body)/1 var(--font-sans);cursor:pointer;transition:background var(--dur-fast) var(--ease)} +/* ---- 链接 ---- */ +a.plain,.lnk{color:var(--text-primary);text-decoration:underline;text-decoration-color:var(--link-underline);text-underline-offset:3px;transition:text-decoration-color var(--dur-fast) var(--ease)} +a.plain:hover,.lnk:hover{text-decoration-color:var(--c-ink)} +/* 有悬停说明的文字(如「就绪」):虚线下划线 */ +.hint-u{text-decoration:underline dashed;text-decoration-color:var(--hint-underline);text-underline-offset:4px;cursor:help} + +/* ---- 按钮 ---- + * 主按钮墨底白字(页面级一屏一个,对话框里可以有自己的主按钮,Q12);次按钮白底 + 控件边框;文字按钮悬停出 --c-n100。 */ +.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:var(--control-h);padding:0 var(--btn-px);border-radius:var(--radius-ctl);border:var(--line) solid var(--border-control);background:var(--bg-surface);color:var(--text-primary);font:var(--fs-body)/1 var(--font-sans);white-space:nowrap;cursor:pointer;transition:background-color var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)} a.btn{text-decoration:none} -[hidden]{display:none!important} -.btn:hover{background:var(--bg-hover)} -.btn.primary{background:var(--c-ink);border-color:var(--c-ink);color:var(--text-on-dark)} -.btn.primary:hover{background:#34404D} -.btn.text{border-color:transparent;background:none} -.btn.text:hover{background:var(--bg-hover)} -.btn.sm{height:var(--control-h-sm);padding:0 8px} -.btn.lg{height:var(--control-h-lg);padding:0 16px} -.btn[disabled]{color:var(--text-disabled);background:var(--bg-sunken);border-color:var(--bg-sunken);cursor:not-allowed} -.cost{font-size:var(--fs-caption);color:var(--text-secondary)} -.cost .fig{font-weight:500;font-size:13px} -a.plain{color:var(--text-primary);text-underline-offset:3px} +.btn:hover{background:var(--c-n50);border-color:var(--c-ink)} +.btn:active{background:var(--c-n100)} +.btn.primary{background:var(--c-ink);border-color:var(--c-ink);color:var(--text-on-dark);font-weight:var(--fw-bold)} +.btn.primary:hover{background:var(--primary-hover);border-color:var(--primary-hover)} +.btn.primary:active{background:var(--primary-active);border-color:var(--primary-active)} +.btn.text{border-color:transparent;background:none;padding:0 var(--sp-3)} +.btn.text:hover{background:var(--c-n100);border-color:transparent} +.btn.sm{height:var(--control-h-sm);padding:0 var(--btn-px-sm)} +.btn.text.sm{padding:0 10px} +.btn.lg{height:var(--control-h-lg);padding:0 var(--btn-px-lg)} +.btn[disabled],.btn[disabled]:hover{color:var(--text-disabled);background:var(--c-n100);border-color:var(--c-n100);cursor:not-allowed;font-weight:var(--fw-regular)} +.btn.text[disabled]{background:none;border-color:transparent} +.cost{font-size:var(--fs-caption);color:var(--text-secondary);white-space:nowrap} +.cost .fig{font-size:var(--fs-figure-xs);color:var(--text-primary)} /* ---- 问题行 + 页边批注 ---- */ -.results{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-sm)} -.results-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--sp-3);padding:var(--sp-5) var(--sp-6);border-bottom:1px solid var(--border-subtle)} +.results{background:var(--bg-surface);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-card);overflow:hidden} +.results-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--sp-3);padding:var(--sp-5) var(--card-x);border-bottom:var(--line) solid var(--border-subtle)} .results-head h3{font:var(--fw-bold) var(--fs-h2)/var(--lh-h2) var(--font-serif);margin:0} .results-head .proj{color:var(--text-secondary)} .results-head .counts{margin-left:auto;display:flex;gap:var(--sp-2)} -.finding{position:relative;display:grid;grid-template-columns:minmax(0,1fr) var(--margin-note-w);gap:var(--sp-8);padding:var(--sp-5) var(--sp-6);border-top:1px solid var(--border-subtle)} +.finding{position:relative;display:grid;grid-template-columns:minmax(0,1fr) var(--margin-note-w);gap:var(--sp-8);padding:var(--sp-5) var(--card-x);border-top:var(--line) solid var(--border-subtle)} .finding:first-of-type{border-top:0} -.finding.is-fatal::before{content:"";position:absolute;left:0;top:var(--sp-5);bottom:var(--sp-5);width:3px;background:var(--sev-fatal)} +/* v0.2 收尾:「致命」行不再加左侧红竖线,级别只靠实心「致命」标签(与工作台一致);左侧 3px 竖条只表示「当前条」(墨色,§7.18) */ .f-head{display:flex;align-items:center;gap:var(--sp-2)} .f-title{font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans)} -.f-head .flag{margin-left:var(--sp-1);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);padding:0 5px;line-height:18px} +.f-head .flag{margin-left:var(--sp-1);border-radius:var(--radius-tag);background:var(--c-n100);padding:0 6px;line-height:var(--tag-h)} .f-text{margin:var(--sp-2) 0 0;color:var(--text-primary)} .f-text + .f-text{margin-top:0} .f-actions{display:flex;flex-wrap:wrap;gap:var(--sp-2);align-items:center;margin-top:var(--sp-3)} .f-actions .feedback{margin-left:auto;font-size:var(--fs-caption)} +/* 人工确认署名(§3.3、Q2):绿 ✓ + 姓名 + 时间 + 动作 */ .sign{margin-top:var(--sp-3);font-size:var(--fs-body);color:var(--confirmed);display:flex;gap:var(--sp-2);align-items:baseline;flex-wrap:wrap} .sign b{font-weight:var(--fw-bold)} -.sign .fig{font-weight:500} +.sign .fig{font-weight:var(--fw-medium)} .sign .undo{color:var(--text-secondary);font-size:var(--fs-caption);margin-left:var(--sp-2)} +/* 出处(§7.3):左侧 2px 蓝铅笔竖线 + 原句 */ .cite{position:relative;border-left:2px solid var(--ai);padding:2px 0 2px var(--sp-3)} .cite + .cite{margin-top:var(--sp-3)} .finding .cite:first-child::before{content:"";position:absolute;left:calc(-1 * var(--sp-8) - 2px);top:11px;width:calc(var(--sp-8) - 4px);border-top:1.5px dashed var(--ai)} .cite-src{display:block;color:var(--ai);font-size:var(--fs-caption);line-height:var(--lh-caption);text-decoration:none} -.cite-src:hover{color:var(--ai-strong);text-decoration:underline;text-underline-offset:3px} -.cite-src .fig{font-size:13px;font-weight:600} +a.cite-src:hover{color:var(--ai-strong);text-decoration:underline;text-underline-offset:3px} +.cite-src .fig{font-size:var(--fs-figure-xs)} .cite q{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:4;overflow:hidden;quotes:none;margin-top:2px;line-height:var(--lh-read);color:var(--text-primary)} .cite.on{border-left-width:4px;padding-left:calc(var(--sp-3) - 2px)} .cite q mark{background:var(--ai-soft);color:inherit;padding:0 1px} .cite.missing{border-left-style:dashed} .cite.missing p{margin:2px 0 0;color:var(--text-secondary);font-size:var(--fs-caption);line-height:var(--lh-caption)} -.ocr{font:600 11px/1 var(--font-figures);color:var(--text-secondary);border:1px solid var(--border-subtle);padding:1px 3px;border-radius:2px;margin-left:4px;vertical-align:1px} -.folded{padding:var(--sp-3) var(--sp-6);border-top:1px solid var(--border-subtle);background:var(--bg-sunken);color:var(--text-secondary);font-size:var(--fs-body)} +/* 行内出处链接(d5):蓝铅笔字,悬停出蓝铅笔浅底;用在列表行右侧 */ +.cite-link{display:inline-flex;align-items:center;gap:var(--sp-1);margin:-2px -6px;padding:2px 6px;border-radius:var(--radius-ctl);color:var(--ai);font-size:var(--fs-body);text-decoration:none;white-space:nowrap;transition:background-color var(--dur-fast) var(--ease)} +.cite-link:hover{background:var(--ai-soft);color:var(--ai-strong)} +.cite-link .fig{font-size:var(--fs-figure-sm)} +.ocr{font:var(--fw-bold) 11px/1 var(--font-figures);color:var(--text-secondary);background:var(--c-n100);padding:2px 4px;border-radius:var(--radius-xs);margin-left:4px;vertical-align:1px} +.folded{padding:var(--sp-3) var(--card-x);border-top:var(--line) solid var(--border-subtle);color:var(--text-secondary);font-size:var(--fs-body)} -/* ---- 清单与待确认 ---- */ -.pending{border:1.5px dashed var(--ai);border-radius:var(--radius-sm);background:var(--bg-surface);padding:var(--sp-4) var(--sp-5)} +/* ---- 清单与待确认 ---- + * .pending(虚线蓝框)是 v0.1 的待确认区,只剩原型在用;v0.2 起待确认是清单顶部的分组(ui-demo/demo.css 的 .grp-h.ai,§7.6),原型重刷时替换 */ +.pending{border:1.5px dashed var(--ai);border-radius:var(--radius-card);background:var(--bg-surface);padding:var(--card-y) var(--card-x)} .pending h4{margin:0;font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans);color:var(--ai)} .pending > p{margin:var(--sp-1) 0 var(--sp-3);color:var(--text-secondary)} -.item{display:grid;grid-template-columns:24px minmax(0,1fr) auto;gap:var(--sp-3);padding:var(--sp-3) 0;border-top:1px solid var(--border-subtle);align-items:start} +.item{display:grid;grid-template-columns:24px minmax(0,1fr) auto;gap:var(--sp-3);padding:var(--sp-3) 0;border-top:var(--line) solid var(--border-subtle);align-items:start} .item:first-of-type{border-top:0} .imp{font-size:var(--fs-title);line-height:var(--lh-body);text-align:center} .item .t{font-weight:var(--fw-bold)} .item .meta{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-top:4px} .item .side{text-align:right;font-size:var(--fs-caption);line-height:var(--lh-caption)} +/* 选中(Q3):--c-n50 底 + 左侧 3px 墨色竖条;蓝色只留给原文高亮和出处 */ +.item.sel{background:var(--c-n50);box-shadow:inset 3px 0 0 var(--c-ink)} .src-ai{color:var(--ai)} .old{text-decoration:line-through;color:var(--text-secondary)} -.list-head{display:flex;align-items:baseline;gap:var(--sp-3);margin-bottom:var(--sp-3)} +.list-head{display:flex;align-items:center;gap:var(--sp-3);margin-bottom:var(--sp-3)} .list-head h4{margin:0;font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans)} -/* ---- 参数对照表 ---- */ -table.data{width:100%;border-collapse:collapse;background:var(--bg-surface);border:1px solid var(--border-subtle)} -table.data th{background:var(--bg-sunken);text-align:left;font-weight:var(--fw-bold);font-size:var(--fs-caption);color:var(--text-secondary);padding:var(--sp-2) var(--sp-3);border-bottom:2px solid var(--border-strong);white-space:nowrap} +/* ---- 表格(参数对照表、流水、项目列表)---- + * v0.2:表头无底色、无粗线,12px 辅助灰列名;行间发丝线;首尾列与卡片左右边距对齐。 + * 单独摆放时自带卡片外框;放进 .card.flush 时去掉外框。 */ +table.data{width:100%;border-collapse:separate;border-spacing:0;background:var(--bg-surface);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-card)} +.card table.data,.results table.data{border:0;border-radius:0;background:none} +table.data th{text-align:left;font-weight:var(--fw-regular);font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary);padding:var(--sp-3) var(--sp-3) var(--sp-2);white-space:nowrap;background:none} +table.data td{padding:10px var(--sp-3);border-top:var(--line) solid var(--border-subtle);vertical-align:top;height:44px} +table.data th:first-child,table.data td:first-child{padding-left:var(--card-x)} +table.data th:last-child,table.data td:last-child{padding-right:var(--card-x)} +.card:not(.flush) table.data th:first-child,.card:not(.flush) table.data td:first-child{padding-left:0} +.card:not(.flush) table.data th:last-child,.card:not(.flush) table.data td:last-child{padding-right:0} table.data td:first-child{white-space:nowrap} -table.data td{padding:var(--sp-2) var(--sp-3);border-bottom:1px solid var(--border-subtle);vertical-align:top;height:40px} -table.data tr:last-child td{border-bottom:0} -table.data .num{font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-weight:500;font-size:15px;white-space:nowrap} +table.data tbody tr:last-child td:first-child{border-bottom-left-radius:var(--radius-card)} +table.data tbody tr:last-child td:last-child{border-bottom-right-radius:var(--radius-card)} +table.data .num{font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-weight:var(--fw-medium);font-size:var(--fs-figure-sm);white-space:nowrap} table.data .r{text-align:right} -table.data a{color:var(--ai);text-decoration:none;font-size:var(--fs-caption);white-space:nowrap} -table.data a .fig{font-size:13px} +table.data a:not(.btn):not(.plain):not(.lnk){color:var(--ai);text-decoration:none;font-size:var(--fs-caption);white-space:nowrap} +table.data a:not(.btn):not(.plain):not(.lnk):hover{color:var(--ai-strong);text-decoration:underline;text-underline-offset:3px} +table.data a .fig{font-size:var(--fs-figure-xs)} +table.data.hover tbody tr{cursor:pointer} +table.data.hover tbody tr td{transition:background-color var(--dur-fast) var(--ease)} +table.data.hover tbody tr:hover td{background:var(--bg-hover)} +/* 选中行(Q3):与读标清单一致 */ +table.data tbody tr.sel td{background:var(--c-n50)} +table.data tbody tr.sel td:first-child{box-shadow:inset 3px 0 0 var(--c-ink)} +/* 参数对照表「判断」列(§7.7):只用四个词;同一行另有「级别」列(Q5) */ .verdict{font-weight:var(--fw-bold);white-space:nowrap} .verdict.fatal{color:var(--sev-fatal)} .verdict.major{color:var(--sev-major-text)} .verdict.manual{color:var(--text-secondary);font-weight:var(--fw-regular)} /* ---- 原文查看器 ---- */ -.viewer{background:var(--bg-sunken);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);display:flex;flex-direction:column} -.viewer-bar{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-2) var(--sp-3);background:var(--bg-surface);border-bottom:1px solid var(--border-subtle);font-size:var(--fs-caption)} -.viewer-bar b{font-size:var(--fs-body)} -.viewer-bar .pager{margin-left:auto;display:flex;gap:var(--sp-2);align-items:center} -.sheet{margin:var(--sp-4) auto;width:86%;background:#fff;box-shadow:0 1px 2px rgba(29,39,51,.12);padding:28px 32px;font:13px/1.9 "Songti SC","SimSun",var(--font-serif);color:#222} +.viewer{background:var(--c-n100);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-card);display:flex;flex-direction:column;overflow:hidden} +.viewer-bar{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-2) var(--sp-3) var(--sp-2) var(--sp-4);background:var(--bg-surface);border-bottom:var(--line) solid var(--border-subtle);font-size:var(--fs-caption)} +.viewer-bar b{font-size:var(--fs-body);white-space:nowrap} +.viewer-bar > span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.viewer-bar .pager{margin-left:auto;display:flex;flex:none;gap:var(--sp-2);align-items:center;white-space:nowrap} +.sheet{margin:var(--sp-4) auto;width:86%;background:var(--c-white);box-shadow:var(--shadow-sheet);padding:28px 32px;font:var(--fs-doc)/var(--lh-doc) var(--font-doc);color:var(--text-primary)} .sheet p{margin:0 0 6px} -.sheet .hl{background:var(--ai-soft);outline:1px solid var(--ai);outline-offset:1px} -.sheet .hl.on{outline-width:2px} -.sheet .faint{color:#888} +/* 原句高亮(§7.8):蓝铅笔浅底 + 蓝色底边;跨行时每一行都带底边,换行处不断开(box-decoration-break)。当前出处底边加粗为 2px */ +.sheet .hl{background:var(--ai-soft);box-shadow:inset 0 -1px 0 var(--ai);padding:1px 0;-webkit-box-decoration-break:clone;box-decoration-break:clone} +.sheet .hl.on{box-shadow:inset 0 -2px 0 var(--ai)} +.sheet .faint{color:var(--text-secondary)} /* ---- 工作台 ---- */ .proj-name{font-weight:var(--fw-bold);font-size:var(--fs-title);line-height:var(--lh-title)} -.proj-sub{font-size:var(--fs-caption);color:var(--text-secondary)} +.proj-sub{font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} .due-soon{color:var(--warn-text)} .due-today{color:var(--warn-text);font-weight:var(--fw-bold)} .due-past{color:var(--text-secondary)} .ready{display:flex;gap:6px;align-items:center;flex-wrap:wrap} /* ---- 额度 ---- */ -.quota .big{font:600 var(--fs-figure)/var(--lh-figure) var(--font-figures);font-variant-numeric:tabular-nums} -.quota .unit{color:var(--text-secondary);margin-left:4px} -.bar{height:6px;background:var(--bg-sunken);border-radius:1px;overflow:hidden;margin:var(--sp-2) 0} -.bar i{display:block;height:100%;background:var(--c-ink)} +.quota .big{font:var(--fw-bold) var(--fs-figure)/var(--lh-figure) var(--font-figures);font-variant-numeric:tabular-nums} +.quota .unit{color:var(--text-secondary);margin-left:4px;font-family:var(--font-figures);font-variant-numeric:tabular-nums} +.bar{height:6px;background:var(--c-n100);border-radius:3px;overflow:hidden;margin:var(--sp-2) 0} +.bar i{display:block;height:100%;background:var(--c-ink);border-radius:3px} .bar.warn i{background:var(--warn)} -.flow td.plus{color:var(--confirmed)} +/* 流水「+ / −」(Q1):都用墨色,靠符号区分;绿色只表示人工确认 */ +.flow td.plus,.flow td.minus{color:var(--text-primary)} -/* ---- 提醒 ---- */ -.alert{display:flex;gap:var(--sp-3);padding:var(--sp-3) var(--sp-4);border-left:3px solid var(--c-ink);background:var(--bg-surface);border-radius:0 var(--radius-sm) var(--radius-sm) 0} +/* ---- 提醒(§7.12)---- + * v0.2:和卡片同规格的白条(圆角 12、发丝线),不用彩色浅底和左边线;颜色只在图标和关键字上。 */ +.alert{display:flex;gap:var(--sp-3);align-items:flex-start;padding:14px var(--card-x);background:var(--bg-surface);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-card)} .alert + .alert{margin-top:var(--sp-3)} -.alert.warn{border-left-color:var(--warn);background:var(--warn-soft)} -.alert.warn b{color:var(--warn-text)} -.alert.done{border-left-color:var(--confirmed)} +.alert.warn b,.alert .when{color:var(--warn-text);font-weight:var(--fw-bold)} .alert p{margin:0} -.ico{flex:none;width:18px;height:18px;border-radius:50%;display:inline-grid;place-items:center;font:700 12px/1 var(--font-sans);margin-top:2px} +.alert > .ic{flex:none;margin-top:2px} +.alert.warn > .ic{color:var(--warn)} +.alert.done > .ic{color:var(--confirmed)} +.ico{flex:none;width:18px;height:18px;border-radius:50%;display:inline-grid;place-items:center;font:var(--fw-bold) var(--fs-caption)/1 var(--font-sans);margin-top:2px} .ico.i{border:1.5px solid var(--c-ink)} .ico.w{border:1.5px solid var(--warn);color:var(--warn-text)} -.ico.ok{background:var(--confirmed);color:#fff} +.ico.ok{background:var(--confirmed);color:var(--c-white)} /* ---- 表单 ---- */ .field{display:flex;flex-direction:column;gap:6px;max-width:360px} .field + .field{margin-top:var(--sp-4)} .field label{font-weight:var(--fw-bold)} .field label small{font-weight:var(--fw-regular);color:var(--text-secondary)} -.input{height:var(--control-h);border:1px solid var(--border-control);border-radius:var(--radius-sm);padding:0 10px;font:var(--fs-body) var(--font-sans);color:var(--text-primary);background:var(--bg-surface)} +.input{height:var(--control-h);border:var(--line) solid var(--border-control);border-radius:var(--radius-ctl);padding:0 var(--sp-3);font:var(--fs-body) var(--font-sans);color:var(--text-primary);background:var(--bg-surface)} .input::placeholder{color:var(--text-secondary)} .field.err .input{border-color:var(--warn);box-shadow:inset 0 0 0 1px var(--warn)} .field .msg{font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} .field.err .msg{color:var(--warn-text);display:flex;gap:6px;align-items:center} .field.err .msg .ico{width:14px;height:14px;font-size:10px;margin:0} .chips{display:flex;gap:6px;flex-wrap:wrap} -.chips .btn{height:24px;padding:0 8px;font-size:var(--fs-caption)} +.chips .btn{height:26px;padding:0 var(--sp-2);font-size:var(--fs-caption);border-radius:var(--radius-tag)} /* ---- 进度 ---- */ .steps{list-style:none;margin:0;padding:0} .steps li{display:flex;gap:var(--sp-3);align-items:center;padding:6px 0} .steps .dot{width:18px;height:18px;border-radius:50%;border:1.5px solid var(--border-control);flex:none;display:grid;place-items:center;font-size:11px} -.steps .done .dot{background:var(--c-ink);border-color:var(--c-ink);color:#fff} +.steps .done .dot{background:var(--c-ink);border-color:var(--c-ink);color:var(--c-white)} .steps .now .dot{border-color:var(--c-ink);border-width:2px} .steps .now{font-weight:var(--fw-bold)} .steps .todo{color:var(--text-secondary)} -/* ---- 报告页眉 ---- */ -.report{background:#fff;border:1px solid var(--border-subtle);max-width:620px;padding:28px 36px;font-size:13px;line-height:1.6} -.report-head{display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--c-ink);padding-bottom:10px} +/* ---- 报告页眉(模拟 PDF,字号按纸面,不跟界面字阶) ---- */ +.report{background:var(--c-white);border:var(--line) solid var(--border-subtle);max-width:620px;padding:28px 36px;font-size:13px;line-height:1.6} +.report-head{display:flex;justify-content:space-between;align-items:center;border-bottom:var(--line) solid var(--c-ink);padding-bottom:10px} .report-head img{height:26px} .report-head span{font-size:11px;color:var(--text-secondary)} -.report h3{font:600 22px/1.4 var(--font-serif);margin:22px 0 4px} +.report h3{font:var(--fw-bold) 22px/1.4 var(--font-serif);margin:22px 0 4px} .report dl{display:grid;grid-template-columns:96px 1fr;gap:4px 12px;margin:14px 0} .report dt{color:var(--text-secondary)} .report dd{margin:0} -.scope{border:1px solid var(--border-subtle);padding:10px 14px;margin-top:10px} +.scope{border:var(--line) solid var(--border-subtle);padding:10px 14px;margin-top:10px} .scope b{display:block;margin-bottom:2px} -.report-foot{margin-top:18px;padding-top:8px;border-top:1px solid var(--border-subtle);display:flex;justify-content:space-between;font-size:11px;color:var(--text-secondary)} +.report-foot{margin-top:18px;padding-top:8px;border-top:var(--line) solid var(--border-subtle);display:flex;justify-content:space-between;font-size:11px;color:var(--text-secondary)} @media (max-width:1100px){ diff --git a/docs/design/system/tokens.css b/docs/design/system/tokens.css index c67c607..8cd73fa 100644 --- a/docs/design/system/tokens.css +++ b/docs/design/system/tokens.css @@ -4,6 +4,8 @@ * 对比度一律按 WCAG 2.x 公式计算。v0.1 的注释数值以校样纸 #F5F6F3 为底,括号内为白底; * v0.2 起页面底色改为 --c-n50 #F8F8F9(比校样纸更亮),对比度介于两者之间。 * v0.2(决策 U5):页面改为白色主调 + 墨色派生的中性浅灰;圆角三档;控件高 36;墨色渐变窄侧栏。 + * v0.2 补充(2026-11-03,Q6 / Q7 已确认):悬停 / 按下 / 遮罩等语义色、数字字号三档、卡片与页面内边距。 + * 组件里不再写裸色值(#34404D、#222、#888 之类),一律用这里的变量。 */ :root { /* ---------- 基础色板:校样色系 ---------- */ @@ -30,6 +32,12 @@ --c-n50: #F8F8F9; /* 墨 3%:页面底色、行 / 格悬停 */ --c-n100: #F1F2F3; /* 墨 6%:中性标签底、文字按钮悬停、进度槽 */ --c-nline: #E6E7E9; /* 墨 11%:卡片边框、发丝分隔线 */ + --c-n300: #BBBEC2; /* 墨 30%(v0.2):普通链接的下划线(悬停时变墨色) */ + --c-n400: #A5A9AD; /* 墨 40%(v0.2):带悬停说明的文字的虚线下划线(如「就绪」) */ + + /* 墨色的交互态(v0.2,Q6):主按钮、选中日期等墨底元素 */ + --c-ink-hover: #38414B; /* 墨 88% 混白:悬停变浅 */ + --c-ink-active: #171F29; /* 墨 80% 混黑:按下变深 */ /* 侧栏(v0.2):墨色竖向渐变,下端混 18% 蓝铅笔得到深墨蓝;只作底色,不承担 AI 语义 */ --side-top: var(--c-ink); @@ -40,6 +48,9 @@ --side-hover: rgba(255, 255, 255, .07); --side-active: rgba(255, 255, 255, .14); --side-line: rgba(255, 255, 255, .12); + --side-dots: rgba(255, 255, 255, .14); /* 底部点阵(v0.2) */ + --side-avatar-bg: rgba(255, 255, 255, .16); /* 44px 头像底(v0.2) */ + --side-avatar-ring: rgba(255, 255, 255, .22); /* 头像 1px 外圈(v0.2) */ /* 套餐标签(v0.2):只用于账户区的套餐标签,从赭色派生的金色阶梯;靠深浅区分档次 */ --plan-free-bg: rgba(255, 255, 255, .12); @@ -76,12 +87,21 @@ --sev-major-text: var(--c-ochre-ink); --sev-major-soft: var(--c-ochre-soft); --sev-hint: var(--c-sub); - --sev-hint-soft: var(--c-sunken); + --sev-hint-soft: var(--c-n100); /* v0.2:原为 --c-sunken */ --warn: var(--c-ochre); /* 系统提醒、表单错误、额度预警:不用红 */ --warn-text: var(--c-ochre-ink); --warn-soft: var(--c-ochre-soft); + /* 交互与浮层(v0.2,Q6) */ + --link-underline: var(--c-n300); /* 普通链接:墨色字 + 浅下划线,悬停下划线变墨 */ + --hint-underline: var(--c-n400); /* 有悬停说明的文字:虚线下划线 */ + --primary-hover: var(--c-ink-hover); + --primary-active: var(--c-ink-active); + --scrim: rgba(29, 39, 51, .32); /* 对话框遮罩:墨 32% */ + --topbar-glass: rgba(255, 255, 255, .82); /* 顶栏滚动后:半透明白 + 毛玻璃 */ + --on-dark-line: rgba(255, 255, 255, .24); /* 墨底浮层(轻提示)里的分隔线 */ + /* ---------- 字体 ---------- */ --font-sans: "PingFang SC", "Microsoft YaHei", "Noto Sans SC", "Noto Sans CJK SC", "Source Han Sans SC", system-ui, sans-serif; --font-serif: "Noto Serif SC", "Source Han Serif SC", "Songti SC", serif; @@ -94,7 +114,13 @@ --fs-title: 16px; --lh-title: 24px; /* 问题标题、卡片标题、区块标题 */ --fs-h2: 20px; --lh-h2: 28px; /* 页面标题(宋体) */ --fs-h1: 24px; --lh-h1: 32px; /* 工作台、报告标题(宋体) */ - --fs-figure: 30px; --lh-figure: 36px; /* 统计数字(Barlow) */ + --fs-figure: 28px; --lh-figure: 36px; /* 统计数字、额度剩余(Barlow 600)。v0.2:原 30 */ + --fs-figure-sm: 15px; --lh-figure-sm: 22px; /* 正文行里独立出现的数字:表格数值、日期、页码、筛选计数(v0.2,Q7) */ + --fs-figure-xs: 13px; --lh-figure-xs: 20px; /* 12px 小字行里的数字:标签计数、出处页码、扣点说明(v0.2,Q7) */ + /* .fig 默认用 --fs-figure-sm;12px 小字里自动换成 --fs-figure-xs;统计大数字(.big)用 --fs-figure(见 components.css) */ + /* 原文排版(v0.2):原文查看器里的「纸页」模拟招标文件的打印效果,用宋体,不跟界面字阶 */ + --font-doc: "Songti SC", "SimSun", var(--font-serif); + --fs-doc: 13px; --lh-doc: 24px; --fw-regular: 400; --fw-medium: 500; --fw-bold: 600; @@ -104,31 +130,50 @@ --sp-6: 24px; --sp-8: 32px; --sp-10: 40px; --sp-12: 48px; --sp-16: 64px; /* ---------- 圆角、线、阴影 ---------- */ - --radius-tag: 6px; /* 标签、问题级别标签(v0.2) */ + --radius-xs: 4px; /* 复选框、开关滑钮、套餐标签等 ≤ 16px 的小件(v0.2,Q4) */ + --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); + --shadow-overlay: 0 4px 16px rgba(29, 39, 51, .14), 0 1px 3px rgba(29, 39, 51, .10); /* 只给浮层:下拉、对话框、提示、轻提示 */ + --shadow-sheet: 0 1px 2px rgba(29, 39, 51, .12); /* 原文查看器里的「纸页」 */ /* ---------- 尺寸 ---------- */ --control-h: 36px; /* 默认按钮、输入框(v0.2:原 32) */ --control-h-sm: 32px; /* 表格行内、筛选标签(v0.2:原 28) */ --control-h-lg: 40px; /* 登录、空状态主操作 */ + --btn-px: 14px; /* 按钮左右内边距:36 / 32 / 40 对应 14 / 12 / 16(v0.2,以 d5 为准) */ + --btn-px-sm: 12px; + --btn-px-lg: 16px; + --tag-h: 22px; /* 中性标签、问题级别标签的高度(v0.2,以 d5 为准) */ + --row-h: 64px; /* 列表行最小高度:两行文字(名称 + 辅助灰小字)的项目行(v0.2) */ --rail-w: 96px; /* 侧栏(v0.2):单一状态,上图标下文字,不折叠 */ --topbar-h: 56px; /* 顶栏:透明,滚动后半透明白 + 毛玻璃 */ --sidebar-w: 216px; /* v0.1 宽侧栏,仅旧原型使用,重刷后删除 */ --margin-note-w: 280px; /* 页边批注栏 */ --content-max: 1200px; + /* 页面与卡片的节奏(v0.2,以 d5 为准):全页一个间距 */ + --pad-page: 32px; /* 内容区左右内边距;1400 以下 24 */ + --gap-card: 16px; /* 卡片之间、主列与右栏之间 */ + --card-x: 20px; /* 卡片内左右边距:所有卡片统一 */ + --card-y: 16px; /* 卡片内上下边距(不分区的整块卡片) */ + --card-h-min: 56px; /* 卡片头(标题行)最小高度 */ + --aside-w: 296px; /* 工作台右侧信息栏;1400 以下 256 */ + /* ---------- 动效 ---------- */ --dur-fast: 120ms; --dur-base: 200ms; --ease: cubic-bezier(.2, 0, 0, 1); } +@media (max-width: 1399px) { + :root { --pad-page: 24px; --aside-w: 256px; } +} + @media (prefers-reduced-motion: reduce) { :root { --dur-fast: 0ms; --dur-base: 0ms; } } diff --git a/docs/design/ui-demo/cards.html b/docs/design/ui-demo/cards.html index 3ca86e6..c0e9c8d 100644 --- a/docs/design/ui-demo/cards.html +++ b/docs/design/ui-demo/cards.html @@ -1,4 +1,9 @@ + @@ -9,223 +14,295 @@ +

③ 卡片

-

卡片是白底 + 1px 分隔线 + 2px 圆角,不加阴影(层次靠分隔线和底色)。卡片里的按钮一律用次按钮或文字按钮,把主按钮留给页面级操作。

- +

只有一种容器:白卡片,1px 发丝线,圆角 12,无阴影,卡片里不再套面板。工作台只用列表,不做项目卡片视图(Q11)。

+
- -
-
01

项目卡

规范 §7.9 · 卡片视图(表格视图见 ④)
-
-
-
某市政务服务中心智慧大厅建设项目
某市政务服务管理办公室 · 政府采购 · 公开招标
已检查
-
-
投标截止11月20日 09:30
剩 17 天
-
所投包采购包 1
-
就绪未就绪
-
-
致命3重要3提示2第 2 次检查 · 11月2日
-
-
-
-
某区智慧社区平台运维服务
某区民政局 · 政府采购 · 竞争性磋商
已检查
-
-
响应截止剩 2 天 5 小时
-
所投包全部
-
就绪就绪
-
-
致命 —重要1第 3 次检查 · 11月3日
-
-
-
-
某县数据中心机房改造项目
某县大数据管理局 · 招标投标
编制中
-
-
投标截止今天 17:00 截止
-
所投标段标段 1
-
清单草稿 v1
-
-
跟进中编制中已检查已递交
-
待确认 2 条
-
-
-
-
-
某区智慧校园一期
某区教育局 · 政府采购 · 公开招标
已递交
-
-
投标截止已截止
-
所投包全部
-
递交10月28日
-
-
✓王敏10月28日 09:12标记已递交
-
-
-
-
-
- -

↑ 加载中(骨架屏)

-
- -
- -
- - +
-
02

统计格

规范 §4.2 Figure 30/36
-
-
进行中的项目4

已递交 1 · 已归档 1

-
7 天内截止2

最近:今天 17:00

-
待处理的致命问题3

在 1 个项目里

-
本期解析剩余37次

检查剩 12 次 · 查看明细

-
- +
01

统计卡

规范 §4.2 · §9.5
+ 可点筛选(工作台):按下后标题加粗、底边出墨色线 +
+ + + + +
+ 只读(额度页):数字后可带单位 +
+
本期解析13/ 50 次
+
本期检查8/ 20 次
+
本期退回2次
+
+
- +
-
03

额度卡

规范 §7.10 · §8.3 · 红线 9
-
-
-

本期额度

专业版 · 至 2027-10-31
-
-
解析剩余37/ 50

已用 13 次

-
检查剩余12/ 20

已用 8 次

+
02

额度卡

规范 §7.10 · §8.3 · 红线 9
+
+
+

本期额度

至 2027-10-31
+
+
+
解析剩余37/ 50
+
检查剩余12/ 20
+
- -
-
-

检查额度

已用 80%
- 检查剩余4/ 20 次 -
-

已用 80%。同一项目 24 小时内复查不扣额度。

-
提醒每期只出现一次查看流水
-
-
-

检查额度

已用完
- 检查剩余0/ 20 次 -
-

本期检查额度已用完,续费或下期发放后可继续。

-
查看流水
-
+ +
+
+

检查额度

已用 80%
+
+ 检查剩余4/ 20 + +

同一项目 24 小时内复查不扣额度。

+
+ +
+
+

检查额度

已用完
+
+ 检查剩余0/ 20 + +

本期检查额度已用完,可在额度页提交续费申请,或等下期发放。

+
+
查看流水
+
-
顶栏常驻解析37检查12解析37检查4检查用到 80% 时,顶栏描边变赭色
+ + 顶栏额度(白底只为陈列;真实顶栏见 工作台 · 80% / 用完) +
+
正常解析37检查12
+
检查用到 80%解析37检查4
+
检查用完(悬停说明续费)解析37检查0
+
+ - +
-
04

任务进度卡

规范 §7.11
+
03

任务进度卡

规范 §7.11
-
-

正在解析招标文件

已用时 1 分 40 秒
-

ZC-2026-0418 采购文件.pdf · 186 页

-
    -
  1. ✓识别文件格式
  2. -
  3. ✓提取文字186 页
  4. -
  5. ✓识别分包2 个包
  6. -
  7. 提取清单62%
  8. -
  9. 核对出处
  10. -
-
62%
-

可以离开本页,完成后会发站内通知。

-
取消后退回 1 次解析额度
-
-
-

解析失败

11月2日 14:31
-

关于 ZC-2026-0418 的更正公告.pdf

-
    -
  1. ✓识别文件格式
  2. -
  3. !提取文字
  4. -
  5. 识别分包
  6. -
  7. 提取清单
  8. -
-
!

文件已加密,无法读取文字。已退回 1 次解析额度。

请上传未加密的版本,或在 PDF 阅读器里另存一份。
-
E-20261102-3M8A
-
-
-
-

解析完成

用时 3 分 12 秒
-
-
提取要求
26 条 · 其中 ★ 4 条
-
待确认
2 条没有可靠出处
-
分包
2 个采购包
-
-
已扣 1 次解析
+
+

正在解析招标文件

已用时 1 分 40 秒
+
+

ZC-2026-0418 采购文件.pdf · 186 页

+
    +
  1. ✓识别文件格式
  2. +
  3. ✓提取文字186 页
  4. +
  5. ✓识别分包2 个包
  6. +
  7. 提取清单
  8. +
  9. 核对出处
  10. +
+
62%
+

可以离开本页,完成后会发站内通知。

-
-

检查排队中

前面还有 2 个任务 · 约 4 分钟
+
取消后退回 1 次解析
+
+ +
+

解析失败

11月2日 14:31
+
+

关于 ZC-2026-0418 的更正公告.pdf

+
    +
  1. ✓识别文件格式
  2. +
  3. !提取文字
  4. +
  5. 识别分包
  6. +
  7. 提取清单
  8. +
+

文件已加密,无法读取文字。已退回 1 次解析额度。请上传未加密的版本。

+
E-20261102-3M8A
+
+ +
+
+

解析完成

用时 3 分 12 秒
+
+
+
提取要求
26 条 · 其中 ★ 4 条
+
待确认
2 条(没有可靠出处)
+
分包
2 个采购包
+
+
+
去读标已扣 1 次解析
+
+
+
检查排队中前面 2 个任务 · 约 4 分钟
+
+
  • 阶段:✓ 完成(墨色实心)、当前(加粗)、未开始(辅助灰);失败用赭色「!」并说明原因和退回额度,不用红。
- +
-
05

AI 摘要卡

规范 §7.16 · §8.1 · 红线 1、2
-
-
-
AI 摘要

项目要点

未确认 · 每一条都可以点出处核对
-
-
-
法律体系
政府采购 · 公开招标(按「投标无效」口径检查)
-
预算 / 限价
采购包 1:286.00 万元(最高限价同预算)第 2 页
-
评标方法
综合评分法:价格 10、技术 40、实施方案 20、售后 10、认证 6、业绩 8第 31 页
-
联合体
不接受第 2 页
-
保证金
不收取第 21 页
-
样品
未找到要求 · 已查找:第二章、第三章、第六章
-
+
04

AI 摘要卡

规范 §7.16 · §8.1 · 红线 1、2
+
+
+

项目要点

AI 摘要
+
+
+
+
法律体系
政府采购 · 公开招标(按「投标无效」口径检查)
+
预算 / 限价
采购包 1:286.00 万元(最高限价同预算)第 2 页
+
评标方法
综合评分法:价格 10、技术 40、实施方案 20、售后 10、认证 6、业绩 8第 31 页
+
联合体
不接受第 2 页
+
保证金
不收取第 21 页
+
样品
未找到要求 · 已查找:第二章、第三章、第六章
+
+
+
-
判断有误?
-
-
-

项目要点

✓王敏11月2日 14:20确认无误
-
-
-
法律体系
政府采购 · 公开招标(按「投标无效」口径检查)
-
预算 / 限价
采购包 1:286.00 万元(最高限价同预算)第 2 页
-
评标方法
综合评分法:价格 10、技术 40、实施方案 20、售后 10、认证 6、业绩 8第 31 页
-
联合体
不接受 已修改 · 查看记录第 2 页
-
保证金
不收取第 21 页
-
样品
未找到要求 · 已查找:第二章、第三章、第六章
-
+
判断有误?
+
+ +
+

项目要点

+
+
+
+
法律体系
政府采购 · 公开招标(按「投标无效」口径检查)
+
预算 / 限价
采购包 1:286.00 万元(最高限价同预算)第 2 页
+
评标方法
综合评分法:价格 10、技术 40、实施方案 20、售后 10、认证 6、业绩 8第 31 页
+
联合体
不接受 已修改第 2 页
+
保证金
不收取第 21 页
+
样品
未找到要求 · 已查找:第二章、第三章、第六章
+
+
王敏11月2日 14:20确认无误
+
-
- +
+
- + - +
-
06

关键日期卡

规范 §7.9 倒计时 · §9.3
+
05

关键日期卡

规范 §7.9 倒计时 · §9.3
-
-

关键日期

以补遗 1 更正后的时间为准
-
    -
  • 10-28采购文件公告发布
  • -
  • 11-09询问、质疑截止公告期满后 7 个工作日 · 剩 6 天第 6 页
  • -
  • 11-20 09:30投标截止 / 开标剩 17 天 · 已被补遗 1 更新,原为 11-18 09:30补遗 1 · 第 1 页
  • -
  • 2027-02-18投标有效期至截止日起 90 天第 21 页
  • -
-

MVP 只在站内显示倒计时,不发截止提醒。

-
-
-

7 天内截止

全部项目
-
    -
  • 今天 17:00某县数据中心机房改造项目剩 6 小时 · 编制中标段 1
  • -
  • 11-05 14:30某区智慧社区平台运维服务剩 2 天 5 小时 · 就绪全部
  • -
-
  • > 72 小时:墨色「11月20日 09:30 · 剩 17 天」;≤ 72 小时:赭色小字;≤ 24 小时:赭色加粗;已过:辅助灰「已截止」。
  • 截止临近不用红色、不闪烁。
-
+
+

关键日期

已按补遗 1 更新
+
+
    +
  • 10月28日采购文件公告发布
  • +
  • 11月9日询问、质疑截止剩 6 天第 6 页
  • +
  • 11月20日 09:30投标截止 / 开标剩 17 天 · 补遗 1 更新,原为 11月18日 09:30补遗 1 · 第 1 页
  • +
  • 2027年2月18日投标有效期至截止日起 90 天第 21 页
  • +
+
+
+
+

7 天内截止

全部项目
+
+
    +
  • 今天 17:00某县数据中心机房改造项目剩 6 小时 · 编制中标段 1
  • +
  • 11月5日 16:00某区智慧社区平台运维服务剩 2 天 5 小时 · 已检查全部
  • +
+
+
+ +
+ + +
+
06

项目信息卡

规范 §7.9 · §9.1
+
+
+

项目信息

+
+
+
采购人
某市政务服务管理办公室
+
项目编号
ZC-2026-0418
+
体系
政府采购 · 公开招标
+
所投包
采购包 1
+
投标截止
11月20日 09:30 · 剩 17 天
+
状态
已检查未就绪
+
+
跟进中编制中已检查已递交
+
+
+
+

项目信息

+
+
+
招标人
某县大数据管理局
+
项目编号
JS-2026-1107
+
体系
招标投标 · 公开招标
+
所投标段
标段 1
+
投标截止
今天 17:00 截止
+
状态
编制中
+
+
跟进中编制中已检查已递交
+
+
+
+

项目信息

+
+ +

加载中:骨架屏静止,不做明暗起伏(Q8)

+
+
+
+
@@ -233,8 +310,33 @@ diff --git a/docs/design/ui-demo/controls.html b/docs/design/ui-demo/controls.html index 065eaf9..8439ceb 100644 --- a/docs/design/ui-demo/controls.html +++ b/docs/design/ui-demo/controls.html @@ -1,4 +1,10 @@ + @@ -9,15 +15,37 @@ +

① 控件总览

-

每个控件按「默认 → 悬停 → 按下 → 聚焦 → 禁用 → 加载中 / 错误」陈列。灰色小字是状态名;标「可以点」的是可交互的。示意数据取自原型的「某市政务服务中心智慧大厅建设项目」。

+

按状态陈列。灰色小字是状态名,标「可以点」的可以交互。

@@ -31,20 +59,19 @@ 主按钮 次按钮 - 文字按钮(不用) + 文字按钮不用 -
+

尺寸

-
-
  • 28:表格行内 · 32:默认 · 40:登录页、空状态
+
+
  • 32 表格行内 · 36 默认 · 40 登录页、空状态

带图标 · 只有图标

@@ -52,18 +79,28 @@ - +
-
  • 图标必须和文字一起出现;只有图标时要有 aria-label 和悬停提示。
+
  • 只有图标时要有 aria-label 和悬停提示。

会扣点的按钮 可以点

-
扣 1 次检查 · 剩 12 次
+
扣 1 次检查 · 剩 12 次
24 小时内复查,不扣额度
-
剩 0 次 · 按钮仍可点
+
剩 0 次 · 仍可点
+
+

一屏一个主按钮,按上下文算

+
+
页面级:只有一个,放在页头右侧 +
智慧大厅建设项目扣 1 次检查 · 剩 12 次
+
对话框:独立上下文,可以有自己的主按钮 +
确认清单 v3?
+
+ +
@@ -71,17 +108,17 @@
02

输入框与文本域

规范 §7.13
-
默认(占位符用辅助灰)
+
默认
悬停
-
聚焦(2px 墨色焦点环 + 2px 间隙)
+
聚焦
已填写
错误(赭色,不用红)
!手机号应为 11 位数字
禁用
-
只读(系统识别结果,可复制)
-
带说明文字
天
从投标截止日起算,招标文件第 21 页
+
只读
+
带单位 · 说明
天
招标文件第 21 页
-
+

文本域 · 字数 可以输入

@@ -95,75 +132,91 @@
-
  • 搜索框的放大镜只做辅助,占位符写清楚能搜什么。
  • 输入框高 32,左右内边距 10;数值输入用 Barlow 等宽数字。
+
  • 高 36,圆角 8;聚焦时边框变墨并加 1px 墨色内圈。占位符写清楚能搜什么。
-
03

下拉选择

AR10 · Reka UI Select 打底
+
03

下拉选择

规范 §7.21
-
未选择
+
未选择 · 可以点
已选择 · 可以点
-
-
-
展开(浮层:4px 圆角 + 阴影)
+
+
+
展开
-
+
错误
- !请选择资质类型,查标时按类型核对
+ !请选择资质类型
禁用
由招标文件识别,在项目设置里修改
-
表格行内(28 高)
+
表格行内(高 32)
-
  • 键盘:↑ ↓ 移动、Enter 选中、Esc 收起。选中项加粗并在右侧打 ✓(形状 + 字重,不只靠颜色)。
  • 不可选的项用禁用灰,并在右侧写原因。
+
  • 选中项加粗,右侧墨色 ✓;不用蓝色,也不用底色。悬停和键盘移动出浅灰底。
  • 不可选的项变禁用灰,右侧写原因。键盘:↑ ↓ 移动、Enter 选中、Esc 收起。
-
04

日期选择

规范 §9.3 · AR10
+
04

日期选择

规范 §7.22 · §9.3
-

收起 · 可以点

-
-
- 以补遗 1 的更正为准 · 剩 17 天
-
-
-
-
- !发证日期晚于有效期,请核对证书
+

输入框 可以输入、可以点

+
+ +
+ + + +
+ 24 小时制 +
+
+ +
+ + + +
+ 可以键入「2027/5/14」 +
+
+ +
+ !发证日期晚于有效期,请核对证书 +
-

展开

+

展开 · 带时间

-
  • 一周从周一开始;数字用 Barlow 等宽。
  • 今天:控件灰描边;选中:墨底白字;关键日期:下方小圆点。
+
  • 周一开始,非本月的格子留空。今天:控件灰描边;选中:墨底白字;关键日期:下方小圆点。
  • 时间 24 小时制。

有不可选的日期

-
  • 不可选的日期加删除线(不只靠变灰)。
  • 界面显示「11月20日 09:30」,输入框里用「2026-11-20」便于核对和粘贴。
+
  • 不可选的日期只变禁用灰,没有悬停,不加删除线。
+
  • 输入框写「2026-11-20 09:30」,可以直接键入「2026/11/20 9:30」等写法,离开时整理;下方显示「11月20日 09:30 · 剩 17 天」,跨年时带年份。
-
05

复选 · 单选 · 开关

规范 §8.2 批量勾选
+
05

复选 · 单选 · 开关

规范 §7.19 · §8.2

复选框

未选
已选
-
半选(表头全选)
+
半选
聚焦
禁用
禁用 · 已选
@@ -190,46 +243,52 @@

开关 可以点

-
关
-
开
-
禁用 · 关
-
禁用 · 开
+
关
+
开
+
禁用 · 关
+
禁用 · 开
- - +
额度用到 80% 时站内提醒每期提醒一次
+
资质到期前 30 天提醒
-
  • 规范没有定义开关。这里沿用 2px 圆角做成方形滑块(不用胶囊形),旁边写「开 / 关」,不只靠颜色。
+
  • 矩形轨道 + 方块滑钮,不用胶囊形;开为墨色,旁边写「开 / 关」。
  • 只用于立即生效的设置;要提交的表单项用复选框。
-
06

级别标签 · 状态标签 · AI 标识 · 署名

规范 §3.3 · §7.2 · §7.5 · §7.16
+
06

级别标签 · 中性标签 · AI 标识 · 署名

规范 §3.3 · §7.2 · §7.16

问题级别(实心 / 描边 / 无框)

致命重要提示计数致命3重要3提示3
参数判断疑似不满足疑似不满足满足需人工判断未找到响应
-

项目状态 · 就绪 · 复查标记

-
跟进中编制中已检查已递交已归档
-
未就绪就绪新增沿用本次已解决
-
清单状态草稿 v3已确认 v2✓王敏11月2日 15:12确认
-
分类资格条件无效条款技术参数采购包 1通用
+

中性标签(浅灰实底,无描边)

+
状态跟进中编制中已检查已递交已归档
+
就绪已检查就绪已检查未就绪
+
分类资格条件无效条款技术参数采购包 1
+
复查新增沿用本次已解决清单草稿 v3已确认 v2
-

AI 产出 → 人工确认(颜色说明谁说的)

-
AI 摘要AI 初稿AI 提取人工添加补遗 1已修改 · 查看记录
+

AI 产出 → 人工确认

+
AI 摘要AI 初稿AI 提取来源AI人工添加补遗 1
-
AI 产出:蓝铅笔标签 + 左侧虚线 -
AI 摘要未确认预算 418.00 万元,分 2 个采购包;采购包 1 最高限价 286.00 万元。招标文件 · 第 2 页
-
人工确认后:墨色正文 + 实线 + 绿色署名 +
确认前:「AI」标签 + 左侧 2px 蓝铅笔虚线,正文墨色 · 可以点 +
+ AI 摘要 + 预算 418.00 万元,分 2 个采购包;采购包 1 最高限价 286.00 万元。招标文件 · 第 2 页 +
+
王敏11月3日 10:05确认无误
+
+
确认后:去掉虚线,换成署名行(绿 ✓ + 姓名 + 时间)
预算 418.00 万元,分 2 个采购包;采购包 1 最高限价 286.00 万元。招标文件 · 第 2 页 -
✓王敏11月2日 14:20确认无误
+
王敏11月2日 14:20确认无误
问题处理后的署名行 -
✓王敏11月2日 14:20已修改撤销
-
–王敏11月2日 14:26已忽略:招标文件允许撤销
+
王敏11月2日 14:20已修改
+
王敏11月2日 14:26已忽略
+

原因:招标文件允许

@@ -237,51 +296,53 @@
-
07

出处

规范 §7.3 · 最重要的组件
+
07

出处

规范 §7.3

招标原文 → 投标原文

招标文件 · 第 41 页★ 2U机架式服务器,CPU:配置≥2颗海光架构32C 2.7GHz,内存: ≥512G DDR4,系统盘: 2块≥480G SSD……
技术册 · 第 12 页第 12 行 内存:256G DDR4 无偏离
-
  • 页码用 Barlow;原句照录,不加空格、不改标点。
+ 列表行里的出处链接 + 技术册 · 第 12 页 +
  • 原句照录,不加空格、不改标点;页码用 Barlow。

「未找到」也是出处

招标文件 · 第 44 页★ 支持硬件BFD,最小报文发送间隔≤50ms,提供相关证明材料(包含但不限于产品官网公开参数截图证明等)。
-
投标文件 · 未找到

证明材料未找到 · 已查找:技术册(全部章节)、商务册(附件)

-
  • 竖线改虚线,写明找过哪些分册、哪些章节。
+
投标文件 · 未找到

已查找:技术册(全部章节)、商务册(附件)

+
  • 竖线改虚线,写明找过哪些分册。
-

OCR · 截断 · 当前选中

-
商务册 · 第 3 页OCR投标人(电子签章):南京某信息技术有限公司  日期:2026年11月25日
+

OCR · 截断 · 当前

+
商务册 · 第 3 页OCR投标人(电子签章):南京某信息技术有限公司 日期:2026年11月25日
招标文件 · 第 23 页2.财务状况报告,至少提供:(1)本项目开标时间前6个月内任一时间截点的资产负债表的扫描件1份;(2)本项目开标时间前6个月内任何1月(不含开标当月)利润表月报表的扫描件1份。3.本项目开标时间前6个月内任何1月的依法缴纳税收和社会保障资金的相关材料的扫描件。
-
  • 选中的出处竖线加粗到 4px。超过 3 行截断,「展开原文」看全句。
+
  • 当前打开的出处竖线加粗到 4px;超过 3 行截断。
-
08

提醒(页内 Alert)

规范 §7.12 · §7.15
+
08

提醒

规范 §7.12 · §7.15
-
i

清单未确认,检查结果以当前清单为准。

-
!

本期检查额度已用 80%。剩 4 次,额度用完后续费或下期发放后可继续。

查看额度
-
!

投标截止还剩 2 天 5 小时。还有 3 项致命问题待处理。

截止临近用赭色,不用红色;只提醒,不拦截。
+

清单未确认,检查结果以当前清单为准。

+

某县数据中心机房改造项目 今天 17:00 截止,还有 3 项致命问题待处理。

+

本期检查额度 已用 80%,剩 4 次。

查看额度
-
!

解析失败:文件已加密。已退回 1 次解析额度。请上传未加密的版本。

关于 ZC-2026-0418 的更正公告.pdf
-
i

出了点问题,请稍后重试。本次操作未扣额度。

错误编号 E-20261103-7Q4K · 联系客服时请提供
-
✓

报告已导出:智慧大厅建设项目-辅助检查报告-第 2 次.pdf

+

解析失败:文件已加密。已退回 1 次解析额度。

关于 ZC-2026-0418 的更正公告.pdf
+

出了点问题,请稍后重试。本次操作未扣额度。

错误编号 E-20261103-7Q4K
+

报告已导出:智慧大厅建设项目-辅助检查报告-第 2 次.pdf

- +
-
09

轻提示(Toast)

规范 §7.12
+
09

轻提示

规范 §7.12

样式

@@ -294,38 +355,38 @@

触发 可以点

-
  • 只用于操作结果;4 秒后消失,鼠标悬停时暂停计时。
  • 可撤销的操作在轻提示里给「撤销」;失败不用轻提示,用页内提醒说清原因。
  • 多条从下往上堆叠,屏幕阅读器会播报。
+
  • 只用于操作结果,4 秒消失,悬停暂停。可撤销的操作给「撤销」。
  • 失败不用轻提示,用页内提醒说清原因。
-
10

对话框

规范 §7.14 · 只用于不可撤销的操作
+
10

对话框

规范 §7.14 · 不可撤销的操作和表单
-

打开真实对话框 可以点

+

打开对话框 可以点

- +
-
  • 宽 480(确认)/ 640(表单),圆角 4。
  • 标题写要做的事,正文写后果;主操作在右,按钮写动作「删除项目」,不写「确定」。
  • 删除没有红色:靠输入项目名确认。Esc 关闭,焦点回到触发按钮。
+
  • 宽 480(确认)/ 640(表单),圆角 12,遮罩墨色 32%。对话框不裁剪下拉和日期浮层。
  • 标题写要做的事,正文写后果;主按钮在右,写动作,不写「确定」。对话框可以有自己的主按钮。
  • 删除不用红色,靠输入项目名确认。Esc 关闭,焦点回到触发按钮。
- +
-
11

标签页 · 分段控件 · 分页

规范 §5.3
+
11

标签页 · 筛选 · 分页 · 选中

规范 §5.3 · §7.18 · §7.20

标签页 可以点,← → 切换

@@ -336,20 +397,42 @@

项目要点、关键日期、文件和检查记录。

- +
-

分段控件(筛选) 可以点

+

筛选 可以点

-
+

分页 可以点

-
  • 当前页用墨色描边 + 加粗(不填色,避免和主按钮抢)。每页条数固定 20 / 50,不做无限滚动,方便「第几页第几条」沟通。
+
  • 当前页浅灰实底 + 加粗,和筛选的按下态一样;只在超过一页时显示。
+
+
+

当前条 · 清单项

可以点,↑ ↓ 移动
+
+
★
投标保证金人民币 5 万元
资格条件通用
+
投标有效期 90 天
商务条款通用
+
★
应用服务器内存 ≥ 512G DDR4
技术参数采购包 1
+
+
+
+

当前条 · 勾选 · 表格行

点行定位,点复选框勾选
+ + + + + + + +
资质类型有效期至
ISO/IEC 27001 信息安全管理体系体系认证2027年5月14日
信息系统建设和服务能力等级 CS3资质证书2028年1月9日
智慧大厅综合管理平台 V2.0软件著作权长期
+
+
+
  • 当前条(清单项、表格行、问题):最浅的灰底 + 左侧 3px 墨色竖条;键盘焦点另加 2px 墨色内框。竖条只表示「当前」,和问题级别无关,致命行不加红竖线。
  • 勾选(批量处理)只有复选框是选中状态,不加底色和竖条,和当前条可以同时出现。
  • 筛选、分页、分类导航的「当前项」用浅灰实底 + 加粗。蓝色只留给原文高亮和出处。
@@ -361,14 +444,14 @@
还没有招标文件

上传后,标博士会逐条提取要求,每条都附原文出处。

扣 1 次解析 · 剩 37 次
-

骨架屏(加载清单)

+

骨架屏

- +

分阶段进度

@@ -383,43 +466,33 @@

可以离开本页,完成后会发站内通知。

-
-
检查剩余 · 正常12/ 20 次

已用 8 次

-
检查剩余 · 已用 80%4/ 20 次

已用 80%,剩 4 次

-
检查剩余 · 已用完0/ 20 次

本期检查额度已用完,续费或下期发放后可继续

+
+
检查剩余12/ 20 次

已用 8 次

+
检查剩余 · 已用 80%4/ 20 次

已用 80%,剩 4 次

+
检查剩余 · 已用完0/ 20 次

本期检查额度已用完,可在额度页提交续费申请

-
13

提示气泡 · 下拉菜单 · 快捷键

规范 §6 · §8.2
+
13

提示气泡 · 操作菜单 · 快捷键

规范 §6 · §7.21 · §8.2

提示气泡

-
就绪
+
就绪
-
  • 处理三按钮的悬停提示写清含义(§7.4)。墨底白字,4px 圆角。
+
  • 墨底白字,圆角 8。解释性的话放在这里,不写在界面上。
-

下拉菜单(行内更多操作) 可以点

-
-
-
-
-
+

操作菜单 可以点

+
列表行的「更多」
+
+
  • 会扣点的项第二行写扣多少、还剩多少;不可用的项变禁用灰,右侧写原因。

快捷键

- - - - - - - - -
J / K下一条 / 上一条
O · Enter打开当前出处
1 2 3确认无误 / 已修改 / 忽略
U撤销当前条
?快捷键说明
-

在「④ 列表与表格」和「⑥ 读标」页可以实际用 J / K。

+
+

按页面只列能用的键:这是查标(问题列表)的一组;读标只有 J / K、Enter、?。「④ 列表与表格」和「⑥ 读标」页可以实际用。

@@ -440,8 +513,50 @@ $$("[data-count-field]").forEach((f) => { $$("[data-expand]").forEach((b) => b.onclick = () => { const q = b.previousElementSibling; const on = q.classList.toggle("clamp"); q.style.webkitLineClamp = on ? "" : "unset"; b.textContent = on ? "展开原文" : "收起"; }); /* 额度不足:按钮仍可点,点了说明原因 */ -$("[data-nomore]").onclick = () => dialog({ title: "本期检查额度已用完", ok: "联系客服加额度", cancel: "关闭", - body: "

这次检查需要 1 次检查额度,本期已用完 20 次。续费或下期(2027-11-01)发放后可以继续。

24 小时内对同一项目的复查不扣额度,不受影响。

" }); +$("[data-nomore]").onclick = () => dialog({ title: "本期检查额度已用完", ok: "提交续费申请", cancel: "关闭", + body: "

这次检查需要 1 次检查额度,本期 20 次已用完。可以在额度页提交续费申请(对公转账后由运营开通),或等下期(2027-11-01)发放。

24 小时内对同一项目的复查不扣额度。

", + onOk: () => toast("(演示)打开额度页的续费申请", { ok: false }) }); + +/* 开关:立即生效,给轻提示 */ +$$("[data-sw-toast]").forEach((s) => s.addEventListener("click", () => toast(`${s.dataset.swToast}已${s.getAttribute("aria-checked") === "true" ? "打开" : "关闭"}`))); + +/* AI 产出 → 确认(Q2):去掉虚线,换成署名行 */ +(function () { + const b = $("[data-ai]"); + const set = (done, act) => { b.classList.toggle("done", done); if (act) $("[data-ai-act]", b).textContent = act; }; + $("[data-ai-ok]", b).onclick = () => { set(true, "确认无误"); toast("已确认", { undo: () => set(false) }); }; + $("[data-ai-edit]", b).onclick = () => toast("(演示)修改后同样换成署名行,动作写「已修改」", { ok: false }); + $("[data-ai-undo]", b).onclick = (e) => { e.preventDefault(); set(false); $("[data-ai-ok]", b).focus(); }; +})(); + +/* 日期输入(Q10):共用组件 Demo.dateInput(),.dt[data-dt] 自动初始化,这里不用写脚本 */ + +/* 当前条(§7.18):清单项 */ +(function () { + const box = $("[data-sel-list]"), its = $$(".item", box); + const sel = (it, focus) => { its.forEach((x) => { const on = x === it; x.classList.toggle("sel", on); x.setAttribute("aria-selected", on); x.tabIndex = on ? 0 : -1; }); if (focus) it.focus(); }; + its.forEach((it) => { + it.addEventListener("click", (e) => { if (!e.target.closest("a")) sel(it); }); + it.addEventListener("focus", () => it.classList.toggle("focus", it.matches(":focus-visible"))); + it.addEventListener("blur", () => it.classList.remove("focus")); + }); + box.addEventListener("keydown", (e) => { + const k = its.indexOf(document.activeElement); if (k < 0) return; + const d = e.key === "ArrowDown" ? 1 : e.key === "ArrowUp" ? -1 : 0; if (!d) return; + e.preventDefault(); sel(its[(k + d + its.length) % its.length], true); + }); + $$(".cite-src", box).forEach((a) => a.addEventListener("click", (e) => { e.preventDefault(); toast("(演示)在原文查看器里打开,原句用蓝铅笔浅底高亮", { ok: false }); })); +})(); +/* 表格行:点行 = 当前条(tr.sel);复选框 = 勾选,只改复选框本身(§7.18) */ +(function () { + const t = $("[data-sel-table]"), all = $("[data-all]", t), cks = $$("tbody .ck", t); + const sync = () => { const n = cks.filter((c) => c.checked).length; all.checked = n === cks.length; all.indeterminate = n > 0 && n < cks.length; }; + cks.forEach((c) => c.addEventListener("change", sync)); + all.addEventListener("change", () => { cks.forEach((c) => { c.checked = all.checked; }); sync(); }); + $$("tbody tr", t).forEach((tr) => tr.addEventListener("click", (e) => { if (e.target.closest("input")) return; $$("tbody tr", t).forEach((x) => x.classList.toggle("sel", x === tr)); })); + sync(); +})(); +$$("[data-undo-demo]").forEach((b) => b.onclick = () => toast("(演示)撤销后回到处理按钮", { ok: false })); /* 轻提示 */ $("[data-t1]").onclick = () => toast("已保存「ISO/IEC 27001 信息安全管理体系」"); @@ -449,27 +564,46 @@ $("[data-t2]").onclick = () => toast("已将 3 项「提示」标记为确认无 $("[data-t3]").onclick = () => { toast("清单 v3 已确认"); setTimeout(() => toast("已核对补遗更新"), 300); setTimeout(() => toast("报告已导出"), 600); }; /* 对话框 */ -$("[data-d1]").onclick = () => dialog({ +const openD1 = () => dialog({ title: "删除项目「某区智慧社区平台运维服务」?", ok: "删除项目", - body: `

项目下的招标文件、清单、检查结果和报告会一起删除。删除后 30 天内可以在回收站恢复,之后彻底删除。

+ body: `

项目下的招标文件、清单、检查结果和报告会一起删除。30 天内可以在回收站恢复。

`, onOpen: (s) => { const ok = $("[data-ok]", s), i = $("#dn", s); ok.disabled = true; i.addEventListener("input", () => { ok.disabled = i.value.trim() !== "某区智慧社区平台运维服务"; }); }, - onOk: () => toast("已删除,可在回收站恢复(30 天内)") + onOk: () => toast("已删除,30 天内可在回收站恢复") }); -$("[data-d2]").onclick = () => dialog({ +const openD2 = () => dialog({ title: "标记「某市政务服务中心智慧大厅建设项目」为已递交?", ok: "标记已递交", - body: `
!

还有 3 项致命问题待处理。标记已递交不受影响,处理记录会如实保留。

-

标记后项目进入「已递交」,不能再发起检查;清单和检查结果保留,可以导出报告。

`, + body: `

还有 3 项致命问题未处理。标记不受影响,处理记录会如实保留。

+

标记后不能再发起检查;清单和检查结果保留,可以导出报告。

`, onOk: () => toast("已标记为已递交") }); -$("[data-d3]").onclick = () => dialog({ - title: "忽略并注明原因", wide: true, ok: "忽略这一项", +const openD3 = () => dialog({ // 表单对话框(640):反馈判断有误。忽略原因不用对话框,在问题行内展开(§7.4) + title: "反馈:判断有误", wide: true, ok: "提交反馈", body: `

重要 开标一览表与分项价格表合计不一致

-
-
- 「已忽略」不等于确认无误,报告里单独列出。
`, - onOpen: (s) => { $$("[data-chip]", s).forEach((c) => c.onclick = () => { $("#ir", s).value = c.textContent; $("#ir", s).focus(); }); }, - onOk: (s) => { const v = $("#ir", s).value.trim(); const f = $("#ir", s).closest(".field"); if (v.length < 5) { f.classList.add("err"); $(".msg", f).innerHTML = '!至少写 5 个字,说明为什么不处理'; return false; } toast("已忽略 · 原因已记录", { undo: () => toast("已撤销", { ok: false }) }); } +
+
+
+
+
/ 120 页
+
反馈只用于改进检查,不改变这一条的处理状态。
+
`, + onOk: (s) => { const t = $("#fb-t", s); if (!t.value.trim()) { Demo.fieldError(t.closest(".field"), "请写一句哪里不对"); t.focus(); return false; } toast("已收到反馈"); } +}); +$("[data-d1]").onclick = openD1; +$("[data-d2]").onclick = openD2; +$("[data-d3]").onclick = openD3; + +/* 操作菜单:列表行「更多」,和工作台同一个浮动菜单(Demo.floatMenu);静态展开的那份用 Demo.menuHTML 生成同样的项 */ +const MORE = [ + { label: "处理记录" }, { label: "复制为新项目" }, { label: "追加补遗 / 澄清", sub: '扣 1 次解析 · 剩 37 次' }, "-", + { label: "标记已递交" }, { label: "归档", disabled: "递交后可用" }, { label: "删除项目" } +]; +$("[data-menu-static]").innerHTML = Demo.menuHTML(MORE.map((it, i) => i === 1 ? { ...it, active: true } : it)); +$("[data-menu-static]").addEventListener("click", (e) => { const b = e.target.closest("[data-i]"); if (b && !b.hasAttribute("aria-disabled")) toast(`(演示)${b.firstChild.textContent}`, { ok: false }); }); +$("[data-keys]").innerHTML = Demo.keysTable(Demo.KEYS.check); +$("[data-more]").addEventListener("click", (e) => { + e.stopPropagation(); + Demo.floatMenu(e.currentTarget, MORE, (act) => { if (act === "删除项目") openD1(); else if (act === "标记已递交") openD2(); else toast(`(演示)${act}`, { ok: false }); }); }); /* 分页 */ @@ -477,7 +611,7 @@ $("[data-d3]").onclick = () => dialog({ const el = $("[data-pager]"); let cur = 1; const total = 86, per = 20, pages = Math.ceil(total / per); const render = () => { const nums = []; for (let i = 1; i <= pages; i++) nums.push(i); - el.innerHTML = `共 ${total} 条 · 每页 ${per} 条 + el.innerHTML = `共 ${total} 条 ${nums.map((n) => ``).join("")} @@ -487,6 +621,16 @@ $("[data-d3]").onclick = () => dialog({ el.addEventListener("change", (e) => { const v = +e.target.value; if (v >= 1 && v <= pages) cur = v; render(); }); render(); })(); + +/* ?open=select | date | menu | d1 | d2 | d3:直接打开浮层(截图用) */ +addEventListener("load", () => { + const o = new URLSearchParams(location.search).get("open"); + const at = (sel, fn) => { const el = document.querySelector(sel); if (el.getBoundingClientRect().bottom > innerHeight) el.scrollIntoView({ block: "start" }); setTimeout(fn, 50); }; + if (o === "select") at("#s-select", () => $("#sel-demo .select").click()); + if (o === "date") at("#s-date", () => $("#dt-demo .icon-btn").click()); + if (o === "menu") at("#s-tip", () => $("[data-more]").click()); + if ({ d1: 1, d2: 1, d3: 1 }[o]) ({ d1: openD1, d2: openD2, d3: openD3 })[o](); +}); diff --git a/docs/design/ui-demo/demo.css b/docs/design/ui-demo/demo.css index 00a78bd..ee6ae84 100644 --- a/docs/design/ui-demo/demo.css +++ b/docs/design/ui-demo/demo.css @@ -1,47 +1,50 @@ -/* 标博士 · UI demo 样式(2026-11-03) - * 依赖 ../system/tokens.css 和 ../system/components.css,只用 tokens.css 的变量,不写裸色值。 +/* 标博士 · UI demo 样式 v0.2(2026-11-03) + * 依赖 ../system/tokens.css 和 ../system/components.css,只用 tokens.css 的变量,不写裸色值(Q6)。 + * 视觉以定稿样张 explore/workbench-d5.html 为准(决策 U5),Q1–Q12 已于 2026-11-03 确认。 * 分两部分: - * A. 演示页排版:只属于 demo 页面(章节标题、状态陈列格),不进组件库; + * A. 演示页排版:只属于 demo 页面(章节标题、状态陈列格、目录卡),不进组件库; * B. 新增组件:design-system.md 有描述、但 components.css 里还没有的组件。UI 精修定稿后再并入 components.css。 - * 新增组件清单见 index.html。 */ + * 新增组件清单见 index.html。类名和 demo.js 的接口沿用 v0.1,页面不用改调用方式。 + * 收尾(2026-11-03):各页重刷时的页内绕过已收回这里——项目行(B20)、署名行(B16)、日期输入(B6)、 + * 对照布局(B19)、当前条 / 勾选条(B18)、行内说明(B18 .u4);对话框不再裁剪浮层(B11)。 */ /* ===================================================================== * A. 演示页排版 * ===================================================================== */ body.demo{min-width:1280px} -.d-top{position:sticky;top:0;z-index:30;display:flex;align-items:center;gap:var(--sp-8);height:52px;padding:0 var(--sp-8);background:var(--bg-page);border-bottom:1px solid var(--border-subtle)} +.d-top{position:sticky;top:0;z-index:30;display:flex;align-items:center;gap:var(--sp-6);height:var(--topbar-h);padding:0 var(--pad-page);background:var(--topbar-glass);-webkit-backdrop-filter:blur(14px) saturate(1.2);backdrop-filter:blur(14px) saturate(1.2);border-bottom:var(--line) solid var(--border-subtle)} .d-top .brand img{height:26px;display:block} -.d-top .tag{margin-left:calc(-1 * var(--sp-5))} -.d-nav{display:flex;gap:var(--sp-1);margin-left:auto} -.d-nav a{display:flex;align-items:center;height:52px;padding:0 var(--sp-3);color:var(--text-secondary);text-decoration:none;white-space:nowrap} -.d-nav a:hover{color:var(--text-primary)} -.d-nav a.on{color:var(--text-primary);font-weight:var(--fw-bold);box-shadow:inset 0 -2px 0 var(--c-ink)} -.d-page{max-width:calc(var(--content-max) + 2 * var(--sp-8));margin:0 auto;padding:var(--sp-10) var(--sp-8) var(--sp-16)} +.d-top .tag{margin-left:calc(-1 * var(--sp-3))} +.d-nav{display:flex;gap:2px;margin-left:auto} +.d-nav a{display:flex;align-items:center;height:var(--control-h-sm);padding:0 10px;border-radius:var(--radius-ctl);color:var(--text-secondary);text-decoration:none;white-space:nowrap;transition:background-color var(--dur-fast) var(--ease)} +.d-nav a:hover{background:var(--c-n100);color:var(--text-primary)} +.d-nav a.on{background:var(--c-n100);color:var(--text-primary);font-weight:var(--fw-bold)} +.d-page{max-width:calc(var(--content-max) + 2 * var(--pad-page));margin:0 auto;padding:var(--sp-10) var(--pad-page) var(--sp-16)} .d-head h1{font:var(--fw-bold) var(--fs-h1)/var(--lh-h1) var(--font-serif);margin:0} .d-head p{color:var(--text-secondary);margin:var(--sp-2) 0 0;max-width:60em} .d-toc{display:flex;flex-wrap:wrap;gap:var(--sp-2) var(--sp-4);margin-top:var(--sp-4);font-size:var(--fs-caption)} -.d-toc a{color:var(--text-secondary);text-underline-offset:3px} +.d-toc a{color:var(--text-secondary);text-decoration-color:var(--link-underline);text-underline-offset:3px} .d-toc a:hover{color:var(--text-primary)} .d-sec{margin-top:var(--sp-12);scroll-margin-top:72px} -.d-sec-h{display:flex;align-items:baseline;gap:var(--sp-3);border-bottom:2px solid var(--border-strong);padding-bottom:var(--sp-2);margin-bottom:var(--sp-5)} +.d-sec-h{display:flex;align-items:baseline;gap:var(--sp-3);border-bottom:var(--line) solid var(--border-subtle);padding-bottom:var(--sp-3);margin-bottom:var(--sp-5)} .d-sec-h h2{font:var(--fw-bold) var(--fs-h2)/var(--lh-h2) var(--font-serif);margin:0} .d-sec-h .no{font:var(--fw-bold) var(--fs-title)/1 var(--font-figures);color:var(--text-secondary)} .d-sec-h .ref{margin-left:auto;font-size:var(--fs-caption);color:var(--text-secondary)} -.d-grid{display:grid;gap:var(--sp-6);align-items:start} +.d-grid{display:grid;gap:var(--gap-card);align-items:start} .d-grid.c2{grid-template-columns:repeat(2,minmax(0,1fr))} .d-grid.c3{grid-template-columns:repeat(3,minmax(0,1fr))} .d-grid.eq{align-items:stretch} .d-grid.c-side{grid-template-columns:minmax(0,1fr) 360px} -.d-box{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);padding:var(--sp-5) var(--sp-6)} +.d-box{background:var(--bg-surface);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-card);padding:var(--sp-5) var(--card-x)} .d-box > h3{font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans);margin:0 0 var(--sp-4);display:flex;gap:var(--sp-2);align-items:baseline} .d-box > h3 small{font-weight:var(--fw-regular);font-size:var(--fs-caption);color:var(--text-secondary)} .d-box.flat{background:none;border-style:dashed;border-color:var(--border-control)} -.d-states{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:var(--sp-5) var(--sp-5)} +.d-states{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:var(--sp-5)} .d-states.wide{grid-template-columns:repeat(auto-fill,minmax(220px,1fr))} .d-state{display:flex;flex-direction:column;align-items:flex-start;gap:var(--sp-2);min-width:0} .d-state > small,.d-cap{font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} .d-state .field{width:100%} -.d-rules{margin:var(--sp-4) 0 0;padding:var(--sp-3) 0 0 1.2em;border-top:1px solid var(--border-subtle);font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} +.d-rules{margin:var(--sp-4) 0 0;padding:var(--sp-3) 0 0 1.2em;border-top:var(--line) solid var(--border-subtle);font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} .d-rules li + li{margin-top:2px} .d-label{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary);margin-bottom:var(--sp-2)} .d-stack > * + *{margin-top:var(--sp-4)} @@ -52,12 +55,18 @@ body.demo{min-width:1280px} .d-matrix td{padding:var(--sp-2) var(--sp-3) var(--sp-2) 0;vertical-align:middle} .d-matrix td:first-child{font-size:var(--fs-caption);color:var(--text-secondary);white-space:nowrap;width:72px} .d-open{position:relative;padding-bottom:var(--sp-2)} /* 静态展开的浮层,占位用 */ +/* 陈列盒、对话框里的表格:不再带自己的外框(卡片里不套面板),首尾列贴齐容器内边距;没有表头时第一行不画上边线 */ +:is(.d-box,.dialog) table.data{border:0;border-radius:0;background:none} +:is(.d-box,.dialog) table.data :is(th,td):first-child{padding-left:0} +:is(.d-box,.dialog) table.data :is(th,td):last-child{padding-right:0} +:is(.d-box,.dialog) table.data:not(:has(thead)) tbody tr:first-child td{border-top:0} +table.data.keys td:first-child{width:1%;padding-right:var(--sp-6)} /* 快捷键表(Demo.keysTable) */ .d-open .menu,.d-open .cal{position:static;margin-top:var(--sp-1)} /* 目录页 */ -.d-cards{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--sp-4)} -.d-link{display:block;background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);padding:var(--sp-4) var(--sp-5);color:var(--text-primary);text-decoration:none} -.d-link:hover{border-color:var(--border-control)} +.d-cards{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--gap-card)} +.d-link{display:block;background:var(--bg-surface);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-card);padding:var(--sp-4) var(--card-x);color:var(--text-primary);text-decoration:none;transition:background-color var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)} +.d-link:hover{background:var(--c-n50);border-color:var(--border-control)} .d-link b{display:flex;gap:var(--sp-2);align-items:baseline;font-size:var(--fs-title);line-height:var(--lh-title)} .d-link b .fig{color:var(--text-secondary);font-size:var(--fs-title)} .d-link p{margin:var(--sp-2) 0 0;color:var(--text-secondary);font-size:var(--fs-caption);line-height:var(--lh-caption)} @@ -66,45 +75,78 @@ body.demo{min-width:1280px} * B. 新增组件 * ===================================================================== */ -/* ---- B1 图标(Lucide 线性,1.5px 描边,16 / 20px,§6) ---- */ +/* ---- B1 图标(Lucide 线性,1.5px 描边,16 / 20px,§6;侧栏菜单 22px) ---- */ .ic{width:16px;height:16px;flex:none;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;vertical-align:-3px} .ic.lg{width:20px;height:20px} .ic.xl{width:32px;height:32px;stroke-width:1.25} +.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap} -/* ---- B2 页面框架:侧边栏 + 顶栏(§5.3) ---- */ -body.app{display:grid;grid-template-columns:var(--sidebar-w) minmax(0,1fr);min-height:100vh;min-width:1280px} -.sidebar{position:sticky;top:0;height:100vh;display:flex;flex-direction:column;border-right:1px solid var(--border-subtle);background:var(--bg-page);padding:var(--sp-5) var(--sp-3)} -.sidebar .brand{display:block;padding:0 var(--sp-2) var(--sp-6)} -.sidebar .brand img{height:30px;display:block} -.nav{display:flex;flex-direction:column;gap:2px} -.nav a{display:flex;align-items:center;gap:var(--sp-3);height:36px;padding:0 var(--sp-3);border-radius:var(--radius-sm);color:var(--text-primary);text-decoration:none} -.nav a:hover{background:var(--bg-hover)} -.nav a.on{background:var(--bg-surface);box-shadow:inset 2px 0 0 var(--c-ink);font-weight:var(--fw-bold)} -.nav a .fig{margin-left:auto;font-weight:var(--fw-medium);color:var(--text-secondary)} -.nav .sub{margin:var(--sp-5) var(--sp-3) var(--sp-2);font-size:var(--fs-caption);color:var(--text-secondary)} -.sidebar-foot{margin-top:auto;padding:var(--sp-3);border-top:1px solid var(--border-subtle);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)} -.main{min-width:0;display:flex;flex-direction:column} -.top{position:sticky;top:0;z-index:20;display:flex;align-items:center;gap:var(--sp-4);height:52px;padding:0 var(--sp-8);background:var(--bg-page);border-bottom:1px solid var(--border-subtle)} -.crumbs{display:flex;gap:var(--sp-2);color:var(--text-secondary);min-width:0;white-space:nowrap;overflow:hidden} +/* ---- B2 页面框架 v0.2(§5.3):96px 墨色渐变侧栏 + 透明顶栏 + 内容区左上角圆角 ---- + * 结构由 demo.js 的 Demo.shell() 生成:body.app > aside.sidebar + main.main > header.top + 页面内容 */ +body.app{display:grid;grid-template-columns:var(--rail-w) minmax(0,1fr);min-height:100vh;min-width:1280px;background:linear-gradient(180deg,var(--side-top) 0%,var(--side-bottom) 100%) fixed} +.sidebar{position:sticky;top:0;height:100vh;display:flex;flex-direction:column;padding:0 0 var(--sp-4) 10px;color:var(--side-fg);min-width:0} +.sidebar::after{content:"";position:absolute;left:0;right:0;bottom:0;height:260px;pointer-events:none;background-image:radial-gradient(var(--side-dots) 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)} +.sidebar > *{position:relative;z-index:1} +.sidebar .brand{display:flex;justify-content:center;padding:var(--sp-5) 10px var(--sp-5) 0;border-radius:var(--radius-ctl)} +.sidebar .brand img{width:60px;height:auto;display:block} +.sidebar .nav{display:flex;flex-direction:column;gap:var(--sp-1)} +.sidebar .nav a{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-1);height:62px;margin-right:10px;border-radius:var(--radius-card);font-size:var(--fs-caption);line-height:16px;color:var(--side-fg);text-align:center;text-decoration:none;white-space:nowrap;transition:background-color var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)} +.sidebar .nav a .ic{width:22px;height:22px} +.sidebar .nav a:hover{background:var(--side-hover);color:var(--side-fg-strong)} +.sidebar .nav a[aria-current]{background:var(--side-active);color:var(--side-fg-strong);font-weight:var(--fw-bold)} +.sidebar .nav .sep{display:block;height:1px;margin:10px 22px 10px 12px;background:var(--side-line)} +.sidebar a:focus-visible{outline:2px solid var(--side-fg-strong);outline-offset:-2px} +.sidebar .me{display:flex;flex-direction:column;align-items:center;margin-top:auto;padding:var(--sp-4) 10px var(--sp-1) 0;color:var(--side-muted);text-align:center} +.sidebar .avatar{display:inline-grid;place-items:center;width:44px;height:44px;margin-bottom:var(--sp-2);border-radius:50%;background:var(--side-avatar-bg);box-shadow:0 0 0 1px var(--side-avatar-ring);color:var(--side-fg-strong);font-size:var(--fs-title);font-weight:var(--fw-bold)} +.sidebar .uname{color:var(--side-fg-strong);font-size:var(--fs-body);line-height:var(--lh-body);font-weight:var(--fw-bold)} +/* 套餐标签(§3.1):只在账户区使用;免费版中性、专业版浅金、企业版金色实底 */ +.plan{display:inline-block;margin-top:var(--sp-1);padding:0 var(--sp-2);border-radius:var(--radius-xs);font-size:var(--fs-caption);line-height:20px;font-weight:var(--fw-bold);white-space:nowrap} +.plan[data-plan="free"]{background:var(--plan-free-bg);color:var(--side-fg)} +.plan[data-plan="pro"]{background:var(--plan-pro-bg);color:var(--plan-pro-fg);box-shadow:inset 0 0 0 1px var(--plan-pro-line)} +.plan[data-plan="ent"]{background:var(--plan-ent-bg);color:var(--c-ink)} + +.main{min-width:0;display:flex;flex-direction:column;min-height:100vh;background:var(--bg-page);border-top-left-radius:var(--radius-card)} +.top{position:sticky;top:0;z-index:20;display:flex;align-items:center;gap:var(--sp-3);height:var(--topbar-h);padding:0 var(--pad-page);background:transparent;border-bottom:var(--line) solid transparent;border-top-left-radius:var(--radius-card);transition:background-color var(--dur-base) var(--ease),border-color var(--dur-base) var(--ease)} +.top.scrolled{background:var(--topbar-glass);-webkit-backdrop-filter:blur(14px) saturate(1.2);backdrop-filter:blur(14px) saturate(1.2);border-bottom-color:var(--border-subtle)} +/* 左侧「公司 / 页面」 */ +.crumbs{display:flex;align-items:center;gap:6px;min-width:0;color:var(--text-secondary);white-space:nowrap;overflow:hidden} +.crumbs > *{min-width:0;overflow:hidden;text-overflow:ellipsis} +.crumbs > span{flex:none} +.crumbs .org{flex:none;color:var(--text-primary);font-weight:var(--fw-bold)} .crumbs a{color:var(--text-secondary);text-decoration:none} -.crumbs a:hover{color:var(--text-primary);text-decoration:underline;text-underline-offset:3px} -.crumbs b{color:var(--text-primary);font-weight:var(--fw-regular);overflow:hidden;text-overflow:ellipsis} -.top-right{margin-left:auto;display:flex;align-items:center;gap:var(--sp-3)} -.quota-chip{display:flex;gap:var(--sp-3);align-items:center;height:28px;padding:0 var(--sp-3);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);color:var(--text-secondary);text-decoration:none;font-size:var(--fs-caption);background:var(--bg-surface)} -.quota-chip.warn{border-color:var(--warn);color:var(--warn-text)} -.quota-chip .fig{color:var(--text-primary);font-size:var(--fs-body);margin-left:3px} -.content{padding:var(--sp-6) var(--sp-8) var(--sp-16);max-width:calc(var(--content-max) + 2 * var(--sp-8));width:100%} -.page-title{display:flex;align-items:flex-end;gap:var(--sp-4);margin-bottom:var(--sp-5)} +.crumbs a:hover{color:var(--text-primary);text-decoration:underline;text-decoration-color:var(--link-underline);text-underline-offset:3px} +.crumbs b{font-weight:var(--fw-regular)} +/* 右侧:搜索、额度、通知 */ +.top-search{position:relative;display:flex;align-items:center;flex:none;width:280px;height:var(--control-h);margin-left:auto;border:var(--line) solid var(--border-control);border-radius:var(--radius-ctl);background:var(--bg-surface);transition:width var(--dur-base) var(--ease),border-color var(--dur-fast) var(--ease)} +.top-search:hover{border-color:var(--c-ink)} +.top-search:focus-within{width:360px;border-color:var(--c-ink);box-shadow:0 0 0 1px var(--c-ink)} +.top-search > .ic{position:absolute;left:11px;color:var(--text-secondary);pointer-events:none} +.top-search input{flex:1;min-width:0;height:100%;padding:0 var(--sp-2) 0 34px;border:0;background:transparent;font:inherit;color:var(--text-primary);outline:0} +.top-search input::placeholder{color:var(--text-secondary)} +.top-search input::-webkit-search-cancel-button{display:none} +.top-search .kbd{margin-right:6px} +.top .vsep{flex:none;width:1px;height:20px;margin:0 var(--sp-1);background:var(--border-subtle)} +.top-right{display:flex;align-items:center;gap:var(--sp-1);flex:none} +/* 顶栏额度(§7.10、Q9):用到 ≥ 80% 时该项数字变赭色加粗(.warn);不用红 */ +.quota-chip{display:inline-flex;align-items:center;gap:6px;height:var(--control-h-sm);padding:0 10px;border-radius:var(--radius-ctl);color:var(--text-secondary);text-decoration:none;white-space:nowrap;transition:background-color var(--dur-fast) var(--ease)} +.quota-chip:hover{background:var(--c-n100)} +.quota-chip .q{display:inline-flex;align-items:baseline;gap:6px} +.quota-chip .fig{color:var(--text-primary);font-size:var(--fs-figure-sm)} +.quota-chip .q.warn .fig,.quota-chip.warn .fig{color:var(--warn-text);font-weight:var(--fw-bold)} +.quota-chip .sep{width:1px;height:12px;margin:0 var(--sp-1);background:var(--border-subtle)} +.content{width:100%;padding:20px var(--pad-page) var(--sp-12)} +/* 页头:标题 + 一句话;右侧放页面级主按钮(Q12:页面级一个) */ +.page-title{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--sp-6);margin-bottom:20px} .page-title h1{font:var(--fw-bold) var(--fs-h1)/var(--lh-h1) var(--font-serif);margin:0} -.page-title p{margin:var(--sp-1) 0 0;color:var(--text-secondary)} -.page-title .actions{margin-left:auto;display:flex;gap:var(--sp-2);align-items:center} +.page-title p{margin:2px 0 0;color:var(--text-secondary)} +.page-title .actions{margin-left:auto;display:flex;gap:var(--sp-3);align-items:center} .section-h{display:flex;align-items:center;gap:var(--sp-3);margin:var(--sp-8) 0 var(--sp-3)} .section-h h2{font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans);margin:0} .section-h .more{margin-left:auto;display:flex;gap:var(--sp-2);align-items:center} +.stack > * + *{margin-top:var(--gap-card)} /* 项目页头 */ -.proj-head{padding:var(--sp-5) var(--sp-8) 0;border-bottom:1px solid var(--border-subtle);background:var(--bg-page)} +.proj-head{padding:var(--sp-4) var(--pad-page) 0;border-bottom:var(--line) solid var(--border-subtle)} .proj-head .line1{display:flex;align-items:flex-start;gap:var(--sp-4)} .proj-head h1{font:var(--fw-bold) var(--fs-h1)/var(--lh-h1) var(--font-serif);margin:0} .proj-head .meta{margin-top:var(--sp-1);color:var(--text-secondary);display:flex;flex-wrap:wrap;gap:var(--sp-2) var(--sp-4)} @@ -112,11 +154,12 @@ body.app{display:grid;grid-template-columns:var(--sidebar-w) minmax(0,1fr);min-h .proj-head .facts small{display:block;font-size:var(--fs-caption);color:var(--text-secondary)} /* ---- B3 按钮补充状态(§7.1) ---- */ -.btn:active,.btn.is-active{background:var(--bg-sunken);border-color:var(--c-ink)} -.btn.is-hover{background:var(--bg-hover)} -.btn.primary.is-hover{background:color-mix(in srgb,var(--c-ink) 92%,var(--c-white))} -.btn.primary:active,.btn.primary.is-active{background:var(--c-ink);box-shadow:inset 0 2px 0 color-mix(in srgb,var(--c-white) 18%,transparent)} -.btn.text:active,.btn.text.is-active{background:var(--bg-sunken);border-color:transparent} +.btn.is-hover{background:var(--c-n50);border-color:var(--c-ink)} +.btn.is-active{background:var(--c-n100);border-color:var(--c-ink)} +.btn.primary.is-hover{background:var(--primary-hover);border-color:var(--primary-hover)} +.btn.primary.is-active{background:var(--primary-active);border-color:var(--primary-active)} +.btn.text.is-hover{background:var(--c-n100);border-color:transparent} +.btn.text:active,.btn.text.is-active{background:var(--c-n100);border-color:transparent} .btn.is-focus{outline:2px solid var(--focus-ring);outline-offset:2px} .btn .ic{margin:0 -2px} /* 加载中:两个文字叠在同一格,按宽的那个定宽,切换时宽度不变 */ @@ -125,114 +168,134 @@ body.app{display:grid;grid-template-columns:var(--sidebar-w) minmax(0,1fr);min-h .btn.swap > .busy,.btn.swap.is-loading > .idle{visibility:hidden} .btn.swap.is-loading > .busy{visibility:visible} .btn.is-loading{cursor:progress} -.btn.primary.is-loading{background:color-mix(in srgb,var(--c-ink) 80%,var(--c-white));border-color:transparent} +.btn.primary.is-loading{background:var(--primary-hover);border-color:var(--primary-hover)} .btn.is-loading:not(.primary){color:var(--text-secondary)} -.icon-btn{display:inline-grid;place-items:center;width:32px;height:32px;border-radius:var(--radius-sm);color:var(--text-primary);background:none;border:1px solid transparent;cursor:pointer;position:relative;flex:none} -.icon-btn:hover{background:var(--bg-hover)} +/* 只有图标的按钮:36 / 32,悬停出 --c-n100;必须有 aria-label 和悬停提示(§6) */ +.icon-btn{position:relative;flex:none;display:inline-grid;place-items:center;width:var(--control-h);height:var(--control-h);padding:0;border:var(--line) solid transparent;border-radius:var(--radius-ctl);background:none;color:var(--text-primary);cursor:pointer;transition:background-color var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)} +.icon-btn:hover{background:var(--c-n100)} .icon-btn.bordered{border-color:var(--border-control);background:var(--bg-surface)} -.icon-btn.sm{width:28px;height:28px} -.icon-btn .dot{position:absolute;top:6px;right:6px;width:6px;height:6px;border-radius:50%;background:var(--c-ink)} +.icon-btn.bordered:hover{border-color:var(--c-ink);background:var(--c-n50)} +.icon-btn.sm{width:var(--control-h-sm);height:var(--control-h-sm)} +.icon-btn.muted{color:var(--text-secondary)} +.icon-btn .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)} .icon-btn[disabled]{color:var(--text-disabled);cursor:not-allowed;background:none} .btn-group{display:inline-flex} .btn-group .btn + .btn{margin-left:-1px} .btn-group .btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0} .btn-group .btn:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0} +.btn-group .btn:hover{position:relative;z-index:1} -/* ---- B4 输入框、文本域、带单位、搜索(§7.13) ---- */ +/* ---- B4 输入框、文本域、带单位、搜索(§7.13) ---- + * 聚焦:边框变墨 + 1px 墨色内圈(合计 2px,与顶栏搜索一致);错误:赭色边框 + 赭色内圈 */ .input{width:100%;transition:border-color var(--dur-fast) var(--ease)} -.input:hover{border-color:var(--c-ink)} -.input:focus,.input.is-focus{outline:2px solid var(--focus-ring);outline-offset:2px;border-color:var(--c-ink)} -.input.is-hover{border-color:var(--c-ink)} -.input[disabled],.input.is-disabled{background:var(--bg-sunken);border-color:var(--bg-sunken);color:var(--text-disabled);cursor:not-allowed} +.input:hover,.input.is-hover{border-color:var(--c-ink)} +.input:focus,.input.is-focus{outline:0;border-color:var(--c-ink);box-shadow:0 0 0 1px var(--c-ink)} +.field.err .input:focus{border-color:var(--warn);box-shadow:0 0 0 1px var(--warn)} +.input[disabled],.input.is-disabled{background:var(--c-n100);border-color:var(--c-n100);color:var(--text-disabled);cursor:not-allowed} .input[disabled]::placeholder{color:var(--text-disabled)} -.input[readonly]{background:var(--bg-sunken);border-color:var(--border-subtle)} -.input[readonly]:focus{outline-color:var(--border-control)} -textarea.input{height:auto;min-height:80px;padding:var(--sp-2) 10px;resize:vertical;line-height:var(--lh-body)} -select.input{appearance:none;padding-right:28px;cursor:pointer} +.input[readonly]{background:var(--c-n50);border-color:var(--border-subtle)} +.input[readonly]:focus{box-shadow:none;border-color:var(--border-control)} +textarea.input{height:auto;min-height:80px;padding:var(--sp-2) var(--sp-3);resize:vertical;line-height:var(--lh-body)} +select.input{appearance:none;padding-right:32px;cursor:pointer} .sel-wrap{position:relative;display:block} -.sel-wrap > .ic{position:absolute;right:8px;top:8px;pointer-events:none;color:var(--text-secondary)} +.sel-wrap > .ic{position:absolute;right:10px;top:10px;pointer-events:none;color:var(--text-secondary)} .field label{display:flex;gap:var(--sp-1);align-items:baseline} .field label small{margin-left:2px} .field .msg-row{display:flex;gap:var(--sp-3);align-items:baseline} .field .msg-row .msg{flex:1} -.field .count{font:var(--fw-medium) var(--fs-caption)/var(--lh-caption) var(--font-figures);font-variant-numeric:tabular-nums;color:var(--text-secondary);white-space:nowrap} +.field .count{font:var(--fw-medium) var(--fs-figure-xs)/var(--lh-caption) var(--font-figures);font-variant-numeric:tabular-nums;color:var(--text-secondary);white-space:nowrap} .field.err .count{color:var(--warn-text)} .field.err textarea.input,.field.err .select{border-color:var(--warn);box-shadow:inset 0 0 0 1px var(--warn)} .field.dis label{color:var(--text-disabled)} .field.fill{max-width:none} .field + .field{margin-top:0} .input-unit{display:flex} -.input-unit .input{border-top-right-radius:0;border-bottom-right-radius:0;font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-size:15px} -.input-unit .unit{display:flex;align-items:center;padding:0 10px;border:1px solid var(--border-control);border-left:0;border-radius:0 var(--radius-sm) var(--radius-sm) 0;background:var(--bg-sunken);color:var(--text-secondary);white-space:nowrap} +.input-unit .input{border-top-right-radius:0;border-bottom-right-radius:0;font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-size:var(--fs-figure-sm)} +.input-unit .unit{display:flex;align-items:center;padding:0 var(--sp-3);border:var(--line) solid var(--border-control);border-left:0;border-radius:0 var(--radius-ctl) var(--radius-ctl) 0;background:var(--c-n50);color:var(--text-secondary);white-space:nowrap} .search{position:relative;display:block} -.search > .ic{position:absolute;left:10px;top:8px;color:var(--text-secondary);pointer-events:none} -.search .input{padding-left:32px} -.search .kbd{position:absolute;right:8px;top:6px} +.search > .ic{position:absolute;left:11px;top:10px;color:var(--text-secondary);pointer-events:none} +.search .input{padding-left:34px} +.search .kbd{position:absolute;right:7px;top:7px} /* 表单布局 */ .form{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--sp-5) var(--sp-6)} .form .span2{grid-column:1 / -1} .form .field{max-width:none} .form-sec{margin:0;padding:0;border:0;min-width:0} -.form-sec + .form-sec{margin-top:var(--sp-8);padding-top:var(--sp-6);border-top:1px solid var(--border-subtle)} +.form-sec + .form-sec{margin-top:var(--sp-8);padding-top:var(--sp-6);border-top:var(--line) solid var(--border-subtle)} .form-sec > legend{float:left;width:100%;padding:0;margin:0 0 var(--sp-4);font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans)} .form-sec > legend small{font-weight:var(--fw-regular);font-size:var(--fs-caption);color:var(--text-secondary);margin-left:var(--sp-2)} .form-sec > legend + *{clear:both} -.form-foot{display:flex;gap:var(--sp-3);align-items:center;margin-top:var(--sp-8);padding-top:var(--sp-5);border-top:1px solid var(--border-subtle)} +.form-foot{display:flex;gap:var(--sp-3);align-items:center;margin-top:var(--sp-8);padding-top:var(--sp-5);border-top:var(--line) solid var(--border-subtle)} .form-foot .cost{margin-right:auto} .err-sum{margin-bottom:var(--sp-5)} .err-sum ul{margin:var(--sp-1) 0 0;padding-left:1.2em} .err-sum a{color:var(--warn-text);text-underline-offset:3px} -/* ---- B5 下拉选择 / 下拉菜单(浮层,§5.1 只有浮层有阴影) ---- */ +/* ---- B5 下拉选择 / 下拉菜单(Q4:选中项加粗,右侧墨色 ✓;浮层圆角 12,只有浮层有阴影) ---- */ .pop{position:relative;display:block} .pop.inline{display:inline-block} -.select{display:flex;align-items:center;gap:var(--sp-2);width:100%;height:var(--control-h);padding:0 8px 0 10px;border:1px solid var(--border-control);border-radius:var(--radius-sm);background:var(--bg-surface);font:var(--fs-body) var(--font-sans);color:var(--text-primary);text-align:left;cursor:pointer} +.select{display:flex;align-items:center;gap:var(--sp-2);width:100%;height:var(--control-h);padding:0 10px 0 var(--sp-3);border:var(--line) solid var(--border-control);border-radius:var(--radius-ctl);background:var(--bg-surface);font:var(--fs-body) var(--font-sans);color:var(--text-primary);text-align:left;cursor:pointer;transition:border-color var(--dur-fast) var(--ease)} .select:hover{border-color:var(--c-ink)} -.select[aria-expanded="true"],.select.is-focus{outline:2px solid var(--focus-ring);outline-offset:2px;border-color:var(--c-ink)} +.select[aria-expanded="true"],.select.is-focus{outline:0;border-color:var(--c-ink);box-shadow:0 0 0 1px var(--c-ink)} .select .val{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .select .ph{color:var(--text-secondary)} .select > .ic{color:var(--text-secondary);transition:transform var(--dur-fast) var(--ease)} .select[aria-expanded="true"] > .ic{transform:rotate(180deg)} -.select[disabled]{background:var(--bg-sunken);border-color:var(--bg-sunken);color:var(--text-disabled);cursor:not-allowed} +.select[disabled]{background:var(--c-n100);border-color:var(--c-n100);color:var(--text-disabled);cursor:not-allowed} .select.sm{height:var(--control-h-sm)} -.menu{position:absolute;z-index:45;top:calc(100% + 4px);left:0;min-width:100%;max-height:280px;overflow:auto;background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-md);box-shadow:var(--shadow-overlay);padding:var(--sp-1) 0;margin:0;list-style:none} +.menu{position:absolute;z-index:45;top:calc(100% + 4px);left:0;min-width:100%;max-height:300px;overflow:auto;margin:0;padding:6px;list-style:none;background:var(--bg-surface);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-card);box-shadow:var(--shadow-overlay)} .menu.right{left:auto;right:0} -.menu [role=option],.menu [role=menuitem]{display:flex;align-items:center;gap:var(--sp-2);min-height:32px;padding:5px var(--sp-3);cursor:pointer;white-space:nowrap} -.menu [role=option]:hover,.menu [role=menuitem]:hover,.menu .is-active{background:var(--bg-hover)} +.menu.float{position:fixed;top:auto;left:auto;min-width:248px} /* 由脚本按触发按钮定位,如列表行的「更多」 */ +.menu [role=option],.menu [role=menuitem]{display:flex;align-items:center;gap:var(--sp-2);width:100%;min-height:34px;padding:6px 10px;border:0;border-radius:var(--radius-tag);background:none;font:var(--fs-body)/var(--lh-body) var(--font-sans);color:var(--text-primary);text-align:left;cursor:pointer;white-space:nowrap} +.menu [role=option]:hover,.menu [role=menuitem]:hover,.menu .is-active,.menu [role=menuitem]:focus-visible{background:var(--c-n100);outline:0} .menu [aria-selected="true"]{font-weight:var(--fw-bold)} -.menu [aria-selected="true"]::after{content:"✓";margin-left:auto;padding-left:var(--sp-4)} -.menu [aria-disabled="true"]{color:var(--text-disabled);cursor:not-allowed;background:none} -.menu .grp{padding:var(--sp-2) var(--sp-3) 2px;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} -.menu hr{border:0;border-top:1px solid var(--border-subtle);margin:var(--sp-1) 0} +.menu [aria-selected="true"]::after{content:"✓";margin-left:auto;padding-left:var(--sp-4);color:var(--c-ink);font-weight:var(--fw-bold)} +.menu [aria-disabled="true"],.menu [aria-disabled="true"]:hover{color:var(--text-disabled);cursor:not-allowed;background:none} +.menu [aria-disabled="true"]:focus-visible{background:var(--c-n50)} +.menu .grp{padding:var(--sp-2) 10px 2px;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} +.menu hr{border:0;border-top:var(--line) solid var(--border-subtle);margin:6px 0} .menu small{color:var(--text-secondary);font-size:var(--fs-caption);margin-left:auto;padding-left:var(--sp-4)} +.menu small .fig{color:var(--text-primary);font-size:var(--fs-figure-xs)} +/* 两行菜单项:动作 + 说明(扣点说明写在这里,§8.3) */ +.menu .two{flex-direction:column;align-items:flex-start;gap:0} +.menu .two small{display:block;margin:0;padding:0;line-height:var(--lh-caption)} .menu .ic{color:var(--text-secondary)} -/* ---- B6 日期选择(§9.3 日期格式) ---- */ -.date-trigger{font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-size:15px} -.date-trigger .ph{font-family:var(--font-sans);font-size:var(--fs-body)} -.cal{position:absolute;z-index:45;top:calc(100% + 4px);left:0;width:280px;background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-md);box-shadow:var(--shadow-overlay);padding:var(--sp-3)} +/* ---- B6 日期输入与日期选择(§7.22、§9.3;Q4:不可选日期只变禁用灰;Q10:输入「2026-11-20 09:30」) ---- + * 日期输入 .dt[data-dt]:可直接键入(接受 2026/11/20 9:30 等常见写法,离开时整理成标准格式),右侧按钮打开日期选择器; + * 带时间的字段(data-time)在选择器底部填时间,24 小时制;下方 [data-dt-msg] 显示「11月20日 09:30 · 剩 N 天」(data-left)。 + * 交互见 demo.js 的 Demo.dateInput(),页面里不用再写脚本。 */ +.dt{position:relative;display:block} +.dt > .input{padding-right:40px;font-family:var(--font-figures);font-variant-numeric:tabular-nums;font-size:var(--fs-figure-sm)} +.dt > .input::placeholder{font-family:var(--font-sans);font-size:var(--fs-body)} +.dt > .icon-btn{position:absolute;top:2px;right:2px;color:var(--text-secondary)} +.dt > .icon-btn:hover,.dt > .icon-btn[aria-expanded="true"]{color:var(--text-primary)} +.dt.up > .cal{top:auto;bottom:calc(100% + 4px)} +.dt.d-open > .cal{position:static;margin-top:var(--sp-1)} +.cal{position:absolute;z-index:45;top:calc(100% + 4px);left:0;width:288px;padding:var(--sp-3);background:var(--bg-surface);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-card);box-shadow:var(--shadow-overlay)} .cal-h{display:flex;align-items:center;gap:var(--sp-1);margin-bottom:var(--sp-2)} .cal-h b{flex:1;text-align:center;font-weight:var(--fw-bold)} -.cal-h b .fig{font-size:15px} +.cal-h b .fig{font-size:var(--fs-figure-sm)} .cal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;text-align:center} .cal-grid .wd{font-size:var(--fs-caption);line-height:28px;color:var(--text-secondary)} -.cal-grid button{position:relative;height:32px;border:1px solid transparent;border-radius:var(--radius-sm);background:none;font:var(--fw-medium) 15px/1 var(--font-figures);font-variant-numeric:tabular-nums;color:var(--text-primary);cursor:pointer} -.cal-grid button:hover{background:var(--bg-hover)} -.cal-grid button.out{color:var(--text-disabled)} -.cal-grid button.today{border-color:var(--border-control)} -.cal-grid button.mark::after{content:"";position:absolute;left:50%;bottom:3px;width:4px;height:4px;margin-left:-2px;border-radius:50%;background:var(--c-ink)} -.cal-grid button[aria-selected="true"]{background:var(--c-ink);color:var(--text-on-dark);border-color:var(--c-ink)} +.cal-grid button{position:relative;height:34px;border:0;border-radius:var(--radius-ctl);background:none;font:var(--fw-medium) var(--fs-figure-sm)/1 var(--font-figures);font-variant-numeric:tabular-nums;color:var(--text-primary);cursor:pointer;transition:background-color var(--dur-fast) var(--ease)} +.cal-grid button:not([disabled]):hover{background:var(--c-n100)} +/* 非本月的格子留空(不显示日期),和不可选日期(禁用灰、无悬停)不再混淆 */ +.cal-grid .pad{height:34px} +.cal-grid button.today{box-shadow:inset 0 0 0 1px var(--border-control)} +.cal-grid button.mark::after{content:"";position:absolute;left:50%;bottom:4px;width:4px;height:4px;margin-left:-2px;border-radius:50%;background:var(--c-ink)} +.cal-grid button[aria-selected="true"]{background:var(--c-ink);color:var(--text-on-dark);box-shadow:none} +.cal-grid button[aria-selected="true"]:hover{background:var(--primary-hover)} .cal-grid button[aria-selected="true"].mark::after{background:var(--c-white)} -.cal-grid button[disabled]{color:var(--text-disabled);cursor:not-allowed;background:none;text-decoration:line-through} -.cal-f{display:flex;align-items:center;gap:var(--sp-2);margin-top:var(--sp-2);padding-top:var(--sp-2);border-top:1px solid var(--border-subtle);font-size:var(--fs-caption);color:var(--text-secondary)} +.cal-grid button[disabled]{color:var(--text-disabled);cursor:not-allowed;background:none;font-weight:var(--fw-regular)} +.cal-grid button[disabled].mark::after{background:var(--text-disabled)} +.cal-f{display:flex;align-items:center;gap:var(--sp-2);margin-top:var(--sp-2);padding-top:var(--sp-2);border-top:var(--line) solid var(--border-subtle);font-size:var(--fs-caption);color:var(--text-secondary)} .cal-f .btn{margin-left:auto} -.cal-f .time{width:72px;height:var(--control-h-sm);font-family:var(--font-figures);font-size:15px;text-align:center} -.dt-pair{display:grid;grid-template-columns:minmax(0,1fr) 96px;gap:var(--sp-2)} -.field.err .dt-pair > .input:not([aria-invalid]){border-color:var(--border-control);box-shadow:none} +.cal-f .time{width:72px;height:var(--control-h-sm);font-family:var(--font-figures);font-size:var(--fs-figure-sm);text-align:center} -/* ---- B7 复选框、单选、开关(§8.2 批量勾选) ---- */ -.ck{appearance:none;flex:none;display:inline-grid;place-content:center;width:16px;height:16px;margin:0;border:1px solid var(--border-control);border-radius:var(--radius-sm);background:var(--bg-surface);cursor:pointer;vertical-align:-3px} +/* ---- B7 复选框、单选、开关(§8.2 批量勾选;Q4 开关) ---- */ +.ck{appearance:none;flex:none;display:inline-grid;place-content:center;width:16px;height:16px;margin:0;border:var(--line) solid var(--border-control);border-radius:var(--radius-xs);background:var(--bg-surface);cursor:pointer;vertical-align:-3px} .ck:hover{border-color:var(--c-ink)} .ck::after{content:"";display:none} .ck:checked,.ck[type=checkbox]:indeterminate{background:var(--c-ink);border-color:var(--c-ink)} @@ -241,9 +304,9 @@ select.input{appearance:none;padding-right:28px;cursor:pointer} .ck[type=radio]{border-radius:50%} .ck[type=radio]:checked{background:var(--bg-surface);border-color:var(--c-ink)} .ck[type=radio]:checked::after{width:8px;height:8px;border:0;border-radius:50%;background:var(--c-ink);transform:none} -.ck:disabled{background:var(--bg-sunken);border-color:var(--c-rule);cursor:not-allowed} +.ck:disabled{background:var(--c-n100);border-color:var(--border-subtle);cursor:not-allowed} .ck:disabled:checked{background:var(--c-muted);border-color:var(--c-muted)} -.ck[type=radio]:disabled:checked{background:var(--bg-sunken)} +.ck[type=radio]:disabled:checked{background:var(--c-n100)} .ck[type=radio]:disabled:checked::after{background:var(--c-muted)} .ck.is-focus{outline:2px solid var(--focus-ring);outline-offset:2px} .choice{display:inline-flex;gap:var(--sp-2);align-items:flex-start;cursor:pointer;line-height:var(--lh-body)} @@ -253,132 +316,146 @@ select.input{appearance:none;padding-right:28px;cursor:pointer} .choice.dis small{color:var(--text-disabled)} .choices{display:flex;flex-direction:column;gap:var(--sp-2)} .choices.h{flex-direction:row;flex-wrap:wrap;gap:var(--sp-2) var(--sp-6)} -.choice-card{display:flex;gap:var(--sp-3);align-items:flex-start;padding:var(--sp-3) var(--sp-4);border:1px solid var(--border-control);border-radius:var(--radius-sm);background:var(--bg-surface);cursor:pointer} +.choice-card{display:flex;gap:var(--sp-3);align-items:flex-start;padding:var(--sp-3) var(--sp-4);border:var(--line) solid var(--border-control);border-radius:var(--radius-ctl);background:var(--bg-surface);cursor:pointer;transition:border-color var(--dur-fast) var(--ease),background-color var(--dur-fast) var(--ease)} +.choice-card:hover{background:var(--c-n50)} .choice-card:has(:checked){border-color:var(--c-ink);box-shadow:inset 0 0 0 1px var(--c-ink)} .choice-card .ck{margin-top:3px} .choice-card b{display:block} .choice-card small{display:block;color:var(--text-secondary);font-size:var(--fs-caption);line-height:var(--lh-caption)} -/* 开关:沿用 2px 圆角,不做胶囊形(§5.1) */ +/* 开关(Q4):圆角 6 的矩形轨道 + 圆角 4 的方块滑钮;开为墨色;旁边写「开 / 关」;不用胶囊形 */ .switch{display:inline-flex;align-items:center;gap:var(--sp-2);padding:0;border:0;background:none;font:var(--fs-body) var(--font-sans);color:var(--text-primary);cursor:pointer} -.switch .track{position:relative;width:32px;height:18px;border:1px solid var(--border-control);border-radius:var(--radius-sm);background:var(--bg-surface);transition:background var(--dur-fast) var(--ease)} -.switch .track::after{content:"";position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:1px;background:var(--c-muted);transition:transform var(--dur-fast) var(--ease),background var(--dur-fast) var(--ease)} +.switch .track{position:relative;flex:none;width:34px;height:20px;border:var(--line) solid var(--border-control);border-radius:var(--radius-tag);background:var(--bg-surface);transition:background-color var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)} +.switch .track::after{content:"";position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:var(--radius-xs);background:var(--c-muted);transition:transform var(--dur-fast) var(--ease),background-color var(--dur-fast) var(--ease)} +.switch:hover .track{border-color:var(--c-ink)} .switch[aria-checked="true"] .track{background:var(--c-ink);border-color:var(--c-ink)} .switch[aria-checked="true"] .track::after{transform:translateX(14px);background:var(--c-white)} -.switch .st{font-size:var(--fs-caption);color:var(--text-secondary);min-width:2em} +.switch .st{min-width:2em;font-size:var(--fs-caption);color:var(--text-secondary)} +.switch[aria-checked="true"] .st{color:var(--text-primary)} .switch[disabled]{color:var(--text-disabled);cursor:not-allowed} -.switch[disabled] .track{background:var(--bg-sunken);border-color:var(--c-rule)} -.switch[disabled] .track::after{background:var(--c-rule)} +.switch[disabled] .track{background:var(--c-n100);border-color:var(--border-subtle)} +.switch[disabled] .track::after{background:var(--border-subtle)} .switch[disabled][aria-checked="true"] .track{background:var(--c-muted);border-color:var(--c-muted)} +.switch[disabled][aria-checked="true"] .track::after{background:var(--c-n100)} +.switch[disabled] .st{color:var(--text-disabled)} -/* ---- B8 分段控件、标签页(§5.3 页签、§7.5 分类) ---- */ -.seg{display:inline-flex;border:1px solid var(--border-control);border-radius:var(--radius-sm);overflow:hidden;background:var(--bg-surface)} -.seg button{height:28px;padding:0 10px;border:0;background:none;font:var(--fs-body) var(--font-sans);color:var(--text-primary);cursor:pointer;display:flex;gap:6px;align-items:center;white-space:nowrap} -.seg button:hover{background:var(--bg-hover)} -.seg button + button{border-left:1px solid var(--border-subtle)} -.seg button[aria-pressed="true"]{background:var(--c-ink);color:var(--text-on-dark)} -.seg button[aria-pressed="true"] .fig{color:var(--text-on-dark)} -.seg button[aria-pressed="true"] .sev.hint,.seg button[aria-pressed="true"] .sev.major{background:var(--c-white)} -.seg .fig{font-weight:var(--fw-medium);color:var(--text-secondary)} -.seg .sev .fig{color:inherit} -.tabs{display:flex;gap:var(--sp-6);border-bottom:1px solid var(--border-subtle)} +/* ---- B8 筛选(分段)、标签页(§5.3 页签、§7.5 分类) ---- + * v0.2 筛选:文字按钮一排,按下 = --c-n100 实底 + 加粗(与工作台项目卡片头一致),不再用墨底反白。 */ +.seg{display:inline-flex;align-items:center;gap:2px} +.seg button{display:inline-flex;align-items:center;gap:6px;height:var(--control-h-sm);padding:0 10px;border:0;border-radius:var(--radius-ctl);background:none;font:var(--fs-body) var(--font-sans);color:var(--text-secondary);white-space:nowrap;cursor:pointer;transition:background-color var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)} +.seg button:hover{background:var(--c-n100);color:var(--text-primary)} +.seg button[aria-pressed="true"]{background:var(--c-n100);color:var(--text-primary);font-weight:var(--fw-bold)} +.seg .fig{font-size:var(--fs-figure-sm);color:inherit} +.seg .sev .fig{font-size:var(--fs-figure-xs)} +.tabs{display:flex;gap:var(--sp-6);border-bottom:var(--line) solid var(--border-subtle)} .tabs.flush{border-bottom:0} .tabs [role=tab]{display:flex;gap:6px;align-items:center;margin-bottom:-1px;padding:var(--sp-2) 0 10px;border:0;border-bottom:2px solid transparent;background:none;font:var(--fs-body) var(--font-sans);color:var(--text-secondary);cursor:pointer;text-decoration:none;white-space:nowrap} .tabs [role=tab]:hover{color:var(--text-primary)} .tabs [role=tab][aria-selected="true"]{color:var(--text-primary);font-weight:var(--fw-bold);border-bottom-color:var(--c-ink)} .tabs [role=tab][aria-disabled="true"]{color:var(--text-disabled);cursor:not-allowed} -.tabs [role=tab] > .fig{font-weight:var(--fw-medium)} +.tabs [role=tab] > .fig{font-size:var(--fs-figure-sm)} + .tabpanel{padding-top:var(--sp-4)} -/* ---- B9 分页 ---- */ -.pagination{display:flex;align-items:center;gap:var(--sp-1);margin-top:var(--sp-3);font-size:var(--fs-caption);color:var(--text-secondary)} +/* ---- B9 分页(Q4:当前页 --c-n100 实底 + 加粗,与筛选一致) ---- */ +.pagination{display:flex;align-items:center;gap:2px;margin-top:var(--sp-3);font-size:var(--fs-caption);color:var(--text-secondary)} .pagination .total{margin-right:auto} -.pagination .fig{font-weight:var(--fw-medium)} -.pagination button.pg{min-width:28px;height:28px;padding:0 6px;border:1px solid transparent;border-radius:var(--radius-sm);background:none;font:var(--fw-medium) 15px/1 var(--font-figures);font-variant-numeric:tabular-nums;color:var(--text-primary);cursor:pointer} -.pagination button.pg:hover{background:var(--bg-hover)} -.pagination button.pg[aria-current="page"]{border-color:var(--c-ink);font-weight:var(--fw-bold);background:var(--bg-surface)} +.pagination .fig{font-size:var(--fs-figure-xs)} +.pagination button.pg{min-width:var(--control-h-sm);height:var(--control-h-sm);padding:0 var(--sp-2);border:0;border-radius:var(--radius-ctl);background:none;font:var(--fw-medium) var(--fs-figure-sm)/1 var(--font-figures);font-variant-numeric:tabular-nums;color:var(--text-primary);cursor:pointer;transition:background-color var(--dur-fast) var(--ease)} +.pagination button.pg:hover{background:var(--c-n100)} +.pagination button.pg[aria-current="page"]{background:var(--c-n100);font-weight:var(--fw-bold)} .pagination button.pg[disabled]{color:var(--text-disabled);cursor:not-allowed;background:none} .pagination .gap{width:20px;text-align:center} .pagination .jump{display:flex;align-items:center;gap:6px;margin-left:var(--sp-4)} -.pagination .jump .input{width:52px;height:28px;text-align:center;font-family:var(--font-figures);font-size:15px} +.pagination .jump .input{width:52px;height:var(--control-h-sm);text-align:center;font-family:var(--font-figures);font-size:var(--fs-figure-sm)} /* ---- B10 轻提示 Toast(§7.12:只用于操作结果,4 秒消失,悬停暂停) ---- */ -.toasts{position:fixed;left:50%;bottom:var(--sp-8);transform:translateX(-50%);z-index:60;display:flex;flex-direction:column;gap:var(--sp-2);align-items:center;pointer-events:none} -.toast{display:flex;gap:var(--sp-3);align-items:center;min-height:40px;padding:var(--sp-2) var(--sp-4);background:var(--c-ink);color:var(--text-on-dark);border-radius:var(--radius-md);box-shadow:var(--shadow-overlay);pointer-events:auto;white-space:nowrap} +.toasts{position:fixed;left:50%;bottom:28px;transform:translateX(-50%);z-index:60;display:flex;flex-direction:column;gap:var(--sp-2);align-items:center;pointer-events:none} +.toast{display:flex;gap:var(--sp-3);align-items:center;min-height:40px;padding:var(--sp-2) var(--sp-4);background:var(--c-ink);color:var(--text-on-dark);border-radius:var(--radius-ctl);box-shadow:var(--shadow-overlay);pointer-events:auto;white-space:nowrap} .toast .ok{display:inline-grid;place-items:center;width:16px;height:16px;border-radius:50%;background:var(--c-green);color:var(--c-white);font-size:11px;line-height:1} .toast a,.toast button.lnk{color:var(--text-on-dark);text-decoration:underline;text-underline-offset:3px;background:none;border:0;font:inherit;cursor:pointer;padding:0} -.toast .sep{width:1px;align-self:stretch;margin:2px 0;background:color-mix(in srgb,var(--c-white) 24%,transparent)} +.toast .sep{width:1px;align-self:stretch;margin:2px 0;background:var(--on-dark-line)} .toast.static{position:static} -/* ---- B11 对话框(§7.14) ---- */ -.scrim{position:fixed;inset:0;z-index:50;display:grid;place-items:center;background:color-mix(in srgb,var(--c-ink) 32%,transparent)} -.dialog{position:relative;width:480px;max-width:calc(100vw - 32px);max-height:calc(100vh - 64px);overflow:auto;background:var(--bg-surface);border-radius:var(--radius-md);box-shadow:var(--shadow-overlay);padding:var(--sp-6)} +/* ---- B11 对话框(§7.14:480 / 640,圆角 12;对话框里可以有自己的主按钮,Q12) ---- */ +/* 对话框本身不滚动、不裁剪(overflow 可见),里面的下拉、日期浮层可以伸出对话框;内容比窗口高时由遮罩滚动 */ +.scrim{position:fixed;inset:0;z-index:50;display:grid;overflow:auto;padding:var(--sp-8) var(--sp-4);background:var(--scrim)} +.dialog{position:relative;margin:auto;width:480px;max-width:100%;padding:var(--sp-6);background:var(--bg-surface);border-radius:var(--radius-card);box-shadow:var(--shadow-overlay)} .dialog.wide{width:640px} -.dialog.static{position:static;max-width:none;box-shadow:var(--shadow-overlay)} +.dialog.static{margin:0;max-width:none} /* 陈列用:直接摆在页面里 */ .dialog h2{font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans);margin:0 var(--sp-8) var(--sp-3) 0} .dialog .x{position:absolute;top:var(--sp-4);right:var(--sp-4)} .dialog .body > p{margin:0 0 var(--sp-3)} .dialog .body > .field{margin-top:var(--sp-4)} .dialog .foot{display:flex;justify-content:flex-end;align-items:center;gap:var(--sp-2);margin-top:var(--sp-6)} .dialog .foot .left{margin-right:auto} +.dialog table.data td{height:auto} -/* ---- B12 提醒补充(§7.12、§7.15) ---- */ -.alert{align-items:flex-start} +/* ---- B12 提醒补充(§7.12、§7.15):右侧操作、副说明、错误编号 ---- */ +.alert:has(> .btn,> .act){align-items:center} +.alert:has(> .btn,> .act) > .ic{margin-top:0} .alert > div{flex:1;min-width:0} -.alert .act{margin-left:auto;flex:none;display:flex;gap:var(--sp-2);align-items:center} +.alert > p{flex:1} +.alert .act{margin-left:auto;flex:none;align-self:center;display:flex;gap:var(--sp-2);align-items:center} +.alert > .btn{flex:none;align-self:center} .alert .sub{display:block;margin-top:2px;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} .alert .fig{font-weight:var(--fw-medium)} -.errno{font:var(--fw-medium) var(--fs-caption)/1 var(--font-figures);color:var(--text-secondary);letter-spacing:.02em} +.errno{font:var(--fw-medium) var(--fs-figure-xs)/1 var(--font-figures);color:var(--text-secondary);letter-spacing:.02em} /* ---- B13 提示气泡(悬停 / 聚焦显示,§6 只有图标的按钮要有悬停提示) ---- */ [data-tip]{position:relative} -[data-tip]:hover::after,[data-tip]:focus-visible::after,[data-tip].tip-on::after{content:attr(data-tip);position:absolute;z-index:55;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);max-width:280px;width:max-content;padding:5px var(--sp-2);border-radius:var(--radius-md);background:var(--c-ink);color:var(--text-on-dark);font:var(--fw-regular) var(--fs-caption)/var(--lh-caption) var(--font-sans);white-space:normal;text-align:left;box-shadow:var(--shadow-overlay);pointer-events:none} -[data-tip].tip-below:hover::after,[data-tip].tip-below.tip-on::after{bottom:auto;top:calc(100% + 6px)} -[data-tip].tip-start:hover::after,[data-tip].tip-start.tip-on::after{left:0;transform:none} -[data-tip].tip-left:hover::after,[data-tip].tip-left.tip-on::after{left:auto;right:0;transform:none} +[data-tip]:hover::after,[data-tip]:focus-visible::after,[data-tip].tip-on::after{content:attr(data-tip);position:absolute;z-index:55;bottom:calc(100% + 8px);left:50%;transform:translateX(-50%);width:max-content;max-width:280px;padding:6px 10px;border-radius:var(--radius-ctl);background:var(--c-ink);color:var(--text-on-dark);font:var(--fw-regular) var(--fs-caption)/var(--lh-caption) var(--font-sans);white-space:normal;text-align:left;pointer-events:none} +[data-tip].tip-below:hover::after,[data-tip].tip-below:focus-visible::after,[data-tip].tip-below.tip-on::after{bottom:auto;top:calc(100% + 8px)} +[data-tip].tip-start:hover::after,[data-tip].tip-start:focus-visible::after,[data-tip].tip-start.tip-on::after{left:0;transform:none} +[data-tip].tip-left:hover::after,[data-tip].tip-left:focus-visible::after,[data-tip].tip-left.tip-on::after{left:auto;right:0;transform:none} -/* ---- B14 空状态、骨架屏、进度(§7.11、§7.15) ---- */ +/* ---- B14 空状态、骨架屏、进度(§7.11、§7.15;Q8 骨架屏静止,不做明暗起伏) ---- */ .empty{display:flex;flex-direction:column;align-items:center;text-align:center;padding:var(--sp-12) var(--sp-6)} .empty > .ic{color:var(--text-secondary);margin-bottom:var(--sp-3)} .empty b{font-size:var(--fs-title);line-height:var(--lh-title)} .empty p{margin:var(--sp-1) 0 var(--sp-5);color:var(--text-secondary);max-width:30em} .empty.sm{padding:var(--sp-8) var(--sp-4)} -.empty.sm p{margin-bottom:var(--sp-3)} -.skel{display:block;height:12px;border-radius:var(--radius-sm);background:var(--bg-sunken);animation:skel 1.6s var(--ease) infinite alternate} +.empty.sm b{font-size:var(--fs-body);line-height:var(--lh-body)} +.empty.sm p{margin-bottom:0} +.empty.sm > .btn{margin-top:var(--sp-4)} +.skel{display:block;height:12px;border-radius:var(--radius-xs);background:var(--c-n100)} .skel.t{height:16px;width:40%} .skel.w60{width:60%} .skel.w80{width:80%} .skel.w30{width:30%} .skel.w20{width:20%} .skel + .skel{margin-top:var(--sp-2)} -.skel-row{display:grid;grid-template-columns:24px minmax(0,1fr) 120px;gap:var(--sp-3);padding:var(--sp-3) 0;border-top:1px solid var(--border-subtle)} +.skel-row{display:grid;grid-template-columns:24px minmax(0,1fr) 120px;gap:var(--sp-3);padding:var(--sp-3) 0;border-top:var(--line) solid var(--border-subtle)} .skel-row:first-child{border-top:0} -@keyframes skel{from{opacity:1}to{opacity:.45}} -@media (prefers-reduced-motion:reduce){.skel{animation:none}} -.progress{display:flex;align-items:center;gap:var(--sp-3)} +.progress{display:flex;align-items:center;gap:10px} .progress .bar{flex:1;margin:0} -.progress .fig{font-weight:var(--fw-medium);min-width:3em;text-align:right} +.progress .fig{min-width:3em;text-align:right} .steps .fig{font-weight:var(--fw-medium);color:var(--text-secondary);margin-left:auto} .steps li > span:nth-child(2){flex:1} .steps .fail .dot{border-color:var(--warn);color:var(--warn-text);font-weight:var(--fw-bold)} .steps .fail{color:var(--warn-text)} /* ---- B15 杂项小件 ---- */ -.kbd{display:inline-block;min-width:20px;padding:0 5px;border:1px solid var(--border-control);border-bottom-width:2px;border-radius:3px;font:var(--fw-medium) var(--fs-caption)/18px var(--font-figures);text-align:center;background:var(--bg-surface);color:var(--text-primary)} +.kbd{display:inline-flex;align-items:center;justify-content:center;min-width:22px;height:22px;padding:0 6px;border-radius:var(--radius-tag);background:var(--c-n100);font:var(--fw-bold) var(--fs-caption)/1 var(--font-figures);color:var(--text-secondary);white-space:nowrap} .muted{color:var(--text-secondary)} .nw{white-space:nowrap} .cap{font-size:var(--fs-caption);line-height:var(--lh-caption)} .fig.m{font-weight:var(--fw-medium)} +.fig.sm{font-size:var(--fs-figure-sm)} +.fig.xs{font-size:var(--fs-figure-xs)} +/* 数字默认字号见 components.css(.fig = 15,小字里 13);这里补上 demo 层的 12px 小字容器 */ +:is(.d-cap,.d-label,.d-rules,.filters-on,.ai-label,.hint-warn,.sign.sm,.sign-note,.dates .left,.stat p,.quota p,.lb-h) .fig{font-size:var(--fs-figure-xs)} .kv{display:grid;grid-template-columns:96px minmax(0,1fr);gap:var(--sp-2) var(--sp-4);margin:0} .kv dt{color:var(--text-secondary)} .kv dd{margin:0} -.chip{display:inline-flex;align-items:center;gap:var(--sp-1);height:24px;padding:0 4px 0 8px;border:1px solid var(--border-control);border-radius:var(--radius-sm);background:var(--bg-surface);font-size:var(--fs-caption);white-space:nowrap} -.chip button{display:inline-grid;place-items:center;width:18px;height:18px;padding:0;border:0;border-radius:var(--radius-sm);background:none;color:var(--text-secondary);cursor:pointer} -.chip button:hover{background:var(--bg-hover);color:var(--text-primary)} -.drop{display:flex;flex-direction:column;align-items:center;gap:var(--sp-1);border:1.5px dashed var(--border-control);border-radius:var(--radius-sm);background:var(--bg-surface);padding:var(--sp-8) var(--sp-6);text-align:center;cursor:pointer} -.drop:hover,.drop.is-over{border-color:var(--c-ink);background:var(--bg-hover)} +/* 可移除的筛选标签:中性实底 */ +.chip{display:inline-flex;align-items:center;gap:var(--sp-1);height:var(--tag-h);padding:0 3px 0 var(--sp-2);border-radius:var(--radius-tag);background:var(--c-n100);font-size:var(--fs-caption);white-space:nowrap} +.chip button{display:inline-grid;place-items:center;width:18px;height:18px;padding:0;border:0;border-radius:var(--radius-xs);background:none;color:var(--text-secondary);cursor:pointer} +.chip button:hover{background:var(--border-subtle);color:var(--text-primary)} +.drop{display:flex;flex-direction:column;align-items:center;gap:var(--sp-1);border:1.5px dashed var(--border-control);border-radius:var(--radius-card);background:var(--bg-surface);padding:var(--sp-8) var(--sp-6);text-align:center;cursor:pointer;transition:background-color var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)} +.drop:hover,.drop.is-over{border-color:var(--c-ink);background:var(--c-n50)} .drop > .ic{color:var(--text-secondary)} .drop b{font-size:var(--fs-title);line-height:var(--lh-title)} .drop span{color:var(--text-secondary);font-size:var(--fs-caption);line-height:var(--lh-caption)} -.file-row{display:grid;grid-template-columns:40px minmax(0,1fr) auto;gap:var(--sp-3);align-items:center;padding:var(--sp-3) var(--sp-4);background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-sm)} +.file-row{display:grid;grid-template-columns:40px minmax(0,1fr) auto;gap:var(--sp-3);align-items:center;padding:var(--sp-3) var(--sp-4);background:var(--bg-surface);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-card)} .file-row + .file-row{margin-top:var(--sp-2)} -.file-row .ext{font:var(--fw-bold) 11px/1 var(--font-figures);border:1px solid var(--border-control);border-radius:var(--radius-sm);padding:4px 0;text-align:center} +.file-row .ext{font:var(--fw-bold) 11px/1 var(--font-figures);background:var(--c-n100);border-radius:var(--radius-xs);padding:5px 0;text-align:center} .file-row b{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .file-row .acts{display:flex;gap:var(--sp-1);align-items:center} .flowline{display:flex;align-items:center} @@ -392,55 +469,74 @@ select.input{appearance:none;padding-right:28px;cursor:pointer} .flowline.sm{font-size:var(--fs-caption)} .flowline.sm .st i{width:8px;height:8px} .flowline.sm .st.now i{border-width:2.5px} +/* 头像(列表、记录里的小头像) */ +.av{display:inline-grid;place-items:center;flex:none;width:22px;height:22px;border-radius:50%;background:var(--c-n100);color:var(--text-primary);font-size:var(--fs-caption);font-weight:var(--fw-bold);line-height:1} -/* ---- B16 AI 产出块(§3.3 左侧虚线竖线、§7.16) ---- */ -.ai-block{border-left:1.5px dashed var(--ai);padding:var(--sp-1) 0 var(--sp-1) var(--sp-3)} -.ai-block.done{border-left:2px solid var(--c-ink)} +/* ---- B16 AI 产出块(§3.3、§7.16;Q2) ---- + * 确认前:「AI」小标签 + 左侧 2px 蓝铅笔虚线;正文始终墨色。 + * 确认后(.done):虚线去掉,下方出署名行(.sign:绿 ✓ + 姓名 + 时间)。左内边距保留,正文不跳动。 */ +.ai-block{border-left:2px dashed var(--ai);padding:var(--sp-1) 0 var(--sp-1) var(--sp-3);color:var(--text-primary)} +.ai-block.done{border-left-color:transparent} .ai-label{display:flex;gap:var(--sp-2);align-items:center;margin-bottom:var(--sp-2);font-size:var(--fs-caption);color:var(--text-secondary)} -.flag.box{border:1px solid var(--border-subtle);border-radius:var(--radius-sm);padding:0 5px;line-height:18px} +.flag.box{background:var(--c-n100);border-radius:var(--radius-tag);padding:0 6px;line-height:var(--tag-h)} .cite-src.inline{display:inline;margin-left:6px;white-space:nowrap} +/* 署名行(§7.4、§7.16、Q2):确认后的人工签名,绿 ✓ + 姓名 + 时间 + 动作,可撤销。 + *
王敏11月3日 14:30确认无误
+ * 下方说明(忽略原因、确认说明)用

;「已忽略」不是确认:.sign.ign 用辅助灰和「–」。 */ +.sign .fig{font-weight:var(--fw-medium)} +.sign .undo{margin-left:var(--sp-1);padding:0;border:0;background:none;font:var(--fs-caption)/var(--lh-caption) var(--font-sans);color:var(--text-secondary);text-decoration:underline;text-decoration-color:var(--link-underline);text-underline-offset:3px;cursor:pointer} +.sign .undo:hover{color:var(--text-primary);text-decoration-color:var(--c-ink)} .sign.ign{color:var(--text-secondary)} -.sign.sm{font-size:var(--fs-caption);margin-top:var(--sp-2)} +.sign.sm{font-size:var(--fs-caption);line-height:var(--lh-caption);margin-top:var(--sp-2)} +.sign-note{margin:2px 0 0;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} .more-cites{display:inline-block;margin-top:var(--sp-2);font-size:var(--fs-caption);color:var(--ai);background:none;border:0;padding:0;cursor:pointer;font-family:var(--font-sans)} -.more-cites:hover{text-decoration:underline;text-underline-offset:3px} +.more-cites:hover{color:var(--ai-strong);text-decoration:underline;text-underline-offset:3px} .cite q.clamp{-webkit-line-clamp:3} .cite .expand{font-size:var(--fs-caption);color:var(--ai);background:none;border:0;padding:0;cursor:pointer;font-family:var(--font-sans)} -/* ---- B17 卡片 ---- */ -.card-h{display:flex;align-items:center;gap:var(--sp-2);margin-bottom:var(--sp-3)} -.card-h h3{margin:0;font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans)} -.card-h .more{margin-left:auto;display:flex;gap:var(--sp-2);align-items:center} -.card.tight{padding:var(--sp-4) var(--sp-5)} -.card-foot{display:flex;gap:var(--sp-2);align-items:center;margin:var(--sp-4) calc(-1 * var(--sp-6)) calc(-1 * var(--sp-5));padding:var(--sp-3) var(--sp-6);border-top:1px solid var(--border-subtle)} -.card.tight .card-foot{margin:var(--sp-4) calc(-1 * var(--sp-5)) calc(-1 * var(--sp-4));padding:var(--sp-3) var(--sp-5)} +/* ---- B17 卡片:卡片头、卡片底、统计、额度、关键日期(§7.9、§7.10) ---- + * 整块卡片 .card:头用 .card-h,底用 .card-foot(贴边、带发丝线); + * 分区卡片 .card.flush:.card-h / .card-b / .card-f 见 components.css。 */ +.card-h{display:flex;align-items:center;gap:var(--sp-3);margin-bottom:var(--sp-3)} +.card-h .more{margin-left:auto;display:flex;gap:var(--sp-3);align-items:center} +.card.tight{padding:var(--sp-3) var(--card-x)} +.card-foot{display:flex;gap:var(--sp-4);align-items:center;margin:var(--sp-4) calc(-1 * var(--card-x)) calc(-1 * var(--card-y));padding:var(--sp-3) var(--card-x);border-top:var(--line) solid var(--border-subtle)} +.card.tight .card-foot{margin-bottom:calc(-1 * var(--sp-3))} .card-foot .r{margin-left:auto;display:flex;gap:var(--sp-2);align-items:center} -/* 项目卡 */ -.pcard{display:flex;flex-direction:column;gap:var(--sp-3);position:relative} -.pcard.is-fatal::before{content:"";position:absolute;left:-1px;top:var(--sp-5);bottom:var(--sp-5);width:3px;background:var(--sev-fatal)} -.pcard .pc-top{display:flex;gap:var(--sp-3);align-items:flex-start} -.pcard .pc-top > div{flex:1;min-width:0} -.pcard .card-foot{margin-top:auto} -.pcard .facts{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--sp-3);padding:var(--sp-3) 0;border-top:1px solid var(--border-subtle);border-bottom:1px solid var(--border-subtle)} -.pcard .facts small{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} -.pcard .facts > div{min-width:0} -/* 统计格 */ -.stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--sp-4)} -.stat{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);padding:var(--sp-4) var(--sp-5)} -.stat small{display:block;color:var(--text-secondary);font-size:var(--fs-caption);line-height:var(--lh-caption)} +/* 项目卡已删除(Q11:MVP 不做项目卡片视图) */ +/* 统计(d5):一张卡片四格,竖向发丝线分隔;格子可以是 button(点击筛选,aria-pressed 时底部 2px 墨色边条) */ +.stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));background:var(--bg-surface);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-card);overflow:hidden} +.stats.c3{grid-template-columns:repeat(3,minmax(0,1fr))} +.stat{position:relative;display:flex;flex-direction:column;align-items:flex-start;gap:2px;min-width:0;padding:var(--sp-4) var(--card-x);border:0;border-left:var(--line) solid var(--border-subtle);background:none;font:inherit;color:inherit;text-align:left} +.stat:first-child{border-left:0} +button.stat{cursor:pointer;transition:background-color var(--dur-fast) var(--ease)} +button.stat:hover{background:var(--c-n50)} +button.stat:focus-visible{outline-offset:-2px;border-radius:0} +.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 small{display:block;font-size:var(--fs-body);line-height:var(--lh-body);color:var(--text-secondary)} .stat .big{font:var(--fw-bold) var(--fs-figure)/var(--lh-figure) var(--font-figures);font-variant-numeric:tabular-nums} -.stat .unit{color:var(--text-secondary);margin-left:4px} -.stat p{margin:var(--sp-1) 0 0;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} +.stat .big.fatal{color:var(--sev-fatal)} +.stat .big.due{color:var(--warn-text)} +.stat .unit{color:var(--text-secondary);margin-left:4px;font-family:var(--font-figures)} +.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{margin:0;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} .stat p.due-soon,.stat p.due-today{color:var(--warn-text)} /* 额度卡 */ .quota small{display:block;color:var(--text-secondary);font-size:var(--fs-caption);line-height:var(--lh-caption)} .quota .q2{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-5)} +.quota .q2 .bar{margin:6px 0 0} +.quota .big.warn{color:var(--warn-text)} .quota p{margin:0;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} .quota p.warn{color:var(--warn-text)} /* 关键日期 */ .dates{list-style:none;margin:0;padding:0} -.dates li{display:grid;grid-template-columns:88px minmax(0,1fr) auto;gap:var(--sp-3);align-items:baseline;padding:var(--sp-2) 0;border-top:1px solid var(--border-subtle)} +.dates li{display:grid;grid-template-columns:88px minmax(0,1fr) auto;gap:var(--sp-3);align-items:baseline;padding:var(--sp-2) 0;border-top:var(--line) solid var(--border-subtle)} .dates li:first-child{border-top:0} -.dates li > .fig{font-weight:var(--fw-medium);font-size:15px} +.dates li > .fig{font-weight:var(--fw-medium);font-size:var(--fs-figure-sm)} .dates li.past{color:var(--text-secondary)} .dates li.key > span:nth-child(2) > b{font-weight:var(--fw-bold)} .dates .left{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} @@ -454,71 +550,151 @@ select.input{appearance:none;padding-right:28px;cursor:pointer} .toolbar .pop .select{width:auto;min-width:132px} .filters-on{display:flex;gap:var(--sp-2);align-items:center;flex-wrap:wrap;margin:calc(-1 * var(--sp-1)) 0 var(--sp-3);font-size:var(--fs-caption);color:var(--text-secondary)} table.data th.sortable{padding:0} -.th-sort{display:flex;align-items:center;gap:4px;width:100%;padding:var(--sp-2) var(--sp-3);border:0;background:none;font:inherit;color:inherit;cursor:pointer;text-align:left;white-space:nowrap} +.th-sort{display:flex;align-items:center;gap:4px;width:100%;padding:var(--sp-3) var(--sp-3) var(--sp-2);border:0;background:none;font:inherit;color:inherit;cursor:pointer;text-align:left;white-space:nowrap} +table.data th.sortable:first-child .th-sort{padding-left:var(--card-x)} +table.data th.sortable:last-child .th-sort{padding-right:var(--card-x)} .th-sort:hover{color:var(--text-primary)} .th-sort .ic{width:14px;height:14px;color:var(--text-disabled)} th[aria-sort] .th-sort{color:var(--text-primary)} th[aria-sort] .th-sort .ic{color:var(--text-primary)} table.data th.r .th-sort{justify-content:flex-end} table.data .ckc{width:40px;padding-right:0} -table.data.hover tbody tr{cursor:pointer} -table.data.hover tbody tr:hover td{background:var(--bg-hover)} -table.data tbody tr.sel td{background:var(--bg-sunken)} -table.data tbody tr.sel td:first-child{box-shadow:inset 2px 0 0 var(--c-ink)} table.data td.empty-cell{height:auto;padding:0} table.data td .cap{color:var(--text-secondary)} table.data .acts{white-space:nowrap;text-align:right} table.data td.mid{vertical-align:middle} -table.data tr.is-fatal td:first-child{box-shadow:inset 3px 0 0 var(--sev-fatal)} -.batchbar{position:sticky;top:calc(52px + var(--sp-2));z-index:10;display:flex;align-items:center;gap:var(--sp-3);height:44px;padding:0 var(--sp-4);margin-bottom:var(--sp-2);background:var(--bg-surface);border:1px solid var(--c-ink);border-radius:var(--radius-sm)} +.batchbar{position:sticky;top:calc(var(--topbar-h) + var(--sp-2));z-index:10;display:flex;align-items:center;gap:var(--sp-3);height:48px;padding:0 var(--sp-2) 0 var(--card-x);margin-bottom:var(--sp-2);background:var(--bg-surface);border:var(--line) solid var(--c-ink);border-radius:var(--radius-card)} .batchbar b .fig{font-weight:var(--fw-bold)} .batchbar .r{margin-left:auto;display:flex;gap:var(--sp-2);align-items:center} -.list-box{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-sm)} -.list-box > .lb-h{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-1) var(--sp-4);border-bottom:1px solid var(--border-subtle);background:var(--bg-sunken);font-size:var(--fs-caption);color:var(--text-secondary)} +.list-box{background:var(--bg-surface);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-card);overflow:hidden} +.list-box > .lb-h{display:flex;align-items:center;gap:var(--sp-3);min-height:40px;padding:var(--sp-1) var(--card-x);border-bottom:var(--line) solid var(--border-subtle);font-size:var(--fs-caption);color:var(--text-secondary)} .list-box > .lb-h .r{margin-left:auto;display:flex;gap:var(--sp-2);align-items:center} -.list-box .item{padding:var(--sp-3) var(--sp-4);border-top:1px solid var(--border-subtle)} +.list-box .item{padding:var(--sp-3) var(--card-x);border-top:var(--line) solid var(--border-subtle)} .list-box .item:first-of-type{border-top:0} .list-box .item.has-ck{grid-template-columns:20px 24px minmax(0,1fr) auto} .list-box .item .ck{margin-top:3px} -.item.sel{background:var(--ai-soft);box-shadow:inset 3px 0 0 var(--ai)} -.item.focus{box-shadow:inset 0 0 0 2px var(--c-ink)} -.item[tabindex]{cursor:pointer;outline:none} -.item[tabindex]:hover{background:var(--bg-hover)} -.item.sel:hover{background:var(--ai-soft)} +/* 当前条与勾选条(§7.18,v0.2 收尾): + * 当前条(键盘 J / K、点击定位到的那一条)= --c-n50 底 + 左侧 3px 墨色竖条:.item.sel / .finding.sel / tr.sel; + * 键盘操作时再加 2px 墨色内框:条目自身 :focus-visible、加 .focus,或列表容器 .kb-on / :focus-visible; + * 勾选条(批量处理的复选框)只有复选框是选中状态,不加底色和竖条; + * 致命行不加红竖线,竖条只表示「当前」,和级别无关。 */ +.finding.sel{background:var(--c-n50);box-shadow:inset 3px 0 0 var(--c-ink)} +.finding{transition:background-color var(--dur-fast) var(--ease)} +:is(.item,.finding).focus,.item[tabindex]:focus-visible{box-shadow:inset 0 0 0 2px var(--focus-ring)} +:is(.item,.finding).sel.focus,.item.sel[tabindex]:focus-visible,:is(.kb-on,:focus-visible) > :is(.item,.finding).sel{box-shadow:inset 3px 0 0 var(--c-ink),inset 0 0 0 2px var(--focus-ring)} +table.data tbody tr.sel.focus td{box-shadow:inset 0 2px 0 var(--focus-ring),inset 0 -2px 0 var(--focus-ring)} +.item[tabindex]{cursor:pointer;outline:none;transition:background-color var(--dur-fast) var(--ease)} +.item[tabindex]:hover{background:var(--c-n50)} .item .upd{margin-top:6px;display:flex;gap:var(--sp-2);align-items:center;font-size:var(--fs-caption)} -.item .upd .btn{height:24px;font-size:var(--fs-caption)} +.item .upd .btn{height:24px;padding:0 var(--sp-2);font-size:var(--fs-caption);border-radius:var(--radius-tag)} -/* 问题行的紧凑版本(列表页) */ -.finding .ckw{position:absolute;left:var(--sp-2);top:calc(var(--sp-5) + 2px)} -.finding.has-ck{padding-left:var(--sp-8)} -.finding.focus{box-shadow:inset 0 0 0 2px var(--c-ink)} -.u4{margin-top:var(--sp-3);padding:var(--sp-3) var(--sp-4);background:var(--bg-sunken);border-radius:var(--radius-sm)} -.u4 .field{max-width:none} -.u4 .row{margin-top:var(--sp-2)} -.group-h{display:flex;align-items:center;gap:var(--sp-2);width:100%;padding:var(--sp-3) var(--sp-6);border:0;border-top:1px solid var(--border-subtle);background:var(--bg-sunken);color:var(--text-secondary);font:var(--fs-body) var(--font-sans);cursor:pointer;text-align:left} -.group-h:hover{color:var(--text-primary)} +/* 问题行带复选框(列表页批量处理只给「提示」) */ +.finding .ckw{position:absolute;left:14px;top:calc(var(--sp-5) + 2px)} +.finding.has-ck{padding-left:calc(var(--card-x) + 24px)} +/* 行内说明(§7.4、U4):忽略原因、致命问题「确认无误」的说明,在问题行内展开,替换处理按钮;无底色,不在卡片里再套面板 */ +.u4{margin-top:var(--sp-3)} +.u4 .field{max-width:560px} +.u4 .chips .btn{height:26px} +.u4 .row{margin-top:var(--sp-3);gap:var(--sp-2)} +.group-h{display:flex;align-items:center;gap:var(--sp-2);width:100%;padding:var(--sp-3) var(--card-x);border:0;border-top:var(--line) solid var(--border-subtle);background:none;color:var(--text-secondary);font:var(--fs-body) var(--font-sans);cursor:pointer;text-align:left;transition:background-color var(--dur-fast) var(--ease)} +.group-h:hover{background:var(--c-n50);color:var(--text-primary)} .verdict.hint{color:var(--sev-hint)} -/* ---- B19 对照布局:分类导航 + 列表 + 原文查看器(§5.3、§7.8) ---- */ -.split{display:grid;grid-template-columns:200px minmax(0,1fr) minmax(420px,45%);flex:1;min-height:0} -.cats{border-right:1px solid var(--border-subtle);padding:var(--sp-4) var(--sp-3);overflow:auto} -.cats button{display:flex;width:100%;align-items:center;justify-content:space-between;height:34px;padding:0 var(--sp-3);border:0;background:none;border-radius:var(--radius-sm);font:var(--fs-body) var(--font-sans);color:var(--text-primary);cursor:pointer;text-align:left} -.cats button:hover{background:var(--bg-hover)} -.cats button[aria-current="true"]{background:var(--bg-surface);box-shadow:inset 2px 0 0 var(--c-ink);font-weight:var(--fw-bold)} -.cats button .fig{font-weight:var(--fw-medium);color:var(--text-secondary)} +/* ---- B19 对照布局:分类导航 + 列表 + 原文查看器(§5.3、§7.8;v0.2 收尾,原 .split) ---- + *

+ *
…
+ * + *
…
+ *
+ * 尺寸:分类导航 176(1400 以下 152);列表最小 560;查看器默认 45%,最小 400,拖动或 ← → 调整(Demo.cmp)。 + * 1280 宽时:列表 ≥ 560、查看器 ≤ 560,不横向滚动。整屏布局:.cmp 撑满剩余高度,三栏各自滚动。 */ +.cmp{--cats-w:176px;--list-min:560px;--viewer-min:400px;flex:1;min-height:0;display:grid;grid-template-columns:minmax(var(--list-min),1fr) var(--gap-card) var(--vw,45%);padding:0 var(--pad-page) var(--sp-4)} +@media (max-width:1399px){.cmp{--cats-w:152px}} +.cmp-list{display:grid;grid-template-columns:var(--cats-w) minmax(0,1fr);min-height:0;overflow:hidden} +.cmp-list > .lst{position:relative;min-height:0;overflow:auto} +.cmp > .viewer{min-width:0} +/* 拖动条:平时不可见,悬停 / 拖动时出一条浅灰细线,键盘聚焦时墨色 */ +.rsz{position:relative;cursor:col-resize;outline:0;touch-action:none} +.rsz::after{content:"";position:absolute;top:var(--sp-3);bottom:var(--sp-3);left:50%;width:2px;margin-left:-1px;border-radius:1px;background:transparent;transition:background-color var(--dur-fast) var(--ease)} +.rsz:hover::after,.rsz.on::after{background:var(--c-n300)} +.rsz:focus-visible::after{background:var(--focus-ring)} +body.dragging{cursor:col-resize;user-select:none} +/* 分类导航:在列表卡片左侧;当前分类 = --c-n100 实底 + 加粗(导航的「当前」,不是列表项的「当前条」) */ +.cats{display:flex;flex-direction:column;gap:2px;border-right:var(--line) solid var(--border-subtle);padding:var(--sp-3) var(--sp-2);overflow:auto} +.cats button{display:flex;flex:none;width:100%;align-items:center;justify-content:space-between;height:var(--control-h-sm);padding:0 10px;border:0;background:none;border-radius:var(--radius-ctl);font:var(--fs-body) var(--font-sans);color:var(--text-primary);cursor:pointer;text-align:left;transition:background-color var(--dur-fast) var(--ease)} +.cats button:hover{background:var(--c-n100)} +/* 当前分类:和筛选一样用 --c-n100 实底 + 加粗(导航的「当前」,不是列表项的「选中」) */ +.cats button[aria-current="true"]{background:var(--c-n100);font-weight:var(--fw-bold)} +.cats button .fig{font-size:var(--fs-figure-sm);font-weight:var(--fw-medium);color:var(--text-secondary)} .cats button.ai,.cats button.ai .fig{color:var(--ai)} -.cats hr{border:0;border-top:1px solid var(--border-subtle);margin:var(--sp-3) 0} -.cats .hint{font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary);padding:0 var(--sp-3);margin:0} +.cats hr{width:calc(100% - 20px);border:0;border-top:var(--line) solid var(--border-subtle);margin:var(--sp-2) 10px} +.cats .hint{margin:auto 0 0;padding:var(--sp-3) 10px 0;font-size:var(--fs-caption);line-height:26px;color:var(--text-secondary)} +.cats .hint .kbd{margin-right:2px} +/* 列表里的分组标题;.ai 是清单顶部的「待确认」分组(§7.6 v0.2:蓝色标题,不再用虚线框) */ +.grp-h{display:flex;align-items:baseline;gap:var(--sp-2);padding:var(--sp-5) var(--card-x) var(--sp-2)} +.grp-h b{font-weight:var(--fw-bold)} +.grp-h .fig{font-weight:var(--fw-medium);color:var(--text-secondary)} +.grp-h.ai b,.grp-h.ai .fig{color:var(--ai)} +.grp-h p{margin:0;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} .viewer{min-height:0} -.viewer-bar{flex-wrap:wrap;min-height:44px} -.viewer-bar .doc-name{display:flex;gap:var(--sp-2);align-items:baseline;min-width:0} +.viewer-bar{flex-wrap:nowrap;min-height:48px} +.viewer-bar .doc-name{flex:1;display:flex;gap:var(--sp-2);align-items:baseline;min-width:0} +.viewer-bar .pager{gap:var(--sp-1)} +.viewer-bar .pager > .fig{margin-right:var(--sp-2);font-size:var(--fs-figure-xs);color:var(--text-secondary)} .viewer-bar .doc-file{color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px} .viewer-body{flex:1;overflow:auto;padding:var(--sp-4) 0} -.viewer .sheet{min-height:560px;margin:0 auto;width:calc(100% - 48px);max-width:640px;position:relative;background:var(--c-white);color:var(--text-primary)} +.viewer .sheet{min-height:560px;margin:0 auto;width:calc(100% - 48px);max-width:640px;position:relative;background:var(--bg-surface);color:var(--text-primary)} .sheet .pno{position:absolute;bottom:14px;left:0;right:0;text-align:center;font:var(--fs-caption) var(--font-figures);color:var(--text-secondary)} .sheet .h{font-weight:var(--fw-bold);margin:4px 0 10px} .sheet .faint{color:var(--text-secondary)} -.sheet .stub{height:9px;background:var(--bg-sunken);margin:12px 0;border-radius:1px} +.sheet .stub{height:9px;background:var(--c-n100);margin:12px 0;border-radius:var(--radius-xs)} .sheet .stub.s{width:60%} .viewer-note{font-size:var(--fs-caption);color:var(--text-secondary);padding:var(--sp-2) var(--sp-6) 0;margin:0;text-align:center} + +/* ---- B20 项目行(§7.9,v0.2 收尾:工作台和列表页共用) ---- + * 栅格对齐的列表(不是 table),整行可点: + *
项目 · 投标截止 · 状态 · 致命 · 下一步 · (空)
+ * + * 规则:「下一步」表头和内容左对齐,行悬停 / 聚焦时箭头右移 3px;「更多」单独一列常驻,平时辅助灰,行悬停变墨色;「致命」列居中。 */ +:root{--prow-cols:minmax(0,1fr) 120px 120px 64px 140px var(--control-h-sm);--prow-gap:var(--sp-4)} +@media (max-width:1399px){:root{--prow-cols:minmax(0,1fr) 108px 108px 56px 132px var(--control-h-sm);--prow-gap:var(--sp-3)}} +.prow-head,.prow{display:grid;grid-template-columns:var(--prow-cols);column-gap:var(--prow-gap);align-items:center;padding:0 var(--card-x)} +.prow-head{padding-bottom:var(--sp-2);font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} +:is(.prow-head,.prow) > .c{justify-self:center;text-align:center} +.prows{list-style:none;margin:0;padding:0} +.prows > .empty{border-top:var(--line) solid var(--border-subtle)} +.prow{position:relative;min-height:var(--row-h);padding-top:10px;padding-bottom:10px;border-top:var(--line) solid var(--border-subtle);cursor:pointer;transition:background-color var(--dur-fast) var(--ease)} +.prow:hover,.prow:focus-within{background:var(--bg-hover)} +.prow:last-child{border-radius:0 0 var(--radius-card) var(--radius-card)} +.prow:has(.pname:focus-visible){outline:2px solid var(--focus-ring);outline-offset:-2px} +.prow .pt{min-width:0} +.pname{display:block;font-weight:var(--fw-bold);color:var(--text-primary);text-decoration:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;outline:0} +.prow .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} +.prow .dl b{display:block;font-weight:var(--fw-regular);white-space:nowrap} +.prow .dl b.due-today{font-weight:var(--fw-bold)} +.prow .dl small{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary);white-space:nowrap} +.prow .st{display:flex;align-items:center;gap:var(--sp-2);white-space:nowrap} +.prow .st .hint-u{position:relative;z-index:1} +.prow.arch .pname,.prow.arch .dl b{color:var(--text-secondary);font-weight:var(--fw-regular)} +.dash{color:var(--text-secondary)} +.nx{position:relative;z-index:1;display:inline-flex;align-items:center;gap:var(--sp-1);height:var(--control-h-sm);padding:0;border:0;background:none;font:inherit;color:var(--text-primary);white-space:nowrap;cursor:pointer} +.nx span{text-decoration:underline;text-decoration-color:var(--link-underline);text-underline-offset:3px;transition:text-decoration-color var(--dur-fast) var(--ease)} +.nx:hover span{text-decoration-color:var(--c-ink)} +.nx .ic{color:var(--text-secondary);transition:transform var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)} +.prow:hover .nx .ic,.prow:focus-within .nx .ic{transform:translateX(3px);color:var(--text-primary)} +.prow .more-cell{justify-self:end} +.prow .icon-btn{position:relative;z-index:1;color:var(--text-secondary)} +.prow:hover .icon-btn,.prow:focus-within .icon-btn,.prow .icon-btn[aria-expanded="true"]{color:var(--text-primary)} +/* 可排序的列名(栅格表头版的 .th-sort) */ +.prow-head .srt{display:inline-flex;align-items:center;gap:4px;padding:0;border:0;background:none;font:inherit;color:inherit;cursor:pointer;white-space:nowrap} +.prow-head .srt:hover,.prow-head .srt[data-dir]{color:var(--text-primary)} +.prow-head .srt .ic{width:14px;height:14px;color:var(--text-disabled)} +.prow-head .srt[data-dir] .ic{color:var(--text-primary)} diff --git a/docs/design/ui-demo/demo.js b/docs/design/ui-demo/demo.js index 028aa55..4d4adec 100644 --- a/docs/design/ui-demo/demo.js +++ b/docs/design/ui-demo/demo.js @@ -1,5 +1,8 @@ -/* 标博士 · UI demo 公共脚本:图标、页面框架、轻提示、对话框、下拉、日期、开关、标签页、排序。 - * 只演示交互,不连后端。正式实现里这些交互件由 Reka UI 打底(AR10)。 */ +/* 标博士 · UI demo 公共脚本 v0.2:图标、页面框架、轻提示、对话框、下拉、浮动菜单、日期、开关、标签页、排序。 + * 只演示交互,不连后端。正式实现里这些交互件由 Reka UI 打底(AR10)。 + * v0.2:Demo.shell() 生成定稿样张 explore/workbench-d5.html 的框架(96px 墨色渐变侧栏 + 透明顶栏 + 内容区圆角)。 + * 收尾(2026-11-03):日期输入 Demo.dateInput()(.dt[data-dt] 自动初始化)、字段错误 Demo.fieldError()、 + * 浮动菜单支持禁用项、快捷键说明按页面传入(Demo.KEYS)、对照布局拖动宽度 Demo.cmp()(.cmp[data-cmp] 自动初始化)。 */ (function () { const $ = (s, el = document) => el.querySelector(s); const $$ = (s, el = document) => [...el.querySelectorAll(s)]; @@ -41,7 +44,14 @@ paperclip: '', book: '', archive: '', - layers: '' + layers: '', + house: '', + award: '', + wallet: '', + "alert-circle": '', + info: '', + "check-circle": '', + eye: '' }; const icon = (n, cls = "") => ``; function hydrate(root = document) { @@ -61,35 +71,116 @@ `; } - /* ---------- 产品页面框架(组合页) ---------- */ - function shell(opt) { - const nav = [["page-workbench.html", "grid", "工作台", "workbench"], ["#", "cert", "资质库", "quals"], ["#", "gauge", "额度", "quota"]] - .map(([h, i, t, k]) => `${icon(i, "lg")}${t}`).join(""); - const crumbs = (opt.crumbs || []).map((c, i, a) => i < a.length - 1 ? `${c[0]}/` : `${c[0]}`).join(""); + /* ---------- 产品页面框架 v0.2(组合页) ---------- + * opt.nav:当前菜单 workbench / quals / quota + * opt.crumbs:[[文字, 链接], …],最后一项是当前页;顶栏左侧显示「公司 / 页面」 + * opt.search:{ placeholder, onInput(q) };不传 onInput 时回车只出演示提示。Ctrl K 聚焦 + * opt.quota:{ parse: [剩余, 总数], check: [剩余, 总数] };URL 带 ?quota=warn / ?quota=out 可预览 80% 和用完(Q9) + * opt.plan:free / pro / ent(也可用 ?plan=) */ + const ORG = "南京某信息技术有限公司"; + function shell(opt = {}) { + const qs = new URLSearchParams(location.search); + const nav = [["page-workbench.html", "house", "工作台", "workbench"], ["#", "award", "资质库", "quals"], ["#", "wallet", "额度", "quota"]] + .map(([h, i, t, k]) => `${icon(i)}${t}`).join(""); + const crumbs = (opt.crumbs || []).map((c, i, a) => i < a.length - 1 && c[1] ? `${esc(c[0])}` : `${esc(c[0])}${i < a.length - 1 ? '' : ""}`).join(""); + const PLAN = { free: "免费版", pro: "专业版", ent: "企业版" }; + const plan = PLAN[qs.get("plan")] ? qs.get("plan") : (opt.plan || "pro"); + const quota = Object.assign({ parse: [37, 50], check: [12, 20] }, opt.quota || {}); + if (qs.get("quota") === "warn") quota.check = [3, 20]; + if (qs.get("quota") === "out") quota.check = [0, 20]; + const qItem = (name, [left, total]) => `${name}${left}`; + const out = [["解析", quota.parse], ["检查", quota.check]].filter(([, [l]]) => l <= 0).map(([n]) => n); + const qTip = `本期剩余额度:解析 ${quota.parse[0]} / ${quota.parse[1]} 次,检查 ${quota.check[0]} / ${quota.check[1]} 次` + + (out.length ? `。本期${out.join("、")}额度已用完,可在额度页提交续费申请,或等下期发放` : ""); + document.body.classList.add("app"); document.body.insertAdjacentHTML("afterbegin", ``); + + +
王敏${PLAN[plan]}
`); const main = $("main"); main.classList.add("main"); - main.insertAdjacentHTML("afterbegin", `
${crumbs}
+ main.insertAdjacentHTML("afterbegin", `
+
${ORG}${crumbs}
+ +
- 解析37检查12 - - + ${qItem("解析", quota.parse)}${qItem("检查", quota.check)} +
`); + const top = $(".top", main), gq = $("[data-gq]", main); + const onScroll = () => top.classList.toggle("scrolled", (main.scrollTop || window.scrollY) > 4); + addEventListener("scroll", onScroll, { passive: true }); main.addEventListener("scroll", onScroll, { passive: true }); onScroll(); + $("[data-gsearch]", main).addEventListener("submit", (e) => { e.preventDefault(); if (!(opt.search && opt.search.onInput)) toast("(演示)全局搜索:这里只做了样式", { ok: false }); }); + if (opt.search && opt.search.onInput) gq.addEventListener("input", () => opt.search.onInput(gq.value.trim())); + document.addEventListener("keydown", (e) => { + if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") { e.preventDefault(); gq.focus(); } + else if (e.key === "Escape" && document.activeElement === gq) gq.blur(); + }); + document.addEventListener("click", (e) => { if (e.target.closest("[data-demo-nav]")) { e.preventDefault(); toast("(演示)这一页没有做", { ok: false }); } }); $("[data-notice]").onclick = () => toast("解析完成:补遗 1 已并入清单(新增 2 条,更新 1 条)。"); - $("[data-keys]").onclick = keysDialog; } - function keysDialog() { - dialog({ title: "快捷键", cancel: null, ok: "知道了", - body: ` - - - - -
J / K下一条 / 上一条
O 或 Enter在原文查看器里打开当前出处
1 2 3确认无误 / 已修改 / 忽略(3 会打开原因输入框)
U撤销当前条的处理
?显示本说明
` }); + + /* ---------- 浮动菜单:列表行的「更多」等(role=menu,↑ ↓ 移动,Esc 关闭,焦点回到触发按钮) ---------- + * items:[{ label, sub, act, disabled }] 或 "-"(分隔线) + * sub:第二行说明,可以是 HTML(如扣点说明); + * disabled:不可用的原因(如「递交后可用」),该项变禁用灰、右侧写原因,点了不触发; + * onPick(act, item)。Demo.menuHTML(items) 生成同样的菜单项,供陈列页静态展示。 */ + function menuHTML(items) { + return items.map((it, i) => it === "-" ? "
" : ``).join(""); + } + let fm = null; + function closeFloatMenu(focusBack) { + if (!fm) return; + fm.el.remove(); fm.btn.setAttribute("aria-expanded", "false"); + if (focusBack) fm.btn.focus(); + fm = null; + } + function floatMenu(btn, items, onPick) { + if (fm && fm.btn === btn) return closeFloatMenu(); + closeFloatMenu(); + const el = document.createElement("div"); + el.className = "menu float"; el.setAttribute("role", "menu"); + el.innerHTML = menuHTML(items); + document.body.appendChild(el); + const r = btn.getBoundingClientRect(); + const below = r.bottom + 4 + el.offsetHeight < innerHeight; + el.style.top = `${below ? r.bottom + 4 : r.top - 4 - el.offsetHeight}px`; + el.style.left = `${Math.max(8, r.right - el.offsetWidth)}px`; + btn.setAttribute("aria-expanded", "true"); + fm = { el, btn }; + el.addEventListener("click", (e) => { const b = e.target.closest("[data-i]"); if (!b) return; e.stopPropagation(); const it = items[+b.dataset.i]; if (it.disabled) return; closeFloatMenu(true); if (onPick) onPick(it.act || it.label, it); }); + el.addEventListener("keydown", (e) => { + const its = $$("[role=menuitem]", el), 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(); } + if (e.key === "Escape") { e.stopPropagation(); closeFloatMenu(true); } + if (e.key === "Tab") closeFloatMenu(); + }); + $("[role=menuitem]", el).focus(); + } + document.addEventListener("click", (e) => { if (fm && !fm.el.contains(e.target) && !fm.btn.contains(e.target)) closeFloatMenu(); }); + addEventListener("scroll", () => closeFloatMenu(), { passive: true, capture: true }); + addEventListener("resize", () => closeFloatMenu()); + + /* ---------- 快捷键说明(§8.2):按页面传入,只列本页能用的键 ---------- + * Demo.keysDialog(Demo.KEYS.reading) 读标:J / K、Enter、? + * Demo.keysDialog(Demo.KEYS.check) 查标(问题列表):J / K、O / Enter、1 / 2 / 3、X、U、? + * 也可以传自己的 [[键的 HTML, 说明], …];Demo.keysTable(list) 生成同样的表格供静态展示。 */ + const kb = (k) => `${k}`; + const KEY = { + jk: [`${kb("J")} / ${kb("K")}`, "下一条 / 上一条"], + enter: [kb("Enter"), "在原文查看器里打开当前条的出处"], + open: [`${kb("O")} 或 ${kb("Enter")}`, "在原文查看器里打开当前出处"], + act: [`${kb("1")} ${kb("2")} ${kb("3")}`, "确认无误 / 已修改 / 忽略(忽略、致命的确认无误在行内写说明)"], + x: [kb("X"), "勾选当前条(只限「提示」)"], + undo: [kb("U"), "撤销刚才的处理"], + help: [kb("?"), "显示本说明"] + }; + const KEYS = { reading: [KEY.jk, KEY.enter, KEY.help], check: [KEY.jk, KEY.open, KEY.act, KEY.x, KEY.undo, KEY.help] }; + const keysTable = (list) => `${list.map(([k, t]) => ``).join("")}
${k}${t}
`; + function keysDialog(list = KEYS.check) { + dialog({ title: "快捷键", cancel: null, ok: "知道了", body: keysTable(list) }); } /* ---------- 轻提示:4 秒消失,悬停暂停 ---------- */ @@ -119,7 +210,12 @@ document.body.style.overflow = "hidden"; const close = () => { s.remove(); document.body.style.overflow = ""; document.removeEventListener("keydown", onKey, true); if (back && back.focus) back.focus(); }; const onKey = (e) => { - if (e.key === "Escape") { e.stopPropagation(); close(); } + if (e.key === "Escape") { + e.stopPropagation(); + const op = $(".pop.open", s); // 先收起对话框里展开的下拉 / 日期选择器 + if (op) { closePops(); ($("input, .select", op) || op).focus(); return; } + close(); + } if (e.key === "Tab") { // 焦点锁定在对话框内 const f = $$("button:not([disabled]), input:not([disabled]), textarea, select, a[href]", s); if (!f.length) return; @@ -172,54 +268,199 @@ menu.addEventListener("click", (e) => { const o = e.target.closest('[role=option], [role=menuitem]'); if (o && o.getAttribute("aria-disabled") !== "true") { e.stopPropagation(); pick(o); } }); } - /* ---------- 日期选择 ---------- */ + /* ---------- 日期选择(§7.22) ---------- + * 周一开始;非本月的格子留空;不可选日期(opt.min 之前)禁用灰、无悬停;今天描边;关键日期(opt.marks)下方小圆点。 + * 日期键统一写成不补零的「2026-11-3」。 */ const WD = ["一", "二", "三", "四", "五", "六", "日"]; + const TODAY = new Date(2026, 10, 3), TODAY_KEY = "2026-11-3"; // 演示里的「今天」 + const pad2 = (n) => String(n).padStart(2, "0"); + const fig = (v) => `${v}`; + const keyOf = (d) => `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`; function calHTML(y, m, sel, opt = {}) { - const first = new Date(y, m, 1), start = (first.getDay() + 6) % 7, days = new Date(y, m + 1, 0).getDate(); - const prevDays = new Date(y, m, 0).getDate(); + const start = (new Date(y, m, 1).getDay() + 6) % 7, days = new Date(y, m + 1, 0).getDate(); let cells = ""; - for (let i = 0; i < 42; i++) { + for (let i = 0, n = Math.ceil((start + days) / 7) * 7; i < n; i++) { const d = i - start + 1; - let dd, out = false, mm = m; - if (d < 1) { dd = prevDays + d; out = true; mm = m - 1; } else if (d > days) { dd = d - days; out = true; mm = m + 1; } else dd = d; - const date = new Date(y, mm, dd), key = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; - const cls = [out ? "out" : "", key === opt.today ? "today" : "", (opt.marks || []).includes(key) ? "mark" : ""].join(" "); - cells += ``; - if (i === 34 && d >= days) break; + if (d < 1 || d > days) { cells += ''; continue; } + const date = new Date(y, m, d), key = keyOf(date); + const cls = [key === (opt.today || TODAY_KEY) ? "today" : "", (opt.marks || []).includes(key) ? "mark" : ""].join(" ").trim(); + cells += ``; } + const note = opt.note !== undefined ? opt.note : (opt.marks || []).length ? "圆点是本项目的关键日期" : ""; return `
${y} 年 ${m + 1} 月
${WD.map((w) => `${w}`).join("")}${cells}
-
${opt.time ? `时间 ` : '点圆点的日期是本项目的关键日期'}
`; +
${opt.time ? `时间 ` : `${note}`}
`; } + /* 只有日历、没有输入框的陈列(.pop.d-open[data-date]) */ function initDate(p) { if (p.dataset.ready) return; p.dataset.ready = 1; - const trg = $(".select", p), cal = $(".cal", p); - let [y, m] = (p.dataset.month || "2026-11").split("-").map(Number); m -= 1; - let sel = p.dataset.value || ""; - const opt = { today: "2026-11-3", marks: (p.dataset.marks || "").split(",").filter(Boolean), time: p.dataset.time, min: p.dataset.min ? new Date(...p.dataset.min.split("-").map((v, i) => i === 1 ? v - 1 : +v)) : null }; + const cal = $(".cal", p); + let [y, m] = (p.dataset.month || p.dataset.value || "2026-11").split("-").map(Number); m -= 1; + let sel = p.dataset.value ? keyOf(parseDT(p.dataset.value, false).dt) : ""; + const opt = { marks: (p.dataset.marks || "").split(",").filter(Boolean).map((k) => keyOf(parseDT(k, false).dt)), time: p.dataset.time, min: p.dataset.min ? parseDT(p.dataset.min, false).dt : null }; const render = () => { cal.innerHTML = calHTML(y, m, sel, opt); }; - const fmt = (k) => { const [a, b, c] = k.split("-"); return `${a}-${String(b).padStart(2, "0")}-${String(c).padStart(2, "0")}`; }; render(); - if (!p.classList.contains("d-open")) cal.hidden = true; - trg?.addEventListener("click", (e) => { e.stopPropagation(); const o = !p.classList.contains("open"); closePops(p); p.classList.toggle("open", o); cal.hidden = !o; trg.setAttribute("aria-expanded", o); }); cal.addEventListener("click", (e) => { e.stopPropagation(); const b = e.target.closest("button"); if (!b) return; if (b.dataset.cm) { m += +b.dataset.cm; if (m < 0) { m = 11; y--; } if (m > 11) { m = 0; y++; } render(); return; } - if (b.dataset.today) { y = 2026; m = 10; sel = opt.today; } + if (b.dataset.today) { y = 2026; m = 10; sel = TODAY_KEY; } else if (b.dataset.d) sel = b.dataset.d; else return; render(); - if (trg) { $(".val", trg).innerHTML = fmt(sel); p.closest(".field")?.classList.remove("err"); if (!p.classList.contains("d-open")) { p.classList.remove("open"); cal.hidden = true; trg.setAttribute("aria-expanded", "false"); trg.focus(); } } - p.dispatchEvent(new CustomEvent("change", { detail: fmt(sel) })); }); } + /* ---------- 日期输入(Q10):.dt[data-dt] ---------- + *
+ *
+ * + * + *
+ * 24 小时制
+ * data-time:带时间(24 小时制,选择器底部填时间);data-left:下方写「· 剩 N 天」;data-req:必填,空着时的错误说明; + * data-marks / data-min / data-note:关键日期圆点 / 最早可选日期 / 选择器底部的圆点说明;类名加 d-open:选择器常开(陈列用)。 + * 可直接键入,接受「2026/11/20 9:30」「2026.11.20 9:30」「2026年11月20日」等写法,离开时整理成「2026-11-20 09:30」, + * 下方 [data-dt-msg] 显示界面写法「11月20日 09:30 · 剩 17 天」。没碰过的字段离开时不报错;报错之后边输入边复查。 + * 每次整理后在 .dt 上派发 dt-change(detail:{ ok, empty, val, dt, days });p.dt.commit({ required }) 供提交时调用。 */ + function parseDT(raw, withTime) { + const s = String(raw || "").trim().replace(/[\/.年月]/g, "-").replace(/日/g, "").replace(/:/g, ":").replace(/\s+/g, " "); + const eg = withTime ? "2026-11-20 09:30" : "2026-11-20"; + const m = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?: ?(\d{1,2}):(\d{1,2}))?$/); + if (!m) return { msg: `请按「${eg}」的格式填写` }; + const [, y, mo, d, hh, mi] = m.map((v) => v === undefined ? v : +v); + const dt = new Date(y, mo - 1, d); + if (dt.getMonth() !== mo - 1 || dt.getDate() !== d) return { msg: `${y} 年 ${mo} 月没有 ${d} 日,请核对日期` }; + if (withTime && hh === undefined) return { msg: `请补上时间,例如「${y}-${pad2(mo)}-${pad2(d)} 09:30」` }; + if (withTime && hh > 23) return { msg: "时间用 24 小时制,小时在 00–23 之间" }; + if (withTime && mi > 59) return { msg: "分钟在 00–59 之间" }; + const time = withTime ? `${pad2(hh)}:${pad2(mi)}` : ""; + const val = `${y}-${pad2(mo)}-${pad2(d)}` + (withTime ? ` ${time}` : ""); + const days = Math.round((dt - TODAY) / 864e5); + const disp = `${y !== TODAY.getFullYear() ? fig(y) + "年" : ""}${fig(mo)}月${fig(d)}日` + (withTime ? ` ${fig(time)}` : ""); + return { val, dt, days, disp, key: keyOf(dt), time }; + } + /* 字段错误(§7.13):赭色边框 + 下方「!」说明;其他说明和提醒暂时隐藏。msg 为空时清除。返回是否无错 */ + let uid = 0; + function fieldError(f, msg) { + if (!f) return !msg; + f.classList.toggle("err", !!msg); + const inp = $("input:not([type=checkbox]):not([type=radio]), textarea", f); if (inp) inp.toggleAttribute("aria-invalid", !!msg); + let m = $(":scope > .msg.e", f); + if (msg) { + if (!m) { m = document.createElement("span"); m.className = "msg e"; m.id = (f.id || "fld" + ++uid) + "-err"; f.appendChild(m); } + m.innerHTML = `${msg}`; + $$(":scope > .msg:not(.e), :scope > .hint-warn", f).forEach((x) => { x.hidden = true; }); + if (inp) inp.setAttribute("aria-describedby", m.id); + } else { + if (m) m.remove(); + $$(":scope > .msg, :scope > .hint-warn:not([data-off])", f).forEach((x) => { x.hidden = false; }); + const d = $(":scope > .msg[id]", f); if (inp) { if (d) inp.setAttribute("aria-describedby", d.id); else inp.removeAttribute("aria-describedby"); } + } + return !msg; + } + function dateInput(p) { + if (p.dataset.ready) return; + p.dataset.ready = 1; p.classList.add("pop"); + const inp = $("input", p), btn = $(":scope > .icon-btn", p), cal = $(".cal", p), f = p.closest(".field"); + const msg = f && $("[data-dt-msg]", f), def = msg ? msg.innerHTML : ""; + if (msg && !msg.id) msg.id = (inp.id || "dt" + ++uid) + "-msg"; + if (msg) inp.setAttribute("aria-describedby", msg.id); + const withTime = p.dataset.time !== undefined, still = p.classList.contains("d-open"); + const opt = { marks: (p.dataset.marks || "").split(",").filter(Boolean).map((k) => keyOf(parseDT(k, false).dt)), min: p.dataset.min ? parseDT(p.dataset.min, false).dt : null, note: p.dataset.note }; + let y = TODAY.getFullYear(), m = TODAY.getMonth(), sel = "", time = p.dataset.time || "09:00", dirty = false; + const left = (r) => p.dataset.left === undefined ? "" : r.days > 0 ? ` · 剩 ${fig(r.days)} 天` : r.days === 0 ? " · 今天截止" : " · 已截止"; + const sync = () => { const r = parseDT(inp.value, withTime); if (r.val) { sel = r.key; y = r.dt.getFullYear(); m = r.dt.getMonth(); if (r.time) time = r.time; } }; + const render = () => { cal.innerHTML = calHTML(y, m, sel, { ...opt, time: withTime ? time : undefined }); }; + function commit(o = {}) { + if (inp.disabled) { fieldError(f, ""); return true; } + const v = inp.value.trim(), r = v ? parseDT(v, withTime) : {}; + let err = ""; + if (!v) { err = o.required && p.dataset.req ? p.dataset.req : ""; if (msg) msg.innerHTML = def; } + else if (r.msg) err = r.msg; + else { inp.value = r.val; if (msg) msg.innerHTML = r.disp + left(r); } + fieldError(f, err); + p.dispatchEvent(new CustomEvent("dt-change", { bubbles: true, detail: { ok: !err, empty: !v, ...r } })); + return !err; + } + p.dt = { commit, parse: () => parseDT(inp.value, withTime) }; + const close = (focus) => { p.classList.remove("open", "up"); cal.hidden = true; btn.setAttribute("aria-expanded", "false"); if (focus) inp.focus(); }; + const open = () => { + closePops(p); closeFloatMenu(); + sync(); render(); p.classList.add("open"); cal.hidden = false; btn.setAttribute("aria-expanded", "true"); + const r = p.getBoundingClientRect(); + p.classList.toggle("up", r.bottom + cal.offsetHeight + 8 > innerHeight && r.top > cal.offsetHeight + 8); + ($("[aria-selected=true]", cal) || $(".today", cal) || $(".cal-grid button:not([disabled])", cal)).focus(); + }; + const write = () => { if (!sel) return; const [a, b, c] = sel.split("-"); inp.value = `${a}-${pad2(b)}-${pad2(c)}` + (withTime ? ` ${time}` : ""); dirty = true; commit(); }; + if (still) { sync(); render(); } else cal.hidden = true; + if (inp.value.trim()) commit(); + btn.addEventListener("click", (e) => { e.stopPropagation(); if (still) return; p.classList.contains("open") ? close(true) : open(); }); + cal.addEventListener("click", (e) => { + e.stopPropagation(); + const b = e.target.closest("button"); if (!b || b.disabled) return; + if (b.dataset.cm) { m += +b.dataset.cm; if (m < 0) { m = 11; y--; } if (m > 11) { m = 0; y++; } render(); return; } + if (b.dataset.today) { y = TODAY.getFullYear(); m = TODAY.getMonth(); sel = TODAY_KEY; } + else if (b.dataset.d) sel = b.dataset.d; else return; + render(); write(); + if (!still && !withTime) close(true); + else ($(`[data-d="${sel}"]`, cal) || b).focus(); + }); + cal.addEventListener("input", (e) => { + if (!e.target.classList.contains("time")) return; + const t = e.target.value.trim().replace(":", ":").match(/^(\d{1,2}):(\d{2})$/); + if (t && +t[1] < 24 && +t[2] < 60) { time = `${pad2(t[1])}:${t[2]}`; write(); } + }); + cal.addEventListener("keydown", (e) => { + if (e.key === "Escape" && !still) { e.stopPropagation(); close(true); return; } + if (e.key === "Enter" && e.target.classList.contains("time")) { e.preventDefault(); if (!still) close(true); return; } + const cells = $$(".cal-grid button:not([disabled])", cal), i = cells.indexOf(document.activeElement); + const d = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }[e.key]; + if (i >= 0 && d) { e.preventDefault(); (cells[i + d] || cells[i]).focus(); } + }); + inp.addEventListener("keydown", (e) => { if (e.key === "ArrowDown" && e.altKey) { e.preventDefault(); open(); } }); + inp.addEventListener("input", () => { + dirty = true; + if (f && f.classList.contains("err")) { const v = inp.value.trim(); if (v ? !parseDT(v, withTime).msg : !p.dataset.req) fieldError(f, ""); } + }); + inp.addEventListener("blur", (e) => { + if (e.relatedTarget && p.contains(e.relatedTarget)) return; // 焦点进了日期选择器 + if (dirty || (f && f.classList.contains("err"))) commit({ required: true }); + }); + } + + /* ---------- 对照布局(§5.3):拖动或用 ← → 调整原文查看器宽度;清单不窄于 --list-min,查看器不窄于 --viewer-min ---------- */ + function cmp(el) { + if (el.dataset.ready) return; + el.dataset.ready = 1; + const r = $(":scope > .rsz", el); if (!r) return; + const num = (v) => parseFloat(getComputedStyle(el).getPropertyValue(v)) || 0; + const box = () => { const b = el.getBoundingClientRect(), cs = getComputedStyle(el); return { right: b.right - parseFloat(cs.paddingRight), w: b.width - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight), gap: r.offsetWidth }; }; + const cur = () => $(":scope > .viewer", el).offsetWidth; + function setW(px) { + const { w, gap } = box(), max = w - gap - num("--list-min"); + px = Math.round(Math.max(num("--viewer-min"), Math.min(max, px))); + el.style.setProperty("--vw", px + "px"); + r.setAttribute("aria-valuenow", px); + } + r.setAttribute("aria-valuemin", num("--viewer-min")); + r.addEventListener("pointerdown", (e) => { + e.preventDefault(); r.setPointerCapture(e.pointerId); r.classList.add("on"); document.body.classList.add("dragging"); + const mv = (ev) => { const { right, gap } = box(); setW(right - ev.clientX - gap / 2); }; + const up = () => { r.classList.remove("on"); document.body.classList.remove("dragging"); r.removeEventListener("pointermove", mv); r.removeEventListener("pointerup", up); }; + r.addEventListener("pointermove", mv); r.addEventListener("pointerup", up); + }); + r.addEventListener("keydown", (e) => { if (e.key === "ArrowLeft" || e.key === "ArrowRight") { e.preventDefault(); setW(cur() + (e.key === "ArrowLeft" ? 32 : -32)); } }); + addEventListener("resize", () => { if (el.style.getPropertyValue("--vw")) setW(cur()); }); + } + /* ---------- 开关、分段、标签页、半选、排序、加载按钮 ---------- */ function init(root = document) { $$(".pop[data-select], .pop[data-menu]", root).forEach(initSelect); $$(".pop[data-date]", root).forEach(initDate); + $$(".dt[data-dt]", root).forEach(dateInput); + $$(".cmp[data-cmp]", root).forEach(cmp); $$(".switch:not([data-ready])", root).forEach((s) => { s.dataset.ready = 1; const upd = () => { const st = $(".st", s); if (st) st.textContent = s.getAttribute("aria-checked") === "true" ? "开" : "关"; }; @@ -270,6 +511,6 @@ }); } - window.Demo = { $, $$, esc, icon, hydrate, demoTop, shell, toast, dialog, init, sortable, keysDialog, calHTML }; + window.Demo = { $, $$, esc, icon, hydrate, demoTop, shell, toast, dialog, init, sortable, keysDialog, keysTable, KEYS, calHTML, parseDT, fieldError, dateInput, cmp, floatMenu, closeFloatMenu, menuHTML }; document.addEventListener("DOMContentLoaded", () => { hydrate(); init(); }); })(); diff --git a/docs/design/ui-demo/forms.html b/docs/design/ui-demo/forms.html index df54ee9..6ef316f 100644 --- a/docs/design/ui-demo/forms.html +++ b/docs/design/ui-demo/forms.html @@ -1,4 +1,12 @@ + @@ -10,11 +18,24 @@ @@ -22,50 +43,56 @@

② 表单

-

三张真实业务表单:新建项目、资质录入、处理问题时的「忽略并注明原因」。标签在上,必填写「(必填)」,错误用赭色边框 + 下方赭色小字 + 「!」图标,离开输入框时校验、提交时汇总。

- +

必填写「(必填)」,错误用赭色;离开输入框时校验,提交时汇总。日期输入「2026-11-20 09:30」,显示「11月20日 09:30」。

+
-
01

新建项目

规范 §7.13 · §8.3 · FR-M2-01
+
01

新建项目

§7.13 · §8.3 · §9.3 · Q10 · Q12
-
- + +

新建项目

+ +
-
- 项目信息招标文件解析后会自动补全,可以先只填名称 -
-
只在你的工作台里显示,可以用简称
-
-
-
-
- - - -
-
-
-
-
-
- 用于工作台的倒计时;补遗更正后会提示你核对
-
-
+
+ 项目信息解析后自动补全,可以先只填名称 +
+
可以用简称
+
+
+
+
+ + + +
+
+
+
+
+
+ + + +
+ 24 小时制
+
+
-
- 招标文件PDF、Word 或扫描件;补遗可以以后追加 -
-
拖入或点击上传招标文件单个文件不超过 200 MB · 扩展名不可信时以文件内容为准
-
-
-
-
- -
- 扣 1 次解析 · 剩 37 次 · 解析失败、超时自动退回 - +
+ 招标文件PDF、Word 或扫描件;补遗以后可以追加 +
招标文件 +
拖入或点击上传单个文件不超过 200 MB
+
+
+
+
+
+
+ 扣 1 次解析 · 剩 37 次失败不扣 +
@@ -74,19 +101,20 @@

试一试

    -
  1. 什么都不填,直接点「创建并开始解析」:顶部汇总 + 每个字段下方说明,点汇总里的链接跳到字段。
  2. -
  3. 在项目名称里输入再清空,离开输入框时就会校验。
  4. +
  5. 直接点「创建并开始解析」:顶部汇总,点汇总里的字段名跳过去。直接看汇总
  6. +
  7. 投标截止键入「2026/11/20 9:30」,离开后自动整理成标准格式;键入「2026-11-20 25:00」看错误。
  8. 点上传区,会加入两个示意文件,其中一个扩展名和内容不符。
  9. -
  10. 全部填好后提交:按钮变成「正在创建…」,宽度不变,表单整体禁用。
  11. +
  12. 填好后提交:按钮变成「正在创建…」,宽度不变,整表禁用。

规则

    -
  • 必填写「(必填)」,不用红色星号;选填项多的表单反过来写「(选填)」。
  • -
  • 错误说明写清楚怎么改,不写「格式错误」。
  • -
  • 会扣点的提交按钮,左侧直接写扣多少、还剩多少。
  • -
  • 文件扩展名和内容不符只提醒,不拦截(样本分析 07 的结论)。
  • +
  • 必填写「(必填)」,不用红星;选填多时反过来写「(选填)」。
  • +
  • 错误说明写怎么改,不写「格式错误」。
  • +
  • 没碰过的字段离开时不报错;报错之后边输入边复查,改对立即消失。
  • +
  • 会扣点的提交按钮,左侧写扣多少、还剩多少。
  • +
  • 扩展名和内容不符只提醒,不拦截。
@@ -95,121 +123,138 @@
-
02

同一字段的状态对照

规范 §7.13
+
02

状态对照

§7.13 · §7.22 · Q10
-
-
正常
-
聚焦
-
错误
!请填写证书编号,查标时用它核对附件
-
禁用
已被 2 个已递交项目引用,不能修改
+ 文本输入 +
+
正常
+
悬停
+
聚焦
+
错误:要改了才能保存
请填写证书编号
+
提醒:可以保存
早于投标截止 11月20日
+
禁用
已被已递交项目引用
-
-
-

填写中

正常状态
-
-
-
-
-
不扣额度
+
+ 日期时间输入(Q10):输入框写「2026-11-20 09:30」,可直接键入常见写法(左一键入的是「2026/11/20 9:30」),离开时整理;下方显示「11月20日 09:30 · 剩 N 天」 +
+
已填写
+
+ 24 小时制
+
选择器展开:选日期,底部填时间
+
+
错误
+
+ 24 小时制
-
-

提交中

整表禁用,按钮换成「正在…」,宽度不变
+
+
+
+

提交前

+
+
+
+
+
不扣额度
+
+
+

提交中

整表禁用,按钮宽度不变
-
-
-
-
+
+
+
+
-
不扣额度
+
不扣额度
-
03

资质录入

FR-M6 最小资质库 · 红线 5 不编造
+
03

资质录入

最小资质库 · 红线 5 · Q12
- +
-
04

忽略并注明原因 · 致命问题的确认说明

规范 §7.4 · §7.13 · U4
+
04

忽略原因 · 致命问题的确认说明

§7.4 · §7.13 · U4 · Q2
重要开标一览表与分项价格表合计不一致
-

开标一览表 287.60 万元,分项价格表合计 286.80 万元,相差 0.80 万元。按招标文件规定,两者不一致时以开标一览表为准,不作无效处理;但会影响价格评审。

-
+

开标一览表 287.60 万元,分项价格表合计 286.80 万元,相差 0.80 万元。招标文件规定以开标一览表为准,不作无效处理,但会影响价格评审。

+
-
- -
!至少写 5 个字,说明为什么不处理3 / 200
+
+ +
至少写 5 个字,说明为什么不处理3 / 200
-
「已忽略」不等于确认无误,报告里单独列出
+
「已忽略」在报告里单独列出
-
+
致命授权委托书的单位签章位置未发现盖章新增
-

商务册第 5 页《法定代表人授权委托书》的「投标人(电子签章)」位置是空的。本检查只看有无,不鉴定真伪。

-
+

商务册第 5 页《法定代表人授权委托书》的「投标人(电子签章)」位置是空的。只看有无,不鉴定真伪。

+
- -
对致命问题说「没问题」,需要写一句说明;说明会同时作为误报反馈提交。30 / 200
+ +
说明会同时作为误报反馈提交30 / 200
-
+
@@ -221,80 +266,146 @@ const { $, $$, toast, dialog, icon, esc } = Demo; Demo.demoTop("forms.html"); -/* ---------- 新建项目 ---------- */ -const F = $("[data-new]"); -let files = [], dateVal = ""; -$("[data-new-date]").addEventListener("change", (e) => { dateVal = e.detail; setErr("f-due", ""); }); -function setErr(id, msg) { - const f = document.getElementById(id); - f.classList.toggle("err", !!msg); - let m = $(":scope > .msg.e", f); - if (msg) { - if (!m) { m = document.createElement("span"); m.className = "msg e"; f.appendChild(m); } - m.innerHTML = `!${msg}`; - $$(":scope > .msg:not(.e)", f).forEach((x) => x.hidden = true); - } else { if (m) m.remove(); $$(":scope > .msg", f).forEach((x) => x.hidden = false); } +/* 日期输入(Q10)由 demo.js 的 Demo.dateInput() 处理:可直接键入、离开时整理、下方显示「11月20日 09:30 · 剩 N 天」。 + * 字段错误用 Demo.fieldError():离开时校验,报错后边输入边复查。 */ +const setErr = Demo.fieldError; +const label = (f) => ($("label", f)?.firstChild.textContent || $(".vh", f)?.textContent || "").trim(); + +/* ---------- 01 新建项目 ---------- */ +const F = $("[data-new]"), sum = $("[data-errsum]"), dueDT = $("#f-due [data-dt]"); +let files = []; +const RULES = { + "f-name": () => $("#n-name").value.trim() ? "" : "请填写项目名称,可以先写简称", + "f-due": null, // 由日期输入自己校验(必填 + 格式) + "f-file": () => files.length ? "" : "请上传招标文件,至少一份" +}; +function check(id) { + const ok = id === "f-due" ? dueDT.dt.commit({ required: true }) : setErr(document.getElementById(id), RULES[id]()); + if (!sum.hidden) summary(false); + return ok; } -function checkName() { setErr("f-name", $("#n-name").value.trim() ? "" : "请填写项目名称,可以先写简称,之后再改"); } -$("#n-name").addEventListener("blur", checkName); +function summary(focus) { + const errs = Object.keys(RULES).map((id) => document.getElementById(id)).filter((f) => f.classList.contains("err")); + sum.hidden = !errs.length; + $("[data-errn]").textContent = errs.length; + $("[data-errlist]").innerHTML = errs.map((f) => `
  • ${esc(label(f))}:${esc($(".msg.e", f).textContent.replace(/^!/, ""))}
  • `).join(""); + if (focus && errs.length) { sum.scrollIntoView({ block: "center" }); sum.focus({ preventScroll: true }); } +} +let nameDirty = false; +$("#n-name").addEventListener("input", () => { nameDirty = true; if ($("#f-name").classList.contains("err") && !RULES["f-name"]()) check("f-name"); }); +$("#n-name").addEventListener("blur", () => { if (nameDirty || $("#f-name").classList.contains("err")) check("f-name"); }); +F.addEventListener("dt-change", () => { if (!sum.hidden) summary(false); }); +sum.addEventListener("click", (e) => { const a = e.target.closest("[data-go]"); if (!a) return; e.preventDefault(); const f = document.getElementById(a.dataset.go); f.scrollIntoView({ block: "center" }); ($("input, [data-drop]", f)).focus({ preventScroll: true }); }); + function renderFiles() { - $("[data-files]").innerHTML = files.map((f, i) => `
    ${f.ext}
    ${esc(f.name)}${f.warn ? `!${f.warn}` : `${f.meta}`}
    `).join(""); + $("[data-files]").innerHTML = files.map((f, i) => `
    ${f.ext}
    ${esc(f.name)}${f.warn ? `${f.warn}` : `${f.meta}`}
    `).join(""); } -$("[data-drop]").addEventListener("click", () => { - files = [{ ext: "PDF", name: "ZC-2026-0418 采购文件.pdf", meta: "186 页 · 8.4 MB" }, { ext: "DOC", name: "关于 ZC-2026-0418 的更正公告.pdf", warn: "扩展名是 .pdf,内容是 Word 文档,会按 Word 解析" }]; - renderFiles(); setErr("f-file", ""); -}); -$("[data-files]").addEventListener("click", (e) => { const b = e.target.closest("[data-rm]"); if (b) { files.splice(+b.dataset.rm, 1); renderFiles(); } }); +const addFiles = () => { + files = [{ ext: "PDF", name: "ZC-2026-0418 采购文件.pdf", meta: `186 页 · 8.4 MB` }, { ext: "DOC", name: "关于 ZC-2026-0418 的更正公告.pdf", warn: "扩展名是 .pdf,内容是 Word 文档,会按 Word 解析" }]; + renderFiles(); check("f-file"); +}; +$("[data-drop]").addEventListener("click", addFiles); +$("[data-drop]").addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); addFiles(); } }); +$("[data-files]").addEventListener("click", (e) => { const b = e.target.closest("[data-rm]"); if (b) { files.splice(+b.dataset.rm, 1); renderFiles(); if (!files.length && !sum.hidden) check("f-file"); } }); +$("[data-cancel]", F).onclick = () => toast("(演示)取消:回到工作台", { ok: false }); + F.addEventListener("submit", (e) => { e.preventDefault(); - checkName(); - setErr("f-due", dateVal ? "" : "请选择投标截止日期,用于倒计时"); - setErr("f-file", files.length ? "" : "请上传招标文件,至少一份"); - const errs = $$(".field.err", F); - const sum = $("[data-errsum]"); - if (errs.length) { - $("[data-errn]").textContent = errs.length; - $("[data-errlist]").innerHTML = errs.map((f) => `
  • ${$("label", f)?.firstChild.textContent.trim() || "招标文件"}:${$(".msg.e", f).textContent.replace(/^!/, "")}
  • `).join(""); - sum.hidden = false; sum.scrollIntoView({ block: "center", behavior: "smooth" }); - return; - } + const ok = Object.keys(RULES).map(check).every(Boolean); + if (!ok) { summary(true); return; } sum.hidden = true; const b = $("[data-submit]"); b.classList.add("is-loading"); b.setAttribute("aria-busy", "true"); - $$("input, textarea, button:not([data-submit]), .drop", F).forEach((x) => { x.disabled = true; x.closest(".field")?.classList.add("dis"); }); - setTimeout(() => { b.classList.remove("is-loading"); b.removeAttribute("aria-busy"); $$("input, textarea, button", F).forEach((x) => x.disabled = false); $$(".field.dis", F).forEach((x) => x.classList.remove("dis")); toast("项目已创建,正在解析招标文件 · 可以离开本页"); }, 2400); + const ctl = $$("input, textarea, button:not([data-submit]), .select", F); + ctl.forEach((x) => x.disabled = true); + $$(".field", F).forEach((x) => x.classList.add("dis")); + $("[data-drop]").setAttribute("aria-disabled", "true"); $("[data-drop]").style.pointerEvents = "none"; + setTimeout(() => { + b.classList.remove("is-loading"); b.removeAttribute("aria-busy"); + ctl.forEach((x) => x.disabled = false); $$(".field.dis", F).forEach((x) => x.classList.remove("dis")); + $("[data-drop]").removeAttribute("aria-disabled"); $("[data-drop]").style.pointerEvents = ""; + toast("项目已创建,正在解析招标文件,可以离开本页"); + }, 2400); +}); +addEventListener("DOMContentLoaded", () => { // 等 demo.js 初始化完日期输入 + if (new URLSearchParams(location.search).get("state") === "errors") { + $("#n-due").value = "2026-11-20 25:00"; + Object.keys(RULES).forEach(check); summary(false); + } }); -/* ---------- 资质对话框 ---------- */ +/* ---------- 03 资质录入:到期按项目的投标截止日判断(智慧大厅 11月20日),只提醒,不拦截 ---------- */ +const DUE = new Date(2026, 10, 20); +const expF = $("#f-qexp"), expHint = $("[data-exp-hint]", expF), expDT = $("[data-dt]", expF); +expF.addEventListener("dt-change", (e) => { + const d = e.detail, late = d.ok && !d.empty && d.dt < DUE; + if (late) delete expHint.dataset.off; else expHint.dataset.off = ""; + expHint.hidden = !late || expF.classList.contains("err"); +}); +$("[data-long]").addEventListener("click", () => setTimeout(() => { + const long = $("[data-long]").getAttribute("aria-checked") === "true"; + $("#q-exp").disabled = long; $(".icon-btn", expDT).disabled = long; + if (long) { expHint.dataset.off = ""; expHint.hidden = true; setErr(expF, ""); } else expDT.dt.commit({ required: true }); +})); + $("[data-qadd]").onclick = () => dialog({ title: "添加资质", wide: true, ok: "保存资质", footLeft: '不扣额度', - body: `

    数据只来自你录入的内容,系统不从外部补数据。

    -
    -
    -
    -
    -
    -
    ${icon("upload")}拖入或点击上传 PDF / 图片
    `, - onOk: (s) => { - let bad = 0; - [["qd-name", "#qdn", "请填写资质名称,和证书上的写法一致"], ["qd-no", "#qdo", "请填写证书编号,查标时用它核对附件"]].forEach(([id, sel, msg]) => { - const f = $("#" + id, s), v = $(sel, s).value.trim(); - f.classList.toggle("err", !v); $(".msg", f)?.remove(); - if (!v) { bad++; f.insertAdjacentHTML("beforeend", `!${msg}`); } + body: `
    +
    +
    +
    +
    +
    +
    ${icon("upload")}拖入或点击上传 PDF / 图片
    `, + onOpen: (s) => { + const R = { + "qd-name": () => $("#qdn", s).value.trim() ? "" : "请填写资质名称,和证书上的写法一致", + "qd-no": () => $("#qdo", s).value.trim() ? "" : "请填写证书编号,查标时用它核对附件" + }; + const exp = $("#qd-exp [data-dt]", s); + s._check = () => [...Object.entries(R).map(([id, fn]) => setErr($("#" + id, s), fn())), exp.dt.commit({ required: true })].every(Boolean); + Object.entries(R).forEach(([id, fn]) => { + const f = $("#" + id, s), inp = $("input", f); let d = false; + inp.addEventListener("input", () => { d = true; if (f.classList.contains("err") && !fn()) setErr(f, ""); }); + inp.addEventListener("blur", () => { if (d || f.classList.contains("err")) setErr(f, fn()); }); }); - if (bad) return false; + }, + onOk: (s) => { + if (!s._check()) { $(".field.err input", s).focus(); return false; } toast("已保存资质"); } }); -/* ---------- 忽略原因 ---------- */ -const ig = $("[data-ign]"), ta = $("#ig1", ig), fld = $(".field", ig); -const validate = (show) => { const n = ta.value.trim().length; $("[data-n]", ig).textContent = ta.value.length; const bad = n < 5; if (show || !bad) { fld.classList.toggle("err", bad); $(".msg", fld).innerHTML = bad ? '!至少写 5 个字,说明为什么不处理' : "报告和处理记录里会显示这段原因"; } return !bad; }; -ta.addEventListener("input", () => validate(false)); -ta.addEventListener("blur", () => validate(true)); -$$("[data-chip]", ig).forEach((c) => c.onclick = () => { ta.value = c.textContent; validate(true); ta.focus(); }); -$("[data-ign-ok]").onclick = () => { if (validate(true)) toast("已忽略 · 原因已记录", { undo: () => toast("已撤销", { ok: false }) }); }; +/* ---------- 04 忽略原因 / 致命确认说明 ---------- */ +const NOW = "11月3日 14:30"; +$$("[data-u4]").forEach((box) => { + const kind = box.dataset.u4, ta = $("textarea", box), fld = $(".field", box), msg = $(".msg", fld), def = msg.dataset.def; + const bad = () => ta.value.trim().length < 5; + const show = (on) => { fld.classList.toggle("err", on); ta.toggleAttribute("aria-invalid", on); msg.innerHTML = on ? `${kind === "ign" ? "至少写 5 个字,说明为什么不处理" : "请写一句说明,至少 5 个字"}` : def; }; + ta.addEventListener("input", () => { $("[data-n]", box).textContent = ta.value.length; if (fld.classList.contains("err") && !bad()) show(false); }); + ta.addEventListener("blur", () => show(bad())); + $$("[data-chip]", box).forEach((c) => c.onclick = () => { ta.value = c.textContent; $("[data-n]", box).textContent = ta.value.length; show(false); ta.focus(); }); + $("[data-x]", box).onclick = () => toast("(演示)取消:收起输入框", { ok: false }); + const okBtn = $("[data-ok]", box); + okBtn.onclick = () => { + if (bad()) { show(true); ta.focus(); return; } + const done = () => { + const html = kind === "ign" // 署名行(共用 .sign):「已忽略」不是确认,用辅助灰和「–」 + ? `
    王敏${NOW}已忽略

    原因:${esc(ta.value.trim())}

    ` + : `
    王敏${NOW}确认无误

    说明:${esc(ta.value.trim())} · 已作为误报反馈提交

    `; + box.hidden = true; + box.insertAdjacentHTML("afterend", `
    ${html}
    `); + const d = box.nextElementSibling; + $("[data-undo]", d).onclick = () => { d.remove(); box.hidden = false; ta.focus(); toast("已撤销", { ok: false }); }; + $("[data-undo]", d).focus(); + toast(kind === "ign" ? "已忽略 · 原因已记录" : "已确认无误 · 说明已作为反馈提交"); + }; + if (kind === "ok") { okBtn.classList.add("is-loading"); okBtn.setAttribute("aria-busy", "true"); setTimeout(() => { okBtn.classList.remove("is-loading"); okBtn.removeAttribute("aria-busy"); done(); }, 900); } + else done(); + }; +}); +document.addEventListener("click", (e) => { if (e.target.closest("[data-demo]")) { e.preventDefault(); toast("(演示)在原文查看器里打开这一页", { ok: false }); } }); diff --git a/docs/design/ui-demo/index.html b/docs/design/ui-demo/index.html index f42a4c1..b049aa9 100644 --- a/docs/design/ui-demo/index.html +++ b/docs/design/ui-demo/index.html @@ -11,18 +11,20 @@
    -

    标博士 · UI demo(2026-11-03)

    -

    用已确定的界面风格(方向 A「校样」、设计规范 v0.1)把常见控件和它们组合成的页面摆出来,用来决定 UI 精修的方向。颜色、字号、间距只用 system/tokens.css 的变量;已有组件直接用 system/components.css,规范里有描述但还没实现的组件放在本目录的 demo.css。不用任何 UI 框架和 CDN(AR10),图标是 Lucide 风格的内联 SVG(U3)。

    +

    标博士 · UI demo v0.2(2026-11-03)

    +

    按设计规范 v0.2(决策 U5)重刷:白色主调、墨色派生的浅灰、只有一种白卡片容器、圆角三档、96px 墨色渐变侧栏、透明顶栏。v0.2 风格定稿样张见 explore/workbench-d5.html,各页的侧栏、顶栏、卡片、表头、标签、按钮、筛选、行悬停都以它为准。颜色、字号、间距只用 system/tokens.css 的变量;已有组件在 system/components.css,规范里有描述但还没并入组件库的放在本目录的 demo.css。不用任何 UI 框架和 CDN(AR10),图标是 Lucide 风格的内联 SVG(U3)。

    cd docs && python3 -m http.server 8767
     # 打开 http://127.0.0.1:8767/design/ui-demo/   (设计基准 1440 宽,最小 1280)
    @@ -30,61 +32,65 @@ pre.run{margin:var(--sp-3) 0 0;padding:var(--sp-3) var(--sp-4);background:var(--
    01

    页面

    示意数据:政府采购 · IT 集成,复用原型 prototype/assets/data.js
    - ①控件总览

    按钮(类型 × 6 种状态、尺寸、扣点说明)、输入框 / 文本域 / 下拉选择 / 日期选择 / 复选 / 单选 / 开关、级别标签、AI 标识与署名、出处 5 种形态、页内提醒、轻提示、对话框、标签页、分段、分页、空状态、骨架屏、进度、提示气泡、下拉菜单、快捷键。

    - ②表单

    新建项目(离开时校验 + 提交汇总 + 提交中整表禁用)、同一字段的正常 / 聚焦 / 错误 / 禁用 / 提交中对照、资质录入(错误与「到期提醒」的区别)、忽略并注明原因、致命问题的「确认无误」说明(U4)。

    - ③卡片

    项目卡(致命 / 临近截止 / 今天截止 / 已递交 / 加载中 / 新建)、统计格、额度卡(正常 / 80% / 用完)、任务进度卡(进行中 / 失败退回 / 完成 / 排队)、AI 摘要卡(确认前后)、关键日期卡。

    - ④列表与表格

    项目列表(筛选、搜索、表头排序、空结果)、清单列表(分类、★ 筛选、筛选标签、J / K)、问题列表(级别筛选、只对「提示」批量、J / K + 1 / 2 / 3、已处理折叠)、额度流水、参数对照表、两种空结果。

    - ⑤组合页 · 工作台

    侧边栏 + 顶栏框架;截止提醒、统计格、项目表格、待处理的致命问题,右侧任务进度、额度、最近处理。看「列表」布局的密度。

    - ⑥组合页 · 读标

    项目页头 + 页签、清单状态与确认、分类导航、清单(待确认区、补遗更新、搜索)、原文查看器并排高亮。可以用 J / K 逐条看原文。看「对照」布局的密度。

    + ⑤组合页 · 工作台

    v0.2 框架(侧栏、透明顶栏、搜索、额度、通知);截止提醒、一卡四格统计(可点筛选)、项目列表(下一步 / 更多)、待处理的致命问题,右侧任务、额度、最近处理。

    + ⑥组合页 · 读标

    项目页头 + 页签、清单状态与确认、分类导航、清单(待确认区、补遗更新、搜索)、原文查看器并排高亮。J / K 逐条看原文。

    + ★风格探索

    工作台的六轮方案(a–f、d2–d5),d5 为 v0.2 定稿样张。

    + ①控件总览

    按钮(类型 × 状态、尺寸、扣点说明)、输入框 / 下拉 / 日期 / 复选 / 单选 / 开关、级别标签、AI 标识与署名、出处、提醒、轻提示、对话框、标签页、筛选、分页、空状态、骨架屏、进度、提示气泡、菜单、快捷键。

    + ②表单

    新建项目(离开时校验、提交汇总、提交中整表禁用)、字段的正常 / 聚焦 / 错误 / 禁用对照、资质录入、忽略并注明原因、致命问题的「确认无误」说明(U4)。

    + ③卡片

    统计、额度卡(正常 / 80% / 用完)、任务进度卡、AI 摘要卡(确认前后)、关键日期卡。MVP 不做项目卡片视图(Q11)。

    + ④列表与表格

    项目列表、清单列表(分类、★ 筛选、J / K)、问题列表(级别筛选、只对「提示」批量、1 / 2 / 3)、额度流水、参数对照表(含级别列)、空结果。

    资质库、额度页属于和工作台相同的「列表」布局,控件已在 ②③④ 覆盖,没有单独做组合页。

    -
    02

    新增组件清单

    都在 demo.css 的 B 部分;精修定稿后并入 system/components.css
    +
    02

    新增组件清单

    B 部分在 demo.css;精修定稿后并入 system/components.css
    - - - - - - - - - + + + + + + + + + + - - - - - - - - - + + + + + + + + + +
    编号组件类名依据说明
    B1图标.ic§6 · U3Lucide 路径内联,1.5px 描边,16 / 20 / 32
    B2页面框架.sidebar .top .crumbs .quota-chip .content .page-title .proj-head§5.3从原型 app.css 搬过来,去掉裸色值
    B3按钮补充状态.btn.swap .is-loading .icon-btn .btn-group§7.1按下态、加载中定宽换字、只有图标的按钮、按钮组
    B4输入框补充.input:focus/[disabled]/[readonly] .input-unit .search .form .form-sec .err-sum§7.13聚焦 / 禁用 / 只读、带单位、搜索、两列表单、错误汇总、字数
    B5下拉选择 · 下拉菜单.select .menu .popAR10选中项加粗 + ✓,不可选项写原因;正式实现用 Reka UI Select / DropdownMenu
    B6日期选择.cal .dt-pair .date-trigger§9.3 · AR10周一开始、Barlow 数字、今天描边、选中墨底、关键日期圆点、不可选删除线
    B7复选 · 单选 · 单选卡片 · 开关.ck .choice .choice-card .switch§8.2墨色勾选、半选;开关是方形滑块 + 开 / 关文字
    B8分段控件 · 标签页.seg .tabs [role=tab]§5.3 · §7.5← → 切换、不可用页签
    B9分页.pagination—规范未定义;当前页墨色描边
    —卡片 · 中性标签 · 链接 · 出处链接 · 数字.card .card.flush .card-h .card-b .card-f .tag .tag.ai .lnk .hint-u .cite-link .fig .sheet .hl§4.2 · §5.1 · §7.8 · §7.16在 components.css。全页只有一种白卡片;中性标签 --c-n100 实底;.fig 默认 15、12px 小字里 13、统计大数字用 .big(28);原文纸面用 --font-doc --fs-doc --lh-doc,原句高亮 = 蓝铅笔浅底 + 蓝色底边;致命问题行不加红竖线
    B1图标.ic .vh§6 · U3Lucide 路径内联,1.5px 描边,16 / 20 / 32;侧栏菜单 22
    B2页面框架Demo.shell() · .sidebar .me .plan .main .top .crumbs .top-search .quota-chip .content .page-title .proj-head§5.3 · U596px 墨色渐变侧栏(上图标下文字、底部点阵、头像 + 套餐);透明顶栏,滚动后毛玻璃;内容区左上角 12 圆角
    B3按钮补充状态.btn.swap .is-loading .icon-btn .btn-group§7.1按下变深(--c-ink-active)、加载中定宽换字、只有图标的按钮 36 / 32、按钮组
    B4输入框补充.input:focus/[disabled]/[readonly] .input-unit .search .form .form-sec .err-sum§7.13聚焦 = 墨色边框 + 1px 内圈;带单位、搜索、两列表单、错误汇总、字数
    B5下拉选择 · 下拉菜单 · 浮动菜单.select .menu .pop .menu.float .menu .two · Demo.floatMenu() Demo.menuHTML()Q4 · AR10选中项加粗 + 右侧墨色 ✓;两行菜单项写扣点说明;禁用项(disabled: "原因")变禁用灰、右侧写原因;正式实现用 Reka UI Select / DropdownMenu
    B6日期输入 · 日期选择.dt[data-dt] .cal · Demo.dateInput() Demo.parseDT() Demo.fieldError()§7.22 · §9.3 · Q4 · Q10可键入「2026-11-20 09:30」及常见写法,离开时整理;选择器带 24 小时时间;下方显示「11月20日 09:30 · 剩 N 天」。日历非本月格子留空,不可选日期禁用灰、无悬停
    B7复选 · 单选 · 单选卡片 · 开关.ck .choice .choice-card .switch§8.2 · Q4复选框圆角 4;开关 = 圆角 6 矩形轨道 + 圆角 4 方块滑钮 + 「开 / 关」字
    B8筛选 · 标签页.seg .tabs [role=tab]§5.3 · §7.5筛选是一排文字按钮,按下 = --c-n100 实底 + 加粗;页签 ← → 切换
    B9分页.paginationQ4当前页 --c-n100 实底 + 加粗,与筛选一致
    B10轻提示.toasts .toast§7.124 秒消失、悬停暂停、可撤销、堆叠
    B11对话框.scrim .dialog .wide§7.14480 / 640,焦点锁定,Esc 关闭,焦点回到触发处
    B12提醒补充.alert .act .sub .errno§7.12 · §7.15右侧操作、副说明、错误编号
    B13提示气泡[data-tip]§6墨底白字;正式实现用 Reka UI Tooltip
    B14空状态 · 骨架屏 · 进度.empty .skel .skel-row .progress .steps .fail§7.11 · §7.15骨架缓慢明暗变化,减少动态效果时静止
    B15杂项.kbd .kv .chip .drop .file-row .flowline§8.2快捷键、键值表、可移除的筛选标签、上传区、文件行、状态流
    B16AI 产出块.ai-block .ai-label .sign.ign .more-cites§3.3 · §7.16左侧虚线 → 确认后墨色实线;「已忽略」灰色署名
    B17卡片.card-h .card-foot .pcard .stats .stat .quota .dates§7.9 · §7.10项目卡、统计格、额度卡、关键日期
    B18表格补充.toolbar .th-sort .batchbar .list-box .filters-on§7.7 · §8.2排序、勾选、选中行、批量栏、空结果行、问题行勾选
    B19对照布局.split .cats .viewer-body§5.3 · §7.8分类导航 + 列表 + 原文查看器
    B11对话框.scrim .dialog .wide§7.14 · Q12480 / 640,圆角 12,遮罩 --scrim;对话框不裁剪浮层(内容过高时遮罩滚动);焦点锁定、Esc 先收起浮层再关闭;对话框里可以有自己的主按钮
    B12提醒补充.alert .act .sub .errno§7.12 · §7.15白卡片式提醒,颜色只在图标和关键字;右侧操作、副说明、错误编号
    B13提示气泡[data-tip] .tip-below .tip-start .tip-left§6墨底白字、圆角 8;正式实现用 Reka UI Tooltip
    B14空状态 · 骨架屏 · 进度.empty .skel .skel-row .progress .steps .fail§7.11 · §7.15 · Q8骨架屏静止,不做明暗起伏
    B15杂项 · 快捷键说明.kbd .kv .chip .drop .file-row .flowline .av · Demo.keysDialog(Demo.KEYS.reading / .check) Demo.keysTable()§8.2快捷键说明按页面传入:读标 J / K、Enter、?;查标 J / K、O、1 / 2 / 3、X、U、?
    B16AI 产出块 · 署名行.ai-block .ai-label .sign .sign.sm .sign.ign .sign .undo .sign-note .more-cites§7.4 · §7.16 · Q2正文始终墨色;确认前「AI」标签 + 左侧 2px 蓝铅笔虚线,确认后去掉虚线、出署名行(绿 ✓ + 姓名 + 时间 + 动作 + 撤销),原因 / 说明写在 .sign-note
    B17卡片件.card-h .card-foot .stats .stat .quota .dates§7.9 · §7.10 · Q11一卡四格统计(格子可做筛选按钮)、额度卡、关键日期;项目卡已删除(Q11)
    B18表格补充 · 当前条 · 行内说明.toolbar .th-sort .batchbar .filters-on tr.sel .item.sel .finding.sel .kb-on .u4§7.4 · §7.7 · §7.18 · §8.2 · U4当前条 = --c-n50 底 + 左侧 3px 墨色竖条,键盘再加 2px 内框;勾选只看复选框;忽略原因、致命的确认说明在行内展开(无底色)
    B19对照布局.cmp[data-cmp] .cmp-list .cats .lst .rsz .grp-h .grp-h.ai .viewer-body · Demo.cmp()§5.3 · §7.6 · §7.8分类导航 176(1400 以下 152)+ 列表(最小 560)+ 原文查看器(默认 45%、最小 400,可拖动或 ← →);待确认是清单顶部的蓝色标题分组
    B20项目行.prow-head .prows .prow .pname .psub .nx .more-cell .srt§7.9工作台和列表页共用:下一步左对齐、行悬停箭头右移 3px;更多单独一列常驻;致命列居中
    -
    -
    03

    做的过程中发现的规范缺口 · 待拍板

    UI 精修阶段逐条确认
    +
    +
    03

    Q1–Q13 的答案(2026-11-03 已确认)

    Q1–Q12 已写进 design-system.md 对应章节
    - + - - - - - - - - - - - - + + + + + + + + + + + + +
    #问题demo 现在的做法需要决定
    #问题结论规范
    Q1额度流水的「+」用了定稿绿沿用 components.css 的 .flow td.plus(绿)§3.4 说绿色只表示「有人确认过」。建议改成墨色「+」,靠符号区分
    Q2AI 产出的正文是什么颜色正文始终墨色,只用「AI」标签 + 左侧虚线区分§7.16 写「确认后文字变为墨色」,暗示确认前不是墨色;大段蓝字阅读负担重,建议明确「正文始终墨色」
    Q3「选中」用什么颜色读标清单选中 = 蓝铅笔浅底(和原文高亮对应);表格勾选 = 凹陷灰 + 墨色竖线蓝色只属于系统和出处(§3.4)。读标选中是否保留蓝色,需统一
    Q4开关、分页、下拉、日期选择的视觉开关做成方形滑块 + 开 / 关文字;分页当前页描边;下拉选中加粗 + ✓;不可选日期加删除线规范第 7 节没有这几个组件,确认后补进规范
    Q5参数对照表加「级别」列多了一列级别标签,判断词颜色有形状兜底§7.7 的表没有这一列;只靠判断词颜色对色弱不够
    Q6悬停、按下、遮罩缺颜色变量用 color-mix() 从墨色算;components.css 的 #34404D、原文页的 #222 #888 是裸色值建议 tokens.css 增加 --c-ink-hover、--scrim 等语义变量
    Q7字阶外的字号Barlow 数字用了 13 / 15px(components.css 已有)§4.2 字阶里没有,建议加 --fs-figure-sm 等变量
    Q8骨架屏动不动1.6 秒缓慢明暗变化;减少动态效果时静止和「不闪烁」原则是否冲突
    Q9顶栏额度 80% 后是否变色顶栏额度描边变赭色§7.10 只写了额度条,没写顶栏
    Q10日期的输入格式界面显示「11月20日 09:30」,输入框用「2026-11-20」§9.3 只规定了显示格式
    Q11项目卡片视图做了项目卡,工作台仍用表格§7.9 只定义了项目行;要不要提供卡片视图
    Q12卡片 / 页面里同时出现多个主按钮组合页每屏一个主按钮;demo 陈列页为了对比会出现多个表单页、对话框里的「保存」是否也算「一屏一个」
    Q1额度流水的「+」改为墨色,靠「+ / −」符号区分,不用绿色§3.4 · §7.10
    Q2AI 产出的正文颜色正文始终墨色;AI 身份靠「AI」小标签和左侧 2px 蓝铅笔虚线;确认后去掉虚线,换成署名行(绿 ✓ + 姓名 + 时间)§7.16
    Q3「选中」用什么颜色统一为 --c-n50 底 + 左侧 3px 墨色竖条(读标清单、表格选中都一样);蓝色只留给原文高亮和出处链接§7.18
    Q4开关、分页、下拉、日期选择开关:圆角 6 矩形轨道 + 圆角 4 方块滑钮,开为墨色,旁边写「开 / 关」。分页:当前页 --c-n100 实底加粗。下拉:选中项加粗、右侧墨色 ✓。日期:不可选只变禁用灰,不加删除线§7.19–7.22
    Q5参数对照表的「级别」列保留§7.7
    Q6悬停、按下、遮罩的颜色用 tokens.css 的 --c-ink-hover --c-ink-active --scrim 等变量,清掉 #34404D #222 #888 这类裸色值§3.1
    Q7字阶外的数字字号--fs-figure 28、--fs-figure-sm 15、--fs-figure-xs 13§4.2
    Q8骨架屏动不动静止,不做明暗起伏§7.15
    Q9顶栏额度 80% 后用到 ≥ 80% 时该项数字变赭色(--warn-text)加粗;用完时悬停说明续费方式;不用红§7.10
    Q10日期的输入格式输入框「2026-11-20 09:30」(24 小时制,配日期选择器);显示「11月20日 09:30」§9.3
    Q11项目卡片视图MVP 不做,工作台只用列表;cards 页的「项目卡」改作他用或删掉§7.9
    Q12「一屏一个主按钮」怎么算按上下文算:页面级一个;对话框里可以有自己的主按钮;陈列页不受限§7.1
    Q13流水是否记录 0 点的免扣行记。24 小时内复查不扣点时,流水记一行「免扣(24 小时内复查)」、数量 0(见 ④ 列表与表格 04),让用户能核对复查确实没扣点(红线 9「可查」)。FR-M6-03 已补「免扣」类型§7.10 · FR-M6-03
    diff --git a/docs/design/ui-demo/lists.html b/docs/design/ui-demo/lists.html index 6c33c55..e1b6f4b 100644 --- a/docs/design/ui-demo/lists.html +++ b/docs/design/ui-demo/lists.html @@ -1,4 +1,9 @@ + @@ -10,9 +15,34 @@ @@ -20,111 +50,118 @@

    ④ 列表与表格

    -

    筛选栏 + 表格 / 列表。表头凹陷灰底 + 2px 墨色底线,行高 40;数字用 Barlow 等宽。演示筛选、排序、批量勾选、键盘逐条处理和空结果。

    +

    列表和表格都放在白卡片里;筛选在卡片头,表头只有 12px 辅助灰列名,行间发丝线,悬停浅灰。演示筛选、排序、键盘逐条处理、批量勾选、分页和空结果。

    -
    01

    项目列表

    规范 §7.9 · 可以筛选、搜索、点表头排序
    -
    -
    -
    -
    -
    -
    - - - -
    所投包状态就绪
    -

    默认按截止时间升序,截止临近的排在前面。「就绪」只在「已检查」显示,只表示致命问题都已处理,不代表投标文件没有问题。

    +
    01

    项目列表

    §7.9 · 行与工作台一致
    +
    +
    +

    项目

    +
    +
    +
    +
    +
    +
    + + + 状态 + + 下一步 +
    +
      +
      -
      02

      清单列表

      规范 §7.5 · 点这块后按 J / K 移动、Enter 打开出处
      -
      -
      - -
      J K 移动 · Enter 打开出处
      -
      -
      -
      +
      02

      清单列表

      §7.5 · §7.18 · 点列表后按 J / K 移动
      +
      +
      +
      + +
      J K 移动Enter 打开出处
      +
      +
      +
      +
      -
      03

      问题列表

      规范 §7.4 · §8.2 · 只有「提示」级可以批量
      -
      -
      - - - - +
      03

      问题列表

      §7.4 · §7.18 · §8.2 · U4 · 只有「提示」可以批量处理
      + +
      +
      +
      + + + + +
      +
      - 只显示待处理的问题;已处理的在列表底部。 -
      -
      - -
      +
      +
      +
      • 当前条:浅灰底 + 左侧墨色竖条,键盘操作时再加墨色内框;勾选只看复选框,不加底色。致命行不加红竖线,级别只看「致命」标签。
      • 「忽略」和致命问题的「确认无误」在行内展开说明框,不弹对话框(U4)。
      -
      04

      额度流水

      规范 §7.10 · 红线 9 额度即货币
      -
      -
      -
      -
      -
      - - - - - - - - - - - - -
      时间类型额度数量余额关联项目 / 任务说明
      11-02 14:31退回解析+137智慧大厅建设项目 · 解析任务 PJ-0417解析失败:文件已加密
      └11-02 14:28预扣解析−136智慧大厅建设项目 · 解析任务 PJ-0417和上一行的退回对应
      11-02 14:20—检查012智慧大厅建设项目 · 第 2 次检查24 小时内复查,不扣
      11-02 10:05结算解析−137智慧大厅建设项目 · 补遗 1
      11-01 16:00结算检查−112智慧大厅建设项目 · 第 1 次检查
      11-01 10:40结算解析−138智慧大厅建设项目 · 招标文件
      11-01 09:30人工调整检查+213—运营:系统异常补偿,工单 GD-1101
      11-01 09:00发放解析 / 检查+50 / +2050 / 20专业版开通(对公转账)开通单 KT-1101
      - -
      • 增加用定稿绿「+」(这里绿色表示「入账」,是否允许请在精修时拍板,见目录页的待定问题),扣减用墨色「−」;退回记录和对应的预扣记录相邻显示。
      +
      04

      额度流水

      §7.10 · §7.20 · 红线 9 额度即货币
      +
      +
      +

      本期流水

      +
      +
      2026-11-01 至 2027-10-31
      +
      + + + +
      时间类型额度数量余额关联项目 · 任务说明
      +
      +
      +
      • 「+」和「−」都用墨色,只靠符号区分,不用绿色(Q1)。退回和对应的预扣相邻显示。期末余额与顶栏一致:解析 37、检查 12。
      • 24 小时内复查记一行 0 点的「免扣」,这一行是否保留待确认(Q13)。
      • 分页(Q4):当前页浅灰实底加粗,和筛选一致;只在超过一页时显示。
      -
      05

      参数对照表

      规范 §7.7
      - +
      05

      参数对照表

      §7.7 · 点一行在原文查看器里对照
      +
      - - - - - - + + + + + +
      参数项要求投标响应判断级别出处
      ★ 应用服务器 · 内存≥ 512G DDR4256G DDR4疑似不满足致命第 41 页 ↔ 技术册 第 12 页
      ★ 应用服务器 · 系统盘2 块 ≥ 960G SSD
      补遗 1 更新,原为 ≥ 480G
      2 块 960G SSD满足补遗 1 第 1 页 ↔ 技术册 第 12 页
      ★ 应用服务器 · CPU≥ 2 颗 32C 2.7GHz2 颗 7380 32C 2.7GHz满足第 41 页 ↔ 技术册 第 12 页
      ★ 核心交换机 · BFD 间隔≤ 50ms3.3ms满足第 44 页 ↔ 技术册 第 58 页
      ★ 核心交换机 · 证明材料官网参数截图等—未找到响应重要第 44 页 ↔ 已查找:技术册、商务册附件
      ▲ 核心交换机 · 交换容量≥ 19.2Tbps19.2Tbps / 48Tbps需人工判断提示第 44 页 ↔ 技术册 第 58 页
      ★ 应用服务器 · 内存≥ 512G DDR4256G DDR4疑似不满足致命第 41 页 ↔ 技术册 第 12 页
      ★ 应用服务器 · 系统盘2 块 ≥ 960G SSD
      补遗 1 更新,原为 ≥ 480G
      2 块 960G SSD满足—补遗 1 第 1 页 ↔ 技术册 第 12 页
      ★ 应用服务器 · CPU≥ 2 颗 32C 2.7GHz2 颗 7380 32C 2.7GHz满足—第 41 页 ↔ 技术册 第 12 页
      ★ 核心交换机 · BFD 间隔≤ 50ms3.3ms满足—第 44 页 ↔ 技术册 第 58 页
      ★ 核心交换机 · 证明材料官网参数截图等—未找到响应重要第 44 页 ↔ 已查找:技术册、商务册附件
      ▲ 核心交换机 · 交换容量≥ 19.2Tbps19.2Tbps / 48Tbps需人工判断—第 44 页 ↔ 技术册 第 58 页
      排队叫号一体机 · 显示屏≥ 21.5 英寸21 英寸疑似不满足提示第 47 页 ↔ 技术册 第 77 页
      -
      • 判断只用四个词:满足 / 疑似不满足 / 未找到响应 / 需人工判断。「满足」用墨色普通字,不用绿色。
      • 要求和响应列用 Barlow,数值左对齐。规范的表没有「级别」列,这里加了一列标签,让判断词的颜色有形状兜底(色弱)——是否保留请拍板。
      +
      • 判断只用四个词:满足 / 疑似不满足 / 未找到响应 / 需人工判断。「满足」用墨色普通字,不用绿色。
      • 保留「级别」列(Q5):标签的实心 / 描边 / 无框给判断词的颜色做形状兜底;「满足」「需人工判断」写「—」。
      -
      06

      空结果

      规范 §7.15
      +
      06

      空结果

      §7.15
      - 筛选后没有结果:写清楚条件,给「清除筛选」 - -
      项目所投包投标截止状态
      没有符合条件的项目

      「已归档」里没有名称含「医保」的项目。

      + 筛选后没有结果:写清条件,给「清除筛选」 +
      +

      项目

      +
      • 没有符合条件的项目

        「已归档」里没有名称或采购人含「医保」的项目。

      +
      - 问题全部处理完:说清楚意味着什么,不庆祝 -
      待处理的问题都已处理

      致命问题已全部处理,项目为「就绪」。这只表示每一项都有人处理过,不代表投标文件没有问题。

      -
      + 问题都处理完:说清楚意味着什么,不庆祝 +
      +
      待处理的问题都已处理

      项目为「就绪」。这只表示每一项都有人处理过,不代表投标文件没有问题。

      + +
      @@ -134,43 +171,83 @@ diff --git a/docs/design/ui-demo/page-reading.html b/docs/design/ui-demo/page-reading.html index 81465db..3145e98 100644 --- a/docs/design/ui-demo/page-reading.html +++ b/docs/design/ui-demo/page-reading.html @@ -1,4 +1,11 @@ + @@ -10,59 +17,93 @@
      -
      -
      +
      +
      -

      某市政务服务中心智慧大厅建设项目

      -
      某市政务服务管理办公室政府采购 · 公开招标投 采购包 1(共 2 个包)ZC-2026-0418
      -
      -
      -
      投标截止11月20日 09:30 · 剩 17 天
      -
      状态已检查
      -
      就绪未就绪
      +

      +

      某市政务服务管理办公室 · 政府采购 · 公开招标 · 投采购包 1(共 2 个包)· ZC-2026-0418

      +
      +
      投标截止
      11月20日 09:30剩 17 天
      +
      状态
      已检查
      +
      就绪
      未就绪
      +
      - -
      + +
      +

      项目检查清单

      -
      +
      - 反馈:有遗漏的要求 + 反馈遗漏
      -
      - -
      -
      + +
      +
      + +
      +
      + +
      @@ -71,122 +112,140 @@ main.main{height:100vh;overflow:hidden} diff --git a/docs/design/ui-demo/page-workbench.html b/docs/design/ui-demo/page-workbench.html index 3fd7d8c..c8e3a98 100644 --- a/docs/design/ui-demo/page-workbench.html +++ b/docs/design/ui-demo/page-workbench.html @@ -1,4 +1,9 @@ + @@ -10,123 +15,212 @@
      -

      工作台

      11月3日 周二 · 按投标截止时间排序,截止临近的排在前面。

      -
      扣 1 次解析 · 剩 37 次
      +

      工作台

      下午好,王敏。今天是 11月3日,周二。

      +
      + 扣 1 次解析 · 剩 37 次 + 新建项目 +
      -
      !

      某县数据中心机房改造项目今天 17:00 截止。清单还是草稿,还没有做过检查。

      去确认清单
      - -
      -
      进行中的项目4

      已递交 1 · 已归档 1

      -
      7 天内截止2

      最近:今天 17:00

      -
      待处理的致命问题3

      在 1 个项目里

      -
      待确认的清单条目4

      在 2 个项目里

      +
      + +

      某县数据中心机房改造项目 今天 17:00 截止,清单还没确认。

      + 确认清单
      +
      + + + + +
      +
      -
      -
      -
      -
      -
      - - - -
      项目投标截止状态就绪致命
      -

      「就绪」只表示致命问题都已处理,不代表投标文件没有问题。

      - -

      待处理的致命问题

      致命3去查标页处理
      -
      -
      致命
      报价超过采购包 1 的最高限价智慧大厅建设项目 · 开标一览表 287.60 万元,限价 286.00 万元
      报价册 · 第 1 页
      -
      致命
      ★ 条款疑似不满足:应用服务器内存智慧大厅建设项目 · 响应 256G,要求 ≥ 512G · 上次标记为已修改,问题仍在
      技术册 · 第 12 页
      -
      致命
      授权委托书的单位签章位置未发现盖章智慧大厅建设项目 · 只看有无,不鉴定真伪
      商务册 · 第 5 页
      -
      -
      - -
      -
      -

      正在进行的任务

      -
      解析补遗 2提取清单
      -
      某市医保信息系统等保测评服务
      -
      62%
      -
      检查排队中 · 智慧社区平台运维 · 前面还有 1 个任务
      -

      可以离开本页,完成后会发站内通知。

      -
      - -
      -

      本期额度

      至 2027-10-31
      -
      -
      解析剩余37/ 50
      -
      检查剩余12/ 20
      +
      +
      +
      +

      项目

      +
      + + + + +
      +
      - -
      + +
        + -
        -

        最近处理

        全部记录
        -
          -
        • 王敏 ✓ 已修改「授权委托书签章」14:26
        • -
        • 王敏 确认了清单 v2 · 智慧大厅11-02
        • -
        • 系统 完成第 2 次检查 · 致命 311-02
        • -
        • 系统 并入补遗 1 · 更新 2 条11-02
        • -
        • 李工 忽略「其他项目名称」:业绩介绍11-01
        • +
          +
          +

          待处理的致命问题

          + 致命3 + 查看全部 +
          +
            +
          • 致命
            报价超过采购包 1 的最高限价智慧大厅建设项目 · 开标一览表 287.60 万元,限价 286.00 万元
            报价册 · 第 1 页去处理
          • +
          • 致命
            ★ 条款疑似不满足:应用服务器内存智慧大厅建设项目 · 响应 256G,要求 ≥ 512G · 上次已修改,问题仍在
            技术册 · 第 12 页去处理
          • +
          • 致命
            授权委托书的单位签章位置未发现盖章智慧大厅建设项目 · 只看有无,不鉴定真伪
            商务册 · 第 5 页去处理
          -
        +
        + +