Merge remote-tracking branch 'origin/master'

master
夜笙歌 2 weeks ago
commit f148a367e7

@ -8,7 +8,7 @@ import { ProdBaseMachineInfoVO, ProdBaseMachineInfoForm, ProdBaseMachineInfoQuer
* @returns {*}
*/
export const listDmsBaseMachineInfoIn = (query?: ProdBaseMachineInfoQuery): AxiosPromise<ProdBaseMachineInfoVO[]> => {
export const listDmsBaseMachineInfo = (query?: ProdBaseMachineInfoQuery): AxiosPromise<ProdBaseMachineInfoVO[]> => {
return request({
url: '/dms/dmsBaseMachineInfo/list',
method: 'get',

@ -64,6 +64,11 @@ export interface DmsRecordShutDownVO {
*/
shutTypeName: string;
/**
* ()
*/
shutReasonName: string;
}
export interface DmsRecordShutDownForm extends BaseEntity {
@ -132,6 +137,11 @@ export interface DmsRecordShutDownForm extends BaseEntity {
*/
shutTypeName?: string;
/**
* ()
*/
shutReasonName?: string;
}
export interface DmsRecordShutDownQuery extends PageQuery {
@ -200,6 +210,12 @@ export interface DmsRecordShutDownQuery extends PageQuery {
*
*/
shutTypeName?: string;
/**
* ()
*/
shutReasonName?: string;
}

@ -129,8 +129,8 @@
<script setup name='AlarmType' lang='ts'>
import { listAlarmType, getAlarmType, delAlarmType, addAlarmType, updateAlarmType } from '@/api/dms/alarmType';
import { AlarmTypeVO, AlarmTypeQuery, AlarmTypeForm } from '@/api/dms/alarmType/types';
import { getBaseDeviceCategoryListInDMS } from '@/api/dms/baseDeviceCategory';
import { getBaseDeviceTypeListInDMS } from '@/api/dms/baseDeviceType';
import { getBaseDeviceCategoryList } from '@/api/dms/baseDeviceCategory';
import { getBaseDeviceTypeList } from '@/api/dms/baseDeviceType';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { dms_level_status } = toRefs<any>(proxy?.useDict('dms_level_status'));
@ -155,14 +155,14 @@ const dialog = reactive<DialogOption>({
/** 获取设备大类下拉框 */
const deviceCategoryList = ref([]);
const getBaseDeviceCategoryListSelect = async () => {
let res = await getBaseDeviceCategoryListInDMS(null);
let res = await getBaseDeviceCategoryList(null);
deviceCategoryList.value = res.data;
};
/** 获取设备类型下拉框 */
const deviceTypeList = ref([]);
const getBaseDeviceTypeListSelect = async () => {
let res = await getBaseDeviceTypeListInDMS(null);
let res = await getBaseDeviceTypeList(null);
deviceTypeList.value = res.data;
};

@ -190,14 +190,12 @@ const data = reactive<PageData<DmsBaseDeviceSuppliesForm, DmsBaseDeviceSuppliesQ
},
rules: {
machineId: [
{ required: true, message: "设备不能为空", trigger: "blur" }
{ required: true, message: "设备不能为空", trigger: "change" },
{ required: true, message: "设备不能为空", trigger: "change" }
],
suppliesName: [
{ required: true, message: "配套信息名称不能为空", trigger: "blur" }
],
machineId: [
{ required: true, message: "设备不能为空", trigger: "change" }
],
]
}
});

@ -134,7 +134,7 @@
<script setup name="DmsBaseInspectRoute" lang="ts">
import { listDmsBaseInspectRoute, getDmsBaseInspectRoute, delDmsBaseInspectRoute, addDmsBaseInspectRoute, updateDmsBaseInspectRoute } from '@/api/dms/dmsBaseInspectRoute';
import { DmsBaseInspectRouteVO, DmsBaseInspectRouteQuery, DmsBaseInspectRouteForm } from '@/api/dms/dmsBaseInspectRoute/types';
import {getBaseDeviceTypeListInDMS} from "@/api/dms/baseDeviceType";
import {getBaseDeviceTypeList} from "@/api/dms/baseDeviceType";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { inspect_type } = toRefs<any>(proxy?.useDict('inspect_type'));
@ -304,7 +304,7 @@ const handleExport = () => {
let deviceTypeOptions = ref([])
const getDeviceTypeOptions = async () => {
const res = await getBaseDeviceTypeListInDMS(null);
const res = await getBaseDeviceTypeList(null);
deviceTypeOptions.value = res.data;
}

@ -126,7 +126,7 @@
<script setup name="DmsBaseLubeStandard" lang="ts">
import { listDmsBaseLubeStandard, getDmsBaseLubeStandard, delDmsBaseLubeStandard, addDmsBaseLubeStandard, updateDmsBaseLubeStandard } from '@/api/dms/dmsBaseLubeStandard';
import { DmsBaseLubeStandardVO, DmsBaseLubeStandardQuery, DmsBaseLubeStandardForm } from '@/api/dms/dmsBaseLubeStandard/types';
import {getBaseDeviceTypeListInDMS} from "@/api/dms/baseDeviceType";
import {getBaseDeviceTypeList} from "@/api/dms/baseDeviceType";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { active_flag } = toRefs<any>(proxy?.useDict('active_flag'));
@ -301,7 +301,7 @@ const handleExport = () => {
//
let deviceTypeOptions = ref([]);
const getBaseDeviceTypeOption = async () => {
const res = await getBaseDeviceTypeListInDMS(null);
const res = await getBaseDeviceTypeList(null);
deviceTypeOptions.value = res.data;
}

@ -136,7 +136,7 @@
import { listDmsBaseLubeStation, getDmsBaseLubeStation, delDmsBaseLubeStation, addDmsBaseLubeStation, updateDmsBaseLubeStation } from '@/api/dms/dmsBaseLubeStation';
import { DmsBaseLubeStationVO, DmsBaseLubeStationQuery, DmsBaseLubeStationForm } from '@/api/dms/dmsBaseLubeStation/types';
import {getDmsBaseLubeStandardList} from "@/api/dms/dmsBaseLubeStandard";
import {getBaseDeviceTypeListInDMS} from "@/api/dms/baseDeviceType";
import {getBaseDeviceTypeList} from "@/api/dms/baseDeviceType";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { active_flag } = toRefs<any>(proxy?.useDict('active_flag'));
@ -310,7 +310,7 @@ const handleExport = () => {
//
let deviceTypeOptions = ref([]);
const getBaseDeviceTypeOption = async () => {
const res = await getBaseDeviceTypeListInDMS(null);
const res = await getBaseDeviceTypeList(null);
deviceTypeOptions.value = res.data;
}

@ -129,7 +129,7 @@
<script setup name="DmsBaseMaintStandard" lang="ts">
import { listDmsBaseMaintStandard, getDmsBaseMaintStandard, delDmsBaseMaintStandard, addDmsBaseMaintStandard, updateDmsBaseMaintStandard } from '@/api/dms/dmsBaseMaintStandard';
import { DmsBaseMaintStandardVO, DmsBaseMaintStandardQuery, DmsBaseMaintStandardForm } from '@/api/dms/dmsBaseMaintStandard/types';
import {getBaseDeviceTypeListInDMS} from "@/api/dms/baseDeviceType";
import {getBaseDeviceTypeList} from "@/api/dms/baseDeviceType";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { active_flag } = toRefs<any>(proxy?.useDict('active_flag'));
@ -298,7 +298,7 @@ const handleExport = () => {
//
let dviceTypeOptions = ref([]);
const getDviceTypeOption = async () => {
const res = await getBaseDeviceTypeListInDMS(null);
const res = await getBaseDeviceTypeList(null);
dviceTypeOptions.value = res.data;
}

@ -149,7 +149,7 @@
import { listDmsBaseMaintStation, getDmsBaseMaintStation, delDmsBaseMaintStation, addDmsBaseMaintStation, updateDmsBaseMaintStation } from '@/api/dms/dmsBaseMaintStation';
import { DmsBaseMaintStationVO, DmsBaseMaintStationQuery, DmsBaseMaintStationForm } from '@/api/dms/dmsBaseMaintStation/types';
import {getDmsBaseMaintStandardList} from "@/api/dms/dmsBaseMaintStandard";
import {getBaseDeviceTypeListInDMS} from "@/api/dms/baseDeviceType";
import {getBaseDeviceTypeList} from "@/api/dms/baseDeviceType";
import { getDmsBaseMaintProjectList } from '@/api/dms/dmsBaseMaintProject';
@ -332,7 +332,7 @@ const getMaintStandardOtions = async () => {
//
let dviceTypeOptions = ref([]);
const getDviceTypeOption = async () => {
const res = await getBaseDeviceTypeListInDMS(null);
const res = await getBaseDeviceTypeList(null);
dviceTypeOptions.value = res.data;
}

@ -204,7 +204,8 @@ const data = reactive<PageData<DmsBaseSpecialdeviceParamForm, DmsBaseSpecialdevi
},
rules: {
machineId: [
{ required: true, message: "设备不能为空", trigger: "blur" }
{ required: true, message: "设备不能为空", trigger: "blur" },
{ required: true, message: "设备不能为空", trigger: "change" }
],
paramCode: [
{ required: true, message: "参数编号不能为空", trigger: "blur" }
@ -214,9 +215,6 @@ const data = reactive<PageData<DmsBaseSpecialdeviceParamForm, DmsBaseSpecialdevi
],
activeFlag: [
{ required: true, message: "激活标识不能为空", trigger: "change" }
],
machineId: [
{ required: true, message: "设备不能为空", trigger: "change" }
]
}
});

@ -4,20 +4,7 @@
<el-card shadow="never">
<div style="display: flex; justify-content: space-between">
<div>
<el-button
v-if="submitButtonShow"
:loading="buttonLoading"
type="info"
@click="submitForm('draft')">
暂存
</el-button>
<el-button
v-if="submitButtonShow"
:loading="buttonLoading"
type="primary"
@click="submitForm('submit')">
提交
</el-button>
<!-- 移除提交按钮Web端不再创建工单 -->
<el-button
v-if="approvalButtonShow"
:loading="buttonLoading"
@ -86,150 +73,84 @@
</el-form>
</el-card>
<!-- 当前步骤处理区域 -->
<!-- PDA端创建的工单详情只读显示 -->
<el-card shadow="never">
<h4 class="form-header">{{ currentStepTitle }}</h4>
<h4 class="form-header">PDA端创建的工单详情只读</h4>
<el-form
ref="processFormRef"
v-loading="loading"
:disabled="routeParams.type === 'view'"
:model="form"
:rules="rules"
label-width="120px">
<el-row>
<!-- 第一步故障报修信息 -->
<template v-if="currentStepOrder === 1">
<el-col :span="8">
<el-form-item label="故障类型" prop="faultType">
<el-select v-model="form.faultType" placeholder="请选择故障类型">
<el-option
v-for="dict in activity_fault_type"
:key="dict.value"
:label="dict.label"
:value="dict.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="维修类型" prop="repairType">
<el-select
v-model="form.repairType"
placeholder="请选择维修类型"
@change="updateOutsourcingValidation">
<el-option
v-for="dict in fault_repair_type"
:key="dict.value"
:label="dict.label"
:value="dict.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="外协单位" prop="outsourcingId">
<el-input v-model="form.outsourcingId" placeholder="请输入外协单位" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="故障描述" prop="faultDescription">
<el-input
v-model="form.faultDescription"
type="textarea"
:rows="3"
placeholder="请输入故障描述" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="涉及操作" prop="designOperations">
<el-input
v-model="form.designOperations"
type="textarea"
:rows="2"
placeholder="请输入涉及操作" />
</el-form-item>
</el-col>
</template>
<!-- 故障报修信息只读显示 -->
<el-col :span="8">
<el-form-item label="故障类型">
<el-input v-model="form.faultType" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="维修类型">
<el-input v-model="form.repairType" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="外协单位">
<el-input v-model="outsourcingName" disabled />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="故障描述">
<el-input
v-model="form.faultDescription"
type="textarea"
:rows="3"
disabled />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="涉及操作">
<el-input
v-model="form.designOperations"
type="textarea"
:rows="2"
disabled />
</el-form-item>
</el-col>
<!-- 第二步维修处理 -->
<template v-if="currentStepOrder === 2">
<el-col :span="8">
<el-form-item label="故障类型">
<el-input v-model="form.faultType" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="维修类型">
<el-input v-model="form.repairType" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="维修人" prop="repairer">
<el-input v-model="form.repairer" placeholder="请输入维修人" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="检查后的故障判断" prop="checkedFault">
<el-input
v-model="form.checkedFault"
type="textarea"
:rows="3"
placeholder="请输入检查后的故障判断" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="维修措施" prop="protectedMethod">
<el-input
v-model="form.protectedMethod"
type="textarea"
:rows="3"
placeholder="请输入维修措施" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="维修内容" prop="repairContent">
<el-input
v-model="form.repairContent"
type="textarea"
:rows="4"
placeholder="请输入维修内容" />
</el-form-item>
</el-col>
</template>
<!-- 第三步维修确认 -->
<template v-if="currentStepOrder === 3">
<!-- 维修处理信息只读显示 -->
<template v-if="form.repairer">
<el-col :span="8">
<el-form-item label="维修人">
<el-input v-model="form.repairer" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="维修结果" prop="repairConfirm">
<el-select v-model="form.repairConfirm" placeholder="请选择维修结果">
<el-option
v-for="dict in fault_repair_confirm"
:key="dict.value"
:label="dict.label"
:value="dict.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="关联更换零部件" prop="componentsPartsId">
<el-input v-model="form.componentsPartsId" placeholder="请输入关联更换零部件内容" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="维修内容">
<el-input v-model="form.repairContent" type="textarea" :rows="3" disabled />
<el-form-item label="检查后的故障判断">
<el-input
v-model="form.checkedFault"
type="textarea"
:rows="3"
disabled />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="维修措施">
<el-input v-model="form.protectedMethod" type="textarea" :rows="3" disabled />
<el-input
v-model="form.protectedMethod"
type="textarea"
:rows="3"
disabled />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="维修内容">
<el-input
v-model="form.repairContent"
type="textarea"
:rows="4"
disabled />
</el-form-item>
</el-col>
</template>
@ -249,7 +170,7 @@
</el-form-item>
</el-col>
<!-- 处理意见除第一步外都需要 -->
<!-- 处理意见审批时需要 -->
<el-col :span="24" v-if="needProcessResolution">
<el-form-item label="处理意见" prop="processHandleResolution">
<el-input
@ -277,12 +198,11 @@
<script setup name="FaultWorkOrderProcess" lang="ts">
//
import { ref, reactive, computed, onMounted, getCurrentInstance, toRefs, watch } from 'vue';
import { ref, reactive, computed, onMounted, getCurrentInstance, toRefs } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ElMessageBox, type FormInstance } from 'element-plus';
import type { ComponentInternalInstance } from 'vue';
import { getDmsBillsFaultInstance, approveWorkOrder, confirmRepairResult } from '@/api/dms/dmsBillsFaultInstance';
import { addDmsFaultInstanceActivity } from '@/api/dms/dmsFaultInstanceActivity';
import { pageByTaskWait } from '@/api/workflow/task';
import { getCurrentDateTime } from '@/utils/dateUtils';
import { getDmsBaseOutsourcingInfoList } from '@/api/dms/dmsBaseOutsourcingInfo';
import SubmitVerify from '@/views/dms/dmsFaultInstanceActivity/submitVerify.vue';
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
@ -299,15 +219,14 @@ const routeParams = ref<Record<string, any>>({});
const workOrder = ref<any>({});
const workOrderStatusText = ref('');
const outsourceList = ref<any[]>([]);
const outsourcingName = ref('');
//
const processFormRef = ref<ElFormInstance>();
const processFormRef = ref<FormInstance>();
const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
//
const currentTask = ref<any>(null);
const currentStepOrder = ref(1);
//
const taskVariables = ref<any>({});
//
@ -325,7 +244,6 @@ const initFormData = {
repairConfirm: undefined,
componentsPartsId: undefined,
processHandleResolution: undefined,
processStepOrder: undefined,
approveStatus: undefined
};
@ -333,68 +251,60 @@ const form = reactive({ ...initFormData });
//
const rules = reactive({
faultType: [{ required: false, message: '故障类型不能为空', trigger: 'change' }],
faultDescription: [{ required: false, message: '故障描述不能为空', trigger: 'blur' }],
repairType: [{ required: false, message: '维修类型不能为空', trigger: 'change' }],
outsourcingId: [{ required: false, message: '外协单位不能为空', trigger: 'change' }],
repairer: [{ required: false, message: '维修人不能为空', trigger: 'blur' }],
checkedFault: [{ required: false, message: '检查后的故障判断不能为空', trigger: 'blur' }],
protectedMethod: [{ required: false, message: '维修措施不能为空', trigger: 'blur' }],
repairContent: [{ required: false, message: '维修内容不能为空', trigger: 'blur' }],
repairConfirm: [{ required: false, message: '维修结果不能为空', trigger: 'change' }],
processHandleResolution: [{ required: false, message: '处理意见不能为空', trigger: 'blur' }],
approveStatus: [{ required: false, message: '审批状态不能为空', trigger: 'change' }]
approveStatus: [{ required: false, message: '审批状态不能为空', trigger: 'change' }],
processHandleResolution: [{ required: false, message: '处理意见不能为空', trigger: 'blur' }]
});
//
const needProcessResolution = computed(() => currentStepOrder.value > 1);
const currentStepName = computed(() => {
const stepNames = {
1: '故障报修',
2: '维修处理',
3: '维修确认'
};
return stepNames[currentStepOrder.value] || '处理工单';
const needProcessResolution = computed(() => {
return isApprovalStage.value || confirmButtonShow.value;
});
const currentStepTitle = computed(() => {
const stepTitles = {
1: '步骤1故障报修要求参数',
2: '步骤2维修处理实际参数',
3: '步骤3维修确认'
};
return stepTitles[currentStepOrder.value] || '工单处理';
const currentStepName = computed(() => {
if (workOrder.value?.approveStatus === '1') {
return '审批工单';
}
return '处理工单';
});
const approvalButtonShow = computed(() => {
return routeParams.value.type === 'approval' && currentTask.value;
// (1)
return workOrder.value?.approveStatus === '1';
});
const confirmButtonShow = computed(() => {
//
// 1. confirm
// 2. (2)(0)
// 3. (2) -
return routeParams.value.type === 'confirm' ||
(workOrder.value?.billsStatus === '2' &&
workOrder.value?.repairConfirm === '0' &&
workOrder.value?.approveStatus === '2');
//
console.log('主管确认按钮显示条件检查:', {
billsStatus: workOrder.value?.billsStatus,
billsStatusType: typeof workOrder.value?.billsStatus,
approveStatus: workOrder.value?.approveStatus,
approveStatusType: typeof workOrder.value?.approveStatus,
repairConfirm: workOrder.value?.repairConfirm,
repairConfirmType: typeof workOrder.value?.repairConfirm
});
//
// (2) && (2) && (0)
// 使
const billsStatusMatches = String(workOrder.value?.billsStatus) === '2';
const approveStatusMatches = String(workOrder.value?.approveStatus) === '2';
const repairConfirmMatches = String(workOrder.value?.repairConfirm) === '0';
const shouldShow = billsStatusMatches && approveStatusMatches && repairConfirmMatches;
console.log('主管确认按钮显示结果:', {
billsStatusMatches,
approveStatusMatches,
repairConfirmMatches,
shouldShow
});
return shouldShow;
});
//
const isApprovalStage = computed(() => {
return routeParams.value.type === 'approval' && workOrder.value?.approveStatus === '1';
});
//
const submitButtonShow = computed(() => {
return (
routeParams.value.type === 'add' ||
(routeParams.value.type === 'update' &&
workOrder.value?.billsStatus &&
(workOrder.value.billsStatus === 'draft' || workOrder.value.billsStatus === 'cancel' || workOrder.value.billsStatus === 'back'))
);
return workOrder.value?.approveStatus === '1';
});
//
@ -409,11 +319,6 @@ onMounted(async () => {
//
await getOutsourceList();
//
if (routeParams.value.type === 'approval') {
await loadCurrentTask();
}
//
initializeFormData();
@ -422,28 +327,19 @@ onMounted(async () => {
} catch (error) {
console.error('初始化失败:', error);
proxy?.$modal.msgError('加载数据失败');
(proxy as any)?.$modal.msgError('加载数据失败');
} finally {
loading.value = false;
}
});
//
watch(() => form.repairType, (newValue) => {
updateOutsourcingValidation();
//
if (newValue !== '2') {
form.outsourcingId = undefined;
}
});
//
const loadWorkOrderInfo = async () => {
// querybusinessId
const businessId = routeParams.value.id;
if (!businessId) {
proxy?.$modal.msgError('缺少工单ID参数');
(proxy as any)?.$modal.msgError('缺少工单ID参数');
return;
}
@ -454,50 +350,10 @@ const loadWorkOrderInfo = async () => {
workOrderStatusText.value = getStatusText(workOrder.value.billsStatus || '');
};
//
const loadCurrentTask = async () => {
// querybusinessIdtaskId
const businessId = routeParams.value.id;
const taskId = routeParams.value.taskId;
// taskIdbusinessId
const taskRes = await pageByTaskWait({
pageNum: 1,
pageSize: 10,
flowCode: 'Fault01' //
});
const tasks = taskRes.rows.filter(task =>
task.businessId === businessId || task.id === taskId
);
if (tasks.length > 0) {
currentTask.value = tasks[0];
//
const nodeName = currentTask.value.nodeName;
if (nodeName === '故障报修') {
currentStepOrder.value = 1;
} else if (nodeName === '维修处理') {
currentStepOrder.value = 2;
} else if (nodeName === '维修确认') {
currentStepOrder.value = 3;
}
//
if (needProcessResolution.value) {
rules.processHandleResolution = [
{ required: true, message: '处理意见不能为空', trigger: 'blur' }
];
}
}
};
//
const initializeFormData = () => {
//
form.repairInstanceId = workOrder.value?.repairInstanceId;
form.processStepOrder = currentStepOrder.value;
// undefined
form.faultType = workOrder.value?.faultType || '';
@ -511,84 +367,19 @@ const initializeFormData = () => {
form.repairer = workOrder.value?.repairer || '';
form.repairConfirm = workOrder.value?.repairConfirm || '';
form.componentsPartsId = workOrder.value?.componentsPartsId || '';
//
if (form.outsourcingId && outsourceList.value.length > 0) {
const outsourcing = outsourceList.value.find(item => item.outsourcingId === form.outsourcingId);
outsourcingName.value = outsourcing?.outsourcingName || '';
}
//
if (routeParams.value.type === 'approval' && !form.approveStatus) {
form.approveStatus = undefined; //
}
};
//
const submitForm = async (status: string) => {
if (!processFormRef.value) return;
try {
const valid = await processFormRef.value.validate();
if (valid) {
buttonLoading.value = true;
if (status === 'draft') {
//
await addDmsFaultInstanceActivity({
repairInstanceId: form.repairInstanceId,
faultType: form.faultType,
faultDescription: form.faultDescription,
designOperations: form.designOperations,
repairType: form.repairType,
outsourcingId: form.outsourcingId,
checkedFault: form.checkedFault || '',
repairContent: form.repairContent || '',
protectedMethod: form.protectedMethod || '',
repairer: form.repairer || '',
repairConfirm: form.repairConfirm,
componentsPartsId: form.componentsPartsId,
processHandleResolution: form.processHandleResolution || '',
processStepOrder: form.processStepOrder,
handleTime: getCurrentDateTime()
});
proxy?.$modal.msgSuccess('暂存成功');
goBack();
} else {
//
//
taskVariables.value = {
entity: {
repairInstanceId: form.repairInstanceId,
faultType: form.faultType,
faultDescription: form.faultDescription,
designOperations: form.designOperations,
repairType: form.repairType,
outsourcingId: form.outsourcingId,
checkedFault: form.checkedFault || '',
repairContent: form.repairContent || '',
protectedMethod: form.protectedMethod || '',
repairer: form.repairer || '',
repairConfirm: form.repairConfirm,
componentsPartsId: form.componentsPartsId,
processHandleResolution: form.processHandleResolution || '',
processStepOrder: form.processStepOrder
}
};
//
if (routeParams.value.taskId) {
submitVerifyRef.value?.openDialog(routeParams.value.taskId);
} else {
//
proxy?.$modal.msgSuccess('提交成功');
goBack();
}
}
}
} catch (error) {
console.error('提交失败:', error);
proxy?.$modal.msgError('提交失败');
} finally {
buttonLoading.value = false;
}
};
//
const approvalVerifyOpen = async () => {
if (!processFormRef.value) return;
@ -596,26 +387,26 @@ const approvalVerifyOpen = async () => {
try {
const valid = await processFormRef.value.validate();
if (valid) {
await proxy?.$modal.confirm('是否确认提交审批?');
await (proxy as any)?.$modal.confirm('是否确认提交审批?');
buttonLoading.value = true;
//
const approveStatus = form.approveStatus;
const message = form.processHandleResolution || '';
await approveWorkOrder(form.repairInstanceId, approveStatus, message);
const statusText = approveStatus === '2' ? '通过' : '拒绝';
proxy?.$modal.msgSuccess(`审批${statusText}成功`);
(proxy as any)?.$modal.msgSuccess(`审批${statusText}成功`);
//
goBack();
}
} catch (error: any) {
if (error !== 'cancel' && error !== 'close') {
console.error('审批失败:', error);
proxy?.$modal.msgError('审批失败:' + (error.message || '未知错误'));
(proxy as any)?.$modal.msgError('审批失败:' + (error.message || '未知错误'));
}
} finally {
buttonLoading.value = false;
@ -629,50 +420,67 @@ const submitCallback = async (approvalResult?: any) => {
//
const confirmResult = form.repairConfirm || '1'; //
await confirmRepairResult(form.repairInstanceId, confirmResult);
proxy?.$modal.msgSuccess('确认完成');
} else {
//
await addDmsFaultInstanceActivity({
repairInstanceId: form.repairInstanceId,
faultType: form.faultType,
faultDescription: form.faultDescription,
designOperations: form.designOperations,
repairType: form.repairType,
outsourcingId: form.outsourcingId,
checkedFault: form.checkedFault || '',
repairContent: form.repairContent || '',
protectedMethod: form.protectedMethod || '',
repairer: form.repairer || '',
repairConfirm: form.repairConfirm,
componentsPartsId: form.componentsPartsId,
processHandleResolution: form.processHandleResolution || '',
processStepOrder: form.processStepOrder,
handleTime: getCurrentDateTime()
});
proxy?.$modal.msgSuccess(`${currentStepName.value}完成`);
(proxy as any)?.$modal.msgSuccess('确认完成');
}
//
goBack();
} catch (error: any) {
console.error('操作失败:', error);
proxy?.$modal.msgError('操作失败:' + (error.message || '未知错误'));
(proxy as any)?.$modal.msgError('操作失败:' + (error.message || '未知错误'));
}
};
//
const handleConfirm = async () => {
try {
//
// 使
const result = await ElMessageBox.confirm(
'请选择维修结果确认方式:\n• 通过:维修通过,工单完成\n• 退回:维修不通过,返回维修环节重新维修',
'主管确认',
{
confirmButtonText: '通过',
cancelButtonText: '退回',
type: 'warning',
distinguishCancelAndClose: true
}
);
buttonLoading.value = true;
//
form.repairConfirm = '1'; // 1
//
await approvalVerifyOpen();
} catch (error: any) {
if (error !== 'cancel' && error !== 'close') {
console.error('确认失败:', error);
proxy?.$modal.msgError('确认失败:' + (error.message || '未知错误'));
//
await confirmRepairResult(form.repairInstanceId, form.repairConfirm);
(proxy as any)?.$modal.msgSuccess('确认通过完成');
goBack();
} catch (action: any) {
if (action === 'cancel') {
// 退
try {
buttonLoading.value = true;
form.repairConfirm = '2'; // 2
await confirmRepairResult(form.repairInstanceId, form.repairConfirm);
(proxy as any)?.$modal.msgSuccess('退回维修环节完成');
goBack();
} catch (confirmError: any) {
console.error('退回操作失败:', confirmError);
(proxy as any)?.$modal.msgError('退回操作失败:' + (confirmError.message || '未知错误'));
} finally {
buttonLoading.value = false;
}
} else if (action === 'close') {
// ESC
console.log('用户取消了确认操作');
} else {
console.error('确认失败:', action);
(proxy as any)?.$modal.msgError('确认失败:' + (action.message || '未知错误'));
}
} finally {
if (form.repairConfirm === '1') {
buttonLoading.value = false;
}
}
};
@ -684,7 +492,7 @@ const handleApprovalRecord = () => {
//
const goBack = () => {
proxy?.$tab.closePage(proxy?.$route);
(proxy as any)?.$tab.closePage((proxy as any)?.$route);
router.go(-1);
};
@ -698,16 +506,6 @@ const getOutsourceList = async () => {
}
};
//
const updateOutsourcingValidation = () => {
//
if (routeParams.value.type !== 'approval' && form.repairType === '2') {
rules.outsourcingId = [{ required: true, message: '外协单位不能为空', trigger: 'change' }];
} else {
rules.outsourcingId = [{ required: false, message: '外协单位不能为空', trigger: 'change' }];
}
};
//
const setDynamicValidationRules = () => {
//
@ -715,44 +513,17 @@ const setDynamicValidationRules = () => {
rules[key] = [{ required: false, message: rules[key][0].message, trigger: rules[key][0].trigger }];
});
//
if (routeParams.value.type === 'approval') {
//
//
if (workOrder.value?.approveStatus === '1') {
//
rules.approveStatus = [{ required: true, message: '审批状态不能为空', trigger: 'change' }];
if (needProcessResolution.value) {
rules.processHandleResolution = [{ required: true, message: '处理意见不能为空', trigger: 'blur' }];
}
} else if (routeParams.value.type === 'confirm') {
//
rules.repairConfirm = [{ required: true, message: '维修结果不能为空', trigger: 'change' }];
rules.processHandleResolution = [{ required: true, message: '处理意见不能为空', trigger: 'blur' }];
} else {
//
if (currentStepOrder.value === 1) {
//
rules.faultType = [{ required: true, message: '故障类型不能为空', trigger: 'change' }];
rules.faultDescription = [{ required: true, message: '故障描述不能为空', trigger: 'blur' }];
rules.repairType = [{ required: true, message: '维修类型不能为空', trigger: 'change' }];
} else if (currentStepOrder.value === 2) {
//
rules.repairer = [{ required: true, message: '维修人不能为空', trigger: 'blur' }];
rules.checkedFault = [{ required: true, message: '检查后的故障判断不能为空', trigger: 'blur' }];
rules.protectedMethod = [{ required: true, message: '维修措施不能为空', trigger: 'blur' }];
rules.repairContent = [{ required: true, message: '维修内容不能为空', trigger: 'blur' }];
if (needProcessResolution.value) {
rules.processHandleResolution = [{ required: true, message: '处理意见不能为空', trigger: 'blur' }];
}
} else if (currentStepOrder.value === 3) {
//
rules.repairConfirm = [{ required: true, message: '维修结果不能为空', trigger: 'change' }];
if (needProcessResolution.value) {
rules.processHandleResolution = [{ required: true, message: '处理意见不能为空', trigger: 'blur' }];
}
}
} else if (workOrder.value?.billsStatus === '2' &&
workOrder.value?.approveStatus === '2' &&
workOrder.value?.repairConfirm === '0') {
//
rules.processHandleResolution = [{ required: true, message: '处理意见不能为空', trigger: 'blur' }];
}
//
updateOutsourcingValidation();
};
//

@ -0,0 +1,561 @@
# 维修工单业务流程优化文档
## 目录
1. [概述](#概述)
2. [修改内容总结](#修改内容总结)
3. [业务流程梳理](#业务流程梳理)
4. [工作流集成](#工作流集成)
5. [状态流转逻辑](#状态流转逻辑)
6. [外协ID字段映射问题解决](#外协id字段映射问题解决)
7. [Web端功能简化](#web端功能简化)
8. [接口文档](#接口文档)
9. [数据表结构](#数据表结构)
---
## 概述
本次优化基于原有故障维修工单系统完善了审批流程、PDA端状态检查、主管确认等关键业务功能并与Warm-flow工作流框架深度集成实现了完整的维修工单生命周期管理。
### 核心改进
- **完善审批机制**:增加工单审批环节,确保维修申请的合规性
- **状态流转优化**:明确各阶段状态转换规则,防止非法操作
- **工作流集成**与Warm-flow框架深度整合实现标准化流程管控
- **PDA端适配**保持PDA代码不变的前提下完善后端状态检查逻辑
- **外协ID映射修复**解决PDA端outsrcId与后端outsourcingId的字段映射问题
- **Web端功能简化**Web端专注于审批功能避免重复信息录入
- **查询逻辑优化**:修复多条记录查询异常问题
---
## 修改内容总结
### ✅ 已完成功能
1. **完整的审批流程** - Web端审批PDA端状态检查
2. **状态流转控制** - 严格的状态检查和转换逻辑
3. **工作流集成** - 与Warm-flow框架深度整合
4. **主管确认机制** - 维修质量的最终把关
5. **循环维修机制** - 确认不通过自动返回维修环节
6. **质量控制闭环** - 从申请到确认的完整质量管控体系
7. **PDA端适配** - 保持原有PDA代码不变
8. **代码质量保障** - 通过全面审核确保与PDA标准完全一致
9. **外协ID映射修复** - 解决PDA端outsrcId与后端outsourcingId的字段映射问题
10. **Web端功能简化** - Web端专注审批PDA端负责工单创建和维修避免重复录入
11. **查询逻辑优化** - 使用instance_activity_id确保获取最新活动记录
### 🔄 业务流程
PDA创建工单 → Web审批 → PDA维修 → Web确认 → 工单完成
### 📊 技术特点
- **代码复用性高** - 模块化设计,便于扩展
- **状态管理严格** - 防止非法操作和数据不一致
- **工作流标准化** - 符合企业流程管控要求
- **接口设计清晰** - 前后端分离,易于维护
- **PDA标准兼容** - 完全符合原有PDA客户端要求
- **字段映射完善** - 解决不同端字段类型差异问题
- **职责分离明确** - 各端专注自身核心功能,提高效率
---
## 业务流程梳理
### 整体流程图
```
PDA端创建工单 → 工作流启动 → Web端审批 → PDA端维修 → Web端主管确认 → 工单完成
↓ ↓ ↓ ↓ ↓
申请阶段 待审批状态 审批通过 维修完成 确认完成
```
### 各端操作详情
#### PDA端操作
1. **创建工单** (`insertFaultInstsanceActivity`)
- 选择设备
- 填写故障信息(故障类型、描述、涉及操作)
- 选择维修类型(内部维修/外协维修)
- 如果选择外协选择外协单位传递outsrcId
- 上传故障图片
- 提交后自动启动工作流并生成DmsFaultInstanceActivity记录
2. **获取工单** (`getBillsFaultInstance4Repair`)
- 查询待维修工单列表
- 检查审批状态
3. **开始维修** (`startRepair`)
- 检查审批状态(必须为"2"通过)
- 更新工单状态为"1"维修中
- 记录维修开始时间
4. **完成维修** (`completeRepair`)
- 填写维修内容、措施、维修人员
- 上传维修后图片
- 更新工单状态为"2"维修完成
- 设置确认状态为"0"待确认
#### Web端操作
1. **审批处理** (`/dmsFaultInstanceActivity?type=approval`)
- 通过我的待办跳转到审批页面
- 查看PDA端创建的工单详细信息只读
- 使用submitVerify组件完成审批
- **注意**Web端不创建工单不编辑工单信息专注于审批功能
2. **主管确认** (`/confirm`)
- 查看PDA端完成的维修结果
- 确认维修质量
- 选择确认结果:
- **确认通过**:工单完成,流程结束
- **确认不通过**:工单返回维修环节,形成循环
### 业务流程简化原则
1. **职责分离**
- **PDA端**:工单创建、维修执行、信息录入
- **Web端**:审批决策、质量确认
2. **避免重复录入**
- 工单信息只在PDA端录入一次
- Web端只读显示不允许编辑
3. **按创建时间倒序显示**
- 工单实例信息按创建时间倒序排列
- 确保最新的维修记录优先显示
---
## 外协ID字段映射问题解决
### 问题描述
1. **字段类型不匹配**PDA端使用 `outsrcId`int类型后端期望 `outsourcingId`Long类型
2. **数据表结构差异**`dms_bills_fault_instance` 表没有 `outsourcingId` 字段,该字段在 `dms_fault_instance_activity` 表中
3. **查询逻辑问题**:查询最新活动记录时使用了错误的字段排序,导致返回多条记录
### 解决方案
#### 1. 字段转换处理
```java
// 在DmsBillsFaultInstanceServiceImpl.insertByBo()中添加转换逻辑
if (bo.getOutsrcId() != null && bo.getOutsrcId() > 0) {
bo.setOutsourcingId(Long.valueOf(bo.getOutsrcId()));
}
```
#### 2. Bo类字段扩展
```java
// 在DmsBillsFaultInstanceBo中添加字段
/**
* 外协IDPDA端传递的int类型需要转换为outsourcingId
*/
private Integer outsrcId;
```
#### 3. 查询逻辑修复
```sql
-- 修复前:可能返回多条相同 process_step_order 的记录
and dfia.process_step_order = (select max(process_step_order) ...)
-- 修复后:确保返回唯一的最新记录
and dfia.instance_activity_id = (select max(instance_activity_id) ...)
```
#### 4. PDA端兼容性处理
```java
// 在DmsMobileController中处理字段转换
if (voInstance.getOutsourcingId() != null) {
try {
mobileInstance.setOutsrcId(Math.toIntExact(voInstance.getOutsourcingId()));
} catch (ArithmeticException e) {
log.warn("外协单位ID转换失败值过大: {}", voInstance.getOutsourcingId());
mobileInstance.setOutsrcId(0); // 设置为0作为默认值
}
} else {
mobileInstance.setOutsrcId(0); // 当outsourcingId为null时明确设置为0
}
```
---
## Web端功能简化
### 简化原则
1. **移除创建功能**Web端不再提供工单创建功能
2. **移除编辑功能**Web端不再提供工单信息编辑功能
3. **专注审批**Web端只负责审批和主管确认
4. **只读显示**:所有工单信息以只读方式显示
### 具体修改
#### 1. 页面结构简化
- 移除"提交"和"暂存"按钮
- 移除分步骤的表单编辑界面
- 改为统一的只读信息展示
#### 2. 表单验证简化
```javascript
// 只保留审批相关的验证规则
const rules = reactive({
approveStatus: [{ required: false, message: '审批状态不能为空', trigger: 'change' }],
processHandleResolution: [{ required: false, message: '处理意见不能为空', trigger: 'blur' }]
});
```
#### 3. 业务逻辑简化
- 移除工单创建逻辑
- 移除工单编辑逻辑
- 保留审批和确认逻辑
#### 4. 界面展示优化
```vue
<!-- PDA端创建的工单详情只读显示 -->
<el-card shadow="never">
<h4 class="form-header">PDA端创建的工单详情只读</h4>
<!-- 所有字段都设置为disabled -->
</el-card>
```
---
## 工作流集成
### 工作流定义
- **流程代码**`Fault01`
- **节点配置**
1. 故障申请PDA端
2. 工单审批Web端
3. 维修处理PDA端
4. 主管确认Web端
### 状态映射关系
| 工作流状态 | 工单状态 | 审批状态 | 确认状态 | 说明 |
|-----------|---------|---------|---------|------|
| 故障申请 | 0(待维修) | 1(待审批) | null | PDA端创建工单后 |
| 工单审批 | 0(待维修) | 2(审批通过)/3(审批拒绝) | null | Web端审批后 |
| 维修处理 | 1(维修中)/2(维修完成) | 2(审批通过) | 0(待确认) | PDA端维修过程 |
| 主管确认 | 2(维修完成) | 2(审批通过) | 1(确认完成)/2(确认不通过) | Web端确认 |
---
## 状态流转逻辑
### 状态检查规则
1. **开始维修前检查**
- 审批状态必须为"2"(审批通过)
- 工单状态必须为"0"(待维修)
2. **完成维修后状态**
- 工单状态更新为"2"(维修完成)
- 确认状态设置为"0"(待确认)
3. **主管确认规则**
- 确认通过:工单完成,流程结束
- 确认不通过:返回维修环节,形成循环
### 循环维修机制
- 主管确认不通过时,工单状态回到"0"(待维修)
- PDA端可以重新开始维修
- 系统创建新的活动记录,保持历史记录完整性
---
## 接口文档
### PDA端接口
#### 1. 创建故障工单
- **接口**`POST /dms/mobile/insertFaultInstsanceActivity`
- **参数**
```json
{
"deviceId": 20033,
"outsrcId": 1, // PDA端传递的外协ID
"faultType": "1",
"faultDescription": "设备故障描述",
"repairType": "2"
}
```
#### 2. 获取工单详情
- **接口**`GET /dms/mobile/getBillsFaultInstance4Repair/{repairInstanceId}`
- **返回**包含转换后的outsrcId字段
#### 3. 开始维修
- **接口**`POST /dms/mobile/startRepair`
- **状态检查**:审批状态必须为"2"
#### 4. 完成维修
- **接口**`POST /dms/mobile/completeRepair`
- **状态更新**:工单状态→"2",确认状态→"0"
### Web端接口
#### 1. 审批工单
- **接口**`POST /dms/dmsBillsFaultInstance/approve`
- **参数**
```json
{
"repairInstanceId": "工单ID",
"approveStatus": "2", // 2-通过3-拒绝
"message": "审批意见"
}
```
#### 2. 主管确认
- **接口**`POST /dms/dmsBillsFaultInstance/confirm`
- **参数**
```json
{
"repairInstanceId": "工单ID",
"confirmResult": "1", // 1-确认通过2-确认不通过
"confirmUser": "确认人"
}
```
---
## 数据表结构
### 主要表结构
#### 1. dms_bills_fault_instance故障工单主表
- `repair_instance_id`:工单主键
- `bills_status`工单状态0待维修1维修中2维修完成
- `approve_status`审批状态1待审批2审批通过3审批拒绝
- `repair_confirm`确认状态0待确认1确认完成2确认不通过
#### 2. dms_fault_instance_activity故障工单活动表
- `instance_activity_id`活动主键自增ID
- `repair_instance_id`:关联工单主键
- `outsourcing_id`外协单位IDLong类型
- `process_step_order`:流程步骤顺序
### 字段映射关系
| PDA端字段 | 后端字段 | 数据类型转换 | 说明 |
|----------|---------|-------------|------|
| outsrcId | outsourcingId | int → Long | 外协单位ID |
| deviceId | machineId | int → Long | 设备ID |
| repairInstanceId | repairInstanceId | String → Long | 工单ID |
---
## 🚀 后续优化建议
1. 添加工单超时预警机制
2. 完善维修知识库集成
3. 增加维修效率统计分析
4. 支持批量操作功能
5. 前端常量统一管理(避免硬编码)
6. 添加外协单位管理功能
7. 完善维修历史记录查询和统计分析
8. 优化查询性能,添加适当索引
9. 完善异常处理和日志记录
10. 添加数据备份和恢复机制
## 问题背景
在维修工单系统的使用过程中,发现了以下问题:
1. PDA端显示的工单数量与数据库实际记录不符
2. 点击工单时出现SQL查询异常`Expected one result (or null) to be returned by selectOne(), but found: 2`
3. 外协ID字段映射问题PDA端传递`outsrcId`(int),后端期望`outsourcingId`(Long)
4. Web端和PDA端功能重复用户需要在两个端重复录入信息
## 根本原因分析
1. **查询逻辑问题**:使用`MAX(process_step_order)`查询最新记录时,在循环维修场景下会返回多条相同最大值的记录
2. **字段映射不匹配**PDA端使用`outsrcId`(int),后端使用`outsourcingId`(Long)
3. **业务流程设计问题**Web端和PDA端都在创建和编辑工单信息
4. **数据库异常处理**:在某些边界情况下,查询可能返回多条记录导致系统异常
## 解决方案
### 1. 数据库查询优化
**问题**`selectFaultInstanceJoinFirstAndDeviceById`查询可能返回多条记录
**解决方案**
- 修改SQL查询逻辑使用更精确的条件确保返回唯一记录
- 添加备用查询方案,当主查询失败时自动切换
```xml
<!-- 优化后的查询SQL -->
<select id="selectFaultInstanceJoinFirstAndDeviceById" parameterType="Long" resultMap="DmsBillsFaultInstanceResult">
select dbfi.repair_instance_id, dbfi.fault_source_type, dbfi.fault_source_id,
-- 其他字段...
from dms_bills_fault_instance dbfi
left join prod_base_machine_info dbdl on dbfi.machine_id=dbdl.machine_id
left join dms_fault_instance_activity dfia on (dbfi.repair_instance_id=dfia.repair_instance_id
and dfia.instance_activity_id = (
select max(instance_activity_id)
from dms_fault_instance_activity dfia2
where dfia2.repair_instance_id = dbfi.repair_instance_id
and dfia2.process_step_order = (
select max(process_step_order)
from dms_fault_instance_activity dfia3
where dfia3.repair_instance_id = dbfi.repair_instance_id
)
))
where dbfi.repair_instance_id = #{repairInstanceId}
</select>
```
### 2. 防重复机制
**问题**:在网络不稳定或重试场景下,可能重复插入维修记录
**解决方案**
- 在开始维修前检查是否已存在相同步骤的记录
- 添加幂等性保护,避免重复操作
```java
// 防重复检查
MPJLambdaWrapper<DmsFaultInstanceActivity> checkWrapper = JoinWrappers.lambda(DmsFaultInstanceActivity.class)
.eq(DmsFaultInstanceActivity::getRepairInstanceId, repairInstanceId)
.eq(DmsFaultInstanceActivity::getProcessStepOrder, 2L);
Long existingCount = dmsFaultInstanceActivityMapper.selectCount(checkWrapper);
if (existingCount > 0) {
log.warn("工单已经开始维修无需重复操作repairInstanceId: {}", repairInstanceId);
return 1; // 返回成功,避免重复操作
}
```
### 3. 异常处理机制
**问题**:当查询返回多条记录时,系统直接抛出异常
**解决方案**
- 添加异常捕获和备用查询方案
- 提供降级处理机制
```java
try {
// 主查询方案
DmsBillsFaultInstanceVo dmsBillsFaultInstance = baseMapper.selectFaultInstanceJoinFirstAndDeviceById(repairInstanceId);
return dmsBillsFaultInstance;
} catch (Exception e) {
if (e.getMessage() != null && e.getMessage().contains("Expected one result")) {
log.warn("检测到查询返回多条记录异常尝试备用查询方案repairInstanceId: {}", repairInstanceId);
// 备用方案:分别查询工单基本信息和最新活动记录
DmsBillsFaultInstanceVo workOrder = baseMapper.selectVoById(repairInstanceId);
// 查询最新的活动记录并合并数据
// ...
return workOrder;
}
throw new ServiceException("查询工单详情失败:" + e.getMessage());
}
```
### 4. 数据清理建议
如果数据库中已经存在重复或异常数据,建议执行以下清理操作:
```sql
-- 1. 查找重复的活动记录
SELECT repair_instance_id, process_step_order, COUNT(*) as count
FROM dms_fault_instance_activity
GROUP BY repair_instance_id, process_step_order
HAVING COUNT(*) > 1;
-- 2. 保留最新的记录,删除重复记录(谨慎操作,建议先备份)
-- 这里需要根据具体情况制定清理策略
```
### 5. 外协ID字段映射修复
**问题**PDA端传递`outsrcId`(int),后端期望`outsourcingId`(Long)
**解决方案**
1. 在`DmsBillsFaultInstanceBo`中添加`outsrcId`字段用于接收PDA端数据
2. 在`insertByBo`方法中实现字段转换
3. 在返回数据时进行反向转换确保PDA端兼容性
```java
// 在DmsBillsFaultInstanceBo中添加字段
private Integer outsrcId; // PDA端传递的外协ID
// 在insertByBo方法中转换
if (bo.getOutsrcId() != null && bo.getOutsrcId() > 0) {
bo.setOutsourcingId(Long.valueOf(bo.getOutsrcId()));
}
```
### 6. Web端功能简化
**问题**Web端和PDA端功能重复造成用户困惑
**解决方案**
1. **PDA端职责**:工单创建、维修执行、信息录入
2. **Web端职责**:审批决策、质量确认
3. **简化流程**PDA创建工单 → Web审批 → PDA维修 → Web确认 → 工单完成
#### Web端修改内容
- 移除工单创建和编辑功能
- 所有工单信息设为只读显示
- 专注于审批和确认功能
### 7. 业务流程重新设计
#### 优化后的流程:
```
1. PDA端创建工单
├── 录入故障信息
├── 上传故障图片
└── 提交审批
2. Web端审批
├── 查看工单详情(只读)
├── 审批决策(通过/拒绝)
└── 推送通知
3. PDA端维修执行
├── 接收审批通知
├── 开始维修
├── 记录维修过程
├── 更换零部件记录
├── 上传维修图片
└── 完成维修
4. Web端质量确认
├── 查看维修结果(只读)
├── 确认维修质量
└── 工单完成
```
## 技术实现细节
### 字段映射关系
| PDA端字段 | 后端字段 | 数据类型转换 | 说明 |
|----------|---------|-------------|------|
| outsrcId | outsourcingId | int → Long | 外协单位ID |
| deviceId | machineId | int → Long | 设备ID |
### 关键代码修改点
1. **DmsBillsFaultInstanceServiceImpl.java**
- 添加outsrcId到outsourcingId转换逻辑
- 增加防重复检查机制
- 添加异常处理和备用查询方案
2. **DmsBillsFaultInstanceMapper.xml**
- 优化查询逻辑,确保返回唯一记录
3. **Web端Vue页面**
- 简化功能,移除创建编辑逻辑
- 改为只读显示模式
### 监控和日志
- 添加详细的调试日志跟踪outsourcingId的传递过程
- 记录查询异常和备用方案的使用情况
- 监控重复操作的发生频率
## 预期效果
1. ✅ 解决SQL查询返回多条记录的异常
2. ✅ 修复外协ID字段映射问题
3. ✅ 简化Web端功能避免重复录入
4. ✅ 明确各端职责分工
5. ✅ 保持PDA代码完全不变
6. ✅ 提高系统稳定性和用户体验
7. ✅ 增强异常处理能力
8. ✅ 提供数据清理和恢复机制
## 风险控制
1. **数据安全**:所有数据库操作都在事务中执行
2. **向下兼容**保持PDA端接口不变
3. **降级处理**:提供备用查询方案
4. **监控告警**:记录异常情况便于排查
5. **回滚机制**:保留原有逻辑作为备用方案
## 后续优化建议
1. 考虑添加数据库索引优化查询性能
2. 实现自动数据清理机制
3. 添加更完善的监控和告警系统
4. 考虑引入分布式锁防止并发问题
5. 优化工作流程,减少不必要的步骤

@ -142,7 +142,7 @@
<script setup name="DmsKnowledgeLube" lang="ts">
import { listDmsKnowledgeLube, getDmsKnowledgeLube, delDmsKnowledgeLube, addDmsKnowledgeLube, updateDmsKnowledgeLube } from '@/api/dms/dmsKnowledgeLube';
import { DmsKnowledgeLubeVO, DmsKnowledgeLubeQuery, DmsKnowledgeLubeForm } from '@/api/dms/dmsKnowledgeLube/types';
import {getBaseDeviceTypeListInDMS} from "@/api/dms/baseDeviceType";
import {getBaseDeviceTypeList} from "@/api/dms/baseDeviceType";
import {getDmsBaseLubeStationList} from "@/api/dms/dmsBaseLubeStation";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@ -320,7 +320,7 @@ const handleExport = () => {
//
let deviceTypeOptions = ref([]);
const getBaseDeviceTypeOption = async () => {
const res = await getBaseDeviceTypeListInDMS(null);
const res = await getBaseDeviceTypeList(null);
deviceTypeOptions.value = res.data;
}

@ -142,7 +142,7 @@
<script setup name="DmsKnowledgeMaint" lang="ts">
import { listDmsKnowledgeMaint, getDmsKnowledgeMaint, delDmsKnowledgeMaint, addDmsKnowledgeMaint, updateDmsKnowledgeMaint } from '@/api/dms/dmsKnowledgeMaint';
import { DmsKnowledgeMaintVO, DmsKnowledgeMaintQuery, DmsKnowledgeMaintForm } from '@/api/dms/dmsKnowledgeMaint/types';
import {getBaseDeviceTypeListInDMS} from "@/api/dms/baseDeviceType";
import {getBaseDeviceTypeList} from "@/api/dms/baseDeviceType";
import {getDmsBaseMaintStationList} from "@/api/dms/dmsBaseMaintStation";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@ -320,7 +320,7 @@ const handleExport = () => {
//
let deviceTypeOptions = ref([]);
const getBaseDeviceTypeOption = async () => {
const res = await getBaseDeviceTypeListInDMS(null);
const res = await getBaseDeviceTypeList(null);
deviceTypeOptions.value = res.data;
}

@ -133,7 +133,7 @@
<script setup name="DmsKnowledgeRepair" lang="ts">
import { listDmsKnowledgeRepair, getDmsKnowledgeRepair, delDmsKnowledgeRepair, addDmsKnowledgeRepair, updateDmsKnowledgeRepair } from '@/api/dms/dmsKnowledgeRepair';
import { DmsKnowledgeRepairVO, DmsKnowledgeRepairQuery, DmsKnowledgeRepairForm } from '@/api/dms/dmsKnowledgeRepair/types';
import {getBaseDeviceTypeListInDMS} from "@/api/dms/baseDeviceType";
import {getBaseDeviceTypeList} from "@/api/dms/baseDeviceType";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { active_flag } = toRefs<any>(proxy?.useDict('active_flag'));
@ -306,7 +306,7 @@ const handleExport = () => {
//
let deviceTypeOptions = ref([]);
const getBaseDeviceTypeOption = async () => {
const res = await getBaseDeviceTypeListInDMS(null);
const res = await getBaseDeviceTypeList(null);
deviceTypeOptions.value = res.data;
}

@ -58,12 +58,12 @@
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['dms:dmsRecordShutDown:add']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['dms:dmsRecordShutDown:edit']"></el-button>
</el-col>
<!-- <el-col :span="1.5">-->
<!-- <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['dms:dmsRecordShutDown:add']"></el-button>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['dms:dmsRecordShutDown:edit']"></el-button>-->
<!-- </el-col>-->
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['dms:dmsRecordShutDown:remove']"></el-button>
</el-col>
@ -78,8 +78,8 @@
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="主键标识" align="center" prop="recordShutDownId" v-if="columns[0].visible"/>
<el-table-column label="设备" align="center" prop="machineName" v-if="columns[2].visible"/>
<el-table-column label="停机类型" align="center" prop="shutTypeId" v-if="columns[3].visible"/>
<el-table-column label="停机原因" align="center" prop="shutReasonId" v-if="columns[4].visible"/>
<el-table-column label="停机类型" align="center" prop="shutTypeName" v-if="columns[3].visible"/>
<el-table-column label="停机原因" align="center" prop="shutReasonName" v-if="columns[4].visible"/>
<el-table-column label="停机开始时间" align="center" prop="shutBeginTime" width="180" v-if="columns[5].visible">
<template #default="scope">
<span>{{ parseTime(scope.row.shutBeginTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
@ -90,8 +90,17 @@
<span>{{ parseTime(scope.row.shutEndTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="停机时长(毫秒)" align="center" prop="shutTime" v-if="columns[7].visible"/>
<el-table-column label="停机标识" align="center" prop="downtimeFlag" v-if="columns[8].visible"/>
<!-- <el-table-column label="停机时长(毫秒)" align="center" prop="shutTime" v-if="columns[7].visible"/>-->
<el-table-column label="停机时长" align="center" prop="shutTime" v-if="columns[7].visible">
<template #default="scope">
<span>{{ formatDuration(scope.row.shutTime) }}</span>
</template>
</el-table-column>
<el-table-column label="停机标识" align="center" prop="downtimeFlag" v-if="columns[8].visible">
<template #default="scope">
<dict-tag :options="downtime_flag" :value="scope.row.downtimeFlag"/>
</template>
</el-table-column>
<el-table-column label="停机原因" align="center" prop="shutReason" v-if="columns[9].visible"/>
<el-table-column label="激活标识" align="center" prop="activeFlag" v-if="columns[10].visible">
<template #default="scope">
@ -173,7 +182,14 @@
<el-input v-model="form.shutTime" placeholder="请输入停机时长(毫秒)" />
</el-form-item>
<el-form-item label="停机标识" prop="downtimeFlag">
<el-input v-model="form.downtimeFlag" placeholder="请输入停机标识" />
<!-- <el-input v-model="form.downtimeFlag" placeholder="请输入停机标识" />-->
<el-radio-group v-model="form.downtimeFlag">
<el-radio
v-for="dict in downtime_flag"
:key="dict.value"
:value="dict.value"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="激活标识" prop="activeFlag">
<el-radio-group v-model="form.activeFlag">
@ -206,7 +222,7 @@ import {getDmsBaseShutTypeList} from "@/api/dms/dmsBaseShutType";
import {getDmsBaseShutReasonList} from "@/api/dms/dmsBaseShutReason";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { active_flag } = toRefs<any>(proxy?.useDict('active_flag'));
const { active_flag, downtime_flag } = toRefs<any>(proxy?.useDict('active_flag', 'downtime_flag'));
const dmsRecordShutDownList = ref<DmsRecordShutDownVO[]>([]);
const buttonLoading = ref(false);
@ -231,7 +247,7 @@ const columns = ref<FieldOption[]>([
{ key: 1, label: `租户编号`, visible: false },
{ key: 2, label: `设备`, visible: true },
{ key: 3, label: `停机类型`, visible: true },
{ key: 4, label: `停机原因id`, visible: false },
{ key: 4, label: `停机原因`, visible: true },
{ key: 5, label: `停机开始时间`, visible: true },
{ key: 6, label: `停机结束时间`, visible: true },
{ key: 7, label: `停机时长(毫秒)`, visible: true },
@ -260,6 +276,7 @@ const initFormData: DmsRecordShutDownForm = {
remark: undefined,
machineName: undefined,
shutTypeName: undefined,
shutReasonNameName: undefined,
}
const data = reactive<PageData<DmsRecordShutDownForm, DmsRecordShutDownQuery>>({
@ -279,6 +296,7 @@ const data = reactive<PageData<DmsRecordShutDownForm, DmsRecordShutDownQuery>>({
activeFlag: undefined,
machineName: undefined,
shutTypeName: undefined,
shutReasonNameName: undefined,
params: {
}
},
@ -413,6 +431,14 @@ const handleShutReasonChange = (shutReasonId: number) => {
form.value.shutTypeId = selectedReason?.shutTypeId || ''; //
};
const formatDuration = (millis: number): string => {
if (!millis || millis <= 0) return '0秒'
const hours = Math.floor(millis / 3600000)
const minutes = Math.floor((millis % 3600000) / 60000)
const seconds = Math.floor((millis % 60000) / 1000)
return `${hours ? hours + '小时' : ''}${minutes ? minutes + '分' : ''}${seconds}`
}
onMounted(() => {
getMachineOtions();
getShutTypeOtions();

@ -91,12 +91,12 @@
</el-button>
</el-button-group>
</div>
<div class="toolbar-right">
<el-button type="primary" @click="exportData">
<el-icon><Download /></el-icon>
导出数据
</el-button>
</div>
<!-- <div class="toolbar-right">-->
<!-- <el-button type="primary" @click="exportData">-->
<!-- <el-icon><Download /></el-icon>-->
<!-- 导出数据-->
<!-- </el-button>-->
<!-- </div>-->
</div>
<!-- 查询过滤器 -->
@ -385,15 +385,15 @@
<el-col :span="6">
<el-statistic title="计划保养次数" :value="maintenanceStats.planned" />
</el-col>
<el-col :span="6">
<el-statistic title="临时保养次数" :value="maintenanceStats.temporary" />
</el-col>
<el-col :span="6">
<el-statistic title="平均保养间隔" :value="maintenanceStats.avgInterval" suffix="天" />
</el-col>
<el-col :span="6">
<el-statistic title="下次保养时间" :value="maintenanceStats.nextDate || '未计划'" />
</el-col>
<!-- <el-col :span="6">-->
<!-- <el-statistic title="临时保养次数" :value="maintenanceStats.temporary" />-->
<!-- </el-col>-->
<!-- <el-col :span="6">-->
<!-- <el-statistic title="平均保养间隔" :value="maintenanceStats.avgInterval" suffix="天" />-->
<!-- </el-col>-->
<!-- <el-col :span="6">-->
<!-- <el-statistic title="下次保养时间" :value="maintenanceStats.nextDate || '未计划'" />-->
<!-- </el-col>-->
</el-row>
</el-card>
</el-col>
@ -408,13 +408,13 @@
<el-table-column prop="maintDate" label="保养日期" />
<el-table-column prop="maintPerson" label="保养人员" />
<el-table-column prop="maintContent" label="保养内容" show-overflow-tooltip />
<el-table-column prop="maintResult" label="保养结果">
<template #default="scope">
<el-tag :type="scope.row.maintResult === '合格' ? 'success' : 'warning'">
{{ scope.row.maintResult }}
</el-tag>
</template>
</el-table-column>
<!-- <el-table-column prop="maintResult" label="保养结果">-->
<!-- <template #default="scope">-->
<!-- <el-tag :type="scope.row.maintResult === '合格' ? 'success' : 'warning'">-->
<!-- {{ scope.row.maintResult }}-->
<!-- </el-tag>-->
<!-- </template>-->
<!-- </el-table-column>-->
</el-table>
</el-card>
</el-col>
@ -426,10 +426,10 @@
</div>
</template>
<script setup name="ProdBaseMachineInfo">
<script setup name="ProdBaseMachineInfo" lang="ts">
import { ref, reactive, toRefs, getCurrentInstance, onMounted, onUnmounted, shallowRef, markRaw, watch } from 'vue';
import { listDmsBaseMachineInfoIn, getDmsBaseMachineInfoList, getDmsBaseMachineInfo } from '@/api/dms/dmsBaseMachineInfo';
import { listBaseDeviceTypeInDMS, getBaseDeviceTypeListInDMS } from '@/api/dms/baseDeviceType';
import { listDmsBaseMachineInfo, getDmsBaseMachineInfoList, getDmsBaseMachineInfo } from '@/api/dms/dmsBaseMachineInfo';
import { listBaseDeviceType, getBaseDeviceTypeList } from '@/api/dms/baseDeviceType';
import { listDmsBaseDevicePurchase, getDmsBaseDevicePurchaseList } from '@/api/dms/dmsBaseDevicePurchase';
import { listDmsBaseDeviceInstall, getDmsBaseDeviceInstallList } from '@/api/dms/dmsBaseDeviceInstall';
import { listDmsBaseDeviceDebugging, getDmsBaseDeviceDebuggingList } from '@/api/dms/dmsBaseDeviceDebugging';
@ -459,8 +459,8 @@ import {
const { proxy } = getCurrentInstance();
// TODO:
const { machine_status, debug_status, fault_status, maint_level, maint_status } = toRefs(proxy.useDict(
'machine_status', 'debug_status', 'fault_status', 'maint_level', 'maint_status'
const { machine_status, debug_status, fault_status, maint_level, maint_status, handle_status, bills_status, active_flag } = toRefs(proxy.useDict(
'machine_status', 'debug_status', 'fault_status', 'maint_level', 'maint_status', 'handle_status', 'bills_status', 'active_flag'
));
// TODO:
// const { active_flag } = toRefs<any>(proxy?.useDict('active_flag'));
@ -489,7 +489,7 @@ const deviceTypes = ref([]);
//
const queryParams = ref({
pageNum: 1,
pageSize: 12,
pageSize: 10,
machineCode: undefined,
machineName: undefined,
machineLocation: undefined,
@ -618,7 +618,7 @@ const updateCurrentTime = () => {
const getList = async () => {
loading.value = true;
try {
const res = await listDmsBaseMachineInfoIn(queryParams.value);
const res = await listDmsBaseMachineInfo(queryParams.value);
machineList.value = res.rows || [];
total.value = res.total || 0;
@ -635,7 +635,7 @@ const getList = async () => {
//
const getDeviceTypes = async () => {
try {
const res = await getBaseDeviceTypeListInDMS({ pageNum: 1, pageSize: 100 });
const res = await getBaseDeviceTypeList({ pageNum: 1, pageSize: 100 });
deviceTypes.value = res.data;
} catch (error) {
console.error('获取设备类型失败:', error);
@ -1109,10 +1109,10 @@ const viewDetails = async (machine) => {
]);
};
//
const exportData = () => {
proxy.$modal.msgInfo('导出功能开发中...');
};
// //
// const exportData = () => {
// proxy.$modal.msgInfo('...');
// };
//
let timer = null;
@ -1227,14 +1227,14 @@ const loadMaintenanceStats = async (machineId) => {
//
const loadLifecycleTypes = async () => {
if (!selectedMachine.value) return;
const machineId = selectedMachine.value.machineId;
try {
// 使API
const [
purchaseRes,
installRes,
installRes,
debugRes,
maintRes,
faultRes,
@ -1249,7 +1249,7 @@ const loadLifecycleTypes = async () => {
getDmsBillsInspectInstanceList({ machineId }).catch(() => ({ data: [] })),
getBaseAlarmInfoList({ machineId }).catch(() => ({ data: [] }))
]);
//
lifecycleTypes.value.forEach(type => {
switch (type.value) {
@ -1273,7 +1273,7 @@ const loadLifecycleTypes = async () => {
break;
}
});
//
lifecycleStats.value = {
faultCount: faultRes.data?.length || 0,
@ -1302,7 +1302,7 @@ const loadLifecycleTypes = async () => {
width: 100%;
height: 60px;
z-index: 0;
svg {
width: 100%;
height: 100%;
@ -1320,7 +1320,7 @@ const loadLifecycleTypes = async () => {
border-radius: 6px;
margin-bottom: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.panel-left {
.page-title {
font-size: 18px;
@ -1331,14 +1331,14 @@ const loadLifecycleTypes = async () => {
align-items: center;
gap: 6px;
}
.page-subtitle {
font-size: 11px;
color: #7f8c8d;
margin: 1px 0 0 0;
}
}
.panel-right {
.current-time {
display: flex;
@ -1356,7 +1356,7 @@ const loadLifecycleTypes = async () => {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 8px;
margin-bottom: 8px;
.stat-item {
position: relative;
background: #fff;
@ -1365,12 +1365,12 @@ const loadLifecycleTypes = async () => {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
overflow: hidden;
&:hover {
transform: translateY(-1px);
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.15);
}
.stat-bg-effect {
position: absolute;
top: 0;
@ -1381,7 +1381,7 @@ const loadLifecycleTypes = async () => {
opacity: 0.1;
transform: translate(25px, -25px);
}
.stat-inner {
position: relative;
z-index: 2;
@ -1389,7 +1389,7 @@ const loadLifecycleTypes = async () => {
align-items: center;
gap: 10px;
}
.stat-icon-wrapper {
display: flex;
align-items: center;
@ -1399,35 +1399,35 @@ const loadLifecycleTypes = async () => {
border-radius: 6px;
color: #fff;
}
.stat-info {
flex: 1;
.stat-number {
display: flex;
align-items: baseline;
gap: 3px;
.value-animate {
font-size: 20px;
font-weight: 700;
color: #2c3e50;
}
.unit {
font-size: 11px;
color: #7f8c8d;
font-weight: 500;
}
}
.stat-title {
font-size: 12px;
color: #7f8c8d;
margin-top: 1px;
}
}
.stat-chart {
position: absolute;
bottom: 6px;
@ -1435,28 +1435,28 @@ const loadLifecycleTypes = async () => {
width: 50px;
height: 20px;
opacity: 0.3;
svg {
width: 100%;
height: 100%;
}
}
&.stat-total {
.stat-icon-wrapper { background: linear-gradient(45deg, #667eea, #764ba2); }
.stat-bg-effect { background: #667eea; }
}
&.stat-running {
.stat-icon-wrapper { background: linear-gradient(45deg, #56ab2f, #a8e6cf); }
.stat-bg-effect { background: #56ab2f; }
}
&.stat-maintenance {
.stat-icon-wrapper { background: linear-gradient(45deg, #f093fb, #f5576c); }
.stat-bg-effect { background: #f093fb; }
}
&.stat-alarm {
.stat-icon-wrapper { background: linear-gradient(45deg, #fa709a, #fee140); }
.stat-bg-effect { background: #fa709a; }
@ -1469,7 +1469,7 @@ const loadLifecycleTypes = async () => {
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
.toolbar-left, .toolbar-right {
display: flex;
gap: 6px;
@ -1478,12 +1478,12 @@ const loadLifecycleTypes = async () => {
.search-filter-card {
margin-bottom: 8px;
.filter-header {
display: flex;
justify-content: space-between;
align-items: center;
.filter-title {
display: flex;
align-items: center;
@ -1491,11 +1491,11 @@ const loadLifecycleTypes = async () => {
font-weight: 600;
}
}
:deep(.el-card__body) {
padding: 12px;
}
.el-form {
.el-form-item {
margin-bottom: 8px;
@ -1510,10 +1510,10 @@ const loadLifecycleTypes = async () => {
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.machine-card-wrapper {
cursor: pointer;
.machine-card {
background: #fff;
border-radius: 4px;
@ -1521,22 +1521,22 @@ const loadLifecycleTypes = async () => {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
position: relative;
&:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
&.selected {
border: 2px solid #409EFF;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
}
.status-indicator {
position: absolute;
top: 6px;
right: 6px;
.status-dot {
display: inline-block;
width: 5px;
@ -1544,17 +1544,17 @@ const loadLifecycleTypes = async () => {
border-radius: 50%;
animation: pulse 2s infinite;
}
&.status-0 .status-dot { background: #f56c6c; }
&.status-1 .status-dot { background: #67c23a; }
&.status-2 .status-dot { background: #e6a23c; }
&.status-3 .status-dot { background: #909399; }
}
.machine-visual {
text-align: center;
margin-bottom: 6px;
.machine-icon-bg {
display: inline-flex;
align-items: center;
@ -1566,17 +1566,17 @@ const loadLifecycleTypes = async () => {
color: #fff;
margin-bottom: 4px;
}
.machine-number {
font-size: 11px;
font-weight: 600;
color: #2c3e50;
}
}
.machine-details {
margin-bottom: 6px;
.machine-name {
font-size: 13px;
font-weight: 600;
@ -1588,7 +1588,7 @@ const loadLifecycleTypes = async () => {
text-overflow: ellipsis;
white-space: nowrap;
}
.machine-meta {
.meta-item {
display: flex;
@ -1600,19 +1600,19 @@ const loadLifecycleTypes = async () => {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:last-child {
margin-bottom: 0;
}
}
}
}
.quick-actions {
display: flex;
gap: 4px;
justify-content: center;
.el-button {
padding: 2px 6px;
font-size: 10px;
@ -1622,35 +1622,35 @@ const loadLifecycleTypes = async () => {
}
}
}
.list-view {
.status-cell {
display: flex;
align-items: center;
gap: 4px;
.status-badge {
display: inline-block;
width: 5px;
height: 5px;
border-radius: 50%;
&.status-0 { background: #f56c6c; }
&.status-1 { background: #67c23a; }
&.status-2 { background: #e6a23c; }
&.status-3 { background: #909399; }
}
}
.date-text {
font-size: 12px;
color: #606266;
}
:deep(.danger-row) {
background-color: rgba(245, 108, 108, 0.05);
}
:deep(.warning-row) {
background-color: rgba(230, 162, 60, 0.05);
}
@ -1670,12 +1670,12 @@ const loadLifecycleTypes = async () => {
padding: 12px;
margin-bottom: 12px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
.overview-header {
display: flex;
align-items: center;
gap: 10px;
.device-avatar {
display: flex;
align-items: center;
@ -1686,14 +1686,14 @@ const loadLifecycleTypes = async () => {
border-radius: 10px;
color: #fff;
}
.device-basic {
h2 {
margin: 0 0 3px 0;
font-size: 16px;
color: #2c3e50;
}
p {
margin: 0;
font-size: 12px;
@ -1701,22 +1701,22 @@ const loadLifecycleTypes = async () => {
}
}
}
.overview-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
gap: 10px;
.stat {
text-align: center;
.stat-label {
display: block;
font-size: 11px;
color: #7f8c8d;
margin-bottom: 3px;
}
.stat-value {
display: block;
font-size: 18px;
@ -1726,21 +1726,21 @@ const loadLifecycleTypes = async () => {
}
}
}
.lifecycle-type-selector {
margin-bottom: 12px;
h3 {
margin: 0 0 10px 0;
font-size: 15px;
color: #2c3e50;
}
.type-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 6px;
.type-card {
background: #fff;
border: 2px solid #e1e6f0;
@ -1750,16 +1750,16 @@ const loadLifecycleTypes = async () => {
cursor: pointer;
transition: all 0.3s ease;
position: relative;
&:hover {
border-color: #409EFF;
}
&.active {
border-color: #409EFF;
background: #f0f9ff;
}
.type-icon {
display: inline-flex;
align-items: center;
@ -1770,14 +1770,14 @@ const loadLifecycleTypes = async () => {
margin-bottom: 5px;
color: #fff;
}
.type-name {
display: block;
font-size: 11px;
color: #2c3e50;
font-weight: 500;
}
.type-count {
position: absolute;
top: -3px;
@ -1793,28 +1793,28 @@ const loadLifecycleTypes = async () => {
}
}
}
.lifecycle-timeline-container {
h3 {
margin: 0 0 10px 0;
font-size: 15px;
color: #2c3e50;
}
.timeline-wrapper {
position: relative;
.timeline-item {
position: relative;
padding-left: 35px;
padding-bottom: 16px;
&:last-child {
.timeline-line {
display: none;
}
}
.timeline-marker {
position: absolute;
left: 0;
@ -1828,47 +1828,47 @@ const loadLifecycleTypes = async () => {
color: #fff;
z-index: 2;
}
.timeline-content {
background: #fff;
border-radius: 6px;
padding: 10px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
.event-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
h4 {
margin: 0;
font-size: 13px;
color: #2c3e50;
}
.event-time {
font-size: 11px;
color: #7f8c8d;
}
}
.event-description {
font-size: 12px;
color: #606266;
margin: 0 0 6px 0;
}
.event-tags {
margin-bottom: 6px;
.el-tag {
margin-right: 3px;
margin-bottom: 3px;
}
}
}
.timeline-line {
position: absolute;
left: 13px;
@ -1888,7 +1888,7 @@ const loadLifecycleTypes = async () => {
font-weight: 600;
color: #2c3e50;
}
.maintenance-section {
.maintenance-summary {
margin-bottom: 12px;
@ -1899,7 +1899,7 @@ const loadLifecycleTypes = async () => {
.filter-btn-primary {
background: linear-gradient(45deg, #667eea, #764ba2);
border: none;
&:hover {
background: linear-gradient(45deg, #5a6fd8, #6a4190);
}
@ -1910,12 +1910,12 @@ const loadLifecycleTypes = async () => {
transform: scale(0.95);
box-shadow: 0 0 0 0 currentColor;
}
70% {
transform: scale(1);
box-shadow: 0 0 0 10px transparent;
}
100% {
transform: scale(0.95);
box-shadow: 0 0 0 0 transparent;

Loading…
Cancel
Save