0925
This commit is contained in:
@ -39,3 +39,11 @@ export const syrenwuDetail = (id) => {
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
export const syrenwujilu = (data) => {
|
||||
return request({
|
||||
url: '/ops/testTask/record',
|
||||
method: 'get',
|
||||
params: data
|
||||
});
|
||||
};
|
||||
|
||||
@ -316,3 +316,58 @@ export const removeClass = (ele: HTMLElement, cls: string) => {
|
||||
export const isExternal = (path: string) => {
|
||||
return /^(https?:|http?:|mailto:|tel:)/.test(path);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取步骤状态对应的样式类
|
||||
* @param {string|number} status - 步骤状态码
|
||||
* @returns {string} 样式类名
|
||||
*/
|
||||
export const getStatusClass = (status: string | number): string => {
|
||||
// 处理可能的数字输入
|
||||
const statusStr = status?.toString() || '';
|
||||
const statusClassMap: Record<string, string> = {
|
||||
'1': 'status-pending',
|
||||
'2': 'status-executing',
|
||||
'3': 'status-completed',
|
||||
'4': 'status-delayed',
|
||||
'5': 'status-failed'
|
||||
};
|
||||
return statusClassMap[statusStr] || 'status-unknown';
|
||||
};
|
||||
|
||||
/**
|
||||
* 格式化日期时间(用于步骤条)
|
||||
* @param {string} dateTime - 日期时间字符串
|
||||
* @returns {string} 格式化后的日期时间
|
||||
*/
|
||||
export const formatDateTime = (dateTime: string): string => {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取步骤状态文本
|
||||
* @param {string|number} status - 步骤状态码
|
||||
* @returns {string} 状态文本
|
||||
*/
|
||||
export const getStepStatusText = (status: string | number): string => {
|
||||
const statusStr = status?.toString() || '';
|
||||
const statusMap: Record<string, string> = {
|
||||
'1': '待执行',
|
||||
'2': '执行中',
|
||||
'3': '已完成',
|
||||
'4': '已延期',
|
||||
'5': '失败'
|
||||
};
|
||||
return statusMap[statusStr] || '未知状态';
|
||||
};
|
||||
|
||||
@ -312,43 +312,101 @@
|
||||
v-model="detailDialogVisible"
|
||||
title="巡检计划详情"
|
||||
width="800px"
|
||||
class="detail-dialog"
|
||||
center
|
||||
:show-close="true"
|
||||
custom-class="beautified-detail-dialog"
|
||||
:before-close="handleCloseDetailDialog"
|
||||
class="custom-experiment-dialog"
|
||||
>
|
||||
<div class="detail-content">
|
||||
<div class="detail-header">
|
||||
<h3 class="detail-title">{{ detailData.planName || '巡检计划' }}</h3>
|
||||
<el-tag :type="detailData.status === '1' ? 'success' : 'info'" class="detail-status-tag">
|
||||
<div class="task-detail-container">
|
||||
<!-- 基础信息区 -->
|
||||
<div class="detail-card">
|
||||
<h3 class="card-title">基础信息</h3>
|
||||
<div class="card-content">
|
||||
<div class="info-row">
|
||||
<div class="info-item">
|
||||
<span class="info-label">计划名称:</span>
|
||||
<span class="info-value">{{ detailData.planName || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">状态:</span>
|
||||
<span class="info-value task-status">
|
||||
<el-tag :type="detailData.status === '1' ? 'success' : 'info'">
|
||||
{{ detailData.status === '1' ? '启用' : detailData.status === '2' ? '停用' : '-' }}
|
||||
</el-tag>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-item">
|
||||
<span class="info-label">计划类型:</span>
|
||||
<span class="info-value">{{ getPlanTypeText(detailData.planType) || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">巡检对象:</span>
|
||||
<span class="info-value">{{ getObjectTypeText(detailData.objectType) || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-item">
|
||||
<span class="info-label">巡检频率:</span>
|
||||
<span class="info-value">{{ detailData.inspectionFrequency || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">负责人:</span>
|
||||
<span class="info-value">{{ detailData.nickName || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-item">
|
||||
<span class="info-label">开始日期:</span>
|
||||
<span class="info-value">{{ formatDate(detailData.beginTime) || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">结束日期:</span>
|
||||
<span class="info-value">{{ formatDate(detailData.endTime) || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-item">
|
||||
<span class="info-label">计划开始时间:</span>
|
||||
<span class="info-value">{{ detailData.planBeginTime || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">持续时间:</span>
|
||||
<span class="info-value">{{ detailData.duration || '-' }}分钟</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-item">
|
||||
<span class="info-label">巡检项:</span>
|
||||
<div class="info-value">
|
||||
<span v-for="(item, index) in detailData.itemVoList" :key="item.id" class="inspection-item-tag">
|
||||
{{ item.name }}
|
||||
<span v-if="index < detailData.itemVoList.length - 1" class="item-separator">、</span>
|
||||
</span>
|
||||
<span v-if="!detailData.itemVoList || detailData.itemVoList.length === 0">-</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">电站ID:</span>
|
||||
<span class="info-value">{{ detailData.projectId || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-main">
|
||||
<el-descriptions :column="{ xs: 1, sm: 1, md: 2, lg: 2 }" class="detail-descriptions" border>
|
||||
<el-descriptions-item label="计划类型" class="detail-item">{{ getPlanTypeText(detailData.planType) || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="巡检对象" class="detail-item">{{ getObjectTypeText(detailData.objectType) || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="巡检频率" class="detail-item">{{ detailData.inspectionFrequency || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="负责人" class="detail-item">{{ detailData.nickName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="开始日期" class="detail-item">{{ formatDate(detailData.beginTime) || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="结束日期" class="detail-item">{{ formatDate(detailData.endTime) || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="计划开始时间" class="detail-item">{{ detailData.planBeginTime || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="持续时间" class="detail-item">{{ detailData.duration || '-' }}分钟</el-descriptions-item>
|
||||
<el-descriptions-item label="巡检项ID" class="detail-item">{{ detailData.inspectionItemId || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="电站ID" class="detail-item">{{ detailData.projectId || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<!-- 备注信息 -->
|
||||
<div v-if="detailData.remark" class="detail-card">
|
||||
<h3 class="card-title">备注信息</h3>
|
||||
<div class="card-content">
|
||||
<div class="description-content">
|
||||
{{ detailData.remark }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="detailData.remark" class="detail-remark">
|
||||
<h4 class="remark-title">备注信息</h4>
|
||||
<p class="remark-content">{{ detailData.remark }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="closeDetailDialog" class="close-btn">关闭</el-button>
|
||||
<el-button @click="closeDetailDialog">关闭</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@ -980,6 +1038,8 @@ const handleInspectionManagement3 = () => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import url('./css/detail-dialog.css');
|
||||
@import url('./css/step-bars.css');
|
||||
.operation-inspection {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
@ -1123,47 +1183,127 @@ const handleInspectionManagement3 = () => {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.detail-dialog .el-dialog__body {
|
||||
/* 弹窗样式 */
|
||||
.create-plan-dialog .el-dialog__body {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* 详情弹窗样式 - 与工单列表页面保持一致 */
|
||||
.custom-experiment-dialog .el-dialog__body {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.task-detail-container {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
/* 详情卡片样式 */
|
||||
.detail-card {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid #f0f2f5;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 2px solid #409eff;
|
||||
}
|
||||
|
||||
.detail-status-tag {
|
||||
padding: 4px 12px;
|
||||
.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;
|
||||
}
|
||||
|
||||
.detail-descriptions {
|
||||
margin-bottom: 20px;
|
||||
/* 骨架屏样式 */
|
||||
.skeleton-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.detail-item .el-descriptions__label {
|
||||
.skeleton-card {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.skeleton-header {
|
||||
height: 20px;
|
||||
width: 30%;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.skeleton-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.skeleton-row {
|
||||
height: 16px;
|
||||
width: 100%;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 优先级标签样式 */
|
||||
.task-status {
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.remark-title {
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.remark-content {
|
||||
.description-content {
|
||||
padding: 12px;
|
||||
background-color: #f5f7fa;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 4px;
|
||||
line-height: 1.6;
|
||||
color: #4e5969;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1080,6 +1080,8 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import url('./css/detail-dialog.css');
|
||||
@import url('./css/step-bars.css');
|
||||
.inspection-tasks {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
|
||||
@ -1244,11 +1244,69 @@ const handleInspection7 = () => {
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* 详情弹窗样式 */
|
||||
.custom-experiment-dialog .el-dialog__body {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* 详情卡片样式 */
|
||||
.detail-card {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid #f0f2f5;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
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;
|
||||
}
|
||||
/* 详情弹窗样式 */
|
||||
.custom-experiment-dialog {
|
||||
.detail-content {
|
||||
@ -1304,96 +1362,6 @@ const handleInspection7 = () => {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* 任务详情容器 */
|
||||
.task-detail-container {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
/* 详情卡片样式 */
|
||||
.detail-card {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #ebeef5;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
flex: 0 0 50%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.info-item.full-width {
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 骨架屏样式 */
|
||||
.skeleton-loading {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.skeleton-card {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
background-color: #f5f7fa;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.skeleton-header {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background-color: #e8e8e8;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.skeleton-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.skeleton-row {
|
||||
height: 16px;
|
||||
background-color: #e8e8e8;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 多图片展示容器样式 */
|
||||
.images-container {
|
||||
display: flex;
|
||||
@ -1443,20 +1411,7 @@ const handleInspection7 = () => {
|
||||
color: #909399;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
/* 加载状态样式 */
|
||||
.loading-state {
|
||||
text-align: center;
|
||||
padding: 60px 0;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.loading-state .el-icon-loading {
|
||||
font-size: 36px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.custom-experiment-dialog {
|
||||
width: 90% !important;
|
||||
|
||||
287
src/views/zhinengxunjian/css/detail-dialog.css
Normal file
287
src/views/zhinengxunjian/css/detail-dialog.css
Normal file
@ -0,0 +1,287 @@
|
||||
/* 详情弹窗通用样式 */
|
||||
/* 详情卡片样式 */
|
||||
.detail-card {
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background-color: #fafafa;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
margin: 0 0 16px 0;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 2px solid #409eff;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 信息行和信息项样式 */
|
||||
.info-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
flex: 1;
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
.info-item.full-width {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #303133;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 步骤相关样式 */
|
||||
.steps-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
padding: 16px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background-color: #409eff;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.step-name {
|
||||
font-weight: 500;
|
||||
color: #1d2129;
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.step-purpose {
|
||||
color: #606266;
|
||||
margin-bottom: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.step-time,
|
||||
.step-finish-time,
|
||||
.step-remark {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* 状态相关样式 */
|
||||
.step-status {
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* 步骤状态样式 */
|
||||
.step-status.status-pending {
|
||||
background-color: #e6f7ff;
|
||||
color: #1677ff;
|
||||
border: 1px solid #91d5ff;
|
||||
}
|
||||
|
||||
.step-status.status-executing {
|
||||
background-color: #fffbe6;
|
||||
color: #fa8c16;
|
||||
border: 1px solid #ffe58f;
|
||||
}
|
||||
|
||||
.step-status.status-completed {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
border: 1px solid #b7eb8f;
|
||||
}
|
||||
|
||||
.step-status.status-delayed {
|
||||
background-color: #fff2f0;
|
||||
color: #ff4d4f;
|
||||
border: 1px solid #ffccc7;
|
||||
}
|
||||
|
||||
/* 通用状态颜色样式 */
|
||||
.status-pending {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.status-executing {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.status-delayed {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.status-unknown {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 加载状态样式 */
|
||||
.loading-details {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
/* 骨架屏加载 */
|
||||
.skeleton-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.skeleton-card {
|
||||
background-color: #f2f2f2;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
animation: skeleton-loading 1.5s infinite;
|
||||
}
|
||||
|
||||
.skeleton-header {
|
||||
height: 24px;
|
||||
width: 140px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.skeleton-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.skeleton-row {
|
||||
height: 20px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.skeleton-row:nth-child(1) {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.skeleton-row:nth-child(2) {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.skeleton-row:nth-child(3) {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
@keyframes skeleton-loading {
|
||||
0% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.info-item {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 弹窗按钮样式 */
|
||||
.dialog-footer .el-button {
|
||||
padding: 10px 24px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.dialog-footer .el-button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
/* 其他相关样式 */
|
||||
.fail-reason {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.no-info {
|
||||
color: #909399;
|
||||
font-style: italic;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
text-align: center;
|
||||
padding: 80px 20px;
|
||||
color: #6c757d;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.loading-state i {
|
||||
display: block;
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
color: #1677ff;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
padding: 30px 20px;
|
||||
background-color: #fafafa;
|
||||
border-radius: 8px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
206
src/views/zhinengxunjian/css/step-bars.css
Normal file
206
src/views/zhinengxunjian/css/step-bars.css
Normal file
@ -0,0 +1,206 @@
|
||||
/* 步骤容器样式 */
|
||||
.steps-container {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 单个步骤项样式 */
|
||||
.step-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
padding: 15px;
|
||||
background-color: #fafafa;
|
||||
border-radius: 6px;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 步骤项悬停效果 */
|
||||
.step-item:hover {
|
||||
background-color: #f5f7fa;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
/* 步骤序号样式 */
|
||||
.step-number {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-color: #409eff;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
flex-shrink: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 步骤连接线样式 */
|
||||
.step-item:not(:last-child)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
left: 16px;
|
||||
width: 2px;
|
||||
height: calc(100% + 5px);
|
||||
background-color: #e4e7ed;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* 步骤内容样式 */
|
||||
.step-content {
|
||||
padding: 30px 20px;
|
||||
background-color: #fafafa;
|
||||
border-radius: 8px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* 步骤信息样式 */
|
||||
.step-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 步骤名称样式 */
|
||||
.step-name {
|
||||
font-weight: 500;
|
||||
color: #1d2129;
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 步骤目的样式 */
|
||||
.step-purpose {
|
||||
color: #606266;
|
||||
margin-bottom: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 步骤时间样式 */
|
||||
.step-time,
|
||||
.step-finish-time,
|
||||
.step-remark {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* 添加步骤按钮样式 */
|
||||
.add-step-btn {
|
||||
color: #409eff;
|
||||
display: block;
|
||||
margin: 15px auto 0;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.add-step-btn:hover {
|
||||
color: #66b1ff;
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
/* 删除步骤按钮样式 */
|
||||
.delete-step-btn {
|
||||
color: #f56c6c;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.delete-step-btn:hover {
|
||||
color: #ff8590;
|
||||
background-color: #fef0f0;
|
||||
}
|
||||
|
||||
/* 步骤状态标签样式 */
|
||||
.step-status {
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* 步骤状态样式 - 待执行 */
|
||||
.step-status.status-pending {
|
||||
background-color: #e6f7ff;
|
||||
color: #1677ff;
|
||||
border: 1px solid #91d5ff;
|
||||
}
|
||||
|
||||
/* 步骤状态样式 - 执行中 */
|
||||
.step-status.status-executing {
|
||||
background-color: #fffbe6;
|
||||
color: #fa8c16;
|
||||
border: 1px solid #ffe58f;
|
||||
}
|
||||
|
||||
/* 步骤状态样式 - 已完成 */
|
||||
.step-status.status-completed {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
border: 1px solid #b7eb8f;
|
||||
}
|
||||
|
||||
/* 步骤状态样式 - 已延期 */
|
||||
.step-status.status-delayed {
|
||||
background-color: #fff2f0;
|
||||
color: #ff4d4f;
|
||||
border: 1px solid #ffccc7;
|
||||
}
|
||||
|
||||
/* 响应式设计 - 中等屏幕 */
|
||||
@media (max-width: 1024px) {
|
||||
.steps-container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 13px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 - 小屏幕 */
|
||||
@media (max-width: 768px) {
|
||||
.steps-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.step-item > * {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
margin-bottom: 10px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.step-item:not(:last-child)::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@ -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,23 +1109,54 @@ 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接口
|
||||
// 编辑模式下,需要为每个步骤添加id,并调用updatejiedian接口
|
||||
let nodeIds = '';
|
||||
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 {
|
||||
// 创建模式下,调用addjiedian接口,直接传递数组
|
||||
const jiedianResponse = await addjiedian(stepsData);
|
||||
|
||||
if (jiedianResponse.code !== 200) {
|
||||
@ -1055,13 +1165,13 @@ const submitCreate = async () => {
|
||||
}
|
||||
|
||||
// 获取返回的ids,实际返回格式中msg字段包含ids字符串,data为null
|
||||
let nodeIds = '';
|
||||
if (jiedianResponse.code === 200 && jiedianResponse.msg) {
|
||||
nodeIds = jiedianResponse.msg;
|
||||
} else {
|
||||
ElMessage.warning('未获取到有效的步骤ID');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 准备工单数据
|
||||
const workOrderData = {
|
||||
@ -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;
|
||||
|
||||
@ -259,7 +259,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>
|
||||
@ -295,77 +304,103 @@
|
||||
</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">
|
||||
<span class="detail-label">执行人:</span>
|
||||
<span class="detail-value">{{ detailData.getOrderPersonVo?.userName || '-' }}</span>
|
||||
<div class="info-item">
|
||||
<span class="info-label">执行人:</span>
|
||||
<span class="info-value">{{ detailData.getOrderPersonVo?.userName || '-' }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">接单时间:</span>
|
||||
<span class="detail-value">{{ detailData.getOrderTime ? formatDate(detailData.getOrderTime) : '-' }}</span>
|
||||
<div class="info-item">
|
||||
<span class="info-label">接单时间:</span>
|
||||
<span class="info-value">{{ detailData.getOrderTime ? formatDate(detailData.getOrderTime) : '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">截止时间:</span>
|
||||
<span class="detail-value">{{ detailData.endTime ? formatDate(detailData.endTime) : '-' }}</span>
|
||||
<div class="info-item">
|
||||
<span class="info-label">截止时间:</span>
|
||||
<span class="info-value">{{ detailData.endTime ? formatDate(detailData.endTime) : '-' }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">完成时间:</span>
|
||||
<span class="detail-value">{{ detailData.finishiOrderTime ? formatDate(detailData.finishiOrderTime) : '-' }}</span>
|
||||
<div class="info-item">
|
||||
<span class="info-label">完成时间:</span>
|
||||
<span class="info-value">{{ detailData.finishiOrderTime ? formatDate(detailData.finishiOrderTime) : '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">执行地点:</span>
|
||||
<span class="detail-value">{{ detailData.position || '-' }}</span>
|
||||
<div class="info-item">
|
||||
<span class="info-label">执行地点:</span>
|
||||
<span class="info-value">{{ detailData.position || '-' }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">相关设备:</span>
|
||||
<span class="detail-value">{{ detailData.device || '-' }}</span>
|
||||
<div class="info-item">
|
||||
<span class="info-label">相关设备:</span>
|
||||
<span class="info-value">{{ detailData.device || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -384,17 +419,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>
|
||||
@ -465,15 +502,12 @@
|
||||
<script setup>
|
||||
import { ref, computed, reactive } from 'vue';
|
||||
import router from '@/router';
|
||||
import { gongdanlist, gongdanRecord, updategongdan, gongdanDetail, uploadgongdan } from '@/api/zhinengxunjian/gongdan/index';
|
||||
import { addjiedian } from '@/api/zhinengxunjian/jiedian';
|
||||
import { gongdanlist, gongdanRecord, updategongdan, gongdanDetail } from '@/api/zhinengxunjian/gongdan/index';
|
||||
import { updatejiedian } from '@/api/zhinengxunjian/jiedian';
|
||||
import { xunjianUserlist } from '@/api/zhinengxunjian/xunjian';
|
||||
import ImageUpload from '@/components/ImageUpload/index.vue';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
|
||||
// 激活的选项卡
|
||||
const activeTab = ref('list');
|
||||
|
||||
// 筛选条件
|
||||
const workOrderType = ref('all');
|
||||
const workOrderStatus = ref('all');
|
||||
@ -662,6 +696,52 @@ const formatDate = (dateString) => {
|
||||
return `${year}/${month}/${day} ${hours}:${minutes}`;
|
||||
};
|
||||
|
||||
// 根据步骤状态获取对应的样式类
|
||||
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 groupNodesByModule = (nodes) => {
|
||||
if (!nodes || !nodes.length) return [];
|
||||
return [{ module: '', items: nodes }];
|
||||
|
||||
// 初始化加载数据
|
||||
const initData = async () => {
|
||||
await fetchStatisticsData();
|
||||
@ -797,17 +877,6 @@ const handleCurrentChange = (val) => {
|
||||
currentPage.value = val;
|
||||
};
|
||||
|
||||
// 选项卡点击
|
||||
const handleTabClick = (tab) => {
|
||||
console.log('切换到选项卡:', tab.name);
|
||||
// 重置筛选条件和分页
|
||||
workOrderType.value = 'all';
|
||||
workOrderStatus.value = 'all';
|
||||
priority.value = 'all';
|
||||
createDate.value = '';
|
||||
currentPage.value = 1;
|
||||
};
|
||||
|
||||
// 操作按钮事件
|
||||
const handleViewDetail = async (row) => {
|
||||
try {
|
||||
@ -875,10 +944,6 @@ const groupNodesByModule = (nodes) => {
|
||||
}));
|
||||
};
|
||||
|
||||
const handleEvaluate = (row) => {
|
||||
console.log('评价:', row);
|
||||
};
|
||||
|
||||
const handleCancel = (row) => {
|
||||
console.log('取消工单:', row);
|
||||
};
|
||||
@ -1100,15 +1165,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 ? formatDate(node.intendedTime, 'YYYY-MM-DD HH:mm') : ''
|
||||
}));
|
||||
// 确保至少有一个空步骤
|
||||
if (createForm.steps.length === 0) {
|
||||
createForm.steps = [{ content: '' }];
|
||||
createForm.steps = [{ name: '', intendedPurpose: '', intendedTime: '' }];
|
||||
}
|
||||
} else {
|
||||
// 如果没有nodes数据,重置为默认的一个空步骤
|
||||
createForm.steps = [{ content: '' }];
|
||||
createForm.steps = [{ name: '', intendedPurpose: '', intendedTime: '' }];
|
||||
}
|
||||
|
||||
// 存储当前编辑的工单ID,用于区分是创建还是编辑操作
|
||||
@ -1149,7 +1216,7 @@ const createForm = reactive({
|
||||
description: '',
|
||||
location: '',
|
||||
relatedEquipment: '',
|
||||
steps: [{ content: '' }], // 工单步骤数组
|
||||
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }], // 工单步骤数组
|
||||
file: '',
|
||||
fileList: [],
|
||||
resultDescription: '',
|
||||
@ -1168,7 +1235,7 @@ const createFormRules = {
|
||||
|
||||
// 添加试验步骤
|
||||
const addStep = () => {
|
||||
createForm.steps.push({ content: '' });
|
||||
createForm.steps.push({ name: '', intendedPurpose: '', intendedTime: '' });
|
||||
};
|
||||
|
||||
// 删除试验步骤
|
||||
@ -1191,22 +1258,54 @@ 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: 1 // 使用数字值而不是字符串,确保状态字段不为空
|
||||
}));
|
||||
|
||||
// 首先调用addjiedian接口
|
||||
// 编辑模式下,需要为每个步骤添加id,并调用updatejiedian接口
|
||||
let nodeIds = '';
|
||||
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 {
|
||||
// 创建模式下,调用addjiedian接口,直接传递数组
|
||||
const jiedianResponse = await addjiedian(stepsData);
|
||||
|
||||
if (jiedianResponse.code !== 200) {
|
||||
@ -1214,14 +1313,14 @@ const submitCreate = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取返回的ids
|
||||
let nodeIds = '';
|
||||
// 获取返回的ids,实际返回格式中msg字段包含ids字符串,data为null
|
||||
if (jiedianResponse.code === 200 && jiedianResponse.msg) {
|
||||
nodeIds = jiedianResponse.msg;
|
||||
} else {
|
||||
ElMessage.warning('未获取到有效的步骤ID');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 准备工单数据
|
||||
const workOrderData = {
|
||||
@ -1268,7 +1367,7 @@ const submitCreate = async () => {
|
||||
// 重置表单
|
||||
Object.keys(createForm).forEach((key) => {
|
||||
if (key === 'steps') {
|
||||
createForm[key] = [{ content: '' }];
|
||||
createForm[key] = [{ name: '', intendedPurpose: '', intendedTime: '' }];
|
||||
} else if (key === 'fileList') {
|
||||
createForm[key] = [];
|
||||
} else {
|
||||
@ -1298,7 +1397,7 @@ const cancelCreate = () => {
|
||||
// 重置表单
|
||||
Object.keys(createForm).forEach((key) => {
|
||||
if (key === 'steps') {
|
||||
createForm[key] = [{ content: '' }];
|
||||
createForm[key] = [{ name: '', intendedPurpose: '', intendedTime: '' }];
|
||||
} else if (key === 'fileList') {
|
||||
createForm[key] = [];
|
||||
} else {
|
||||
@ -1341,9 +1440,16 @@ const handleInspectionManagement2 = () => {
|
||||
const handleInspectionManagement3 = () => {
|
||||
router.push('/rili/zhixingjilu');
|
||||
};
|
||||
|
||||
// 关闭详情弹窗
|
||||
const handleCloseDetailDialog = () => {
|
||||
detailDialogVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import url('./css/detail-dialog.css');
|
||||
@import url('./css/step-bars.css');
|
||||
.dispatch-records {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
@ -1871,72 +1977,105 @@ const handleInspectionManagement3 = () => {
|
||||
box-shadow: 0 0 0 2px rgba(245, 108, 108, 0.1);
|
||||
}
|
||||
|
||||
/* 详情弹窗样式 */
|
||||
.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;
|
||||
}
|
||||
|
||||
/* 优先级标签样式 */
|
||||
|
||||
@ -251,10 +251,6 @@
|
||||
<span class="info-label">维修人:</span>
|
||||
<span class="info-value">{{ detailData.sendPersonVo?.userName || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">所属团队:</span>
|
||||
<span class="info-value">{{ detailData.sendPersonVo?.teamName || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -281,12 +277,9 @@
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">现场支持:</span>
|
||||
<span class="info-value">{{
|
||||
detailData.support === '1' ? '支持' : detailData.support === '2' ? '不支持' : detailData.support || '-'
|
||||
}}</span>
|
||||
<span class="info-value">{{ detailData.support || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 已完成状态的额外信息 -->
|
||||
<div v-if="detailData.status === '3'" class="info-row">
|
||||
<div class="info-item">
|
||||
@ -1386,45 +1379,51 @@ const handleInspectionManagement2 = () => {
|
||||
}
|
||||
|
||||
/* 详情弹窗样式 */
|
||||
.task-detail-container {
|
||||
max-height: 600px;
|
||||
.custom-experiment-dialog .el-dialog__body {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.task-detail-container {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
/* 详情卡片样式 */
|
||||
.detail-card {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid #f0f2f5;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
padding: 16px 20px;
|
||||
margin: 0;
|
||||
background-color: #fafafa;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 2px solid #409eff;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 20px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
/* 信息行和信息项样式 */
|
||||
.info-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
margin-bottom: 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
flex: 0 0 50%;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.info-item.full-width {
|
||||
@ -1432,47 +1431,104 @@ const handleInspectionManagement2 = () => {
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #86909c;
|
||||
margin-right: 8px;
|
||||
min-width: 80px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #303133;
|
||||
color: #4e5969;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
font-size: 14px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 图片容器样式 */
|
||||
.images-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
margin-top: 12px;
|
||||
padding: 10px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 单个图片项样式 */
|
||||
.image-item {
|
||||
flex: 0 0 auto;
|
||||
width: 200px; /* 固定宽度 */
|
||||
height: 160px; /* 固定高度 */
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.image-item:hover {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
/* 图片样式 */
|
||||
.detail-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover; /* 保持比例填充容器 */
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.detail-image:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
/* 图片加载失败样式 */
|
||||
.detail-image[src=''] {
|
||||
background-color: #f0f0f0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* 骨架屏样式 */
|
||||
.skeleton-loading {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.skeleton-card {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background-color: #f8f9fa;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.skeleton-header {
|
||||
height: 56px;
|
||||
background-color: #f1f3f5;
|
||||
height: 20px;
|
||||
width: 30%;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.skeleton-content {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.skeleton-row {
|
||||
height: 20px;
|
||||
margin-bottom: 12px;
|
||||
background-color: #e9ecef;
|
||||
height: 16px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.skeleton-row:last-child {
|
||||
margin-bottom: 0;
|
||||
/* 无数据提示 */
|
||||
.no-info {
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
/* 分配弹窗样式 */
|
||||
|
||||
@ -423,7 +423,16 @@
|
||||
<div class="steps-container">
|
||||
<div class="step-item" v-for="(step, index) in formData.steps" :key="index">
|
||||
<div class="step-number">{{ index + 1 }}</div>
|
||||
<el-input v-model="step.content" placeholder="输入试验步骤" />
|
||||
<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="formData.steps.length > 1"
|
||||
type="text"
|
||||
@ -524,9 +533,29 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实验步骤 -->
|
||||
<div v-if="detailData.testStep" class="detail-section">
|
||||
<h3 class="section-title">实验步骤</h3>
|
||||
<!-- 执行步骤 -->
|
||||
<div v-if="detailData.nodes && detailData.nodes.length > 0" class="detail-card">
|
||||
<h3 class="card-title">执行步骤</h3>
|
||||
<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>
|
||||
|
||||
<!-- 兼容旧格式:如果没有nodes数据但有testStep字符串 -->
|
||||
<div v-else-if="detailData.testStep" class="detail-section">
|
||||
<h3 class="section-title">执行步骤</h3>
|
||||
<div class="steps-container">
|
||||
<div v-for="(step, index) in detailData.testStep.split(',')" :key="index" class="step-item">
|
||||
<div class="step-number">{{ index + 1 }}</div>
|
||||
@ -589,7 +618,7 @@ import router from '@/router';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { shiyanDetail, shiyanlist, addshiyan, updateshiyan } from '@/api/zhinengxunjian/shiyan/index';
|
||||
import { xunjianUserlist } from '@/api/zhinengxunjian/xunjian/index';
|
||||
import { addjiedian } from '@/api/zhinengxunjian/jiedian/index';
|
||||
import { addjiedian, updatejiedian } from '@/api/zhinengxunjian/jiedian/index';
|
||||
// 1. 选项卡状态管理
|
||||
const activeTab = ref('plan'); // 默认为"巡检计划"
|
||||
const timeRange = ref('month'); // 统计时间范围:月/周/日
|
||||
@ -822,7 +851,11 @@ const formData = ref({
|
||||
envRequirements: '',
|
||||
manager: '',
|
||||
participants: [], // 改为数组存储多选的用户ID
|
||||
steps: [{ content: '' }, { content: '' }, { content: '' }],
|
||||
steps: [
|
||||
{ name: '', intendedPurpose: '', intendedTime: '' },
|
||||
{ name: '', intendedPurpose: '', intendedTime: '' },
|
||||
{ name: '', intendedPurpose: '', intendedTime: '' }
|
||||
],
|
||||
equipments: [
|
||||
{ name: '服务器(型号:XYZ-9000)', selected: false },
|
||||
{ name: '网络测试仪(型号:NT-5000)', selected: false },
|
||||
@ -920,8 +953,8 @@ const handleSave = async () => {
|
||||
inspectionItems: '',
|
||||
testSolutions: formData.value.riskMitigation,
|
||||
testStep: formData.value.steps
|
||||
.filter((step) => step.content.trim())
|
||||
.map((step) => step.content)
|
||||
.filter((step) => step.name.trim() || step.intendedPurpose.trim())
|
||||
.map((step) => `${step.name || ''}: ${step.intendedPurpose || ''}`)
|
||||
.join(','),
|
||||
testDevice: formData.value.equipments
|
||||
.filter((equip) => equip.selected)
|
||||
@ -932,26 +965,64 @@ const handleSave = async () => {
|
||||
id: editRecordId.value // 若后端用planId等,需改为对应字段名
|
||||
};
|
||||
|
||||
// 4. 调用接口
|
||||
// 4. 处理步骤数据并调用接口
|
||||
let response;
|
||||
if (editRecordId.value) {
|
||||
// 编辑模式:调用更新接口
|
||||
response = await updateshiyan(requestData);
|
||||
} else {
|
||||
|
||||
// 处理步骤数据格式
|
||||
const stepsData = formData.value.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(),
|
||||
remark: step.content.trim(),
|
||||
status: '1', // 使用数字代码
|
||||
// module值为2(与工单列表的1不同)
|
||||
module: 2,
|
||||
sort: index + 1
|
||||
params: {},
|
||||
module: 3,
|
||||
code: index + 1,
|
||||
name: step.name,
|
||||
intendedPurpose: step.intendedPurpose,
|
||||
intendedTime: step.intendedTime ? new Date(step.intendedTime).toISOString() : new Date().toISOString(),
|
||||
finishTime: '',
|
||||
remark: '',
|
||||
status: 2
|
||||
}));
|
||||
|
||||
// 首先调用addjiedian接口
|
||||
// 获取nodeIds
|
||||
let nodeIds = '';
|
||||
if (editRecordId.value) {
|
||||
// 编辑模式:获取试验详情,以获取原始步骤的id
|
||||
const detailResponse = await shiyanDetail(editRecordId.value);
|
||||
if (detailResponse.code !== 200) {
|
||||
ElMessage.error('获取试验详情失败');
|
||||
return;
|
||||
}
|
||||
|
||||
const experimentDetail = detailResponse.data.rows?.[0] || detailResponse.data;
|
||||
// 兼容两种数据结构:可能在rows数组中,也可能直接在data中
|
||||
|
||||
if (experimentDetail.nodes && Array.isArray(experimentDetail.nodes)) {
|
||||
// 按code排序原始nodes数组
|
||||
const sortedNodes = [...experimentDetail.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 = experimentDetail.nodeIds;
|
||||
}
|
||||
|
||||
// 编辑模式:调用更新接口
|
||||
response = await updateshiyan(requestData);
|
||||
} else {
|
||||
// 新增模式:调用addjiedian接口创建步骤
|
||||
const jiedianResponse = await addjiedian(stepsData);
|
||||
|
||||
if (jiedianResponse.code !== 200) {
|
||||
@ -960,7 +1031,6 @@ const handleSave = async () => {
|
||||
}
|
||||
|
||||
// 获取返回的ids,实际返回格式中msg字段包含ids字符串
|
||||
let nodeIds = '';
|
||||
if (jiedianResponse.code === 200 && jiedianResponse.msg) {
|
||||
nodeIds = jiedianResponse.msg;
|
||||
} else {
|
||||
@ -1002,7 +1072,11 @@ const resetForm = () => {
|
||||
envRequirements: '', // 环境要求为空
|
||||
manager: '', // 负责人为空
|
||||
participants: [], // 参与人员为空数组
|
||||
steps: [{ content: '' }, { content: '' }, { content: '' }], // 步骤内容为空
|
||||
steps: [
|
||||
{ name: '', intendedPurpose: '', intendedTime: '' },
|
||||
{ name: '', intendedPurpose: '', intendedTime: '' },
|
||||
{ name: '', intendedPurpose: '', intendedTime: '' }
|
||||
], // 步骤内容为空
|
||||
equipments: [
|
||||
{ name: '服务器(型号:XYZ-9000)', selected: false },
|
||||
{ name: '网络测试仪(型号:NT-5000)', selected: false },
|
||||
@ -1086,8 +1160,12 @@ const handleEditRecord = async (row) => {
|
||||
const sortedNodes = [...recordDetail.nodes].sort((a, b) => (a.code || 0) - (b.code || 0));
|
||||
// 转换为所需的格式
|
||||
sortedNodes.forEach((node) => {
|
||||
if (node.intendedPurpose && node.intendedPurpose.trim()) {
|
||||
steps.push({ content: node.intendedPurpose.trim() });
|
||||
if ((node.name && node.name.trim()) || (node.intendedPurpose && node.intendedPurpose.trim())) {
|
||||
steps.push({
|
||||
name: node.name || '',
|
||||
intendedPurpose: node.intendedPurpose || '',
|
||||
intendedTime: node.intendedTime || ''
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1099,13 +1177,18 @@ const handleEditRecord = async (row) => {
|
||||
// 移除序号前缀(如"1. "),只保留内容
|
||||
const content = stepText.replace(/^\d+\.\s*/, '').trim();
|
||||
if (content) {
|
||||
steps.push({ content });
|
||||
// 对于旧格式数据,我们将内容放入intendedPurpose字段
|
||||
steps.push({
|
||||
name: `步骤${steps.length + 1}`,
|
||||
intendedPurpose: content,
|
||||
intendedTime: ''
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// 确保至少有3个步骤(如果解析后为空)
|
||||
while (steps.length < 3) {
|
||||
steps.push({ content: '' });
|
||||
steps.push({ name: '', intendedPurpose: '', intendedTime: '' });
|
||||
}
|
||||
|
||||
// 4. 处理testDevice:将逗号分隔的字符串转换为设备数组
|
||||
@ -1183,7 +1266,7 @@ const handleEditRecord = async (row) => {
|
||||
};
|
||||
// 添加新步骤
|
||||
const addStep = () => {
|
||||
formData.value.steps.push({ content: '' });
|
||||
formData.value.steps.push({ name: '', intendedPurpose: '', intendedTime: '' });
|
||||
};
|
||||
|
||||
// 删除步骤
|
||||
@ -1291,10 +1374,54 @@ const formatDate = (dateString) => {
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
};
|
||||
|
||||
// 根据步骤状态获取对应的样式类
|
||||
const getStatusClass = (status) => {
|
||||
// 处理可能的数字输入
|
||||
const statusStr = status?.toString() || '';
|
||||
const statusClassMap = {
|
||||
'1': 'status-pending',
|
||||
'2': 'status-running',
|
||||
'3': 'status-completed',
|
||||
'4': 'status-delayed',
|
||||
'5': 'status-failed'
|
||||
};
|
||||
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': '已延期',
|
||||
'5': '失败'
|
||||
};
|
||||
return statusMap[statusStr] || '未知状态';
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 1. 基础容器样式(继承试验系统) */
|
||||
@import url('./css/step-bars.css');
|
||||
@import url('./css/detail-dialog.css');
|
||||
.operation-inspection {
|
||||
padding: 20px;
|
||||
background-color: #f9fbfd;
|
||||
@ -1336,7 +1463,7 @@ const formatDate = (dateString) => {
|
||||
box-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
|
||||
}
|
||||
|
||||
/* 3. 页面标题(与试验系统一致) */
|
||||
/* 3. 页面标题 */
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
@ -2094,221 +2221,4 @@ const formatDate = (dateString) => {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 详情弹窗样式 */
|
||||
.custom-experiment-dialog .el-dialog__body {
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
/* 详情区块 */
|
||||
.detail-section {
|
||||
margin-bottom: 24px;
|
||||
padding: 16px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1890ff;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #e8f4ff;
|
||||
}
|
||||
|
||||
/* 基础信息网格 */
|
||||
.detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 14px;
|
||||
color: #2c3e50;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
/* 文本区域 */
|
||||
.detail-textarea {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.detail-text {
|
||||
font-size: 14px;
|
||||
color: #495057;
|
||||
line-height: 1.6;
|
||||
padding: 8px 0;
|
||||
min-height: 60px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 设备列表样式 */
|
||||
.device-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.device-tag {
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
background-color: #f0f9ff;
|
||||
color: #1890ff;
|
||||
border: 1px solid #bae7ff;
|
||||
border-radius: 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 步骤条样式 */
|
||||
.steps-container {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.step-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.step-item:not(:last-child)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 17px;
|
||||
top: 36px;
|
||||
bottom: -16px;
|
||||
width: 2px;
|
||||
background-color: #e4e7ed;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background-color: #1890ff;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
flex: 1;
|
||||
padding: 8px 16px;
|
||||
background-color: #fafafa;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
color: #2c3e50;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 列表样式 */
|
||||
.participant-list,
|
||||
.inspection-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.participant-item,
|
||||
.inspection-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
padding: 12px 16px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.participant-name,
|
||||
.inspection-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #2c3e50;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.participant-team,
|
||||
.participant-role,
|
||||
.inspection-type {
|
||||
font-size: 13px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.participant-item,
|
||||
.inspection-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
padding: 12px 16px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.participant-name,
|
||||
.inspection-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #2c3e50;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.participant-team,
|
||||
.participant-role,
|
||||
.inspection-type {
|
||||
font-size: 13px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
/* 详情弹窗响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.detail-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.participant-item,
|
||||
.inspection-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.participant-name,
|
||||
.inspection-name {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="operation-inspection">
|
||||
<!-- 顶部导航选项卡 -->
|
||||
<div class="navigation-tabs">
|
||||
<div class="nav-tab" @click="handleInspection1">待办事项</div>
|
||||
<div class="nav-tab" @click="handleInspection2">巡检管理</div>
|
||||
@ -11,6 +12,7 @@
|
||||
<div class="nav-tab" @click="handleInspection7">运维组织</div>
|
||||
</div>
|
||||
|
||||
<!-- 头部操作按钮 -->
|
||||
<div class="header-container">
|
||||
<div class="header-actions">
|
||||
<el-button type="primary" class="export-btn">筛选</el-button>
|
||||
@ -27,7 +29,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. 筛选和操作区域 -->
|
||||
<!-- 筛选和操作区域 -->
|
||||
<div class="filter-and-actions">
|
||||
<div class="filters">
|
||||
<el-select v-model="filterStatus" placeholder="巡检状态" clearable>
|
||||
@ -54,13 +56,13 @@
|
||||
class="date-picker"
|
||||
></el-date-picker>
|
||||
|
||||
<el-button type="primary" icon="Search" class="search-btn"> 搜索 </el-button>
|
||||
<el-button type="primary" class="search-btn"> 搜索 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 5. 主内容区 -->
|
||||
<!-- 主内容区 -->
|
||||
<div class="content-container">
|
||||
<!-- 5.3 巡检记录(根据图片调整) -->
|
||||
<!-- 试验记录 -->
|
||||
<div v-if="activeTab === 'record'" class="record-container">
|
||||
<h2 class="section-title">试验记录与报告</h2>
|
||||
<p class="section-subtitle">截止至 {{ currentDate }}</p>
|
||||
@ -87,96 +89,62 @@
|
||||
|
||||
<!-- 试验记录列表 -->
|
||||
<div class="test-records">
|
||||
<!-- 数据库性能巡检记录 -->
|
||||
<div class="test-record-card passed">
|
||||
<!-- 动态生成试验记录卡片 -->
|
||||
<div
|
||||
v-for="(record, recordIndex) in testRecords"
|
||||
:key="record.id"
|
||||
class="test-record-card"
|
||||
:class="{ 'passed': record.status === 'completed', 'failed': record.status === 'failed' }"
|
||||
>
|
||||
<div class="record-header">
|
||||
<h3 class="record-title">数据库性能巡检</h3>
|
||||
<h3 class="record-title">{{ record.taskName || '试验任务' }}</h3>
|
||||
<p class="record-date">
|
||||
{{ testRecords[0].date }} <span class="record-time">耗时: {{ testRecords[0].duration }}</span>
|
||||
开始时间
|
||||
{{ formatDate(record.beginTime) }}
|
||||
<span class="record-time">计划完成时间: {{ record.planFinishTime ? formatDate(record.planFinishTime) : '未知' }}</span>
|
||||
</p>
|
||||
<span class="status-tag status-passed">通过</span>
|
||||
<span class="status-tag" :class="getStatusClass(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 试验进度 -->
|
||||
<div class="test-progress">
|
||||
<div class="progress-step active">
|
||||
<div class="step-number">1</div>
|
||||
<div class="step-name">准备环境</div>
|
||||
</div>
|
||||
<div class="progress-line active"></div>
|
||||
<div class="progress-step active">
|
||||
<div class="step-number">2</div>
|
||||
<div class="step-name">50%负载</div>
|
||||
</div>
|
||||
<div class="progress-line active"></div>
|
||||
<div class="progress-step active">
|
||||
<div class="step-number">3</div>
|
||||
<div class="step-name">80%负载</div>
|
||||
</div>
|
||||
<div class="progress-line active"></div>
|
||||
<div class="progress-step active">
|
||||
<div class="step-number">4</div>
|
||||
<div class="step-name">100%负载</div>
|
||||
<!-- 动态生成试验进度步骤条 -->
|
||||
<div class="test-progress" v-if="record.nodes && record.nodes.length">
|
||||
<template v-for="(node, index) in sortedNodes(record.nodes)" :key="node.id">
|
||||
<div class="progress-step" :class="getNodeStatusClass(node.status, record.status)">
|
||||
<div class="step-number">{{ node.code }}</div>
|
||||
<div class="step-name">步骤名称:{{ node.name }}</div>
|
||||
<div class="step-name">预期试验目的:{{ node.intendedPurpose }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 进度线,最后一个节点没有线 -->
|
||||
<div
|
||||
v-if="index < sortedNodes(record.nodes).length - 1"
|
||||
class="progress-line"
|
||||
:class="getLineStatusClass(index, sortedNodes(record.nodes), record.status)"
|
||||
></div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 试验结果 -->
|
||||
<div class="test-result">
|
||||
<h4 class="result-title">试验结果</h4>
|
||||
<p class="result-content">系统在100%负载下稳定运行1小时,CPU平均使用率92%,内存使用率88%,无崩溃或异常重启现象。</p>
|
||||
<p class="result-details">
|
||||
平均响应时间: {{ testRecords[0].responseTime }} | 错误率: {{ testRecords[0].errorRate }} | 温度值: {{ testRecords[0].temperature }}
|
||||
<div class="test-result" :class="{ 'failure-analysis': record.status === 'failed' }">
|
||||
<h4 class="result-title">
|
||||
{{ record.status === 'failed' ? '失败原因分析' : '试验结果' }}
|
||||
</h4>
|
||||
|
||||
<p class="result-content">
|
||||
{{ record.status === 'failed' ? record.failReason || '未提供失败原因' : record.testFinal || '试验完成,未提供详细结果' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="record-actions">
|
||||
<button class="operate-btn view-btn">查看详情</button>
|
||||
<button class="operate-btn report-btn">生成报告</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 1000用户并发测试记录 -->
|
||||
<div class="test-record-card failed">
|
||||
<div class="record-header">
|
||||
<h3 class="record-title">1000用户并发测试</h3>
|
||||
<p class="record-date">
|
||||
{{ testRecords[1].date }} <span class="record-time">耗时: {{ testRecords[1].duration }}</span>
|
||||
<p class="result-details" v-if="record.status !== 'failed'">
|
||||
计划时间: {{ formatDate(record.planBeginTime) }} | 进度: {{ record.progress }}% | 负责人:
|
||||
{{ record.personInfo?.userName || '未知' }}
|
||||
</p>
|
||||
<span class="status-tag status-failed">失败</span>
|
||||
</div>
|
||||
|
||||
<!-- 试验进度 -->
|
||||
<div class="test-progress">
|
||||
<div class="progress-step active">
|
||||
<div class="step-number">1</div>
|
||||
<div class="step-name">准备环境</div>
|
||||
</div>
|
||||
<div class="progress-line active"></div>
|
||||
<div class="progress-step active">
|
||||
<div class="step-number">2</div>
|
||||
<div class="step-name">300用户</div>
|
||||
</div>
|
||||
<div class="progress-line active"></div>
|
||||
<div class="progress-step active">
|
||||
<div class="step-number">3</div>
|
||||
<div class="step-name">500用户</div>
|
||||
</div>
|
||||
<div class="progress-line failed"></div>
|
||||
<div class="progress-step failed">
|
||||
<div class="step-number">4</div>
|
||||
<div class="step-name">800用户</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 失败原因分析 -->
|
||||
<div class="test-result failure-analysis">
|
||||
<h4 class="result-title">失败原因分析</h4>
|
||||
<p class="result-content">当并发用户数达到780人时,数据库连接耗尽,新用户无法建立数据库连接,导致系统响应超时。</p>
|
||||
|
||||
<!-- 改进建议 -->
|
||||
<div class="improvement-suggestion">
|
||||
<!-- 改进建议(仅失败时显示) -->
|
||||
<div class="improvement-suggestion" v-if="record.status === 'failed' && record.faileTips">
|
||||
<i class="fas fa-lightbulb"></i>
|
||||
<p>建议: 增加数据库连接池最大连接数,优化长连接超时时间,增加连接复用机制分析评估。</p>
|
||||
<p>建议: {{ record.faileTips }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -185,10 +153,13 @@
|
||||
<button class="operate-btn report-btn">生成报告</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 无数据提示 -->
|
||||
<div v-if="!testRecords.length" class="no-records">暂无试验记录数据</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 5.1 巡检计划表格 -->
|
||||
<!-- 巡检计划表格 -->
|
||||
<div v-if="activeTab === 'plan'" class="table-container">
|
||||
<el-table :data="planTableData" border>
|
||||
<el-table-column prop="name" label="计划名称" width="220">
|
||||
@ -228,7 +199,7 @@
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 5.2 巡检任务表格 -->
|
||||
<!-- 巡检任务表格 -->
|
||||
<div v-if="activeTab === 'task'" class="table-container">
|
||||
<el-table :data="taskTableData" border>
|
||||
<el-table-column prop="name" label="任务名称" width="220"></el-table-column>
|
||||
@ -257,7 +228,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 6. 分页 -->
|
||||
<!-- 分页 -->
|
||||
<div class="pagination" v-if="activeTab !== 'record'">
|
||||
<p class="total-records">显示1到{{ pageSize }}条,共{{ totalRecords }}条记录</p>
|
||||
<el-pagination
|
||||
@ -275,11 +246,12 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import router from '@/router';
|
||||
import { syrenwulist, syrenwujilu, syrenwuDetail } from '@/api/zhinengxunjian/shiyan/renwu';
|
||||
|
||||
// 1. 选项卡状态管理
|
||||
const activeTab = ref('record'); // 默认显示"巡检记录"
|
||||
const activeTab = ref('record'); // 默认显示"试验记录"
|
||||
const showFilter = ref(false);
|
||||
|
||||
// 2. 筛选条件
|
||||
@ -287,6 +259,11 @@ const filterStatus = ref('all');
|
||||
const filterType = ref('all');
|
||||
const dateRange = ref([]);
|
||||
|
||||
// 3. 试验记录数据
|
||||
const testRecords = ref([]);
|
||||
const planTableData = ref([]);
|
||||
const taskTableData = ref([]);
|
||||
|
||||
// 4. 当前日期
|
||||
const currentDate = computed(() => {
|
||||
const date = new Date();
|
||||
@ -297,28 +274,178 @@ const currentDate = computed(() => {
|
||||
|
||||
// 5. 统计数据
|
||||
const statData = ref({
|
||||
completed: 12,
|
||||
passRate: 83,
|
||||
pendingAnalysis: 3,
|
||||
avgDuration: '42分钟'
|
||||
completed: 0,
|
||||
passRate: 0,
|
||||
pendingAnalysis: 0,
|
||||
avgDuration: '0分钟'
|
||||
});
|
||||
|
||||
// 6. 试验记录数据
|
||||
const testRecords = ref([
|
||||
{
|
||||
date: '2025-06-15',
|
||||
duration: '1小时45分钟',
|
||||
responseTime: '1.2s',
|
||||
errorRate: '0%',
|
||||
temperature: '72°C'
|
||||
},
|
||||
{
|
||||
date: '2025-06-12',
|
||||
duration: '2小时10分钟'
|
||||
}
|
||||
]);
|
||||
// 6. 分页相关
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(20);
|
||||
const totalRecords = ref(0);
|
||||
|
||||
// 9. 方法:切换顶部导航
|
||||
// 7. 方法:获取试验记录数据
|
||||
const getTestRecords = async () => {
|
||||
try {
|
||||
const response = await syrenwulist({
|
||||
projectId: 1,
|
||||
page: currentPage.value,
|
||||
size: pageSize.value
|
||||
});
|
||||
console.log('syrenwulist API响应:', response);
|
||||
|
||||
if (response && response.code === 200 && response.rows) {
|
||||
testRecords.value = response.rows;
|
||||
totalRecords.value = response.total;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取试验记录失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 8. 方法:获取统计数据
|
||||
const getStatisticsData = async () => {
|
||||
try {
|
||||
const response = await syrenwujilu({ projectId: 1 });
|
||||
console.log('syrenwujilu API响应:', response);
|
||||
|
||||
if (response && response.data) {
|
||||
// 映射API返回的数据到statData
|
||||
const apiData = response.data;
|
||||
|
||||
statData.value.completed = parseInt(apiData.finishCount) || 0;
|
||||
statData.value.passRate = parseFloat(apiData.passValue) || 0;
|
||||
statData.value.pendingAnalysis = parseInt(apiData.failCount) || 0;
|
||||
|
||||
// 格式化平均试验时长
|
||||
const avgTime = parseInt(apiData.averageTestTime) || 0;
|
||||
statData.value.avgDuration = `${avgTime}分钟`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取统计数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 9. 辅助方法:对节点按code排序
|
||||
const sortedNodes = (nodes) => {
|
||||
return [...nodes].sort((a, b) => a.code - b.code);
|
||||
};
|
||||
|
||||
// 10. 辅助方法:格式化日期
|
||||
const formatDate = (dateString) => {
|
||||
if (!dateString) return '未知日期';
|
||||
const date = new Date(dateString);
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
// 11. 辅助方法:格式化时长(假设单位为分钟)
|
||||
const formatDuration = (minutes) => {
|
||||
if (minutes < 60) {
|
||||
return `${minutes}分钟`;
|
||||
} else {
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const mins = minutes % 60;
|
||||
return `${hours}小时${mins}分钟`;
|
||||
}
|
||||
};
|
||||
|
||||
// 12. 辅助方法:获取节点状态类名
|
||||
const getNodeStatusClass = (nodeStatus, recordStatus) => {
|
||||
// 节点状态: 2-未完成, 其他假设为已完成
|
||||
// 记录状态: 'failed'-失败, 'completed'-完成, 其他为进行中
|
||||
if (recordStatus === 'failed') {
|
||||
// 如果记录失败,找到失败阶段的节点
|
||||
return nodeStatus === '2' ? 'failed' : 'active';
|
||||
} else if (recordStatus === 'completed') {
|
||||
return 'active';
|
||||
} else {
|
||||
// 进行中状态,已完成的节点标记为active
|
||||
return nodeStatus !== '2' ? 'active' : '';
|
||||
}
|
||||
};
|
||||
|
||||
// 13. 辅助方法:获取进度线状态类名
|
||||
const getLineStatusClass = (index, nodes, recordStatus) => {
|
||||
// 如果记录失败,找到第一个未完成的节点前的线为active
|
||||
if (recordStatus === 'failed') {
|
||||
return nodes[index].status !== '2' ? 'active' : 'failed';
|
||||
} else if (recordStatus === 'completed') {
|
||||
return 'active';
|
||||
} else {
|
||||
// 进行中状态,已完成节点之间的线为active
|
||||
return nodes[index].status !== '2' ? 'active' : '';
|
||||
}
|
||||
};
|
||||
|
||||
// 14. 辅助方法:获取进度颜色
|
||||
const getProgressColor = (status) => {
|
||||
const colorMap = {
|
||||
'drafted': '#e5e7eb',
|
||||
'in-progress': '#165dff',
|
||||
'completed': '#00b42a',
|
||||
'paused': '#86909c'
|
||||
};
|
||||
return colorMap[status] || '#e5e7eb';
|
||||
};
|
||||
|
||||
// 15. 辅助方法:获取状态文本
|
||||
const getStatusText = (status) => {
|
||||
const statusMap = {
|
||||
'1': '进行中',
|
||||
'completed': '通过',
|
||||
'failed': '失败',
|
||||
'paused': '已暂停',
|
||||
'drafted': '草稿',
|
||||
'in-progress': '进行中',
|
||||
'normal': '正常',
|
||||
'attention': '需关注',
|
||||
'problem': '有问题'
|
||||
};
|
||||
return statusMap[status] || '未知状态';
|
||||
};
|
||||
|
||||
// 16. 辅助方法:获取任务状态文本
|
||||
const getTaskStatusText = (status) => {
|
||||
const statusMap = {
|
||||
'pending': '待接受',
|
||||
'accepted': '进行中',
|
||||
'completed': '已完成',
|
||||
'rejected': '已拒绝'
|
||||
};
|
||||
return statusMap[status] || '未知状态';
|
||||
};
|
||||
|
||||
// 17. 辅助方法:获取状态类名
|
||||
const getStatusClass = (status) => {
|
||||
const classMap = {
|
||||
'1': 'status-in-progress',
|
||||
'completed': 'status-passed',
|
||||
'failed': 'status-failed',
|
||||
'paused': 'status-paused',
|
||||
'pending': 'status-pending',
|
||||
'accepted': 'status-accepted',
|
||||
'rejected': 'status-rejected',
|
||||
'normal': 'status-normal',
|
||||
'attention': 'status-attention',
|
||||
'problem': 'status-problem'
|
||||
};
|
||||
return classMap[status] || 'status-pending';
|
||||
};
|
||||
|
||||
// 18. 分页事件处理
|
||||
const handlePageChange = (page) => {
|
||||
currentPage.value = page;
|
||||
getTestRecords();
|
||||
};
|
||||
|
||||
const handleSizeChange = (size) => {
|
||||
pageSize.value = size;
|
||||
currentPage.value = 1;
|
||||
getTestRecords();
|
||||
};
|
||||
|
||||
// 19. 导航方法
|
||||
const handleInspection1 = () => {
|
||||
router.push('/rili/rili');
|
||||
};
|
||||
@ -341,17 +468,28 @@ const handleInspection7 = () => {
|
||||
router.push('/rili/renyuanzhuangtai');
|
||||
};
|
||||
const handleInspectionManagement1 = () => {
|
||||
activeTab.value = 'plan';
|
||||
router.push('/rili/shiyanguanli');
|
||||
};
|
||||
const handleInspectionManagement2 = () => {
|
||||
activeTab.value = 'task';
|
||||
router.push('/rili/shiyanrenwu');
|
||||
};
|
||||
const handleInspectionManagement3 = () => {
|
||||
activeTab.value = 'record';
|
||||
router.push('/rili/shiyanjilu');
|
||||
};
|
||||
|
||||
// 20. 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
getStatisticsData();
|
||||
getTestRecords();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import url('./css/detail-dialog.css');
|
||||
@import url('./css/step-bars.css');
|
||||
/* 1. 基础容器样式 */
|
||||
.operation-inspection {
|
||||
padding: 20px;
|
||||
@ -359,44 +497,38 @@ const handleInspectionManagement3 = () => {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 导航栏样式 */
|
||||
/* 2. 顶部导航选项卡 */
|
||||
.navigation-tabs {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||
padding: 2px;
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nav-tab {
|
||||
padding: 12px 24px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
color: #6b7280;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.nav-tab:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.nav-tab:hover {
|
||||
color: #409eff;
|
||||
background-color: #ecf5ff;
|
||||
.nav-tab:hover:not(.active) {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.nav-tab.active {
|
||||
background-color: #409eff;
|
||||
background-color: #165dff;
|
||||
color: #fff;
|
||||
box-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 选项卡样式 */
|
||||
/* 3. 选项卡样式 */
|
||||
.tabs-wrapper {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
@ -405,34 +537,20 @@ const handleInspectionManagement3 = () => {
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 筛选栏样式 */
|
||||
.filter-bar {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
/* 4. 头部容器 */
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.filter-bar .el-select,
|
||||
.filter-bar .el-date-picker {
|
||||
width: 150px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* 5. 筛选和操作区域 */
|
||||
.filter-and-actions {
|
||||
display: flex;
|
||||
@ -462,8 +580,8 @@ const handleInspectionManagement3 = () => {
|
||||
}
|
||||
.search-btn,
|
||||
.export-btn {
|
||||
height: 36px;
|
||||
border-radius: 4px;
|
||||
background-color: #165dff;
|
||||
border-color: #165dff;
|
||||
}
|
||||
.filter-btn {
|
||||
background-color: #f3f4f6;
|
||||
@ -662,17 +780,7 @@ const handleInspectionManagement3 = () => {
|
||||
margin: 0 0 20px 0;
|
||||
text-align: right;
|
||||
}
|
||||
/* 头部容器 - 替换了固定gap的flex布局 */
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
/* 12. 统计卡片样式 */
|
||||
.stat-grid {
|
||||
display: grid;
|
||||
@ -811,36 +919,35 @@ const handleInspectionManagement3 = () => {
|
||||
|
||||
.progress-line {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
border-top: 2px dashed #e5e7eb;
|
||||
margin: 10px 0;
|
||||
height: 2px;
|
||||
background-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.progress-step.active .step-number {
|
||||
background-color: #10b981;
|
||||
background-color: #165dff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.progress-step.active .step-name {
|
||||
color: #10b981;
|
||||
color: #165dff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.progress-line.active {
|
||||
border-top-color: #10b981;
|
||||
background-color: #165dff;
|
||||
}
|
||||
|
||||
.progress-step.failed .step-number {
|
||||
background-color: red;
|
||||
background-color: #dc2626;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.progress-step.failed .step-name {
|
||||
color: red;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.progress-line.failed {
|
||||
border-top-color: red;
|
||||
background-color: #dc2626;
|
||||
}
|
||||
|
||||
/* 15. 试验结果样式 */
|
||||
@ -906,7 +1013,17 @@ const handleInspectionManagement3 = () => {
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
/* 17. 响应式适配 */
|
||||
/* 17. 无数据提示样式 */
|
||||
.no-records {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
color: #6b7280;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
/* 18. 响应式适配 */
|
||||
@media (max-width: 1200px) {
|
||||
.stat-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
|
||||
@ -137,7 +137,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 添加新任务弹窗 -->
|
||||
<el-dialog v-model="createTaskDialogVisible" title="添加新任务" width="500px" :before-close="handleCancelCreateTask">
|
||||
<el-dialog v-model="createTaskDialogVisible" title="添加新任务" width="700px" :before-close="handleCancelCreateTask">
|
||||
<el-form ref="createTaskFormRef" :model="createTaskForm" :rules="createTaskRules" label-width="80px">
|
||||
<el-form-item label="任务名称" prop="taskName">
|
||||
<el-input v-model="createTaskForm.taskName" placeholder="输入任务名称" />
|
||||
@ -192,6 +192,27 @@
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 步骤条区域 -->
|
||||
<el-form-item label="执行步骤" prop="steps">
|
||||
<div class="steps-container">
|
||||
<div class="step-item" v-for="(step, index) in createTaskForm.steps" :key="index">
|
||||
<div class="step-number">{{ index + 1 }}</div>
|
||||
<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="createTaskForm.steps.length > 1" type="text" @click="removeStep(index)" style="color: #f56c6c"> 删除 </el-button>
|
||||
</div>
|
||||
<el-button type="text" class="add-step-btn" @click="addStep">添加步骤</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@ -215,7 +236,7 @@
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">任务状态:</span>
|
||||
<span class="info-value" :class="getStatusClass(detailData.status)">
|
||||
<span class="info-value" :class="getTaskStatusClass(detailData.status)">
|
||||
{{ getStatusText(detailData.status) }}
|
||||
</span>
|
||||
</div>
|
||||
@ -260,7 +281,7 @@
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">联系电话:</span>
|
||||
<span class="info-value">{{ detailData.personInfo.phone }}</span>
|
||||
<span class="info-value">{{ detailData.personInfo.phonenumber }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="detailData.personInfo" class="info-row">
|
||||
@ -268,10 +289,6 @@
|
||||
<span class="info-label">性别:</span>
|
||||
<span class="info-value">{{ detailData.personInfo.sex === '1' ? '男' : '女' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">民族:</span>
|
||||
<span class="info-value">{{ detailData.personInfo.nation }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-info">暂无执行人信息</div>
|
||||
</div>
|
||||
@ -307,6 +324,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 步骤条 -->
|
||||
<div v-if="detailData.nodes && detailData.nodes.length > 0" class="detail-card">
|
||||
<h3 class="card-title">执行步骤</h3>
|
||||
<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>
|
||||
|
||||
<!-- 任务执行结果信息卡片(如果有) -->
|
||||
<div v-if="detailData.testFinal || detailData.failReason" class="detail-card">
|
||||
<h3 class="card-title">执行结果信息</h3>
|
||||
@ -346,8 +383,10 @@ import router from '@/router';
|
||||
import { syrenwulist, syrenwuDetail, addsyrenwu, updatesyrenwu } from '@/api/zhinengxunjian/shiyan/renwu';
|
||||
import { shiyanlist } from '@/api/zhinengxunjian/shiyan';
|
||||
import { xunjianUserlist } from '@/api/zhinengxunjian/xunjian/index';
|
||||
import { addjiedian } from '@/api/zhinengxunjian/jiedian/index';
|
||||
// 引入Element Plus组件(提示/空状态/骨架屏/弹窗)
|
||||
import { ElMessage, ElEmpty, ElSkeleton, ElForm, ElMessageBox } from 'element-plus';
|
||||
import { Plus } from '@element-plus/icons-vue';
|
||||
|
||||
/**
|
||||
* 根据任务ID获取完整的任务详情数据
|
||||
@ -379,6 +418,25 @@ const loading = ref(false);
|
||||
// 筛选条件(与接口参数对应)
|
||||
const taskStatus = ref(''); // 任务状态:1=待执行,2=暂停(已延期),3=失败,4=执行中,5=已完成
|
||||
const planType = ref(''); // 关联计划ID:1=每日,2=每周,3=每月
|
||||
|
||||
/**
|
||||
* 将节点数据按模块分组
|
||||
* @param {Array} nodes - 节点数据数组
|
||||
* @returns {Array} 分组后的模块数组
|
||||
*/
|
||||
const groupNodesByModule = (nodes) => {
|
||||
if (!nodes || !Array.isArray(nodes)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 这里简单地将所有节点放在一个默认模块下,实际应用中可以根据节点数据的module字段进行分组
|
||||
const defaultGroup = {
|
||||
module: '测试步骤',
|
||||
items: nodes
|
||||
};
|
||||
|
||||
return [defaultGroup];
|
||||
};
|
||||
const executor = ref('all'); // 执行人ID:all=全部
|
||||
|
||||
// 用户列表(通过xunjianUserlist接口获取)
|
||||
@ -420,15 +478,58 @@ const getStatusText = (status) => {
|
||||
* @param {string} status - 任务状态码
|
||||
* @returns {string} 样式类名
|
||||
*/
|
||||
|
||||
/**
|
||||
* 获取步骤状态对应的样式类
|
||||
* @param {string|number} status - 步骤状态码
|
||||
* @returns {string} 样式类名
|
||||
*/
|
||||
const getStatusClass = (status) => {
|
||||
// 处理可能的数字输入
|
||||
const statusStr = status?.toString() || '';
|
||||
const statusClassMap = {
|
||||
'1': 'status-pending',
|
||||
'2': 'status-delayed',
|
||||
'3': 'status-failed',
|
||||
'4': 'status-running',
|
||||
'5': 'status-completed'
|
||||
'3': 'status-executing',
|
||||
'4': 'status-completed'
|
||||
};
|
||||
return statusClassMap[status] || '';
|
||||
return statusClassMap[statusStr] || 'status-unknown';
|
||||
};
|
||||
|
||||
/**
|
||||
* 格式化日期时间(用于步骤条)
|
||||
* @param {string} dateTime - 日期时间字符串
|
||||
* @returns {string} 格式化后的日期时间
|
||||
*/
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取步骤状态文本
|
||||
* @param {string|number} status - 步骤状态码
|
||||
* @returns {string} 状态文本
|
||||
*/
|
||||
const getStepStatusText = (status) => {
|
||||
const statusStr = status?.toString() || '';
|
||||
const statusMap = {
|
||||
'1': '待执行',
|
||||
'2': '执行中',
|
||||
'3': '已完成',
|
||||
'4': '已延期'
|
||||
};
|
||||
return statusMap[statusStr] || '未知状态';
|
||||
};
|
||||
|
||||
// 创建任务弹窗
|
||||
@ -441,7 +542,8 @@ const createTaskForm = ref({
|
||||
relatedPlan: '', // 关联计划ID(接口testPlanId)
|
||||
executor: '', // 执行人ID(接口person)
|
||||
workTimeRange1: null, // 工作时间段1
|
||||
workTimeRange2: null // 工作时间段2
|
||||
workTimeRange2: null, // 工作时间段2
|
||||
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }] // 步骤数据数组
|
||||
});
|
||||
// 创建任务表单规则
|
||||
const createTaskRules = {
|
||||
@ -453,6 +555,21 @@ const createTaskRules = {
|
||||
executor: [{ required: true, message: '请选择执行人', trigger: 'change' }]
|
||||
};
|
||||
|
||||
// 添加步骤
|
||||
const addStep = () => {
|
||||
createTaskForm.value.steps.push({ name: '', intendedPurpose: '', intendedTime: '' });
|
||||
};
|
||||
|
||||
// 删除步骤
|
||||
const removeStep = (index) => {
|
||||
// 确保至少保留一个步骤
|
||||
if (createTaskForm.value.steps.length <= 1) {
|
||||
ElMessage.warning('至少需要保留一个步骤');
|
||||
return;
|
||||
}
|
||||
createTaskForm.value.steps.splice(index, 1);
|
||||
};
|
||||
|
||||
// 构建timeInfo字符串
|
||||
const getTaskTimeInfoString = () => {
|
||||
const timeInfoArray = [];
|
||||
@ -831,6 +948,48 @@ const handleSaveTask = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证所有步骤
|
||||
const hasEmptyStep = createTaskForm.value.steps.some((step) => !step.name.trim() || !step.intendedPurpose.trim());
|
||||
if (hasEmptyStep) {
|
||||
ElMessage.warning('请填写完整所有步骤信息');
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理步骤数据
|
||||
let nodeIds = '';
|
||||
if (createTaskForm.value.steps && createTaskForm.value.steps.length > 0) {
|
||||
// 过滤非空步骤并映射为所需格式
|
||||
const validSteps = createTaskForm.value.steps
|
||||
.filter((step) => step.name.trim() && step.intendedPurpose.trim())
|
||||
.map((step, index) => ({
|
||||
createTime: new Date().toISOString(),
|
||||
updateTime: new Date().toISOString(),
|
||||
params: {},
|
||||
module: 3,
|
||||
code: index + 1,
|
||||
name: step.name,
|
||||
intendedPurpose: step.intendedPurpose,
|
||||
intendedTime: step.intendedTime ? new Date(step.intendedTime).toISOString() : new Date().toISOString(),
|
||||
finishTime: '',
|
||||
remark: '',
|
||||
status: 2
|
||||
}));
|
||||
|
||||
if (validSteps.length > 0) {
|
||||
try {
|
||||
// 调用addjiedian接口获取nodeIds
|
||||
const jiedianResponse = await addjiedian(validSteps);
|
||||
if (jiedianResponse.code === 200 && jiedianResponse.msg) {
|
||||
nodeIds = jiedianResponse.msg; // 直接使用字符串格式,不转换为数组
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('添加节点失败:', error);
|
||||
ElMessage.error('添加执行步骤失败');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const createParams = {
|
||||
createDept: 0, // 可根据实际情况从全局状态获取
|
||||
createBy: 0, // 可根据实际情况从全局状态获取当前用户ID
|
||||
@ -863,7 +1022,8 @@ const handleSaveTask = async () => {
|
||||
finalInfo: '',
|
||||
pauseFor: '',
|
||||
pauseTime: now.toISOString(),
|
||||
planFinishTime: createTaskForm.value.timeRange[1] // 计划完成时间
|
||||
planFinishTime: createTaskForm.value.timeRange[1], // 计划完成时间
|
||||
nodeIds: nodeIds // 步骤节点ID数组
|
||||
};
|
||||
|
||||
// 3. 调用创建接口
|
||||
@ -904,7 +1064,10 @@ const handleCancelCreateTask = () => {
|
||||
inspectionTarget: '',
|
||||
timeRange: [],
|
||||
relatedPlan: '',
|
||||
executor: ''
|
||||
executor: '',
|
||||
workTimeRange1: null,
|
||||
workTimeRange2: null,
|
||||
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }]
|
||||
};
|
||||
};
|
||||
|
||||
@ -954,6 +1117,9 @@ const pagedTasks = computed(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import url('./css/step-bars.css');
|
||||
@import url('./css/detail-dialog.css');
|
||||
|
||||
/* 原有样式不变,新增无数据提示样式 */
|
||||
.inspection-tasks {
|
||||
padding: 20px;
|
||||
@ -1068,6 +1234,27 @@ const pagedTasks = computed(() => {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
|
||||
/* 自定义步骤条样式覆盖 */
|
||||
.custom-steps .el-step__description {
|
||||
white-space: pre-wrap;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.module-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.module-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
}
|
||||
|
||||
/* 卡片悬停效果 */
|
||||
.task-card:hover {
|
||||
transform: translateY(-3px);
|
||||
@ -1206,7 +1393,8 @@ const pagedTasks = computed(() => {
|
||||
color: #165dff;
|
||||
}
|
||||
|
||||
.start-btn {
|
||||
.start-btn,
|
||||
.report-btn {
|
||||
background-color: #165dff;
|
||||
border-color: #165dff;
|
||||
}
|
||||
@ -1221,11 +1409,6 @@ const pagedTasks = computed(() => {
|
||||
border-color: #00b42a;
|
||||
}
|
||||
|
||||
.report-btn {
|
||||
background-color: #86909c;
|
||||
border-color: #86909c;
|
||||
}
|
||||
|
||||
/* 分页区域样式 */
|
||||
.pagination-section {
|
||||
display: flex;
|
||||
|
||||
@ -133,7 +133,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 添加新任务弹窗 -->
|
||||
<el-dialog v-model="createTaskDialogVisible" title="添加新任务" width="500px" :before-close="handleCancelCreateTask">
|
||||
<el-dialog v-model="createTaskDialogVisible" title="添加新任务" width="700px" :before-close="handleCancelCreateTask">
|
||||
<el-form ref="createTaskFormRef" :model="createTaskForm" :rules="createTaskRules" label-width="80px">
|
||||
<el-form-item label="任务名称" prop="taskName">
|
||||
<el-input v-model="createTaskForm.taskName" placeholder="输入任务名称" />
|
||||
@ -211,6 +211,27 @@
|
||||
<el-option label="设备运行状态" value="5" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 步骤条 -->
|
||||
<el-form-item label="执行步骤" class="form-item" style="width: 100%">
|
||||
<div class="steps-container">
|
||||
<div class="step-item" v-for="(step, index) in createTaskForm.steps" :key="index">
|
||||
<div class="step-number">{{ index + 1 }}</div>
|
||||
<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="createTaskForm.steps.length > 1" type="text" @click="removeStep(index)" style="color: #f56c6c"> 删除 </el-button>
|
||||
</div>
|
||||
<el-button type="text" class="add-step-btn" @click="addStep">添加步骤</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
@ -304,7 +325,7 @@
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">联系电话:</span>
|
||||
<span class="info-value">{{ detailData.person?.phone || '-' }}</span>
|
||||
<span class="info-value">{{ detailData.person?.phonenumber || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
@ -312,10 +333,6 @@
|
||||
<span class="info-label">性别:</span>
|
||||
<span class="info-value">{{ detailData.person?.sex === '1' ? '男' : detailData.person?.sex === '2' ? '女' : '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">民族:</span>
|
||||
<span class="info-value">{{ detailData.person?.nation || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -356,6 +373,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 执行步骤信息卡片 -->
|
||||
<div v-if="detailData.nodes && detailData.nodes.length > 0" class="detail-card">
|
||||
<h3 class="card-title">执行步骤</h3>
|
||||
<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>
|
||||
|
||||
<!-- 执行结果信息卡片 -->
|
||||
<div v-if="detailData.taskType === '2' || detailData.taskType === 2" class="detail-card">
|
||||
<h3 class="card-title">延期信息</h3>
|
||||
@ -388,37 +425,10 @@
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import router from '@/router';
|
||||
|
||||
import { xjrenwuDetail, xjrenwuExport, xjrenwulist, addxjrenwu, updatexjrenwu, delxjrenwu } from '@/api/zhinengxunjian/xunjian/renwu';
|
||||
import { xjrenwuDetail, xjrenwulist, addxjrenwu, updatexjrenwu } from '@/api/zhinengxunjian/xunjian/renwu';
|
||||
import { xunjianUserlist, xunjianlist } from '@/api/zhinengxunjian/xunjian/index';
|
||||
import { ElMessage, ElLoading } from 'element-plus';
|
||||
|
||||
// 根据任务类型获取对应的文本(1待执行2已延期3执行中4已完成)
|
||||
const getTaskTypeText = (type) => {
|
||||
const typeMap = {
|
||||
'1': '待执行',
|
||||
'2': '已延期',
|
||||
'3': '执行中',
|
||||
'4': '已完成'
|
||||
};
|
||||
// 处理可能的数字输入
|
||||
return typeMap[type.toString()] || '未知类型';
|
||||
};
|
||||
|
||||
// 根据问题类型获取对应的文本(1磁盘使用率2内存使用率3服务状态4响应时间5设备运行状态)
|
||||
const getProblemTypeText = (type) => {
|
||||
const problemTypeMap = {
|
||||
'1': '磁盘使用率',
|
||||
'2': '内存使用率',
|
||||
'3': '服务状态',
|
||||
'4': '响应时间',
|
||||
'5': '设备运行状态'
|
||||
};
|
||||
// 处理可能的数字输入
|
||||
return problemTypeMap[type.toString()] || '未知问题';
|
||||
};
|
||||
|
||||
// 激活的选项卡
|
||||
const activeTab = ref('task');
|
||||
import { addjiedian } from '@/api/zhinengxunjian/jiedian/index';
|
||||
import { ElMessage, ElLoading, ElForm } from 'element-plus';
|
||||
|
||||
// 筛选条件
|
||||
const taskStatus = ref('');
|
||||
@ -428,7 +438,7 @@ const executor = ref('');
|
||||
// 任务数据 - 初始为空数组,通过API获取
|
||||
const tasks = ref([]);
|
||||
|
||||
// 详情弹窗相关变量
|
||||
// 任务详情弹窗相关变量
|
||||
const detailDialogVisible = ref(false);
|
||||
const detailData = ref(null);
|
||||
const isDetailLoading = ref(false);
|
||||
@ -459,6 +469,34 @@ const getStatusClass = (status) => {
|
||||
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 statusConfig = {
|
||||
pending: {
|
||||
@ -505,11 +543,7 @@ const getTaskList = async () => {
|
||||
const params = {
|
||||
pageSize: pageSize.value,
|
||||
pageNum: currentPage.value,
|
||||
personId: executor.value !== '' ? executor.value : undefined,
|
||||
// 根据任务状态映射到后端需要的taskType
|
||||
taskType: taskStatus.value ? mapTaskStatusToType(taskStatus.value) : undefined,
|
||||
// 添加计划类型筛选
|
||||
planType: planType.value || undefined
|
||||
personId: 1
|
||||
};
|
||||
|
||||
const response = await xjrenwulist(params);
|
||||
@ -583,44 +617,6 @@ const getTaskList = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 辅助函数:将前端状态映射为后端需要的taskType
|
||||
const mapTaskStatusToType = (status) => {
|
||||
const statusMap = {
|
||||
'pending': '1',
|
||||
'delayed': '2',
|
||||
'executing': '3',
|
||||
'completed': '4'
|
||||
};
|
||||
return statusMap[status] || '';
|
||||
};
|
||||
|
||||
// 根据person对象获取执行人姓名
|
||||
const getExecutorName = (person) => {
|
||||
if (person && typeof person === 'object' && person.userName) {
|
||||
return person.userName;
|
||||
}
|
||||
const executorMap = {
|
||||
'zhangming': '张明',
|
||||
'lihua': '李华',
|
||||
'wangqiang': '王强',
|
||||
'zhaowei': '赵伟'
|
||||
};
|
||||
return executorMap[person] || '未知用户';
|
||||
};
|
||||
|
||||
// 根据plan对象获取计划名称
|
||||
const getPlanName = (plan) => {
|
||||
if (plan && typeof plan === 'object' && plan.planName) {
|
||||
return plan.planName;
|
||||
}
|
||||
const planMap = {
|
||||
'daily': '每日巡检计划',
|
||||
'weekly': '每周巡检计划',
|
||||
'monthly': '每月巡检计划'
|
||||
};
|
||||
return planMap[plan] || '未知计划';
|
||||
};
|
||||
|
||||
// 页面加载时获取数据
|
||||
onMounted(() => {
|
||||
getTaskList();
|
||||
@ -675,10 +671,11 @@ const createTaskForm = ref({
|
||||
timeRange: [],
|
||||
workTimeRange1: null,
|
||||
workTimeRange2: null,
|
||||
relatedPlan: 'all',
|
||||
relatedPlan: '',
|
||||
executor: '',
|
||||
taskType: '1', // 默认待执行
|
||||
problemType: ''
|
||||
problemType: '',
|
||||
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }] // 任务步骤数组
|
||||
});
|
||||
|
||||
const createTaskRules = {
|
||||
@ -694,6 +691,17 @@ const handleCreateTask = () => {
|
||||
openCreateTaskDialog();
|
||||
};
|
||||
|
||||
// 重置步骤表单
|
||||
const resetStepForm = () => {
|
||||
Object.keys(stepForm).forEach((key) => {
|
||||
stepForm[key] = '';
|
||||
});
|
||||
currentStep.value = 0;
|
||||
if (stepFormRef.value) stepFormRef.value.resetFields();
|
||||
if (deviceFormRef.value) deviceFormRef.value.resetFields();
|
||||
if (faultFormRef.value) faultFormRef.value.resetFields();
|
||||
};
|
||||
|
||||
// 构建timeInfo字符串
|
||||
const getTaskTimeInfoString = () => {
|
||||
const timeInfoArray = [];
|
||||
@ -719,6 +727,21 @@ const getTaskTimeInfoString = () => {
|
||||
return timeInfoArray.join(',');
|
||||
};
|
||||
|
||||
// 添加步骤
|
||||
const addStep = () => {
|
||||
createTaskForm.value.steps.push({ name: '', intendedPurpose: '', intendedTime: '' });
|
||||
};
|
||||
|
||||
// 删除步骤
|
||||
const removeStep = (index) => {
|
||||
// 确保至少保留一个步骤
|
||||
if (createTaskForm.value.steps.length <= 1) {
|
||||
ElMessage.warning('至少需要保留一个步骤');
|
||||
return;
|
||||
}
|
||||
createTaskForm.value.steps.splice(index, 1);
|
||||
};
|
||||
|
||||
// 保存任务
|
||||
const handleSaveTask = async () => {
|
||||
// 表单验证
|
||||
@ -727,6 +750,13 @@ const handleSaveTask = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证所有步骤
|
||||
const hasEmptyStep = createTaskForm.value.steps.some((step) => !step.name.trim() || !step.intendedPurpose.trim());
|
||||
if (hasEmptyStep) {
|
||||
ElMessage.warning('请填写完整所有步骤信息');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取timeInfo字符串
|
||||
const taskTimeInfo = getTaskTimeInfoString();
|
||||
@ -736,12 +766,45 @@ const handleSaveTask = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 准备步骤数据,与工单列表页面保持一致的格式
|
||||
const stepsData = createTaskForm.value.steps
|
||||
.filter((step) => step.name.trim() && step.intendedPurpose.trim())
|
||||
.map((step, index) => ({
|
||||
createTime: new Date().toISOString(),
|
||||
updateTime: new Date().toISOString(),
|
||||
params: {},
|
||||
module: 2,
|
||||
code: index + 1,
|
||||
name: step.name,
|
||||
intendedPurpose: step.intendedPurpose,
|
||||
intendedTime: step.intendedTime ? new Date(step.intendedTime).toISOString() : new Date().toISOString(),
|
||||
finishTime: '',
|
||||
remark: '',
|
||||
status: 2
|
||||
}));
|
||||
|
||||
// 调用添加节点接口,直接传递步骤数组
|
||||
const jiedianResponse = await addjiedian(stepsData);
|
||||
|
||||
if (jiedianResponse.code !== 200) {
|
||||
ElMessage.error('创建步骤失败');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取返回的ids,实际返回格式中msg字段包含ids字符串,data为null
|
||||
let nodeIds = '';
|
||||
if (jiedianResponse.code === 200 && jiedianResponse.msg) {
|
||||
nodeIds = jiedianResponse.msg;
|
||||
} else {
|
||||
ElMessage.warning('未获取到有效的步骤ID');
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建接口所需的数据结构
|
||||
const apiData = {
|
||||
createDept: 0,
|
||||
createBy: 0,
|
||||
projectId: 1,
|
||||
|
||||
createTime: new Date().toISOString(),
|
||||
updateBy: 0,
|
||||
updateTime: new Date().toISOString(),
|
||||
params: {
|
||||
property1: 'string',
|
||||
@ -757,7 +820,8 @@ const handleSaveTask = async () => {
|
||||
personId: createTaskForm.value.executor !== 'all' ? createTaskForm.value.executor : 0,
|
||||
taskProgress: 0,
|
||||
taskType: createTaskForm.value.taskType,
|
||||
problemType: createTaskForm.value.problemType
|
||||
problemType: createTaskForm.value.problemType,
|
||||
nodeIds: nodeIds // 添加步骤ID字符串,与工单列表页面保持一致
|
||||
};
|
||||
|
||||
// 调用新增任务接口
|
||||
@ -774,10 +838,11 @@ const handleSaveTask = async () => {
|
||||
timeRange: [],
|
||||
workTimeRange1: null,
|
||||
workTimeRange2: null,
|
||||
relatedPlan: 'all',
|
||||
relatedPlan: '',
|
||||
executor: '',
|
||||
taskType: '1',
|
||||
problemType: ''
|
||||
problemType: '',
|
||||
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }]
|
||||
};
|
||||
// 重新获取任务列表
|
||||
getTaskList();
|
||||
@ -846,11 +911,8 @@ const getPlansList = async () => {
|
||||
label: item.planName || `计划${item.id}`,
|
||||
value: item.id.toString()
|
||||
}));
|
||||
|
||||
planList.value.unshift({ label: '全部计划', value: 'all' });
|
||||
} catch (error) {
|
||||
console.error('获取计划列表失败:', error);
|
||||
planList.value = [{ label: '全部计划', value: 'all' }];
|
||||
}
|
||||
};
|
||||
|
||||
@ -868,8 +930,13 @@ const handleCancelCreateTask = () => {
|
||||
taskName: '',
|
||||
inspectionTarget: '',
|
||||
timeRange: [],
|
||||
relatedPlan: 'all',
|
||||
executor: 'all'
|
||||
workTimeRange1: null,
|
||||
workTimeRange2: null,
|
||||
relatedPlan: '',
|
||||
executor: '',
|
||||
taskType: '1',
|
||||
problemType: '',
|
||||
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }]
|
||||
};
|
||||
};
|
||||
|
||||
@ -974,6 +1041,7 @@ const handleAction = async (task) => {
|
||||
const updateData = {
|
||||
...originalTask.rawData,
|
||||
id: task.id,
|
||||
startTime: new Date().toISOString().slice(0, 19).replace('T', ' '),
|
||||
taskType: '3', // 3表示执行中
|
||||
status: 'executing',
|
||||
taskProgress: 0
|
||||
@ -1039,6 +1107,9 @@ const handleAction = async (task) => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import url('./css/step-bars.css');
|
||||
@import url('./css/detail-dialog.css');
|
||||
|
||||
.inspection-tasks {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
@ -1394,6 +1465,96 @@ const handleAction = async (task) => {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 步骤条展示样式 */
|
||||
.step-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
padding: 12px;
|
||||
background-color: #fafafa;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.step-item:hover {
|
||||
background-color: #f5f7fa;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background-color: #409eff;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-name {
|
||||
font-weight: 500;
|
||||
color: #1d2129;
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.step-purpose {
|
||||
color: #606266;
|
||||
margin-bottom: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.step-time,
|
||||
.step-finish-time,
|
||||
.step-remark {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.step-status {
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* 步骤状态样式 */
|
||||
.step-status.status-pending {
|
||||
background-color: #e6f7ff;
|
||||
color: #1677ff;
|
||||
border: 1px solid #91d5ff;
|
||||
}
|
||||
|
||||
.step-status.status-executing {
|
||||
background-color: #fffbe6;
|
||||
color: #fa8c16;
|
||||
border: 1px solid #ffe58f;
|
||||
}
|
||||
|
||||
.step-status.status-completed {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
border: 1px solid #b7eb8f;
|
||||
}
|
||||
|
||||
.step-status.status-delayed {
|
||||
background-color: #fff2f0;
|
||||
color: #ff4d4f;
|
||||
border: 1px solid #ffccc7;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
@ -1573,4 +1734,11 @@ const handleAction = async (task) => {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.step-content {
|
||||
padding: 30px 20px;
|
||||
background-color: #fafafa;
|
||||
border-radius: 8px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user