Skip to content

Commit 5d77f7a

Browse files
committed
feat: enhance modal component with loading states and action handling
1 parent cfe1b3b commit 5d77f7a

File tree

7 files changed

+59
-20
lines changed

7 files changed

+59
-20
lines changed

frontend/src/components/Modal/index.vue

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
<script setup lang="ts">
2-
import { computed, provide } from 'vue'
2+
import { computed, provide, ref } from 'vue'
33
44
import { WindowToggleMaximise } from '@/bridge'
55
import useI18n from '@/lang'
66
77
export interface Props {
8-
open: boolean
98
title?: string
109
footer?: boolean
1110
maxHeight?: string
@@ -19,6 +18,10 @@ export interface Props {
1918
cancelText?: string
2019
submitText?: string
2120
maskClosable?: boolean
21+
onOk?: () => MaybePromise<boolean | void>
22+
onCancel?: () => MaybePromise<boolean | void>
23+
beforeClose?: (isOk: boolean) => MaybePromise<boolean | void>
24+
afterClose?: (isOk: boolean) => void
2225
}
2326
2427
const props = withDefaults(defineProps<Props>(), {
@@ -37,16 +40,32 @@ const props = withDefaults(defineProps<Props>(), {
3740
maskClosable: false,
3841
})
3942
43+
const open = defineModel('open', { default: false })
44+
45+
const cancelLoading = ref(false)
46+
const submitLoading = ref(false)
47+
4048
const { t } = useI18n.global
4149
42-
const emits = defineEmits(['update:open', 'ok'])
50+
const handleAction = async (isOk: boolean) => {
51+
const loading = isOk ? submitLoading : cancelLoading
52+
const action = isOk ? props.onOk : props.onCancel
53+
54+
loading.value = true
55+
try {
56+
if (!((await action?.()) ?? true) || !((await props.beforeClose?.(isOk)) ?? true)) {
57+
return
58+
}
59+
} finally {
60+
loading.value = false
61+
}
4362
44-
const handleSubmit = () => {
45-
emits('update:open', false)
46-
emits('ok')
63+
open.value = false
64+
props.afterClose?.(isOk)
4765
}
4866
49-
const handleCancel = () => emits('update:open', false)
67+
const handleSubmit = () => handleAction(true)
68+
const handleCancel = () => handleAction(false)
5069
5170
const onMaskClick = () => props.maskClosable && handleCancel()
5271
@@ -81,10 +100,17 @@ provide('submit', handleSubmit)
81100
</div>
82101
<div v-if="footer" class="action">
83102
<slot name="action" />
84-
<Button v-if="cancel" @click="handleCancel" :type="maskClosable ? 'text' : 'normal'">
103+
<Button
104+
v-if="cancel"
105+
@click="handleCancel"
106+
:loading="cancelLoading"
107+
:type="maskClosable ? 'text' : 'normal'"
108+
>
85109
{{ t(cancelText) }}
86110
</Button>
87-
<Button v-if="submit" @click="handleSubmit" type="primary">{{ t(submitText) }}</Button>
111+
<Button v-if="submit" @click="handleSubmit" :loading="submitLoading" type="primary">
112+
{{ t(submitText) }}
113+
</Button>
88114
</div>
89115
</div>
90116
</div>

frontend/src/types/typescript.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
type Recordable<T = any> = { [x: string]: T }
2+
3+
type MaybePromise<T> = T | Promise<T>

frontend/src/utils/others.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@ import { APP_TITLE, APP_VERSION } from '@/utils'
55

66
export const deepClone = <T>(json: T): T => JSON.parse(JSON.stringify(json))
77

8-
export const omit = <T, K extends keyof T>(obj: T, fields: K[]): Omit<T, K> => {
9-
const _obj = deepClone(obj)
10-
fields.forEach((field) => {
11-
delete _obj[field]
12-
})
13-
return _obj
8+
export const omit = <T extends object, K extends keyof T>(obj: T, props: K[]): Omit<T, K> => {
9+
const result = {} as T
10+
const omitSet = new Set(props)
11+
for (const key in obj) {
12+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
13+
if (!omitSet.has(key as unknown as K)) {
14+
result[key] = obj[key]
15+
}
16+
}
17+
}
18+
return result as Omit<T, K>
1419
}
1520

1621
export const omitArray = <T, K extends keyof T>(arr: T[], fields: K[]): Omit<T, K>[] => {

frontend/src/views/ProfilesView/components/ProxyGroupsConfig.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ subscribesStore.subscribes.forEach(async ({ id, name, proxies }) => {
250250

251251
<Modal
252252
v-model:open="showSortModal"
253-
@ok="handleSortGroupEnd"
253+
:on-ok="handleSortGroupEnd"
254254
mask-closable
255255
title="kernel.proxyGroups.sort"
256256
max-width="80"
@@ -274,7 +274,13 @@ subscribesStore.subscribes.forEach(async ({ id, name, proxies }) => {
274274
</div>
275275
</Modal>
276276

277-
<Modal v-model:open="showModal" @ok="handleAddEnd" title="profile.group" width="80" height="80">
277+
<Modal
278+
v-model:open="showModal"
279+
:on-ok="handleAddEnd"
280+
title="profile.group"
281+
width="80"
282+
height="80"
283+
>
278284
<div class="form-item">
279285
{{ t('kernel.proxyGroups.name') }}
280286
<Input v-model="fields.name" autofocus />

frontend/src/views/ProfilesView/components/RulesConfig.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ const showLost = () => message.warn('kernel.rules.notFound')
136136

137137
<Modal
138138
v-model:open="showModal"
139-
@ok="handleAddEnd"
139+
:on-ok="handleAddEnd"
140140
title="profile.rule"
141141
max-width="80"
142142
max-height="80"

frontend/src/views/RulesetsView/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ const onSortUpdate = debounce(rulesetsStore.saveRulesets, 1000)
364364
v-model:open="showRulesetList"
365365
:title="rulesetTitle"
366366
:footer="false"
367-
@ok="onEditRuelsetListEnd"
367+
:on-ok="onEditRuelsetListEnd"
368368
height="90"
369369
width="90"
370370
>

frontend/src/views/SubscribesView/components/ProxiesView.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ const getProxyByName = async (name: string) => {
246246
:cancel="isEdit"
247247
:mask-closable="!isEdit"
248248
:title="isEdit ? (details ? 'common.edit' : 'common.add') : 'common.details'"
249-
@ok="onEditEnd"
249+
:on-ok="onEditEnd"
250250
cancel-text="common.close"
251251
max-height="80"
252252
max-width="80"

0 commit comments

Comments
 (0)