update dashboard store

This commit is contained in:
2026-03-20 14:19:32 +08:00
parent 4d89875105
commit e9ffb85aff
3 changed files with 378 additions and 0 deletions
@@ -0,0 +1,208 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useDashboardStore } from '../dashboard'
vi.mock('@/utils/request', () => ({
api: {
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
delete: vi.fn(),
},
}))
const mockOverview = {
today: { success: 10, failed: 2 },
this_week: { success: 50, failed: 5 },
this_month: { success: 200, failed: 20 },
by_type: [
{ data_type: 'order', success: 80, failed: 10 },
{ data_type: 'product', success: 60, failed: 5 },
{ data_type: 'refund', success: 40, failed: 3 },
{ data_type: 'inventory', success: 20, failed: 2 },
],
}
const mockTrendData = [
{ date: '2026-03-18', success: 30, failed: 2 },
{ date: '2026-03-19', success: 25, failed: 3 },
{ date: '2026-03-20', success: 40, failed: 1 },
]
const mockBreakdownData = [
{ id: 1, name: 'Acme Corp', success: 100, failed: 10 },
{ id: 2, name: 'Beta Inc', success: 80, failed: 5 },
]
describe('useDashboardStore', () => {
beforeEach(() => {
setActivePinia(createPinia())
vi.restoreAllMocks()
})
describe('initial state', () => {
it('S1: starts with correct initial state', () => {
const store = useDashboardStore()
expect(store.overview).toBeNull()
expect(store.overviewLoading).toBe(false)
expect(store.trendData).toEqual([])
expect(store.trendGroupBy).toBe('day')
expect(store.breakdownData).toEqual([])
expect(store.breakdownDimension).toBe('company')
})
})
describe('fetchOverview', () => {
it('S2: fetches overview data successfully', async () => {
const { api } = await import('@/utils/request')
vi.mocked(api.get).mockResolvedValueOnce(mockOverview)
const store = useDashboardStore()
await store.fetchOverview()
expect(api.get).toHaveBeenCalledWith('/api/v1/dashboard/overview')
expect(store.overview).toEqual(mockOverview)
expect(store.overviewLoading).toBe(false)
expect(store.overviewError).toBe('')
})
it('S3: handles overview fetch error', async () => {
const { api } = await import('@/utils/request')
vi.mocked(api.get).mockRejectedValueOnce(new Error('网络错误'))
const store = useDashboardStore()
await store.fetchOverview()
expect(store.overview).toBeNull()
expect(store.overviewError).toBe('网络错误')
expect(store.overviewLoading).toBe(false)
})
})
describe('fetchTrend', () => {
it('S4: fetches trend data with default params', async () => {
const { api } = await import('@/utils/request')
vi.mocked(api.get).mockResolvedValueOnce(mockTrendData)
const store = useDashboardStore()
await store.fetchTrend()
expect(api.get).toHaveBeenCalledWith('/api/v1/dashboard/trend', expect.objectContaining({
group_by: 'day',
}))
expect(store.trendData).toEqual(mockTrendData)
})
it('S5: passes filter params to trend API', async () => {
const { api } = await import('@/utils/request')
vi.mocked(api.get).mockResolvedValueOnce(mockTrendData)
const store = useDashboardStore()
store.trendGroupBy = 'week'
store.trendDataType = 'order'
store.trendFrom = '2026-03-01'
store.trendTo = '2026-03-20'
await store.fetchTrend()
expect(api.get).toHaveBeenCalledWith('/api/v1/dashboard/trend', expect.objectContaining({
group_by: 'week',
data_type: 'order',
from: '2026-03-01',
to: '2026-03-20',
}))
})
it('S6: handles trend fetch error', async () => {
const { api } = await import('@/utils/request')
vi.mocked(api.get).mockRejectedValueOnce(new Error('趋势接口超时'))
const store = useDashboardStore()
await store.fetchTrend()
expect(store.trendData).toEqual([])
expect(store.trendError).toBe('趋势接口超时')
})
})
describe('fetchBreakdown', () => {
it('S7: fetches breakdown data with default params', async () => {
const { api } = await import('@/utils/request')
vi.mocked(api.get).mockResolvedValueOnce(mockBreakdownData)
const store = useDashboardStore()
await store.fetchBreakdown()
expect(api.get).toHaveBeenCalledWith('/api/v1/dashboard/breakdown', expect.objectContaining({
dimension: 'company',
}))
expect(store.breakdownData).toEqual(mockBreakdownData)
})
it('S8: passes filter params to breakdown API', async () => {
const { api } = await import('@/utils/request')
vi.mocked(api.get).mockResolvedValueOnce(mockBreakdownData)
const store = useDashboardStore()
store.breakdownDimension = 'store'
store.breakdownDataType = 'refund'
store.breakdownFrom = '2026-03-01'
store.breakdownTo = '2026-03-20'
await store.fetchBreakdown()
expect(api.get).toHaveBeenCalledWith('/api/v1/dashboard/breakdown', expect.objectContaining({
dimension: 'store',
data_type: 'refund',
from: '2026-03-01',
to: '2026-03-20',
}))
})
it('S9: handles breakdown fetch error', async () => {
const { api } = await import('@/utils/request')
vi.mocked(api.get).mockRejectedValueOnce(new Error('分维度接口失败'))
const store = useDashboardStore()
await store.fetchBreakdown()
expect(store.breakdownData).toEqual([])
expect(store.breakdownError).toBe('分维度接口失败')
})
})
describe('fetchAll', () => {
it('S10: calls all three fetch functions in parallel', async () => {
const { api } = await import('@/utils/request')
vi.mocked(api.get).mockImplementation((url: string) => {
if (url.includes('overview')) return Promise.resolve(mockOverview) as never
if (url.includes('trend')) return Promise.resolve(mockTrendData) as never
if (url.includes('breakdown')) return Promise.resolve(mockBreakdownData) as never
return Promise.resolve(null) as never
})
const store = useDashboardStore()
await store.fetchAll()
expect(store.overview).toEqual(mockOverview)
expect(store.trendData).toEqual(mockTrendData)
expect(store.breakdownData).toEqual(mockBreakdownData)
})
it('S11: one API failure does not block others', async () => {
const { api } = await import('@/utils/request')
vi.mocked(api.get).mockImplementation((url: string) => {
if (url.includes('overview')) return Promise.reject(new Error('概览失败')) as never
if (url.includes('trend')) return Promise.resolve(mockTrendData) as never
if (url.includes('breakdown')) return Promise.resolve(mockBreakdownData) as never
return Promise.resolve(null) as never
})
const store = useDashboardStore()
await store.fetchAll()
expect(store.overview).toBeNull()
expect(store.overviewError).toBe('概览失败')
expect(store.trendData).toEqual(mockTrendData)
expect(store.breakdownData).toEqual(mockBreakdownData)
})
})
})
+125
View File
@@ -0,0 +1,125 @@
import { api } from '@/utils/request'
import type {
DashboardOverview,
DashboardTrendPoint,
DashboardTrendParams,
DashboardBreakdownItem,
DashboardBreakdownParams,
} from '@/types/api'
export const useDashboardStore = defineStore('dashboard', () => {
// ─── Overview ───
const overview = ref<DashboardOverview | null>(null)
const overviewLoading = ref(false)
const overviewError = ref('')
// ─── Trend ───
const trendData = ref<DashboardTrendPoint[]>([])
const trendLoading = ref(false)
const trendError = ref('')
const trendGroupBy = ref<'day' | 'week' | 'month'>('day')
const trendDataType = ref<string | undefined>(undefined)
const trendFrom = ref<string | undefined>(undefined)
const trendTo = ref<string | undefined>(undefined)
// ─── Breakdown ───
const breakdownData = ref<DashboardBreakdownItem[]>([])
const breakdownLoading = ref(false)
const breakdownError = ref('')
const breakdownDimension = ref<'company' | 'platform' | 'store'>('company')
const breakdownDataType = ref<string | undefined>(undefined)
const breakdownFrom = ref<string | undefined>(undefined)
const breakdownTo = ref<string | undefined>(undefined)
async function fetchOverview() {
overviewLoading.value = true
overviewError.value = ''
try {
overview.value = await api.get<DashboardOverview>('/api/v1/dashboard/overview')
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : '获取概览数据失败'
overviewError.value = msg
message.error(msg)
} finally {
overviewLoading.value = false
}
}
async function fetchTrend() {
trendLoading.value = true
trendError.value = ''
try {
const params: DashboardTrendParams = {
group_by: trendGroupBy.value,
data_type: trendDataType.value,
from: trendFrom.value,
to: trendTo.value,
}
trendData.value = await api.get<DashboardTrendPoint[]>(
'/api/v1/dashboard/trend',
params as unknown as Record<string, unknown>,
)
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : '获取趋势数据失败'
trendError.value = msg
message.error(msg)
} finally {
trendLoading.value = false
}
}
async function fetchBreakdown() {
breakdownLoading.value = true
breakdownError.value = ''
try {
const params: DashboardBreakdownParams = {
dimension: breakdownDimension.value,
data_type: breakdownDataType.value,
from: breakdownFrom.value,
to: breakdownTo.value,
}
breakdownData.value = await api.get<DashboardBreakdownItem[]>(
'/api/v1/dashboard/breakdown',
params as unknown as Record<string, unknown>,
)
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : '获取分维度数据失败'
breakdownError.value = msg
message.error(msg)
} finally {
breakdownLoading.value = false
}
}
async function fetchAll() {
await Promise.allSettled([fetchOverview(), fetchTrend(), fetchBreakdown()])
}
return {
// Overview
overview,
overviewLoading,
overviewError,
// Trend
trendData,
trendLoading,
trendError,
trendGroupBy,
trendDataType,
trendFrom,
trendTo,
// Breakdown
breakdownData,
breakdownLoading,
breakdownError,
breakdownDimension,
breakdownDataType,
breakdownFrom,
breakdownTo,
// Actions
fetchOverview,
fetchTrend,
fetchBreakdown,
fetchAll,
}
})
+45
View File
@@ -208,6 +208,51 @@ export interface FailedMessageFilters {
failed_at_range: [string, string] | null failed_at_range: [string, string] | null
} }
/** ─── Dashboard 统计 ─── */
export interface SuccessFailedCount {
success: number
failed: number
}
export interface DataTypeCount extends SuccessFailedCount {
data_type: string
}
export interface DashboardOverview {
today: SuccessFailedCount
this_week: SuccessFailedCount
this_month: SuccessFailedCount
by_type: DataTypeCount[]
}
export interface DashboardTrendPoint {
date: string
success: number
failed: number
}
export interface DashboardTrendParams {
from?: string
to?: string
group_by?: 'day' | 'week' | 'month'
data_type?: string
}
export interface DashboardBreakdownItem {
id: number
name: string
success: number
failed: number
}
export interface DashboardBreakdownParams {
dimension?: 'company' | 'platform' | 'store'
from?: string
to?: string
data_type?: string
}
/** 业务异常 */ /** 业务异常 */
export class ApiError extends Error { export class ApiError extends Error {
code: number code: number