iter(g318): 补配方 20 示范分步表单向导的步间校验
This commit is contained in:
@@ -431,6 +431,42 @@ card({ title: '发布检查单(可拖动,Alt+↑↓ 键盘搬位)' },
|
||||
- 拖到最近滚动容器的上下沿会按帧自动滚(感应带 48px),长清单一屏装不下也能拖到位;容器是整页滚动时组件不接管,交给浏览器。跨列表搬运用两个实例各持一个顺序信号。
|
||||
- 条目文案要唯一——重名会让「谁被搬走了」失去参照,需要重复内容时把序号拼进文案(`'确认条款 #1'`)。
|
||||
|
||||
## 配方 20:分步表单向导(步进条 + 按步校验 + 完成反馈)
|
||||
|
||||
```ts
|
||||
const step = signal(0); // 当前步索引:stepper 与内容区共用一份信号
|
||||
const name = signal(''); const phone = signal(''); const remark = signal('');
|
||||
const account = formItem({ label: '账号名', required: true, value: name, rules: [{ min: 2, max: 12 }] }, textField({ value: name }));
|
||||
const contact = formItem({ label: '手机号', required: true, value: phone, rules: [{ pattern: /^1\d{10}$/, message: '请输入 11 位手机号' }] }, textField({ value: phone }));
|
||||
const note = formItem({ label: '备注', value: remark }, textarea({ value: remark }));
|
||||
const pages = [[account], [contact, note], []]; // 每步的字段句柄,末页是完成页:推进校验与回跳重放都读这一份
|
||||
const body = h('div', {});
|
||||
effect(() => { body.replaceChildren(...pages[step()].map((f) => f.el)); }); // 步进即换页:只挂当前步的字段,末页自然留白
|
||||
const last = pages.length - 1; // 末步下标:步进条、边界钳制与提交判断共用
|
||||
const go = (delta) => { // delta>0 是「下一步」:先过当前步校验,不过就不换页
|
||||
if (delta > 0 && pages[step()].some((f) => !f.validate())) return;
|
||||
step(Math.min(Math.max(step() + delta, 0), last));
|
||||
};
|
||||
card({ title: '开通向导' },
|
||||
stepper({ steps: [{ label: '账号' }, { label: '联系' }, { label: '完成' }], active: step, clickable: true }),
|
||||
body,
|
||||
button({ label: '上一步', variant: 'ghost', disabled: computed(() => step() === 0), onClick: () => go(-1) }),
|
||||
button({ label: '下一步', onClick: () => {
|
||||
if (step() === last) { toast.success('已提交开通申请'); step(0); return; } // 末步即提交:同步校验已在前两步拦过,直接收尾归零
|
||||
go(1);
|
||||
} }),
|
||||
);
|
||||
```
|
||||
|
||||
要点:
|
||||
- `stepper` 的 `active` 与字段容器读同一个 `step` 信号:点步骤条回跳、点「上一步」、提交后归零都只改这一个信号,不存在两份步号。
|
||||
- 「下一步」先对当前步的 formItem 句柄逐个 `validate()`,有失败项就停在原地标红——把校验拦在步与步之间,不要攒到最后一步一起报错,用户会迷失在哪一步填错。
|
||||
- `clickable: true` 只放行**已完成**步骤的回跳,点不存在的未来步骤不会生效,所以回跳不会绕过校验;回跳后再次「下一步」会重放该步校验,已填过的值不会丢(信号还在)。
|
||||
- 换页用 `body.replaceChildren()` 重挂当前步字段:formItem 句柄是稳定对象,重复挂载不会重建控件,信号值与错误态都保留;末页句柄数组为空,`replaceChildren()` 空参即清空,不用特判。
|
||||
- 「下一步」按钮的 label 是静态的,想在末步显示「提交」就用两个按钮按 `step()` 条件渲染(`h` 里读信号的函数会跟随重渲染);上面的示例为省篇幅在 onClick 里判末步。
|
||||
- 末步提交前若还有异步规则(如邀请码查重),改用 formItem 的 `validateAsync` 聚合 await,校验期间禁用提交按钮;纯同步表单 `validate()` 已足够。
|
||||
- 提交成功给 `toast.success` 反馈并把 `step` 归零,向导可以立即重开;要保留数据就别归零,改跳一个只读的完成页。
|
||||
|
||||
## 通用约定
|
||||
|
||||
- 状态一律放信号(`signal` / `computed`),组件只读写信号,不持有业务数据副本。
|
||||
|
||||
Reference in New Issue
Block a user