feat(dms): 优化故障实例活动页面功能

- 新增审批记录组件,支持查看流程图和审批信息
- 重构表单验证逻辑,提高用户体验
-优化工作流变量处理,简化审批流程
- 调整表单字段初始化,确保数据准确性
master
zangchenhao 3 weeks ago
parent 3ccdf499ed
commit a7ae94db56

@ -0,0 +1,280 @@
<template>
<div class="container">
<el-dialog v-model="visible" draggable title="审批记录" :width="props.width" :height="props.height" :close-on-click-modal="false">
<el-tabs v-model="tabActiveName" class="demo-tabs">
<el-tab-pane v-loading="loading" label="流程图" name="image" style="height: 68vh">
<div
ref="imageWrapperRef"
class="image-wrapper"
@wheel="handleMouseWheel"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="handleMouseUp"
@mouseleave="handleMouseLeave"
@dblclick="resetTransform"
:style="transformStyle"
>
<el-card class="box-card">
<el-image :src="imgUrl" class="scalable-image" />
</el-card>
</div>
</el-tab-pane>
<el-tab-pane v-loading="loading" label="审批信息" name="info">
<div>
<el-table :data="historyList" style="width: 100%" border fit>
<el-table-column type="index" label="序号" align="center" width="60"></el-table-column>
<el-table-column prop="nodeName" label="任务名称" sortable align="center"></el-table-column>
<el-table-column prop="approveName" :show-overflow-tooltip="true" label="办理人" sortable align="center">
<template #default="scope">
<template v-if="scope.row.approveName">
<el-tag v-for="(item, index) in scope.row.approveName.split(',')" :key="index" type="success">{{ item }}</el-tag>
</template>
<template v-else> <el-tag type="success"></el-tag></template>
</template>
</el-table-column>
<el-table-column prop="flowStatus" label="状态" width="80" sortable align="center">
<template #default="scope">
<dict-tag :options="wf_task_status" :value="scope.row.flowStatus"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="message" label="审批意见" :show-overflow-tooltip="true" sortable align="center"></el-table-column>
<el-table-column prop="createTime" label="开始时间" width="160" :show-overflow-tooltip="true" sortable align="center"></el-table-column>
<el-table-column prop="updateTime" label="结束时间" width="160" :show-overflow-tooltip="true" sortable align="center"></el-table-column>
<el-table-column
prop="runDuration"
label="运行时长"
width="140"
:show-overflow-tooltip="true"
sortable
align="center"
></el-table-column>
<el-table-column prop="attachmentList" width="120" label="附件" align="center">
<template #default="scope">
<el-popover v-if="scope.row.attachmentList && scope.row.attachmentList.length > 0" placement="right" :width="310" trigger="click">
<template #reference>
<el-button type="primary" style="margin-right: 16px">附件</el-button>
</template>
<el-table border :data="scope.row.attachmentList">
<el-table-column prop="originalName" width="202" :show-overflow-tooltip="true" label="附件名称"></el-table-column>
<el-table-column prop="name" width="80" align="center" :show-overflow-tooltip="true" label="操作">
<template #default="tool">
<el-button type="text" @click="handleDownload(tool.row.ossId)"></el-button>
</template>
</el-table-column>
</el-table>
</el-popover>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
</el-tabs>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { flowImage } from '@/api/workflow/instance';
import { propTypes } from '@/utils/propTypes';
import { listByIds } from '@/api/system/oss';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_task_status } = toRefs<any>(proxy?.useDict('wf_task_status'));
const props = defineProps({
width: propTypes.string.def('80%'),
height: propTypes.string.def('100%')
});
const loading = ref(false);
const visible = ref(false);
const historyList = ref<Array<any>>([]);
const tabActiveName = ref('image');
const imgUrl = ref('');
//
const init = async (businessId: string | number) => {
visible.value = true;
loading.value = true;
tabActiveName.value = 'image';
historyList.value = [];
flowImage(businessId).then((resp) => {
if (resp.data) {
historyList.value = resp.data.list;
imgUrl.value = 'data:image/gif;base64,' + resp.data.image;
if (historyList.value.length > 0) {
historyList.value.forEach((item) => {
if (item.ext) {
getIds(item.ext).then((res) => {
item.attachmentList = res.data;
});
} else {
item.attachmentList = [];
}
});
}
loading.value = false;
}
});
};
const getIds = async (ids: string | number) => {
const res = await listByIds(ids);
return res;
};
/** 下载按钮操作 */
const handleDownload = (ossId: string) => {
proxy?.$download.oss(ossId);
};
const imageWrapperRef = ref<HTMLElement | null>(null);
const scale = ref(1); //
const maxScale = 3; //
const minScale = 0.5; //
let isDragging = false;
let startX = 0;
let startY = 0;
let currentTranslateX = 0;
let currentTranslateY = 0;
const handleMouseWheel = (event: WheelEvent) => {
event.preventDefault();
let newScale = scale.value - event.deltaY / 1000;
newScale = Math.max(minScale, Math.min(newScale, maxScale));
if (newScale !== scale.value) {
scale.value = newScale;
resetDragPosition(); // 使
}
};
const handleMouseDown = (event: MouseEvent) => {
if (scale.value > 1) {
event.preventDefault(); //
isDragging = true;
startX = event.clientX;
startY = event.clientY;
}
};
const handleMouseMove = (event: MouseEvent) => {
if (!isDragging || !imageWrapperRef.value) return;
const deltaX = event.clientX - startX;
const deltaY = event.clientY - startY;
startX = event.clientX;
startY = event.clientY;
currentTranslateX += deltaX;
currentTranslateY += deltaY;
//
const bounds = getBounds();
if (currentTranslateX > bounds.maxTranslateX) {
currentTranslateX = bounds.maxTranslateX;
} else if (currentTranslateX < bounds.minTranslateX) {
currentTranslateX = bounds.minTranslateX;
}
if (currentTranslateY > bounds.maxTranslateY) {
currentTranslateY = bounds.maxTranslateY;
} else if (currentTranslateY < bounds.minTranslateY) {
currentTranslateY = bounds.minTranslateY;
}
applyTransform();
};
const handleMouseUp = () => {
isDragging = false;
};
const handleMouseLeave = () => {
isDragging = false;
};
const resetTransform = () => {
scale.value = 1;
currentTranslateX = 0;
currentTranslateY = 0;
applyTransform();
};
const resetDragPosition = () => {
currentTranslateX = 0;
currentTranslateY = 0;
applyTransform();
};
const applyTransform = () => {
if (imageWrapperRef.value) {
imageWrapperRef.value.style.transform = `translate(${currentTranslateX}px, ${currentTranslateY}px) scale(${scale.value})`;
}
};
const getBounds = () => {
if (!imageWrapperRef.value) return { minTranslateX: 0, maxTranslateX: 0, minTranslateY: 0, maxTranslateY: 0 };
const imgRect = imageWrapperRef.value.getBoundingClientRect();
const containerRect = imageWrapperRef.value.parentElement?.getBoundingClientRect() ?? imgRect;
const minTranslateX = (containerRect.width - imgRect.width * scale.value) / 2;
const maxTranslateX = -(containerRect.width - imgRect.width * scale.value) / 2;
const minTranslateY = (containerRect.height - imgRect.height * scale.value) / 2;
const maxTranslateY = -(containerRect.height - imgRect.height * scale.value) / 2;
return { minTranslateX, maxTranslateX, minTranslateY, maxTranslateY };
};
const transformStyle = computed(() => ({
transition: isDragging ? 'none' : 'transform 0.2s ease'
}));
/**
* 对外暴露子组件方法
*/
defineExpose({
init
});
</script>
<style lang="scss" scoped>
.triangle {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
border-radius: 6px;
}
.triangle::after {
content: ' ';
position: absolute;
top: 8em;
right: 215px;
border: 15px solid;
border-color: transparent #fff transparent transparent;
}
.container {
:deep(.el-dialog .el-dialog__body) {
max-height: calc(100vh - 170px) !important;
min-height: calc(100vh - 170px) !important;
}
}
.image-wrapper {
width: 100%;
overflow: hidden;
position: relative;
margin: 0 auto;
display: flex;
justify-content: center;
align-items: center;
user-select: none; /* 禁用文本选择 */
cursor: grab; /* 设置初始鼠标指针为可拖动 */
}
.image-wrapper:active {
cursor: grabbing; /* 当正在拖动时改变鼠标指针 */
}
.scalable-image {
object-fit: contain;
width: 100%;
padding: 15px;
}
</style>

@ -52,7 +52,7 @@
<el-row>
<el-col :span="8">
<el-form-item label="工单编号">
<el-input v-model="workOrder.billsFaultCode" disabled />
<el-input :value="workOrder?.billsFaultCode || ''" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
@ -62,24 +62,24 @@
</el-col>
<el-col :span="8">
<el-form-item label="申请人">
<el-input v-model="workOrder.applyUser" disabled />
<el-input :value="workOrder?.applyUser || ''" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="设备编号">
<el-input v-model="workOrder.machineCode" disabled />
<el-input :value="workOrder?.machineCode || ''" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="设备名称">
<el-input v-model="workOrder.machineName" disabled />
<el-input :value="workOrder?.machineName || ''" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="申请时间">
<el-input v-model="workOrder.applyTime" disabled />
<el-input :value="workOrder?.applyTime || ''" disabled />
</el-form-item>
</el-col>
</el-row>
@ -333,9 +333,9 @@ const form = reactive({ ...initFormData });
//
const rules = reactive({
faultType: [{ required: true, message: '故障类型不能为空', trigger: 'change' }],
faultDescription: [{ required: true, message: '故障描述不能为空', trigger: 'blur' }],
repairType: [{ required: true, message: '维修类型不能为空', trigger: 'change' }],
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' }],
@ -377,14 +377,14 @@ const confirmButtonShow = computed(() => {
// 2. (2)(0)
// 3. (2) -
return routeParams.value.type === 'confirm' ||
(workOrder.value.billsStatus === '2' &&
workOrder.value.repairConfirm === '0' &&
workOrder.value.approveStatus === '2');
(workOrder.value?.billsStatus === '2' &&
workOrder.value?.repairConfirm === '0' &&
workOrder.value?.approveStatus === '2');
});
//
const isApprovalStage = computed(() => {
return routeParams.value.type === 'approval' && workOrder.value.approveStatus === '1';
return routeParams.value.type === 'approval' && workOrder.value?.approveStatus === '1';
});
//
@ -392,7 +392,7 @@ const submitButtonShow = computed(() => {
return (
routeParams.value.type === 'add' ||
(routeParams.value.type === 'update' &&
workOrder.value.billsStatus &&
workOrder.value?.billsStatus &&
(workOrder.value.billsStatus === 'draft' || workOrder.value.billsStatus === 'cancel' || workOrder.value.billsStatus === 'back'))
);
});
@ -417,13 +417,8 @@ onMounted(async () => {
//
initializeFormData();
//
if (routeParams.value.type === 'approval' && workOrder.value.approveStatus === '1') {
rules.approveStatus = [{ required: true, message: '审批状态不能为空', trigger: 'change' }];
}
//
updateOutsourcingValidation();
//
setDynamicValidationRules();
} catch (error) {
console.error('初始化失败:', error);
@ -453,10 +448,10 @@ const loadWorkOrderInfo = async () => {
}
const res = await getDmsBillsFaultInstance(businessId);
workOrder.value = res.data;
workOrder.value = res.data || {}; // null
//
workOrderStatusText.value = getStatusText(workOrder.value.billsStatus);
// -
workOrderStatusText.value = getStatusText(workOrder.value.billsStatus || '');
};
//
@ -501,29 +496,25 @@ const loadCurrentTask = async () => {
//
const initializeFormData = () => {
//
form.repairInstanceId = workOrder.value.repairInstanceId;
form.repairInstanceId = workOrder.value?.repairInstanceId;
form.processStepOrder = currentStepOrder.value;
if (currentStepOrder.value === 1) {
//
form.faultType = workOrder.value.faultType;
form.faultDescription = workOrder.value.faultDescription;
form.designOperations = workOrder.value.designOperations;
form.repairType = workOrder.value.repairType;
form.outsourcingId = workOrder.value.outsourcingId;
} else {
//
form.faultType = workOrder.value.faultType;
form.faultDescription = workOrder.value.faultDescription;
form.designOperations = workOrder.value.designOperations;
form.repairType = workOrder.value.repairType;
form.outsourcingId = workOrder.value.outsourcingId;
form.checkedFault = workOrder.value.checkedFault;
form.repairContent = workOrder.value.repairContent;
form.protectedMethod = workOrder.value.protectedMethod;
form.repairer = workOrder.value.repairer;
form.repairConfirm = workOrder.value.repairConfirm;
form.componentsPartsId = workOrder.value.componentsPartsId;
// undefined
form.faultType = workOrder.value?.faultType || '';
form.faultDescription = workOrder.value?.faultDescription || '';
form.designOperations = workOrder.value?.designOperations || '';
form.repairType = workOrder.value?.repairType || '';
form.outsourcingId = workOrder.value?.outsourcingId || '';
form.checkedFault = workOrder.value?.checkedFault || '';
form.repairContent = workOrder.value?.repairContent || '';
form.protectedMethod = workOrder.value?.protectedMethod || '';
form.repairer = workOrder.value?.repairer || '';
form.repairConfirm = workOrder.value?.repairConfirm || '';
form.componentsPartsId = workOrder.value?.componentsPartsId || '';
//
if (routeParams.value.type === 'approval' && !form.approveStatus) {
form.approveStatus = undefined; //
}
};
@ -605,76 +596,36 @@ const approvalVerifyOpen = async () => {
try {
const valid = await processFormRef.value.validate();
if (valid) {
//
if (routeParams.value.type === 'approval' && !form.approveStatus) {
proxy?.$modal.msgError('请选择审批状态');
return;
}
await proxy?.$modal.confirm('是否确认提交审批?');
buttonLoading.value = true;
//
const approveStatus = form.approveStatus;
const message = form.processHandleResolution || '';
// - Warm-FlowsubmitVerify
taskVariables.value = {
// -
variables: {},
//
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.type === 'approval' && form.approveStatus) {
// approveStatusvariablesWarm-Flow使
taskVariables.value.variables.approveStatus = parseInt(form.approveStatus);
// submitVerify
taskVariables.value.approveStatus = form.approveStatus;
console.log('设置审批状态变量:', parseInt(form.approveStatus));
}
//
if (routeParams.value.type === 'confirm' && form.repairConfirm) {
// 01
const repairConfirmValue = form.repairConfirm === '1' ? 0 : 1;
taskVariables.value.variables.repairConfirm = repairConfirmValue;
taskVariables.value.repairConfirm = repairConfirmValue.toString();
console.log('设置确认结果变量:', repairConfirmValue);
}
console.log('完整的工作流变量:', taskVariables.value);
// 使taskId
submitVerifyRef.value?.openDialog(routeParams.value.taskId);
await approveWorkOrder(form.repairInstanceId, approveStatus, message);
const statusText = approveStatus === '2' ? '通过' : '拒绝';
proxy?.$modal.msgSuccess(`审批${statusText}成功`);
//
goBack();
}
} catch (error) {
proxy?.$modal.msgError('表单验证失败,请检查输入项');
} catch (error: any) {
if (error !== 'cancel' && error !== 'close') {
console.error('审批失败:', error);
proxy?.$modal.msgError('审批失败:' + (error.message || '未知错误'));
}
} finally {
buttonLoading.value = false;
}
};
//
const submitCallback = async (approvalResult?: any) => {
try {
//
if (routeParams.value.type === 'approval') {
// 使
const approveStatus = form.approveStatus; // 使
const message = approvalResult?.message || form.processHandleResolution || '';
await approveWorkOrder(form.repairInstanceId, approveStatus, message);
const statusText = approveStatus === '2' ? '通过' : '拒绝';
proxy?.$modal.msgSuccess(`审批${statusText}成功`);
} else if (routeParams.value.type === 'confirm') {
if (routeParams.value.type === 'confirm') {
//
const confirmResult = form.repairConfirm || '1'; //
await confirmRepairResult(form.repairInstanceId, confirmResult);
@ -749,15 +700,61 @@ const getOutsourceList = async () => {
//
const updateOutsourcingValidation = () => {
if (form.repairType === '2') {
//
//
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 = () => {
//
Object.keys(rules).forEach(key => {
rules[key] = [{ required: false, message: rules[key][0].message, trigger: rules[key][0].trigger }];
});
//
if (routeParams.value.type === 'approval') {
//
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' }];
}
}
}
//
updateOutsourcingValidation();
};
//
const getStatusText = (status: string) => {
const statusMap = {

@ -305,7 +305,7 @@
form.value.flowCopyList = flowCopyList;
}
//
//
if (props.taskVariables && typeof props.taskVariables === 'object') {
const taskVars = props.taskVariables as any;

Loading…
Cancel
Save