feat(工单): 支持手动输入设备信息或从列表选择
添加设备来源选项,允许用户在创建工单时选择从设备列表选择或手动输入设备信息 后端接口适配处理两种设备来源方式 添加terser依赖用于前端代码压缩优化
This commit is contained in:
+35
-12
@@ -269,6 +269,8 @@ router.post('/', async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
deviceId,
|
||||
deviceName,
|
||||
serialNumber,
|
||||
faultCategory,
|
||||
faultSubCategory,
|
||||
priority,
|
||||
@@ -279,23 +281,44 @@ router.post('/', async (req, res) => {
|
||||
tags
|
||||
} = req.body;
|
||||
|
||||
// 获取设备信息
|
||||
const device = await Device.findByPk(deviceId, {
|
||||
include: [{ model: require('../models/Rack') }]
|
||||
});
|
||||
if (!device) {
|
||||
return res.status(404).json({ error: '设备不存在' });
|
||||
let device = null;
|
||||
let ticketDeviceName = '';
|
||||
let ticketDeviceModel = '';
|
||||
let ticketSerialNumber = '';
|
||||
let ticketLocation = '';
|
||||
|
||||
// 判断是选择设备还是手动输入
|
||||
if (deviceId) {
|
||||
// 从设备列表选择
|
||||
device = await Device.findByPk(deviceId, {
|
||||
include: [{ model: require('../models/Rack') }]
|
||||
});
|
||||
if (!device) {
|
||||
return res.status(404).json({ error: '设备不存在' });
|
||||
}
|
||||
ticketDeviceName = device.name;
|
||||
ticketDeviceModel = device.model;
|
||||
ticketSerialNumber = device.serialNumber;
|
||||
ticketLocation = device.Rack ? `${device.Rack.name}` : '未知位置';
|
||||
} else if (deviceName && serialNumber) {
|
||||
// 手动输入设备信息
|
||||
ticketDeviceName = deviceName;
|
||||
ticketSerialNumber = serialNumber;
|
||||
ticketDeviceModel = '';
|
||||
ticketLocation = '未知位置';
|
||||
} else {
|
||||
return res.status(400).json({ error: '请选择设备或手动输入设备信息' });
|
||||
}
|
||||
|
||||
const ticketId = `TKT${Date.now().toString(36).toUpperCase()}${Math.random().toString(36).substr(2, 4).toUpperCase()}`;
|
||||
|
||||
const ticket = await Ticket.create({
|
||||
ticketId,
|
||||
title: title || `${faultCategory} - ${device.name}`,
|
||||
deviceId,
|
||||
deviceName: device.name,
|
||||
deviceModel: device.model,
|
||||
serialNumber: device.serialNumber,
|
||||
title: title || `${faultCategory} - ${ticketDeviceName}`,
|
||||
deviceId: deviceId || null,
|
||||
deviceName: ticketDeviceName,
|
||||
deviceModel: ticketDeviceModel,
|
||||
serialNumber: ticketSerialNumber,
|
||||
faultCategory,
|
||||
faultSubCategory,
|
||||
priority: priority || 'medium',
|
||||
@@ -303,7 +326,7 @@ router.post('/', async (req, res) => {
|
||||
expectedCompletionDate: expectedCompletionDate ? new Date(expectedCompletionDate) : null,
|
||||
reporterId: req.body.reporterId || 'USER001',
|
||||
reporterName: req.body.reporterName || '系统用户',
|
||||
location: device.Rack ? `${device.Rack.name}` : '未知位置',
|
||||
location: ticketLocation,
|
||||
attachments: attachments || [],
|
||||
tags: tags || [],
|
||||
status: 'pending'
|
||||
|
||||
Generated
+80
@@ -24,6 +24,7 @@
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@vitejs/plugin-react": "^4.0.3",
|
||||
"jsdom": "^27.3.0",
|
||||
"terser": "^5.44.1",
|
||||
"vite": "^4.4.9",
|
||||
"vitest": "^4.0.16"
|
||||
}
|
||||
@@ -1126,6 +1127,17 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
@@ -1908,6 +1920,19 @@
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/adler-32": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
|
||||
@@ -2127,6 +2152,13 @@
|
||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
@@ -2220,6 +2252,13 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/compute-scroll-into-view": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
|
||||
@@ -3890,6 +3929,16 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -3900,6 +3949,17 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ssf": {
|
||||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
|
||||
@@ -3958,6 +4018,26 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.44.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz",
|
||||
"integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.15.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
"bin": {
|
||||
"terser": "bin/terser"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/three": {
|
||||
"version": "0.160.1",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.160.1.tgz",
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@vitejs/plugin-react": "^4.0.3",
|
||||
"jsdom": "^27.3.0",
|
||||
"terser": "^5.44.1",
|
||||
"vite": "^4.4.9",
|
||||
"vitest": "^4.0.16"
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ function TicketManagement() {
|
||||
});
|
||||
|
||||
const [searchFilters, setSearchFilters] = useState({});
|
||||
const [deviceSource, setDeviceSource] = useState('select');
|
||||
|
||||
const fetchTickets = useCallback(async (page = 1, pageSize = 10, filters = {}) => {
|
||||
try {
|
||||
@@ -148,9 +149,18 @@ function TicketManagement() {
|
||||
if (ticketData.completionDate) {
|
||||
ticketData.completionDate = dayjs(ticketData.completionDate);
|
||||
}
|
||||
if (ticket.deviceId) {
|
||||
setDeviceSource('select');
|
||||
ticketData.deviceId = ticket.deviceId;
|
||||
} else {
|
||||
setDeviceSource('manual');
|
||||
ticketData.deviceName = ticket.deviceName;
|
||||
ticketData.serialNumber = ticket.serialNumber;
|
||||
}
|
||||
form.setFieldsValue(ticketData);
|
||||
} else {
|
||||
form.resetFields();
|
||||
setDeviceSource('select');
|
||||
}
|
||||
setModalVisible(true);
|
||||
}, []);
|
||||
@@ -168,6 +178,12 @@ function TicketManagement() {
|
||||
completionDate: values.completionDate ? values.completionDate.format('YYYY-MM-DD HH:mm:ss') : null
|
||||
};
|
||||
|
||||
if (deviceSource === 'manual') {
|
||||
ticketData.deviceId = null;
|
||||
ticketData.deviceName = values.deviceName;
|
||||
ticketData.serialNumber = values.serialNumber;
|
||||
}
|
||||
|
||||
if (editingTicket) {
|
||||
await axios.put(`/api/tickets/${editingTicket.ticketId}`, ticketData);
|
||||
message.success('工单更新成功');
|
||||
@@ -182,11 +198,12 @@ function TicketManagement() {
|
||||
setModalVisible(false);
|
||||
fetchTickets();
|
||||
setEditingTicket(null);
|
||||
setDeviceSource('select');
|
||||
} catch (error) {
|
||||
message.error(editingTicket ? '工单更新失败' : '工单创建失败');
|
||||
console.error(error);
|
||||
}
|
||||
}, [editingTicket, fetchTickets]);
|
||||
}, [editingTicket, fetchTickets, deviceSource]);
|
||||
|
||||
const handleDelete = useCallback(async (ticketId) => {
|
||||
Modal.confirm({
|
||||
@@ -473,16 +490,34 @@ function TicketManagement() {
|
||||
<Input placeholder="请输入工单标题" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="deviceId" label="关联设备" rules={[{ required: true }]}>
|
||||
<Select placeholder="选择设备" showSearch optionFilterProp="children">
|
||||
{devices.map(device => (
|
||||
<Option key={device.deviceId} value={device.deviceId}>
|
||||
{device.name} - {device.serialNumber}
|
||||
</Option>
|
||||
))}
|
||||
<Form.Item label="设备来源">
|
||||
<Select value={deviceSource} onChange={setDeviceSource} style={{ width: 200 }}>
|
||||
<Option value="select">从设备列表选择</Option>
|
||||
<Option value="manual">手动输入设备信息</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
{deviceSource === 'select' ? (
|
||||
<Form.Item name="deviceId" label="关联设备" rules={[{ required: true }]}>
|
||||
<Select placeholder="选择设备" showSearch optionFilterProp="children">
|
||||
{devices.map(device => (
|
||||
<Option key={device.deviceId} value={device.deviceId}>
|
||||
{device.name} - {device.serialNumber}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
) : (
|
||||
<>
|
||||
<Form.Item name="deviceName" label="设备名称" rules={[{ required: true }]}>
|
||||
<Input placeholder="请输入设备名称" />
|
||||
</Form.Item>
|
||||
<Form.Item name="serialNumber" label="设备序列号" rules={[{ required: true }]}>
|
||||
<Input placeholder="请输入设备序列号" />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Form.Item name="faultCategory" label="故障分类" rules={[{ required: true }]}>
|
||||
<Select placeholder="选择故障分类">
|
||||
{categories.map(cat => (
|
||||
|
||||
Reference in New Issue
Block a user