From 3143a8bd34449c4a5a707dd0761be9f1ce7185bc Mon Sep 17 00:00:00 2001 From: HIsheR Date: Mon, 26 Oct 2026 11:48:31 +0800 Subject: [PATCH] =?UTF-8?q?iter(g37):=20llms-full=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E7=BB=84=E5=90=88=E9=85=8D=E6=96=B9=E7=AB=A0=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ua/backlog.json | 8 ++++ AGENTS.md | 2 +- llms-full.txt | 82 +++++++++++++++++++++++++++++++++++ packages/tools/cli/bin/ua.js | 9 ++++ packages/tools/cli/recipes.md | 80 ++++++++++++++++++++++++++++++++++ 5 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 packages/tools/cli/recipes.md diff --git a/.ua/backlog.json b/.ua/backlog.json index cb02da0..f8d03e0 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -652,6 +652,14 @@ "acceptance": "result({ status, title, description, extra }) 四档语义色 + 居中终态布局 + role=\"status\";组件数 43→44 三处计数同步;npm run check 全绿", "status": "done", "notes": "自补:组件补充" + }, + { + "id": "g37", + "title": "llms-full 增加组合配方章节", + "motivation": "清单只能描述单组件参数,Agent 拼多组件(表单+校验+提交反馈、表格+分页+批量、危险操作确认)时没有权威范例,容易各写一套", + "acceptance": "packages/tools/cli/recipes.md 作为唯一来源,ua docs 时追加到 llms-full.txt 末尾;缺少文件时不报错;npm run check 全绿", + "status": "open", + "notes": "自补:文档与 Agent 查询体验" } ] } diff --git a/AGENTS.md b/AGENTS.md index 118ec53..1824e56 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -50,7 +50,7 @@ 2. **CLI**:`node packages/tools/cli/bin/ua.js list | info | index | docs | style | verify | changelog` -3. **静态索引**:`.ua/index.json`(机器可读索引)、`llms.txt`(速览)或 **`llms-full.txt`(全量 Agent 文档:逐组件参数 / 回调 / 手把手步骤 / 令牌,无图形纯文本,喂给 Agent 首选)**。 +3. **静态索引**:`.ua/index.json`(机器可读索引)、`llms.txt`(速览)或 **`llms-full.txt`(全量 Agent 文档:逐组件参数 / 回调 / 手把手步骤 / 令牌 / 组合配方,无图形纯文本,喂给 Agent 首选)**。 ## 贡献新组件的强制约定 diff --git a/llms-full.txt b/llms-full.txt index f31ff74..c282101 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1353,3 +1353,85 @@ node packages/tools/cli/bin/ua.js |docs|tokens - --ua-font-size-lg: 14px 皮肤包只允许消费这些变量:写死 #hex 会被 verify 拦截,拼错变量名会被令牌引用校验拦截。 + + +# 组合配方(Recipes) + +> 单个组件的参数表说明「怎么用」,这些配方说明「怎么拼」。所有配方都只消费组件对外 API,不依赖内部文件。 + +## 配方 1:表单校验 + 提交反馈 + +```ts +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:危险操作二次确认 + +```ts +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:表格筛选 + 分页 + 批量操作 + +```ts +const picked = signal([]); +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:长列表数据加载与空态 + +```ts +const loading = signal(true); +const items = signal([]); +list({ items: items(), bordered: true, emptyText: loading() ? '加载中…' : '暂无数据' }); +``` + +要点: +- 空态文案随加载态切换;列表本身会在 `items` 为空时渲染 `empty`,不需要业务层再判断一次。 +- 需要骨架屏就换成 `skeleton`,加载完成再 `renderList` 成真实行。 + +## 通用约定 + +- 状态一律放信号(`signal` / `computed`),组件只读写信号,不持有业务数据副本。 +- 反馈分层:`toast` 右上角瞬时、`notification` 右下角可长读、`result` 整页终态、`alert` 页内常驻。 +- 样式不要写颜色与间距字面值,先跑 `ua tokens` 查 `--ua-*`(verify 会拦截未定义令牌与 `#hex`)。 diff --git a/packages/tools/cli/bin/ua.js b/packages/tools/cli/bin/ua.js index fc76f0f..3e8119d 100644 --- a/packages/tools/cli/bin/ua.js +++ b/packages/tools/cli/bin/ua.js @@ -359,12 +359,21 @@ function renderAgentDocs(index, tokens) { } lines.push('---', ''); } +/*** 读取组合配方(与 CLI 同目录的 recipes.md),缺失时返回空串 ***/ +function loadRecipes() { + const p = path.join(ROOT, 'packages', 'tools', 'cli', 'recipes.md'); + return fs.existsSync(p) ? fs.readFileSync(p, 'utf8').trim() : ''; +} + const tokenList = loadTokenList(); // 令牌唯一来源:tokens.css(含字号、间距复合值) if (tokenList.length) { lines.push('## 设计令牌(--ua-*)', ''); for (const t of tokenList) lines.push(`- ${t.cssVar}: ${t.value}${t.dark && t.dark !== t.value ? `(暗色 ${t.dark})` : ''}`); lines.push('', '皮肤包只允许消费这些变量:写死 #hex 会被 verify 拦截,拼错变量名会被令牌引用校验拦截。', ''); } + + const recipes = loadRecipes(); // 组合配方:跨组件的拼装指导,清单参数表表达不了 + if (recipes) lines.push('', recipes, ''); return lines.join('\n'); } diff --git a/packages/tools/cli/recipes.md b/packages/tools/cli/recipes.md new file mode 100644 index 0000000..5381ccd --- /dev/null +++ b/packages/tools/cli/recipes.md @@ -0,0 +1,80 @@ +# 组合配方(Recipes) + +> 单个组件的参数表说明「怎么用」,这些配方说明「怎么拼」。所有配方都只消费组件对外 API,不依赖内部文件。 + +## 配方 1:表单校验 + 提交反馈 + +```ts +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:危险操作二次确认 + +```ts +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:表格筛选 + 分页 + 批量操作 + +```ts +const picked = signal([]); +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:长列表数据加载与空态 + +```ts +const loading = signal(true); +const items = signal([]); +list({ items: items(), bordered: true, emptyText: loading() ? '加载中…' : '暂无数据' }); +``` + +要点: +- 空态文案随加载态切换;列表本身会在 `items` 为空时渲染 `empty`,不需要业务层再判断一次。 +- 需要骨架屏就换成 `skeleton`,加载完成再 `renderList` 成真实行。 + +## 通用约定 + +- 状态一律放信号(`signal` / `computed`),组件只读写信号,不持有业务数据副本。 +- 反馈分层:`toast` 右上角瞬时、`notification` 右下角可长读、`result` 整页终态、`alert` 页内常驻。 +- 样式不要写颜色与间距字面值,先跑 `ua tokens` 查 `--ua-*`(verify 会拦截未定义令牌与 `#hex`)。