diff --git a/frontend/src/stores/failed-message.ts b/frontend/src/stores/failed-message.ts new file mode 100644 index 0000000..4ee31fc --- /dev/null +++ b/frontend/src/stores/failed-message.ts @@ -0,0 +1,86 @@ +import { api } from '@/utils/request' +import type { + PaginatedData, + FailedMessageRecord, + FailedMessageFilters, +} from '@/types/api' + +export type { FailedMessageRecord } + +interface LookupItem { + id: number + name: string + label?: string +} + +export const useFailedMessageStore = defineStore('failedMessage', () => { + const messages = ref([]) + const loading = ref(false) + const pagination = reactive({ + page: 1, + per_page: 15, + total: 0, + }) + const filters = reactive({ + data_type: undefined, + platform_id: undefined, + failed_at_range: null, + }) + + const platforms = ref([]) + const platformMap = computed( + () => new Map(platforms.value.map((p) => [p.id, p.label || p.name || `平台 #${p.id}`])), + ) + + async function loadLookups() { + try { + const p = await api.get('/api/v1/platforms') + platforms.value = p + } catch (err: unknown) { + console.warn('加载平台列表失败', err) + } + } + + async function fetchMessages() { + loading.value = true + try { + const data = await api.get>('/api/v1/failed-messages', { + page: pagination.page, + per_page: pagination.per_page, + data_type: filters.data_type || undefined, + platform_id: filters.platform_id || undefined, + failed_at_from: filters.failed_at_range?.[0] || undefined, + failed_at_to: filters.failed_at_range?.[1] || undefined, + }) + messages.value = data.items + pagination.total = data.total + pagination.page = data.page + } catch (err: unknown) { + messages.value = [] + pagination.total = 0 + const msg = err instanceof Error ? err.message : '获取失败消息列表失败' + message.error(msg) + } finally { + loading.value = false + } + } + + function resetFilters() { + filters.data_type = undefined + filters.platform_id = undefined + filters.failed_at_range = null + pagination.page = 1 + } + + return { + messages, + loading, + pagination, + filters, + platforms, + platformMap, + loadLookups, + fetchMessages, + resetFilters, + } +}) diff --git a/frontend/src/stores/mq-status.ts b/frontend/src/stores/mq-status.ts new file mode 100644 index 0000000..c9a38f6 --- /dev/null +++ b/frontend/src/stores/mq-status.ts @@ -0,0 +1,55 @@ +import { api } from '@/utils/request' +import type { MqStatusData, MqQueueType, MqQueueInfo } from '@/types/api' + +export const useMqStatusStore = defineStore('mqStatus', () => { + const statusData = ref(null) + const loading = ref(false) + const errorMessage = ref('') + const queueType = ref(undefined) + + const healthySummary = computed(() => { + if (!statusData.value) return { healthy: 0, total: 0 } + const allQueues: MqQueueInfo[] = [ + ...statusData.value.business_queues, + ...statusData.value.retry_queues, + ...(!Array.isArray(statusData.value.error_queue) ? [statusData.value.error_queue] : []), + ] + const healthy = allQueues.filter( + (q) => q.status === 'active' || q.status === 'empty', + ).length + return { healthy, total: allQueues.length } + }) + + async function fetchStatus() { + loading.value = true + errorMessage.value = '' + try { + const params: Record = {} + if (queueType.value) { + params.queue = queueType.value + } + const data = await api.get('/api/v1/mq/status', params) + statusData.value = data + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : '获取 MQ 状态失败' + errorMessage.value = msg + message.error(msg) + } finally { + loading.value = false + } + } + + function resetFilter() { + queueType.value = undefined + } + + return { + statusData, + loading, + errorMessage, + queueType, + healthySummary, + fetchStatus, + resetFilter, + } +}) diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts index 25e92fb..c781375 100644 --- a/frontend/src/types/api.ts +++ b/frontend/src/types/api.ts @@ -147,6 +147,67 @@ export interface RefundItemFilters { created_date_range: [string, string] | null } +/** ─── MQ 状态监控 ─── */ + +export type MqQueueStatusEnum = 'high_load' | 'processing' | 'active' | 'empty' | 'error' + +export interface MqQueueInfo { + queue: string + messages: number | string + consumers: number | string + status: MqQueueStatusEnum +} + +export interface MqSummary { + total_messages: number + total_consumers: number +} + +export interface MqStatusData { + business_queues: MqQueueInfo[] + retry_queues: MqQueueInfo[] + error_queue: MqQueueInfo | [] + summary: MqSummary + fetched_at: string +} + +export type MqQueueType = 'orders' | 'products' | 'refunds' | 'inventory' + +/** ─── 失败消息 ─── */ + +export type FailedMessageDataType = 'order' | 'product' | 'refund' | 'inventory' + +/** 失败消息列表记录 */ +export interface FailedMessageRecord { + id: number + error_id: string + data_type: FailedMessageDataType + platform: string + platform_id: number + company_id: number + store_id: number + error_type: string + error_message: string + retry_count: number + message_id: string + failed_at: string + created_at: string +} + +/** 失败消息详情(含错误堆栈和原始消息) */ +export interface FailedMessageDetail extends FailedMessageRecord { + error_code: string | null + error_trace: string | null + original_message: Record | null +} + +/** 失败消息筛选参数 */ +export interface FailedMessageFilters { + data_type: FailedMessageDataType | undefined + platform_id: number | undefined + failed_at_range: [string, string] | null +} + /** 业务异常 */ export class ApiError extends Error { code: number