chore: 升级 shadcn-vue 组件到v4最新版 (#7972)

* fix: useStore is deprecated

* chore: update deps

* feat: 升级shadcn-ui v4

* fix: workbench style

* feat: 升级shadcn-ui v4 step2

* feat: 升级shadcn-ui v4 step3

* chore: 升级shadcn v4

* fix: pagination

* fix: dark style

* fix: doc import

* feat: 增加详情组件,参考 antdv-next

* docs: descriptions docs

* docs: Browser Support

* feat: add table action

* feat: icon use vbenIcon

* fix: type error

* fix: dropdown popConfirm

* feat: 使用默认的文字交互

* feat: 优化渲染性能
This commit is contained in:
xingyu
2026-05-31 15:18:46 +08:00
committed by GitHub
parent 108d7ff335
commit 04fbb7a556
239 changed files with 6015 additions and 3205 deletions

View File

@@ -5,7 +5,7 @@ import { computed, ref, useSlots } from 'vue';
import { VbenTooltip } from '@vben-core/shadcn-ui';
import { Code } from 'lucide-vue-next';
import { Code } from '@lucide/vue';
import {
TabsContent,
TabsIndicator,

View File

@@ -198,6 +198,14 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
link: 'common-ui/vben-ellipsis-text',
text: 'EllipsisText',
},
{
link: 'common-ui/vben-descriptions',
text: 'Descriptions',
},
{
link: 'common-ui/vben-table-action',
text: 'TableAction',
},
{
link: 'common-ui/vben-cropper',
text: 'Cropper',

View File

@@ -196,6 +196,14 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
link: 'common-ui/vben-ellipsis-text',
text: 'EllipsisText 省略文本',
},
{
link: 'common-ui/vben-descriptions',
text: 'Descriptions 描述列表',
},
{
link: 'common-ui/vben-table-action',
text: 'TableAction 表格操作',
},
{
link: 'common-ui/vben-cropper',
text: 'Cropper 图片裁剪',

View File

@@ -15,13 +15,13 @@
}
},
"dependencies": {
"@lucide/vue": "catalog:",
"@vben-core/shadcn-ui": "workspace:*",
"@vben/common-ui": "workspace:*",
"@vben/locales": "workspace:*",
"@vben/plugins": "workspace:*",
"@vben/styles": "workspace:*",
"antdv-next": "catalog:",
"lucide-vue-next": "catalog:",
"medium-zoom": "catalog:",
"reka-ui": "catalog:",
"vitepress-plugin-group-icons": "catalog:"

View File

@@ -0,0 +1,102 @@
---
outline: deep
---
# Vben Descriptions 描述列表
`Descriptions` 用于成组展示只读的字段信息,常用于详情页、信息预览等场景。组件基于 shadcn-ui 构建API 参考 Ant Design Vue 的 Descriptions支持响应式列数、跨列、边框、垂直布局等能力。
> 如果文档内没有覆盖到你需要的细节,可以结合在线示例一起查看。
::: info 写在前面
组件提供两种使用方式:通过 `items` 数据驱动(推荐),或通过子组件 `VbenDescriptionsItem` 声明列表项。两者可按需选择,`items` 优先级更高。:::
## 基础用法
通过 `items` 传入字段数组,每项包含 `label``content`。默认按断点自适应列数(`xs` 1 列、`sm` 2 列、`md` 及以上 3 列)。
<DemoPreview dir="demos/vben-descriptions/basic" />
## 带边框
设置 `bordered` 展示边框样式,配合 `title` 标题与 `#extra` 插槽(位于标题右侧的操作区域)。
<DemoPreview dir="demos/vben-descriptions/bordered" />
## 垂直布局
通过 `layout="vertical"` 让标签位于内容上方。
<DemoPreview dir="demos/vben-descriptions/vertical" />
## 不同尺寸
通过 `size` 设置 `small``middle``large` 三种尺寸。
<DemoPreview dir="demos/vben-descriptions/size" />
## 跨列与响应式
单项通过 `span` 设置跨列数,`'filled'` 表示占满当前行剩余空间;`column` 支持传入按断点配置的对象实现响应式列数。
<DemoPreview dir="demos/vben-descriptions/span" />
## 子组件用法
不传 `items` 时,可在默认插槽中使用 `VbenDescriptionsItem` 声明列表项,内容支持默认插槽或 `#content` 插槽自定义。
<DemoPreview dir="demos/vben-descriptions/custom" />
## API
### Descriptions Props
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| items | 数据驱动的列表项;不传则读取默认插槽 | `DescriptionsItemType[]` | - |
| bordered | 是否展示边框 | `boolean` | `false` |
| column | 一行的列数,支持按断点配置 | `number \| Partial<Record<Breakpoint, number>>` | `{ xs: 1, sm: 2, md: 3, xxxl: 4 }` |
| layout | 布局方式 | `'horizontal' \| 'vertical'` | `'horizontal'` |
| size | 尺寸 | `'small' \| 'middle' \| 'large'` | `'middle'` |
| colon | 是否显示冒号(仅非边框的水平布局生效) | `boolean` | `true` |
| title | 标题 | `string` | - |
| extra | 标题右侧的操作区域 | `string` | - |
| labelStyle | 统一的标签样式 | `CSSProperties` | - |
| contentStyle | 统一的内容样式 | `CSSProperties` | - |
| class | 根节点自定义类名 | `string` | - |
### Descriptions Slots
| 插槽名 | 描述 |
| ------- | ---------------------------------- |
| title | 自定义标题 |
| extra | 自定义标题右侧操作区域 |
| default | 放置 `VbenDescriptionsItem` 子组件 |
### DescriptionsItem
`items` 数组中的每一项,或子组件 `VbenDescriptionsItem` 的属性。
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| label | 标签 | `string \| number \| (() => VNode) \| Component` | - |
| content | 内容 | `string \| number \| (() => VNode) \| Component` | - |
| span | 跨列数,`'filled'` 占满当前行剩余 | `number \| 'filled' \| Partial<Record<Breakpoint, number>>` | `1` |
| labelStyle | 标签样式 | `CSSProperties` | - |
| contentStyle | 内容样式 | `CSSProperties` | - |
| key | 唯一标识 | `string \| number` | - |
### DescriptionsItem Slots
仅子组件用法可用。
| 插槽名 | 描述 |
| ------- | ------------------------ |
| default | 内容(等价于 `content` |
| content | 自定义内容 |
| label | 自定义标签 |
::: tip Breakpoint
响应式断点 `Breakpoint` 取值为 `'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'xxxl'`,断点像素与 Ant Design 一致(`sm` 576、`md` 768、`lg` 992、`xl` 1200、`xxl` 1600、`xxxl` 2000。:::

View File

@@ -0,0 +1,165 @@
---
outline: deep
---
# Vben TableAction 表格操作
`TableAction` 用于在表格操作列中渲染一组操作按钮,参考 vben2 的 TableAction 设计。基于 shadcn-ui 构建,支持权限控制、气泡确认、提示、下拉「更多」、分割线等能力,可在表格内外任意场景复用。
> 如果文档内没有覆盖到你需要的细节,可以结合在线示例一起查看。
::: info 写在前面
组件本身不依赖任何业务逻辑(不直接读取权限 store权限通过注入 `hasPermission` 实现,从而保持核心层零耦合、可跨框架复用。在 vxe-table 中推荐通过列插槽(`slots: { default: 'action' }`)在页面里渲染,不改变表格原有的渲染机制。:::
## 基础用法
通过 `actions` 传入操作项数组,每项包含 `text``onClick` 等;`danger` 标记危险操作,`divider` 显示按钮间分割线。
<DemoPreview dir="demos/vben-table-action/basic" />
## 提示
通过 `tooltip` 为操作项添加提示,支持字符串或 `{ content, side }` 配置。
<DemoPreview dir="demos/vben-table-action/tooltip" />
## 气泡确认
通过 `popConfirm` 开启点击前的气泡确认,常用于删除等危险操作。
<DemoPreview dir="demos/vben-table-action/popconfirm" />
## 更多下拉
通过 `dropdownActions` 将次要操作收纳到「更多」下拉中,`moreText` 可自定义按钮文案。
<DemoPreview dir="demos/vben-table-action/dropdown" />
## 权限控制
为操作项设置 `auth` 权限码,并注入 `hasPermission` 判断函数,无权限的操作会被隐藏。
<DemoPreview dir="demos/vben-table-action/permission" />
## 在 vxe-table 中使用
不改变 vxe-table 原有渲染方式,推荐在列配置中声明插槽,在页面通过插槽渲染。
::: tip 推荐:使用适配器封装的版本项目的 `#/adapter/vxe-table` 已对 `VbenTableAction` 做了二次封装,内部统一注入了 `hasPermission`(基于 `useAccess().hasAccessByCodes`)。因此从适配器引入时**无需再传入 `:has-permission`**,只需通过操作项的 `auth` 字段声明权限码即可。:::
```ts
// data.ts —— 列配置声明插槽
{
align: 'center',
field: 'operation',
fixed: 'right',
slots: { default: 'action' },
title: $t('system.user.operation'),
width: 180,
}
```
```vue
<!-- list.vue —— 从适配器引入权限自动注入无需传入 has-permission -->
<script setup lang="ts">
import { VbenTableAction } from '#/adapter/vxe-table';
</script>
<template>
<Grid>
<template #action="{ row }">
<template #action="{ row }">
<VbenTableAction
:actions="[
{
text: $t('common.detail'),
icon: 'lucide:eye',
onClick: () => onDetail(row),
},
{
text: $t('common.edit'),
icon: 'lucide:edit',
onClick: () => onEdit(row),
},
]"
:dropdown-actions="[
{
text: $t('common.delete'),
icon: 'lucide:trash-2',
danger: true,
onClick: () => onDelete(row),
auth: ['AC_100100'],
},
]"
align="center"
/>
</template>
</template>
</Grid>
</template>
```
若直接从 `@vben/common-ui` 引入核心组件(不经过适配器),组件不依赖任何业务逻辑,需自行注入 `hasPermission`
```vue
<script setup lang="ts">
import { useAccess } from '@vben/access';
import { VbenTableAction } from '@vben/common-ui';
const { hasAccessByCodes } = useAccess();
function hasPermission(auth?: string | string[]) {
if (!auth) return true;
return hasAccessByCodes(Array.isArray(auth) ? auth : [auth]);
}
</script>
<template>
<VbenTableAction
v-bind="useActions(row, onActionClick)"
:has-permission="hasPermission"
align="center"
/>
</template>
```
## API
### TableAction Props
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| actions | 主操作按钮 | `ActionItem[]` | `[]` |
| dropdownActions | 「更多」下拉中的操作 | `ActionItem[]` | `[]` |
| align | 对齐方式 | `'start' \| 'center' \| 'end'` | `'end'` |
| divider | 按钮之间是否显示分割线 | `boolean` | `false` |
| moreText | 「更多」按钮文案(提供时显示在图标右侧) | `string` | - |
| hasPermission | 权限判断函数,返回 `false` 则隐藏对应 `auth` 的操作(从 `#/adapter/vxe-table` 引入时已自动注入,无需手动传入) | `(auth?: string \| string[]) => boolean` | - |
| class | 根节点自定义类名 | `string` | - |
### ActionItem
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| text | 按钮文本 | `string` | - |
| icon | 图标组件 | `string`\| `VbenIcon` | - |
| onClick | 点击回调 | `() => void` | - |
| auth | 权限码,配合 `hasPermission` 过滤 | `string \| string[]` | - |
| ifShow | 是否显示 | `boolean \| (() => boolean)` | `true` |
| disabled | 是否禁用 | `boolean` | `false` |
| loading | 加载状态 | `boolean` | `false` |
| danger | 危险操作(红色文字) | `boolean` | `false` |
| tooltip | 提示 | `string \| { content: string; side?: 'top' \| 'bottom' \| 'left' \| 'right' }` | - |
| popConfirm | 气泡确认 | `TableActionPopConfirm` | - |
| variant | 按钮样式变体 | `ButtonVariants['variant']` | `'link'` |
| size | 按钮尺寸 | `ButtonVariants['size']` | `'sm'` |
| key | 唯一标识 | `string \| number` | - |
### TableActionPopConfirm
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| title | 提示标题 | `string` | `'Are you sure?'` |
| okText | 确认按钮文案 | `string` | `'OK'` |
| cancelText | 取消按钮文案 | `string` | `'Cancel'` |
| confirm | 确认回调;未提供时回退到 `action.onClick` | `() => void` | - |

View File

@@ -3,8 +3,8 @@ import { h } from 'vue';
import { alert, prompt, useAlertContext, VbenButton } from '@vben/common-ui';
import { BadgeJapaneseYen } from '@lucide/vue';
import { Input, RadioGroup, Select } from 'antdv-next';
import { BadgeJapaneseYen } from 'lucide-vue-next';
function showPrompt() {
prompt({

View File

@@ -0,0 +1,18 @@
<script lang="ts" setup>
import { VbenDescriptions } from '@vben/common-ui';
const items = [
{ content: 'Vben', label: '用户名' },
{ content: '13800138000', label: '手机号' },
{ content: '中国 · 杭州', label: '居住地' },
{ content: '前端工程师', label: '职位' },
{
content: '这是一段较长的备注信息,用于演示跨列展示。',
label: '备注',
span: 3,
},
];
</script>
<template>
<VbenDescriptions :items="items" />
</template>

View File

@@ -0,0 +1,22 @@
<script lang="ts" setup>
import { VbenDescriptions } from '@vben/common-ui';
const items = [
{ content: 'Vben', label: '用户名' },
{ content: '13800138000', label: '手机号' },
{ content: '正常', label: '状态' },
{ content: '中国 · 杭州', label: '居住地' },
{
content: '浙江省杭州市西湖区某某街道某某小区 1 幢 2 单元',
label: '地址',
span: 3,
},
];
</script>
<template>
<VbenDescriptions bordered title="用户信息" :items="items">
<template #extra>
<span style="color: #1677ff; cursor: pointer">编辑</span>
</template>
</VbenDescriptions>
</template>

View File

@@ -0,0 +1,17 @@
<script lang="ts" setup>
import { VbenDescriptions, VbenDescriptionsItem } from '@vben/common-ui';
</script>
<template>
<!-- 通过子组件 VbenDescriptionsItem 声明列表项 -->
<VbenDescriptions bordered :column="2">
<VbenDescriptionsItem label="用户名">Vben</VbenDescriptionsItem>
<VbenDescriptionsItem label="状态">
<span style="color: #52c41a"> 正常</span>
</VbenDescriptionsItem>
<VbenDescriptionsItem label="备注" :span="2">
<template #content>
<span style="color: #888">通过 #content 插槽自定义内容</span>
</template>
</VbenDescriptionsItem>
</VbenDescriptions>
</template>

View File

@@ -0,0 +1,35 @@
<script lang="ts" setup>
import { VbenDescriptions } from '@vben/common-ui';
const items = [
{ content: 'Vben', label: '用户名' },
{ content: '13800138000', label: '手机号' },
{ content: '中国 · 杭州', label: '居住地' },
{ content: '前端工程师', label: '职位' },
];
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 16px">
<VbenDescriptions
size="small"
bordered
title="Small"
:column="2"
:items="items"
/>
<VbenDescriptions
size="middle"
bordered
title="Middle"
:column="2"
:items="items"
/>
<VbenDescriptions
size="large"
bordered
title="Large"
:column="2"
:items="items"
/>
</div>
</template>

View File

@@ -0,0 +1,15 @@
<script lang="ts" setup>
import { VbenDescriptions } from '@vben/common-ui';
const items = [
{ content: '1', label: 'A' },
{ content: '2span: 2', label: 'B', span: 2 },
{ content: '3', label: 'C' },
{ content: '占满当前行剩余空间', label: 'Dspan: filled', span: 'filled' },
{ content: '5', label: 'E' },
];
</script>
<template>
<!-- 列数随断点变化xs 1 sm 2 md 及以上 3 -->
<VbenDescriptions bordered :column="{ md: 3, sm: 2, xs: 1 }" :items="items" />
</template>

View File

@@ -0,0 +1,13 @@
<script lang="ts" setup>
import { VbenDescriptions } from '@vben/common-ui';
const items = [
{ content: 'Vben', label: '用户名' },
{ content: '13800138000', label: '手机号' },
{ content: '中国 · 杭州', label: '居住地' },
{ content: '这是一段较长的备注信息。', label: '备注', span: 3 },
];
</script>
<template>
<VbenDescriptions bordered layout="vertical" :items="items" />
</template>

View File

@@ -0,0 +1,28 @@
<script lang="ts" setup>
import type { ActionItem } from '@vben/common-ui';
import { ref } from 'vue';
import { VbenTableAction } from '@vben/common-ui';
const last = ref('无');
const actions: ActionItem[] = [
{ key: 'edit', onClick: () => (last.value = '编辑'), text: '编辑' },
{ key: 'detail', onClick: () => (last.value = '详情'), text: '详情' },
{
danger: true,
key: 'delete',
onClick: () => (last.value = '删除'),
text: '删除',
},
];
</script>
<template>
<div>
<VbenTableAction :actions="actions" align="start" divider />
<p style="margin-top: 8px; font-size: 13px; opacity: 0.7">
最近点击{{ last }}
</p>
</div>
</template>

View File

@@ -0,0 +1,44 @@
<script lang="ts" setup>
import type { ActionItem } from '@vben/common-ui';
import { ref } from 'vue';
import { VbenTableAction } from '@vben/common-ui';
const last = ref('无');
const actions: ActionItem[] = [
{ key: 'edit', onClick: () => (last.value = '编辑'), text: '编辑' },
];
const dropdownActions: ActionItem[] = [
{ key: 'copy', onClick: () => (last.value = '复制'), text: '复制' },
{ key: 'export', onClick: () => (last.value = '导出'), text: '导出' },
{
danger: true,
key: 'remove',
// 下拉项同样支持气泡确认
popConfirm: {
cancelText: '取消',
confirm: () => (last.value = '已移除'),
okText: '确认',
title: '确定移除吗?',
},
text: '移除',
},
];
</script>
<template>
<div>
<VbenTableAction
:actions="actions"
:dropdown-actions="dropdownActions"
align="start"
divider
more-text="更多"
/>
<p style="margin-top: 8px; font-size: 13px; opacity: 0.7">
最近点击{{ last }}
</p>
</div>
</template>

View File

@@ -0,0 +1,28 @@
<script lang="ts" setup>
import type { ActionItem } from '@vben/common-ui';
import { VbenTableAction } from '@vben/common-ui';
// 模拟当前用户拥有的权限码
const allow = new Set(['user:detail', 'user:edit']);
function hasPermission(auth?: string | string[]) {
if (!auth) return true;
const codes = Array.isArray(auth) ? auth : [auth];
return codes.some((code) => allow.has(code));
}
const actions: ActionItem[] = [
{ auth: 'user:edit', key: 'edit', text: '编辑' },
{ auth: 'user:detail', key: 'detail', text: '详情' },
// 无 user:delete 权限,按钮被隐藏
{ auth: 'user:delete', danger: true, key: 'delete', text: '删除(无权限)' },
];
</script>
<template>
<VbenTableAction
:actions="actions"
:has-permission="hasPermission"
align="start"
/>
</template>

View File

@@ -0,0 +1,32 @@
<script lang="ts" setup>
import type { ActionItem } from '@vben/common-ui';
import { ref } from 'vue';
import { VbenTableAction } from '@vben/common-ui';
const last = ref('无');
const actions: ActionItem[] = [
{ key: 'edit', onClick: () => (last.value = '编辑'), text: '编辑' },
{
danger: true,
key: 'delete',
popConfirm: {
cancelText: '取消',
confirm: () => (last.value = '已删除'),
okText: '确认',
title: '确定删除这一行吗?',
},
text: '删除',
},
];
</script>
<template>
<div>
<VbenTableAction :actions="actions" align="start" />
<p style="margin-top: 8px; font-size: 13px; opacity: 0.7">
最近操作{{ last }}
</p>
</div>
</template>

View File

@@ -0,0 +1,17 @@
<script lang="ts" setup>
import type { ActionItem } from '@vben/common-ui';
import { VbenTableAction } from '@vben/common-ui';
const actions: ActionItem[] = [
{ key: 'edit', text: '编辑', tooltip: '编辑这一行' },
{
key: 'detail',
text: '详情',
tooltip: { content: '查看详情', side: 'top' },
},
];
</script>
<template>
<VbenTableAction :actions="actions" align="start" />
</template>

View File

@@ -0,0 +1,102 @@
---
outline: deep
---
# Vben Descriptions
`Descriptions` displays a group of read-only fields, commonly used on detail pages and information previews. It is built on shadcn-ui with an API modeled after Ant Design Vue's Descriptions, supporting responsive columns, column spanning, borders, and vertical layout.
> If the documentation does not cover the details you need, please refer to the online examples.
::: info Before you start
The component supports two usages: data-driven via `items` (recommended), or declaring entries with the `VbenDescriptionsItem` child component. `items` takes precedence when both are provided. :::
## Basic Usage
Pass an array of fields via `items`, each with a `label` and `content`. Columns adapt to breakpoints by default (1 column on `xs`, 2 on `sm`, 3 on `md` and above).
<DemoPreview dir="demos/vben-descriptions/basic" />
## Bordered
Set `bordered` for a bordered style, combined with the `title` prop and the `#extra` slot (an action area on the right of the title).
<DemoPreview dir="demos/vben-descriptions/bordered" />
## Vertical Layout
Use `layout="vertical"` to place labels above their content.
<DemoPreview dir="demos/vben-descriptions/vertical" />
## Sizes
Use `size` to switch between `small`, `middle`, and `large`.
<DemoPreview dir="demos/vben-descriptions/size" />
## Span & Responsive
Set `span` on an item to span multiple columns; `'filled'` fills the remaining space of the current row. `column` accepts a breakpoint-keyed object for responsive columns.
<DemoPreview dir="demos/vben-descriptions/span" />
## Child Component Usage
When `items` is omitted, declare entries with `VbenDescriptionsItem` in the default slot. Content can be customized via the default slot or the `#content` slot.
<DemoPreview dir="demos/vben-descriptions/custom" />
## API
### Descriptions Props
| Prop | Description | Type | Default |
| --- | --- | --- | --- |
| items | Data-driven entries; reads the default slot when omitted | `DescriptionsItemType[]` | - |
| bordered | Whether to show borders | `boolean` | `false` |
| column | Columns per row, supports breakpoint config | `number \| Partial<Record<Breakpoint, number>>` | `{ xs: 1, sm: 2, md: 3, xxxl: 4 }` |
| layout | Layout direction | `'horizontal' \| 'vertical'` | `'horizontal'` |
| size | Size | `'small' \| 'middle' \| 'large'` | `'middle'` |
| colon | Show colon (only for non-bordered horizontal layout) | `boolean` | `true` |
| title | Title | `string` | - |
| extra | Action area on the right of the title | `string` | - |
| labelStyle | Shared label style | `CSSProperties` | - |
| contentStyle | Shared content style | `CSSProperties` | - |
| class | Custom class for the root node | `string` | - |
### Descriptions Slots
| Slot | Description |
| ------- | ------------------------------------- |
| title | Custom title |
| extra | Custom action area beside the title |
| default | Place `VbenDescriptionsItem` children |
### DescriptionsItem
Each entry in `items`, or the props of the `VbenDescriptionsItem` child component.
| Prop | Description | Type | Default |
| --- | --- | --- | --- |
| label | Label | `string \| number \| (() => VNode) \| Component` | - |
| content | Content | `string \| number \| (() => VNode) \| Component` | - |
| span | Columns to span, `'filled'` fills the rest of the row | `number \| 'filled' \| Partial<Record<Breakpoint, number>>` | `1` |
| labelStyle | Label style | `CSSProperties` | - |
| contentStyle | Content style | `CSSProperties` | - |
| key | Unique key | `string \| number` | - |
### DescriptionsItem Slots
Available only for the child component usage.
| Slot | Description |
| ------- | --------------------------------- |
| default | Content (equivalent to `content`) |
| content | Custom content |
| label | Custom label |
::: tip Breakpoint
The responsive `Breakpoint` is one of `'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'xxxl'`, with pixel values aligned with Ant Design (`sm` 576, `md` 768, `lg` 992, `xl` 1200, `xxl` 1600, `xxxl` 2000). :::

View File

@@ -0,0 +1,165 @@
---
outline: deep
---
# Vben TableAction
`TableAction` renders a group of action buttons for table operation columns, inspired by the TableAction component from vben2. Built on shadcn-ui, it supports permission control, popconfirm, tooltips, a "more" dropdown, and dividers, and can be reused inside or outside tables.
> If the documentation does not cover the details you need, please refer to the online examples.
::: info Before you start
The component carries no business logic (it does not read the permission store directly); permissions are handled by injecting `hasPermission`, keeping the core layer decoupled and reusable across frameworks. Inside vxe-table, the recommended approach is to render it via a column slot (`slots: { default: 'action' }`) on the page, without changing the table's original rendering mechanism. :::
## Basic Usage
Pass an array of action items via `actions`, each with `text`, `onClick`, etc. `danger` marks destructive actions, and `divider` shows separators between buttons.
<DemoPreview dir="demos/vben-table-action/basic" />
## Tooltip
Add a tooltip to an action via `tooltip`, accepting a string or a `{ content, side }` object.
<DemoPreview dir="demos/vben-table-action/tooltip" />
## PopConfirm
Use `popConfirm` to require confirmation before the action runs, commonly used for destructive actions like delete.
<DemoPreview dir="demos/vben-table-action/popconfirm" />
## More Dropdown
Use `dropdownActions` to collapse secondary actions into a "more" dropdown. `moreText` customizes the button label.
<DemoPreview dir="demos/vben-table-action/dropdown" />
## Permission Control
Set an `auth` code on an action and inject a `hasPermission` resolver; actions without permission are hidden.
<DemoPreview dir="demos/vben-table-action/permission" />
## Usage with vxe-table
Without changing vxe-table's rendering mechanism, declare a slot in the column config and render it on the page.
::: tip Recommended: use the adapter-wrapped version The project's `#/adapter/vxe-table` re-wraps `VbenTableAction` and injects `hasPermission` internally (based on `useAccess().hasAccessByCodes`). So when you import it from the adapter, **you no longer need to pass `:has-permission`** — just declare permission codes via the `auth` field of each action. :::
```ts
// data.ts — declare a slot in the column config
{
align: 'center',
field: 'operation',
fixed: 'right',
slots: { default: 'action' },
title: $t('system.user.operation'),
width: 180,
}
```
```vue
<!-- list.vue import from the adapter; permission is auto-injected, no has-permission needed -->
<script setup lang="ts">
import { VbenTableAction } from '#/adapter/vxe-table';
</script>
<template>
<Grid>
<template #action="{ row }">
<template #action="{ row }">
<VbenTableAction
:actions="[
{
text: $t('common.detail'),
icon: 'lucide:eye',
onClick: () => onDetail(row),
},
{
text: $t('common.edit'),
icon: 'lucide:edit',
onClick: () => onEdit(row),
},
]"
:dropdown-actions="[
{
text: $t('common.delete'),
icon: 'lucide:trash-2',
danger: true,
onClick: () => onDelete(row),
auth: ['AC_100100'],
},
]"
align="center"
/>
</template>
</template>
</Grid>
</template>
```
If you import the core component directly from `@vben/common-ui` (without going through the adapter), the component carries no business logic and you need to inject `hasPermission` yourself:
```vue
<script setup lang="ts">
import { useAccess } from '@vben/access';
import { VbenTableAction } from '@vben/common-ui';
const { hasAccessByCodes } = useAccess();
function hasPermission(auth?: string | string[]) {
if (!auth) return true;
return hasAccessByCodes(Array.isArray(auth) ? auth : [auth]);
}
</script>
<template>
<VbenTableAction
v-bind="useActions(row, onActionClick)"
:has-permission="hasPermission"
align="center"
/>
</template>
```
## API
### TableAction Props
| Prop | Description | Type | Default |
| --- | --- | --- | --- |
| actions | Main action buttons | `ActionItem[]` | `[]` |
| dropdownActions | Actions inside the "more" dropdown | `ActionItem[]` | `[]` |
| align | Alignment | `'start' \| 'center' \| 'end'` | `'end'` |
| divider | Whether to show separators between buttons | `boolean` | `false` |
| moreText | Label for the "more" button (shown beside the icon) | `string` | - |
| hasPermission | Permission resolver; returning `false` hides the action with that `auth` (auto-injected when imported from `#/adapter/vxe-table`, no need to pass manually) | `(auth?: string \| string[]) => boolean` | - |
| class | Custom class for the root node | `string` | - |
### ActionItem
| Prop | Description | Type | Default |
| --- | --- | --- | --- |
| text | Button text | `string` | - |
| icon | Icon component | `string` \| `VbenIcon` | - |
| onClick | Click callback | `() => void` | - |
| auth | Permission code, filtered by `hasPermission` | `string \| string[]` | - |
| ifShow | Whether to show | `boolean \| (() => boolean)` | `true` |
| disabled | Whether disabled | `boolean` | `false` |
| loading | Loading state | `boolean` | `false` |
| danger | Destructive action (red text) | `boolean` | `false` |
| tooltip | Tooltip | `string \| { content: string; side?: 'top' \| 'bottom' \| 'left' \| 'right' }` | - |
| popConfirm | PopConfirm | `TableActionPopConfirm` | - |
| variant | Button variant | `ButtonVariants['variant']` | `'link'` |
| size | Button size | `ButtonVariants['size']` | `'sm'` |
| key | Unique key | `string \| number` | - |
### TableActionPopConfirm
| Prop | Description | Type | Default |
| --- | --- | --- | --- |
| title | Confirm title | `string` | `'Are you sure?'` |
| okText | Confirm button text | `string` | `'OK'` |
| cancelText | Cancel button text | `string` | `'Cancel'` |
| confirm | Confirm callback; falls back to `action.onClick` if omitted | `() => void` | - |

View File

@@ -26,7 +26,7 @@
## Browser Support
- **Local development** is recommended using the **latest version of Chrome**. **Versions below Chrome 80 are not supported**.
- **Local development** is recommended using the **latest version of Chrome**. **Tailwind CSS v4.0 is designed for Safari 16.4+, Chrome 111+, and Firefox 128+**.
- **Production environment** supports modern browsers, IE is not supported.

View File

@@ -26,7 +26,7 @@
## 浏览器支持
- **本地开发**推荐使用`Chrome 最新版`浏览器,**不支持**`Chrome 80`以下版本
- **本地开发**推荐使用`Chrome 最新版`浏览器,**不支持**` Tailwind CSS v4.0 is designed for Safari 16.4+, Chrome 111+, and Firefox 128+
- **生产环境**支持现代浏览器,不支持 IE。