mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-06 08:06:43 +08:00
动态Tab组件
This commit is contained in:
@@ -0,0 +1,98 @@
|
|||||||
|
// 动态标签页相关类型
|
||||||
|
interface DynamicHeaderTabButton {
|
||||||
|
icon: string
|
||||||
|
color?: string | ComputedRef<string>
|
||||||
|
variant?: 'flat' | 'text' | 'elevated' | 'tonal' | 'outlined' | 'plain'
|
||||||
|
size?: string
|
||||||
|
class?: string
|
||||||
|
action?: () => void
|
||||||
|
show?: boolean | ComputedRef<boolean>
|
||||||
|
dataAttr?: string // 用于VMenu定位的data属性
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DynamicHeaderTabItem {
|
||||||
|
title: string
|
||||||
|
icon?: string
|
||||||
|
tab: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DynamicHeaderTabConfig {
|
||||||
|
items: DynamicHeaderTabItem[]
|
||||||
|
modelValue: string
|
||||||
|
appendButtons?: DynamicHeaderTabButton[]
|
||||||
|
routePath?: string
|
||||||
|
onUpdateModelValue?: (value: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDynamicHeaderTab() {
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
// 尝试从inject获取
|
||||||
|
const registerDynamicHeaderTab = inject<(tab: DynamicHeaderTabConfig) => void>('registerDynamicHeaderTab')
|
||||||
|
const unregisterDynamicHeaderTab = inject<() => void>('unregisterDynamicHeaderTab')
|
||||||
|
|
||||||
|
// 注册动态标签页
|
||||||
|
const registerHeaderTab = (config: {
|
||||||
|
items: DynamicHeaderTabItem[]
|
||||||
|
modelValue: Ref<string>
|
||||||
|
appendButtons?: DynamicHeaderTabButton[]
|
||||||
|
}) => {
|
||||||
|
const tabConfig: DynamicHeaderTabConfig = {
|
||||||
|
items: config.items,
|
||||||
|
modelValue: config.modelValue.value,
|
||||||
|
appendButtons: config.appendButtons,
|
||||||
|
routePath: route.path,
|
||||||
|
onUpdateModelValue: (value: string) => {
|
||||||
|
config.modelValue.value = value
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听modelValue变化并更新配置
|
||||||
|
watch(config.modelValue, newValue => {
|
||||||
|
tabConfig.modelValue = newValue
|
||||||
|
// 重新注册以更新值
|
||||||
|
if (registerDynamicHeaderTab) {
|
||||||
|
registerDynamicHeaderTab(tabConfig)
|
||||||
|
} else if (typeof window !== 'undefined') {
|
||||||
|
// 使用全局方法作为备用
|
||||||
|
const globalRegister = (window as any).__VUE_INJECT_DYNAMIC_HEADER_TAB__
|
||||||
|
if (globalRegister) {
|
||||||
|
globalRegister(tabConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 在组件卸载时取消注册
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (unregisterDynamicHeaderTab) {
|
||||||
|
unregisterDynamicHeaderTab()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始注册
|
||||||
|
if (registerDynamicHeaderTab) {
|
||||||
|
registerDynamicHeaderTab(tabConfig)
|
||||||
|
} else if (typeof window !== 'undefined') {
|
||||||
|
// 使用全局方法作为备用
|
||||||
|
const globalRegister = (window as any).__VUE_INJECT_DYNAMIC_HEADER_TAB__
|
||||||
|
if (globalRegister) {
|
||||||
|
globalRegister(tabConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消注册
|
||||||
|
const unregisterHeaderTab = () => {
|
||||||
|
if (unregisterDynamicHeaderTab) {
|
||||||
|
unregisterDynamicHeaderTab()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
registerHeaderTab,
|
||||||
|
unregisterHeaderTab,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出类型以供其他地方使用
|
||||||
|
export type { DynamicHeaderTabButton, DynamicHeaderTabItem, DynamicHeaderTabConfig }
|
||||||
@@ -8,6 +8,7 @@ import SearchBar from '@/layouts/components/SearchBar.vue'
|
|||||||
import ShortcutBar from '@/layouts/components/ShortcutBar.vue'
|
import ShortcutBar from '@/layouts/components/ShortcutBar.vue'
|
||||||
import UserProfile from '@/layouts/components/UserProfile.vue'
|
import UserProfile from '@/layouts/components/UserProfile.vue'
|
||||||
import QuickAccess from '@/layouts/components/QuickAccess.vue'
|
import QuickAccess from '@/layouts/components/QuickAccess.vue'
|
||||||
|
import HeaderTab from '@/layouts/components/HeaderTab.vue'
|
||||||
import { useUserStore } from '@/stores'
|
import { useUserStore } from '@/stores'
|
||||||
import { getNavMenus } from '@/router/i18n-menu'
|
import { getNavMenus } from '@/router/i18n-menu'
|
||||||
import { NavMenu } from '@/@layouts/types'
|
import { NavMenu } from '@/@layouts/types'
|
||||||
@@ -64,6 +65,92 @@ const showPluginQuickAccess = ref(false)
|
|||||||
// 离线状态管理
|
// 离线状态管理
|
||||||
const { setAppOffline, isOffline } = useGlobalOfflineStatus()
|
const { setAppOffline, isOffline } = useGlobalOfflineStatus()
|
||||||
|
|
||||||
|
// 动态标签页相关
|
||||||
|
// 定义动态标签页类型
|
||||||
|
interface DynamicHeaderTab {
|
||||||
|
items: Array<{ title: string; icon: string; tab: string }>
|
||||||
|
modelValue: string
|
||||||
|
appendButtons?: Array<{
|
||||||
|
icon: string
|
||||||
|
color?: string | ComputedRef<string>
|
||||||
|
variant?: 'flat' | 'text' | 'elevated' | 'tonal' | 'outlined' | 'plain'
|
||||||
|
size?: string
|
||||||
|
class?: string
|
||||||
|
action?: () => void
|
||||||
|
show?: boolean | ComputedRef<boolean>
|
||||||
|
dataAttr?: string
|
||||||
|
}>
|
||||||
|
routePath?: string // 用于标识哪个路由注册的
|
||||||
|
onUpdateModelValue?: (value: string) => void // 用于通知值更新
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提供动态标签页注册和获取的方法
|
||||||
|
const dynamicHeaderTab = ref<DynamicHeaderTab | null>(null)
|
||||||
|
|
||||||
|
// 提供一个方法让其他组件注册动态标签页
|
||||||
|
const registerDynamicHeaderTab = (tab: DynamicHeaderTab) => {
|
||||||
|
// 保存注册标签页的路由路径
|
||||||
|
tab.routePath = route.path
|
||||||
|
dynamicHeaderTab.value = tab
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提供一个方法让其他组件取消注册动态标签页
|
||||||
|
const unregisterDynamicHeaderTab = () => {
|
||||||
|
dynamicHeaderTab.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标签页值更新处理
|
||||||
|
const handleTabChange = (newValue: string) => {
|
||||||
|
if (dynamicHeaderTab.value) {
|
||||||
|
dynamicHeaderTab.value.modelValue = newValue
|
||||||
|
// 通知注册的页面更新值
|
||||||
|
if (dynamicHeaderTab.value.onUpdateModelValue) {
|
||||||
|
dynamicHeaderTab.value.onUpdateModelValue(newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加全局注册方法,解决注入不可用的问题
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
// 确保在浏览器环境中
|
||||||
|
;(window as any).__VUE_INJECT_DYNAMIC_HEADER_TAB__ = registerDynamicHeaderTab
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提供给其他组件使用
|
||||||
|
provide('registerDynamicHeaderTab', registerDynamicHeaderTab)
|
||||||
|
provide('unregisterDynamicHeaderTab', unregisterDynamicHeaderTab)
|
||||||
|
|
||||||
|
// 监听路由变化来清除动态标签页
|
||||||
|
watch(
|
||||||
|
() => route.path,
|
||||||
|
newPath => {
|
||||||
|
// 当路由变化时,清除动态标签页(如果不是同一个路由注册的)
|
||||||
|
if (dynamicHeaderTab.value && dynamicHeaderTab.value.routePath !== newPath) {
|
||||||
|
dynamicHeaderTab.value = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: false },
|
||||||
|
)
|
||||||
|
|
||||||
|
// 显示动态标签页
|
||||||
|
const showDynamicHeaderTab = computed(() => {
|
||||||
|
return (
|
||||||
|
dynamicHeaderTab.value &&
|
||||||
|
dynamicHeaderTab.value.items.length > 0 &&
|
||||||
|
// 确保只在注册的路由路径下显示标签页
|
||||||
|
(!dynamicHeaderTab.value.routePath || dynamicHeaderTab.value.routePath === route.path)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 在组件销毁时清理
|
||||||
|
onUnmounted(() => {
|
||||||
|
dynamicHeaderTab.value = null
|
||||||
|
// 清理全局方法
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
delete (window as any).__VUE_INJECT_DYNAMIC_HEADER_TAB__
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 监听Service Worker消息
|
// 监听Service Worker消息
|
||||||
const handleServiceWorkerMessage = (event: MessageEvent) => {
|
const handleServiceWorkerMessage = (event: MessageEvent) => {
|
||||||
if (event.data && event.data.type === 'OFFLINE_STATUS') {
|
if (event.data && event.data.type === 'OFFLINE_STATUS') {
|
||||||
@@ -270,6 +357,30 @@ onMounted(() => {
|
|||||||
transition: contentTransition,
|
transition: contentTransition,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
<!-- 👉 Dynamic Header Tab -->
|
||||||
|
<div v-if="showDynamicHeaderTab" class="dynamic-header-tab-container">
|
||||||
|
<HeaderTab
|
||||||
|
:items="dynamicHeaderTab!.items"
|
||||||
|
:model-value="dynamicHeaderTab!.modelValue"
|
||||||
|
@update:model-value="handleTabChange"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<template v-for="button in dynamicHeaderTab!.appendButtons" :key="button.icon">
|
||||||
|
<VBtn
|
||||||
|
v-if="typeof button.show === 'boolean' ? button.show !== false : (button.show as any)?.value !== false"
|
||||||
|
:icon="button.icon"
|
||||||
|
:variant="button.variant || 'text'"
|
||||||
|
:color="typeof button.color === 'string' ? button.color : (button.color as any)?.value || 'gray'"
|
||||||
|
:size="button.size || 'default'"
|
||||||
|
:class="button.class || 'settings-icon-button'"
|
||||||
|
:data-menu-activator="button.dataAttr"
|
||||||
|
@click="button.action"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</HeaderTab>
|
||||||
|
</div>
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -117,14 +117,11 @@ onUnmounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.tab-header {
|
.tab-header {
|
||||||
position: sticky;
|
position: relative;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
border-block-end: 1px solid rgba(var(--v-theme-on-surface), 0.05);
|
|
||||||
inset-block-start: 0;
|
|
||||||
margin-block-end: 16px;
|
margin-block-end: 16px;
|
||||||
padding-block: 8px;
|
padding-block: 8px;
|
||||||
padding-inline: 16px;
|
padding-inline: 16px;
|
||||||
|
|||||||
+27
-14
@@ -119,6 +119,9 @@ async function saveTabOrder() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用动态标签页
|
||||||
|
const { registerHeaderTab } = useDynamicHeaderTab()
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
initDiscoverTabs()
|
initDiscoverTabs()
|
||||||
await loadOrderConfig()
|
await loadOrderConfig()
|
||||||
@@ -130,28 +133,38 @@ onBeforeMount(async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 注册动态标签页
|
||||||
|
registerHeaderTab({
|
||||||
|
items: discoverTabItems.value,
|
||||||
|
modelValue: activeTab,
|
||||||
|
appendButtons: [
|
||||||
|
{
|
||||||
|
icon: 'mdi-order-alphabetical-ascending',
|
||||||
|
variant: 'text',
|
||||||
|
color: 'grey',
|
||||||
|
class: 'settings-icon-button',
|
||||||
|
action: () => {
|
||||||
|
orderConfigDialog.value = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
onActivated(async () => {
|
onActivated(async () => {
|
||||||
await loadExtraDiscoverSources()
|
await loadExtraDiscoverSources()
|
||||||
sortSubscribeOrder()
|
sortSubscribeOrder()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function useDynamicHeaderTab(): { registerHeaderTab: any } {
|
||||||
|
throw new Error('Function not implemented.')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VHeaderTab :items="discoverTabItems" v-model="activeTab">
|
<VWindow v-model="activeTab" class="disable-tab-transition" :touch="false">
|
||||||
<template #append>
|
|
||||||
<VBtn
|
|
||||||
icon="mdi-order-alphabetical-ascending"
|
|
||||||
variant="text"
|
|
||||||
color="grey"
|
|
||||||
size="default"
|
|
||||||
class="settings-icon-button"
|
|
||||||
@click="orderConfigDialog = true"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</VHeaderTab>
|
|
||||||
|
|
||||||
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
|
|
||||||
<VWindowItem value="themoviedb">
|
<VWindowItem value="themoviedb">
|
||||||
<transition name="fade-slide" appear>
|
<transition name="fade-slide" appear>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
+114
-80
@@ -4,6 +4,7 @@ import SubscribePopularView from '@/views/subscribe/SubscribePopularView.vue'
|
|||||||
import SubscribeShareView from '@/views/subscribe/SubscribeShareView.vue'
|
import SubscribeShareView from '@/views/subscribe/SubscribeShareView.vue'
|
||||||
import SubscribeEditDialog from '@/components/dialog/SubscribeEditDialog.vue'
|
import SubscribeEditDialog from '@/components/dialog/SubscribeEditDialog.vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useDynamicHeaderTab } from '@/composables/useDynamicHeaderTab'
|
||||||
|
|
||||||
import { getSubscribeMovieTabs, getSubscribeTvTabs } from '@/router/i18n-menu'
|
import { getSubscribeMovieTabs, getSubscribeTvTabs } from '@/router/i18n-menu'
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ const route = useRoute()
|
|||||||
|
|
||||||
const subType = route.meta.subType?.toString()
|
const subType = route.meta.subType?.toString()
|
||||||
const subId = ref(route.query.id as string)
|
const subId = ref(route.query.id as string)
|
||||||
const activeTab = ref(route.query.tab)
|
const activeTab = ref((route.query.tab as string) || '')
|
||||||
const shareViewKey = ref(0)
|
const shareViewKey = ref(0)
|
||||||
|
|
||||||
// 获取标签页
|
// 获取标签页
|
||||||
@@ -46,89 +47,64 @@ const searchShares = () => {
|
|||||||
searchShareDialog.value = false
|
searchShareDialog.value = false
|
||||||
shareViewKey.value++
|
shareViewKey.value++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VMenu activator选择器
|
||||||
|
const filterActivator = computed(() => '[data-menu-activator="filter-btn"]')
|
||||||
|
const searchActivator = computed(() => '[data-menu-activator="search-btn"]')
|
||||||
|
|
||||||
|
// 使用动态标签页
|
||||||
|
const { registerHeaderTab } = useDynamicHeaderTab()
|
||||||
|
|
||||||
|
// 注册动态标签页
|
||||||
|
onMounted(() => {
|
||||||
|
// 设置初始activeTab值
|
||||||
|
if (!activeTab.value && subscribeTabs.value.length > 0) {
|
||||||
|
activeTab.value = subscribeTabs.value[0].tab
|
||||||
|
}
|
||||||
|
registerHeaderTab({
|
||||||
|
items: subscribeTabs.value,
|
||||||
|
modelValue: activeTab,
|
||||||
|
appendButtons: [
|
||||||
|
{
|
||||||
|
icon: 'mdi-filter-multiple-outline',
|
||||||
|
variant: 'text',
|
||||||
|
color: computed(() => (subscribeFilter.value ? 'primary' : 'gray')),
|
||||||
|
class: 'settings-icon-button',
|
||||||
|
dataAttr: 'filter-btn',
|
||||||
|
action: () => {
|
||||||
|
filterSubscribeDialog.value = true
|
||||||
|
},
|
||||||
|
show: computed(() => activeTab.value === 'mysub'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'mdi-movie-search-outline',
|
||||||
|
variant: 'text',
|
||||||
|
color: computed(() => (shareKeyword.value ? 'primary' : 'gray')),
|
||||||
|
class: 'settings-icon-button',
|
||||||
|
dataAttr: 'search-btn',
|
||||||
|
action: () => {
|
||||||
|
searchShareDialog.value = true
|
||||||
|
},
|
||||||
|
show: computed(() => activeTab.value === 'share'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'mdi-clipboard-edit-outline',
|
||||||
|
variant: 'text',
|
||||||
|
color: 'gray',
|
||||||
|
class: 'settings-icon-button',
|
||||||
|
action: () => {
|
||||||
|
subscribeEditDialog.value = true
|
||||||
|
},
|
||||||
|
show: computed(() => activeTab.value === 'mysub'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VHeaderTab :items="subscribeTabs" v-model="activeTab">
|
<VWindow v-model="activeTab" class="disable-tab-transition content-window" :touch="false">
|
||||||
<template #append>
|
|
||||||
<VMenu
|
|
||||||
v-if="activeTab === 'mysub'"
|
|
||||||
v-model="filterSubscribeDialog"
|
|
||||||
width="20rem"
|
|
||||||
:close-on-content-click="false"
|
|
||||||
scrim
|
|
||||||
>
|
|
||||||
<template #activator="{ props }">
|
|
||||||
<VBtn
|
|
||||||
icon="mdi-filter-multiple-outline"
|
|
||||||
variant="text"
|
|
||||||
:color="subscribeFilter ? '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('subscribe.filterSubscriptions') }}
|
|
||||||
</VCardTitle>
|
|
||||||
<VDialogCloseBtn @click="filterSubscribeDialog = false" />
|
|
||||||
</VCardItem>
|
|
||||||
<VCardText>
|
|
||||||
<VTextField v-model="subscribeFilter" :label="t('subscribe.name')" clearable density="comfortable" />
|
|
||||||
</VCardText>
|
|
||||||
</VCard>
|
|
||||||
</VMenu>
|
|
||||||
<VMenu
|
|
||||||
v-if="activeTab === 'share'"
|
|
||||||
v-model="searchShareDialog"
|
|
||||||
width="25rem"
|
|
||||||
:close-on-content-click="false"
|
|
||||||
scrim
|
|
||||||
>
|
|
||||||
<template #activator="{ props }">
|
|
||||||
<VBtn
|
|
||||||
icon="mdi-movie-search-outline"
|
|
||||||
variant="text"
|
|
||||||
:color="shareKeyword ? 'primary' : 'gray'"
|
|
||||||
size="default"
|
|
||||||
class="settings-icon-button"
|
|
||||||
v-bind="props"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<VCard>
|
|
||||||
<VCardItem>
|
|
||||||
<VCardTitle>
|
|
||||||
<VIcon icon="mdi-movie-search-outline" class="mr-2" />
|
|
||||||
{{ t('subscribe.searchShares') }}
|
|
||||||
</VCardTitle>
|
|
||||||
<VDialogCloseBtn @click="searchShareDialog = false" />
|
|
||||||
</VCardItem>
|
|
||||||
<VCardText>
|
|
||||||
<VTextField v-model="shareKeyword" :label="t('subscribe.keyword')" clearable density="comfortable">
|
|
||||||
<template #append>
|
|
||||||
<VBtn prepend-icon="mdi-magnify" color="primary" @click="searchShares">{{ t('common.search') }}</VBtn>
|
|
||||||
</template>
|
|
||||||
</VTextField>
|
|
||||||
</VCardText>
|
|
||||||
</VCard>
|
|
||||||
</VMenu>
|
|
||||||
<VBtn
|
|
||||||
v-if="activeTab === 'mysub'"
|
|
||||||
icon="mdi-clipboard-edit-outline"
|
|
||||||
variant="text"
|
|
||||||
color="gray"
|
|
||||||
size="default"
|
|
||||||
class="settings-icon-button"
|
|
||||||
@click="subscribeEditDialog = true"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</VHeaderTab>
|
|
||||||
|
|
||||||
<VWindow v-model="activeTab" class="disable-tab-transition" :touch="false">
|
|
||||||
<VWindowItem value="mysub">
|
<VWindowItem value="mysub">
|
||||||
<transition name="fade-slide" appear>
|
<transition name="fade-slide" appear>
|
||||||
<div>
|
<div>
|
||||||
@@ -152,6 +128,58 @@ const searchShares = () => {
|
|||||||
</VWindowItem>
|
</VWindowItem>
|
||||||
</VWindow>
|
</VWindow>
|
||||||
|
|
||||||
|
<!-- 订阅过滤弹窗 -->
|
||||||
|
<Teleport to="body" v-if="filterSubscribeDialog">
|
||||||
|
<VMenu
|
||||||
|
v-model="filterSubscribeDialog"
|
||||||
|
width="20rem"
|
||||||
|
:close-on-content-click="false"
|
||||||
|
:activator="filterActivator"
|
||||||
|
location="bottom end"
|
||||||
|
>
|
||||||
|
<VCard>
|
||||||
|
<VCardItem>
|
||||||
|
<VCardTitle>
|
||||||
|
<VIcon icon="mdi-filter-multiple-outline" class="mr-2" />
|
||||||
|
{{ t('subscribe.filterSubscriptions') }}
|
||||||
|
</VCardTitle>
|
||||||
|
<VDialogCloseBtn @click="filterSubscribeDialog = false" />
|
||||||
|
</VCardItem>
|
||||||
|
<VCardText>
|
||||||
|
<VTextField v-model="subscribeFilter" :label="t('subscribe.name')" clearable density="comfortable" />
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
</VMenu>
|
||||||
|
</Teleport>
|
||||||
|
|
||||||
|
<!-- 搜索订阅分享弹窗 -->
|
||||||
|
<Teleport to="body" v-if="searchShareDialog">
|
||||||
|
<VMenu
|
||||||
|
v-model="searchShareDialog"
|
||||||
|
width="25rem"
|
||||||
|
:close-on-content-click="false"
|
||||||
|
:activator="searchActivator"
|
||||||
|
location="bottom end"
|
||||||
|
>
|
||||||
|
<VCard>
|
||||||
|
<VCardItem>
|
||||||
|
<VCardTitle>
|
||||||
|
<VIcon icon="mdi-movie-search-outline" class="mr-2" />
|
||||||
|
{{ t('subscribe.searchShares') }}
|
||||||
|
</VCardTitle>
|
||||||
|
<VDialogCloseBtn @click="searchShareDialog = false" />
|
||||||
|
</VCardItem>
|
||||||
|
<VCardText>
|
||||||
|
<VTextField v-model="shareKeyword" :label="t('subscribe.keyword')" clearable density="comfortable">
|
||||||
|
<template #append>
|
||||||
|
<VBtn prepend-icon="mdi-magnify" color="primary" @click="searchShares">{{ t('common.search') }}</VBtn>
|
||||||
|
</template>
|
||||||
|
</VTextField>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
</VMenu>
|
||||||
|
</Teleport>
|
||||||
|
|
||||||
<!-- 订阅编辑弹窗 -->
|
<!-- 订阅编辑弹窗 -->
|
||||||
<SubscribeEditDialog
|
<SubscribeEditDialog
|
||||||
v-if="subscribeEditDialog"
|
v-if="subscribeEditDialog"
|
||||||
@@ -163,3 +191,9 @@ const searchShares = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.content-window {
|
||||||
|
margin-block-start: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user