Browse Source
* refactor: global components can be customized * refactor: remove use Toast and reconstruct the form adapterpull/4635/head
committed by
GitHub
63 changed files with 1757 additions and 2095 deletions
@ -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 }; |
|||
@ -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 }; |
|||
@ -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 }; |
|||
@ -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 }; |
|||
@ -0,0 +1,7 @@ |
|||
--- |
|||
outline: deep |
|||
--- |
|||
|
|||
# Vben Vxe Table 表格 |
|||
|
|||
TODO |
|||
@ -1,4 +1,4 @@ |
|||
import './default/index.css'; |
|||
import './dark/index.css'; |
|||
import './default.css'; |
|||
import './dark.css'; |
|||
|
|||
export {}; |
|||
|
|||
@ -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(); |
|||
@ -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 }; |
|||
@ -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 }; |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue