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

View File

@ -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;
// 处理步骤数据格式
const stepsData = formData.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
}));
// 获取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 {
// 处理步骤数据格式
const stepsData = formData.value.steps
.filter((step) => step.content.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
}));
// 首先调用addjiedian接口
// 新增模式调用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>