Compare commits

...

35 Commits

Author SHA1 Message Date
vben
7ab00250bf chore: release 5.5.0 2024-12-04 22:57:27 +08:00
Vben
9896a67c21 feat: add api-select component (#5024) 2024-12-04 22:56:29 +08:00
Netfan
db38ef522f fix: Page header class in fixed mode (#5023) 2024-12-04 22:56:06 +08:00
Netfan
845f2a2abd fix: header left padding fixed (#5007) 2024-12-04 21:43:54 +08:00
Netfan
9b73792dc9 fix: extra menu title follow locale change (#5006) 2024-12-04 21:43:29 +08:00
Netfan
fccbe44cf7 feat: v-loading support for element plus (#5008) 2024-12-04 21:42:48 +08:00
Netfan
e23486dbc6 feat: form component IconPicker (#5005) 2024-12-04 21:42:21 +08:00
Netfan
935df713f3 fix: app config support .env.local (#5012) 2024-12-04 21:41:22 +08:00
Netfan
17c7ce8f21 feat: improve page component (#5013)
* feat: `page` component support fixed header

* docs: `page`  component documentation

* docs: Improve `props` types of `page`

* docs: improve `fixedHeader` description of `page`

* fix: `page` header border color with fixedHeader

* feat: add `headerClass` for `Page`
2024-12-04 21:40:41 +08:00
vben
24b9aa44d2 chore: Revert "fix: form 表单不支持field.xxx.xx格式的defaultValue配置 (#4965)"
This reverts commliit 12f216c0e7.
2024-12-02 00:47:06 +08:00
Vben
014e6d38a0 chore: update deps (#4993) 2024-12-01 21:53:52 +08:00
leizhiyou
12f216c0e7 fix: form 表单不支持field.xxx.xx格式的defaultValue配置 (#4965)
* fix: form 表单不支持field.xxx.xx格式的defaultValue配置

* chore: 修复代码规范问题
2024-12-01 21:48:54 +08:00
Netfan
ae3f7cb909 fix: mixed menu layout in full content mode (#4990) 2024-12-01 21:37:36 +08:00
Netfan
32117b73aa docs: add form slots docs (#4992) 2024-12-01 21:37:19 +08:00
huangfe1
e8992a1d16 chore: update modal.vue (#4987)
loading时候 子组件禁用点击事件

Co-authored-by: Vben <ann.vben@gmail.com>
2024-11-30 11:18:22 +08:00
Svend
3c4af23edf fix: 修复 Form Api 根据字段名移除表单项,字段取反了的问题 (#4971) 2024-11-30 10:58:17 +08:00
LinaBell
e3a93970f4 fix: when VxeTable toolbarConfig.refresh is enabled, it will carry incorrect parameters (#4980) 2024-11-30 10:57:23 +08:00
richex-cn
7b9866158b chore: update deprecated document link in .github/ISSUE_TEMPLATE (#4986) 2024-11-30 10:56:42 +08:00
Netfan
3fb286b552 fix: element hover style in dark theme (#4983) 2024-11-30 10:55:29 +08:00
Netfan
253abc5ef1 chore: tailwind css icon example (#4969) 2024-11-28 15:10:14 +08:00
Jeff
5f55799572 fix: button-control page mistake (#4963)
* fix: button-control page mistake

按钮控制展示逻辑错误

* fix: button-control.vue button text
2024-11-28 10:01:26 +08:00
vince
54a9ff088f feat: upgrade vite version to 6.0.0 (#4961)
* chore: upgrade vite version to 6.0.0

* chore: update lock
2024-11-27 15:52:25 +08:00
Netfan
73502677ff feat: add placement for Drawer (#4958) 2024-11-27 11:29:25 +08:00
Netfan
dedba18553 feat: add confirmDisabled for Dialog (#4959) 2024-11-27 11:28:49 +08:00
huangxiaomin
f85badf482 fix: the route path did not synchronize with the page (#4947) 2024-11-25 15:07:52 +08:00
眼圈发黑
12f25cf3a2 style: typo (#4948) 2024-11-25 15:07:16 +08:00
vben
c8dd9bbf0b chore: release 5.4.8 2024-11-24 22:00:41 +08:00
Vben
3587ec54eb fix: supplement datepicker component (#4943)
* fix: supplement datepicker component

* chore: typo
2024-11-24 21:56:41 +08:00
Vben
dbcb7138f2 fix: resolve issue with Upload component not working correctly inside Form (#4916) 2024-11-17 21:37:37 +08:00
ryomahan
fe58af2e78 fix: form-api.setValues can't resolve nested fields (#4915)
fix #4912
2024-11-17 21:04:35 +08:00
huangxiaomin
94c68c966e fix: fieldMappingTime data error when clear inputvalue (#4906) 2024-11-17 21:04:04 +08:00
Arthur Darkstone
77083abcc5 feat: add 3 resize examples (#4907) 2024-11-17 21:01:32 +08:00
Netfan
1302092798 fix: dialog opened/closed event triggered incorrectly,fixed #4902 (#4908) 2024-11-17 20:55:19 +08:00
Mintnoii
ec53bf8084 docs: optimize the introduction in both Chinese and English (#4913)
* 优化简介中文文档

1. 优化文案及病句
2. 统一格式

* Optimize the introduction English document

1. Optimize copywriting and sentences
2. Uniform format
2024-11-17 20:54:28 +08:00
Netfan
b87d41bada fix: adjust useWatermark logic (#4896) 2024-11-15 14:06:13 +08:00
100 changed files with 3231 additions and 4052 deletions

View File

@@ -62,7 +62,7 @@ body:
description: Before submitting the issue, please make sure you do the following
# description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com).
options:
- label: Read the [docs](https://anncwb.github.io/vue-vben-admin-doc/)
- label: Read the [docs](https://doc.vben.pro/)
required: true
- label: Ensure the code is up to date. (Some issues have been fixed in the latest version)
required: true

View File

@@ -62,7 +62,7 @@ body:
label: Validations
description: Before submitting the issue, please make sure you do the following
options:
- label: Read the [docs](https://anncwb.github.io/vue-vben-admin-doc/)
- label: Read the [docs](https://doc.vben.pro/)
required: true
- label: Ensure the code is up to date. (Some issues have been fixed in the latest version)
required: true

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/web-antd",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { globalShareState } from '@vben/common-ui';
import { ApiSelect, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
@@ -48,12 +48,14 @@ const withDefaultPlaceholder = <T extends Component>(
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiSelect'
| 'AutoComplete'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'DefaultButton'
| 'Divider'
| 'IconPicker'
| 'Input'
| 'InputNumber'
| 'InputPassword'
@@ -77,7 +79,20 @@ async function initComponentAdapter() {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: (props, { attrs, slots }) => {
return h(
ApiSelect,
{
...props,
...attrs,
component: Select,
loadingSlot: 'suffixIcon',
visibleEvent: 'onDropdownVisibleChange',
modelField: 'value',
},
slots,
);
},
AutoComplete,
Checkbox,
CheckboxGroup,
@@ -87,6 +102,7 @@ async function initComponentAdapter() {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
IconPicker,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/web-ele",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -8,19 +8,21 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { globalShareState } from '@vben/common-ui';
import { ApiSelect, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
ElButton,
ElCheckbox,
ElCheckboxGroup,
ElDatePicker,
ElDivider,
ElInput,
ElInputNumber,
ElNotification,
ElRadioGroup,
ElSelect,
ElSelectV2,
ElSpace,
ElSwitch,
ElTimePicker,
@@ -40,10 +42,12 @@ const withDefaultPlaceholder = <T extends Component>(
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiSelect'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'IconPicker'
| 'Input'
| 'InputNumber'
| 'RadioGroup'
@@ -60,11 +64,23 @@ async function initComponentAdapter() {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: (props, { attrs, slots }) => {
return h(
ApiSelect,
{
...props,
...attrs,
component: ElSelectV2,
loadingSlot: 'loading',
visibleEvent: 'onDropdownVisibleChange',
},
slots,
);
},
Checkbox: ElCheckbox,
CheckboxGroup: ElCheckboxGroup,
// 自定义默认按钮
DefaulButton: (props, { attrs, slots }) => {
DefaultButton: (props, { attrs, slots }) => {
return h(ElButton, { ...props, attrs, type: 'info' }, slots);
},
// 自定义主要按钮
@@ -72,6 +88,7 @@ async function initComponentAdapter() {
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
},
Divider: ElDivider,
IconPicker,
Input: withDefaultPlaceholder(ElInput, 'input'),
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
RadioGroup: ElRadioGroup,
@@ -79,6 +96,7 @@ async function initComponentAdapter() {
Space: ElSpace,
Switch: ElSwitch,
TimePicker: ElTimePicker,
DatePicker: ElDatePicker,
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
Upload: ElUpload,
};

View File

@@ -7,6 +7,7 @@ import '@vben/styles';
import '@vben/styles/ele';
import { useTitle } from '@vueuse/core';
import { ElLoading } from 'element-plus';
import { $t, setupI18n } from '#/locales';
@@ -19,6 +20,9 @@ async function bootstrap(namespace: string) {
await initComponentAdapter();
const app = createApp(App);
// 注册Element Plus提供的v-loading指令
app.directive('loading', ElLoading.directive);
// 国际化 i18n 配置
await setupI18n(app);

View File

@@ -1,4 +1,6 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { Page } from '@vben/common-ui';
import {
@@ -6,6 +8,7 @@ import {
ElCard,
ElMessage,
ElNotification,
ElSegmented,
ElSpace,
ElTable,
} from 'element-plus';
@@ -47,6 +50,10 @@ const tableData = [
{ prop1: '5', prop2: 'E' },
{ prop1: '6', prop2: 'F' },
];
const segmentedValue = ref('Mon');
const segmentedOptions = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
</script>
<template>
@@ -54,7 +61,8 @@ const tableData = [
description="支持多语言,主题功能集成切换等"
title="Element Plus组件使用演示"
>
<ElCard class="mb-5">
<div class="flex flex-wrap gap-5">
<ElCard class="mb-5 w-auto">
<template #header> 按钮 </template>
<ElSpace>
<ElButton text>Text</ElButton>
@@ -66,7 +74,7 @@ const tableData = [
<ElButton type="danger"> Error </ElButton>
</ElSpace>
</ElCard>
<ElCard class="mb-5">
<ElCard class="mb-5 w-80">
<template #header> Message </template>
<ElSpace>
<ElButton type="info" @click="info"> 信息 </ElButton>
@@ -75,7 +83,7 @@ const tableData = [
<ElButton type="success" @click="success"> 成功 </ElButton>
</ElSpace>
</ElCard>
<ElCard class="mb-5">
<ElCard class="mb-5 w-80">
<template #header> Notification </template>
<ElSpace>
<ElButton type="info" @click="notify('info')"> 信息 </ElButton>
@@ -84,11 +92,26 @@ const tableData = [
<ElButton type="success" @click="notify('success')"> 成功 </ElButton>
</ElSpace>
</ElCard>
<ElCard class="mb-5">
<ElCard class="mb-5 w-auto">
<template #header> Segmented </template>
<ElSegmented
v-model="segmentedValue"
:options="segmentedOptions"
size="large"
/>
</ElCard>
<ElCard class="mb-5 w-80">
<template #header> V-Loading </template>
<div class="flex size-72 items-center justify-center" v-loading="true">
一些演示的内容
</div>
</ElCard>
<ElCard class="mb-5 w-80">
<ElTable :data="tableData" stripe>
<ElTable.TableColumn label="测试列1" prop="prop1" />
<ElTable.TableColumn label="测试列2" prop="prop2" />
</ElTable>
</ElCard>
</div>
</Page>
</template>

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/web-naive",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { globalShareState } from '@vben/common-ui';
import { ApiSelect, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
@@ -42,10 +42,12 @@ const withDefaultPlaceholder = <T extends Component>(
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiSelect'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'IconPicker'
| 'Input'
| 'InputNumber'
| 'RadioGroup'
@@ -63,6 +65,18 @@ async function initComponentAdapter() {
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: (props, { attrs, slots }) => {
return h(
ApiSelect,
{
...props,
...attrs,
component: NSelect,
modelField: 'value',
},
slots,
);
},
Checkbox: NCheckbox,
CheckboxGroup: NCheckboxGroup,
DatePicker: NDatePicker,
@@ -75,6 +89,7 @@ async function initComponentAdapter() {
return h(NButton, { ...props, attrs, type: 'primary' }, slots);
},
Divider: NDivider,
IconPicker,
Input: withDefaultPlaceholder(NInput, 'input'),
InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
RadioGroup: NRadioGroup,

View File

@@ -124,7 +124,7 @@ function sidebarCommercial(): DefaultTheme.SidebarItem[] {
return [
{
link: 'community',
text: '社区',
text: '交流群',
},
{
link: 'technical-support',
@@ -148,6 +148,16 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
},
],
},
{
collapsed: false,
text: '布局组件',
items: [
{
link: 'layout-ui/page',
text: 'Page 页面',
},
],
},
{
collapsed: false,
text: '通用组件',
@@ -266,7 +276,7 @@ function nav(): DefaultTheme.NavItem[] {
},
{
link: '/commercial/community',
text: '👨‍👦‍👦 社区',
text: '👨‍👦‍👦 交流群',
// items: [
// {
// link: 'https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=22ySzj7pKiw&businessType=9&from=246610&biz=ka&mainSourceId=share&subSourceId=others&jumpsource=shorturl#/pc',

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/docs",
"version": "5.4.7",
"version": "5.5.0",
"private": true,
"scripts": {
"build": "vitepress build",

View File

@@ -20,7 +20,10 @@
::: tip
因为微信群人数有限制,加微信群前,你可以通过[赞助](../sponsor/personal.md)任意金额,主动发送截图给作者,备注`加入微信群`即可。
因为微信群人数有限制,加微信群要求:
- 通过[赞助](../sponsor/personal.md)任意金额。
- 发送赞助`截图`,备注`加入微信群`即可。
:::

View File

@@ -88,6 +88,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` |
| confirmText | 确认按钮文本 | `string\|slot` | `确认` |
| cancelText | 取消按钮文本 | `string\|slot` | `取消` |
| placement | 抽屉弹出位置 | `'left'\|'right'\|'top'\|'bottom'` | `right` |
| showCancelButton | 显示取消按钮 | `boolean` | `true` |
| showConfirmButton | 显示确认按钮文本 | `boolean` | `true` |
| class | modal的class宽度通过这个配置 | `string` | - |

View File

@@ -87,7 +87,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { globalShareState } from '@vben/common-ui';
import { globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
@@ -149,6 +149,7 @@ export type ComponentType =
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| 'IconPicker';
| BaseFormComponentType;
async function initComponentAdapter() {
@@ -166,6 +167,7 @@ async function initComponentAdapter() {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
IconPicker,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
@@ -419,7 +421,7 @@ export interface FormSchema<
help?: string;
/** 表单项 */
label?: string;
// 自定义组件内部渲染
/** 自定义组件内部渲染 */
renderComponentContent?: RenderComponentContentType;
/** 字段规则 */
rules?: FormSchemaRuleType;
@@ -500,3 +502,20 @@ import { z } from '#/adapter/form';
});
}
```
## Slots
可以使用以下插槽在表单中插入自定义的内容
| 插槽名 | 描述 |
| ------------- | ------------------ |
| reset-before | 重置按钮之前的位置 |
| submit-before | 提交按钮之前的位置 |
| expand-before | 展开按钮之前的位置 |
| expand-after | 展开按钮之后的位置 |
::: tip 字段插槽
除了以上内置插槽之外,`schema`属性中每个字段的`fieldName`都可以作为插槽名称,这些字段插槽的优先级高于`component`定义的组件。也就是说,当提供了与`fieldName`同名的插槽时,这些插槽的内容将会作为这些字段的组件,此时`component`的值将会被忽略。
:::

View File

@@ -93,13 +93,14 @@ const [Modal, modalApi] = useVbenModal({
| modal | 显示遮罩 | `boolean` | `true` |
| header | 显示header | `boolean` | `true` |
| footer | 显示footer | `boolean\|slot` | `true` |
| confirmDisabled | 禁用确认按钮 | `boolean` | `false` |
| confirmLoading | 确认按钮loading状态 | `boolean` | `false` |
| closeOnClickModal | 点击遮罩关闭弹窗 | `boolean` | `true` |
| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` |
| confirmText | 确认按钮文本 | `string\|slot` | `确认` |
| cancelText | 取消按钮文本 | `string\|slot` | `取消` |
| showCancelButton | 显示取消按钮 | `boolean` | `true` |
| showConfirmButton | 显示确认按钮文本 | `boolean` | `true` |
| showConfirmButton | 显示确认按钮 | `boolean` | `true` |
| class | modal的class宽度通过这个配置 | `string` | - |
| contentClass | modal内容区域的class | `string` | - |
| footerClass | modal底部区域的class | `string` | - |

View File

@@ -6,6 +6,10 @@
:::
## 布局组件
布局组件一般在页面内容区域用作顶层容器组件,提供一些统一的布局样式和基本功能。
## 通用组件
通用组件是一些常用的组件,比如弹窗、抽屉、表单等。大部分基于 `Tailwind CSS` 实现,可适用于不同 UI 组件库的应用。

View File

@@ -0,0 +1,45 @@
---
outline: deep
---
# Page 常规页面组件
提供一个常规页面布局的组件,包括头部、内容区域、底部三个部分。
::: info 写在前面
本组件是一个基本布局组件。如果有更多的通用页面布局需求(比如双列布局等),可以根据实际需求自行封装。
:::
## 基础用法
`Page`作为你的业务页面的根组件即可。
### Props
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| title | 页面标题 | `string\|slot` | - |
| description | 页面描述(标题下的内容) | `string\|slot` | - |
| contentClass | 内容区域的class | `string` | - |
| headerClass | 头部区域的class | `string` | - |
| footerClass | 底部区域的class | `string` | - |
| autoContentHeight | 自动调整内容区域的高度 | `boolean` | `false` |
| fixedHeader | 固定头部在页面内容区域顶部,在滚动时保持可见 | `boolean` | `false` |
::: tip 注意
如果`title``description``extra`三者均未提供有效内容(通过`props`或者`slots`均可),则页面头部区域不会渲染。
:::
### Slots
| 插槽名称 | 描述 |
| ----------- | ------------ |
| default | 页面内容 |
| title | 页面标题 |
| description | 页面描述 |
| extra | 页面头部右侧 |
| footer | 页面底部 |

View File

@@ -18,7 +18,7 @@
- **Permission Validation**: Comprehensive permission validation solutions, including button-level permission control.
- **Multi-Theme**: Built-in multiple theme configurations & dark mode to meet personalized needs.
- **Dynamic Menu**: Supports dynamic menus that can display based on permissions.
- **Mock Data**: High-performance local Mock data solution based on Nitro.
- **Mock Data**: High-performance local Mock data solution based on `Nitro`.
- **Rich Components**: Provides a wide range of components to meet most business needs.
- **Standardization**: Code quality is ensured with tools like `ESLint`, `Prettier`, `Stylelint`, `Publint`, and `CSpell`.
- **Engineering**: Development efficiency is improved with tools like `Pnpm Monorepo`, `TurboRepo`, and `Changeset`.
@@ -26,9 +26,9 @@
## 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**. **Versions below Chrome 80 are not supported**.
**Production environment** supports modern browsers, IE is not supported.
- **Production environment** supports modern browsers, IE is not supported.
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png" alt="IE" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Safari |
| :-: | :-: | :-: | :-: | :-: |
@@ -37,12 +37,10 @@
## Contribution
- [Vben Admin](https://github.com/vbenjs/vue-vben-admin) is still being actively updated. Contributions are welcome to help maintain and improve the project, aiming to create a better mid- to backend solution.
- If you wish to join us, you can provide suggestions or submit pull requests. We will invite you to join based on your activity.
- If you wish to join us, you can start by contributing in the following ways, and we will invite you to join based on your activity.
::: info Join Us
If you wish to join us, you can start by contributing in the following ways, and we will invite you to join based on your activity:
- Regularly submit `PRs`.
- Provide valuable suggestions.
- Participate in discussions and help resolve some `issues`.

View File

@@ -18,7 +18,7 @@
- **权限验证**:完善的权限验证方案,按钮级别权限控制。
- **多主题**:内置多种主题配置和黑暗模式,满足个性化需求。
- **动态菜单**:支持动态菜单,可以根据权限配置显示菜单。
- **Mock 数据**:基于 Nitro 的本地高性能 Mock 数据方案。
- **Mock 数据**:基于 `Nitro` 的本地高性能 Mock 数据方案。
- **组件丰富**:提供了丰富的组件,可以满足大部分的业务需求。
- **规范**:代码规范,使用 `ESLint``Prettier``Stylelint``Publint``CSpell` 等工具保证代码质量。
- **工程化**:使用 `Pnpm Monorepo``TurboRepo``Changeset` 等工具,提高开发效率。
@@ -26,9 +26,9 @@
## 浏览器支持
**本地开发**推荐使用`Chrome 最新版`浏览器,**不支持**`Chrome 80`以下版本。
- **本地开发**推荐使用`Chrome 最新版`浏览器,**不支持**`Chrome 80`以下版本。
**生产环境**支持现代浏览器,不支持 IE。
- **生产环境**支持现代浏览器,不支持 IE。
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png" alt="IE" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Safari |
| :-: | :-: | :-: | :-: | :-: |
@@ -37,13 +37,13 @@
## 贡献
- [Vben Admin](https://github.com/vbenjs/vue-vben-admin) 还在持续更新中,本项目欢迎您的参与,共同维护,逐步完善,打造更好的中后台解决方案。
- 如果你加入我们,可以提供有价值的建议或者参与讨论,协助解决 issue- 如果你想加入我们,可以提供有价值的建议或者参与讨论,协助解决 issue,我们会根据你的活跃度邀请你加入。
- 如果你有兴趣加入我们,可以通过以下方式开始,我们会根据你的活跃度邀请你加入。
::: info 加入我们
- 长期提交 `PR`
- 提供一些好的建议。
- 参与讨论,帮助解决一些 `issue`
- 提供有价值的建议。
- 参与讨论,帮助解决 `issue`
- 共同维护文档。
:::

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/commitlint-config",
"version": "5.4.7",
"version": "5.5.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -18,6 +18,7 @@ export async function unicorn(): Promise<Linter.Config[]> {
'unicorn/better-regex': 'off',
'unicorn/consistent-destructuring': 'off',
'unicorn/consistent-function-scoping': 'off',
'unicorn/expiring-todo-comments': 'off',
'unicorn/filename-case': 'off',
'unicorn/import-style': 'off',
'unicorn/no-array-for-each': 'off',

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/stylelint-config",
"version": "5.4.7",
"version": "5.5.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/node-utils",
"version": "5.4.7",
"version": "5.5.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/tailwind-config",
"version": "5.4.7",
"version": "5.5.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/tsconfig",
"version": "5.4.7",
"version": "5.5.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/vite-config",
"version": "5.4.7",
"version": "5.5.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -1,5 +1,6 @@
import type { ApplicationPluginOptions } from '../typing';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import { fs } from '@vben/node-utils';
@@ -21,12 +22,11 @@ function getConfFiles() {
const script = process.env.npm_lifecycle_script as string;
const reg = /--mode ([\d_a-z]+)/;
const result = reg.exec(script);
let mode = 'production';
if (result) {
const mode = result[1];
return ['.env', `.env.${mode}`];
mode = result[1] as string;
}
return ['.env', '.env.production'];
return ['.env', '.env.local', `.env.${mode}`, `.env.${mode}.local`];
}
/**
@@ -42,11 +42,14 @@ async function loadEnv<T = Record<string, string>>(
for (const confFile of confFiles) {
try {
const envPath = await fs.readFile(join(process.cwd(), confFile), {
const confFilePath = join(process.cwd(), confFile);
if (existsSync(confFilePath)) {
const envPath = await fs.readFile(confFilePath, {
encoding: 'utf8',
});
const env = dotenv.parse(envPath);
envConfig = { ...envConfig, ...env };
}
} catch (error) {
console.error(`Error while parsing ${confFile}`, error);
}

View File

@@ -1,6 +1,6 @@
{
"name": "vben-admin-monorepo",
"version": "5.4.7",
"version": "5.5.0",
"private": true,
"keywords": [
"monorepo",
@@ -99,7 +99,7 @@
"node": ">=20.10.0",
"pnpm": ">=9.12.0"
},
"packageManager": "pnpm@9.13.0",
"packageManager": "pnpm@9.14.4",
"pnpm": {
"peerDependencyRules": {
"allowedVersions": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/design",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
@@ -28,7 +28,7 @@
".": {
"types": "./src/index.ts",
"development": "./src/index.ts",
"default": "./dist/style.css"
"default": "./dist/design.css"
}
},
"publishConfig": {

View File

@@ -58,6 +58,8 @@
/* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */
--accent: 216 5% 19%;
--accent-dark: 240 0% 22%;
--accent-darker: 240 0% 26%;
--accent-lighter: 216 5% 12%;
--accent-hover: 216 5% 24%;
--accent-foreground: 0 0% 98%;

View File

@@ -58,6 +58,8 @@
/* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */
--accent: 240 5% 96%;
--accent-dark: 216 14% 93%;
--accent-darker: 216 11% 91%;
--accent-lighter: 240 0% 98%;
--accent-hover: 200deg 10% 90%;
--accent-foreground: 240 6% 10%;

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/icons",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/shared",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
@@ -81,19 +81,21 @@
"dependencies": {
"@ctrl/tinycolor": "catalog:",
"@tanstack/vue-store": "catalog:",
"@types/lodash.get": "catalog:",
"@vue/shared": "catalog:",
"clsx": "catalog:",
"dayjs": "catalog:",
"defu": "catalog:",
"lodash.clonedeep": "catalog:",
"lodash.get": "catalog:",
"lodash.isequal": "catalog:",
"nprogress": "catalog:",
"tailwind-merge": "catalog:",
"theme-colors": "catalog:"
},
"devDependencies": {
"@types/lodash.clonedeep": "catalog:",
"@types/lodash.get": "catalog:",
"@types/lodash.isequal": "catalog:",
"@types/nprogress": "catalog:"
}
}

View File

@@ -15,3 +15,5 @@ export * from './update-css-variables';
export * from './util';
export * from './window';
export { default as cloneDeep } from 'lodash.clonedeep';
export { default as get } from 'lodash.get';
export { default as isEqual } from 'lodash.isequal';

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/typings",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/composables",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/preferences",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/form-ui",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -91,6 +91,11 @@ function handleRangeTimeValue(values: Record<string, any>) {
fieldMappingTime.forEach(
([field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD']) => {
if (startTimeKey && endTimeKey && values[field] === null) {
delete values[startTimeKey];
delete values[endTimeKey];
}
if (!values[field]) {
delete values[field];
return;

View File

@@ -13,13 +13,13 @@ import { toRaw } from 'vue';
import { Store } from '@vben-core/shared/store';
import {
bindMethods,
createMerge,
isFunction,
isObject,
mergeWithArrayOverride,
StateHandler,
} from '@vben-core/shared/utils';
import { objectPick } from '@vueuse/core';
function getDefaultState(): VbenFormProps {
return {
actionWrapperClass: '',
@@ -185,7 +185,7 @@ export class FormApi {
const fieldSet = new Set(fields);
const schema = this.state?.schema ?? [];
const filterSchema = schema.filter((item) => fieldSet.has(item.fieldName));
const filterSchema = schema.filter((item) => !fieldSet.has(item.fieldName));
this.setState({
schema: filterSchema,
@@ -250,8 +250,17 @@ export class FormApi {
form.setValues(fields, shouldValidate);
return;
}
const fieldNames = this.state?.schema?.map((item) => item.fieldName) ?? [];
const filteredFields = objectPick(fields, fieldNames);
const fieldMergeFn = createMerge((obj, key, value) => {
if (key in obj) {
obj[key] =
!Array.isArray(obj[key]) && isObject(obj[key])
? fieldMergeFn(obj[key], value)
: value;
}
return true;
});
const filteredFields = fieldMergeFn(fields, form.values);
form.setValues(filteredFields, shouldValidate);
}

View File

@@ -209,8 +209,9 @@ function fieldBindEvent(slotProps: Record<string, any>) {
if (modelValue && isObject(modelValue) && bindEventField) {
value = isEventObjectLike(modelValue)
? modelValue?.target?.[bindEventField]
: modelValue;
: (modelValue?.[bindEventField] ?? modelValue);
}
if (bindEventField) {
return {
[`onUpdate:${bindEventField}`]: handler,
@@ -223,6 +224,7 @@ function fieldBindEvent(slotProps: Record<string, any>) {
if (!shouldUnwrap) {
return onChange?.(e);
}
return onChange?.(e?.target?.[bindEventField] ?? e);
},
onInput: () => {},

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/layout-ui",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -63,7 +63,7 @@ const logoStyle = computed((): CSSProperties => {
<header
:class="theme"
:style="style"
class="border-border bg-header top-0 flex w-full flex-[0_0_auto] items-center border-b transition-[margin-top] duration-200"
class="border-border bg-header top-0 flex w-full flex-[0_0_auto] items-center border-b pl-2 transition-[margin-top] duration-200"
>
<div v-if="slots.logo" :style="logoStyle">
<slot name="logo"></slot>

View File

@@ -191,7 +191,10 @@ watchEffect(() => {
function calcMenuWidthStyle(isHiddenDom: boolean): CSSProperties {
const { extraWidth, fixedExtra, isSidebarMixed, show, width } = props;
let widthValue = `${width + (isSidebarMixed && fixedExtra && extraVisible.value ? extraWidth : 0)}px`;
let widthValue =
width === 0
? '0px'
: `${width + (isSidebarMixed && fixedExtra && extraVisible.value ? extraWidth : 0)}px`;
const { collapseWidth } = props;

View File

@@ -192,7 +192,7 @@ const headerFixed = computed(() => {
});
const showSidebar = computed(() => {
return isSideMode.value && sidebarEnable.value;
return isSideMode.value && sidebarEnable.value && !props.sidebarHidden;
});
/**
@@ -503,7 +503,7 @@ function handleHeaderToggle() {
<div
ref="contentRef"
class="flex flex-1 flex-col overflow-hidden transition-all duration-300 ease-in"
class="flex flex-1 flex-col transition-all duration-300 ease-in"
>
<div
:class="[
@@ -533,7 +533,7 @@ function handleHeaderToggle() {
<template #toggle-button>
<VbenIconButton
v-if="showHeaderToggleButton"
class="my-0 ml-2 mr-1 rounded-md"
class="my-0 mr-1 rounded-md"
@click="handleHeaderToggle"
>
<Menu class="size-4" />

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/menu-ui",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -41,6 +41,7 @@ export class DrawerApi {
loading: false,
modal: true,
openAutoFocus: false,
placement: 'right',
showCancelButton: true,
showConfirmButton: true,
title: '',

View File

@@ -4,6 +4,8 @@ import type { DrawerApi } from './drawer-api';
import type { Component, Ref } from 'vue';
export type DrawerPlacement = 'bottom' | 'left' | 'right' | 'top';
export interface DrawerProps {
/**
* 取消按钮文字
@@ -72,6 +74,12 @@ export interface DrawerProps {
* 是否自动聚焦
*/
openAutoFocus?: boolean;
/**
* 抽屉位置
* @default right
*/
placement?: DrawerPlacement;
/**
* 是否显示取消按钮
* @default true

View File

@@ -62,6 +62,7 @@ const {
loading: showLoading,
modal,
openAutoFocus,
placement,
showCancelButton,
showConfirmButton,
title,
@@ -119,11 +120,13 @@ function handleFocusOutside(e: Event) {
<SheetContent
:class="
cn('flex w-[520px] flex-col', drawerClass, {
'!w-full': isMobile,
'!w-full': isMobile || placement === 'bottom' || placement === 'top',
'max-h-[100vh]': placement === 'bottom' || placement === 'top',
})
"
:modal="modal"
:open="state?.isOpen"
:side="placement"
@close-auto-focus="handleFocusOutside"
@escape-key-down="escapeKeyDown"
@focus-outside="handleFocusOutside"

View File

@@ -41,6 +41,7 @@ export class ModalApi {
class: '',
closeOnClickModal: true,
closeOnPressEscape: true,
confirmDisabled: false,
confirmLoading: false,
contentClass: '',
draggable: false,

View File

@@ -35,6 +35,10 @@ export interface ModalProps {
* @default true
*/
closeOnPressEscape?: boolean;
/**
* 禁用确认按钮
*/
confirmDisabled?: boolean;
/**
* 确定按钮 loading
* @default false

View File

@@ -59,6 +59,7 @@ const {
closable,
closeOnClickModal,
closeOnPressEscape,
confirmDisabled,
confirmLoading,
confirmText,
contentClass,
@@ -235,7 +236,7 @@ function handleFocusOutside(e: Event) {
ref="wrapperRef"
:class="
cn('relative min-h-40 flex-1 overflow-y-auto p-3', contentClass, {
'overflow-hidden': showLoading,
'pointer-events-none overflow-hidden': showLoading,
})
"
>
@@ -285,6 +286,7 @@ function handleFocusOutside(e: Event) {
<component
:is="components.PrimaryButton || VbenButton"
v-if="showConfirmButton"
:disabled="confirmDisabled"
:loading="confirmLoading"
@click="() => modalApi?.onConfirm()"
>

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/shadcn-ui",
"version": "5.4.7",
"version": "5.5.0",
"#main": "./dist/index.mjs",
"#module": "./dist/index.mjs",
"homepage": "https://github.com/vbenjs/vue-vben-admin",

View File

@@ -32,8 +32,8 @@ const style = computed(() => {
.dark .vben-spine-text {
background:
radial-gradient(circle at center, rgb(24 24 26 / 80%), transparent) -200% 50% /
200% 100% no-repeat,
radial-gradient(circle at center, rgb(24 24 26 / 80%), transparent) -200%
50% / 200% 100% no-repeat,
#f4f4f4;
}

View File

@@ -48,13 +48,16 @@ const delegatedProps = computed(() => {
const forwarded = useForwardPropsEmits(delegatedProps, emits);
const contentRef = ref<InstanceType<typeof DialogContent> | null>(null);
function onAnimationEnd() {
function onAnimationEnd(event: AnimationEvent) {
// 只有在 contentRef 的动画结束时才触发 opened/closed 事件
if (event.target === contentRef.value?.$el) {
if (props.open) {
emits('opened');
} else {
emits('closed');
}
}
}
defineExpose({
getContentRef: () => contentRef.value,
});

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/tabs-ui",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/constants",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/access",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/common-ui",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
@@ -22,6 +22,7 @@
"dependencies": {
"@vben-core/form-ui": "workspace:*",
"@vben-core/popup-ui": "workspace:*",
"@vben-core/preferences": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*",
"@vben-core/shared": "workspace:*",
"@vben/constants": "workspace:*",

View File

@@ -0,0 +1,182 @@
<script lang="ts" setup>
import type { AnyPromiseFunction } from '@vben/types';
import { computed, ref, unref, useAttrs, type VNode, watch } from 'vue';
import { LoaderCircle } from '@vben/icons';
import { get, isEqual, isFunction } from '@vben-core/shared/utils';
import { objectOmit } from '@vueuse/core';
type OptionsItem = {
[name: string]: any;
disabled?: boolean;
label?: string;
value?: string;
};
interface Props {
// 组件
component: VNode;
numberToString?: boolean;
api?: (arg?: any) => Promise<OptionsItem[] | Record<string, any>>;
params?: Record<string, any>;
resultField?: string;
labelField?: string;
valueField?: string;
immediate?: boolean;
alwaysLoad?: boolean;
beforeFetch?: AnyPromiseFunction<any, any>;
afterFetch?: AnyPromiseFunction<any, any>;
options?: OptionsItem[];
// 尾部插槽
loadingSlot?: string;
// 可见时触发的事件名
visibleEvent?: string;
modelField?: string;
}
defineOptions({ name: 'ApiSelect', inheritAttrs: false });
const props = withDefaults(defineProps<Props>(), {
labelField: 'label',
valueField: 'value',
resultField: '',
visibleEvent: '',
numberToString: false,
params: () => ({}),
immediate: true,
alwaysLoad: false,
loadingSlot: '',
beforeFetch: undefined,
afterFetch: undefined,
modelField: 'modelValue',
api: undefined,
options: () => [],
});
const emit = defineEmits<{
optionsChange: [OptionsItem[]];
}>();
const modelValue = defineModel({ default: '' });
const attrs = useAttrs();
const refOptions = ref<OptionsItem[]>([]);
const loading = ref(false);
// 首次是否加载过了
const isFirstLoaded = ref(false);
const getOptions = computed(() => {
const { labelField, valueField, numberToString } = props;
const data: OptionsItem[] = [];
const refOptionsData = unref(refOptions);
for (const next of refOptionsData) {
if (next) {
const value = get(next, valueField);
data.push({
...objectOmit(next, [labelField, valueField]),
label: get(next, labelField),
value: numberToString ? `${value}` : value,
});
}
}
return data.length > 0 ? data : props.options;
});
const bindProps = computed(() => {
return {
[props.modelField]: unref(modelValue),
[`onUpdate:${props.modelField}`]: (val: string) => {
modelValue.value = val;
},
...objectOmit(attrs, ['onUpdate:value']),
...(props.visibleEvent
? {
[props.visibleEvent]: handleFetchForVisible,
}
: {}),
};
});
async function fetchApi() {
let { api, beforeFetch, afterFetch, params, resultField } = props;
if (!api || !isFunction(api) || loading.value) {
return;
}
refOptions.value = [];
try {
loading.value = true;
if (beforeFetch && isFunction(beforeFetch)) {
params = (await beforeFetch(params)) || params;
}
let res = await api(params);
if (afterFetch && isFunction(afterFetch)) {
res = (await afterFetch(res)) || res;
}
isFirstLoaded.value = true;
if (Array.isArray(res)) {
refOptions.value = res;
emitChange();
return;
}
if (resultField) {
refOptions.value = get(res, resultField) || [];
}
emitChange();
} catch (error) {
console.warn(error);
// reset status
isFirstLoaded.value = false;
} finally {
loading.value = false;
}
}
async function handleFetchForVisible(visible: boolean) {
if (visible) {
if (props.alwaysLoad) {
await fetchApi();
} else if (!props.immediate && !unref(isFirstLoaded)) {
await fetchApi();
}
}
}
watch(
() => props.params,
(value, oldValue) => {
if (isEqual(value, oldValue)) {
return;
}
fetchApi();
},
{ deep: true, immediate: props.immediate },
);
function emitChange() {
emit('optionsChange', unref(getOptions));
}
</script>
<template>
<div v-bind="{ ...$attrs }">
<component
:is="component"
v-bind="bindProps"
:options="getOptions"
:placeholder="$attrs.placeholder"
>
<template v-for="item in Object.keys($slots)" #[item]="data">
<slot :name="item" v-bind="data || {}"></slot>
</template>
<template v-if="loadingSlot && loading" #[loadingSlot]>
<LoaderCircle class="animate-spin" />
</template>
</component>
</div>
</template>

View File

@@ -0,0 +1 @@
export { default as ApiSelect } from './api-select.vue';

View File

@@ -1,46 +0,0 @@
import { mount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest';
import { EllipsisText } from '..';
describe('ellipsis-text.vue', () => {
it('renders the correct content and truncates text', async () => {
const wrapper = mount(EllipsisText, {
props: {
line: 1,
title: 'Test Title',
},
slots: {
default: 'This is a very long text that should be truncated.',
},
});
expect(wrapper.text()).toContain('This is a very long text');
// 检查 ellipsis 是否应用了正确的 class
const ellipsis = wrapper.find('.truncate');
expect(ellipsis.exists()).toBe(true);
});
it('expands text on click if expand is true', async () => {
const wrapper = mount(EllipsisText, {
props: {
expand: true,
line: 1,
},
slots: {
default: 'This is a very long text that should be truncated.',
},
});
const ellipsis = wrapper.find('.truncate');
// 点击 ellipsis应该触发 expandChange参数为 false
await ellipsis.trigger('click');
expect(wrapper.emitted('expandChange')).toBeTruthy();
expect(wrapper.emitted('expandChange')?.[0]).toEqual([true]);
// 再次点击,应该触发 expandChange参数为 false
await ellipsis.trigger('click');
expect(wrapper.emitted('expandChange')?.length).toBe(2);
expect(wrapper.emitted('expandChange')?.[1]).toEqual([false]);
});
});

View File

@@ -1,10 +1,12 @@
<script setup lang="ts">
import { ref, useTemplateRef, watch, watchEffect } from 'vue';
import { computed, ref, watch, watchEffect } from 'vue';
import { usePagination } from '@vben/hooks';
import { EmptyIcon, Grip } from '@vben/icons';
import { EmptyIcon, Grip, listIcons } from '@vben/icons';
import { $t } from '@vben/locales';
import {
Button,
Input,
Pagination,
PaginationEllipsis,
PaginationFirst,
@@ -18,9 +20,11 @@ import {
VbenPopover,
} from '@vben-core/shadcn-ui';
import { refDebounced } from '@vueuse/core';
interface Props {
value?: string;
pageSize?: number;
prefix?: string;
/**
* 图标列表
*/
@@ -28,48 +32,65 @@ interface Props {
}
const props = withDefaults(defineProps<Props>(), {
value: '',
prefix: 'ant-design',
pageSize: 36,
icons: () => [],
});
const emit = defineEmits<{
change: [string];
'update:value': [string];
}>();
const refTrigger = useTemplateRef<HTMLElement>('refTrigger');
const currentSelect = ref('');
const currentList = ref(props.icons);
const currentPage = ref(1);
const modelValue = defineModel({ default: '', type: String });
watch(
() => props.icons,
(newIcons) => {
currentList.value = newIcons;
},
{ immediate: true },
const visible = ref(false);
const currentSelect = ref('');
const currentPage = ref(1);
const keyword = ref('');
const keywordDebounce = refDebounced(keyword, 300);
const currentList = computed(() => {
try {
if (props.prefix) {
const icons = listIcons('', props.prefix);
if (icons.length === 0) {
console.warn(`No icons found for prefix: ${props.prefix}`);
}
return icons;
} else {
return props.icons;
}
} catch (error) {
console.error('Failed to load icons:', error);
return [];
}
});
const showList = computed(() => {
return currentList.value.filter((item) =>
item.includes(keywordDebounce.value),
);
});
const { paginationList, total, setCurrentPage } = usePagination(
currentList,
showList,
props.pageSize,
);
watchEffect(() => {
currentSelect.value = props.value;
currentSelect.value = modelValue.value;
});
watch(
() => currentSelect.value,
(v) => {
emit('update:value', v);
emit('change', v);
},
);
const handleClick = (icon: string) => {
currentSelect.value = icon;
modelValue.value = icon;
close();
};
const handlePageChange = (page: number) => {
@@ -77,22 +98,46 @@ const handlePageChange = (page: number) => {
setCurrentPage(page);
};
function changeOpenState() {
refTrigger.value?.click?.();
function toggleOpenState() {
visible.value = !visible.value;
}
defineExpose({ changeOpenState });
function open() {
visible.value = true;
}
function close() {
visible.value = false;
}
defineExpose({ toggleOpenState, open, close });
</script>
<template>
<VbenPopover
v-model:open="visible"
:content-props="{ align: 'end', alignOffset: -11, sideOffset: 8 }"
content-class="p-0 pt-3"
>
<template #trigger>
<div ref="refTrigger">
<VbenIcon :icon="currentSelect || Grip" class="size-5" />
<slot :close="close" :icon="currentSelect" :open="open" name="trigger">
<div class="flex items-center gap-2">
<Input
:value="currentSelect"
class="flex-1 cursor-pointer"
v-bind="$attrs"
:placeholder="$t('ui.iconPicker.placeholder')"
/>
<VbenIcon :icon="currentSelect || Grip" class="size-8" />
</div>
</slot>
</template>
<div class="mb-2 flex w-full">
<Input
v-model="keyword"
:placeholder="$t('ui.iconPicker.search')"
class="mx-2"
/>
</div>
<template v-if="paginationList.length > 0">
<div class="grid max-h-[360px] w-full grid-cols-6 justify-items-center">

View File

@@ -1,3 +1,4 @@
export * from './api-select';
export * from './captcha';
export * from './ellipsis-text';
export * from './icon-picker';

View File

@@ -1,5 +1,15 @@
<script setup lang="ts">
import { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue';
import {
computed,
nextTick,
onMounted,
ref,
type StyleValue,
useTemplateRef,
} from 'vue';
import { preferences } from '@vben-core/preferences';
import { cn } from '@vben-core/shared/utils';
interface Props {
title?: string;
@@ -9,6 +19,10 @@ interface Props {
* 根据content可见高度自适应
*/
autoContentHeight?: boolean;
/** 头部固定 */
fixedHeader?: boolean;
headerClass?: string;
footerClass?: string;
}
defineOptions({
@@ -20,6 +34,7 @@ const {
description = '',
autoContentHeight = false,
title = '',
fixedHeader = false,
} = defineProps<Props>();
const headerHeight = ref(0);
@@ -29,6 +44,17 @@ const shouldAutoHeight = ref(false);
const headerRef = useTemplateRef<HTMLDivElement>('headerRef');
const footerRef = useTemplateRef<HTMLDivElement>('footerRef');
const headerStyle = computed<StyleValue>(() => {
return fixedHeader
? {
position: 'sticky',
zIndex: 200,
top:
preferences.header.mode === 'fixed' ? 'var(--vben-header-height)' : 0,
}
: undefined;
});
const contentStyle = computed(() => {
if (autoContentHeight) {
return {
@@ -69,7 +95,16 @@ onMounted(() => {
$slots.extra
"
ref="headerRef"
class="bg-card relative px-6 py-4"
:class="
cn(
'bg-card relative px-6 py-4',
headerClass,
fixedHeader
? 'border-border border-b transition-all duration-200'
: '',
)
"
:style="headerStyle"
>
<slot name="title">
<div v-if="title" class="mb-2 flex text-lg font-semibold">
@@ -95,7 +130,12 @@ onMounted(() => {
<div
v-if="$slots.footer"
ref="footerRef"
class="bg-card align-center absolute bottom-0 left-0 right-0 flex px-6 py-4"
:class="
cn(
footerClass,
'bg-card align-center absolute bottom-0 left-0 right-0 flex px-6 py-4',
)
"
>
<slot name="footer"></slot>
</div>

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/hooks",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -260,6 +260,9 @@ export function useElementPlusDesignTokens() {
'--el-fill-color-light': getCssVariableValue('--accent'),
'--el-fill-color-lighter': getCssVariableValue('--accent-lighter'),
'--el-fill-color-dark': getCssVariableValue('--accent-dark'),
'--el-fill-color-darker': getCssVariableValue('--accent-darker'),
// 解决ElLoading背景色问题
'--el-mask-color': isDark.value
? 'rgba(0,0,0,.8)'

View File

@@ -1,8 +1,11 @@
import type { Watermark, WatermarkOptions } from 'watermark-js-plus';
import { nextTick, onUnmounted, ref } from 'vue';
import { nextTick, onUnmounted, readonly, ref } from 'vue';
import { updatePreferences } from '@vben/preferences';
const watermark = ref<Watermark>();
const unmountedHooked = ref<boolean>(false);
const cachedOptions = ref<Partial<WatermarkOptions>>({
advancedStyle: {
colorStops: [
@@ -45,7 +48,7 @@ export function useWatermark() {
...options,
};
watermark.value = new Watermark(cachedOptions.value);
updatePreferences({ app: { watermark: true } });
await watermark.value?.create();
}
@@ -62,16 +65,24 @@ export function useWatermark() {
}
function destroyWatermark() {
watermark.value?.destroy();
if (watermark.value) {
watermark.value.destroy();
watermark.value = undefined;
}
updatePreferences({ app: { watermark: false } });
}
// 只在第一次调用时注册卸载钩子,防止重复注册以致于在路由切换时销毁了水印
if (!unmountedHooked.value) {
unmountedHooked.value = true;
onUnmounted(() => {
destroyWatermark();
});
}
return {
destroyWatermark,
updateWatermark,
watermark,
watermark: readonly(watermark),
};
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/layouts",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -110,8 +110,17 @@ const {
sidebarVisible,
} = useMixedMenu();
function wrapperMenus(menus: MenuRecordRaw[]) {
return mapTree(menus, (item) => {
/**
* 包装菜单,翻译菜单名称
* @param menus 原始菜单数据
* @param deep 是否深度包装。对于双列布局,只需要包装第一层,因为更深层的数据会在扩展菜单中重新包装
*/
function wrapperMenus(menus: MenuRecordRaw[], deep: boolean = true) {
return deep
? mapTree(menus, (item) => {
return { ...cloneDeep(item), name: $t(item.name) };
})
: menus.map((item) => {
return { ...cloneDeep(item), name: $t(item.name) };
});
}
@@ -257,7 +266,7 @@ const headerSlots = computed(() => {
<template #mixed-menu>
<LayoutMixedMenu
:active-path="extraActiveMenu"
:menus="wrapperMenus(headerMenus)"
:menus="wrapperMenus(headerMenus, false)"
:rounded="isMenuRounded"
:theme="sidebarTheme"
@default-select="handleDefaultSelect"

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/plugins",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -43,7 +43,11 @@ function extendProxyOption(
const data = await configFn(
params,
{
...customValues,
/**
* 开启toolbarConfig.refresh功能
* 点击刷新按钮 这里的值为PointerEvent 会携带错误参数
*/
...(customValues instanceof PointerEvent ? {} : customValues),
...formValues,
},
...args,

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/request",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/icons",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/locales",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -21,6 +21,10 @@
"pointAriaLabel": "Click point",
"clickInOrder": "Please click in order"
},
"iconPicker": {
"placeholder": "Select an icon",
"search": "Search icon..."
},
"fallback": {
"pageNotFound": "Oops! Page Not Found",
"pageNotFoundDesc": "Sorry, we couldn't find the page you were looking for.",

View File

@@ -21,6 +21,10 @@
"pointAriaLabel": "点击点",
"clickInOrder": "请依次点击"
},
"iconPicker": {
"placeholder": "选择一个图标",
"search": "搜索图标..."
},
"fallback": {
"pageNotFound": "哎呀!未找到页面",
"pageNotFoundDesc": "抱歉,我们无法找到您要找的页面。",

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/preferences",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/stores",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/styles",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/types",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/utils",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/playground",
"version": "5.4.7",
"version": "5.5.0",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { globalShareState } from '@vben/common-ui';
import { ApiSelect, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
@@ -48,12 +48,14 @@ const withDefaultPlaceholder = <T extends Component>(
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiSelect'
| 'AutoComplete'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'DefaultButton'
| 'Divider'
| 'IconPicker'
| 'Input'
| 'InputNumber'
| 'InputPassword'
@@ -78,6 +80,20 @@ async function initComponentAdapter() {
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: (props, { attrs, slots }) => {
return h(
ApiSelect,
{
...props,
...attrs,
component: Select,
loadingSlot: 'suffixIcon',
modelField: 'value',
visibleEvent: 'onVisibleChange',
},
slots,
);
},
AutoComplete,
Checkbox,
CheckboxGroup,
@@ -87,6 +103,7 @@ async function initComponentAdapter() {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
IconPicker,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),

View File

@@ -156,6 +156,18 @@ const routes: RouteRecordRaw[] = [
title: $t('demos.features.hideChildrenInMenu'),
},
children: [
{
name: 'HideChildrenInMenuDemo',
path: '',
component: () =>
import(
'#/views/demos/features/hide-menu-children/children.vue'
),
meta: {
hideInMenu: true,
title: $t('demos.features.hideChildrenInMenu'),
},
},
{
name: 'HideChildrenInMenuChildrenDemo',
path: '/demos/features/hide-menu-children/children',

View File

@@ -81,17 +81,17 @@ async function changeAccount(role: string) {
<Card class="mb-5" title="组件形式控制 - 权限码">
<AccessControl :codes="['AC_100100']" type="code">
<Button class="mr-4"> Super 账号可见 ["AC_1000001"] </Button>
<Button class="mr-4"> Super 账号可见 ["AC_100100"] </Button>
</AccessControl>
<AccessControl :codes="['AC_100030']" type="code">
<Button class="mr-4"> Admin 账号可见 ["AC_100010"] </Button>
<Button class="mr-4"> Admin 账号可见 ["AC_100030"] </Button>
</AccessControl>
<AccessControl :codes="['AC_1000001']" type="code">
<Button class="mr-4"> User 账号可见 ["AC_1000001"] </Button>
</AccessControl>
<AccessControl :codes="['AC_100100', 'AC_100010']" type="code">
<AccessControl :codes="['AC_100100', 'AC_100030']" type="code">
<Button class="mr-4">
Super & Admin 账号可见 ["AC_100100","AC_1000001"]
Super & Admin 账号可见 ["AC_100100","AC_100030"]
</Button>
</AccessControl>
</Card>
@@ -117,35 +117,35 @@ async function changeAccount(role: string) {
<Card class="mb-5" title="函数形式控制">
<Button v-if="hasAccessByCodes(['AC_100100'])" class="mr-4">
Super 账号可见 ["AC_1000001"]
Super 账号可见 ["AC_100100"]
</Button>
<Button v-if="hasAccessByCodes(['AC_100030'])" class="mr-4">
Admin 账号可见 ["AC_100010"]
Admin 账号可见 ["AC_100030"]
</Button>
<Button v-if="hasAccessByCodes(['AC_1000001'])" class="mr-4">
User 账号可见 ["AC_1000001"]
</Button>
<Button v-if="hasAccessByCodes(['AC_100100', 'AC_1000001'])" class="mr-4">
Super & Admin 账号可见 ["AC_100100","AC_1000001"]
<Button v-if="hasAccessByCodes(['AC_100100', 'AC_100030'])" class="mr-4">
Super & Admin 账号可见 ["AC_100100","AC_100030"]
</Button>
</Card>
<Card class="mb-5" title="指令方式 - 权限码">
<Button class="mr-4" v-access:code="['AC_100100']">
Super 账号可见 ["AC_1000001"]
Super 账号可见 ["AC_100100"]
</Button>
<Button class="mr-4" v-access:code="['AC_100030']">
Admin 账号可见 ["AC_100010"]
Admin 账号可见 ["AC_100030"]
</Button>
<Button class="mr-4" v-access:code="['AC_1000001']">
User 账号可见 ["AC_1000001"]
</Button>
<Button class="mr-4" v-access:code="['AC_100100', 'AC_1000001']">
Super & Admin 账号可见 ["AC_100100","AC_1000001"]
<Button class="mr-4" v-access:code="['AC_100100', 'AC_100030']">
Super & Admin 账号可见 ["AC_100100","AC_100030"]
</Button>
</Card>
<Card class="mb-5" title="指令方式 - 角色">
<Card v-if="accessMode === 'frontend'" class="mb-5" title="指令方式 - 角色">
<Button class="mr-4" v-access:role="['super']"> Super 角色可见 </Button>
<Button class="mr-4" v-access:role="['admin']"> Admin 角色可见 </Button>
<Button class="mr-4" v-access:role="['user']"> User 角色可见 </Button>

View File

@@ -1,87 +0,0 @@
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { IconPicker } from '@vben/common-ui';
import { listIcons } from '@vben/icons';
import { Input } from 'ant-design-vue';
import iconsData from './icons.data';
export interface Props {
allowClear?: boolean;
pageSize?: number;
/**
* 可以通过prefix获取系统中使用的图标集
*/
prefix?: string;
readonly?: boolean;
value?: string;
width?: string;
}
// Don't inherit FormItem disabled、placeholder...
defineOptions({
inheritAttrs: false,
});
const props = withDefaults(defineProps<Props>(), {
allowClear: true,
pageSize: 36,
prefix: '',
readonly: false,
value: '',
width: '100%',
});
const refIconPicker = ref();
const currentSelect = ref('');
const currentList = computed(() => {
try {
if (props.prefix) {
const icons = listIcons('', props.prefix);
if (icons.length === 0) {
console.warn(`No icons found for prefix: ${props.prefix}`);
}
return icons;
} else {
const prefix = iconsData.prefix;
return iconsData.icons.map((icon) => `${prefix}:${icon}`);
}
} catch (error) {
console.error('Failed to load icons:', error);
return [];
}
});
const triggerPopover = () => {
refIconPicker.value?.changeOpenState?.();
};
const handleChange = (icon: string) => {
currentSelect.value = icon;
};
</script>
<template>
<Input
v-model:value="currentSelect"
:allow-clear="props.allowClear"
:readonly="props.readonly"
:style="{ width }"
class="cursor-pointer"
placeholder="点击选中图标"
@click="triggerPopover"
>
<template #addonAfter>
<IconPicker
ref="refIconPicker"
:icons="currentList"
:page-size="pageSize"
:value="currentSelect"
@change="handleChange"
/>
</template>
</Input>
</template>

View File

@@ -1,793 +0,0 @@
export default {
icons: [
'account-book-filled',
'account-book-outlined',
'account-book-twotone',
'aim-outlined',
'alert-filled',
'alert-outlined',
'alert-twotone',
'alibaba-outlined',
'align-center-outlined',
'align-left-outlined',
'align-right-outlined',
'alipay-circle-filled',
'alipay-circle-outlined',
'alipay-outlined',
'alipay-square-filled',
'aliwangwang-filled',
'aliwangwang-outlined',
'aliyun-outlined',
'amazon-circle-filled',
'amazon-outlined',
'amazon-square-filled',
'android-filled',
'android-outlined',
'ant-cloud-outlined',
'ant-design-outlined',
'apartment-outlined',
'api-filled',
'api-outlined',
'api-twotone',
'apple-filled',
'apple-outlined',
'appstore-add-outlined',
'appstore-filled',
'appstore-outlined',
'appstore-twotone',
'area-chart-outlined',
'arrow-down-outlined',
'arrow-left-outlined',
'arrow-right-outlined',
'arrow-up-outlined',
'arrows-alt-outlined',
'audio-filled',
'audio-muted-outlined',
'audio-outlined',
'audio-twotone',
'audit-outlined',
'backward-filled',
'backward-outlined',
'bank-filled',
'bank-outlined',
'bank-twotone',
'bar-chart-outlined',
'barcode-outlined',
'bars-outlined',
'behance-circle-filled',
'behance-outlined',
'behance-square-filled',
'behance-square-outlined',
'bell-filled',
'bell-outlined',
'bell-twotone',
'bg-colors-outlined',
'block-outlined',
'bold-outlined',
'book-filled',
'book-outlined',
'book-twotone',
'border-bottom-outlined',
'border-horizontal-outlined',
'border-inner-outlined',
'border-left-outlined',
'border-outer-outlined',
'border-outlined',
'border-right-outlined',
'border-top-outlined',
'border-verticle-outlined',
'borderless-table-outlined',
'box-plot-filled',
'box-plot-outlined',
'box-plot-twotone',
'branches-outlined',
'bug-filled',
'bug-outlined',
'bug-twotone',
'build-filled',
'build-outlined',
'build-twotone',
'bulb-filled',
'bulb-outlined',
'bulb-twotone',
'calculator-filled',
'calculator-outlined',
'calculator-twotone',
'calendar-filled',
'calendar-outlined',
'calendar-twotone',
'camera-filled',
'camera-outlined',
'camera-twotone',
'car-filled',
'car-outlined',
'car-twotone',
'caret-down-filled',
'caret-down-outlined',
'caret-left-filled',
'caret-left-outlined',
'caret-right-filled',
'caret-right-outlined',
'caret-up-filled',
'caret-up-outlined',
'carry-out-filled',
'carry-out-outlined',
'carry-out-twotone',
'check-circle-filled',
'check-circle-outlined',
'check-circle-twotone',
'check-outlined',
'check-square-filled',
'check-square-outlined',
'check-square-twotone',
'chrome-filled',
'chrome-outlined',
'ci-circle-filled',
'ci-circle-outlined',
'ci-circle-twotone',
'ci-outlined',
'ci-twotone',
'clear-outlined',
'clock-circle-filled',
'clock-circle-outlined',
'clock-circle-twotone',
'close-circle-filled',
'close-circle-outlined',
'close-circle-twotone',
'close-outlined',
'close-square-filled',
'close-square-outlined',
'close-square-twotone',
'cloud-download-outlined',
'cloud-filled',
'cloud-outlined',
'cloud-server-outlined',
'cloud-sync-outlined',
'cloud-twotone',
'cloud-upload-outlined',
'cluster-outlined',
'code-filled',
'code-outlined',
'code-sandbox-circle-filled',
'code-sandbox-outlined',
'code-sandbox-square-filled',
'code-twotone',
'codepen-circle-filled',
'codepen-circle-outlined',
'codepen-outlined',
'codepen-square-filled',
'coffee-outlined',
'column-height-outlined',
'column-width-outlined',
'comment-outlined',
'compass-filled',
'compass-outlined',
'compass-twotone',
'compress-outlined',
'console-sql-outlined',
'contacts-filled',
'contacts-outlined',
'contacts-twotone',
'container-filled',
'container-outlined',
'container-twotone',
'control-filled',
'control-outlined',
'control-twotone',
'copy-filled',
'copy-outlined',
'copy-twotone',
'copyright-circle-filled',
'copyright-circle-outlined',
'copyright-circle-twotone',
'copyright-outlined',
'copyright-twotone',
'credit-card-filled',
'credit-card-outlined',
'credit-card-twotone',
'crown-filled',
'crown-outlined',
'crown-twotone',
'customer-service-filled',
'customer-service-outlined',
'customer-service-twotone',
'dash-outlined',
'dashboard-filled',
'dashboard-outlined',
'dashboard-twotone',
'database-filled',
'database-outlined',
'database-twotone',
'delete-column-outlined',
'delete-filled',
'delete-outlined',
'delete-row-outlined',
'delete-twotone',
'delivered-procedure-outlined',
'deployment-unit-outlined',
'desktop-outlined',
'diff-filled',
'diff-outlined',
'diff-twotone',
'dingding-outlined',
'dingtalk-circle-filled',
'dingtalk-outlined',
'dingtalk-square-filled',
'disconnect-outlined',
'dislike-filled',
'dislike-outlined',
'dislike-twotone',
'dollar-circle-filled',
'dollar-circle-outlined',
'dollar-circle-twotone',
'dollar-outlined',
'dollar-twotone',
'dot-chart-outlined',
'double-left-outlined',
'double-right-outlined',
'down-circle-filled',
'down-circle-outlined',
'down-circle-twotone',
'down-outlined',
'down-square-filled',
'down-square-outlined',
'down-square-twotone',
'download-outlined',
'drag-outlined',
'dribbble-circle-filled',
'dribbble-outlined',
'dribbble-square-filled',
'dribbble-square-outlined',
'dropbox-circle-filled',
'dropbox-outlined',
'dropbox-square-filled',
'edit-filled',
'edit-outlined',
'edit-twotone',
'ellipsis-outlined',
'enter-outlined',
'environment-filled',
'environment-outlined',
'environment-twotone',
'euro-circle-filled',
'euro-circle-outlined',
'euro-circle-twotone',
'euro-outlined',
'euro-twotone',
'exception-outlined',
'exclamation-circle-filled',
'exclamation-circle-outlined',
'exclamation-circle-twotone',
'exclamation-outlined',
'expand-alt-outlined',
'expand-outlined',
'experiment-filled',
'experiment-outlined',
'experiment-twotone',
'export-outlined',
'eye-filled',
'eye-invisible-filled',
'eye-invisible-outlined',
'eye-invisible-twotone',
'eye-outlined',
'eye-twotone',
'facebook-filled',
'facebook-outlined',
'fall-outlined',
'fast-backward-filled',
'fast-backward-outlined',
'fast-forward-filled',
'fast-forward-outlined',
'field-binary-outlined',
'field-number-outlined',
'field-string-outlined',
'field-time-outlined',
'file-add-filled',
'file-add-outlined',
'file-add-twotone',
'file-done-outlined',
'file-excel-filled',
'file-excel-outlined',
'file-excel-twotone',
'file-exclamation-filled',
'file-exclamation-outlined',
'file-exclamation-twotone',
'file-filled',
'file-gif-outlined',
'file-image-filled',
'file-image-outlined',
'file-image-twotone',
'file-jpg-outlined',
'file-markdown-filled',
'file-markdown-outlined',
'file-markdown-twotone',
'file-outlined',
'file-pdf-filled',
'file-pdf-outlined',
'file-pdf-twotone',
'file-ppt-filled',
'file-ppt-outlined',
'file-ppt-twotone',
'file-protect-outlined',
'file-search-outlined',
'file-sync-outlined',
'file-text-filled',
'file-text-outlined',
'file-text-twotone',
'file-twotone',
'file-unknown-filled',
'file-unknown-outlined',
'file-unknown-twotone',
'file-word-filled',
'file-word-outlined',
'file-word-twotone',
'file-zip-filled',
'file-zip-outlined',
'file-zip-twotone',
'filter-filled',
'filter-outlined',
'filter-twotone',
'fire-filled',
'fire-outlined',
'fire-twotone',
'flag-filled',
'flag-outlined',
'flag-twotone',
'folder-add-filled',
'folder-add-outlined',
'folder-add-twotone',
'folder-filled',
'folder-open-filled',
'folder-open-outlined',
'folder-open-twotone',
'folder-outlined',
'folder-twotone',
'folder-view-outlined',
'font-colors-outlined',
'font-size-outlined',
'fork-outlined',
'form-outlined',
'format-painter-filled',
'format-painter-outlined',
'forward-filled',
'forward-outlined',
'frown-filled',
'frown-outlined',
'frown-twotone',
'fullscreen-exit-outlined',
'fullscreen-outlined',
'function-outlined',
'fund-filled',
'fund-outlined',
'fund-projection-screen-outlined',
'fund-twotone',
'fund-view-outlined',
'funnel-plot-filled',
'funnel-plot-outlined',
'funnel-plot-twotone',
'gateway-outlined',
'gif-outlined',
'gift-filled',
'gift-outlined',
'gift-twotone',
'github-filled',
'github-outlined',
'gitlab-filled',
'gitlab-outlined',
'global-outlined',
'gold-filled',
'gold-outlined',
'gold-twotone',
'golden-filled',
'google-circle-filled',
'google-outlined',
'google-plus-circle-filled',
'google-plus-outlined',
'google-plus-square-filled',
'google-square-filled',
'group-outlined',
'hdd-filled',
'hdd-outlined',
'hdd-twotone',
'heart-filled',
'heart-outlined',
'heart-twotone',
'heat-map-outlined',
'highlight-filled',
'highlight-outlined',
'highlight-twotone',
'history-outlined',
'home-filled',
'home-outlined',
'home-twotone',
'hourglass-filled',
'hourglass-outlined',
'hourglass-twotone',
'html5-filled',
'html5-outlined',
'html5-twotone',
'idcard-filled',
'idcard-outlined',
'idcard-twotone',
'ie-circle-filled',
'ie-outlined',
'ie-square-filled',
'import-outlined',
'inbox-outlined',
'info-circle-filled',
'info-circle-outlined',
'info-circle-twotone',
'info-outlined',
'insert-row-above-outlined',
'insert-row-below-outlined',
'insert-row-left-outlined',
'insert-row-right-outlined',
'instagram-filled',
'instagram-outlined',
'insurance-filled',
'insurance-outlined',
'insurance-twotone',
'interaction-filled',
'interaction-outlined',
'interaction-twotone',
'issues-close-outlined',
'italic-outlined',
'key-outlined',
'laptop-outlined',
'layout-filled',
'layout-outlined',
'layout-twotone',
'left-circle-filled',
'left-circle-outlined',
'left-circle-twotone',
'left-outlined',
'left-square-filled',
'left-square-outlined',
'left-square-twotone',
'like-filled',
'like-outlined',
'like-twotone',
'line-chart-outlined',
'line-height-outlined',
'line-outlined',
'link-outlined',
'linkedin-filled',
'linkedin-outlined',
'loading-3-quarters-outlined',
'loading-outlined',
'lock-filled',
'lock-outlined',
'lock-twotone',
'login-outlined',
'logout-outlined',
'mac-command-filled',
'mac-command-outlined',
'mail-filled',
'mail-outlined',
'mail-twotone',
'man-outlined',
'medicine-box-filled',
'medicine-box-outlined',
'medicine-box-twotone',
'medium-circle-filled',
'medium-outlined',
'medium-square-filled',
'medium-workmark-outlined',
'meh-filled',
'meh-outlined',
'meh-twotone',
'menu-fold-outlined',
'menu-outlined',
'menu-unfold-outlined',
'merge-cells-outlined',
'message-filled',
'message-outlined',
'message-twotone',
'minus-circle-filled',
'minus-circle-outlined',
'minus-circle-twotone',
'minus-outlined',
'minus-square-filled',
'minus-square-outlined',
'minus-square-twotone',
'mobile-filled',
'mobile-outlined',
'mobile-twotone',
'money-collect-filled',
'money-collect-outlined',
'money-collect-twotone',
'monitor-outlined',
'more-outlined',
'node-collapse-outlined',
'node-expand-outlined',
'node-index-outlined',
'notification-filled',
'notification-outlined',
'notification-twotone',
'number-outlined',
'one-to-one-outlined',
'ordered-list-outlined',
'paper-clip-outlined',
'partition-outlined',
'pause-circle-filled',
'pause-circle-outlined',
'pause-circle-twotone',
'pause-outlined',
'pay-circle-filled',
'pay-circle-outlined',
'percentage-outlined',
'phone-filled',
'phone-outlined',
'phone-twotone',
'pic-center-outlined',
'pic-left-outlined',
'pic-right-outlined',
'picture-filled',
'picture-outlined',
'picture-twotone',
'pie-chart-filled',
'pie-chart-outlined',
'pie-chart-twotone',
'play-circle-filled',
'play-circle-outlined',
'play-circle-twotone',
'play-square-filled',
'play-square-outlined',
'play-square-twotone',
'plus-circle-filled',
'plus-circle-outlined',
'plus-circle-twotone',
'plus-outlined',
'plus-square-filled',
'plus-square-outlined',
'plus-square-twotone',
'pound-circle-filled',
'pound-circle-outlined',
'pound-circle-twotone',
'pound-outlined',
'poweroff-outlined',
'printer-filled',
'printer-outlined',
'printer-twotone',
'profile-filled',
'profile-outlined',
'profile-twotone',
'project-filled',
'project-outlined',
'project-twotone',
'property-safety-filled',
'property-safety-outlined',
'property-safety-twotone',
'pull-request-outlined',
'pushpin-filled',
'pushpin-outlined',
'pushpin-twotone',
'qq-circle-filled',
'qq-outlined',
'qq-square-filled',
'qrcode-outlined',
'question-circle-filled',
'question-circle-outlined',
'question-circle-twotone',
'question-outlined',
'radar-chart-outlined',
'radius-bottomleft-outlined',
'radius-bottomright-outlined',
'radius-setting-outlined',
'radius-upleft-outlined',
'radius-upright-outlined',
'read-filled',
'read-outlined',
'reconciliation-filled',
'reconciliation-outlined',
'reconciliation-twotone',
'red-envelope-filled',
'red-envelope-outlined',
'red-envelope-twotone',
'reddit-circle-filled',
'reddit-outlined',
'reddit-square-filled',
'redo-outlined',
'reload-outlined',
'rest-filled',
'rest-outlined',
'rest-twotone',
'retweet-outlined',
'right-circle-filled',
'right-circle-outlined',
'right-circle-twotone',
'right-outlined',
'right-square-filled',
'right-square-outlined',
'right-square-twotone',
'rise-outlined',
'robot-filled',
'robot-outlined',
'rocket-filled',
'rocket-outlined',
'rocket-twotone',
'rollback-outlined',
'rotate-left-outlined',
'rotate-right-outlined',
'safety-certificate-filled',
'safety-certificate-outlined',
'safety-certificate-twotone',
'safety-outlined',
'save-filled',
'save-outlined',
'save-twotone',
'scan-outlined',
'schedule-filled',
'schedule-outlined',
'schedule-twotone',
'scissor-outlined',
'search-outlined',
'security-scan-filled',
'security-scan-outlined',
'security-scan-twotone',
'select-outlined',
'send-outlined',
'setting-filled',
'setting-outlined',
'setting-twotone',
'shake-outlined',
'share-alt-outlined',
'shop-filled',
'shop-outlined',
'shop-twotone',
'shopping-cart-outlined',
'shopping-filled',
'shopping-outlined',
'shopping-twotone',
'shrink-outlined',
'signal-filled',
'sisternode-outlined',
'sketch-circle-filled',
'sketch-outlined',
'sketch-square-filled',
'skin-filled',
'skin-outlined',
'skin-twotone',
'skype-filled',
'skype-outlined',
'slack-circle-filled',
'slack-outlined',
'slack-square-filled',
'slack-square-outlined',
'sliders-filled',
'sliders-outlined',
'sliders-twotone',
'small-dash-outlined',
'smile-filled',
'smile-outlined',
'smile-twotone',
'snippets-filled',
'snippets-outlined',
'snippets-twotone',
'solution-outlined',
'sort-ascending-outlined',
'sort-descending-outlined',
'sound-filled',
'sound-outlined',
'sound-twotone',
'split-cells-outlined',
'star-filled',
'star-outlined',
'star-twotone',
'step-backward-filled',
'step-backward-outlined',
'step-forward-filled',
'step-forward-outlined',
'stock-outlined',
'stop-filled',
'stop-outlined',
'stop-twotone',
'strikethrough-outlined',
'subnode-outlined',
'swap-left-outlined',
'swap-outlined',
'swap-right-outlined',
'switcher-filled',
'switcher-outlined',
'switcher-twotone',
'sync-outlined',
'table-outlined',
'tablet-filled',
'tablet-outlined',
'tablet-twotone',
'tag-filled',
'tag-outlined',
'tag-twotone',
'tags-filled',
'tags-outlined',
'tags-twotone',
'taobao-circle-filled',
'taobao-circle-outlined',
'taobao-outlined',
'taobao-square-filled',
'team-outlined',
'thunderbolt-filled',
'thunderbolt-outlined',
'thunderbolt-twotone',
'to-top-outlined',
'tool-filled',
'tool-outlined',
'tool-twotone',
'trademark-circle-filled',
'trademark-circle-outlined',
'trademark-circle-twotone',
'trademark-outlined',
'transaction-outlined',
'translation-outlined',
'trophy-filled',
'trophy-outlined',
'trophy-twotone',
'twitter-circle-filled',
'twitter-outlined',
'twitter-square-filled',
'underline-outlined',
'undo-outlined',
'ungroup-outlined',
'unlock-filled',
'unlock-outlined',
'unlock-twotone',
'unordered-list-outlined',
'up-circle-filled',
'up-circle-outlined',
'up-circle-twotone',
'up-outlined',
'up-square-filled',
'up-square-outlined',
'up-square-twotone',
'upload-outlined',
'usb-filled',
'usb-outlined',
'usb-twotone',
'user-add-outlined',
'user-delete-outlined',
'user-outlined',
'user-switch-outlined',
'usergroup-add-outlined',
'usergroup-delete-outlined',
'verified-outlined',
'vertical-align-bottom-outlined',
'vertical-align-middle-outlined',
'vertical-align-top-outlined',
'vertical-left-outlined',
'vertical-right-outlined',
'video-camera-add-outlined',
'video-camera-filled',
'video-camera-outlined',
'video-camera-twotone',
'wallet-filled',
'wallet-outlined',
'wallet-twotone',
'warning-filled',
'warning-outlined',
'warning-twotone',
'wechat-filled',
'wechat-outlined',
'weibo-circle-filled',
'weibo-circle-outlined',
'weibo-outlined',
'weibo-square-filled',
'weibo-square-outlined',
'whats-app-outlined',
'wifi-outlined',
'windows-filled',
'windows-outlined',
'woman-outlined',
'yahoo-filled',
'yahoo-outlined',
'youtube-filled',
'youtube-outlined',
'yuque-filled',
'yuque-outlined',
'zhihu-circle-filled',
'zhihu-outlined',
'zhihu-square-filled',
'zoom-in-outlined',
'zoom-out-outlined',
],
prefix: 'ant-design',
};

View File

@@ -1,6 +1,9 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { ref } from 'vue';
import { IconPicker, Page } from '@vben/common-ui';
import {
IconifyIcon,
MdiGithub,
MdiGoogle,
MdiKeyboardEsc,
@@ -16,9 +19,9 @@ import {
SvgDownloadIcon,
} from '@vben/icons';
import { Card } from 'ant-design-vue';
import { Card, Input } from 'ant-design-vue';
import IconPicker from './icon-picker.vue';
const iconValue = ref('ant-design:trademark-outlined');
</script>
<template>
@@ -60,15 +63,64 @@ import IconPicker from './icon-picker.vue';
</div>
</Card>
<Card class="mb-5" title="图标选择器(Iconify)">
<div class="flex items-center gap-5">
<IconPicker width="300px" />
<Card class="mb-5" title="Tailwind CSS">
<div class="flex items-center gap-5 text-3xl">
<span class="icon-[ant-design--alipay-circle-outlined]"></span>
<span class="icon-[ant-design--account-book-filled]"></span>
<span class="icon-[ant-design--container-outlined]"></span>
<span class="icon-[svg-spinners--wind-toy]"></span>
<span class="icon-[svg-spinners--blocks-wave]"></span>
<span class="icon-[line-md--compass-filled-loop]"></span>
</div>
</Card>
<Card title="图标选择器(Svg)">
<Card class="mb-5" title="图标选择器">
<div class="mb-5 flex items-center gap-5">
<span>原始样式(Iconify):</span>
<IconPicker class="w-[200px]" />
</div>
<div class="mb-5 flex items-center gap-5">
<span>原始样式(svg):</span>
<IconPicker class="w-[200px]" prefix="svg" />
</div>
<div class="mb-5 flex items-center gap-5">
<span>完整替换触发组件:</span>
<IconPicker class="w-[200px]">
<template #trigger="{ icon }">
<Input
:value="icon"
placeholder="点击这里选择图标"
style="width: 300px"
>
<template #addonAfter>
<IconifyIcon
:icon="icon || 'ant-design:appstore-filled'"
class="text-2xl"
/>
</template>
</Input>
</template>
</IconPicker>
</div>
<div class="flex items-center gap-5">
<IconPicker prefix="svg" width="300px" />
<span>可手动输入只能点击图标打开弹窗:</span>
<Input
v-model:value="iconValue"
allow-clear
placeholder="点击这里选择图标"
style="width: 300px"
>
<template #addonAfter>
<IconPicker v-model="iconValue" class="w-[200px]">
<template #trigger="{ icon }">
<IconifyIcon
:icon="icon || 'ant-design:appstore-filled'"
class="text-2xl"
/>
</template>
</IconPicker>
</template>
</Input>
</div>
</Card>
</Page>

View File

@@ -4,7 +4,12 @@ import { useWatermark } from '@vben/hooks';
import { Button, Card } from 'ant-design-vue';
const { destroyWatermark, updateWatermark } = useWatermark();
const { destroyWatermark, updateWatermark, watermark } = useWatermark();
async function recreateWaterMark() {
destroyWatermark();
await updateWatermark({});
}
async function createWaterMark() {
await updateWatermark({
@@ -21,7 +26,7 @@ async function createWaterMark() {
],
type: 'linear',
},
content: 'hello my watermark',
content: `hello my watermark\n${new Date().toLocaleString()}`,
globalAlpha: 0.5,
gridLayoutOptions: {
cols: 2,
@@ -57,10 +62,25 @@ async function createWaterMark() {
</template>
<Card title="使用">
<Button class="mr-2" type="primary" @click="createWaterMark()">
<Button
:disabled="!!watermark"
class="mr-2"
type="primary"
@click="recreateWaterMark"
>
创建水印
</Button>
<Button danger @click="destroyWatermark">移除水印</Button>
<Button
:disabled="!watermark"
class="mr-2"
type="primary"
@click="createWaterMark"
>
更新水印
</Button>
<Button :disabled="!watermark" danger @click="destroyWatermark">
移除水印
</Button>
</Card>
</Page>
</template>

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { Page, useVbenDrawer } from '@vben/common-ui';
import { type DrawerPlacement, Page, useVbenDrawer } from '@vben/common-ui';
import { Button, Card } from 'ant-design-vue';
@@ -13,6 +13,7 @@ import SharedDataDemo from './shared-data-demo.vue';
const [BaseDrawer, baseDrawerApi] = useVbenDrawer({
// 连接抽离的组件
connectedComponent: BaseDemo,
// placement: 'left',
});
const [AutoHeightDrawer, autoHeightDrawerApi] = useVbenDrawer({
@@ -31,7 +32,8 @@ const [FormDrawer, formDrawerApi] = useVbenDrawer({
connectedComponent: FormDrawerDemo,
});
function openBaseDrawer() {
function openBaseDrawer(placement: DrawerPlacement = 'right') {
baseDrawerApi.setState({ placement });
baseDrawerApi.open();
}
@@ -81,7 +83,16 @@ function openFormDrawer() {
<Card class="mb-4" title="基本使用">
<p class="mb-3">一个基础的抽屉示例</p>
<Button type="primary" @click="openBaseDrawer">打开抽屉</Button>
<Button type="primary" @click="openBaseDrawer('right')">右侧打开</Button>
<Button class="ml-2" type="primary" @click="openBaseDrawer('bottom')">
底部打开
</Button>
<Button class="ml-2" type="primary" @click="openBaseDrawer('left')">
左侧打开
</Button>
<Button class="ml-2" type="primary" @click="openBaseDrawer('top')">
顶部打开
</Button>
</Card>
<Card class="mb-4" title="内容高度自适应滚动">

View File

@@ -1,13 +1,18 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { Page } from '@vben/common-ui';
import { Button, Card, message } from 'ant-design-vue';
import { Button, Card, message, TabPane, Tabs } from 'ant-design-vue';
import dayjs from 'dayjs';
import { useVbenForm } from '#/adapter/form';
import { getAllMenusApi } from '#/api';
import DocButton from '../doc-button.vue';
const activeTab = ref('basic');
const [BaseForm, baseFormApi] = useVbenForm({
// 所有表单项共用,可单独在表单内覆盖
commonConfig: {
@@ -36,6 +41,27 @@ const [BaseForm, baseFormApi] = useVbenForm({
// 界面显示的label
label: '字符串',
},
{
// 组件需要在 #/adapter.ts内注册并加上类型
component: 'ApiSelect',
// 对应组件的参数
componentProps: {
// 菜单接口转options格式
afterFetch: (data: { name: string; path: string }[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.path,
}));
},
// 菜单接口
api: getAllMenusApi,
placeholder: '请选择',
},
// 字段名
fieldName: 'api',
// 界面显示的label
label: 'ApiSelect',
},
{
component: 'InputPassword',
componentProps: {
@@ -53,6 +79,11 @@ const [BaseForm, baseFormApi] = useVbenForm({
label: '数字(带后缀)',
suffix: () => '¥',
},
{
component: 'IconPicker',
fieldName: 'icon',
label: '图标',
},
{
component: 'Select',
componentProps: {
@@ -331,18 +362,31 @@ function handleSetFormValue() {
<Page
content-class="flex flex-col gap-4"
description="表单组件基础示例,请注意,该页面用到的参数代码会添加一些简单注释,方便理解,请仔细查看。"
fixed-header
header-class="pb-0"
title="表单组件"
>
<template #description>
<div class="text-muted-foreground">
<p>
表单组件基础示例请注意该页面用到的参数代码会添加一些简单注释方便理解请仔细查看
</p>
</div>
<Tabs v-model:active-key="activeTab" :tab-bar-style="{ marginBottom: 0 }">
<TabPane key="basic" tab="基础示例" />
<TabPane key="layout" tab="自定义布局" />
</Tabs>
</template>
<template #extra>
<DocButton path="/components/common-ui/vben-form" />
</template>
<Card title="基础示例">
<Card v-show="activeTab === 'basic'" title="基础示例">
<template #extra>
<Button type="primary" @click="handleSetFormValue">设置表单值</Button>
</template>
<BaseForm />
</Card>
<Card title="使用tailwind自定义布局">
<Card v-show="activeTab === 'layout'" title="使用tailwind自定义布局">
<CustomLayoutForm />
</Card>
</Page>

View File

@@ -22,10 +22,10 @@ const [Modal, modalApi] = useVbenModal({
});
function handleUpdate(len: number) {
modalApi.setState({ loading: true });
modalApi.setState({ confirmDisabled: true, loading: true });
setTimeout(() => {
list.value = Array.from({ length: len }, (_v, k) => k + 1);
modalApi.setState({ loading: false });
modalApi.setState({ confirmDisabled: false, loading: false });
}, 2000);
}
</script>

View File

@@ -77,6 +77,7 @@ function openFormModal() {
<template>
<Page
description="弹窗组件常用于在不离开当前页面的情况下显示额外的信息、表单或操作提示更多api请查看组件文档。"
fixed-header
title="弹窗组件示例"
>
<template #extra>

View File

@@ -3,42 +3,56 @@ import { ref } from 'vue';
import { Page, VResize } from '@vben/common-ui';
const width = ref(200);
const height = ref(200);
const top = ref(200);
const left = ref(200);
const colorMap = ['red', 'green', 'yellow', 'gray'];
const resize = (newRect: {
type TSize = {
height: number;
left: number;
top: number;
width: number;
}) => {
width.value = newRect.width;
height.value = newRect.height;
top.value = newRect.top;
left.value = newRect.left;
};
const sizeList = ref<TSize[]>([
{ height: 200, left: 200, top: 200, width: 200 },
{ height: 300, left: 300, top: 300, width: 300 },
{ height: 400, left: 400, top: 400, width: 400 },
{ height: 500, left: 500, top: 500, width: 500 },
]);
const resize = (size?: TSize, rect?: TSize) => {
if (!size || !rect) return;
size.height = rect.height;
size.left = rect.left;
size.top = rect.top;
size.width = rect.width;
};
</script>
<template>
<Page description="Resize组件基础示例" title="Resize组件">
<div class="m-4 bg-blue-500 p-48 text-xl">
<div v-for="size in sizeList" :key="size.width">
{{
`width: ${width}px, height: ${height}px, top: ${top}px, left: ${left}px`
`width: ${size.width}px, height: ${size.height}px, top: ${size.top}px, left: ${size.left}px`
}}
</div>
</div>
<template v-for="(_, idx) of 4" :key="idx">
<VResize
:h="200"
:is-active="true"
:w="200"
:x="200"
:y="200"
@dragging="resize"
@resizing="resize"
:h="100 * (idx + 1)"
:w="100 * (idx + 1)"
:x="100 * (idx + 1)"
:y="100 * (idx + 1)"
@dragging="(rect) => resize(sizeList[idx], rect)"
@resizing="(rect) => resize(sizeList[idx], rect)"
>
<div class="h-full w-full bg-red-500"></div>
<div
:style="{ backgroundColor: colorMap[idx] }"
class="h-full w-full"
></div>
</VResize>
</template>
</Page>
</template>

5069
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,135 +13,137 @@ packages:
- docs
- playground
catalog:
'@ast-grep/napi': ^0.30.0
'@ast-grep/napi': ^0.31.0
'@changesets/changelog-github': ^0.5.0
'@changesets/cli': ^2.27.9
'@changesets/git': ^3.0.1
'@clack/prompts': ^0.7.0
'@commitlint/cli': ^19.5.0
'@commitlint/config-conventional': ^19.5.0
'@changesets/cli': ^2.27.10
'@changesets/git': ^3.0.2
'@clack/prompts': ^0.8.2
'@commitlint/cli': ^19.6.0
'@commitlint/config-conventional': ^19.6.0
'@ctrl/tinycolor': ^4.1.0
'@eslint/js': ^9.14.0
'@faker-js/faker': ^9.2.0
'@iconify/json': ^2.2.271
'@eslint/js': ^9.16.0
'@faker-js/faker': ^9.3.0
'@iconify/json': ^2.2.279
'@iconify/tailwind': ^1.1.3
'@iconify/vue': ^4.1.2
'@intlify/core-base': ^10.0.4
'@intlify/unplugin-vue-i18n': ^5.3.1
'@intlify/core-base': ^10.0.5
'@intlify/unplugin-vue-i18n': ^6.0.0
'@jspm/generator': ^2.4.1
'@manypkg/get-packages': ^2.2.2
'@nolebase/vitepress-plugin-git-changelog': ^2.9.0
'@playwright/test': ^1.48.2
'@pnpm/workspace.read-manifest': ^2.2.1
'@nolebase/vitepress-plugin-git-changelog': ^2.11.1
'@playwright/test': ^1.49.0
'@pnpm/workspace.read-manifest': ^1000.0.0
'@stylistic/stylelint-plugin': ^3.1.1
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
'@tailwindcss/typography': ^0.5.15
'@tanstack/vue-query': ^5.59.20
'@tanstack/vue-store': ^0.5.7
'@tanstack/vue-query': ^5.62.2
'@tanstack/vue-store': ^0.6.0
'@types/archiver': ^6.0.3
'@types/eslint': ^9.6.1
'@types/html-minifier-terser': ^7.0.2
'@types/jsonwebtoken': ^9.0.7
'@types/lodash.clonedeep': ^4.5.9
'@types/lodash.get': ^4.4.9
'@types/node': ^22.9.0
'@types/lodash.isequal': ^4.5.8
'@types/node': ^22.10.1
'@types/nprogress': ^0.2.3
'@types/postcss-import': ^14.0.3
'@types/qrcode': ^1.5.5
'@types/sortablejs': ^1.15.8
'@typescript-eslint/eslint-plugin': ^8.14.0
'@typescript-eslint/parser': ^8.14.0
'@typescript-eslint/eslint-plugin': ^8.17.0
'@typescript-eslint/parser': ^8.17.0
'@vee-validate/zod': ^4.14.7
'@vite-pwa/vitepress': ^0.5.3
'@vitejs/plugin-vue': ^5.2.0
'@vitejs/plugin-vue-jsx': ^4.1.0
'@vue/reactivity': ^3.5.12
'@vue/shared': ^3.5.12
'@vitejs/plugin-vue': ^5.2.1
'@vitejs/plugin-vue-jsx': ^4.1.1
'@vue/reactivity': ^3.5.13
'@vue/shared': ^3.5.13
'@vue/test-utils': ^2.4.6
'@vueuse/core': ^11.2.0
'@vueuse/integrations': ^11.2.0
'@vueuse/core': ^12.0.0
'@vueuse/integrations': ^12.0.0
ant-design-vue: ^4.2.6
archiver: ^7.0.1
autoprefixer: ^10.4.20
axios: ^1.7.7
axios: ^1.7.9
axios-mock-adapter: ^2.1.0
cac: ^6.7.14
chalk: ^5.3.0
cheerio: 1.0.0
circular-dependency-scanner: ^2.3.0
class-variance-authority: ^0.7.0
class-variance-authority: ^0.7.1
clsx: ^2.1.1
commitlint-plugin-function-rules: ^4.0.1
consola: ^3.2.3
cross-env: ^7.0.3
cspell: ^8.16.0
cspell: ^8.16.1
cssnano: ^7.0.6
cz-git: ^1.11.0
czg: ^1.11.0
dayjs: ^1.11.13
defu: ^6.1.4
depcheck: ^1.4.7
dotenv: ^16.4.5
dotenv: ^16.4.7
echarts: ^5.5.1
element-plus: ^2.8.7
eslint: ^9.14.0
eslint-config-turbo: ^2.2.3
element-plus: ^2.9.0
eslint: ^9.16.0
eslint-config-turbo: ^2.3.3
eslint-plugin-command: ^0.2.6
eslint-plugin-eslint-comments: ^3.2.0
eslint-plugin-import-x: ^4.4.2
eslint-plugin-jsdoc: ^50.5.0
eslint-plugin-jsonc: ^2.18.1
eslint-plugin-n: ^17.13.1
eslint-plugin-import-x: ^4.5.0
eslint-plugin-jsdoc: ^50.6.0
eslint-plugin-jsonc: ^2.18.2
eslint-plugin-n: ^17.14.0
eslint-plugin-no-only-tests: ^3.3.0
eslint-plugin-perfectionist: ^3.9.1
eslint-plugin-prettier: ^5.2.1
eslint-plugin-regexp: ^2.6.0
eslint-plugin-unicorn: ^56.0.0
eslint-plugin-regexp: ^2.7.0
eslint-plugin-unicorn: ^56.0.1
eslint-plugin-unused-imports: ^4.1.4
eslint-plugin-vitest: ^0.5.4
eslint-plugin-vue: ^9.31.0
eslint-plugin-vue: ^9.32.0
execa: ^9.5.1
find-up: ^7.0.0
get-port: ^7.1.0
globals: ^15.12.0
globals: ^15.13.0
h3: ^1.13.0
happy-dom: ^15.11.4
happy-dom: ^15.11.7
html-minifier-terser: ^7.2.0
husky: ^9.1.6
husky: ^9.1.7
is-ci: ^3.0.1
jsonc-eslint-parser: ^2.4.0
jsonwebtoken: ^9.0.2
lint-staged: ^15.2.10
lodash.clonedeep: ^4.5.0
lodash.get: ^4.4.2
lucide-vue-next: ^0.456.0
lodash.isequal: ^4.5.0
lucide-vue-next: ^0.465.0
medium-zoom: ^1.1.0
naive-ui: ^2.40.1
naive-ui: ^2.40.3
nitropack: ^2.10.4
nprogress: ^0.2.0
ora: ^8.1.1
pinia: 2.2.2
pinia-plugin-persistedstate: ^4.1.3
pkg-types: ^1.2.1
playwright: ^1.48.2
playwright: ^1.49.0
postcss: ^8.4.49
postcss-antd-fixes: ^0.2.0
postcss-html: ^1.7.0
postcss-import: ^16.1.0
postcss-preset-env: ^10.1.0
postcss-preset-env: ^10.1.1
postcss-scss: ^4.0.9
prettier: ^3.3.3
prettier-plugin-tailwindcss: ^0.6.8
prettier: ^3.4.2
prettier-plugin-tailwindcss: ^0.6.9
publint: ^0.2.12
qrcode: ^1.5.4
radix-vue: ^1.9.9
resolve.exports: ^2.0.2
radix-vue: ^1.9.10
resolve.exports: ^2.0.3
rimraf: ^6.0.1
rollup: ^4.26.0
rollup: ^4.28.0
rollup-plugin-visualizer: ^5.12.0
sass: 1.80.6
sortablejs: ^1.15.3
stylelint: ^16.10.0
sortablejs: ^1.15.6
stylelint: ^16.11.0
stylelint-config-recess-order: ^5.1.1
stylelint-config-recommended: ^14.0.1
stylelint-config-recommended-scss: ^14.1.0
@@ -149,33 +151,33 @@ catalog:
stylelint-config-standard: ^36.0.1
stylelint-order: ^6.0.4
stylelint-prettier: ^5.0.2
stylelint-scss: ^6.9.0
tailwind-merge: ^2.5.4
tailwindcss: ^3.4.14
stylelint-scss: ^6.10.0
tailwind-merge: ^2.5.5
tailwindcss: ^3.4.16
tailwindcss-animate: ^1.0.7
theme-colors: ^0.1.0
turbo: ^2.2.3
typescript: ^5.6.3
turbo: ^2.3.3
typescript: 5.6.3
unbuild: ^3.0.0-rc.11
unplugin-element-plus: ^0.8.0
vee-validate: ^4.14.7
vite: ^5.4.11
vite: ^6.0.2
vite-plugin-compression: ^0.5.1
vite-plugin-dts: 4.2.1
vite-plugin-html: ^3.2.2
vite-plugin-lazy-import: ^1.0.7
vite-plugin-pwa: ^0.21.0
vite-plugin-vue-devtools: ^7.6.4
vite-plugin-pwa: ^0.21.1
vite-plugin-vue-devtools: ^7.6.7
vitepress: ^1.5.0
vitepress-plugin-group-icons: ^1.3.0
vitest: ^2.1.4
vue: ^3.5.12
vitepress-plugin-group-icons: ^1.3.1
vitest: ^2.1.8
vue: ^3.5.13
vue-eslint-parser: ^9.4.3
vue-i18n: ^10.0.4
vue-router: ^4.4.5
vue-i18n: ^10.0.5
vue-router: ^4.5.0
vue-tsc: ^2.1.10
vxe-pc-ui: ^4.2.53
vxe-table: ^4.8.14
vxe-pc-ui: ^4.3.14
vxe-table: ^4.9.14
watermark-js-plus: ^1.5.7
zod: ^3.23.8
zod-defaults: ^0.1.3

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/turbo-run",
"version": "5.4.7",
"version": "5.5.0",
"private": true,
"license": "MIT",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/vsh",
"version": "5.4.7",
"version": "5.5.0",
"private": true,
"license": "MIT",
"type": "module",