This commit is contained in:
dhr
2025-09-25 20:03:08 +08:00
parent 9913a7854c
commit 6b9bfb66b1
15 changed files with 4715 additions and 1591 deletions

View File

@ -207,7 +207,16 @@
<div class="steps-container">
<div class="step-item" v-for="(step, index) in createForm.steps" :key="index">
<div class="step-number">{{ index + 1 }}</div>
<el-input v-model="step.content" placeholder="输入试验步骤" style="flex: 1; margin-right: 10px" />
<el-input v-model="step.name" placeholder="输入步骤名称" style="flex: 1; margin-right: 10px" />
<el-input v-model="step.intendedPurpose" placeholder="输入预期目的" style="flex: 1; margin-right: 10px" />
<el-date-picker
v-model="step.intendedTime"
type="datetime"
placeholder="选择计划时间"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm"
style="width: 180px; margin-right: 10px"
/>
<el-button v-if="createForm.steps.length > 1" type="text" class="delete-step-btn" @click="deleteStep(index)" style="color: #f56c6c">
删除
</el-button>
@ -244,51 +253,77 @@
</el-dialog>
<!-- 工单详情弹窗 -->
<el-dialog v-model="detailDialogVisible" title="工单详情" width="900px" class="beautiful-dialog" center>
<div v-if="isDetailLoading" class="loading-state">
<i class="el-icon-loading el-icon--loading"></i>
<span>加载中...</span>
<el-dialog
v-model="detailDialogVisible"
title="工单详情"
width="800px"
:before-close="handleCloseDetailDialog"
class="custom-experiment-dialog"
>
<div v-if="isDetailLoading" class="skeleton-loading">
<div class="skeleton-card">
<div class="skeleton-header"></div>
<div class="skeleton-content">
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
</div>
</div>
<div class="skeleton-card">
<div class="skeleton-header"></div>
<div class="skeleton-content">
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
</div>
</div>
<div class="skeleton-card">
<div class="skeleton-header"></div>
<div class="skeleton-content">
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
</div>
</div>
</div>
<div v-else-if="detailData" class="detail-container">
<div v-else-if="detailData" class="task-detail-container">
<!-- 基础信息区 -->
<div class="detail-card">
<h3 class="card-title">基础信息</h3>
<div class="card-content">
<div class="info-row">
<div class="detail-item">
<span class="detail-label">工单编号</span>
<span class="detail-value">WO-{{ detailData.id }}</span>
<div class="info-item">
<span class="info-label">工单编号</span>
<span class="info-value">WO-{{ detailData.id }}</span>
</div>
<div class="detail-item">
<span class="detail-label">工单标题</span>
<span class="detail-value">{{ detailData.title }}</span>
<div class="info-item">
<span class="info-label">工单标题</span>
<span class="info-value">{{ detailData.title }}</span>
</div>
</div>
<div class="info-row">
<div class="detail-item">
<span class="detail-label">工单类型</span>
<span class="detail-value">{{ mapCodeToType(detailData.type) }}</span>
<div class="info-item">
<span class="info-label">工单类型</span>
<span class="info-value">{{ mapCodeToType(detailData.type) }}</span>
</div>
<div class="detail-item">
<span class="detail-label">优先级</span>
<span class="detail-value task-status priority-{{ mapPriorityToClass(detailData.level) }}">{{
<div class="info-item">
<span class="info-label">优先级</span>
<span class="info-value task-status priority-{{ mapPriorityToClass(detailData.level) }}">{{
mapCodeToPriority(detailData.level)
}}</span>
</div>
</div>
<div class="info-row">
<div class="detail-item">
<span class="detail-label">创建人</span>
<span class="detail-value">{{ detailData.sendOrderPersonVo?.userName || '-' }}</span>
<div class="info-item">
<span class="info-label">创建人</span>
<span class="info-value">{{ detailData.sendOrderPersonVo?.userName || '-' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">创建时间</span>
<span class="detail-value">{{ detailData.createTime ? formatDate(detailData.createTime) : '-' }}</span>
<div class="info-item">
<span class="info-label">创建时间</span>
<span class="info-value">{{ detailData.createTime ? formatDate(detailData.createTime) : '-' }}</span>
</div>
</div>
<div class="info-row">
<div class="detail-item">
<div class="info-item">
<span class="detail-label">执行人</span>
<span class="detail-value">{{ detailData.getOrderPersonVo?.userName || '-' }}</span>
</div>
@ -333,17 +368,19 @@
<!-- 步骤条 -->
<div v-if="detailData.nodes && detailData.nodes.length > 0" class="detail-card">
<h3 class="card-title">执行步骤</h3>
<div class="card-content">
<div v-for="(group, groupIndex) in groupNodesByModule(detailData.nodes)" :key="groupIndex" class="module-group">
<div class="module-title">{{ group.module }}</div>
<el-steps :active="-1" finish-status="success" class="custom-steps">
<el-step
v-for="(node, index) in group.items"
:key="node.id"
:title="node.name"
:description="`目的: ${node.intendedPurpose || '-'}`"
/>
</el-steps>
<div class="steps-container">
<div v-for="(node, index) in detailData.nodes" :key="node.id || index" class="step-item">
<div class="step-number">{{ node.code || index + 1 }}</div>
<div class="step-info">
<div class="step-name">{{ node.name || '未命名步骤' }}</div>
<div class="step-purpose">{{ node.intendedPurpose || '无说明' }}</div>
<div class="step-time">计划时间{{ formatDateTime(node.intendedTime) }}</div>
<div v-if="node.finishTime" class="step-finish-time">完成时间{{ formatDateTime(node.finishTime) }}</div>
<div v-if="node.remark" class="step-remark">备注{{ node.remark }}</div>
</div>
<div class="step-status" :class="getStatusClass(node.status)">
{{ getStepStatusText(node.status) }}
</div>
</div>
</div>
</div>
@ -414,8 +451,8 @@
<script setup>
import { ref, computed, reactive } from 'vue';
import router from '@/router';
import { gongdanlist, addgongdan, updategongdan, gongdanDetail, uploadgongdan } from '@/api/zhinengxunjian/gongdan/index';
import { addjiedian } from '@/api/zhinengxunjian/jiedian';
import { gongdanlist, addgongdan, updategongdan, gongdanDetail } from '@/api/zhinengxunjian/gongdan/index';
import { addjiedian, updatejiedian } from '@/api/zhinengxunjian/jiedian';
import { xunjianUserlist } from '@/api/zhinengxunjian/xunjian';
import ImageUpload from '@/components/ImageUpload/index.vue';
import { ElMessageBox } from 'element-plus';
@ -605,7 +642,7 @@ const getPriorityTagType = (priority) => {
return priorityMap[priority] || 'default';
};
// 获取状态标签样式
// 状态标签样式
const getStatusTagType = (status) => {
const statusMap = {
'已接单': 'primary',
@ -617,7 +654,47 @@ const getStatusTagType = (status) => {
return statusMap[status] || 'default';
};
// 搜索处理
// 根据步骤状态获取对应的样式类
const getStatusClass = (status) => {
// 处理可能的数字输入
const statusStr = status?.toString() || '';
const statusClassMap = {
'1': 'status-pending',
'2': 'status-delayed',
'3': 'status-executing',
'4': 'status-completed'
};
return statusClassMap[statusStr] || 'status-unknown';
};
// 格式化日期时间(用于步骤条)
const formatDateTime = (dateTime) => {
if (!dateTime) return '-';
try {
const date = new Date(dateTime);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
} catch (error) {
return dateTime;
}
};
// 获取步骤状态文本
const getStepStatusText = (status) => {
const statusStr = status?.toString() || '';
const statusMap = {
'1': '待执行',
'2': '执行中',
'3': '已完成',
'4': '已延期'
};
return statusMap[statusStr] || '未知状态';
};
const handleSearch = () => {
currentPage.value = 1; // 重置到第一页
fetchWorkOrderList(); // 重新获取数据
@ -780,7 +857,7 @@ const confirmAssign = async () => {
// 从执行人列表中查找选中的执行人信息
const selectedExecutorInfo = executors.value.find((item) => item.userId === selectedExecutor.value);
// 先获取完整的工单详情,确保有所有必要字段(与编辑弹窗一样的方式)
// 先获取完整的工单详情,确保有所有必要字段(
const detailResponse = await gongdanDetail(currentTaskId.value);
if (detailResponse.code !== 200) {
ElMessage.error('获取工单详情失败');
@ -933,15 +1010,17 @@ const handleEdit = async (row) => {
const sortedNodes = [...workOrderDetail.nodes].sort((a, b) => (a.code || 0) - (b.code || 0));
// 转换为createForm.steps所需的格式
createForm.steps = sortedNodes.map((node) => ({
content: node.intendedPurpose || ''
name: node.name || '',
intendedPurpose: node.intendedPurpose || '',
intendedTime: node.intendedTime || ''
}));
// 确保至少有一个空步骤
if (createForm.steps.length === 0) {
createForm.steps = [{ content: '' }];
createForm.steps = [{ name: '', intendedPurpose: '', intendedTime: '' }];
}
} else {
// 如果没有nodes数据重置为默认的一个空步骤
createForm.steps = [{ content: '' }];
createForm.steps = [{ name: '', intendedPurpose: '', intendedTime: '' }];
}
// 存储当前编辑的工单ID用于区分是创建还是编辑操作
@ -985,7 +1064,7 @@ const createForm = reactive({
resultDescription: '',
needAssignee: 'true',
assignee: '',
steps: [{ content: '' }],
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }],
file: '',
fileList: []
});
@ -1007,7 +1086,7 @@ const handleCreateWorkOrder = () => {
// 添加试验步骤
const addStep = () => {
createForm.steps.push({ content: '' });
createForm.steps.push({ name: '', intendedPurpose: '', intendedTime: '' });
};
// 删除试验步骤
@ -1030,37 +1109,68 @@ const submitCreate = async () => {
// 准备步骤数据
const stepsData = createForm.steps
.filter((step) => step.content.trim())
.filter((step) => step.name.trim() && step.intendedPurpose.trim())
.map((step, index) => ({
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
params: {},
module: 1,
code: index + 1,
name: `步骤${index + 1}`,
intendedPurpose: step.content,
intendedTime: new Date().toISOString(),
finishTime: new Date().toISOString(),
name: step.name,
intendedPurpose: step.intendedPurpose,
intendedTime: step.intendedTime ? new Date(step.intendedTime).toISOString() : new Date().toISOString(),
finishTime: '',
remark: '',
status: '1' // 使用数字代码而不是字符串,避免数据长度超出限制
status: 2
}));
// 首先调用addjiedian接口
const jiedianResponse = await addjiedian(stepsData);
if (jiedianResponse.code !== 200) {
ElMessage.error('创建步骤失败');
return;
}
// 获取返回的ids实际返回格式中msg字段包含ids字符串data为null
// 编辑模式下需要为每个步骤添加id并调用updatejiedian接口
let nodeIds = '';
if (jiedianResponse.code === 200 && jiedianResponse.msg) {
nodeIds = jiedianResponse.msg;
if (editingWorkOrderId.value) {
// 获取工单详情以获取原始步骤的id
const detailResponse = await gongdanDetail(editingWorkOrderId.value);
if (detailResponse.code !== 200) {
ElMessage.error('获取工单详情失败');
return;
}
const workOrderDetail = detailResponse.data;
if (workOrderDetail.nodes && Array.isArray(workOrderDetail.nodes)) {
// 按code排序原始nodes数组
const sortedNodes = [...workOrderDetail.nodes].sort((a, b) => (a.code || 0) - (b.code || 0));
// 为新的步骤数据添加id
const updatedSteps = stepsData.map((step, index) => ({
...step,
id: sortedNodes[index]?.id || 0 // 使用原始步骤的id如果不存在则使用0
}));
// 调用updatejiedian接口更新步骤直接传递数组
const updateResponse = await updatejiedian(updatedSteps);
if (updateResponse.code !== 200) {
ElMessage.error('更新步骤失败');
return;
}
// 使用原始的nodeIds避免重新创建步骤
nodeIds = workOrderDetail.nodeIds;
}
} else {
ElMessage.warning('未获取到有效的步骤ID');
return;
// 创建模式下调用addjiedian接口直接传递数组
const jiedianResponse = await addjiedian(stepsData);
if (jiedianResponse.code !== 200) {
ElMessage.error('创建步骤失败');
return;
}
// 获取返回的ids实际返回格式中msg字段包含ids字符串data为null
if (jiedianResponse.code === 200 && jiedianResponse.msg) {
nodeIds = jiedianResponse.msg;
} else {
ElMessage.warning('未获取到有效的步骤ID');
return;
}
}
// 准备工单数据
@ -1199,6 +1309,9 @@ const handleInspectionManagement3 = () => {
</script>
<style scoped>
@import url('./css/step-bars.css');
@import url('./css/detail-dialog.css');
.work-order-management {
padding: 20px;
background-color: #f5f7fa;
@ -1679,72 +1792,105 @@ const handleInspectionManagement3 = () => {
gap: 10px;
}
}
/* 详情弹窗样式 */
.beautiful-dialog .el-dialog__body {
max-height: 600px;
/* 详情弹窗样式 - 与保修管理页面保持一致 */
.custom-experiment-dialog .el-dialog__body {
max-height: 60vh;
overflow-y: auto;
padding: 20px;
padding: 24px;
}
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 0;
color: #909399;
}
.detail-container {
.task-detail-container {
padding: 10px 0;
}
/* 详情卡片样式 */
.detail-card {
margin-bottom: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
overflow: hidden;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
border: 1px solid #f0f2f5;
}
.card-title {
font-size: 14px;
font-weight: 500;
font-size: 16px;
font-weight: 600;
color: #1d2129;
padding: 12px 16px;
background-color: #f7f8fa;
border-bottom: 1px solid #f0f2f5;
margin-bottom: 0;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 2px solid #409eff;
}
.card-content {
padding: 0 4px;
}
/* 信息行和信息项样式 */
.info-row {
display: flex;
margin-bottom: 16px;
flex-wrap: wrap;
}
.info-item {
flex: 0 0 50%;
margin-bottom: 12px;
display: flex;
align-items: flex-start;
}
.info-item.full-width {
flex: 0 0 100%;
}
.info-label {
font-weight: 500;
color: #86909c;
margin-right: 8px;
min-width: 80px;
flex-shrink: 0;
}
.info-value {
color: #4e5969;
flex: 1;
word-break: break-all;
font-size: 14px;
}
/* 骨架屏样式 */
.skeleton-loading {
display: flex;
flex-direction: column;
gap: 16px;
}
.skeleton-card {
background-color: #f5f5f5;
border-radius: 8px;
padding: 16px;
}
.info-row {
.skeleton-header {
height: 20px;
width: 30%;
background-color: #e0e0e0;
border-radius: 4px;
margin-bottom: 12px;
}
.skeleton-content {
display: flex;
flex-wrap: wrap;
margin-bottom: 8px;
flex-direction: column;
gap: 8px;
}
.detail-item {
display: flex;
margin-bottom: 10px;
font-size: 13px;
flex: 0 0 50%;
box-sizing: border-box;
padding-right: 12px;
}
.detail-label {
flex: 0 0 80px;
color: #86909c;
}
.detail-value {
flex: 1;
color: #4e5969;
word-break: break-all;
.skeleton-row {
height: 16px;
width: 100%;
background-color: #e0e0e0;
border-radius: 4px;
}
/* 优先级标签样式 */
@ -1784,23 +1930,6 @@ const handleInspectionManagement3 = () => {
font-size: 13px;
}
.steps-container {
padding: 16px;
background-color: #fafafa;
border-radius: 4px;
}
.module-group {
margin-bottom: 24px;
}
.module-title {
font-size: 14px;
font-weight: bold;
margin-bottom: 12px;
color: #333;
}
/* 多图片展示容器样式 */
.images-container {
display: flex;
@ -1948,23 +2077,7 @@ const handleInspectionManagement3 = () => {
line-height: 1.6;
}
.steps-container {
padding: 16px;
background-color: #fafafa;
border-radius: 4px;
}
.module-group {
margin-bottom: 24px;
}
.module-title {
font-size: 14px;
font-weight: bold;
margin-bottom: 12px;
color: #333;
}
/* 自定义步骤条样式覆盖 */
.custom-steps .el-step__description {
white-space: pre-wrap;
font-size: 12px;