2102 lines
110 KiB
Plaintext
2102 lines
110 KiB
Plaintext
# @ui-arch 组件库 Agent 文档(无图形版)
|
||
|
||
> 自动生成于 2026-10-26T07:21:05.139Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
||
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
||
|
||
## 快速上手
|
||
|
||
### @ui-arch/frame
|
||
|
||
1. 引入:import { signal, computed, effect, h, mount } from '@ui-arch/frame'
|
||
2. 声明状态:const count = signal(0);无参调用读值 count(),传参写值 count(1)
|
||
3. 渲染视图:mount('#app', () => h('div', null, () => `count = ${count()}`))——函数型子节点即响应式文本
|
||
4. 绑定事件:props 里 on 开头且值为函数的键(onClick 等)自动 addEventListener
|
||
5. 派生数据:const double = computed(() => count() * 2),视图里直接调用 double(),依赖自动追踪
|
||
6. 列表渲染:renderList(tbody, () => rows.map(r => h('tr', null, r.name))),信号变化整体替换子节点
|
||
|
||
### @ui-arch/table-core
|
||
|
||
1. 创建实例:const table = createTable({ data, columns, rowKey: 'id', plugins: [filterPlugin(), sortPlugin(), paginationPlugin()] })
|
||
2. 取派生数据:table.getVisibleRows() 返回 { rows, total };total 语义固定 = 筛选后、分页前的行数
|
||
3. 变更状态:只能 dispatch action,如 table.dispatch({ type: 'toggleSort', key: 'name' });状态不可变
|
||
4. 订阅刷新:table.subscribe(fn),fn 里重新 getVisibleRows() 并交给渲染层
|
||
5. 分页信息:api.getPageInfo() 返回 { page, pageSize, totalPages };越界页码自动钳制
|
||
6. 扩展能力:实现 TablePlugin(derive / patchState / api 三钩子)传入 plugins 即插入管线
|
||
|
||
### @ui-arch/tokens
|
||
|
||
1. 引入:在应用入口 import '@ui-arch/tokens/tokens.css'
|
||
2. 消费:组件样式一律 var(--ua-color-accent) 这类变量,不写死色值
|
||
3. 暗色:根元素设 data-theme=dark,变量值自动切换
|
||
4. 扩展:新令牌先同时加进 tokens.css 与 tokens.json,再重新生成文档站
|
||
|
||
### @ui-arch/ui
|
||
|
||
1. 安装依赖:pnpm add @ui-arch/ui @ui-arch/frame(workspace 内自动链接)
|
||
2. 引入样式:在应用入口 import '@ui-arch/ui/ui.css',暗色主题在根元素设 data-theme='dark'
|
||
3. 创建状态:import { signal } from '@ui-arch/frame',如 const name = signal('')
|
||
4. 调用组件:const el = textField({ value: name }),把 el 挂到你的容器或配合 h() 组合
|
||
5. 读写状态:name() 读、name('张三') 写,界面自动同步,无需手动操作 DOM
|
||
|
||
### @ui-arch/cli
|
||
|
||
1. 装好依赖后先跑 npm run check 确认基线全绿(build → demo → style → typecheck → smoke → index → verify)
|
||
2. 查组件:ua list 看全量,ua info <组件id> 看 props/步骤/示例(ua info <包名> 看单包 api 与约定,加 --json 出原始清单)
|
||
3. 查令牌:ua tokens 打印全部 --ua-* 变量(颜色带暗色值),--json 输出结构化数组;写样式前先查,禁止硬编码
|
||
4. 改完清单跑 npm run docs:同时刷新人类文档站与 llms-full.txt(Agent 首选喂料)
|
||
5. 在 ~/.workbuddy/mcp.json 注册 ua mcp 后,Agent 可直接调 list_components / get_component / search_components / get_tokens
|
||
6. 在 ~/.workbuddy/mcp.json 注册 ua mcp 后,Agent 可直接调 list_components / get_component / search_components
|
||
|
||
## @ui-arch/ui(skin-dom)v0.1.0
|
||
|
||
28 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。
|
||
|
||
### image — 图片 Image
|
||
|
||
参数:
|
||
|
||
- src(必填):string —— 图片地址(缩略图与大图共用)
|
||
- alt(默认 ''):string —— 替代文本;缺省空串
|
||
- width:number —— 缩略图宽度像素,高度自适应;缺省走样式默认
|
||
- fallback(默认 '图片加载失败'):string —— 加载失败时缩略图内显示的占位文案;缺省「图片加载失败」
|
||
|
||
示例:
|
||
|
||
```
|
||
image({ src: '/a.png', alt: '架构图', width: 160 })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 基础用法只传 src:渲染缩略图,hover 上浮提亮,光标变放大镜
|
||
2. 点击缩略图(或聚焦后回车)打开全屏遮罩大图,Esc 或点击遮罩任意处关闭,焦点归还缩略图
|
||
3. 宽度用 width(像素)控制,高度按比例自适应,容器内不撑破 max-width
|
||
4. alt 尽量传有意义的描述,读屏与图片加载失败时都依赖它
|
||
|
||
现场演示:apps/docs/demo.html#sec-image
|
||
|
||
### button — 按钮 Button
|
||
|
||
参数:
|
||
|
||
- label(必填):string —— 按钮文案
|
||
- variant(默认 primary):'primary' | 'ghost' | 'danger' —— 视觉强度;缺省 primary
|
||
- type:'button' | 'submit' | 'reset' —— 原生类型;缺省 button,表单内不会意外提交
|
||
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
|
||
- onClick(回调):() => void —— 点击回调
|
||
|
||
示例:
|
||
|
||
```
|
||
button({ label: '提交', variant: 'primary', onClick: () => console.log('clicked') })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 确定语义强度:主操作用 primary(默认),次要操作用 ghost,危险操作用 danger
|
||
2. 绑定 onClick 回调处理点击逻辑
|
||
3. 需要禁用态时自行在回调首行卫语句判断,或外层包条件渲染
|
||
4. 放进 <form> 里需要提交时显式传 type: "submit";缺省 button 不会触发原生提交
|
||
|
||
现场演示:apps/docs/demo.html#sec-counter
|
||
|
||
### textField — 文本输入 TextField
|
||
|
||
参数:
|
||
|
||
- value:Signal<string> —— 双向绑定信号:输入写回信号,信号变化写回输入框
|
||
- placeholder:string —— 占位文案
|
||
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
|
||
- onInput(回调):(value: string) => void —— 输入回调,每次按键触发
|
||
- prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染
|
||
- suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染
|
||
- clearable(默认 false):boolean —— 开启后有值即显示一键清空按钮;缺省 false
|
||
- maxCount:number —— 字数上限:同时设原生 maxlength,并在输入框内侧显示实时计数;缺省不限
|
||
|
||
示例:
|
||
|
||
```
|
||
const name = signal(''); const el = textField({ value: name, placeholder: '输入名字…', onInput: (v) => console.log(v) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 先用 signal('') 创建字符串信号
|
||
2. 传入 value 完成双向绑定:用户输入自动写回,外部赋值自动回显
|
||
3. 在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}`
|
||
4. 需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载
|
||
5. 需要限制长度就给 maxCount:超限输入被原生拦截,输入框内侧实时显示 n/上限
|
||
|
||
现场演示:apps/docs/demo.html#sec-bind
|
||
|
||
### inputNumber — 数字输入 InputNumber
|
||
|
||
参数:
|
||
|
||
- value(必填):Signal<number> —— 当前值信号,双向绑定(按钮/键盘写回、外部赋值同步)
|
||
- min:number —— 最小值;缺省无下限
|
||
- max:number —— 最大值;缺省无上限
|
||
- step(默认 1):number —— 步长;缺省 1
|
||
- precision:number —— 小数位数:写回与显示都按 toFixed(p) 归一(消除步进浮点尾差,step 0.1 也干净);缺省不处理
|
||
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
|
||
- onChange(回调):(value: number) => void —— 值变化回调(已夹取到合法区间)
|
||
- prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染
|
||
- suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染
|
||
|
||
示例:
|
||
|
||
```
|
||
const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onChange: (v) => console.log(v) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 用 signal(1) 创建数字信号并传入 value 完成双向绑定
|
||
2. 点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析
|
||
3. 非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用
|
||
4. 金额、比例这类小数场景传 precision:值与显示都固定小数位,步进不再出现 0.30000000000000004
|
||
5. 需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载
|
||
|
||
现场演示:apps/docs/demo.html#sec-inputnumber
|
||
|
||
### textarea — 多行输入 Textarea
|
||
|
||
参数:
|
||
|
||
- value:Signal<string> —— 双向绑定信号
|
||
- rows(默认 3):number —— 初始行数;缺省 3
|
||
- placeholder:string —— 占位文案
|
||
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
|
||
- onInput(回调):(value: string) => void —— 输入回调
|
||
- prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染
|
||
- suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染
|
||
- maxCount:number —— 字数上限:同时设原生 maxlength,并在下方附加块显示实时计数;缺省不限
|
||
|
||
示例:
|
||
|
||
```
|
||
const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话介绍…' })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 创建信号并传入 value
|
||
2. 用 rows 控制初始高度,用户可纵向拉伸
|
||
3. 字数统计等派生逻辑直接在响应式文本里读 bio().length
|
||
4. 需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载
|
||
|
||
现场演示:apps/docs/demo.html#sec-form
|
||
|
||
### slider — 滑块 Slider
|
||
|
||
参数:
|
||
|
||
- value(必填):Signal<number> —— 当前值信号,双向绑定:拖动写回,外部赋值同步位置
|
||
- min(默认 0):number —— 最小值;缺省 0
|
||
- max(默认 100):number —— 最大值;缺省 100
|
||
- step(默认 1):number —— 步长;缺省 1
|
||
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
|
||
- showValue(默认 true):boolean —— 是否右侧显示当前值;缺省 true
|
||
- onChange(回调):(value: number) => void —— 拖动回调
|
||
|
||
示例:
|
||
|
||
```
|
||
const vol = signal(40); slider({ value: vol, min: 0, max: 100, step: 5, onChange: (v) => console.log(v) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 创建数值信号(初值在 min/max 区间内),传入 value 完成双向绑定
|
||
2. 用 min / max / step 限定范围与粒度,如 step: 5 只取 5 的倍数
|
||
3. 拖动或按 ←→ 键改变值:原生 range 自带键盘与触屏交互,无需额外处理
|
||
4. 只想看拖动条不显示数字时传 showValue: false;需要自定义展示可自行读 signal
|
||
5. 禁用场景传 disabled(布尔或信号),置灰且不可拖
|
||
|
||
现场演示:apps/docs/demo.html#sec-slider
|
||
|
||
### checkbox — 复选框 Checkbox
|
||
|
||
参数:
|
||
|
||
- checked:Signal<boolean> —— 勾选状态信号,双向绑定
|
||
- label:string —— 右侧文案;缺省只渲染勾选框
|
||
- indeterminate:boolean | Signal<boolean> —— 半选态(批量选择时部分子项选中):仅影响视觉与读屏 mixed 播报,不改变 checked 值;点一下即回到全选
|
||
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
|
||
- onChange(回调):(checked: boolean) => void —— 变更回调
|
||
|
||
示例:
|
||
|
||
```
|
||
const agreed = signal(false); checkbox({ label: '同意条款', checked: agreed, onChange: (v) => console.log(v) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 创建布尔信号 agreed = signal(false)
|
||
2. 传入 checked 与 label,勾选即写回信号
|
||
3. 表单提交前用 agreed() 判断是否勾选
|
||
4. 做全选/子项联动时把「部分选中」折算成 indeterminate(布尔或信号):视觉显示横杠,读屏播报 mixed,点一下即全选
|
||
|
||
现场演示:apps/docs/demo.html#sec-form
|
||
|
||
### switchButton — 开关 Switch
|
||
|
||
参数:
|
||
|
||
- checked:Signal<boolean> —— 开关状态信号,双向绑定
|
||
- ariaLabel:string —— 无可见文案时的可访问名(如纯图标开关);缺省取 label
|
||
- label:string —— 左侧文案
|
||
- loading:boolean | Signal<boolean> —— 加载中:锁定交互、滑块换成转圈并挂 aria-busy(异步提交防误点);与 disabled 同时生效
|
||
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
|
||
- onChange(回调):(checked: boolean) => void —— 变更回调
|
||
|
||
示例:
|
||
|
||
```
|
||
const night = signal(true); switchButton({ label: '夜间模式', checked: night })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 创建布尔信号并传入 checked
|
||
2. 轨道与滑块的动画由 CSS 完成,无需干预
|
||
3. 状态变化在 onChange 里做副作用(如切换主题)
|
||
4. 切换要等异步请求就传 loading(布尔或信号):请求期间滑块转圈、不可点,完成后自动恢复
|
||
|
||
现场演示:apps/docs/demo.html#sec-form
|
||
|
||
### select — 下拉选择 Select(自绘弹层)
|
||
|
||
参数:
|
||
|
||
- options(必填):OptionItem[] —— 选项列表,每项 { value, label }
|
||
- value:Signal<string> —— 选中值信号,双向绑定
|
||
- placeholder:string —— 未选中时触发器显示的占位文案
|
||
- searchable:boolean —— 弹层顶部带搜索框,按标签包含匹配过滤;缺省 false
|
||
- clearable:boolean —— 有值时触发器右缘出现清空按钮(收起态按 Delete / Backspace 也能清);缺省 false,用于「可选筛选项」
|
||
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步;禁用时触发器置灰且不展开弹层
|
||
- onChange(回调):(value: string) => void —— 选中回调
|
||
|
||
示例:
|
||
|
||
```
|
||
const city = signal(''); select({ options: [{ value: 'bj', label: '北京' }], value: city, placeholder: '选择城市…' })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 准备 options 数组,value 是提交用值、label 是展示文案
|
||
2. 创建信号并传入 value;未传时组件内部自持状态
|
||
3. 点击触发器展开弹层,点选项写回信号并收起;点外部或按 Esc 也会收起
|
||
4. 选中项在弹层中用淡色弱化标记,不抢视觉焦点
|
||
5. 弹层展开时按 Esc 只收起下拉并回焦触发器;若下拉在弹窗/抽屉内,再按一次 Esc 才关外层(逐层关闭)
|
||
6. searchable: true 时打开即聚焦搜索框,输入实时过滤;↑↓/Home/End 只在可见项间移动,无命中显示「无匹配选项」
|
||
7. 需要允许「不选」的筛选项就开 clearable:有值时右缘出现 ×,点它把值写成空串并回调 onChange(''),键盘在收起态按 Delete / Backspace 等价
|
||
|
||
现场演示:apps/docs/demo.html#sec-form
|
||
|
||
### menu — 下拉菜单 Menu
|
||
|
||
参数:
|
||
|
||
- label(必填):string —— 触发器文案
|
||
- items(必填):MenuItem[] —— 菜单条目:{ value, label, disabled?, danger?, divider?, shortcut? }
|
||
- variant(默认 ghost):'primary' | 'ghost' —— 触发器视觉强度;缺省 ghost
|
||
- align(默认 start):'start' | 'end' —— 弹层对齐方向;缺省 start(与触发器左对齐)
|
||
- onSelect(回调):(value: string) => void —— 选中回调,禁用项不触发
|
||
|
||
示例:
|
||
|
||
```
|
||
menu({ label: '操作', items: [{ value: 'edit', label: '编辑' }, { value: 'del', label: '删除', danger: true }], onSelect: (v) => console.log(v) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 准备 items 数组:value 是回调标识、label 是展示文案,插一条 { divider: true } 即可分出条目组
|
||
2. 传 label 决定触发器文案;默认 ghost 轻量样式,主操作用 variant: 'primary'
|
||
3. 点触发器展开弹层,点条目收起并触发 onSelect;点外部或按 Esc 也会收起(Esc 后焦点回到触发器)
|
||
4. 条目要给快捷键提示时传 shortcut(如 '⌘K'):右侧灰字展示,组件不绑定按键,实际快捷键由调用方在全局监听
|
||
5. 键盘:Tab 聚焦触发器,↑↓ 展开并在可用条目间环形移动,Enter / Space 选中;disabled 项自动跳过
|
||
6. 靠右对齐避免溢出时传 align: 'end';危险操作给条目加 danger: true
|
||
7. 展开时按 Esc 只收起菜单并回焦触发器,不会连带关闭所在弹窗(逐层关闭)
|
||
|
||
现场演示:apps/docs/demo.html#sec-menu
|
||
|
||
### radioGroup — 单选组 RadioGroup
|
||
|
||
参数:
|
||
|
||
- options(必填):OptionItem[] —— 选项列表
|
||
- value:Signal<string> —— 选中值信号,双向绑定
|
||
- disabled:boolean | Signal<boolean> —— 整组禁用:全部选项与文案置灰且不可选
|
||
- onChange(回调):(value: string) => void —— 变更回调
|
||
|
||
示例:
|
||
|
||
```
|
||
const size = signal('m'); radioGroup({ options: [{ value: 's', label: '小' }, { value: 'm', label: '中' }], value: size })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 创建字符串信号,初值设为默认选项的 value
|
||
2. 传入 options 与 value,组内自动互斥
|
||
3. 读取 size() 获取当前选中值
|
||
4. 需要整组禁用时传 disabled:布尔值静态禁用,也可传信号(如 locked)实现动态锁定
|
||
|
||
现场演示:apps/docs/demo.html#sec-form
|
||
|
||
### accordion — 折叠面板 Accordion
|
||
|
||
参数:
|
||
|
||
- items(必填):AccordionItem[] —— 面板列表:{ value, title, content, disabled? }
|
||
- active:Signal<string> —— 受控展开面板信号(单开模式);缺省由组件内部持有状态
|
||
- multiple(默认 false):boolean —— 允许多个面板同时展开;缺省 false(手风琴,单开)
|
||
- onChange(回调):(active: string) => void —— 展开变化回调:收起时传空串
|
||
|
||
示例:
|
||
|
||
```
|
||
accordion({ items: [{ value: 'a', title: '账户设置', content: '内容…' }, { value: 'b', title: '通知', content: h('p', null, '…') }] })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 准备 items:value 作标识、title 作标题、content 可为字符串 / DOM 节点 / 响应式函数
|
||
2. 默认手风琴模式:展开一个自动收起其他;需要同时展开多个时传 multiple: true
|
||
3. 需要外部控制或读取展开项时传 active 信号(受控单开),否则组件内部自持状态
|
||
4. 标题是原生 button:Tab 聚焦、Enter / Space 切换,展开态带 aria-expanded + region 关联
|
||
5. 内容区用 grid 行高 0fr → 1fr 过渡,无需测量高度;reduced-motion 用户自动静态切换
|
||
|
||
现场演示:apps/docs/demo.html#sec-accordion
|
||
|
||
### breadcrumb — 面包屑 Breadcrumb
|
||
|
||
参数:
|
||
|
||
- items(必填):BreadcrumbItem[] —— 路径条目:{ label, href? },末项自动视为当前页
|
||
- separator(默认 /):string —— 分隔符;缺省 '/'
|
||
- onNavigate(回调):(item, index) => void —— 无 href 条目的点击回调
|
||
- maxItems:number —— 最多展示几项:超出时首项之后折叠成「…」按钮(aria-label 报出折叠条数),点开即完整展开;缺省不折叠
|
||
|
||
示例:
|
||
|
||
```
|
||
breadcrumb({ items: [{ label: '首页', href: '/' }, { label: '设置' }, { label: '账户' }], onNavigate: (it) => console.log(it.label) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 按层级顺序准备 items,最后一项即当前页,自动渲染为不可点的灰色文字并带 aria-current='page'
|
||
2. 需要真实跳转就给条目加 href(渲染 <a>);单页应用想自行接管时不给 href,改用 onNavigate 回调
|
||
3. 改分隔符传 separator,如 '›';分隔符对读屏隐藏
|
||
4. 语义结构为 nav + ol + li,可直接放进页头或卡片标题栏
|
||
5. 路径过深时传 maxItems(如 4):首项 + 「…」+ 末几项,点「…」原地展开全部,末项始终是当前页
|
||
|
||
现场演示:apps/docs/demo.html#sec-breadcrumb
|
||
|
||
### card — 卡片 Card
|
||
|
||
参数:
|
||
|
||
- title:string —— 标题;缺省不渲染标题行
|
||
- ...children:unknown[] —— 任意子节点:字符串 / DOM / 响应式函数
|
||
- extra:unknown —— 标题行右侧操作区(按钮 / 统计文案);缺省不渲染
|
||
- footer:unknown —— 底部操作区;缺省不渲染
|
||
- bordered:boolean —— 是否显示边框;缺省 true
|
||
- hoverable:boolean —— 悬停浮起(可点击卡片);缺省 false
|
||
|
||
示例:
|
||
|
||
```
|
||
card({ title: '用户表' }, dataTable({ data, columns }))
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. card 是容器组件:第一个参数是 props,其余参数是任意子节点
|
||
2. 传 title 显示标题行,否则只有内容区
|
||
3. 子节点可以混合字符串、DOM 节点与响应式函数
|
||
4. title 与 extra 同处标题行:有 extra 时右侧对齐,没标题也能只放操作区
|
||
5. footer 渲染在内容之后的底部区,右对齐并带上分隔线,适合放主次按钮
|
||
6. bordered:false 去边框(嵌在已有边界的容器里),hoverable 让整卡悬停浮起(自带 reduced-motion 兜底)
|
||
|
||
现场演示:apps/docs/demo.html#sec-card
|
||
|
||
### badge — 徽标 Badge
|
||
|
||
参数:
|
||
|
||
- label:string —— 徽标文案;与 count / dot 三选一,dot 时作可访问名
|
||
- variant:'default' | 'success' | 'warning' | 'danger' —— 语义色;缺省 default
|
||
- count:number —— 数字角标:超过 max 显示 N+,负数归零
|
||
- max:number —— 数字上限;缺省 99
|
||
- dot:boolean —— 只显示小红点(无文字),role=img + label 作名称
|
||
|
||
示例:
|
||
|
||
```
|
||
badge({ label: '成功', variant: 'success' })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 纯状态文案直接传 label,配 variant 定语义色
|
||
2. 要显示数量传 count(如未读数),超过 max(缺省 99)自动渲染成 99+
|
||
3. 只提示「有更新」用 dot: true,配合 label 给出读屏名(如「有未读消息」)
|
||
|
||
现场演示:apps/docs/demo.html#sec-feedback
|
||
|
||
### tag — 标签 Tag
|
||
|
||
参数:
|
||
|
||
- label(必填):string —— 标签文案
|
||
- variant(默认 default):'default' | 'success' | 'warning' | 'danger' | 'info' —— 语义色;缺省 default
|
||
- closable(默认 false):boolean —— 显示 × 关闭按钮;缺省 false(纯展示)
|
||
- onClose(回调):() => void —— 关闭回调:调用方从数据源移除该项后重新渲染
|
||
- checkable(默认 false):boolean —— 可选中:渲染为筛选片(button + aria-pressed);与 closable 互斥,同时传以 checkable 为准
|
||
- checked:Signal<boolean> —— 选中态信号,双向绑定(点击写回、外部赋值同步);不传则组件内部自持
|
||
- onChange:(checked: boolean) => void —— 选中态变化回调,参数是点击后的新值
|
||
|
||
示例:
|
||
|
||
```
|
||
tag({ label: '前端', variant: 'info', closable: true, onClose: () => remove('前端') })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 纯展示用 badge,需要可移除的标签(筛选项 / 已选条件)用 tag
|
||
2. 按语义选 variant:info 中性标记、success 通过、warning 待处理、danger 已失效
|
||
3. closable: true 时必须配 onClose:把数据源里的该项删掉再重渲染(组件无状态,不自行摘除节点)
|
||
4. 组合用法:容器里用 renderList 按信号数组渲染 tag 列表,删除即写回信号
|
||
5. 标签是内联元素,可直接排进 .row 等容器,自动换行由容器决定
|
||
6. 需要可点的筛选片时传 checkable + checked:点击写回信号,视觉与 aria-pressed 同步,不做整块重渲染(焦点不丢)
|
||
|
||
现场演示:apps/docs/demo.html#sec-tag
|
||
|
||
### avatar — 头像 Avatar
|
||
|
||
参数:
|
||
|
||
- name(必填):string —— 名称:生成占位首字,并作为图片 alt 与悬停标题
|
||
- src:string —— 图片地址;缺省用首字占位
|
||
- size(默认 32):number —— 直径像素;缺省 32
|
||
- variant(默认 default):'default' | 'success' | 'warning' | 'danger' —— 占位底色语义档;缺省 default
|
||
|
||
示例:
|
||
|
||
```
|
||
avatar({ name: '张三' }); avatar({ name: 'Alice', size: 40, variant: 'success' })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 只传 name:用名称首字做占位(中文取首字、英文大写),底色走 accent 淡化,暗色自动适配
|
||
2. 有图片就传 src:渲染 img 并用 name 作 alt,读屏语义完整;图片缺失时由浏览器 alt 兜底
|
||
3. 尺寸用 size(像素)控制,头像不参与布局拉伸(flex:none),可安全排进 .row 与表格单元格
|
||
4. 需要区分类别(在线/告警)时用 variant 换底色档,颜色一律来自令牌
|
||
|
||
现场演示:apps/docs/demo.html#sec-avatar
|
||
|
||
### alert — 提示条 Alert
|
||
|
||
参数:
|
||
|
||
- message(必填):string —— 提示内容
|
||
- title:string —— 加粗标题行;缺省只渲染正文
|
||
- variant(默认 info):'info' | 'success' | 'warning' | 'danger' —— 语义色;缺省 info
|
||
- closable(默认 false):boolean —— 显示 × 关闭按钮,退场后从 DOM 移除;缺省 false
|
||
- onClose(回调):() => void —— 关闭回调,供调用方同步业务状态
|
||
- banner(默认 false):boolean —— 通栏形态:去圆角、只保留下边框、左右撑满,适合页面顶部通知
|
||
- showIcon(默认 false):boolean —— 显示语义图标(info ℹ / success ✓ / warning ! / danger ✕),纯装饰不进读屏
|
||
- icon:string | HTMLElement —— 自定义图标节点,传了即覆盖语义图标
|
||
|
||
示例:
|
||
|
||
```
|
||
alert({ message: '操作可能存在风险', variant: 'warning' })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 按信息级别选 variant:info 提示、success 成功、warning 警告、danger 错误
|
||
2. 传入 message 即可,入场自带下滑动画
|
||
3. 需要可关闭或带操作按钮时,外层用 h() 组合 button 自行拼装,alert 只负责语义底色
|
||
4. 需要用户可自行消除的提示传 closable: true:点 × 淡出并从 DOM 移除,配合 onClose 同步业务状态
|
||
5. 有标题与正文两层信息时传 title(加粗首行),正文仍走 message
|
||
6. banner: true 出通栏形态(圆角与左右边框归零),页面顶部通知直接铺满容器即可,不需要额外包裹层
|
||
|
||
现场演示:apps/docs/demo.html#sec-feedback
|
||
|
||
### skeleton — 骨架屏 Skeleton
|
||
|
||
参数:
|
||
|
||
- variant(默认 rect):'text' | 'rect' | 'circle' —— 占位形状;缺省 rect(整块矩形)
|
||
- rows(默认 3):number —— text 形状的行数;缺省 3,末行自动缩短模拟段落
|
||
|
||
示例:
|
||
|
||
```
|
||
skeleton({ variant: 'text', rows: 3 }); skeleton({ variant: 'circle' }); skeleton({ variant: 'rect' })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 数据加载期间用 skeleton 顶替真实内容,加载完成后整体替换
|
||
2. 文本段落用 variant: 'text' + rows 控制行数,头像用 'circle',卡片/图片区域用 'rect'
|
||
3. 占位块自带扫光动画,reduced-motion 用户自动静态化为纯灰块
|
||
4. 组合布局:skeleton 是普通 DOM,可放进 card / row 等任意容器
|
||
|
||
现场演示:apps/docs/demo.html#sec-skeleton
|
||
|
||
### toast — 消息提示 Toast
|
||
|
||
参数:
|
||
|
||
- message(必填):string —— 提示文案
|
||
- duration(默认 3000):number —— 自动消失毫秒;缺省 3000
|
||
- closable(默认 false):boolean —— 显示关闭按钮,点 × 立即收起;缺省 false(仅可关闭的 toast 接收指针事件,其余保持穿透)
|
||
- variant(默认 info):'info' | 'success' | 'warning' | 'danger' —— 语义色;缺省 info(toast.error 对应 danger)
|
||
- actionLabel:string —— 操作按钮文案(如「撤销」);传了才渲染,点后 toast 立即收起
|
||
- onAction(回调):() => void —— 操作按钮回调,在 toast 收起之后触发(撤销删除之类)
|
||
|
||
示例:
|
||
|
||
```
|
||
toast.success('保存成功'); toast.error('网络请求失败', 5000)
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 直接调用语义工厂:toast.info / success / warning / error(message, duration?),无需先建容器
|
||
2. 不传 duration 时默认停留 3 秒后自动淡出并移除节点
|
||
3. 多条消息在右上角自动纵向堆叠,互不遮挡
|
||
4. 需要完整入参(含 variant)时用 showToast(props);组件挂载容器由内部懒创建,样式随 ui.css 一起引入
|
||
5. 同屏最多 4 条,超出自动挤掉最早一条;需要用户主动确认的消息用 showToast({ closable: true }) 给个 ×
|
||
6. 要「撤销」这类操作时给 showToast 传 actionLabel 与 onAction:按钮出现在文案右侧,点完先收起再回调,duration 建议放到 5000 让用户来得及点
|
||
|
||
现场演示:apps/docs/demo.html#sec-toast
|
||
|
||
### tooltip — 悬浮提示 Tooltip
|
||
|
||
参数:
|
||
|
||
- content(必填):string —— 提示文案(纯文本)
|
||
- placement(默认 top):'top' | 'bottom' —— 气泡方位;缺省 top(触发器上方)
|
||
- delay(默认 150):number —— 出场延迟毫秒;缺省 150,传 0 立即显示
|
||
- trigger(必填):HTMLElement —— 第二个参数:被包裹的触发器元素(按钮 / 输入框等任意 DOM)
|
||
|
||
示例:
|
||
|
||
```
|
||
tooltip({ content: '保存后立即生效' }, button({ label: '保存', onClick: save }))
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 把提示要挂在的元素作为第二个参数传入,返回的包裹节点替换原位置(气泡绝对定位,不占布局)
|
||
2. 鼠标悬停或键盘聚焦后延迟 delay 毫秒出场,移出 / 失焦 / Esc 收起;空间不足时自动翻到另一侧
|
||
3. 触摸屏没有 hover:手指按下即出场,抬手后停留 1.2 秒再收起,免得手指正挡着就消失
|
||
4. 气泡设了 pointer-events: none,不会挡住下面的点击;aria-describedby 已关联,读屏能念出提示文案
|
||
|
||
现场演示:apps/docs/demo.html#sec-tooltip
|
||
|
||
### popover — 气泡卡片 Popover
|
||
|
||
参数:
|
||
|
||
- content(必填):unknown —— 弹层内容:字符串 / DOM 节点 / 响应式函数,可放按钮等交互元素
|
||
- title:string —— 弹层标题;缺省不渲染标题行
|
||
- placement(默认 bottom):'top' | 'bottom' —— 弹层方位;缺省 bottom(触发器下方)
|
||
- trigger(默认 click):'click' | 'hover' —— 触发方式;缺省 click
|
||
- delay(默认 150):number —— hover 模式出场延迟毫秒;缺省 150
|
||
- onOpenChange(回调):(open: boolean) => void —— 开合回调
|
||
- triggerEl(第二个参数)(必填):HTMLElement —— 第二个参数:被包裹的触发器元素
|
||
- open:Signal<boolean> —— 受控开合信号;不传则内部维护,传了外部可编程开合(popconfirm 即基于它)
|
||
|
||
示例:
|
||
|
||
```
|
||
popover({ title: '筛选条件', content: h('div', null, '任意可交互内容') }, button({ label: '筛选' }))
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 创建触发器元素,再用 popover(props, trigger) 包一层:返回带弹层的包裹元素
|
||
2. 点击触发器开合;点弹层外部或按 Esc 收起(Esc 后焦点回触发器)
|
||
3. content 里可以放 button / checkbox 等交互元素(tooltip 只能纯文本),点击弹层内部不会误关
|
||
4. hover 场景传 trigger: 'hover' + delay 控制多快出场;placement: 'top' 让弹层往上翻
|
||
5. 需要感知开合做副作用(如懒加载数据)时用 onOpenChange
|
||
6. 展开时按 Esc 只收起气泡并回焦触发器,不会连带关闭所在弹窗(逐层关闭)
|
||
|
||
现场演示:apps/docs/demo.html#sec-popover
|
||
|
||
### spinner — 加载指示器 Spinner
|
||
|
||
参数:
|
||
|
||
- size(默认 18):number —— 直径像素;缺省 18
|
||
- label:string —— 读屏播报文案,如「正在加载订单」;缺省「加载中」
|
||
|
||
示例:
|
||
|
||
```
|
||
spinner(); spinner({ size: 26 })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 数据加载时插入 spinner(),加载完成移除
|
||
2. 用 size 匹配上下文尺寸(按钮内用小号,整页 loading 用大号)
|
||
3. 与 skeleton 分工:已知内容结构用 skeleton 占位,只有『在跑』的短等待用 spinner
|
||
4. 区块级加载给 spinner 传 label 说明在加载什么,读屏才不会只念一句「加载中」
|
||
|
||
现场演示:apps/docs/demo.html#sec-feedback
|
||
|
||
### progress — 进度条 Progress
|
||
|
||
参数:
|
||
|
||
- value:Signal<number> —— 当前进度 0-100 的信号;不传即不确定态(未知时长的加载)
|
||
- showText:boolean —— 是否右侧显示百分比;缺省 true(不确定态忽略)
|
||
- label:string —— 可访问名,如「上传进度」;缺省不确定态念「进行中」
|
||
- variant:'default' | 'success' | 'warning' | 'danger' —— 填充语义色;缺省 default(满格自动切 success)
|
||
|
||
示例:
|
||
|
||
```
|
||
const pct = signal(0); progress({ value: pct }); setInterval(() => pct((pct() + 5) % 105), 400)
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 传 value 信号给定量进度,不传则退化为往复的不确定态
|
||
2. label 写清这是什么进度,读屏会把名称与百分比一起念出来
|
||
3. 失败或告警用 variant: 'danger' / 'warning',该颜色在满格时也不被 success 覆盖
|
||
|
||
现场演示:apps/docs/demo.html#sec-feedback
|
||
|
||
### tabs — 标签页 Tabs
|
||
|
||
参数:
|
||
|
||
- tabs(必填):TabItem[] —— 页签列表,每项 { value, label, disabled?, closable? }
|
||
- active:Signal<string> —— 当前激活页签 value 信号,双向绑定
|
||
- onChange(回调):(value: string) => void —— 切换回调
|
||
- onClose(回调):(value: string) => void —— 关闭页签回调:组件不自行移除,凭 value 从数据源里摘掉后重绘
|
||
- closable:boolean —— 页签项上的开关:true 时渲染关闭按钮(×),关闭按钮不进 Tab 序但可用鼠标点;缺省不可关
|
||
|
||
示例:
|
||
|
||
```
|
||
const tab = signal('a'); tabs({ tabs: [{ value: 'a', label: '概览', closable: true }, { value: 'b', label: '明细', disabled: true }], active: tab, onClose: (v) => toast.info(`关闭 ${v}`) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 创建信号 tab = signal('a'),初值为默认页签
|
||
2. tabs 只渲染页签头;页签内容用响应式函数按信号切换,如 () => tab() === 'a' ? panelA : panelB
|
||
3. 点击或方向键(←→ 环形)、Home/End(直达首/末页签)切换,滑动指示条自动跟随;disabled: true 的页签置灰不可点
|
||
4. 需要可关闭页签时给该项加 closable: true,× 或聚焦后按 Delete 触发 onClose(value);组件不改数据源,调用方从数组里摘掉该项再重绘
|
||
5. 键盘可访问:Tab 聚焦激活页签,←→ 在启用页签间环形移动,Home/End 跳首个/末个启用页签
|
||
|
||
现场演示:apps/docs/demo.html#sec-tabs-modal
|
||
|
||
### stepper — 步骤条 Stepper
|
||
|
||
参数:
|
||
|
||
- steps(必填):StepperItem[] —— 步骤列表,每项 { label, description? }
|
||
- active(必填):Signal<number> —— 当前步索引(0 基)信号,双向绑定:回跳写回、外部赋值同步
|
||
- clickable:boolean —— 已完成步骤是否可点击回跳;缺省 false
|
||
- onChange(回调):(index: number) => void —— 步骤切换回调(点击回跳触发)
|
||
|
||
示例:
|
||
|
||
```
|
||
const step = signal(1); stepper({ steps: [{ label: '下单' }, { label: '支付' }, { label: '完成' }], active: step, clickable: true })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 用 signal(0) 创建当前步索引,传入 active;索引之前的步骤显示 ✓ 已完成态
|
||
2. 流程推进时外部赋值即可:step(step() + 1),组件三态自动跟随
|
||
3. clickable: true 时已完成步骤可点击回跳并写回信号,配合 onChange 同步业务状态
|
||
|
||
现场演示:apps/docs/demo.html#sec-stepper
|
||
|
||
### rate — 评分 Rate
|
||
|
||
参数:
|
||
|
||
- value(必填):Signal<number> —— 当前评分(1 基,allowHalf 时 0.5 步进)信号,双向绑定:点选/键盘写回、外部赋值同步
|
||
- max(默认 5):number —— 星标总数;缺省 5
|
||
- allowHalf(默认 false):boolean —— 允许半星:点击星标左半区记 0.5 分,键盘步进同为 0.5;缺省整数
|
||
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
|
||
- onChange(回调):(value: number) => void —— 评分变化回调
|
||
|
||
示例:
|
||
|
||
```
|
||
const score = signal(0); rate({ value: score, max: 5, onChange: (v) => console.log(v) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 用 signal(0) 创建评分信号并传入 value;0 表示未评分
|
||
2. 点击星标或聚焦后按 →/↑ 加一分、←/↓ 减一分;悬停高亮预览、移出还原
|
||
3. max 可调整星标总数;disabled 支持布尔或信号禁用
|
||
4. 要支持半星就传 allowHalf:点星标左半区记 0.5 分,悬停位置实时预览半格填充
|
||
|
||
现场演示:apps/docs/demo.html#sec-rate
|
||
|
||
### empty — 空状态 Empty
|
||
|
||
参数:
|
||
|
||
- title:string —— 主标题;缺省不渲染
|
||
- description:string —— 补充说明;缺省不渲染
|
||
- icon:unknown —— 图标区内容(字符串或图标 DOM);缺省渲染 ⌀ 占位符号,装饰性元素不进无障碍树
|
||
- action:unknown —— 操作区内容(常见为按钮);缺省不渲染
|
||
|
||
示例:
|
||
|
||
```
|
||
empty({ title: '暂无数据', description: '调整筛选条件后再试', action: button({ label: '重置筛选', variant: 'ghost' }) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 在列表 / 表格 / 筛选结果为空时渲染 empty 占位
|
||
2. title 一句话说明状态,description 补充原因或建议
|
||
3. action 传入 DOM(如 ghost 按钮)引导用户下一步操作
|
||
4. 要换成自家插图就传 icon(字符串或 SVG 节点):它固定 aria-hidden,文案仍由 title / description 承担
|
||
|
||
现场演示:apps/docs/demo.html#sec-empty
|
||
|
||
### divider — 分隔线 Divider
|
||
|
||
参数:
|
||
|
||
- label:string —— 居中文案标签;缺省纯线条
|
||
- vertical:boolean —— 是否垂直分隔线(用于行内工具栏);缺省水平
|
||
- dashed:boolean —— 是否虚线;缺省实线
|
||
|
||
示例:
|
||
|
||
```
|
||
divider({ label: '更多信息' }) / divider({ vertical: true })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 在区块之间插入 divider 划分内容层次
|
||
2. 传 label 在水平线上显示居中文案,如「更多信息」
|
||
3. 工具栏内用 vertical: true 的行内竖线,dashed: true 切虚线弱化分隔感
|
||
|
||
现场演示:apps/docs/demo.html#sec-divider
|
||
|
||
### space — 间距容器 Space
|
||
|
||
参数:
|
||
|
||
- direction:'horizontal' | 'vertical' —— 主轴方向;缺省 horizontal 横排
|
||
- size:'sm' | 'md' | 'lg' | number —— 子元素间距:三档走 --ua-space-* 令牌,数字按像素;缺省 md
|
||
- align:'start' | 'center' | 'end' | 'baseline' —— 交叉轴对齐;缺省 center,按钮与文字混排不错位
|
||
- justify:'start' | 'center' | 'end' | 'between' —— 主轴分布;缺省 start,between 两端撑开
|
||
- wrap:boolean —— 空间不足是否换行;缺省 false
|
||
- block:boolean —— 是否占满父容器宽度;缺省按内容收缩
|
||
|
||
示例:
|
||
|
||
```
|
||
space({ size: 'lg' }, button({ label: '保存' }), button({ label: '取消', variant: 'ghost' })) / space({ direction: 'vertical', align: 'start' }, ...)
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 把一排需要统一间距的元素交给 space 托管,不再各自写 margin
|
||
2. 调间距:size 传 sm/md/lg 走令牌,需要精确像素时直接给数字
|
||
3. 竖排用 direction: 'vertical',配 align: 'start' 让子元素左对齐
|
||
4. 工具栏两端撑开用 justify: 'between' + block: true;窄屏放不下时加 wrap
|
||
|
||
现场演示:apps/docs/demo.html#sec-space
|
||
|
||
### pagination — 分页器 Pagination
|
||
|
||
参数:
|
||
|
||
- page(必填):Signal<number> —— 当前页码信号,双向绑定:外部赋值自动翻页,点击页码写回信号
|
||
- pageSize(必填):number —— 每页条数
|
||
- total(必填):number —— 总条数
|
||
- onChange(回调):(page: number) => void —— 翻页回调
|
||
- jumpable(默认 false):boolean —— 末尾显示跳页输入框(总页数 > 1 才出现);缺省 false
|
||
- pageSizes:number[] —— 每页条数候选(如 [10, 20, 50]);传了才渲染条数切换器
|
||
- onPageSizeChange(回调):(size: number) => void —— 切换每页条数回调,同时自动回到第 1 页
|
||
|
||
示例:
|
||
|
||
```
|
||
const page = signal(1); pagination({ page, pageSize: 3, total: 24, onChange: (p) => console.log(p) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 创建页码信号 page = signal(1),与 pageSize / total 一起传入
|
||
2. 页码 ≤7 全部展开;更多时只保留首尾与当前页 ±1,缺口折叠为省略号
|
||
3. 首末页自动禁用 ‹ › 翻页按钮,当前页禁点并标记 aria-current
|
||
4. 与 dataTable 联动:把同一个 page 信号传给 dataTable 的 page 参数,两处翻页自动同步
|
||
5. 键盘可达:全部是原生 button,Tab 聚焦后 Enter / Space 触发
|
||
6. pageSizes 传候选数组即出现「N 条/页」切换器,切换后自动回第 1 页;onPageSizeChange 拿新条数重算数据
|
||
|
||
现场演示:apps/docs/demo.html#sec-pagination
|
||
|
||
### modal — 弹窗 Modal
|
||
|
||
参数:
|
||
|
||
- open(必填):Signal<boolean> —— 开关信号:true 显示 / false 隐藏
|
||
- title(必填):string —— 标题
|
||
- size(默认 md):'sm' | 'md' | 'lg' —— 宽度档位:sm 340px / md 420px / lg 640px;缺省 md
|
||
- footer:unknown —— 底部操作区内容(常见为按钮组,右对齐);缺省不渲染
|
||
- closeOnOverlay:boolean —— 点遮罩是否关闭弹窗;缺省 true,表单类可传 false 防误关(× 与 Esc 仍可用)
|
||
- onClose(回调):() => void —— 关闭回调(点遮罩、× 或 Esc 触发)
|
||
- ...children:unknown[] —— 弹窗内容子节点
|
||
|
||
示例:
|
||
|
||
```
|
||
const open = signal(false); modal({ title: '确认操作', open, onClose: () => open(false) }, h('p', null, '内容'))
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 创建布尔信号 open = signal(false),弹窗初始隐藏
|
||
2. 把 open 传给 modal,其余参数为弹窗内容子节点
|
||
3. 打开:open(true);关闭:open(false),点遮罩、× 或 Esc 会自动写回 false;多弹层叠加时 Esc 只关最上层(后开先关)
|
||
4. 入场自带遮罩淡入 + 弹窗上浮动画
|
||
5. 底部操作按钮放进 footer(如取消/确定),需要更宽内容用 size: 'lg',窄确认框用 'sm'
|
||
6. 打开后 Tab 焦点锁在弹窗内,关闭自动把焦点还给打开前的元素
|
||
|
||
现场演示:apps/docs/demo.html#sec-tabs-modal
|
||
|
||
### drawer — 抽屉 Drawer
|
||
|
||
参数:
|
||
|
||
- title(必填):string —— 标题
|
||
- open(必填):Signal<boolean> —— 开关信号:true 滑出 / false 收起
|
||
- placement(默认 right):'left' | 'right' | 'top' | 'bottom' —— 滑出方向;缺省 right
|
||
- width(默认 320):number —— 面板尺寸(像素):左右滑出为宽度,上下滑出为高度;缺省 320
|
||
- footer:unknown —— 底部操作区内容;缺省不渲染
|
||
- closeOnOverlay:boolean —— 点遮罩是否关闭抽屉;缺省 true(× 与 Esc 始终可用)
|
||
- modal:boolean —— 是否模态:false 时无遮罩、不锁背景滚动、不困焦点,页面仍可操作(检查器 / 详情面板);缺省 true
|
||
- onClose(回调):() => void —— 关闭回调(点遮罩 / × / Esc 触发)
|
||
- ...children:unknown[] —— 面板内容子节点
|
||
|
||
示例:
|
||
|
||
```
|
||
const open = signal(false); drawer({ title: '详情', open, width: 360, onClose: () => open(false) }, h('p', null, '内容'))
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 创建布尔信号 open = signal(false),传给 drawer;其余参数为面板内容子节点
|
||
2. 打开后从侧边滑出:placement: 'right'(默认)或 'left',width 控制宽度(窄屏自动收到 92vw)
|
||
3. 点遮罩、× 或按 Esc 关闭并写回 false;打开时焦点落在面板首个可交互元素,Esc 只关最上层弹层
|
||
4. 底部操作按钮放进 footer;与 modal 的分工:modal 是强打断的确认,drawer 保留主视图上下文做详情/编辑
|
||
5. 面板内容与 modal 一样接受任意 DOM 与响应式函数
|
||
6. 要「一边看一边改」就把 modal 设成 false:遮罩透明且不吃点击、背景滚动不锁、Tab 不被困在面板里,× 与 Esc 仍能关
|
||
|
||
现场演示:apps/docs/demo.html#sec-drawer
|
||
|
||
### form — 表单 Form / 表单字段 FormItem
|
||
|
||
参数:
|
||
|
||
- layout(默认 vertical):'vertical' | 'horizontal' —— 布局方向;缺省 vertical(horizontal 为标签左置)
|
||
- submitText:string —— 提交按钮文案;缺省「提交」
|
||
- resetText:string —— 重置按钮文案;缺省不渲染
|
||
- onSubmit(回调):() => void —— 全部字段校验通过后的提交回调
|
||
- (formItem)label(必填):string —— 字段标签
|
||
- (formItem)value(必填):Signal<string> —— 字段值信号,校验数据源
|
||
- (formItem)required:boolean —— 必填:显示红星并自动追加非空规则
|
||
- (formItem)rules:FormRule[] —— 校验规则:required / min / max / pattern / validator,按序执行取首个失败
|
||
- (formItem)validateOn(默认 blur):'input' | 'blur' | 'submit' —— 校验时机;缺省 blur(输入时自动清已报错误)
|
||
- (formItem)hint:string —— 控件下方辅助说明(格式提示等);报错时让位给错误文案
|
||
- onReset:() => void —— 重置按钮点击回调;组件不自行清空字段,由调用方重置信号;不传 resetText 不渲染按钮
|
||
- onError(回调):(count: number) => void —— 校验不通过时的回调,参数为不通字段数(适合埋点或顶部提示)
|
||
- errorSummary:boolean —— 是否在表单顶部显示「有 N 项需要修改」汇总条;缺省 true,自带错误 UI 时传 false
|
||
|
||
示例:
|
||
|
||
```
|
||
const name = signal(''); const f = form({ onSubmit: submit }, [formItem({ label: '用户名', required: true, value: name, rules: [{ min: 2, max: 12 }] }, textField({ value: name }))])
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 为每个字段创建 signal 与控件,如 const name = signal(''); textField({ value: name })
|
||
2. 用 formItem 包住控件:formItem({ label, required, value, rules }, control),返回 { el, validate } 句柄
|
||
3. 把句柄数组交给 form:form({ onSubmit }, items),form 渲染全部字段 + 提交/重置按钮
|
||
4. 提交时逐项校验:全过触发 onSubmit;有错则标红控件、显示错误文案并聚焦首个出错项
|
||
5. 规则语义:required 非空(trim)、min/max 长度、pattern 正则、validator 自定义(true 通过 / 字符串为错误)
|
||
6. 非必填字段为空时跳过其余规则;Enter 键触发表单原生 submit(已拦截走校验流程)
|
||
7. 错误态覆盖包裹式控件:标红作用于控件根与内部原生控件(含自绘 select 的触发器),并同步 aria-invalid,读屏与视觉一致
|
||
8. 触发时机用 input / change / focusout(均会冒泡),因此 checkbox / select / 单选组这类包裹式控件同样能失焦校验
|
||
9. 单行输入框内按 Enter 等同点击提交(textarea 内仍是换行);提交按钮为 type="submit",不会与点击回调重复触发
|
||
10. rules.validator 返回 Promise 即为异步规则(如「邀请码是否已被使用」远程校验):提交时逐项 await,校验期间提交按钮禁用,结论按轮次取舍避免乱序覆盖
|
||
11. formItem 返回 { el, validate, validateAsync } 句柄:validate 只跑同步规则(供 blur/input 即时反馈),validateAsync 逐条 await 供 form 聚合提交
|
||
12. 需要给字段加格式说明就传 hint:平时显示在控件下方,校验失败时自动让位给错误文案,同一位置不堆两行
|
||
|
||
现场演示:apps/docs/demo.html#sec-formlab
|
||
|
||
### dataTable — 数据表格 DataTable
|
||
|
||
参数:
|
||
|
||
- data(必填):T[] —— 全量行数据,每行须含 id 字段作 rowKey
|
||
- columns(必填):ColumnDef<T>[] —— 列定义:内核字段 { key, header?, sortable?, width? } + 皮肤字段 { align?, format? };width 数字为固定像素,'flex' 自适应
|
||
- filterKey:string —— 工具栏筛选的列;缺省取第一列
|
||
- pageSize(默认 5):number —— 每页行数;缺省 5
|
||
- selectable(默认 false):boolean —— 启用行选择列:全选三态(含半选)、行 checkbox、已选计数;缺省 false
|
||
- page:Signal<number> —— 受控页码信号:与内核分页双向同步,传同一个信号给独立 pagination 组件即可联动
|
||
- loading:boolean | Signal<boolean> —— 加载态:为真时数据行换成骨架行(行数随 pageSize),筛选输入与翻页按钮临时禁用,容器带 aria-busy
|
||
- selected:Signal<RowKey[]> —— 受控选中行信号:与内核选择双向同步(内容比较,不来回派发),供批量操作读取/改写
|
||
- exportable:boolean —— 工具栏显示「导出 CSV」:导出筛选 + 排序后的全量行(不分页);缺省 false
|
||
- exportName:string —— 导出文件名;缺省 table.csv
|
||
|
||
示例:
|
||
|
||
```
|
||
dataTable({ data: users, selectable: true, columns: [{ key: 'name', header: '姓名', sortable: true }], pageSize: 5 })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 准备行数据,确保每行有唯一 id(跨页选择与刷新不丢行靠它)
|
||
2. 定义 columns:sortable: true 的列表头可点击排序(升 → 降 → 取消循环),标记箭头 ↑↓↕
|
||
3. 列展示细节用皮肤字段:align 控制表头与单元格对齐,format(value, row) 返回纯文本(走 textContent,不需要转义)
|
||
4. 列宽用内核字段 width:数字(像素)固定该列,'flex' 或不设则按内容自适应
|
||
5. selectable: true 开启行选择:表头全选框带 indeterminate 半选态,工具栏显示已选计数
|
||
6. 选中行用低视觉比重样式(淡色底 + 弱化文字),选择状态存 rowKey,跨页不丢失
|
||
7. 工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行
|
||
8. 表头粘性固定(滚动容器 max-height 420px),斑马纹 + 悬停高亮由 CSS 提供
|
||
9. 数据在途时传 loading(布尔或信号):表格体换成骨架行、筛选与翻页禁用,返回后置回 false 即恢复
|
||
10. 需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用
|
||
11. 需要批量操作时传 selected 信号:内部勾选实时写回,外部置空或赋值即同步整表(跨页保留,按 rowKey 存)
|
||
12. 可排序表头的 aria-sort 跟随排序态同步(ascending / descending / none),读屏能直接播报当前列序,箭头只是视觉补充
|
||
13. 需要导出时开 exportable:按钮导的是筛选+排序后的全量行,与当前页无关,单元格按 format 输出
|
||
|
||
现场演示:apps/docs/demo.html#sec-table
|
||
|
||
### timeline — 时间线 Timeline
|
||
|
||
参数:
|
||
|
||
- items(必填):TimelineItem[] —— 条目列表,按时间顺序自上而下;每项含 title(必填)、content、time、variant
|
||
- mode(默认 left):'left' | 'right' —— 轴线与节点所在侧;缺省 left(内容在右),right 时内容左对齐轴线居右
|
||
- pending(默认 false):boolean —— 末尾追加虚线「进行中…」节点,表示流程尚未结束;缺省 false
|
||
|
||
示例:
|
||
|
||
```
|
||
timeline({ items: [{ title: '提交审批', time: '09:12', variant: 'success' }, { title: '财务复核', time: '10:30', content: '等待张工处理' }], pending: true })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 事件流按时间顺序排(组件不做排序):较早的条目放前面,最新的放最后
|
||
2. variant 只承载语义:success 完成、warning 待处理、danger 失败、default 普通记录(节点色走 --ua-* 令牌,随暗色主题变化)
|
||
3. time 是纯展示文案,格式自由('09:12' / '10-23 18:00'),组件不做日期解析
|
||
4. 流程未结束时传 pending: true,末尾自动补一个虚线脉冲节点,完成后置回 false 即消失
|
||
5. 需要轴线居右(如右侧栏时间流)时传 mode: 'right',条目与节点整体镜像
|
||
6. content 为纯文本;需要按钮等交互内容时改用 accordion / card 组合
|
||
|
||
现场演示:apps/docs/demo.html#sec-timeline
|
||
|
||
### segmented — 分段控制器 Segmented
|
||
|
||
参数:
|
||
|
||
- options(必填):OptionItem[] —— 分段选项列表(互斥),每项含 value / label / disabled
|
||
- value(必填):Signal<string> —— 当前选中值信号,双向绑定(点击写回,外部赋值即同步滑块)
|
||
- size(默认 md):'sm' | 'md' —— 尺寸档位;缺省 md
|
||
- disabled:boolean | Signal<boolean> —— 整组禁用:布尔直接置位,传信号则动态同步;单项禁用走 options[].disabled
|
||
- onChange(回调):(value: string) => void —— 切换回调(禁用项与重复点击当前项都不触发)
|
||
|
||
示例:
|
||
|
||
```
|
||
segmented({ options: [{ value: 'day', label: '日' }, { value: 'week', label: '周' }], value: range })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 和 tabs 的分工:tabs 是页签(下划线指示条 + 面板语义),segmented 是紧凑互斥切换(视图 / 单位 / 时间粒度)
|
||
2. value 必须传信号:点击写回信号,外部改信号滑块自动平移,不要在回调里另存一份状态
|
||
3. options[].disabled 置灰单项;disabled 传布尔或信号则整组失效(连滑块一起置灰)
|
||
4. 键盘:←→ 在可用项间环形移动并选中,Tab 只落在当前项上(漫游 tabindex)
|
||
5. 滑块几何由组件测量按钮位置得到,不要在外部改按钮宽度 / 内边距,否则需自行重渲染
|
||
6. 需要带图标或更复杂布局的分段,改用 radioGroup + 自定义样式
|
||
|
||
现场演示:apps/docs/demo.html#sec-segmented
|
||
|
||
### descriptions — 描述列表 Descriptions
|
||
|
||
参数:
|
||
|
||
- items(必填):DescriptionItem[] —— 描述项列表:每项含 label(字段名)、value(字符串 / DOM / 响应式函数)、span(跨列数)
|
||
- column(默认 3):1 | 2 | 3 | 4 —— 桌面列数;缺省 3,窄屏(≤640px)由样式表降为单列
|
||
- bordered(默认 false):boolean —— 表格形态(带分隔线与字段名底纹);缺省 false 纯网格
|
||
- size(默认 md):'sm' | 'md' —— 尺寸档位;缺省 md
|
||
- title:string —— 分组标题;缺省不渲染标题行
|
||
|
||
示例:
|
||
|
||
```
|
||
descriptions({ title: '订单信息', column: 2, bordered: true, items: [{ label: '订单号', value: 'RB-2048' }, { label: '金额', value: '¥1,280.00' }, { label: '备注', value: '加急', span: 2 }] })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 详情页 / 抽屉里展示只读字段用它,不要用 form(form 承载输入与校验)
|
||
2. column 按信息密度定:字段多且短用 3-4 列,字段长(地址、备注)用 2 列并配 span 占整行
|
||
3. value 支持任意 DOM:放 tag、badge、button 都可以;要给值做响应式更新就传函数 () => 内容
|
||
4. bordered: true 是表格形态(适合打印与密集信息),纯网格形态更轻,适合卡片内嵌
|
||
5. 窄屏自动降为单列,无需在调用方写媒体查询
|
||
6. 纯展示组件,无状态无回调:值变了由调用方重新渲染或传函数式 value
|
||
|
||
现场演示:apps/docs/demo.html#sec-descriptions
|
||
|
||
### statistic — 统计数值 Statistic
|
||
|
||
参数:
|
||
|
||
- title:string —— 指标名(小字灰标题);缺省不渲染
|
||
- value(必填):Signal<number> —— 数值信号:变化时滚动过渡到新值;倒计时模式可不传
|
||
- countdown:number —— 目标时间戳(毫秒):传了即倒计时模式,每秒显示 DD 天 HH:MM:SS,忽略 value
|
||
- precision(默认 0):number —— 小数位,千分位分隔自动带;缺省 0
|
||
- prefix:string —— 数字前缀(如 ¥);缺省不渲染
|
||
- suffix:string —— 数字后缀(如 元 / %);缺省不渲染
|
||
- trend:'up' | 'down' —— 环比方向:up 红 / down 绿(A 股涨红跌绿口径);缺省不渲染
|
||
|
||
示例:
|
||
|
||
```
|
||
const gmv = signal(128000); statistic({ title: '本月 GMV', value: gmv, prefix: '¥', precision: 2, trend: 'up' })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. value 必须传信号:组件用 effect 监听,值变了自动滚动过渡,不要在外部手动改文本
|
||
2. 首跑也会从 0 滚到目标值(入场动画);不想要就初始化信号为终值后在挂载后再改
|
||
3. precision 控制小数位,千分位由 toLocaleString 自动加,不要自己拼格式化
|
||
4. trend 只表达方向与颜色(涨红跌绿),数值本身与环比幅度仍由调用方组织
|
||
5. 多条指标并排时用 descriptions 或 flex 容器排列,本组件不自带网格
|
||
6. 用户开启「减弱动效」时组件直接落终值,无需调用方判断
|
||
7. 做倒计时(活动剩余时间等)传 countdown: 目标时间戳,组件每秒刷新并在到点/卸载时自动停表
|
||
|
||
现场演示:apps/docs/demo.html#sec-statistic
|
||
|
||
### list — 列表 List
|
||
|
||
参数:
|
||
|
||
- items(必填):ListItem[] —— 条目列表,每项 { title, description?, avatar?, extra? }
|
||
- header:string —— 列表头标题;缺省不渲染
|
||
- footer:string —— 列表尾说明;缺省不渲染
|
||
- bordered:boolean —— 是否带外边框与分隔线;缺省 false
|
||
- emptyText:string —— 无数据占位文案;缺省「暂无数据」
|
||
- onItemClick(回调):(item: ListItem, index: number) => void —— 条目点击回调;提供后条目可点击并带 hover 反馈
|
||
|
||
示例:
|
||
|
||
```
|
||
list({ header: '待办', bordered: true, items: [{ title: '报销单 RB-2048', description: '等待复核', avatar: avatar({ name: '王' }) }], onItemClick: (item) => console.log(item.title) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 准备 items 数组,每项至少给 title,description 作次要说明
|
||
2. avatar 传头像组件(如 avatar({ name }))、extra 传右侧附加(如 tag(...)),不传该侧不留位
|
||
3. 需要点击时传 onItemClick(item, index),条目自动加 hover 底色;bordered: true 给外边框与分隔线
|
||
4. items 为空数组时自动渲染 empty 占位,文案可用 emptyText 覆盖
|
||
|
||
现场演示:apps/docs/demo.html#sec-list
|
||
|
||
### carousel — 轮播 Carousel
|
||
|
||
参数:
|
||
|
||
- items(必填):unknown[] —— 每张幻灯片内容,顺序即播放顺序
|
||
- index:Signal<number> —— 当前下标信号;不传则内部维护
|
||
- autoplay:number —— 自动前进间隔(毫秒);缺省不自动播放
|
||
- height:number —— 固定高度像素;缺省由内容撑开
|
||
- label:string —— 区域无障碍名称;缺省「轮播」
|
||
- onChange(回调):(index: number) => void —— 切换回调:点击指示点/箭头/键盘/自动前进都会触发
|
||
|
||
示例:
|
||
|
||
```
|
||
const i = signal(0); carousel({ items: [h('div', null, 'A'), h('div', null, 'B')], index: i, height: 150, autoplay: 4000, onChange: (k) => console.log(k) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. items 每项是任意节点(图、卡片、文字均可),顺序即播放顺序
|
||
2. index 传自己的信号即可外部受控(外部改信号轮播会跟),不传则由组件内部维护
|
||
3. height 控固定高度(缺省随内容撑开);autoplay 传毫秒数自动前进,鼠标悬停自动暂停
|
||
4. 键盘:聚焦轮播后 ←→ 切换,事件不冒泡避免连带切换外层 tabs/menu
|
||
5. 指示点与箭头都可点击,末张下一张环形回到首张
|
||
|
||
现场演示:apps/docs/demo.html#sec-carousel
|
||
|
||
### tree — 树形 Tree
|
||
|
||
参数:
|
||
|
||
- nodes(必填):TreeNode[] —— 根节点列表,每项 { key, label, children?, disabled? }
|
||
- expanded:Signal<string[]> —— 展开的 key 集合;不传则内部维护
|
||
- selected:Signal<string> —— 选中 key 信号;不传则内部维护
|
||
- defaultExpandAll:boolean —— 首屏展开全部分支;缺省 false
|
||
- searchable:boolean —— 顶部搜索框:按标签过滤,命中节点保留父链并自动展开;缺省 false
|
||
- onSelect(回调):(node: TreeNode) => void —— 选中回调:点击标签或 Enter/空格触发
|
||
- multi:boolean —— 是否显示勾选框并开启多选;缺省 false
|
||
- checked:Signal<string[]> —— 勾选 key 集合(multi 时生效);不传则内部维护
|
||
- onCheck:(keys: string[]) => void —— 勾选变更回调(父子已级联,传副本)
|
||
|
||
示例:
|
||
|
||
```
|
||
const sel = signal(''); tree({ nodes: [{ key: 'src', label: 'src', children: [{ key: 'ui', label: 'ui' }] }], selected: sel, onSelect: (n) => console.log(n.key) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 节点树用 { key, label, children } 描述,key 唯一即可(选中与展开都按 key 记)
|
||
2. 传 selected 信号即可外部受控;不传则组件内部维护选中态
|
||
3. 点箭头只展开/收起,点标签才选中;叶子节点箭头位置留空以对齐
|
||
4. 键盘:↑↓ 在可见节点间移动、Home/End 到首末行(只移动焦点),Enter/空格 选中、→ 展开、← 收起;disabled 节点点击与键盘都不响应
|
||
5. defaultExpandAll 首屏展开全部分支(外部已给 expanded 初值时以外部为准)
|
||
6. multi: true 时每行前置勾选框:勾父级下钻全选子孙(跳过禁用项),子孙全选后父级自动上勾、部分勾选显示半选
|
||
7. 勾选框点击不触发选中(stopPropagation),键盘在多选模式下 Enter/空格改为切换勾选
|
||
8. 节点多时开 searchable:输入即过滤,命中节点连同父链一起显示,隐藏的枝不参与方向键遍历
|
||
|
||
现场演示:apps/docs/demo.html#sec-tree
|
||
|
||
### upload — 文件选择 Upload
|
||
|
||
参数:
|
||
|
||
- accept:string —— 接受的类型(同原生 accept),如 'image/*,.pdf'
|
||
- multiple:boolean —— 允许多选;缺省 false(单选时新选择覆盖旧的)
|
||
- hint:string —— 提示文案;缺省「点击或拖拽文件到此处」
|
||
- maxSize:number —— 单文件体积上限(字节);超限丢弃并在提示行说明,缺省不限
|
||
- maxCount:number —— multiple 时的文件数上限;超出部分丢弃并提示,缺省不限
|
||
- disabled:boolean —— 禁用:不可点选、不接受拖拽
|
||
- onChange(回调):(files: File[]) => void —— 文件变化回调:选择/拖拽/移除后都触发,回传副本
|
||
|
||
示例:
|
||
|
||
```
|
||
upload({ multiple: true, accept: 'image/*', hint: '拖拽图片到此处', onChange: (files) => console.log(files.map((f) => f.name)) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 把 upload 放进表单,accept 限定类型、multiple 允许多选
|
||
2. 需要拦体积就给 maxSize(字节),限制数量给 maxCount
|
||
3. 被拒的文件不会进清单,原因显示在下方提示行(拖拽与点选走同一套校验)
|
||
4. onChange 拿到的 File[] 是副本,自行发请求上传(组件不内置网络层)
|
||
|
||
现场演示:apps/docs/demo.html#sec-upload
|
||
|
||
### notification — 通知卡片 Notification
|
||
|
||
参数:
|
||
|
||
- title(必填):string —— 通知标题
|
||
- description:string —— 补充说明;缺省不渲染
|
||
- variant:'info' | 'success' | 'warning' | 'danger' —— 语义色(左侧条与圆点);缺省 info
|
||
- duration:number —— 自动关闭毫秒;缺省 0 = 常驻不自动关,非 0 时悬停 / 聚焦暂停计时
|
||
- closable:boolean —— 是否显示关闭按钮;缺省 true
|
||
- action:unknown —— 操作区内容(如按钮);缺省不渲染
|
||
|
||
示例:
|
||
|
||
```
|
||
const { close } = notification({ title: '导出完成', description: 'report.csv 已开始下载', variant: 'success', duration: 4000 }); // close() 手动关闭
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 调用即入列:右下角堆叠,同屏最多 3 条,超出自动挤掉最早一条
|
||
2. 缺省常驻(duration 0),需要自动消失就传毫秒数,如 duration: 4000
|
||
3. 返回 { el, close } 句柄:程序化关闭用 close(),需要自己挂交互就用 el
|
||
4. action 放操作区(常见为按钮);variant 决定左侧条与圆点语义色
|
||
5. 与 toast 的分工:toast 右上角轻量瞬时,notification 右下角可长读、可操作
|
||
|
||
现场演示: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
|
||
|
||
### transfer — 穿梭框 Transfer
|
||
|
||
参数:
|
||
|
||
- dataSource(必填):TransferItem[] —— 全量条目,左右两栏都按是否在 targetKeys 里派生
|
||
- targetKeys:Signal<string[]> —— 右侧集合信号;不传则组件内部维护,传了即外部受控
|
||
- titles:[string, string] —— 左右栏标题;缺省「待选列表 / 已选列表」
|
||
- searchable:boolean —— 是否显示搜索框;缺省 true,逐栏独立过滤
|
||
- height:number —— 列表区固定高度像素;缺省走样式表 220,超出滚动
|
||
- onChange:(keys: string[]) => void —— 右侧集合变化回调(搬运后触发,传副本)
|
||
|
||
示例:
|
||
|
||
```
|
||
transfer({ targetKeys: enabled, titles: ['待选', '已启用'], onChange: (keys) => toast.info(`已启用 ${keys.length} 项`), dataSource: [{ key: 'a11y', label: '无障碍语义' }, { key: 'ssr', label: 'SSR(暂不可用)', disabled: true }] })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. dataSource 是唯一数据源,条目归属由 targetKeys 决定;搬运只改 targetKeys,不删条目
|
||
2. 两栏独立勾选,勾选后点中间 → / ← 整体搬运,搬完两栏勾选自动清空;无勾选时按钮自动禁用
|
||
3. 每栏均有搜索框与全选:全选只覆盖当前搜索结果里未禁用的条目,部分勾选时全选框显示半选
|
||
4. disabled 条目不可勾选、不计入全选基数、也不会被搬运
|
||
5. 传入 targetKeys 信号即为受控用法,外部写入即可程序化搬运(如「一键全选」)
|
||
|
||
现场演示:apps/docs/demo.html#sec-transfer
|
||
|
||
### cascader — 级联选择 Cascader
|
||
|
||
参数:
|
||
|
||
- options(必填):CascaderOption[] —— 根级选项,children 递归出列,value 逐级拼成路径
|
||
- value:Signal<string[]> —— 选中路径信号;不传则内部维护,传了即外部受控
|
||
- placeholder:string —— 未选时占位文案;缺省「请选择」
|
||
- separator:string —— 路径分隔符;缺省「 / 」
|
||
- changeOnSelect:boolean —— 允许停在中间层;缺省 false 必须选到叶子才提交
|
||
- disabled:boolean | Signal<boolean> —— 禁用触发器;支持信号响应式禁用
|
||
- onChange:(path: string[]) => void —— 选中回调(叶子或中间层,传副本)
|
||
|
||
示例:
|
||
|
||
```
|
||
cascader({ value: areaPath, changeOnSelect: true, options: [{ value: 'east', label: '华东', children: [{ value: 'sh', label: '上海' }] }] })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 传入嵌套 options,第一列是根级选项,悬停带 children 的项即展开下一列
|
||
2. 点击叶子提交;changeOnSelect 为 true 时点中间层也提交(适合「省/市」这类粗粒度就够用的场景)
|
||
3. value 是路径数组(逐级 value),传信号即受控;触发器文案按 separator 拼接已提交路径
|
||
4. 键盘:↑↓ 同列移动、→ 下钻、← 回退、Enter/空格 提交、Esc 收起(与弹窗共用一个 Esc 栈)
|
||
5. 面板空间不足时自动向上翻转;disabled 选项不可点也不会展开下一列
|
||
|
||
现场演示:apps/docs/demo.html#sec-cascader
|
||
|
||
### splitter — 可拖拽分栏 Splitter
|
||
|
||
参数:
|
||
|
||
- panes(必填):unknown[] —— 各栏内容(节点或纯文本),顺序即排列顺序
|
||
- direction:'horizontal' | 'vertical' —— 分割方向;缺省 horizontal(左右)
|
||
- sizes:Signal<number[]> —— 各栏百分比数组(和为 100);不传则内部均分
|
||
- min:number —— 每栏最小百分比;缺省 10,拖到边界即停
|
||
- onChange:(sizes: number[]) => void —— 拖拽 / 键盘调整后回调(传副本)
|
||
|
||
示例:
|
||
|
||
```
|
||
splitter({ sizes: ratio, min: 15, panes: [leftPanel, rightPanel], onChange: (s) => ratio([...s]) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. panes 长度决定栏数与分隔条数(n 栏对应 n-1 条),栏宽由 sizes 百分比驱动
|
||
2. 指针按住分隔条拖动即可改比例,拖动过程两栏之和恒定、各自不低于 min
|
||
3. 分隔条可 Tab 聚焦,← → / ↑ ↓ 每次调 2%,按住 Shift 调 10%;双击复位为均分
|
||
4. direction 传 vertical 变为上下分栏,键盘方向键与光标同步切换
|
||
5. 传入 sizes 信号即受控,外部写入可做「预设布局」切换(如 1:1 / 2:1)
|
||
|
||
现场演示:apps/docs/demo.html#sec-splitter
|
||
|
||
### autoComplete — 自动补全 AutoComplete
|
||
|
||
参数:
|
||
|
||
- options(必填):AutoCompleteOption[] | ((q: string) => AutoCompleteOption[]) —— 候选项:静态列表或按关键字计算的函数(可远程联想)
|
||
- value:Signal<string> —— 输入值信号;不传则内部维护,传了即外部受控
|
||
- placeholder:string —— 占位文案,同时作为无障碍名称
|
||
- emptyText:string —— 无匹配时的提示文案;缺省「无匹配项」
|
||
- maxItems:number —— 最多展示条数;缺省 8
|
||
- disabled:boolean | Signal<boolean> —— 禁用输入与弹层
|
||
- onInput:(query: string) => void —— 每次输入回调,适合触发远程请求
|
||
- onSelect:(option: AutoCompleteOption) => void —— 选中回调(点击或 Enter)
|
||
|
||
示例:
|
||
|
||
```
|
||
autoComplete({ value: acValue, placeholder: '输入组件名', options: [{ value: 'dataTable', hint: '表格' }], onSelect: (o) => toast.success(o.value) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 输入任何字符即按包含匹配(忽略大小写)过滤候选,空串时弹层不展开
|
||
2. ↑↓ 在候选间环形移动并高亮,Enter 选中,Esc 收起,Tab 失焦也会收起
|
||
3. 候选行支持右侧 hint(分类 / 拼音),选中后值写回输入框并触发 onSelect
|
||
4. options 传函数时每次输入都会用当前关键字调用,可在此发起远程请求并返回新数组
|
||
5. value 传信号即受控:外部写入会同步回填输入框内容
|
||
|
||
现场演示:apps/docs/demo.html#sec-autocomplete
|
||
|
||
### datePicker — 日期选择 DatePicker
|
||
|
||
参数:
|
||
|
||
- value:Signal<string> —— 选中日期(YYYY-MM-DD)信号;不传则内部维护
|
||
- placeholder:string —— 未选时占位文案;缺省「请选择日期」
|
||
- min:string —— 最早可选日期(含,YYYY-MM-DD);更早的日置灰不可点
|
||
- max:string —— 最晚可选日期(含,YYYY-MM-DD);更晚的日置灰不可点
|
||
- disabled:boolean | Signal<boolean> —— 禁用触发器
|
||
- onChange:(date: string) => void —— 选中回调(点击日期或 Enter)
|
||
|
||
示例:
|
||
|
||
```
|
||
datePicker({ value: dateVal, min: '2026-10-20', max: '2026-10-31', onChange: (d) => toast.success(d) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 点触发器展开当月网格,打开时视图自动对准已选日期(无选中则今天)
|
||
2. ‹ › 翻月,底部「今天」一键跳今天并提交;今天用描边标记、选中用实心底色
|
||
3. 键盘:←→ 逐日、↑↓ 逐周、PageUp/PageDown 逐月、Enter 提交,跨月会自动翻页
|
||
4. min / max 为闭区间,越界日期置灰不可点也不可键盘选中
|
||
5. value 传信号即受控;Esc 收起(与弹窗/抽屉共用逐层关闭栈)
|
||
|
||
现场演示:apps/docs/demo.html#sec-datepicker
|
||
|
||
### backTop — 返回顶部 BackTop
|
||
|
||
参数:
|
||
|
||
- target:HTMLElement | Window —— 监听的滚动容器;缺省 window(整页)
|
||
- threshold:number —— 滚动超过多少像素才浮现;缺省 300
|
||
- progress:boolean —— 是否显示滚动进度环;缺省 true
|
||
- onClick:() => void —— 回顶之后的回调(适合埋点)
|
||
|
||
示例:
|
||
|
||
```
|
||
backTop({ threshold: 300, onClick: () => toast.info('已回到顶部') })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 把返回的元素挂到页面任意位置即可,组件自身 position: fixed 定位在右下角
|
||
2. 滚动越过 threshold 才浮现(淡入 + 上移微动画),回到阈值内自动隐藏
|
||
3. progress 为 true 时外环弧长等于已滚比例;内滚容器用 target 传入该元素
|
||
4. 点击平滑回顶;系统开启「减少动效」时改为瞬间跳转
|
||
|
||
现场演示:apps/docs/demo.html#sec-backtop
|
||
|
||
### colorPicker — 颜色选择 ColorPicker
|
||
|
||
参数:
|
||
|
||
- value:Signal<string> —— 颜色值信号(CSS 颜色串,支持 var(--ua-*));不传则内部维护
|
||
- presets:string[] —— 预设色板;缺省取语义令牌色(accent/success/warning/danger/text/border)
|
||
- placeholder:string —— 未选时占位文案;缺省「请选择颜色」
|
||
- disabled:boolean | Signal<boolean> —— 禁用触发器
|
||
- onChange:(color: string) => void —— 选定回调(点色板、取色器或手输回车)
|
||
|
||
示例:
|
||
|
||
```
|
||
colorPicker({ value: colorVal, onChange: (c) => toast.success(c) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 点触发器展开弹层:上方是预设色板,下方是原生取色器 + 手输框
|
||
2. 预设缺省直接用 --ua-* 令牌色,所以暗色主题切换后色块自动跟着变,无需维护两套色值
|
||
3. 点色块即提交;手输框支持任意 CSS 颜色串(含 var(--ua-color-accent)),回车提交
|
||
4. 触发器左侧色块始终预览当前值,点击外部或 Esc 收起
|
||
|
||
现场演示:apps/docs/demo.html#sec-colorpicker
|
||
|
||
### tour — 引导漫游 Tour
|
||
|
||
参数:
|
||
|
||
- steps(必填):TourStep[] —— 步骤序列:每步含 target 选择器、title、可选 description
|
||
- padding:number —— 聚光区相对目标的外扩像素;缺省 6
|
||
- onFinish:() => void —— 走完最后一步的回调
|
||
- onClose:() => void —— 中途跳过(× 或 Esc)的回调
|
||
|
||
示例:
|
||
|
||
```
|
||
const t = tour({ steps: [{ target: '#sec-tour', title: '引导漫游', description: '按选择器定位' }], onFinish: () => toast.success('完成') }); document.body.append(t.el); t.start();
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. tour() 返回 { el, start, close } 句柄:el 必须挂到 body(fixed 定位,不占布局)
|
||
2. start() 从第一步播放,可重复调用重播;close() 立即退出并按跳过处理
|
||
3. 聚光框按 target 选择器定位并外扩 padding,其余区域由超大 box-shadow 压暗(省掉独立遮罩层)
|
||
4. 卡片优先落在目标下方,空间不足自动翻到上方,左右夹在视口内;滚动 / 缩放会实时重算
|
||
5. 末步按钮文案变「完成」并触发 onFinish;× 或 Esc 触发 onClose,焦点归还开启前的元素
|
||
|
||
现场演示:apps/docs/demo.html#sec-tour
|
||
|
||
### watermark — 水印 Watermark
|
||
|
||
参数:
|
||
|
||
- text(必填):string —— 水印文字,支持 \n 多行
|
||
- opacity:number —— 透明度;缺省 0.12
|
||
- rotate:number —— 旋转角度(度);缺省 -20
|
||
- gap:number —— 平铺间距像素;缺省 140
|
||
- fontSize:number —— 字号像素;缺省 14
|
||
- color:string —— 颜色(CSS 串或 var(--ua-*) 令牌);缺省取 --ua-color-text-muted 的实色
|
||
- children:...unknown[] —— 被水印覆盖的内容,作为第二个及之后的实参传入
|
||
|
||
示例:
|
||
|
||
```
|
||
watermark({ text: '内部资料\n仅限内网', gap: 160 }, card({ title: '受保护内容' }, h('p', null, '正文')))
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 第一个参数是配置,其后所有实参都被当作内容渲染在水印层下方
|
||
2. 组件用一块 gap 见方的 canvas 画单个旋转单元,再 background-repeat 铺满,因此不需要监听尺寸变化
|
||
3. color 传 var(--ua-*) 时会先解析成计算值(canvas 不认 CSS 变量),所以暗色主题下颜色会自动跟随
|
||
4. 覆盖层为 pointer-events:none,下方按钮/输入框照常可点;watermark 根节点自带圆角与裁剪
|
||
|
||
现场演示:apps/docs/demo.html#sec-watermark
|
||
|
||
### checkboxGroup — 多选组 CheckboxGroup
|
||
|
||
参数:
|
||
|
||
- options(必填):CheckOption[] —— 选项集合:value / label / 可选 disabled
|
||
- value:Signal<string[]> —— 选中值数组信号;不传则内部维护
|
||
- min:number —— 最少选几项:到下限后已选项禁用,防止取消
|
||
- max:number —— 最多选几项:到上限后未选项禁用
|
||
- checkAll:boolean —— 是否显示全选行(含半选);缺省 false
|
||
- direction:'horizontal' | 'vertical' —— 排列方向;缺省 horizontal
|
||
- disabled:boolean | Signal<boolean> —— 整组禁用
|
||
- onChange:(values: string[]) => void —— 变更回调(传副本)
|
||
|
||
示例:
|
||
|
||
```
|
||
checkboxGroup({ value: permPick, min: 1, max: 3, checkAll: true, options: [{ value: 'read', label: '读取' }, { value: 'write', label: '写入' }] })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. options 顺序即渲染顺序;勾选后把整份数组写回 value 信号并触发 onChange
|
||
2. max 生效时未选项自动禁用,min 生效时已选项自动禁用——用禁用表达边界,不抛错也不静默回滚
|
||
3. checkAll 行的选中/半选只统计未禁用项,禁用项不计入基数
|
||
4. direction 传 vertical 改为纵向排列;disabled 支持信号,整组响应式禁用
|
||
|
||
现场演示:apps/docs/demo.html#sec-checkgroup
|
||
|
||
### affix — 固钉 Affix
|
||
|
||
参数:
|
||
|
||
- offset:number —— 距视口顶部多少像素后转为固定;缺省 0
|
||
- position:'top' | 'bottom' —— 固定边:贴顶放头部工具栏,贴底放底部操作条;缺省 top
|
||
- target:HTMLElement | Window —— 监听的滚动容器;缺省 window(整页)
|
||
- children:...unknown[] —— 被固定的内容,作为第二个及之后的实参传入
|
||
|
||
示例:
|
||
|
||
```
|
||
affix({ offset: 8 }, card({ title: '滚动时我会贴顶' }, h('p', null, '正文')))
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 把要贴住的内容作为实参传入;组件内部包两层:外层占位、内层负责 fixed
|
||
2. 滚动到 offset 以内(默认贴视口顶)时内层转 fixed,外层同步撑出等高占位,页面不会塌跳
|
||
3. 固定期间宽度与横向位置跟随占位容器,窗口缩放或父容器变宽都自动贴合
|
||
4. 内滚容器场景把该元素传给 target,判定改为基于它的滚动位置
|
||
5. 底部操作条传 position: 'bottom':占位容器底边越过「视口底 - offset」时才固定,对应的内联边距写 bottom
|
||
|
||
现场演示:apps/docs/demo.html#sec-affix
|
||
|
||
### multiSelect — 多选下拉 MultiSelect
|
||
|
||
参数:
|
||
|
||
- options(必填):CheckOption[] —— 选项集合:value / label / 可选 disabled
|
||
- value:Signal<string[]> —— 选中值数组信号;不传则内部维护
|
||
- placeholder:string —— 未选时占位文案;已选时让位给标签
|
||
- max:number —— 最多选几项:到上限后未选项自动禁用
|
||
- searchable:boolean —— 是否可输入过滤;缺省 true
|
||
- disabled:boolean | Signal<boolean> —— 禁用整个下拉
|
||
- onChange:(values: string[]) => void —— 变更回调(传副本)
|
||
|
||
示例:
|
||
|
||
```
|
||
multiSelect({ value: tagPick, max: 3, placeholder: '选择技术栈', options: [{ value: 'ts', label: 'TypeScript' }, { value: 'css', label: 'CSS 令牌' }] })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 选中项以标签形式回显,点标签上的 × 或空输入时按退格即可删除末个标签
|
||
2. 点输入区展开弹层并聚焦输入框,输入字符即时过滤(大小写不敏感)
|
||
3. 点选项只切换该项、弹层不收起,适合连续多选;到 max 后未选项置灰不可点
|
||
4. ↑↓ 环形移动高亮、Enter 切换当前项、Esc 收起(与其它弹层共用一个 Esc 栈)
|
||
5. value 传数组信号即受控,外部写入会同步标签区
|
||
|
||
现场演示:apps/docs/demo.html#sec-multiselect
|
||
|
||
### anchor — 锚点导航 Anchor
|
||
|
||
参数:
|
||
|
||
- items:AnchorItem[] —— 锚点项集合,每项含 href(形如 #sec-x)、title、可选 disabled
|
||
- offset:number —— 定位与高亮判定预留的顶部距离,适合有吸顶栏的页面;缺省 0
|
||
- container:HTMLElement | Window —— 监听的滚动容器;缺省 window(整页滚动)
|
||
- label:string —— nav 的无障碍名称;缺省「锚点导航」
|
||
- onChange:(href: string) => void —— 跳转后的回调,参数为命中的 href(适合埋点或联动)
|
||
|
||
示例:
|
||
|
||
```
|
||
anchor({ items: [{ href: '#sec-button', title: '按钮' }, { href: '#sec-card', title: '卡片' }], offset: 72 })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 在页面任意位置放一个容器,调用 anchor 并把返回的 nav 塞进去(固定侧栏或吸顶栏均可)
|
||
2. items 里每条 href 指向页面上真实存在的区块 id,标题即链接文案;目标缺失的项在滚动判定时自动跳过
|
||
3. 页面有吸顶导航时把它的高度传给 offset,跳转与高亮判定都会预留这段距离
|
||
4. 滚动容器不是整页时用 container 传入该元素;点击走平滑滚动,系统开启「减少动效」时改为瞬移,hash 用 replaceState 回写不堆历史
|
||
|
||
现场演示:apps/docs/demo.html#sec-anchor
|
||
|
||
### popconfirm — 二次确认 Popconfirm
|
||
|
||
参数:
|
||
|
||
- title(必填):string —— 气泡标题,通常写成一句问句(如「删除这条记录?」)
|
||
- description:string —— 补充说明:影响范围、是否可撤销;缺省给一句通用提示
|
||
- confirmText:string —— 确认按钮文案;缺省「确定」
|
||
- cancelText:string —— 取消按钮文案;缺省「取消」
|
||
- variant:'default' | 'danger' —— 确认按钮语义色;删除类操作传 danger,缺省 default
|
||
- placement:'top' | 'bottom' —— 气泡方位;缺省 top,压在触发器上方不盖住被操作的行
|
||
- onConfirm(回调):() => void —— 点确定后的回调(此时弹层已收起)
|
||
- onCancel(回调):() => void —— 取消 / 点外部 / Esc 收起时的回调
|
||
- open:Signal<boolean> —— 受控开合信号;不传则内部维护,传了可由外部编程开合
|
||
|
||
示例:
|
||
|
||
```
|
||
popconfirm({ title: '删除这条记录?', description: '删除后无法恢复', variant: 'danger', confirmText: '删除', onConfirm: () => toast.success('已删除') }, button({ label: '删除', variant: 'danger' }))
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 把要保护的触发器元素(按钮 / 图标)作为第二个参数传进去,返回的包裹节点替换原位置
|
||
2. 写清 title(问句)与 description(影响范围),删除类操作把 variant 设成 danger,确认按钮会自动变成危险色
|
||
3. 点确定先收起弹层再触发 onConfirm,回调里可以放心同步改数据;取消、点外部、Esc 一律走 onCancel
|
||
4. 展开时焦点自动落在「取消」上,误按回车不会执行危险操作;需要外部控制开合时传 open 信号
|
||
|
||
现场演示:apps/docs/demo.html#sec-popconfirm
|
||
|
||
### calendar — 月历 Calendar
|
||
|
||
参数:
|
||
|
||
- value:Signal<string> —— 选中日期(YYYY-MM-DD)信号;不传则内部维护
|
||
- month:Signal<string> —— 展示月份(YYYY-MM);不传则内部维护,初始跟随 value 或今天
|
||
- marker(回调):(date: string) => string | null —— 每日标记:返回文案即渲染小徽标(如「3 项」),同时写进 aria-label;返回 null 不标记
|
||
- onSelect(回调):(date: string) => void —— 选中日期回调(点击或 Enter / 空格)
|
||
- onMonthChange(回调):(month: string) => void —— 翻月回调,参数为 YYYY-MM(可在此拉取当月数据)
|
||
|
||
示例:
|
||
|
||
```
|
||
const day = signal(''); calendar({ value: day, marker: (d) => (d === '2026-10-26' ? '2 项' : null), onSelect: (d) => toast.info(d) })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 把 calendar 放在页面容器里即可,它自带标题行(月份 + 上下月箭头)、星期表头与 6 行网格
|
||
2. 选中日期用 value 信号双向绑定;不传则组件内部维护,onSelect 里拿字符串自己处理
|
||
3. 需要显示每天有几件事就传 marker:返回非空文案即渲染小徽标,并拼进 aria-label 让读屏也听得到
|
||
4. 键盘:Tab 进入网格一次,←→ 逐日、↑↓ 逐周、Enter / 空格 选中;跨月会自动翻页并交接焦点
|
||
|
||
现场演示:apps/docs/demo.html#sec-calendar
|
||
|
||
### typography — 排版文本 Typography
|
||
|
||
参数:
|
||
|
||
- content(必填):string | Node —— 文本内容:字符串按纯文本渲染;传 DOM 则原样插入(此时不参与截断与复制)
|
||
- variant(默认 text):'title' | 'text' | 'paragraph' —— 排版形态,决定语义标签与字号:title 出 h1-h5、paragraph 出 p、text 出 span
|
||
- level(默认 3):1 | 2 | 3 | 4 | 5 —— 标题层级,仅 variant=title 生效;越界值会被夹到 1-5
|
||
- type:'secondary' | 'success' | 'warning' | 'danger' —— 语义色调;缺省主文本色
|
||
- strong(默认 false):boolean —— 加粗强调
|
||
- underline(默认 false):boolean —— 下划线(可为链接式文案保留指引视觉)
|
||
- delete(默认 false):boolean —— 删除线,用于作废文案;会同时弱化文字颜色
|
||
- mark(默认 false):boolean —— 高亮底纹(警示色 24% 混透明),用于搜索命中片段
|
||
- code(默认 false):boolean —— 等宽代码样式,用于行内字段名 / 命令
|
||
- align:'left' | 'center' | 'right' —— 水平对齐;缺省左对齐
|
||
- ellipsis:number —— 截断行数(1 起):超出显示省略号,纯文本自动挂 title 供悬停看全文;缺省不截断
|
||
- copyable(默认 false):boolean —— 右侧附复制按钮:点击写剪贴板并回显「已复制 / 复制失败」,1.2 秒后复原
|
||
|
||
示例:
|
||
|
||
```
|
||
typography({ variant: 'title', level: 2, content: '部署说明' }); typography({ content: 'RB-2048', code: true, copyable: true }); typography({ variant: 'paragraph', ellipsis: 2, content: '一段很长的说明……' })
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 正文一律用 variant='text' 或 'paragraph',标题用 variant='title' + level,标签自动出 h1-h5,别自己写 h 标签再叠样式
|
||
2. 文内强调用装饰位组合:strong 加粗、mark 高亮、code 等宽、underline / delete 表示链接与作废,一个组件内可叠加
|
||
3. 长描述(卡片摘要、表格说明)用 ellipsis 给行数:超出显示省略号并自动挂 title,悬停即看全文,不必额外做展开按钮
|
||
4. 订单号 / 密钥 / 命令这类要复制的内容传 copyable:按钮独立于文本节点,复制失败会回显「复制失败」而不是静默
|
||
5. content 传 DOM 时组件只负责语义标签与类名:截断与复制对 DOM 内容不生效(取不到稳定文本)
|
||
6. 段落连续排列时相邻 .ua-type-paragraph 自动留出间距,单个段落不额外撑高,嵌进卡片也不会有空隙
|
||
|
||
现场演示:apps/docs/demo.html#sec-typography
|
||
|
||
### grid — 栅格容器 Grid
|
||
|
||
参数:
|
||
|
||
- columns(默认 3):number —— 固定列数(按等宽铺开);窄屏(≤640px)由样式表统一降为单列
|
||
- min:string —— 自适应最小列宽(如 '220px'):给了就按 auto-fit 铺列,列数随可用宽度自然增减,此时忽略 columns
|
||
- gutter(默认 md):SpaceSize —— 列间距:sm/md/lg 走令牌,数字按像素
|
||
- align(默认 stretch):'start' | 'center' | 'end' | 'stretch' —— 交叉轴对齐:同行子项高度不一时决定各自贴哪边或拉满
|
||
- dense(默认 false):boolean —— 紧凑填充:小项回填前面留下的空洞(瀑布式排布)
|
||
|
||
示例:
|
||
|
||
```
|
||
grid({ columns: 3, gutter: 'md' }, card({ title: '实例数' }, statistic({ value: total })), card({ title: '错误率' }, statistic({ value: errRate }))); grid({ min: '240px', align: 'start' }, ...items)
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 页面骨架用 grid({ columns: n }) 铺等宽卡片:子项宽度由列模板算,别在子项上写 width,窄屏会自动变成单列
|
||
2. 卡片宽度要求「不窄于 X」时用 min(如 '240px'):auto-fit 会按可用宽度决定列数,比手写断点稳,此时不要再传 columns
|
||
3. 列间距统一走 gutter(sm/md/lg 令牌或像素),不要在子项上写 margin——相邻 margin 会叠加成双倍间距
|
||
4. 同行卡片高度参差时:要等高(默认 stretch),要各自顶端对齐传 align: 'start'
|
||
5. 子项必须是单个元素(card / div / 自定义块),grid 只负责排布且已对子项设 min-width: 0,长文本不会撑破列宽
|
||
6. 只要横向一排展示且元素不等宽,用 space 更轻;grid 面向二维对齐
|
||
|
||
现场演示:apps/docs/demo.html#sec-grid
|
||
|
||
### floatButton — 悬浮操作按钮 FloatButton
|
||
|
||
参数:
|
||
|
||
- icon:string | Node —— 图标:字符串按字符(emoji / 符号)渲染,传 DOM 原样插入
|
||
- label:string —— 文字标签;给了就出「图标 + 文字」胶囊,缺省为纯图标圆形
|
||
- badge:string | number —— 角标(数字或短文案,如 3 / '新');缺省不渲染
|
||
- tooltip:string —— 悬停提示,同时用作 aria-label;纯图标时必传,否则读屏听到默认「快捷操作」
|
||
- position(默认 right-bottom):'right-bottom' | 'left-bottom' —— 停靠角落;右下与 backTop 同侧,需同时出现时错开一左一右
|
||
- offset(默认 24):number —— 距边距离(px);同角落堆叠多个时按倍数递增
|
||
- onClick(回调):() => void —— 点击回调
|
||
|
||
示例:
|
||
|
||
```
|
||
document.body.append(floatButton({ icon: '+', label: '新建工单', position: 'left-bottom', onClick: () => toast.success('已新建') })); document.body.append(floatButton({ icon: '?', tooltip: '帮助中心', badge: 3 }))
|
||
```
|
||
|
||
手把手:
|
||
|
||
1. 把返回节点挂到 document.body(不是页面容器内):它是 fixed 定位,挂在容器里会被 overflow: hidden 裁剪
|
||
2. 只有图标时务必传 tooltip:它同时是 aria-label,读屏用户靠它知道按钮干什么;有 label 时 tooltip 可省
|
||
3. label 存在即「图标 + 文字」胶囊,只有图标即 44px 圆形;两种形态都保证触摸目标不小于 44px
|
||
4. 同一角落要放多个按钮时递增 offset(如 24 / 80),或把它们并进同一个 flex 容器统一排布——组件自身不做堆叠计算
|
||
5. 右下角已被 backTop 占用,页面同时有返回顶部时用 position: 'left-bottom'
|
||
6. 纯展示 + 单回调,没有选中态与开合状态;需要多级操作请用 menu / popover 挂在 onClick 上
|
||
|
||
现场演示:apps/docs/demo.html#sec-floatbutton
|
||
|
||
API 速览:
|
||
|
||
- button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用;type 缺省 button(表单内不隐式提交)
|
||
- textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块,clearable 开启后有值即显一键清空按钮,maxCount 设原生上限并实时显示字数
|
||
- inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节(长按连发步进),min/max/step 夹取,precision 小数位归一;prefix/suffix 可选渲染为整组前后附加块
|
||
- textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块,maxCount 显示实时字数并设原生上限
|
||
- slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值
|
||
- checkbox(props):复选框,checked 信号双向绑定,indeterminate 半选态(读屏播报 mixed),disabled 支持布尔或信号禁用
|
||
- switchButton(props):开关,checked 信号双向绑定,loading 加载锁(滑块转圈 + aria-busy),disabled 支持布尔或信号禁用
|
||
- select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用,空间不足自动翻转,键入字母快跳选项;searchable 开启弹层搜索框,clearable 有值时给清空按钮
|
||
- menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭,空间不足自动翻转,键入字母快跳菜单项
|
||
- flip(trigger, panel, preferred):浮层上下翻转判定,按触发器与面板的视口几何决定 preferred 方位放不下时是否翻转,返回实际生效方位 'top' | 'bottom'
|
||
- radioGroup(props):单选组,options + value 信号双向绑定,disabled 整组禁用
|
||
- accordion(props):折叠面板,标题按钮 + grid 行高过渡,单开(手风琴)或多开,支持禁用面板与 active 信号受控
|
||
- breadcrumb(props):面包屑,nav + ol 语义结构,末项为当前页(aria-current),有 href 走原生链接、无 href 交给 onNavigate
|
||
- card(props, ...children):卡片容器
|
||
- badge(props):徽标,variant = default | success | warning | danger
|
||
- tag(props):可关闭标签,variant 五档语义色,closable + onClose 支持从数据源移除(纯展示请用 badge)
|
||
- avatar(props):头像,有 src 渲染图片(alt 用名称)、无 src 用首字占位,size 可调 + 四档语义底色
|
||
- alert(props):提示条,variant 四档语义色 + 可选 title 与 closable 关闭按钮,role=alert 读屏可播报
|
||
- toast.info/success/warning/error(message, duration?):消息提示工厂,右上角堆叠,默认 3s 自动消失(悬停暂停、移出续时),出入场微动画;showToast(props) 为完整入参版本(closable 显示关闭按钮,同屏最多 4 条)
|
||
- tooltip(props, trigger):包裹式悬浮提示,hover / 键盘聚焦延迟出场,Esc 收起;触摸屏按下即出、抬手延迟 1.2s 收起,气泡 pointer-events: none 不挡点击
|
||
- popover(props, trigger):可交互气泡卡片,包裹触发器,默认点击开合 + 点外部 / Esc 收起,content 可放按钮等交互元素;视口一侧空间不足时自动翻转
|
||
- skeleton(props):骨架屏占位,variant = text | rect | circle,text 支持 rows 多行,扫光微动画
|
||
- pagination(props):独立分页器,page 信号双向绑定,页码过多折叠省略号,首末页禁用翻页,jumpable 时末尾可输入页码直接跳转,键盘可达;pageSizes 可切每页条数
|
||
- spinner(props):加载指示器,size 控制直径
|
||
- progress(props):进度条,value 信号驱动 0-100(满格切 success 色);不传 value 退化为不确定态无限往复
|
||
- tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签;页签项 closable 时带关闭按钮(聚焦后 Delete 同样可关),关闭只回调 onClose 由调用方摘数据
|
||
- stepper(props):步骤条,done/current/upcoming 三态,active 信号双向绑定,clickable 时已完成步骤可回跳
|
||
- rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配,allowHalf 开启半星(左半区 0.5 分)
|
||
- empty(props):空状态占位,title/description 可选,action 放操作区
|
||
- divider(props):分隔线,水平/垂直两种方向,可带居中文案标签,dashed 切虚线
|
||
- space(props):间距容器,flex gap 统一子元素间距,direction / size / align / justify / wrap / block 可调
|
||
- modal(props, ...children):弹窗,open 信号驱动显隐,Esc / 遮罩 / × 关闭,size 三档宽度,footer 提供底部操作区
|
||
- drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭
|
||
- form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit
|
||
- formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄
|
||
- dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core
|
||
- image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽,加载失败自动落占位(fallback 自定义文案)
|
||
- timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点
|
||
- segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用
|
||
- descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列
|
||
- statistic(props):统计数值,标题 + 大号数字 + 前后缀 + 环比趋势,value 变化时 rAF 滚动过渡(400ms easeOutCubic),减弱动效偏好下直落终值
|
||
- list(props):列表,header/footer 外壳 + 条目 avatar/title/description/extra,onItemClick 让条目可点击,items 为空回落空状态
|
||
- carousel(props):轮播,items 横排位移,指示点/箭头/←→ 键切换,autoplay 毫秒自动前进且悬停暂停,index 信号可外部受控
|
||
- tree(props):树形,递归节点 + 箭头展开/收起 + 点选节点,键盘 Enter 选中、→ 展开、← 收起,禁用节点不响应
|
||
- upload(props):文件选择,点击或拖拽入列,清单可逐个移除,onChange 回传文件数组(不内置上传请求)
|
||
- notification(props):右下角通知卡片(标题 + 描述 + 操作区),返回 { el, close } 句柄,缺省常驻需手动关闭
|
||
- result(props):结果页,status 四档语义色 + 标题 + 说明 + 操作区,用于提交完成/校验失败等终态
|
||
- transfer(props):穿梭框,左右两栏勾选后整体搬运,targetKeys 信号双向绑定,两栏各自搜索与全选(含半选),禁用条目不参与搬运也不可勾选
|
||
- cascader(props):级联选择,多列联动面板(悬停下钻 / 点击提交),value 为路径数组信号,changeOnSelect 允许停在中间层,全键盘可操作
|
||
- splitter(props):可拖拽分栏容器,分隔条支持指针拖拽 / 方向键微调 / 双击复位,sizes 为百分比数组信号
|
||
- autoComplete(props):输入联想,输入即过滤候选、↑↓ 环形高亮 / Enter 选中 / Esc 收起,options 可传函数做远程联想
|
||
- datePicker(props):日期选择,单月网格 + 翻月 / 今天快捷键,value 为 YYYY-MM-DD 信号,min/max 闭区间限制,方向键逐日逐周可导航
|
||
- backTop(props):返回顶部悬浮按钮,越过阈值才浮现,外环 conic-gradient 显示已滚比例,点击平滑回顶
|
||
- colorPicker(props):颜色选择,预设色板(缺省语义令牌色)+ 原生取色器 + 手输 CSS 颜色串,value 为颜色串信号
|
||
- tour(props):引导漫游,返回 { el, start, close } 句柄;聚光框按 target 选择器定位,卡片自动选方位,Esc / 跳过走 onClose
|
||
- watermark(props, ...children):内容容器 + 平铺水印覆盖层,canvas 生成单元、颜色取自令牌,覆盖层不挡交互
|
||
- checkboxGroup(props):多选组,value 为数组信号,min/max 用禁用边界项实现(不抛错),checkAll 支持半选且只统计未禁用项
|
||
- affix(props, ...children):固钉,滚过 offset 后内容转 fixed 贴顶、外层保留占位高度,固定后宽度随容器自适应
|
||
- multiSelect(props):多选下拉,标签式回显 + 可搜索弹层,点选不收起便于连续多选,max 到限后未选项禁用
|
||
- anchor(props):锚点导航,竖排区块链接,滚动时按目标位置高亮当前区块(aria-current 同步),点击按 offset 偏移量平滑定位并回写 hash;container 可指定内滚容器
|
||
- popconfirm(props, trigger):二次确认气泡,挂在任意触发器上,就地确认危险操作;确定后先收起再回调,取消 / 点外部 / Esc 走 onCancel,展开即聚焦「取消」
|
||
- calendar(props):月历,整月网格内嵌页面(排期 / 值班 / 日程),每日可带 marker 标记文案,方向键漫游 + Enter 选中,翻月走 onMonthChange
|
||
- typography(props):排版文本,variant 决定语义(标题出 h1-h5 / 段落出 p / 行内出 span),支持语义色、装饰位、对齐、多行截断与复制按钮
|
||
- grid(props, ...children):栅格容器,columns 固定列数或 min 走 auto-fit 自适应列宽,gutter 统一列间距,窄屏自动降为单列
|
||
- floatButton(props):悬浮操作按钮,固定停靠左下 / 右下角,label 缺省为纯图标圆形,badge 走角标,tooltip 兼作 aria-label
|
||
|
||
一行用法:
|
||
|
||
```
|
||
const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')
|
||
```
|
||
|
||
约定:
|
||
|
||
- 皮肤不持有业务状态,一律委托 headless 内核或调用方信号
|
||
- 表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调
|
||
- 语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色
|
||
- 无障碍基线:装饰性占位(skeleton)aria-hidden、加载指示(spinner)role=status、关闭按钮必须 aria-label、弹层弹窗 aria-haspopup/expanded、读屏可见的状态一律同步 aria-*
|
||
|
||
---
|
||
|
||
## @ui-arch/frame(headless-core)v0.1.0
|
||
|
||
零依赖微框架:组件 = 返回 DOM 的普通函数。signal/effect/computed 管状态,h() 管 DOM,无虚拟 DOM 无生命周期抽象。
|
||
|
||
API 速览:
|
||
|
||
- signal(initial) -> Signal<T>:无参读值并收集依赖,传参写值并触发订阅
|
||
- effect(fn):立即执行并追踪依赖,信号变化自动重跑
|
||
- computed(fn):由信号派生的只读计算值
|
||
- h(tag, props, ...children):hyperscript;on* 绑事件,函数值属性/子节点自动响应式
|
||
- renderList(el, fn):信号变化时整体替换容器子节点
|
||
- mount(target, view):挂载视图
|
||
|
||
一行用法:
|
||
|
||
```
|
||
const count = signal(0); mount('#app', () => h('button', { onClick: () => count(count() + 1) }, () => `count = ${count()}`))
|
||
```
|
||
|
||
约定:
|
||
|
||
- 组件 = 返回 DOM 节点的普通函数,不做类组件与生命周期抽象
|
||
- 跨系统状态(如 table-core)用桥接信号:subscribe 里递增 version 信号
|
||
|
||
---
|
||
|
||
## @ui-arch/table-core(headless-core)v0.1.0
|
||
|
||
Headless 表格内核:不渲染任何 DOM,只管理状态与派生。任何渲染层通过 getVisibleRows() 拿行模型,配 subscribe() 刷新。
|
||
|
||
API 速览:
|
||
|
||
- createTable(options) -> TableInstance: create headless table instance
|
||
- sortPlugin(options?) -> multi-column sorting plugin (asc -> desc -> off cycle)
|
||
- filterPlugin(options?) -> column filter plugin (default substring match, custom predicate supported)
|
||
- paginationPlugin() -> client-side pagination with page clamping, exposes setPage/nextPage/prevPage/getPageInfo
|
||
- selectionPlugin() -> rowKey-based selection, exposes selectAllVisible
|
||
|
||
一行用法:
|
||
|
||
```
|
||
const table = createTable({ data, columns, rowKey: 'id', plugins: [filterPlugin(), sortPlugin(), paginationPlugin()], initialState: { pagination: { page: 1, pageSize: 20 } } }); table.dispatch({ type: 'toggleSort', key: 'name' }); table.getVisibleRows()
|
||
```
|
||
|
||
约定:
|
||
|
||
- selection/expanded must store rowKey, never row index
|
||
- derive pipeline order is fixed: filter -> sort -> paginate
|
||
- actions are the only way to mutate state; state is immutable
|
||
|
||
---
|
||
|
||
## @ui-arch/tokens(design-tokens)v0.1.0
|
||
|
||
设计令牌唯一来源:CSS 变量 --ua-* + 同名 JSON。皮肤只允许消费变量,禁止硬编码颜色;暗色只换值不换名。
|
||
|
||
API 速览:
|
||
|
||
- tokens.css: import '@ui-arch/tokens/tokens.css' once at app root
|
||
- tokens.json: programmatic access for tools and agents
|
||
|
||
一行用法:
|
||
|
||
```
|
||
import '@ui-arch/tokens/tokens.css'; then use var(--ua-color-accent) etc. in any skin package
|
||
```
|
||
|
||
---
|
||
|
||
## @ui-arch/cli(tooling)v0.1.0
|
||
|
||
工程层工具包(private,不发布)。所有 Agent 与人都通过它读取组件库:清单驱动索引、文档、MCP 三件套,外加 style / verify 两道门禁。
|
||
|
||
API 速览:
|
||
|
||
- ua index:扫描 packages/ 两层目录的 ua.manifest.json,生成 .ua/index.json 与 llms.txt
|
||
- ua list:列出注册表内所有包与组件数
|
||
- ua info <包名|组件id>:包名返回摘要,组件 id 返回 props 表 + 步骤 + 示例;--json 输出原始清单
|
||
- ua docs:生成人类文档站 apps/docs/index.html + demo.html 与 Agent 全量文档 llms-full.txt
|
||
- ua style:注释规范门禁(导出必须 /*** ***/、接口字段行尾注释、行注释中文),违规退出码 1
|
||
- ua verify:清单一致性门禁(id 唯一 / exports 对应 / props 必带 n t d / steps≥3 / demo 锚点存在)
|
||
- ua commit-lint <文件|message>:提交信息门禁(type 枚举 / scope 规则 / subject 中文祈使 ≤50 字符),规范见 COMMITTING.md,违规退出码 1
|
||
- ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components
|
||
|
||
一行用法:
|
||
|
||
```
|
||
node packages/tools/cli/bin/ua.js <index|list|info <包名|组件id>|docs|tokens|style|verify|commit-lint|mcp>;仓库内等价于 npm run index / docs / style / verify
|
||
```
|
||
|
||
约定:
|
||
|
||
- 零依赖:只用 node 标准库,Node >= 18
|
||
- 清单是单一来源:代码注释、文档站、llms-full.txt、MCP 全部从 ua.manifest.json 派生
|
||
- 门禁只增不改:新增校验必须能被人手复现,报错要指出文件与字段
|
||
|
||
---
|
||
|
||
## 设计令牌(--ua-*)
|
||
|
||
- --ua-color-bg: #ffffff(暗色 #101216)
|
||
- --ua-color-surface: #f6f7f9(暗色 #1a1c22)
|
||
- --ua-color-text: #1c1e21(暗色 #e7e9ec)
|
||
- --ua-color-text-muted: #6b7280(暗色 #888780)
|
||
- --ua-color-border: rgba(0, 0, 0, 0.15)(暗色 rgba(255, 255, 255, 0.15))
|
||
- --ua-color-accent: #185fa5(暗色 #378add)
|
||
- --ua-color-accent-contrast: #ffffff(暗色 #042c53)
|
||
- --ua-color-danger: #a32d2d(暗色 #d96a6a)
|
||
- --ua-color-success: #0f6e56(暗色 #4fc39e)
|
||
- --ua-color-warning: #9a6b1f(暗色 #d9a94f)
|
||
- --ua-radius-sm: 4px
|
||
- --ua-radius-md: 8px
|
||
- --ua-radius-lg: 12px
|
||
- --ua-space-1: 4px
|
||
- --ua-space-2: 8px
|
||
- --ua-space-3: 12px
|
||
- --ua-space-4: 16px
|
||
- --ua-space-table-cell: var(--ua-space-2) var(--ua-space-3)
|
||
- --ua-font-size-sm: 12px
|
||
- --ua-font-size-md: 13px
|
||
- --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<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:长列表数据加载与空态
|
||
|
||
```ts
|
||
const loading = signal(true);
|
||
const items = signal<ListItem[]>([]);
|
||
list({ items: items(), bordered: true, emptyText: loading() ? '加载中…' : '暂无数据' });
|
||
```
|
||
|
||
要点:
|
||
- 空态文案随加载态切换;列表本身会在 `items` 为空时渲染 `empty`,不需要业务层再判断一次。
|
||
- 需要骨架屏就换成 `skeleton`,加载完成再 `renderList` 成真实行。
|
||
|
||
## 配方 5:数据表 + 独立分页器 + 导出
|
||
|
||
```ts
|
||
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:长表单编辑页(吸底操作条 + 提交锁定)
|
||
|
||
```ts
|
||
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`)。
|