fix(web): 控件对齐与输入友好整改(控件高度令牌化 + 对齐自检脚本)

问题不是"不好看",而是**可测量的错位**:同一行里原生 date 输入 38.8px、数字输入
36.8px、按钮 34px;16px 的勾选框与 36.8px 的下拉同排;因子行的下拉与权重框没有
可见标签;列表里的勾选框点不中;参数非法时「运行回测」直接置灰且不说原因。

根因:控件高度靠「上下 padding + 行高」拼出来,而 input / select / button 的原生行高
各不相同(Chrome 的 date 还会多 2px),必然参差;加上各处内联像素宽度
(style={{width:220}}、flex:1)与自搓布局,列自然对不齐。

改动:
- 新增控件高度令牌 --ctl-h-sm/md/lg(28/34/38px)与 --ctl-px,.input/select/.btn/
  .icon-btn/date 统一显式 height(不再拼 padding);原生 checkbox/radio 统一 16px,
  点击热区交给外层 label(.check/.radio-row/.check--cell),表格整格可点
- 因子行、选股条件行改为「表头 + CSS 栅格」成列对齐,列宽由样式决定,
  去掉内联像素宽度与 flex 拉伸,配 aria-label 供读屏分辨重复行
- 表单友好化:错误提示改为**失焦或提交后**才出现(清空重填的瞬间不再标红);
  提交被拦下时一次展开全部行内错误 + 自动聚焦并滚动到第一个问题字段;
  运行/保存按钮不再因参数非法而置灰(灰按钮不说原因 = 看起来不可点却无响应),
  改为可点击并讲清原因;补齐 topN/costs 两处「产生了却没人显示」的行内错误落点
- 数值字段补 inputMode/step/min/max 与单位、取值范围提示;工具条检索/筛选用
  .input--search/.input--filter/.input--picker 类,不再写内联宽度
- /experiments 筛选无结果的空态与「暂无实验」区分开(原文案会让人以为归档丢了)
- 同一页面可能挂两份表单:radio name 与 label/for 加表单实例前缀(useId),
  否则两边单选互相取消、label 指错控件
- 新增 scripts/verify_ui_alignment.py:系统 Chrome + 原生 CDP(仅标准库,
  独占随机端口与临时 profile),按 7 个页面 × 1500/375px 检查同排等高、
  高度取值归一、点击目标、标签与无障碍名、字号圆角一致、尺寸匹配内容、
  横向溢出、提示裁切;本次基线 108/32 → 现 140/140

验证:pytest 388 passed、ruff 全绿(顺带清掉 qlib_verify.py 一处死代码)、
tsc 0 错误、图表单测 7 passed、next build 成功、契约自检 59/59、
对齐自检 140/140(含 375px 小屏)。
This commit is contained in:
Simon
2026-09-27 09:01:55 +08:00
parent 82240e383d
commit 9aaca12751
14 changed files with 1103 additions and 209 deletions
+55 -2
View File
@@ -439,6 +439,56 @@ cd backend && PYTHONPATH=. .venv/bin/python ../scripts/verify_strategy_workspace
# 只验接口与页面(跳过回测 Job):加 --skip-job
```
#### 6.3.2 界面规范与对齐自检(控件尺寸 / 输入友好)
界面不是"能看就行":控件错位、尺寸与内容不匹配、点不中、报错说不清,都会直接变成操作错误。
因此把可度量的部分**写成规范 + 自检脚本**,而不是靠肉眼。
**① 控件高度只有三档令牌**(`app/globals.css` 的 `--ctl-h-sm/md/lg` = 28 / 34 / 38px):
| 令牌 | 用途 | 典型控件 |
|---|---|---|
| `--ctl-h-sm` | 密集行 | 小按钮、图标按钮、可点击 chip |
| `--ctl-h-md` | 标准 | 输入框、下拉、按钮(默认) |
| `--ctl-h-lg` | 主操作 | 顶栏图标按钮 |
规则:**同一行内的控件必须等高**。做法是给 `.input / select / .btn / .icon-btn` 显式
`height`,而不是靠上下 padding 拼高度 —— 后者会得到 34 / 36.8 / 38.8 三种结果
(Chrome 的原生 `date` 输入还会多 2px),同一行就肉眼可见地参差。
**② 尺寸与内容匹配**:数值/日期输入不拉满整行(`.form-grid` 下 `max-width:180px`);
选项文案长的选择器给更宽的类(`.input--picker`);工具条检索/筛选用
`.input--search` / `.input--filter`,**不在 JSX 里写内联像素宽度**;重复行(因子、条件)
用"表头 + CSS 栅格"成列对齐,列宽由样式表决定。
**③ 点击目标**:控件 ≥28px;16px 的勾选框/单选按钮由外层 `label`(`.check` / `.radio-row`)
撑开热区,表格里的勾选框用 `.check--cell` 让整个单元可点。
**④ 标签与无障碍名**:每个输入都有可见标签或 `aria-label`;只有图标的按钮必须有
`aria-label`/`title`(否则读屏只会念"按钮")。
**⑤ 输入友好**:校验错误**失焦或提交后**才提示(清空重填的瞬间不标红);
提交被拦下时**一次展开全部**行内错误并**聚焦到第一个问题字段**;
运行/保存按钮**不因参数非法而置灰**(灰按钮不说原因属于"看起来不可点却无响应"),
而是可点击并讲清原因。
**对齐自检**(用系统 Chrome + 原生 CDP,仅标准库;会独占随机端口与临时 profile):
```bash
python3 scripts/verify_ui_alignment.py # 7 个页面 × 1500/375px,共 140 项检查
python3 scripts/verify_ui_alignment.py --dump # 打印每行控件的宽高与字号明细
python3 scripts/verify_ui_alignment.py --pages /backtest --width 375
```
检查项:同排控件等高、控件高度取值归一(≤3 种)、点击目标、标签/无障碍名、
字号与圆角一致、尺寸匹配内容、无横向滚动、提示文本不被裁切。
> 说明:本平台为**深色单主题**(`color-scheme: dark`),因此"深浅两套主题都测对比度"
> 这一条不适用;对比度按深色主题实测(WCAG 相对亮度公式,正文对背景):
> 正文 `--text-1` 对 `--bg-0` **17.09:1**、对卡片 `--surface-1` **15.26:1**;
> 标签 `--text-2` 对卡片 **8.42:1**;提示 `--text-3` 对卡片 **6.12:1**;
> 错误 `--neg` 对卡片 **7.04:1** —— 均高于正文 4.5:1 / 次要文字 3:1 的门槛。
---
## 7. AI Agent
@@ -496,8 +546,9 @@ pnpm run build # 13 条路由,含 /strategies /backtest /e
```bash
cd backend
PYTHONPATH=. .venv/bin/python ../scripts/verify_strategy_workspace.py # 策略库/说明/名称/选股直通/归档链路(56 项,约 4 分钟)
PYTHONPATH=. .venv/bin/python ../scripts/verify_strategy_workspace.py # 策略库/说明/名称/选股直通/归档链路(59 项,约 4 分钟)
PYTHONPATH=. .venv/bin/python ../scripts/verify_backtest_page_contract.py # 回测结果结构契约(约 4 分钟)
python3 ../scripts/verify_ui_alignment.py # UI 对齐与控件一致性(140 项,约 3 分钟)
```
覆盖重点:Provider 归一化与 Failover 审计、Repository 幂等与「未来函数阻断」
@@ -505,7 +556,9 @@ PYTHONPATH=. .venv/bin/python ../scripts/verify_backtest_page_contract.py # 回
**数据库目标守卫**(`192.168.1.10` 被拒 / 本机放行 / 列表可用环境变量覆盖)、
**QlibEngine 数据管线**(bin 落盘格式/roundtrip/端到端回测)、Job 状态机与 Experiment
归档、Agent 工具白名单与编排、LLM 配置加载、API 端到端、**策略说明推导**
(`describe_strategy` 的分支/互斥/缺失值语义)、**股票名称回填**(回测与选股两侧)。
(`describe_strategy` 的分支/互斥/缺失值语义)、**股票名称回填**(回测与选股两侧)、
**UI 对齐与控件一致性**(同排等高 / 尺寸归一 / 点击目标 / 标签 / 无障碍名 / 横向溢出,
7 个页面 × 1500 与 375px 两种视口,见 §6.3.2)。
---