
Veltra Ui
- 66 installs
- 5 repo stars
- Updated August 5, 2026
- cabinet-fe/ultra-ui
veltra-ui is an agent skill that forces Vue 3 work to consume veltra-ui components, tokens, composables, and Vite helpers before custom or third-party UI
About
veltra-ui is an agent skill that steers Vue 3 development toward the veltra-ui monorepo: desktop components, shared styles and Design Tokens, composables, directives, icons, utilities, and Vite integration. Solo builders on admin dashboards, internal tools, or customer-facing SaaS shells use it to avoid reinventing buttons, tables, and action menus when the design system already ships UAction groups, themed controls, and build plumbing. The procedure is deliberate retrieval—scan the package map, open the relevant component or `packages/styles` docs, and only then implement custom code if no suitable primitive exists. That keeps agent context small while preserving consistency across forms, data tables, and destructive confirms. It is a library discipline skill, not a generic Vue tutorial; teams not on veltra-ui or Vue 3 should skip it.
- Search-first workflow: load only the package docs slice you need (desktop, styles, utils, vite)
- Desktop components live under `packages/desktop/` with per-component types and examples
- Styles, themes, and Design Tokens centralized in `packages/styles/`
- Composables, directives, icons, and Vite tooling each have dedicated doc entry files
- UAction / UActionGroup patterns for table row actions, confirm-danger flows, and grouped defaults
Veltra Ui by the numbers
- 66 all-time installs (skills.sh)
- Ranked #1,172 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cabinet-fe/ultra-ui --skill veltra-uiAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 66 |
|---|---|
| repo stars | ★ 5 |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 5, 2026 |
| Repository | cabinet-fe/ultra-ui ↗ |
What it does
Build Vue 3 product UIs by reusing veltra-ui components, tokens, composables, and Vite helpers instead of pulling ad-hoc external libraries.
Who is it for?
Best when you're committed to Vue 3 plus the veltra-ui design system and want consistent agent output on forms, tables, and admin chrome.
Skip if: React or Svelte codebases, greenfield projects without veltra-ui in the repo, or flows where you have already approved a non-veltra component strategy.
When should I use this skill?
Frontmatter and body require veltra-ui whenever Vue 3 is the frontend framework—search package docs before adding new UI or external libraries.
What you get
New screens and refactors align with veltra-ui primitives—documented imports, tokenized styling, and standard action patterns—reducing UI drift across the app.
- Vue SFCs using veltra-ui components and tokens per documented APIs
- Themed layouts aligned with `packages/styles` guidance
- Vite config or plugin usage per `vite.md` when build integration is needed
By the numbers
- Six documentation entry areas: desktop, styles, compositions, directives, icons, utils, plus vite.md
- UAction examples document grouped actions with `max`, confirm-danger, and table column slots
Files
veltra-ui
veltra-ui 是一套 Vue 3 UI 体系。
开发 Vue 3 功能时,优先使用 veltra-ui 已提供的组件、样式、工具函数、组合式方法、指令、图标与构建集成。只有检索文档和源码后确认没有合适能力时,才新增实现或引入外部方案。
使用方式
- 先按需求检索下方包文档入口,不要把完整 API 预加载到上下文。
- 组件相关先看
desktop/文档目录,再按组件名检索具体 API、示例和类型。 - 样式、主题与 Design Tokens 看
styles/;工具、组合式方法、指令、图标、Vite 集成分别看对应入口。
文档结构
desktop/ ← 桌面组件
styles/ ← 样式、主题、Design Tokens
compositions.md ← 组合式方法
directives.md ← 指令
icons.md ← 图标
utils.md ← 工具函数 / 共享类型
vite.md ← Vite 工具@veltra/compositions
Vue 3 组合式函数。前置依赖 vue@^3.5、@floating-ui/dom、@cat-kit/fe、@veltra/utils。
import {
useConfig,
useFallbackProps,
useFormFallbackProps,
useModel,
usePop,
useDrag,
useFocus,
useUserAction,
useTransition,
useResizeObserver,
useReactiveSize,
useVirtualizer,
useComponentProps
} from '@veltra/compositions'---
高频 API
useConfig() — 全局配置
const { config, setConfig } = useConfig()
setConfig({ size: 'large', animation: false })
setConfig({ form: { labelWidth: 120 } }) // 深合并
config.size // ComponentSize
config.animation // boolean
config.form.labelWidth // number
config.paginator.pageSize // number
config.paginator.pageSizeOptions // number[]config 只读;config.size 变化自动同步 <html> size 类名。State 形状:
interface State {
animation: boolean
size: ComponentSize
form: { labelWidth?: number | string }
paginator: { pageSize: number; pageSizeOptions: number[] }
}useFallbackProps() — 多级属性回退
function useFallbackProps<F extends Record<string, any>>(
propsList: Record<string, any>[],
fallbackProps: F
): { [K in keyof F]: ComputedRef<F[K]> }从右向左找第一个非 undefined:propsList[last] → ... → propsList[0] → useConfig 全局 → fallbackProps。
const props = defineProps<{ size?: ComponentSize; disabled?: boolean }>()
const { size, disabled } = useFallbackProps([props], { size: 'default', disabled: false })useFormFallbackProps() — 表单专用
封装表单组件回退默认值 { size: 'default', disabled: false, readonly: false }。可只覆盖部分字段。链:props → formProps → config → 默认值。
import { injectFormContext } from '@veltra/utils'
const { formProps } = injectFormContext()
const { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props])useModel() — 双向绑定
function useModel<P, N extends keyof P = 'modelValue'>(options: {
props: P
emit: (...args: any[]) => void
propName?: N // 默认 'modelValue'
local?: boolean | (() => boolean) // 默认 true
shallow?: boolean
defaultValue?: P[N]
}): Ref<P[N] | undefined>
useModel({ props, emit }) // 本地副本(默认)
useModel({ props, emit, local: false }) // 纯代理:set 仅 emit
useModel({ props, emit, local: () => props.modelValue === undefined }) // 受控自动切换
useModel({ props, emit, propName: 'visible' }) // 自定义 propusePop() — 浮框定位
基于 @floating-ui/dom,支持 flip/shift/offset/arrow,自动监听触发器祖先滚动与 window resize。模块级单例 <div id="pop-container"> 自动挂在 document.body。
function usePop(options: {
triggerRef: ShallowRef<HTMLElement | undefined>
contentRef: ShallowRef<HTMLElement | undefined>
arrowRef?: ShallowRef<HTMLElement | undefined>
direction?: ShallowRef<'top' | 'bottom' | 'left' | 'right'> | 'top' | 'bottom' | 'left' | 'right'
alignment?: ShallowRef<'center' | 'start' | 'end'> | 'center' | 'start' | 'end'
arrowSize?: number // 默认 10
onTriggerPositionChange?: () => void // 仅注册监听,回调内自行调用 update
onBeforeUpdate?: (triggerEl: HTMLElement, contentEl: HTMLElement) => void
onAfterUpdate?: (pos: ComputePositionReturn) => void
onPop?: (pos: ComputePositionReturn) => void
}): { update: () => Promise<void>; popperContainerId: string }
const { update } = usePop({
triggerRef,
contentRef,
direction: 'bottom',
alignment: 'center',
onTriggerPositionChange: () => update(),
onBeforeUpdate: (t, c) => {
c.style.minWidth = t.offsetWidth + 'px'
}
})---
低频 API
useDrag(options)
useDrag({
target: shallowRef<HTMLElement>(),
rangeX: [0, 500],
rangeY: [0, 300], // [number, number],可省略
initial: { offsetX: 0, offsetY: 0 },
onDragStart: (e) => {},
onDrag: ({ x, y, offsetX, offsetY, e }) => {},
onDragEnd: ({ offsetX, offsetY }) => {}
}) // 返回 { update: ({ offsetX?, offsetY? }) => void }仅响应左键。x/y 为本次累计偏移;offsetX/offsetY 为 range 钳制后最终偏移。
useFocus(cb?)
const { focus, handleFocus, handleBlur } = useFocus((focused) => {}) // @focus / @bluruseUserAction() — 区分用户动作 / 程序回流
解决 emit → props 回灌 → watch 副作用循环更新。userAction(fn) 包装为异步函数,进入 actionCount++,await nextTick() 后 --。
const { userAction, isUserActive } = useUserAction()
const handleSelect = userAction((d: Date) => {
current.value = d
emit('update:modelValue', d)
})
watch(
() => props.modelValue,
(v) => {
if (isUserActive()) return // 用户动作期间跳过回显
current.value = v
}
)useTransition(type, options) — 命令式过渡
两种类型,签名不同;均返回 { toggle(active), enter(), leave() }。
// CSS 类:生成 `${name}-enter-from|active|to` 与 `${name}-leave-from|active|to`
useTransition('css', {
target: shallowRef<HTMLElement>(),
name: 'fade',
keepEnterTo: false, // name: string | Ref<string>
afterEnter: () => {},
afterLeave: () => {}
})
// 内联 style
useTransition('style', {
target: shallowRef<HTMLElement>(),
enterTo: { opacity: '1' },
enterActive: { transition: 'opacity .3s' },
leaveActive: { transition: 'opacity .3s' }
})useResizeObserver(options)
useResizeObserver({
targets: elRef, // 单 ref 或 ref 数组
onResize: (entries) => {},
when: () => true // when 可选
}) // 返回 { disconnect: () => void }派生 useObserverCallback() 返回 { observeEl, unobserveEl },按元素维度注册回调。
useReactiveSize(target | targets)
const size = useReactiveSize(elRef) // reactive { width, height }
const sizes = useReactiveSize([el1, el2]) // reactive[]
// 模板直接 size.width(非 ref,无需 .value)useVirtualizer(options) — 虚拟滚动
@cat-kit/fe 的 Virtualizer Vue 适配层。totalSize/beforeSize/afterSize 命令式写 style.height|width 落 DOM,避免滚动期 Vue 重渲染。约束:initialOffset / initialViewport 仅构造时生效。
const { virtualizer, items, isScrolling, snapshot } = useVirtualizer({
count: countRef, // Ref<number>
scrollEl: scrollRef,
contentEl,
beforeEl,
afterEl, // 可选,自动写对应尺寸
estimateSize: () => 40,
getItemKey: (i) => list.value[i].id
// 其他 VirtualizerOptions 字段(不含 count)
})
// virtualizer.scrollToIndex / scrollToOffset / setOptions / measureElement 直接调用useComponentProps(props)
返回一个组件,把通用属性合并到默认插槽子节点(子节点已显式定义的属性优先)。少量复合组件用。
---
相关
desktop/components/form/api.md、desktop/components/form/types.d.ts、styles/index.md(主题)
UAction / UActionGroup - 操作按钮
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UAction 示例
基础操作组
<u-action-group :max="4">
<u-action @run="handleView">查看</u-action>
<u-action @run="handleEdit">编辑</u-action>
<u-action need-confirm type="danger" @run="handleDelete">删除</u-action>
</u-action-group>统一默认样式 + 单独覆盖
<u-action-group type="info" size="default" :text="false">
<u-action @run="handleCopy">复制</u-action>
<u-action @run="handlePaste">粘贴</u-action>
<!-- 覆盖组默认值 -->
<u-action type="danger" @run="handleRemove">移除</u-action>
</u-action-group>在表格操作列中使用
<u-table :columns="columns" :data="data" row-key="id">
<template #column:action="{ row }">
<u-action-group :max="3">
<u-action @run="handleEdit(row)">编辑</u-action>
<u-action @run="handleDetail(row)">详情</u-action>
<u-action need-confirm type="danger" @run="handleDelete(row)">删除</u-action>
<u-action @run="handleCopy(row)">复制</u-action>
</u-action-group>
</template>
</u-table>
<script setup>
import { defineTableColumns } from '@veltra/desktop'
const columns = defineTableColumns([
{ key: 'name', name: '名称' },
{ key: 'action', name: '操作', width: 200, align: 'center' }
])
</script>始终在下拉菜单 + 圆形图标(@veltra/icons)
<u-action-group circle>
<u-action type="primary" :icon="Edit" @run="handleEdit">
{{ '' }}
</u-action>
<!-- in-dropdown 强制收纳到下拉,方便在紧凑场景隐藏次常用操作 -->
<u-action type="danger" :icon="Delete" in-dropdown @run="handleDelete">
{{ '' }}
</u-action>
</u-action-group>手动关闭下拉菜单
<script setup lang="ts">
import { useTemplateRef } from 'vue'
const groupRef = useTemplateRef('group')
const handleRun = () => {
// ... 执行操作后关闭下拉
groupRef.value?.closeTip()
}
</script>
<template>
<u-action-group ref="group" :max="2">
<u-action @run="handleRun">操作一</u-action>
<u-action @run="handleRun">操作二</u-action>
<u-action @run="handleRun">操作三</u-action>
</u-action-group>
</template>import type { ColorType, DeconstructValue } from '@veltra/utils'
import type { ButtonProps } from './button'
/** 操作组件属性 */
export interface ActionProps extends ButtonProps {
/** 是否需要确认 */
needConfirm?: boolean
/**
* 是否始终位于下拉菜单中,无视 `max` 限制
* @default false
*/
inDropdown?: boolean
}
/** 操作组组件属性 */
export interface ActionGroupProps {
/** 是否加载中 */
loading?: boolean
/**
* 是否为圆形按钮,适用于图标类。`hover` 模式下默认对所有子项生效
* @default false
*/
circle?: boolean
/**
* 最大可显示按钮数量,超出部分自动收纳到下拉菜单
* @default 3
*/
max?: number
/**
* 子项默认尺寸
* @default 'small'
*/
size?: 'small' | 'default' | 'large'
/**
* 子项默认是否使用文本样式
* @default true
*/
text?: boolean
/**
* 子项默认按钮类型
* @default 'primary'
*/
type?: ColorType
}
/** 操作组件定义的事件 */
export interface ActionEmits {
(e: 'run'): void
}
/** 操作组件暴露的属性和方法(组件内部使用) */
export interface _ActionExposed {}
export interface _ActionGroupExposed {
closeTip: () => void
}
/** 操作组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type ActionExposed = DeconstructValue<_ActionExposed>
export type ActionGroupExposed = DeconstructValue<_ActionGroupExposed>
UAutoComplete - 自动补全
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UAutoComplete 示例
基础用法
<script setup lang="ts">
import { ref } from 'vue'
const query = ref('')
const fruits = ['Apple', 'Banana', 'Cherry', 'Durian', 'Grape', 'Mango', 'Orange', 'Peach']
</script>
<template>
<u-auto-complete v-model="query" :suggestions="fruits" placeholder="输入水果名称" />
</template>异步建议
<script setup lang="ts">
import { ref } from 'vue'
const query = ref('')
async function fetchSuggestions(keyword?: string): Promise<string[]> {
if (!keyword) return []
const res = await fetch(`/api/search?q=${encodeURIComponent(keyword)}`)
return res.json()
}
</script>
<template>
<u-auto-complete v-model="query" :suggestions="fetchSuggestions" placeholder="搜索..." />
</template>自定义选项模板
<script setup lang="ts">
import { ref } from 'vue'
const query = ref('')
const users = ['Alice', 'Bob', 'Charlie', 'Diana']
</script>
<template>
<u-auto-complete v-model="query" :suggestions="users">
<template #default="{ option }">
<span style="font-weight: bold;">👤 {{ option }}</span>
</template>
</u-auto-complete>
</template>只读模式
<script setup lang="ts">
import { ref } from 'vue'
const query = ref('Apple')
const fruits = ['Apple', 'Banana', 'Cherry']
</script>
<template>
<u-auto-complete v-model="query" :suggestions="fruits" readonly />
</template>import type { DeconstructValue, FormComponentProps } from '@veltra/utils'
/** 自动补全组件组件属性 */
export interface AutoCompleteProps extends FormComponentProps {
modelValue?: string
/** 占位符 */
placeholder?: string
/** 建议 */
suggestions?: string[] | (() => Promise<string[]> | string[])
/** 是否可清空 */
clearable?: boolean
/** 是否允许输入不在建议列表中的自定义值 */
allowCustom?: boolean
}
/** 自动补全组件组件定义的事件 */
export interface AutoCompleteEmits {
(e: 'update:modelValue', value: string): void
(e: 'select', value: string): void
}
/** 自动补全组件组件暴露的属性和方法(组件内部使用) */
export interface _AutoCompleteExposed {
open: () => void
close: () => void
}
/** 自动补全组件组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type AutoCompleteExposed = DeconstructValue<_AutoCompleteExposed>
UBadge - 徽标
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UBadge 示例
基础使用
<u-badge :value="5">
<u-button>消息</u-button>
</u-badge>
<u-badge :value="10" type="primary">
<u-button>通知</u-button>
</u-badge>超出最大值
<u-badge :value="120" type="danger">
<u-button>评论</u-button>
<!-- 显示 99+ -->
</u-badge>
<u-badge :value="50" :max="49" type="info">
<u-button>消息</u-button>
<!-- 显示 49+ -->
</u-badge>圆点模式 + 自定义颜色
<u-badge dot type="danger">
<span>未读消息</span>
</u-badge>
<u-badge :value="9" color="#ff6b6b">
<u-button>自定义背景色</u-button>
</u-badge>文本值 + 隐藏
<u-badge value="NEW" type="success">
<u-button>活动</u-button>
</u-badge>
<u-badge :value="0" :hidden="count === 0">
<u-button>待处理</u-button>
</u-badge>import type { ColorType, ComponentProps, DeconstructValue } from '@veltra/utils'
/** 徽章组件属性 */
export interface BadgeProps extends ComponentProps {
/** 显示值 */
value?: number | string
/** 类别 */
type?: ColorType
/** 自定义背景色 */
color?: string
/** 是否隐藏 Badge */
hidden?: boolean
/** 最大值 {{max}}+ */
max?: number
/** 是否显示小圆点 */
dot?: boolean
}
/** 徽章组件定义的事件 */
export interface BadgeEmits {
(e: 'update:modelValue', value: string): void
}
/** 徽章组件暴露的属性和方法(组件内部使用) */
export interface _BadgeExposed {}
/** 徽章组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type BadgeExposed = DeconstructValue<_BadgeExposed>
UBatchEdit - 批量编辑
类型文件
见 ./types.d.ts
示例
见 ./examples.md
辅助工具
本组件通常配合以下工具来使用。
defineTableColumns
与 UTable 相同,为左侧表格列批量设置公共列属性。
使用示例:
import { defineTableColumns } from '@veltra/desktop'UBatchEdit 示例
基础用法
<script setup lang="ts">
import { defineTableColumns } from '@veltra/desktop'
import { reactive, ref } from 'vue'
const columns = defineTableColumns([
{ name: '姓名', key: 'name', width: 120 },
{ name: '年龄', key: 'age', width: 80 }
])
const data = ref([
{ name: '张三', age: 28 },
{ name: '李四', age: 32 }
])
const model = reactive({ name: '', age: undefined as number | undefined })
</script>
<template>
<u-batch-edit v-model:data="data" :columns="columns" :model="model">
<template #form>
<u-input field="name" label="姓名" :rules="{ required: true }" />
<u-number-input field="age" label="年龄" :min="0" :max="120" />
</template>
</u-batch-edit>
</template>带校验规则
<script setup lang="ts">
import { defineTableColumns } from '@veltra/desktop'
import { reactive, ref } from 'vue'
const columns = defineTableColumns([
{ name: '名称', key: 'name', width: 120 },
{ name: '数量', key: 'count', width: 80 }
])
const data = ref([{ name: '项目 A', count: 1 }])
const model = reactive({ name: '', count: 0 })
</script>
<template>
<u-batch-edit v-model:data="data" :columns="columns" :model="model">
<template #form>
<u-input field="name" label="名称" :rules="{ required: true }" />
<u-number-input field="count" label="数量" :rules="{ min: 0 }" />
</template>
</u-batch-edit>
</template>快速编辑
开启 quick-edit 后,编辑行时表单会实时写回 row.data(经 model 中转),且不调用 saveMethod;新增仍走保存流程。
<script setup lang="ts">
import { defineTableColumns } from '@veltra/desktop'
import { reactive, ref } from 'vue'
const columns = defineTableColumns([
{ name: '姓名', key: 'name', width: 120 },
{ name: '年龄', key: 'age', width: 80 }
])
const data = ref([{ name: '张三', age: 28 }])
const model = reactive({ name: '', age: undefined as number | undefined })
</script>
<template>
<u-batch-edit v-model:data="data" :columns="columns" :model="model" quick-edit>
<template #form="{ row }">
<u-input field="name" label="姓名" />
<u-number-input field="age" label="年龄" :min="0" />
</template>
</u-batch-edit>
</template>功能限制
通过 features 控制可用操作;支持数组或对象(false / 函数动态禁用)。
<script setup lang="ts">
import { defineTableColumns } from '@veltra/desktop'
import type { BatchEditFeature } from '@veltra/desktop'
import { reactive, ref } from 'vue'
const columns = defineTableColumns([
{ name: '姓名', key: 'name', width: 120 },
{ name: '年龄', key: 'age', width: 80 }
])
const data = ref([{ name: '张三', age: 28 }])
const model = reactive({ name: '', age: undefined as number | undefined })
const features: BatchEditFeature[] = ['create', 'update']
</script>
<template>
<u-batch-edit
v-model:data="data"
:columns="columns"
:model="model"
:features="features"
:actions-props="{ delete: { needConfirm: true } }"
>
<template #form>
<u-input field="name" label="姓名" />
<u-number-input field="age" label="年龄" :min="0" />
</template>
</u-batch-edit>
</template>import type { DeconstructValue } from '@veltra/utils'
import type { ActionProps } from './action'
import type { TableColumn, TableColumnSlotsScope, TableEmits, TableProps, TableRow } from './table'
/** 批量编辑列 */
export interface BatchEditColumn extends TableColumn {}
export type BatchEditFeature = 'create' | 'update' | 'delete' | 'view' | 'createChild'
export type BatchEditFormStatus = 'hidden'
/** 批量编辑状态 */
export interface BatchEditStates {
/** 层级 */
depth: number
/** 表单可见性 */
formVisible: boolean
/** 表单操作类型 */
formActionType: 'create' | 'update' | 'view' | 'createChild'
/** 加载状态 */
loading: boolean
/** 当前编辑行 */
row?: TableRow
/** 当前编辑或者新增的父级行 */
parentRow?: TableRow
/** 行索引路径, */
indexPath: number[]
}
/** 批量编辑组件属性 */
export interface BatchEditProps extends TableProps {
/**
* 表单数据
* @description 与右侧 UForm 绑定的 reactive 对象
*/
model?: Record<string, any>
/** 表格标题 */
title?: string
/**
* 列的宽度定义
*/
cols?: string | [string, string]
/** 只读模式 */
readonly?: boolean
/**
* 开启快速编辑
* @description 开启后,编辑行时表单实时写回 `row.data`(经 `model` 中转),且不调用 `saveMethod`
*/
quickEdit?: boolean
/**
* 新增前的钩子
* @description 仅作用于 create 类操作,在保存时调用。可直接修改传入的 draft 对象
*/
beforeCreate?: (
data: Record<string, any>,
parentData?: Record<string, any>
) => void | Promise<void>
/** label的宽度 */
labelWidth?: string | number
/** 删除方法 */
deleteMethod?: (data: Record<string, any>[]) => any
/**
* 保存方法
* @description 保存时调用。`quick` 模式下编辑行时实时写回,不调用此方法;新增时与 `normal` 模式一致
* @returns 如果返回一个值,那么这个值会被插入,否则插入的为表单值
*/
saveMethod?: (
/** 表单数据 */
data: Record<string, any>,
/** 操作类型 */
actionType: BatchEditStates['formActionType'],
/** 父级数据 */
parentData?: Record<string, any>
) => any
/**
* 可用功能,不穿则对功能没有任何限制
*
* ## 用法
* ```ts
* // 只允许新增和更新
* const features = ['create', 'update']
* // 不允许新增,并且只有当行深度小于2时才允许新增子级,对其他功能不做限制
* const features = {
* create: false,
* createChild: row => row.depth < 2
* }
* ```
*/
features?:
| Array<BatchEditFeature>
| {
[key in BatchEditFeature]?: boolean | ((row: TableRow) => boolean)
}
/**
* 操作按钮的属性配置, 可以是action组件的任意属性
* @example
* ```ts
* const actionsProps = {
* delete: {
* needConfirm: true,
* circle: false
* }
* }
* ```
*/
actionsProps?: Partial<Record<BatchEditFeature, ActionProps>>
}
/** 批量编辑组件定义的事件 */
export interface BatchEditEmits extends TableEmits {
/** 更新数据 */
(e: 'update:data', value: Record<string, any>[]): void
}
export type BatchEditSlots = {
form?: (props: {
/** 当前编辑的层级 */
depth?: number
/** 当前编辑的行 */
row?: TableRow
/** 当前编辑的行索引 */
index?: number
/** 操作的目标行索引路径 */
indexes?: number[]
}) => any
header?: () => any
} & Partial<{ [key: `column:${string}`]: (props: TableColumnSlotsScope) => any }>
/** 批量编辑组件暴露的属性和方法(组件内部使用) */
export interface _BatchEditExposed {}
/** 批量编辑组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type BatchEditExposed = DeconstructValue<_BatchEditExposed>
UBreadcrumb - 面包屑
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UBreadcrumb 示例
基础用法
<template>
<u-breadcrumb
:items="[
{ title: '首页', href: '/home' },
{ title: '产品', href: '/products' },
{ title: '详情' }
]"
/>
</template>禁用项与点击事件
<template>
<u-breadcrumb
:items="[
{ title: '首页', href: '/home' },
{ title: '分类', disabled: true },
{ title: '详情' }
]"
@click="handleClick"
/>
</template>
<script setup lang="ts">
import type { BreadcrumbItem } from '@veltra/desktop'
const handleClick = (item: BreadcrumbItem, index: number, ev: Event) => {
console.log('clicked:', item.title, index)
}
</script>自定义分隔符与项渲染
<template>
<u-breadcrumb :items="crumbs">
<template #separator>→</template>
<template #item="{ item, isLast }">
<span :style="{ fontWeight: isLast ? 'bold' : 'normal' }">
{{ item.title }}
</span>
</template>
</u-breadcrumb>
</template>
<script setup lang="ts">
const crumbs = [{ title: '首页', href: '/' }, { title: '设置' }, { title: '安全' }]
</script>末级作为链接
<template>
<u-breadcrumb
last-linked
:items="[
{ title: '首页', href: '/' },
{ title: '列表', href: '/list' },
{ title: '详情', href: '/detail/42' }
]"
/>
</template>import type { ComponentSize, DeconstructValue } from '@veltra/utils'
/** 面包屑单项 */
export interface BreadcrumbItem {
/** 展示文案 */
title: string
/** 存在时渲染为 `<a>`,由浏览器处理导航 */
href?: string
/** 为 true 时不跳转、不触发 click */
disabled?: boolean
}
/** 面包屑组件属性 */
export interface BreadcrumbProps {
/** 路径项,顺序为从一级到末级 */
items: BreadcrumbItem[]
/** 尺寸 */
size?: ComponentSize
/**
* 末级是否作为链接渲染
* @default false — 末级为当前页,使用 `aria-current="page"`
*/
lastLinked?: boolean
}
/** `item` 插槽作用域 */
export interface BreadcrumbSlotScope {
item: BreadcrumbItem
index: number
isLast: boolean
}
/** 面包屑组件事件 */
export interface BreadcrumbEmits {
/**
* 可交互项(无 `href` 的链式项)被点击时触发;有 `href` 时不触发(走原生导航)
*/
(e: 'click', item: BreadcrumbItem, index: number, ev: Event): void
}
/** @internal */
export interface _BreadcrumbExposed {}
export type BreadcrumbExposed = DeconstructValue<_BreadcrumbExposed>
UButton / UButtonGroup - 按钮
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UButton 示例
颜色 / 尺寸 / 模式
<u-button>默认</u-button>
<u-button type="primary">主要</u-button>
<u-button type="success">成功</u-button>
<u-button type="warning">警告</u-button>
<u-button type="danger">危险</u-button>
<u-button size="small" type="primary">小</u-button>
<u-button size="large" type="primary">大</u-button>
<u-button plain type="primary">朴素</u-button>
<u-button text type="success">文本</u-button>
<u-button loading type="primary">加载中</u-button>
<u-button disabled type="danger">禁用</u-button>图标 / 圆形 / 自定义加载图标
<script setup>
import { Search, Edit, Refresh } from '@veltra/icons/normal'
</script>
<template>
<u-button type="primary" :icon="Search">搜索</u-button>
<u-button type="primary" :icon="Search" icon-position="right">搜索</u-button>
<u-button type="primary" circle :icon="Edit" />
<u-button loading type="primary" :loading-icon="Refresh">刷新</u-button>
</template>阻止冒泡 / 获取 DOM
<script setup lang="ts">
import { useTemplateRef } from 'vue'
const btnRef = useTemplateRef('btn')
// btnRef.value?.el → HTMLButtonElement
</script>
<template>
<div @click="handleOuter">
<u-button ref="btn" type="primary" :propagate="false" @click="handleClick">
不冒泡到外层
</u-button>
</div>
</template>UButtonGroup — 统一 props 透传
<u-button-group v-slot="{ props }" size="small" disabled>
<u-button v-bind="props" type="primary">剪切</u-button>
<u-button v-bind="props" type="primary">复制</u-button>
<u-button v-bind="props" type="primary">粘贴</u-button>
</u-button-group>UButtonGroup — 统一 props 透传(续)
<script setup>
import { shallowRef } from 'vue'
import { bem } from '@veltra/utils'
const buttons = [
{ type: 'primary' as const, text: '选项一' },
{ type: 'primary' as const, text: '选项二' }
]
const active = shallowRef(0)
const cls = bem('button')
</script>
<template>
<u-button-group v-slot="{ props }">
<u-button
v-for="(btn, i) in buttons"
:key="i"
v-bind="props"
:class="cls.is('active', i === active)"
@click="active = i"
>
{{ btn.text }}
</u-button>
</u-button-group>
</template>import type { ColorType, ComponentProps, DeconstructValue } from '@veltra/utils'
import type { Component, ShallowRef } from 'vue'
/** 按钮类型 */
export type ButtonType = ColorType
/** 按钮属性类型 */
export interface ButtonProps extends ComponentProps {
/** 按钮类型 */
type?: ButtonType
/** 是否以文本形式展示 */
text?: boolean
/** 朴素模式 */
plain?: boolean
/** 加载中 */
loading?: boolean
/** 加载图标 */
loadingIcon?: Component
/** 圆形 */
circle?: boolean
/** 禁用 */
disabled?: boolean
/** 图标 */
icon?: Component
/** 图标大小, 单位px */
iconSize?: number
/** 图标位置 */
iconPosition?: 'left' | 'right'
/** 事件是否传播(冒泡或者捕获) */
propagate?: boolean
}
export interface ButtonEmits {
/** 点击事件 */
(name: 'click', e: MouseEvent): void
}
/** 在组件内部引用 */
export interface _ButtonExposed {
el: ShallowRef<HTMLButtonElement | undefined>
}
/** 按钮暴露的属性和方法 */
export type ButtonExposed = DeconstructValue<_ButtonExposed>
UCalendar - 日历
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UCalendar 示例
基础用法
<script setup lang="ts">
import { ref } from 'vue'
const date = ref<string>('2026-05-13')
</script>
<template>
<u-calendar v-model="date" />
</template>监听日期变化
<script setup lang="ts">
import { ref, watch } from 'vue'
const date = ref<string>(new Date().toISOString().slice(0, 10))
watch(date, (val) => {
console.log('选中日期已变更:', val)
})
</script>
<template>
<u-calendar v-model="date" />
</template>自定义选中日期样式
.u-calendar__day--current {
&:hover {
background-color: var(--color-primary-light);
}
}配合其他组件使用
<script setup lang="ts">
import { ref } from 'vue'
const date = ref<string>()
</script>
<template>
<div class="demo-calendar">
<p v-if="date">选中的日期:{{ date }}</p>
<u-calendar v-model="date" />
</div>
</template>import type { Dater } from '@cat-kit/core'
import type { DeconstructValue } from '@veltra/utils'
/** day接口 */
export interface CalendarDay {
date: Dater
/** 是否今日 */
isToday?: boolean
/** 日期类型:上月, 本月, 下月 */
type: 'pre' | 'current' | 'next'
/** 是否禁止选择 */
disabled?: boolean
}
export interface CalendarMonth {
date: Dater
/** 是否禁止选择 */
disabled?: boolean
/** 年月标识 */
key: string
/** 月份 */
month: number
}
export interface CalendarYear {
date: Dater
/** 是否禁止选择 */
disabled?: boolean
/** 年份 */
year: number
}
/** 日历组件属性 */
export interface CalendarProps {
modelValue?: string
}
/** 日历组件定义的事件 */
export interface CalendarEmits {
(e: 'update:modelValue', value: string): void
}
/** 日历组件暴露的属性和方法(组件内部使用) */
export interface _CalendarExposed {}
/** 日历组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type CalendarExposed = DeconstructValue<_CalendarExposed>
UCard - 卡片
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UCard 示例
基础卡片
<u-card width="320">
<u-card-content>这是一张基础卡片</u-card-content>
</u-card>带封面的卡片
<u-card width="360">
<u-card-cover src="https://picsum.photos/360/200" height="200" />
<u-card-header><h3>卡片标题</h3></u-card-header>
<u-card-content>
<p>卡片正文内容,描述这张卡片的相关信息。</p>
</u-card-content>
<u-card-action align-right>
<u-button type="primary" text>操作一</u-button>
<u-button type="primary" text>操作二</u-button>
</u-card-action>
</u-card>融合样式卡片
<u-card integrate>
<u-card-header>无阴影卡片</u-card-header>
<u-card-content>
<p>当 integrate 为 true 时,卡片没有阴影,适合嵌入到其他容器中使用。</p>
</u-card-content>
</u-card>封面模式内容
<u-card width="400">
<u-card-content cover>
<img
src="https://picsum.photos/400/180"
alt="封面"
style="width: 100%; border-radius: inherit"
/>
</u-card-content>
<u-card-header>自定义封面布局</u-card-header>
<u-card-content>
<p>使用 cover 模式可以让内容区无缝贴合图片。</p>
</u-card-content>
</u-card>import type { ComponentProps, DeconstructValue } from '@veltra/utils'
/** 卡片组件属性 */
export interface CardProps extends ComponentProps {
/** 宽度 */
width?: string | number
/** 融合样式,卡片不再有阴影 */
integrate?: boolean
}
export interface CardActionProps {
/** 右对齐 */
alignRight?: boolean
}
export interface CardContentProps {
/** 封面模式 */
cover?: boolean
}
export interface CardCoverProps {
/** 封面图片地址 */
src: string
/** 封面高度 */
height?: string | number
}
export interface CardEmits {}
export interface _CardExposed {}
export type CardExposed = DeconstructValue<_CardExposed>
UCascade - 级联选择器
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UCascade 示例
基础单选
<script setup lang="ts">
import { ref } from 'vue'
const value = ref<string>()
const options = [
{
value: '1',
label: '北京',
children: [
{ value: '11', label: '朝阳区' },
{ value: '12', label: '海淀区' }
]
},
{
value: '2',
label: '上海',
children: [
{ value: '21', label: '浦东新区' },
{ value: '22', label: '徐汇区' }
]
}
]
</script>
<template>
<u-cascade v-model="value" :data="options" />
</template>多选模式
<script setup lang="ts">
import { ref } from 'vue'
const value = ref<string[]>([])
const options = [
{
value: '1',
label: '技术部',
children: [
{ value: '11', label: '前端组' },
{ value: '12', label: '后端组' }
]
},
{
value: '2',
label: '产品部',
children: [
{ value: '21', label: '移动端' },
{ value: '22', label: 'PC 端' }
]
}
]
</script>
<template>
<u-cascade v-model="value" :data="options" multiple />
</template>搜索过滤
<template>
<u-cascade v-model="value" :data="options" filterable placeholder="搜索地区" />
</template>严格模式 + 自定义字段
<template>
<u-cascade
v-model="value"
:data="data"
strict
label-key="name"
value-key="id"
children-key="subs"
/>
</template>import type { TreeNode } from '@cat-kit/core'
import type { DeconstructValue, FormComponentProps } from '@veltra/utils'
export interface CascadeNode<
Data extends Record<string, any> = Record<string, any>
> extends TreeNode<Data, CascadeNode<Data>> {
visible: boolean
value: string
label: string
}
/** 级联选择器组件属性 */
export interface CascadeProps extends FormComponentProps {
/**
* 分隔符
* @default '/'
*/
separator?: string
/** 数据值 */
modelValue?: string[] | string
/** 级联数据项的标签字段 */
labelKey?: string
/** 级联数据项的值字段 */
valueKey?: string
/** 占位符 */
placeholder?: string
/** 是否可清除 */
clearable?: boolean
/** 子级字段 */
childrenKey?: string
/** 严格模式 */
strict?: boolean
/**
* 数据项
*/
data?: Record<string, any>[]
/**
* 禁用项
*/
disabledNode?: (item: Record<string, any>) => boolean
/**
* 多选
*/
multiple?: boolean
/**
* 搜索
*/
filterable?: boolean
visibilityLimit?: number
}
export interface PanelItem {
key: number
nodes: CascadeNode[]
}
/** 级联选择器组件定义的事件 */
export interface CascadeEmits {
(e: 'update:modelValue', value?: string | string[]): void
(e: 'change', value: string[], label: string[], data: Record<string, any>[]): void
(e: 'change', value?: string, label?: string, item?: Record<string, any>): void
(e: 'clear'): void
}
/** 级联选择器组件暴露的属性和方法(组件内部使用) */
export interface _CascadeExposed {}
/** 级联选择器组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type CascadeExposed = DeconstructValue<_CascadeExposed>
UCheckTag - 可选标签
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UCheckTag 示例
基础用法
<script setup lang="ts">
import { ref } from 'vue'
const checked = ref(false)
</script>
<template>
<u-check-tag v-model="checked">标签</u-check-tag>
</template>受控用法
<script setup lang="ts">
import { ref } from 'vue'
const checked = ref(true)
function handleChange(value: boolean) {
console.log('checked:', value)
}
</script>
<template>
<u-check-tag :checked="checked" @update:model-value="handleChange"> 受控标签 </u-check-tag>
</template>多选标签组
<script setup lang="ts">
import { reactive } from 'vue'
const tags = reactive([
{ label: 'Vue', checked: false },
{ label: 'React', checked: true },
{ label: 'Angular', checked: false }
])
</script>
<template>
<div class="tag-group">
<u-check-tag v-for="tag in tags" :key="tag.label" v-model="tag.checked">
{{ tag.label }}
</u-check-tag>
</div>
</template>import type { DeconstructValue } from '@veltra/utils'
/** check-tag组件属性 */
export interface CheckTagProps {
modelValue?: boolean
checked?: boolean
}
/** check-tag组件定义的事件 */
export interface CheckTagEmits {
(e: 'update:modelValue', value: boolean): void
}
/** check-tag组件暴露的属性和方法(组件内部使用) */
export interface _CheckTagExposed {}
/** check-tag组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type CheckTagExposed = DeconstructValue<_CheckTagExposed>
UCheckboxGroup - 复选框组
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UCheckboxGroup 示例
基础用法
<script setup lang="ts">
import { ref } from 'vue'
const checked = ref<(string | number)[]>([])
const items = [
{ label: '苹果', value: 'apple' },
{ label: '香蕉', value: 'banana' },
{ label: '橙子', value: 'orange' }
]
</script>
<template>
<u-checkbox-group v-model="checked" :items="items" />
</template>在 UForm 中使用
<script setup lang="ts">
import { reactive } from 'vue'
const formData = reactive({ hobbies: ['reading'] as string[] })
const hobbyList = [
{ label: '阅读', value: 'reading' },
{ label: '运动', value: 'sports' }
]
</script>
<template>
<u-form :model="formData">
<u-checkbox-group label="爱好" field="hobbies" :items="hobbyList" />
</u-form>
</template>import type { FormComponentProps } from '@veltra/utils'
/** 复选框组, 用来选择一组数据组件属性 */
export interface CheckboxGroupProps extends FormComponentProps {
/** 值 */
modelValue?: Array<any>
/** 复选框项 */
items: Array<Record<string, any>>
/** 标签文本的key */
labelKey?: string
/** 值的key */
valueKey?: string
/** 块级显示 */
block?: boolean
}
/** 复选框组, 用来选择一组数据组件属性 */
export interface CheckboxGroupEmits {
(e: 'update:modelValue', value: Array<any>): void
}
/** 复选框组, 用来选择一组数据暴露的属性和方法 */
export interface CheckboxGroupExposed {}
UCheckbox - 复选框
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UCheckbox 示例
基础用法
<script setup lang="ts">
import { ref } from 'vue'
const agreed = ref(false)
</script>
<template>
<u-checkbox v-model="agreed">我已阅读并同意</u-checkbox>
</template>半选状态(全选场景)
<script setup lang="ts">
import { computed, ref } from 'vue'
const checkedItems = ref<string[]>([])
const options = ['苹果', '香蕉', '橙子']
const isChecked = computed(() => checkedItems.value.length === options.length)
const isIndeterminate = computed(
() => checkedItems.value.length > 0 && checkedItems.value.length < options.length
)
function handleCheckAll(checked: boolean) {
checkedItems.value = checked ? [...options] : []
}
</script>
<template>
<u-checkbox :model-value="isChecked" :indeterminate="isIndeterminate" @change="handleCheckAll">
全选
</u-checkbox>
</template>禁用与只读
<template>
<u-checkbox v-model="checked" disabled>禁用状态</u-checkbox>
<u-checkbox v-model="checked" readonly>只读状态</u-checkbox>
</template>在 UForm 中使用
<script setup lang="ts">
import { reactive } from 'vue'
const formData = reactive({ remember: false })
</script>
<template>
<u-form :model="formData" disabled>
<u-checkbox label="记住登录" field="remember">30 天内免登录</u-checkbox>
</u-form>
</template>import type { ColorType, FormComponentProps } from '@veltra/utils'
/** 复选框组件属性 */
export interface CheckboxProps extends FormComponentProps {
/** 部分选中 */
indeterminate?: boolean
/** 是否选中 */
modelValue?: boolean
}
export interface CheckboxButtonProps extends FormComponentProps {
/** 是否选中 */
modelValue?: boolean
/** 是否圆角 */
round?: boolean
/** 类型 */
type?: ColorType
}
export interface CheckboxEmits {
(name: 'update:modelValue', checked: boolean): void
(name: 'change', checked: boolean, e: MouseEvent): void
}
export interface CheckboxButtonEmits {
(name: 'update:modelValue', checked: boolean): void
(name: 'change', checked: boolean): void
}
/** 复选框暴露的属性和方法 */
export interface CheckboxExposed {}
UCodeEditor - 代码编辑器
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UCodeEditor 示例
基础用法
<script setup lang="ts">
import { shallowRef } from 'vue'
const code = shallowRef('console.log("Hello, World!")')
</script>
<template>
<u-code-editor v-model="code" language="js" />
</template>暗色主题 + JSON 编辑
<script setup lang="ts">
import { shallowRef } from 'vue'
const jsonCode = shallowRef(`{
"name": "Ultra UI",
"version": "1.0.0"
}`)
</script>
<template>
<u-code-editor v-model="jsonCode" language="json" dark :default-lines="6" />
</template>只读代码展示 + 自定义行数
<script setup lang="ts">
const snippet = `SELECT *
FROM users
WHERE status = 'active'
ORDER BY created_at DESC`
</script>
<template>
<u-code-editor :model-value="snippet" language="sql" readonly :default-lines="5" />
</template>在 UForm 中使用
<script setup lang="ts">
import { reactive } from 'vue'
const form = reactive({ script: '' })
</script>
<template>
<u-form :model="form">
<u-code-editor
label="自定义脚本"
field="script"
language="js"
:default-lines="12"
tips="请输入合法的 JavaScript 代码"
/>
</u-form>
</template>import type { DeconstructValue, FormComponentProps } from '@veltra/utils'
export type CodeEditorLang = 'js' | 'sql' | 'java' | 'json'
/** 代码编辑器组件属性(不支持 `size`) */
export interface CodeEditorProps extends Omit<FormComponentProps, 'size'> {
modelValue?: string
/** 定义语言 */
language?: CodeEditorLang
/** 是否使用暗色主题 */
dark?: boolean
/**
* 默认显示的行数,用于撑起编辑器最小高度,超出后滚动
* @default 8
*/
defaultLines?: number
}
/** 代码编辑器组件定义的事件 */
export interface CodeEditorEmits {
(e: 'update:modelValue', value: string): void
}
/** 代码编辑器组件暴露的属性和方法(组件内部使用) */
export interface _CodeEditorExposed {}
/** 代码编辑器组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type CodeEditorExposed = DeconstructValue<_CodeEditorExposed>
UCollapse / UCollapseItem - 折叠面板
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UCollapse 示例
基础用法
<script setup lang="ts">
import { ref } from 'vue'
import type { CollapseModelValue } from '@veltra/desktop'
const active = ref<CollapseModelValue>(['1'])
</script>
<template>
<u-collapse v-model="active">
<u-collapse-item value="1" title="标题 1">内容 1</u-collapse-item>
<u-collapse-item value="2" title="标题 2">内容 2</u-collapse-item>
<u-collapse-item value="3" title="标题 3">内容 3</u-collapse-item>
</u-collapse>
</template>手风琴模式
<template>
<!-- accordion 模式下 modelValue 为单值。每个折叠项表现为独立的精致胶囊卡片 -->
<u-collapse v-model="active" accordion>
<u-collapse-item value="a" title="常规设置">…</u-collapse-item>
<u-collapse-item value="b" title="高级配置">…</u-collapse-item>
<u-collapse-item value="c" title="关于" disabled>…</u-collapse-item>
</u-collapse>
</template>自定义图标与标题
<script setup lang="ts">
import { Star, ArrowDown } from '@veltra/icons/normal'
</script>
<template>
<u-collapse v-model="active" :expand-icon="ArrowDown">
<u-collapse-item value="1">
<template #title>
<span style="display:inline-flex;align-items:center;gap:6px">
<u-icon><Star /></u-icon>
收藏夹
</span>
</template>
收藏内容
</u-collapse-item>
<u-collapse-item value="2" title="动态图标">
<template #icon="{ isActive }">
<u-icon :style="{ color: isActive ? 'var(--u-color-primary)' : '' }">
<ArrowDown />
</u-icon>
</template>
根据展开状态切换图标样式
</u-collapse-item>
</u-collapse>
</template>默认展开与全部折叠(default-collapse-all)
<script setup lang="ts">
import { ref } from 'vue'
import type { CollapseModelValue } from '@veltra/desktop'
const activeExpand = ref<CollapseModelValue>()
const activeCollapse = ref<CollapseModelValue>()
</script>
<template>
<!-- default-collapse-all 默认为 false,未传递绑定初始值时默认展开全部折叠项 -->
<u-collapse v-model="activeExpand">
<u-collapse-item value="x1" title="模块 A">默认全部展开</u-collapse-item>
<u-collapse-item value="x2" title="模块 B">默认全部展开</u-collapse-item>
</u-collapse>
<!-- 显式配置 default-collapse-all 后,即使没有初始绑定值,组件也会默认折叠收起所有项 -->
<u-collapse v-model="activeCollapse" default-collapse-all>
<u-collapse-item value="y1" title="模块 A">初始化默认为折叠收起状态</u-collapse-item>
<u-collapse-item value="y2" title="模块 B">只有手动点击头部才会展开</u-collapse-item>
</u-collapse>
</template>import type { ComponentProps } from '@veltra/utils'
import type { Component } from 'vue'
/** Collapse 项的唯一标识 */
export type CollapseValue = string | number
/** Collapse modelValue:手风琴模式为单值,普通模式为数组(也兼容传入单值) */
export type CollapseModelValue = CollapseValue | CollapseValue[]
/** Collapse 组件属性 */
export interface CollapseProps extends ComponentProps {
/** 当前展开项的 value(单个或多个) */
modelValue?: CollapseModelValue
/**
* 是否手风琴模式(一次只能展开一项)
* @default false
*/
accordion?: boolean
/**
* 是否默认折叠全部项。设为 false 时默认全部展开。
* @default false
*/
defaultCollapseAll?: boolean
/**
* 自定义展开图标组件,活动态会自动旋转 90°。
* 接受任意 Vue 组件(SFC、Functional Component 等)。
*/
expandIcon?: Component
}
export interface CollapseEmits {
(e: 'update:modelValue', value: CollapseModelValue): void
/** 当前展开项变更时触发 */
(e: 'change', value: CollapseModelValue): void
}
/** CollapseItem 组件属性 */
export interface CollapseItemProps {
/** 唯一标识 */
value: CollapseValue
/** 标题文本(也可使用 #title 插槽) */
title?: string
/** 是否禁用 */
disabled?: boolean
}
UConditionEditor - 条件编辑器
类型文件
见 ./types.d.ts
示例
见 ./examples.md
辅助工具
本组件通常配合以下工具来使用。
evaluateConditionExpression
对条件表达式 JSON 求值,与编辑器 UI 解耦的纯函数。
使用示例:
import { evaluateConditionExpression } from '@veltra/desktop'createEmptyGroup / createEmptyLeaf
创建空的条件分组或叶子节点。
使用示例:
import { createEmptyGroup, createEmptyLeaf } from '@veltra/desktop'UConditionEditor 示例
基础使用
<script setup lang="ts">
import { shallowRef } from 'vue'
import type { ConditionExpression, ConditionField } from '@veltra/desktop'
const expression = shallowRef<ConditionExpression>({
type: 'group',
connectors: ['and'],
children: [
{
type: 'condition',
field: 'status',
operator: 'eq',
value: { kind: 'constant', value: '进行中' }
},
{
type: 'condition',
field: 'priority',
operator: 'gt',
value: { kind: 'constant', value: '3' }
}
]
})
const fields: ConditionField[] = [
{ label: '状态', value: 'status', type: 'string' },
{ label: '优先级', value: 'priority', type: 'number' },
{ label: '已完成', value: 'completed', type: 'boolean' },
{ label: '截止日期', value: 'deadline', type: 'date' },
{
label: '类型',
value: 'type',
type: 'enum',
enumOptions: [
{ label: '需求', value: 'requirement' },
{ label: '缺陷', value: 'bug' }
]
}
]
</script>
<template>
<u-condition-editor v-model="expression" :fields="fields" />
</template>嵌套分组 + 混合逻辑
// 表达式语义:status == open AND (priority > 3 OR tag contains '紧急')
const expression: ConditionExpression = {
type: 'group',
connectors: ['and'],
children: [
{
type: 'condition',
field: 'status',
operator: 'eq',
value: { kind: 'constant', value: 'open' }
},
{
type: 'group',
connectors: ['or'],
children: [
{
type: 'condition',
field: 'priority',
operator: 'gt',
value: { kind: 'constant', value: '3' }
},
{
type: 'condition',
field: 'tag',
operator: 'contains',
value: { kind: 'constant', value: '紧急' }
}
]
}
]
}变量注入 + 运行期求值
<script setup lang="ts">
import { computed, shallowRef } from 'vue'
import { evaluateConditionExpression } from '@veltra/desktop'
import type { ConditionExpression, ConditionField, VariableItem } from '@veltra/desktop'
const expression = shallowRef<ConditionExpression>({
type: 'group',
connectors: [],
children: [
{
type: 'condition',
field: 'status',
operator: 'eq',
value: { kind: 'variable', name: 'currentUser.status' }
}
]
})
const fields: ConditionField[] = [{ label: '状态', value: 'status', type: 'string' }]
const variables: VariableItem[] = [
{
label: '当前用户',
value: 'currentUser',
children: [{ label: '状态', value: 'currentUser.status' }]
}
]
const data = { currentUser: { status: 'active' } }
const ok = computed(() => evaluateConditionExpression(expression.value, { fields, data }))
</script>
<template>
<u-condition-editor v-model="expression" :fields="fields" :variables="variables" />
<div>是否满足条件:{{ ok }}</div>
</template>禁用 / 只读
<u-condition-editor v-model="expression" :fields="fields" disabled />
<u-condition-editor v-model="expression" :fields="fields" readonly />import type { ComponentSize, DeconstructValue } from '@veltra/utils'
import type { VariableItem } from './expression-editor'
/** 字段定义 */
export interface ConditionField {
label: string
value: string
type: 'string' | 'number' | 'boolean' | 'date' | 'enum'
enumOptions?: { label: string; value: string }[]
}
/** 条件右侧值:常量或变量引用 */
export type ConditionValue =
| { kind: 'constant'; value: string }
| { kind: 'variable'; name: string }
/** 单行条件叶子节点 */
export interface ConditionLeaf {
type: 'condition'
field: string
operator: string
value: ConditionValue
}
/** 行间逻辑连接符 */
export type ConditionConnector = 'and' | 'or'
/** 条件组节点 —— 与叶子节点通过 children 统一编排 */
export interface ConditionGroup {
type: 'group'
children: ConditionNode[]
/**
* 子项之间的连接符
*
* - `connectors[i]` 用于 `children[i]` 与 `children[i + 1]` 之间
* - 长度应等于 `children.length - 1`;缺失项默认为 `and`
*/
connectors: ConditionConnector[]
}
/** 树节点:叶子或分组 */
export type ConditionNode = ConditionLeaf | ConditionGroup
/** 顶层表达式 = 根分组 */
export type ConditionExpression = ConditionGroup
export interface ConditionEditorProps {
modelValue?: ConditionExpression
fields?: ConditionField[]
variables?: VariableItem[]
size?: ComponentSize
disabled?: boolean
readonly?: boolean
}
export interface ConditionEditorEmits {
(e: 'update:modelValue', value: ConditionExpression): void
}
export interface _ConditionEditorExposed {}
export type ConditionEditorExposed = DeconstructValue<_ConditionEditorExposed>
UContextmenu - 右键菜单
类型文件
见 ./types.d.ts
示例
见 ./examples.md
辅助工具
本组件通常配合以下工具来使用。
contextmenu
在鼠标位置弹出右键菜单(函数式 API)。
使用示例:
import { contextmenu } from '@veltra/desktop'UContextmenu 示例
基础 + 图标 + async 回调
<script setup lang="ts">
import { ref } from 'vue'
import { Edit, Copy, Delete } from '@veltra/icons/normal'
import type { ContextMenuItem } from '@veltra/desktop'
const visible = ref(false)
const pos = ref({ x: 0, y: 0 })
const menus: ContextMenuItem[] = [
{ label: '编辑', icon: Edit, callback: () => console.log('编辑') },
{ label: '复制', icon: Copy, callback: () => console.log('复制') },
{
label: '删除',
icon: Delete,
callback: async () => {
// 异步执行期间显示 loading,阻止菜单关闭
await new Promise((resolve) => setTimeout(resolve, 2000))
}
}
]
function onContextMenu(e: MouseEvent) {
pos.value = { x: e.clientX, y: e.clientY }
visible.value = true
}
</script>
<template>
<div style="height: 300px; border: 1px dashed #ccc" @contextmenu.prevent="onContextMenu">
右键点击此区域
</div>
<u-contextmenu v-if="visible" :mouse-position="pos" :menus="menus" @destroy="visible = false" />
</template>动态菜单(函数形式 + 禁用判定)
function getMenus(): ContextMenuItem[] {
return [
{ label: '新增', callback: () => console.log('新增') },
{ label: '编辑', disabled: () => !hasPermission(), callback: () => {} },
{ label: '删除', disabled: true }
]
}自定义宽度 + 尺寸
<u-contextmenu
v-if="visible"
:mouse-position="pos"
:menus="menus"
:width="240"
size="large"
@destroy="visible = false"
/>import type { ComponentProps, DeconstructValue } from '@veltra/utils'
import type { Component } from 'vue'
/**
* 右键菜单项
*/
export interface ContextmenuItem {
/** 菜单名称 */
label: string
/** 菜单描述 */
description?: string
/** 菜单图标 */
icon?: Component
/** 子菜单 */
children?: ContextmenuItem[]
/** 菜单点击时的回调 */
callback?: () => any
/** 是否禁用 */
disabled?: boolean | (() => boolean)
}
/** 鼠标右键菜单组件属性 */
export interface ContextmenuProps extends ComponentProps {
/** 鼠标位置 */
mousePosition: { x: number; y: number }
/**
* 菜单宽度
* @default 200
*/
width?: number | string
/** 菜单项 */
menus: ContextmenuItem[] | (() => ContextmenuItem[])
}
/** 鼠标右键菜单组件定义的事件 */
export interface ContextmenuEmits {
(e: 'destroy'): void
}
/** 鼠标右键菜单组件暴露的属性和方法(组件内部使用) */
export interface _ContextmenuExposed {}
/** 鼠标右键菜单组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type ContextmenuExposed = DeconstructValue<_ContextmenuExposed>
UDatePanel - 日期面板
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UDatePanel 示例
基础日期选择
<script setup lang="ts">
import { ref } from 'vue'
import type { Dater } from '@cat-kit/core'
const date = ref<Dater>()
</script>
<template>
<u-date-panel :date="date" @select:date="date = $event" />
</template>禁用日期(禁用过去日期)
<script setup lang="ts">
import { ref } from 'vue'
import { date } from '@cat-kit/core'
import type { Dater } from '@cat-kit/core'
const date = ref<Dater>()
function disabledDate(d: Dater): boolean {
return d.timestamp < date().timestamp
}
</script>
<template>
<u-date-panel :date="date" :disabled-date="disabledDate" @select:date="date = $event" />
</template>日期范围选择
<script setup lang="ts">
import { ref } from 'vue'
import type { Dater } from '@cat-kit/core'
const rangeDate = ref<[Dater, Dater]>()
function onRangeSelect(val?: [Dater, Dater]) {
rangeDate.value = val
}
</script>
<template>
<u-date-panel range :range-date="rangeDate" @select:range-date="onRangeSelect" />
</template>年份选择(type="year")
<script setup lang="ts">
import { ref } from 'vue'
import type { Dater } from '@cat-kit/core'
const year = ref<Dater>()
</script>
<template>
<u-date-panel type="year" :date="year" @select:date="year = $event" />
</template>import type { Dater } from '@cat-kit/core'
import type { FormComponentProps } from '@veltra/utils'
export type PanelType = 'day' | 'month' | 'year'
export interface DatePanelProps {
date?: Dater
rangeDate?: [Dater, Dater]
range?: boolean
disabledDate?: (date: Dater) => boolean
type?: 'date' | 'month' | 'year'
size?: FormComponentProps['size']
}
export interface DatePanelEmits {
(e: 'select:date', date: Dater): void
(e: 'select:range-date', rangeDate?: [Dater, Dater]): void
}
UDatePicker - 日期选择器
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UDatePicker 示例
基础日期选择
<script setup>
import { shallowRef } from 'vue'
const date = shallowRef('')
const month = shallowRef('')
const year = shallowRef('')
</script>
<template>
<u-date-picker v-model="date" type="date" />
<u-date-picker v-model="month" type="month" />
<u-date-picker v-model="year" type="year" />
</template>禁用日期
<script setup>
import { date, type Dater } from '@cat-kit/core'
import { shallowRef } from 'vue'
const d = shallowRef('')
function disabledDate(d: Dater) {
return d.timestamp <= Date.now()
}
</script>
<template>
<u-date-picker v-model="d" :disabled-date="disabledDate" />
<p>选中: {{ d }}</p>
</template>modelValue 传入 Date / number
<script setup>
import { ref } from 'vue'
const dateRef = ref(new Date())
const timestampRef = ref(Date.now())
</script>
<template>
<u-date-picker v-model="dateRef" />
<u-date-picker v-model="timestampRef" />
</template>自定义格式
<template>
<u-date-picker v-model="date" format="yyyy年MM月dd日" />
<u-date-picker v-model="month" type="month" format="yyyy/MM" />
</template>在 UForm 中使用
<script setup>
import { reactive } from 'vue'
const formData = reactive({ birthday: '', joinDate: '' })
</script>
<template>
<u-form :model="formData">
<u-date-picker label="生日" field="birthday" />
<u-date-picker label="入职日期" field="joinDate" />
</u-form>
</template>import type { Dater } from '@cat-kit/core'
import type { DeconstructValue, FormComponentProps } from '@veltra/utils'
/** date-picker组件属性 */
export interface DatePickerProps extends FormComponentProps {
modelValue?: string | number | Date
/** 占位 */
placeholder?: string
/** 日期类型 */
type?: 'date' | 'month' | 'year'
/** 日期格式化 */
format?: string
/** 日期值格式化, 当没有指定时默认使用format属性,仅当值和显示的内容不一致时才需要使用到该属性 */
valueFormat?: string
/** 最小可选日期 */
disabledDate?: (date: Dater) => boolean
/** 是否显示清除按钮 */
clearable?: boolean
}
/** date-picker组件定义的事件 */
export interface DatePickerEmits {
(e: 'update:modelValue', value?: string | number | Date): void
}
/** date-picker组件暴露的属性和方法(组件内部使用) */
export interface _DatePickerExposed {}
/** date-picker组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type DatePickerExposed = DeconstructValue<_DatePickerExposed>
UDateRangePicker - 日期范围选择器
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UDateRangePicker 示例
基础用法
<u-date-range-picker v-model="range" />限制可选日期
<script setup>
const disabledDate = (d) => d.isBefore(new Date())
</script>
<template>
<u-date-range-picker v-model="range" :disabled-date="disabledDate" />
</template>月份范围选择
<u-date-range-picker v-model="range" type="month" format="yyyy年MM月" />只读模式
<u-date-range-picker v-model="range" readonly />import type { Dater } from '@cat-kit/core'
import type { DeconstructValue, FormComponentProps } from '@veltra/utils'
/** date-range-picker组件属性 */
export interface DateRangePickerProps extends FormComponentProps {
modelValue?: [string, string]
/** 占位 */
placeholder?: [string, string]
/** 日期类型 */
type?: 'date' | 'month' | 'year'
/** 日期格式化 */
format?: string
/** 日期值格式化, 当没有指定时默认使用format属性,仅当值和显示的内容不一致时才需要使用到该属性 */
valueFormat?: string
/** 最小可选日期 */
disabledDate?: (date: Dater) => boolean
/** 是否显示清除按钮 */
clearable?: boolean
}
/** date-range-picker组件定义的事件 */
export interface DateRangePickerEmits {
(e: 'update:modelValue', value?: [string, string]): void
}
/** date-range-picker组件暴露的属性和方法(组件内部使用) */
export interface _DateRangePickerExposed {}
/** date-range-picker组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type DateRangePickerExposed = DeconstructValue<_DateRangePickerExposed>
UDialog - 对话框
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UDialog 示例
基础对话框
<script setup lang="ts">
import { ref } from 'vue'
const visible = ref(false)
</script>
<template>
<u-button @click="visible = true">打开对话框</u-button>
<u-dialog v-model="visible" title="提示" style="width: 680px">
<p>这是对话框内容</p>
<template #footer="{ close }">
<u-button text @click="close()">取消</u-button>
<u-button type="primary" @click="close()">确认</u-button>
</template>
</u-dialog>
</template>使用 trigger 插槽
<template>
<u-dialog title="消息" @closed="console.log('已关闭')">
<template #trigger>
<u-button>打开对话框</u-button>
</template>
<p>通过 trigger 插槽控制显隐</p>
</u-dialog>
</template>非模态对话框
<template>
<u-dialog v-model="visible" title="非模态" :modal="false">
<p>不显示模态遮罩层,点击遮罩不会关闭</p>
</u-dialog>
</template>最大化与默认插槽作用域
<template>
<u-dialog v-model="visible" title="详情">
<template #default="{ maximized }">
<!-- 可以利用 maximized 来做一些操作,比如设置高度 100% 来让内容也跟着全屏 -->
<p v-if="maximized">对话框已最大化</p>
<p v-else>对话框处于正常尺寸</p>
</template>
</u-dialog>
</template>表单对话框
<script setup lang="ts">
import { reactive, ref, useTemplateRef } from 'vue'
const visible = ref(false)
const formRef = useTemplateRef('form')
const formData = reactive({ name: '' })
async function handleConfirm(close: () => void) {
const valid = await formRef.value?.validate()
if (valid) {
console.log(formData)
close()
}
}
</script>
<template>
<u-dialog v-model="visible" title="新建">
<u-form ref="form" :model="formData">
<u-input label="名称" field="name" :rules="{ required: true }" />
</u-form>
<template #footer="{ close }">
<u-button text @click="close()">取消</u-button>
<u-button type="primary" @click="handleConfirm(close)">确认</u-button>
</template>
</u-dialog>
</template>import type { ComponentSize, DeconstructValue } from '@veltra/utils'
/** 对话框过渡动画名称 */
export type DialogTransition = 'fade-scale'
/** 对话框组件属性 */
export interface DialogProps {
/** 显示或隐藏 */
modelValue?: boolean
/** 弹框标题,header的别名 */
title?: string
/** 弹框头部内容,别名是header */
header?: string
/** 大小尺寸 */
size?: ComponentSize
/** 显示模态层 */
modal?: boolean
/** 全屏 */
fullscreen?: boolean
/** 弹框过渡动画,默认为 spring */
transition?: DialogTransition
}
/** 对话框组件定义的事件 */
export interface DialogEmits {
/** 更新对话框的显示 */
(e: 'update:modelValue', visible: boolean): void
/** 对话框完全关闭后触发的事件 */
(e: 'closed'): void
}
/** 对话框组件暴露的属性和方法(组件内部使用) */
export interface _DialogExposed {
/** 关闭对话框 */
close: () => void
}
/** 对话框组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type DialogExposed = DeconstructValue<_DialogExposed>
UDrawer - 抽屉
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UDrawer 示例
基础用法 — 右侧抽屉
<script setup lang="ts">
import { ref } from 'vue'
const visible = ref(false)
</script>
<template>
<u-button @click="visible = true">打开抽屉</u-button>
<u-drawer v-model="visible" title="用户详情">
<p>这里是抽屉的主体内容。</p>
</u-drawer>
</template>左侧导航抽屉
<script setup lang="ts">
import { ref } from 'vue'
const menuVisible = ref(false)
</script>
<template>
<u-button @click="menuVisible = true">菜单</u-button>
<u-drawer v-model="menuVisible" direction="left" show-close>
<nav>
<ul>
<li>首页</li>
<li>关于</li>
<li>联系</li>
</ul>
</nav>
</u-drawer>
</template>底部抽屉 + 监听事件
<script setup lang="ts">
import { ref } from 'vue'
import type { DrawerEmits } from '@veltra/desktop'
const pickerVisible = ref(false)
const onClose: DrawerEmits['close'] = () => {
console.log('抽屉开始关闭')
}
const onClosed: DrawerEmits['closed'] = () => {
console.log('抽屉已完全关闭')
}
</script>
<template>
<u-button @click="pickerVisible = true">选择</u-button>
<u-drawer
v-model="pickerVisible"
direction="bottom"
show-close
@close="onClose"
@closed="onClosed"
>
<div class="picker-content">
<p>选项 A</p>
<p>选项 B</p>
<p>选项 C</p>
</div>
</u-drawer>
</template>顶部提示抽屉
<script setup lang="ts">
import { ref } from 'vue'
const noticeVisible = ref(false)
</script>
<template>
<u-button type="text" @click="noticeVisible = true">🔔 查看通知</u-button>
<u-drawer v-model="noticeVisible" direction="top">
<div class="notice">
<h3>系统通知</h3>
<p>您有 3 条新消息。</p>
</div>
</u-drawer>
</template>import type { DeconstructValue } from '@veltra/utils'
/** 抽屉方向 */
export type DrawerDirection = 'left' | 'right' | 'top' | 'bottom'
/** 抽屉模式 */
export type DrawerMode = 'edge' | 'inset'
/** 抽屉组件属性 */
export interface DrawerProps {
/** 是否显示抽屉 */
modelValue?: boolean
/** 抽屉方向 */
direction?: DrawerDirection
/** 是否显示关闭按钮 */
showClose?: boolean
/** 抽屉标题 */
title?: string
}
/** 抽屉组件定义的事件 */
export interface DrawerEmits {
(e: 'update:modelValue', value: boolean): void
/** 关闭时触发 */
(e: 'close'): void
/** 完全关闭后触发 */
(e: 'closed'): void
}
/** 抽屉组件暴露的属性和方法(组件内部使用) */
export interface _DrawerExposed {}
/** 抽屉组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type DrawerExposed = DeconstructValue<_DrawerExposed>
UDropdown - 下拉菜单
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UDropdown 示例
悬浮触发
<template>
<UDropdown>
<template #trigger>
<UButton>悬浮打开</UButton>
</template>
<template #content>
<div style="padding: 8px 12px">菜单内容</div>
</template>
</UDropdown>
</template>点击触发
<template>
<UDropdown trigger="click">
<template #trigger>
<UButton>点击打开</UButton>
</template>
<template #content>
<div style="padding: 8px 12px">点击触发的菜单</div>
</template>
</UDropdown>
</template>受控模式 + 键盘事件
<template>
<UDropdown
trigger="click"
:visible="visible"
@update:visible="visible = $event"
@keydown="handleKeydown"
>
<template #trigger>
<UButton>受控下拉</UButton>
</template>
<template #content>
<div style="padding: 8px 12px">按 Esc 关闭</div>
</template>
</UDropdown>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const visible = ref(false)
function handleKeydown(e: KeyboardEvent) {
if (e.key === 'Escape') {
visible.value = false
}
}
</script>禁用状态
<template>
<UDropdown disabled>
<template #trigger>
<UButton disabled>禁用状态</UButton>
</template>
<template #content>
<div style="padding: 8px 12px">不会弹出</div>
</template>
</UDropdown>
</template>import type { DeconstructValue } from '@veltra/utils'
import type { CSSProperties } from 'vue'
/** 下拉框组件属性 */
export interface DropdownProps {
/**
* 触发方式
* @default 'hover'
*/
trigger?: 'hover' | 'click' | 'custom'
/**
* 宽度
* @default - 跟随触发宽度
*/
width?: string
/**
* 最小宽度
*/
minWidth?: string
/**
* 内容容器标签
*/
contentTag?: string
/** 内容容器类 */
contentClass?: unknown
/** 内容容器样式 */
contentStyle?: CSSProperties | string
/** 显示下拉框 */
visible?: boolean
/** 禁用 */
disabled?: boolean
}
/** 下拉框组件定义的事件 */
export interface DropdownEmits {
/** 下拉框显示或隐藏事件 */
(e: 'update:visible', visible: boolean): void
/** 键盘事件 */
(e: 'keydown', event: KeyboardEvent): void
}
/** 下拉框组件暴露的属性和方法(组件内部使用) */
export interface _DropdownExposed {
/**
* 打开下拉擦菜单
* @param config 配置
*/
open: (config?: {
/** 自定义触发元素 */
trigger?: HTMLElement
}) => void
/** 关闭 */
close: () => void
/** 更新下拉框位置 */
updateDropdown: () => void
}
/** 下拉框组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type DropdownExposed = DeconstructValue<_DropdownExposed>
UDualNav - 双栏导航
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UDualNav 示例
应用切换与子菜单
<script setup lang="ts">
import { shallowRef } from 'vue'
import { HouseFilled, SettingFilled, UserGroup } from '@veltra/icons/normal'
import type { NavItem } from '@veltra/desktop'
const currentPath = shallowRef('/apps/home')
const menus = shallowRef<NavItem[]>([
{ title: '工作台', icon: HouseFilled, path: '/apps/home' },
{
title: '业务中心',
icon: UserGroup,
path: '/apps/business',
children: [
{ title: '模块管理', path: '/apps/business/modules' },
{ title: '数据字典', path: '/apps/business/dict' }
]
},
{
title: '系统设置',
icon: SettingFilled,
path: '/apps/settings',
children: [{ title: '基础设置', path: '/apps/settings/basic' }]
}
])
</script>
<template>
<u-dual-nav
:menus="menus"
:current-path="currentPath"
@item-click="currentPath = $event.path"
/>
</template>无子菜单应用直接跳转
<script setup lang="ts">
import type { NavItem } from '@veltra/desktop'
const menus: NavItem[] = [
{
title: '文档中心',
path: '/docs',
children: [{ title: '快速开始', path: '/docs/start' }]
},
{ title: '帮助', path: '/help' }
]
</script>
<template>
<u-dual-nav :menus="menus" current-path="/docs/start" @item-click="console.log($event.path)" />
</template>import type { DeconstructValue } from '@veltra/utils'
import type { NavItem } from './nav'
/** 双栏导航根级应用项;`description` 在左轨 tooltip 与右栏顶部展示 */
export interface DualNavRootItem extends NavItem {
/** 子导航 */
children?: NavItem[]
}
/** 双栏导航组件属性 */
export interface DualNavProps {
/** 当前路径 */
currentPath?: string
/** 根级应用导航列表 */
menus?: DualNavRootItem[]
}
/** 双栏导航组件定义的事件 */
export interface DualNavEmits {
(e: 'item-click', item: NavItem): void
}
/** 双栏导航组件暴露的属性和方法(组件内部使用) */
export interface _DualNavExposed {}
/** 双栏导航组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type DualNavExposed = DeconstructValue<_DualNavExposed>
UEmpty - 空状态
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UEmpty 示例
基础用法
<u-empty />自定义文案与大小
<u-empty text="搜索无结果" :size="64" />条件渲染
<script setup lang="ts">
import { ref } from 'vue'
const list = ref<string[]>([])
</script>
<template>
<div v-if="list.length">
<!-- 列表内容 -->
</div>
<u-empty v-else text="暂无列表数据" />
</template>搭配其他组件
<div style="text-align: center">
<u-empty text="还没有订单" />
<u-button type="primary" style="margin-top: 12px">去下单</u-button>
</div>import type { DeconstructValue } from '@veltra/utils'
/** 空内容组件属性 */
export interface EmptyProps {
/** 图标大小, 默认48 */
size?: number
/** 空文本 */
text?: string
}
/** 空内容组件定义的事件 */
export interface EmptyEmits {}
/** 空内容组件暴露的属性和方法(组件内部使用) */
export interface _EmptyExposed {}
/** 空内容组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type EmptyExposed = DeconstructValue<_EmptyExposed>
UExpressionEditor - 表达式编辑器
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UExpressionEditor 示例
基础使用
<script setup lang="ts">
import { shallowRef } from 'vue'
import type { VariableItem } from '@veltra/desktop'
const expression = shallowRef('你好{form.user.name}')
const variables: VariableItem[] = [
{
label: '表单数据',
value: 'form',
children: [
{
label: '用户信息',
value: 'form.user',
children: [
{ label: '姓名', value: 'form.user.name', type: 'string' },
{ label: '年龄', value: 'form.user.age', type: 'number' }
]
}
]
}
]
</script>
<template>
<u-expression-editor v-model="expression" :variables="variables" />
</template>允许选择分支变量
<template>
<u-expression-editor v-model="expression" :variables="variables" selectable-levels="any" />
</template>禁用与只读
<template>
<u-expression-editor v-model="expression" :variables="variables" disabled />
<u-expression-editor v-model="expression" :variables="variables" readonly />
</template>平坦变量列表
<script setup lang="ts">
import { ref } from 'vue'
import type { VariableItem } from '@veltra/desktop'
const expression = ref('')
const variables: VariableItem[] = [
{ label: '当前用户', value: 'user.name', type: 'string' },
{ label: '当前日期', value: 'date.today', type: 'date' },
{ label: '订单金额', value: 'order.amount', type: 'number' },
{ label: '是否会员', value: 'user.vip', type: 'boolean' }
]
</script>
<template>
<u-expression-editor
v-model="expression"
:variables="variables"
placeholder="输入表达式,@ 可插入变量"
/>
</template>import type { DeconstructValue, FormComponentProps } from '@veltra/utils'
export interface VariableItem {
label: string
value: string
/** 可选类型标识(如 string、number) */
type?: string
/** 子级变量(支持树形结构) */
children?: VariableItem[]
}
/** 选中范围:仅叶子节点,或允许任意层级(含分支) */
export type ExpressionSelectableLevels = 'leaf' | 'any'
/** 表达式编辑器组件属性 */
export interface ExpressionEditorProps extends FormComponentProps {
modelValue?: string
placeholder?: string
/** 变量列表 */
variables?: VariableItem[]
/**
* 是否允许选中任意层级的变量(含中间分支)。
* - `'leaf'`(默认):仅叶子节点可选;分支节点上 Enter / → 进入下一级
* - `'any'`:分支节点上 Enter 选中分支本身、→ 进入下一级
*/
selectableLevels?: ExpressionSelectableLevels
}
/** 表达式编辑器组件定义的事件 */
export interface ExpressionEditorEmits {
(e: 'update:modelValue', value: string): void
}
/** 表达式编辑器组件暴露的属性和方法(组件内部使用) */
export interface _ExpressionEditorExposed {}
/** 表达式编辑器组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type ExpressionEditorExposed = DeconstructValue<_ExpressionEditorExposed>
UFilePicker - 文件选择器
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UFilePicker 示例
基础点击选择
<script setup>
const handlePick = (files: File[]) => {
console.log('选中文件:', files.map(f => f.name))
}
</script>
<template>
<u-file-picker @pick="handlePick">
<button>选择文件</button>
</u-file-picker>
</template>限制类型 & 多选
<script setup>
const handlePick = (files: File[]) => {
// 仅 .pdf 或 image 文件会进入 files
console.log(files)
}
</script>
<template>
<u-file-picker accept=".pdf,image/*" multiple @pick="handlePick">
<button>上传 PDF 或图片(可多选)</button>
</u-file-picker>
</template>拖拽上传 + 拖拽高亮
<script setup>
const handlePick = (files: File[]) => {
console.log('拖入文件:', files.map(f => f.name))
}
</script>
<template>
<u-file-picker @pick="handlePick" v-slot="{ isDragover }">
<div :class="['drop-zone', isDragover && 'drop-zone--active']">
{{ isDragover ? '松开即可上传' : '拖拽文件到此处,或点击选择' }}
</div>
</u-file-picker>
</template>
<style scoped>
.drop-zone {
padding: 40px;
border: 2px dashed #ccc;
text-align: center;
cursor: pointer;
}
.drop-zone--active {
border-color: #409eff;
background: rgba(64, 158, 255, 0.1);
}
</style>自定义渲染标签
<template>
<u-file-picker tag="span" accept="image/*" @pick="onPick">
<a href="javascript:void(0)">点击上传图片</a>
</u-file-picker>
</template>import type { DeconstructValue, FormComponentProps } from '@veltra/utils'
/** 文件上传器组件属性 */
export interface UploaderProps extends FormComponentProps {
/** 渲染标签 */
tag?: string
/** 允许上传的文件类型 */
accept?: string
/** 是否允许多选 */
multiple?: boolean
}
/** 文件上传器组件定义的事件 */
export interface UploaderEmits {
/** 拾取 */
(e: 'pick', files: File[]): void
}
/** 文件上传器组件暴露的属性和方法(组件内部使用) */
export interface _UploaderExposed {}
/** 文件上传器组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type UploaderExposed = DeconstructValue<_UploaderExposed>
UFileViewer - 文件查看器
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UFileViewer 示例
基础内嵌预览
<script setup lang="ts">
import { ref } from 'vue'
import type { FileViewerItem } from '@veltra/desktop'
const files = ref<FileViewerItem[]>([
{ name: 'report.pdf', src: '/files/report.pdf', size: 204800 },
{ name: 'screenshot.png', src: '/files/screenshot.png' },
{ name: 'data.xlsx', src: '/files/data.xlsx', size: 10240 }
])
</script>
<template>
<u-file-viewer v-model="activeId" :files="files" :sidebar-width="240" />
</template>全屏模态
<script setup lang="ts">
import { ref } from 'vue'
const open = ref(false)
const files = ref([{ name: 'photo.jpg', src: '/photos/photo.jpg', size: 512000 }])
</script>
<template>
<u-button @click="open = true">预览图片</u-button>
<u-file-viewer v-model:open="open" :files="files" :downloadable="false" />
</template>二进制数据预览
<script setup lang="ts">
import { ref } from 'vue'
const files = ref([
{
name: 'uploaded.csv',
src: new Blob(['a,b,c\n1,2,3'], { type: 'text/csv' }),
kind: 'sheet' as const,
size: 15
}
])
</script>
<template>
<u-file-viewer :files="files" :sidebar-width="false" :sheet-max-rows="0" />
</template>监听切换与错误
<script setup lang="ts">
import { ref } from 'vue'
import type { FileViewerItem } from '@veltra/desktop'
const activeId = ref<string>()
const files = ref<FileViewerItem[]>([
{ name: 'doc.docx', src: '/docs/doc.docx' },
{ name: 'broken.pdf', src: '/files/not-found.pdf' }
])
function onChange(file: FileViewerItem) {
console.log('切换到:', file.name)
}
function onError({ file, error }: { file: FileViewerItem; error: unknown }) {
console.error('预览失败:', file.name, error)
}
</script>
<template>
<u-file-viewer v-model="activeId" :files="files" @change="onChange" @error="onError" />
</template>import type { DeconstructValue } from '@veltra/utils'
import type { ShallowRef } from 'vue'
/** 预览器类别:xlsx 与 csv 归一为 sheet */
export type FileViewerKind = 'image' | 'video' | 'pdf' | 'sheet' | 'docx' | 'text'
/** 单个预览文件定义 */
export interface FileViewerItem {
/** 唯一 id;未提供时组件内部按索引生成 */
id?: string
/** 展示名(通常为原始文件名) */
name: string
/**
* 文件源:
* - string: URL(支持 http(s):、blob:、data:)
* - File / Blob / ArrayBuffer / Uint8Array: 二进制原始数据
*/
src: string | File | Blob | ArrayBuffer | Uint8Array
/** 类型;缺省时根据 name 后缀推断 */
kind?: FileViewerKind
/** MIME type,仅用于原生 <video> / <img> / 下载时的类型提示 */
mime?: string
/** 文件大小(字节),可选,仅用于侧栏展示 */
size?: number
}
/** 文件预览组件属性 */
export interface FileViewerProps {
/** 待预览的文件列表 */
files: FileViewerItem[]
/** 当前激活文件 id(配合 v-model) */
modelValue?: string
/** 侧栏宽度(CSS length 或 false 隐藏侧栏),默认 280px */
sidebarWidth?: string | number | false
/** sheet 场景单文件最大渲染行数,默认 50000;0 表示不截断 */
sheetMaxRows?: number
/** 是否显示下载按钮,默认 true */
downloadable?: boolean
/**
* 全屏模态模式开关(支持 v-model:open)。
*
* - `undefined`(缺省):内嵌模式,组件在原位置渲染
* - `true` / `false`:进入模态模式,Teleport 到 body,按本值控制显隐
*/
open?: boolean
/** 模态模式下点击背景是否关闭,默认 true */
closeOnClickBackdrop?: boolean
/** 模态模式下按 ESC 是否关闭,默认 true */
closeOnEsc?: boolean
}
/** 文件预览组件事件 */
export interface FileViewerEmits {
(e: 'update:modelValue', id: string): void
(e: 'update:open', value: boolean): void
(e: 'change', file: FileViewerItem): void
(e: 'error', err: { file: FileViewerItem; error: unknown }): void
}
/** 文件预览组件暴露的属性和方法(组件内部使用) */
export interface _FileViewerExposed {
activeId: ShallowRef<string | undefined>
activate: (id: string) => void
next: () => void
prev: () => void
}
/** 文件预览组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type FileViewerExposed = DeconstructValue<_FileViewerExposed>
UFloatButton - 浮动按钮
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UFloatButton 示例
基础用法
<script setup lang="ts">
import type { FloatButtonItem } from '@veltra/desktop'
const items: FloatButtonItem[] = [
{ key: 'add', name: '新增' },
{ key: 'edit', name: '编辑' },
{ key: 'delete', name: '删除' }
]
const onAction = (key: string) => {
console.log(key)
}
</script>
<template>
<u-float-button :items="items" @click="onAction" />
</template>带图标与不同颜色
<script setup lang="ts">
import { AddIcon, EditIcon, DeleteIcon } from '@veltra/icons'
const items = [
{ key: 'add', icon: AddIcon, name: '新增' },
{ key: 'edit', icon: EditIcon, name: '编辑', type: 'info' },
{ key: 'delete', icon: DeleteIcon, name: '删除', type: 'danger' }
]
</script>
<template>
<u-float-button :items="items" @click="(key) => console.log(key)" />
</template>纯图标(无 name)
<script setup lang="ts">
import { SettingIcon, NotificationIcon } from '@veltra/icons'
const items = [
{ key: 'settings', icon: SettingIcon },
{ key: 'notifications', icon: NotificationIcon }
]
</script>
<template>
<u-float-button size="small" :items="items" />
</template>结合路由跳转
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { CreateIcon, SearchIcon } from '@veltra/icons'
const router = useRouter()
const items = [
{ key: '/create', icon: CreateIcon, name: '新建' },
{ key: '/search', icon: SearchIcon, name: '搜索' }
]
const handleClick = (key: string) => {
router.push(key)
}
</script>
<template>
<u-float-button :items="items" @click="handleClick" />
</template>import type { ComponentProps, DeconstructValue } from '@veltra/utils'
import type { Component } from 'vue'
import type { ButtonType } from './button'
export interface FloatButtonItem {
/** 一个图标 */
icon?: Component
/** 名称 */
name?: string
/** 按钮颜色类别 */
type?: ButtonType
/** 标识,用来确定唯一性 */
key: string
}
/** 悬浮按钮组件属性 */
export interface FloatButtonProps extends ComponentProps {
/** 操作项 */
items?: FloatButtonItem[]
}
/** 悬浮按钮组件定义的事件 */
export interface FloatButtonEmits {
(e: 'click', key: string): void
}
/** 悬浮按钮组件暴露的属性和方法(组件内部使用) */
export interface _FloatButtonExposed {}
/** 悬浮按钮组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type FloatButtonExposed = DeconstructValue<_FloatButtonExposed>
UFormItem - 表单项
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UFormItem 示例
显式使用UFormItem时,需在内部控件上自行v-model绑定model对应路径;field与rules仍写在UFormItem上用于注册校验。
多组件组合
<script setup lang="ts">
import { reactive } from 'vue'
const formData = reactive({ dateRange: { startDate: '', endDate: '' } })
</script>
<template>
<u-form :model="formData">
<u-form-item label="日期范围" field="dateRange">
<u-date-picker v-model="formData.dateRange.startDate" />
<span> 至 </span>
<u-date-picker v-model="formData.dateRange.endDate" />
</u-form-item>
</u-form>
</template>自定义 label
<script setup lang="ts">
import { reactive } from 'vue'
const formData = reactive({ agree: false })
</script>
<template>
<u-form :model="formData">
<u-form-item field="agree">
<template #label>
<span>我已阅读并同意 <a href="/terms">条款</a>:</span>
</template>
<u-checkbox v-model="formData.agree" />
</u-form-item>
</u-form>
</template>覆盖标签宽度与添加提示
<script setup lang="ts">
import { reactive } from 'vue'
const formData = reactive({ short: '', long: '' })
</script>
<template>
<u-form :model="formData">
<u-form-item label="短标签" field="short" :label-width="120" tips="这里是说明文字">
<u-input v-model="formData.short" />
</u-form-item>
<u-form-item label="长标签" field="long" :label-width="200">
<u-input v-model="formData.long" />
</u-form-item>
</u-form>
</template>响应式栅格布局
<script setup lang="ts">
import { reactive } from 'vue'
const formData = reactive({ name: '', age: 18 })
</script>
<template>
<u-form :model="formData">
<!-- 默认占满行,md+ 占 6 列 -->
<u-form-item label="姓名" field="name" :span="{ default: 'full', md: 6 }">
<u-input v-model="formData.name" />
</u-form-item>
<!-- 默认占满行,md+ 占 6 列 -->
<u-form-item label="年龄" field="age" :span="{ default: 'full', md: 6 }">
<u-number-input v-model="formData.age" />
</u-form-item>
</u-form>
</template>import type { FormComponentProps } from '@veltra/utils'
/** 组件项组件属性 */
export interface FormItemProps extends FormComponentProps {
/** 标签宽度 */
labelWidth?: string | number
}
/** 组件项组件定义的事件 */
export interface FormItemEmits {}
/** 组件项组件暴露的属性和方法 */
export interface FormItemExposed {}
UForm - 表单
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UForm 示例
UForm会拦截默认插槽中带field的子组件,自动生成UFormItem并绑定model对应路径的值。校验规则通过控件或UFormItem的rules属性声明;调用formRef.validate()触发全部字段校验,或传入keys仅校验指定字段。校验失败时会自动滚动到第一个错误项。字段值变化时会自动重新校验(reset()期间会抑制)。
>
下方示例涵盖常用表单控件及验证、按字段校验、清除校验与重置。
基础 + 校验
<script setup lang="ts">
import { reactive, useTemplateRef } from 'vue'
const formRef = useTemplateRef('form')
const formData = reactive({ username: '', email: '', age: 18, customField: '' })
async function handleSubmit() {
const valid = await formRef.value?.validate()
if (valid) console.log('提交:', formData)
}
</script>
<template>
<u-form ref="form" :model="formData" label-width="100px" :cols="1">
<u-input
label="用户名"
field="username"
:rules="{
required: '用户名不能为空',
minLen: [2, '至少 2 个字符'],
maxLen: [20, '最多 20 个字符']
}"
/>
<u-input label="邮箱" field="email" :rules="{ required: true, preset: 'email' }" />
<u-number-input label="年龄" field="age" :rules="{ min: 0, max: 150 }" />
<u-input
label="自定义"
field="customField"
:rules="{ validator: async (val) => (val === 'admin' ? '该值已被占用' : undefined) }"
/>
</u-form>
<u-button type="primary" @click="handleSubmit">提交</u-button>
</template>嵌套字段
<script setup lang="ts">
import { reactive } from 'vue'
const formData = reactive({ name: '', contact: { email: '', phone: '' } })
</script>
<template>
<u-form :model="formData" label-width="100px" :cols="1">
<u-input label="姓名" field="name" :rules="{ required: true }" />
<u-input label="邮箱" field="contact.email" :rules="{ required: true, preset: 'email' }" />
<u-input label="电话" field="contact.phone" :rules="{ required: true }" />
</u-form>
</template>按字段校验
<script setup lang="ts">
import { reactive, useTemplateRef } from 'vue'
const formRef = useTemplateRef('form')
const formData = reactive({ username: '', email: '' })
async function handleSubmit() {
// 校验全部
const valid = await formRef.value?.validate()
if (valid) console.log('提交:', formData)
}
async function saveDraft() {
const valid = await formRef.value?.validate(['username'])
if (valid) console.log('存草稿:', formData)
}
</script>
<template>
<u-form ref="form" :model="formData" :cols="1">
<u-input label="用户名" field="username" :rules="{ required: true }" />
<u-input label="邮箱" field="email" :rules="{ required: true, preset: 'email' }" />
</u-form>
<u-button type="primary" @click="saveDraft">存草稿</u-button>
<u-button type="primary" @click="handleSubmit">提交</u-button>
</template>清除校验和重置
<script setup lang="ts">
import { reactive, useTemplateRef } from 'vue'
const formRef = useTemplateRef('form')
const formData = reactive({ name: '' })
</script>
<template>
<u-form ref="form" :model="formData" :cols="1">
<u-input label="姓名" field="name" :rules="{ required: true }" />
</u-form>
<u-button @click="formRef.value?.clearValidate()">清除校验</u-button>
<u-button @click="formRef.value?.reset()">重置</u-button>
</template>import type { ComponentProps, DeconstructValue } from '@veltra/utils'
import type { ShallowRef } from 'vue'
/** 表单组件属性 */
export interface FormProps extends ComponentProps {
/**
* 自定义表单列数
* - 默认根据尺寸断点自动排列
*/
cols?: number
/** 表单数据 */
model?: Record<string, any>
// showModified?: boolean
/** 表单项label宽度 */
labelWidth?: string | number
/** 是否不显示tips */
noTips?: boolean
/** 是否只读 */
readonly?: boolean
/** 是否禁用 */
disabled?: boolean
}
export interface FormEmits {
(e: 'field:change', field: string, value: any): void
}
export interface _FormExposed {
el: ShallowRef<HTMLElement | null | undefined>
validate: (keys?: string[]) => Promise<boolean>
clearValidate: () => void
/** 将 model 恢复为最近一次 props.model 引用变更时的快照,并清除校验 */
reset: () => void
}
export type FormExposed = DeconstructValue<_FormExposed>
UGanttChart - 甘特图
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UGanttChart 示例
基础用法
<script setup lang="ts">
import { ref } from 'vue'
const selected = ref<string>()
</script>
<template>
<u-gantt-chart v-model="selected" />
</template>监听选中变化
<script setup lang="ts">
import { ref } from 'vue'
const taskId = ref<string>()
const onSelect = (id: string) => {
console.log('选中任务:', id)
}
</script>
<template>
<u-gantt-chart v-model="taskId" @update:model-value="onSelect" />
</template>传入初始值
<script setup lang="ts">
import { ref } from 'vue'
const currentId = ref('task-001')
</script>
<template>
<u-gantt-chart v-model="currentId" />
</template>/** 甘特图组件属性 */
export interface GanttChartProps {
modelValue?: string
}
/** 甘特图组件定义的事件 */
export interface GanttChartEmits {
(e: 'update:modelValue', value: string): void
}
/** 甘特图组件暴露的属性和方法(组件内部使用) */
export interface _GanttChartExposed {}
/** 甘特图组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export interface GanttChartExposed {}
UGridInput - 网格输入框
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UGridInput 示例
基础用法
<script setup lang="ts">
import { ref } from 'vue'
const code = ref('')
</script>
<template>
<u-grid-input v-model="code" />
</template>自定义长度与分隔符
<script setup lang="ts">
import { ref } from 'vue'
const pin = ref('')
const onInput = (val: string) => {
console.log('当前输入:', val)
}
</script>
<template>
<u-grid-input v-model="pin" :length="4" separator=" " @input="onInput" />
</template>允许输入零
<script setup lang="ts">
import { ref } from 'vue'
const code = ref('')
</script>
<template>
<u-grid-input v-model="code" :length="6" :zero="true" />
</template>调用 clear 清空
<script setup lang="ts">
import { useTemplateRef } from 'vue'
const inputRef = useTemplateRef('input')
const handleClear = () => {
inputRef.value?.clear()
}
</script>
<template>
<u-grid-input ref="input" />
<u-button @click="handleClear">清空</u-button>
</template>UGrid - 栅格布局
类型文件
见 ./types.d.ts
示例
见 ./examples.md
UGrid 示例
固定列数
<u-grid :cols="24" :gap="16">
<u-grid-item :span="12"><div style="background: var(--u-color-primary-light); padding: 16px">span 12</div></u-grid-item>
<u-grid-item :span="12"><div style="background: var(--u-color-primary-light); padding: 16px">span 12</div></u-grid-item>
<u-grid-item :span="8"><div style="background: var(--u-color-primary-light); padding: 16px">span 8</div></u-grid-item>
<u-grid-item :span="8"><div style="background: var(--u-color-primary-light); padding: 16px">span 8</div></u-grid-item>
<u-grid-item :span="8"><div style="background: var(--u-color-primary-light); padding: 16px">span 8</div></u-grid-item>
</u-grid>响应式断点列数 + 响应式跨距
<u-grid :cols="{ xs: 4, sm: 8, md: 12, lg: 24 }" :gap="12">
<u-grid-item :span="{ xs: 4, sm: 6, md: 8, lg: 6, default: 24 }">
<div style="background: var(--u-color-primary-light); padding: 12px">响应式跨距</div>
</u-grid-item>
<u-grid-item :span="{ xs: 2, sm: 4, md: 6, lg: 6, default: 12 }">
<div style="background: var(--u-color-primary-light); padding: 12px">另一列</div>
</u-grid-item>
</u-grid>函数动态列数 + 满行跨距
<u-grid :cols="(bp) => (bp.level < 3 ? 12 : 24)" :gap="16">
<u-grid-item span="full">
<div style="background: var(--u-color-primary-light); padding: 16px">整行标题</div>
</u-grid-item>
<u-grid-item :span="8"><div style="background: var(--u-color-primary-light); padding: 16px">A</div></u-grid-item>
<u-grid-item :span="8"><div style="background: var(--u-color-primary-light); padding: 16px">B</div></u-grid-item>
<u-grid-item :span="8"><div style="background: var(--u-color-primary-light); padding: 16px">C</div></u-grid-item>
</u-grid>监听断点变化
<template>
<u-grid :cols="{ xs: 6, md: 12, lg: 24 }" @breakpoint-change="onBreakpointChange">
<u-grid-item :span="{ xs: 6, md: 6, default: 12 }">
<div style="background: var(--u-color-primary-light); padding: 12px">
当前断点: {{ bp?.name }}({{ bp?.level }})
</div>
</u-grid-item>
</u-grid>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { Breakpoint } from '@veltra/desktop'
const bp = ref<Breakpoint>()
function onBreakpointChange(b: Breakpoint) {
bp.value = b
}
</script>import type { BreakpointName, DeconstructValue } from '@veltra/utils'
import type { ShallowRef } from 'vue'
export interface Breakpoint {
name: BreakpointName
level: number
}
/** 断点列 */
export interface BreakCols {
/** 超小尺寸 */
xs?: number
/** 小尺寸 */
sm?: number
/** 中等尺寸 */
md?: number
/** 大尺寸 */
lg?: number
/** 中大尺寸 */
xl?: number
/** 默认尺寸 */
default?: number
}
/** 网格布局组件属性 */
export interface GridProps {
/**
* 栅格列数, 可传入数字,对象或者函数
* @default 24
* @example
* ```ts
* // 数字
* const cols = 12
* // 对象
* const cols = {
* xs: 12,
* sm: 12,
* md: 12,
* lg: 24,
* xl: 24
* }
* // 函数
* const cols = (size: 'xs' | 'sm' | 'md' | 'lg' | 'xl', sizeLevel: number) => {
* if (sizeLevel < 3) return 12
* return 24
* }
* ```
*/
cols?: number | BreakCols | ((breakpoint: Breakpoint) => number)
/** 渲染标签 */
tag?: string
/** 间隔, 为字符串时可以同时指定行间隔和列间隔 */
gap?: number | string
}
/**
* 网格布局项组件事件
*/
export interface GridEmits {
/** 尺寸变更 */
(e: 'resize', rect: DOMRect): void
/** 断点变更 */
(e: 'breakpoint-change', breakpoint: Breakpoint): void
}
/** 网格布局项组件属性 */
export interface GridItemProps {
/** 跨距,当指定为0时,则代表隐藏, 默认为1 */
span?:
| number
| 'full'
| ({
[key in BreakpointName]?: 'full' | number
} & { default: number | 'full' })
/** 容器标签 */
tag?: string
}
/** 网格组件暴露的属性和方法(组件内部使用) */
export interface _GridExposed {
el: ShallowRef<HTMLElement | null>
}
/** 网格组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
export type GridExposed = DeconstructValue<_GridExposed>
Related skills
How it compares
Design-system integration playbook for Vue—not a standalone component npm you install blindly without repo docs.
FAQ
Who is veltra-ui for?
Vue 3 developers and agent users on projects that already depend on veltra-ui who want retrieval-guided use of shared components and tokens.
When should I use veltra-ui?
During Build frontend for any Vue screen; during Validate prototype when mocking UI with real system components; during Ship review when checking that new views used documented primitives instead of duplicates.
Is veltra-ui safe to install?
Review the Security Audits panel on this Prism page; the skill only guides local doc usage but your agent still edits project files—treat that like any codegen skill.