Show aftersales stats and recent orders on dashboard

- Adds a second row of cards (total / pending / closed / rejected)
- New "最近售后工单" table linking to detail pages
- DashboardStats type extended; dashboardApi maps backend overview

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Frudrax Cheng
2026-05-26 11:04:25 +08:00
parent eab66bc3e9
commit f61004ba12
3 changed files with 156 additions and 4 deletions
+135 -3
View File
@@ -1,10 +1,44 @@
import { useEffect, useState } from 'react';
import { Card, Row, Col, Statistic, Table, Spin, message, Tag } from 'antd';
import { TeamOutlined, KeyOutlined, CheckCircleOutlined, UserOutlined } from '@ant-design/icons';
import {
TeamOutlined,
KeyOutlined,
CheckCircleOutlined,
UserOutlined,
ToolOutlined,
ClockCircleOutlined,
ExclamationCircleOutlined,
} from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import { dashboardApi } from '@/services/api';
import type { DashboardStats } from '@/types';
import type {
DashboardStats,
AftersalesServiceType,
AftersalesWorkOrderStatus,
} from '@/types';
const SERVICE_TYPE_LABEL: Record<AftersalesServiceType, string> = {
software: '软件',
hardware: '硬件',
other: '其他',
};
const WORK_ORDER_STATUS_LABEL: Record<AftersalesWorkOrderStatus, string> = {
created: '待处理',
pending_confirmation: '待客户确认',
closed: '已完成',
rejected: '已退回',
};
const WORK_ORDER_STATUS_COLOR: Record<AftersalesWorkOrderStatus, string> = {
created: 'default',
pending_confirmation: 'processing',
closed: 'success',
rejected: 'warning',
};
function DashboardPage() {
const navigate = useNavigate();
const [loading, setLoading] = useState(true);
const [stats, setStats] = useState<DashboardStats | null>(null);
@@ -26,7 +60,14 @@ function DashboardPage() {
if (loading) {
return (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '400px' }}>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '400px',
}}
>
<Spin size="large" />
</div>
);
@@ -77,6 +118,49 @@ function DashboardPage() {
</Col>
</Row>
<Row gutter={[16, 16]} style={{ marginBottom: '24px' }}>
<Col xs={24} sm={12} lg={6}>
<Card>
<Statistic
title="售后工单总数"
value={stats?.totalAftersales || 0}
prefix={<ToolOutlined />}
valueStyle={{ color: '#165DFF' }}
/>
</Card>
</Col>
<Col xs={24} sm={12} lg={6}>
<Card>
<Statistic
title="待客户确认"
value={stats?.pendingConfirmation || 0}
prefix={<ClockCircleOutlined />}
valueStyle={{ color: '#faad14' }}
/>
</Card>
</Col>
<Col xs={24} sm={12} lg={6}>
<Card>
<Statistic
title="已完成工单"
value={stats?.closedAftersales || 0}
prefix={<CheckCircleOutlined />}
valueStyle={{ color: '#52c41a' }}
/>
</Card>
</Col>
<Col xs={24} sm={12} lg={6}>
<Card>
<Statistic
title="已退回工单"
value={stats?.rejectedAftersales || 0}
prefix={<ExclamationCircleOutlined />}
valueStyle={{ color: '#ff4d4f' }}
/>
</Card>
</Col>
</Row>
<Card title="最近生成的序列号" style={{ marginBottom: '24px' }}>
<Table
columns={[
@@ -114,6 +198,54 @@ function DashboardPage() {
pagination={false}
/>
</Card>
<Card title="最近售后工单">
<Table
columns={[
{
title: '工单号',
dataIndex: 'serialNumber',
key: 'serialNumber',
render: (sn: string) => (
<a
style={{ fontFamily: 'monospace', color: '#165DFF' }}
onClick={() => navigate(`/admin/aftersales/${sn}`)}
>
{sn}
</a>
),
},
{ title: '客户公司', dataIndex: 'companyName', key: 'companyName' },
{
title: '服务类型',
dataIndex: 'serviceType',
key: 'serviceType',
render: (type: AftersalesServiceType) =>
SERVICE_TYPE_LABEL[type] || type,
},
{ title: '处理人', dataIndex: 'technicianName', key: 'technicianName' },
{
title: '状态',
dataIndex: 'workOrderStatus',
key: 'workOrderStatus',
render: (status: AftersalesWorkOrderStatus) => (
<Tag color={WORK_ORDER_STATUS_COLOR[status]}>
{WORK_ORDER_STATUS_LABEL[status]}
</Tag>
),
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
render: (date: string) => new Date(date).toLocaleString('zh-CN'),
},
]}
dataSource={stats?.recentAftersales || []}
rowKey="serialNumber"
pagination={false}
/>
</Card>
</div>
);
}
+5
View File
@@ -212,6 +212,10 @@ export const dashboardApi = {
totalEmployeeSerials: data.overview?.totalEmployeeSerials || 0,
activeSerials: data.overview?.activeSerials || 0,
inactiveSerials: data.overview?.inactiveSerials || 0,
totalAftersales: data.overview?.totalAftersales || 0,
pendingConfirmation: data.overview?.pendingConfirmation || 0,
closedAftersales: data.overview?.closedAftersales || 0,
rejectedAftersales: data.overview?.rejectedAftersales || 0,
monthlyData: data.monthlyStats || [],
recentCompanies: data.recentCompanies?.map((c: any) => ({
id: c.companyName,
@@ -227,6 +231,7 @@ export const dashboardApi = {
createdAt: s.createdAt,
type: s.type,
})) || [],
recentAftersales: data.recentAftersales || [],
};
}
throw new Error('获取统计数据失败');
+15
View File
@@ -99,12 +99,26 @@ export interface ApiResponse<T> {
error?: string;
}
export interface DashboardRecentAftersales {
serialNumber: string;
companyName: string;
serviceType: AftersalesServiceType;
workOrderStatus: AftersalesWorkOrderStatus;
authorizationStatus: AftersalesAuthorizationStatus;
technicianName: string;
createdAt: string;
}
export interface DashboardStats {
totalCompanies: number;
totalSerials: number;
totalEmployeeSerials: number;
activeSerials: number;
inactiveSerials: number;
totalAftersales: number;
pendingConfirmation: number;
closedAftersales: number;
rejectedAftersales: number;
monthlyData: Array<{
month: string;
companies: number;
@@ -112,6 +126,7 @@ export interface DashboardStats {
}>;
recentCompanies: Company[];
recentSerials: Serial[];
recentAftersales: DashboardRecentAftersales[];
}
export interface CompanyFilter {