refactor: implement lazy-loaded tab components with silent background data refresh for settings pages

This commit is contained in:
jxxghp
2026-05-17 14:17:50 +08:00
parent 0e005c3c7e
commit c5e2b1349f
12 changed files with 222 additions and 87 deletions

View File

@@ -8,10 +8,18 @@ import StorageCard from '@/components/cards/StorageCard.vue'
import { useI18n } from 'vue-i18n'
import { useTheme } from 'vuetify'
import { storageAttributes } from '@/api/constants'
import { useSilentSettingRefresh } from '@/composables/useSilentSettingRefresh'
const { t } = useI18n()
const { global: globalTheme } = useTheme()
const props = defineProps({
active: {
type: Boolean,
default: true,
},
})
// 拖拽排序和分类编辑弹窗按需加载,避免设置框架预加载目录页时带上这些交互依赖。
const Draggable = defineAsyncComponent(() => import('vuedraggable').then(module => module.default))
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(() => {
loadDirectories()
loadStorages()
loadMediaCategories()
loadSystemSettings()
loadPageData()
})
useSilentSettingRefresh(loadPageData, {
active: computed(() => props.active),
})
</script>

View File

@@ -6,6 +6,7 @@ import NotificationChannelCard from '@/components/cards/NotificationChannelCard.
import { useI18n } from 'vue-i18n'
import { notificationSwitchDict } from '@/api/constants'
import { useTheme, useDisplay } from 'vuetify'
import { useSilentSettingRefresh } from '@/composables/useSilentSettingRefresh'
// 显示器宽度
const display = useDisplay()
@@ -13,6 +14,13 @@ const display = useDisplay()
// 国际化
const { t } = useI18n()
const props = defineProps({
active: {
type: Boolean,
default: true,
},
})
// 通知渠道排序和进度弹窗按需加载,避免通知设置 chunk 直接包含拖拽库。
const Draggable = defineAsyncComponent(() => import('vuedraggable').then(module => module.default))
const ProgressDialog = defineAsyncComponent(() => import('@/components/dialog/ProgressDialog.vue'))
@@ -308,12 +316,22 @@ function getNotificationSwitchText(type: string | undefined) {
return notificationSwitchDict[type]
}
async function loadPageData() {
await Promise.all([
loadNotificationSetting(),
loadNotificationSwitchs(),
loadNotificationTime(),
loadTemplateConfigs(),
])
}
// 加载数据
onMounted(() => {
loadNotificationSetting()
loadNotificationSwitchs()
loadNotificationTime()
loadTemplateConfigs()
loadPageData()
})
useSilentSettingRefresh(loadPageData, {
active: computed(() => props.active && !editorVisible.value),
})
</script>

View File

@@ -7,10 +7,18 @@ import { CustomRule, FilterRuleGroup } from '@/api/types'
import CustomerRuleCard from '@/components/cards/CustomRuleCard.vue'
import FilterRuleGroupCard from '@/components/cards/FilterRuleGroupCard.vue'
import { useI18n } from 'vue-i18n'
import { useSilentSettingRefresh } from '@/composables/useSilentSettingRefresh'
// 国际化
const { t } = useI18n()
const props = defineProps({
active: {
type: Boolean,
default: true,
},
})
// 拖拽库和导入弹窗只在规则编辑交互中需要,拆出设置页入口 chunk。
const Draggable = defineAsyncComponent(() => import('vuedraggable').then(module => module.default))
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(() => {
loadMediaCategories()
queryCustomRules()
queryFilterRuleGroups()
queryTorrentPriority()
loadPageData()
})
useSilentSettingRefresh(loadPageData, {
active: computed(() => props.active),
})
</script>

View File

@@ -3,10 +3,18 @@ import { useToast } from 'vue-toastification'
import api from '@/api'
import type { FilterRuleGroup, Site } from '@/api/types'
import { useI18n } from 'vue-i18n'
import { useSilentSettingRefresh } from '@/composables/useSilentSettingRefresh'
// 国际化
const { t } = useI18n()
const props = defineProps({
active: {
type: Boolean,
default: true,
},
})
// 提示框
const $toast = useToast()
@@ -176,12 +184,16 @@ async function loadSystemSettings() {
}
}
async function loadPageData() {
await Promise.all([querySites(), queryFilterRuleGroups(), querySelectedSites(), loadSearchSetting(), loadSystemSettings()])
}
onMounted(() => {
querySites()
queryFilterRuleGroups()
querySelectedSites()
loadSearchSetting()
loadSystemSettings()
loadPageData()
})
useSilentSettingRefresh(loadPageData, {
active: computed(() => props.active),
})
</script>

View File

@@ -3,10 +3,18 @@ import { useToast } from 'vue-toastification'
import api from '@/api'
import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
import { useI18n } from 'vue-i18n'
import { useSilentSettingRefresh } from '@/composables/useSilentSettingRefresh'
// 国际化
const { t } = useI18n()
const props = defineProps({
active: {
type: Boolean,
default: true,
},
})
// 提示框
const $toast = useToast()
@@ -122,6 +130,10 @@ async function saveSiteSetting(value: { [key: string]: any }) {
onMounted(() => {
loadSiteSettings()
})
useSilentSettingRefresh(loadSiteSettings, {
active: computed(() => props.active),
})
</script>
<template>

View File

@@ -4,10 +4,18 @@ import api from '@/api'
import type { FilterRuleGroup, Site } from '@/api/types'
import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
import { useI18n } from 'vue-i18n'
import { useSilentSettingRefresh } from '@/composables/useSilentSettingRefresh'
// 国际化
const { t } = useI18n()
const props = defineProps({
active: {
type: Boolean,
default: true,
},
})
// 提示框
const $toast = useToast()
@@ -184,12 +192,22 @@ async function saveSubscribeSetting() {
}
}
async function loadPageData() {
await Promise.all([
querySites(),
queryFilterRuleGroups(),
querySelectedRssSites(),
querySubscribeRules(),
loadSystemSettings(),
])
}
onMounted(() => {
querySites()
queryFilterRuleGroups()
querySelectedRssSites()
querySubscribeRules()
loadSystemSettings()
loadPageData()
})
useSilentSettingRefresh(loadPageData, {
active: computed(() => props.active),
})
</script>

View File

@@ -10,6 +10,7 @@ import { useI18n } from 'vue-i18n'
import { downloaderOptions, mediaServerOptions } from '@/api/constants'
import { useDisplay, useTheme } from 'vuetify'
import { useLlmProviderDirectory } from '@/composables/useLlmProviderDirectory'
import { useSilentSettingRefresh } from '@/composables/useSilentSettingRefresh'
const display = useDisplay()
const theme = useTheme()
@@ -19,6 +20,13 @@ const isTransparentTheme = computed(() => theme.name.value === 'transparent')
// 国际化
const { t } = useI18n()
const props = defineProps({
active: {
type: Boolean,
default: true,
},
})
// 下载器/媒体服务器排序和进度弹窗按需加载,降低系统设置页入口解析量。
const Draggable = defineAsyncComponent(() => import('vuedraggable').then(module => module.default))
const ProgressDialog = defineAsyncComponent(() => import('@/components/dialog/ProgressDialog.vue'))
@@ -847,12 +855,11 @@ async function saveScrapingSwitchs() {
}
// 加载数据
onMounted(() => {
loadDownloaderSetting()
loadMediaServerSetting()
loadSystemSettings()
loadScrapingSwitchs()
})
async function loadPageData() {
await Promise.all([loadDownloaderSetting(), loadMediaServerSetting(), loadSystemSettings(), loadScrapingSwitchs()])
}
onMounted(loadPageData)
onActivated(async () => {
isRequest.value = true
@@ -866,6 +873,16 @@ onBeforeUnmount(() => {
invalidateLlmTestState()
})
useSilentSettingRefresh(
async () => {
if (progressDialog.value || advancedDialog.value || testingLlm.value || savingBasic.value) return
await loadPageData()
},
{
active: computed(() => props.active),
},
)
watch(currentLlmSnapshotKey, (snapshotKey, previousSnapshotKey) => {
if (snapshotKey !== previousSnapshotKey) invalidateLlmTestState()
})

View File

@@ -231,6 +231,10 @@ onMounted(getModules)
.system-health-check {
display: flex;
flex-direction: column;
flex: 1 1 auto;
block-size: 100%;
min-block-size: 0;
overflow: hidden;
}
.progress-container {
@@ -316,6 +320,7 @@ onMounted(getModules)
flex: 1;
min-block-size: 0;
overflow-y: auto;
overscroll-behavior: contain;
padding-block: 0 16px;
padding-inline: 16px;
}