Skip to content

Commit eabe2dd

Browse files
committed
feat: enhance modal component with loading states and action handling
1 parent 4ed7b94 commit eabe2dd

File tree

9 files changed

+55
-22
lines changed

9 files changed

+55
-22
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/DnsRulesConfig.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ const renderRule = (rule: IDNSRule) => {
170170

171171
<Modal
172172
v-model:open="showEditModal"
173-
@ok="handleAddEnd"
173+
:on-ok="handleAddEnd"
174174
title="kernel.dns.tab.rules"
175175
max-width="80"
176176
max-height="80"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ const renderServer = (server: IDNSServer) => {
147147

148148
<Modal
149149
v-model:open="showEditModal"
150-
@ok="handleAddEnd"
150+
:on-ok="handleAddEnd"
151151
title="kernel.dns.tab.servers"
152152
max-width="80"
153153
max-height="80"

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

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

215215
<Modal
216216
v-model:open="showSortModal"
217-
@ok="handleSortGroupEnd"
217+
:on-ok="handleSortGroupEnd"
218218
mask-closable
219219
title="kernel.outbounds.sort"
220220
max-width="80"
@@ -233,7 +233,7 @@ subscribesStore.subscribes.forEach(async ({ id, name, proxies }) => {
233233

234234
<Modal
235235
v-model:open="showEditModal"
236-
@ok="handleAddEnd"
236+
:on-ok="handleAddEnd"
237237
title="kernel.outbounds.name"
238238
width="80"
239239
height="80"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ const renderRule = (rule: IRule) => {
162162

163163
<Modal
164164
v-model:open="showEditModal"
165-
@ok="handleAddEnd"
165+
:on-ok="handleAddEnd"
166166
title="kernel.route.tab.rules"
167167
max-width="80"
168168
max-height="80"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ const handleUse = (ruleset: any) => {
101101

102102
<Modal
103103
v-model:open="showEditModal"
104-
@ok="handleAddEnd"
104+
:on-ok="handleAddEnd"
105105
title="kernel.route.tab.rule_set"
106106
max-width="80"
107107
max-height="80"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ const getProxyByTag = async (tag: string) => {
243243
:cancel="isEdit"
244244
:mask-closable="!isEdit"
245245
:title="isEdit ? (details ? 'common.edit' : 'common.add') : 'common.details'"
246-
@ok="onEditEnd"
246+
:on-ok="onEditEnd"
247247
cancel-text="common.close"
248248
max-height="80"
249249
max-width="80"

0 commit comments

Comments
 (0)