mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-19 14:39:29 +08:00
优化动态标签页注册逻辑
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)'
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
// 取消注册
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -122,8 +122,6 @@ onUnmounted(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-block-end: 16px;
|
||||
padding-block: 8px;
|
||||
padding-inline: 16px;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)'
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user