@@ -246,7 +246,7 @@ function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
diff --git a/apps/web-naive/src/views/demos/naive/array-form/index.vue b/apps/web-naive/src/views/demos/naive/array-form/index.vue
new file mode 100644
index 000000000..f08551e9b
--- /dev/null
+++ b/apps/web-naive/src/views/demos/naive/array-form/index.vue
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+ 设置表单值
+ 获取表单值
+
+ 提交校验
+
+
+
+
+
+
diff --git a/apps/web-tdesign/src/app.vue b/apps/web-tdesign/src/app.vue
index f2fa88408..01ce79db0 100644
--- a/apps/web-tdesign/src/app.vue
+++ b/apps/web-tdesign/src/app.vue
@@ -3,6 +3,7 @@ import type { GlobalConfigProvider } from 'tdesign-vue-next';
import { watch } from 'vue';
+import { useTDesignDesignTokens } from '@vben/hooks';
import { usePreferences } from '@vben/preferences';
import { merge } from 'es-toolkit/compat';
@@ -12,10 +13,16 @@ import zhConfig from 'tdesign-vue-next/es/locale/zh_CN';
defineOptions({ name: 'App' });
const { isDark } = usePreferences();
+// 将 Vben 设计系统的 CSS 变量适配到 TDesign 的设计变量上
+useTDesignDesignTokens();
+
watch(
() => isDark.value,
(dark) => {
- document.documentElement.setAttribute('theme-mode', dark ? 'dark' : '');
+ document.documentElement.setAttribute(
+ 'theme-mode',
+ dark ? 'dark' : 'light',
+ );
},
{ immediate: true },
);
diff --git a/apps/web-tdesign/src/bootstrap.ts b/apps/web-tdesign/src/bootstrap.ts
index 5d07717c6..ad7fef6ad 100644
--- a/apps/web-tdesign/src/bootstrap.ts
+++ b/apps/web-tdesign/src/bootstrap.ts
@@ -5,8 +5,6 @@ import { registerLoadingDirective } from '@vben/common-ui/es/loading';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
-// import '@vben/styles/antd';
-// 引入组件库的少量全局样式变量
import { useTitle } from '@vueuse/core';
@@ -17,6 +15,7 @@ import { initSetupVbenForm } from './adapter/form';
import App from './app.vue';
import { router } from './router';
+// 引入组件库的少量全局样式变量
import 'tdesign-vue-next/es/style/index.css';
async function bootstrap(namespace: string) {
diff --git a/docs/.vitepress/components/preview-group.vue b/docs/.vitepress/components/preview-group.vue
index ccf1f7e87..f816dd348 100644
--- a/docs/.vitepress/components/preview-group.vue
+++ b/docs/.vitepress/components/preview-group.vue
@@ -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,
diff --git a/docs/.vitepress/config/en.mts b/docs/.vitepress/config/en.mts
index fcbe50edf..9154d2fa4 100644
--- a/docs/.vitepress/config/en.mts
+++ b/docs/.vitepress/config/en.mts
@@ -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',
diff --git a/docs/.vitepress/config/zh.mts b/docs/.vitepress/config/zh.mts
index c90418b8d..fa97f00cb 100644
--- a/docs/.vitepress/config/zh.mts
+++ b/docs/.vitepress/config/zh.mts
@@ -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 图片裁剪',
diff --git a/docs/package.json b/docs/package.json
index 400fb2a6b..674e8ea33 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -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:"
diff --git a/docs/src/components/common-ui/vben-descriptions.md b/docs/src/components/common-ui/vben-descriptions.md
new file mode 100644
index 000000000..fa48c7257
--- /dev/null
+++ b/docs/src/components/common-ui/vben-descriptions.md
@@ -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 列)。
+
+
+
+## 带边框
+
+设置 `bordered` 展示边框样式,配合 `title` 标题与 `#extra` 插槽(位于标题右侧的操作区域)。
+
+
+
+## 垂直布局
+
+通过 `layout="vertical"` 让标签位于内容上方。
+
+
+
+## 不同尺寸
+
+通过 `size` 设置 `small`、`middle`、`large` 三种尺寸。
+
+
+
+## 跨列与响应式
+
+单项通过 `span` 设置跨列数,`'filled'` 表示占满当前行剩余空间;`column` 支持传入按断点配置的对象实现响应式列数。
+
+
+
+## 子组件用法
+
+不传 `items` 时,可在默认插槽中使用 `VbenDescriptionsItem` 声明列表项,内容支持默认插槽或 `#content` 插槽自定义。
+
+
+
+## API
+
+### Descriptions Props
+
+| 属性名 | 描述 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| items | 数据驱动的列表项;不传则读取默认插槽 | `DescriptionsItemType[]` | - |
+| bordered | 是否展示边框 | `boolean` | `false` |
+| column | 一行的列数,支持按断点配置 | `number \| Partial
>` | `{ 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>` | `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)。:::
diff --git a/docs/src/components/common-ui/vben-table-action.md b/docs/src/components/common-ui/vben-table-action.md
new file mode 100644
index 000000000..6ffe34dc7
--- /dev/null
+++ b/docs/src/components/common-ui/vben-table-action.md
@@ -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` 显示按钮间分割线。
+
+
+
+## 提示
+
+通过 `tooltip` 为操作项添加提示,支持字符串或 `{ content, side }` 配置。
+
+
+
+## 气泡确认
+
+通过 `popConfirm` 开启点击前的气泡确认,常用于删除等危险操作。
+
+
+
+## 更多下拉
+
+通过 `dropdownActions` 将次要操作收纳到「更多」下拉中,`moreText` 可自定义按钮文案。
+
+
+
+## 权限控制
+
+为操作项设置 `auth` 权限码,并注入 `hasPermission` 判断函数,无权限的操作会被隐藏。
+
+
+
+## 在 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
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+若直接从 `@vben/common-ui` 引入核心组件(不经过适配器),组件不依赖任何业务逻辑,需自行注入 `hasPermission`:
+
+```vue
+
+
+
+
+
+```
+
+## 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` | - |
diff --git a/docs/src/demos/vben-alert/prompt/index.vue b/docs/src/demos/vben-alert/prompt/index.vue
index 5109c4791..7e807d065 100644
--- a/docs/src/demos/vben-alert/prompt/index.vue
+++ b/docs/src/demos/vben-alert/prompt/index.vue
@@ -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({
diff --git a/docs/src/demos/vben-descriptions/basic/index.vue b/docs/src/demos/vben-descriptions/basic/index.vue
new file mode 100644
index 000000000..8b73fe637
--- /dev/null
+++ b/docs/src/demos/vben-descriptions/basic/index.vue
@@ -0,0 +1,18 @@
+
+
+
+
diff --git a/docs/src/demos/vben-descriptions/bordered/index.vue b/docs/src/demos/vben-descriptions/bordered/index.vue
new file mode 100644
index 000000000..082157297
--- /dev/null
+++ b/docs/src/demos/vben-descriptions/bordered/index.vue
@@ -0,0 +1,22 @@
+
+
+
+
+ 编辑
+
+
+
diff --git a/docs/src/demos/vben-descriptions/custom/index.vue b/docs/src/demos/vben-descriptions/custom/index.vue
new file mode 100644
index 000000000..cd93ea0fc
--- /dev/null
+++ b/docs/src/demos/vben-descriptions/custom/index.vue
@@ -0,0 +1,17 @@
+
+
+
+
+ Vben
+
+ ● 正常
+
+
+
+ 通过 #content 插槽自定义内容
+
+
+
+
diff --git a/docs/src/demos/vben-descriptions/size/index.vue b/docs/src/demos/vben-descriptions/size/index.vue
new file mode 100644
index 000000000..d75a7e6b2
--- /dev/null
+++ b/docs/src/demos/vben-descriptions/size/index.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
diff --git a/docs/src/demos/vben-descriptions/span/index.vue b/docs/src/demos/vben-descriptions/span/index.vue
new file mode 100644
index 000000000..1cde5fea0
--- /dev/null
+++ b/docs/src/demos/vben-descriptions/span/index.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/docs/src/demos/vben-descriptions/vertical/index.vue b/docs/src/demos/vben-descriptions/vertical/index.vue
new file mode 100644
index 000000000..8c480835d
--- /dev/null
+++ b/docs/src/demos/vben-descriptions/vertical/index.vue
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/docs/src/demos/vben-table-action/basic/index.vue b/docs/src/demos/vben-table-action/basic/index.vue
new file mode 100644
index 000000000..b88cd4a19
--- /dev/null
+++ b/docs/src/demos/vben-table-action/basic/index.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
+ 最近点击:{{ last }}
+
+
+
diff --git a/docs/src/demos/vben-table-action/dropdown/index.vue b/docs/src/demos/vben-table-action/dropdown/index.vue
new file mode 100644
index 000000000..b7664e51e
--- /dev/null
+++ b/docs/src/demos/vben-table-action/dropdown/index.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+ 最近点击:{{ last }}
+
+
+
diff --git a/docs/src/demos/vben-table-action/permission/index.vue b/docs/src/demos/vben-table-action/permission/index.vue
new file mode 100644
index 000000000..bce245ca8
--- /dev/null
+++ b/docs/src/demos/vben-table-action/permission/index.vue
@@ -0,0 +1,28 @@
+
+
+
+
diff --git a/docs/src/demos/vben-table-action/popconfirm/index.vue b/docs/src/demos/vben-table-action/popconfirm/index.vue
new file mode 100644
index 000000000..513430ced
--- /dev/null
+++ b/docs/src/demos/vben-table-action/popconfirm/index.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
+ 最近操作:{{ last }}
+
+
+
diff --git a/docs/src/demos/vben-table-action/tooltip/index.vue b/docs/src/demos/vben-table-action/tooltip/index.vue
new file mode 100644
index 000000000..2bd75e28b
--- /dev/null
+++ b/docs/src/demos/vben-table-action/tooltip/index.vue
@@ -0,0 +1,17 @@
+
+
+
+
diff --git a/docs/src/en/components/common-ui/vben-descriptions.md b/docs/src/en/components/common-ui/vben-descriptions.md
new file mode 100644
index 000000000..4685b1f5b
--- /dev/null
+++ b/docs/src/en/components/common-ui/vben-descriptions.md
@@ -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).
+
+
+
+## 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).
+
+
+
+## Vertical Layout
+
+Use `layout="vertical"` to place labels above their content.
+
+
+
+## Sizes
+
+Use `size` to switch between `small`, `middle`, and `large`.
+
+
+
+## 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.
+
+
+
+## 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.
+
+
+
+## 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>` | `{ 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>` | `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). :::
diff --git a/docs/src/en/components/common-ui/vben-table-action.md b/docs/src/en/components/common-ui/vben-table-action.md
new file mode 100644
index 000000000..7c1e76553
--- /dev/null
+++ b/docs/src/en/components/common-ui/vben-table-action.md
@@ -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.
+
+
+
+## Tooltip
+
+Add a tooltip to an action via `tooltip`, accepting a string or a `{ content, side }` object.
+
+
+
+## PopConfirm
+
+Use `popConfirm` to require confirmation before the action runs, commonly used for destructive actions like delete.
+
+
+
+## More Dropdown
+
+Use `dropdownActions` to collapse secondary actions into a "more" dropdown. `moreText` customizes the button label.
+
+
+
+## Permission Control
+
+Set an `auth` code on an action and inject a `hasPermission` resolver; actions without permission are hidden.
+
+
+
+## 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
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+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
+
+
+
+
+
+```
+
+## 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` | - |
diff --git a/docs/src/en/guide/essentials/server.md b/docs/src/en/guide/essentials/server.md
index c22b08f43..b17db80dd 100644
--- a/docs/src/en/guide/essentials/server.md
+++ b/docs/src/en/guide/essentials/server.md
@@ -98,7 +98,7 @@ VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
::: tip How to Dynamically Modify API Endpoint in Production
-Variables starting with `VITE_GLOB_*` in the `.env` file are injected into the `_app.config.js` file during packaging. After packaging, you can modify the corresponding API addresses in `dist/_app.config.js` and refresh the page to apply the changes. This eliminates the need to package multiple times for different environments, allowing a single package to be deployed across multiple API environments.
+Variables starting with `VITE_GLOB_*` in the `.env` file are injected into the `_app-config-{version}-{hash}.js` file during packaging. After packaging, you can modify the corresponding API addresses in `dist/_app-config-{version}-{hash}.js` and refresh the page to apply the changes. This eliminates the need to package multiple times for different environments, allowing a single package to be deployed across multiple API environments.
:::
diff --git a/docs/src/en/guide/essentials/settings.md b/docs/src/en/guide/essentials/settings.md
index 69aedbde2..3bbc4383b 100644
--- a/docs/src/en/guide/essentials/settings.md
+++ b/docs/src/en/guide/essentials/settings.md
@@ -21,7 +21,7 @@ The rules are consistent with [Vite Env Variables and Modes](https://vitejs.dev/
console.log(import.meta.env.VITE_PROT);
```
-- Variables starting with `VITE_GLOB_*` will be added to the `_app.config.js` configuration file during packaging.
+- Variables starting with `VITE_GLOB_*` will be added to the `_app-config-{version}-{hash}.js` configuration file during packaging.
:::
@@ -87,9 +87,9 @@ VITE_ARCHIVER=true
## Dynamic Configuration in Production Environment
-When executing `pnpm build` in the root directory of the monorepo, a `dist/_app.config.js` file will be automatically generated in the corresponding application and inserted into `index.html`.
+When executing `pnpm build` in the root directory of the monorepo, a `dist/_app-config-{version}-{hash}.js` file will be automatically generated in the corresponding application and inserted into `index.html`.
-`_app.config.js` is a dynamic configuration file that allows for modifications to the configuration dynamically based on different environments after the project has been built. The content is as follows:
+`_app-config-{version}-{hash}.js` is a dynamic configuration file that allows for modifications to the configuration dynamically based on different environments after the project has been built. The content is as follows:
```ts
window._VBEN_ADMIN_PRO_APP_CONF_ = {
@@ -104,11 +104,11 @@ Object.defineProperty(window, '_VBEN_ADMIN_PRO_APP_CONF_', {
### Purpose
-`_app.config.js` is used for projects that need to dynamically modify configurations after packaging, such as API endpoints. There's no need to repackage; you can simply modify the variables in `/dist/_app.config.js` after packaging, and refresh to update the variables in the code. A `js` file is used to ensure that the configuration file is loaded early in the order.
+`_app-config-{version}-{hash}.js` is used for projects that need to dynamically modify configurations after packaging, such as API endpoints. There's no need to repackage; you can simply modify the variables in `/dist/_app-config-{version}-{hash}.js` after packaging, and refresh to update the variables in the code. A `js` file is used to ensure that the configuration file is loaded early in the order.
### Usage
-To access the variables inside `_app.config.js`, you need to use the `useAppConfig` method provided by `@vben/hooks`.
+To access the variables inside `_app-config-{version}-{hash}.js`, you need to use the `useAppConfig` method provided by `@vben/hooks`.
```ts
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
diff --git a/docs/src/en/guide/introduction/vben.md b/docs/src/en/guide/introduction/vben.md
index 623a7ffe0..2e32d7787 100644
--- a/docs/src/en/guide/introduction/vben.md
+++ b/docs/src/en/guide/introduction/vben.md
@@ -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.
diff --git a/docs/src/guide/essentials/server.md b/docs/src/guide/essentials/server.md
index 337248fc3..2ae16e763 100644
--- a/docs/src/guide/essentials/server.md
+++ b/docs/src/guide/essentials/server.md
@@ -98,7 +98,7 @@ VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
::: tip 打包如何动态修改接口地址
-`.env` 文件内的 `VITE_GLOB_*` 开头的变量会在打包的时候注入 `_app.config.js` 文件内。在 `dist/_app.config.js` 修改相应的接口地址后刷新页面即可,不需要在根据不同环境打包多次,一次打包可以用于多个不同接口环境的部署。
+`.env` 文件内的 `VITE_GLOB_*` 开头的变量会在打包的时候注入 `_app-config-{version}-{hash}.js` 文件内。在 `dist/_app-config-{version}-{hash}.js` 修改相应的接口地址后刷新页面即可,不需要在根据不同环境打包多次,一次打包可以用于多个不同接口环境的部署。
:::
diff --git a/docs/src/guide/essentials/settings.md b/docs/src/guide/essentials/settings.md
index 067a1cb95..32111d4f1 100644
--- a/docs/src/guide/essentials/settings.md
+++ b/docs/src/guide/essentials/settings.md
@@ -21,7 +21,7 @@
console.log(import.meta.env.VITE_PROT);
```
-- 以 `VITE_GLOB_*` 开头的的变量,在打包的时候,会被加入 `_app.config.js`配置文件当中.
+- 以 `VITE_GLOB_*` 开头的的变量,在打包的时候,会被加入 `_app-config-{version}-{hash}.js`配置文件当中.
:::
@@ -86,9 +86,9 @@ VITE_ARCHIVER=true
## 生产环境动态配置
-当在大仓根目录下,执行 `pnpm build`构建项目之后,会自动在对应的应用下生成 `dist/_app.config.js`文件并插入 `index.html`。
+当在大仓根目录下,执行 `pnpm build`构建项目之后,会自动在对应的应用下生成 `dist/_app-config-{version}-{hash}.js`文件并插入 `index.html`。
-`_app.config.js` 是一个动态配置文件,可以在项目构建之后,根据不同的环境动态修改配置。内容如下:
+`_app-config-{version}-{hash}.js` 是一个动态配置文件,可以在项目构建之后,根据不同的环境动态修改配置。内容如下:
```ts
window._VBEN_ADMIN_PRO_APP_CONF_ = {
@@ -103,11 +103,11 @@ Object.defineProperty(window, '_VBEN_ADMIN_PRO_APP_CONF_', {
### 作用
-`_app.config.js` 用于项目在打包后,需要动态修改配置的需求,如接口地址。不用重新进行打包,可在打包后修改 /`dist/_app.config.js` 内的变量,刷新即可更新代码内的局部变量。这里使用`js`文件,是为了确保配置文件加载顺序保持在前面。
+`_app-config-{version}-{hash}.js` 用于项目在打包后,需要动态修改配置的需求,如接口地址。不用重新进行打包,可在打包后修改 /`dist/_app-config-{version}-{hash}.js` 内的变量,刷新即可更新代码内的局部变量。这里使用`js`文件,是为了确保配置文件加载顺序保持在前面。
### 使用
-想要获取 `_app.config.js` 内的变量,需要使用`@vben/hooks`提供的 `useAppConfig`方法。
+想要获取 `_app-config-{version}-{hash}.js` 内的变量,需要使用`@vben/hooks`提供的 `useAppConfig`方法。
```ts
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
diff --git a/docs/src/guide/introduction/vben.md b/docs/src/guide/introduction/vben.md
index 4f0f25978..513956d96 100644
--- a/docs/src/guide/introduction/vben.md
+++ b/docs/src/guide/introduction/vben.md
@@ -26,7 +26,7 @@
## 浏览器支持
-- **本地开发**推荐使用`Chrome 最新版`浏览器,**不支持**`Chrome 80`以下版本。
+- **本地开发**推荐使用`Chrome 最新版`浏览器,**不支持**` Tailwind CSS v4.0 is designed for Safari 16.4+, Chrome 111+, and Firefox 128+。
- **生产环境**支持现代浏览器,不支持 IE。
diff --git a/internal/lint-configs/commitlint-config/index.d.ts b/internal/lint-configs/commitlint-config/index.d.ts
new file mode 100644
index 000000000..14b40f139
--- /dev/null
+++ b/internal/lint-configs/commitlint-config/index.d.ts
@@ -0,0 +1,6 @@
+// eslint-disable-next-line n/no-extraneous-import
+import type { UserConfig } from '@commitlint/types';
+
+declare const userConfig: UserConfig;
+
+export default userConfig;
diff --git a/internal/lint-configs/commitlint-config/package.json b/internal/lint-configs/commitlint-config/package.json
index 0fef91827..580394856 100644
--- a/internal/lint-configs/commitlint-config/package.json
+++ b/internal/lint-configs/commitlint-config/package.json
@@ -18,6 +18,7 @@
"module": "./index.mjs",
"exports": {
".": {
+ "types": "./index.d.ts",
"import": "./index.mjs",
"default": "./index.mjs"
}
diff --git a/internal/lint-configs/eslint-config/src/configs/node.ts b/internal/lint-configs/eslint-config/src/configs/node.ts
index 95d998673..a79083e47 100644
--- a/internal/lint-configs/eslint-config/src/configs/node.ts
+++ b/internal/lint-configs/eslint-config/src/configs/node.ts
@@ -32,7 +32,7 @@ export async function node(): Promise {
'error',
{
ignores: [],
- version: '>=20.12.0',
+ version: '>=22.18.0',
},
],
'n/prefer-global/buffer': ['error', 'never'],
diff --git a/internal/vite-config/src/plugins/extra-app-config.ts b/internal/vite-config/src/plugins/extra-app-config.ts
index 813819bbd..903aa6fd6 100644
--- a/internal/vite-config/src/plugins/extra-app-config.ts
+++ b/internal/vite-config/src/plugins/extra-app-config.ts
@@ -13,7 +13,7 @@ interface PluginOptions {
root: string;
}
-const GLOBAL_CONFIG_FILE_NAME = '_app.config.js';
+const GLOBAL_CONFIG_FILE_NAME = '_app-config';
const VBEN_ADMIN_PRO_APP_CONF = '_VBEN_ADMIN_PRO_APP_CONF_';
/**
@@ -27,6 +27,7 @@ async function viteExtraAppConfigPlugin({
}: PluginOptions): Promise {
let publicPath: string;
let source: string;
+ let hash: string;
if (!isBuild) {
return;
@@ -38,11 +39,12 @@ async function viteExtraAppConfigPlugin({
async configResolved(config) {
publicPath = ensureTrailingSlash(config.base);
source = await getConfigSource();
+ hash = generatorContentHash(source, 8);
},
async generateBundle() {
try {
this.emitFile({
- fileName: GLOBAL_CONFIG_FILE_NAME,
+ fileName: `${GLOBAL_CONFIG_FILE_NAME}-${version}-${hash}.js`,
source,
type: 'asset',
});
@@ -58,9 +60,7 @@ async function viteExtraAppConfigPlugin({
},
name: 'vite:extra-app-config',
async transformIndexHtml(html) {
- const hash = `v=${version}-${generatorContentHash(source, 8)}`;
-
- const appConfigSrc = `${publicPath}${GLOBAL_CONFIG_FILE_NAME}?${hash}`;
+ const appConfigSrc = `${publicPath}${GLOBAL_CONFIG_FILE_NAME}-${version}-${hash}.js`;
return {
html,
diff --git a/package.json b/package.json
index 5c4d867ba..d49bf2c21 100644
--- a/package.json
+++ b/package.json
@@ -105,5 +105,5 @@
"node": "^22.18.0 || ^24.0.0",
"pnpm": ">=11.0.0"
},
- "packageManager": "pnpm@11.2.2"
+ "packageManager": "pnpm@11.5.0"
}
diff --git a/packages/@core/base/icons/package.json b/packages/@core/base/icons/package.json
index 558a5b7ca..23504aca4 100644
--- a/packages/@core/base/icons/package.json
+++ b/packages/@core/base/icons/package.json
@@ -36,7 +36,7 @@
},
"dependencies": {
"@iconify/vue": "catalog:",
- "lucide-vue-next": "catalog:",
+ "@lucide/vue": "catalog:",
"vue": "catalog:"
}
}
diff --git a/packages/@core/base/icons/src/lucide.ts b/packages/@core/base/icons/src/lucide.ts
index 172e79685..01ea54df3 100644
--- a/packages/@core/base/icons/src/lucide.ts
+++ b/packages/@core/base/icons/src/lucide.ts
@@ -36,7 +36,6 @@ export {
EyeOff,
FoldHorizontal,
Fullscreen,
- Github,
Grid,
Grip,
GripVertical,
@@ -99,4 +98,4 @@ export {
Upload,
UserRoundPen,
X,
-} from 'lucide-vue-next';
+} from '@lucide/vue';
diff --git a/packages/@core/base/shared/src/utils/__tests__/update-css-variables.test.ts b/packages/@core/base/shared/src/utils/__tests__/update-css-variables.test.ts
index 4a9cdadc7..3d1113c01 100644
--- a/packages/@core/base/shared/src/utils/__tests__/update-css-variables.test.ts
+++ b/packages/@core/base/shared/src/utils/__tests__/update-css-variables.test.ts
@@ -28,3 +28,21 @@ it('updateCSSVariables should update CSS variables in :root selector', () => {
updatedStyleContent?.includes('fontSize: 16px;'),
).toBe(true);
});
+
+it('updateCSSVariables should support a custom selector', () => {
+ document.head.innerHTML = ``;
+
+ // 使用自定义选择器(如 TDesign 的 theme-mode 选择器)更新 CSS 变量
+ updateCSSVariables(
+ { '--td-brand-color': 'rgb(0, 82, 217)' },
+ 'tdesign-styles',
+ ":root[theme-mode='dark']",
+ );
+
+ const styleElement = document.querySelector('#tdesign-styles');
+ const content = styleElement?.textContent ?? '';
+
+ // 选择器与变量都应正确写入
+ expect(content.startsWith(":root[theme-mode='dark'] {")).toBe(true);
+ expect(content.includes('--td-brand-color: rgb(0, 82, 217);')).toBe(true);
+});
diff --git a/packages/@core/base/shared/src/utils/update-css-variables.ts b/packages/@core/base/shared/src/utils/update-css-variables.ts
index 657deaa7d..296e2e57c 100644
--- a/packages/@core/base/shared/src/utils/update-css-variables.ts
+++ b/packages/@core/base/shared/src/utils/update-css-variables.ts
@@ -1,10 +1,15 @@
/**
* 更新 CSS 变量的函数
* @param variables 要更新的 CSS 变量与其新值的映射
+ * @param id 内联样式表的 id,便于复用与覆盖
+ * @param selector CSS 变量挂载的选择器,默认 `:root`。
+ * 对于像 TDesign 这种将变量定义在 `:root[theme-mode='dark']` 等更高优先级选择器下的组件库,
+ * 需要传入相同(或更高)优先级的选择器才能正确覆盖。
*/
function updateCSSVariables(
variables: { [key: string]: string },
id = '__vben-styles__',
+ selector = ':root',
): void {
// 获取或创建内联样式表元素
const styleElement =
@@ -13,7 +18,7 @@ function updateCSSVariables(
styleElement.id = id;
// 构建要更新的 CSS 变量的样式文本
- let cssText = ':root {';
+ let cssText = `${selector} {`;
for (const key in variables) {
if (Object.prototype.hasOwnProperty.call(variables, key)) {
cssText += `${key}: ${variables[key]};`;
diff --git a/packages/@core/composables/src/use-simple-locale/messages.ts b/packages/@core/composables/src/use-simple-locale/messages.ts
index efc5c3cc7..671ae33c2 100644
--- a/packages/@core/composables/src/use-simple-locale/messages.ts
+++ b/packages/@core/composables/src/use-simple-locale/messages.ts
@@ -9,6 +9,7 @@ export const messages: Record> = {
prompt: 'Prompt',
reset: 'Reset',
submit: 'Submit',
+ confirmTitle: 'Please Confirm',
},
'zh-CN': {
cancel: '取消',
@@ -18,6 +19,7 @@ export const messages: Record> = {
prompt: '提示',
reset: '重置',
submit: '提交',
+ confirmTitle: '请确认',
},
};
diff --git a/packages/@core/ui-kit/form-ui/src/components/form-field-array.vue b/packages/@core/ui-kit/form-ui/src/components/form-field-array.vue
new file mode 100644
index 000000000..1d7a0730a
--- /dev/null
+++ b/packages/@core/ui-kit/form-ui/src/components/form-field-array.vue
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
+ |
+ #
+ |
+
+
+ |
+
+ {{ actionText }}
+ |
+
+
+
+
+ |
+ {{ index + 1 }}
+ |
+
+
+ |
+
+
+
+
+ |
+
+
+
+
+
+ {{ emptyText }}
+
+
+
+
+ {{ addButtonText }}
+
+
+
diff --git a/packages/@core/ui-kit/form-ui/src/config.ts b/packages/@core/ui-kit/form-ui/src/config.ts
index 645f51f3b..9448d2ac1 100644
--- a/packages/@core/ui-kit/form-ui/src/config.ts
+++ b/packages/@core/ui-kit/form-ui/src/config.ts
@@ -20,6 +20,8 @@ import { globalShareState } from '@vben-core/shared/global-state';
import { defineRule } from 'vee-validate';
+import VbenFormFieldArray from './components/form-field-array.vue';
+
const DEFAULT_MODEL_PROP_NAME = 'modelValue';
export const DEFAULT_FORM_COMMON_CONFIG: FormCommonConfig = {};
@@ -28,6 +30,7 @@ export const COMPONENT_MAP: Record = {
DefaultButton: h(VbenButton, { size: 'sm', variant: 'outline' }),
PrimaryButton: h(VbenButton, { size: 'sm', variant: 'default' }),
VbenCheckbox,
+ VbenFormFieldArray,
VbenInput,
VbenInputPassword,
VbenPinInput,
diff --git a/packages/@core/ui-kit/form-ui/src/index.ts b/packages/@core/ui-kit/form-ui/src/index.ts
index 9bb3fd33c..90006842f 100644
--- a/packages/@core/ui-kit/form-ui/src/index.ts
+++ b/packages/@core/ui-kit/form-ui/src/index.ts
@@ -4,6 +4,7 @@ export type {
BaseFormComponentType,
ExtendedFormApi,
FormLayout,
+ VbenFormFieldArrayProps,
VbenFormProps,
FormSchema as VbenFormSchema,
} from './types';
diff --git a/packages/@core/ui-kit/form-ui/src/types.ts b/packages/@core/ui-kit/form-ui/src/types.ts
index 7b6f53d64..8aea697f6 100644
--- a/packages/@core/ui-kit/form-ui/src/types.ts
+++ b/packages/@core/ui-kit/form-ui/src/types.ts
@@ -14,6 +14,7 @@ export type BaseFormComponentType =
| 'DefaultButton'
| 'PrimaryButton'
| 'VbenCheckbox'
+ | 'VbenFormFieldArray'
| 'VbenInput'
| 'VbenInputPassword'
| 'VbenPinInput'
@@ -309,6 +310,32 @@ export type FormSchema<
P extends Record = Record,
> = FormSchemaDiscriminated | FormSchemaFallback;
+/**
+ * 数组编辑器(VbenFormFieldArray)的组件参数
+ */
+export interface VbenFormFieldArrayProps<
+ T extends BaseFormComponentType = BaseFormComponentType,
+ P extends Record = Record,
+> {
+ /** 操作列表头文案 */
+ actionText?: string;
+ /** 「添加」按钮文案 */
+ addButtonText?: string;
+ /** 新增一行时生成的默认数据;缺省时按列定义的 fieldName 生成空对象 */
+ createRow?: () => Record;
+ disabled?: boolean;
+ /** 空数据文案 */
+ emptyText?: string;
+ /** 最多行数 */
+ max?: number;
+ /** 最少行数 */
+ min?: number;
+ /** 列定义,每一列是一个子字段(复用 FormSchema) */
+ schema: FormSchema[];
+ /** 是否显示序号列 */
+ showIndex?: boolean;
+}
+
export type HandleSubmitFn = (
values: Record,
) => Promise | void;
diff --git a/packages/@core/ui-kit/form-ui/src/use-vben-form.ts b/packages/@core/ui-kit/form-ui/src/use-vben-form.ts
index 00c60d05b..da3c57d90 100644
--- a/packages/@core/ui-kit/form-ui/src/use-vben-form.ts
+++ b/packages/@core/ui-kit/form-ui/src/use-vben-form.ts
@@ -6,7 +6,7 @@ import type {
import { defineComponent, h, isReactive, onBeforeUnmount, watch } from 'vue';
-import { useStore } from '@vben-core/shared/store';
+import { useSelector } from '@vben-core/shared/store';
import { FormApi } from './form-api';
import VbenUseForm from './vben-use-form.vue';
@@ -19,7 +19,7 @@ export function useVbenForm<
const api = new FormApi(options as unknown as VbenFormProps);
const extendedApi: ExtendedFormApi = api as never;
extendedApi.useStore = (selector) => {
- return useStore(api.store, selector);
+ return useSelector(api.store, selector);
};
const Form = defineComponent(
diff --git a/packages/@core/ui-kit/menu-ui/src/components/menu-item.vue b/packages/@core/ui-kit/menu-ui/src/components/menu-item.vue
index e9236aaf4..017026423 100644
--- a/packages/@core/ui-kit/menu-ui/src/components/menu-item.vue
+++ b/packages/@core/ui-kit/menu-ui/src/components/menu-item.vue
@@ -37,7 +37,7 @@ const menuIcon = computed(() =>
const isHttp = computed(() => isHttpUrl(item.parentPaths.at(-1)));
const isTopLevelMenuItem = computed(
- () => parentMenu.value?.type.name === 'Menu',
+ () => parentMenu.value?.type.name === 'MenuUI',
);
const collapseShowTitle = computed(
diff --git a/packages/@core/ui-kit/menu-ui/src/components/menu.vue b/packages/@core/ui-kit/menu-ui/src/components/menu.vue
index acb243fc8..5d9f5780a 100644
--- a/packages/@core/ui-kit/menu-ui/src/components/menu.vue
+++ b/packages/@core/ui-kit/menu-ui/src/components/menu.vue
@@ -37,7 +37,7 @@ import SubMenu from './sub-menu.vue';
interface Props extends MenuProps {}
-defineOptions({ name: 'Menu' });
+defineOptions({ name: 'MenuUI' });
const props = withDefaults(defineProps(), {
accordion: true,
diff --git a/packages/@core/ui-kit/menu-ui/src/components/sub-menu.vue b/packages/@core/ui-kit/menu-ui/src/components/sub-menu.vue
index 5c8c8a860..afc0afa16 100644
--- a/packages/@core/ui-kit/menu-ui/src/components/sub-menu.vue
+++ b/packages/@core/ui-kit/menu-ui/src/components/sub-menu.vue
@@ -54,7 +54,7 @@ const opened = computed(() => {
return rootMenu?.openedMenus.includes(props.path);
});
const isTopLevelMenuSubmenu = computed(
- () => parentMenu.value?.type.name === 'Menu',
+ () => parentMenu.value?.type.name === 'MenuUI',
);
const mode = computed(() => rootMenu?.props.mode ?? 'vertical');
const rounded = computed(() => rootMenu?.props.rounded);
diff --git a/packages/@core/ui-kit/menu-ui/src/hooks/use-menu-context.ts b/packages/@core/ui-kit/menu-ui/src/hooks/use-menu-context.ts
index 357b296b9..1e3955dcd 100644
--- a/packages/@core/ui-kit/menu-ui/src/hooks/use-menu-context.ts
+++ b/packages/@core/ui-kit/menu-ui/src/hooks/use-menu-context.ts
@@ -42,7 +42,7 @@ function useSubMenuContext() {
if (!instance) {
throw new Error('instance is required');
}
- const parentMenu = findComponentUpward(instance, ['Menu', 'SubMenu']);
+ const parentMenu = findComponentUpward(instance, ['MenuUI', 'SubMenu']);
const subMenu = inject(`subMenu:${parentMenu?.uid}`) as SubMenuProvider;
return subMenu;
}
diff --git a/packages/@core/ui-kit/menu-ui/src/hooks/use-menu.ts b/packages/@core/ui-kit/menu-ui/src/hooks/use-menu.ts
index 9207445f9..0ee93fd9a 100644
--- a/packages/@core/ui-kit/menu-ui/src/hooks/use-menu.ts
+++ b/packages/@core/ui-kit/menu-ui/src/hooks/use-menu.ts
@@ -16,7 +16,7 @@ function useMenu() {
const parentPaths = computed(() => {
let parent = instance.parent;
const paths: string[] = [instance.props.path as string];
- while (parent?.type.name !== 'Menu') {
+ while (parent?.type.name !== 'MenuUI') {
if (parent?.props.path) {
paths.unshift(parent.props.path as string);
}
@@ -27,7 +27,7 @@ function useMenu() {
});
const parentMenu = computed(() => {
- return findComponentUpward(instance, ['Menu', 'SubMenu']);
+ return findComponentUpward(instance, ['MenuUI', 'SubMenu']);
});
return {
diff --git a/packages/@core/ui-kit/popup-ui/src/alert/alert.vue b/packages/@core/ui-kit/popup-ui/src/alert/alert.vue
index d49e0cc8b..fd7b95dff 100644
--- a/packages/@core/ui-kit/popup-ui/src/alert/alert.vue
+++ b/packages/@core/ui-kit/popup-ui/src/alert/alert.vue
@@ -156,7 +156,7 @@ async function handleOpenChange(val: boolean) {
:class="
cn(
containerClass,
- 'inset-x-0 mx-auto flex max-h-[80%] flex-col p-0 duration-300 sm:w-130 sm:max-w-[80%] sm:rounded-(--radius)',
+ 'flex max-h-[80%] flex-col p-0 duration-300 sm:w-130 sm:max-w-[80%] sm:rounded-(--radius)',
{
'border border-border': bordered,
'shadow-3xl': !bordered,
@@ -197,7 +197,7 @@ async function handleOpenChange(val: boolean) {
{{ cancelText || $t('cancel') }}
diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue
index 0a7fd35f0..5abbf3823 100644
--- a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue
+++ b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue
@@ -54,7 +54,8 @@ const components = globalShareState.getComponents();
const id = useId();
provide('DISMISSABLE_DRAWER_ID', id);
-// const wrapperRef = ref();
+// @ts-expect-error unused
+const wrapperRef = ref();
const { $t } = useSimpleLocale();
const { isMobile } = useIsMobile();
@@ -285,8 +286,8 @@ const getForceMount = computed(() => {
-
import type { ExtendedModalApi, ModalProps } from './modal';
-import {
- computed,
- nextTick,
- onDeactivated,
- provide,
- ref,
- unref,
- useId,
- watch,
-} from 'vue';
+import { computed, nextTick, onDeactivated, ref, unref, watch } from 'vue';
-import {
- useIsMobile,
- usePriorityValues,
- useSimpleLocale,
-} from '@vben-core/composables';
+import { usePriorityValues, useSimpleLocale } from '@vben-core/composables';
import { Expand, Shrink } from '@vben-core/icons';
import {
Dialog,
@@ -57,12 +44,7 @@ const headerRef = ref();
// @ts-expect-error unused
const footerRef = ref();
-const id = useId();
-
-provide('DISMISSABLE_MODAL_ID', id);
-
const { $t } = useSimpleLocale();
-const { isMobile } = useIsMobile();
const state = props.modalApi?.useStore?.();
const {
@@ -101,7 +83,7 @@ const {
zIndex,
} = usePriorityValues(props, state);
-const shouldFullscreen = computed(() => fullscreen.value || isMobile.value);
+const shouldFullscreen = computed(() => fullscreen.value);
const shouldDraggable = computed(
() => draggable.value && !shouldFullscreen.value && header.value,
@@ -199,15 +181,8 @@ function handleOpenAutoFocus(e: Event) {
// pointer-down-outside
function pointerDownOutside(e: Event) {
- const target = e.target as HTMLElement;
- const isDismissableModal = target?.dataset.dismissableModal;
- if (
- !closeOnClickModal.value ||
- isDismissableModal !== id ||
- submitting.value
- ) {
+ if (!closeOnClickModal.value || submitting.value) {
e.preventDefault();
- e.stopPropagation();
}
}
@@ -216,6 +191,10 @@ function handleFocusOutside(e: Event) {
e.stopPropagation();
}
+function handleCloseAutoFocus(_e: Event) {
+ // allow reka-ui to return focus to the trigger element on close
+}
+
const getForceMount = computed(() => {
return !unref(destroyOnClose) && unref(firstOpened);
});
@@ -233,7 +212,7 @@ function handleClosed() {