0925
This commit is contained in:
@ -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;
|
||||
|
||||
Reference in New Issue
Block a user