+
} onClick={() => showModal()}>
+ 创建工单
+
+ }>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ } htmlType="submit">
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
setProcessingModalVisible(false)}
+ footer={null}
+ width={600}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }>
+ 提交处理结果
+
+
+
+
+
+
+
+
setDetailModalVisible(false)}
+ footer={[
+ ,
+ selectedTicket?.status !== 'closed' && selectedTicket?.status !== 'completed' && (
+ } onClick={() => {
+ setDetailModalVisible(false);
+ handleProcess(selectedTicket);
+ }}>
+ 处理工单
+
+ )
+ ]}
+ width={900}
+ >
+ {selectedTicket && (
+
+
+
+ {selectedTicket.ticketId}
+ {selectedTicket.title}
+ {selectedTicket.deviceName}
+ {selectedTicket.serialNumber}
+ {selectedTicket.faultCategory}
+
+
+ {getPriorityText(selectedTicket.priority)}
+
+
+
+
+ {getStatusText(selectedTicket.status)}
+
+
+ {selectedTicket.reporter?.username || '-'}
+
+ {selectedTicket.createdAt ? dayjs(selectedTicket.createdAt).format('YYYY-MM-DD HH:mm:ss') : '-'}
+
+
+ {selectedTicket.expectedCompletionDate ? dayjs(selectedTicket.expectedCompletionDate).format('YYYY-MM-DD HH:mm:ss') : '-'}
+
+
+ {selectedTicket.completionDate ? dayjs(selectedTicket.completionDate).format('YYYY-MM-DD HH:mm:ss') : '-'}
+
+ {selectedTicket.description || '-'}
+ {selectedTicket.resolution || '-'}
+ {selectedTicket.notes || '-'}
+
+
+
+
+
+ {operationRecords.map((record, index) => (
+
+ {record.operationType}
+ 操作人: {record.operatorName || '-'}
+ 内容: {record.operationDescription || '-'}
+
+ ))}
+ {operationRecords.length === 0 && (
+ 暂无操作记录
+ )}
+
+
+
+ )}
+
+
+ );
+}
+
+export default TicketManagement;
diff --git a/frontend/src/pages/TicketStatistics.jsx b/frontend/src/pages/TicketStatistics.jsx
new file mode 100644
index 0000000..ec736a9
--- /dev/null
+++ b/frontend/src/pages/TicketStatistics.jsx
@@ -0,0 +1,457 @@
+import React, { useState, useEffect } from 'react';
+import { Card, Row, Col, Statistic, Table, DatePicker, Select, Space, Tag, message } from 'antd';
+import { BarChartOutlined, PieChartOutlined, RiseOutlined, FallOutlined, ClockCircleOutlined, CheckCircleOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import axios from 'axios';
+import dayjs from 'dayjs';
+
+const { RangePicker } = DatePicker;
+const { Option } = Select;
+
+function TicketStatistics() {
+ const [loading, setLoading] = useState(true);
+ const [dateRange, setDateRange] = useState([
+ dayjs().subtract(30, 'days'),
+ dayjs()
+ ]);
+ const [statistics, setStatistics] = useState({
+ total: 0,
+ pending: 0,
+ inProgress: 0,
+ completed: 0,
+ closed: 0,
+ avgProcessingTime: 0,
+ byCategory: [],
+ byPriority: [],
+ byStatus: [],
+ byDevice: [],
+ trend: []
+ });
+
+ const fetchStatistics = async () => {
+ try {
+ setLoading(true);
+ const params = {
+ startDate: dateRange[0].format('YYYY-MM-DD'),
+ endDate: dateRange[1].format('YYYY-MM-DD')
+ };
+
+ const response = await axios.get('/api/tickets/statistics', { params });
+ setStatistics(response.data);
+ } catch (error) {
+ message.error('获取统计数据失败');
+ console.error('获取统计数据失败:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchStatistics();
+ }, [dateRange]);
+
+ const handleDateChange = (dates) => {
+ if (dates) {
+ setDateRange(dates);
+ }
+ };
+
+ const getStatusColor = (status) => {
+ const colors = {
+ pending: 'orange',
+ assigned: 'blue',
+ in_progress: 'processing',
+ completed: 'green',
+ closed: 'default'
+ };
+ return colors[status] || 'default';
+ };
+
+ const getStatusText = (status) => {
+ const texts = {
+ pending: '待处理',
+ assigned: '已分配',
+ in_progress: '处理中',
+ completed: '已完成',
+ closed: '已关闭'
+ };
+ return texts[status] || status;
+ };
+
+ const getPriorityColor = (priority) => {
+ const colors = {
+ low: 'green',
+ medium: 'orange',
+ high: 'red',
+ urgent: 'magenta'
+ };
+ return colors[priority] || 'default';
+ };
+
+ const getPriorityText = (priority) => {
+ const texts = {
+ low: '低',
+ medium: '中',
+ high: '高',
+ urgent: '紧急'
+ };
+ return texts[priority] || priority;
+ };
+
+ const statusColumns = [
+ {
+ title: '状态',
+ dataIndex: 'status',
+ key: 'status',
+ width: 120,
+ render: (status) => (
+