iter(g135): 表单支持多列布局

This commit is contained in:
HIsheR
2026-10-26 15:27:18 +08:00
parent 7b41630b96
commit 21950d9ddd
7 changed files with 35 additions and 7 deletions
+8
View File
@@ -1436,6 +1436,14 @@
"acceptance": "新增 ua sync-counts(幂等,无变化不写盘)把三份文档的「N 个组件 / N 个 DOM 组件 / 共 N 个」统一改写为清单实际数量;verify 的计数正则覆盖 README 标题形态并提示修法;npm run check 全绿",
"notes": "自补:构建/门禁工具链改进(实测把 README 标题改成 61 后 verify 能报行号,sync-counts 一键修回)",
"status": "done"
},
{
"id": "g135",
"title": "表单支持多列布局 columns",
"motivation": "字段多的编辑页只能一列往下堆,长表单要滚很久;页面里已有 grid,form 却没用上,调用方只能自己拆成多个 form(校验被割裂)",
"acceptance": "form 新增 columns(1/2/3),>1 时字段走 grid 铺列、窄屏自动单列、按钮区始终整行;单向依赖(form 复用 grid);manifest props/steps 与 demo 同步;npm run check 全绿",
"notes": "自补:既有组件可用性细节",
"status": "open"
}
]
}
+9 -2
View File
@@ -1,6 +1,6 @@
{
"$schema": "ui-arch-registry-index/0.1",
"generatedAt": "2026-10-26T07:24:00.739Z",
"generatedAt": "2026-10-26T07:26:53.819Z",
"repoRoot": ".",
"packages": [
{
@@ -1772,6 +1772,12 @@
"d": "布局方向;缺省 vertical(horizontal 为标签左置)",
"df": "vertical"
},
{
"n": "columns",
"t": "1 | 2 | 3",
"d": "字段列数:>1 时字段走栅格铺列(沿用 grid 的窄屏单列),按钮区始终整行;缺省 1",
"df": "1"
},
{
"n": "submitText",
"t": "string",
@@ -1851,7 +1857,8 @@
"单行输入框内按 Enter 等同点击提交(textarea 内仍是换行);提交按钮为 type=\"submit\",不会与点击回调重复触发",
"rules.validator 返回 Promise 即为异步规则(如「邀请码是否已被使用」远程校验):提交时逐项 await,校验期间提交按钮禁用,结论按轮次取舍避免乱序覆盖",
"formItem 返回 { el, validate, validateAsync } 句柄:validate 只跑同步规则(供 blur/input 即时反馈),validateAsync 逐条 await 供 form 聚合提交",
"需要给字段加格式说明就传 hint:平时显示在控件下方,校验失败时自动让位给错误文案,同一位置不堆两行"
"需要给字段加格式说明就传 hint:平时显示在控件下方,校验失败时自动让位给错误文案,同一位置不堆两行",
"字段多的编辑页传 columns: 2 或 3 走多列栅格:列间距与窄屏堆叠沿用 grid,按钮区始终整行;单列表单不要传(默认 1)"
]
},
{
+2 -2
View File
@@ -487,9 +487,9 @@ const content = h(
section(
'sec-formlab',
'表单校验',
'form + formItem:必填 / 长度 / 正则同步校验,validator 返回 Promise 即异步规则(提交会等待);全部通过才触发提交。校验不通过时顶部出现「有 N 项需要修改」汇总条(role=alert 会被读屏播报)并聚焦第一处,onError 拿到不通字段数。',
'form + formItem:必填 / 长度 / 正则同步校验,validator 返回 Promise 即异步规则(提交会等待);全部通过才触发提交。校验不通过时顶部出现「有 N 项需要修改」汇总条(role=alert 会被读屏播报)并聚焦第一处,onError 拿到不通字段数。本例 columns: 2 走两列栅格,窄屏自动堆叠,按钮区始终整行。',
form(
{ submitText: '注册', onSubmit: () => formResult(`提交成功:${username()} / ${email()} / ${city()}`) },
{ columns: 2, submitText: '注册', onSubmit: () => formResult(`提交成功:${username()} / ${email()} / ${city()}`) },
[
formItem({ label: '用户名', required: true, value: username, rules: [{ min: 2, max: 12, message: '用户名需 2-12 个字符' }] }, textField({ placeholder: '2-12 个字符', value: username })),
formItem({ label: '邮箱', required: true, value: email, hint: '用于登录与找回密码,不会公开', rules: [{ pattern: /^[\w.-]+@[\w-]+\.[\w.]+$/, message: '邮箱格式不正确' }] }, textField({ placeholder: 'you@example.com', value: email })),
+3 -1
View File
@@ -1,6 +1,6 @@
# @ui-arch 组件库 Agent 文档(无图形版)
> 自动生成于 2026-10-26T07:24:00.739Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 自动生成于 2026-10-26T07:26:53.819Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
## 快速上手
@@ -922,6 +922,7 @@ const open = signal(false); drawer({ title: '详情', open, width: 360, onClose:
参数:
- layout(默认 vertical):'vertical' | 'horizontal' —— 布局方向;缺省 vertical(horizontal 为标签左置)
- columns(默认 1):1 | 2 | 3 —— 字段列数:>1 时字段走栅格铺列(沿用 grid 的窄屏单列),按钮区始终整行;缺省 1
- submitText:string —— 提交按钮文案;缺省「提交」
- resetText:string —— 重置按钮文案;缺省不渲染
- onSubmit(回调):() => void —— 全部字段校验通过后的提交回调
@@ -955,6 +956,7 @@ const name = signal(''); const f = form({ onSubmit: submit }, [formItem({ label:
10. rules.validator 返回 Promise 即为异步规则(如「邀请码是否已被使用」远程校验):提交时逐项 await,校验期间提交按钮禁用,结论按轮次取舍避免乱序覆盖
11. formItem 返回 { el, validate, validateAsync } 句柄:validate 只跑同步规则(供 blur/input 即时反馈),validateAsync 逐条 await 供 form 聚合提交
12. 需要给字段加格式说明就传 hint:平时显示在控件下方,校验失败时自动让位给错误文案,同一位置不堆两行
13. 字段多的编辑页传 columns: 2 或 3 走多列栅格:列间距与窄屏堆叠沿用 grid,按钮区始终整行;单列表单不要传(默认 1)
现场演示:apps/docs/demo.html#sec-formlab
+4 -1
View File
@@ -1,5 +1,6 @@
import { h } from '@ui-arch/frame';
import { button } from './button.js';
import { grid } from './grid.js';
import type { FormItemHandle, FormItemProps, FormProps, FormRule } from './types.js';
/*** 执行规则的同步部分(required / min / max / pattern);null 通过,字符串为错误消息 ***/
@@ -124,11 +125,13 @@ export function form(props: FormProps, items: FormItemHandle[]): HTMLElement {
props.onSubmit?.();
};
const fields = items.map((i) => i.el); // 字段节点:单列直接铺,多列交给栅格
const board = props.columns && props.columns > 1 ? grid({ columns: props.columns, gutter: 'lg' }, ...fields) : fields; // 多列布局复用 grid
const el = h(
'form',
{ class: props.layout === 'horizontal' ? 'ua-form ua-form-h' : 'ua-form' },
props.errorSummary === false ? null : summary, // 汇总条:关掉时连节点都不插
...items.map((i) => i.el),
board, // 多列时是一层 grid,单列时是字段数组(h 自动扁平化)
actions,
);
el.addEventListener('submit', (e) => {
+1
View File
@@ -400,6 +400,7 @@ export interface FormItemHandle {
/*** 表单容器入参 ***/
export interface FormProps {
layout?: 'vertical' | 'horizontal'; // 布局方向;缺省 vertical
columns?: 1 | 2 | 3; // 字段列数:>1 时字段走栅格铺列(窄屏自动单列);缺省 1
submitText?: string; // 提交按钮文案;缺省「提交」
resetText?: string; // 重置按钮文案;缺省不渲染重置按钮
onSubmit?: () => void; // 全部字段校验通过后的提交回调
+8 -1
View File
@@ -1643,6 +1643,12 @@
"d": "布局方向;缺省 vertical(horizontal 为标签左置)",
"df": "vertical"
},
{
"n": "columns",
"t": "1 | 2 | 3",
"d": "字段列数:>1 时字段走栅格铺列(沿用 grid 的窄屏单列),按钮区始终整行;缺省 1",
"df": "1"
},
{
"n": "submitText",
"t": "string",
@@ -1722,7 +1728,8 @@
"单行输入框内按 Enter 等同点击提交(textarea 内仍是换行);提交按钮为 type=\"submit\",不会与点击回调重复触发",
"rules.validator 返回 Promise 即为异步规则(如「邀请码是否已被使用」远程校验):提交时逐项 await,校验期间提交按钮禁用,结论按轮次取舍避免乱序覆盖",
"formItem 返回 { el, validate, validateAsync } 句柄:validate 只跑同步规则(供 blur/input 即时反馈),validateAsync 逐条 await 供 form 聚合提交",
"需要给字段加格式说明就传 hint:平时显示在控件下方,校验失败时自动让位给错误文案,同一位置不堆两行"
"需要给字段加格式说明就传 hint:平时显示在控件下方,校验失败时自动让位给错误文案,同一位置不堆两行",
"字段多的编辑页传 columns: 2 或 3 走多列栅格:列间距与窄屏堆叠沿用 grid,按钮区始终整行;单列表单不要传(默认 1)"
]
},
{