- 工作台 KPI 与订单趋势接入 workplace-stats/trend API - 实时监控页接入 monitor/overview,含订单/客户/轨迹统计与图表 - 新增 monitor/workplace API 封装及 nginx/vite 代理配置 Co-authored-by: Cursor <cursoragent@cursor.com>
207 lines
7.3 KiB
TypeScript
207 lines
7.3 KiB
TypeScript
import { axiosInstance } from '@/api/rbac'
|
|
import { resolveAdminApiPath } from '@/utils/adminApi'
|
|
|
|
function monitorUrl(path: string): string {
|
|
return resolveAdminApiPath(path)
|
|
}
|
|
|
|
function assertBusinessOk(body: unknown) {
|
|
if (body == null) throw new Error('空响应')
|
|
const payload = body as Record<string, unknown>
|
|
const code = payload.code ?? payload.Code
|
|
const ok =
|
|
code === undefined ||
|
|
code === null ||
|
|
code === 0 ||
|
|
code === 200 ||
|
|
code === '0' ||
|
|
code === '200' ||
|
|
payload.success === true ||
|
|
payload.Success === true
|
|
if (!ok) {
|
|
throw new Error(
|
|
String(payload.msg ?? payload.Msg ?? payload.message ?? payload.Message ?? '请求失败'),
|
|
)
|
|
}
|
|
}
|
|
|
|
function num(raw: unknown): number {
|
|
const value = Number(raw ?? 0)
|
|
return Number.isFinite(value) ? value : 0
|
|
}
|
|
|
|
function growth(raw: unknown): number | null {
|
|
if (raw === null || raw === undefined || raw === '') return null
|
|
const value = Number(raw)
|
|
return Number.isFinite(value) ? value : null
|
|
}
|
|
|
|
function normalizeRegionTrend(raw: unknown) {
|
|
const bundle = (raw ?? {}) as Record<string, unknown>
|
|
const normalizePeriod = (periodRaw: unknown) => {
|
|
const period = (periodRaw ?? {}) as Record<string, unknown>
|
|
const labels = period.labels ?? period.Labels
|
|
const seriesRaw = period.series ?? period.Series
|
|
return {
|
|
labels: Array.isArray(labels) ? labels.map(String) : [],
|
|
series: Array.isArray(seriesRaw)
|
|
? seriesRaw.map((item) => {
|
|
const row = item as Record<string, unknown>
|
|
const values = row.values ?? row.Values
|
|
return {
|
|
region: String(row.region ?? row.Region ?? ''),
|
|
values: Array.isArray(values) ? values.map((v) => num(v)) : [],
|
|
}
|
|
})
|
|
: [],
|
|
}
|
|
}
|
|
|
|
return {
|
|
week: normalizePeriod(bundle.week ?? bundle.Week),
|
|
month: normalizePeriod(bundle.month ?? bundle.Month),
|
|
}
|
|
}
|
|
|
|
export interface MonitorOverview {
|
|
order: {
|
|
todayCreated: number
|
|
todayCreatedGrowthPercent: number | null
|
|
total: number
|
|
totalMonthGrowthPercent: number | null
|
|
pendingPayment: number
|
|
pendingOnline: number
|
|
pendingSign: number
|
|
problematic: number
|
|
todayRevenue: number
|
|
todayRevenueGrowthPercent: number | null
|
|
}
|
|
customer: {
|
|
totalCustomers: number
|
|
totalCustomersMonthGrowthPercent: number | null
|
|
newCustomersToday: number
|
|
newCustomersGrowthPercent: number | null
|
|
onlineCustomers: number
|
|
inactiveLogin7d: number
|
|
inactiveLogin7dDelta: number
|
|
}
|
|
orderTrend7d: Array<{ label: string; count: number }>
|
|
revenueTrend7d: Array<{ label: string; revenue: number; grossProfit: number }>
|
|
regionOrderTrend: ReturnType<typeof normalizeRegionTrend>
|
|
regionReceivableTrend: ReturnType<typeof normalizeRegionTrend>
|
|
regionSignRates: Array<{ region: string; rate: number }>
|
|
trackRates: {
|
|
trackCompleteRate: number
|
|
trackTimelyRate: number
|
|
otdRate: number
|
|
}
|
|
}
|
|
|
|
function normalizeOverview(raw: Record<string, unknown>): MonitorOverview {
|
|
const order = (raw.order ?? raw.Order ?? {}) as Record<string, unknown>
|
|
const customer = (raw.customer ?? raw.Customer ?? {}) as Record<string, unknown>
|
|
const track = (raw.trackRates ?? raw.TrackRates ?? {}) as Record<string, unknown>
|
|
const orderTrendRaw = raw.orderTrend7d ?? raw.OrderTrend7d
|
|
const revenueTrendRaw = raw.revenueTrend7d ?? raw.RevenueTrend7d
|
|
const signRatesRaw = raw.regionSignRates ?? raw.RegionSignRates
|
|
|
|
return {
|
|
order: {
|
|
todayCreated: num(order.todayCreated ?? order.TodayCreated),
|
|
todayCreatedGrowthPercent: growth(order.todayCreatedGrowthPercent ?? order.TodayCreatedGrowthPercent),
|
|
total: num(order.total ?? order.Total),
|
|
totalMonthGrowthPercent: growth(order.totalMonthGrowthPercent ?? order.TotalMonthGrowthPercent),
|
|
pendingPayment: num(order.pendingPayment ?? order.PendingPayment),
|
|
pendingOnline: num(order.pendingOnline ?? order.PendingOnline),
|
|
pendingSign: num(order.pendingSign ?? order.PendingSign),
|
|
problematic: num(order.problematic ?? order.Problematic),
|
|
todayRevenue: num(order.todayRevenue ?? order.TodayRevenue),
|
|
todayRevenueGrowthPercent: growth(order.todayRevenueGrowthPercent ?? order.TodayRevenueGrowthPercent),
|
|
},
|
|
customer: {
|
|
totalCustomers: num(customer.totalCustomers ?? customer.TotalCustomers),
|
|
totalCustomersMonthGrowthPercent: growth(
|
|
customer.totalCustomersMonthGrowthPercent ?? customer.TotalCustomersMonthGrowthPercent,
|
|
),
|
|
newCustomersToday: num(customer.newCustomersToday ?? customer.NewCustomersToday),
|
|
newCustomersGrowthPercent: growth(
|
|
customer.newCustomersGrowthPercent ?? customer.NewCustomersGrowthPercent,
|
|
),
|
|
onlineCustomers: num(customer.onlineCustomers ?? customer.OnlineCustomers),
|
|
inactiveLogin7d: num(customer.inactiveLogin7d ?? customer.InactiveLogin7d),
|
|
inactiveLogin7dDelta: num(customer.inactiveLogin7dDelta ?? customer.InactiveLogin7dDelta),
|
|
},
|
|
orderTrend7d: Array.isArray(orderTrendRaw)
|
|
? orderTrendRaw.map((item) => {
|
|
const row = item as Record<string, unknown>
|
|
return {
|
|
label: String(row.label ?? row.Label ?? ''),
|
|
count: num(row.count ?? row.Count),
|
|
}
|
|
})
|
|
: [],
|
|
revenueTrend7d: Array.isArray(revenueTrendRaw)
|
|
? revenueTrendRaw.map((item) => {
|
|
const row = item as Record<string, unknown>
|
|
return {
|
|
label: String(row.label ?? row.Label ?? ''),
|
|
revenue: num(row.revenue ?? row.Revenue),
|
|
grossProfit: num(row.grossProfit ?? row.GrossProfit),
|
|
}
|
|
})
|
|
: [],
|
|
regionOrderTrend: normalizeRegionTrend(raw.regionOrderTrend ?? raw.RegionOrderTrend),
|
|
regionReceivableTrend: normalizeRegionTrend(raw.regionReceivableTrend ?? raw.RegionReceivableTrend),
|
|
regionSignRates: Array.isArray(signRatesRaw)
|
|
? signRatesRaw.map((item) => {
|
|
const row = item as Record<string, unknown>
|
|
return {
|
|
region: String(row.region ?? row.Region ?? ''),
|
|
rate: num(row.rate ?? row.Rate),
|
|
}
|
|
})
|
|
: [],
|
|
trackRates: {
|
|
trackCompleteRate: num(track.trackCompleteRate ?? track.TrackCompleteRate),
|
|
trackTimelyRate: num(track.trackTimelyRate ?? track.TrackTimelyRate),
|
|
otdRate: num(track.otdRate ?? track.OtdRate),
|
|
},
|
|
}
|
|
}
|
|
|
|
/** 实时监控概览 GET /monitor/overview */
|
|
export async function getMonitorOverview(): Promise<MonitorOverview> {
|
|
const res = await axiosInstance.get(monitorUrl('/monitor/overview'))
|
|
assertBusinessOk(res.data)
|
|
const payload = res.data as Record<string, unknown>
|
|
const data = (payload.data ?? payload.Data ?? payload) as Record<string, unknown>
|
|
return normalizeOverview(data)
|
|
}
|
|
|
|
export function formatMonitorNumber(value: number): string {
|
|
return Number(value || 0).toLocaleString()
|
|
}
|
|
|
|
export function formatMonitorCurrency(value: number): string {
|
|
return `¥ ${Number(value || 0).toLocaleString(undefined, {
|
|
minimumFractionDigits: 0,
|
|
maximumFractionDigits: 0,
|
|
})}`
|
|
}
|
|
|
|
export function formatMonitorGrowth(value: number | null | undefined): {
|
|
show: boolean
|
|
up: boolean
|
|
text: string
|
|
} {
|
|
if (value == null || Number.isNaN(Number(value))) {
|
|
return { show: false, up: true, text: '' }
|
|
}
|
|
const numValue = Number(value)
|
|
return {
|
|
show: true,
|
|
up: numValue >= 0,
|
|
text: `${Math.abs(numValue).toFixed(1)}%`,
|
|
}
|
|
}
|