From 8ac71c4efdbb4b0b7a009148b7bf3d3a7adffb57 Mon Sep 17 00:00:00 2001 From: HIsheR Date: Mon, 26 Oct 2026 11:47:48 +0800 Subject: [PATCH] =?UTF-8?q?iter(g36):=20=E6=96=B0=E5=A2=9E=20result=20?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E9=A1=B5=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ua/backlog.json | 8 ++++++ .ua/index.json | 40 ++++++++++++++++++++++++++++-- AGENTS.md | 2 +- ARCHITECTURE.md | 2 +- README.md | 6 ++--- apps/demo/main.ts | 12 +++++++++ llms-full.txt | 27 +++++++++++++++++++- llms.txt | 1 + packages/skin/ui/src/index.ts | 1 + packages/skin/ui/src/result.ts | 18 ++++++++++++++ packages/skin/ui/src/types.ts | 8 ++++++ packages/skin/ui/ua.manifest.json | 38 +++++++++++++++++++++++++++- packages/skin/ui/ui.css | 41 +++++++++++++++++++++++++++++++ 13 files changed, 195 insertions(+), 9 deletions(-) create mode 100644 packages/skin/ui/src/result.ts diff --git a/.ua/backlog.json b/.ua/backlog.json index d87a4d3..2b0cd60 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -644,6 +644,14 @@ "acceptance": "notification({ title, description, variant, duration, closable, action }) 右下角堆叠(与 toast 分区)、超出 3 条挤掉最早、返回 { el, close }、duration 0 常驻;组件数 42→43 三处计数同步;npm run check 全绿", "status": "done", "notes": "自补:组件补充;与 toast 分区避免视觉冲突" + }, + { + "id": "g36", + "title": "新增 result 结果页组件", + "motivation": "提交成功/校验失败/权限不足这类终态页每次手写图标+标题+按钮,语义色和留白都不统一", + "acceptance": "result({ status, title, description, extra }) 四档语义色 + 居中终态布局 + role=\"status\";组件数 43→44 三处计数同步;npm run check 全绿", + "status": "open", + "notes": "自补:组件补充" } ] } diff --git a/.ua/index.json b/.ua/index.json index f29dab7..4ff0503 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T03:46:32.583Z", + "generatedAt": "2026-10-26T03:47:28.088Z", "repoRoot": ".", "packages": [ { @@ -186,7 +186,8 @@ "carousel(props):轮播,items 横排位移,指示点/箭头/←→ 键切换,autoplay 毫秒自动前进且悬停暂停,index 信号可外部受控", "tree(props):树形,递归节点 + 箭头展开/收起 + 点选节点,键盘 Enter 选中、→ 展开、← 收起,禁用节点不响应", "upload(props):文件选择,点击或拖拽入列,清单可逐个移除,onChange 回传文件数组(不内置上传请求)", - "notification(props):右下角通知卡片(标题 + 描述 + 操作区),返回 { el, close } 句柄,缺省常驻需手动关闭" + "notification(props):右下角通知卡片(标题 + 描述 + 操作区),返回 { el, close } 句柄,缺省常驻需手动关闭", + "result(props):结果页,status 四档语义色 + 标题 + 说明 + 操作区,用于提交完成/校验失败等终态" ], "keyTypes": [ "ButtonProps", @@ -1993,6 +1994,41 @@ "action 放操作区(常见为按钮);variant 决定左侧条与圆点语义色", "与 toast 的分工:toast 右上角轻量瞬时,notification 右下角可长读、可操作" ] + }, + { + "id": "result", + "title": "结果页 Result", + "demo": "sec-result", + "props": [ + { + "n": "status", + "t": "'success' | 'warning' | 'danger' | 'info'", + "d": "终态语义色(图标底色);缺省 info" + }, + { + "n": "title", + "t": "string", + "r": 1, + "d": "主标题" + }, + { + "n": "description", + "t": "string", + "d": "补充说明;缺省不渲染" + }, + { + "n": "extra", + "t": "unknown", + "d": "操作区内容(常见为主次按钮);缺省不渲染" + } + ], + "example": "result({ status: 'success', title: '提交成功', description: '工单 #2049 已受理', extra: button({ label: '查看工单', variant: 'primary', onClick: () => go() }) })", + "steps": [ + "status 决定图标底色(success 绿 / warning 黄 / danger 红 / info 蓝),整体居中排布", + "title 必填;description 用于说明原因或下一步,超过一行自动居中换行", + "extra 放操作区,常见为主按钮 + 幽灵按钮;不传则不渲染", + "组件自带 role=\"status\",插入页面后读屏会播报终态文案" + ] } ] }, diff --git a/AGENTS.md b/AGENTS.md index 3ebe551..118ec53 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,7 +21,7 @@ - `packages/core/table-core`(@ui-arch/table-core):Headless 表格逻辑内核 - `packages/core/frame`(@ui-arch/frame):signal + hyperscript 微框架 -- `packages/skin/ui`(@ui-arch/ui):43 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌 +- `packages/skin/ui`(@ui-arch/ui):44 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌 - `packages/design/tokens`(@ui-arch/tokens):设计令牌 - `packages/tools/cli`(@ui-arch/cli):`ua` 命令行 + MCP 注册表服务(private) - `apps/demo`(@ui-arch/demo):交互演示,产物 apps/demo/dist/demo.html diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 578e583..38eb862 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -34,7 +34,7 @@ ui-arch-mvp/ │ │ ├── table-core/ # 表格状态内核 │ │ └── frame/ # 信号 + hyperscript 微框架 │ ├── skin/ # 皮肤层(DOM 组件,可发布) -│ │ └── ui/ # 43 个组件 + ui.css +│ │ └── ui/ # 44 个组件 + ui.css │ ├── design/ # 资产层(可发布) │ │ └── tokens/ # --ua-* 设计令牌 │ └── tools/ # 工程层(private,不发布) diff --git a/README.md b/README.md index 445f446..69de82d 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,10 @@ npm run demo # 打开演示:apps/demo/dist/demo.html npm run docs # 生成人类文档站 apps/docs/index.html + Agent 全量文档 llms-full.txt ``` -## 组件(共 43 个) +## 组件(共 44 个) -accordion · alert · avatar · badge · breadcrumb · button · carousel · card · checkbox · dataTable · descriptions · divider · drawer · empty · form · image · inputNumber · list · menu · modal · notification · pagination · popover · progress · radioGroup · rate · segmented · select · skeleton · slider · spinner · statistic · stepper · switchButton · tabs · tag · textField · textarea · timeline · toast · tooltip · tree · upload +accordion · alert · avatar · badge · breadcrumb · button · carousel · card · checkbox · dataTable · descriptions · divider · drawer · empty · form · image · inputNumber · list · menu · modal · notification · pagination · popover · progress · radioGroup · rate · result · segmented · select · skeleton · slider · spinner · statistic · stepper · switchButton · tabs · tag · textField · textarea · timeline · toast · tooltip · tree · upload 分组:表单(button / textField / inputNumber / textarea / checkbox / switchButton / select / radioGroup / slider / rate / segmented / form / formItem)、反馈(alert / toast / spinner / progress / skeleton / badge / empty / statistic)、浮层(tooltip / popover / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion / stepper)、数据(dataTable / descriptions / tag / avatar / card)、其他(divider / timeline)。 @@ -25,7 +25,7 @@ accordion · alert · avatar · badge · breadcrumb · button · carousel · car |---|---| | `packages/core/frame` | @ui-arch/frame:signal / computed / effect + hyperscript(无虚拟 DOM) | | `packages/core/table-core` | @ui-arch/table-core:headless 表格(filter → sort → paginate 派生管线) | -| `packages/skin/ui` | @ui-arch/ui:43 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) | +| `packages/skin/ui` | @ui-arch/ui:44 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) | | `packages/design/tokens` | @ui-arch/tokens:设计令牌(暗色只换值不换名) | | `packages/tools/cli` | @ui-arch/cli:`ua` 命令行与 stdio MCP server | | `apps/demo` | 交互演示(esbuild 打包成单文件) | diff --git a/apps/demo/main.ts b/apps/demo/main.ts index 3aa9f59..c646f4c 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -29,6 +29,7 @@ import { carousel, tree, notification, + result, upload, menu, modal, @@ -671,6 +672,17 @@ const content = h( button({ label: '错误(可关)', variant: 'danger', onClick: () => notification({ title: '保存失败', description: '服务端返回 500,请稍后重试', variant: 'danger' }) }), ), ), + section( + 'sec-result', + '结果页', + 'result:终态页(成功 / 警告 / 失败 / 中性),状态符号 + 标题 + 说明 + 操作区,适合提交完成、权限不足等场景。', + h( + 'div', + { class: 'row' }, + result({ status: 'success', title: '提交成功', description: '报销单 RB-2049 已进入财务复核,预计 2 个工作日内出结果。', extra: h('div', { class: 'row' }, button({ label: '查看单据', variant: 'primary', onClick: () => toast.success('打开单据') }), button({ label: '继续提交', variant: 'ghost', onClick: () => toast.info('已新建草稿') })) }), + result({ status: 'danger', title: '校验未通过', description: '发票金额与报销金额不一致,请核对后重新提交。' }), + ), + ), section( 'sec-menu', '下拉菜单', diff --git a/llms-full.txt b/llms-full.txt index 579e7a7..f31ff74 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T03:46:32.583Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T03:47:28.088Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -1142,6 +1142,30 @@ const { close } = notification({ title: '导出完成', description: 'report.csv 现场演示:apps/docs/demo.html#sec-notification +### result — 结果页 Result + +参数: + +- status:'success' | 'warning' | 'danger' | 'info' —— 终态语义色(图标底色);缺省 info +- title(必填):string —— 主标题 +- description:string —— 补充说明;缺省不渲染 +- extra:unknown —— 操作区内容(常见为主次按钮);缺省不渲染 + +示例: + +``` +result({ status: 'success', title: '提交成功', description: '工单 #2049 已受理', extra: button({ label: '查看工单', variant: 'primary', onClick: () => go() }) }) +``` + +手把手: + +1. status 决定图标底色(success 绿 / warning 黄 / danger 红 / info 蓝),整体居中排布 +2. title 必填;description 用于说明原因或下一步,超过一行自动居中换行 +3. extra 放操作区,常见为主按钮 + 幽灵按钮;不传则不渲染 +4. 组件自带 role="status",插入页面后读屏会播报终态文案 + +现场演示:apps/docs/demo.html#sec-result + API 速览: - button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用 @@ -1189,6 +1213,7 @@ API 速览: - tree(props):树形,递归节点 + 箭头展开/收起 + 点选节点,键盘 Enter 选中、→ 展开、← 收起,禁用节点不响应 - upload(props):文件选择,点击或拖拽入列,清单可逐个移除,onChange 回传文件数组(不内置上传请求) - notification(props):右下角通知卡片(标题 + 描述 + 操作区),返回 { el, close } 句柄,缺省常驻需手动关闭 +- result(props):结果页,status 四档语义色 + 标题 + 说明 + 操作区,用于提交完成/校验失败等终态 一行用法: diff --git a/llms.txt b/llms.txt index da5e406..3aa1046 100644 --- a/llms.txt +++ b/llms.txt @@ -92,6 +92,7 @@ - tree(props):树形,递归节点 + 箭头展开/收起 + 点选节点,键盘 Enter 选中、→ 展开、← 收起,禁用节点不响应 - upload(props):文件选择,点击或拖拽入列,清单可逐个移除,onChange 回传文件数组(不内置上传请求) - notification(props):右下角通知卡片(标题 + 描述 + 操作区),返回 { el, close } 句柄,缺省常驻需手动关闭 + - result(props):结果页,status 四档语义色 + 标题 + 说明 + 操作区,用于提交完成/校验失败等终态 - 用法: `const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')` - 约定: 皮肤不持有业务状态,一律委托 headless 内核或调用方信号 - 约定: 表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调 diff --git a/packages/skin/ui/src/index.ts b/packages/skin/ui/src/index.ts index 8e64d4e..c0744bb 100644 --- a/packages/skin/ui/src/index.ts +++ b/packages/skin/ui/src/index.ts @@ -31,6 +31,7 @@ export { segmented } from './segmented.js'; export { stepper } from './stepper.js'; export { rate } from './rate.js'; export { empty } from './empty.js'; +export { result } from './result.js'; export { divider } from './divider.js'; export { list } from './list.js'; export { carousel } from './carousel.js'; diff --git a/packages/skin/ui/src/result.ts b/packages/skin/ui/src/result.ts new file mode 100644 index 0000000..11f35ea --- /dev/null +++ b/packages/skin/ui/src/result.ts @@ -0,0 +1,18 @@ +import { h } from '@ui-arch/frame'; +import type { ResultProps } from './types.js'; + +// 语义状态对应的符号(纯装饰,读屏跳过) +const GLYPH: Record = { success: '✓', warning: '!', danger: '×', info: 'i' }; + +/*** 结果页:状态符号 + 标题 + 说明 + 操作区,用于提交完成 / 校验失败 / 无权限等终态页面 ***/ +export function result(props: ResultProps): HTMLElement { + const status = props.status ?? 'info'; // 缺省中性信息 + return h( + 'div', + { class: `ua-result ua-result-${status}`, role: 'status' }, // role=status 由读屏播报终态 + h('div', { class: 'ua-result-icon', 'aria-hidden': 'true' }, GLYPH[status] ?? 'i'), + h('div', { class: 'ua-result-title' }, props.title), + props.description ? h('div', { class: 'ua-result-desc' }, props.description) : null, + props.extra ? h('div', { class: 'ua-result-extra' }, props.extra) : null, // 操作区:常见为主次按钮 + ) as HTMLElement; +} diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index 346562a..78e3c65 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -420,6 +420,14 @@ export interface ListProps { onItemClick?: (item: ListItem, index: number) => void; // 条目点击回调;提供后条目可点击并带 hover 反馈 } +/*** 结果页入参 ***/ +export interface ResultProps { + status?: 'success' | 'warning' | 'danger' | 'info'; // 终态语义色;缺省 info + title: string; // 主标题(必填) + description?: string; // 补充说明;缺省不渲染 + extra?: unknown; // 操作区内容(常见为主次按钮);缺省不渲染 +} + /*** 通知入参 ***/ export interface NotificationProps { title: string; // 通知标题(必填) diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 2a14ba7..bfe506f 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -57,7 +57,8 @@ "carousel(props):轮播,items 横排位移,指示点/箭头/←→ 键切换,autoplay 毫秒自动前进且悬停暂停,index 信号可外部受控", "tree(props):树形,递归节点 + 箭头展开/收起 + 点选节点,键盘 Enter 选中、→ 展开、← 收起,禁用节点不响应", "upload(props):文件选择,点击或拖拽入列,清单可逐个移除,onChange 回传文件数组(不内置上传请求)", - "notification(props):右下角通知卡片(标题 + 描述 + 操作区),返回 { el, close } 句柄,缺省常驻需手动关闭" + "notification(props):右下角通知卡片(标题 + 描述 + 操作区),返回 { el, close } 句柄,缺省常驻需手动关闭", + "result(props):结果页,status 四档语义色 + 标题 + 说明 + 操作区,用于提交完成/校验失败等终态" ], "keyTypes": [ "ButtonProps", @@ -1864,6 +1865,41 @@ "action 放操作区(常见为按钮);variant 决定左侧条与圆点语义色", "与 toast 的分工:toast 右上角轻量瞬时,notification 右下角可长读、可操作" ] + }, + { + "id": "result", + "title": "结果页 Result", + "demo": "sec-result", + "props": [ + { + "n": "status", + "t": "'success' | 'warning' | 'danger' | 'info'", + "d": "终态语义色(图标底色);缺省 info" + }, + { + "n": "title", + "t": "string", + "r": 1, + "d": "主标题" + }, + { + "n": "description", + "t": "string", + "d": "补充说明;缺省不渲染" + }, + { + "n": "extra", + "t": "unknown", + "d": "操作区内容(常见为主次按钮);缺省不渲染" + } + ], + "example": "result({ status: 'success', title: '提交成功', description: '工单 #2049 已受理', extra: button({ label: '查看工单', variant: 'primary', onClick: () => go() }) })", + "steps": [ + "status 决定图标底色(success 绿 / warning 黄 / danger 红 / info 蓝),整体居中排布", + "title 必填;description 用于说明原因或下一步,超过一行自动居中换行", + "extra 放操作区,常见为主按钮 + 幽灵按钮;不传则不渲染", + "组件自带 role=\"status\",插入页面后读屏会播报终态文案" + ] } ] } diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index 9fbb6c1..34df3cd 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -2363,3 +2363,44 @@ button.ua-step-dot:hover { transition: none; } } + +/* —— 结果页 —— */ +.ua-result { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: var(--ua-space-2); + padding: var(--ua-space-4) var(--ua-space-3); + min-height: 180px; + justify-content: center; +} +.ua-result-icon { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: 50%; + font-size: var(--ua-font-size-lg); + font-weight: 700; + color: var(--ua-color-accent-contrast); + background: var(--ua-color-accent); +} +.ua-result-success .ua-result-icon { background: var(--ua-color-success); } +.ua-result-warning .ua-result-icon { background: var(--ua-color-warning); } +.ua-result-danger .ua-result-icon { background: var(--ua-color-danger); } +.ua-result-title { + font-size: var(--ua-font-size-lg); + font-weight: 600; + color: var(--ua-color-text); +} +.ua-result-desc { + max-width: 380px; + font-size: var(--ua-font-size-sm); + color: var(--ua-color-text-muted); + line-height: 1.6; +} +.ua-result-extra { + margin-top: var(--ua-space-1); +}