优化动态标签页注册逻辑

This commit is contained in:
jxxghp
2025-07-05 12:13:08 +08:00
parent da0756adf0
commit 873bf905ab
11 changed files with 358 additions and 256 deletions

View File

@@ -9,7 +9,7 @@
// 磨砂渐变效果
backdrop-filter: blur(20px);
block-size: calc(env(safe-area-inset-top, 0px) + 5rem);
block-size: calc(env(safe-area-inset-top, 0px) + var(--navbar-height) + 5rem);
content: "";
inset-block-start: 0;
inset-inline: 0;
@@ -18,10 +18,10 @@
mask: linear-gradient(
to bottom,
rgba(0, 0, 0, 100%) 0%,
rgba(0, 0, 0, 90%) calc(env(safe-area-inset-top, 0px) + 1rem),
rgba(0, 0, 0, 70%) calc(env(safe-area-inset-top, 0px) + 2rem),
rgba(0, 0, 0, 50%) calc(env(safe-area-inset-top, 0px) + 3rem),
rgba(0, 0, 0, 20%) calc(env(safe-area-inset-top, 0px) + 4rem),
rgba(0, 0, 0, 90%) calc(env(safe-area-inset-top, 0px) + var(--navbar-height) + 1rem),
rgba(0, 0, 0, 70%) calc(env(safe-area-inset-top, 0px) + var(--navbar-height) + 2rem),
rgba(0, 0, 0, 50%) calc(env(safe-area-inset-top, 0px) + var(--navbar-height) + 3rem),
rgba(0, 0, 0, 20%) calc(env(safe-area-inset-top, 0px) + var(--navbar-height) + 4rem),
rgba(0, 0, 0, 0%) 100%
);
pointer-events: none;

View File

@@ -38,15 +38,23 @@ export default defineComponent({
)
// 👉 Navbar
const navbar = h('header', { class: ['layout-navbar navbar-blur'] }, [
h(
'div',
{ class: 'navbar-content-container' },
slots.navbar?.({
toggleVerticalOverlayNavActive: toggleIsOverlayNavActive,
}),
),
])
const navbar = h(
'header',
{ class: ['layout-navbar navbar-blur'] },
[
h(
'div',
{ class: 'navbar-content-container' },
slots.navbar?.({
toggleVerticalOverlayNavActive: toggleIsOverlayNavActive,
}),
),
// 👉 Dynamic Header Tab in NavBar
slots['dynamic-header-tab']?.()
? h('div', { class: 'layout-dynamic-header-tab' }, slots['dynamic-header-tab']?.())
: null,
].filter(Boolean),
)
const main = h(
'main',

View File

@@ -243,14 +243,14 @@ function stopDrag() {
// 外层DIV大小控制
const scrollStyle = computed(() => {
return appMode
return appMode.value
? 'height: calc(100vh - 10.5rem - env(safe-area-inset-bottom) - 7rem)'
: 'height: calc(100vh - 10.5rem - env(safe-area-inset-bottom)'
})
// 文件列表大小限制
const fileListStyle = computed(() => {
return appMode
return appMode.value
? 'height: calc(100vh - 14rem - env(safe-area-inset-bottom) - 7rem)'
: 'height: calc(100vh - 14rem - env(safe-area-inset-bottom)'
})

View File

@@ -62,23 +62,50 @@ export function useDynamicHeaderTab() {
}
})
// 在组件卸载时取消注册
onUnmounted(() => {
// 注册函数
const doRegister = () => {
// 确保路由路径是最新的
tabConfig.routePath = route.path
if (registerDynamicHeaderTab) {
registerDynamicHeaderTab(tabConfig)
} else if (typeof window !== 'undefined') {
// 使用全局方法作为备用
const globalRegister = (window as any).__VUE_INJECT_DYNAMIC_HEADER_TAB__
if (globalRegister) {
globalRegister(tabConfig)
}
}
}
// 取消注册函数
const doUnregister = () => {
if (unregisterDynamicHeaderTab) {
unregisterDynamicHeaderTab()
}
}
// 初始注册延迟到下个tick确保路由已经完全切换
nextTick(() => {
doRegister()
})
// 初始注册
if (registerDynamicHeaderTab) {
registerDynamicHeaderTab(tabConfig)
} else if (typeof window !== 'undefined') {
// 使用全局方法作为备用
const globalRegister = (window as any).__VUE_INJECT_DYNAMIC_HEADER_TAB__
if (globalRegister) {
globalRegister(tabConfig)
}
}
// 处理页面激活时重新注册支持keep-alive缓存的页面
onActivated(() => {
nextTick(() => {
doRegister()
})
})
// 处理页面失活时取消注册支持keep-alive缓存的页面
onDeactivated(() => {
doUnregister()
})
// 在组件卸载时取消注册
onUnmounted(() => {
doUnregister()
})
}
// 取消注册

View File

@@ -91,7 +91,8 @@ const dynamicHeaderTab = ref<DynamicHeaderTab | null>(null)
const registerDynamicHeaderTab = (tab: DynamicHeaderTab) => {
// 保存注册标签页的路由路径
tab.routePath = route.path
dynamicHeaderTab.value = tab
// 强制更新,确保响应式系统能检测到变化
dynamicHeaderTab.value = { ...tab }
}
// 提供一个方法让其他组件取消注册动态标签页
@@ -123,11 +124,17 @@ provide('unregisterDynamicHeaderTab', unregisterDynamicHeaderTab)
// 监听路由变化来清除动态标签页
watch(
() => route.path,
newPath => {
// 当路由变化时,清除动态标签页(如果不是同一个路由注册的)
if (dynamicHeaderTab.value && dynamicHeaderTab.value.routePath !== newPath) {
dynamicHeaderTab.value = null
}
(newPath, oldPath) => {
// 使用nextTick确保新页面的组件已经挂载完成
nextTick(() => {
// 延迟一小段时间,让新页面有机会注册标签页
setTimeout(() => {
// 如果当前标签页不属于新路由,则清除
if (dynamicHeaderTab.value && dynamicHeaderTab.value.routePath !== route.path) {
dynamicHeaderTab.value = null
}
}, 50) // 减少延迟时间,但仍然给新页面注册机会
})
},
{ immediate: false },
)
@@ -138,7 +145,7 @@ const showDynamicHeaderTab = computed(() => {
dynamicHeaderTab.value &&
dynamicHeaderTab.value.items.length > 0 &&
// 确保只在注册的路由路径下显示标签页
(!dynamicHeaderTab.value.routePath || dynamicHeaderTab.value.routePath === route.path)
dynamicHeaderTab.value.routePath === route.path
)
})
@@ -286,7 +293,7 @@ onMounted(() => {
/>
</div>
</div>
<VerticalNavLayout>
<VerticalNavLayout :style="{ '--navbar-height': showDynamicHeaderTab ? '2.5rem' : '0px' }">
<!-- 👉 Navbar -->
<template #navbar="{ toggleVerticalOverlayNavActive }">
<div class="d-flex h-100 align-center mx-1">
@@ -349,16 +356,9 @@ onMounted(() => {
<template #after-vertical-nav-items />
<!-- 👉 下拉跟随动画 -->
<div
class="main-content-wrapper"
:style="{
transform: contentTransform,
transition: contentTransition,
}"
>
<!-- 👉 Dynamic Header Tab -->
<div v-if="showDynamicHeaderTab" class="dynamic-header-tab-container">
<!-- 👉 Dynamic Header Tab -->
<template #dynamic-header-tab>
<div v-if="showDynamicHeaderTab">
<HeaderTab
:items="dynamicHeaderTab!.items"
:model-value="dynamicHeaderTab!.modelValue"
@@ -380,7 +380,17 @@ onMounted(() => {
</template>
</HeaderTab>
</div>
</template>
<!-- 👉 下拉跟随动画 -->
<div
class="main-content-wrapper"
:style="{
transform: contentTransform,
transition: contentTransition,
paddingTop: showDynamicHeaderTab ? '3rem' : '0px',
}"
>
<slot />
</div>

View File

@@ -122,8 +122,6 @@ onUnmounted(() => {
display: flex;
align-items: center;
justify-content: space-between;
margin-block-end: 16px;
padding-block: 8px;
padding-inline: 16px;
}

View File

@@ -4,12 +4,13 @@ import { DownloaderConf } from '@/api/types'
import DownloadingListView from '@/views/reorganize/DownloadingListView.vue'
import NoDataFound from '@/components/NoDataFound.vue'
import { useI18n } from 'vue-i18n'
import { useDynamicHeaderTab } from '@/composables/useDynamicHeaderTab'
// 国际化
const { t } = useI18n()
const route = useRoute()
const activeTab = ref(route.query.tab)
const activeTab = ref<string>((route.query.tab as string) || '')
// 下载器
const downloaders = ref<DownloaderConf[]>([])
@@ -22,6 +23,9 @@ const downloaderItems = computed(() => {
}))
})
// 使用动态标签页
const { registerHeaderTab } = useDynamicHeaderTab()
// 调用API查询下载器设置
async function loadDownloaderSetting() {
try {
@@ -33,19 +37,30 @@ async function loadDownloaderSetting() {
}
}
// 注册动态标签页
const registerTabs = () => {
if (downloaderItems.value.length > 0) {
registerHeaderTab({
items: downloaderItems.value,
modelValue: activeTab,
})
}
}
onMounted(async () => {
await loadDownloaderSetting()
registerTabs()
})
onActivated(async () => {
loadDownloaderSetting()
await loadDownloaderSetting()
registerTabs()
})
</script>
<template>
<div v-if="downloaders.length > 0">
<VHeaderTab :items="downloaderItems" v-model="activeTab" />
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
<VWindow v-model="activeTab" class="disable-tab-transition" :touch="false">
<VWindowItem v-for="item in downloaders" :value="item.name">
<transition name="fade-slide" appear>
<div>

View File

@@ -4,6 +4,7 @@ import { RecommendSource } from '@/api/types'
import MediaCardSlideView from '@/views/discover/MediaCardSlideView.vue'
import { useI18n } from 'vue-i18n'
import { useDisplay } from 'vuetify'
import { useDynamicHeaderTab } from '@/composables/useDynamicHeaderTab'
const display = useDisplay()
@@ -165,7 +166,7 @@ async function saveConfig() {
}
// 标签图标映射
const categoryItems: Record<string, string>[] = [
const categoryItems = computed(() => [
{
title: t('recommend.all'),
icon: 'mdi-filmstrip-box-multiple',
@@ -191,7 +192,10 @@ const categoryItems: Record<string, string>[] = [
icon: 'mdi-trophy',
tab: t('recommend.categoryRankings'),
},
]
])
// 使用动态标签页
const { registerHeaderTab } = useDynamicHeaderTab()
onBeforeMount(async () => {
await loadConfig()
@@ -199,6 +203,23 @@ onBeforeMount(async () => {
onMounted(async () => {
await loadExtraRecommendSources()
// 注册动态标签页
registerHeaderTab({
items: categoryItems.value,
modelValue: currentCategory,
appendButtons: [
{
icon: 'mdi-tune',
variant: 'text',
color: 'grey',
class: 'settings-icon-button',
action: () => {
dialog.value = true
},
},
],
})
})
onActivated(async () => {
@@ -208,20 +229,6 @@ onActivated(async () => {
<template>
<div class="mp-recommend">
<!-- 页面顶部控制栏 -->
<VHeaderTab :items="categoryItems" v-model="currentCategory">
<template #append>
<VBtn
icon="mdi-tune"
variant="text"
color="grey"
size="default"
class="settings-icon-button"
@click="dialog = true"
/>
</template>
</VHeaderTab>
<!-- 滚动内容区域 -->
<div class="recommend-content">
<TransitionGroup name="fade">
@@ -362,12 +369,6 @@ onActivated(async () => {
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
}
.setting-label {
color: rgba(var(--v-theme-on-surface), 0.8);
font-size: 0.9rem;
transition: color 0.2s ease;
}
.setting-item {
position: relative;
overflow: hidden;
@@ -399,37 +400,47 @@ onActivated(async () => {
&.动漫::before {
background-color: #ff9800;
} // Orange
&.::before {
&.排行::before {
background-color: #9c27b0;
} // Purple
&:hover {
border-color: rgba(var(--v-theme-on-surface), 0.15);
background-color: rgba(var(--v-theme-surface-variant), 0.6);
&.enabled {
border-color: rgba(var(--v-theme-primary), 0.3);
background-color: rgba(var(--v-theme-primary), 0.1);
}
&.enabled {
border-color: rgba(var(--v-theme-primary), 0.5);
background-color: rgba(var(--v-theme-primary), 0.05);
.setting-label {
color: rgb(var(--v-theme-primary));
font-weight: 500;
}
&:hover {
box-shadow: 0 4px 12px rgba(var(--v-theme-on-surface), 0.1);
transform: translateY(-2px);
}
}
.setting-item-inner {
display: flex;
align-items: center;
gap: 8px;
}
.setting-check {
margin-inline-end: 8px;
flex-shrink: 0;
}
/* Remove old tune button styles if they exist */
.tune-button {
display: none; // Hide the old button definitively
.setting-label {
flex: 1;
color: rgba(var(--v-theme-on-surface), 0.8);
font-size: 0.9rem;
font-weight: 500;
line-height: 1.2;
transition: color 0.2s ease;
}
.enabled .setting-label {
color: rgba(var(--v-theme-primary), 0.9);
}
@media (width <= 600px) {
.settings-grid {
grid-template-columns: 1fr;
}
}
</style>

View File

@@ -13,17 +13,32 @@ import AccountSettingDirectory from '@/views/setting/AccountSettingDirectory.vue
import AccountSettingRule from '@/views/setting/AccountSettingRule.vue'
import AccountSettingCache from '@/views/setting/AccountSettingCache.vue'
import { getSettingTabs } from '@/router/i18n-menu'
import { useDynamicHeaderTab } from '@/composables/useDynamicHeaderTab'
const route = useRoute()
const activeTab = ref(route.query.tab)
const activeTab = ref((route.query.tab as string) || '')
const settingTabs = computed(() => getSettingTabs())
// 使用动态标签页
const { registerHeaderTab } = useDynamicHeaderTab()
// 注册动态标签页
onMounted(() => {
// 设置初始activeTab值
if (!activeTab.value && settingTabs.value.length > 0) {
activeTab.value = settingTabs.value[0].tab
}
registerHeaderTab({
items: settingTabs.value,
modelValue: activeTab,
})
})
</script>
<template>
<div>
<VHeaderTab :items="settingTabs" v-model="activeTab" />
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
<VWindow v-model="activeTab" class="disable-tab-transition" :touch="false">
<!-- 系统 -->
<VWindowItem value="system">
<transition name="fade-slide" appear>

View File

@@ -14,6 +14,7 @@ import { useDynamicButton } from '@/composables/useDynamicButton'
import { useI18n } from 'vue-i18n'
import PluginMixedSortCard from '@/components/cards/PluginMixedSortCard.vue'
import { usePWA } from '@/composables/usePWA'
import { useDynamicHeaderTab } from '@/composables/useDynamicHeaderTab'
// 国际化
const { t } = useI18n()
@@ -33,6 +34,81 @@ const activeTab = ref('installed')
// 获取插件标签页
const pluginTabs = computed(() => getPluginTabs())
// 使用动态标签页
const { registerHeaderTab } = useDynamicHeaderTab()
// 注册动态标签页在setup顶层立即执行
registerHeaderTab({
items: pluginTabs.value,
modelValue: activeTab,
appendButtons: [
{
icon: 'mdi-filter-multiple-outline',
variant: 'text',
color: computed(() =>
installedFilter.value || hasUpdateFilter.value || enabledFilter.value ? 'primary' : 'gray',
),
class: 'settings-icon-button',
dataAttr: 'installed-filter-btn',
action: () => {
filterInstalledPluginDialog.value = true
},
show: computed(() => activeTab.value === 'installed'),
},
{
icon: 'mdi-filter-multiple-outline',
variant: 'text',
color: computed(() => (isFilterFormEmpty.value ? 'gray' : 'primary')),
class: 'settings-icon-button',
dataAttr: 'market-filter-btn',
action: () => {
filterMarketPluginDialog.value = true
},
show: computed(() => activeTab.value === 'market'),
},
{
icon: 'mdi-refresh',
variant: 'text',
color: 'gray',
class: 'settings-icon-button',
action: () => {
refreshMarket()
},
show: computed(() => activeTab.value === 'market'),
},
{
icon: 'mdi-store-cog',
variant: 'text',
color: 'gray',
class: 'settings-icon-button',
action: () => {
MarketSettingDialog.value = true
},
show: computed(() => activeTab.value === 'market'),
},
{
icon: 'mdi-folder-plus',
variant: 'text',
color: 'gray',
class: 'settings-icon-button',
action: () => {
showNewFolderDialog()
},
show: computed(() => activeTab.value === 'installed' && !currentFolder.value),
},
{
icon: 'mdi-arrow-left',
variant: 'text',
color: 'gray',
class: 'settings-icon-button',
action: () => {
backToMain()
},
show: computed(() => activeTab.value === 'installed' && !!currentFolder.value),
},
],
})
// 插件ID参数
const pluginId = ref(route.query.id)
@@ -798,6 +874,7 @@ function loadMarketMore({ done }: { done: any }) {
}
// 组件挂载后
onMounted(async () => {
await loadPluginOrderConfig()
await loadPluginFolders() // 加载文件夹配置
@@ -1215,173 +1292,118 @@ function onDragStartPlugin(evt: any) {
<template>
<div>
<VHeaderTab :items="pluginTabs" v-model="activeTab">
<template #append>
<VMenu
v-if="activeTab === 'installed'"
v-model="filterInstalledPluginDialog"
width="20rem"
:close-on-content-click="false"
scrim
>
<template #activator="{ props }">
<VBtn
icon="mdi-filter-multiple-outline"
variant="text"
:color="installedFilter || hasUpdateFilter || enabledFilter ? 'primary' : 'gray'"
size="default"
class="settings-icon-button"
v-bind="props"
/>
</template>
<VCard>
<VCardItem>
<VCardTitle>
<VIcon icon="mdi-filter-multiple-outline" class="mr-2" />
{{ t('plugin.filterPlugins') }}
</VCardTitle>
<VDialogCloseBtn @click="filterInstalledPluginDialog = false" />
</VCardItem>
<VCardText>
<!-- 过滤弹窗 -->
<Teleport to="body" v-if="filterInstalledPluginDialog">
<VMenu
v-model="filterInstalledPluginDialog"
width="20rem"
:close-on-content-click="false"
:activator="'[data-menu-activator=installed-filter-btn]'"
location="bottom end"
>
<VCard>
<VCardItem>
<VCardTitle>
<VIcon icon="mdi-filter-multiple-outline" class="mr-2" />
{{ t('plugin.filterPlugins') }}
</VCardTitle>
<VDialogCloseBtn @click="filterInstalledPluginDialog = false" />
</VCardItem>
<VCardText>
<VRow>
<VCol cols="12">
<VCombobox
v-model="installedFilter"
:items="installedPluginNames"
:label="t('plugin.name')"
density="comfortable"
clearable
/>
</VCol>
<VCol cols="6">
<VSwitch v-model="enabledFilter" :label="t('plugin.running')" />
</VCol>
<VCol cols="6">
<VSwitch v-model="hasUpdateFilter" :label="t('plugin.hasNewVersion')" />
</VCol>
</VRow>
</VCardText>
</VCard>
</VMenu>
</Teleport>
<Teleport to="body" v-if="filterMarketPluginDialog">
<VMenu
v-model="filterMarketPluginDialog"
width="25rem"
:close-on-content-click="false"
:activator="'[data-menu-activator=market-filter-btn]'"
location="bottom end"
>
<VCard>
<VCardItem>
<VCardTitle>
<VIcon icon="mdi-filter-multiple-outline" class="mr-2" />
{{ t('plugin.filterPlugins') }}
</VCardTitle>
<VDialogCloseBtn @click="filterMarketPluginDialog = false" />
</VCardItem>
<VCardText>
<!-- 过滤表单 -->
<div v-if="isAppMarketLoaded">
<VRow>
<VCol cols="12">
<VCombobox
v-model="installedFilter"
:items="installedPluginNames"
:label="t('plugin.name')"
<VCol cols="6">
<VTextField v-model="filterForm.name" density="comfortable" :label="t('plugin.name')" clearable />
</VCol>
<VCol v-if="authorFilterOptions.length > 0" cols="6">
<VSelect
v-model="filterForm.author"
:items="authorFilterOptions"
density="comfortable"
chips
:label="t('plugin.author')"
multiple
clearable
/>
</VCol>
<VCol cols="6">
<VSwitch v-model="enabledFilter" :label="t('plugin.running')" />
<VCol v-if="labelFilterOptions.length > 0" cols="6">
<VSelect
v-model="filterForm.label"
:items="labelFilterOptions"
density="comfortable"
chips
:label="t('plugin.label')"
multiple
clearable
/>
</VCol>
<VCol cols="6">
<VSwitch v-model="hasUpdateFilter" :label="t('plugin.hasNewVersion')" />
<VCol v-if="repoFilterOptions.length > 0" cols="6">
<VSelect
v-model="filterForm.repo"
:items="repoFilterOptions"
density="comfortable"
chips
:label="t('plugin.repository')"
multiple
clearable
/>
</VCol>
<VCol v-if="sortOptions.length > 0" cols="6">
<VSelect
v-model="activeSort"
:items="sortOptions"
density="comfortable"
:label="t('plugin.sortTitle')"
/>
</VCol>
</VRow>
</VCardText>
</VCard>
</VMenu>
<VMenu
v-if="activeTab === 'market'"
v-model="filterMarketPluginDialog"
width="25rem"
:close-on-content-click="false"
scrim
>
<template #activator="{ props }">
<VBtn
icon="mdi-filter-multiple-outline"
variant="text"
:color="isFilterFormEmpty ? 'gray' : 'primary'"
size="default"
class="settings-icon-button"
v-bind="props"
/>
</template>
<VCard>
<VCardItem>
<VCardTitle>
<VIcon icon="mdi-filter-multiple-outline" class="mr-2" />
{{ t('plugin.filterPlugins') }}
</VCardTitle>
<VDialogCloseBtn @click="filterMarketPluginDialog = false" />
</VCardItem>
<VCardText>
<!-- 过滤表单 -->
<div v-if="isAppMarketLoaded">
<VRow>
<VCol cols="6">
<VTextField v-model="filterForm.name" density="comfortable" :label="t('plugin.name')" clearable />
</VCol>
<VCol v-if="authorFilterOptions.length > 0" cols="6">
<VSelect
v-model="filterForm.author"
:items="authorFilterOptions"
density="comfortable"
chips
:label="t('plugin.author')"
multiple
clearable
/>
</VCol>
<VCol v-if="labelFilterOptions.length > 0" cols="6">
<VSelect
v-model="filterForm.label"
:items="labelFilterOptions"
density="comfortable"
chips
:label="t('plugin.label')"
multiple
clearable
/>
</VCol>
<VCol v-if="repoFilterOptions.length > 0" cols="6">
<VSelect
v-model="filterForm.repo"
:items="repoFilterOptions"
density="comfortable"
chips
:label="t('plugin.repository')"
multiple
clearable
/>
</VCol>
<VCol v-if="sortOptions.length > 0" cols="6">
<VSelect
v-model="activeSort"
:items="sortOptions"
density="comfortable"
:label="t('plugin.sortTitle')"
/>
</VCol>
</VRow>
</div>
</VCardText>
</VCard>
</VMenu>
<VBtn
v-if="activeTab === 'market'"
icon="mdi-refresh"
variant="text"
color="gray"
size="default"
class="settings-icon-button"
:loading="isMarketRefreshing"
@click="refreshMarket"
/>
<VBtn
v-if="activeTab === 'market'"
icon="mdi-store-cog"
variant="text"
color="gray"
size="default"
class="settings-icon-button"
@click="MarketSettingDialog = true"
/>
<VBtn
v-if="activeTab === 'installed' && !currentFolder"
icon="mdi-folder-plus"
variant="text"
color="gray"
size="default"
class="settings-icon-button"
@click="showNewFolderDialog"
/>
<VBtn
v-if="activeTab === 'installed' && currentFolder"
icon="mdi-arrow-left"
variant="text"
color="gray"
size="default"
class="settings-icon-button"
@click="backToMain"
/>
</template>
</VHeaderTab>
</div>
</VCardText>
</VCard>
</VMenu>
</Teleport>
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition px-2" :touch="false">
<VWindow v-model="activeTab" class="disable-tab-transition px-2" :touch="false">
<!-- 我的插件 -->
<VWindowItem value="installed">
<transition name="fade-slide" appear>
@@ -1626,7 +1648,3 @@ function onDragStartPlugin(evt: any) {
</VCard>
</VDialog>
</template>
<style lang="scss" scoped>
// 样式已移至 PluginMixedSortCard 组件
</style>

View File

@@ -215,7 +215,7 @@ const TransferDict: { [key: string]: string } = {
}
const tableStyle = computed(() => {
return appMode
return appMode.value
? 'height: calc(100vh - 15rem - env(safe-area-inset-bottom) - 7rem)'
: 'height: calc(100vh - 15rem - env(safe-area-inset-bottom)'
})