mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-26 08:36:19 +08:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d1ca09c7bb | ||
![]() |
ff3c5f8581 | ||
![]() |
240f0b5f8d | ||
![]() |
6f3d50984f | ||
![]() |
c491b9e021 | ||
![]() |
6cd9937c03 | ||
![]() |
f89f4f32c7 | ||
![]() |
c432e0ac33 | ||
![]() |
719c9a8f2d | ||
![]() |
a0fbe0b21a | ||
![]() |
f7fa69d33b | ||
![]() |
7c45aeb868 | ||
![]() |
850a6af1e0 | ||
![]() |
d5a210f53f | ||
![]() |
6c4a742627 | ||
![]() |
45987fc1e3 | ||
![]() |
ea962e75d0 | ||
![]() |
24d14c2841 | ||
![]() |
8b961a9d7f |
@@ -134,7 +134,7 @@ If you think this project is helpful to you, you can help the author buy a cup o
|
||||
|
||||

|
||||
|
||||
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
||||
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aee;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
||||
|
||||
## Contributor
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/web-antd",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
127
apps/web-antd/src/adapter/component/index.ts
Normal file
127
apps/web-antd/src/adapter/component/index.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
|
||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||
*/
|
||||
|
||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||
|
||||
import type { Component, SetupContext } from 'vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
import { globalShareState } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
AutoComplete,
|
||||
Button,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
Divider,
|
||||
Input,
|
||||
InputNumber,
|
||||
InputPassword,
|
||||
Mentions,
|
||||
notification,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Textarea,
|
||||
TimePicker,
|
||||
TreeSelect,
|
||||
Upload,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
) => {
|
||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
||||
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
|
||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
||||
};
|
||||
};
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type ComponentType =
|
||||
| 'AutoComplete'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'DefaultButton'
|
||||
| 'Divider'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'InputPassword'
|
||||
| 'Mentions'
|
||||
| 'PrimaryButton'
|
||||
| 'Radio'
|
||||
| 'RadioGroup'
|
||||
| 'RangePicker'
|
||||
| 'Rate'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'Textarea'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
async function initComponentAdapter() {
|
||||
const components: Partial<Record<ComponentType, Component>> = {
|
||||
// 如果你的组件体积比较大,可以使用异步加载
|
||||
// Button: () =>
|
||||
// import('xxx').then((res) => res.Button),
|
||||
|
||||
AutoComplete,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
// 自定义默认按钮
|
||||
DefaultButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
||||
},
|
||||
Divider,
|
||||
Input: withDefaultPlaceholder(Input, 'input'),
|
||||
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||
Mentions: withDefaultPlaceholder(Mentions, 'input'),
|
||||
// 自定义主要按钮
|
||||
PrimaryButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select: withDefaultPlaceholder(Select, 'select'),
|
||||
Space,
|
||||
Switch,
|
||||
Textarea: withDefaultPlaceholder(Textarea, 'input'),
|
||||
TimePicker,
|
||||
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
||||
Upload,
|
||||
};
|
||||
|
||||
// 将组件注册到全局共享状态中
|
||||
globalShareState.setComponents(components);
|
||||
|
||||
// 定义全局共享状态中的消息提示
|
||||
globalShareState.defineMessage({
|
||||
// 复制成功消息提示
|
||||
copyPreferencesSuccess: (title, content) => {
|
||||
notification.success({
|
||||
description: content,
|
||||
message: title,
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export { initComponentAdapter };
|
@@ -1,105 +1,14 @@
|
||||
import type {
|
||||
BaseFormComponentType,
|
||||
VbenFormSchema as FormSchema,
|
||||
VbenFormProps,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import type { Component, SetupContext } from 'vue';
|
||||
import { h } from 'vue';
|
||||
import type { ComponentType } from './component';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
AutoComplete,
|
||||
Button,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
Divider,
|
||||
Input,
|
||||
InputNumber,
|
||||
InputPassword,
|
||||
Mentions,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Textarea,
|
||||
TimePicker,
|
||||
TreeSelect,
|
||||
Upload,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type FormComponentType =
|
||||
| 'AutoComplete'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'Divider'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'InputPassword'
|
||||
| 'Mentions'
|
||||
| 'Radio'
|
||||
| 'RadioGroup'
|
||||
| 'RangePicker'
|
||||
| 'Rate'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'Textarea'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
) => {
|
||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
||||
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
|
||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
||||
};
|
||||
};
|
||||
|
||||
// 初始化表单组件,并注册到form组件内部
|
||||
setupVbenForm<FormComponentType>({
|
||||
components: {
|
||||
AutoComplete,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
// 自定义默认的重置按钮
|
||||
DefaultResetActionButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
||||
},
|
||||
// 自定义默认的提交按钮
|
||||
DefaultSubmitActionButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Divider,
|
||||
Input: withDefaultPlaceholder(Input, 'input'),
|
||||
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||
Mentions: withDefaultPlaceholder(Mentions, 'input'),
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select: withDefaultPlaceholder(Select, 'select'),
|
||||
Space,
|
||||
Switch,
|
||||
Textarea: withDefaultPlaceholder(Textarea, 'input'),
|
||||
TimePicker,
|
||||
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
||||
Upload,
|
||||
},
|
||||
setupVbenForm<ComponentType>({
|
||||
config: {
|
||||
// ant design vue组件库默认都是 v-model:value
|
||||
baseModelPropName: 'value',
|
||||
@@ -130,9 +39,9 @@ setupVbenForm<FormComponentType>({
|
||||
},
|
||||
});
|
||||
|
||||
const useVbenForm = useForm<FormComponentType>;
|
||||
const useVbenForm = useForm<ComponentType>;
|
||||
|
||||
export { useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<FormComponentType>;
|
||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||
export type { VbenFormProps };
|
||||
|
@@ -1,2 +0,0 @@
|
||||
export * from './form';
|
||||
export * from './vxe-table';
|
@@ -74,11 +74,13 @@ function createRequestClient(baseURL: string) {
|
||||
fulfilled: (response) => {
|
||||
const { data: responseData, status } = response;
|
||||
|
||||
const { code, data, message: msg } = responseData;
|
||||
const { code, data } = responseData;
|
||||
if (status >= 200 && status < 400 && code === 0) {
|
||||
return data;
|
||||
}
|
||||
throw new Error(`Error ${status}: ${msg}`);
|
||||
|
||||
const error = { response };
|
||||
throw error;
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -7,10 +7,14 @@ import '@vben/styles/antd';
|
||||
|
||||
import { setupI18n } from '#/locales';
|
||||
|
||||
import { initComponentAdapter } from './adapter/component';
|
||||
import App from './app.vue';
|
||||
import { router } from './router';
|
||||
|
||||
async function bootstrap(namespace: string) {
|
||||
// 初始化组件适配器
|
||||
await initComponentAdapter();
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
// 国际化 i18n 配置
|
||||
|
@@ -45,14 +45,14 @@ async function loadThirdPartyMessage(lang: SupportedLanguagesType) {
|
||||
async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
||||
let locale;
|
||||
switch (lang) {
|
||||
case 'zh-CN': {
|
||||
locale = await import('dayjs/locale/zh-cn');
|
||||
break;
|
||||
}
|
||||
case 'en-US': {
|
||||
locale = await import('dayjs/locale/en');
|
||||
break;
|
||||
}
|
||||
case 'zh-CN': {
|
||||
locale = await import('dayjs/locale/zh-cn');
|
||||
break;
|
||||
}
|
||||
// 默认使用英语
|
||||
default: {
|
||||
locale = await import('dayjs/locale/en');
|
||||
@@ -71,14 +71,14 @@ async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
||||
*/
|
||||
async function loadAntdLocale(lang: SupportedLanguagesType) {
|
||||
switch (lang) {
|
||||
case 'zh-CN': {
|
||||
antdLocale.value = antdDefaultLocale;
|
||||
break;
|
||||
}
|
||||
case 'en-US': {
|
||||
antdLocale.value = antdEnLocale;
|
||||
break;
|
||||
}
|
||||
case 'zh-CN': {
|
||||
antdLocale.value = antdDefaultLocale;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -32,6 +32,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
component: AuthPageLayout,
|
||||
meta: {
|
||||
hideInTab: true,
|
||||
title: 'Authentication',
|
||||
},
|
||||
name: 'Authentication',
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/web-ele",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
104
apps/web-ele/src/adapter/component/index.ts
Normal file
104
apps/web-ele/src/adapter/component/index.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
|
||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||
*/
|
||||
|
||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||
|
||||
import type { Component, SetupContext } from 'vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
import { globalShareState } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
ElButton,
|
||||
ElCheckbox,
|
||||
ElCheckboxGroup,
|
||||
ElDivider,
|
||||
ElInput,
|
||||
ElInputNumber,
|
||||
ElNotification,
|
||||
ElRadioGroup,
|
||||
ElSelect,
|
||||
ElSpace,
|
||||
ElSwitch,
|
||||
ElTimePicker,
|
||||
ElTreeSelect,
|
||||
ElUpload,
|
||||
} from 'element-plus';
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
) => {
|
||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
||||
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
|
||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
||||
};
|
||||
};
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type ComponentType =
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'Divider'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'RadioGroup'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
async function initComponentAdapter() {
|
||||
const components: Partial<Record<ComponentType, Component>> = {
|
||||
// 如果你的组件体积比较大,可以使用异步加载
|
||||
// Button: () =>
|
||||
// import('xxx').then((res) => res.Button),
|
||||
|
||||
Checkbox: ElCheckbox,
|
||||
CheckboxGroup: ElCheckboxGroup,
|
||||
// 自定义默认按钮
|
||||
DefaulButton: (props, { attrs, slots }) => {
|
||||
return h(ElButton, { ...props, attrs, type: 'info' }, slots);
|
||||
},
|
||||
// 自定义主要按钮
|
||||
PrimaryButton: (props, { attrs, slots }) => {
|
||||
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Divider: ElDivider,
|
||||
Input: withDefaultPlaceholder(ElInput, 'input'),
|
||||
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
|
||||
RadioGroup: ElRadioGroup,
|
||||
Select: withDefaultPlaceholder(ElSelect, 'select'),
|
||||
Space: ElSpace,
|
||||
Switch: ElSwitch,
|
||||
TimePicker: ElTimePicker,
|
||||
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
|
||||
Upload: ElUpload,
|
||||
};
|
||||
|
||||
// 将组件注册到全局共享状态中
|
||||
globalShareState.setComponents(components);
|
||||
|
||||
// 定义全局共享状态中的消息提示
|
||||
globalShareState.defineMessage({
|
||||
// 复制成功消息提示
|
||||
copyPreferencesSuccess: (title, content) => {
|
||||
ElNotification({
|
||||
title,
|
||||
message: content,
|
||||
position: 'bottom-right',
|
||||
duration: 0,
|
||||
type: 'success',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export { initComponentAdapter };
|
@@ -1,82 +1,14 @@
|
||||
import type {
|
||||
BaseFormComponentType,
|
||||
VbenFormSchema as FormSchema,
|
||||
VbenFormProps,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import type { Component, SetupContext } from 'vue';
|
||||
import { h } from 'vue';
|
||||
import type { ComponentType } from './component';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
ElButton,
|
||||
ElCheckbox,
|
||||
ElCheckboxGroup,
|
||||
ElDivider,
|
||||
ElInput,
|
||||
ElInputNumber,
|
||||
ElRadioGroup,
|
||||
ElSelect,
|
||||
ElSpace,
|
||||
ElSwitch,
|
||||
ElTimePicker,
|
||||
ElTreeSelect,
|
||||
ElUpload,
|
||||
} from 'element-plus';
|
||||
// 业务表单组件适配
|
||||
|
||||
export type FormComponentType =
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'Divider'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'RadioGroup'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
) => {
|
||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
||||
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
|
||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
||||
};
|
||||
};
|
||||
|
||||
// 初始化表单组件,并注册到form组件内部
|
||||
setupVbenForm<FormComponentType>({
|
||||
components: {
|
||||
Checkbox: ElCheckbox,
|
||||
CheckboxGroup: ElCheckboxGroup,
|
||||
// 自定义默认的重置按钮
|
||||
DefaultResetActionButton: (props, { attrs, slots }) => {
|
||||
return h(ElButton, { ...props, attrs, type: 'info' }, slots);
|
||||
},
|
||||
// 自定义默认的提交按钮
|
||||
DefaultSubmitActionButton: (props, { attrs, slots }) => {
|
||||
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Divider: ElDivider,
|
||||
Input: withDefaultPlaceholder(ElInput, 'input'),
|
||||
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
|
||||
RadioGroup: ElRadioGroup,
|
||||
Select: withDefaultPlaceholder(ElSelect, 'select'),
|
||||
Space: ElSpace,
|
||||
Switch: ElSwitch,
|
||||
TimePicker: ElTimePicker,
|
||||
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
|
||||
Upload: ElUpload,
|
||||
},
|
||||
setupVbenForm<ComponentType>({
|
||||
config: {
|
||||
modelPropNameMap: {
|
||||
Upload: 'fileList',
|
||||
@@ -98,9 +30,9 @@ setupVbenForm<FormComponentType>({
|
||||
},
|
||||
});
|
||||
|
||||
const useVbenForm = useForm<FormComponentType>;
|
||||
const useVbenForm = useForm<ComponentType>;
|
||||
|
||||
export { useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<FormComponentType>;
|
||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||
export type { VbenFormProps };
|
||||
|
@@ -1,2 +0,0 @@
|
||||
export * from './form';
|
||||
export * from './vxe-table';
|
@@ -74,11 +74,12 @@ function createRequestClient(baseURL: string) {
|
||||
fulfilled: (response) => {
|
||||
const { data: responseData, status } = response;
|
||||
|
||||
const { code, data, message: msg } = responseData;
|
||||
const { code, data } = responseData;
|
||||
if (status >= 200 && status < 400 && code === 0) {
|
||||
return data;
|
||||
}
|
||||
throw new Error(`Error ${status}: ${msg}`);
|
||||
const error = { response };
|
||||
throw error;
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -7,10 +7,13 @@ import '@vben/styles/ele';
|
||||
|
||||
import { setupI18n } from '#/locales';
|
||||
|
||||
import { initComponentAdapter } from './adapter/component';
|
||||
import App from './app.vue';
|
||||
import { router } from './router';
|
||||
|
||||
async function bootstrap(namespace: string) {
|
||||
// 初始化组件适配器
|
||||
await initComponentAdapter();
|
||||
const app = createApp(App);
|
||||
|
||||
// 国际化 i18n 配置
|
||||
|
@@ -45,14 +45,14 @@ async function loadThirdPartyMessage(lang: SupportedLanguagesType) {
|
||||
async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
||||
let locale;
|
||||
switch (lang) {
|
||||
case 'zh-CN': {
|
||||
locale = await import('dayjs/locale/zh-cn');
|
||||
break;
|
||||
}
|
||||
case 'en-US': {
|
||||
locale = await import('dayjs/locale/en');
|
||||
break;
|
||||
}
|
||||
case 'zh-CN': {
|
||||
locale = await import('dayjs/locale/zh-cn');
|
||||
break;
|
||||
}
|
||||
// 默认使用英语
|
||||
default: {
|
||||
locale = await import('dayjs/locale/en');
|
||||
@@ -71,14 +71,14 @@ async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
||||
*/
|
||||
async function loadElementLocale(lang: SupportedLanguagesType) {
|
||||
switch (lang) {
|
||||
case 'zh-CN': {
|
||||
elementLocale.value = defaultLocale;
|
||||
break;
|
||||
}
|
||||
case 'en-US': {
|
||||
elementLocale.value = enLocale;
|
||||
break;
|
||||
}
|
||||
case 'zh-CN': {
|
||||
elementLocale.value = defaultLocale;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -32,6 +32,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
component: AuthPageLayout,
|
||||
meta: {
|
||||
hideInTab: true,
|
||||
title: 'Authentication',
|
||||
},
|
||||
name: 'Authentication',
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/web-naive",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
103
apps/web-naive/src/adapter/component/index.ts
Normal file
103
apps/web-naive/src/adapter/component/index.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
|
||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||
*/
|
||||
|
||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||
|
||||
import type { Component, SetupContext } from 'vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
import { globalShareState } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
NButton,
|
||||
NCheckbox,
|
||||
NCheckboxGroup,
|
||||
NDatePicker,
|
||||
NDivider,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
NRadioGroup,
|
||||
NSelect,
|
||||
NSpace,
|
||||
NSwitch,
|
||||
NTimePicker,
|
||||
NTreeSelect,
|
||||
NUpload,
|
||||
} from 'naive-ui';
|
||||
|
||||
import { message } from '#/adapter/naive';
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
) => {
|
||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
||||
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
|
||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
||||
};
|
||||
};
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type ComponentType =
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'Divider'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'RadioGroup'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
async function initComponentAdapter() {
|
||||
const components: Partial<Record<ComponentType, Component>> = {
|
||||
// 如果你的组件体积比较大,可以使用异步加载
|
||||
// Button: () =>
|
||||
// import('xxx').then((res) => res.Button),
|
||||
|
||||
Checkbox: NCheckbox,
|
||||
CheckboxGroup: NCheckboxGroup,
|
||||
DatePicker: NDatePicker,
|
||||
// 自定义默认按钮
|
||||
DefaultButton: (props, { attrs, slots }) => {
|
||||
return h(NButton, { ...props, attrs, type: 'info' }, slots);
|
||||
},
|
||||
// 自定义主要按钮
|
||||
PrimaryButton: (props, { attrs, slots }) => {
|
||||
return h(NButton, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Divider: NDivider,
|
||||
Input: withDefaultPlaceholder(NInput, 'input'),
|
||||
InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
|
||||
RadioGroup: NRadioGroup,
|
||||
Select: withDefaultPlaceholder(NSelect, 'select'),
|
||||
Space: NSpace,
|
||||
Switch: NSwitch,
|
||||
TimePicker: NTimePicker,
|
||||
TreeSelect: withDefaultPlaceholder(NTreeSelect, 'select'),
|
||||
Upload: NUpload,
|
||||
};
|
||||
|
||||
// 将组件注册到全局共享状态中
|
||||
globalShareState.setComponents(components);
|
||||
|
||||
// 定义全局共享状态中的消息提示
|
||||
globalShareState.defineMessage({
|
||||
// 复制成功消息提示
|
||||
copyPreferencesSuccess: (title, content) => {
|
||||
message.success(content || title, {
|
||||
duration: 0,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export { initComponentAdapter };
|
@@ -1,84 +1,14 @@
|
||||
import type {
|
||||
BaseFormComponentType,
|
||||
VbenFormSchema as FormSchema,
|
||||
VbenFormProps,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import type { Component, SetupContext } from 'vue';
|
||||
import { h } from 'vue';
|
||||
import type { ComponentType } from './component';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
NButton,
|
||||
NCheckbox,
|
||||
NCheckboxGroup,
|
||||
NDatePicker,
|
||||
NDivider,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
NRadioGroup,
|
||||
NSelect,
|
||||
NSpace,
|
||||
NSwitch,
|
||||
NTimePicker,
|
||||
NTreeSelect,
|
||||
NUpload,
|
||||
} from 'naive-ui';
|
||||
// 业务表单组件适配
|
||||
|
||||
export type FormComponentType =
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'Divider'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'RadioGroup'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
) => {
|
||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
||||
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
|
||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
||||
};
|
||||
};
|
||||
|
||||
// 初始化表单组件,并注册到form组件内部
|
||||
setupVbenForm<FormComponentType>({
|
||||
components: {
|
||||
Checkbox: NCheckbox,
|
||||
CheckboxGroup: NCheckboxGroup,
|
||||
DatePicker: NDatePicker,
|
||||
// 自定义默认的重置按钮
|
||||
DefaultResetActionButton: (props, { attrs, slots }) => {
|
||||
return h(NButton, { ...props, attrs, type: 'info' }, slots);
|
||||
},
|
||||
// 自定义默认的提交按钮
|
||||
DefaultSubmitActionButton: (props, { attrs, slots }) => {
|
||||
return h(NButton, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Divider: NDivider,
|
||||
Input: withDefaultPlaceholder(NInput, 'input'),
|
||||
InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
|
||||
RadioGroup: NRadioGroup,
|
||||
Select: withDefaultPlaceholder(NSelect, 'select'),
|
||||
Space: NSpace,
|
||||
Switch: NSwitch,
|
||||
TimePicker: NTimePicker,
|
||||
TreeSelect: withDefaultPlaceholder(NTreeSelect, 'select'),
|
||||
Upload: NUpload,
|
||||
},
|
||||
setupVbenForm<ComponentType>({
|
||||
config: {
|
||||
// naive-ui组件不接受onChang事件,所以需要禁用
|
||||
disabledOnChangeListener: true,
|
||||
@@ -107,9 +37,9 @@ setupVbenForm<FormComponentType>({
|
||||
},
|
||||
});
|
||||
|
||||
const useVbenForm = useForm<FormComponentType>;
|
||||
const useVbenForm = useForm<ComponentType>;
|
||||
|
||||
export { useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<FormComponentType>;
|
||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||
export type { VbenFormProps };
|
||||
|
@@ -1,3 +0,0 @@
|
||||
export * from './form';
|
||||
export * from './naive';
|
||||
export * from './vxe-table';
|
@@ -12,7 +12,7 @@ import {
|
||||
} from '@vben/request';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import { message } from '#/adapter';
|
||||
import { message } from '#/adapter/naive';
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
import { refreshTokenApi } from './core';
|
||||
@@ -73,11 +73,12 @@ function createRequestClient(baseURL: string) {
|
||||
fulfilled: (response) => {
|
||||
const { data: responseData, status } = response;
|
||||
|
||||
const { code, data, message: msg } = responseData;
|
||||
const { code, data } = responseData;
|
||||
if (status >= 200 && status < 400 && code === 0) {
|
||||
return data;
|
||||
}
|
||||
throw new Error(`Error ${status}: ${msg}`);
|
||||
const error = { response };
|
||||
throw error;
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -6,10 +6,13 @@ import '@vben/styles';
|
||||
|
||||
import { setupI18n } from '#/locales';
|
||||
|
||||
import { initComponentAdapter } from './adapter/component';
|
||||
import App from './app.vue';
|
||||
import { router } from './router';
|
||||
|
||||
async function bootstrap(namespace: string) {
|
||||
// 初始化组件适配器
|
||||
initComponentAdapter();
|
||||
const app = createApp(App);
|
||||
|
||||
// 国际化 i18n 配置
|
||||
|
@@ -6,7 +6,7 @@ import type {
|
||||
import { generateAccessible } from '@vben/access';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
import { message } from '#/adapter';
|
||||
import { message } from '#/adapter/naive';
|
||||
import { getAllMenusApi } from '#/api';
|
||||
import { BasicLayout, IFrameView } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
@@ -32,6 +32,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
component: AuthPageLayout,
|
||||
meta: {
|
||||
hideInTab: true,
|
||||
title: 'Authentication',
|
||||
},
|
||||
name: 'Authentication',
|
||||
|
@@ -9,7 +9,7 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
||||
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import { notification } from '#/adapter';
|
||||
import { notification } from '#/adapter/naive';
|
||||
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
|
@@ -164,6 +164,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
|
||||
link: 'common-ui/vben-form',
|
||||
text: 'Form 表单',
|
||||
},
|
||||
{
|
||||
link: 'common-ui/vben-vxe-table',
|
||||
text: 'Vxe Table 表格',
|
||||
},
|
||||
{
|
||||
link: 'common-ui/vben-count-to-animator',
|
||||
text: 'CountToAnimator 数字动画',
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import '@vben/styles';
|
||||
|
||||
import './variables.css';
|
||||
import './base.css';
|
||||
import '@vben/styles';
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/docs",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "vitepress build",
|
||||
|
127
docs/src/_env/adapter/component.ts
Normal file
127
docs/src/_env/adapter/component.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
|
||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||
*/
|
||||
|
||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||
|
||||
import type { Component, SetupContext } from 'vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
import { globalShareState } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
AutoComplete,
|
||||
Button,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
Divider,
|
||||
Input,
|
||||
InputNumber,
|
||||
InputPassword,
|
||||
Mentions,
|
||||
notification,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Textarea,
|
||||
TimePicker,
|
||||
TreeSelect,
|
||||
Upload,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
) => {
|
||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
||||
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
|
||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
||||
};
|
||||
};
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type ComponentType =
|
||||
| 'AutoComplete'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'DefaultButton'
|
||||
| 'Divider'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'InputPassword'
|
||||
| 'Mentions'
|
||||
| 'PrimaryButton'
|
||||
| 'Radio'
|
||||
| 'RadioGroup'
|
||||
| 'RangePicker'
|
||||
| 'Rate'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'Textarea'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
async function initComponentAdapter() {
|
||||
const components: Partial<Record<ComponentType, Component>> = {
|
||||
// 如果你的组件体积比较大,可以使用异步加载
|
||||
// Button: () =>
|
||||
// import('xxx').then((res) => res.Button),
|
||||
|
||||
AutoComplete,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
// 自定义默认按钮
|
||||
DefaultButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
||||
},
|
||||
Divider,
|
||||
Input: withDefaultPlaceholder(Input, 'input'),
|
||||
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||
Mentions: withDefaultPlaceholder(Mentions, 'input'),
|
||||
// 自定义主要按钮
|
||||
PrimaryButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select: withDefaultPlaceholder(Select, 'select'),
|
||||
Space,
|
||||
Switch,
|
||||
Textarea: withDefaultPlaceholder(Textarea, 'input'),
|
||||
TimePicker,
|
||||
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
||||
Upload,
|
||||
};
|
||||
|
||||
// 将组件注册到全局共享状态中
|
||||
globalShareState.setComponents(components);
|
||||
|
||||
// 定义全局共享状态中的消息提示
|
||||
globalShareState.defineMessage({
|
||||
// 复制成功消息提示
|
||||
copyPreferencesSuccess: (title, content) => {
|
||||
notification.success({
|
||||
description: content,
|
||||
message: title,
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export { initComponentAdapter };
|
@@ -1,99 +1,23 @@
|
||||
import type {
|
||||
BaseFormComponentType,
|
||||
VbenFormSchema as FormSchema,
|
||||
VbenFormProps,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import { h } from 'vue';
|
||||
import type { ComponentType } from './component';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
AutoComplete,
|
||||
Button,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
Divider,
|
||||
Input,
|
||||
InputNumber,
|
||||
InputPassword,
|
||||
Mentions,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Textarea,
|
||||
TimePicker,
|
||||
TreeSelect,
|
||||
Upload,
|
||||
} from 'ant-design-vue';
|
||||
import { initComponentAdapter } from './component';
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type FormComponentType =
|
||||
| 'AutoComplete'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'Divider'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'InputPassword'
|
||||
| 'Mentions'
|
||||
| 'Radio'
|
||||
| 'RadioGroup'
|
||||
| 'RangePicker'
|
||||
| 'Rate'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'Textarea'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
// 初始化表单组件,并注册到form组件内部
|
||||
setupVbenForm<FormComponentType>({
|
||||
components: {
|
||||
AutoComplete,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
// 自定义默认的重置按钮
|
||||
DefaultResetActionButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
||||
},
|
||||
// 自定义默认的提交按钮
|
||||
DefaultSubmitActionButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Divider,
|
||||
Input,
|
||||
InputNumber,
|
||||
InputPassword,
|
||||
Mentions,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Textarea,
|
||||
TimePicker,
|
||||
TreeSelect,
|
||||
Upload,
|
||||
},
|
||||
initComponentAdapter();
|
||||
setupVbenForm<ComponentType>({
|
||||
config: {
|
||||
// ant design vue组件库默认都是 v-model:value
|
||||
baseModelPropName: 'value',
|
||||
|
||||
// 一些组件是 v-model:checked 或者 v-model:fileList
|
||||
// naive-ui组件不接受onChang事件,所以需要禁用
|
||||
disabledOnChangeListener: true,
|
||||
// naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效
|
||||
emptyStateValue: null,
|
||||
modelPropNameMap: {
|
||||
Checkbox: 'checked',
|
||||
Radio: 'checked',
|
||||
@@ -102,14 +26,12 @@ setupVbenForm<FormComponentType>({
|
||||
},
|
||||
},
|
||||
defineRules: {
|
||||
// 输入项目必填国际化适配
|
||||
required: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null || value.length === 0) {
|
||||
return $t('formRules.required', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// 选择项目必填国际化适配
|
||||
selectRequired: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null) {
|
||||
return $t('formRules.selectRequired', [ctx.label]);
|
||||
@@ -119,9 +41,9 @@ setupVbenForm<FormComponentType>({
|
||||
},
|
||||
});
|
||||
|
||||
const useVbenForm = useForm<FormComponentType>;
|
||||
const useVbenForm = useForm<ComponentType>;
|
||||
|
||||
export { useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<FormComponentType>;
|
||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||
export type { VbenFormProps };
|
||||
|
@@ -3,7 +3,7 @@
|
||||
社区交流群主要是为了方便大家交流,提问,解答问题,分享经验等。偏自助方式,如果你有问题,可以通过以下方式加入社区交流群:
|
||||
|
||||
- [QQ频道](https://pd.qq.com/s/16p8lvvob):推荐!!!主要提供问题解答,分享经验等。
|
||||
- QQ群:[1群](https://qm.qq.com/q/YacMHPYAMu)、[2群](https://qm.qq.com/q/ajVKZvFICk)、[3群](https://qm.qq.com/q/36zdwThP2E),[4群](https://qm.qq.com/q/sCzSlm3504),[老群](https://qm.qq.com/q/MEmHoCLbG0),主要使用者的交流群。
|
||||
- QQ群:[大群](https://qm.qq.com/q/MEmHoCLbG0),[1群](https://qm.qq.com/q/YacMHPYAMu)、[2群](https://qm.qq.com/q/ajVKZvFICk)、[3群](https://qm.qq.com/q/36zdwThP2E),[4群](https://qm.qq.com/q/sCzSlm3504),主要使用者的交流群。
|
||||
- [Discord](https://discord.com/invite/VU62jTecad): 主要提供问题解答,分享经验等。
|
||||
|
||||
::: tip
|
||||
|
@@ -14,6 +14,12 @@ outline: deep
|
||||
|
||||
:::
|
||||
|
||||
::: tip README
|
||||
|
||||
下方示例代码中的,存在一些国际化、主题色未适配问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
|
||||
|
||||
:::
|
||||
|
||||
## 基础用法
|
||||
|
||||
使用 `useVbenDrawer` 创建最基础的模态框。
|
||||
|
@@ -20,116 +20,23 @@ outline: deep
|
||||
|
||||
### 适配器说明
|
||||
|
||||
每个应用都有不同的 UI 框架,所以在应用的 `src/adapter/form` 内部,你可以根据自己的需求,进行组件适配。下面是 `Ant Design Vue` 的适配器示例代码,可根据注释查看说明:
|
||||
每个应用都有不同的 UI 框架,所以在应用的 `src/adapter/form` 和 `src/adapter/component` 内部,你可以根据自己的需求,进行组件适配。下面是 `Ant Design Vue` 的适配器示例代码,可根据注释查看说明:
|
||||
|
||||
::: details ant design 适配器
|
||||
::: details ant design vue 表单适配器
|
||||
|
||||
```ts
|
||||
import type {
|
||||
BaseFormComponentType,
|
||||
VbenFormSchema as FormSchema,
|
||||
VbenFormProps,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import type { Component, SetupContext } from 'vue';
|
||||
import { h } from 'vue';
|
||||
import type { ComponentType } from './component';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
AutoComplete,
|
||||
Button,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
Divider,
|
||||
Input,
|
||||
InputNumber,
|
||||
InputPassword,
|
||||
Mentions,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Textarea,
|
||||
TimePicker,
|
||||
TreeSelect,
|
||||
Upload,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type FormComponentType =
|
||||
| 'AutoComplete'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'Divider'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'InputPassword'
|
||||
| 'Mentions'
|
||||
| 'Radio'
|
||||
| 'RadioGroup'
|
||||
| 'RangePicker'
|
||||
| 'Rate'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'Textarea'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
) => {
|
||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
||||
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
|
||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
||||
};
|
||||
};
|
||||
|
||||
// 初始化表单组件,并注册到form组件内部
|
||||
setupVbenForm<FormComponentType>({
|
||||
components: {
|
||||
AutoComplete,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
// 自定义默认的重置按钮
|
||||
DefaultResetActionButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
||||
},
|
||||
// 自定义默认的提交按钮
|
||||
DefaultSubmitActionButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Divider,
|
||||
Input: withDefaultPlaceholder(Input, 'input'),
|
||||
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||
Mentions: withDefaultPlaceholder(Mentions, 'input'),
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select: withDefaultPlaceholder(Select, 'select'),
|
||||
Space,
|
||||
Switch,
|
||||
Textarea: withDefaultPlaceholder(Textarea, 'input'),
|
||||
TimePicker,
|
||||
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
||||
Upload,
|
||||
},
|
||||
setupVbenForm<ComponentType>({
|
||||
config: {
|
||||
// 是否禁用onChange事件监听,naive ui组件库默认不需要监听onChange事件,否则会在控制台报错
|
||||
disabledOnChangeListener: true,
|
||||
// ant design vue组件库默认都是 v-model:value
|
||||
baseModelPropName: 'value',
|
||||
// 一些组件是 v-model:checked 或者 v-model:fileList
|
||||
@@ -158,16 +65,149 @@ setupVbenForm<FormComponentType>({
|
||||
},
|
||||
});
|
||||
|
||||
const useVbenForm = useForm<FormComponentType>;
|
||||
const useVbenForm = useForm<ComponentType>;
|
||||
|
||||
export { useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<FormComponentType>;
|
||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||
export type { VbenFormProps };
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: details ant design vue 组件适配器
|
||||
|
||||
```ts
|
||||
/**
|
||||
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
|
||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||
*/
|
||||
|
||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||
|
||||
import type { Component, SetupContext } from 'vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
import { globalShareState } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
AutoComplete,
|
||||
Button,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
Divider,
|
||||
Input,
|
||||
InputNumber,
|
||||
InputPassword,
|
||||
Mentions,
|
||||
notification,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Textarea,
|
||||
TimePicker,
|
||||
TreeSelect,
|
||||
Upload,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
) => {
|
||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
||||
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
|
||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
||||
};
|
||||
};
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type ComponentType =
|
||||
| 'AutoComplete'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'DefaultButton'
|
||||
| 'Divider'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'InputPassword'
|
||||
| 'Mentions'
|
||||
| 'PrimaryButton'
|
||||
| 'Radio'
|
||||
| 'RadioGroup'
|
||||
| 'RangePicker'
|
||||
| 'Rate'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'Textarea'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
async function initComponentAdapter() {
|
||||
const components: Partial<Record<ComponentType, Component>> = {
|
||||
// 如果你的组件体积比较大,可以使用异步加载
|
||||
// Button: () =>
|
||||
// import('xxx').then((res) => res.Button),
|
||||
|
||||
AutoComplete,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
// 自定义默认按钮
|
||||
DefaultButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
||||
},
|
||||
Divider,
|
||||
Input: withDefaultPlaceholder(Input, 'input'),
|
||||
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||
Mentions: withDefaultPlaceholder(Mentions, 'input'),
|
||||
// 自定义主要按钮
|
||||
PrimaryButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select: withDefaultPlaceholder(Select, 'select'),
|
||||
Space,
|
||||
Switch,
|
||||
Textarea: withDefaultPlaceholder(Textarea, 'input'),
|
||||
TimePicker,
|
||||
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
||||
Upload,
|
||||
};
|
||||
|
||||
// 将组件注册到全局共享状态中
|
||||
globalShareState.setComponents(components);
|
||||
|
||||
// 定义全局共享状态中的消息提示
|
||||
globalShareState.defineMessage({
|
||||
// 复制成功消息提示
|
||||
copyPreferencesSuccess: (title, content) => {
|
||||
notification.success({
|
||||
description: content,
|
||||
message: title,
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export { initComponentAdapter };
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 基础用法
|
||||
|
||||
::: tip README
|
||||
@@ -218,7 +258,7 @@ _注意_ 需要指定 `dependencies` 的 `triggerFields` 属性,设置由谁
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { useVbenForm } from '#/adapter';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
// Form 为弹窗组件
|
||||
// formApi 为弹窗的方法
|
||||
@@ -271,6 +311,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
|
||||
| collapsedRows | 折叠时保持的行数 | `number` | `1` |
|
||||
| commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - |
|
||||
| schema | 表单项的每一项配置 | `FormSchema` | - |
|
||||
| submitOnEnter | 按下回车健时提交表单 | `boolean` | false |
|
||||
|
||||
### TS 类型说明
|
||||
|
||||
@@ -435,7 +476,7 @@ rules的值可以是一个字符串,也可以是一个zod的schema。
|
||||
rules也支持 zod 的 schema,可以进行更复杂的校验,zod 的使用请查看 [zod文档](https://zod.dev/)。
|
||||
|
||||
```ts
|
||||
import { z } from '#/adapter';
|
||||
import { z } from '#/adapter/form';
|
||||
|
||||
// 基础类型
|
||||
{
|
||||
|
@@ -14,6 +14,12 @@ outline: deep
|
||||
|
||||
:::
|
||||
|
||||
::: tip README
|
||||
|
||||
下方示例代码中的,存在一些国际化、主题色未适配问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
|
||||
|
||||
:::
|
||||
|
||||
## 基础用法
|
||||
|
||||
使用 `useVbenModal` 创建最基础的模态框。
|
||||
|
7
docs/src/components/common-ui/vben-vxe-table.md
Normal file
7
docs/src/components/common-ui/vben-vxe-table.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Vben Vxe Table 表格
|
||||
|
||||
文档待补充,如果需要使用,可先行查看 vxe-table 文档和 示例代码,内部有部分注释。
|
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { Button, message, Space } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
const [BaseForm, formApi] = useVbenForm({
|
||||
// 所有表单项共用,可单独在表单内覆盖
|
||||
@@ -81,6 +81,97 @@ function handleClick(
|
||||
| 'updateSubmitButton',
|
||||
) {
|
||||
switch (action) {
|
||||
case 'batchAddSchema': {
|
||||
formApi.setState((prev) => {
|
||||
const currentSchema = prev?.schema ?? [];
|
||||
const newSchema = [];
|
||||
for (let i = 0; i < 2; i++) {
|
||||
newSchema.push({
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入',
|
||||
},
|
||||
fieldName: `field${i}${Date.now()}`,
|
||||
label: `field+`,
|
||||
});
|
||||
}
|
||||
return {
|
||||
schema: [...currentSchema, ...newSchema],
|
||||
};
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'batchDeleteSchema': {
|
||||
formApi.setState((prev) => {
|
||||
const currentSchema = prev?.schema ?? [];
|
||||
return {
|
||||
schema: currentSchema.slice(0, -2),
|
||||
};
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'disabled': {
|
||||
formApi.setState({ commonConfig: { disabled: true } });
|
||||
break;
|
||||
}
|
||||
case 'hiddenAction': {
|
||||
formApi.setState({ showDefaultActions: false });
|
||||
break;
|
||||
}
|
||||
case 'hiddenResetButton': {
|
||||
formApi.setState({ resetButtonOptions: { show: false } });
|
||||
break;
|
||||
}
|
||||
case 'hiddenSubmitButton': {
|
||||
formApi.setState({ submitButtonOptions: { show: false } });
|
||||
break;
|
||||
}
|
||||
case 'labelWidth': {
|
||||
formApi.setState({
|
||||
commonConfig: {
|
||||
labelWidth: 150,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'resetDisabled': {
|
||||
formApi.setState({ commonConfig: { disabled: false } });
|
||||
break;
|
||||
}
|
||||
case 'resetLabelWidth': {
|
||||
formApi.setState({
|
||||
commonConfig: {
|
||||
labelWidth: 100,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'showAction': {
|
||||
formApi.setState({ showDefaultActions: true });
|
||||
break;
|
||||
}
|
||||
case 'showResetButton': {
|
||||
formApi.setState({ resetButtonOptions: { show: true } });
|
||||
break;
|
||||
}
|
||||
case 'showSubmitButton': {
|
||||
formApi.setState({ submitButtonOptions: { show: true } });
|
||||
break;
|
||||
}
|
||||
case 'updateActionAlign': {
|
||||
formApi.setState({
|
||||
// 可以自行调整class
|
||||
actionWrapperClass: 'text-center',
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'updateResetButton': {
|
||||
formApi.setState({
|
||||
resetButtonOptions: { disabled: true },
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'updateSchema': {
|
||||
formApi.updateSchema([
|
||||
{
|
||||
@@ -106,103 +197,12 @@ function handleClick(
|
||||
message.success('字段 `fieldOptions` 下拉选项更新成功。');
|
||||
break;
|
||||
}
|
||||
|
||||
case 'labelWidth': {
|
||||
formApi.setState({
|
||||
commonConfig: {
|
||||
labelWidth: 150,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'resetLabelWidth': {
|
||||
formApi.setState({
|
||||
commonConfig: {
|
||||
labelWidth: 100,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'disabled': {
|
||||
formApi.setState({ commonConfig: { disabled: true } });
|
||||
break;
|
||||
}
|
||||
case 'resetDisabled': {
|
||||
formApi.setState({ commonConfig: { disabled: false } });
|
||||
break;
|
||||
}
|
||||
case 'hiddenAction': {
|
||||
formApi.setState({ showDefaultActions: false });
|
||||
break;
|
||||
}
|
||||
case 'showAction': {
|
||||
formApi.setState({ showDefaultActions: true });
|
||||
break;
|
||||
}
|
||||
case 'hiddenResetButton': {
|
||||
formApi.setState({ resetButtonOptions: { show: false } });
|
||||
break;
|
||||
}
|
||||
case 'showResetButton': {
|
||||
formApi.setState({ resetButtonOptions: { show: true } });
|
||||
break;
|
||||
}
|
||||
case 'hiddenSubmitButton': {
|
||||
formApi.setState({ submitButtonOptions: { show: false } });
|
||||
break;
|
||||
}
|
||||
case 'showSubmitButton': {
|
||||
formApi.setState({ submitButtonOptions: { show: true } });
|
||||
break;
|
||||
}
|
||||
case 'updateResetButton': {
|
||||
formApi.setState({
|
||||
resetButtonOptions: { disabled: true },
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'updateSubmitButton': {
|
||||
formApi.setState({
|
||||
submitButtonOptions: { loading: true },
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'updateActionAlign': {
|
||||
formApi.setState({
|
||||
// 可以自行调整class
|
||||
actionWrapperClass: 'text-center',
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'batchAddSchema': {
|
||||
formApi.setState((prev) => {
|
||||
const currentSchema = prev?.schema ?? [];
|
||||
const newSchema = [];
|
||||
for (let i = 0; i < 2; i++) {
|
||||
newSchema.push({
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入',
|
||||
},
|
||||
fieldName: `field${i}${Date.now()}`,
|
||||
label: `field+`,
|
||||
});
|
||||
}
|
||||
return {
|
||||
schema: [...currentSchema, ...newSchema],
|
||||
};
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'batchDeleteSchema': {
|
||||
formApi.setState((prev) => {
|
||||
const currentSchema = prev?.schema ?? [];
|
||||
return {
|
||||
schema: currentSchema.slice(0, -2),
|
||||
};
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
const [BaseForm] = useVbenForm({
|
||||
// 所有表单项共用,可单独在表单内覆盖
|
||||
|
@@ -3,7 +3,7 @@ import { h } from 'vue';
|
||||
|
||||
import { Input, message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
const [Form] = useVbenForm({
|
||||
// 所有表单项共用,可单独在表单内覆盖
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
const [Form] = useVbenForm({
|
||||
// 提交函数
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
const [QueryForm] = useVbenForm({
|
||||
// 默认展开
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm, z } from '#/adapter';
|
||||
import { useVbenForm, z } from '#/adapter/form';
|
||||
|
||||
const [Form] = useVbenForm({
|
||||
// 所有表单项共用,可单独在表单内覆盖
|
||||
|
@@ -233,12 +233,13 @@ function createRequestClient(baseURL: string) {
|
||||
fulfilled: (response) => {
|
||||
const { data: responseData, status } = response;
|
||||
|
||||
const { code, data, message: msg } = responseData;
|
||||
const { code, data } = responseData;
|
||||
|
||||
if (status >= 200 && status < 400 && code === 0) {
|
||||
return data;
|
||||
}
|
||||
throw new Error(`Error ${status}: ${msg}`);
|
||||
const error = { response };
|
||||
throw error;
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# 友情链接
|
||||
|
||||
如果您的网站是与 Vben Admin 相关的,或者也属于开源项目,欢迎联系我们,我们会将与您的网站加入交换友情链接。
|
||||
如果您的网站是与 Vben Admin 相关的,也属于开源项目,欢迎联系我们,我们会将与您的网站加入交换友情链接。
|
||||
|
||||
## 交换方式
|
||||
|
||||
|
@@ -236,12 +236,13 @@ function createRequestClient(baseURL: string) {
|
||||
fulfilled: (response) => {
|
||||
const { data: responseData, status } = response;
|
||||
|
||||
const { code, data, message: msg } = responseData;
|
||||
const { code, data } = responseData;
|
||||
|
||||
if (status >= 200 && status < 400 && code === 0) {
|
||||
return data;
|
||||
}
|
||||
throw new Error(`Error ${status}: ${msg}`);
|
||||
const error = { response };
|
||||
throw error;
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/commitlint-config",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
|
@@ -101,6 +101,7 @@ const customConfig: Linter.Config[] = [
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
// 不能引入@vben/*里面的包
|
||||
files: [
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/stylelint-config",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/node-utils",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/tailwind-config",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/tsconfig",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/vite-config",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
|
@@ -16,7 +16,7 @@ function defineConfig(
|
||||
let projectType = type;
|
||||
|
||||
// 根据包是否存在 index.html,自动判断类型
|
||||
if (type === 'auto') {
|
||||
if (projectType === 'auto') {
|
||||
const htmlPath = join(process.cwd(), 'index.html');
|
||||
projectType = existsSync(htmlPath) ? 'application' : 'library';
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vben-admin-pro",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"private": true,
|
||||
"keywords": [
|
||||
"monorepo",
|
||||
@@ -28,7 +28,7 @@
|
||||
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build",
|
||||
"build:analyze": "turbo build:analyze",
|
||||
"build:antd": "pnpm run build --filter=@vben/web-antd",
|
||||
"build:docker": "./build-local-docker-image.sh",
|
||||
"build:docker": "./scripts/deploy/build-local-docker-image.sh",
|
||||
"build:docs": "pnpm run build --filter=@vben/docs",
|
||||
"build:ele": "pnpm run build --filter=@vben/web-ele",
|
||||
"build:naive": "pnpm run build --filter=@vben/web-naive",
|
||||
@@ -57,7 +57,7 @@
|
||||
"reinstall": "pnpm clean --del-lock && pnpm install",
|
||||
"test:unit": "vitest run --dom",
|
||||
"test:e2e": "turbo run test:e2e",
|
||||
"update:deps": "pnpm update --latest --recursive",
|
||||
"update:deps": "npx taze -r -w",
|
||||
"version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -99,7 +99,7 @@
|
||||
"node": ">=20.10.0",
|
||||
"pnpm": ">=9.5.0"
|
||||
},
|
||||
"packageManager": "pnpm@9.12.1",
|
||||
"packageManager": "pnpm@9.12.2",
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"allowedVersions": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/design",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -94,7 +94,6 @@
|
||||
}
|
||||
|
||||
/* 只有非mac下才进行调整,mac下使用默认滚动条 */
|
||||
|
||||
html:not([data-platform='macOs']) {
|
||||
::-webkit-scrollbar {
|
||||
@apply h-[10px] w-[10px];
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import './default/index.css';
|
||||
import './dark/index.css';
|
||||
import './default.css';
|
||||
import './dark.css';
|
||||
|
||||
export {};
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import './design-tokens';
|
||||
|
||||
import './css/global.css';
|
||||
import './css/transition.css';
|
||||
import './css/nprogress.css';
|
||||
import './css/ui.css';
|
||||
import './design-tokens';
|
||||
|
||||
export {};
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/icons",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -9,5 +9,6 @@ export default defineBuildConfig({
|
||||
'src/utils/index',
|
||||
'src/color/index',
|
||||
'src/cache/index',
|
||||
'src/global-state',
|
||||
],
|
||||
});
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/shared",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
@@ -11,7 +11,8 @@
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm unbuild"
|
||||
"build": "pnpm unbuild",
|
||||
"stub": "pnpm unbuild --stub"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
@@ -42,6 +43,11 @@
|
||||
"types": "./src/store.ts",
|
||||
"development": "./src/store.ts",
|
||||
"default": "./dist/store.mjs"
|
||||
},
|
||||
"./global-state": {
|
||||
"types": "./dist/global-state.d.ts",
|
||||
"development": "./src/global-state.ts",
|
||||
"default": "./dist/global-state.mjs"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
@@ -63,15 +69,18 @@
|
||||
"default": "./dist/cache/index.mjs"
|
||||
},
|
||||
"./store": {
|
||||
"types": "./dist/store/index.d.ts",
|
||||
"types": "./dist/store.d.ts",
|
||||
"default": "./dist/store.mjs"
|
||||
},
|
||||
"./global-state": {
|
||||
"types": "./dist/global-state.d.ts",
|
||||
"default": "./dist/global-state.mjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "catalog:",
|
||||
"@tanstack/vue-store": "catalog:",
|
||||
"@vue/reactivity": "catalog:",
|
||||
"@vue/shared": "catalog:",
|
||||
"clsx": "catalog:",
|
||||
"defu": "catalog:",
|
||||
|
45
packages/@core/base/shared/src/global-state.ts
Normal file
45
packages/@core/base/shared/src/global-state.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 全局复用的变量、组件、配置,各个模块之间共享
|
||||
* 通过单例模式实现,单例必须注意不受请求影响,例如用户信息这些需要根据请求获取的。后续如果有ssr需求,也不会影响
|
||||
*/
|
||||
|
||||
interface ComponentsState {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface MessageState {
|
||||
copyPreferencesSuccess?: (title: string, content?: string) => void;
|
||||
}
|
||||
|
||||
export interface IGlobalSharedState {
|
||||
components: ComponentsState;
|
||||
message: MessageState;
|
||||
}
|
||||
|
||||
class GlobalShareState {
|
||||
#components: ComponentsState = {};
|
||||
#message: MessageState = {};
|
||||
|
||||
/**
|
||||
* 定义框架内部各个场景的消息提示
|
||||
*/
|
||||
public defineMessage({ copyPreferencesSuccess }: MessageState) {
|
||||
this.#message = {
|
||||
copyPreferencesSuccess,
|
||||
};
|
||||
}
|
||||
|
||||
public getComponents(): ComponentsState {
|
||||
return this.#components;
|
||||
}
|
||||
|
||||
public getMessage(): MessageState {
|
||||
return this.#message;
|
||||
}
|
||||
|
||||
public setComponents(value: ComponentsState) {
|
||||
this.#components = value;
|
||||
}
|
||||
}
|
||||
|
||||
export const globalShareState = new GlobalShareState();
|
@@ -5,7 +5,6 @@ export * from './inference';
|
||||
export * from './letter';
|
||||
export * from './merge';
|
||||
export * from './nprogress';
|
||||
export * from './reactivity';
|
||||
export * from './state-handler';
|
||||
export * from './to';
|
||||
export * from './tree';
|
||||
|
@@ -1,26 +0,0 @@
|
||||
import { isProxy, isReactive, isRef, toRaw } from '@vue/reactivity';
|
||||
|
||||
function deepToRaw<T extends Record<string, any>>(sourceObj: T): T {
|
||||
const objectIterator = (input: any): any => {
|
||||
if (Array.isArray(input)) {
|
||||
return input.map((item) => objectIterator(item));
|
||||
}
|
||||
if (isRef(input) || isReactive(input) || isProxy(input)) {
|
||||
return objectIterator(toRaw(input));
|
||||
}
|
||||
if (input && typeof input === 'object') {
|
||||
const result = {} as T;
|
||||
for (const key in input) {
|
||||
if (Object.prototype.hasOwnProperty.call(input, key)) {
|
||||
result[key as keyof T] = objectIterator(input[key]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return input;
|
||||
};
|
||||
|
||||
return objectIterator(sourceObj);
|
||||
}
|
||||
|
||||
export { deepToRaw };
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/typings",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/composables",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -10,13 +10,6 @@ function useSortable<T extends HTMLElement>(
|
||||
// @ts-expect-error - This is a dynamic import
|
||||
'sortablejs/modular/sortable.complete.esm.js'
|
||||
);
|
||||
// const { AutoScroll } = await import(
|
||||
// // @ts-expect-error - This is a dynamic import
|
||||
// 'sortablejs/modular/sortable.core.esm.js'
|
||||
// );
|
||||
|
||||
// Sortable?.default?.mount?.(AutoScroll);
|
||||
|
||||
const sortable = Sortable?.default?.create?.(sortableContainer, {
|
||||
animation: 300,
|
||||
delay: 400,
|
||||
|
@@ -93,7 +93,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj
|
||||
"mode": "dark",
|
||||
"radius": "0.5",
|
||||
"semiDarkHeader": false,
|
||||
"semiDarkSidebar": true,
|
||||
"semiDarkSidebar": false,
|
||||
},
|
||||
"transition": {
|
||||
"enable": true,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/preferences",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -82,7 +82,6 @@ const defaultPreferences: Preferences = {
|
||||
showIcon: true,
|
||||
showMaximize: true,
|
||||
showMore: true,
|
||||
|
||||
styleType: 'chrome',
|
||||
},
|
||||
theme: {
|
||||
@@ -94,7 +93,7 @@ const defaultPreferences: Preferences = {
|
||||
mode: 'dark',
|
||||
radius: '0.5',
|
||||
semiDarkHeader: false,
|
||||
semiDarkSidebar: true,
|
||||
semiDarkSidebar: false,
|
||||
},
|
||||
transition: {
|
||||
enable: true,
|
||||
|
@@ -18,7 +18,7 @@ function updateCSSVariables(preferences: Preferences) {
|
||||
|
||||
const theme = preferences?.theme ?? {};
|
||||
|
||||
const { builtinType, colorPrimary, mode, radius } = theme;
|
||||
const { builtinType, mode, radius } = theme;
|
||||
|
||||
// html 设置 dark 类
|
||||
if (Reflect.has(theme, 'mode')) {
|
||||
@@ -58,7 +58,7 @@ function updateCSSVariables(preferences: Preferences) {
|
||||
Reflect.has(theme, 'colorSuccess') ||
|
||||
Reflect.has(theme, 'colorWarning')
|
||||
) {
|
||||
preferences.theme.colorPrimary = builtinTypeColorPrimary || colorPrimary;
|
||||
// preferences.theme.colorPrimary = builtinTypeColorPrimary || colorPrimary;
|
||||
updateMainColorVariables(preferences);
|
||||
}
|
||||
|
||||
|
@@ -75,6 +75,11 @@ watch(
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
handleReset,
|
||||
handleSubmit,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
@@ -83,8 +88,11 @@ watch(
|
||||
"
|
||||
:style="queryFormStyle"
|
||||
>
|
||||
<!-- 重置按钮前 -->
|
||||
<slot name="reset-before"></slot>
|
||||
|
||||
<component
|
||||
:is="COMPONENT_MAP.DefaultResetActionButton"
|
||||
:is="COMPONENT_MAP.DefaultButton"
|
||||
v-if="resetButtonOptions.show"
|
||||
class="mr-3"
|
||||
type="button"
|
||||
@@ -94,8 +102,11 @@ watch(
|
||||
{{ resetButtonOptions.content }}
|
||||
</component>
|
||||
|
||||
<!-- 提交按钮前 -->
|
||||
<slot name="submit-before"></slot>
|
||||
|
||||
<component
|
||||
:is="COMPONENT_MAP.DefaultSubmitActionButton"
|
||||
:is="COMPONENT_MAP.PrimaryButton"
|
||||
v-if="submitButtonOptions.show"
|
||||
type="button"
|
||||
@click="handleSubmit"
|
||||
@@ -104,6 +115,9 @@ watch(
|
||||
{{ submitButtonOptions.content }}
|
||||
</component>
|
||||
|
||||
<!-- 展开按钮前 -->
|
||||
<slot name="expand-before"></slot>
|
||||
|
||||
<VbenExpandableArrow
|
||||
v-if="rootProps.showCollapseButton"
|
||||
v-model:model-value="collapsed"
|
||||
@@ -111,5 +125,8 @@ watch(
|
||||
>
|
||||
<span>{{ collapsed ? $t('expand') : $t('collapse') }}</span>
|
||||
</VbenExpandableArrow>
|
||||
|
||||
<!-- 展开按钮后 -->
|
||||
<slot name="expand-after"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -15,6 +15,7 @@ import {
|
||||
VbenPinInput,
|
||||
VbenSelect,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
import { globalShareState } from '@vben-core/shared/global-state';
|
||||
|
||||
import { defineRule } from 'vee-validate';
|
||||
|
||||
@@ -23,8 +24,8 @@ const DEFAULT_MODEL_PROP_NAME = 'modelValue';
|
||||
export const DEFAULT_FORM_COMMON_CONFIG: FormCommonConfig = {};
|
||||
|
||||
export const COMPONENT_MAP: Record<BaseFormComponentType, Component> = {
|
||||
DefaultResetActionButton: h(VbenButton, { size: 'sm', variant: 'outline' }),
|
||||
DefaultSubmitActionButton: h(VbenButton, { size: 'sm', variant: 'default' }),
|
||||
DefaultButton: h(VbenButton, { size: 'sm', variant: 'outline' }),
|
||||
PrimaryButton: h(VbenButton, { size: 'sm', variant: 'default' }),
|
||||
VbenCheckbox,
|
||||
VbenInput,
|
||||
VbenInputPassword,
|
||||
@@ -41,7 +42,7 @@ export const COMPONENT_BIND_EVENT_MAP: Partial<
|
||||
export function setupVbenForm<
|
||||
T extends BaseFormComponentType = BaseFormComponentType,
|
||||
>(options: VbenFormAdapterOptions<T>) {
|
||||
const { components, config, defineRules } = options;
|
||||
const { config, defineRules } = options;
|
||||
|
||||
const { disabledOnChangeListener = false, emptyStateValue = undefined } =
|
||||
(config || {}) as FormCommonConfig;
|
||||
@@ -63,6 +64,8 @@ export function setupVbenForm<
|
||||
| Record<BaseFormComponentType, string>
|
||||
| undefined;
|
||||
|
||||
const components = globalShareState.getComponents();
|
||||
|
||||
for (const component of Object.keys(components)) {
|
||||
const key = component as BaseFormComponentType;
|
||||
COMPONENT_MAP[key] = components[component as never];
|
||||
|
@@ -35,6 +35,7 @@ function getDefaultState(): VbenFormProps {
|
||||
showCollapseButton: false,
|
||||
showDefaultActions: true,
|
||||
submitButtonOptions: {},
|
||||
submitOnEnter: false,
|
||||
wrapperClass: 'grid-cols-1',
|
||||
};
|
||||
}
|
||||
|
@@ -278,10 +278,10 @@ function autofocus() {
|
||||
cn(
|
||||
'flex leading-6',
|
||||
{
|
||||
'mr-2 flex-shrink-0': !isVertical,
|
||||
'mr-2 flex-shrink-0 justify-end': !isVertical,
|
||||
'flex-row': isVertical,
|
||||
},
|
||||
!isVertical && labelClass,
|
||||
labelClass,
|
||||
)
|
||||
"
|
||||
:help="help"
|
||||
|
@@ -1,20 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { FormLabel, VbenHelpTooltip } from '@vben-core/shadcn-ui';
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
help?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
const props = defineProps<Props>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FormLabel class="flex flex-row-reverse items-center">
|
||||
<FormLabel :class="cn('mb-1 flex items-center', props.class)">
|
||||
<span v-if="required" class="text-destructive mr-[2px]">*</span>
|
||||
<slot></slot>
|
||||
<VbenHelpTooltip v-if="help" trigger-class="size-3.5 ml-1">
|
||||
{{ help }}
|
||||
</VbenHelpTooltip>
|
||||
<slot></slot>
|
||||
<span v-if="required" class="text-destructive mr-[2px]">*</span>
|
||||
</FormLabel>
|
||||
</template>
|
||||
|
@@ -9,8 +9,8 @@ import type { Component, HtmlHTMLAttributes, Ref } from 'vue';
|
||||
export type FormLayout = 'horizontal' | 'vertical';
|
||||
|
||||
export type BaseFormComponentType =
|
||||
| 'DefaultResetActionButton'
|
||||
| 'DefaultSubmitActionButton'
|
||||
| 'DefaultButton'
|
||||
| 'PrimaryButton'
|
||||
| 'VbenCheckbox'
|
||||
| 'VbenInput'
|
||||
| 'VbenInputPassword'
|
||||
@@ -319,7 +319,6 @@ export interface VbenFormProps<
|
||||
* 重置按钮参数
|
||||
*/
|
||||
resetButtonOptions?: ActionButtonOptions;
|
||||
|
||||
/**
|
||||
* 是否显示默认操作按钮
|
||||
* @default true
|
||||
@@ -330,6 +329,12 @@ export interface VbenFormProps<
|
||||
* 提交按钮参数
|
||||
*/
|
||||
submitButtonOptions?: ActionButtonOptions;
|
||||
|
||||
/**
|
||||
* 是否在回车时提交表单
|
||||
* @default false
|
||||
*/
|
||||
submitOnEnter?: boolean;
|
||||
}
|
||||
|
||||
export type ExtendedFormApi = {
|
||||
@@ -341,7 +346,6 @@ export type ExtendedFormApi = {
|
||||
export interface VbenFormAdapterOptions<
|
||||
T extends BaseFormComponentType = BaseFormComponentType,
|
||||
> {
|
||||
components: Partial<Record<T, Component>>;
|
||||
config?: {
|
||||
baseModelPropName?: string;
|
||||
disabledOnChangeListener?: boolean;
|
||||
|
@@ -6,6 +6,8 @@ import type { ExtendedFormApi, VbenFormProps } from './types';
|
||||
import { useForwardPriorityValues } from '@vben-core/composables';
|
||||
// import { isFunction } from '@vben-core/shared/utils';
|
||||
|
||||
import { useTemplateRef } from 'vue';
|
||||
|
||||
import FormActions from './components/form-actions.vue';
|
||||
import {
|
||||
COMPONENT_BIND_EVENT_MAP,
|
||||
@@ -21,6 +23,8 @@ interface Props extends VbenFormProps {
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const formActionsRef = useTemplateRef<typeof FormActions>('formActionsRef');
|
||||
|
||||
const state = props.formApi?.useStore?.();
|
||||
|
||||
const forward = useForwardPriorityValues(props, state);
|
||||
@@ -34,10 +38,18 @@ props.formApi?.mount?.(form);
|
||||
const handleUpdateCollapsed = (value: boolean) => {
|
||||
props.formApi?.setState({ collapsed: !!value });
|
||||
};
|
||||
|
||||
function handleKeyDownEnter() {
|
||||
if (!state.value.submitOnEnter || !formActionsRef.value) {
|
||||
return;
|
||||
}
|
||||
formActionsRef.value?.handleSubmit?.();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form
|
||||
@keydown.enter.prevent="handleKeyDownEnter"
|
||||
v-bind="forward"
|
||||
:collapsed="state.collapsed"
|
||||
:component-bind-event-map="COMPONENT_BIND_EVENT_MAP"
|
||||
@@ -56,9 +68,23 @@ const handleUpdateCollapsed = (value: boolean) => {
|
||||
<slot v-bind="slotProps">
|
||||
<FormActions
|
||||
v-if="forward.showDefaultActions"
|
||||
ref="formActionsRef"
|
||||
:model-value="state.collapsed"
|
||||
@update:model-value="handleUpdateCollapsed"
|
||||
/>
|
||||
>
|
||||
<template #reset-before="resetSlotProps">
|
||||
<slot name="reset-before" v-bind="resetSlotProps"></slot>
|
||||
</template>
|
||||
<template #submit-before="submitSlotProps">
|
||||
<slot name="submit-before" v-bind="submitSlotProps"></slot>
|
||||
</template>
|
||||
<template #expand-before="expandBeforeSlotProps">
|
||||
<slot name="expand-before" v-bind="expandBeforeSlotProps"></slot>
|
||||
</template>
|
||||
<template #expand-after="expandAfterSlotProps">
|
||||
<slot name="expand-after" v-bind="expandAfterSlotProps"></slot>
|
||||
</template>
|
||||
</FormActions>
|
||||
</slot>
|
||||
</template>
|
||||
</Form>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/layout-ui",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/menu-ui",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -23,6 +23,7 @@ import {
|
||||
VbenLoading,
|
||||
VisuallyHidden,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
import { globalShareState } from '@vben-core/shared/global-state';
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
interface Props extends DrawerProps {
|
||||
@@ -33,6 +34,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
drawerApi: undefined,
|
||||
});
|
||||
|
||||
const components = globalShareState.getComponents();
|
||||
|
||||
const id = useId();
|
||||
provide('DISMISSABLE_DRAWER_ID', id);
|
||||
|
||||
@@ -187,7 +190,8 @@ function handleFocusOutside(e: Event) {
|
||||
>
|
||||
<slot name="prepend-footer"></slot>
|
||||
<slot name="footer">
|
||||
<VbenButton
|
||||
<component
|
||||
:is="components.DefaultButton || VbenButton"
|
||||
v-if="showCancelButton"
|
||||
variant="ghost"
|
||||
@click="() => drawerApi?.onCancel()"
|
||||
@@ -195,8 +199,10 @@ function handleFocusOutside(e: Event) {
|
||||
<slot name="cancelText">
|
||||
{{ cancelText || $t('cancel') }}
|
||||
</slot>
|
||||
</VbenButton>
|
||||
<VbenButton
|
||||
</component>
|
||||
|
||||
<component
|
||||
:is="components.PrimaryButton || VbenButton"
|
||||
v-if="showConfirmButton"
|
||||
:loading="confirmLoading"
|
||||
@click="() => drawerApi?.onConfirm()"
|
||||
@@ -204,7 +210,7 @@ function handleFocusOutside(e: Event) {
|
||||
<slot name="confirmText">
|
||||
{{ confirmText || $t('confirm') }}
|
||||
</slot>
|
||||
</VbenButton>
|
||||
</component>
|
||||
</slot>
|
||||
<slot name="append-footer"></slot>
|
||||
</SheetFooter>
|
||||
|
@@ -22,6 +22,7 @@ import {
|
||||
VbenLoading,
|
||||
VisuallyHidden,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
import { globalShareState } from '@vben-core/shared/global-state';
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { useModalDraggable } from './use-modal-draggable';
|
||||
@@ -34,6 +35,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
modalApi: undefined,
|
||||
});
|
||||
|
||||
const components = globalShareState.getComponents();
|
||||
|
||||
const contentRef = ref();
|
||||
const wrapperRef = ref<HTMLElement>();
|
||||
const dialogRef = ref();
|
||||
@@ -256,7 +259,8 @@ function handleFocusOutside(e: Event) {
|
||||
>
|
||||
<slot name="prepend-footer"></slot>
|
||||
<slot name="footer">
|
||||
<VbenButton
|
||||
<component
|
||||
:is="components.DefaultButton || VbenButton"
|
||||
v-if="showCancelButton"
|
||||
variant="ghost"
|
||||
@click="() => modalApi?.onCancel()"
|
||||
@@ -264,8 +268,10 @@ function handleFocusOutside(e: Event) {
|
||||
<slot name="cancelText">
|
||||
{{ cancelText || $t('cancel') }}
|
||||
</slot>
|
||||
</VbenButton>
|
||||
<VbenButton
|
||||
</component>
|
||||
|
||||
<component
|
||||
:is="components.PrimaryButton || VbenButton"
|
||||
v-if="showConfirmButton"
|
||||
:loading="confirmLoading"
|
||||
@click="() => modalApi?.onConfirm()"
|
||||
@@ -273,7 +279,7 @@ function handleFocusOutside(e: Event) {
|
||||
<slot name="confirmText">
|
||||
{{ confirmText || $t('confirm') }}
|
||||
</slot>
|
||||
</VbenButton>
|
||||
</component>
|
||||
</slot>
|
||||
<slot name="append-footer"></slot>
|
||||
</DialogFooter>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/shadcn-ui",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"#main": "./dist/index.mjs",
|
||||
"#module": "./dist/index.mjs",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
|
@@ -23,7 +23,6 @@ export * from './sheet';
|
||||
export * from './switch';
|
||||
export * from './tabs';
|
||||
export * from './textarea';
|
||||
export * from './toast';
|
||||
export * from './toggle';
|
||||
export * from './toggle-group';
|
||||
export * from './tooltip';
|
||||
|
@@ -1,35 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import {
|
||||
ToastRoot,
|
||||
type ToastRootEmits,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
|
||||
import { type ToastProps, toastVariants } from './toast';
|
||||
|
||||
const props = defineProps<ToastProps>();
|
||||
|
||||
const emits = defineEmits<ToastRootEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ToastRoot
|
||||
v-bind="forwarded"
|
||||
:class="cn(toastVariants({ variant }), props.class)"
|
||||
@update:open="onOpenChange"
|
||||
>
|
||||
<slot></slot>
|
||||
</ToastRoot>
|
||||
</template>
|
@@ -1,29 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { ToastAction, type ToastActionProps } from 'radix-vue';
|
||||
|
||||
const props = defineProps<{ class?: any } & ToastActionProps>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ToastAction
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'hover:bg-secondary focus:ring-ring group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive border-border inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors focus:outline-none focus:ring-1 disabled:pointer-events-none disabled:opacity-50',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</ToastAction>
|
||||
</template>
|
@@ -1,34 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { X } from 'lucide-vue-next';
|
||||
import { ToastClose, type ToastCloseProps } from 'radix-vue';
|
||||
|
||||
const props = defineProps<
|
||||
{
|
||||
class?: any;
|
||||
} & ToastCloseProps
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ToastClose
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'text-foreground/50 hover:text-foreground absolute right-1 top-1 rounded-md p-1 opacity-0 transition-opacity focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<X class="size-4" />
|
||||
</ToastClose>
|
||||
</template>
|
@@ -1,24 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { ToastDescription, type ToastDescriptionProps } from 'radix-vue';
|
||||
|
||||
const props = defineProps<{ class?: any } & ToastDescriptionProps>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ToastDescription
|
||||
:class="cn('text-sm opacity-90', props.class)"
|
||||
v-bind="delegatedProps"
|
||||
>
|
||||
<slot></slot>
|
||||
</ToastDescription>
|
||||
</template>
|
@@ -1,11 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ToastProvider, type ToastProviderProps } from 'radix-vue';
|
||||
|
||||
const props = defineProps<ToastProviderProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ToastProvider v-bind="props">
|
||||
<slot></slot>
|
||||
</ToastProvider>
|
||||
</template>
|
@@ -1,24 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { ToastTitle, type ToastTitleProps } from 'radix-vue';
|
||||
|
||||
const props = defineProps<{ class?: any } & ToastTitleProps>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ToastTitle
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('text-sm font-semibold [&+div]:text-xs', props.class)"
|
||||
>
|
||||
<slot></slot>
|
||||
</ToastTitle>
|
||||
</template>
|
@@ -1,27 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { ToastViewport, type ToastViewportProps } from 'radix-vue';
|
||||
|
||||
const props = defineProps<{ class?: any } & ToastViewportProps>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ToastViewport
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'fixed top-0 z-[1200] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
@@ -1,36 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { isVNode } from 'vue';
|
||||
|
||||
import Toast from './Toast.vue';
|
||||
import ToastClose from './ToastClose.vue';
|
||||
import ToastDescription from './ToastDescription.vue';
|
||||
import ToastProvider from './ToastProvider.vue';
|
||||
import ToastTitle from './ToastTitle.vue';
|
||||
import ToastViewport from './ToastViewport.vue';
|
||||
import { useToast } from './use-toast';
|
||||
|
||||
const { toasts } = useToast();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ToastProvider swipe-direction="down">
|
||||
<Toast v-for="toast in toasts" :key="toast.id" v-bind="toast">
|
||||
<div class="grid gap-1">
|
||||
<ToastTitle v-if="toast.title">
|
||||
{{ toast.title }}
|
||||
</ToastTitle>
|
||||
<template v-if="toast.description">
|
||||
<ToastDescription v-if="isVNode(toast.description)">
|
||||
<component :is="toast.description" />
|
||||
</ToastDescription>
|
||||
<ToastDescription v-else>
|
||||
{{ toast.description }}
|
||||
</ToastDescription>
|
||||
</template>
|
||||
<ToastClose />
|
||||
</div>
|
||||
<component :is="toast.action" />
|
||||
</Toast>
|
||||
<ToastViewport />
|
||||
</ToastProvider>
|
||||
</template>
|
@@ -1,11 +0,0 @@
|
||||
export * from './toast';
|
||||
export { default as Toast } from './Toast.vue';
|
||||
export { default as ToastAction } from './ToastAction.vue';
|
||||
export { default as ToastClose } from './ToastClose.vue';
|
||||
export { default as ToastDescription } from './ToastDescription.vue';
|
||||
export { default as Toaster } from './Toaster.vue';
|
||||
export { default as ToastProvider } from './ToastProvider.vue';
|
||||
export { default as ToastTitle } from './ToastTitle.vue';
|
||||
export { default as ToastViewport } from './ToastViewport.vue';
|
||||
|
||||
export { toast, useToast } from './use-toast';
|
@@ -1,27 +0,0 @@
|
||||
import type { ToastRootProps } from 'radix-vue';
|
||||
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
|
||||
export const toastVariants = cva(
|
||||
'group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border border-border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
|
||||
{
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'border bg-background border-border text-foreground',
|
||||
destructive:
|
||||
'destructive group border-destructive bg-destructive text-destructive-foreground',
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
type ToastVariants = VariantProps<typeof toastVariants>;
|
||||
|
||||
export interface ToastProps extends ToastRootProps {
|
||||
class?: any;
|
||||
onOpenChange?: ((value: boolean) => void) | undefined;
|
||||
variant?: ToastVariants['variant'];
|
||||
}
|
@@ -1,168 +0,0 @@
|
||||
import type { ToastProps } from './toast';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
import type { Component, VNode } from 'vue';
|
||||
|
||||
const TOAST_LIMIT = 1;
|
||||
const TOAST_REMOVE_DELAY = 1_000_000;
|
||||
|
||||
export type StringOrVNode = (() => VNode) | string | VNode;
|
||||
|
||||
type ToasterToast = {
|
||||
action?: Component;
|
||||
description?: StringOrVNode;
|
||||
id: string;
|
||||
title?: string;
|
||||
} & ToastProps;
|
||||
|
||||
const actionTypes = {
|
||||
ADD_TOAST: 'ADD_TOAST',
|
||||
DISMISS_TOAST: 'DISMISS_TOAST',
|
||||
REMOVE_TOAST: 'REMOVE_TOAST',
|
||||
UPDATE_TOAST: 'UPDATE_TOAST',
|
||||
} as const;
|
||||
|
||||
let count = 0;
|
||||
|
||||
function genId() {
|
||||
count = (count + 1) % Number.MAX_VALUE;
|
||||
return count.toString();
|
||||
}
|
||||
|
||||
type ActionType = typeof actionTypes;
|
||||
|
||||
type Action =
|
||||
| {
|
||||
toast: Partial<ToasterToast>;
|
||||
type: ActionType['UPDATE_TOAST'];
|
||||
}
|
||||
| {
|
||||
toast: ToasterToast;
|
||||
type: ActionType['ADD_TOAST'];
|
||||
}
|
||||
| {
|
||||
toastId?: ToasterToast['id'];
|
||||
type: ActionType['DISMISS_TOAST'];
|
||||
}
|
||||
| {
|
||||
toastId?: ToasterToast['id'];
|
||||
type: ActionType['REMOVE_TOAST'];
|
||||
};
|
||||
|
||||
interface State {
|
||||
toasts: ToasterToast[];
|
||||
}
|
||||
|
||||
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
||||
|
||||
function addToRemoveQueue(toastId: string) {
|
||||
if (toastTimeouts.has(toastId)) return;
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
toastTimeouts.delete(toastId);
|
||||
dispatch({
|
||||
toastId,
|
||||
type: actionTypes.REMOVE_TOAST,
|
||||
});
|
||||
}, TOAST_REMOVE_DELAY);
|
||||
|
||||
toastTimeouts.set(toastId, timeout);
|
||||
}
|
||||
|
||||
const state = ref<State>({
|
||||
toasts: [],
|
||||
});
|
||||
|
||||
function dispatch(action: Action) {
|
||||
switch (action.type) {
|
||||
case actionTypes.ADD_TOAST: {
|
||||
state.value.toasts = [action.toast, ...state.value.toasts].slice(
|
||||
0,
|
||||
TOAST_LIMIT,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case actionTypes.UPDATE_TOAST: {
|
||||
state.value.toasts = state.value.toasts.map((t) =>
|
||||
t.id === action.toast.id ? { ...t, ...action.toast } : t,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case actionTypes.DISMISS_TOAST: {
|
||||
const { toastId } = action;
|
||||
|
||||
if (toastId) {
|
||||
addToRemoveQueue(toastId);
|
||||
} else {
|
||||
state.value.toasts.forEach((toast) => {
|
||||
addToRemoveQueue(toast.id);
|
||||
});
|
||||
}
|
||||
|
||||
state.value.toasts = state.value.toasts.map((t) =>
|
||||
t.id === toastId || toastId === undefined
|
||||
? {
|
||||
...t,
|
||||
open: false,
|
||||
}
|
||||
: t,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case actionTypes.REMOVE_TOAST: {
|
||||
state.value.toasts =
|
||||
action.toastId === undefined
|
||||
? []
|
||||
: state.value.toasts.filter((t) => t.id !== action.toastId);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function useToast() {
|
||||
return {
|
||||
dismiss: (toastId?: string) =>
|
||||
dispatch({ toastId, type: actionTypes.DISMISS_TOAST }),
|
||||
toast,
|
||||
toasts: computed(() => state.value.toasts),
|
||||
};
|
||||
}
|
||||
|
||||
type Toast = Omit<ToasterToast, 'id'>;
|
||||
|
||||
function toast(props: Toast) {
|
||||
const id = genId();
|
||||
|
||||
const update = (props: ToasterToast) =>
|
||||
dispatch({
|
||||
toast: { ...props, id },
|
||||
type: actionTypes.UPDATE_TOAST,
|
||||
});
|
||||
|
||||
const dismiss = () =>
|
||||
dispatch({ toastId: id, type: actionTypes.DISMISS_TOAST });
|
||||
|
||||
dispatch({
|
||||
toast: {
|
||||
...props,
|
||||
id,
|
||||
onOpenChange: (open: boolean) => {
|
||||
if (!open) dismiss();
|
||||
},
|
||||
open: true,
|
||||
},
|
||||
type: actionTypes.ADD_TOAST,
|
||||
});
|
||||
|
||||
return {
|
||||
dismiss,
|
||||
id,
|
||||
update,
|
||||
};
|
||||
}
|
||||
|
||||
export { toast, useToast };
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/tabs-ui",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.1",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -7,7 +7,6 @@ import { computed, ref } from 'vue';
|
||||
|
||||
import { Pin, X } from '@vben-core/icons';
|
||||
import { VbenContextMenu, VbenIcon } from '@vben-core/shadcn-ui';
|
||||
import { deepToRaw } from '@vben-core/shared/utils';
|
||||
|
||||
interface Props extends TabsProps {}
|
||||
|
||||
@@ -40,19 +39,21 @@ const style = computed(() => {
|
||||
};
|
||||
});
|
||||
|
||||
const tabsView = computed((): TabConfig[] => {
|
||||
return props.tabs.map((_tab) => {
|
||||
const tab = deepToRaw(_tab);
|
||||
const tabsView = computed(() => {
|
||||
return props.tabs.map((tab) => {
|
||||
const { fullPath, meta, name, path } = tab || {};
|
||||
const { affixTab, icon, newTabTitle, tabClosable, title } = meta || {};
|
||||
return {
|
||||
...tab,
|
||||
affixTab: !!tab.meta?.affixTab,
|
||||
closable: Reflect.has(tab.meta, 'tabClosable')
|
||||
? !!tab.meta.tabClosable
|
||||
: true,
|
||||
icon: tab.meta.icon as string,
|
||||
key: tab.fullPath || tab.path,
|
||||
title: (tab.meta?.newTabTitle || tab.meta?.title || tab.name) as string,
|
||||
};
|
||||
affixTab: !!affixTab,
|
||||
closable: Reflect.has(meta, 'tabClosable') ? !!tabClosable : true,
|
||||
fullPath,
|
||||
icon: icon as string,
|
||||
key: fullPath || path,
|
||||
meta,
|
||||
name,
|
||||
path,
|
||||
title: (newTabTitle || title || name) as string,
|
||||
} as TabConfig;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -69,7 +70,13 @@ const tabsView = computed((): TabConfig[] => {
|
||||
v-for="(tab, i) in tabsView"
|
||||
:key="tab.key"
|
||||
ref="tabRef"
|
||||
:class="[{ 'is-active': tab.key === active, draggable: !tab.affixTab }]"
|
||||
:class="[
|
||||
{
|
||||
'is-active': tab.key === active,
|
||||
draggable: !tab.affixTab,
|
||||
'affix-tab': tab.affixTab,
|
||||
},
|
||||
]"
|
||||
:data-active-tab="active"
|
||||
:data-index="i"
|
||||
class="tabs-chrome__item draggable translate-all group relative -mr-3 flex h-full select-none items-center"
|
||||
|
@@ -7,7 +7,6 @@ import { computed } from 'vue';
|
||||
|
||||
import { Pin, X } from '@vben-core/icons';
|
||||
import { VbenContextMenu, VbenIcon } from '@vben-core/shadcn-ui';
|
||||
import { deepToRaw } from '@vben-core/shared/utils';
|
||||
|
||||
interface Props extends TabsProps {}
|
||||
|
||||
@@ -46,19 +45,21 @@ const typeWithClass = computed(() => {
|
||||
return typeClasses[props.styleType || 'plain'] || { content: '' };
|
||||
});
|
||||
|
||||
const tabsView = computed((): TabConfig[] => {
|
||||
return props.tabs.map((_tab) => {
|
||||
const tab = deepToRaw(_tab);
|
||||
const tabsView = computed(() => {
|
||||
return props.tabs.map((tab) => {
|
||||
const { fullPath, meta, name, path } = tab || {};
|
||||
const { affixTab, icon, newTabTitle, tabClosable, title } = meta || {};
|
||||
return {
|
||||
...tab,
|
||||
affixTab: !!tab.meta?.affixTab,
|
||||
closable: Reflect.has(tab.meta, 'tabClosable')
|
||||
? !!tab.meta.tabClosable
|
||||
: true,
|
||||
icon: tab.meta.icon as string,
|
||||
key: tab.fullPath || tab.path,
|
||||
title: (tab.meta?.newTabTitle || tab.meta?.title || tab.name) as string,
|
||||
};
|
||||
affixTab: !!affixTab,
|
||||
closable: Reflect.has(meta, 'tabClosable') ? !!tabClosable : true,
|
||||
fullPath,
|
||||
icon: icon as string,
|
||||
key: fullPath || path,
|
||||
meta,
|
||||
name,
|
||||
path,
|
||||
title: (newTabTitle || title || name) as string,
|
||||
} as TabConfig;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -76,6 +77,7 @@ const tabsView = computed((): TabConfig[] => {
|
||||
{
|
||||
'is-active dark:bg-accent bg-primary/15': tab.key === active,
|
||||
draggable: !tab.affixTab,
|
||||
'affix-tab': tab.affixTab,
|
||||
},
|
||||
typeWithClass.content,
|
||||
]"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user