自实现 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
+1
View File
@@ -8,6 +8,7 @@ export {}
/* prettier-ignore */ /* prettier-ignore */
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
ConfirmDialog: typeof import('./src/@core/components/ConfirmDialog.vue')['default']
DialogCloseBtn: typeof import('./src/@core/components/DialogCloseBtn.vue')['default'] DialogCloseBtn: typeof import('./src/@core/components/DialogCloseBtn.vue')['default']
ErrorHeader: typeof import('./src/@core/components/ErrorHeader.vue')['default'] ErrorHeader: typeof import('./src/@core/components/ErrorHeader.vue')['default']
ExistIcon: typeof import('./src/@core/components/ExistIcon.vue')['default'] ExistIcon: typeof import('./src/@core/components/ExistIcon.vue')['default']
-1
View File
@@ -62,7 +62,6 @@
"vue3-perfect-scrollbar": "^2.0.0", "vue3-perfect-scrollbar": "^2.0.0",
"vuedraggable": "^4.1.0", "vuedraggable": "^4.1.0",
"vuetify": "3.7.3", "vuetify": "3.7.3",
"vuetify-use-dialog": "^0.6.11",
"webfontloader": "^1.6.28" "webfontloader": "^1.6.28"
}, },
"devDependencies": { "devDependencies": {
+86
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>
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useToast } from 'vue-toast-notification' import { useToast } from 'vue-toast-notification'
import { useConfirm } from 'vuetify-use-dialog' import { useConfirm } from '@/composables/useConfirm'
import api from '@/api' import api from '@/api'
import type { Plugin } from '@/api/types' import type { Plugin } from '@/api/types'
import { isNullOrEmptyObject } from '@core/utils' import { isNullOrEmptyObject } from '@core/utils'
+2 -15
View File
@@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useToast } from 'vue-toast-notification' import { useToast } from 'vue-toast-notification'
import { useConfirm } from 'vuetify-use-dialog' import { useConfirm } from '@/composables/useConfirm'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useDisplay } from 'vuetify' 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 class="plugin-folder-card__body" :class="{ 'plugin-folder-card__body--no-icon': !shouldShowIcon }">
<!-- 文件夹图标 --> <!-- 文件夹图标 -->
<div v-if="shouldShowIcon" class="plugin-folder-card__icon-container"> <div v-if="shouldShowIcon" class="plugin-folder-card__icon-container">
<VIcon <VIcon :icon="folderIcon" :size="display.mobile ? 56 : 72" class="cursor-move" :color="iconColor" />
:icon="folderIcon"
:size="display.mobile ? 56 : 72"
class="plugin-folder-card__folder-icon"
:color="iconColor"
/>
</div> </div>
<!-- 文件夹信息 --> <!-- 文件夹信息 -->
@@ -534,14 +529,6 @@ const dropdownItems = ref([
flex-shrink: 0; flex-shrink: 0;
} }
&__folder-icon {
transition: all 0.3s ease;
.plugin-folder-card--hover & {
transform: scale(1.05);
}
}
&__info { &__info {
text-align: left; text-align: left;
min-height: 0; min-height: 0;
+1 -1
View File
@@ -11,7 +11,7 @@ import api from '@/api'
import type { Site, SiteStatistic, SiteUserData } from '@/api/types' import type { Site, SiteStatistic, SiteUserData } from '@/api/types'
import { isNullOrEmptyObject } from '@/@core/utils' import { isNullOrEmptyObject } from '@/@core/utils'
import { formatFileSize } from '@/@core/utils/formatters' import { formatFileSize } from '@/@core/utils/formatters'
import { useConfirm } from 'vuetify-use-dialog' import { useConfirm } from '@/composables/useConfirm'
// 国际化 // 国际化
const { t } = useI18n() const { t } = useI18n()
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useToast } from 'vue-toast-notification' import { useToast } from 'vue-toast-notification'
import { useConfirm } from 'vuetify-use-dialog' import { useConfirm } from '@/composables/useConfirm'
import SubscribeEditDialog from '../dialog/SubscribeEditDialog.vue' import SubscribeEditDialog from '../dialog/SubscribeEditDialog.vue'
import SubscribeFilesDialog from '../dialog/SubscribeFilesDialog.vue' import SubscribeFilesDialog from '../dialog/SubscribeFilesDialog.vue'
import SubscribeShareDialog from '../dialog/SubscribeShareDialog.vue' import SubscribeShareDialog from '../dialog/SubscribeShareDialog.vue'
+1 -1
View File
@@ -4,7 +4,7 @@ import { Subscribe, User } from '@/api/types'
import { useUserStore } from '@/stores' import { useUserStore } from '@/stores'
import avatar1 from '@images/avatars/avatar-1.png' import avatar1 from '@images/avatars/avatar-1.png'
import { useToast } from 'vue-toast-notification' 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 UserAddEditDialog from '@/components/dialog/UserAddEditDialog.vue'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
+1 -1
View File
@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Workflow } from '@/api/types' import { Workflow } from '@/api/types'
import { useToast } from 'vue-toast-notification' 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 WorkflowAddEditDialog from '@/components/dialog/WorkflowAddEditDialog.vue'
import WorkflowActionsDialog from '@/components/dialog/WorkflowActionsDialog.vue' import WorkflowActionsDialog from '@/components/dialog/WorkflowActionsDialog.vue'
import api from '@/api' import api from '@/api'
@@ -4,7 +4,7 @@ import { numberValidator } from '@/@validators'
import api from '@/api' import api from '@/api'
import type { DownloaderConf, FilterRuleGroup, Site, Subscribe, TransferDirectoryConf } from '@/api/types' import type { DownloaderConf, FilterRuleGroup, Site, Subscribe, TransferDirectoryConf } from '@/api/types'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { useConfirm } from 'vuetify-use-dialog' import { useConfirm } from '@/composables/useConfirm'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { qualityOptions, resolutionOptions, effectOptions } from '@/api/constants' import { qualityOptions, resolutionOptions, effectOptions } from '@/api/constants'
// i18n // i18n
+1 -1
View File
@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { AxiosRequestConfig } from 'axios' import type { AxiosRequestConfig } from 'axios'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { useConfirm } from 'vuetify-use-dialog' import { useConfirm } from '@/composables/useConfirm'
import { useToast } from 'vue-toast-notification' import { useToast } from 'vue-toast-notification'
import ReorganizeDialog from '../dialog/ReorganizeDialog.vue' import ReorganizeDialog from '../dialog/ReorganizeDialog.vue'
import { formatBytes } from '@core/utils/formatters' import { formatBytes } from '@core/utils/formatters'
+88
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 })
},
}
+13 -31
View File
@@ -13,6 +13,7 @@ import { checkPrefersColorSchemeIsDark } from '@/@core/utils'
import { getCurrentLocale, setI18nLanguage } from '@/plugins/i18n' import { getCurrentLocale, setI18nLanguage } from '@/plugins/i18n'
import { saveLocalTheme } from '@/@core/utils/theme' import { saveLocalTheme } from '@/@core/utils/theme'
import type { ThemeSwitcherTheme } from '@layouts/types' import type { ThemeSwitcherTheme } from '@layouts/types'
import { useConfirm } from '@/composables/useConfirm'
// 认证 Store // 认证 Store
const authStore = useAuthStore() const authStore = useAuthStore()
@@ -32,9 +33,6 @@ const progressDialog = ref(false)
// 站点认证对话框 // 站点认证对话框
const siteAuthDialog = ref(false) const siteAuthDialog = ref(false)
// 重启确认对话框
const restartDialog = ref(false)
// 自定义CSS弹窗 // 自定义CSS弹窗
const cssDialog = ref(false) const cssDialog = ref(false)
@@ -47,6 +45,9 @@ const showLanguageMenu = ref(false)
// 自定义CSS // 自定义CSS
const customCSS = ref('') const customCSS = ref('')
// 确认框
const { createConfirm } = useConfirm()
// 执行注销操作 // 执行注销操作
function logout() { function logout() {
// 清除登录状态信息 // 清除登录状态信息
@@ -57,7 +58,6 @@ function logout() {
// 执行重启操作 // 执行重启操作
async function restart() { async function restart() {
restartDialog.value = false
// 调用API重启 // 调用API重启
try { try {
// 显示等待框 // 显示等待框
@@ -79,7 +79,15 @@ async function restart() {
// 显示重启确认对话框 // 显示重启确认对话框
async function showRestartDialog() { 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')" /> <ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="t('app.restarting')" />
<!-- 用户认证对话框 --> <!-- 用户认证对话框 -->
<UserAuthDialog v-if="siteAuthDialog" v-model="siteAuthDialog" @done="siteAuthDone" @close="siteAuthDialog = false" /> <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 --> <!-- 自定义 CSS -->
<VDialog v-if="cssDialog" v-model="cssDialog" max-width="50rem" scrollable :fullscreen="!display.mdAndUp.value"> <VDialog v-if="cssDialog" v-model="cssDialog" max-width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
<VCard> <VCard>
+2 -19
View File
@@ -24,7 +24,7 @@ import { fetchGlobalSettings } from './utils/globalSetting'
// 5. 其他插件和功能模块 // 5. 其他插件和功能模块
import ToastPlugin from 'vue-toast-notification' import ToastPlugin from 'vue-toast-notification'
import VuetifyUseDialog from 'vuetify-use-dialog' import ConfirmDialog from '@/composables/useConfirm'
import VueApexCharts from 'vue3-apexcharts' import VueApexCharts from 'vue3-apexcharts'
// 6. 注册自定义组件 // 6. 注册自定义组件
@@ -102,24 +102,7 @@ initializeApp().then(() => {
.use(ToastPlugin, { .use(ToastPlugin, {
position: 'bottom-right', position: 'bottom-right',
}) })
.use(VuetifyUseDialog, { .use(ConfirmDialog)
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(i18n) .use(i18n)
.mount('#app') .mount('#app')
}) })
-7
View File
@@ -7704,13 +7704,6 @@ vuedraggable@^4.1.0:
dependencies: dependencies:
sortablejs "1.14.0" sortablejs "1.14.0"
vuetify-use-dialog@^0.6.11:
version "0.6.11"
resolved "https://registry.yarnpkg.com/vuetify-use-dialog/-/vuetify-use-dialog-0.6.11.tgz#8800cc56b234dae1dfa44a7f06a6bb1a33ad4b39"
integrity sha512-iPAu6MsN8suuNAS1M6JN2CaOXRgr7LZ2u+UNtAw0Fi3AjianzVIrnRNhQcAZjmE8Hu6ZwAbgte1p47qU6OazLw==
dependencies:
defu "^6.1.4"
vuetify@3.7.3: vuetify@3.7.3:
version "3.7.3" version "3.7.3"
resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-3.7.3.tgz#0e89f7f0298d452510bcbc01b0e9b53a5ce6e883" resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-3.7.3.tgz#0e89f7f0298d452510bcbc01b0e9b53a5ce6e883"