diff --git a/.changeset/.prettierrc.json b/.changeset/.prettierrc.json new file mode 100644 index 000000000..544138be4 --- /dev/null +++ b/.changeset/.prettierrc.json @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/.changeset/fancy-ears-walk.md b/.changeset/fancy-ears-walk.md new file mode 100644 index 000000000..f73cb596b --- /dev/null +++ b/.changeset/fancy-ears-walk.md @@ -0,0 +1,7 @@ +--- +'@vben/styles': patch +'@vben-core/form-ui': patch +'@vben/web-naive': patch +--- + +feat(@core/form-ui): 新增 useVbenForm 数组编辑器 VbenFormFieldArray diff --git a/apps/web-antd/src/views/dashboard/workspace/index.vue b/apps/web-antd/src/views/dashboard/workspace/index.vue index 8f6620310..1cfbef1bc 100644 --- a/apps/web-antd/src/views/dashboard/workspace/index.vue +++ b/apps/web-antd/src/views/dashboard/workspace/index.vue @@ -238,7 +238,7 @@ function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) { -
+
@@ -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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + + 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() {