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:
+135
-3
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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('获取统计数据失败');
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user