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:
+136
-4
@@ -1,10 +1,44 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Card, Row, Col, Statistic, Table, Spin, message, Tag } from 'antd';
|
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 { 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() {
|
function DashboardPage() {
|
||||||
|
const navigate = useNavigate();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [stats, setStats] = useState<DashboardStats | null>(null);
|
const [stats, setStats] = useState<DashboardStats | null>(null);
|
||||||
|
|
||||||
@@ -26,7 +60,14 @@ function DashboardPage() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '400px' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '400px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Spin size="large" />
|
<Spin size="large" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -77,6 +118,49 @@ function DashboardPage() {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</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' }}>
|
<Card title="最近生成的序列号" style={{ marginBottom: '24px' }}>
|
||||||
<Table
|
<Table
|
||||||
columns={[
|
columns={[
|
||||||
@@ -114,8 +198,56 @@ function DashboardPage() {
|
|||||||
pagination={false}
|
pagination={false}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DashboardPage;
|
export default DashboardPage;
|
||||||
|
|||||||
@@ -212,6 +212,10 @@ export const dashboardApi = {
|
|||||||
totalEmployeeSerials: data.overview?.totalEmployeeSerials || 0,
|
totalEmployeeSerials: data.overview?.totalEmployeeSerials || 0,
|
||||||
activeSerials: data.overview?.activeSerials || 0,
|
activeSerials: data.overview?.activeSerials || 0,
|
||||||
inactiveSerials: data.overview?.inactiveSerials || 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 || [],
|
monthlyData: data.monthlyStats || [],
|
||||||
recentCompanies: data.recentCompanies?.map((c: any) => ({
|
recentCompanies: data.recentCompanies?.map((c: any) => ({
|
||||||
id: c.companyName,
|
id: c.companyName,
|
||||||
@@ -227,6 +231,7 @@ export const dashboardApi = {
|
|||||||
createdAt: s.createdAt,
|
createdAt: s.createdAt,
|
||||||
type: s.type,
|
type: s.type,
|
||||||
})) || [],
|
})) || [],
|
||||||
|
recentAftersales: data.recentAftersales || [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
throw new Error('获取统计数据失败');
|
throw new Error('获取统计数据失败');
|
||||||
|
|||||||
@@ -99,12 +99,26 @@ export interface ApiResponse<T> {
|
|||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DashboardRecentAftersales {
|
||||||
|
serialNumber: string;
|
||||||
|
companyName: string;
|
||||||
|
serviceType: AftersalesServiceType;
|
||||||
|
workOrderStatus: AftersalesWorkOrderStatus;
|
||||||
|
authorizationStatus: AftersalesAuthorizationStatus;
|
||||||
|
technicianName: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DashboardStats {
|
export interface DashboardStats {
|
||||||
totalCompanies: number;
|
totalCompanies: number;
|
||||||
totalSerials: number;
|
totalSerials: number;
|
||||||
totalEmployeeSerials: number;
|
totalEmployeeSerials: number;
|
||||||
activeSerials: number;
|
activeSerials: number;
|
||||||
inactiveSerials: number;
|
inactiveSerials: number;
|
||||||
|
totalAftersales: number;
|
||||||
|
pendingConfirmation: number;
|
||||||
|
closedAftersales: number;
|
||||||
|
rejectedAftersales: number;
|
||||||
monthlyData: Array<{
|
monthlyData: Array<{
|
||||||
month: string;
|
month: string;
|
||||||
companies: number;
|
companies: number;
|
||||||
@@ -112,6 +126,7 @@ export interface DashboardStats {
|
|||||||
}>;
|
}>;
|
||||||
recentCompanies: Company[];
|
recentCompanies: Company[];
|
||||||
recentSerials: Serial[];
|
recentSerials: Serial[];
|
||||||
|
recentAftersales: DashboardRecentAftersales[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompanyFilter {
|
export interface CompanyFilter {
|
||||||
|
|||||||
Reference in New Issue
Block a user