将所有 VDialog 组件替换为 DialogWrapper 组件

This commit is contained in:
jxxghp
2025-07-10 12:44:37 +08:00
parent 273d1f8ef2
commit 0918fa1685
63 changed files with 463 additions and 146 deletions

View File

@@ -59,7 +59,7 @@ function handleCancel() {
</script>
<template>
<VDialog :model-value="modelValue" @update:model-value="emit('update:modelValue', $event)" :max-width="width">
<DialogWrapper :model-value="modelValue" @update:model-value="emit('update:modelValue', $event)" :max-width="width">
<VCard>
<VCardItem>
<div class="d-flex align-center justify-start mt-3">
@@ -82,5 +82,5 @@ function handleCancel() {
</VCardActions>
<VDialogCloseBtn @click="handleCancel" />
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -0,0 +1,70 @@
<template>
<VDialog v-model="dialogModel" v-bind="$attrs" @update:model-value="handleDialogChange">
<slot />
</VDialog>
</template>
<script setup lang="ts">
import { computed, watch, onBeforeUnmount } from 'vue'
import { useDialogScrollLockWithWatch } from '@/composables/useDialogScrollLock'
// Props
interface Props {
modelValue?: boolean
// 滚动锁定配置
scrollLock?: boolean
preserveScrollPosition?: boolean
preventTouchScroll?: boolean
}
const props = withDefaults(defineProps<Props>(), {
scrollLock: true,
preserveScrollPosition: true,
preventTouchScroll: true,
})
// Emits
const emit = defineEmits<{
'update:modelValue': [value: boolean]
}>()
// 计算属性
const dialogModel = computed({
get: () => props.modelValue || false,
set: (value: boolean) => emit('update:modelValue', value),
})
// 使用滚动锁定
const { isLocked, lockScroll, restoreScroll } = useDialogScrollLockWithWatch(dialogModel, {
autoRestore: true,
preserveScrollPosition: props.preserveScrollPosition,
preventTouchScroll: props.preventTouchScroll,
})
// 处理弹窗状态变化
const handleDialogChange = (value: boolean) => {
emit('update:modelValue', value)
}
// 监听弹窗状态变化
watch(
dialogModel,
newValue => {
if (props.scrollLock) {
if (newValue) {
lockScroll()
} else {
restoreScroll()
}
}
},
{ immediate: true },
)
// 组件卸载时确保恢复滚动
onBeforeUnmount(() => {
if (isLocked.value) {
restoreScroll()
}
})
</script>

View File

@@ -46,7 +46,8 @@ $header: ".layout-navbar";
/* Ensure header styles are preserved when dialog is opened,
regardless of scroll state
*/
html.v-overlay-scroll-blocked &.window-scrolled.layout-navbar-fixed {
html.v-overlay-scroll-blocked &.window-scrolled.layout-navbar-fixed,
html.dialog-scroll-locked &.layout-navbar-fixed {
#{$header} {
@extend %default-layout-vertical-nav-scrolled-sticky-elevated-nav;

View File

@@ -88,6 +88,9 @@ export default defineComponent({
},
})
// 检查是否有弹窗打开通过CSS类名判断
const isDialogOpen = document.documentElement.classList.contains('dialog-scroll-locked')
return h(
'div',
{
@@ -96,7 +99,7 @@ export default defineComponent({
'layout-navbar-fixed',
mdAndDown.value && 'layout-overlay-nav',
route.meta.layoutWrapperClasses,
scrollDistance.value && 'window-scrolled',
(scrollDistance.value || isDialogOpen) && 'window-scrolled',
],
},
[verticalNav, h('div', { class: 'layout-content-wrapper' }, [navbar, main, footer]), layoutOverlay],

View File

@@ -133,7 +133,7 @@ const instructions = computed(() => {
</Teleport>
<!-- 手动安装说明对话框 -->
<VDialog v-model="showInstructions" max-width="500">
<DialogWrapper v-model="showInstructions" max-width="500">
<VCard>
<VCardItem>
<VCardTitle class="d-flex align-center">
@@ -170,7 +170,7 @@ const instructions = computed(() => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>
<style scoped>

View File

@@ -110,7 +110,7 @@ function onClose() {
<VImg :src="filter_svg" cover class="mt-7" max-width="3rem" />
</VCardText>
</VCard>
<VDialog
<DialogWrapper
v-if="ruleInfoDialog"
v-model="ruleInfoDialog"
scrollable
@@ -215,6 +215,6 @@ function onClose() {
}}</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</div>
</template>

View File

@@ -196,7 +196,7 @@ onUnmounted(() => {
</VCard>
</VHover>
<VDialog
<DialogWrapper
v-if="downloaderInfoDialog"
v-model="downloaderInfoDialog"
scrollable
@@ -383,6 +383,6 @@ onUnmounted(() => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</div>
</template>

View File

@@ -223,7 +223,7 @@ function onClose() {
<VImg :src="filter_group_svg" cover class="mt-10" max-width="3rem" />
</VCardText>
</VCard>
<VDialog
<DialogWrapper
v-if="groupInfoDialog"
v-model="groupInfoDialog"
scrollable
@@ -308,7 +308,7 @@ function onClose() {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
<ImportCodeDialog
v-if="importCodeDialog"
v-model="importCodeDialog"

View File

@@ -204,7 +204,7 @@ onMounted(() => {
</VCardText>
</VCard>
<VDialog
<DialogWrapper
v-if="mediaServerInfoDialog"
v-model="mediaServerInfoDialog"
scrollable
@@ -506,6 +506,6 @@ onMounted(() => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</div>
</template>

View File

@@ -141,7 +141,7 @@ function onClose() {
</VCardText>
</VCard>
<VDialog
<DialogWrapper
v-if="notificationInfoDialog"
v-model="notificationInfoDialog"
scrollable
@@ -476,6 +476,6 @@ function onClose() {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</div>
</template>

View File

@@ -267,15 +267,15 @@ const dropdownItems = ref([
<!-- 安装插件进度框 -->
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" />
<!-- 更新日志 -->
<VDialog v-if="releaseDialog" v-model="releaseDialog" width="600" scrollable>
<DialogWrapper v-if="releaseDialog" v-model="releaseDialog" width="600" scrollable>
<VCard :title="t('plugin.updateHistoryTitle', { name: props.plugin?.plugin_name })">
<VDialogCloseBtn @click="releaseDialog = false" />
<VDivider />
<VersionHistory :history="props.plugin?.history" />
</VCard>
</VDialog>
</DialogWrapper>
<!-- 插件详情-->
<VDialog v-if="detailDialog" v-model="detailDialog" max-width="30rem">
<DialogWrapper v-if="detailDialog" v-model="detailDialog" max-width="30rem">
<VCard>
<VDialogCloseBtn @click="detailDialog = false" />
<VCardText>
@@ -335,6 +335,6 @@ const dropdownItems = ref([
</VCol>
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
</div>
</template>

View File

@@ -547,7 +547,7 @@ watch(
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" />
<!-- 更新日志 -->
<VDialog v-if="releaseDialog" v-model="releaseDialog" width="600" scrollable :fullscreen="!display.mdAndUp.value">
<DialogWrapper v-if="releaseDialog" v-model="releaseDialog" width="600" scrollable :fullscreen="!display.mdAndUp.value">
<VCard :title="t('plugin.updateHistoryTitle', { name: props.plugin?.plugin_name })">
<VDialogCloseBtn @click="releaseDialog = false" />
<VDivider />
@@ -562,10 +562,10 @@ watch(
</VBtn>
</VCardItem>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 实时日志弹窗 -->
<VDialog
<DialogWrapper
v-if="loggingDialog"
v-model="loggingDialog"
scrollable
@@ -591,10 +591,10 @@ watch(
<LoggingView :logfile="`plugins/${props.plugin?.id?.toLowerCase()}.log`" />
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 插件分身对话框 -->
<VDialog
<DialogWrapper
v-if="pluginCloneDialog"
v-model="pluginCloneDialog"
width="600"
@@ -700,7 +700,7 @@ watch(
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</div>
</template>

View File

@@ -350,7 +350,7 @@ const dropdownItems = ref([
</VHover>
<!-- 重命名对话框 -->
<VDialog v-if="renameDialog" v-model="renameDialog" max-width="400">
<DialogWrapper v-if="renameDialog" v-model="renameDialog" max-width="400">
<VCard>
<VCardItem>
<template #prepend>
@@ -374,10 +374,10 @@ const dropdownItems = ref([
<VBtn color="primary" prepend-icon="mdi-check" class="px-5" @click="confirmRename">确认</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 设置对话框 -->
<VDialog
<DialogWrapper
v-if="settingDialog"
v-model="settingDialog"
max-width="600"
@@ -480,7 +480,7 @@ const dropdownItems = ref([
<VBtn color="primary" prepend-icon="mdi-content-save" class="px-5" @click="saveSettings">保存</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</div>
</template>

View File

@@ -220,7 +220,7 @@ function onClose() {
@close="smbConfigDialog = false"
@done="handleDone"
/>
<VDialog
<DialogWrapper
v-if="customConfigDialog"
v-model="customConfigDialog"
scrollable
@@ -263,6 +263,6 @@ function onClose() {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</div>
</template>

View File

@@ -278,7 +278,7 @@ onMounted(() => {
</VCard>
<!-- 更多来源对话框 -->
<VDialog v-model="showMoreTorrents" max-width="25rem" location="center">
<DialogWrapper v-model="showMoreTorrents" max-width="25rem" location="center">
<VCard>
<VCardTitle class="py-3 d-flex align-center">
<span>其他来源</span>
@@ -361,7 +361,7 @@ onMounted(() => {
</VList>
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
<AddDownloadDialog
v-if="addDownloadDialog"

View File

@@ -132,7 +132,7 @@ onMounted(() => {
})
</script>
<template>
<VDialog max-width="35rem" scrollable>
<DialogWrapper max-width="35rem" scrollable>
<VCard>
<VCardItem class="py-2">
<template #prepend>
@@ -209,5 +209,5 @@ onMounted(() => {
</VBtn>
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -70,7 +70,7 @@ async function savaAlistConfig() {
</script>
<template>
<VDialog width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
<DialogWrapper width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
<VCard>
<VDialogCloseBtn @click="emit('close')" />
<VCardItem>
@@ -143,5 +143,5 @@ async function savaAlistConfig() {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -110,7 +110,7 @@ onUnmounted(() => {
</script>
<template>
<VDialog width="40rem" scrollable :fullscreen="!display.mdAndUp.value">
<DialogWrapper width="40rem" scrollable :fullscreen="!display.mdAndUp.value">
<VCard>
<VDialogCloseBtn @click="emit('close')" />
<VCardItem>
@@ -148,5 +148,5 @@ onUnmounted(() => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -170,7 +170,7 @@ onMounted(() => {
})
</script>
<template>
<VDialog max-width="40rem" scrollable>
<DialogWrapper max-width="40rem" scrollable>
<VCard>
<VDialogCloseBtn @click="emit('close')" />
<VCardText>
@@ -286,5 +286,5 @@ onMounted(() => {
</VCol>
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -146,7 +146,7 @@ async function doDelete() {
}
</script>
<template>
<VDialog max-width="40rem" scrollable>
<DialogWrapper max-width="40rem" scrollable>
<VCard>
<VCardText>
<VCol>
@@ -243,7 +243,7 @@ async function doDelete() {
</VCardText>
<VDialogCloseBtn @click="emit('close')" />
</VCard>
</VDialog>
</DialogWrapper>
</template>
<style lang="scss">

View File

@@ -24,7 +24,7 @@ function handleImport() {
</script>
<template>
<VDialog width="40rem" scrollable max-height="85vh">
<DialogWrapper width="40rem" scrollable max-height="85vh">
<VCard>
<VCardItem>
<template #prepend>
@@ -43,5 +43,5 @@ function handleImport() {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -15,12 +15,12 @@ defineProps({
const emit = defineEmits(['close'])
</script>
<template>
<VDialog max-width="50rem">
<DialogWrapper max-width="50rem">
<VCard>
<VDialogCloseBtn @click="emit('close')" />
<VCardItem>
<MediaInfoCard :context="context" />
</VCardItem>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -148,7 +148,7 @@ onBeforeMount(async () => {
})
</script>
<template>
<VDialog scrollable max-width="60rem" :fullscreen="!display.mdAndUp.value">
<DialogWrapper scrollable max-width="60rem" :fullscreen="!display.mdAndUp.value">
<!-- Vuetify 渲染模式 -->
<VCard v-if="renderMode === 'vuetify'" :title="`${props.plugin?.plugin_name} - ${t('dialog.pluginConfig.title')}`">
<VDialogCloseBtn @click="emit('close')" />
@@ -187,5 +187,5 @@ onBeforeMount(async () => {
<!-- 进度框 -->
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" />
</VDialog>
</DialogWrapper>
</template>

View File

@@ -124,7 +124,7 @@ onMounted(() => {
})
</script>
<template>
<VDialog scrollable max-width="80rem" :fullscreen="!display.mdAndUp.value">
<DialogWrapper scrollable max-width="80rem" :fullscreen="!display.mdAndUp.value">
<!-- Vuetify 渲染模式 -->
<VCard v-if="renderMode === 'vuetify'" :title="`${props.plugin?.plugin_name}`">
<VDialogCloseBtn @click="emit('close')" />
@@ -160,5 +160,5 @@ onMounted(() => {
/>
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -63,7 +63,7 @@ onMounted(() => {
</script>
<template>
<VDialog width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
<DialogWrapper width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
<VCard>
<VCardItem>
<VCardTitle>
@@ -89,5 +89,5 @@ onMounted(() => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -10,12 +10,12 @@ const props = defineProps({
</script>
<template>
<!-- Progress Dialog -->
<VDialog :scrim="false" width="25rem">
<DialogWrapper :scrim="false" width="25rem">
<VCard elevation="3" color="primary">
<VCardText class="text-center">
{{ props.text || t('dialog.progress.processing') }}
<VProgressLinear color="white" class="mb-0 mt-1" :model-value="props.value" indeterminate />
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -57,7 +57,7 @@ async function handleReset() {
</script>
<template>
<VDialog width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
<DialogWrapper width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
<VCard>
<VDialogCloseBtn @click="emit('close')" />
<VCardItem>
@@ -99,5 +99,5 @@ async function handleReset() {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -269,7 +269,7 @@ onUnmounted(() => {
</script>
<template>
<VDialog scrollable max-width="45rem" :fullscreen="!display.mdAndUp.value">
<DialogWrapper scrollable max-width="45rem" :fullscreen="!display.mdAndUp.value">
<VCard>
<VCardItem class="py-2">
<template #prepend> <VIcon icon="mdi-folder-move" class="me-2" /> </template>
@@ -487,7 +487,7 @@ onUnmounted(() => {
<!-- 手动整理进度框 -->
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" :value="progressValue" />
<!-- TMDB ID搜索框 -->
<VDialog v-model="mediaSelectorDialog" width="40rem" scrollable max-height="85vh">
<DialogWrapper v-model="mediaSelectorDialog" width="40rem" scrollable max-height="85vh">
<MediaIdSelector
v-if="mediaSource === 'themoviedb'"
v-model="transferForm.tmdbid"
@@ -500,6 +500,6 @@ onUnmounted(() => {
@close="mediaSelectorDialog = false"
:type="mediaSource"
/>
</VDialog>
</VDialog>
</DialogWrapper>
</DialogWrapper>
</template>

View File

@@ -357,7 +357,7 @@ onMounted(() => {
})
</script>
<template>
<VDialog v-model="dialog" max-width="42rem" scrollable :fullscreen="!display.mdAndUp.value">
<DialogWrapper v-model="dialog" max-width="42rem" scrollable :fullscreen="!display.mdAndUp.value">
<VCard class="search-dialog">
<!-- 搜索输入框 -->
<VCardItem class="pa-4 pa-sm-5 search-box-container">
@@ -741,7 +741,7 @@ onMounted(() => {
</div>
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 站点选择对话框 -->
<SearchSiteDialog

View File

@@ -56,7 +56,7 @@ const filteredSites = computed(() => {
</script>
<template>
<!-- Site Selection Dialog -->
<VDialog max-width="40rem" fullscreen-mobile>
<DialogWrapper max-width="40rem" fullscreen-mobile>
<VCard class="site-dialog">
<VCardItem>
<template #prepend>
@@ -169,7 +169,7 @@ const filteredSites = computed(() => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>
<style scoped>
.site-checkbox-wrapper {

View File

@@ -147,7 +147,7 @@ onMounted(async () => {
</script>
<template>
<VDialog scrollable :close-on-back="false" eager max-width="45rem" :fullscreen="!display.mdAndUp.value">
<DialogWrapper scrollable :close-on-back="false" eager max-width="45rem" :fullscreen="!display.mdAndUp.value">
<VCard>
<VCardItem :class="props.oper === 'add' ? 'py-3' : 'py-2'">
<template #prepend>
@@ -350,5 +350,5 @@ onMounted(async () => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -71,7 +71,7 @@ async function updateSiteCookie() {
}
</script>
<template>
<VDialog max-width="30rem" scrollable>
<DialogWrapper max-width="30rem" scrollable>
<!-- Dialog Content -->
<VCard :title="t('dialog.siteCookieUpdate.title')">
<VDialogCloseBtn @click="emit('close')" />
@@ -114,5 +114,5 @@ async function updateSiteCookie() {
</VCard>
<!-- 进度框 -->
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" />
</VDialog>
</DialogWrapper>
</template>

View File

@@ -130,7 +130,7 @@ onMounted(() => {
})
</script>
<template>
<VDialog scrollable fullscreen :scrim="false" transition="dialog-bottom-transition">
<DialogWrapper scrollable fullscreen :scrim="false" transition="dialog-bottom-transition">
<VCard>
<!-- Toolbar -->
<div>
@@ -281,7 +281,7 @@ onMounted(() => {
@error="addDownloadError"
@close="addDownloadDialog = false"
/>
</VDialog>
</DialogWrapper>
</template>
<style lang="scss" scoped>

View File

@@ -287,7 +287,7 @@ onBeforeMount(() => {
</script>
<template>
<VDialog scrollable eager max-width="80rem" :fullscreen="!display.mdAndUp.value">
<DialogWrapper scrollable eager max-width="80rem" :fullscreen="!display.mdAndUp.value">
<VCard>
<VCardItem>
<VCardTitle
@@ -484,5 +484,5 @@ onBeforeMount(() => {
</VCard>
<!-- 进度框 -->
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="t('dialog.siteUserData.refreshing')" />
</VDialog>
</DialogWrapper>
</template>

View File

@@ -50,7 +50,7 @@ async function saveSmbConfig() {
</script>
<template>
<VDialog width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
<DialogWrapper width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
<VCard>
<VDialogCloseBtn @click="emit('close')" />
<VCardItem>
@@ -127,5 +127,5 @@ async function saveSmbConfig() {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -280,7 +280,7 @@ onMounted(() => {
</script>
<template>
<VDialog scrollable max-width="45rem" :fullscreen="!display.mdAndUp.value">
<DialogWrapper scrollable max-width="45rem" :fullscreen="!display.mdAndUp.value">
<VCard>
<VCardItem class="py-2">
<VDialogCloseBtn @click="emit('close')" />
@@ -539,5 +539,5 @@ onMounted(() => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -85,7 +85,7 @@ onBeforeMount(() => {
})
</script>
<template>
<VDialog scrollable max-width="80rem" :fullscreen="!display.mdAndUp.value">
<DialogWrapper scrollable max-width="80rem" :fullscreen="!display.mdAndUp.value">
<VCard>
<VCardItem class="my-2">
<VDialogCloseBtn @click="emit('close')" />
@@ -206,7 +206,7 @@ onBeforeMount(() => {
</div>
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
</template>
<style lang="scss" scoped>

View File

@@ -146,7 +146,7 @@ function getMediaTypeText(type: string | undefined) {
</script>
<template>
<VDialog scrollable max-width="50rem" :fullscreen="!display.mdAndUp.value">
<DialogWrapper scrollable max-width="50rem" :fullscreen="!display.mdAndUp.value">
<VCard class="mx-auto" width="100%">
<VCardItem>
<VCardTitle>{{ t('dialog.subscribeHistory.title', { type: getMediaTypeText(props.type) }) }}</VCardTitle>
@@ -220,5 +220,5 @@ function getMediaTypeText(type: string | undefined) {
</VCard>
<!-- 进度框 -->
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" />
</VDialog>
</DialogWrapper>
</template>

View File

@@ -55,7 +55,7 @@ const $toast = useToast()
</script>
<template>
<VDialog scrollable max-width="30rem" :fullscreen="!display.mdAndUp.value">
<DialogWrapper scrollable max-width="30rem" :fullscreen="!display.mdAndUp.value">
<VCard>
<VCardItem class="py-2">
<template #prepend>
@@ -112,5 +112,5 @@ const $toast = useToast()
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -154,7 +154,7 @@ onUnmounted(() => {
</script>
<template>
<VDialog scrollable max-width="50rem" :fullscreen="!display.mdAndUp.value">
<DialogWrapper scrollable max-width="50rem" :fullscreen="!display.mdAndUp.value">
<VCard class="mx-auto" width="100%">
<VCardItem>
<VCardTitle>{{ t('dialog.transferQueue.title') }}</VCardTitle>
@@ -202,5 +202,5 @@ onUnmounted(() => {
</VWindow>
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -115,7 +115,7 @@ onUnmounted(() => {
</script>
<template>
<VDialog width="40rem" scrollable :fullscreen="!display.mdAndUp.value">
<DialogWrapper width="40rem" scrollable :fullscreen="!display.mdAndUp.value">
<VCard>
<VDialogCloseBtn @click="emit('close')" />
<VCardItem>
@@ -147,5 +147,5 @@ onUnmounted(() => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -366,7 +366,7 @@ onMounted(() => {
</script>
<template>
<VDialog scrollable max-width="40rem" :fullscreen="!display.mdAndUp.value">
<DialogWrapper scrollable max-width="40rem" :fullscreen="!display.mdAndUp.value">
<VCard>
<VCardItem :class="props.oper === 'add' ? 'py-3' : 'py-2'">
<template #prepend>
@@ -619,5 +619,5 @@ onMounted(() => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -134,7 +134,7 @@ onMounted(async () => {
</script>
<template>
<VDialog width="40rem" scrollable>
<DialogWrapper width="40rem" scrollable>
<VCard>
<VCardItem>
<VCardTitle>
@@ -179,5 +179,5 @@ onMounted(async () => {
</VBtn>
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -197,7 +197,7 @@ const isMacOS = computed(() => {
</script>
<template>
<VDialog scrollable fullscreen :scrim="false" transition="dialog-bottom-transition">
<DialogWrapper scrollable fullscreen :scrim="false" transition="dialog-bottom-transition">
<VCard class="workflow-dialog">
<!-- Toolbar -->
<VToolbar color="primary" density="comfortable">
@@ -256,7 +256,7 @@ const isMacOS = computed(() => {
@close="importCodeDialog = false"
@save="saveCodeString"
/>
</VDialog>
</DialogWrapper>
</template>
<style lang="scss">

View File

@@ -85,7 +85,7 @@ async function editWorkflow() {
</script>
<template>
<VDialog scrollable :close-on-back="false" eager max-width="30rem" :fullscreen="!display.mdAndUp.value">
<DialogWrapper scrollable :close-on-back="false" eager max-width="30rem" :fullscreen="!display.mdAndUp.value">
<VCard>
<VCardItem>
<template #prepend>
@@ -140,5 +140,5 @@ async function editWorkflow() {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -68,7 +68,7 @@ const $toast = useToast()
</script>
<template>
<VDialog scrollable max-width="30rem" :fullscreen="!display.mdAndUp.value">
<DialogWrapper scrollable max-width="30rem" :fullscreen="!display.mdAndUp.value">
<VCard>
<VCardItem class="py-2">
<template #prepend>
@@ -132,5 +132,5 @@ const $toast = useToast()
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -712,7 +712,7 @@ onMounted(() => {
</VCardText>
</VCard>
<!-- 重命名弹窗 -->
<VDialog v-if="renamePopper" v-model="renamePopper" max-width="35rem">
<DialogWrapper v-if="renamePopper" v-model="renamePopper" max-width="35rem">
<VCard>
<VCardItem>
<template #prepend>
@@ -746,7 +746,7 @@ onMounted(() => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 文件整理弹窗 -->
<ReorganizeDialog
v-if="transferPopper"

View File

@@ -166,7 +166,7 @@ const sortIcon = computed(() => {
<VIcon icon="mdi-arrow-up-bold-outline" />
</IconBtn>
<!-- 新建文件夹 -->
<VDialog v-model="newFolderPopper" max-width="35rem">
<DialogWrapper v-model="newFolderPopper" max-width="35rem">
<template #activator="{ props }">
<IconBtn>
<VIcon v-bind="props" icon="mdi-folder-plus-outline" />
@@ -191,6 +191,6 @@ const sortIcon = computed(() => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</VToolbar>
</template>

View File

@@ -0,0 +1,242 @@
import { ref, watch, onBeforeUnmount, readonly } from 'vue'
// 弹窗滚动锁定配置
export interface DialogScrollLockOptions {
// 是否在组件卸载时自动恢复滚动
autoRestore?: boolean
// 是否保存和恢复滚动位置
preserveScrollPosition?: boolean
// 是否阻止触摸事件穿透
preventTouchScroll?: boolean
// 自定义锁定时的样式
lockStyles?: {
overflow?: string
position?: string
width?: string
}
}
// 默认配置
const DEFAULT_OPTIONS: Required<DialogScrollLockOptions> = {
autoRestore: true,
preserveScrollPosition: true,
preventTouchScroll: true,
lockStyles: {
overflow: 'hidden',
position: 'fixed',
width: '100%',
},
}
export function useDialogScrollLock(options: DialogScrollLockOptions = {}) {
const config = { ...DEFAULT_OPTIONS, ...options }
// 状态管理
const isLocked = ref(false)
const savedScrollPosition = ref(0)
const originalBodyStyles = ref<{ [key: string]: string }>({})
const originalDocumentStyles = ref<{ [key: string]: string }>({})
const originalHtmlStyles = ref<{ [key: string]: string }>({})
// 保存当前滚动位置
const saveScrollPosition = () => {
if (config.preserveScrollPosition) {
savedScrollPosition.value =
window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
}
}
// 保存原始样式
const saveOriginalStyles = () => {
// 保存 body 样式
originalBodyStyles.value = {
overflow: document.body.style.overflow,
position: document.body.style.position,
top: document.body.style.top,
width: document.body.style.width,
}
// 保存 documentElement 样式
originalDocumentStyles.value = {
overflow: document.documentElement.style.overflow,
}
// 保存 html 样式
originalHtmlStyles.value = {
overflow: document.documentElement.style.overflow,
}
}
// 阻止触摸滚动事件
const preventTouchScroll = (event: TouchEvent) => {
if (isLocked.value && config.preventTouchScroll) {
event.preventDefault()
}
}
// 锁定滚动
const lockScroll = () => {
if (isLocked.value) return
// 保存当前状态
saveScrollPosition()
saveOriginalStyles()
// 应用锁定样式到 body
document.body.style.overflow = config.lockStyles.overflow || 'hidden'
document.body.style.position = config.lockStyles.position || 'fixed'
document.body.style.width = config.lockStyles.width || '100%'
// 应用锁定样式到 documentElement
document.documentElement.style.overflow = config.lockStyles.overflow || 'hidden'
// 添加 CSS 类名
document.documentElement.classList.add('v-overlay-scroll-blocked')
// 如果需要保持滚动位置设置top偏移
if (config.preserveScrollPosition) {
document.body.style.top = `-${savedScrollPosition.value}px`
}
// 保持navbar的滚动状态 - 添加一个CSS变量来记录滚动位置
if (savedScrollPosition.value > 0) {
document.documentElement.style.setProperty('--saved-scroll-y', `${savedScrollPosition.value}px`)
document.documentElement.classList.add('dialog-scroll-locked')
}
// 添加触摸事件监听器
if (config.preventTouchScroll) {
document.addEventListener('touchmove', preventTouchScroll, { passive: false })
}
isLocked.value = true
}
// 恢复滚动
const restoreScroll = () => {
if (!isLocked.value) return
// 恢复原始样式
document.body.style.overflow = originalBodyStyles.value.overflow || ''
document.body.style.position = originalBodyStyles.value.position || ''
document.body.style.top = originalBodyStyles.value.top || ''
document.body.style.width = originalBodyStyles.value.width || ''
document.documentElement.style.overflow = originalDocumentStyles.value.overflow || ''
// 移除 CSS 类名
document.documentElement.classList.remove('v-overlay-scroll-blocked')
document.documentElement.classList.remove('dialog-scroll-locked')
// 移除CSS变量
document.documentElement.style.removeProperty('--saved-scroll-y')
// 移除触摸事件监听器
if (config.preventTouchScroll) {
document.removeEventListener('touchmove', preventTouchScroll)
}
// 恢复滚动位置
if (config.preserveScrollPosition) {
window.scrollTo(0, savedScrollPosition.value)
}
isLocked.value = false
}
// 切换滚动锁定状态
const toggleScrollLock = (lock?: boolean) => {
const shouldLock = lock !== undefined ? lock : !isLocked.value
if (shouldLock) {
lockScroll()
} else {
restoreScroll()
}
}
// 监听响应式值的变化
const watchTarget = (target: any) => {
return watch(
target,
newValue => {
toggleScrollLock(!!newValue)
},
{ immediate: false },
)
}
// 生命周期清理
onBeforeUnmount(() => {
if (config.autoRestore && isLocked.value) {
restoreScroll()
}
})
return {
// 状态
isLocked: readonly(isLocked),
savedScrollPosition: readonly(savedScrollPosition),
// 方法
lockScroll,
restoreScroll,
toggleScrollLock,
watchTarget,
// 工具方法
saveScrollPosition,
}
}
// 便捷的自动监听版本
export function useDialogScrollLockWithWatch(target: any, options: DialogScrollLockOptions = {}) {
const scrollLock = useDialogScrollLock(options)
// 自动监听目标值的变化
const stopWatcher = scrollLock.watchTarget(target)
// 返回所有功能 + 停止监听的方法
return {
...scrollLock,
stopWatcher,
}
}
// 全局弹窗检测和管理
export function useGlobalDialogScrollLock() {
const activeDialogs = ref<Set<string>>(new Set())
const registerDialog = (dialogId: string) => {
activeDialogs.value.add(dialogId)
if (activeDialogs.value.size === 1) {
// 第一个弹窗时锁定滚动
lockGlobalScroll()
}
}
const unregisterDialog = (dialogId: string) => {
activeDialogs.value.delete(dialogId)
if (activeDialogs.value.size === 0) {
// 没有弹窗时恢复滚动
unlockGlobalScroll()
}
}
const lockGlobalScroll = () => {
document.body.style.overflow = 'hidden'
document.documentElement.classList.add('v-overlay-scroll-blocked')
}
const unlockGlobalScroll = () => {
document.body.style.overflow = ''
document.documentElement.classList.remove('v-overlay-scroll-blocked')
}
return {
activeDialogs: readonly(activeDialogs),
registerDialog,
unregisterDialog,
lockGlobalScroll,
unlockGlobalScroll,
}
}

View File

@@ -243,7 +243,7 @@ onMounted(() => {
</VCard>
</VMenu>
<!-- 名称测试弹窗 -->
<VDialog
<DialogWrapper
v-if="nameTestDialog"
v-model="nameTestDialog"
max-width="45rem"
@@ -263,9 +263,9 @@ onMounted(() => {
<NameTestView />
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 网络测试弹窗 -->
<VDialog
<DialogWrapper
v-if="netTestDialog"
v-model="netTestDialog"
max-width="35rem"
@@ -285,9 +285,9 @@ onMounted(() => {
<NetTestView />
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 实时日志弹窗 -->
<VDialog
<DialogWrapper
v-if="loggingDialog"
v-model="loggingDialog"
scrollable
@@ -313,9 +313,9 @@ onMounted(() => {
<LoggingView logfile="moviepilot.log" />
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 过滤规则弹窗 -->
<VDialog
<DialogWrapper
v-if="ruleTestDialog"
v-model="ruleTestDialog"
max-width="35rem"
@@ -335,9 +335,9 @@ onMounted(() => {
<RuleTestView />
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 系统健康检查弹窗 -->
<VDialog
<DialogWrapper
v-if="systemTestDialog"
v-model="systemTestDialog"
max-width="35rem"
@@ -357,9 +357,9 @@ onMounted(() => {
<ModuleTestView />
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 消息中心弹窗 -->
<VDialog
<DialogWrapper
v-if="messageDialog"
v-model="messageDialog"
max-width="50rem"
@@ -402,5 +402,5 @@ onMounted(() => {
</div>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -518,7 +518,7 @@ onUnmounted(() => {
<!-- 用户认证对话框 -->
<UserAuthDialog v-if="siteAuthDialog" v-model="siteAuthDialog" @done="siteAuthDone" @close="siteAuthDialog = false" />
<!-- 自定义 CSS -->
<VDialog v-if="cssDialog" v-model="cssDialog" max-width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
<DialogWrapper v-if="cssDialog" v-model="cssDialog" max-width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
<VCard>
<VCardItem>
<VCardTitle>
@@ -539,7 +539,7 @@ onUnmounted(() => {
</VBtn>
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
</template>
<style lang="scss" scoped>

View File

@@ -370,7 +370,7 @@ onDeactivated(() => {
</Teleport>
<!-- 弹窗根据配置生成选项 -->
<VDialog v-if="dialog" v-model="dialog" max-width="35rem" :fullscreen="!display.mdAndUp.value" scrollable>
<DialogWrapper v-if="dialog" v-model="dialog" max-width="35rem" :fullscreen="!display.mdAndUp.value" scrollable>
<VCard>
<VCardItem>
<VCardTitle>
@@ -423,7 +423,7 @@ onDeactivated(() => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>
<style lang="scss" scoped>
.settings-card-header {

View File

@@ -199,7 +199,7 @@ onActivated(async () => {
</VWindowItem>
</VWindow>
<!-- 弹窗根据配置生成选项 -->
<VDialog
<DialogWrapper
v-if="orderConfigDialog"
v-model="orderConfigDialog"
max-width="35rem"
@@ -243,7 +243,7 @@ onActivated(async () => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 快速滚动到顶部按钮 -->
<Teleport to="body" v-if="route.path === '/discover'">
<VScrollToTopBtn />

View File

@@ -248,7 +248,7 @@ onActivated(async () => {
</div>
<!-- 设置面板 -->
<VDialog v-model="dialog" width="35rem" class="settings-dialog" scrollable :fullscreen="!display.mdAndUp.value">
<DialogWrapper v-model="dialog" width="35rem" class="settings-dialog" scrollable :fullscreen="!display.mdAndUp.value">
<VCard class="settings-card">
<VCardItem class="settings-card-header">
<VCardTitle>
@@ -300,7 +300,7 @@ onActivated(async () => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 快速滚动到顶部按钮 -->
<Teleport to="body" v-if="route.path === '/recommend'">

View File

@@ -1552,7 +1552,7 @@ function onDragStartPlugin(evt: any) {
/>
<!-- 插件搜索窗口 -->
<VDialog
<DialogWrapper
v-if="SearchDialog"
v-model="SearchDialog"
scrollable
@@ -1611,20 +1611,20 @@ function onDragStartPlugin(evt: any) {
</VVirtualScroll>
</VList>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 安装插件进度框 -->
<VDialog v-if="progressDialog" v-model="progressDialog" :scrim="false" width="25rem">
<DialogWrapper v-if="progressDialog" v-model="progressDialog" :scrim="false" width="25rem">
<VCard color="primary">
<VCardText class="text-center">
{{ progressText }}
<VProgressLinear indeterminate color="white" class="mb-0 mt-1" />
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 新建文件夹对话框 -->
<VDialog v-if="newFolderDialog" v-model="newFolderDialog" max-width="400">
<DialogWrapper v-if="newFolderDialog" v-model="newFolderDialog" max-width="400">
<VCard>
<VDialogCloseBtn @click="newFolderDialog = false" />
<VCardItem>
@@ -1646,5 +1646,5 @@ function onDragStartPlugin(evt: any) {
}}</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -261,7 +261,7 @@ onMounted(() => {
</div>
</div>
</div>
<VDialog v-if="releaseDialog" v-model="releaseDialog" width="600" scrollable>
<DialogWrapper v-if="releaseDialog" v-model="releaseDialog" width="600" scrollable>
<VCard>
<VCardItem>
<VDialogCloseBtn @click="releaseDialog = false" />
@@ -269,7 +269,7 @@ onMounted(() => {
</VCardItem>
<VCardText v-html="releaseDialogBody" />
</VCard>
</VDialog>
</DialogWrapper>
</template>
<style type="scss" scoped>

View File

@@ -423,7 +423,7 @@ onMounted(() => {
</VCard>
<!-- 重新识别对话框 -->
<VDialog v-model="reidentifyDialog" scrollable max-width="35rem">
<DialogWrapper v-model="reidentifyDialog" scrollable max-width="35rem">
<VCard>
<VCardItem class="py-2">
<template #prepend>
@@ -469,5 +469,5 @@ onMounted(() => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -444,7 +444,7 @@ onMounted(() => {
:indeterminate="true"
/>
<!-- 模板编辑器对话框 -->
<VDialog v-model="editorVisible" v-if="editorVisible" max-width="50rem" :fullscreen="!display.mdAndUp.value">
<DialogWrapper v-model="editorVisible" v-if="editorVisible" max-width="50rem" :fullscreen="!display.mdAndUp.value">
<VCard>
<VCardItem class="py-2">
<template #prepend>
@@ -472,7 +472,7 @@ onMounted(() => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>
<style scoped>
/* Monaco编辑器容器样式 */

View File

@@ -732,7 +732,7 @@ onDeactivated(() => {
</VRow>
<!-- 高级系统设置 -->
<VDialog
<DialogWrapper
v-if="advancedDialog"
v-model="advancedDialog"
scrollable
@@ -1328,5 +1328,5 @@ onDeactivated(() => {
</VForm>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
</template>

View File

@@ -617,7 +617,7 @@ const handleSortIconClick = () => {
</VCard>
<!-- 全部筛选弹窗 -->
<VDialog
<DialogWrapper
v-model="allFilterMenuOpen"
max-width="50rem"
location="center"
@@ -690,10 +690,10 @@ const handleSortIconClick = () => {
</div>
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 筛选弹窗 -->
<VDialog v-model="filterMenuOpen" max-width="25rem" location="center" max-height="85vh">
<DialogWrapper v-model="filterMenuOpen" max-width="25rem" location="center" max-height="85vh">
<VCard>
<VCardTitle class="py-3 d-flex align-center">
<VIcon :icon="getFilterIcon(currentFilter)" class="me-2"></VIcon>
@@ -735,7 +735,7 @@ const handleSortIconClick = () => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 资源列表 -->
<VInfiniteScroll mode="intersect" side="end" :items="displayDataList" class="overflow-visible" @load="loadMore">

View File

@@ -597,7 +597,7 @@ onMounted(() => {
</VCard>
<!-- 全部筛选弹窗 -->
<VDialog
<DialogWrapper
v-model="allFilterMenuOpen"
max-width="50rem"
location="center"
@@ -670,10 +670,10 @@ onMounted(() => {
</div>
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 筛选弹窗 -->
<VDialog v-model="filterMenuOpen" max-width="25rem" max-height="85vh" location="center">
<DialogWrapper v-model="filterMenuOpen" max-width="25rem" max-height="85vh" location="center">
<VCard>
<VCardTitle class="py-3 d-flex align-center">
<VIcon :icon="getFilterIcon(currentFilter)" class="me-2"></VIcon>
@@ -715,7 +715,7 @@ onMounted(() => {
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</DialogWrapper>
<!-- 资源列表容器 -->
<VCard class="resource-list-container">

View File

@@ -454,7 +454,7 @@ watch(
</VRow>
<!-- 双重验证弹窗 -->
<VDialog v-if="otpDialog" v-model="otpDialog" max-width="45rem" scrollable>
<DialogWrapper v-if="otpDialog" v-model="otpDialog" max-width="45rem" scrollable>
<!-- 开启双重验证弹窗内容 -->
<VCard>
<VDialogCloseBtn @click="otpDialog = false" />
@@ -492,6 +492,6 @@ watch(
</VForm>
</VCardText>
</VCard>
</VDialog>
</DialogWrapper>
</div>
</template>