4.4 KiB
4.4 KiB
组合配方(Recipes)
单个组件的参数表说明「怎么用」,这些配方说明「怎么拼」。所有配方都只消费组件对外 API,不依赖内部文件。
配方 1:表单校验 + 提交反馈
const name = signal('');
const email = signal('');
const formRef = form({ onSubmit: (ok) => (ok ? result({ status: 'success', title: '已保存' }) : toast.error('请检查表单')) }, [
formItem({ label: '名称', value: name, required: true, rules: [{ min: 2 }] }, textField({ value: name })),
formItem({ label: '邮箱', value: email, required: true, rules: [{ pattern: /^\S+@\S+$/ }] }, textField({ value: email })),
]);
要点:
- 校验规则挂在
formItem,值信号只有一个来源(value同时给formItem与控件),避免两处各存一份。 - 提交结果用
toast(轻量、瞬时)或result(整页终态)表达,不要两者同时弹。
配方 2:危险操作二次确认
const open = signal(false);
const dangerOpen = (key: string) => {
open(true);
return modal({
title: `确认删除 ${key}?`,
open,
size: 'sm',
onClose: () => open(false),
footer: h('div', { class: 'row' },
button({ label: '取消', variant: 'ghost', onClick: () => open(false) }),
button({ label: '删除', variant: 'danger', onClick: () => { open(false); toast.success('已删除'); } })),
}, h('p', null, '删除后不可恢复。'));
};
要点:
modal自带焦点陷阱与 Esc 逐层关闭,不要在业务层再挂 Esc 监听。- 确认按钮用
variant: 'danger',取消用ghost,主次分明。
配方 3:表格筛选 + 分页 + 批量操作
const picked = signal<string[]>([]);
const page = signal(1);
dataTable({
data: rows,
columns: [{ key: 'name', header: '名称', sortable: true }, { key: 'city', header: '城市' }],
pageSize: 10,
page, // 同一 page 信号也给 pagination,二者自动联动
selectable: true,
selected: picked,
loading: saving,
});
pagination({ page, pageSize: 10, total: rows.length, jumpable: true, onChange: (p) => page(p) });
要点:
page信号是表格与分页器的唯一共享状态;外部改页码前自己钳制范围(core 的setPage不做钳制)。- 选中一律存
rowKey,翻页/筛选后仍然稳定;全选只作用于当前可见行。 - 批量操作按钮在
picked().length === 0时禁用,避免空提交。
配方 4:长列表数据加载与空态
const loading = signal(true);
const items = signal<ListItem[]>([]);
list({ items: items(), bordered: true, emptyText: loading() ? '加载中…' : '暂无数据' });
要点:
- 空态文案随加载态切换;列表本身会在
items为空时渲染empty,不需要业务层再判断一次。 - 需要骨架屏就换成
skeleton,加载完成再renderList成真实行。
配方 5:数据表 + 独立分页器 + 导出
const page = signal(1);
const table = dataTable({
data: rows,
columns: [
{ key: 'name', header: '姓名', sortable: true, width: 160 },
{ key: 'city', header: '城市', align: 'center', width: 'flex' },
],
page, // 受控页码:与外部 pagination 双向联动
selectable: true,
exportable: true, // 导出筛选 + 排序后的全量行(不是当前页)
exportName: '用户列表.csv',
});
const pager = pagination({ page, pageSize: 5, total: rows.length, jumpable: true, pageSizes: [5, 10, 20] });
要点:
- 页码只保留一个来源:
dataTable与pagination共传同一个page信号,翻页由内核写回,桥接层会钳制越界值。 exportable导出的是「筛选 + 排序后、未分页」的全量行,列序与表头一致、单元格走format;想导出当前页请自行读getVisibleRows()。- 列宽拖拽只对
width给数字的列生效('flex'列交给浏览器自适应);双击手柄回到声明宽度。 - 行选择一律按 rowKey 记账,筛选换掉可见行后已选仍在,批量操作前用已选计数确认。
通用约定
- 状态一律放信号(
signal/computed),组件只读写信号,不持有业务数据副本。 - 反馈分层:
toast右上角瞬时、notification右下角可长读、result整页终态、alert页内常驻。 - 样式不要写颜色与间距字面值,先跑
ua tokens查--ua-*(verify 会拦截未定义令牌与#hex)。