0928
This commit is contained in:
@ -64,16 +64,40 @@
|
|||||||
<p class="stat-label">本月完成试验</p>
|
<p class="stat-label">本月完成试验</p>
|
||||||
<p class="stat-value">
|
<p class="stat-value">
|
||||||
{{ statData.completed
|
{{ statData.completed
|
||||||
}}<span class="stat-change" :class="statData.completedGrowth >= 0 ? 'up' : 'down'">
|
}}<span
|
||||||
较上月 {{ statData.completedGrowth >= 0 ? '↑' : '↓' }}{{ Math.abs(statData.completedGrowth) }}%
|
class="stat-change"
|
||||||
|
:class="{
|
||||||
|
'green': statData.completedGrowth > 0,
|
||||||
|
'gray': Math.abs(statData.completedGrowth - 100) < 0.01,
|
||||||
|
'red': statData.completedGrowth < 0
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
较上月
|
||||||
|
{{
|
||||||
|
Math.abs(statData.completedGrowth - 100) < 0.01
|
||||||
|
? '无增长'
|
||||||
|
: (statData.completedGrowth >= 0 ? '↑' : '↓') + Math.abs(statData.completedGrowth) + '%'
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<p class="stat-label">试验通过率</p>
|
<p class="stat-label">试验通过率</p>
|
||||||
<p class="stat-value">
|
<p class="stat-value">
|
||||||
{{ statData.passRate }}%<span class="stat-change" :class="statData.passRateGrowth >= 0 ? 'up' : 'down'">
|
{{ statData.passRate }}%<span
|
||||||
较上月 {{ statData.passRateGrowth >= 0 ? '↑' : '↓' }}{{ Math.abs(statData.passRateGrowth) }}%
|
class="stat-change"
|
||||||
|
:class="{
|
||||||
|
'green': statData.passRateGrowth > 0,
|
||||||
|
'gray': Math.abs(statData.passRateGrowth - 100) < 0.01,
|
||||||
|
'red': statData.passRateGrowth < 0
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
较上月
|
||||||
|
{{
|
||||||
|
Math.abs(statData.passRateGrowth - 100) < 0.01
|
||||||
|
? '无增长'
|
||||||
|
: (statData.passRateGrowth >= 0 ? '↑' : '↓') + Math.abs(statData.passRateGrowth) + '%'
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -85,8 +109,20 @@
|
|||||||
<p class="stat-label">平均试验时长</p>
|
<p class="stat-label">平均试验时长</p>
|
||||||
<p class="stat-value">
|
<p class="stat-value">
|
||||||
{{ statData.avgDuration
|
{{ statData.avgDuration
|
||||||
}}<span class="stat-change" :class="statData.avgDurationGrowth >= 0 ? 'down' : 'up'">
|
}}<span
|
||||||
较上月 {{ statData.avgDurationGrowth >= 0 ? '↑' : '↓' }}{{ Math.abs(statData.avgDurationGrowth) }}分钟
|
class="stat-change"
|
||||||
|
:class="{
|
||||||
|
'green': statData.avgDurationGrowth > 100, // 数据大于100(上升)时显示绿色
|
||||||
|
'gray': Math.abs(statData.avgDurationGrowth - 100) < 0.01,
|
||||||
|
'red': statData.avgDurationGrowth < 100 // 数据小于100(下降)时显示红色
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
较上月
|
||||||
|
{{
|
||||||
|
Math.abs(statData.avgDurationGrowth - 100) < 0.01
|
||||||
|
? '无增长'
|
||||||
|
: (statData.avgDurationGrowth <= 0 ? '↓' : '↑') + Math.abs(statData.avgDurationGrowth) + '%'
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -134,11 +170,11 @@
|
|||||||
<!-- 试验结果 -->
|
<!-- 试验结果 -->
|
||||||
<div class="test-result" :class="{ 'failure-analysis': record.status === 'failed' }">
|
<div class="test-result" :class="{ 'failure-analysis': record.status === 'failed' }">
|
||||||
<h4 class="result-title">
|
<h4 class="result-title">
|
||||||
{{ record.status === 'failed' ? '失败原因分析' : '试验结果' }}
|
{{ record.status === '3' ? '失败原因分析' : '试验结果' }}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<p class="result-content">
|
<p class="result-content">
|
||||||
{{ record.status === 'failed' ? record.failReason || '未提供失败原因' : record.testFinal || '试验完成,未提供详细结果' }}
|
{{ record.status === '3' ? record.failReason || '未提供失败原因' : record.testFinal || '试验未完成,未提供详细结果' }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="result-details" v-if="record.status !== 'failed'">
|
<p class="result-details" v-if="record.status !== 'failed'">
|
||||||
@ -435,6 +471,9 @@ const getStatisticsData = async () => {
|
|||||||
// 处理增长率数据
|
// 处理增长率数据
|
||||||
statData.value.completedGrowth = parseInt(apiData.finishCountAdd) || 0;
|
statData.value.completedGrowth = parseInt(apiData.finishCountAdd) || 0;
|
||||||
statData.value.passRateGrowth = parseFloat(apiData.passValueAdd) || 0;
|
statData.value.passRateGrowth = parseFloat(apiData.passValueAdd) || 0;
|
||||||
|
|
||||||
|
// 对于平均试验时长,时长减少是好的,所以我们需要反转逻辑
|
||||||
|
// 这里直接使用从API获取的增长率值,但在显示时根据正负来判断样式
|
||||||
statData.value.avgDurationGrowth = parseFloat(apiData.averageTestTimeAdd) || 0;
|
statData.value.avgDurationGrowth = parseFloat(apiData.averageTestTimeAdd) || 0;
|
||||||
} else {
|
} else {
|
||||||
console.warn('获取统计数据失败或返回格式不正确:', response);
|
console.warn('获取统计数据失败或返回格式不正确:', response);
|
||||||
@ -470,8 +509,14 @@ const formatDateTime = (dateTimeString) => {
|
|||||||
|
|
||||||
// 12. 辅助方法:获取节点状态类名
|
// 12. 辅助方法:获取节点状态类名
|
||||||
const getNodeStatusClass = (nodeStatus, recordStatus) => {
|
const getNodeStatusClass = (nodeStatus, recordStatus) => {
|
||||||
// 节点状态: 2-未完成, 其他假设为已完成
|
// 节点状态: 2-未完成, 3-失败, 其他假设为已完成
|
||||||
// 记录状态: 'failed'-失败, 'completed'-完成, 其他为进行中
|
// 记录状态: 'failed'-失败, 'completed'-完成, 其他为进行中
|
||||||
|
|
||||||
|
// 如果节点本身状态为3(失败),直接返回failed类名
|
||||||
|
if (nodeStatus === '3') {
|
||||||
|
return 'failed';
|
||||||
|
}
|
||||||
|
|
||||||
if (recordStatus === 'failed') {
|
if (recordStatus === 'failed') {
|
||||||
// 如果记录失败,找到失败阶段的节点
|
// 如果记录失败,找到失败阶段的节点
|
||||||
return nodeStatus === '2' ? 'failed' : 'active';
|
return nodeStatus === '2' ? 'failed' : 'active';
|
||||||
@ -486,6 +531,12 @@ const getNodeStatusClass = (nodeStatus, recordStatus) => {
|
|||||||
// 13. 辅助方法:获取进度线状态类名
|
// 13. 辅助方法:获取进度线状态类名
|
||||||
const getLineStatusClass = (index, nodes, recordStatus) => {
|
const getLineStatusClass = (index, nodes, recordStatus) => {
|
||||||
// 如果记录失败,找到第一个未完成的节点前的线为active
|
// 如果记录失败,找到第一个未完成的节点前的线为active
|
||||||
|
|
||||||
|
// 检查当前节点状态是否为3(失败)
|
||||||
|
if (nodes[index].status === '3') {
|
||||||
|
return 'failed';
|
||||||
|
}
|
||||||
|
|
||||||
if (recordStatus === 'failed') {
|
if (recordStatus === 'failed') {
|
||||||
return nodes[index].status !== '2' ? 'active' : 'failed';
|
return nodes[index].status !== '2' ? 'active' : 'failed';
|
||||||
} else if (recordStatus === 'completed') {
|
} else if (recordStatus === 'completed') {
|
||||||
@ -962,6 +1013,21 @@ onMounted(async () => {
|
|||||||
color: #ff7d00;
|
color: #ff7d00;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stat-change.green {
|
||||||
|
background-color: #e6ffed;
|
||||||
|
color: #00b42a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-change.red {
|
||||||
|
background-color: #fff1f0;
|
||||||
|
color: #f5222d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-change.gray {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
/* 13. 试验记录样式 */
|
/* 13. 试验记录样式 */
|
||||||
.test-records {
|
.test-records {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -1053,17 +1119,17 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.progress-step.active .step-number {
|
.progress-step.active .step-number {
|
||||||
background-color: #165dff;
|
background-color: #00b42a;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-step.active .step-name {
|
.progress-step.active .step-name {
|
||||||
color: #165dff;
|
color: #00b42a;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-line.active {
|
.progress-line.active {
|
||||||
background-color: #165dff;
|
background-color: #00b42a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-step.failed .step-number {
|
.progress-step.failed .step-number {
|
||||||
|
|||||||
@ -72,6 +72,28 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="task-details">
|
<div class="task-details">
|
||||||
|
<!-- 失败卡片特殊展示 -->
|
||||||
|
<div v-if="task.status === '3'" class="failed-task-details">
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">失败时间</span>
|
||||||
|
<span class="detail-value">{{ task.failTime || '未记录' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">试验阶段</span>
|
||||||
|
<span class="detail-value">{{ task.testStage || '未记录' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">执行人</span>
|
||||||
|
<span class="detail-value">{{ task.executor }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item failed-reason-item">
|
||||||
|
<span class="detail-label">失败原因</span>
|
||||||
|
<span class="detail-value failed-reason">{{ task.originalData?.failReason || '未填写' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 其他状态的卡片展示 -->
|
||||||
|
<div v-else>
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
<span class="detail-label">计划时间</span>
|
<span class="detail-label">计划时间</span>
|
||||||
<span class="detail-value">{{ task.planTime }}</span>
|
<span class="detail-value">{{ task.planTime }}</span>
|
||||||
@ -103,19 +125,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 已完成/失败结果 -->
|
<!-- 已完成结果 -->
|
||||||
<div v-if="task.status === '5' || task.status === '3'" class="task-result">
|
<div v-if="task.status === '5'" class="task-result">
|
||||||
<span class="detail-label">结果</span>
|
<span class="detail-label">结果</span>
|
||||||
<span class="detail-value" :class="task.resultClass">{{ task.result }}</span>
|
<span class="detail-value" :class="task.resultClass">{{ task.result }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="task-actions">
|
<div class="task-actions">
|
||||||
|
<!-- 失败卡片的特殊操作按钮 -->
|
||||||
|
<div v-if="task.status === '3'" class="failed-task-actions">
|
||||||
<el-button type="text" class="action-btn view-btn" @click="handleView(task)"> 详情 </el-button>
|
<el-button type="text" class="action-btn view-btn" @click="handleView(task)"> 详情 </el-button>
|
||||||
<el-button type="primary" :class="task.actionClass" @click="handleAction(task)">
|
<el-button type="primary" :class="task.actionClass" @click="handleAction(task)">
|
||||||
{{ task.actionText }}
|
{{ task.actionText }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 其他状态的操作按钮 -->
|
||||||
|
<div v-else>
|
||||||
|
<el-button type="text" class="action-btn view-btn" @click="handleView(task)"> 详情 </el-button>
|
||||||
|
<el-button type="primary" :class="task.actionClass" @click="handleAction(task)">
|
||||||
|
{{ task.actionText }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -372,6 +406,29 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 日志弹窗 -->
|
||||||
|
<el-dialog v-model="logsDialogVisible" title="任务执行日志" width="700px" :close-on-click-modal="false">
|
||||||
|
<div v-if="!logsLoading" class="logs-container">
|
||||||
|
<div v-if="logsData.length > 0" class="logs-list">
|
||||||
|
<div v-for="(log, index) in logsData" :key="index" class="log-item">
|
||||||
|
<div class="log-time">{{ log.timestamp || '-' }}</div>
|
||||||
|
<div class="log-content">{{ log.content || '-' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="no-logs">
|
||||||
|
<el-empty description="暂无执行日志" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="loading-logs">
|
||||||
|
<el-skeleton :count="5" class="log-skeleton" />
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="logsDialogVisible = false">关闭</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -385,7 +442,12 @@ import { shiyanlist } from '@/api/zhinengxunjian/shiyan';
|
|||||||
import { xunjianUserlist } from '@/api/zhinengxunjian/xunjian/index';
|
import { xunjianUserlist } from '@/api/zhinengxunjian/xunjian/index';
|
||||||
import { addjiedian } from '@/api/zhinengxunjian/jiedian/index';
|
import { addjiedian } from '@/api/zhinengxunjian/jiedian/index';
|
||||||
// 引入Element Plus组件(提示/空状态/骨架屏/弹窗)
|
// 引入Element Plus组件(提示/空状态/骨架屏/弹窗)
|
||||||
import { ElMessage, ElEmpty, ElSkeleton, ElForm, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElEmpty, ElSkeleton, ElForm, ElMessageBox, ElDialog } from 'element-plus';
|
||||||
|
|
||||||
|
// 日志弹窗相关变量
|
||||||
|
const logsDialogVisible = ref(false);
|
||||||
|
const logsData = ref([]);
|
||||||
|
const logsLoading = ref(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据任务ID获取完整的任务详情数据
|
* 根据任务ID获取完整的任务详情数据
|
||||||
@ -696,9 +758,9 @@ const mapApiToView = (apiData) => {
|
|||||||
},
|
},
|
||||||
'3': {
|
'3': {
|
||||||
statusText: '失败',
|
statusText: '失败',
|
||||||
cardClass: 'card-delayed',
|
cardClass: 'card-failed',
|
||||||
tagClass: 'tag-delayed',
|
tagClass: 'tag-failed',
|
||||||
actionText: '重试',
|
actionText: '重新执行',
|
||||||
actionClass: 'reschedule-btn',
|
actionClass: 'reschedule-btn',
|
||||||
result: '失败',
|
result: '失败',
|
||||||
resultClass: 'result-abnormal'
|
resultClass: 'result-abnormal'
|
||||||
@ -755,6 +817,46 @@ const mapApiToView = (apiData) => {
|
|||||||
executorName = getUserById(apiData.person);
|
executorName = getUserById(apiData.person);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 格式化失败时间
|
||||||
|
const formatFailTime = (timeStr) => {
|
||||||
|
if (timeStr) {
|
||||||
|
const date = new Date(timeStr);
|
||||||
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(
|
||||||
|
date.getHours()
|
||||||
|
).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
return '未记录';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成试验阶段信息
|
||||||
|
const getTestStage = () => {
|
||||||
|
try {
|
||||||
|
// 优先查找nodes数组中status为2的第一条数据
|
||||||
|
if (apiData && apiData.nodes && Array.isArray(apiData.nodes)) {
|
||||||
|
const firstStatusTwoNode = apiData.nodes.find((node) => {
|
||||||
|
// 确保node存在且有status属性
|
||||||
|
if (!node || node.status === undefined) return false;
|
||||||
|
// 处理status可能是字符串或数字的情况
|
||||||
|
return node.status === '2' || node.status === 2;
|
||||||
|
});
|
||||||
|
if (firstStatusTwoNode && firstStatusTwoNode.name) {
|
||||||
|
return firstStatusTwoNode.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果没有找到符合条件的nodes数据,检查是否有明确的试验阶段信息
|
||||||
|
if (apiData && apiData.testStage) {
|
||||||
|
return apiData.testStage;
|
||||||
|
}
|
||||||
|
// 如果没有明确的阶段信息,尝试从关联计划中获取
|
||||||
|
if (apiData && apiData.testPlan && apiData.testPlan.stage) {
|
||||||
|
return apiData.testPlan.stage;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取试验阶段信息失败:', error);
|
||||||
|
}
|
||||||
|
return '未记录';
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: apiData.id, // 任务ID(v-for的key,唯一标识)
|
id: apiData.id, // 任务ID(v-for的key,唯一标识)
|
||||||
title: apiData.taskName || '未命名任务', // 任务名称
|
title: apiData.taskName || '未命名任务', // 任务名称
|
||||||
@ -774,7 +876,10 @@ const mapApiToView = (apiData) => {
|
|||||||
actionText: statusConfig.actionText,
|
actionText: statusConfig.actionText,
|
||||||
actionClass: statusConfig.actionClass,
|
actionClass: statusConfig.actionClass,
|
||||||
testFinal: apiData.testFinal, // 结果(用于详情页)
|
testFinal: apiData.testFinal, // 结果(用于详情页)
|
||||||
originalData: apiData // 保存原始数据,用于后续操作
|
originalData: apiData, // 保存原始数据,用于后续操作
|
||||||
|
// 失败卡片特有字段
|
||||||
|
failTime: formatFailTime(apiData.failTime),
|
||||||
|
testStage: getTestStage()
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -880,11 +985,44 @@ const handleAction = async (task) => {
|
|||||||
resultType = 'normal'; // 现在在外部作用域中定义
|
resultType = 'normal'; // 现在在外部作用域中定义
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error === 'cancel') {
|
if (error === 'cancel') {
|
||||||
// 用户点击取消(异常)
|
// 用户点击取消(异常),弹出失败原因输入框
|
||||||
updateParams.status = '5';
|
try {
|
||||||
updateParams.progress = 100;
|
const failReasonResult = await ElMessageBox.prompt('请输入失败原因', '试验异常', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
inputPlaceholder: '请详细描述失败原因...',
|
||||||
|
inputValidator: (value) => {
|
||||||
|
if (!value || value.trim() === '') {
|
||||||
|
return '失败原因不能为空';
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 用户输入了失败原因并确认
|
||||||
|
updateParams.status = '3';
|
||||||
|
updateParams.progress = '';
|
||||||
updateParams.testFinal = '异常';
|
updateParams.testFinal = '异常';
|
||||||
resultType = 'abnormal'; // 现在在外部作用域中定义
|
updateParams.failReason = failReasonResult.value; // 绑定失败原因参数
|
||||||
|
updateParams.failTime = formatLocalDateTime(new Date()); // 记录失败时间
|
||||||
|
resultType = 'abnormal';
|
||||||
|
|
||||||
|
// 将第一条未完成的步骤状态改为3(失败)
|
||||||
|
if (taskDetails.nodes && Array.isArray(taskDetails.nodes)) {
|
||||||
|
const firstUnfinishedNode = taskDetails.nodes.find((node) => {
|
||||||
|
return node.status === '2' || node.status === 2;
|
||||||
|
});
|
||||||
|
if (firstUnfinishedNode) {
|
||||||
|
firstUnfinishedNode.status = '3';
|
||||||
|
// 确保更新到updateParams中
|
||||||
|
updateParams.nodes = taskDetails.nodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (innerError) {
|
||||||
|
// 用户取消了失败原因输入
|
||||||
|
return;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 关闭弹窗,不执行操作
|
// 关闭弹窗,不执行操作
|
||||||
return;
|
return;
|
||||||
@ -895,50 +1033,51 @@ const handleAction = async (task) => {
|
|||||||
switch (task.status) {
|
switch (task.status) {
|
||||||
case '1': // 待执行 → 开始执行(状态改为4)
|
case '1': // 待执行 → 开始执行(状态改为4)
|
||||||
updateParams.status = '4';
|
updateParams.status = '4';
|
||||||
updateParams.progress = 10; // 初始进度10%
|
updateParams.progress = 0; // 初始进度10%
|
||||||
// 设置开始时间为当前时间
|
// 设置开始时间为当前时间(使用本地时间而非UTC时间)
|
||||||
updateParams.planBeginTime = new Date().toISOString().slice(0, 16).replace('T', ' ');
|
updateParams.planBeginTime = formatLocalDateTime(new Date());
|
||||||
break;
|
break;
|
||||||
case '2': // 已延期 → 重新安排(状态改为1,重置时间)
|
case '2': // 已延期 → 重新安排(状态改为1,重置时间)
|
||||||
updateParams.status = '1';
|
updateParams.status = '1';
|
||||||
updateParams.beginTime = new Date().toISOString().slice(0, 16).replace('T', ' ');
|
updateParams.beginTime = formatLocalDateTime(new Date());
|
||||||
break;
|
break;
|
||||||
case '3': // 失败 → 重试(状态改为1)
|
case '3': // 失败 → 重试(状态改为1)
|
||||||
updateParams.status = '1';
|
updateParams.status = '1';
|
||||||
|
// 清空失败相关字段,使用适合各字段数据类型的默认值
|
||||||
|
updateParams.failReason = '';
|
||||||
|
updateParams.failTime = ''; // 时间类型字段使用null
|
||||||
|
updateParams.failPhase = ''; // 整数类型字段使用0
|
||||||
|
|
||||||
|
// 将失败的步骤状态改回2(未完成)
|
||||||
|
if (taskDetails.nodes && Array.isArray(taskDetails.nodes)) {
|
||||||
|
taskDetails.nodes.forEach((node) => {
|
||||||
|
if (node.status === '3' || node.status === 3) {
|
||||||
|
node.status = '2';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 确保更新到updateParams中
|
||||||
|
updateParams.nodes = taskDetails.nodes;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用更新接口
|
// 对于执行中状态('4')的任务,预先设置好时间字段
|
||||||
|
if (task.status === '4') {
|
||||||
|
// 根据结果类型设置相应的时间(使用本地时间而非UTC时间)
|
||||||
|
if (resultType === 'normal') {
|
||||||
|
updateParams.planFinishTime = formatLocalDateTime(new Date());
|
||||||
|
} else if (resultType === 'abnormal') {
|
||||||
|
updateParams.failTime = formatLocalDateTime(new Date());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用更新接口(只调用一次)
|
||||||
const response = await updatesyrenwu(updateParams);
|
const response = await updatesyrenwu(updateParams);
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
ElMessage.success(`任务${task.actionText}成功`);
|
ElMessage.success(`任务${task.actionText}成功`);
|
||||||
|
|
||||||
// 只有在接口调用成功后才设置时间
|
|
||||||
if (task.status === '4') {
|
|
||||||
// 获取最新的任务详情,确保包含所有字段
|
|
||||||
const latestTaskDetails = await getTaskDetails(task.id);
|
|
||||||
if (latestTaskDetails) {
|
|
||||||
// 创建包含所有字段的新参数对象
|
|
||||||
const timeUpdateParams = {
|
|
||||||
...latestTaskDetails,
|
|
||||||
id: task.id
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据结果类型设置相应的时间(现在resultType已在作用域内)
|
|
||||||
if (resultType === 'normal') {
|
|
||||||
timeUpdateParams.planFinishTime = new Date().toISOString().slice(0, 16).replace('T', ' ');
|
|
||||||
} else if (resultType === 'abnormal') {
|
|
||||||
timeUpdateParams.failTime = new Date().toISOString().slice(0, 16).replace('T', ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 再次调用接口更新时间
|
|
||||||
await updatesyrenwu(timeUpdateParams);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getTaskList(); // 刷新任务列表
|
getTaskList(); // 刷新任务列表
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(`任务${task.actionText}失败:` + response.msg);
|
ElMessage.error(`任务${task.actionText}失败:` + response.msg);
|
||||||
@ -948,6 +1087,20 @@ const handleAction = async (task) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化本地日期时间为 'YYYY-MM-DD HH:mm' 格式
|
||||||
|
* @param {Date} date - 日期对象
|
||||||
|
* @returns {string} 格式化后的日期时间字符串
|
||||||
|
*/
|
||||||
|
const formatLocalDateTime = (date) => {
|
||||||
|
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}`;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打开创建任务弹窗
|
* 打开创建任务弹窗
|
||||||
*/
|
*/
|
||||||
@ -1049,7 +1202,7 @@ const handleSaveTask = async () => {
|
|||||||
progress: 0, // 初始进度0%
|
progress: 0, // 初始进度0%
|
||||||
failReason: '',
|
failReason: '',
|
||||||
failTime: '', // 失败时间(新增时为空)
|
failTime: '', // 失败时间(新增时为空)
|
||||||
failPhase: 0,
|
failPhase: '',
|
||||||
faileAnalyze: '',
|
faileAnalyze: '',
|
||||||
faileTips: '',
|
faileTips: '',
|
||||||
testLongTime: 0,
|
testLongTime: 0,
|
||||||
@ -1267,6 +1420,10 @@ const getTaskStatusClass = (status) => {
|
|||||||
box-shadow: 0 4px 16px rgba(82, 196, 26, 0.15);
|
box-shadow: 0 4px 16px rgba(82, 196, 26, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-failed {
|
||||||
|
box-shadow: 0 4px 16px rgba(255, 77, 79, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
/* 左侧状态线颜色 */
|
/* 左侧状态线颜色 */
|
||||||
.card-pending::before {
|
.card-pending::before {
|
||||||
background-color: #1677ff;
|
background-color: #1677ff;
|
||||||
@ -1280,6 +1437,9 @@ const getTaskStatusClass = (status) => {
|
|||||||
.card-completed::before {
|
.card-completed::before {
|
||||||
background-color: #52c41a;
|
background-color: #52c41a;
|
||||||
}
|
}
|
||||||
|
.card-failed::before {
|
||||||
|
background-color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
/* 卡片悬停效果 */
|
/* 卡片悬停效果 */
|
||||||
.task-card:hover {
|
.task-card:hover {
|
||||||
@ -1336,6 +1496,12 @@ const getTaskStatusClass = (status) => {
|
|||||||
border-color: #b7eb8f;
|
border-color: #b7eb8f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tag-failed {
|
||||||
|
background-color: #fff2f0;
|
||||||
|
color: #ff4d4f;
|
||||||
|
border-color: #ffccc7;
|
||||||
|
}
|
||||||
|
|
||||||
.task-details {
|
.task-details {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
@ -1419,6 +1585,28 @@ const getTaskStatusClass = (status) => {
|
|||||||
color: #165dff;
|
color: #165dff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 失败卡片特殊样式 */
|
||||||
|
.failed-task-details {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed-reason-item {
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px dashed #f0f2f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed-reason {
|
||||||
|
color: #f53f3f;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed-task-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
/* 分页区域样式 */
|
/* 分页区域样式 */
|
||||||
.pagination-section {
|
.pagination-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -1472,6 +1660,46 @@ const getTaskStatusClass = (status) => {
|
|||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 日志弹窗样式 */
|
||||||
|
.logs-container {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-list {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-item {
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid #f0f2f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-time {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #86909c;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-content {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #1d2129;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-logs {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-skeleton {
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* 任务详情弹窗样式 */
|
/* 任务详情弹窗样式 */
|
||||||
.task-detail-container {
|
.task-detail-container {
|
||||||
max-height: 600px;
|
max-height: 600px;
|
||||||
|
|||||||
Reference in New Issue
Block a user