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:
+183
-14
@@ -51,6 +51,18 @@
|
||||
--r-md: 12px;
|
||||
--r-lg: 16px;
|
||||
|
||||
/* 控件高度令牌 —— 对齐的根基。
|
||||
为什么必须显式定高:input / select / button 的原生行高与内边距各不相同,
|
||||
靠 padding 拼高度会得到 34 / 36.8 / 38.8 三种结果(Chrome 的 date 输入还会
|
||||
多出 2px),同一行里就参差不齐。显式 height 是唯一能保证等高的做法。 */
|
||||
--ctl-h-sm: 28px; /* 密集行的小按钮 / 图标按钮 */
|
||||
--ctl-h-md: 34px; /* 标准控件:输入框 / 下拉 / 按钮 */
|
||||
--ctl-h-lg: 38px; /* 主操作控件(大号输入框、登录式表单) */
|
||||
|
||||
/* 控件横向内边距:与高度同源,保证左右留白一致 */
|
||||
--ctl-px: 12px;
|
||||
--ctl-px-sm: 10px;
|
||||
|
||||
/* 间距(4px 节奏) */
|
||||
--sp-1: 4px;
|
||||
--sp-2: 8px;
|
||||
@@ -395,7 +407,10 @@ textarea {
|
||||
background: var(--surface-3);
|
||||
color: var(--text-1);
|
||||
border-radius: var(--r-sm);
|
||||
padding: 7px 14px;
|
||||
/* 显式高度 + 横向内边距(不再用「上下 padding 拼高度」):
|
||||
这样按钮与同排的输入框/下拉必然等高,换字体或换浏览器也不会错位。 */
|
||||
height: var(--ctl-h-md);
|
||||
padding: 0 var(--ctl-px);
|
||||
font-size: var(--fs-sm);
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
@@ -442,7 +457,8 @@ textarea {
|
||||
}
|
||||
|
||||
.btn--sm {
|
||||
padding: 4px 10px;
|
||||
height: var(--ctl-h-sm);
|
||||
padding: 0 var(--ctl-px-sm);
|
||||
font-size: var(--fs-xs);
|
||||
border-radius: var(--r-xs);
|
||||
}
|
||||
@@ -456,8 +472,8 @@ textarea {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
width: var(--ctl-h-sm);
|
||||
height: var(--ctl-h-sm);
|
||||
border-radius: var(--r-sm);
|
||||
color: var(--text-2);
|
||||
background: transparent;
|
||||
@@ -471,21 +487,39 @@ textarea {
|
||||
color: var(--text-1);
|
||||
}
|
||||
|
||||
/* 大号图标按钮:顶栏等需要更舒服点击目标的位置(仍属于 28/34/38 三级刻度) */
|
||||
.icon-btn--lg {
|
||||
width: var(--ctl-h-lg);
|
||||
height: var(--ctl-h-lg);
|
||||
}
|
||||
|
||||
/* ---------- 表单 ---------- */
|
||||
|
||||
.input,
|
||||
select.input,
|
||||
textarea.input {
|
||||
width: 100%;
|
||||
/* 与 .btn 同一个高度令牌:输入框与按钮同排必然等高。
|
||||
Chrome 的原生 date 输入比其它控件高 2px,靠 height 一并压平。 */
|
||||
height: var(--ctl-h-md);
|
||||
background: #0d1424;
|
||||
border: 1px solid var(--line-strong);
|
||||
color: var(--text-1);
|
||||
border-radius: var(--r-sm);
|
||||
padding: 7px 10px;
|
||||
padding: 0 var(--ctl-px-sm);
|
||||
font-size: var(--fs-sm);
|
||||
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
/* 多行输入不套用控件定高(内容行数决定高度) */
|
||||
textarea.input {
|
||||
height: auto;
|
||||
min-height: 76px;
|
||||
padding: 8px var(--ctl-px-sm);
|
||||
line-height: 1.6;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.input:hover {
|
||||
border-color: rgba(150, 165, 195, 0.42);
|
||||
}
|
||||
@@ -514,19 +548,53 @@ input[type="date"].input {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
/* 原生勾选控件:尺寸统一到 16px,并把「点击目标」交给外层 label(.check / .radio-row)——
|
||||
16px 的方块本身永远达不到 28px 触控目标,靠 label 撑开热区才是正确做法。 */
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0;
|
||||
flex: none;
|
||||
accent-color: var(--accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 单行勾选项(与相邻的下拉/输入同高,保证整行对齐一致) */
|
||||
.check {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-2);
|
||||
min-height: var(--ctl-h-md);
|
||||
color: var(--text-2);
|
||||
font-size: var(--fs-sm);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.check:hover {
|
||||
color: var(--text-1);
|
||||
}
|
||||
|
||||
/* 表格单元里的勾选框:把整个单元做成热区(16px 方块在表格里几乎点不中)。
|
||||
用 label 包住 → 点文字/空白都能选中,行高即为常规控件高度。 */
|
||||
.check--cell {
|
||||
min-height: var(--ctl-h-md);
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
padding: 0 var(--sp-1);
|
||||
}
|
||||
|
||||
input[type="number"].input,
|
||||
input[type="date"].input {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 数值/日期字段按内容给宽度:内容只有几位数字,拉满整行既难读也难与相邻控件对齐 */
|
||||
.form-grid .field > input[type="number"].input {
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -539,11 +607,31 @@ input[type="date"].input {
|
||||
font-size: var(--fs-xs);
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.2px;
|
||||
/* 无标签时也占一行高度(栅格里标签行高不一致会让控件上下错位) */
|
||||
min-height: 17px;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
.field__hint {
|
||||
color: var(--text-3);
|
||||
font-size: var(--fs-xs);
|
||||
line-height: 1.5;
|
||||
/* 提示不裁切:说明文字被 line-clamp 截断等于把信息藏起来。
|
||||
跨行对齐由 .form-grid 的 align-items:end 保证,不靠裁切文案。 */
|
||||
}
|
||||
|
||||
/* 工具条里的检索框 / 筛选器:宽度由类统一,避免每处写内联像素宽度 */
|
||||
.input--search {
|
||||
width: min(240px, 100%);
|
||||
}
|
||||
|
||||
.input--filter {
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
/* 选项文案较长的选择器(如「代码 名称(收益)」)给更宽的宽度 */
|
||||
.input--picker {
|
||||
width: min(320px, 100%);
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
@@ -553,9 +641,74 @@ input[type="date"].input {
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
/* 整行字段(长文本框、日期区间、条件编辑器) */
|
||||
.field--wide {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.form-grid .btn {
|
||||
align-self: end;
|
||||
height: 34px;
|
||||
height: var(--ctl-h-md);
|
||||
}
|
||||
|
||||
/* 小节标题(表单内的分组标题,与 .card__title 区分:更小、不抢层级) */
|
||||
.form-section__title {
|
||||
font-size: var(--fs-sm);
|
||||
font-weight: 600;
|
||||
color: var(--text-1);
|
||||
}
|
||||
|
||||
/* 因子行:表头 + 栅格行,靠栅格列宽保证「因子 / 权重 / 操作」三列严格对齐。
|
||||
反面做法是在 JSX 里给每个控件写死像素宽度(style={{width:220}}),
|
||||
一旦文案或字体变化就会错位。 */
|
||||
.factor-rows {
|
||||
display: grid;
|
||||
gap: var(--sp-2);
|
||||
}
|
||||
|
||||
.factor-rows__head,
|
||||
.factor-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(150px, 1fr) 118px auto;
|
||||
align-items: center;
|
||||
gap: var(--sp-2);
|
||||
}
|
||||
|
||||
.factor-rows__head {
|
||||
color: var(--text-3);
|
||||
font-size: var(--fs-xs);
|
||||
padding-bottom: 2px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.factor-row .btn {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
/* 选股条件行:与因子行同一套栅格思路(表头 + 成列),
|
||||
列宽由 CSS 决定,不在 JSX 里写内联像素宽度。 */
|
||||
.cond-rows {
|
||||
display: grid;
|
||||
gap: var(--sp-2);
|
||||
}
|
||||
|
||||
.cond-rows__head,
|
||||
.cond-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(150px, 1fr) 84px minmax(96px, 140px) auto;
|
||||
align-items: center;
|
||||
gap: var(--sp-2);
|
||||
}
|
||||
|
||||
.cond-rows__head {
|
||||
color: var(--text-3);
|
||||
font-size: var(--fs-xs);
|
||||
padding-bottom: 2px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.cond-row .btn {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.row {
|
||||
@@ -995,6 +1148,9 @@ table.tbl {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
/* 历史记录 chip 是可点击控件:高度必须达到点击目标下限,
|
||||
不能被 padding 压到 27px 这种「看得见点不准」的尺寸 */
|
||||
min-height: var(--ctl-h-sm);
|
||||
background: var(--surface-3);
|
||||
border: 1px solid var(--line-strong);
|
||||
border-radius: 999px;
|
||||
@@ -1002,6 +1158,13 @@ table.tbl {
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--text-2);
|
||||
}
|
||||
button.chip {
|
||||
cursor: pointer;
|
||||
}
|
||||
button.chip:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--text-1);
|
||||
}
|
||||
|
||||
.chip b {
|
||||
color: var(--text-1);
|
||||
@@ -1675,24 +1838,30 @@ table.tbl {
|
||||
.radio-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gap: var(--sp-1);
|
||||
}
|
||||
.radio-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 7px;
|
||||
font-size: 13px;
|
||||
align-items: center;
|
||||
gap: var(--sp-2);
|
||||
/* 点击目标由整行承担(16px 的圆点本身太小),并与 .check 同一高度节奏 */
|
||||
min-height: var(--ctl-h-sm);
|
||||
padding: 2px 0;
|
||||
font-size: var(--fs-sm);
|
||||
color: var(--text-2);
|
||||
cursor: pointer;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.radio-row input {
|
||||
margin-top: 2px;
|
||||
flex: none;
|
||||
}
|
||||
.radio-row:hover {
|
||||
color: var(--text-1);
|
||||
}
|
||||
/* 单选行里的说明文字:与选项标题同一行时用弱化色,不抢视线 */
|
||||
.radio-row .hint {
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
/* ---------- 归档详情:元数据条 / KV / 规格表 / 代码块 ---------- */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user