优化动态标签页注册逻辑

This commit is contained in:
jxxghp
2025-07-05 12:13:53 +08:00
parent da0756adf0
commit 873bf905ab
11 changed files with 358 additions and 256 deletions
@@ -9,7 +9,7 @@
// 磨砂渐变效果 // 磨砂渐变效果
backdrop-filter: blur(20px); 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: ""; content: "";
inset-block-start: 0; inset-block-start: 0;
inset-inline: 0; inset-inline: 0;
@@ -18,10 +18,10 @@
mask: linear-gradient( mask: linear-gradient(
to bottom, to bottom,
rgba(0, 0, 0, 100%) 0%, rgba(0, 0, 0, 100%) 0%,
rgba(0, 0, 0, 90%) calc(env(safe-area-inset-top, 0px) + 1rem), 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) + 2rem), 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) + 3rem), 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) + 4rem), rgba(0, 0, 0, 20%) calc(env(safe-area-inset-top, 0px) + var(--navbar-height) + 4rem),
rgba(0, 0, 0, 0%) 100% rgba(0, 0, 0, 0%) 100%
); );
pointer-events: none; pointer-events: none;
+17 -9
View File
@@ -38,15 +38,23 @@ export default defineComponent({
) )
// 👉 Navbar // 👉 Navbar
const navbar = h('header', { class: ['layout-navbar navbar-blur'] }, [ const navbar = h(
h( 'header',
'div', { class: ['layout-navbar navbar-blur'] },
{ class: 'navbar-content-container' }, [
slots.navbar?.({ h(
toggleVerticalOverlayNavActive: toggleIsOverlayNavActive, '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( const main = h(
'main', 'main',
+2 -2
View File
@@ -243,14 +243,14 @@ function stopDrag() {
// 外层DIV大小控制 // 外层DIV大小控制
const scrollStyle = computed(() => { 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) - 7rem)'
: 'height: calc(100vh - 10.5rem - env(safe-area-inset-bottom)' : 'height: calc(100vh - 10.5rem - env(safe-area-inset-bottom)'
}) })
// 文件列表大小限制 // 文件列表大小限制
const fileListStyle = computed(() => { 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) - 7rem)'
: 'height: calc(100vh - 14rem - env(safe-area-inset-bottom)' : 'height: calc(100vh - 14rem - env(safe-area-inset-bottom)'
}) })
+39 -12
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) { if (unregisterDynamicHeaderTab) {
unregisterDynamicHeaderTab() unregisterDynamicHeaderTab()
} }
}
// 初始注册(延迟到下个tick,确保路由已经完全切换)
nextTick(() => {
doRegister()
}) })
// 初始注册 // 处理页面激活时重新注册(支持keep-alive缓存的页面)
if (registerDynamicHeaderTab) { onActivated(() => {
registerDynamicHeaderTab(tabConfig) nextTick(() => {
} else if (typeof window !== 'undefined') { doRegister()
// 使用全局方法作为备用 })
const globalRegister = (window as any).__VUE_INJECT_DYNAMIC_HEADER_TAB__ })
if (globalRegister) {
globalRegister(tabConfig) // 处理页面失活时取消注册(支持keep-alive缓存的页面)
} onDeactivated(() => {
} doUnregister()
})
// 在组件卸载时取消注册
onUnmounted(() => {
doUnregister()
})
} }
// 取消注册 // 取消注册
+28 -18
View File
@@ -91,7 +91,8 @@ const dynamicHeaderTab = ref<DynamicHeaderTab | null>(null)
const registerDynamicHeaderTab = (tab: DynamicHeaderTab) => { const registerDynamicHeaderTab = (tab: DynamicHeaderTab) => {
// 保存注册标签页的路由路径 // 保存注册标签页的路由路径
tab.routePath = route.path tab.routePath = route.path
dynamicHeaderTab.value = tab // 强制更新,确保响应式系统能检测到变化
dynamicHeaderTab.value = { ...tab }
} }
// 提供一个方法让其他组件取消注册动态标签页 // 提供一个方法让其他组件取消注册动态标签页
@@ -123,11 +124,17 @@ provide('unregisterDynamicHeaderTab', unregisterDynamicHeaderTab)
// 监听路由变化来清除动态标签页 // 监听路由变化来清除动态标签页
watch( watch(
() => route.path, () => route.path,
newPath => { (newPath, oldPath) => {
// 当路由变化时,清除动态标签页(如果不是同一个路由注册的) // 使用nextTick确保新页面的组件已经挂载完成
if (dynamicHeaderTab.value && dynamicHeaderTab.value.routePath !== newPath) { nextTick(() => {
dynamicHeaderTab.value = null // 延迟一小段时间,让新页面有机会注册标签页
} setTimeout(() => {
// 如果当前标签页不属于新路由,则清除
if (dynamicHeaderTab.value && dynamicHeaderTab.value.routePath !== route.path) {
dynamicHeaderTab.value = null
}
}, 50) // 减少延迟时间,但仍然给新页面注册机会
})
}, },
{ immediate: false }, { immediate: false },
) )
@@ -138,7 +145,7 @@ const showDynamicHeaderTab = computed(() => {
dynamicHeaderTab.value && dynamicHeaderTab.value &&
dynamicHeaderTab.value.items.length > 0 && 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>
</div> </div>
<VerticalNavLayout> <VerticalNavLayout :style="{ '--navbar-height': showDynamicHeaderTab ? '2.5rem' : '0px' }">
<!-- 👉 Navbar --> <!-- 👉 Navbar -->
<template #navbar="{ toggleVerticalOverlayNavActive }"> <template #navbar="{ toggleVerticalOverlayNavActive }">
<div class="d-flex h-100 align-center mx-1"> <div class="d-flex h-100 align-center mx-1">
@@ -349,16 +356,9 @@ onMounted(() => {
<template #after-vertical-nav-items /> <template #after-vertical-nav-items />
<!-- 👉 下拉跟随动画 --> <!-- 👉 Dynamic Header Tab -->
<div <template #dynamic-header-tab>
class="main-content-wrapper" <div v-if="showDynamicHeaderTab">
:style="{
transform: contentTransform,
transition: contentTransition,
}"
>
<!-- 👉 Dynamic Header Tab -->
<div v-if="showDynamicHeaderTab" class="dynamic-header-tab-container">
<HeaderTab <HeaderTab
:items="dynamicHeaderTab!.items" :items="dynamicHeaderTab!.items"
:model-value="dynamicHeaderTab!.modelValue" :model-value="dynamicHeaderTab!.modelValue"
@@ -380,7 +380,17 @@ onMounted(() => {
</template> </template>
</HeaderTab> </HeaderTab>
</div> </div>
</template>
<!-- 👉 下拉跟随动画 -->
<div
class="main-content-wrapper"
:style="{
transform: contentTransform,
transition: contentTransition,
paddingTop: showDynamicHeaderTab ? '3rem' : '0px',
}"
>
<slot /> <slot />
</div> </div>
-2
View File
@@ -122,8 +122,6 @@ onUnmounted(() => {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-block-end: 16px;
padding-block: 8px;
padding-inline: 16px; padding-inline: 16px;
} }
+19 -4
View File
@@ -4,12 +4,13 @@ import { DownloaderConf } from '@/api/types'
import DownloadingListView from '@/views/reorganize/DownloadingListView.vue' import DownloadingListView from '@/views/reorganize/DownloadingListView.vue'
import NoDataFound from '@/components/NoDataFound.vue' import NoDataFound from '@/components/NoDataFound.vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useDynamicHeaderTab } from '@/composables/useDynamicHeaderTab'
// 国际化 // 国际化
const { t } = useI18n() const { t } = useI18n()
const route = useRoute() const route = useRoute()
const activeTab = ref(route.query.tab) const activeTab = ref<string>((route.query.tab as string) || '')
// 下载器 // 下载器
const downloaders = ref<DownloaderConf[]>([]) const downloaders = ref<DownloaderConf[]>([])
@@ -22,6 +23,9 @@ const downloaderItems = computed(() => {
})) }))
}) })
// 使用动态标签页
const { registerHeaderTab } = useDynamicHeaderTab()
// 调用API查询下载器设置 // 调用API查询下载器设置
async function loadDownloaderSetting() { async function loadDownloaderSetting() {
try { try {
@@ -33,19 +37,30 @@ async function loadDownloaderSetting() {
} }
} }
// 注册动态标签页
const registerTabs = () => {
if (downloaderItems.value.length > 0) {
registerHeaderTab({
items: downloaderItems.value,
modelValue: activeTab,
})
}
}
onMounted(async () => { onMounted(async () => {
await loadDownloaderSetting() await loadDownloaderSetting()
registerTabs()
}) })
onActivated(async () => { onActivated(async () => {
loadDownloaderSetting() await loadDownloaderSetting()
registerTabs()
}) })
</script> </script>
<template> <template>
<div v-if="downloaders.length > 0"> <div v-if="downloaders.length > 0">
<VHeaderTab :items="downloaderItems" v-model="activeTab" /> <VWindow v-model="activeTab" class="disable-tab-transition" :touch="false">
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
<VWindowItem v-for="item in downloaders" :value="item.name"> <VWindowItem v-for="item in downloaders" :value="item.name">
<transition name="fade-slide" appear> <transition name="fade-slide" appear>
<div> <div>
+49 -38
View File
@@ -4,6 +4,7 @@ import { RecommendSource } from '@/api/types'
import MediaCardSlideView from '@/views/discover/MediaCardSlideView.vue' import MediaCardSlideView from '@/views/discover/MediaCardSlideView.vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { useDynamicHeaderTab } from '@/composables/useDynamicHeaderTab'
const display = useDisplay() const display = useDisplay()
@@ -165,7 +166,7 @@ async function saveConfig() {
} }
// 标签图标映射 // 标签图标映射
const categoryItems: Record<string, string>[] = [ const categoryItems = computed(() => [
{ {
title: t('recommend.all'), title: t('recommend.all'),
icon: 'mdi-filmstrip-box-multiple', icon: 'mdi-filmstrip-box-multiple',
@@ -191,7 +192,10 @@ const categoryItems: Record<string, string>[] = [
icon: 'mdi-trophy', icon: 'mdi-trophy',
tab: t('recommend.categoryRankings'), tab: t('recommend.categoryRankings'),
}, },
] ])
// 使用动态标签页
const { registerHeaderTab } = useDynamicHeaderTab()
onBeforeMount(async () => { onBeforeMount(async () => {
await loadConfig() await loadConfig()
@@ -199,6 +203,23 @@ onBeforeMount(async () => {
onMounted(async () => { onMounted(async () => {
await loadExtraRecommendSources() 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 () => { onActivated(async () => {
@@ -208,20 +229,6 @@ onActivated(async () => {
<template> <template>
<div class="mp-recommend"> <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"> <div class="recommend-content">
<TransitionGroup name="fade"> <TransitionGroup name="fade">
@@ -362,12 +369,6 @@ onActivated(async () => {
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); 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 { .setting-item {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
@@ -399,37 +400,47 @@ onActivated(async () => {
&.动漫::before { &.动漫::before {
background-color: #ff9800; background-color: #ff9800;
} // Orange } // Orange
&.::before { &.排行::before {
background-color: #9c27b0; background-color: #9c27b0;
} // Purple } // Purple
&:hover { &.enabled {
border-color: rgba(var(--v-theme-on-surface), 0.15); border-color: rgba(var(--v-theme-primary), 0.3);
background-color: rgba(var(--v-theme-surface-variant), 0.6); background-color: rgba(var(--v-theme-primary), 0.1);
} }
&.enabled { &:hover {
border-color: rgba(var(--v-theme-primary), 0.5); box-shadow: 0 4px 12px rgba(var(--v-theme-on-surface), 0.1);
background-color: rgba(var(--v-theme-primary), 0.05); transform: translateY(-2px);
.setting-label {
color: rgb(var(--v-theme-primary));
font-weight: 500;
}
} }
} }
.setting-item-inner { .setting-item-inner {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px;
} }
.setting-check { .setting-check {
margin-inline-end: 8px; flex-shrink: 0;
} }
/* Remove old tune button styles if they exist */ .setting-label {
.tune-button { flex: 1;
display: none; // Hide the old button definitively 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> </style>
+18 -3
View File
@@ -13,17 +13,32 @@ import AccountSettingDirectory from '@/views/setting/AccountSettingDirectory.vue
import AccountSettingRule from '@/views/setting/AccountSettingRule.vue' import AccountSettingRule from '@/views/setting/AccountSettingRule.vue'
import AccountSettingCache from '@/views/setting/AccountSettingCache.vue' import AccountSettingCache from '@/views/setting/AccountSettingCache.vue'
import { getSettingTabs } from '@/router/i18n-menu' import { getSettingTabs } from '@/router/i18n-menu'
import { useDynamicHeaderTab } from '@/composables/useDynamicHeaderTab'
const route = useRoute() const route = useRoute()
const activeTab = ref(route.query.tab) const activeTab = ref((route.query.tab as string) || '')
const settingTabs = computed(() => getSettingTabs()) 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> </script>
<template> <template>
<div> <div>
<VHeaderTab :items="settingTabs" v-model="activeTab" /> <VWindow v-model="activeTab" class="disable-tab-transition" :touch="false">
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
<!-- 系统 --> <!-- 系统 -->
<VWindowItem value="system"> <VWindowItem value="system">
<transition name="fade-slide" appear> <transition name="fade-slide" appear>
+180 -162
View File
@@ -14,6 +14,7 @@ import { useDynamicButton } from '@/composables/useDynamicButton'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import PluginMixedSortCard from '@/components/cards/PluginMixedSortCard.vue' import PluginMixedSortCard from '@/components/cards/PluginMixedSortCard.vue'
import { usePWA } from '@/composables/usePWA' import { usePWA } from '@/composables/usePWA'
import { useDynamicHeaderTab } from '@/composables/useDynamicHeaderTab'
// 国际化 // 国际化
const { t } = useI18n() const { t } = useI18n()
@@ -33,6 +34,81 @@ const activeTab = ref('installed')
// 获取插件标签页 // 获取插件标签页
const pluginTabs = computed(() => getPluginTabs()) 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参数 // 插件ID参数
const pluginId = ref(route.query.id) const pluginId = ref(route.query.id)
@@ -798,6 +874,7 @@ function loadMarketMore({ done }: { done: any }) {
} }
// 组件挂载后 // 组件挂载后
onMounted(async () => { onMounted(async () => {
await loadPluginOrderConfig() await loadPluginOrderConfig()
await loadPluginFolders() // 加载文件夹配置 await loadPluginFolders() // 加载文件夹配置
@@ -1215,173 +1292,118 @@ function onDragStartPlugin(evt: any) {
<template> <template>
<div> <div>
<VHeaderTab :items="pluginTabs" v-model="activeTab"> <!-- 过滤弹窗 -->
<template #append> <Teleport to="body" v-if="filterInstalledPluginDialog">
<VMenu <VMenu
v-if="activeTab === 'installed'" v-model="filterInstalledPluginDialog"
v-model="filterInstalledPluginDialog" width="20rem"
width="20rem" :close-on-content-click="false"
:close-on-content-click="false" :activator="'[data-menu-activator=installed-filter-btn]'"
scrim location="bottom end"
> >
<template #activator="{ props }"> <VCard>
<VBtn <VCardItem>
icon="mdi-filter-multiple-outline" <VCardTitle>
variant="text" <VIcon icon="mdi-filter-multiple-outline" class="mr-2" />
:color="installedFilter || hasUpdateFilter || enabledFilter ? 'primary' : 'gray'" {{ t('plugin.filterPlugins') }}
size="default" </VCardTitle>
class="settings-icon-button" <VDialogCloseBtn @click="filterInstalledPluginDialog = false" />
v-bind="props" </VCardItem>
/> <VCardText>
</template> <VRow>
<VCard> <VCol cols="12">
<VCardItem> <VCombobox
<VCardTitle> v-model="installedFilter"
<VIcon icon="mdi-filter-multiple-outline" class="mr-2" /> :items="installedPluginNames"
{{ t('plugin.filterPlugins') }} :label="t('plugin.name')"
</VCardTitle> density="comfortable"
<VDialogCloseBtn @click="filterInstalledPluginDialog = false" /> clearable
</VCardItem> />
<VCardText> </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> <VRow>
<VCol cols="12"> <VCol cols="6">
<VCombobox <VTextField v-model="filterForm.name" density="comfortable" :label="t('plugin.name')" clearable />
v-model="installedFilter" </VCol>
:items="installedPluginNames" <VCol v-if="authorFilterOptions.length > 0" cols="6">
:label="t('plugin.name')" <VSelect
v-model="filterForm.author"
:items="authorFilterOptions"
density="comfortable" density="comfortable"
chips
:label="t('plugin.author')"
multiple
clearable clearable
/> />
</VCol> </VCol>
<VCol cols="6"> <VCol v-if="labelFilterOptions.length > 0" cols="6">
<VSwitch v-model="enabledFilter" :label="t('plugin.running')" /> <VSelect
v-model="filterForm.label"
:items="labelFilterOptions"
density="comfortable"
chips
:label="t('plugin.label')"
multiple
clearable
/>
</VCol> </VCol>
<VCol cols="6"> <VCol v-if="repoFilterOptions.length > 0" cols="6">
<VSwitch v-model="hasUpdateFilter" :label="t('plugin.hasNewVersion')" /> <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> </VCol>
</VRow> </VRow>
</VCardText> </div>
</VCard> </VCardText>
</VMenu> </VCard>
<VMenu </VMenu>
v-if="activeTab === 'market'" </Teleport>
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>
<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"> <VWindowItem value="installed">
<transition name="fade-slide" appear> <transition name="fade-slide" appear>
@@ -1626,7 +1648,3 @@ function onDragStartPlugin(evt: any) {
</VCard> </VCard>
</VDialog> </VDialog>
</template> </template>
<style lang="scss" scoped>
// 样式已移至 PluginMixedSortCard 组件
</style>
+1 -1
View File
@@ -215,7 +215,7 @@ const TransferDict: { [key: string]: string } = {
} }
const tableStyle = computed(() => { 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) - 7rem)'
: 'height: calc(100vh - 15rem - env(safe-area-inset-bottom)' : 'height: calc(100vh - 15rem - env(safe-area-inset-bottom)'
}) })