mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-03 23:01:04 +08:00
refactor: standardize keep-alive data refreshing using useKeepAliveRefresh composable across views and dashboards
This commit is contained in:
@@ -6,17 +6,10 @@ import { type PropType } from 'vue'
|
||||
const elementProps = defineProps({
|
||||
config: Object as PropType<RenderProps>,
|
||||
})
|
||||
// key
|
||||
const componentKey = ref(0)
|
||||
|
||||
onActivated(() => {
|
||||
componentKey.value++
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Component
|
||||
:key="componentKey"
|
||||
:is="elementProps.config?.component"
|
||||
v-if="!elementProps.config?.html"
|
||||
v-bind="elementProps.config?.props"
|
||||
@@ -34,7 +27,6 @@ onActivated(() => {
|
||||
/>
|
||||
</Component>
|
||||
<Component
|
||||
:key="componentKey"
|
||||
:is="elementProps.config?.component"
|
||||
v-if="elementProps.config?.html"
|
||||
v-bind="elementProps.config?.props"
|
||||
|
||||
@@ -22,6 +22,7 @@ export function useKeepAliveRefresh(refresh: RefreshHandler, options: KeepAliveR
|
||||
let activatedCount = 0
|
||||
let refreshing = false
|
||||
let pendingRefresh = false
|
||||
let refreshScheduled = false
|
||||
|
||||
const isActive = () => options.active === undefined || Boolean(toValue(options.active))
|
||||
|
||||
@@ -48,7 +49,14 @@ export function useKeepAliveRefresh(refresh: RefreshHandler, options: KeepAliveR
|
||||
}
|
||||
|
||||
function requestRefresh() {
|
||||
void nextTick(runRefresh)
|
||||
// 同一轮激活里可能同时触发路由激活和 tab 激活,合并成一次静默刷新。
|
||||
if (refreshScheduled) return
|
||||
|
||||
refreshScheduled = true
|
||||
void nextTick(async () => {
|
||||
refreshScheduled = false
|
||||
await runRefresh()
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -280,6 +280,40 @@ async function getPluginDashboardMeta() {
|
||||
}
|
||||
}
|
||||
|
||||
function clearPluginDashboardTimer(pluginDashboardId: string) {
|
||||
if (!refreshTimers.value[pluginDashboardId]) return
|
||||
|
||||
clearTimeout(refreshTimers.value[pluginDashboardId])
|
||||
delete refreshTimers.value[pluginDashboardId]
|
||||
}
|
||||
|
||||
function schedulePluginDashboardRefresh(item: DashboardItem) {
|
||||
const pluginDashboardId = buildPluginDashboardId(item.id, item.key)
|
||||
clearPluginDashboardTimer(pluginDashboardId)
|
||||
|
||||
if (
|
||||
item.attrs?.refresh &&
|
||||
pluginDashboardRefreshStatus.value[pluginDashboardId] &&
|
||||
enableConfig.value[pluginDashboardId] &&
|
||||
isRequest.value
|
||||
) {
|
||||
refreshTimers.value[pluginDashboardId] = setTimeout(() => {
|
||||
getPluginDashboard(item.id, item.key)
|
||||
}, item.attrs.refresh * 1000)
|
||||
}
|
||||
}
|
||||
|
||||
function refreshEnabledPluginDashboards() {
|
||||
if (!superUser || isNullOrEmptyObject(pluginDashboardMeta.value)) return
|
||||
|
||||
pluginDashboardMeta.value.forEach((pluginDashboard: { id: string; key: string }) => {
|
||||
const pluginDashboardId = buildPluginDashboardId(pluginDashboard.id, pluginDashboard.key)
|
||||
if (enableConfig.value[pluginDashboardId]) {
|
||||
getPluginDashboard(pluginDashboard.id, pluginDashboard.key)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取一个插件的仪表板配置项
|
||||
async function getPluginDashboard(id: string, key: string) {
|
||||
try {
|
||||
@@ -309,22 +343,7 @@ async function getPluginDashboard(id: string, key: string) {
|
||||
}
|
||||
const pluginDashboardId = buildPluginDashboardId(id, key)
|
||||
// 定时刷新
|
||||
if (
|
||||
res.attrs?.refresh &&
|
||||
pluginDashboardRefreshStatus.value[pluginDashboardId] &&
|
||||
enableConfig.value[pluginDashboardId] &&
|
||||
isRequest.value
|
||||
) {
|
||||
// 清除之前的定时器
|
||||
if (refreshTimers.value[pluginDashboardId]) {
|
||||
clearTimeout(refreshTimers.value[pluginDashboardId])
|
||||
}
|
||||
// 设置新的定时器
|
||||
let timer = setTimeout(() => {
|
||||
getPluginDashboard(id, key)
|
||||
}, res.attrs.refresh * 1000)
|
||||
refreshTimers.value[pluginDashboardId] = timer
|
||||
}
|
||||
schedulePluginDashboardRefresh(res)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
@@ -346,10 +365,12 @@ onBeforeMount(async () => {
|
||||
|
||||
onActivated(() => {
|
||||
isRequest.value = true
|
||||
refreshEnabledPluginDashboards()
|
||||
})
|
||||
|
||||
onDeactivated(() => {
|
||||
isRequest.value = false
|
||||
Object.keys(refreshTimers.value).forEach(clearPluginDashboardTimer)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -61,10 +61,10 @@ onActivated(async () => {
|
||||
<template>
|
||||
<div v-if="downloaders.length > 0">
|
||||
<VWindow v-model="activeTab" class="disable-tab-transition" :touch="false">
|
||||
<VWindowItem v-for="item in downloaders" :value="item.name">
|
||||
<VWindowItem v-for="item in downloaders" :key="item.name" :value="item.name">
|
||||
<transition name="fade-slide" appear>
|
||||
<div>
|
||||
<DownloadingListView :name="item.name" />
|
||||
<DownloadingListView :name="item.name" :active="activeTab === item.name" />
|
||||
</div>
|
||||
</transition>
|
||||
</VWindowItem>
|
||||
|
||||
@@ -296,14 +296,14 @@ onMounted(() => {
|
||||
<VWindowItem value="popular">
|
||||
<transition name="fade-slide" appear>
|
||||
<div>
|
||||
<SubscribePopularView :type="subType" :active="activeTab === 'popular'" />
|
||||
<SubscribePopularView :type="subType" />
|
||||
</div>
|
||||
</transition>
|
||||
</VWindowItem>
|
||||
<VWindowItem value="share">
|
||||
<transition name="fade-slide" appear>
|
||||
<div>
|
||||
<SubscribeShareView :keyword="shareKeyword" :active="activeTab === 'share'" />
|
||||
<SubscribeShareView :keyword="shareKeyword" />
|
||||
</div>
|
||||
</transition>
|
||||
</VWindowItem>
|
||||
|
||||
@@ -15,7 +15,6 @@ const route = useRoute()
|
||||
const { appMode } = usePWA()
|
||||
|
||||
const activeTab = ref((route.query.tab as string) || 'list')
|
||||
const listViewKey = ref(0)
|
||||
const workflowListViewRef = ref<InstanceType<typeof WorkflowListView> | null>(null)
|
||||
|
||||
// 获取标签页
|
||||
@@ -37,6 +36,10 @@ function openAddWorkflowDialog() {
|
||||
workflowListViewRef.value?.openAddDialog()
|
||||
}
|
||||
|
||||
function refreshWorkflowList() {
|
||||
workflowListViewRef.value?.refresh()
|
||||
}
|
||||
|
||||
const shareKeywordUpdater = debounce((keyword: string) => {
|
||||
shareKeyword.value = keyword.trim()
|
||||
}, 300)
|
||||
@@ -98,14 +101,14 @@ onMounted(() => {
|
||||
<VWindowItem value="list">
|
||||
<transition name="fade-slide" appear>
|
||||
<div>
|
||||
<WorkflowListView ref="workflowListViewRef" :key="listViewKey" />
|
||||
<WorkflowListView ref="workflowListViewRef" />
|
||||
</div>
|
||||
</transition>
|
||||
</VWindowItem>
|
||||
<VWindowItem value="share">
|
||||
<transition name="fade-slide" appear>
|
||||
<div>
|
||||
<WorkflowShareView :keyword="shareKeyword" @update="listViewKey++" />
|
||||
<WorkflowShareView :keyword="shareKeyword" @update="refreshWorkflowList" />
|
||||
</div>
|
||||
</transition>
|
||||
</VWindowItem>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { hexToRgb } from '@layouts/utils'
|
||||
import api from '@/api'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useBackground } from '@/composables/useBackground'
|
||||
import { useKeepAliveRefresh } from '@/composables/useKeepAliveRefresh'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -29,8 +30,6 @@ const variableTheme = controlledComputed(
|
||||
() => vuetifyTheme.current.value.variables,
|
||||
)
|
||||
|
||||
const chartKey = ref(0)
|
||||
|
||||
// 时间序列
|
||||
const series = ref([
|
||||
{
|
||||
@@ -123,18 +122,14 @@ async function loadCpuData() {
|
||||
}
|
||||
|
||||
// 使用数据刷新定时器
|
||||
const { loading } = useDataRefresh(
|
||||
const { loading, refresh } = useDataRefresh(
|
||||
'analytics-cpu',
|
||||
loadCpuData,
|
||||
2000, // 2秒间隔
|
||||
true // 立即执行
|
||||
)
|
||||
|
||||
onActivated(() => {
|
||||
nextTick(() => {
|
||||
chartKey.value += 1
|
||||
})
|
||||
})
|
||||
useKeepAliveRefresh(refresh)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -148,7 +143,7 @@ onActivated(() => {
|
||||
<VCardTitle>CPU</VCardTitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VApexChart :key="chartKey" type="line" :options="chartOptions" :series="series" :height="150" />
|
||||
<VApexChart type="line" :options="chartOptions" :series="series" :height="150" />
|
||||
<p class="text-center font-weight-medium mb-0">{{ t('dashboard.current') }}:{{ current }}%</p>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
@@ -5,6 +5,7 @@ import api from '@/api'
|
||||
import { formatBytes } from '@/@core/utils/formatters'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useBackground } from '@/composables/useBackground'
|
||||
import { useKeepAliveRefresh } from '@/composables/useKeepAliveRefresh'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -30,8 +31,6 @@ const variableTheme = controlledComputed(
|
||||
() => vuetifyTheme.current.value.variables,
|
||||
)
|
||||
|
||||
const chartKey = ref(0)
|
||||
|
||||
// 时间序列
|
||||
const series = ref([
|
||||
{
|
||||
@@ -128,19 +127,14 @@ async function loadMemoryData() {
|
||||
}
|
||||
|
||||
// 使用数据刷新定时器
|
||||
const { loading } = useDataRefresh(
|
||||
const { loading, refresh } = useDataRefresh(
|
||||
'analytics-memory',
|
||||
loadMemoryData,
|
||||
3000, // 3秒间隔
|
||||
true // 立即执行
|
||||
)
|
||||
|
||||
onActivated(() => {
|
||||
// 使用nextTick确保DOM准备完成后再更新chartKey
|
||||
nextTick(() => {
|
||||
chartKey.value += 1
|
||||
})
|
||||
})
|
||||
useKeepAliveRefresh(refresh)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -154,7 +148,7 @@ onActivated(() => {
|
||||
<VCardTitle>{{ t('dashboard.memory') }}</VCardTitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VApexChart :key="chartKey" type="area" :options="chartOptions" :series="series" :height="150" />
|
||||
<VApexChart type="area" :options="chartOptions" :series="series" :height="150" />
|
||||
<p class="text-center font-weight-medium mb-0">{{ t('dashboard.current') }}:{{ formatBytes(usedMemory) }}</p>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { hexToRgb } from '@layouts/utils'
|
||||
import api from '@/api'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useBackground } from '@/composables/useBackground'
|
||||
import { useKeepAliveRefresh } from '@/composables/useKeepAliveRefresh'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -29,8 +30,6 @@ const variableTheme = controlledComputed(
|
||||
() => vuetifyTheme.current.value.variables,
|
||||
)
|
||||
|
||||
const chartKey = ref(0)
|
||||
|
||||
// 时间序列 - 上行和下行流量
|
||||
const series = ref([
|
||||
{
|
||||
@@ -161,18 +160,14 @@ async function getNetworkUsage() {
|
||||
}
|
||||
|
||||
// 使用数据刷新定时器
|
||||
useDataRefresh(
|
||||
const { refresh } = useDataRefresh(
|
||||
'dashboard-network',
|
||||
getNetworkUsage,
|
||||
2000, // 2秒间隔
|
||||
true // 立即执行
|
||||
)
|
||||
|
||||
onActivated(() => {
|
||||
nextTick(() => {
|
||||
chartKey.value += 1
|
||||
})
|
||||
})
|
||||
useKeepAliveRefresh(refresh)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -186,7 +181,7 @@ onActivated(() => {
|
||||
<VCardTitle>{{ t('dashboard.network') }}</VCardTitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VApexChart :key="chartKey" type="line" :options="chartOptions" :series="series" :height="150" />
|
||||
<VApexChart type="line" :options="chartOptions" :series="series" :height="150" />
|
||||
<div class="d-flex justify-space-between">
|
||||
<p class="text-center font-weight-medium mb-0">
|
||||
<span class="text-warning">{{ t('dashboard.upload') }}</span
|
||||
|
||||
@@ -362,9 +362,6 @@ const draggableFolderPlugins = ref<Plugin[]>([])
|
||||
// 是否正在拖拽排序中
|
||||
const isDraggingSortMode = ref(false)
|
||||
|
||||
// 插件市场分页 key,重置后让 VInfiniteScroll 重新触发首屏加载。
|
||||
const marketInfiniteKey = ref(0)
|
||||
|
||||
// 显示的文件夹列表(按排序显示)
|
||||
const displayedFolders = computed(() => {
|
||||
if (currentFolder.value) return [] // 在文件夹内不显示其他文件夹
|
||||
@@ -810,7 +807,6 @@ async function getPluginStatistics() {
|
||||
async function refreshData() {
|
||||
await fetchInstalledPlugins()
|
||||
await fetchUninstalledPlugins()
|
||||
marketInfiniteKey.value++
|
||||
getPluginStatistics()
|
||||
// 重新加载文件夹配置,确保分身插件能正确显示在文件夹中
|
||||
await loadPluginFolders()
|
||||
@@ -884,7 +880,6 @@ async function refreshMarket() {
|
||||
try {
|
||||
await fetchUninstalledPlugins(true)
|
||||
getPluginStatistics()
|
||||
marketInfiniteKey.value++
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
@@ -896,16 +891,10 @@ async function refreshActiveTabData() {
|
||||
if (sortMode.value || isDraggingSortMode.value) return
|
||||
|
||||
if (activeTab.value === 'market') {
|
||||
isAppMarketLoaded.value = false
|
||||
await fetchInstalledPlugins()
|
||||
await fetchUninstalledPlugins()
|
||||
getPluginStatistics()
|
||||
marketInfiniteKey.value++
|
||||
return
|
||||
}
|
||||
|
||||
await fetchInstalledPlugins()
|
||||
await fetchUninstalledPlugins()
|
||||
getPluginStatistics()
|
||||
// 文件夹配置可能在其它入口被插件操作改变,重新进入时同步一次。
|
||||
await loadPluginFolders()
|
||||
@@ -1716,7 +1705,6 @@ function onDragStartPlugin(evt: any) {
|
||||
mode="intersect"
|
||||
side="end"
|
||||
:items="displayUninstalledList"
|
||||
:key="marketInfiniteKey"
|
||||
@load="loadMarketMore"
|
||||
class="overflow-visible"
|
||||
>
|
||||
|
||||
@@ -8,6 +8,7 @@ import ProgressiveCardGrid from '@/components/misc/ProgressiveCardGrid.vue'
|
||||
import { useUserStore } from '@/stores'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useBackground } from '@/composables/useBackground'
|
||||
import { useKeepAliveRefresh } from '@/composables/useKeepAliveRefresh'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -16,6 +17,7 @@ const { useDataRefresh } = useBackground()
|
||||
// 定义输入参数
|
||||
const props = defineProps<{
|
||||
name: string
|
||||
active?: boolean
|
||||
}>()
|
||||
|
||||
// 用户 Store
|
||||
@@ -63,6 +65,10 @@ const { loading: dataLoading } = useDataRefresh(
|
||||
3000, // 3秒间隔
|
||||
true // 立即执行
|
||||
)
|
||||
|
||||
useKeepAliveRefresh(fetchData, {
|
||||
active: computed(() => props.active !== false),
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -306,9 +306,11 @@ watch(
|
||||
}, 1000),
|
||||
)
|
||||
|
||||
// 获取订阅列表数据
|
||||
async function fetchData(page = currentPage.value, count = itemsPerPage.value) {
|
||||
loading.value = true
|
||||
// 获取历史记录数据,keep-alive 重新进入时可静默刷新,避免表格出现重新加载感。
|
||||
async function fetchData(page = currentPage.value, count = itemsPerPage.value, options: { silent?: boolean } = {}) {
|
||||
if (!options.silent) {
|
||||
loading.value = true
|
||||
}
|
||||
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('history/transfer', {
|
||||
@@ -326,8 +328,11 @@ async function fetchData(page = currentPage.value, count = itemsPerPage.value) {
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
if (!options.silent) {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
// 根据 type 返回不同的图标
|
||||
@@ -761,7 +766,7 @@ onActivated(() => {
|
||||
}
|
||||
|
||||
if (!loading.value) {
|
||||
fetchData()
|
||||
fetchData(currentPage.value, itemsPerPage.value, { silent: true })
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { MediaInfo } from '@/api/types'
|
||||
import MediaCard from '@/components/cards/MediaCard.vue'
|
||||
import NoDataFound from '@/components/NoDataFound.vue'
|
||||
import ProgressiveCardGrid from '@/components/misc/ProgressiveCardGrid.vue'
|
||||
import { useKeepAliveRefresh } from '@/composables/useKeepAliveRefresh'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
@@ -13,10 +12,6 @@ const { t } = useI18n()
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
type: String,
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
// 判断是否有滚动条
|
||||
@@ -116,10 +111,6 @@ watch(
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
useKeepAliveRefresh(resetData, {
|
||||
active: computed(() => props.active),
|
||||
})
|
||||
|
||||
// 拼装参数
|
||||
function getParams() {
|
||||
let params: { [key: string]: any } = {
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { SubscribeShare } from '@/api/types'
|
||||
import NoDataFound from '@/components/NoDataFound.vue'
|
||||
import SubscribeShareCard from '@/components/cards/SubscribeShareCard.vue'
|
||||
import ProgressiveCardGrid from '@/components/misc/ProgressiveCardGrid.vue'
|
||||
import { useKeepAliveRefresh } from '@/composables/useKeepAliveRefresh'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
@@ -14,10 +13,6 @@ const { t } = useI18n()
|
||||
const props = defineProps({
|
||||
// 过滤关键字
|
||||
keyword: String,
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
// 判断是否有滚动条
|
||||
@@ -129,10 +124,6 @@ const isRefreshed = ref(false)
|
||||
const dataList = ref<SubscribeShare[]>([])
|
||||
const currData = ref<SubscribeShare[]>([])
|
||||
|
||||
useKeepAliveRefresh(resetData, {
|
||||
active: computed(() => props.active),
|
||||
})
|
||||
|
||||
// 拼装参数
|
||||
function getParams() {
|
||||
let params: { [key: string]: any } = {
|
||||
|
||||
@@ -6,6 +6,7 @@ import WorkflowTaskCard from '@/components/cards/WorkflowTaskCard.vue'
|
||||
import NoDataFound from '@/components/NoDataFound.vue'
|
||||
import ProgressiveCardGrid from '@/components/misc/ProgressiveCardGrid.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useKeepAliveRefresh } from '@/composables/useKeepAliveRefresh'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -52,9 +53,7 @@ onMounted(() => {
|
||||
fetchData()
|
||||
})
|
||||
|
||||
onActivated(() => {
|
||||
fetchData()
|
||||
})
|
||||
useKeepAliveRefresh(fetchData)
|
||||
|
||||
function openAddDialog() {
|
||||
addDialog.value = true
|
||||
@@ -62,6 +61,7 @@ function openAddDialog() {
|
||||
|
||||
defineExpose({
|
||||
openAddDialog,
|
||||
refresh: fetchData,
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
|
||||
@@ -146,9 +146,8 @@ function removeData(id: string) {
|
||||
dataList.value = dataList.value.filter(item => item.id !== id)
|
||||
}
|
||||
|
||||
onActivated(() => {
|
||||
onMounted(() => {
|
||||
loadEventTypes()
|
||||
fetchData({ done: () => {} })
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user