perf: reduce main page request fanout

Reuse shared page queries across main and reduce repeated page-load requests.
This commit is contained in:
SmileQWQ
2026-04-23 10:32:35 +08:00
parent 5004c3b3c8
commit d54531da1e
12 changed files with 243 additions and 126 deletions

View File

@@ -0,0 +1,14 @@
import { useQuery } from '@tanstack/vue-query'
import { api } from '@/composables/api'
export const BUDGET_STATISTICS_QUERY_KEY = ['statistics-budgets'] as const
export function useBudgetStatisticsQuery() {
return useQuery({
queryKey: BUDGET_STATISTICS_QUERY_KEY,
queryFn: api.getBudgetStatistics,
staleTime: 5_000,
gcTime: 5 * 60_000,
refetchOnWindowFocus: false
})
}

View File

@@ -0,0 +1,20 @@
import { computed, type MaybeRefOrGetter, toValue } from 'vue'
import { useQuery } from '@tanstack/vue-query'
import { api } from '@/composables/api'
type CalendarRange = {
start?: string
end?: string
}
export function useCalendarEventsQuery(params: MaybeRefOrGetter<CalendarRange>) {
const normalizedParams = computed(() => toValue(params))
return useQuery({
queryKey: computed(() => ['calendar-events', normalizedParams.value]),
queryFn: () => api.getCalendarEvents(normalizedParams.value),
staleTime: 5_000,
gcTime: 5 * 60_000,
refetchOnWindowFocus: false
})
}

View File

@@ -0,0 +1,14 @@
import { useQuery } from '@tanstack/vue-query'
import { api } from '@/composables/api'
export const EXCHANGE_RATE_SNAPSHOT_QUERY_KEY = ['exchange-rate-snapshot'] as const
export function useExchangeRateSnapshotQuery() {
return useQuery({
queryKey: EXCHANGE_RATE_SNAPSHOT_QUERY_KEY,
queryFn: api.getExchangeRateSnapshot,
staleTime: 5_000,
gcTime: 5 * 60_000,
refetchOnWindowFocus: false
})
}

View File

@@ -0,0 +1,14 @@
import { useQuery } from '@tanstack/vue-query'
import { api } from '@/composables/api'
export const NOTIFICATION_WEBHOOK_QUERY_KEY = ['notification-webhook'] as const
export function useNotificationWebhookQuery() {
return useQuery({
queryKey: NOTIFICATION_WEBHOOK_QUERY_KEY,
queryFn: api.getNotificationWebhook,
staleTime: 5_000,
gcTime: 5 * 60_000,
refetchOnWindowFocus: false
})
}

View File

@@ -0,0 +1,14 @@
import { useQuery } from '@tanstack/vue-query'
import { api } from '@/composables/api'
export const STATISTICS_OVERVIEW_QUERY_KEY = ['statistics-overview'] as const
export function useStatisticsOverviewQuery() {
return useQuery({
queryKey: STATISTICS_OVERVIEW_QUERY_KEY,
queryFn: api.getStatisticsOverview,
staleTime: 5_000,
gcTime: 5 * 60_000,
refetchOnWindowFocus: false
})
}

View File

@@ -0,0 +1,14 @@
import { useQuery } from '@tanstack/vue-query'
import { api } from '@/composables/api'
export const TAGS_QUERY_KEY = ['tags'] as const
export function useTagsQuery() {
return useQuery({
queryKey: TAGS_QUERY_KEY,
queryFn: api.getTags,
staleTime: 5_000,
gcTime: 5 * 60_000,
refetchOnWindowFocus: false
})
}

View File

@@ -148,13 +148,15 @@
<script setup lang="ts">
import { computed, h, ref, watch } from 'vue'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { useQueryClient } from '@tanstack/vue-query'
import { useWindowSize } from '@vueuse/core'
import { useRouter } from 'vue-router'
import { NButton, NCard, NDataTable, NDivider, NEmpty, NGrid, NGridItem, NProgress, NSpace, NTag, useMessage } from 'naive-ui'
import { WalletOutline } from '@vicons/ionicons5'
import { api } from '@/composables/api'
import { BUDGET_STATISTICS_QUERY_KEY, useBudgetStatisticsQuery } from '@/composables/budget-statistics-query'
import { SETTINGS_QUERY_KEY, useSettingsQuery } from '@/composables/settings-query'
import { TAGS_QUERY_KEY, useTagsQuery } from '@/composables/tags-query'
import ChartView from '@/components/ChartView.vue'
import PageHeader from '@/components/PageHeader.vue'
import TagBudgetSettingsModal from '@/components/TagBudgetSettingsModal.vue'
@@ -167,18 +169,12 @@ const message = useMessage()
const queryClient = useQueryClient()
const tagBudgetModalVisible = ref(false)
const { data: budgetStats } = useQuery({
queryKey: ['statistics-budgets'],
queryFn: api.getBudgetStatistics
})
const { data: budgetStats } = useBudgetStatisticsQuery()
const { data: settings } = useSettingsQuery()
const { data: tags } = useQuery({
queryKey: ['budget-page-tags'],
queryFn: api.getTags,
initialData: []
})
const { data: tagsData } = useTagsQuery()
const tags = computed(() => tagsData.value ?? [])
watch(
() => settings.value?.enableTagBudgets,
@@ -292,11 +288,12 @@ async function saveTagBudgets(tagBudgets: Record<string, number>) {
await api.updateSettings({ tagBudgets })
tagBudgetModalVisible.value = false
message.success('标签月预算已保存')
await Promise.all([
queryClient.invalidateQueries({ queryKey: ['statistics-budgets'] }),
queryClient.invalidateQueries({ queryKey: ['statistics-overview'] }),
queryClient.invalidateQueries({ queryKey: SETTINGS_QUERY_KEY })
])
await Promise.all([
queryClient.invalidateQueries({ queryKey: BUDGET_STATISTICS_QUERY_KEY }),
queryClient.invalidateQueries({ queryKey: ['statistics-overview'] }),
queryClient.invalidateQueries({ queryKey: SETTINGS_QUERY_KEY }),
queryClient.invalidateQueries({ queryKey: TAGS_QUERY_KEY })
])
}
function formatMoney(amount: number, currency: string) {

View File

@@ -101,7 +101,7 @@ import {
TodayOutline,
WalletOutline
} from '@vicons/ionicons5'
import { api } from '@/composables/api'
import { useCalendarEventsQuery } from '@/composables/calendar-events-query'
import { useSettingsQuery } from '@/composables/settings-query'
import PageHeader from '@/components/PageHeader.vue'
import StatCard from '@/components/StatCard.vue'
@@ -119,20 +119,25 @@ const events = ref<CalendarEvent[]>([])
const tab = ref('month')
const selectedDateTs = ref(dayjs().valueOf())
const panelMonthTs = ref(dayjs().startOf('month').valueOf())
let latestMonthRequestId = 0
let ignoreSelectedDateWatch = false
const monthEventsCache = new Map<string, CalendarEvent[]>()
const { data: settings } = useSettingsQuery()
const baseCurrency = computed(() => settings.value?.baseCurrency ?? 'CNY')
const panelMonthRange = computed(() => {
const monthStart = dayjs(panelMonthTs.value).startOf('month')
return {
start: monthStart.format('YYYY-MM-DD'),
end: monthStart.endOf('month').format('YYYY-MM-DD')
}
})
const calendarEventsQuery = useCalendarEventsQuery(panelMonthRange)
const summaryCols = computed(() => (width.value < 640 ? 1 : width.value < 1100 ? 2 : 4))
const calendarCols = computed(() => (width.value < 1100 ? 1 : 2))
onMounted(async () => {
onMounted(() => {
if (width.value < 720) {
tab.value = 'list'
}
await loadEventsForMonth(panelMonthTs.value)
})
watch(selectedDateTs, async (value) => {
@@ -143,56 +148,17 @@ watch(selectedDateTs, async (value) => {
const selectedMonth = dayjs(value).startOf('month')
if (!selectedMonth.isSame(dayjs(panelMonthTs.value), 'month')) {
await loadEventsForMonth(value)
panelMonthTs.value = selectedMonth.valueOf()
}
})
async function loadEventsForMonth(monthTs: number) {
const requestId = ++latestMonthRequestId
const monthStart = dayjs(monthTs).startOf('month')
const cacheKey = monthStart.format('YYYY-MM')
panelMonthTs.value = monthStart.valueOf()
const cached = monthEventsCache.get(cacheKey)
if (cached) {
events.value = cached
void prefetchAdjacentMonths(monthStart.valueOf())
return
}
events.value = []
const start = monthStart.startOf('month').format('YYYY-MM-DD')
const end = monthStart.endOf('month').format('YYYY-MM-DD')
const rows = await api.getCalendarEvents({ start, end })
if (requestId !== latestMonthRequestId) return
monthEventsCache.set(cacheKey, rows)
events.value = rows
void prefetchAdjacentMonths(monthStart.valueOf())
}
async function fetchMonthEvents(monthTs: number) {
const monthStart = dayjs(monthTs).startOf('month')
const cacheKey = monthStart.format('YYYY-MM')
const cached = monthEventsCache.get(cacheKey)
if (cached) return cached
const rows = await api.getCalendarEvents({
start: monthStart.startOf('month').format('YYYY-MM-DD'),
end: monthStart.endOf('month').format('YYYY-MM-DD')
})
monthEventsCache.set(cacheKey, rows)
return rows
}
async function prefetchAdjacentMonths(monthTs: number) {
const currentMonth = dayjs(monthTs).startOf('month')
await Promise.allSettled([
fetchMonthEvents(currentMonth.add(1, 'month').valueOf()),
fetchMonthEvents(currentMonth.subtract(1, 'month').valueOf())
])
}
watch(
() => calendarEventsQuery.data.value,
(value) => {
events.value = value ?? []
},
{ immediate: true }
)
const panelMonthLabel = computed(() => dayjs(panelMonthTs.value).format('YYYY 年 M 月'))
const selectedDateLabel = computed(() => dayjs(selectedDateTs.value).format('YYYY-MM-DD'))
@@ -246,7 +212,7 @@ function handlePanelChange({ year, month }: { year: number; month: number }) {
ignoreSelectedDateWatch = true
selectedDateTs.value = targetSelectedDate.valueOf()
void loadEventsForMonth(targetMonth.valueOf())
panelMonthTs.value = targetMonth.valueOf()
}
function getDaySummary(year: number, month: number, date: number) {

View File

@@ -117,12 +117,11 @@
<script setup lang="ts">
import dayjs from 'dayjs'
import { computed, h } from 'vue'
import { useQuery } from '@tanstack/vue-query'
import { useWindowSize } from '@vueuse/core'
import { NCard, NDataTable, NEmpty, NGrid, NGridItem, NProgress, NTag } from 'naive-ui'
import { CashOutline, GridOutline, LayersOutline, NotificationsOutline, WalletOutline } from '@vicons/ionicons5'
import { api } from '@/composables/api'
import { useSettingsQuery } from '@/composables/settings-query'
import { useStatisticsOverviewQuery } from '@/composables/statistics-overview-query'
import ChartView from '@/components/ChartView.vue'
import PageHeader from '@/components/PageHeader.vue'
import StatCard from '@/components/StatCard.vue'
@@ -132,10 +131,7 @@ import { getSubscriptionStatusTagType, getSubscriptionStatusText } from '@/utils
const { width } = useWindowSize()
const gridOutline = GridOutline
const { data: overview } = useQuery({
queryKey: ['statistics-overview'],
queryFn: api.getStatisticsOverview
})
const { data: overview } = useStatisticsOverviewQuery()
const { data: settings } = useSettingsQuery()

View File

@@ -440,7 +440,7 @@
<script setup lang="ts">
import dayjs from 'dayjs'
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { computed, reactive, ref, watch } from 'vue'
import { useWindowSize } from '@vueuse/core'
import { useQueryClient } from '@tanstack/vue-query'
import {
@@ -475,6 +475,8 @@ import {
} from 'naive-ui'
import { HelpCircleOutline, RefreshOutline, SaveOutline, SettingsOutline } from '@vicons/ionicons5'
import { api } from '@/composables/api'
import { EXCHANGE_RATE_SNAPSHOT_QUERY_KEY, useExchangeRateSnapshotQuery } from '@/composables/exchange-rate-query'
import { NOTIFICATION_WEBHOOK_QUERY_KEY, useNotificationWebhookQuery } from '@/composables/notification-webhook-query'
import { SETTINGS_QUERY_KEY, useSettingsQuery } from '@/composables/settings-query'
import PageHeader from '@/components/PageHeader.vue'
import WallosImportModal from '@/components/WallosImportModal.vue'
@@ -488,6 +490,8 @@ const message = useMessage()
const authStore = useAuthStore()
const queryClient = useQueryClient()
const { data: settingsQueryData } = useSettingsQuery()
const { data: snapshotQueryData } = useExchangeRateSnapshotQuery()
const { data: webhookQueryData } = useNotificationWebhookQuery()
const { width } = useWindowSize()
const helpCircleOutline = HelpCircleOutline
const settingsOutline = SettingsOutline
@@ -695,10 +699,6 @@ function validateAiSettings(action: 'save' | 'connection-test' | 'vision-test')
return false
}
onMounted(async () => {
await Promise.all([loadSnapshot(), loadWebhook()])
})
watch(
settingsQueryData,
(settings) => {
@@ -712,13 +712,26 @@ watch(
{ immediate: true }
)
async function loadSnapshot() {
snapshot.value = await api.getExchangeRateSnapshot()
}
watch(
snapshotQueryData,
(value) => {
snapshot.value = value ?? null
},
{ immediate: true }
)
async function loadWebhook() {
const current = await api.getNotificationWebhook()
Object.assign(webhookForm, current)
watch(
webhookQueryData,
(value) => {
if (!value) return
Object.assign(webhookForm, value)
},
{ immediate: true }
)
function applySavedSettings(result: Settings) {
Object.assign(settingsForm, cloneSettingsForForm(result))
queryClient.setQueryData(SETTINGS_QUERY_KEY, result)
}
async function saveBasicSettings() {
@@ -736,7 +749,7 @@ async function saveBasicSettings() {
defaultOverdueReminderRules: settingsForm.defaultOverdueReminderRules,
tagBudgets: settingsForm.tagBudgets
})
Object.assign(settingsForm, result)
applySavedSettings(result)
message.success('基础设置已保存')
targetCurrency.value = settingsForm.baseCurrency.toUpperCase()
await Promise.all([
@@ -744,7 +757,7 @@ async function saveBasicSettings() {
queryClient.invalidateQueries({ queryKey: ['statistics-overview'] }),
queryClient.invalidateQueries({ queryKey: ['statistics-budgets'] })
])
await loadSnapshot()
await queryClient.invalidateQueries({ queryKey: EXCHANGE_RATE_SNAPSHOT_QUERY_KEY })
} catch (error) {
message.error(error instanceof Error ? error.message : '基础设置保存失败')
} finally {
@@ -757,10 +770,11 @@ async function saveEmailSettings() {
if (!validateEmailSettings('save')) return
savingEmailSettings.value = true
try {
await api.updateSettings({
const result = await api.updateSettings({
emailNotificationsEnabled: settingsForm.emailNotificationsEnabled,
emailConfig: settingsForm.emailConfig
})
applySavedSettings(result)
message.success(settingsForm.emailNotificationsEnabled ? '邮箱通知配置已保存' : '邮箱通知已关闭')
} finally {
savingEmailSettings.value = false
@@ -772,10 +786,11 @@ async function savePushplusSettings() {
if (!validatePushplusSettings('save')) return
savingPushplusSettings.value = true
try {
await api.updateSettings({
const result = await api.updateSettings({
pushplusNotificationsEnabled: settingsForm.pushplusNotificationsEnabled,
pushplusConfig: settingsForm.pushplusConfig
})
applySavedSettings(result)
message.success(settingsForm.pushplusNotificationsEnabled ? 'PushPlus 配置已保存' : 'PushPlus 已关闭')
} finally {
savingPushplusSettings.value = false
@@ -787,10 +802,11 @@ async function saveTelegramSettings() {
if (!validateTelegramSettings('save')) return
savingTelegramSettings.value = true
try {
await api.updateSettings({
const result = await api.updateSettings({
telegramNotificationsEnabled: settingsForm.telegramNotificationsEnabled,
telegramConfig: settingsForm.telegramConfig
})
applySavedSettings(result)
message.success(settingsForm.telegramNotificationsEnabled ? 'Telegram 配置已保存' : 'Telegram 已关闭')
} finally {
savingTelegramSettings.value = false
@@ -805,7 +821,7 @@ async function saveAiSettings() {
aiPromptInput.value = promptTemplate || DEFAULT_AI_SUBSCRIPTION_PROMPT
savingAiSettings.value = true
try {
await api.updateSettings({
const result = await api.updateSettings({
aiConfig: {
...settingsForm.aiConfig,
capabilities: {
@@ -814,6 +830,8 @@ async function saveAiSettings() {
promptTemplate
}
})
applySavedSettings(result)
aiPromptInput.value = result.aiConfig.promptTemplate.trim() || DEFAULT_AI_SUBSCRIPTION_PROMPT
message.success(settingsForm.aiConfig.enabled ? 'AI 识别配置已保存' : 'AI 识别已关闭')
} finally {
savingAiSettings.value = false
@@ -878,6 +896,7 @@ function handleAiPresetChange(value: AiProviderPreset) {
async function refreshRates() {
snapshot.value = await api.refreshExchangeRates()
queryClient.setQueryData(EXCHANGE_RATE_SNAPSHOT_QUERY_KEY, snapshot.value)
message.success('汇率已刷新')
}
@@ -965,6 +984,7 @@ async function saveWebhook() {
ignoreSsl: webhookForm.ignoreSsl
})
Object.assign(webhookForm, saved)
queryClient.setQueryData(NOTIFICATION_WEBHOOK_QUERY_KEY, saved)
message.success(webhookForm.enabled ? 'Webhook 配置已保存' : 'Webhook 已关闭')
} finally {
savingWebhookSettings.value = false

View File

@@ -66,12 +66,11 @@
<script setup lang="ts">
import dayjs from 'dayjs'
import { computed } from 'vue'
import { useQuery } from '@tanstack/vue-query'
import { useWindowSize } from '@vueuse/core'
import { NCard, NEmpty, NGrid, NGridItem } from 'naive-ui'
import { BarChartOutline } from '@vicons/ionicons5'
import { api } from '@/composables/api'
import { useSettingsQuery } from '@/composables/settings-query'
import { useStatisticsOverviewQuery } from '@/composables/statistics-overview-query'
import ChartView from '@/components/ChartView.vue'
import PageHeader from '@/components/PageHeader.vue'
import type { StatisticsOverview, SubscriptionStatus } from '@/types/api'
@@ -80,10 +79,7 @@ import { buildTopSubscriptionsOption } from '@/utils/statistics-top-subscription
const { width } = useWindowSize()
const barChartOutline = BarChartOutline
const { data: overview } = useQuery({
queryKey: ['statistics-overview'],
queryFn: api.getStatisticsOverview
})
const { data: overview } = useStatisticsOverviewQuery()
const { data: settings } = useSettingsQuery()

View File

@@ -249,6 +249,7 @@
import dayjs from 'dayjs'
import { computed, h, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
import { useWindowSize } from '@vueuse/core'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import {
NButton,
NCard,
@@ -274,7 +275,9 @@ import {
SearchOutline
} from '@vicons/ionicons5'
import { api } from '@/composables/api'
import { useExchangeRateSnapshotQuery } from '@/composables/exchange-rate-query'
import { useSettingsQuery } from '@/composables/settings-query'
import { TAGS_QUERY_KEY, useTagsQuery } from '@/composables/tags-query'
import TagManageModal from '@/components/TagManageModal.vue'
import PageHeader from '@/components/PageHeader.vue'
import SubscriptionDetailDrawer from '@/components/SubscriptionDetailDrawer.vue'
@@ -295,12 +298,15 @@ type SortMode = 'custom' | 'renewal' | 'amount-desc' | 'name'
const message = useMessage()
const { width } = useWindowSize()
const queryClient = useQueryClient()
const layersOutline = LayersOutline
const isMobile = computed(() => width.value < 960)
const subscriptions = ref<Subscription[]>([])
const tags = ref<Tag[]>([])
const { data: settings } = useSettingsQuery()
const { data: tagsQueryData } = useTagsQuery()
const { data: snapshotQueryData } = useExchangeRateSnapshotQuery()
const detail = ref<SubscriptionDetail | null>(null)
const paymentRecords = ref<PaymentRecord[]>([])
const currencies = ref<string[]>(['CNY', 'USD', 'EUR', 'GBP', 'JPY', 'HKD'])
@@ -312,6 +318,11 @@ const filters = reactive({
status: null as string | null,
tagIds: [] as string[]
})
const appliedFilters = reactive({
q: '',
status: null as string | null,
tagIds: [] as string[]
})
const sortMode = ref<SortMode>('custom')
const showModal = ref(false)
@@ -380,6 +391,18 @@ const canBatchCancel = computed(
const canBatchDelete = computed(
() => selectedCount.value > 0 && selectedSubscriptions.value.every((item) => item.status !== 'active')
)
const subscriptionQueryParams = computed(() => ({
q: appliedFilters.q || undefined,
status: appliedFilters.status || undefined,
tagIds: appliedFilters.tagIds.length ? appliedFilters.tagIds.join(',') : undefined
}))
const subscriptionsQuery = useQuery({
queryKey: computed(() => ['subscriptions', subscriptionQueryParams.value]),
queryFn: () => api.getSubscriptions(subscriptionQueryParams.value),
staleTime: 5_000,
gcTime: 5 * 60_000,
refetchOnWindowFocus: false
})
const orderedSubscriptions = computed(() => {
const rows = [...subscriptions.value]
@@ -643,7 +666,6 @@ const tableRows = computed<SubscriptionTableRow[]>(() => buildSubscriptionTableR
onMounted(async () => {
desktopPageSize.value = getStoredSubscriptionPageSize()
window.addEventListener('mouseup', resetArmedDrag)
await Promise.all([loadTags(), loadSubscriptions(), loadCurrencies()])
})
onBeforeUnmount(() => {
@@ -668,26 +690,51 @@ watch(
}
)
async function loadTags() {
tags.value = await api.getTags()
}
async function loadSubscriptions() {
resetDragState()
currentPage.value = 1
subscriptions.value = await api.getSubscriptions({
q: filters.q || undefined,
status: filters.status || undefined,
tagIds: filters.tagIds.length ? filters.tagIds.join(',') : undefined
})
const existingIds = new Set(subscriptions.value.map((item) => item.id))
selectedSubscriptionIds.value = selectedSubscriptionIds.value.filter((id) => existingIds.has(id))
const nextTagIds = [...filters.tagIds]
const filtersChanged =
appliedFilters.q !== filters.q ||
appliedFilters.status !== filters.status ||
appliedFilters.tagIds.length !== nextTagIds.length ||
appliedFilters.tagIds.some((id, index) => id !== nextTagIds[index])
appliedFilters.q = filters.q
appliedFilters.status = filters.status
appliedFilters.tagIds = nextTagIds
if (!filtersChanged) {
await subscriptionsQuery.refetch()
}
}
async function loadCurrencies() {
const snapshot = await api.getExchangeRateSnapshot()
currencies.value = Array.from(new Set([snapshot.baseCurrency, ...Object.keys(snapshot.rates)])).sort()
}
watch(
() => subscriptionsQuery.data.value,
(value) => {
subscriptions.value = value ?? []
const existingIds = new Set(subscriptions.value.map((item) => item.id))
selectedSubscriptionIds.value = selectedSubscriptionIds.value.filter((id) => existingIds.has(id))
},
{ immediate: true }
)
watch(
tagsQueryData,
(value) => {
tags.value = value ?? []
},
{ immediate: true }
)
watch(
snapshotQueryData,
(value) => {
if (!value) return
currencies.value = Array.from(new Set([value.baseCurrency, ...Object.keys(value.rates)])).sort()
},
{ immediate: true }
)
watch(
settings,
@@ -699,6 +746,11 @@ watch(
{ immediate: true }
)
async function refetchCurrentSubscriptions() {
await queryClient.invalidateQueries({ queryKey: ['subscriptions'] })
await subscriptionsQuery.refetch()
}
function toggleTagFilter(tagId: string) {
if (filters.tagIds.includes(tagId)) {
filters.tagIds = filters.tagIds.filter((item) => item !== tagId)
@@ -757,7 +809,7 @@ const submitSubscriptionTask = createSingleFlight(async (payload: Record<string,
}
closeModal()
await loadSubscriptions()
await refetchCurrentSubscriptions()
} catch (error) {
message.error(`保存失败:${error instanceof Error ? error.message : 'Unknown'}`)
} finally {
@@ -773,7 +825,7 @@ async function createTag(payload: { name: string; color: string; icon: string; s
try {
await api.createTag(payload)
message.success('标签已创建')
await Promise.all([loadTags(), loadSubscriptions()])
await Promise.all([queryClient.invalidateQueries({ queryKey: TAGS_QUERY_KEY }), refetchCurrentSubscriptions()])
} catch (error) {
message.error(`标签创建失败:${error instanceof Error ? error.message : 'Unknown'}`)
}
@@ -783,7 +835,7 @@ async function updateTag(payload: { name: string; color: string; icon: string; s
try {
await api.updateTag(id, payload)
message.success('标签已更新')
await Promise.all([loadTags(), loadSubscriptions()])
await Promise.all([queryClient.invalidateQueries({ queryKey: TAGS_QUERY_KEY }), refetchCurrentSubscriptions()])
} catch (error) {
message.error(`标签更新失败:${error instanceof Error ? error.message : 'Unknown'}`)
}
@@ -794,7 +846,7 @@ async function deleteTag(tag: Tag) {
await api.deleteTag(tag.id)
message.success(`已删除标签:${tag.name}`)
filters.tagIds = filters.tagIds.filter((item) => item !== tag.id)
await Promise.all([loadTags(), loadSubscriptions()])
await Promise.all([queryClient.invalidateQueries({ queryKey: TAGS_QUERY_KEY }), refetchCurrentSubscriptions()])
} catch (error) {
message.error(`标签删除失败:${error instanceof Error ? error.message : 'Unknown'}`)
}
@@ -850,7 +902,7 @@ async function runBatchRenew() {
const ids = [...selectedSubscriptionIds.value]
const result = await api.batchRenewSubscriptions(ids)
summarizeBatchResult('批量续订', result)
await loadSubscriptions()
await refetchCurrentSubscriptions()
await refreshOpenDetailIfNeeded(ids)
}
@@ -860,7 +912,7 @@ async function runBatchPause() {
const ids = [...selectedSubscriptionIds.value]
const result = await api.batchPauseSubscriptions(ids)
summarizeBatchResult('批量暂停', result)
await loadSubscriptions()
await refetchCurrentSubscriptions()
await refreshOpenDetailIfNeeded(ids)
}
@@ -870,7 +922,7 @@ async function runBatchCancel() {
const ids = [...selectedSubscriptionIds.value]
const result = await api.batchCancelSubscriptions(ids)
summarizeBatchResult('批量取消', result)
await loadSubscriptions()
await refetchCurrentSubscriptions()
await refreshOpenDetailIfNeeded(ids)
}
@@ -884,13 +936,13 @@ async function runBatchDelete() {
showDetailDrawer.value = false
}
clearSelectedSubscriptions()
await loadSubscriptions()
await refetchCurrentSubscriptions()
}
async function quickRenew(row: Subscription) {
await api.renewSubscription(row.id)
message.success(`已续订:${row.name}`)
await loadSubscriptions()
await refetchCurrentSubscriptions()
if (detail.value?.id === row.id) {
detail.value = await api.getSubscription(row.id)
}
@@ -899,7 +951,7 @@ async function quickRenew(row: Subscription) {
async function pause(id: string) {
await api.pauseSubscription(id)
message.success('已暂停')
await loadSubscriptions()
await refetchCurrentSubscriptions()
if (detail.value?.id === id) {
detail.value = await api.getSubscription(id)
}
@@ -908,7 +960,7 @@ async function pause(id: string) {
async function cancel(id: string) {
await api.cancelSubscription(id)
message.success('已停用')
await loadSubscriptions()
await refetchCurrentSubscriptions()
if (detail.value?.id === id) {
detail.value = await api.getSubscription(id)
}
@@ -917,7 +969,7 @@ async function cancel(id: string) {
async function removeSubscription(id: string, name: string) {
await api.deleteSubscription(id)
message.success(`已删除:${name}`)
await loadSubscriptions()
await refetchCurrentSubscriptions()
if (detail.value?.id === id) {
detail.value = null
showDetailDrawer.value = false
@@ -985,7 +1037,7 @@ async function handleDrop(event: DragEvent, targetId: string) {
try {
savingOrder.value = true
await api.reorderSubscriptions(nextIds)
await loadSubscriptions()
await refetchCurrentSubscriptions()
message.success('顺序已更新')
} catch (error) {
message.error(error instanceof Error ? error.message : '排序更新失败')