feat(工单字段): 实现工单字段管理功能

新增工单字段模型、路由和初始化脚本
添加前端工单字段管理页面
修改工单管理页面以支持自定义字段
This commit is contained in:
zhang1106
2025-12-29 08:40:08 +08:00
parent 7e0bf42b03
commit 4a03994fa3
7 changed files with 878 additions and 219 deletions
+148
View File
@@ -0,0 +1,148 @@
const TicketField = require('./models/TicketField');
const { sequelize } = require('./db');
const defaultTicketFields = [
{
fieldName: 'ticketId',
displayName: '工单编号',
fieldType: 'string',
required: true,
order: 1,
visible: true
},
{
fieldName: 'title',
displayName: '标题',
fieldType: 'string',
required: true,
order: 2,
visible: true
},
{
fieldName: 'deviceName',
displayName: '设备名称',
fieldType: 'string',
required: false,
order: 3,
visible: true
},
{
fieldName: 'serialNumber',
displayName: '设备序列号',
fieldType: 'string',
required: false,
order: 4,
visible: true
},
{
fieldName: 'faultCategory',
displayName: '故障分类',
fieldType: 'select',
required: true,
order: 5,
visible: true,
options: []
},
{
fieldName: 'priority',
displayName: '优先级',
fieldType: 'select',
required: true,
order: 6,
visible: true,
options: [
{ value: 'low', label: '低' },
{ value: 'medium', label: '中' },
{ value: 'high', label: '高' },
{ value: 'urgent', label: '紧急' }
]
},
{
fieldName: 'status',
displayName: '状态',
fieldType: 'select',
required: true,
order: 7,
visible: true,
options: [
{ value: 'pending', label: '待处理' },
{ value: 'in_progress', label: '处理中' },
{ value: 'completed', label: '已完成' },
{ value: 'closed', label: '已关闭' }
]
},
{
fieldName: 'reporterName',
displayName: '报告人',
fieldType: 'string',
required: false,
order: 8,
visible: true
},
{
fieldName: 'createdAt',
displayName: '创建时间',
fieldType: 'datetime',
required: false,
order: 9,
visible: true
},
{
fieldName: 'expectedCompletionDate',
displayName: '期望完成时间',
fieldType: 'datetime',
required: false,
order: 10,
visible: true
},
{
fieldName: 'completionDate',
displayName: '完成时间',
fieldType: 'datetime',
required: false,
order: 11,
visible: true
},
{
fieldName: 'description',
displayName: '故障描述',
fieldType: 'textarea',
required: false,
order: 12,
visible: true,
placeholder: '请详细描述故障情况'
},
{
fieldName: 'resolution',
displayName: '解决方案',
fieldType: 'textarea',
required: false,
order: 13,
visible: true,
placeholder: '请输入解决方案'
},
{
fieldName: 'location',
displayName: '位置',
fieldType: 'string',
required: false,
order: 14,
visible: false
}
];
async function initializeTicketFields() {
try {
const count = await TicketField.count();
if (count === 0) {
await TicketField.bulkCreate(defaultTicketFields);
console.log('工单字段初始化完成');
} else {
console.log('工单字段已存在,跳过初始化');
}
} catch (error) {
console.error('工单字段初始化失败:', error);
}
}
module.exports = initializeTicketFields;
+53
View File
@@ -0,0 +1,53 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../db');
const TicketField = sequelize.define('TicketField', {
fieldId: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false
},
fieldName: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
displayName: {
type: DataTypes.STRING,
allowNull: false
},
fieldType: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: 'string'
},
required: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
options: {
type: DataTypes.JSON,
allowNull: true
},
order: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0
},
visible: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true
},
placeholder: {
type: DataTypes.STRING,
allowNull: true
}
}, {
tableName: 'ticketFields',
timestamps: true
});
module.exports = TicketField;
+95
View File
@@ -0,0 +1,95 @@
const express = require('express');
const router = express.Router();
const TicketField = require('../models/TicketField');
router.get('/', async (req, res) => {
try {
const fields = await TicketField.findAll({
order: [['order', 'ASC']]
});
res.json(fields);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.get('/:fieldId', async (req, res) => {
try {
const field = await TicketField.findByPk(req.params.fieldId);
if (!field) {
return res.status(404).json({ error: '字段不存在' });
}
res.json(field);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.post('/', async (req, res) => {
try {
const field = await TicketField.create(req.body);
res.status(201).json(field);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
router.put('/:fieldId', async (req, res) => {
try {
const [updated] = await TicketField.update(req.body, {
where: { fieldId: req.params.fieldId }
});
if (updated) {
const updatedField = await TicketField.findByPk(req.params.fieldId);
res.json(updatedField);
} else {
res.status(404).json({ error: '字段不存在' });
}
} catch (error) {
res.status(400).json({ error: error.message });
}
});
router.delete('/:fieldId', async (req, res) => {
try {
const deleted = await TicketField.destroy({
where: { fieldId: req.params.fieldId }
});
if (deleted) {
res.status(204).json();
} else {
res.status(404).json({ error: '字段不存在' });
}
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.post('/config', async (req, res) => {
try {
const fieldConfigs = req.body;
if (!Array.isArray(fieldConfigs)) {
return res.status(400).json({ error: '输入必须是数组' });
}
const updatedFields = [];
for (const config of fieldConfigs) {
const [updated] = await TicketField.update(
{ visible: config.visible },
{ where: { fieldName: config.fieldName } }
);
if (updated) {
const updatedField = await TicketField.findOne({ where: { fieldName: config.fieldName } });
updatedFields.push(updatedField);
}
}
res.json(updatedFields);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;
+6
View File
@@ -31,6 +31,10 @@ sequelize.authenticate()
// 同步完成后初始化设备字段
return require('./initDeviceFields')();
})
.then(() => {
// 初始化工单字段
return require('./initTicketFields')();
})
.then(() => {
// 初始化工单模型关联
return require('./models/ticketIndex').initializeModels();
@@ -53,6 +57,7 @@ const loginHistoryRoutes = require('./routes/loginHistory');
const operationLogsRoutes = require('./routes/operationLogs');
const ticketRoutes = require('./routes/tickets');
const ticketCategoryRoutes = require('./routes/ticketCategories');
const ticketFieldRoutes = require('./routes/ticketFields');
// 使用路由
app.use('/api/devices', deviceRoutes);
@@ -70,6 +75,7 @@ app.use('/api/login-history', loginHistoryRoutes);
app.use('/api/operation-logs', operationLogsRoutes);
app.use('/api/tickets', ticketRoutes);
app.use('/api/ticket-categories', ticketCategoryRoutes);
app.use('/api/ticket-fields', ticketFieldRoutes);
// 静态文件服务
app.use('/uploads', express.static('uploads'));