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:
@@ -320,8 +320,7 @@ function ExperimentsInner() {
|
||||
tools={
|
||||
<div className="row" style={{ gap: 8 }}>
|
||||
<input
|
||||
className="input"
|
||||
style={{ width: 200 }}
|
||||
className="input input--search"
|
||||
placeholder="搜索 ID / 因子 / 摘要"
|
||||
value={q}
|
||||
onChange={(e) => {
|
||||
@@ -331,8 +330,7 @@ function ExperimentsInner() {
|
||||
aria-label="搜索实验"
|
||||
/>
|
||||
<select
|
||||
className="input"
|
||||
style={{ width: 130 }}
|
||||
className="input input--filter"
|
||||
value={kind}
|
||||
onChange={(e) => {
|
||||
setKind(e.target.value);
|
||||
@@ -358,6 +356,15 @@ function ExperimentsInner() {
|
||||
<div style={{ padding: 18 }}>
|
||||
<SkeletonLines n={6} />
|
||||
</div>
|
||||
) : exps.length === 0 && (q || kind) ? (
|
||||
/* 筛选无结果 ≠ 没有归档:文案必须区分,否则用户会以为归档全丢了 */
|
||||
<Empty
|
||||
icon="search"
|
||||
title="没有匹配的归档"
|
||||
hint={`筛选条件:${[q ? `关键词「${q}」` : "", kind ? `类型「${experimentKindLabel(kind)}」` : ""]
|
||||
.filter(Boolean)
|
||||
.join(" · ")};共 ${total} 条归档。清除筛选即可看到全部。`}
|
||||
/>
|
||||
) : exps.length === 0 ? (
|
||||
<Empty
|
||||
icon="archive"
|
||||
@@ -384,12 +391,15 @@ function ExperimentsInner() {
|
||||
{exps.map((e) => (
|
||||
<tr key={e.id} className={detail?.id === e.id ? "row-active" : undefined}>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={picked.includes(e.id)}
|
||||
onChange={() => togglePick(e.id)}
|
||||
aria-label={`选择 ${e.id} 用于对比`}
|
||||
/>
|
||||
{/* 整个单元都是热区:16px 的方块本身达不到点击目标下限 */}
|
||||
<label className="check check--cell" title={`选择 ${e.id} 用于对比`}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={picked.includes(e.id)}
|
||||
onChange={() => togglePick(e.id)}
|
||||
aria-label={`选择 ${e.id} 用于对比`}
|
||||
/>
|
||||
</label>
|
||||
</td>
|
||||
<td className="cell-mono cell-strong">{e.id}</td>
|
||||
<td>{kindView(e.kind)}</td>
|
||||
|
||||
Reference in New Issue
Block a user