Files
UI/packages/tools/cli/recipes.md
T
2026-10-26 14:51:30 +08:00

6.0 KiB
Raw Blame History

组合配方(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 记账,筛选换掉可见行后已选仍在,批量操作前用已选计数确认。

配方 6:长表单编辑页(吸底操作条 + 提交锁定)

const saving = signal(false);
form(
  {
    onSubmit: (ok) => {
      if (!ok) return; // 失败时汇总条已定位到第一处错误,无需自己数红框
      saving(true);
      save().then(() => toast.success('已保存')).finally(() => saving(false));
    },
  },
  [
    formItem({ label: '标题', value: title, required: true, rules: [{ max: 20 }] }, textField({ value: title, maxCount: 20 })),
    formItem({ label: '权重', value: weight, hint: '0–100%,一位小数' }, inputNumber({ value: weight, min: 0, max: 100, step: 0.1, precision: 1 })),
    formItem({ label: '上架', value: online }, switchButton({ label: '保存后自动上架', checked: online, loading: saving })),
  ],
);
affix({ position: 'bottom', offset: 0 }, h('div', { class: 'row' },
  button({ label: '保存', type: 'submit', disabled: saving }),
));

要点:

  • 长表单的操作条用 affix({ position: 'bottom' }) 贴底固定,翻到表单底部按钮也不丢;提交按钮 type: 'submit' 走 form 聚合校验。
  • 校验失败时 form 顶部的汇总条会播报「有 N 项需要修改」并聚焦第一处(errorSummary: false 可关);hint 写格式说明,报错时自动让位。
  • 提交期间用同一个 saving 信号锁三处:按钮 disabled、开关 loading(转圈 + aria-busy)、业务请求出口,防重复提交与中途改状态。
  • 长度约束一处写够:textField 的 maxCount 设原生上限并实时计数,formItem 的 rules: [{ max }] 只做提交兜底,两处数字保持一致。

通用约定

  • 状态一律放信号(signal / computed),组件只读写信号,不持有业务数据副本。
  • 反馈分层:toast 右上角瞬时、notification 右下角可长读、result 整页终态、alert 页内常驻。
  • 样式不要写颜色与间距字面值,先跑 ua tokens 查 --ua-*(verify 会拦截未定义令牌与 #hex)。