mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-07-10 23:13:20 +08:00
refactor: implement lazy-loaded tab components with silent background data refresh for settings pages
This commit is contained in:
33
src/composables/useSilentSettingRefresh.ts
Normal file
33
src/composables/useSilentSettingRefresh.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { type MaybeRefOrGetter, toValue } from 'vue'
|
||||||
|
import { useKeepAliveRefresh, type KeepAliveRefreshContext } from '@/composables/useKeepAliveRefresh'
|
||||||
|
|
||||||
|
type RefreshHandler = (context?: KeepAliveRefreshContext) => void | Promise<void>
|
||||||
|
|
||||||
|
interface SilentSettingRefreshOptions {
|
||||||
|
active?: MaybeRefOrGetter<boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEditingFormField() {
|
||||||
|
if (typeof document === 'undefined') return false
|
||||||
|
|
||||||
|
const element = document.activeElement
|
||||||
|
if (!(element instanceof HTMLElement)) return false
|
||||||
|
|
||||||
|
// 设置页大多是可编辑表单,正在输入时跳过静默刷新,避免覆盖用户未保存内容。
|
||||||
|
return Boolean(element.closest('input, textarea, select, [contenteditable="true"], .ace_text-input'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置面板重新可见时静默刷新数据;如果用户正在编辑表单,则本轮刷新让路给输入体验。
|
||||||
|
*/
|
||||||
|
export function useSilentSettingRefresh(refresh: RefreshHandler, options: SilentSettingRefreshOptions = {}) {
|
||||||
|
return useKeepAliveRefresh(
|
||||||
|
async context => {
|
||||||
|
if (context?.silent && isEditingFormField()) return
|
||||||
|
await refresh(context)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: options.active === undefined ? undefined : () => Boolean(toValue(options.active)),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -433,7 +433,7 @@ onMounted(() => {
|
|||||||
scrollable
|
scrollable
|
||||||
:fullscreen="!display.mdAndUp.value"
|
:fullscreen="!display.mdAndUp.value"
|
||||||
>
|
>
|
||||||
<VCard>
|
<VCard class="system-health-dialog-card">
|
||||||
<VCardItem>
|
<VCardItem>
|
||||||
<VCardTitle>
|
<VCardTitle>
|
||||||
<VIcon icon="mdi-cog" class="me-2" />
|
<VIcon icon="mdi-cog" class="me-2" />
|
||||||
@@ -442,7 +442,7 @@ onMounted(() => {
|
|||||||
<VDialogCloseBtn @click="systemTestDialog = false" />
|
<VDialogCloseBtn @click="systemTestDialog = false" />
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<VCardText class="pa-0">
|
<VCardText class="system-health-dialog-body pa-0">
|
||||||
<ModuleTestView />
|
<ModuleTestView />
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
@@ -492,3 +492,24 @@ onMounted(() => {
|
|||||||
</VCard>
|
</VCard>
|
||||||
</VDialog>
|
</VDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.system-health-dialog-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.system-health-dialog-body {
|
||||||
|
/* 弹窗正文本身不滚动,滚动只交给健康检查结果列表。 */
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
block-size: min(42rem, calc(100dvh - 8rem - env(safe-area-inset-top) - env(safe-area-inset-bottom)));
|
||||||
|
min-block-size: 0;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.v-dialog--fullscreen) .system-health-dialog-body {
|
||||||
|
block-size: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -19,6 +19,26 @@ const AccountSettingSearch = defineAsyncComponent(() => import('@/views/setting/
|
|||||||
const AccountSettingSubscribe = defineAsyncComponent(() => import('@/views/setting/AccountSettingSubscribe.vue'))
|
const AccountSettingSubscribe = defineAsyncComponent(() => import('@/views/setting/AccountSettingSubscribe.vue'))
|
||||||
const AccountSettingNotification = defineAsyncComponent(() => import('@/views/setting/AccountSettingNotification.vue'))
|
const AccountSettingNotification = defineAsyncComponent(() => import('@/views/setting/AccountSettingNotification.vue'))
|
||||||
|
|
||||||
|
const visitedTabs = ref(new Set<string>())
|
||||||
|
|
||||||
|
const settingTabComponents = [
|
||||||
|
{ value: 'system', component: AccountSettingSystem },
|
||||||
|
{ value: 'directory', component: AccountSettingDirectory },
|
||||||
|
{ value: 'site', component: AccountSettingSite },
|
||||||
|
{ value: 'rule', component: AccountSettingRule },
|
||||||
|
{ value: 'search', component: AccountSettingSearch },
|
||||||
|
{ value: 'subscribe', component: AccountSettingSubscribe },
|
||||||
|
{ value: 'notification', component: AccountSettingNotification },
|
||||||
|
]
|
||||||
|
|
||||||
|
function markTabVisited(tab: string) {
|
||||||
|
if (!tab) return
|
||||||
|
|
||||||
|
const nextTabs = new Set(visitedTabs.value)
|
||||||
|
nextTabs.add(tab)
|
||||||
|
visitedTabs.value = nextTabs
|
||||||
|
}
|
||||||
|
|
||||||
// 使用动态标签页
|
// 使用动态标签页
|
||||||
const { registerHeaderTab } = useDynamicHeaderTab()
|
const { registerHeaderTab } = useDynamicHeaderTab()
|
||||||
|
|
||||||
@@ -34,71 +54,23 @@ onMounted(() => {
|
|||||||
if (!activeTab.value && settingTabs.value.length > 0) {
|
if (!activeTab.value && settingTabs.value.length > 0) {
|
||||||
activeTab.value = settingTabs.value[0].tab
|
activeTab.value = settingTabs.value[0].tab
|
||||||
}
|
}
|
||||||
|
markTabVisited(activeTab.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(activeTab, markTabVisited, { immediate: true })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VWindow v-model="activeTab" class="disable-tab-transition" :touch="false">
|
<VWindow v-model="activeTab" class="disable-tab-transition" :touch="false">
|
||||||
<!-- 系统 -->
|
<VWindowItem v-for="item in settingTabComponents" :key="item.value" :value="item.value">
|
||||||
<VWindowItem value="system">
|
|
||||||
<transition name="fade-slide" appear>
|
<transition name="fade-slide" appear>
|
||||||
<div>
|
<div>
|
||||||
<AccountSettingSystem />
|
<component
|
||||||
</div>
|
:is="item.component"
|
||||||
</transition>
|
v-if="visitedTabs.has(item.value)"
|
||||||
</VWindowItem>
|
:active="activeTab === item.value"
|
||||||
|
/>
|
||||||
<!-- 目录 -->
|
|
||||||
<VWindowItem value="directory">
|
|
||||||
<transition name="fade-slide" appear>
|
|
||||||
<div>
|
|
||||||
<AccountSettingDirectory />
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</VWindowItem>
|
|
||||||
|
|
||||||
<!-- 站点 -->
|
|
||||||
<VWindowItem value="site">
|
|
||||||
<transition name="fade-slide" appear>
|
|
||||||
<div>
|
|
||||||
<AccountSettingSite />
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</VWindowItem>
|
|
||||||
|
|
||||||
<!-- 规则 -->
|
|
||||||
<VWindowItem value="rule">
|
|
||||||
<transition name="fade-slide" appear>
|
|
||||||
<div>
|
|
||||||
<AccountSettingRule />
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</VWindowItem>
|
|
||||||
|
|
||||||
<!-- 搜索 -->
|
|
||||||
<VWindowItem value="search">
|
|
||||||
<transition name="fade-slide" appear>
|
|
||||||
<div>
|
|
||||||
<AccountSettingSearch />
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</VWindowItem>
|
|
||||||
|
|
||||||
<!-- 订阅 -->
|
|
||||||
<VWindowItem value="subscribe">
|
|
||||||
<transition name="fade-slide" appear>
|
|
||||||
<div>
|
|
||||||
<AccountSettingSubscribe />
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</VWindowItem>
|
|
||||||
|
|
||||||
<!-- 通知 -->
|
|
||||||
<VWindowItem value="notification">
|
|
||||||
<transition name="fade-slide" appear>
|
|
||||||
<div>
|
|
||||||
<AccountSettingNotification />
|
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</VWindowItem>
|
</VWindowItem>
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ const router = createRouter({
|
|||||||
path: '/setting',
|
path: '/setting',
|
||||||
component: () => import('../pages/setting.vue'),
|
component: () => import('../pages/setting.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
|
keepAlive: true,
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,10 +8,18 @@ import StorageCard from '@/components/cards/StorageCard.vue'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useTheme } from 'vuetify'
|
import { useTheme } from 'vuetify'
|
||||||
import { storageAttributes } from '@/api/constants'
|
import { storageAttributes } from '@/api/constants'
|
||||||
|
import { useSilentSettingRefresh } from '@/composables/useSilentSettingRefresh'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { global: globalTheme } = useTheme()
|
const { global: globalTheme } = useTheme()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
active: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// 拖拽排序和分类编辑弹窗按需加载,避免设置框架预加载目录页时带上这些交互依赖。
|
// 拖拽排序和分类编辑弹窗按需加载,避免设置框架预加载目录页时带上这些交互依赖。
|
||||||
const Draggable = defineAsyncComponent(() => import('vuedraggable').then(module => module.default))
|
const Draggable = defineAsyncComponent(() => import('vuedraggable').then(module => module.default))
|
||||||
const ProgressDialog = defineAsyncComponent(() => import('@/components/dialog/ProgressDialog.vue'))
|
const ProgressDialog = defineAsyncComponent(() => import('@/components/dialog/ProgressDialog.vue'))
|
||||||
@@ -247,12 +255,17 @@ async function saveSystemSettings(value: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadPageData() {
|
||||||
|
await Promise.all([loadDirectories(), loadStorages(), loadMediaCategories(), loadSystemSettings()])
|
||||||
|
}
|
||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadDirectories()
|
loadPageData()
|
||||||
loadStorages()
|
})
|
||||||
loadMediaCategories()
|
|
||||||
loadSystemSettings()
|
useSilentSettingRefresh(loadPageData, {
|
||||||
|
active: computed(() => props.active),
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import NotificationChannelCard from '@/components/cards/NotificationChannelCard.
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { notificationSwitchDict } from '@/api/constants'
|
import { notificationSwitchDict } from '@/api/constants'
|
||||||
import { useTheme, useDisplay } from 'vuetify'
|
import { useTheme, useDisplay } from 'vuetify'
|
||||||
|
import { useSilentSettingRefresh } from '@/composables/useSilentSettingRefresh'
|
||||||
|
|
||||||
// 显示器宽度
|
// 显示器宽度
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
@@ -13,6 +14,13 @@ const display = useDisplay()
|
|||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
active: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// 通知渠道排序和进度弹窗按需加载,避免通知设置 chunk 直接包含拖拽库。
|
// 通知渠道排序和进度弹窗按需加载,避免通知设置 chunk 直接包含拖拽库。
|
||||||
const Draggable = defineAsyncComponent(() => import('vuedraggable').then(module => module.default))
|
const Draggable = defineAsyncComponent(() => import('vuedraggable').then(module => module.default))
|
||||||
const ProgressDialog = defineAsyncComponent(() => import('@/components/dialog/ProgressDialog.vue'))
|
const ProgressDialog = defineAsyncComponent(() => import('@/components/dialog/ProgressDialog.vue'))
|
||||||
@@ -308,12 +316,22 @@ function getNotificationSwitchText(type: string | undefined) {
|
|||||||
return notificationSwitchDict[type]
|
return notificationSwitchDict[type]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadPageData() {
|
||||||
|
await Promise.all([
|
||||||
|
loadNotificationSetting(),
|
||||||
|
loadNotificationSwitchs(),
|
||||||
|
loadNotificationTime(),
|
||||||
|
loadTemplateConfigs(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadNotificationSetting()
|
loadPageData()
|
||||||
loadNotificationSwitchs()
|
})
|
||||||
loadNotificationTime()
|
|
||||||
loadTemplateConfigs()
|
useSilentSettingRefresh(loadPageData, {
|
||||||
|
active: computed(() => props.active && !editorVisible.value),
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,18 @@ import { CustomRule, FilterRuleGroup } from '@/api/types'
|
|||||||
import CustomerRuleCard from '@/components/cards/CustomRuleCard.vue'
|
import CustomerRuleCard from '@/components/cards/CustomRuleCard.vue'
|
||||||
import FilterRuleGroupCard from '@/components/cards/FilterRuleGroupCard.vue'
|
import FilterRuleGroupCard from '@/components/cards/FilterRuleGroupCard.vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useSilentSettingRefresh } from '@/composables/useSilentSettingRefresh'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
active: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// 拖拽库和导入弹窗只在规则编辑交互中需要,拆出设置页入口 chunk。
|
// 拖拽库和导入弹窗只在规则编辑交互中需要,拆出设置页入口 chunk。
|
||||||
const Draggable = defineAsyncComponent(() => import('vuedraggable').then(module => module.default))
|
const Draggable = defineAsyncComponent(() => import('vuedraggable').then(module => module.default))
|
||||||
const ImportCodeDialog = defineAsyncComponent(() => import('@/components/dialog/ImportCodeDialog.vue'))
|
const ImportCodeDialog = defineAsyncComponent(() => import('@/components/dialog/ImportCodeDialog.vue'))
|
||||||
@@ -365,12 +373,17 @@ async function saveTorrentPriority() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadPageData() {
|
||||||
|
await Promise.all([loadMediaCategories(), queryCustomRules(), queryFilterRuleGroups(), queryTorrentPriority()])
|
||||||
|
}
|
||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadMediaCategories()
|
loadPageData()
|
||||||
queryCustomRules()
|
})
|
||||||
queryFilterRuleGroups()
|
|
||||||
queryTorrentPriority()
|
useSilentSettingRefresh(loadPageData, {
|
||||||
|
active: computed(() => props.active),
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,18 @@ import { useToast } from 'vue-toastification'
|
|||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { FilterRuleGroup, Site } from '@/api/types'
|
import type { FilterRuleGroup, Site } from '@/api/types'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useSilentSettingRefresh } from '@/composables/useSilentSettingRefresh'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
active: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// 提示框
|
// 提示框
|
||||||
const $toast = useToast()
|
const $toast = useToast()
|
||||||
|
|
||||||
@@ -176,12 +184,16 @@ async function loadSystemSettings() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadPageData() {
|
||||||
|
await Promise.all([querySites(), queryFilterRuleGroups(), querySelectedSites(), loadSearchSetting(), loadSystemSettings()])
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
querySites()
|
loadPageData()
|
||||||
queryFilterRuleGroups()
|
})
|
||||||
querySelectedSites()
|
|
||||||
loadSearchSetting()
|
useSilentSettingRefresh(loadPageData, {
|
||||||
loadSystemSettings()
|
active: computed(() => props.active),
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,18 @@ import { useToast } from 'vue-toastification'
|
|||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
|
import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useSilentSettingRefresh } from '@/composables/useSilentSettingRefresh'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
active: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// 提示框
|
// 提示框
|
||||||
const $toast = useToast()
|
const $toast = useToast()
|
||||||
|
|
||||||
@@ -122,6 +130,10 @@ async function saveSiteSetting(value: { [key: string]: any }) {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadSiteSettings()
|
loadSiteSettings()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useSilentSettingRefresh(loadSiteSettings, {
|
||||||
|
active: computed(() => props.active),
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -4,10 +4,18 @@ import api from '@/api'
|
|||||||
import type { FilterRuleGroup, Site } from '@/api/types'
|
import type { FilterRuleGroup, Site } from '@/api/types'
|
||||||
import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
|
import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useSilentSettingRefresh } from '@/composables/useSilentSettingRefresh'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
active: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// 提示框
|
// 提示框
|
||||||
const $toast = useToast()
|
const $toast = useToast()
|
||||||
|
|
||||||
@@ -184,12 +192,22 @@ async function saveSubscribeSetting() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadPageData() {
|
||||||
|
await Promise.all([
|
||||||
|
querySites(),
|
||||||
|
queryFilterRuleGroups(),
|
||||||
|
querySelectedRssSites(),
|
||||||
|
querySubscribeRules(),
|
||||||
|
loadSystemSettings(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
querySites()
|
loadPageData()
|
||||||
queryFilterRuleGroups()
|
})
|
||||||
querySelectedRssSites()
|
|
||||||
querySubscribeRules()
|
useSilentSettingRefresh(loadPageData, {
|
||||||
loadSystemSettings()
|
active: computed(() => props.active),
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useI18n } from 'vue-i18n'
|
|||||||
import { downloaderOptions, mediaServerOptions } from '@/api/constants'
|
import { downloaderOptions, mediaServerOptions } from '@/api/constants'
|
||||||
import { useDisplay, useTheme } from 'vuetify'
|
import { useDisplay, useTheme } from 'vuetify'
|
||||||
import { useLlmProviderDirectory } from '@/composables/useLlmProviderDirectory'
|
import { useLlmProviderDirectory } from '@/composables/useLlmProviderDirectory'
|
||||||
|
import { useSilentSettingRefresh } from '@/composables/useSilentSettingRefresh'
|
||||||
|
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
@@ -19,6 +20,13 @@ const isTransparentTheme = computed(() => theme.name.value === 'transparent')
|
|||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
active: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// 下载器/媒体服务器排序和进度弹窗按需加载,降低系统设置页入口解析量。
|
// 下载器/媒体服务器排序和进度弹窗按需加载,降低系统设置页入口解析量。
|
||||||
const Draggable = defineAsyncComponent(() => import('vuedraggable').then(module => module.default))
|
const Draggable = defineAsyncComponent(() => import('vuedraggable').then(module => module.default))
|
||||||
const ProgressDialog = defineAsyncComponent(() => import('@/components/dialog/ProgressDialog.vue'))
|
const ProgressDialog = defineAsyncComponent(() => import('@/components/dialog/ProgressDialog.vue'))
|
||||||
@@ -847,12 +855,11 @@ async function saveScrapingSwitchs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
onMounted(() => {
|
async function loadPageData() {
|
||||||
loadDownloaderSetting()
|
await Promise.all([loadDownloaderSetting(), loadMediaServerSetting(), loadSystemSettings(), loadScrapingSwitchs()])
|
||||||
loadMediaServerSetting()
|
}
|
||||||
loadSystemSettings()
|
|
||||||
loadScrapingSwitchs()
|
onMounted(loadPageData)
|
||||||
})
|
|
||||||
|
|
||||||
onActivated(async () => {
|
onActivated(async () => {
|
||||||
isRequest.value = true
|
isRequest.value = true
|
||||||
@@ -866,6 +873,16 @@ onBeforeUnmount(() => {
|
|||||||
invalidateLlmTestState()
|
invalidateLlmTestState()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useSilentSettingRefresh(
|
||||||
|
async () => {
|
||||||
|
if (progressDialog.value || advancedDialog.value || testingLlm.value || savingBasic.value) return
|
||||||
|
await loadPageData()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: computed(() => props.active),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
watch(currentLlmSnapshotKey, (snapshotKey, previousSnapshotKey) => {
|
watch(currentLlmSnapshotKey, (snapshotKey, previousSnapshotKey) => {
|
||||||
if (snapshotKey !== previousSnapshotKey) invalidateLlmTestState()
|
if (snapshotKey !== previousSnapshotKey) invalidateLlmTestState()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -231,6 +231,10 @@ onMounted(getModules)
|
|||||||
.system-health-check {
|
.system-health-check {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
block-size: 100%;
|
||||||
|
min-block-size: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-container {
|
.progress-container {
|
||||||
@@ -316,6 +320,7 @@ onMounted(getModules)
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-block-size: 0;
|
min-block-size: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
overscroll-behavior: contain;
|
||||||
padding-block: 0 16px;
|
padding-block: 0 16px;
|
||||||
padding-inline: 16px;
|
padding-inline: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user