refactor(frontend): UI/UX改进和组件优化

- DataTable组件操作列自适应
- 优化各种Modal弹窗
- 统一API调用方式(AbortSignal)
- 添加全局订阅状态管理
- 优化各管理视图的交互和布局
- 修复国际化翻译问题
This commit is contained in:
IanShaw027
2025-12-28 01:00:06 +08:00
parent 9bbe468c91
commit 506cb21cb1
46 changed files with 1582 additions and 644 deletions

View File

@@ -0,0 +1,135 @@
/**
* Subscription Store
* Global state management for user subscriptions with caching and deduplication
*/
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import subscriptionsAPI from '@/api/subscriptions'
import type { UserSubscription } from '@/types'
// Cache TTL: 60 seconds
const CACHE_TTL_MS = 60_000
// Request generation counter to invalidate stale in-flight responses
let requestGeneration = 0
export const useSubscriptionStore = defineStore('subscriptions', () => {
// State
const activeSubscriptions = ref<UserSubscription[]>([])
const loading = ref(false)
const loaded = ref(false)
const lastFetchedAt = ref<number | null>(null)
// In-flight request deduplication
let activePromise: Promise<UserSubscription[]> | null = null
// Auto-refresh interval
let pollerInterval: ReturnType<typeof setInterval> | null = null
// Computed
const hasActiveSubscriptions = computed(() => activeSubscriptions.value.length > 0)
/**
* Fetch active subscriptions with caching and deduplication
* @param force - Force refresh even if cache is valid
*/
async function fetchActiveSubscriptions(force = false): Promise<UserSubscription[]> {
const now = Date.now()
// Return cached data if valid
if (
!force &&
loaded.value &&
lastFetchedAt.value &&
now - lastFetchedAt.value < CACHE_TTL_MS
) {
return activeSubscriptions.value
}
// Return in-flight request if exists (deduplication)
if (activePromise) {
return activePromise
}
const currentGeneration = ++requestGeneration
// Start new request
loading.value = true
activePromise = subscriptionsAPI
.getActiveSubscriptions()
.then((data) => {
if (currentGeneration === requestGeneration) {
activeSubscriptions.value = data
loaded.value = true
lastFetchedAt.value = Date.now()
}
return data
})
.catch((error) => {
console.error('Failed to fetch active subscriptions:', error)
throw error
})
.finally(() => {
loading.value = false
activePromise = null
})
return activePromise
}
/**
* Start auto-refresh polling (every 5 minutes)
*/
function startPolling() {
if (pollerInterval) return
pollerInterval = setInterval(() => {
fetchActiveSubscriptions(true).catch((error) => {
console.error('Subscription polling failed:', error)
})
}, 5 * 60 * 1000) // 5 minutes
}
/**
* Stop auto-refresh polling
*/
function stopPolling() {
if (pollerInterval) {
clearInterval(pollerInterval)
pollerInterval = null
}
}
/**
* Clear all subscription data and stop polling
*/
function clear() {
requestGeneration++
activeSubscriptions.value = []
loaded.value = false
lastFetchedAt.value = null
stopPolling()
}
/**
* Invalidate cache (force next fetch to reload)
*/
function invalidateCache() {
lastFetchedAt.value = null
}
return {
// State
activeSubscriptions,
loading,
hasActiveSubscriptions,
// Actions
fetchActiveSubscriptions,
startPolling,
stopPolling,
clear,
invalidateCache
}
})