refactor(adapter): separate form and component adapters so that components adapt to components other than the form (#4628)
* refactor: global components can be customized * refactor: remove use Toast and reconstruct the form adapterpull/48/MERGE
parent
8b961a9d7f
commit
24d14c2841
|
@ -134,7 +134,7 @@ If you think this project is helpful to you, you can help the author buy a cup o
|
||||||
|
|
||||||
![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png)
|
![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png)
|
||||||
|
|
||||||
<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
|
## Contributor
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
import type {
|
||||||
BaseFormComponentType,
|
|
||||||
VbenFormSchema as FormSchema,
|
VbenFormSchema as FormSchema,
|
||||||
VbenFormProps,
|
VbenFormProps,
|
||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
import type { Component, SetupContext } from 'vue';
|
import type { ComponentType } from './component';
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
setupVbenForm<ComponentType>({
|
||||||
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,
|
|
||||||
},
|
|
||||||
config: {
|
config: {
|
||||||
// ant design vue组件库默认都是 v-model:value
|
// ant design vue组件库默认都是 v-model:value
|
||||||
baseModelPropName: 'value',
|
baseModelPropName: 'value',
|
||||||
|
@ -130,9 +39,9 @@ setupVbenForm<FormComponentType>({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const useVbenForm = useForm<FormComponentType>;
|
const useVbenForm = useForm<ComponentType>;
|
||||||
|
|
||||||
export { useVbenForm, z };
|
export { useVbenForm, z };
|
||||||
|
|
||||||
export type VbenFormSchema = FormSchema<FormComponentType>;
|
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||||
export type { VbenFormProps };
|
export type { VbenFormProps };
|
||||||
|
|
|
@ -7,10 +7,14 @@ import '@vben/styles/antd';
|
||||||
|
|
||||||
import { setupI18n } from '#/locales';
|
import { setupI18n } from '#/locales';
|
||||||
|
|
||||||
|
import { initComponentAdapter } from './adapter/component';
|
||||||
import App from './app.vue';
|
import App from './app.vue';
|
||||||
import { router } from './router';
|
import { router } from './router';
|
||||||
|
|
||||||
async function bootstrap(namespace: string) {
|
async function bootstrap(namespace: string) {
|
||||||
|
// 初始化组件适配器
|
||||||
|
await initComponentAdapter();
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
// 国际化 i18n 配置
|
// 国际化 i18n 配置
|
||||||
|
|
|
@ -32,6 +32,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
component: AuthPageLayout,
|
component: AuthPageLayout,
|
||||||
meta: {
|
meta: {
|
||||||
|
hideInTab: true,
|
||||||
title: 'Authentication',
|
title: 'Authentication',
|
||||||
},
|
},
|
||||||
name: 'Authentication',
|
name: 'Authentication',
|
||||||
|
|
|
@ -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 {
|
import type {
|
||||||
BaseFormComponentType,
|
|
||||||
VbenFormSchema as FormSchema,
|
VbenFormSchema as FormSchema,
|
||||||
VbenFormProps,
|
VbenFormProps,
|
||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
import type { Component, SetupContext } from 'vue';
|
import type { ComponentType } from './component';
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
setupVbenForm<ComponentType>({
|
||||||
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,
|
|
||||||
},
|
|
||||||
config: {
|
config: {
|
||||||
modelPropNameMap: {
|
modelPropNameMap: {
|
||||||
Upload: 'fileList',
|
Upload: 'fileList',
|
||||||
|
@ -98,9 +30,9 @@ setupVbenForm<FormComponentType>({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const useVbenForm = useForm<FormComponentType>;
|
const useVbenForm = useForm<ComponentType>;
|
||||||
|
|
||||||
export { useVbenForm, z };
|
export { useVbenForm, z };
|
||||||
|
|
||||||
export type VbenFormSchema = FormSchema<FormComponentType>;
|
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||||
export type { VbenFormProps };
|
export type { VbenFormProps };
|
||||||
|
|
|
@ -7,10 +7,13 @@ import '@vben/styles/ele';
|
||||||
|
|
||||||
import { setupI18n } from '#/locales';
|
import { setupI18n } from '#/locales';
|
||||||
|
|
||||||
|
import { initComponentAdapter } from './adapter/component';
|
||||||
import App from './app.vue';
|
import App from './app.vue';
|
||||||
import { router } from './router';
|
import { router } from './router';
|
||||||
|
|
||||||
async function bootstrap(namespace: string) {
|
async function bootstrap(namespace: string) {
|
||||||
|
// 初始化组件适配器
|
||||||
|
await initComponentAdapter();
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
// 国际化 i18n 配置
|
// 国际化 i18n 配置
|
||||||
|
|
|
@ -32,6 +32,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
component: AuthPageLayout,
|
component: AuthPageLayout,
|
||||||
meta: {
|
meta: {
|
||||||
|
hideInTab: true,
|
||||||
title: 'Authentication',
|
title: 'Authentication',
|
||||||
},
|
},
|
||||||
name: 'Authentication',
|
name: 'Authentication',
|
||||||
|
|
|
@ -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 {
|
import type {
|
||||||
BaseFormComponentType,
|
|
||||||
VbenFormSchema as FormSchema,
|
VbenFormSchema as FormSchema,
|
||||||
VbenFormProps,
|
VbenFormProps,
|
||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
import type { Component, SetupContext } from 'vue';
|
import type { ComponentType } from './component';
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
setupVbenForm<ComponentType>({
|
||||||
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,
|
|
||||||
},
|
|
||||||
config: {
|
config: {
|
||||||
// naive-ui组件不接受onChang事件,所以需要禁用
|
// naive-ui组件不接受onChang事件,所以需要禁用
|
||||||
disabledOnChangeListener: true,
|
disabledOnChangeListener: true,
|
||||||
|
@ -107,9 +37,9 @@ setupVbenForm<FormComponentType>({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const useVbenForm = useForm<FormComponentType>;
|
const useVbenForm = useForm<ComponentType>;
|
||||||
|
|
||||||
export { useVbenForm, z };
|
export { useVbenForm, z };
|
||||||
|
|
||||||
export type VbenFormSchema = FormSchema<FormComponentType>;
|
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||||
export type { VbenFormProps };
|
export type { VbenFormProps };
|
||||||
|
|
|
@ -6,10 +6,13 @@ import '@vben/styles';
|
||||||
|
|
||||||
import { setupI18n } from '#/locales';
|
import { setupI18n } from '#/locales';
|
||||||
|
|
||||||
|
import { initComponentAdapter } from './adapter/component';
|
||||||
import App from './app.vue';
|
import App from './app.vue';
|
||||||
import { router } from './router';
|
import { router } from './router';
|
||||||
|
|
||||||
async function bootstrap(namespace: string) {
|
async function bootstrap(namespace: string) {
|
||||||
|
// 初始化组件适配器
|
||||||
|
initComponentAdapter();
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
// 国际化 i18n 配置
|
// 国际化 i18n 配置
|
||||||
|
|
|
@ -32,6 +32,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
component: AuthPageLayout,
|
component: AuthPageLayout,
|
||||||
meta: {
|
meta: {
|
||||||
|
hideInTab: true,
|
||||||
title: 'Authentication',
|
title: 'Authentication',
|
||||||
},
|
},
|
||||||
name: 'Authentication',
|
name: 'Authentication',
|
||||||
|
|
|
@ -164,6 +164,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
|
||||||
link: 'common-ui/vben-form',
|
link: 'common-ui/vben-form',
|
||||||
text: 'Form 表单',
|
text: 'Form 表单',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
link: 'common-ui/vben-vxe-table',
|
||||||
|
text: 'Vxe Table 表格',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
link: 'common-ui/vben-count-to-animator',
|
link: 'common-ui/vben-count-to-animator',
|
||||||
text: 'CountToAnimator 数字动画',
|
text: 'CountToAnimator 数字动画',
|
||||||
|
|
|
@ -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 {
|
import type {
|
||||||
BaseFormComponentType,
|
|
||||||
VbenFormSchema as FormSchema,
|
VbenFormSchema as FormSchema,
|
||||||
VbenFormProps,
|
VbenFormProps,
|
||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
import { h } from 'vue';
|
import type { ComponentType } from './component';
|
||||||
|
|
||||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
import { initComponentAdapter } from './component';
|
||||||
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';
|
|
||||||
|
|
||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
initComponentAdapter();
|
||||||
export type FormComponentType =
|
setupVbenForm<ComponentType>({
|
||||||
| '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,
|
|
||||||
},
|
|
||||||
config: {
|
config: {
|
||||||
// ant design vue组件库默认都是 v-model:value
|
|
||||||
baseModelPropName: 'value',
|
baseModelPropName: 'value',
|
||||||
|
// naive-ui组件不接受onChang事件,所以需要禁用
|
||||||
// 一些组件是 v-model:checked 或者 v-model:fileList
|
disabledOnChangeListener: true,
|
||||||
|
// naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效
|
||||||
|
emptyStateValue: null,
|
||||||
modelPropNameMap: {
|
modelPropNameMap: {
|
||||||
Checkbox: 'checked',
|
Checkbox: 'checked',
|
||||||
Radio: 'checked',
|
Radio: 'checked',
|
||||||
|
@ -102,14 +26,12 @@ setupVbenForm<FormComponentType>({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defineRules: {
|
defineRules: {
|
||||||
// 输入项目必填国际化适配
|
|
||||||
required: (value, _params, ctx) => {
|
required: (value, _params, ctx) => {
|
||||||
if (value === undefined || value === null || value.length === 0) {
|
if (value === undefined || value === null || value.length === 0) {
|
||||||
return $t('formRules.required', [ctx.label]);
|
return $t('formRules.required', [ctx.label]);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
// 选择项目必填国际化适配
|
|
||||||
selectRequired: (value, _params, ctx) => {
|
selectRequired: (value, _params, ctx) => {
|
||||||
if (value === undefined || value === null) {
|
if (value === undefined || value === null) {
|
||||||
return $t('formRules.selectRequired', [ctx.label]);
|
return $t('formRules.selectRequired', [ctx.label]);
|
||||||
|
@ -119,9 +41,9 @@ setupVbenForm<FormComponentType>({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const useVbenForm = useForm<FormComponentType>;
|
const useVbenForm = useForm<ComponentType>;
|
||||||
|
|
||||||
export { useVbenForm, z };
|
export { useVbenForm, z };
|
||||||
|
|
||||||
export type VbenFormSchema = FormSchema<FormComponentType>;
|
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||||
export type { VbenFormProps };
|
export type { VbenFormProps };
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
社区交流群主要是为了方便大家交流,提问,解答问题,分享经验等。偏自助方式,如果你有问题,可以通过以下方式加入社区交流群:
|
社区交流群主要是为了方便大家交流,提问,解答问题,分享经验等。偏自助方式,如果你有问题,可以通过以下方式加入社区交流群:
|
||||||
|
|
||||||
- [QQ频道](https://pd.qq.com/s/16p8lvvob):推荐!!!主要提供问题解答,分享经验等。
|
- [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): 主要提供问题解答,分享经验等。
|
- [Discord](https://discord.com/invite/VU62jTecad): 主要提供问题解答,分享经验等。
|
||||||
|
|
||||||
::: tip
|
::: tip
|
||||||
|
|
|
@ -14,6 +14,12 @@ outline: deep
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
::: tip README
|
||||||
|
|
||||||
|
下方示例代码中的,存在一些国际化、主题色未适配问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## 基础用法
|
## 基础用法
|
||||||
|
|
||||||
使用 `useVbenDrawer` 创建最基础的模态框。
|
使用 `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
|
```ts
|
||||||
import type {
|
import type {
|
||||||
BaseFormComponentType,
|
|
||||||
VbenFormSchema as FormSchema,
|
VbenFormSchema as FormSchema,
|
||||||
VbenFormProps,
|
VbenFormProps,
|
||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
import type { Component, SetupContext } from 'vue';
|
import type { ComponentType } from './component';
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
setupVbenForm<ComponentType>({
|
||||||
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,
|
|
||||||
},
|
|
||||||
config: {
|
config: {
|
||||||
// 是否禁用onChange事件监听,naive ui组件库默认不需要监听onChange事件,否则会在控制台报错
|
|
||||||
disabledOnChangeListener: true,
|
|
||||||
// ant design vue组件库默认都是 v-model:value
|
// ant design vue组件库默认都是 v-model:value
|
||||||
baseModelPropName: 'value',
|
baseModelPropName: 'value',
|
||||||
// 一些组件是 v-model:checked 或者 v-model:fileList
|
// 一些组件是 v-model:checked 或者 v-model:fileList
|
||||||
|
@ -158,16 +65,149 @@ setupVbenForm<FormComponentType>({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const useVbenForm = useForm<FormComponentType>;
|
const useVbenForm = useForm<ComponentType>;
|
||||||
|
|
||||||
export { useVbenForm, z };
|
export { useVbenForm, z };
|
||||||
|
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||||
export type VbenFormSchema = FormSchema<FormComponentType>;
|
|
||||||
export type { VbenFormProps };
|
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
|
::: tip README
|
||||||
|
|
|
@ -14,6 +14,12 @@ outline: deep
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
::: tip README
|
||||||
|
|
||||||
|
下方示例代码中的,存在一些国际化、主题色未适配问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## 基础用法
|
## 基础用法
|
||||||
|
|
||||||
使用 `useVbenModal` 创建最基础的模态框。
|
使用 `useVbenModal` 创建最基础的模态框。
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vben Vxe Table 表格
|
||||||
|
|
||||||
|
TODO
|
|
@ -1,6 +1,6 @@
|
||||||
# 友情链接
|
# 友情链接
|
||||||
|
|
||||||
如果您的网站是与 Vben Admin 相关的,或者也属于开源项目,欢迎联系我们,我们会将与您的网站加入交换友情链接。
|
如果您的网站是与 Vben Admin 相关的,也属于开源项目,欢迎联系我们,我们会将与您的网站加入交换友情链接。
|
||||||
|
|
||||||
## 交换方式
|
## 交换方式
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 只有非mac下才进行调整,mac下使用默认滚动条 */
|
/* 只有非mac下才进行调整,mac下使用默认滚动条 */
|
||||||
|
|
||||||
html:not([data-platform='macOs']) {
|
html:not([data-platform='macOs']) {
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
@apply h-[10px] w-[10px];
|
@apply h-[10px] w-[10px];
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import './default/index.css';
|
import './default.css';
|
||||||
import './dark/index.css';
|
import './dark.css';
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|
|
@ -9,5 +9,6 @@ export default defineBuildConfig({
|
||||||
'src/utils/index',
|
'src/utils/index',
|
||||||
'src/color/index',
|
'src/color/index',
|
||||||
'src/cache/index',
|
'src/cache/index',
|
||||||
|
'src/global-state',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm unbuild"
|
"build": "pnpm unbuild",
|
||||||
|
"stub": "pnpm unbuild --stub"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
@ -42,6 +43,11 @@
|
||||||
"types": "./src/store.ts",
|
"types": "./src/store.ts",
|
||||||
"development": "./src/store.ts",
|
"development": "./src/store.ts",
|
||||||
"default": "./dist/store.mjs"
|
"default": "./dist/store.mjs"
|
||||||
|
},
|
||||||
|
"./global-state": {
|
||||||
|
"types": "./dist/global-state.d.ts",
|
||||||
|
"development": "./src/global-state.ts",
|
||||||
|
"default": "./dist/global-state.mjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
@ -63,8 +69,12 @@
|
||||||
"default": "./dist/cache/index.mjs"
|
"default": "./dist/cache/index.mjs"
|
||||||
},
|
},
|
||||||
"./store": {
|
"./store": {
|
||||||
"types": "./dist/store/index.d.ts",
|
"types": "./dist/store.d.ts",
|
||||||
"default": "./dist/store.mjs"
|
"default": "./dist/store.mjs"
|
||||||
|
},
|
||||||
|
"./global-state": {
|
||||||
|
"types": "./dist/global-state.d.ts",
|
||||||
|
"default": "./dist/global-state.mjs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -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();
|
|
@ -10,13 +10,6 @@ function useSortable<T extends HTMLElement>(
|
||||||
// @ts-expect-error - This is a dynamic import
|
// @ts-expect-error - This is a dynamic import
|
||||||
'sortablejs/modular/sortable.complete.esm.js'
|
'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, {
|
const sortable = Sortable?.default?.create?.(sortableContainer, {
|
||||||
animation: 300,
|
animation: 300,
|
||||||
delay: 400,
|
delay: 400,
|
||||||
|
|
|
@ -84,7 +84,7 @@ watch(
|
||||||
:style="queryFormStyle"
|
:style="queryFormStyle"
|
||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
:is="COMPONENT_MAP.DefaultResetActionButton"
|
:is="COMPONENT_MAP.DefaultButton"
|
||||||
v-if="resetButtonOptions.show"
|
v-if="resetButtonOptions.show"
|
||||||
class="mr-3"
|
class="mr-3"
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -95,7 +95,7 @@ watch(
|
||||||
</component>
|
</component>
|
||||||
|
|
||||||
<component
|
<component
|
||||||
:is="COMPONENT_MAP.DefaultSubmitActionButton"
|
:is="COMPONENT_MAP.PrimaryButton"
|
||||||
v-if="submitButtonOptions.show"
|
v-if="submitButtonOptions.show"
|
||||||
type="button"
|
type="button"
|
||||||
@click="handleSubmit"
|
@click="handleSubmit"
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
VbenPinInput,
|
VbenPinInput,
|
||||||
VbenSelect,
|
VbenSelect,
|
||||||
} from '@vben-core/shadcn-ui';
|
} from '@vben-core/shadcn-ui';
|
||||||
|
import { globalShareState } from '@vben-core/shared/global-state';
|
||||||
|
|
||||||
import { defineRule } from 'vee-validate';
|
import { defineRule } from 'vee-validate';
|
||||||
|
|
||||||
|
@ -23,8 +24,8 @@ const DEFAULT_MODEL_PROP_NAME = 'modelValue';
|
||||||
export const DEFAULT_FORM_COMMON_CONFIG: FormCommonConfig = {};
|
export const DEFAULT_FORM_COMMON_CONFIG: FormCommonConfig = {};
|
||||||
|
|
||||||
export const COMPONENT_MAP: Record<BaseFormComponentType, Component> = {
|
export const COMPONENT_MAP: Record<BaseFormComponentType, Component> = {
|
||||||
DefaultResetActionButton: h(VbenButton, { size: 'sm', variant: 'outline' }),
|
DefaultButton: h(VbenButton, { size: 'sm', variant: 'outline' }),
|
||||||
DefaultSubmitActionButton: h(VbenButton, { size: 'sm', variant: 'default' }),
|
PrimaryButton: h(VbenButton, { size: 'sm', variant: 'default' }),
|
||||||
VbenCheckbox,
|
VbenCheckbox,
|
||||||
VbenInput,
|
VbenInput,
|
||||||
VbenInputPassword,
|
VbenInputPassword,
|
||||||
|
@ -41,7 +42,7 @@ export const COMPONENT_BIND_EVENT_MAP: Partial<
|
||||||
export function setupVbenForm<
|
export function setupVbenForm<
|
||||||
T extends BaseFormComponentType = BaseFormComponentType,
|
T extends BaseFormComponentType = BaseFormComponentType,
|
||||||
>(options: VbenFormAdapterOptions<T>) {
|
>(options: VbenFormAdapterOptions<T>) {
|
||||||
const { components, config, defineRules } = options;
|
const { config, defineRules } = options;
|
||||||
|
|
||||||
const { disabledOnChangeListener = false, emptyStateValue = undefined } =
|
const { disabledOnChangeListener = false, emptyStateValue = undefined } =
|
||||||
(config || {}) as FormCommonConfig;
|
(config || {}) as FormCommonConfig;
|
||||||
|
@ -63,6 +64,8 @@ export function setupVbenForm<
|
||||||
| Record<BaseFormComponentType, string>
|
| Record<BaseFormComponentType, string>
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
|
const components = globalShareState.getComponents();
|
||||||
|
|
||||||
for (const component of Object.keys(components)) {
|
for (const component of Object.keys(components)) {
|
||||||
const key = component as BaseFormComponentType;
|
const key = component as BaseFormComponentType;
|
||||||
COMPONENT_MAP[key] = components[component as never];
|
COMPONENT_MAP[key] = components[component as never];
|
||||||
|
|
|
@ -9,8 +9,8 @@ import type { Component, HtmlHTMLAttributes, Ref } from 'vue';
|
||||||
export type FormLayout = 'horizontal' | 'vertical';
|
export type FormLayout = 'horizontal' | 'vertical';
|
||||||
|
|
||||||
export type BaseFormComponentType =
|
export type BaseFormComponentType =
|
||||||
| 'DefaultResetActionButton'
|
| 'DefaultButton'
|
||||||
| 'DefaultSubmitActionButton'
|
| 'PrimaryButton'
|
||||||
| 'VbenCheckbox'
|
| 'VbenCheckbox'
|
||||||
| 'VbenInput'
|
| 'VbenInput'
|
||||||
| 'VbenInputPassword'
|
| 'VbenInputPassword'
|
||||||
|
@ -341,7 +341,6 @@ export type ExtendedFormApi = {
|
||||||
export interface VbenFormAdapterOptions<
|
export interface VbenFormAdapterOptions<
|
||||||
T extends BaseFormComponentType = BaseFormComponentType,
|
T extends BaseFormComponentType = BaseFormComponentType,
|
||||||
> {
|
> {
|
||||||
components: Partial<Record<T, Component>>;
|
|
||||||
config?: {
|
config?: {
|
||||||
baseModelPropName?: string;
|
baseModelPropName?: string;
|
||||||
disabledOnChangeListener?: boolean;
|
disabledOnChangeListener?: boolean;
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {
|
||||||
VbenLoading,
|
VbenLoading,
|
||||||
VisuallyHidden,
|
VisuallyHidden,
|
||||||
} from '@vben-core/shadcn-ui';
|
} from '@vben-core/shadcn-ui';
|
||||||
|
import { globalShareState } from '@vben-core/shared/global-state';
|
||||||
import { cn } from '@vben-core/shared/utils';
|
import { cn } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
interface Props extends DrawerProps {
|
interface Props extends DrawerProps {
|
||||||
|
@ -33,6 +34,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
drawerApi: undefined,
|
drawerApi: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const components = globalShareState.getComponents();
|
||||||
|
|
||||||
const id = useId();
|
const id = useId();
|
||||||
provide('DISMISSABLE_DRAWER_ID', id);
|
provide('DISMISSABLE_DRAWER_ID', id);
|
||||||
|
|
||||||
|
@ -187,7 +190,8 @@ function handleFocusOutside(e: Event) {
|
||||||
>
|
>
|
||||||
<slot name="prepend-footer"></slot>
|
<slot name="prepend-footer"></slot>
|
||||||
<slot name="footer">
|
<slot name="footer">
|
||||||
<VbenButton
|
<component
|
||||||
|
:is="components.DefaultButton || VbenButton"
|
||||||
v-if="showCancelButton"
|
v-if="showCancelButton"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@click="() => drawerApi?.onCancel()"
|
@click="() => drawerApi?.onCancel()"
|
||||||
|
@ -195,8 +199,10 @@ function handleFocusOutside(e: Event) {
|
||||||
<slot name="cancelText">
|
<slot name="cancelText">
|
||||||
{{ cancelText || $t('cancel') }}
|
{{ cancelText || $t('cancel') }}
|
||||||
</slot>
|
</slot>
|
||||||
</VbenButton>
|
</component>
|
||||||
<VbenButton
|
|
||||||
|
<component
|
||||||
|
:is="components.PrimaryButton || VbenButton"
|
||||||
v-if="showConfirmButton"
|
v-if="showConfirmButton"
|
||||||
:loading="confirmLoading"
|
:loading="confirmLoading"
|
||||||
@click="() => drawerApi?.onConfirm()"
|
@click="() => drawerApi?.onConfirm()"
|
||||||
|
@ -204,7 +210,7 @@ function handleFocusOutside(e: Event) {
|
||||||
<slot name="confirmText">
|
<slot name="confirmText">
|
||||||
{{ confirmText || $t('confirm') }}
|
{{ confirmText || $t('confirm') }}
|
||||||
</slot>
|
</slot>
|
||||||
</VbenButton>
|
</component>
|
||||||
</slot>
|
</slot>
|
||||||
<slot name="append-footer"></slot>
|
<slot name="append-footer"></slot>
|
||||||
</SheetFooter>
|
</SheetFooter>
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
VbenLoading,
|
VbenLoading,
|
||||||
VisuallyHidden,
|
VisuallyHidden,
|
||||||
} from '@vben-core/shadcn-ui';
|
} from '@vben-core/shadcn-ui';
|
||||||
|
import { globalShareState } from '@vben-core/shared/global-state';
|
||||||
import { cn } from '@vben-core/shared/utils';
|
import { cn } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
import { useModalDraggable } from './use-modal-draggable';
|
import { useModalDraggable } from './use-modal-draggable';
|
||||||
|
@ -34,6 +35,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
modalApi: undefined,
|
modalApi: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const components = globalShareState.getComponents();
|
||||||
|
|
||||||
const contentRef = ref();
|
const contentRef = ref();
|
||||||
const wrapperRef = ref<HTMLElement>();
|
const wrapperRef = ref<HTMLElement>();
|
||||||
const dialogRef = ref();
|
const dialogRef = ref();
|
||||||
|
@ -256,7 +259,8 @@ function handleFocusOutside(e: Event) {
|
||||||
>
|
>
|
||||||
<slot name="prepend-footer"></slot>
|
<slot name="prepend-footer"></slot>
|
||||||
<slot name="footer">
|
<slot name="footer">
|
||||||
<VbenButton
|
<component
|
||||||
|
:is="components.DefaultButton || VbenButton"
|
||||||
v-if="showCancelButton"
|
v-if="showCancelButton"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@click="() => modalApi?.onCancel()"
|
@click="() => modalApi?.onCancel()"
|
||||||
|
@ -264,8 +268,10 @@ function handleFocusOutside(e: Event) {
|
||||||
<slot name="cancelText">
|
<slot name="cancelText">
|
||||||
{{ cancelText || $t('cancel') }}
|
{{ cancelText || $t('cancel') }}
|
||||||
</slot>
|
</slot>
|
||||||
</VbenButton>
|
</component>
|
||||||
<VbenButton
|
|
||||||
|
<component
|
||||||
|
:is="components.PrimaryButton || VbenButton"
|
||||||
v-if="showConfirmButton"
|
v-if="showConfirmButton"
|
||||||
:loading="confirmLoading"
|
:loading="confirmLoading"
|
||||||
@click="() => modalApi?.onConfirm()"
|
@click="() => modalApi?.onConfirm()"
|
||||||
|
@ -273,7 +279,7 @@ function handleFocusOutside(e: Event) {
|
||||||
<slot name="confirmText">
|
<slot name="confirmText">
|
||||||
{{ confirmText || $t('confirm') }}
|
{{ confirmText || $t('confirm') }}
|
||||||
</slot>
|
</slot>
|
||||||
</VbenButton>
|
</component>
|
||||||
</slot>
|
</slot>
|
||||||
<slot name="append-footer"></slot>
|
<slot name="append-footer"></slot>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
|
|
|
@ -23,7 +23,6 @@ export * from './sheet';
|
||||||
export * from './switch';
|
export * from './switch';
|
||||||
export * from './tabs';
|
export * from './tabs';
|
||||||
export * from './textarea';
|
export * from './textarea';
|
||||||
export * from './toast';
|
|
||||||
export * from './toggle';
|
export * from './toggle';
|
||||||
export * from './toggle-group';
|
export * from './toggle-group';
|
||||||
export * from './tooltip';
|
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 };
|
|
|
@ -13,3 +13,5 @@ export {
|
||||||
VbenPinInput,
|
VbenPinInput,
|
||||||
VbenSpinner,
|
VbenSpinner,
|
||||||
} from '@vben-core/shadcn-ui';
|
} from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
|
export { globalShareState } from '@vben-core/shared/global-state';
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
export * from './components';
|
export * from './components';
|
||||||
export * from './ui';
|
export * from './ui';
|
||||||
export { useToast } from '@vben-core/shadcn-ui';
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
import { useLockStore } from '@vben/stores';
|
import { useLockStore } from '@vben/stores';
|
||||||
import { deepToRaw, mapTree } from '@vben/utils';
|
import { deepToRaw, mapTree } from '@vben/utils';
|
||||||
import { VbenAdminLayout } from '@vben-core/layout-ui';
|
import { VbenAdminLayout } from '@vben-core/layout-ui';
|
||||||
import { Toaster, VbenBackTop, VbenLogo } from '@vben-core/shadcn-ui';
|
import { VbenBackTop, VbenLogo } from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
import { Breadcrumb, CheckUpdates, Preferences } from '../widgets';
|
import { Breadcrumb, CheckUpdates, Preferences } from '../widgets';
|
||||||
import { LayoutContent, LayoutContentSpinner } from './content';
|
import { LayoutContent, LayoutContentSpinner } from './content';
|
||||||
|
@ -312,7 +312,6 @@ const headerSlots = computed(() => {
|
||||||
|
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<slot name="extra"></slot>
|
<slot name="extra"></slot>
|
||||||
<Toaster />
|
|
||||||
<CheckUpdates
|
<CheckUpdates
|
||||||
v-if="preferences.app.enableCheckUpdates"
|
v-if="preferences.app.enableCheckUpdates"
|
||||||
:check-updates-interval="preferences.app.checkUpdatesInterval"
|
:check-updates-interval="preferences.app.checkUpdatesInterval"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h, onMounted, onUnmounted, ref } from 'vue';
|
import { onMounted, onUnmounted, ref } from 'vue';
|
||||||
|
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
import { ToastAction, useToast } from '@vben-core/shadcn-ui';
|
import { useVbenModal } from '@vben-core/popup-ui';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
// 轮训时间,分钟
|
// 轮训时间,分钟
|
||||||
|
@ -18,10 +18,21 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
checkUpdateUrl: import.meta.env.BASE_URL || '/',
|
checkUpdateUrl: import.meta.env.BASE_URL || '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
const lastVersionTag = ref('');
|
|
||||||
let isCheckingUpdates = false;
|
let isCheckingUpdates = false;
|
||||||
|
const currentVersionTag = ref('');
|
||||||
|
const lastVersionTag = ref('');
|
||||||
const timer = ref<ReturnType<typeof setInterval>>();
|
const timer = ref<ReturnType<typeof setInterval>>();
|
||||||
const { toast } = useToast();
|
|
||||||
|
const [UpdateNoticeModal, modalApi] = useVbenModal({
|
||||||
|
closable: false,
|
||||||
|
closeOnPressEscape: false,
|
||||||
|
closeOnClickModal: false,
|
||||||
|
onConfirm() {
|
||||||
|
lastVersionTag.value = currentVersionTag.value;
|
||||||
|
window.location.reload();
|
||||||
|
// handleSubmitLogout();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
async function getVersionTag() {
|
async function getVersionTag() {
|
||||||
try {
|
try {
|
||||||
|
@ -63,38 +74,8 @@ async function checkForUpdates() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function handleNotice(versionTag: string) {
|
function handleNotice(versionTag: string) {
|
||||||
const { dismiss } = toast({
|
currentVersionTag.value = versionTag;
|
||||||
action: h('div', { class: 'inline-flex items-center' }, [
|
modalApi.open();
|
||||||
h(
|
|
||||||
ToastAction,
|
|
||||||
{
|
|
||||||
altText: $t('common.cancel'),
|
|
||||||
onClick: () => dismiss(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
default: () => $t('common.cancel'),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
ToastAction,
|
|
||||||
{
|
|
||||||
altText: $t('common.refresh'),
|
|
||||||
class:
|
|
||||||
'bg-primary text-primary-foreground hover:bg-primary-hover mx-1',
|
|
||||||
onClick: () => {
|
|
||||||
lastVersionTag.value = versionTag;
|
|
||||||
window.location.reload();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
default: () => $t('common.refresh'),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
description: $t('widgets.checkUpdatesDescription'),
|
|
||||||
duration: 0,
|
|
||||||
title: $t('widgets.checkUpdatesTitle'),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
|
@ -138,5 +119,16 @@ onUnmounted(() => {
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<slot></slot>
|
<UpdateNoticeModal
|
||||||
|
:cancel-text="$t('common.cancel')"
|
||||||
|
:confirm-text="$t('common.refresh')"
|
||||||
|
:fullscreen-button="false"
|
||||||
|
:title="$t('widgets.checkUpdatesTitle')"
|
||||||
|
centered
|
||||||
|
content-class="px-8 min-h-10"
|
||||||
|
footer-class="border-none mb-3 mr-3"
|
||||||
|
header-class="border-none"
|
||||||
|
>
|
||||||
|
{{ $t('widgets.checkUpdatesDescription') }}
|
||||||
|
</UpdateNoticeModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -24,11 +24,11 @@ import {
|
||||||
} from '@vben/preferences';
|
} from '@vben/preferences';
|
||||||
import { useVbenDrawer } from '@vben-core/popup-ui';
|
import { useVbenDrawer } from '@vben-core/popup-ui';
|
||||||
import {
|
import {
|
||||||
useToast,
|
|
||||||
VbenButton,
|
VbenButton,
|
||||||
VbenIconButton,
|
VbenIconButton,
|
||||||
VbenSegmented,
|
VbenSegmented,
|
||||||
} from '@vben-core/shadcn-ui';
|
} from '@vben-core/shadcn-ui';
|
||||||
|
import { globalShareState } from '@vben-core/shared/global-state';
|
||||||
|
|
||||||
import { useClipboard } from '@vueuse/core';
|
import { useClipboard } from '@vueuse/core';
|
||||||
|
|
||||||
|
@ -54,7 +54,9 @@ import {
|
||||||
} from './blocks';
|
} from './blocks';
|
||||||
|
|
||||||
const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
|
const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
|
||||||
const { toast } = useToast();
|
|
||||||
|
const message = globalShareState.getMessage();
|
||||||
|
|
||||||
const appLocale = defineModel<SupportedLanguagesType>('appLocale');
|
const appLocale = defineModel<SupportedLanguagesType>('appLocale');
|
||||||
const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
|
const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
|
||||||
const appLayout = defineModel<LayoutType>('appLayout');
|
const appLayout = defineModel<LayoutType>('appLayout');
|
||||||
|
@ -196,10 +198,10 @@ const showBreadcrumbConfig = computed(() => {
|
||||||
async function handleCopy() {
|
async function handleCopy() {
|
||||||
await copy(JSON.stringify(diffPreference.value, null, 2));
|
await copy(JSON.stringify(diffPreference.value, null, 2));
|
||||||
|
|
||||||
toast({
|
message.copyPreferencesSuccess?.(
|
||||||
description: $t('preferences.copyPreferences'),
|
$t('preferences.copyPreferencesSuccessTitle'),
|
||||||
title: $t('preferences.copyPreferencesSuccess'),
|
$t('preferences.copyPreferencesSuccess'),
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleClearCache() {
|
async function handleClearCache() {
|
||||||
|
@ -214,10 +216,6 @@ async function handleReset() {
|
||||||
}
|
}
|
||||||
resetPreferences();
|
resetPreferences();
|
||||||
await loadLocaleMessages(preferences.app.locale);
|
await loadLocaleMessages(preferences.app.locale);
|
||||||
toast({
|
|
||||||
description: $t('preferences.resetTitle'),
|
|
||||||
title: $t('preferences.resetSuccess'),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -149,7 +149,7 @@ if (enableShortcutKey.value) {
|
||||||
:title="$t('common.prompt')"
|
:title="$t('common.prompt')"
|
||||||
centered
|
centered
|
||||||
content-class="px-8 min-h-10"
|
content-class="px-8 min-h-10"
|
||||||
footer-class="border-none mb-4 mr-4"
|
footer-class="border-none mb-3 mr-3"
|
||||||
header-class="border-none"
|
header-class="border-none"
|
||||||
>
|
>
|
||||||
{{ $t('widgets.logoutTip') }}
|
{{ $t('widgets.logoutTip') }}
|
||||||
|
|
|
@ -35,7 +35,7 @@ function loadLocalesMap(modules: Record<string, () => Promise<unknown>>) {
|
||||||
const localesMap: Record<Locale, ImportLocaleFn> = {};
|
const localesMap: Record<Locale, ImportLocaleFn> = {};
|
||||||
|
|
||||||
for (const [path, loadLocale] of Object.entries(modules)) {
|
for (const [path, loadLocale] of Object.entries(modules)) {
|
||||||
const key = path.match(/([\w-]*)\.(yaml|yml|json)/)?.[1];
|
const key = path.match(/([\w-]*)\.(json)/)?.[1];
|
||||||
if (key) {
|
if (key) {
|
||||||
localesMap[key] = loadLocale as ImportLocaleFn;
|
localesMap[key] = loadLocale as ImportLocaleFn;
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,6 +178,7 @@
|
||||||
"plain": "Plain",
|
"plain": "Plain",
|
||||||
"rounded": "Rounded",
|
"rounded": "Rounded",
|
||||||
"copyPreferences": "Copy Preferences",
|
"copyPreferences": "Copy Preferences",
|
||||||
|
"copyPreferencesSuccessTitle": "Copy successful",
|
||||||
"copyPreferencesSuccess": "Copy successful, please override in `src/preferences.ts` under app",
|
"copyPreferencesSuccess": "Copy successful, please override in `src/preferences.ts` under app",
|
||||||
"clearAndLogout": "Clear Cache & Logout",
|
"clearAndLogout": "Clear Cache & Logout",
|
||||||
"mode": "Mode",
|
"mode": "Mode",
|
||||||
|
|
|
@ -178,6 +178,7 @@
|
||||||
"plain": "朴素",
|
"plain": "朴素",
|
||||||
"rounded": "圆润",
|
"rounded": "圆润",
|
||||||
"copyPreferences": "复制偏好设置",
|
"copyPreferences": "复制偏好设置",
|
||||||
|
"copyPreferencesSuccessTitle": "复制成功",
|
||||||
"copyPreferencesSuccess": "复制成功,请在 app 下的 `src/preferences.ts`内进行覆盖",
|
"copyPreferencesSuccess": "复制成功,请在 app 下的 `src/preferences.ts`内进行覆盖",
|
||||||
"clearAndLogout": "清空缓存 & 退出登录",
|
"clearAndLogout": "清空缓存 & 退出登录",
|
||||||
"mode": "模式",
|
"mode": "模式",
|
||||||
|
|
|
@ -523,7 +523,8 @@ function isAffixTab(tab: TabDefinition) {
|
||||||
* @param tab
|
* @param tab
|
||||||
*/
|
*/
|
||||||
function isTabShown(tab: TabDefinition) {
|
function isTabShown(tab: TabDefinition) {
|
||||||
return !tab.meta.hideInTab;
|
const matched = tab?.matched ?? [];
|
||||||
|
return !tab.meta.hideInTab && matched.every((item) => !item.meta.hideInTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -37,7 +37,6 @@ export function resetAllStores() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const allStores = (pinia as any)._s;
|
const allStores = (pinia as any)._s;
|
||||||
|
|
||||||
for (const [_key, store] of allStores) {
|
for (const [_key, store] of allStores) {
|
||||||
store.$reset();
|
store.$reset();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,109 +1,17 @@
|
||||||
import type {
|
import type {
|
||||||
BaseFormComponentType,
|
|
||||||
VbenFormSchema as FormSchema,
|
VbenFormSchema as FormSchema,
|
||||||
VbenFormProps,
|
VbenFormProps,
|
||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
import type { Component, SetupContext } from 'vue';
|
import type { ComponentType } from './component';
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
setupVbenForm<ComponentType>({
|
||||||
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,
|
|
||||||
},
|
|
||||||
config: {
|
config: {
|
||||||
// ant design vue组件库默认都是 v-model:value
|
// ant design vue组件库默认都是 v-model:value
|
||||||
baseModelPropName: 'value',
|
baseModelPropName: 'value',
|
||||||
|
|
||||||
// 一些组件是 v-model:checked 或者 v-model:fileList
|
// 一些组件是 v-model:checked 或者 v-model:fileList
|
||||||
modelPropNameMap: {
|
modelPropNameMap: {
|
||||||
Checkbox: 'checked',
|
Checkbox: 'checked',
|
||||||
|
@ -130,9 +38,8 @@ setupVbenForm<FormComponentType>({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const useVbenForm = useForm<FormComponentType>;
|
const useVbenForm = useForm<ComponentType>;
|
||||||
|
|
||||||
export { useVbenForm, z };
|
export { useVbenForm, z };
|
||||||
|
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||||
export type VbenFormSchema = FormSchema<FormComponentType>;
|
|
||||||
export type { VbenFormProps };
|
export type { VbenFormProps };
|
||||||
|
|
|
@ -9,10 +9,14 @@ import { VueQueryPlugin } from '@tanstack/vue-query';
|
||||||
|
|
||||||
import { setupI18n } from '#/locales';
|
import { setupI18n } from '#/locales';
|
||||||
|
|
||||||
|
import { initComponentAdapter } from './adapter/component';
|
||||||
import App from './app.vue';
|
import App from './app.vue';
|
||||||
import { router } from './router';
|
import { router } from './router';
|
||||||
|
|
||||||
async function bootstrap(namespace: string) {
|
async function bootstrap(namespace: string) {
|
||||||
|
// 初始化组件适配器
|
||||||
|
await initComponentAdapter();
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
// 国际化 i18n 配置
|
// 国际化 i18n 配置
|
||||||
|
|
|
@ -32,6 +32,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
component: AuthPageLayout,
|
component: AuthPageLayout,
|
||||||
meta: {
|
meta: {
|
||||||
|
hideInTab: true,
|
||||||
title: 'Authentication',
|
title: 'Authentication',
|
||||||
},
|
},
|
||||||
name: 'Authentication',
|
name: 'Authentication',
|
||||||
|
|
1962
pnpm-lock.yaml
1962
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue