iter(g296): 修正表单校验失败仍提交

This commit is contained in:
HIsheR
2026-10-26 23:37:15 +08:00
parent 8dd31bde4b
commit 2c0a760f18
6 changed files with 25 additions and 7 deletions
+8 -2
View File
@@ -30,6 +30,10 @@ async function checkRuleAsync(value: string, rule: FormRule): Promise<string | n
return r === true ? null : typeof r === 'string' ? r : rule.message ?? '校验未通过';
}
/*** 字段容器里的焦点落点:自绘控件(非搜索式下拉、评分、开关)没有原生 input,只查 input 会拿到 null,退到按钮 / 带 tabindex 的节点 ***/
const focusableOf = (box: HTMLElement): HTMLElement | null =>
box.querySelector<HTMLElement>('input:not([type="hidden"]), textarea, select, button, [tabindex]:not([tabindex="-1"])');
/*** 表单字段:label + 控件插槽 + 错误区;返回句柄(同步 / 异步两种校验)供 form 聚合 ***/
export function formItem(props: FormItemProps, control: HTMLElement): FormItemHandle {
// —— 状态集中声明 ——
@@ -120,13 +124,15 @@ export function form(props: FormProps, items: FormItemHandle[]): HTMLElement {
if (!msg) continue; // 卫语句:本项通过
bad += 1;
if (firstBad) continue; // 只把首个错误控件留作焦点目标
firstBad = item.el.querySelector('input, textarea, select') as HTMLElement | null;
const target = focusableOf(item.el);
if (!target) item.el.tabIndex = -1; // 整块无可聚焦控件时把字段容器本身设为焦点落点(-1 不进 Tab 序,只接受程序聚焦)
firstBad = target ?? item.el;
}
submitBtn.disabled = false;
running = false;
summary.hidden = bad === 0;
summary.textContent = bad ? `有 ${bad} 项需要修改,已定位到第一处` : '';
if (firstBad) { firstBad.focus(); props.onError?.(bad); return; } // 卫语句:有错误不提交
if (bad > 0) { firstBad?.focus(); props.onError?.(bad); return; } // 有错即中止:判据用错误数而非焦点目标,否则自绘控件字段会漏过去直接提交
props.onSubmit?.();
};
+2 -1
View File
@@ -1905,7 +1905,8 @@
"需要给字段加格式说明就传 hint:平时显示在控件下方,校验失败时自动让位给错误文案,同一位置不堆两行",
"字段多的编辑页传 columns: 2 或 3 走多列栅格:列间距与窄屏堆叠沿用 grid,按钮区始终整行;单列表单不要传(默认 1)",
"多列表单里想整宽就传 span:formItem({ span: 2 }, textarea(...)) 会占满两列,窄屏单列时自动忽略跨列(否则会撑出横向滚动)",
"字段要按另一个字段的值决定是否出现(选了「其他」才出现输入框),给 formItem 传 visible 信号:隐藏项自动清掉错误态并退出校验,不用自己在 items 数组里增删"
"字段要按另一个字段的值决定是否出现(选了「其他」才出现输入框),给 formItem 传 visible 信号:隐藏项自动清掉错误态并退出校验,不用自己在 items 数组里增删",
"校验失败必定中止提交:判据是错误条数而不是「有没有找到焦点目标」,所以只有自绘控件(非搜索式下拉 / 评分 / 开关,容器里没有原生 input)的字段也能拦住;焦点定位顺序是控件 → 触发器 → 字段容器(后者临时 tabindex=-1,只接受程序聚焦,不污染 Tab 序)"
]
},
{