Skip to content

Commit 2fdc06d

Browse files
committed
FaModalFaDrawer 分别增加命令式调用API
1 parent 5726479 commit 2fdc06d

File tree

6 files changed

+457
-183
lines changed

6 files changed

+457
-183
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import type { Component, HTMLAttributes } from 'vue'
2+
import { isVNode } from 'vue'
3+
import Drawer from './index.vue'
4+
5+
export interface DrawerProps {
6+
modelValue?: boolean
7+
side?: 'top' | 'bottom' | 'left' | 'right'
8+
title?: string
9+
description?: string
10+
loading?: boolean
11+
closable?: boolean
12+
centered?: boolean
13+
bordered?: boolean
14+
overlay?: boolean
15+
overlayBlur?: boolean
16+
showConfirmButton?: boolean
17+
showCancelButton?: boolean
18+
confirmButtonText?: string
19+
cancelButtonText?: string
20+
confirmButtonDisabled?: boolean
21+
confirmButtonLoading?: boolean
22+
beforeClose?: (
23+
action: 'confirm' | 'cancel' | 'close',
24+
done: () => void
25+
) => void
26+
header?: boolean
27+
footer?: boolean
28+
closeOnClickOverlay?: boolean
29+
closeOnPressEscape?: boolean
30+
destroyOnClose?: boolean
31+
contentClass?: HTMLAttributes['class']
32+
headerClass?: HTMLAttributes['class']
33+
footerClass?: HTMLAttributes['class']
34+
}
35+
36+
export interface DrawerEmits {
37+
'update:modelValue': [value: boolean]
38+
'open': []
39+
'opened': []
40+
'close': []
41+
'closed': []
42+
'confirm': []
43+
'cancel': []
44+
}
45+
46+
type BaseOptions = Omit<DrawerProps, 'modelValue'> & {
47+
content?: Component | VNode | string
48+
onOpen?: () => void
49+
onOpened?: () => void
50+
onClose?: () => void
51+
onClosed?: () => void
52+
onConfirm?: () => void
53+
onCancel?: () => void
54+
}
55+
56+
export function useFaDrawer() {
57+
function create(initialOptions: BaseOptions) {
58+
const container = document.createElement('div')
59+
const visible = ref(false)
60+
const options = reactive({ ...initialOptions })
61+
const app = createApp({
62+
render() {
63+
return h(Drawer, Object.assign({
64+
'modelValue': visible.value,
65+
'onUpdate:modelValue': (val: boolean) => {
66+
visible.value = val
67+
},
68+
}, options), {
69+
default: () => {
70+
if (typeof options.content === 'string') {
71+
return options.content
72+
}
73+
else if (isVNode(options.content)) {
74+
return options.content
75+
}
76+
else if (options.content) {
77+
return h(options.content)
78+
}
79+
return null
80+
},
81+
})
82+
},
83+
})
84+
// 继承主应用的上下文
85+
const instance = getCurrentInstance()
86+
if (instance && instance.appContext) {
87+
Object.assign(app._context, instance.appContext)
88+
}
89+
app.mount(container)
90+
const open = () => {
91+
visible.value = true
92+
}
93+
const close = () => {
94+
visible.value = false
95+
}
96+
const update = (newOptions: BaseOptions) => {
97+
Object.assign(options, newOptions)
98+
}
99+
return {
100+
open,
101+
close,
102+
update,
103+
}
104+
}
105+
return {
106+
create,
107+
}
108+
}

src/ui/components/FaDrawer/index.vue

Lines changed: 62 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import type { HTMLAttributes } from 'vue'
2+
import type { DrawerEmits, DrawerProps } from '.'
33
import { cn } from '@/utils'
44
import {
55
Sheet,
@@ -15,32 +15,7 @@ defineOptions({
1515
})
1616
1717
const props = withDefaults(
18-
defineProps<{
19-
modelValue?: boolean
20-
side?: 'top' | 'bottom' | 'left' | 'right'
21-
title: string
22-
description?: string
23-
loading?: boolean
24-
closable?: boolean
25-
centered?: boolean
26-
bordered?: boolean
27-
overlay?: boolean
28-
overlayBlur?: boolean
29-
showConfirmButton?: boolean
30-
showCancelButton?: boolean
31-
confirmButtonText?: string
32-
cancelButtonText?: string
33-
confirmButtonDisabled?: boolean
34-
confirmButtonLoading?: boolean
35-
header?: boolean
36-
footer?: boolean
37-
closeOnClickOverlay?: boolean
38-
closeOnPressEscape?: boolean
39-
destroyOnClose?: boolean
40-
contentClass?: HTMLAttributes['class']
41-
headerClass?: HTMLAttributes['class']
42-
footerClass?: HTMLAttributes['class']
43-
}>(),
18+
defineProps<DrawerProps>(),
4419
{
4520
modelValue: false,
4621
side: 'right',
@@ -64,15 +39,7 @@ const props = withDefaults(
6439
},
6540
)
6641
67-
const emits = defineEmits<{
68-
'update:modelValue': [value: boolean]
69-
'open': []
70-
'opened': []
71-
'close': []
72-
'closed': []
73-
'confirm': []
74-
'cancel': []
75-
}>()
42+
const emits = defineEmits<DrawerEmits>()
7643
7744
const id = useId()
7845
provide('DrawerId', id)
@@ -86,9 +53,16 @@ watch(() => props.modelValue, (newValue) => {
8653
const hasOpened = ref(false)
8754
const isClosed = ref(true)
8855
89-
watch(() => isOpen.value, (value) => {
56+
watch(isOpen, (val) => {
57+
emits('update:modelValue', val)
58+
if (val) {
59+
emits('open')
60+
}
61+
else {
62+
emits('close')
63+
}
9064
isClosed.value = false
91-
if (value && !hasOpened.value) {
65+
if (val && !hasOpened.value) {
9266
hasOpened.value = true
9367
}
9468
}, {
@@ -97,25 +71,62 @@ watch(() => isOpen.value, (value) => {
9771
9872
const forceMount = computed(() => !props.destroyOnClose && hasOpened.value)
9973
100-
function updateOpen(value: boolean) {
101-
isOpen.value = value
102-
emits('update:modelValue', value)
74+
async function updateOpen(value: boolean) {
10375
if (value) {
76+
isOpen.value = value
10477
emits('open')
10578
}
10679
else {
107-
emits('close')
80+
if (props.beforeClose) {
81+
await props.beforeClose(
82+
'close',
83+
() => {
84+
isOpen.value = value
85+
emits('close')
86+
},
87+
)
88+
}
89+
else {
90+
isOpen.value = value
91+
emits('close')
92+
}
10893
}
10994
}
11095
111-
function onConfirm() {
112-
updateOpen(false)
113-
emits('confirm')
96+
const isConfirmButtonLoading = ref(false)
97+
98+
async function onConfirm() {
99+
if (props.beforeClose) {
100+
isConfirmButtonLoading.value = true
101+
await props.beforeClose(
102+
'confirm',
103+
() => {
104+
isOpen.value = false
105+
emits('confirm')
106+
},
107+
)
108+
isConfirmButtonLoading.value = false
109+
}
110+
else {
111+
isOpen.value = false
112+
emits('confirm')
113+
}
114114
}
115115
116-
function onCancel() {
117-
updateOpen(false)
118-
emits('cancel')
116+
async function onCancel() {
117+
if (props.beforeClose) {
118+
await props.beforeClose(
119+
'cancel',
120+
() => {
121+
isOpen.value = false
122+
emits('cancel')
123+
},
124+
)
125+
}
126+
else {
127+
isOpen.value = false
128+
emits('cancel')
129+
}
119130
}
120131
121132
function handleFocusOutside(e: Event) {
@@ -187,10 +198,10 @@ function handleAnimationEnd() {
187198
<div class="p-4">
188199
<slot />
189200
</div>
201+
<div v-show="props.loading" class="absolute inset-0 z-1000 size-full flex-center bg-popover/75">
202+
<FaIcon name="i-line-md:loading-twotone-loop" class="size-10" />
203+
</div>
190204
</FaScrollArea>
191-
<div v-show="props.loading" class="absolute inset-0 z-1000 size-full flex-center bg-popover/75">
192-
<FaIcon name="i-line-md:loading-twotone-loop" class="size-10" />
193-
</div>
194205
</div>
195206
<SheetFooter
196207
v-if="footer" :class="cn('p-2 gap-y-2', props.footerClass, {

0 commit comments

Comments
 (0)