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 './default.css'; |
||||
import './dark/index.css'; |
import './dark.css'; |
||||
|
|
||||
export {}; |
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