工单类型差异化处理设计方案
一、现状分析
1.1 现有工单类型
系统当前支持5种工单类型(WorkOrderTypeEnum.java):
- 维护保养 (maintenance) - 系统定时生成
- 故障报修 (fault) - 客户上报
- 软件升级 (software_upgrade) - 管理员/技术员创建
- 硬件升级 (hardware_upgrade) - 管理员/技术员创建
- 需求变更 (requirement_change) - 管理员创建
1.2 现有技术架构
前端: Vue 3 + Element Plus,采用统一页面 + 动态渲染的方式
- 工单列表页:
ui/src/views/business/workOrder/index.vue - 工单处理页:
ui/src/views/business/workOrder/process.vue - 工单详情组件:
ui/src/views/business/workOrder/components/
后端: Spring Boot + MyBatis-Plus,采用分层架构
- 实体层:
BzWorkOrder.java,BzWorkOrderSubtask.java - 服务层:
BzWorkOrderServiceImpl.java,BzWorkOrderSubtaskServiceImpl.java - 控制层:
BzWorkOrderController.java,BzWorkOrderSubtaskController.java - 枚举层:
WorkOrderTypeEnum.java,WorkOrderStatusEnum.java
1.3 现有工单状态流转
待处理(pending)
↓
已分配(assigned) - 分配技术员
↓
已收到(accepted) - 技术员确认
↓
已联系(contacted) - 联系客户
↓
已预约(scheduled) - 预约时间
↓
执行中(in_progress) - 现场处理
↓
已完成(completed)
↓
已关闭(closed)分支状态:
- 已暂停(paused): 从 accepted/scheduled/in_progress 进入
- 已取消(cancelled): 从任何状态都可以取消
二、五种工单类型的差异化设计
2.1 维护保养工单 (MAINTENANCE)
2.1.1 业务特点
- 来源: 系统定时任务根据维护计划自动生成
- 周期性: 日检/周检/月检/季检/年检
- 计划性: 有明确的计划开始和结束时间
- 关联性: 关联维护计划ID和检查模板
- 可拆分: 需要按设备拆分子任务
2.1.2 数据库字段(BzWorkOrder表)
orderType: "maintenance" // 工单类型
orderSource: "system_auto" // 来源-系统自动
planId: Long // 关联的维护计划ID
templateId: Long // 关联的检查模板ID
maintenanceType: String // 维护类型: daily/weekly/monthly/quarterly/yearly
maintenanceCycle: String // 维护周期描述
hasSubtasks: 1 // 有子任务
subtaskCount: Integer // 子任务总数
completedSubtasks: Integer // 已完成子任务数
overallProgress: Integer // 整体进度 0-100
2.1.3 前端差异化处理
工单创建页 (workorder-form.vue)
<!-- 维护保养专属字段 -->
<div v-if="form.orderType === 'maintenance'">
<!-- 1. 维护计划选择 -->
<el-form-item label="维护计划" prop="planId" required>
<el-select v-model="form.planId" placeholder="请选择维护计划">
<el-option v-for="plan in maintenancePlans"
:key="plan.id"
:label="plan.planName"
:value="plan.id" />
</el-select>
</el-form-item>
<!-- 2. 检查模板选择 -->
<el-form-item label="检查模板" prop="templateId" required>
<el-select v-model="form.templateId" placeholder="请选择检查模板">
<el-option v-for="template in checkTemplates"
:key="template.id"
:label="template.templateName"
:value="template.id" />
</el-select>
</el-form-item>
<!-- 3. 维护类型 -->
<el-form-item label="维护类型" prop="maintenanceType" required>
<el-select v-model="form.maintenanceType">
<el-option label="日检" value="daily" />
<el-option label="周检" value="weekly" />
<el-option label="月检" value="monthly" />
<el-option label="季检" value="quarterly" />
<el-option label="年检" value="yearly" />
</el-select>
</el-form-item>
<!-- 4. 设备列表(多选) -->
<el-form-item label="关联设备" prop="equipmentIds" required>
<el-select v-model="form.equipmentIds" multiple placeholder="请选择设备">
<el-option v-for="equipment in equipmentList"
:key="equipment.id"
:label="equipment.equipmentName"
:value="equipment.id" />
</el-select>
</el-form-item>
</div>
工单详情页 (workorder-detail.vue)
<!-- 维护保养专属信息 -->
<div v-if="workorder.orderType === 'maintenance'">
<el-descriptions title="维护保养信息" :column="2" border>
<el-descriptions-item label="维护计划">{{ workorder.planName }}</el-descriptions-item>
<el-descriptions-item label="维护类型">{{ getMaintenanceTypeText(workorder.maintenanceType) }}</el-descriptions-item>
<el-descriptions-item label="检查模板">{{ workorder.templateName }}</el-descriptions-item>
<el-descriptions-item label="维护周期">{{ workorder.maintenanceCycle }}</el-descriptions-item>
</el-descriptions>
<!-- 子任务列表 -->
<el-card title="设备子任务列表" class="mt-3">
<el-table :data="subtaskList" border>
<el-table-column label="设备编码" prop="equipmentCode" width="120" />
<el-table-column label="设备名称" prop="equipmentName" width="150" />
<el-table-column label="子任务状态" prop="status" width="100">
<template #default="scope">
<dict-tag :options="work_order_status" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="指派技术员" prop="assignedTechnician" width="100" />
<el-table-column label="完成进度" prop="progressPercentage" width="150">
<template #default="scope">
<el-progress :percentage="scope.row.progressPercentage || 0" />
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="scope">
<el-button link type="primary" @click="viewSubtaskDetail(scope.row)">详情</el-button>
<el-button link type="success" @click="processSubtask(scope.row)"
v-if="canProcessSubtask(scope.row)">处理</el-button>
</template>
</el-table-column>
</el-table>
<!-- 整体进度统计 -->
<div class="progress-summary mt-3">
<el-statistic title="整体完成进度" :value="workorder.overallProgress" suffix="%" />
<el-statistic title="已完成子任务" :value="workorder.completedSubtasks" :suffix="`/ ${workorder.subtaskCount}`" />
</div>
</el-card>
</div>
工单列表操作按钮
<!-- 拆分设备子任务按钮 - 仅维护保养工单显示 -->
<el-button v-if="row.orderType === 'maintenance' && row.status === 'pending'"
link type="warning" icon="Operation"
@click="handleSplitByEquipment(row)"
v-hasPermi="['business:workOrder:edit']">
拆分设备子任务
</el-button>
2.1.4 后端差异化处理
工单创建服务 (BzWorkOrderServiceImpl.java)
@Override
@Transactional
public int insertBzWorkOrder(BzWorkOrder workOrder) {
// 1. 生成工单编码
workOrder.setOrderCode(generateOrderCode());
// 维护保养工单专属处理
if (WorkOrderTypeEnum.isMaintenance(workOrder.getOrderType())) {
// 1.1 验证维护计划是否存在
BzMaintenancePlan plan = maintenancePlanService.selectById(workOrder.getPlanId());
if (plan == null) {
throw new ServiceException("维护计划不存在");
}
// 1.2 填充维护计划信息
workOrder.setMaintenanceType(plan.getMaintenanceType());
workOrder.setMaintenanceCycle(plan.getCycleDescription());
workOrder.setTemplateId(plan.getTemplateId());
// 1.3 设置默认值
workOrder.setHasSubtasks(1);
workOrder.setSubtaskCount(0);
workOrder.setCompletedSubtasks(0);
workOrder.setOverallProgress(0);
// 1.4 保存工单
int result = workOrderMapper.insertBzWorkOrder(workOrder);
// 1.5 自动拆分子任务(按设备)
if (workOrder.getEquipmentIds() != null && !workOrder.getEquipmentIds().isEmpty()) {
List<BzWorkOrderSubtask> subtasks = decomposeByEquipment(workOrder);
if (!subtasks.isEmpty()) {
subtaskService.batchInsertBzWorkOrderSubtask(subtasks);
// 更新子任务数量
workOrder.setSubtaskCount(subtasks.size());
workOrderMapper.updateBzWorkOrder(workOrder);
}
}
return result;
}
// 其他类型工单的处理...
return workOrderMapper.insertBzWorkOrder(workOrder);
}
/**
* 按设备拆分子任务
*/
private List<BzWorkOrderSubtask> decomposeByEquipment(BzWorkOrder workOrder) {
List<BzWorkOrderSubtask> subtasks = new ArrayList<>();
List<Long> equipmentIds = workOrder.getEquipmentIds();
for (int i = 0; i < equipmentIds.size(); i++) {
Long equipmentId = equipmentIds.get(i);
BzEquipment equipment = equipmentService.selectById(equipmentId);
BzWorkOrderSubtask subtask = new BzWorkOrderSubtask();
subtask.setOrderId(workOrder.getOrderId());
subtask.setSubtaskCode(workOrder.getOrderCode() + "-ST" + String.format("%03d", i + 1));
subtask.setSubtaskTitle(equipment.getEquipmentName() + " - " + workOrder.getMaintenanceType() + "维护");
subtask.setSubtaskType("maintenance");
subtask.setEquipmentId(equipmentId);
subtask.setEquipmentName(equipment.getEquipmentName());
subtask.setTemplateId(workOrder.getTemplateId());
subtask.setStatus("pending");
subtask.setPriority(workOrder.getPriority());
subtask.setProgressPercentage(0);
// 根据维护类型估算时长
subtask.setEstimatedDuration(estimateDurationByMaintenanceType(workOrder.getMaintenanceType()));
subtasks.add(subtask);
}
return subtasks;
}
/**
* 根据维护类型估算时长(分钟)
*/
private Integer estimateDurationByMaintenanceType(String maintenanceType) {
switch (maintenanceType) {
case "daily": return 30; // 30分钟
case "weekly": return 60; // 1小时
case "monthly": return 120; // 2小时
case "quarterly": return 240; // 4小时
case "yearly": return 480; // 8小时
default: return 60;
}
}
工单完成验证
@Override
public int completeWorkOrder(Long orderId) {
BzWorkOrder workOrder = workOrderMapper.selectBzWorkOrderByOrderId(orderId);
// 维护保养工单专属验证
if (WorkOrderTypeEnum.isMaintenance(workOrder.getOrderType())) {
// 检查所有子任务是否都已完成
List<BzWorkOrderSubtask> subtasks = subtaskService.selectBzWorkOrderSubtaskByOrderId(orderId);
long uncompletedCount = subtasks.stream()
.filter(s -> !s.getStatus().equals("completed"))
.count();
if (uncompletedCount > 0) {
throw new ServiceException("存在未完成的设备子任务,无法完成工单");
}
}
// 更新工单状态为已完成
workOrder.setStatus("completed");
workOrder.setCompletedTime(new Date());
return workOrderMapper.updateBzWorkOrder(workOrder);
}
2.1.5 API接口
/**
* 拆分维护保养工单为设备子任务
*/
@PreAuthorize("@ss.hasPermi('business:workOrder:edit')")
@PostMapping("/splitByEquipment/{orderId}")
public AjaxResult splitByEquipment(@PathVariable Long orderId) {
BzWorkOrder workOrder = workOrderService.selectBzWorkOrderByOrderId(orderId);
// 验证工单类型
if (!WorkOrderTypeEnum.isMaintenance(workOrder.getOrderType())) {
return error("仅维护保养工单支持按设备拆分");
}
// 验证工单状态
if (!"pending".equals(workOrder.getStatus())) {
return error("仅待处理状态的工单可以拆分");
}
// 执行拆分
int count = subtaskService.splitByEquipment(orderId);
return success("成功拆分 " + count + " 个设备子任务");
}
2.2 故障报修工单 (FAULT)
2.2.1 业务特点
- 来源: 客户通过系统上报
- 紧急性: 优先级默认为高或紧急
- 响应要求: 24小时内确认收到
- 现场照片: 支持故障现场照片上传
- 快速分配: 需要快速分配给合适的技术员
2.2.2 数据库字段(BzWorkOrder表)
orderType: "fault" // 工单类型
orderSource: "customer_report" // 来源-客户上报
priority: "urgent"/"high" // 优先级(默认高)
reporter: String // 报告人
reporterPhone: String // 报告人电话
reportTime: Date // 报告时间
faultDescription: String // 故障描述
faultType: String // 故障类型: mechanical/electrical/software/other
faultLevel: String // 故障等级: critical/major/minor
faultImages: String // 故障现场照片(JSON数组)
isEmergency: Integer // 是否紧急: 1-是 0-否
responseDeadline: Date // 响应截止时间(24小时后)
diagnosisResult: String // 故障诊断结果
repairMethod: String // 修复方法
replacedParts: String // 更换的备件
2.2.3 前端差异化处理
工单创建页 (workorder-form.vue)
<!-- 故障报修专属字段 -->
<div v-if="form.orderType === 'fault'">
<!-- 1. 报告人信息 -->
<el-form-item label="报告人" prop="reporter" required>
<el-input v-model="form.reporter" placeholder="请输入报告人姓名" />
</el-form-item>
<el-form-item label="联系电话" prop="reporterPhone" required>
<el-input v-model="form.reporterPhone" placeholder="请输入联系电话" />
</el-form-item>
<!-- 2. 故障类型 -->
<el-form-item label="故障类型" prop="faultType" required>
<el-select v-model="form.faultType">
<el-option label="机械故障" value="mechanical" />
<el-option label="电气故障" value="electrical" />
<el-option label="软件故障" value="software" />
<el-option label="其他故障" value="other" />
</el-select>
</el-form-item>
<!-- 3. 故障等级 -->
<el-form-item label="故障等级" prop="faultLevel" required>
<el-radio-group v-model="form.faultLevel">
<el-radio label="critical">
<el-tag type="danger">严重故障</el-tag>
<span class="ml-2">设备无法运行</span>
</el-radio>
<el-radio label="major">
<el-tag type="warning">重要故障</el-tag>
<span class="ml-2">设备功能受限</span>
</el-radio>
<el-radio label="minor">
<el-tag type="info">一般故障</el-tag>
<span class="ml-2">设备基本正常</span>
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 4. 故障描述 -->
<el-form-item label="故障描述" prop="faultDescription" required>
<el-input v-model="form.faultDescription"
type="textarea"
:rows="4"
placeholder="请详细描述故障现象、发生时间、影响范围等" />
</el-form-item>
<!-- 5. 故障现场照片 -->
<el-form-item label="现场照片" prop="faultImages">
<oss-image-upload
v-model="form.faultImages"
:limit="9"
:file-size="10"
accept="image/*"
tips="支持上传最多9张现场照片,单张不超过10MB" />
</el-form-item>
<!-- 6. 是否紧急 -->
<el-form-item label="是否紧急">
<el-switch v-model="form.isEmergency"
:active-value="1"
:inactive-value="0"
active-text="紧急故障"
inactive-text="普通故障" />
<div class="form-tip">
紧急故障将立即通知技术员,并要求2小时内响应
</div>
</el-form-item>
</div>
工单详情页 (workorder-detail.vue)
<!-- 故障报修专属信息 -->
<div v-if="workorder.orderType === 'fault'">
<!-- 故障信息 -->
<el-descriptions title="故障信息" :column="2" border>
<el-descriptions-item label="报告人">{{ workorder.reporter }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ workorder.reporterPhone }}</el-descriptions-item>
<el-descriptions-item label="报告时间">{{ parseTime(workorder.reportTime) }}</el-descriptions-item>
<el-descriptions-item label="响应截止">
<span :class="isOverdue(workorder.responseDeadline) ? 'text-danger' : ''">
{{ parseTime(workorder.responseDeadline) }}
</span>
<el-tag v-if="isOverdue(workorder.responseDeadline)" type="danger" size="small" class="ml-2">
已超时
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="故障类型">
<dict-tag :options="fault_type" :value="workorder.faultType" />
</el-descriptions-item>
<el-descriptions-item label="故障等级">
<el-tag :type="getFaultLevelType(workorder.faultLevel)">
{{ getFaultLevelText(workorder.faultLevel) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="是否紧急">
<el-tag :type="workorder.isEmergency ? 'danger' : 'info'">
{{ workorder.isEmergency ? '紧急' : '普通' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="故障描述" :span="2">
{{ workorder.faultDescription }}
</el-descriptions-item>
</el-descriptions>
<!-- 故障现场照片 -->
<el-card title="故障现场照片" class="mt-3" v-if="workorder.faultImages">
<image-preview-gallery :images="parseImages(workorder.faultImages)" />
</el-card>
<!-- 故障诊断与修复 -->
<el-card title="故障诊断与修复" class="mt-3" v-if="showDiagnosisInfo(workorder)">
<el-descriptions :column="1" border>
<el-descriptions-item label="诊断结果">
{{ workorder.diagnosisResult || '待诊断' }}
</el-descriptions-item>
<el-descriptions-item label="修复方法">
{{ workorder.repairMethod || '待修复' }}
</el-descriptions-item>
<el-descriptions-item label="更换备件">
{{ workorder.replacedParts || '无' }}
</el-descriptions-item>
</el-descriptions>
</el-card>
</div>
工单列表特殊标识
<!-- 故障报修工单的紧急标识 -->
<el-table-column label="工单标题" prop="orderTitle" min-width="200">
<template #default="scope">
<!-- 紧急图标 -->
<el-icon v-if="scope.row.orderType === 'fault' && scope.row.isEmergency"
color="#F56C6C" class="mr-1">
<Warning />
</el-icon>
<!-- 超时标识 -->
<el-tag v-if="scope.row.orderType === 'fault' && isResponseOverdue(scope.row)"
type="danger" size="small" class="mr-1">
超时
</el-tag>
{{ scope.row.orderTitle }}
</template>
</el-table-column>
2.2.4 后端差异化处理
工单创建服务 (BzWorkOrderServiceImpl.java)
@Override
@Transactional
public int insertBzWorkOrder(BzWorkOrder workOrder) {
// 故障报修工单专属处理
if (WorkOrderTypeEnum.isFault(workOrder.getOrderType())) {
// 1. 设置报告时间
workOrder.setReportTime(new Date());
// 2. 设置默认优先级(根据故障等级)
if (workOrder.getPriority() == null) {
if ("critical".equals(workOrder.getFaultLevel())) {
workOrder.setPriority("urgent");
} else if ("major".equals(workOrder.getFaultLevel())) {
workOrder.setPriority("high");
} else {
workOrder.setPriority("medium");
}
}
// 3. 设置响应截止时间
if (workOrder.getIsEmergency() == 1) {
// 紧急故障:2小时内响应
workOrder.setResponseDeadline(DateUtils.addHours(new Date(), 2));
} else {
// 普通故障:24小时内响应
workOrder.setResponseDeadline(DateUtils.addHours(new Date(), 24));
}
// 4. 保存工单
int result = workOrderMapper.insertBzWorkOrder(workOrder);
// 5. 自动分配技术员(根据故障类型和技术员专长匹配)
assignTechnicianByFaultType(workOrder);
// 6. 发送紧急通知
if (workOrder.getIsEmergency() == 1) {
sendEmergencyNotification(workOrder);
}
return result;
}
return workOrderMapper.insertBzWorkOrder(workOrder);
}
/**
* 根据故障类型自动分配技术员
*/
private void assignTechnicianByFaultType(BzWorkOrder workOrder) {
String faultType = workOrder.getFaultType();
// 查询具备对应技能的技术员
List<BzTechnician> technicians = technicianService.selectBySkill(faultType);
if (technicians.isEmpty()) {
log.warn("未找到具备{}技能的技术员", faultType);
return;
}
// 选择当前任务最少的技术员
BzTechnician selectedTechnician = technicians.stream()
.min(Comparator.comparingInt(t ->
workOrderMapper.countByTechnicianAndStatus(t.getTechnicianCode(), "in_progress")))
.orElse(technicians.get(0));
// 分配工单
workOrder.setAssignedTechnicianId(selectedTechnician.getId());
workOrder.setAssignedTechnician(selectedTechnician.getTechnicianCode());
workOrder.setStatus("assigned");
workOrderMapper.updateBzWorkOrder(workOrder);
// 发送分配通知
emailService.sendAssignNotification(workOrder, selectedTechnician);
}
/**
* 发送紧急故障通知
*/
private void sendEmergencyNotification(BzWorkOrder workOrder) {
// 通知项目经理
List<SysUser> managers = userService.selectUsersByRole("projectManager");
for (SysUser manager : managers) {
emailService.sendEmergencyFaultNotification(manager.getEmail(), workOrder);
}
// 如果已分配技术员,同时通知技术员
if (workOrder.getAssignedTechnicianId() != null) {
BzTechnician technician = technicianService.selectById(workOrder.getAssignedTechnicianId());
if (technician != null && technician.getEmail() != null) {
emailService.sendEmergencyFaultNotification(technician.getEmail(), workOrder);
}
}
}
响应超时检查定时任务
/**
* 检查故障报修工单响应超时
* 每小时执行一次
*/
@Scheduled(cron = "0 0 * * * ?")
public void checkFaultResponseOverdue() {
// 查询所有待处理或已分配的故障报修工单
List<BzWorkOrder> faultOrders = workOrderMapper.selectList(
new QueryWrapper<BzWorkOrder>()
.eq("order_type", "fault")
.in("status", Arrays.asList("pending", "assigned"))
.lt("response_deadline", new Date())
);
for (BzWorkOrder order : faultOrders) {
// 发送超时预警
sendOverdueAlert(order);
// 自动升级工单优先级
if (!"urgent".equals(order.getPriority())) {
order.setPriority("urgent");
workOrderMapper.updateBzWorkOrder(order);
}
}
}
2.2.5 API接口
/**
* 客户上报故障
*/
@PostMapping("/reportFault")
public AjaxResult reportFault(@RequestBody @Validated BzWorkOrder workOrder) {
// 验证客户身份
Long userId = SecurityUtils.getUserId();
SysUser user = userService.selectUserById(userId);
// 设置工单信息
workOrder.setOrderType("fault");
workOrder.setOrderSource("customer_report");
workOrder.setReporter(user.getNickName());
workOrder.setReporterPhone(user.getPhonenumber());
workOrder.setCustomerId(user.getCustomerId());
// 创建工单
int result = workOrderService.insertBzWorkOrder(workOrder);
return toAjax(result, "故障报修提交成功,我们将尽快安排技术员处理");
}
/**
* 故障诊断
*/
@PreAuthorize("@ss.hasPermi('business:workOrder:diagnose')")
@PutMapping("/diagnose")
public AjaxResult diagnoseFault(@RequestBody Map<String, Object> params) {
Long orderId = Long.parseLong(params.get("orderId").toString());
String diagnosisResult = params.get("diagnosisResult").toString();
BzWorkOrder workOrder = workOrderService.selectBzWorkOrderByOrderId(orderId);
// 验证工单类型
if (!WorkOrderTypeEnum.isFault(workOrder.getOrderType())) {
return error("仅故障报修工单支持诊断");
}
// 保存诊断结果
workOrder.setDiagnosisResult(diagnosisResult);
workOrderService.updateBzWorkOrder(workOrder);
return success("故障诊断完成");
}
/**
* 修复完成
*/
@PreAuthorize("@ss.hasPermi('business:workOrder:repair')")
@PutMapping("/completeRepair")
public AjaxResult completeRepair(@RequestBody Map<String, Object> params) {
Long orderId = Long.parseLong(params.get("orderId").toString());
String repairMethod = params.get("repairMethod").toString();
String replacedParts = params.get("replacedParts").toString();
BzWorkOrder workOrder = workOrderService.selectBzWorkOrderByOrderId(orderId);
// 保存修复信息
workOrder.setRepairMethod(repairMethod);
workOrder.setReplacedParts(replacedParts);
workOrder.setStatus("completed");
workOrder.setCompletedTime(new Date());
workOrderService.updateBzWorkOrder(workOrder);
// 扣减备件库存
if (StringUtils.isNotEmpty(replacedParts)) {
sparePartService.deductStock(replacedParts);
}
return success("故障修复完成");
}
2.3 软件升级工单 (SOFTWARE_UPGRADE)
2.3.1 业务特点
- 来源: 管理员或技术员创建
- 计划性: 需要详细的升级计划和测试方案
- 版本管理: 关联软件版本库
- 日志记录: 需要上传升级日志
- 回滚机制: 支持升级失败后回滚
2.3.2 数据库字段(BzWorkOrder表)
orderType: "software_upgrade" // 工单类型
orderSource: "admin_create"/"technician_create"
softwareVersion: String // 目标软件版本
currentVersion: String // 当前软件版本
upgradeType: String // 升级类型: major/minor/patch
upgradeMethod: String // 升级方式: online/offline
upgradePackageUrl: String // 升级包下载地址
upgradeDocUrl: String // 升级文档地址
upgradeLogUrl: String // 升级日志文件地址
backupRequired: Integer // 是否需要备份: 1-是 0-否
backupPath: String // 备份路径
testPlanUrl: String // 测试计划文档
testResult: String // 测试结果
rollbackPlan: String // 回滚方案
isRollback: Integer // 是否已回滚: 1-是 0-否
rollbackReason: String // 回滚原因
2.3.3 前端差异化处理
工单创建页 (workorder-form.vue)
<!-- 软件升级专属字段 -->
<div v-if="form.orderType === 'software_upgrade'">
<!-- 1. 版本信息 -->
<el-form-item label="当前版本" prop="currentVersion" required>
<el-input v-model="form.currentVersion" placeholder="如: v2.1.0" />
</el-form-item>
<el-form-item label="目标版本" prop="softwareVersion" required>
<el-select v-model="form.softwareVersion"
placeholder="请选择升级版本"
@change="handleVersionChange">
<el-option v-for="version in approvedVersions"
:key="version.id"
:label="version.versionName"
:value="version.versionNumber">
<span>{{ version.versionName }}</span>
<el-tag size="small" class="ml-2">{{ version.versionType }}</el-tag>
</el-option>
</el-select>
</el-form-item>
<!-- 2. 升级类型 -->
<el-form-item label="升级类型" prop="upgradeType" required>
<el-radio-group v-model="form.upgradeType">
<el-radio label="major">
<el-tag type="danger">重大版本</el-tag>
<span class="ml-2">大版本升级,可能包含架构变更</span>
</el-radio>
<el-radio label="minor">
<el-tag type="warning">次要版本</el-tag>
<span class="ml-2">功能更新,向下兼容</span>
</el-radio>
<el-radio label="patch">
<el-tag type="info">补丁版本</el-tag>
<span class="ml-2">Bug修复,无功能变更</span>
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 3. 升级方式 -->
<el-form-item label="升级方式" prop="upgradeMethod" required>
<el-radio-group v-model="form.upgradeMethod">
<el-radio label="online">在线升级(不停机)</el-radio>
<el-radio label="offline">离线升级(需停机)</el-radio>
</el-radio-group>
</el-form-item>
<!-- 4. 升级包上传 -->
<el-form-item label="升级包" prop="upgradePackageUrl" required>
<file-upload
v-model="form.upgradePackageUrl"
:limit="1"
accept=".zip,.tar.gz,.rar"
:file-size="500"
tips="支持zip、tar.gz格式,最大500MB" />
</el-form-item>
<!-- 5. 升级文档 -->
<el-form-item label="升级文档" prop="upgradeDocUrl">
<file-upload
v-model="form.upgradeDocUrl"
:limit="1"
accept=".pdf,.doc,.docx,.md"
tips="升级操作步骤文档" />
</el-form-item>
<!-- 6. 是否需要备份 -->
<el-form-item label="数据备份">
<el-switch v-model="form.backupRequired"
:active-value="1"
:inactive-value="0"
active-text="升级前备份数据"
inactive-text="无需备份" />
</el-form-item>
<!-- 7. 测试计划 -->
<el-form-item label="测试计划" prop="testPlanUrl">
<file-upload
v-model="form.testPlanUrl"
:limit="1"
accept=".pdf,.doc,.docx,.xlsx"
tips="升级后的测试计划文档" />
</el-form-item>
<!-- 8. 回滚方案 -->
<el-form-item label="回滚方案" prop="rollbackPlan" required>
<el-input v-model="form.rollbackPlan"
type="textarea"
:rows="4"
placeholder="详细描述升级失败后的回滚步骤" />
</el-form-item>
</div>
工单详情页 (workorder-detail.vue)
<!-- 软件升级专属信息 -->
<div v-if="workorder.orderType === 'software_upgrade'">
<!-- 版本信息 -->
<el-descriptions title="版本信息" :column="2" border>
<el-descriptions-item label="当前版本">
<el-tag type="info">{{ workorder.currentVersion }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="目标版本">
<el-tag type="success">{{ workorder.softwareVersion }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="升级类型">
<dict-tag :options="upgrade_type" :value="workorder.upgradeType" />
</el-descriptions-item>
<el-descriptions-item label="升级方式">
{{ workorder.upgradeMethod === 'online' ? '在线升级' : '离线升级' }}
</el-descriptions-item>
<el-descriptions-item label="数据备份">
<el-tag :type="workorder.backupRequired ? 'warning' : 'info'">
{{ workorder.backupRequired ? '需要备份' : '无需备份' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="备份路径" v-if="workorder.backupPath">
<el-link :href="workorder.backupPath" target="_blank">
查看备份
</el-link>
</el-descriptions-item>
</el-descriptions>
<!-- 升级资源 -->
<el-card title="升级资源" class="mt-3">
<el-space direction="vertical" :size="15">
<div v-if="workorder.upgradePackageUrl">
<el-icon><Document /></el-icon>
<el-link :href="workorder.upgradePackageUrl" target="_blank" class="ml-2">
下载升级包
</el-link>
</div>
<div v-if="workorder.upgradeDocUrl">
<el-icon><Document /></el-icon>
<el-link :href="workorder.upgradeDocUrl" target="_blank" class="ml-2">
查看升级文档
</el-link>
</div>
<div v-if="workorder.testPlanUrl">
<el-icon><Document /></el-icon>
<el-link :href="workorder.testPlanUrl" target="_blank" class="ml-2">
查看测试计划
</el-link>
</div>
</el-space>
</el-card>
<!-- 回滚方案 -->
<el-card title="回滚方案" class="mt-3">
<el-alert type="warning" :closable="false" class="mb-3">
升级失败时请按以下步骤执行回滚
</el-alert>
<pre class="rollback-plan">{{ workorder.rollbackPlan }}</pre>
</el-card>
<!-- 升级日志 -->
<el-card title="升级日志" class="mt-3" v-if="workorder.upgradeLogUrl">
<el-link :href="workorder.upgradeLogUrl" target="_blank" type="primary">
<el-icon><Document /></el-icon>
查看升级日志
</el-link>
</el-card>
<!-- 测试结果 -->
<el-card title="测试结果" class="mt-3" v-if="workorder.testResult">
<el-descriptions :column="1" border>
<el-descriptions-item label="测试状态">
<el-tag :type="getTestResultType(workorder.testResult)">
{{ workorder.testResult }}
</el-tag>
</el-descriptions-item>
</el-descriptions>
</el-card>
<!-- 回滚信息 -->
<el-alert v-if="workorder.isRollback"
type="error"
title="该工单已回滚"
class="mt-3"
:closable="false">
<div>回滚原因:{{ workorder.rollbackReason }}</div>
</el-alert>
</div>
工单处理页特殊操作按钮
<!-- 软件升级工单的专属操作 -->
<div v-if="workorder.orderType === 'software_upgrade' && workorder.status === 'in_progress'">
<!-- 上传升级日志 -->
<el-button type="primary" @click="handleUpgradeLog">
<el-icon><Upload /></el-icon>
上传升级日志
</el-button>
<!-- 执行回滚 -->
<el-button type="danger" @click="handleRollback">
<el-icon><RefreshLeft /></el-icon>
执行回滚
</el-button>
<!-- 完成测试 -->
<el-button type="success" @click="handleCompleteTest">
<el-icon><CircleCheck /></el-icon>
完成测试
</el-button>
</div>
2.3.4 后端差异化处理
工单创建服务 (BzWorkOrderServiceImpl.java)
@Override
@Transactional
public int insertBzWorkOrder(BzWorkOrder workOrder) {
// 软件升级工单专属处理
if ("software_upgrade".equals(workOrder.getOrderType())) {
// 1. 验证目标版本是否已审核通过
BzSoftwareVersion targetVersion = softwareVersionService
.selectByVersionNumber(workOrder.getSoftwareVersion());
if (targetVersion == null || !"approved".equals(targetVersion.getStatus())) {
throw new ServiceException("目标版本未审核通过,无法创建升级工单");
}
// 2. 验证版本升级路径是否合法
if (!validateUpgradePath(workOrder.getCurrentVersion(), workOrder.getSoftwareVersion())) {
throw new ServiceException("不支持从当前版本直接升级到目标版本");
}
// 3. 设置升级类型(根据版本号判断)
if (workOrder.getUpgradeType() == null) {
workOrder.setUpgradeType(determineUpgradeType(
workOrder.getCurrentVersion(),
workOrder.getSoftwareVersion()
));
}
// 4. 设置默认优先级
if ("major".equals(workOrder.getUpgradeType())) {
workOrder.setPriority("high"); // 重大升级默认高优先级
} else {
workOrder.setPriority("medium");
}
// 5. 保存工单
int result = workOrderMapper.insertBzWorkOrder(workOrder);
// 6. 创建升级检查清单子任务
createUpgradeChecklistSubtasks(workOrder);
return result;
}
return workOrderMapper.insertBzWorkOrder(workOrder);
}
/**
* 验证升级路径
*/
private boolean validateUpgradePath(String currentVersion, String targetVersion) {
// 解析版本号 (如: v2.1.0 -> [2, 1, 0])
int[] current = parseVersion(currentVersion);
int[] target = parseVersion(targetVersion);
// 不允许降级
if (compareVersion(current, target) >= 0) {
return false;
}
// 跨主版本升级需要经过中间版本
if (target[0] - current[0] > 1) {
return false;
}
return true;
}
/**
* 确定升级类型
*/
private String determineUpgradeType(String currentVersion, String targetVersion) {
int[] current = parseVersion(currentVersion);
int[] target = parseVersion(targetVersion);
if (target[0] > current[0]) {
return "major"; // 主版本升级
} else if (target[1] > current[1]) {
return "minor"; // 次版本升级
} else {
return "patch"; // 补丁升级
}
}
/**
* 创建升级检查清单子任务
*/
private void createUpgradeChecklistSubtasks(BzWorkOrder workOrder) {
List<BzWorkOrderSubtask> subtasks = new ArrayList<>();
// 1. 升级前检查
subtasks.add(createSubtask(workOrder, "升级前检查",
"检查系统状态、备份数据、确认升级包完整性", 1));
// 2. 执行升级
subtasks.add(createSubtask(workOrder, "执行升级",
"按照升级文档执行升级操作", 2));
// 3. 功能测试
subtasks.add(createSubtask(workOrder, "功能测试",
"按照测试计划验证系统功能", 3));
// 4. 性能测试(仅重大升级)
if ("major".equals(workOrder.getUpgradeType())) {
subtasks.add(createSubtask(workOrder, "性能测试",
"验证系统性能指标是否符合要求", 4));
}
// 5. 验收确认
subtasks.add(createSubtask(workOrder, "验收确认",
"客户验收确认升级成功", 5));
// 批量插入子任务
subtaskService.batchInsertBzWorkOrderSubtask(subtasks);
}
升级回滚处理
/**
* 执行软件升级回滚
*/
@Transactional
public int rollbackUpgrade(Long orderId, String rollbackReason) {
BzWorkOrder workOrder = workOrderMapper.selectBzWorkOrderByOrderId(orderId);
// 验证工单类型
if (!"software_upgrade".equals(workOrder.getOrderType())) {
throw new ServiceException("仅软件升级工单支持回滚");
}
// 验证工单状态
if (!"in_progress".equals(workOrder.getStatus())) {
throw new ServiceException("仅执行中的工单可以回滚");
}
// 1. 标记工单已回滚
workOrder.setIsRollback(1);
workOrder.setRollbackReason(rollbackReason);
workOrder.setStatus("cancelled");
workOrder.setCancelledTime(new Date());
// 2. 记录回滚日志
BzWorkOrderStatusLog log = new BzWorkOrderStatusLog();
log.setOrderId(orderId);
log.setFromStatus("in_progress");
log.setToStatus("cancelled");
log.setActionType("rollback");
log.setRemark("升级失败,执行回滚:" + rollbackReason);
log.setOperatorId(SecurityUtils.getUserId());
log.setOperatorName(SecurityUtils.getUsername());
log.setOperationTime(new Date());
statusLogService.insert(log);
// 3. 更新工单
int result = workOrderMapper.updateBzWorkOrder(workOrder);
// 4. 发送回滚通知
emailService.sendRollbackNotification(workOrder);
return result;
}
2.3.5 API接口
/**
* 上传升级日志
*/
@PreAuthorize("@ss.hasPermi('business:workOrder:edit')")
@PostMapping("/uploadUpgradeLog/{orderId}")
public AjaxResult uploadUpgradeLog(@PathVariable Long orderId,
@RequestParam("file") MultipartFile file) {
BzWorkOrder workOrder = workOrderService.selectBzWorkOrderByOrderId(orderId);
// 验证工单类型
if (!"software_upgrade".equals(workOrder.getOrderType())) {
return error("仅软件升级工单支持上传升级日志");
}
// 上传文件到OSS
String fileUrl = ossService.uploadFile(file);
// 保存文件URL
workOrder.setUpgradeLogUrl(fileUrl);
workOrderService.updateBzWorkOrder(workOrder);
return success("升级日志上传成功", fileUrl);
}
/**
* 执行回滚
*/
@PreAuthorize("@ss.hasPermi('business:workOrder:rollback')")
@PostMapping("/rollback/{orderId}")
public AjaxResult rollback(@PathVariable Long orderId,
@RequestBody Map<String, String> params) {
String rollbackReason = params.get("rollbackReason");
if (StringUtils.isEmpty(rollbackReason)) {
return error("请填写回滚原因");
}
int result = workOrderService.rollbackUpgrade(orderId, rollbackReason);
return toAjax(result, "升级回滚成功");
}
/**
* 完成升级测试
*/
@PreAuthorize("@ss.hasPermi('business:workOrder:test')")
@PutMapping("/completeTest/{orderId}")
public AjaxResult completeTest(@PathVariable Long orderId,
@RequestBody Map<String, String> params) {
String testResult = params.get("testResult");
BzWorkOrder workOrder = workOrderService.selectBzWorkOrderByOrderId(orderId);
workOrder.setTestResult(testResult);
// 如果测试通过,可以完成工单
if ("passed".equals(testResult)) {
workOrder.setStatus("completed");
workOrder.setCompletedTime(new Date());
}
workOrderService.updateBzWorkOrder(workOrder);
return success("测试结果已保存");
}
2.4 硬件升级工单 (HARDWARE_UPGRADE)
2.4.1 业务特点
- 来源: 管理员或技术员创建
- 备件管理: 需要先检查备件库存
- 库存扣减: 执行后需登记备件使用情况
- 采购流程: 库存不足时可发起紧急采购
- 验收流程: 需要硬件验收确认
2.4.2 数据库字段(BzWorkOrder表)
orderType: "hardware_upgrade" // 工单类型
orderSource: "admin_create"/"technician_create"
upgradeType: String // 升级类型: replacement/addition/optimization
hardwareType: String // 硬件类型: sensor/controller/motor/other
requiredParts: String // 所需备件清单(JSON数组)
partsCheckStatus: String // 备件检查状态: pending/sufficient/insufficient
insufficientParts: String // 不足的备件(JSON数组)
purchaseRequestId: Long // 关联的采购申请ID
installedParts: String // 已安装备件(JSON数组)
replacedParts: String // 更换下来的旧件(JSON数组)
installationPhoto: String // 安装照片
acceptanceStatus: String // 验收状态: pending/passed/failed
acceptanceReport: String // 验收报告
2.4.3 前端差异化处理
工单创建页 (workorder-form.vue)
<!-- 硬件升级专属字段 -->
<div v-if="form.orderType === 'hardware_upgrade'">
<!-- 1. 升级类型 -->
<el-form-item label="升级类型" prop="upgradeType" required>
<el-radio-group v-model="form.upgradeType">
<el-radio label="replacement">硬件替换</el-radio>
<el-radio label="addition">硬件新增</el-radio>
<el-radio label="optimization">硬件优化</el-radio>
</el-radio-group>
</el-form-item>
<!-- 2. 硬件类型 -->
<el-form-item label="硬件类型" prop="hardwareType" required>
<el-select v-model="form.hardwareType">
<el-option label="传感器" value="sensor" />
<el-option label="控制器" value="controller" />
<el-option label="电机" value="motor" />
<el-option label="执行器" value="actuator" />
<el-option label="电源模块" value="power_module" />
<el-option label="其他" value="other" />
</el-select>
</el-form-item>
<!-- 3. 所需备件清单 -->
<el-form-item label="备件清单" required>
<el-button type="primary" @click="handleAddPart">
<el-icon><Plus /></el-icon>
添加备件
</el-button>
<el-table :data="form.requiredParts" class="mt-3" border>
<el-table-column label="备件编码" prop="partCode" width="150" />
<el-table-column label="备件名称" prop="partName" width="200" />
<el-table-column label="规格型号" prop="specification" width="150" />
<el-table-column label="所需数量" prop="quantity" width="100" />
<el-table-column label="当前库存" prop="stock" width="100">
<template #default="scope">
<span :class="scope.row.stock < scope.row.quantity ? 'text-danger' : 'text-success'">
{{ scope.row.stock }}
</span>
</template>
</el-table-column>
<el-table-column label="操作" width="100">
<template #default="scope">
<el-button link type="danger" @click="handleRemovePart(scope.$index)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
<!-- 4. 库存不足提示 -->
<el-alert v-if="hasInsufficientParts"
type="warning"
title="部分备件库存不足"
:closable="false"
class="mb-3">
<div v-for="part in insufficientParts" :key="part.partCode">
{{ part.partName }}:需要 {{ part.quantity }} 个,库存 {{ part.stock }} 个,
缺少 {{ part.quantity - part.stock }} 个
</div>
<el-button type="primary" size="small" class="mt-2" @click="handleEmergencyPurchase">
发起紧急采购
</el-button>
</el-alert>
<!-- 5. 升级方案 -->
<el-form-item label="升级方案" prop="description" required>
<el-input v-model="form.description"
type="textarea"
:rows="6"
placeholder="请详细描述硬件升级方案、安装步骤、注意事项等" />
</el-form-item>
</div>
工单详情页 (workorder-detail.vue)
<!-- 硬件升级专属信息 -->
<div v-if="workorder.orderType === 'hardware_upgrade'">
<!-- 升级信息 -->
<el-descriptions title="硬件升级信息" :column="2" border>
<el-descriptions-item label="升级类型">
<dict-tag :options="hardware_upgrade_type" :value="workorder.upgradeType" />
</el-descriptions-item>
<el-descriptions-item label="硬件类型">
<dict-tag :options="hardware_type" :value="workorder.hardwareType" />
</el-descriptions-item>
<el-descriptions-item label="备件检查">
<el-tag :type="getPartsCheckStatusType(workorder.partsCheckStatus)">
{{ getPartsCheckStatusText(workorder.partsCheckStatus) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="采购申请" v-if="workorder.purchaseRequestId">
<el-link :href="`/business/purchase/${workorder.purchaseRequestId}`" type="primary">
查看采购申请
</el-link>
</el-descriptions-item>
</el-descriptions>
<!-- 备件清单 -->
<el-card title="所需备件清单" class="mt-3">
<el-table :data="parseRequiredParts(workorder.requiredParts)" border>
<el-table-column label="备件编码" prop="partCode" width="150" />
<el-table-column label="备件名称" prop="partName" width="200" />
<el-table-column label="规格型号" prop="specification" width="150" />
<el-table-column label="所需数量" prop="quantity" width="100" />
<el-table-column label="当前库存" prop="stock" width="100">
<template #default="scope">
<span :class="scope.row.stock < scope.row.quantity ? 'text-danger' : 'text-success'">
{{ scope.row.stock }}
</span>
</template>
</el-table-column>
<el-table-column label="状态" width="100">
<template #default="scope">
<el-tag :type="scope.row.stock >= scope.row.quantity ? 'success' : 'danger'">
{{ scope.row.stock >= scope.row.quantity ? '充足' : '不足' }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 已安装备件 -->
<el-card title="已安装备件" class="mt-3" v-if="workorder.installedParts">
<el-table :data="parseInstalledParts(workorder.installedParts)" border>
<el-table-column label="备件编码" prop="partCode" width="150" />
<el-table-column label="备件名称" prop="partName" width="200" />
<el-table-column label="序列号" prop="serialNumber" width="150" />
<el-table-column label="安装时间" prop="installTime" width="180" />
<el-table-column label="安装人员" prop="installer" width="100" />
</el-table>
</el-card>
<!-- 更换下的旧件 -->
<el-card title="更换下的旧件" class="mt-3" v-if="workorder.replacedParts">
<el-table :data="parseReplacedParts(workorder.replacedParts)" border>
<el-table-column label="备件编码" prop="partCode" width="150" />
<el-table-column label="备件名称" prop="partName" width="200" />
<el-table-column label="序列号" prop="serialNumber" width="150" />
<el-table-column label="使用时长" prop="usageDuration" width="150" />
<el-table-column label="处理方式" prop="disposal" width="100">
<template #default="scope">
<el-tag :type="getDisposalType(scope.row.disposal)">
{{ scope.row.disposal }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 安装照片 -->
<el-card title="安装照片" class="mt-3" v-if="workorder.installationPhoto">
<image-preview-gallery :images="parseImages(workorder.installationPhoto)" />
</el-card>
<!-- 验收信息 -->
<el-card title="验收信息" class="mt-3" v-if="workorder.acceptanceStatus">
<el-descriptions :column="2" border>
<el-descriptions-item label="验收状态">
<el-tag :type="getAcceptanceStatusType(workorder.acceptanceStatus)">
{{ getAcceptanceStatusText(workorder.acceptanceStatus) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="验收报告" v-if="workorder.acceptanceReport">
<el-link :href="workorder.acceptanceReport" target="_blank">
查看验收报告
</el-link>
</el-descriptions-item>
</el-descriptions>
</el-card>
</div>
工单处理页特殊操作
<!-- 硬件升级工单的专属操作 -->
<div v-if="workorder.orderType === 'hardware_upgrade'">
<!-- 检查备件库存 -->
<el-button v-if="workorder.status === 'pending'"
type="primary"
@click="handleCheckStock">
<el-icon><Box /></el-icon>
检查备件库存
</el-button>
<!-- 登记备件使用 -->
<el-button v-if="workorder.status === 'in_progress'"
type="success"
@click="handleRecordPartUsage">
<el-icon><Edit /></el-icon>
登记备件使用
</el-button>
<!-- 上传安装照片 -->
<el-button v-if="workorder.status === 'in_progress'"
type="primary"
@click="handleUploadPhoto">
<el-icon><Camera /></el-icon>
上传安装照片
</el-button>
<!-- 发起验收 -->
<el-button v-if="workorder.status === 'in_progress'"
type="warning"
@click="handleInitiateAcceptance">
<el-icon><DocumentChecked /></el-icon>
发起验收
</el-button>
</div>
2.4.4 后端差异化处理
工单创建服务 (BzWorkOrderServiceImpl.java)
@Override
@Transactional
public int insertBzWorkOrder(BzWorkOrder workOrder) {
// 硬件升级工单专属处理
if ("hardware_upgrade".equals(workOrder.getOrderType())) {
// 1. 自动检查备件库存
List<SparePartRequirement> requiredParts = parseRequiredParts(workOrder.getRequiredParts());
SparePartsCheckResult checkResult = sparePartService.checkStock(requiredParts);
// 2. 设置备件检查状态
if (checkResult.isAllSufficient()) {
workOrder.setPartsCheckStatus("sufficient");
} else {
workOrder.setPartsCheckStatus("insufficient");
workOrder.setInsufficientParts(JSON.toJSONString(checkResult.getInsufficientParts()));
// 如果库存不足,工单状态设置为暂停
workOrder.setStatus("paused");
}
// 3. 设置默认优先级
workOrder.setPriority("high"); // 硬件升级默认高优先级
// 4. 保存工单
int result = workOrderMapper.insertBzWorkOrder(workOrder);
// 5. 如果库存充足,预留备件
if (checkResult.isAllSufficient()) {
sparePartService.reserveParts(workOrder.getOrderId(), requiredParts);
}
return result;
}
return workOrderMapper.insertBzWorkOrder(workOrder);
}
/**
* 检查备件库存
*/
public SparePartsCheckResult checkStock(Long orderId) {
BzWorkOrder workOrder = workOrderMapper.selectBzWorkOrderByOrderId(orderId);
if (!"hardware_upgrade".equals(workOrder.getOrderType())) {
throw new ServiceException("仅硬件升级工单需要检查备件库存");
}
List<SparePartRequirement> requiredParts = parseRequiredParts(workOrder.getRequiredParts());
SparePartsCheckResult checkResult = sparePartService.checkStock(requiredParts);
// 更新工单备件检查状态
if (checkResult.isAllSufficient()) {
workOrder.setPartsCheckStatus("sufficient");
workOrder.setInsufficientParts(null);
// 如果之前是暂停状态,恢复为待处理
if ("paused".equals(workOrder.getStatus())) {
workOrder.setStatus("pending");
}
// 预留备件
sparePartService.reserveParts(workOrder.getOrderId(), requiredParts);
} else {
workOrder.setPartsCheckStatus("insufficient");
workOrder.setInsufficientParts(JSON.toJSONString(checkResult.getInsufficientParts()));
}
workOrderMapper.updateBzWorkOrder(workOrder);
return checkResult;
}
/**
* 登记备件使用
*/
@Transactional
public int recordPartUsage(Long orderId, List<InstalledPart> installedParts,
List<ReplacedPart> replacedParts) {
BzWorkOrder workOrder = workOrderMapper.selectBzWorkOrderByOrderId(orderId);
// 1. 保存已安装备件信息
workOrder.setInstalledParts(JSON.toJSONString(installedParts));
// 2. 保存更换下的旧件信息
if (replacedParts != null && !replacedParts.isEmpty()) {
workOrder.setReplacedParts(JSON.toJSONString(replacedParts));
}
// 3. 扣减备件库存
for (InstalledPart part : installedParts) {
sparePartService.deductStock(part.getPartCode(), part.getQuantity(),
"工单使用:" + workOrder.getOrderCode());
}
// 4. 旧件入库(如果可维修或回收)
for (ReplacedPart part : replacedParts) {
if ("repair".equals(part.getDisposal()) || "recycle".equals(part.getDisposal())) {
sparePartService.addReplacedPart(part);
}
}
// 5. 更新工单
return workOrderMapper.updateBzWorkOrder(workOrder);
}
紧急采购流程
/**
* 发起紧急采购
*/
@Transactional
public Long createEmergencyPurchase(Long orderId) {
BzWorkOrder workOrder = workOrderMapper.selectBzWorkOrderByOrderId(orderId);
// 获取不足的备件
List<InsufficientPart> insufficientParts = parseInsufficientParts(workOrder.getInsufficientParts());
// 创建采购申请
BzPurchaseRequest purchaseRequest = new BzPurchaseRequest();
purchaseRequest.setRequestType("emergency");
purchaseRequest.setRelatedOrderId(orderId);
purchaseRequest.setRelatedOrderCode(workOrder.getOrderCode());
purchaseRequest.setRequestReason("工单备件库存不足");
purchaseRequest.setUrgencyLevel("high");
purchaseRequest.setStatus("pending");
// 添加采购项目
List<BzPurchaseItem> items = new ArrayList<>();
for (InsufficientPart part : insufficientParts) {
BzPurchaseItem item = new BzPurchaseItem();
item.setPartCode(part.getPartCode());
item.setPartName(part.getPartName());
item.setSpecification(part.getSpecification());
item.setQuantity(part.getShortage()); // 短缺数量
items.add(item);
}
purchaseRequest.setItems(JSON.toJSONString(items));
// 保存采购申请
purchaseRequestService.insert(purchaseRequest);
// 关联工单
workOrder.setPurchaseRequestId(purchaseRequest.getId());
workOrderMapper.updateBzWorkOrder(workOrder);
// 发送采购通知
emailService.sendEmergencyPurchaseNotification(purchaseRequest);
return purchaseRequest.getId();
}
2.4.5 API接口
/**
* 检查备件库存
*/
@PreAuthorize("@ss.hasPermi('business:workOrder:checkStock')")
@GetMapping("/checkStock/{orderId}")
public AjaxResult checkStock(@PathVariable Long orderId) {
SparePartsCheckResult result = workOrderService.checkStock(orderId);
return success(result);
}
/**
* 发起紧急采购
*/
@PreAuthorize("@ss.hasPermi('business:workOrder:purchase')")
@PostMapping("/emergencyPurchase/{orderId}")
public AjaxResult emergencyPurchase(@PathVariable Long orderId) {
Long purchaseRequestId = workOrderService.createEmergencyPurchase(orderId);
return success("紧急采购申请已创建", purchaseRequestId);
}
/**
* 登记备件使用
*/
@PreAuthorize("@ss.hasPermi('business:workOrder:recordParts')")
@PostMapping("/recordPartUsage/{orderId}")
public AjaxResult recordPartUsage(@PathVariable Long orderId,
@RequestBody PartUsageRequest request) {
int result = workOrderService.recordPartUsage(
orderId,
request.getInstalledParts(),
request.getReplacedParts()
);
return toAjax(result, "备件使用登记成功");
}
/**
* 发起验收
*/
@PreAuthorize("@ss.hasPermi('business:workOrder:acceptance')")
@PostMapping("/initiateAcceptance/{orderId}")
public AjaxResult initiateAcceptance(@PathVariable Long orderId) {
BzWorkOrder workOrder = workOrderService.selectBzWorkOrderByOrderId(orderId);
// 验证是否已登记备件使用
if (StringUtils.isEmpty(workOrder.getInstalledParts())) {
return error("请先登记备件使用情况");
}
// 设置验收状态
workOrder.setAcceptanceStatus("pending");
workOrderService.updateBzWorkOrder(workOrder);
// 发送验收通知
emailService.sendAcceptanceNotification(workOrder);
return success("验收流程已启动");
}
2.5 需求变更工单 (REQUIREMENT_CHANGE)
2.5.1 业务特点
- 来源: 管理员创建
- 审批流程: 需要多角色评审(项目经理 + 研发负责人)
- 影响评估: 需要评估变更影响范围
- 角色区分: 分配时可选择研发/实施团队
- 变更记录: 详细记录变更内容和原因
2.5.2 数据库字段(BzWorkOrder表)
orderType: "requirement_change" // 工单类型
orderSource: "admin_create" // 来源-管理员创建
changeType: String // 变更类型: new_feature/modification/optimization/bug_fix
changeReason: String // 变更原因
changeContent: String // 变更内容(详细描述)
impactScope: String // 影响范围: minor/moderate/major/critical
impactAnalysis: String // 影响分析
reviewStatus: String // 评审状态: pending/in_review/approved/rejected
reviewers: String // 评审人列表(JSON数组)
reviewRecords: String // 评审记录(JSON数组)
approvalTime: Date // 批准时间
rejectionReason: String // 拒绝原因
implementationTeam: String // 实施团队: development/implementation/both
estimatedWorkload: Integer // 预估工作量(人天)
actualWorkload: Integer // 实际工作量(人天)
testRequirement: String // 测试要求
acceptanceCriteria: String // 验收标准
2.5.3 前端差异化处理
工单创建页 (workorder-form.vue)
<!-- 需求变更专属字段 -->
<div v-if="form.orderType === 'requirement_change'">
<!-- 1. 变更类型 -->
<el-form-item label="变更类型" prop="changeType" required>
<el-select v-model="form.changeType">
<el-option label="新功能开发" value="new_feature" />
<el-option label="功能修改" value="modification" />
<el-option label="功能优化" value="optimization" />
<el-option label="缺陷修复" value="bug_fix" />
</el-select>
</el-form-item>
<!-- 2. 变更原因 -->
<el-form-item label="变更原因" prop="changeReason" required>
<el-input v-model="form.changeReason"
type="textarea"
:rows="3"
placeholder="请说明为什么需要进行此次变更" />
</el-form-item>
<!-- 3. 变更内容 -->
<el-form-item label="变更内容" prop="changeContent" required>
<rich-text-editor v-model="form.changeContent"
placeholder="请详细描述变更的具体内容、功能点、业务流程等" />
</el-form-item>
<!-- 4. 影响范围 -->
<el-form-item label="影响范围" prop="impactScope" required>
<el-radio-group v-model="form.impactScope">
<el-radio label="minor">
<el-tag type="info">轻微</el-tag>
<span class="ml-2">仅影响单个模块</span>
</el-radio>
<el-radio label="moderate">
<el-tag type="warning">中等</el-tag>
<span class="ml-2">影响多个相关模块</span>
</el-radio>
<el-radio label="major">
<el-tag type="danger">重大</el-tag>
<span class="ml-2">影响核心业务流程</span>
</el-radio>
<el-radio label="critical">
<el-tag type="danger">严重</el-tag>
<span class="ml-2">影响整个系统架构</span>
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 5. 影响分析 -->
<el-form-item label="影响分析" prop="impactAnalysis" required>
<el-input v-model="form.impactAnalysis"
type="textarea"
:rows="4"
placeholder="分析此次变更对现有功能、数据、用户、性能等方面的影响" />
</el-form-item>
<!-- 6. 预估工作量 -->
<el-form-item label="预估工作量" prop="estimatedWorkload" required>
<el-input-number v-model="form.estimatedWorkload"
:min="1"
:max="100"
controls-position="right" />
<span class="ml-2">人天</span>
</el-form-item>
<!-- 7. 实施团队 -->
<el-form-item label="实施团队" prop="implementationTeam" required>
<el-radio-group v-model="form.implementationTeam">
<el-radio label="development">研发团队</el-radio>
<el-radio label="implementation">实施团队</el-radio>
<el-radio label="both">研发+实施团队</el-radio>
</el-radio-group>
</el-form-item>
<!-- 8. 测试要求 -->
<el-form-item label="测试要求" prop="testRequirement">
<el-input v-model="form.testRequirement"
type="textarea"
:rows="3"
placeholder="描述需要进行的测试类型和测试场景" />
</el-form-item>
<!-- 9. 验收标准 -->
<el-form-item label="验收标准" prop="acceptanceCriteria" required>
<el-input v-model="form.acceptanceCriteria"
type="textarea"
:rows="3"
placeholder="定义清晰的验收标准,以便后续验收" />
</el-form-item>
<!-- 10. 评审人选择 -->
<el-form-item label="评审人" prop="reviewers" required>
<el-select v-model="form.reviewers"
multiple
placeholder="请选择评审人">
<el-option-group label="项目经理">
<el-option v-for="pm in projectManagers"
:key="pm.userId"
:label="pm.nickName"
:value="pm.userId" />
</el-option-group>
<el-option-group label="研发负责人">
<el-option v-for="dev in devLeaders"
:key="dev.userId"
:label="dev.nickName"
:value="dev.userId" />
</el-option-group>
</el-select>
</el-form-item>
</div>
工单详情页 (workorder-detail.vue)
<!-- 需求变更专属信息 -->
<div v-if="workorder.orderType === 'requirement_change'">
<!-- 变更信息 -->
<el-descriptions title="变更信息" :column="2" border>
<el-descriptions-item label="变更类型">
<dict-tag :options="change_type" :value="workorder.changeType" />
</el-descriptions-item>
<el-descriptions-item label="影响范围">
<el-tag :type="getImpactScopeType(workorder.impactScope)">
{{ getImpactScopeText(workorder.impactScope) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="实施团队">
<dict-tag :options="implementation_team" :value="workorder.implementationTeam" />
</el-descriptions-item>
<el-descriptions-item label="预估工作量">
{{ workorder.estimatedWorkload }} 人天
</el-descriptions-item>
<el-descriptions-item label="实际工作量" v-if="workorder.actualWorkload">
{{ workorder.actualWorkload }} 人天
</el-descriptions-item>
<el-descriptions-item label="变更原因" :span="2">
{{ workorder.changeReason }}
</el-descriptions-item>
</el-descriptions>
<!-- 变更内容 -->
<el-card title="变更内容" class="mt-3">
<div class="rich-text-content" v-html="workorder.changeContent"></div>
</el-card>
<!-- 影响分析 -->
<el-card title="影响分析" class="mt-3">
<pre class="analysis-content">{{ workorder.impactAnalysis }}</pre>
</el-card>
<!-- 评审流程 -->
<el-card title="评审流程" class="mt-3">
<el-descriptions :column="2" border class="mb-3">
<el-descriptions-item label="评审状态">
<el-tag :type="getReviewStatusType(workorder.reviewStatus)">
{{ getReviewStatusText(workorder.reviewStatus) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="批准时间" v-if="workorder.approvalTime">
{{ parseTime(workorder.approvalTime) }}
</el-descriptions-item>
<el-descriptions-item label="拒绝原因" :span="2" v-if="workorder.rejectionReason">
<el-alert type="error" :closable="false">
{{ workorder.rejectionReason }}
</el-alert>
</el-descriptions-item>
</el-descriptions>
<!-- 评审记录时间轴 -->
<el-timeline v-if="workorder.reviewRecords">
<el-timeline-item
v-for="(record, index) in parseReviewRecords(workorder.reviewRecords)"
:key="index"
:timestamp="parseTime(record.reviewTime)"
:type="getReviewResultType(record.result)">
<div class="review-record">
<div class="reviewer-info">
<el-tag>{{ record.reviewerName }}</el-tag>
<el-tag :type="getReviewResultType(record.result)" class="ml-2">
{{ record.result }}
</el-tag>
</div>
<div class="review-comment mt-2" v-if="record.comment">
{{ record.comment }}
</div>
</div>
</el-timeline-item>
</el-timeline>
</el-card>
<!-- 测试要求 -->
<el-card title="测试要求" class="mt-3" v-if="workorder.testRequirement">
<pre class="test-requirement">{{ workorder.testRequirement }}</pre>
</el-card>
<!-- 验收标准 -->
<el-card title="验收标准" class="mt-3">
<pre class="acceptance-criteria">{{ workorder.acceptanceCriteria }}</pre>
</el-card>
</div>
评审操作界面
<!-- 需求变更评审对话框 -->
<el-dialog title="需求变更评审" v-model="reviewDialogOpen" width="700px">
<el-form :model="reviewForm" label-width="100px">
<el-form-item label="评审结果" required>
<el-radio-group v-model="reviewForm.result">
<el-radio label="approved">
<el-tag type="success">通过</el-tag>
</el-radio>
<el-radio label="rejected">
<el-tag type="danger">拒绝</el-tag>
</el-radio>
<el-radio label="need_modification">
<el-tag type="warning">需要修改</el-tag>
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="评审意见" required>
<el-input v-model="reviewForm.comment"
type="textarea"
:rows="5"
placeholder="请输入您的评审意见..." />
</el-form-item>
<!-- 拒绝原因(仅拒绝时显示) -->
<el-form-item label="拒绝原因" v-if="reviewForm.result === 'rejected'" required>
<el-select v-model="reviewForm.rejectionReason" placeholder="请选择拒绝原因">
<el-option label="需求不合理" value="unreasonable" />
<el-option label="技术不可行" value="infeasible" />
<el-option label="影响范围太大" value="too_large_impact" />
<el-option label="优先级不够" value="low_priority" />
<el-option label="其他原因" value="other" />
</el-select>
</el-form-item>
<!-- 修改建议(需要修改时显示) -->
<el-form-item label="修改建议" v-if="reviewForm.result === 'need_modification'" required>
<el-input v-model="reviewForm.modificationSuggestion"
type="textarea"
:rows="4"
placeholder="请详细说明需要修改的内容..." />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="reviewDialogOpen = false">取消</el-button>
<el-button type="primary" @click="submitReview" :loading="reviewLoading">
提交评审
</el-button>
</template>
</el-dialog>
2.5.4 后端差异化处理
工单创建服务 (BzWorkOrderServiceImpl.java)
@Override
@Transactional
public int insertBzWorkOrder(BzWorkOrder workOrder) {
// 需求变更工单专属处理
if ("requirement_change".equals(workOrder.getOrderType())) {
// 1. 设置评审状态为待评审
workOrder.setReviewStatus("pending");
// 2. 设置优先级(根据影响范围)
if ("critical".equals(workOrder.getImpactScope())) {
workOrder.setPriority("urgent");
} else if ("major".equals(workOrder.getImpactScope())) {
workOrder.setPriority("high");
} else {
workOrder.setPriority("medium");
}
// 3. 保存工单
int result = workOrderMapper.insertBzWorkOrder(workOrder);
// 4. 初始化评审记录
List<ReviewRecord> reviewRecords = new ArrayList<>();
List<Long> reviewerIds = parseReviewers(workOrder.getReviewers());
for (Long reviewerId : reviewerIds) {
ReviewRecord record = new ReviewRecord();
record.setReviewerId(reviewerId);
record.setResult("pending");
reviewRecords.add(record);
}
workOrder.setReviewRecords(JSON.toJSONString(reviewRecords));
workOrderMapper.updateBzWorkOrder(workOrder);
// 5. 发送评审通知
sendReviewNotification(workOrder, reviewerIds);
return result;
}
return workOrderMapper.insertBzWorkOrder(workOrder);
}
/**
* 发送评审通知
*/
private void sendReviewNotification(BzWorkOrder workOrder, List<Long> reviewerIds) {
for (Long reviewerId : reviewerIds) {
SysUser reviewer = userService.selectUserById(reviewerId);
if (reviewer != null && reviewer.getEmail() != null) {
emailService.sendReviewNotification(reviewer.getEmail(), workOrder);
}
}
}
评审流程处理
/**
* 提交评审意见
*/
@Transactional
public int submitReview(Long orderId, Long reviewerId, ReviewRequest request) {
BzWorkOrder workOrder = workOrderMapper.selectBzWorkOrderByOrderId(orderId);
// 验证工单类型
if (!"requirement_change".equals(workOrder.getOrderType())) {
throw new ServiceException("仅需求变更工单支持评审");
}
// 验证评审状态
if (!"pending".equals(workOrder.getReviewStatus()) &&
!"in_review".equals(workOrder.getReviewStatus())) {
throw new ServiceException("工单当前状态不支持评审");
}
// 验证评审人权限
List<Long> reviewerIds = parseReviewers(workOrder.getReviewers());
if (!reviewerIds.contains(reviewerId)) {
throw new ServiceException("您不是该工单的评审人");
}
// 更新评审记录
List<ReviewRecord> reviewRecords = parseReviewRecords(workOrder.getReviewRecords());
boolean found = false;
for (ReviewRecord record : reviewRecords) {
if (record.getReviewerId().equals(reviewerId)) {
record.setResult(request.getResult());
record.setComment(request.getComment());
record.setReviewTime(new Date());
record.setReviewerName(SecurityUtils.getUsername());
found = true;
break;
}
}
if (!found) {
throw new ServiceException("未找到您的评审记录");
}
// 保存评审记录
workOrder.setReviewRecords(JSON.toJSONString(reviewRecords));
workOrder.setReviewStatus("in_review");
// 检查所有评审人是否都已评审
boolean allReviewed = reviewRecords.stream()
.allMatch(r -> !"pending".equals(r.getResult()));
if (allReviewed) {
// 所有人都已评审,判断最终结果
long approvedCount = reviewRecords.stream()
.filter(r -> "approved".equals(r.getResult()))
.count();
long rejectedCount = reviewRecords.stream()
.filter(r -> "rejected".equals(r.getResult()))
.count();
if (rejectedCount > 0) {
// 有人拒绝,则评审不通过
workOrder.setReviewStatus("rejected");
workOrder.setRejectionReason(request.getRejectionReason());
workOrder.setStatus("cancelled");
} else if (approvedCount == reviewRecords.size()) {
// 所有人都通过,则评审通过
workOrder.setReviewStatus("approved");
workOrder.setApprovalTime(new Date());
workOrder.setStatus("pending"); // 可以分配执行
} else {
// 有人要求修改
workOrder.setReviewStatus("need_modification");
}
// 发送评审结果通知
emailService.sendReviewResultNotification(workOrder);
}
return workOrderMapper.updateBzWorkOrder(workOrder);
}
工单分配验证
@Override
public int assignWorkOrderToTechnician(Long orderId, String technicianCode, String technicianName) {
BzWorkOrder workOrder = workOrderMapper.selectBzWorkOrderByOrderId(orderId);
// 需求变更工单专属验证
if ("requirement_change".equals(workOrder.getOrderType())) {
// 必须评审通过才能分配
if (!"approved".equals(workOrder.getReviewStatus())) {
throw new ServiceException("需求变更工单必须评审通过后才能分配执行");
}
}
// 执行分配
workOrder.setAssignedTechnicianId(technicianService.getIdByCode(technicianCode));
workOrder.setAssignedTechnician(technicianCode);
workOrder.setStatus("assigned");
return workOrderMapper.updateBzWorkOrder(workOrder);
}
2.5.5 API接口
/**
* 提交评审意见
*/
@PreAuthorize("@ss.hasPermi('business:workOrder:review')")
@PostMapping("/submitReview/{orderId}")
public AjaxResult submitReview(@PathVariable Long orderId,
@RequestBody @Validated ReviewRequest request) {
Long reviewerId = SecurityUtils.getUserId();
int result = workOrderService.submitReview(orderId, reviewerId, request);
return toAjax(result, "评审意见提交成功");
}
/**
* 获取我的待评审工单列表
*/
@PreAuthorize("@ss.hasPermi('business:workOrder:review')")
@GetMapping("/myPendingReviews")
public TableDataInfo myPendingReviews() {
Long userId = SecurityUtils.getUserId();
startPage();
List<BzWorkOrder> list = workOrderService.selectPendingReviewsByReviewer(userId);
return getDataTable(list);
}
/**
* 催办评审
*/
@PreAuthorize("@ss.hasPermi('business:workOrder:urgeReview')")
@PostMapping("/urgeReview/{orderId}")
public AjaxResult urgeReview(@PathVariable Long orderId) {
BzWorkOrder workOrder = workOrderService.selectBzWorkOrderByOrderId(orderId);
// 获取未评审的评审人
List<ReviewRecord> reviewRecords = parseReviewRecords(workOrder.getReviewRecords());
List<Long> pendingReviewerIds = reviewRecords.stream()
.filter(r -> "pending".equals(r.getResult()))
.map(ReviewRecord::getReviewerId)
.collect(Collectors.toList());
if (pendingReviewerIds.isEmpty()) {
return error("所有评审人都已完成评审");
}
// 发送催办通知
for (Long reviewerId : pendingReviewerIds) {
SysUser reviewer = userService.selectUserById(reviewerId);
if (reviewer != null && reviewer.getEmail() != null) {
emailService.sendUrgeReviewNotification(reviewer.getEmail(), workOrder);
}
}
return success("催办通知已发送");
}
/**
* 重新提交评审(修改后)
*/
@PreAuthorize("@ss.hasPermi('business:workOrder:edit')")
@PutMapping("/resubmitReview/{orderId}")
public AjaxResult resubmitReview(@PathVariable Long orderId) {
BzWorkOrder workOrder = workOrderService.selectBzWorkOrderByOrderId(orderId);
// 重置评审状态
workOrder.setReviewStatus("pending");
// 重置所有评审记录
List<ReviewRecord> reviewRecords = parseReviewRecords(workOrder.getReviewRecords());
for (ReviewRecord record : reviewRecords) {
record.setResult("pending");
record.setComment(null);
record.setReviewTime(null);
}
workOrder.setReviewRecords(JSON.toJSONString(reviewRecords));
workOrderService.updateBzWorkOrder(workOrder);
// 重新发送评审通知
List<Long> reviewerIds = reviewRecords.stream()
.map(ReviewRecord::getReviewerId)
.collect(Collectors.toList());
sendReviewNotification(workOrder, reviewerIds);
return success("已重新提交评审");
}
三、统一处理逻辑(通用部分)
3.1 通用状态流转
所有工单类型共享相同的状态流转机制,由 WorkOrderStatusEnum.java 统一管理。
3.2 通用工单操作
以下操作对所有工单类型都适用:
- 查询工单列表/详情
- 修改工单基础信息(标题、描述、优先级等)
- 分配技术员
- 收到工单(技术员确认)
- 联系客户
- 到达现场
- 暂停工单
- 恢复工单
- 取消工单
- 关闭工单
3.3 差异化处理策略
采用if-else分支的方式在统一接口中处理差异化逻辑:
public int processWorkOrder(Long orderId, String action) {
BzWorkOrder workOrder = selectBzWorkOrderByOrderId(orderId);
// 通用处理逻辑
// ...
// 类型差异化处理
if (WorkOrderTypeEnum.isMaintenance(workOrder.getOrderType())) {
// 维护保养专属逻辑
handleMaintenanceWorkOrder(workOrder, action);
} else if (WorkOrderTypeEnum.isFault(workOrder.getOrderType())) {
// 故障报修专属逻辑
handleFaultWorkOrder(workOrder, action);
} else if ("software_upgrade".equals(workOrder.getOrderType())) {
// 软件升级专属逻辑
handleSoftwareUpgradeWorkOrder(workOrder, action);
} else if ("hardware_upgrade".equals(workOrder.getOrderType())) {
// 硬件升级专属逻辑
handleHardwareUpgradeWorkOrder(workOrder, action);
} else if ("requirement_change".equals(workOrder.getOrderType())) {
// 需求变更专属逻辑
handleRequirementChangeWorkOrder(workOrder, action);
}
// 继续通用处理
// ...
}
四、数据字典配置
需要在系统数据字典中配置以下字典项:
4.1 工单类型 (work_order_type)
maintenance - 维护保养
fault - 故障报修
software_upgrade - 软件升级
hardware_upgrade - 硬件升级
requirement_change - 需求变更4.2 故障类型 (fault_type)
mechanical - 机械故障
electrical - 电气故障
software - 软件故障
network - 网络故障
other - 其他故障4.3 故障等级 (fault_level)
critical - 严重故障
major - 重要故障
minor - 一般故障4.4 硬件升级类型 (hardware_upgrade_type)
replacement - 硬件替换
addition - 硬件新增
optimization - 硬件优化4.5 硬件类型 (hardware_type)
sensor - 传感器
controller - 控制器
motor - 电机
actuator - 执行器
power_module - 电源模块
other - 其他4.6 变更类型 (change_type)
new_feature - 新功能开发
modification - 功能修改
optimization - 功能优化
bug_fix - 缺陷修复4.7 影响范围 (impact_scope)
minor - 轻微
moderate - 中等
major - 重大
critical - 严重4.8 实施团队 (implementation_team)
development - 研发团队
implementation - 实施团队
both - 研发+实施五、实现优先级建议
阶段一:核心功能(1-2周)
维护保养工单 - 最常用,优先实现
- 按设备拆分子任务
- 关联检查模板
- 进度统计
故障报修工单 - 客户最关心
- 客户上报入口
- 紧急通知机制
- 响应超时检查
阶段二:高级功能(2-3周)
硬件升级工单
- 备件库存检查
- 备件使用登记
- 紧急采购流程
软件升级工单
- 版本管理
- 升级日志上传
- 回滚机制
阶段三:扩展功能(1-2周)
- 需求变更工单
- 评审流程
- 多角色审批
- 评审记录时间轴
六、总结
6.1 设计优势
- 统一架构: 5种工单类型共享核心数据结构和状态流转机制
- 差异化处理: 通过字段扩展和分支逻辑实现各类型专属功能
- 代码复用: 最大化复用现有代码,避免重复开发
- 易于维护: 集中管理工单逻辑,便于后期扩展新类型
- 用户体验: 统一的前端页面,动态展示不同类型的专属信息
6.2 技术特点
- 前端: Vue 3组件化 + v-if动态渲染 + Element Plus
- 后端: Spring Boot分层架构 + 策略模式 + 模板方法
- 数据库: 单表存储 + JSON扩展字段
- 状态管理: 枚举定义 + 状态机验证
- 权限控制: Spring Security + 按钮级权限
6.3 扩展性
- 新增工单类型只需:
- 在枚举中添加类型定义
- 在前端添加v-if分支渲染
- 在服务层添加差异化处理分支
- 无需修改核心架构
最后编辑:聂盼盼 更新时间:2025-10-28 19:53