From f32684fc5c112ade70c80d5a191b70de753e0fb9 Mon Sep 17 00:00:00 2001 From: zhang1106 <849185023@qq.com> Date: Wed, 1 Apr 2026 10:14:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(server):=20=E6=B7=BB=E5=8A=A0=E5=85=AC?= =?UTF-8?q?=E5=85=B1=E8=B7=AF=E5=BE=84=E8=AE=A4=E8=AF=81=E4=B8=AD=E9=97=B4?= =?UTF-8?q?=E4=BB=B6=E5=92=8C=E6=89=B9=E9=87=8F=E6=93=8D=E4=BD=9C=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor(routes): 重新组织路由顺序并添加批量删除功能 - 在server.js中添加公共路径认证中间件 - 为cables、devicePorts等路由添加批量删除功能 - 重新组织路由顺序,将相关操作分组 - 添加工单导出功能 --- backend/routes/cables.js | 62 +++---- backend/routes/consumables.js | 106 +++++------ backend/routes/devicePorts.js | 224 +++++++++--------------- backend/routes/networkCards.js | 64 +++---- backend/routes/tickets.js | 311 +++++++++++++++++---------------- backend/server.js | 23 +++ 6 files changed, 382 insertions(+), 408 deletions(-) diff --git a/backend/routes/cables.js b/backend/routes/cables.js index 3ebe75c..e417f6f 100644 --- a/backend/routes/cables.js +++ b/backend/routes/cables.js @@ -500,36 +500,7 @@ router.put('/:cableId', async (req, res) => { } }); -router.delete('/:cableId', async (req, res) => { - try { - // 先获取接线信息,用于后续恢复端口状态 - const cable = await Cable.findByPk(req.params.cableId); - - if (!cable) { - return res.status(404).json({ error: '接线不存在' }); - } - - const { sourceDeviceId, sourcePort, targetDeviceId, targetPort } = cable; - - const deleted = await Cable.destroy({ - where: { cableId: req.params.cableId }, - }); - - if (deleted) { - // 自动将源端口和目标端口状态恢复为free - await freePort(sourceDeviceId, sourcePort); - await freePort(targetDeviceId, targetPort); - - res.status(204).json(); - } else { - res.status(404).json({ error: '接线不存在' }); - } - } catch (error) { - console.error('删除接线失败:', error); - res.status(500).json({ error: error.message }); - } -}); - +// 批量删除接线 router.delete('/batch', async (req, res) => { try { const { cableIds } = req.body; @@ -563,6 +534,37 @@ router.delete('/batch', async (req, res) => { } }); +// 删除单个接线 +router.delete('/:cableId', async (req, res) => { + try { + // 先获取接线信息,用于后续恢复端口状态 + const cable = await Cable.findByPk(req.params.cableId); + + if (!cable) { + return res.status(404).json({ error: '接线不存在' }); + } + + const { sourceDeviceId, sourcePort, targetDeviceId, targetPort } = cable; + + const deleted = await Cable.destroy({ + where: { cableId: req.params.cableId }, + }); + + if (deleted) { + // 自动将源端口和目标端口状态恢复为free + await freePort(sourceDeviceId, sourcePort); + await freePort(targetDeviceId, targetPort); + + res.status(204).json(); + } else { + res.status(404).json({ error: '接线不存在' }); + } + } catch (error) { + console.error('删除接线失败:', error); + res.status(500).json({ error: error.message }); + } +}); + router.get('/:cableId', async (req, res) => { try { const cable = await Cable.findByPk(req.params.cableId, { diff --git a/backend/routes/consumables.js b/backend/routes/consumables.js index 132fc52..d459310 100644 --- a/backend/routes/consumables.js +++ b/backend/routes/consumables.js @@ -1116,6 +1116,59 @@ router.post('/logs/import', async (req, res) => { } }); +// 查询归档记录列表 +router.get('/archives', async (req, res) => { + try { + const { keyword, page = 1, pageSize = 10 } = req.query; + const offset = (page - 1) * pageSize; + + const where = {}; + + if (keyword) { + where[Op.or] = [ + { consumableId: { [Op.like]: `%${keyword}%` } }, + { consumableName: { [Op.like]: `%${keyword}%` } }, + { archiveId: { [Op.like]: `%${keyword}%` } }, + ]; + } + + const { count, rows } = await ConsumableLogArchive.findAndCountAll({ + where, + offset, + limit: parseInt(pageSize), + order: [['deletedAt', 'DESC']], + }); + + res.json({ + total: count, + archives: rows, + page: parseInt(page), + pageSize: parseInt(pageSize), + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// 查询单个归档记录详情 +router.get('/archives/:archiveId', async (req, res) => { + try { + const { archiveId } = req.params; + + const archive = await ConsumableLogArchive.findOne({ + where: { archiveId }, + }); + + if (!archive) { + return res.status(404).json({ error: '归档记录不存在' }); + } + + res.json(archive); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + router.get('/:id', async (req, res) => { try { const consumable = await Consumable.findByPk(req.params.id); @@ -1286,59 +1339,6 @@ router.delete('/:id', async (req, res) => { } }); -// 查询归档记录列表 -router.get('/archives', async (req, res) => { - try { - const { keyword, page = 1, pageSize = 10 } = req.query; - const offset = (page - 1) * pageSize; - - const where = {}; - - if (keyword) { - where[Op.or] = [ - { consumableId: { [Op.like]: `%${keyword}%` } }, - { consumableName: { [Op.like]: `%${keyword}%` } }, - { archiveId: { [Op.like]: `%${keyword}%` } }, - ]; - } - - const { count, rows } = await ConsumableLogArchive.findAndCountAll({ - where, - offset, - limit: parseInt(pageSize), - order: [['deletedAt', 'DESC']], - }); - - res.json({ - total: count, - archives: rows, - page: parseInt(page), - pageSize: parseInt(pageSize), - }); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}); - -// 查询单个归档记录详情 -router.get('/archives/:archiveId', async (req, res) => { - try { - const { archiveId } = req.params; - - const archive = await ConsumableLogArchive.findOne({ - where: { archiveId }, - }); - - if (!archive) { - return res.status(404).json({ error: '归档记录不存在' }); - } - - res.json(archive); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}); - // 修改日志记录 router.put('/logs/:id', async (req, res) => { const transaction = await sequelize.transaction(); diff --git a/backend/routes/devicePorts.js b/backend/routes/devicePorts.js index 9f577bb..c0d6709 100644 --- a/backend/routes/devicePorts.js +++ b/backend/routes/devicePorts.js @@ -325,6 +325,30 @@ router.put('/:portId', async (req, res) => { } }); +// 批量删除端口 +router.delete('/batch', async (req, res) => { + try { + const { portIds } = req.body; + + if (!portIds || !Array.isArray(portIds) || portIds.length === 0) { + return res.status(400).json({ error: '请提供有效的端口ID列表' }); + } + + const deletedCount = await DevicePort.destroy({ + where: { portId: { [Op.in]: portIds } }, + }); + + res.json({ + message: `批量删除成功,已删除 ${deletedCount} 个端口`, + deletedCount, + }); + } catch (error) { + console.error('批量删除端口失败:', error); + res.status(500).json({ error: error.message }); + } +}); + +// 删除单个端口 router.delete('/:portId', async (req, res) => { try { const port = await DevicePort.findByPk(req.params.portId); @@ -365,28 +389,6 @@ router.delete('/:portId', async (req, res) => { } }); -router.delete('/batch', async (req, res) => { - try { - const { portIds } = req.body; - - if (!portIds || !Array.isArray(portIds) || portIds.length === 0) { - return res.status(400).json({ error: '请提供有效的端口ID列表' }); - } - - const deletedCount = await DevicePort.destroy({ - where: { portId: { [Op.in]: portIds } }, - }); - - res.json({ - message: `批量删除成功,已删除 ${deletedCount} 个端口`, - deletedCount, - }); - } catch (error) { - console.error('批量删除端口失败:', error); - res.status(500).json({ error: error.message }); - } -}); - router.post('/batch-delete', async (req, res) => { try { const { portIds } = req.body; @@ -409,6 +411,67 @@ router.post('/batch-delete', async (req, res) => { } }); +// 导出所有端口 +router.get('/export/all', async (req, res) => { + try { + const { keyword, status, portType, portSpeed, deviceId, page = 1, pageSize = 5000 } = req.query; + + const parsedPage = Math.max(1, parseInt(page) || 1); + const parsedPageSize = Math.min(10000, Math.max(1, parseInt(pageSize) || 5000)); + const offset = (parsedPage - 1) * parsedPageSize; + + const where = {}; + + if (keyword) { + where[Op.or] = [ + { portName: { [Op.like]: `%${keyword}%` } }, + { deviceId: { [Op.like]: `%${keyword}%` } }, + ]; + } + + if (status && status !== 'all') { + where.status = status; + } + + if (portType && portType !== 'all') { + where.portType = portType; + } + + if (portSpeed && portSpeed !== 'all') { + where.portSpeed = portSpeed; + } + + if (deviceId && deviceId !== 'all') { + where.deviceId = deviceId; + } + + const { count, rows } = await DevicePort.findAndCountAll({ + where, + include: [ + { + model: Device, + as: 'device', + attributes: ['deviceId', 'name', 'type'], + }, + ], + order: [['createdAt', 'DESC']], + offset, + limit: parsedPageSize, + }); + + res.json({ + total: count, + ports: rows, + page: parsedPage, + pageSize: parsedPageSize, + }); + } catch (error) { + console.error('获取端口列表失败:', error); + res.status(500).json({ error: error.message }); + } +}); + +// 获取单个端口 router.get('/:portId', async (req, res) => { try { const port = await DevicePort.findByPk(req.params.portId, { @@ -432,121 +495,4 @@ router.get('/:portId', async (req, res) => { } }); -router.get('/export/all', async (req, res) => { - try { - const { keyword, status, portType, portSpeed, deviceId, page = 1, pageSize = 5000 } = req.query; - - const parsedPage = Math.max(1, parseInt(page) || 1); - const parsedPageSize = Math.min(10000, Math.max(1, parseInt(pageSize) || 5000)); - const offset = (parsedPage - 1) * parsedPageSize; - - const where = {}; - - if (deviceId) { - where.deviceId = deviceId; - } - - if (status && status !== 'all') { - where.status = status; - } - - if (portType && portType !== 'all') { - where.portType = portType; - } - - if (portSpeed && portSpeed !== 'all') { - where.portSpeed = portSpeed; - } - - const timeoutMs = 30000; - const timeoutPromise = new Promise((_, reject) => { - setTimeout(() => reject(new Error('查询超时,请尝试缩小查询范围或减少pageSize')), timeoutMs); - }); - - const countResult = await Promise.race([ - DevicePort.findAll({ - where, - include: [ - { - model: Device, - as: 'device', - attributes: ['deviceId', 'name', 'type', 'rackId'], - include: [ - { - model: require('../models/Rack'), - as: 'rack', - attributes: ['rackId', 'name'], - include: [ - { - model: require('../models/Room'), - as: 'room', - attributes: ['roomId', 'name'], - }, - ], - }, - ], - }, - { - model: NetworkCard, - as: 'networkCard', - attributes: ['nicId', 'name'], - }, - ], - order: [['createdAt', 'DESC']], - limit: parsedPageSize, - offset: offset, - subQuery: false, - }), - timeoutPromise, - ]); - - const ports = countResult; - - const statusMap = { - free: '空闲', - occupied: '占用', - fault: '故障', - }; - - const exportData = ports.map(port => ({ - 端口ID: port.portId, - 设备ID: port.deviceId, - 设备名称: port.device?.name || '-', - 设备类型: port.device?.type || '-', - 机房: port.device?.rack?.room?.name || '-', - 机架: port.device?.rack?.name || '-', - 网卡名称: port.networkCard?.name || '-', - 端口名称: port.portName, - 端口类型: port.portType, - 端口速率: port.portSpeed, - 状态: statusMap[port.status] || port.status, - VLAN_ID: port.vlanId || '-', - 描述: port.description || '-', - 创建时间: port.createdAt ? new Date(port.createdAt).toLocaleString('zh-CN') : '-', - })); - - let filteredExportData = exportData; - if (keyword) { - const searchLower = keyword.toLowerCase(); - filteredExportData = exportData.filter( - item => - item.端口名称?.toLowerCase().includes(searchLower) || - item.端口类型?.toLowerCase().includes(searchLower) || - item.设备名称?.toLowerCase().includes(searchLower) || - item.描述?.toLowerCase().includes(searchLower) - ); - } - - res.json({ - page: parsedPage, - pageSize: parsedPageSize, - total: filteredExportData.length, - ports: filteredExportData, - }); - } catch (error) { - console.error('导出端口失败:', error); - res.status(500).json({ error: error.message }); - } -}); - module.exports = router; diff --git a/backend/routes/networkCards.js b/backend/routes/networkCards.js index 7e3d599..f9efb25 100644 --- a/backend/routes/networkCards.js +++ b/backend/routes/networkCards.js @@ -131,6 +131,39 @@ router.get('/device/:deviceId/with-ports', async (req, res) => { } }); +// 查询网卡 +router.get('/find', async (req, res) => { + try { + const { deviceId, deviceSn, name } = req.query; + + if ((!deviceId && !deviceSn) || !name) { + return res.status(400).json({ error: '缺少设备ID/SN或网卡名称' }); + } + + let device; + if (deviceSn) { + device = await Device.findOne({ where: { serialNumber: deviceSn } }); + if (!device) { + return res.json({ nicId: null }); + } + } + + const networkCard = await NetworkCard.findOne({ + where: { deviceId: device ? device.deviceId : deviceId, name }, + }); + + if (!networkCard) { + return res.json({ nicId: null }); + } + + res.json(networkCard); + } catch (error) { + console.error('查找网卡失败:', error); + res.status(500).json({ error: error.message }); + } +}); + +// 获取单个网卡 router.get('/:nicId', async (req, res) => { try { const networkCard = await NetworkCard.findByPk(req.params.nicId, { @@ -170,37 +203,6 @@ router.get('/:nicId/ports', async (req, res) => { } }); -router.get('/find', async (req, res) => { - try { - const { deviceId, deviceSn, name } = req.query; - - if ((!deviceId && !deviceSn) || !name) { - return res.status(400).json({ error: '缺少设备ID/SN或网卡名称' }); - } - - let device; - if (deviceSn) { - device = await Device.findOne({ where: { serialNumber: deviceSn } }); - if (!device) { - return res.json({ nicId: null }); - } - } - - const networkCard = await NetworkCard.findOne({ - where: { deviceId: device ? device.deviceId : deviceId, name }, - }); - - if (!networkCard) { - return res.json({ nicId: null }); - } - - res.json(networkCard); - } catch (error) { - console.error('查找网卡失败:', error); - res.status(500).json({ error: error.message }); - } -}); - router.post('/', async (req, res) => { try { const { nicId, deviceId, name, description, slotNumber, model, manufacturer, status } = diff --git a/backend/routes/tickets.js b/backend/routes/tickets.js index aaef032..91b5fdb 100644 --- a/backend/routes/tickets.js +++ b/backend/routes/tickets.js @@ -310,6 +310,162 @@ router.get('/', async (req, res) => { } }); +// 导出工单 +const TICKET_EXPORT_FIELDS = [ + { fieldName: 'ticketId', displayName: '工单编号' }, + { fieldName: 'title', displayName: '标题' }, + { fieldName: 'deviceName', displayName: '设备名称' }, + { fieldName: 'deviceModel', displayName: '设备型号' }, + { fieldName: 'serialNumber', displayName: '设备序列号' }, + { fieldName: 'faultCategory', displayName: '故障分类' }, + { fieldName: 'faultSubCategory', displayName: '故障子分类' }, + { fieldName: 'priority', displayName: '优先级' }, + { fieldName: 'status', displayName: '状态' }, + { fieldName: 'description', displayName: '故障描述' }, + { fieldName: 'expectedCompletionDate', displayName: '期望完成时间' }, + { fieldName: 'reporterId', displayName: '报告人ID' }, + { fieldName: 'reporterName', displayName: '报告人' }, + { fieldName: 'assigneeId', displayName: '处理人ID' }, + { fieldName: 'assigneeName', displayName: '处理人' }, + { fieldName: 'location', displayName: '设备位置' }, + { fieldName: 'resolution', displayName: '解决方案' }, + { fieldName: 'completionDate', displayName: '完成时间' }, + { fieldName: 'evaluation', displayName: '评价' }, + { fieldName: 'evaluationRating', displayName: '评价星级' }, + { fieldName: 'createdAt', displayName: '创建时间' }, + { fieldName: 'updatedAt', displayName: '更新时间' }, +]; + +router.get('/export', async (req, res) => { + try { + const { keyword, status, priority, faultCategory, deviceId, format = 'csv', ticketIds } = req.query; + + const where = {}; + + if (ticketIds) { + const ids = Array.isArray(ticketIds) ? ticketIds : [ticketIds]; + where.ticketId = { [Op.in]: ids }; + } else { + if (keyword) { + where[Op.or] = [ + { ticketId: { [Op.like]: `%${keyword}%` } }, + { title: { [Op.like]: `%${keyword}%` } }, + { deviceName: { [Op.like]: `%${keyword}%` } }, + { serialNumber: { [Op.like]: `%${keyword}%` } }, + { description: { [Op.like]: `%${keyword}%` } }, + ]; + } + + if (status && status !== 'all') { + where.status = status; + } + + if (priority && priority !== 'all') { + where.priority = priority; + } + + if (faultCategory && faultCategory !== 'all') { + where.faultCategory = faultCategory; + } + + if (deviceId && deviceId !== 'all') { + where.deviceId = deviceId; + } + } + + const tickets = await Ticket.findAll({ + where, + include: [ + { model: User, as: 'reporter', attributes: ['userId', 'username'] }, + { model: Device, attributes: ['deviceId', 'name', 'type', 'model', 'serialNumber'] }, + ], + order: [['createdAt', 'DESC']], + }); + + const exportData = tickets.map(ticket => { + const item = {}; + TICKET_EXPORT_FIELDS.forEach(({ fieldName, displayName }) => { + let value = ticket[fieldName]; + + if (fieldName === 'priority') { + const priorityMap = { low: '低', medium: '中', high: '高', urgent: '紧急' }; + value = priorityMap[value] || value; + } else if (fieldName === 'status') { + const statusMap = { pending: '待处理', in_progress: '处理中', completed: '已完成', closed: '已关闭' }; + value = statusMap[value] || value; + } else if (fieldName === 'expectedCompletionDate' || fieldName === 'completionDate' || fieldName === 'createdAt' || fieldName === 'updatedAt') { + value = value ? new Date(value).toLocaleString('zh-CN') : ''; + } + + item[displayName] = value !== null && value !== undefined ? String(value) : ''; + }); + + if (ticket.metadata && typeof ticket.metadata === 'object') { + Object.entries(ticket.metadata).forEach(([key, val]) => { + const customDisplayName = key; + item[customDisplayName] = val !== null && val !== undefined ? String(val) : ''; + }); + } + + return item; + }); + + if (format === 'json') { + return res.setHeader('Content-Type', 'application/json; charset=utf-8') + .setHeader('Content-Disposition', `attachment; filename=tickets_${Date.now()}.json`) + .json({ success: true, data: exportData, total: exportData.length }); + } + + if (format === 'xlsx') { + const worksheet = XLSX.utils.json_to_sheet(exportData); + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, '工单数据'); + const xlsxBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'buffer' }); + + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.setHeader('Content-Disposition', `attachment; filename=tickets_${Date.now()}.xlsx`); + return res.send(xlsxBuffer); + } + + const headers = [ + ...TICKET_EXPORT_FIELDS.map(f => ({ id: f.displayName, title: f.displayName })), + ]; + + if (tickets.length > 0 && tickets[0].metadata && typeof tickets[0].metadata === 'object') { + Object.keys(tickets[0].metadata).forEach(key => { + headers.push({ id: key, title: key }); + }); + } + + if (!fs.existsSync(path.join(__dirname, '../temp'))) { + fs.mkdirSync(path.join(__dirname, '../temp')); + } + + const tempFilePath = path.join(__dirname, `../temp/tickets_export_${Date.now()}.csv`); + + const csvWriter = createObjectCsvWriter({ + path: tempFilePath, + header: headers, + encoding: 'utf8', + }); + + await csvWriter.writeRecords(exportData); + + const csvContent = fs.readFileSync(tempFilePath, 'utf8'); + const bom = '\uFEFF'; + const csvWithBom = bom + csvContent; + + fs.unlinkSync(tempFilePath); + + res.setHeader('Content-Type', 'text/csv; charset=utf-8'); + res.setHeader('Content-Disposition', `attachment; filename=tickets_${Date.now()}.csv`); + return res.send(csvWithBom); + } catch (error) { + console.error('导出工单失败:', error); + res.status(500).json({ error: error.message }); + } +}); + // 获取单个工单详情 router.get('/:ticketId', async (req, res) => { try { @@ -654,159 +810,4 @@ router.post('/:ticketId/evaluate', async (req, res) => { } }); -const TICKET_EXPORT_FIELDS = [ - { fieldName: 'ticketId', displayName: '工单编号' }, - { fieldName: 'title', displayName: '标题' }, - { fieldName: 'deviceName', displayName: '设备名称' }, - { fieldName: 'deviceModel', displayName: '设备型号' }, - { fieldName: 'serialNumber', displayName: '设备序列号' }, - { fieldName: 'faultCategory', displayName: '故障分类' }, - { fieldName: 'faultSubCategory', displayName: '故障子分类' }, - { fieldName: 'priority', displayName: '优先级' }, - { fieldName: 'status', displayName: '状态' }, - { fieldName: 'description', displayName: '故障描述' }, - { fieldName: 'expectedCompletionDate', displayName: '期望完成时间' }, - { fieldName: 'reporterId', displayName: '报告人ID' }, - { fieldName: 'reporterName', displayName: '报告人' }, - { fieldName: 'assigneeId', displayName: '处理人ID' }, - { fieldName: 'assigneeName', displayName: '处理人' }, - { fieldName: 'location', displayName: '设备位置' }, - { fieldName: 'resolution', displayName: '解决方案' }, - { fieldName: 'completionDate', displayName: '完成时间' }, - { fieldName: 'evaluation', displayName: '评价' }, - { fieldName: 'evaluationRating', displayName: '评价星级' }, - { fieldName: 'createdAt', displayName: '创建时间' }, - { fieldName: 'updatedAt', displayName: '更新时间' }, -]; - -router.get('/export', async (req, res) => { - try { - const { keyword, status, priority, faultCategory, deviceId, format = 'csv', ticketIds } = req.query; - - const where = {}; - - if (ticketIds) { - const ids = Array.isArray(ticketIds) ? ticketIds : [ticketIds]; - where.ticketId = { [Op.in]: ids }; - } else { - if (keyword) { - where[Op.or] = [ - { ticketId: { [Op.like]: `%${keyword}%` } }, - { title: { [Op.like]: `%${keyword}%` } }, - { deviceName: { [Op.like]: `%${keyword}%` } }, - { serialNumber: { [Op.like]: `%${keyword}%` } }, - { description: { [Op.like]: `%${keyword}%` } }, - ]; - } - - if (status && status !== 'all') { - where.status = status; - } - - if (priority && priority !== 'all') { - where.priority = priority; - } - - if (faultCategory && faultCategory !== 'all') { - where.faultCategory = faultCategory; - } - - if (deviceId && deviceId !== 'all') { - where.deviceId = deviceId; - } - } - - const tickets = await Ticket.findAll({ - where, - include: [ - { model: User, as: 'reporter', attributes: ['userId', 'username'] }, - { model: Device, attributes: ['deviceId', 'name', 'type', 'model', 'serialNumber'] }, - ], - order: [['createdAt', 'DESC']], - }); - - const exportData = tickets.map(ticket => { - const item = {}; - TICKET_EXPORT_FIELDS.forEach(({ fieldName, displayName }) => { - let value = ticket[fieldName]; - - if (fieldName === 'priority') { - const priorityMap = { low: '低', medium: '中', high: '高', urgent: '紧急' }; - value = priorityMap[value] || value; - } else if (fieldName === 'status') { - const statusMap = { pending: '待处理', in_progress: '处理中', completed: '已完成', closed: '已关闭' }; - value = statusMap[value] || value; - } else if (fieldName === 'expectedCompletionDate' || fieldName === 'completionDate' || fieldName === 'createdAt' || fieldName === 'updatedAt') { - value = value ? new Date(value).toLocaleString('zh-CN') : ''; - } - - item[displayName] = value !== null && value !== undefined ? String(value) : ''; - }); - - if (ticket.metadata && typeof ticket.metadata === 'object') { - Object.entries(ticket.metadata).forEach(([key, val]) => { - const customDisplayName = key; - item[customDisplayName] = val !== null && val !== undefined ? String(val) : ''; - }); - } - - return item; - }); - - if (format === 'json') { - return res.setHeader('Content-Type', 'application/json; charset=utf-8') - .setHeader('Content-Disposition', `attachment; filename=tickets_${Date.now()}.json`) - .json({ success: true, data: exportData, total: exportData.length }); - } - - if (format === 'xlsx') { - const worksheet = XLSX.utils.json_to_sheet(exportData); - const workbook = XLSX.utils.book_new(); - XLSX.utils.book_append_sheet(workbook, worksheet, '工单数据'); - const xlsxBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'buffer' }); - - res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); - res.setHeader('Content-Disposition', `attachment; filename=tickets_${Date.now()}.xlsx`); - return res.send(xlsxBuffer); - } - - const headers = [ - ...TICKET_EXPORT_FIELDS.map(f => ({ id: f.displayName, title: f.displayName })), - ]; - - if (tickets.length > 0 && tickets[0].metadata && typeof tickets[0].metadata === 'object') { - Object.keys(tickets[0].metadata).forEach(key => { - headers.push({ id: key, title: key }); - }); - } - - if (!fs.existsSync(path.join(__dirname, '../temp'))) { - fs.mkdirSync(path.join(__dirname, '../temp')); - } - - const tempFilePath = path.join(__dirname, `../temp/tickets_export_${Date.now()}.csv`); - - const csvWriter = createObjectCsvWriter({ - path: tempFilePath, - header: headers, - encoding: 'utf8', - }); - - await csvWriter.writeRecords(exportData); - - const csvContent = fs.readFileSync(tempFilePath, 'utf8'); - const bom = '\uFEFF'; - const csvWithBom = bom + csvContent; - - fs.unlinkSync(tempFilePath); - - res.setHeader('Content-Type', 'text/csv; charset=utf-8'); - res.setHeader('Content-Disposition', `attachment; filename=tickets_${Date.now()}.csv`); - return res.send(csvWithBom); - } catch (error) { - console.error('导出工单失败:', error); - res.status(500).json({ error: error.message }); - } -}); - module.exports = router; diff --git a/backend/server.js b/backend/server.js index 7068a12..e3c3155 100644 --- a/backend/server.js +++ b/backend/server.js @@ -223,9 +223,32 @@ async function initializeApp() { const swaggerUi = require('swagger-ui-express'); const { specs, customCSS } = require('./swagger'); +const { authMiddleware } = require('./middleware/auth'); initializeApp(); +const PUBLIC_PATHS = [ + '/auth', + '/health', + '/docs', + '/api-docs', + '/api-docs.json', +]; + +const isPublicPath = (path) => { + if (path === '' || path === '/') { + return true; + } + return PUBLIC_PATHS.some((publicPath) => path === publicPath || path.startsWith(publicPath + '/')); +}; + +app.use('/api', (req, res, next) => { + if (isPublicPath(req.path)) { + return next(); + } + return authMiddleware(req, res, next); +}); + const deviceRoutes = require('./routes/devices'); const rackRoutes = require('./routes/racks'); const roomRoutes = require('./routes/rooms');