自实现 UseConfirm 组件

This commit is contained in:
jxxghp
2025-05-24 17:19:43 +08:00
parent 21f352aa64
commit 4dd4e0e148
15 changed files with 199 additions and 80 deletions

View File

@@ -0,0 +1,86 @@
<script setup lang="ts">
import { computed } from 'vue'
interface Props {
modelValue: boolean
type?: 'info' | 'warn' | 'error'
title?: string
content?: string
confirmText?: string
cancelText?: string
width?: string | number
}
const props = withDefaults(defineProps<Props>(), {
type: 'info',
title: '',
content: '',
confirmText: '',
cancelText: '',
width: '30rem',
})
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
(e: 'confirm'): void
(e: 'cancel'): void
}>()
// 对话框类型对应的图标和颜色
const typeConfig = {
info: {
icon: 'mdi-information',
color: 'info',
},
warn: {
icon: 'mdi-alert',
color: 'warning',
},
error: {
icon: 'mdi-alert-circle',
color: 'error',
},
}
// 获取当前类型的配置
const currentType = computed(() => typeConfig[props.type])
// 确认按钮点击
function handleConfirm() {
emit('confirm')
emit('update:modelValue', false)
}
// 取消按钮点击
function handleCancel() {
emit('cancel')
emit('update:modelValue', false)
}
</script>
<template>
<VDialog :model-value="modelValue" @update:model-value="emit('update:modelValue', $event)" :max-width="width">
<VCard>
<VCardItem>
<div class="d-flex align-center justify-center mt-3">
<VAvatar :color="currentType.color" variant="text" size="x-large">
<VIcon size="x-large" :icon="currentType.icon" />
</VAvatar>
<div class="mx-3">
<p class="font-weight-bold text-xl text-high-emphasis">{{ title }}</p>
<p>{{ content }}</p>
</div>
</div>
</VCardItem>
<VCardActions class="mx-auto">
<VBtn variant="tonal" color="secondary" class="px-5" @click="handleCancel">
{{ cancelText }}
</VBtn>
<VBtn variant="elevated" :color="currentType.color" @click="handleConfirm" class="px-5">
{{ confirmText }}
</VBtn>
</VCardActions>
<VDialogCloseBtn @click="handleCancel" />
</VCard>
</VDialog>
</template>

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { useToast } from 'vue-toast-notification'
import { useConfirm } from 'vuetify-use-dialog'
import { useConfirm } from '@/composables/useConfirm'
import api from '@/api'
import type { Plugin } from '@/api/types'
import { isNullOrEmptyObject } from '@core/utils'

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { useToast } from 'vue-toast-notification'
import { useConfirm } from 'vuetify-use-dialog'
import { useConfirm } from '@/composables/useConfirm'
import { useI18n } from 'vue-i18n'
import { useDisplay } from 'vuetify'
@@ -290,12 +290,7 @@ const dropdownItems = ref([
<div class="plugin-folder-card__body" :class="{ 'plugin-folder-card__body--no-icon': !shouldShowIcon }">
<!-- 文件夹图标 -->
<div v-if="shouldShowIcon" class="plugin-folder-card__icon-container">
<VIcon
:icon="folderIcon"
:size="display.mobile ? 56 : 72"
class="plugin-folder-card__folder-icon"
:color="iconColor"
/>
<VIcon :icon="folderIcon" :size="display.mobile ? 56 : 72" class="cursor-move" :color="iconColor" />
</div>
<!-- 文件夹信息 -->
@@ -534,14 +529,6 @@ const dropdownItems = ref([
flex-shrink: 0;
}
&__folder-icon {
transition: all 0.3s ease;
.plugin-folder-card--hover & {
transform: scale(1.05);
}
}
&__info {
text-align: left;
min-height: 0;

View File

@@ -11,7 +11,7 @@ import api from '@/api'
import type { Site, SiteStatistic, SiteUserData } from '@/api/types'
import { isNullOrEmptyObject } from '@/@core/utils'
import { formatFileSize } from '@/@core/utils/formatters'
import { useConfirm } from 'vuetify-use-dialog'
import { useConfirm } from '@/composables/useConfirm'
// 国际化
const { t } = useI18n()

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { useToast } from 'vue-toast-notification'
import { useConfirm } from 'vuetify-use-dialog'
import { useConfirm } from '@/composables/useConfirm'
import SubscribeEditDialog from '../dialog/SubscribeEditDialog.vue'
import SubscribeFilesDialog from '../dialog/SubscribeFilesDialog.vue'
import SubscribeShareDialog from '../dialog/SubscribeShareDialog.vue'

View File

@@ -4,7 +4,7 @@ import { Subscribe, User } from '@/api/types'
import { useUserStore } from '@/stores'
import avatar1 from '@images/avatars/avatar-1.png'
import { useToast } from 'vue-toast-notification'
import { useConfirm } from 'vuetify-use-dialog'
import { useConfirm } from '@/composables/useConfirm'
import UserAddEditDialog from '@/components/dialog/UserAddEditDialog.vue'
import { useDisplay } from 'vuetify'
import { useI18n } from 'vue-i18n'

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { Workflow } from '@/api/types'
import { useToast } from 'vue-toast-notification'
import { useConfirm } from 'vuetify-use-dialog'
import { useConfirm } from '@/composables/useConfirm'
import WorkflowAddEditDialog from '@/components/dialog/WorkflowAddEditDialog.vue'
import WorkflowActionsDialog from '@/components/dialog/WorkflowActionsDialog.vue'
import api from '@/api'

View File

@@ -4,7 +4,7 @@ import { numberValidator } from '@/@validators'
import api from '@/api'
import type { DownloaderConf, FilterRuleGroup, Site, Subscribe, TransferDirectoryConf } from '@/api/types'
import { useDisplay } from 'vuetify'
import { useConfirm } from 'vuetify-use-dialog'
import { useConfirm } from '@/composables/useConfirm'
import { useI18n } from 'vue-i18n'
import { qualityOptions, resolutionOptions, effectOptions } from '@/api/constants'
// i18n

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { AxiosRequestConfig } from 'axios'
import type { PropType } from 'vue'
import { useConfirm } from 'vuetify-use-dialog'
import { useConfirm } from '@/composables/useConfirm'
import { useToast } from 'vue-toast-notification'
import ReorganizeDialog from '../dialog/ReorganizeDialog.vue'
import { formatBytes } from '@core/utils/formatters'

View File

@@ -0,0 +1,88 @@
import { ref } from 'vue'
import { createApp } from 'vue'
import i18n from '@/plugins/i18n'
import vuetify from '@/plugins/vuetify'
import ConfirmDialog from '@/@core/components/ConfirmDialog.vue'
import DialogCloseBtn from '@/@core/components/DialogCloseBtn.vue'
interface ConfirmOptions {
type?: 'info' | 'warn' | 'error'
title?: string
content?: string
confirmText?: string
cancelText?: string
width?: string | number
}
let resolvePromise: ((value: boolean) => void) | null = null
// 创建确认对话框实例
async function createConfirmDialog(options: ConfirmOptions = {}) {
return new Promise<boolean>(resolve => {
resolvePromise = resolve
// 创建容器
const container = document.createElement('div')
document.body.appendChild(container)
// 处理国际化
const i18nOptions = {
...options,
title: options.title || i18n.global.t('common.confirm'),
confirmText: options.confirmText || i18n.global.t('common.confirm'),
cancelText: options.cancelText || i18n.global.t('common.cancel'),
}
// 创建应用实例
const app = createApp(ConfirmDialog, {
modelValue: true,
...i18nOptions,
'onUpdate:modelValue': (val: boolean) => {
if (!val) {
cleanup()
}
},
onConfirm: () => {
resolvePromise?.(true)
cleanup()
},
onCancel: () => {
resolvePromise?.(false)
cleanup()
},
})
// 注册必要的组件
app.component('VDialogCloseBtn', DialogCloseBtn)
// 使用插件
app.use(vuetify)
app.use(i18n)
// 挂载应用
app.mount(container)
// 清理函数
const cleanup = () => {
app.unmount()
document.body.removeChild(container)
}
})
}
// 创建一个函数对象,同时支持直接调用和解构
const confirmFunction = Object.assign(createConfirmDialog, {
createConfirm: createConfirmDialog,
})
// 导出 useConfirm 函数
export function useConfirm() {
return confirmFunction
}
// 插件
export default {
install: (app: any) => {
app.provide('confirm', { createConfirm: createConfirmDialog })
},
}

View File

@@ -13,6 +13,7 @@ import { checkPrefersColorSchemeIsDark } from '@/@core/utils'
import { getCurrentLocale, setI18nLanguage } from '@/plugins/i18n'
import { saveLocalTheme } from '@/@core/utils/theme'
import type { ThemeSwitcherTheme } from '@layouts/types'
import { useConfirm } from '@/composables/useConfirm'
// 认证 Store
const authStore = useAuthStore()
@@ -32,9 +33,6 @@ const progressDialog = ref(false)
// 站点认证对话框
const siteAuthDialog = ref(false)
// 重启确认对话框
const restartDialog = ref(false)
// 自定义CSS弹窗
const cssDialog = ref(false)
@@ -47,6 +45,9 @@ const showLanguageMenu = ref(false)
// 自定义CSS
const customCSS = ref('')
// 确认框
const { createConfirm } = useConfirm()
// 执行注销操作
function logout() {
// 清除登录状态信息
@@ -57,7 +58,6 @@ function logout() {
// 执行重启操作
async function restart() {
restartDialog.value = false
// 调用API重启
try {
// 显示等待框
@@ -79,7 +79,15 @@ async function restart() {
// 显示重启确认对话框
async function showRestartDialog() {
restartDialog.value = true
const isConfirmed = await createConfirm({
type: 'warn',
title: t('app.confirmRestart'),
content: t('app.restartTip'),
})
if (!isConfirmed) return
await restart()
}
// 显示站点认证对话框
@@ -417,32 +425,6 @@ onMounted(() => {
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="t('app.restarting')" />
<!-- 用户认证对话框 -->
<UserAuthDialog v-if="siteAuthDialog" v-model="siteAuthDialog" @done="siteAuthDone" @close="siteAuthDialog = false" />
<!-- 重启确认对话框 -->
<VDialog v-if="restartDialog" v-model="restartDialog" max-width="25rem">
<VCard>
<VCardItem>
<div class="d-flex align-center justify-center mt-3">
<VAvatar color="warning" variant="text" size="x-large">
<VIcon size="x-large" icon="mdi-alert" />
</VAvatar>
<div class="ms-3">
<p class="font-weight-bold text-xl text-high-emphasis">{{ t('app.confirmRestart') }}</p>
<p>{{ t('app.restartTip') }}</p>
</div>
</div>
</VCardItem>
<VCardActions class="mx-auto">
<VBtn variant="tonal" color="secondary" class="px-5" @click="restartDialog = false">{{
t('common.cancel')
}}</VBtn>
<VBtn variant="elevated" color="error" @click="restart" prepend-icon="mdi-restart" class="px-5">{{
t('common.confirm')
}}</VBtn>
</VCardActions>
<VDialogCloseBtn @click="restartDialog = false" />
</VCard>
</VDialog>
<!-- 自定义 CSS -->
<VDialog v-if="cssDialog" v-model="cssDialog" max-width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
<VCard>

View File

@@ -24,7 +24,7 @@ import { fetchGlobalSettings } from './utils/globalSetting'
// 5. 其他插件和功能模块
import ToastPlugin from 'vue-toast-notification'
import VuetifyUseDialog from 'vuetify-use-dialog'
import ConfirmDialog from '@/composables/useConfirm'
import VueApexCharts from 'vue3-apexcharts'
// 6. 注册自定义组件
@@ -102,24 +102,7 @@ initializeApp().then(() => {
.use(ToastPlugin, {
position: 'bottom-right',
})
.use(VuetifyUseDialog, {
confirmDialog: {
dialogProps: {
maxWidth: '30rem',
},
confirmationButtonProps: {
color: 'primary',
class: 'me-3 px-5',
'prepend-icon': 'mdi-check',
},
cancellationButtonProps: {
color: 'secondary',
class: 'me-3',
},
confirmationText: i18n.global.t('common.confirm'),
cancellationText: i18n.global.t('common.cancel'),
},
})
.use(ConfirmDialog)
.use(i18n)
.mount('#app')
})