This commit is contained in:
dhr
2025-09-26 20:32:14 +08:00
parent 6b9bfb66b1
commit 3f07f7afe3
18 changed files with 1672 additions and 945 deletions

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="operation-inspection"> <div class="operation-inspection">
<!-- 导航标签 --> <!-- 导航标签 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab active" @click="handleInspection2">巡检管理</div> <div class="nav-tab active" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -10,7 +10,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 子选项卡 --> <!-- 子选项卡 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="execution-records"> <div class="execution-records">
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -10,7 +10,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab active" @click="handleInspection7">运维组织</div> <div class="nav-tab active" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 选项卡 --> <!-- 选项卡 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<div class="inspection-tasks"> <div class="inspection-tasks">
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -9,7 +9,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 选项卡 --> <!-- 选项卡 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">
@ -86,7 +86,7 @@
<!-- 已完成状态的额外信息 --> <!-- 已完成状态的额外信息 -->
<div v-if="task.status === 'completed'" class="task-result"> <div v-if="task.status === 'completed'" class="task-result">
<span class="detail-label">完成时间</span> <span class="detail-label">完成时间</span>
<span class="detail-value">{{ task.completeTime }}</span> <span class="detail-value">{{ task.reportFinishTime }}</span>
</div> </div>
</div> </div>
@ -306,7 +306,7 @@
<div v-if="detailData.status === '3'" class="info-row"> <div v-if="detailData.status === '3'" class="info-row">
<div class="info-item"> <div class="info-item">
<span class="info-label">完成时间</span> <span class="info-label">完成时间</span>
<span class="info-value">{{ formatDate(detailData.completeTime) }}</span> <span class="info-value">{{ formatDate(detailData.reportFinishTime) }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -454,7 +454,10 @@ async function getTaskList() {
// 修复维修人字段从sendPersonVo对象中获取用户名 // 修复维修人字段从sendPersonVo对象中获取用户名
maintainer: item.sendPersonVo?.userName || '未分配', maintainer: item.sendPersonVo?.userName || '未分配',
completeTime: item.completeTime ? formatDate(item.completeTime) : '', // 尝试从多个可能的字段中获取完成时间,确保有值时能正确显示
completeTime: item.completeTime ? formatDate(item.completeTime) : item.finishTime ? formatDate(item.finishTime) : '',
// 直接使用并格式化reportFinishTime字段
reportFinishTime: item.reportFinishTime ? formatDate(item.reportFinishTime) : '',
actionText: getActionText(item.status), actionText: getActionText(item.status),
actionClass: getActionClass(item.status), actionClass: getActionClass(item.status),
reportInfo: item.reportInfo, reportInfo: item.reportInfo,
@ -1003,7 +1006,7 @@ const handleSaveResult = async () => {
statusText: '已完成', // 状态文本 statusText: '已完成', // 状态文本
reportFinal: reportFinal.value, // 处理结果 reportFinal: reportFinal.value, // 处理结果
reportFinishTime: reportFinishTime, // 完成时间 reportFinishTime: reportFinishTime, // 完成时间
completeTime: reportFinishTime, // 完成时间(向后兼容字段) completeTime: reportFinishTime, // 完成时间
// ③ 复用原始任务数据中的所有必要字段 // ③ 复用原始任务数据中的所有必要字段
name: originalTask.title, // 任务名称(原始任务标题) name: originalTask.title, // 任务名称(原始任务标题)
@ -1481,7 +1484,7 @@ onMounted(async () => {
/* 任务卡片样式 - 恢复优先级标签背景色 */ /* 任务卡片样式 - 恢复优先级标签背景色 */
.task-cards { .task-cards {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(310px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 20px; gap: 20px;
margin-bottom: 30px; margin-bottom: 30px;
} }
@ -1494,22 +1497,14 @@ onMounted(async () => {
transition: all 0.3s ease; transition: all 0.3s ease;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
min-height: 260px; /* 增加高度以适应新增的预计完成时间 */ min-height: 280px; /* 增加高度以确保内容完整显示 */
display: flex;
flex-direction: column;
} }
.task-actions { .task-content {
display: flex; flex: 1; /* 内容区域占满剩余空间 */
justify-content: flex-end; margin-bottom: 16px;
align-items: center;
padding-top: 12px;
border-top: 1px solid #f0f2f5;
position: absolute;
bottom: 16px;
right: 16px;
left: 16px;
background-color: #fff;
padding: 12px 0 0 0;
z-index: 10;
} }
.task-actions .el-button { .task-actions .el-button {
@ -1562,6 +1557,9 @@ onMounted(async () => {
font-weight: 500; font-weight: 500;
color: #1d2129; color: #1d2129;
line-height: 1.4; line-height: 1.4;
word-break: break-word;
flex: 1;
margin-right: 8px;
} }
/* 恢复优先级标签背景色样式 */ /* 恢复优先级标签背景色样式 */
@ -1571,6 +1569,7 @@ onMounted(async () => {
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 500;
border: 1px solid transparent; border: 1px solid transparent;
flex-shrink: 0;
} }
.priority-high { .priority-high {
@ -1599,17 +1598,20 @@ onMounted(async () => {
display: flex; display: flex;
margin-bottom: 10px; margin-bottom: 10px;
font-size: 13px; font-size: 13px;
align-items: flex-start;
} }
.detail-label { .detail-label {
flex: 0 0 80px; flex: 0 0 85px;
color: #86909c; color: #86909c;
margin-right: 4px;
} }
.detail-value { .detail-value {
flex: 1; flex: 1;
color: #4e5969; color: #4e5969;
word-break: break-all; word-break: break-word;
line-height: 1.5;
} }
.task-result { .task-result {
@ -1626,13 +1628,7 @@ onMounted(async () => {
align-items: center; align-items: center;
padding-top: 12px; padding-top: 12px;
border-top: 1px solid #f0f2f5; border-top: 1px solid #f0f2f5;
position: absolute;
bottom: 16px;
right: 16px;
left: 16px;
background-color: #fff; background-color: #fff;
padding: 12px 0 0 0;
z-index: 10;
} }
.action-btn { .action-btn {

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="inspection-tasks"> <div class="inspection-tasks">
<!-- 导航栏 --> <!-- 导航栏 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -10,7 +10,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 选项卡 --> <!-- 选项卡 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="execution-records"> <div class="execution-records">
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -10,8 +10,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab active" @click="handleInspection7">运维组织</div> <div class="nav-tab active" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 选项卡 --> <!-- 选项卡 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">
@ -138,7 +137,6 @@
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import router from '@/router'; import router from '@/router';
// 搜索和筛选条件 // 搜索和筛选条件
const searchKeyword = ref(''); const searchKeyword = ref('');
const plateNumber1 = ref(''); const plateNumber1 = ref('');

View File

@ -51,35 +51,90 @@
word-break: break-word; word-break: break-word;
} }
/* 步骤相关样式 */ /* 步骤相关样式 - 详情弹窗专用 - 使用外部CSS样式 */
.steps-container { .task-detail-container .steps-container {
display: flex; width: 100%;
flex-direction: column; padding: 20px;
gap: 12px; border: 1px solid #ebeef5;
border-radius: 8px;
background-color: #fff;
} }
.step-item { .task-detail-container .step-item {
display: flex; display: flex;
gap: 12px; align-items: center;
align-items: flex-start; margin-bottom: 15px;
padding: 16px; padding: 15px;
background-color: #ffffff; background-color: #fafafa;
border-radius: 6px; border-radius: 6px;
border: 1px solid #e4e7ed; position: relative;
transition: all 0.3s ease;
} }
.step-number { .task-detail-container .step-item:hover {
width: 28px; background-color: #f5f7fa;
height: 28px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.task-detail-container .step-number {
width: 32px;
height: 32px;
background-color: #409eff; background-color: #409eff;
color: white; color: white;
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-weight: 600; margin-right: 16px;
font-size: 14px; font-size: 14px;
font-weight: bold;
flex-shrink: 0; flex-shrink: 0;
z-index: 1;
}
.task-detail-container .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;
}
.task-detail-container .step-info {
flex: 1;
}
.task-detail-container .step-name {
font-weight: 500;
color: #1d2129;
margin-bottom: 4px;
font-size: 14px;
}
.task-detail-container .step-purpose {
color: #606266;
margin-bottom: 4px;
font-size: 13px;
}
.task-detail-container .step-time,
.task-detail-container .step-finish-time,
.task-detail-container .step-remark {
color: #909399;
font-size: 12px;
margin-bottom: 2px;
}
.task-detail-container .step-status {
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
flex-shrink: 0;
margin-top: 4px;
} }
.step-info { .step-info {
@ -110,8 +165,8 @@
margin-bottom: 2px; margin-bottom: 2px;
} }
/* 状态相关样式 */ /* 步骤状态样式 - 详情弹窗专用 */
.step-status { .task-detail-container .step-status {
padding: 4px 12px; padding: 4px 12px;
border-radius: 4px; border-radius: 4px;
font-size: 12px; font-size: 12px;
@ -120,26 +175,29 @@
margin-top: 4px; margin-top: 4px;
} }
/* 步骤状态样式 */ /* 步骤状态样式 - 待执行 */
.step-status.status-pending { .task-detail-container .step-status.status-pending {
background-color: #e6f7ff; background-color: #e6f7ff;
color: #1677ff; color: #1677ff;
border: 1px solid #91d5ff; border: 1px solid #91d5ff;
} }
.step-status.status-executing { /* 步骤状态样式 - 执行中 */
.task-detail-container .step-status.status-executing {
background-color: #fffbe6; background-color: #fffbe6;
color: #fa8c16; color: #fa8c16;
border: 1px solid #ffe58f; border: 1px solid #ffe58f;
} }
.step-status.status-completed { /* 步骤状态样式 - 已完成 */
.task-detail-container .step-status.status-completed {
background-color: #f6ffed; background-color: #f6ffed;
color: #52c41a; color: #52c41a;
border: 1px solid #b7eb8f; border: 1px solid #b7eb8f;
} }
.step-status.status-delayed { /* 步骤状态样式 - 已延期 */
.task-detail-container .step-status.status-delayed {
background-color: #fff2f0; background-color: #fff2f0;
color: #ff4d4f; color: #ff4d4f;
border: 1px solid #ffccc7; border: 1px solid #ffccc7;
@ -238,6 +296,35 @@
.info-row { .info-row {
gap: 12px; gap: 12px;
} }
/* 步骤条响应式设计 */
.task-detail-container .steps-container {
padding: 10px;
}
.task-detail-container .step-item {
flex-direction: column;
align-items: flex-start;
padding: 10px;
margin-bottom: 10px;
}
.task-detail-container .step-item > * {
width: 100%;
margin-bottom: 10px;
margin-right: 0 !important;
}
.task-detail-container .step-number {
margin-bottom: 10px;
width: 24px;
height: 24px;
font-size: 12px;
}
.task-detail-container .step-item:not(:last-child)::after {
display: none;
}
} }
/* 弹窗按钮样式 */ /* 弹窗按钮样式 */

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="work-order-management"> <div class="work-order-management">
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -10,7 +10,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab active" @click="handleInspection6">工单管理</div> <div class="nav-tab active" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">
<div style="display: flex; align-items: center; gap: 10px"> <div style="display: flex; align-items: center; gap: 10px">
@ -379,7 +379,7 @@
<div v-if="node.remark" class="step-remark">备注{{ node.remark }}</div> <div v-if="node.remark" class="step-remark">备注{{ node.remark }}</div>
</div> </div>
<div class="step-status" :class="getStatusClass(node.status)"> <div class="step-status" :class="getStatusClass(node.status)">
{{ getStepStatusText(node.status) }} {{ node.status === '2' ? '未完成' : '已完成' }}
</div> </div>
</div> </div>
</div> </div>

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="box-container"> <div class="box-container">
<!-- 导航栏 --> <!-- 导航栏 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab active" @click="handleInspection1">待办事项</div> <div class="nav-tab active" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -10,7 +10,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<div class="main-content"> <div class="main-content">
<!-- 左侧日历区域 --> <!-- 左侧日历区域 -->
<div class="calendar-container"> <div class="calendar-container">
@ -54,6 +54,7 @@
class="todo-item" class="todo-item"
:class="{ 'important': item.taskLevel === '重要', 'completed': item.status === 2 }" :class="{ 'important': item.taskLevel === '重要', 'completed': item.status === 2 }"
> >
<el-checkbox class="todo-checkbox" :checked="item.status === 2" @change="handleStatusChange(item, $event)"></el-checkbox>
<div <div
class="todo-color-indicator" class="todo-color-indicator"
:class="{ :class="{
@ -63,7 +64,6 @@
completed: item.status === 2 completed: item.status === 2
}" }"
></div> ></div>
<el-checkbox class="todo-checkbox" :checked="item.status === 2" @change="handleStatusChange(item, $event)"></el-checkbox>
<div class="todo-content"> <div class="todo-content">
<div class="todo-main"> <div class="todo-main">
<div class="todo-title">{{ item.title }}</div> <div class="todo-title">{{ item.title }}</div>
@ -590,16 +590,6 @@ const handleInspection7 = () => {
min-height: 100vh; min-height: 100vh;
} }
/* 导航栏样式 */
.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;
}
/* 已完成任务的样式 */ /* 已完成任务的样式 */
.todo-color-indicator.completed { .todo-color-indicator.completed {
background-color: #dcdfe6; background-color: #dcdfe6;
@ -609,7 +599,15 @@ const handleInspection7 = () => {
color: #909399; color: #909399;
text-decoration: line-through; text-decoration: line-through;
} }
/* 导航栏样式 */
.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;
}
.nav-tab { .nav-tab {
padding: 12px 24px; padding: 12px 24px;
cursor: pointer; cursor: pointer;
@ -849,13 +847,14 @@ const handleInspection7 = () => {
/* 悬停显示操作按钮 */ /* 悬停显示操作按钮 */
.todo-item:hover .todo-actions { .todo-item:hover .todo-actions {
opacity: 1; background: linear-gradient(to right, rgba(173, 216, 230, 0), rgb(64, 158, 255));
right: 0; right: 0;
opacity: 0.8;
} }
/* 内容区域平移以给按钮留出空间 */ /* 取消内容区域平移效果 */
.todo-item:hover .todo-content { .todo-item:hover .todo-content {
transform: translateX(-120px); transform: none;
} }
.action-icon { .action-icon {
@ -942,7 +941,7 @@ const handleInspection7 = () => {
background-color: #ff4d4f; background-color: #ff4d4f;
} }
::v-deep .custom-date-cell { :deep(.custom-date-cell) {
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 5px; padding: 5px;
@ -983,13 +982,13 @@ const handleInspection7 = () => {
} }
/* 穿透作用域,强制设置日历单元格为正方形 */ /* 穿透作用域,强制设置日历单元格为正方形 */
::v-deep .el-calendar-table td { :deep(.el-calendar-table td) {
padding: 2px; padding: 2px;
vertical-align: top; vertical-align: top;
width: 120px; /* 强制宽度 */ width: 120px; /* 强制宽度 */
height: 120px; /* 强制高度(与宽度一致) */ height: 120px; /* 强制高度(与宽度一致) */
} }
::v-deep .el-calendar-day { :deep(.el-calendar-day) {
padding: 0; /* 移除默认内边距 */ padding: 0; /* 移除默认内边距 */
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="dispatch-records"> <div class="dispatch-records">
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -10,7 +10,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab active" @click="handleInspection6">工单管理</div> <div class="nav-tab active" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 选项卡 --> <!-- 选项卡 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">
@ -430,7 +430,7 @@
<div v-if="node.remark" class="step-remark">备注{{ node.remark }}</div> <div v-if="node.remark" class="step-remark">备注{{ node.remark }}</div>
</div> </div>
<div class="step-status" :class="getStatusClass(node.status)"> <div class="step-status" :class="getStatusClass(node.status)">
{{ getStepStatusText(node.status) }} {{ node.status === '2' ? '未完成' : '已完成' }}
</div> </div>
</div> </div>
</div> </div>
@ -737,11 +737,6 @@ const getStepStatusText = (status) => {
return statusMap[statusStr] || '未知状态'; return statusMap[statusStr] || '未知状态';
}; };
// 按模块分组节点(保留该函数用于兼容)
const groupNodesByModule = (nodes) => {
if (!nodes || !nodes.length) return [];
return [{ module: '', items: nodes }];
// 初始化加载数据 // 初始化加载数据
const initData = async () => { const initData = async () => {
await fetchStatisticsData(); await fetchStatisticsData();

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<div class="inspection-tasks"> <div class="inspection-tasks">
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -9,7 +9,7 @@
<div class="nav-tab active" @click="handleInspection5">抢修管理</div> <div class="nav-tab active" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 选项卡 --> <!-- 选项卡 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">
@ -1065,11 +1065,29 @@ defineExpose({ getTaskList });
async function getTaskList() { async function getTaskList() {
loading.value = true; loading.value = true;
try { try {
const res = await qiangxiulist({ // 构建请求参数,包含筛选条件
const requestParams = {
projectId: 1, projectId: 1,
pageNum: currentPage.value, pageNum: currentPage.value,
pageSize: pageSize.value pageSize: pageSize.value
}); };
// 添加任务状态筛选条件
if (taskStatus.value && taskStatus.value !== 'all') {
requestParams.status = taskStatus.value;
}
// 添加计划类型筛选条件
if (planType.value && planType.value !== 'all') {
requestParams.planType = planType.value;
}
// 添加执行人筛选条件
if (executor.value && executor.value !== 'all') {
requestParams.executor = executor.value;
}
const res = await qiangxiulist(requestParams);
if (res.code === 200 && res.rows) { if (res.code === 200 && res.rows) {
total.value = res.total || 0; total.value = res.total || 0;

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="inspection-tasks"> <div class="inspection-tasks">
<!-- 导航栏 --> <!-- 导航栏 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -10,14 +10,7 @@
<div class="nav-tab active" @click="handleInspection5">抢修管理</div> <div class="nav-tab active" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 页面标题和操作区 -->
<div class="header-section">
<div class="header-actions">
<el-button type="primary" class="export-btn" @click="handleExport"> 导出数据 </el-button>
</div>
</div>
<div class="tabs-wrapper"> <div class="tabs-wrapper">
<div style="display: flex; align-items: center; gap: 10px"> <div style="display: flex; align-items: center; gap: 10px">
@ -1524,12 +1517,7 @@ const handleInspectionManagement2 = () => {
border-radius: 4px; border-radius: 4px;
} }
/* 无数据提示 */ /* */
.no-info {
text-align: center;
color: #909399;
padding: 60px 20px;
}
/* 分配弹窗样式 */ /* 分配弹窗样式 */
.assign-container { .assign-container {

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="operation-organization"> <div class="operation-organization">
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -10,7 +10,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab active" @click="handleInspection7">运维组织</div> <div class="nav-tab active" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 选项卡 --> <!-- 选项卡 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">
@ -132,8 +132,7 @@ import { ref, watch, onMounted } from 'vue';
import router from '@/router'; import router from '@/router';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
// 激活的选项卡 //
const activeTab = ref('personnel');
// 统计数据(保持原有数据不变) // 统计数据(保持原有数据不变)
const totalPersonnel = ref(36); const totalPersonnel = ref(36);
@ -445,36 +444,7 @@ const handleInspectionManagement3 = () => {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
} }
.custom-tabs { /* */
padding-top: 1px;
}
.custom-tabs .el-tabs__header {
margin: 0 -20px;
padding: 0 20px;
border-bottom: 1px solid #e4e7ed;
}
.custom-tabs .el-tabs__nav-wrap::after {
height: 0;
}
.custom-tabs .el-tabs__item {
font-size: 14px;
color: #606266;
padding: 16px 20px;
margin-right: 20px;
}
.custom-tabs .el-tabs__item.is-active {
color: #165dff;
font-weight: 500;
border-bottom: 2px solid #165dff;
}
.custom-tabs .el-tabs__item:hover {
color: #165dff;
}
/* 内容容器样式 */ /* 内容容器样式 */
.content-container { .content-container {

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="operation-inspection"> <div class="operation-inspection">
<!-- 1. 顶部导航选项卡对应原试验系统的外层导航 --> <!-- 1. 顶部导航选项卡对应原试验系统的外层导航 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab active" @click="handleInspection3">试验管理</div> <div class="nav-tab active" @click="handleInspection3">试验管理</div>
@ -10,7 +10,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 选项卡和按钮组合 --> <!-- 选项卡和按钮组合 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">
@ -67,13 +67,7 @@
<el-table-column align="center" prop="type" label="巡检类型" width="120"></el-table-column> <el-table-column align="center" prop="type" label="巡检类型" width="120"></el-table-column>
<el-table-column align="center" prop="cycle" label="巡检周期" width="120"></el-table-column> <el-table-column align="center" prop="cycle" label="巡检周期" width="120"></el-table-column>
<el-table-column align="center" prop="dateRange" label="执行时间范围"></el-table-column> <el-table-column align="center" prop="dateRange" label="执行时间范围"></el-table-column>
<el-table-column align="center" prop="progress" label="完成进度" width="120">
<template #default="scope">
<div class="progress-bar">
<div class="progress-fill" :style="{ width: scope.row.progress + '%', backgroundColor: getProgressColor(scope.row.status) }"></div>
</div>
</template>
</el-table-column>
<el-table-column align="center" prop="status" label="状态" width="100"> <el-table-column align="center" prop="status" label="状态" width="100">
<template #default="scope"> <template #default="scope">
<span :class="['status-tag', `status-${scope.row.status}`]"> <span :class="['status-tag', `status-${scope.row.status}`]">
@ -374,10 +368,10 @@
</el-form-item> </el-form-item>
<el-form-item label="实验对象类型" class="form-item"> <el-form-item label="实验对象类型" class="form-item">
<el-select v-model="formData.testObject" placeholder="请选择实验对象类型" class="form-input"> <el-select v-model="formData.testObject" placeholder="请选择实验对象类型" class="form-input">
<el-option label="1安全试验" value="1" /> <el-option label="安全试验" value="1" />
<el-option label="2网络实验" value="2" /> <el-option label="网络实验" value="2" />
<el-option label="3性能试验" value="3" /> <el-option label="性能试验" value="3" />
<el-option label="4" value="4" /> <el-option label="其他试验" value="4" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</div> </div>
@ -418,36 +412,6 @@
</el-select> </el-select>
</el-form-item> </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 formData.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="formData.steps.length > 1"
type="text"
size="small"
class="delete-step-btn"
@click="deleteStep(index)"
style="color: #f56c6c"
>
删除
</el-button>
</div>
<el-button type="text" size="small" class="add-step-btn" @click="addStep">添加步骤</el-button>
</div>
</el-form-item>
<!-- 所需设备与准备 --> <!-- 所需设备与准备 -->
<el-form-item label="所需资源与设备" class="form-item" style="width: 100%"> <el-form-item label="所需资源与设备" class="form-item" style="width: 100%">
<div class="equipment-list"> <div class="equipment-list">
@ -490,119 +454,113 @@
:close-on-click-modal="false" :close-on-click-modal="false"
:close-on-press-escape="false" :close-on-press-escape="false"
class="custom-experiment-dialog" class="custom-experiment-dialog"
center
> >
<div class="detail-content"> <div v-if="detailData" class="task-detail-container">
<!-- 基础信息 --> <!-- 基础信息卡片 -->
<div class="detail-section"> <div class="detail-card">
<h3 class="section-title">基础信息</h3> <h3 class="card-title">基础信息</h3>
<div class="detail-grid"> <div class="card-content">
<div class="detail-item"> <div class="info-row">
<label class="detail-label">计划名称:</label> <div class="info-item">
<span class="detail-value">{{ detailData.planName || '-' }}</span> <label class="info-label">计划名称:</label>
<span class="info-value">{{ detailData.planName || '-' }}</span>
</div> </div>
<div class="detail-item"> <div class="info-item">
<label class="detail-label">计划编号:</label> <label class="info-label">计划编号:</label>
<span class="detail-value">{{ detailData.planCode || '-' }}</span> <span class="info-value">{{ detailData.planCode || '-' }}</span>
</div> </div>
<div class="detail-item">
<label class="detail-label">实验对象:</label>
<span class="detail-value">{{ getTestObjectText(detailData.testObject) || '-' }}</span>
</div> </div>
<div class="detail-item"> <div class="info-row">
<label class="detail-label">负责人:</label> <div class="info-item">
<span class="detail-value">{{ detailData.person?.userName || '-' }}</span> <label class="info-label">实验对象:</label>
<span class="info-value">{{ getTestObjectText(detailData.testObject) || '-' }}</span>
</div> </div>
<div class="detail-item"> <div class="info-item">
<label class="detail-label">开始时间:</label> <label class="info-label">负责人:</label>
<span class="detail-value">{{ detailData.beginTime ? formatDate(detailData.beginTime) : '-' }}</span> <span class="info-value">{{ detailData.person?.userName || '-' }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<label class="info-label">开始时间:</label>
<span class="info-value">{{ detailData.beginTime ? formatDate(detailData.beginTime) : '-' }}</span>
</div>
<div class="info-item">
<label class="info-label">结束时间:</label>
<span class="info-value">{{ detailData.endTime ? formatDate(detailData.endTime) : '-' }}</span>
</div> </div>
<div class="detail-item">
<label class="detail-label">结束时间:</label>
<span class="detail-value">{{ detailData.endTime ? formatDate(detailData.endTime) : '-' }}</span>
</div> </div>
</div> </div>
</div> </div>
<!-- 实验设备 --> <!-- 实验设备 -->
<div v-if="detailData.testDevice" class="detail-section"> <div v-if="detailData.testDevice" class="detail-card">
<h3 class="section-title">实验设备</h3> <h3 class="card-title">实验设备</h3>
<div class="device-list"> <div class="card-content">
<span v-for="(device, index) in detailData.testDevice.split(',')" :key="index" class="device-tag"> <div v-for="(device, index) in detailData.testDevice.split(',')" :key="index" class="info-item">
{{ device.trim() }} <label class="info-label">设备{{ index + 1 }}:</label>
</span> <span class="info-value">{{ device.trim() }}</span>
</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>
<!-- 兼容旧格式如果没有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>
<div class="step-content">{{ step.trim() }}</div>
</div> </div>
</div> </div>
</div> </div>
<!-- 实验信息 --> <!-- 实验信息 -->
<div class="detail-section"> <div class="detail-card">
<h3 class="section-title">实验信息</h3> <h3 class="card-title">实验信息</h3>
<div class="detail-textarea"> <div class="card-content">
<label class="detail-label">实验说明:</label> <div class="info-item full-width">
<div class="detail-text">{{ detailData.testInfo || '-' }}</div> <label class="info-label">实验说明:</label>
<div class="info-value">{{ detailData.testInfo || '-' }}</div>
</div> </div>
<div class="detail-textarea"> <div class="info-item full-width">
<label class="detail-label">实验设置:</label> <label class="info-label">实验设置:</label>
<div class="detail-text">{{ detailData.testSetting || '-' }}</div> <div class="info-value">{{ detailData.testSetting || '-' }}</div>
</div>
<div class="info-item full-width">
<label class="info-label">解决方案:</label>
<div class="info-value">{{ detailData.testSolutions || '-' }}</div>
</div> </div>
<div class="detail-textarea">
<label class="detail-label">解决方案:</label>
<div class="detail-text">{{ detailData.testSolutions || '-' }}</div>
</div> </div>
</div> </div>
<!-- 参与人员 --> <!-- 参与人员 -->
<div v-if="detailData.persons && detailData.persons.length > 0" class="detail-section"> <div v-if="detailData.persons && detailData.persons.length > 0" class="detail-card">
<h3 class="section-title">参与人员</h3> <h3 class="card-title">参与人员</h3>
<div class="participant-list"> <div class="card-content">
<div v-for="(person, index) in detailData.persons" :key="person.id" class="participant-item"> <div v-for="(person, index) in detailData.persons" :key="person.id" class="info-row">
<span class="participant-name">{{ person.userName }}</span> <div class="info-item">
<span class="participant-team">{{ person.teamName }}</span> <label class="info-label">姓名:</label>
<span class="info-value">{{ person.userName }}</span>
</div>
<div class="info-item">
<label class="info-label">团队:</label>
<span class="info-value">{{ person.teamName }}</span>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- 巡检项目 --> <!-- 巡检项目 -->
<div v-if="detailData.inspectionItemList && detailData.inspectionItemList.length > 0" class="detail-section"> <div v-if="detailData.inspectionItemList && detailData.inspectionItemList.length > 0" class="detail-card">
<h3 class="section-title">巡检项目</h3> <h3 class="card-title">巡检项目</h3>
<div class="inspection-list"> <div class="card-content">
<div v-for="(item, index) in detailData.inspectionItemList" :key="item.id" class="inspection-item"> <div v-for="(item, index) in detailData.inspectionItemList" :key="item.id" class="info-row">
<span class="inspection-name">{{ item.name }}</span> <div class="info-item">
<span class="inspection-type">{{ item.type }}</span> <label class="info-label">项目名称:</label>
<span class="info-value">{{ item.name }}</span>
</div>
<div class="info-item">
<label class="info-label">项目类型:</label>
<span class="info-value">{{ item.type }}</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<div v-else class="loading-details">
<el-skeleton :count="6" :columns="2" />
</div>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="showDetailDialog = false">关闭</el-button> <el-button @click="showDetailDialog = false">关闭</el-button>
@ -618,7 +576,6 @@ import router from '@/router';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { shiyanDetail, shiyanlist, addshiyan, updateshiyan } from '@/api/zhinengxunjian/shiyan/index'; import { shiyanDetail, shiyanlist, addshiyan, updateshiyan } from '@/api/zhinengxunjian/shiyan/index';
import { xunjianUserlist } from '@/api/zhinengxunjian/xunjian/index'; import { xunjianUserlist } from '@/api/zhinengxunjian/xunjian/index';
import { addjiedian, updatejiedian } from '@/api/zhinengxunjian/jiedian/index';
// 1. 选项卡状态管理 // 1. 选项卡状态管理
const activeTab = ref('plan'); // 默认为"巡检计划" const activeTab = ref('plan'); // 默认为"巡检计划"
const timeRange = ref('month'); // 统计时间范围:月/周/日 const timeRange = ref('month'); // 统计时间范围:月/周/日
@ -952,10 +909,6 @@ const handleSave = async () => {
personIds: formData.value.participants.join(','), personIds: formData.value.participants.join(','),
inspectionItems: '', inspectionItems: '',
testSolutions: formData.value.riskMitigation, testSolutions: formData.value.riskMitigation,
testStep: formData.value.steps
.filter((step) => step.name.trim() || step.intendedPurpose.trim())
.map((step) => `${step.name || ''}: ${step.intendedPurpose || ''}`)
.join(','),
testDevice: formData.value.equipments testDevice: formData.value.equipments
.filter((equip) => equip.selected) .filter((equip) => equip.selected)
.map((equip) => equip.name) .map((equip) => equip.name)
@ -965,83 +918,15 @@ const handleSave = async () => {
id: editRecordId.value // 若后端用planId等需改为对应字段名 id: editRecordId.value // 若后端用planId等需改为对应字段名
}; };
// 4. 处理步骤数据并调用接口 // 调用接口
let response; 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) { 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); response = await updateshiyan(requestData);
} else { } else {
// 新增模式调用addjiedian接口创建步骤
const jiedianResponse = await addjiedian(stepsData);
if (jiedianResponse.code !== 200) {
ElMessage.error('创建步骤失败');
return;
}
// 获取返回的ids实际返回格式中msg字段包含ids字符串
if (jiedianResponse.code === 200 && jiedianResponse.msg) {
nodeIds = jiedianResponse.msg;
} else {
ElMessage.warning('未获取到有效的步骤ID');
return;
}
// 新增模式调用添加接口删除请求参数中的id避免后端报错 // 新增模式调用添加接口删除请求参数中的id避免后端报错
const { id, ...addData } = requestData; const { id, ...addData } = requestData;
// 添加nodeIds字段
addData.nodeIds = nodeIds;
response = await addshiyan(addData); response = await addshiyan(addData);
} }
@ -1072,11 +957,6 @@ const resetForm = () => {
envRequirements: '', // 环境要求为空 envRequirements: '', // 环境要求为空
manager: '', // 负责人为空 manager: '', // 负责人为空
participants: [], // 参与人员为空数组 participants: [], // 参与人员为空数组
steps: [
{ name: '', intendedPurpose: '', intendedTime: '' },
{ name: '', intendedPurpose: '', intendedTime: '' },
{ name: '', intendedPurpose: '', intendedTime: '' }
], // 步骤内容为空
equipments: [ equipments: [
{ name: '服务器(型号:XYZ-9000)', selected: false }, { name: '服务器(型号:XYZ-9000)', selected: false },
{ name: '网络测试仪(型号:NT-5000)', selected: false }, { name: '网络测试仪(型号:NT-5000)', selected: false },
@ -1152,45 +1032,6 @@ const handleEditRecord = async (row) => {
const recordDetail = detailResponse.data.rows?.[0] || detailResponse.data; const recordDetail = detailResponse.data.rows?.[0] || detailResponse.data;
// 兼容两种数据结构可能在rows数组中也可能直接在data中 // 兼容两种数据结构可能在rows数组中也可能直接在data中
// 3. 处理步骤数据优先从nodes数组中提取
const steps = [];
// 如果有nodes数组优先从nodes中提取步骤数据
if (recordDetail.nodes && Array.isArray(recordDetail.nodes)) {
// 复制nodes数组并按code升序排序
const sortedNodes = [...recordDetail.nodes].sort((a, b) => (a.code || 0) - (b.code || 0));
// 转换为所需的格式
sortedNodes.forEach((node) => {
if ((node.name && node.name.trim()) || (node.intendedPurpose && node.intendedPurpose.trim())) {
steps.push({
name: node.name || '',
intendedPurpose: node.intendedPurpose || '',
intendedTime: node.intendedTime || ''
});
}
});
}
// 如果nodes中没有数据回退到从testStep字符串解析
else if (recordDetail.testStep) {
// 拆分字符串
const stepItems = recordDetail.testStep.split(',');
stepItems.forEach((stepText) => {
// 移除序号前缀(如"1. "),只保留内容
const content = stepText.replace(/^\d+\.\s*/, '').trim();
if (content) {
// 对于旧格式数据我们将内容放入intendedPurpose字段
steps.push({
name: `步骤${steps.length + 1}`,
intendedPurpose: content,
intendedTime: ''
});
}
});
}
// 确保至少有3个步骤如果解析后为空
while (steps.length < 3) {
steps.push({ name: '', intendedPurpose: '', intendedTime: '' });
}
// 4. 处理testDevice将逗号分隔的字符串转换为设备数组 // 4. 处理testDevice将逗号分隔的字符串转换为设备数组
const equipments = []; const equipments = [];
if (recordDetail.testDevice) { if (recordDetail.testDevice) {
@ -1236,7 +1077,6 @@ const handleEditRecord = async (row) => {
envRequirements: recordDetail.envRequirements || recordDetail.testSetting || '', envRequirements: recordDetail.envRequirements || recordDetail.testSetting || '',
manager: recordDetail.manager || recordDetail.personCharge || '', manager: recordDetail.manager || recordDetail.personCharge || '',
participants: participants, // 从personIds解析的数组 participants: participants, // 从personIds解析的数组
steps: steps, // 解析后的步骤数组
equipments: equipments, // 解析并合并后的设备数组 equipments: equipments, // 解析并合并后的设备数组
riskMitigation: recordDetail.riskMitigation || recordDetail.testSolutions || '' riskMitigation: recordDetail.riskMitigation || recordDetail.testSolutions || ''
}; };
@ -1375,53 +1215,23 @@ const formatDate = (dateString) => {
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}; };
// 根据步骤状态获取对应的样式类 // 日期时间格式化函数
const getStatusClass = (status) => { const formatDateTime = (dateString) => {
// 处理可能的数字输入 if (!dateString) return '';
const statusStr = status?.toString() || ''; const date = new Date(dateString);
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 year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`; const seconds = String(date.getSeconds()).padStart(2, '0');
} catch (error) { return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
return dateTime;
}
};
// 获取步骤状态文本
const getStepStatusText = (status) => {
const statusStr = status?.toString() || '';
const statusMap = {
'1': '待执行',
'2': '执行中',
'3': '已完成',
'4': '已延期',
'5': '失败'
};
return statusMap[statusStr] || '未知状态';
}; };
</script> </script>
<style scoped> <style scoped>
@import url('./css/step-bars.css');
@import url('./css/detail-dialog.css'); @import url('./css/detail-dialog.css');
.operation-inspection { .operation-inspection {
padding: 20px; padding: 20px;
background-color: #f9fbfd; background-color: #f9fbfd;
@ -2095,53 +1905,6 @@ const getStepStatusText = (status) => {
box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1); box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1);
} }
/* 试验步骤样式 */
.steps-container {
border: 1px solid #e4e7ed;
border-radius: 8px;
padding: 16px;
width: 100%;
}
.step-item {
display: flex;
align-items: center;
margin-bottom: 16px;
width: 100%;
}
.step-item:last-child {
margin-bottom: 0;
}
.step-number {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 50%;
background-color: #165dff;
color: white;
font-size: 16px;
font-weight: 600;
margin-right: 16px;
flex-shrink: 0;
}
.step-input:focus {
border-color: #165dff;
outline: none;
}
.add-step-btn {
color: #165dff;
margin-top: 12px;
width: 100%;
text-align: center;
font-size: 14px;
}
/* 设备列表样式 */ /* 设备列表样式 */
.equipment-list { .equipment-list {
border: 1px solid #e4e7ed; border: 1px solid #e4e7ed;
@ -2200,7 +1963,7 @@ const getStepStatusText = (status) => {
border-color: #0d47a1; border-color: #0d47a1;
} }
/* 响应式设计 */ /* 响应式设计 - 保留必要的覆盖样式 */
@media (max-width: 768px) { @media (max-width: 768px) {
.custom-experiment-dialog { .custom-experiment-dialog {
width: 90% !important; width: 90% !important;
@ -2220,5 +1983,13 @@ const getStepStatusText = (status) => {
.new-equipment-input { .new-equipment-input {
width: 100%; width: 100%;
} }
.info-row {
flex-direction: column;
}
.info-item {
min-width: 100%;
}
} }
</style> </style>

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="operation-inspection"> <div class="operation-inspection">
<!-- 顶部导航选项卡 --> <!-- 顶部导航选项卡 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab active" @click="handleInspection3">试验管理</div> <div class="nav-tab active" @click="handleInspection3">试验管理</div>
@ -10,16 +10,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 头部操作按钮 -->
<div class="header-container">
<div class="header-actions">
<el-button type="primary" class="export-btn">筛选</el-button>
<el-button type="primary" class="create-btn">导入数据</el-button>
</div>
</div>
<!-- 选项卡和按钮组合 --> <!-- 选项卡和按钮组合 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">
<div style="display: flex; align-items: center; gap: 10px"> <div style="display: flex; align-items: center; gap: 10px">
@ -56,7 +47,7 @@
class="date-picker" class="date-picker"
></el-date-picker> ></el-date-picker>
<el-button type="primary" class="search-btn"> 搜索 </el-button> <el-button icon="Search" type="primary" class="search-btn"> 搜索 </el-button>
</div> </div>
</div> </div>
@ -71,11 +62,20 @@
<div class="stat-grid"> <div class="stat-grid">
<div class="stat-card"> <div class="stat-card">
<p class="stat-label">本月完成试验</p> <p class="stat-label">本月完成试验</p>
<p class="stat-value">{{ statData.completed }}<span class="stat-change up">较上月 2.4%</span></p> <p class="stat-value">
{{ statData.completed
}}<span class="stat-change" :class="statData.completedGrowth >= 0 ? 'up' : 'down'">
较上月 {{ statData.completedGrowth >= 0 ? '↑' : '↓' }}{{ Math.abs(statData.completedGrowth) }}%
</span>
</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">{{ statData.passRate }}%<span class="stat-change up">较上月 5.6%</span></p> <p class="stat-value">
{{ statData.passRate }}%<span class="stat-change" :class="statData.passRateGrowth >= 0 ? 'up' : 'down'">
较上月 {{ statData.passRateGrowth >= 0 ? '↑' : '↓' }}{{ Math.abs(statData.passRateGrowth) }}%
</span>
</p>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<p class="stat-label">待分析记录</p> <p class="stat-label">待分析记录</p>
@ -83,7 +83,12 @@
</div> </div>
<div class="stat-card"> <div class="stat-card">
<p class="stat-label">平均试验时长</p> <p class="stat-label">平均试验时长</p>
<p class="stat-value">{{ statData.avgDuration }}<span class="stat-change down">较上月 9.4分钟</span></p> <p class="stat-value">
{{ statData.avgDuration
}}<span class="stat-change" :class="statData.avgDurationGrowth >= 0 ? 'down' : 'up'">
较上月 {{ statData.avgDurationGrowth >= 0 ? '↑' : '↓' }}{{ Math.abs(statData.avgDurationGrowth) }}分钟
</span>
</p>
</div> </div>
</div> </div>
@ -149,7 +154,7 @@
</div> </div>
<div class="record-actions"> <div class="record-actions">
<button class="operate-btn view-btn">查看详情</button> <button class="operate-btn view-btn" @click="handleViewDetail(record)">查看详情</button>
<button class="operate-btn report-btn">生成报告</button> <button class="operate-btn report-btn">生成报告</button>
</div> </div>
</div> </div>
@ -192,7 +197,7 @@
<button class="operate-btn execute-btn" v-if="scope.row.status === 'drafted'">执行</button> <button class="operate-btn execute-btn" v-if="scope.row.status === 'drafted'">执行</button>
<button class="operate-btn pause-btn" v-if="scope.row.status === 'in-progress'">暂停</button> <button class="operate-btn pause-btn" v-if="scope.row.status === 'in-progress'">暂停</button>
<button class="operate-btn resume-btn" v-if="scope.row.status === 'paused'">恢复</button> <button class="operate-btn resume-btn" v-if="scope.row.status === 'paused'">恢复</button>
<button class="operate-btn view-btn">查看详情</button> <button class="operate-btn view-btn" @click="handleViewDetail(scope.row)">查看详情</button>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
@ -220,7 +225,7 @@
<div class="operation-buttons"> <div class="operation-buttons">
<button class="operate-btn accept-btn" v-if="scope.row.status === 'pending'">接受</button> <button class="operate-btn accept-btn" v-if="scope.row.status === 'pending'">接受</button>
<button class="operate-btn complete-btn" v-if="scope.row.status === 'accepted'">完成</button> <button class="operate-btn complete-btn" v-if="scope.row.status === 'accepted'">完成</button>
<button class="operate-btn view-btn">查看详情</button> <button class="operate-btn view-btn" @click="handleViewDetail(scope.row)">查看详情</button>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
@ -241,6 +246,106 @@
@size-change="handleSizeChange" @size-change="handleSizeChange"
></el-pagination> ></el-pagination>
</div> </div>
<!-- 详情弹窗 -->
<el-dialog v-model="detailDialogVisible" title="任务详情" width="800px" :close-on-click-modal="false" center>
<div v-if="detailData" class="task-detail-container">
<div class="detail-card">
<h3 class="card-title">基本信息</h3>
<div class="info-row">
<span class="info-label">任务名称</span>
<span class="info-value">{{ detailData.taskName }}</span>
<span class="info-label">任务状态</span>
<span class="info-value" :class="getStatusClass(detailData.status)">
{{ getStatusText(detailData.status) }}
</span>
</div>
<div class="info-row">
<span class="info-label">测试对象</span>
<span class="info-value">{{ detailData.testObject }}</span>
<span class="info-label">完成进度</span>
<span class="info-value">{{ detailData.progress }}%</span>
</div>
<div class="info-row">
<span class="info-label">开始时间</span>
<span class="info-value">{{ detailData.beginTime }}</span>
<span class="info-label">结束时间</span>
<span class="info-value">{{ detailData.endTime }}</span>
</div>
<div class="info-row">
<span class="info-label">时间信息</span>
<span class="info-value">{{ detailData.timeInfo ? detailData.timeInfo.replace(/,/g, '—') : '-' }}</span>
</div>
</div>
<div class="detail-card">
<h3 class="card-title">执行人信息</h3>
<div v-if="detailData.personInfo" class="info-row">
<span class="info-label">执行人姓名</span>
<span class="info-value">{{ detailData.personInfo.userName }}</span>
<span class="info-label">联系电话</span>
<span class="info-value">{{ detailData.personInfo.phonenumber }}</span>
</div>
<div v-if="detailData.personInfo" class="info-row">
<span class="info-label">性别</span>
<span class="info-value">{{ detailData.personInfo.sex === '1' ? '男' : '女' }}</span>
</div>
</div>
<div class="detail-card">
<h3 class="card-title">关联计划</h3>
<div v-if="detailData.testPlan" class="info-row">
<span class="info-label">计划名称</span>
<span class="info-value">{{ detailData.testPlan.planName }}</span>
<span class="info-label">计划编号</span>
<span class="info-value">{{ detailData.testPlan.planCode }}</span>
</div>
<div v-if="detailData.testPlan" class="info-row">
<span class="info-label">计划时间</span>
<span class="info-value">{{ detailData.testPlan.beginTime }} {{ detailData.testPlan.endTime }}</span>
</div>
<div v-if="detailData.testPlan && detailData.testPlan.testDevice" class="info-row">
<span class="info-label">测试设备</span>
<span class="info-value">{{ detailData.testPlan.testDevice }}</span>
</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)">
{{ node.status === '2' ? '未完成' : '已完成' }}
</div>
</div>
</div>
</div>
<div v-if="detailData.testFinal || detailData.failReason" class="detail-card">
<h3 class="card-title">执行结果</h3>
<div v-if="detailData.testFinal" class="info-row">
<span class="info-label">测试结果</span>
<span class="info-value">{{ detailData.testFinal }}</span>
</div>
<div v-if="detailData.failReason" class="info-row">
<span class="info-label">失败原因</span>
<span class="info-value fail-reason">{{ detailData.failReason }}</span>
</div>
</div>
</div>
<div v-else class="loading-details">
<p>加载中...</p>
</div>
<template #footer>
<el-button @click="detailDialogVisible = false">关闭</el-button>
</template>
</el-dialog>
</div> </div>
</div> </div>
</template> </template>
@ -248,11 +353,11 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import router from '@/router'; import router from '@/router';
import { ElMessage } from 'element-plus';
import { syrenwulist, syrenwujilu, syrenwuDetail } from '@/api/zhinengxunjian/shiyan/renwu'; import { syrenwulist, syrenwujilu, syrenwuDetail } from '@/api/zhinengxunjian/shiyan/renwu';
// 1. 选项卡状态管理 // 1. 选项卡状态管理
const activeTab = ref('record'); // 默认显示"试验记录" const activeTab = ref('record'); // 默认显示"试验记录"
const showFilter = ref(false);
// 2. 筛选条件 // 2. 筛选条件
const filterStatus = ref('all'); const filterStatus = ref('all');
@ -277,7 +382,11 @@ const statData = ref({
completed: 0, completed: 0,
passRate: 0, passRate: 0,
pendingAnalysis: 0, pendingAnalysis: 0,
avgDuration: '0分钟' avgDuration: '0分钟',
// 新增:增长率相关数据
completedGrowth: 0,
passRateGrowth: 0,
avgDurationGrowth: 0
}); });
// 6. 分页相关 // 6. 分页相关
@ -310,7 +419,8 @@ const getStatisticsData = async () => {
const response = await syrenwujilu({ projectId: 1 }); const response = await syrenwujilu({ projectId: 1 });
console.log('syrenwujilu API响应:', response); console.log('syrenwujilu API响应:', response);
if (response && response.data) { // 确保接口返回成功状态码(code=200)且有数据
if (response && response.code === 200 && response.data) {
// 映射API返回的数据到statData // 映射API返回的数据到statData
const apiData = response.data; const apiData = response.data;
@ -319,11 +429,21 @@ const getStatisticsData = async () => {
statData.value.pendingAnalysis = parseInt(apiData.failCount) || 0; statData.value.pendingAnalysis = parseInt(apiData.failCount) || 0;
// 格式化平均试验时长 // 格式化平均试验时长
const avgTime = parseInt(apiData.averageTestTime) || 0; const avgTime = parseFloat(apiData.averageTestTime) || 0;
statData.value.avgDuration = `${avgTime}分钟`; statData.value.avgDuration = `${avgTime}分钟`;
// 处理增长率数据
statData.value.completedGrowth = parseInt(apiData.finishCountAdd) || 0;
statData.value.passRateGrowth = parseFloat(apiData.passValueAdd) || 0;
statData.value.avgDurationGrowth = parseFloat(apiData.averageTestTimeAdd) || 0;
} else {
console.warn('获取统计数据失败或返回格式不正确:', response);
// 可以在这里添加错误提示或默认值处理
ElMessage.warning('获取统计数据失败,请稍后重试');
} }
} catch (error) { } catch (error) {
console.error('获取统计数据失败:', error); console.error('获取统计数据异常:', error);
ElMessage.error('获取统计数据异常,请稍后重试');
} }
}; };
@ -339,15 +459,13 @@ const formatDate = (dateString) => {
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
}; };
// 11. 辅助方法:格式化时长(假设单位为分钟) // 11. 辅助方法:格式化日期时间
const formatDuration = (minutes) => { const formatDateTime = (dateTimeString) => {
if (minutes < 60) { if (!dateTimeString) return '未知时间';
return `${minutes}分钟`; const date = new Date(dateTimeString);
} else { return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(
const hours = Math.floor(minutes / 60); date.getHours()
const mins = minutes % 60; ).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
return `${hours}小时${mins}分钟`;
}
}; };
// 12. 辅助方法:获取节点状态类名 // 12. 辅助方法:获取节点状态类名
@ -392,15 +510,19 @@ const getProgressColor = (status) => {
// 15. 辅助方法:获取状态文本 // 15. 辅助方法:获取状态文本
const getStatusText = (status) => { const getStatusText = (status) => {
const statusMap = { const statusMap = {
'1': '进行中', '1': '待执行',
'completed': '通过', '4': '执行中',
'2': '已延期',
'5': '已完成',
'3': '失败',
'completed': '已完成',
'failed': '失败', 'failed': '失败',
'paused': '已暂停', 'paused': '已延期',
'drafted': '草稿', 'drafted': '待执行',
'in-progress': '行中', 'in-progress': '行中',
'normal': '正常', 'normal': '已完成',
'attention': '需关注', 'attention': '执行中',
'problem': '有问题' 'problem': '失败'
}; };
return statusMap[status] || '未知状态'; return statusMap[status] || '未知状态';
}; };
@ -408,10 +530,15 @@ const getStatusText = (status) => {
// 16. 辅助方法:获取任务状态文本 // 16. 辅助方法:获取任务状态文本
const getTaskStatusText = (status) => { const getTaskStatusText = (status) => {
const statusMap = { const statusMap = {
'pending': '待接受', 'pending': '待执行',
'accepted': '行中', 'accepted': '行中',
'completed': '已完成', 'completed': '已完成',
'rejected': '已拒绝' 'rejected': '已拒绝',
'1': '待执行',
'4': '执行中',
'2': '已延期',
'5': '已完成',
'3': '失败'
}; };
return statusMap[status] || '未知状态'; return statusMap[status] || '未知状态';
}; };
@ -419,18 +546,22 @@ const getTaskStatusText = (status) => {
// 17. 辅助方法:获取状态类名 // 17. 辅助方法:获取状态类名
const getStatusClass = (status) => { const getStatusClass = (status) => {
const classMap = { const classMap = {
'1': 'status-in-progress', '1': 'tag-pending', // 待执行
'completed': 'status-passed', '4': 'tag-executing', // 执行中
'2': 'tag-delayed', // 已延期
'5': 'tag-completed', // 已完成
'3': 'status-failed', // 失败
'completed': 'tag-completed',
'failed': 'status-failed', 'failed': 'status-failed',
'paused': 'status-paused', 'paused': 'tag-delayed',
'pending': 'status-pending', 'pending': 'tag-pending',
'accepted': 'status-accepted', 'accepted': 'tag-pending',
'rejected': 'status-rejected', 'rejected': 'status-failed',
'normal': 'status-normal', 'normal': 'tag-completed',
'attention': 'status-attention', 'attention': 'tag-executing',
'problem': 'status-problem' 'problem': 'status-failed'
}; };
return classMap[status] || 'status-pending'; return classMap[status] || 'tag-pending';
}; };
// 18. 分页事件处理 // 18. 分页事件处理
@ -480,10 +611,46 @@ const handleInspectionManagement3 = () => {
router.push('/rili/shiyanjilu'); router.push('/rili/shiyanjilu');
}; };
// 20. 组件挂载时获取数据 // 20. 详情弹窗相关
onMounted(() => { const detailDialogVisible = ref(false);
getStatisticsData(); const detailData = ref(null);
getTestRecords(); const isDetailLoading = ref(false);
// 22. 处理查看详情
const handleViewDetail = async (row) => {
try {
if (!row || !row.id) {
ElMessage.error('记录ID不存在无法查看详情');
return;
}
isDetailLoading.value = true;
const response = await syrenwuDetail(row.id);
if (response && response.code === 200) {
detailData.value = response.data;
detailDialogVisible.value = true;
} else {
ElMessage.error(response?.msg || '获取任务详情失败');
}
} catch (error) {
console.error('查看详情失败:', error);
ElMessage.error('获取任务详情失败');
} finally {
isDetailLoading.value = false;
}
};
// 24. 组件挂载时获取数据 - 确保页面进入时立即调用接口
onMounted(async () => {
// 直接并立即调用数据接口,确保页面加载时能获取到最新数据
try {
// 并行调用两个数据接口以提高加载速度
await Promise.all([getStatisticsData(), getTestRecords()]);
} catch (error) {
console.error('数据加载失败:', error);
ElMessage.error('数据加载失败,请刷新页面重试');
}
}); });
</script> </script>
@ -497,35 +664,40 @@ onMounted(() => {
min-height: 100vh; min-height: 100vh;
} }
/* 2. 顶部导航选项卡 */
.navigation-tabs { .navigation-tabs {
display: flex; display: flex;
margin-bottom: 20px;
background-color: #fff; background-color: #fff;
border-radius: 4px; border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
margin-bottom: 20px; padding: 2px;
overflow: hidden;
} }
.nav-tab { .nav-tab {
padding: 12px 24px; padding: 12px 24px;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.3s ease;
border-radius: 4px;
font-size: 14px; font-size: 14px;
color: #6b7280; color: #606266;
border-right: 1px solid #f0f0f0;
flex: 1; flex: 1;
text-align: center; text-align: center;
border-right: 1px solid #f0f0f0;
} }
.nav-tab:last-child { .nav-tab:last-child {
border-right: none; border-right: none;
} }
.nav-tab:hover:not(.active) {
background-color: #f3f4f6; .nav-tab:hover {
color: #409eff;
background-color: #ecf5ff;
} }
.nav-tab.active { .nav-tab.active {
background-color: #165dff; background-color: #409eff;
color: #fff; color: #fff;
font-weight: 500; box-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
} }
/* 3. 选项卡样式 */ /* 3. 选项卡样式 */
@ -570,24 +742,10 @@ onMounted(() => {
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
} }
.action-buttons {
display: flex;
gap: 12px;
}
.el-select, .el-select,
.date-picker { .date-picker {
width: 160px; width: 160px;
} }
.search-btn,
.export-btn {
background-color: #165dff;
border-color: #165dff;
}
.filter-btn {
background-color: #f3f4f6;
color: #6b7280;
border-color: #e5e7eb;
}
/* 6. 表格容器 */ /* 6. 表格容器 */
.table-container { .table-container {
@ -623,57 +781,58 @@ onMounted(() => {
/* 8. 状态标签样式 */ /* 8. 状态标签样式 */
.status-tag { .status-tag {
display: inline-block; display: inline-block;
padding: 2px 8px; padding: 4px 10px;
border-radius: 4px; border-radius: 6px;
font-size: 12px; font-size: 12px;
font-weight: 500;
border: 1px solid transparent;
} }
.status-drafted {
background-color: #e0efff; /* 与试验任务页面相同的标签样式 */
color: #165dff; .tag-pending {
background-color: #e6f7ff;
color: #1677ff;
border-color: #91d5ff;
} }
.tag-delayed {
background-color: #fff2f0;
color: #ff4d4f;
border-color: #ffccc7;
}
.tag-executing {
background-color: #fffbe6;
color: #fa8c16;
border-color: #ffe58f;
}
.tag-completed {
background-color: #f6ffed;
color: #52c41a;
border-color: #b7eb8f;
}
/* 保留原有的部分样式以确保兼容性 */
.status-in-progress { .status-in-progress {
background-color: #e0f2fe; background-color: #fffbe6;
color: #0284c7; color: #fa8c16;
border-color: #ffe58f;
} }
.status-completed { .status-completed {
background-color: #e6ffed; background-color: #f6ffed;
color: #00b42a; color: #52c41a;
} border-color: #b7eb8f;
.status-paused {
background-color: #f2f3f5;
color: #86909c;
} }
.status-pending { .status-pending {
background-color: #f9fafb; background-color: #e6f7ff;
color: #6b7280; color: #1677ff;
} border-color: #91d5ff;
.status-accepted {
background-color: #eff6ff;
color: #2563eb;
}
.status-rejected {
background-color: #fee2e2;
color: #dc2626;
}
.status-normal {
background-color: #e6ffed;
color: #00b42a;
}
.status-attention {
background-color: #fff7e0;
color: #ff7d00;
}
.status-problem {
background-color: #fff2f0;
color: #f5222d;
}
.status-passed {
background-color: #e6ffed;
color: #00b42a;
} }
.status-failed { .status-failed {
background-color: #fee2e2; background-color: #fff2f0;
color: #dc2626; color: #ff4d4f;
border-color: #ffccc7;
} }
/* 9. 操作按钮样式 */ /* 9. 操作按钮样式 */
@ -691,30 +850,6 @@ onMounted(() => {
background: none; background: none;
transition: all 0.2s; transition: all 0.2s;
} }
.edit-btn {
color: #165dff;
}
.edit-btn:hover {
background-color: #e8f3ff;
}
.execute-btn {
color: #00b42a;
}
.execute-btn:hover {
background-color: #e6ffed;
}
.pause-btn {
color: #ff7d00;
}
.pause-btn:hover {
background-color: #fff7e0;
}
.resume-btn {
color: #722ed1;
}
.resume-btn:hover {
background-color: #f3e8ff;
}
.view-btn { .view-btn {
color: #165dff; color: #165dff;
} }
@ -911,12 +1046,6 @@ onMounted(() => {
margin-bottom: 8px; margin-bottom: 8px;
} }
.step-name {
font-size: 12px;
color: #6b7280;
text-align: center;
}
.progress-line { .progress-line {
flex: 1; flex: 1;
height: 2px; height: 2px;

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="inspection-tasks"> <div class="inspection-tasks">
<!-- 导航栏 --> <!-- 导航栏 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab active" @click="handleInspection3">试验管理</div> <div class="nav-tab active" @click="handleInspection3">试验管理</div>
@ -10,7 +10,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 选项卡 --> <!-- 选项卡 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">
@ -29,7 +29,7 @@
<el-option label="待执行" value="1"></el-option> <el-option label="待执行" value="1"></el-option>
<el-option label="执行中" value="4"></el-option> <el-option label="执行中" value="4"></el-option>
<el-option label="已延期" value="2"></el-option> <el-option label="已延期" value="2"></el-option>
<!-- 接口暂停对应页面已延期 -->
<el-option label="已完成" value="5"></el-option> <el-option label="已完成" value="5"></el-option>
<el-option label="失败" value="3"></el-option> <el-option label="失败" value="3"></el-option>
</el-select> </el-select>
@ -137,7 +137,7 @@
</div> </div>
<!-- 添加新任务弹窗 --> <!-- 添加新任务弹窗 -->
<el-dialog v-model="createTaskDialogVisible" title="添加新任务" width="700px" :before-close="handleCancelCreateTask"> <el-dialog v-model="createTaskDialogVisible" title="添加新任务" width="750px" :before-close="handleCancelCreateTask">
<el-form ref="createTaskFormRef" :model="createTaskForm" :rules="createTaskRules" label-width="80px"> <el-form ref="createTaskFormRef" :model="createTaskForm" :rules="createTaskRules" label-width="80px">
<el-form-item label="任务名称" prop="taskName"> <el-form-item label="任务名称" prop="taskName">
<el-input v-model="createTaskForm.taskName" placeholder="输入任务名称" /> <el-input v-model="createTaskForm.taskName" placeholder="输入任务名称" />
@ -338,7 +338,7 @@
<div v-if="node.remark" class="step-remark">备注{{ node.remark }}</div> <div v-if="node.remark" class="step-remark">备注{{ node.remark }}</div>
</div> </div>
<div class="step-status" :class="getStatusClass(node.status)"> <div class="step-status" :class="getStatusClass(node.status)">
{{ getStepStatusText(node.status) }} {{ node.status === '2' ? '未完成' : '已完成' }}
</div> </div>
</div> </div>
</div> </div>
@ -377,7 +377,7 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted, getCurrentInstance } from 'vue'; import { ref, computed, onMounted } from 'vue';
import router from '@/router'; import router from '@/router';
// 引入已定义的接口函数 // 引入已定义的接口函数
import { syrenwulist, syrenwuDetail, addsyrenwu, updatesyrenwu } from '@/api/zhinengxunjian/shiyan/renwu'; import { syrenwulist, syrenwuDetail, addsyrenwu, updatesyrenwu } from '@/api/zhinengxunjian/shiyan/renwu';
@ -386,7 +386,6 @@ 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 } from 'element-plus';
import { Plus } from '@element-plus/icons-vue';
/** /**
* 根据任务ID获取完整的任务详情数据 * 根据任务ID获取完整的任务详情数据
@ -428,7 +427,6 @@ const groupNodesByModule = (nodes) => {
if (!nodes || !Array.isArray(nodes)) { if (!nodes || !Array.isArray(nodes)) {
return []; return [];
} }
// 这里简单地将所有节点放在一个默认模块下实际应用中可以根据节点数据的module字段进行分组 // 这里简单地将所有节点放在一个默认模块下实际应用中可以根据节点数据的module字段进行分组
const defaultGroup = { const defaultGroup = {
module: '测试步骤', module: '测试步骤',
@ -856,9 +854,18 @@ const handleAction = async (task) => {
id: task.id id: task.id
}; };
// 声明resultType变量提升作用域
let resultType = null;
// 3. 根据任务状态只修改状态相关的字段 // 3. 根据任务状态只修改状态相关的字段
if (task.status === '4') { if (task.status === '4') {
// 执行中 → 完成:使用弹窗确认结果 // 执行中 → 完成:使用弹窗确认结果
try {
// 保持原有结构
} catch (error) {
console.error('捕获到异常:', error);
}
try { try {
const confirmResult = await ElMessageBox.confirm('请选择试验结果', '完成试验', { const confirmResult = await ElMessageBox.confirm('请选择试验结果', '完成试验', {
confirmButtonText: '正常', confirmButtonText: '正常',
@ -870,12 +877,14 @@ const handleAction = async (task) => {
updateParams.status = '5'; updateParams.status = '5';
updateParams.progress = 100; updateParams.progress = 100;
updateParams.testFinal = '正常'; updateParams.testFinal = '正常';
resultType = 'normal'; // 现在在外部作用域中定义
} catch (error) { } catch (error) {
if (error === 'cancel') { if (error === 'cancel') {
// 用户点击取消(异常) // 用户点击取消(异常)
updateParams.status = '5'; updateParams.status = '5';
updateParams.progress = 100; updateParams.progress = 100;
updateParams.testFinal = '异常'; updateParams.testFinal = '异常';
resultType = 'abnormal'; // 现在在外部作用域中定义
} else { } else {
// 关闭弹窗,不执行操作 // 关闭弹窗,不执行操作
return; return;
@ -887,6 +896,8 @@ const handleAction = async (task) => {
case '1': // 待执行 → 开始执行状态改为4 case '1': // 待执行 → 开始执行状态改为4
updateParams.status = '4'; updateParams.status = '4';
updateParams.progress = 10; // 初始进度10% updateParams.progress = 10; // 初始进度10%
// 设置开始时间为当前时间
updateParams.planBeginTime = new Date().toISOString().slice(0, 16).replace('T', ' ');
break; break;
case '2': // 已延期 → 重新安排状态改为1重置时间 case '2': // 已延期 → 重新安排状态改为1重置时间
updateParams.status = '1'; updateParams.status = '1';
@ -904,6 +915,30 @@ const handleAction = async (task) => {
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);
@ -1010,10 +1045,10 @@ const handleSaveTask = async () => {
status: '1', // 初始状态:待执行(必需) status: '1', // 初始状态:待执行(必需)
testPlanId: createTaskForm.value.relatedPlan, // 关联计划ID必需 testPlanId: createTaskForm.value.relatedPlan, // 关联计划ID必需
testSetting: '', // 测试设置 testSetting: '', // 测试设置
planBeginTime: createTaskForm.value.timeRange[0], // 计划开始时间 planBeginTime: '', // 计划开始时间(新增时为空)
progress: 0, // 初始进度0% progress: 0, // 初始进度0%
failReason: '', failReason: '',
failTime: now.toISOString(), failTime: '', // 失败时间(新增时为空)
failPhase: 0, failPhase: 0,
faileAnalyze: '', faileAnalyze: '',
faileTips: '', faileTips: '',
@ -1021,8 +1056,8 @@ const handleSaveTask = async () => {
testFinal: '', testFinal: '',
finalInfo: '', finalInfo: '',
pauseFor: '', pauseFor: '',
pauseTime: now.toISOString(), pauseTime: '', // 暂停时间(新增时为空)
planFinishTime: createTaskForm.value.timeRange[1], // 计划完成时间 planFinishTime: '', // 计划完成时间(新增时为空)
nodeIds: nodeIds // 步骤节点ID数组 nodeIds: nodeIds // 步骤节点ID数组
}; };
@ -1114,6 +1149,18 @@ onMounted(() => {
const pagedTasks = computed(() => { const pagedTasks = computed(() => {
return tasks.value; return tasks.value;
}); });
// 获取任务状态对应的CSS类
const getTaskStatusClass = (status) => {
const statusStr = status?.toString() || '';
const statusMap = {
'1': 'status-pending',
'2': 'status-delayed',
'3': 'status-failed',
'4': 'status-running',
'5': 'status-completed'
};
return statusMap[statusStr] || 'status-pending';
};
</script> </script>
<style scoped> <style scoped>
@ -1234,27 +1281,6 @@ const pagedTasks = computed(() => {
background-color: #52c41a; 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 { .task-card:hover {
transform: translateY(-3px); transform: translateY(-3px);
@ -1393,22 +1419,6 @@ const pagedTasks = computed(() => {
color: #165dff; color: #165dff;
} }
.start-btn,
.report-btn {
background-color: #165dff;
border-color: #165dff;
}
.reschedule-btn {
background-color: #ff7d00;
border-color: #ff7d00;
}
.complete-btn {
background-color: #00b42a;
border-color: #00b42a;
}
/* 分页区域样式 */ /* 分页区域样式 */
.pagination-section { .pagination-section {
display: flex; display: flex;
@ -1536,10 +1546,6 @@ const pagedTasks = computed(() => {
color: #e6a23c; color: #e6a23c;
} }
.status-running {
color: #409eff;
}
.status-completed { .status-completed {
color: #67c23a; color: #67c23a;
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="operation-inspection"> <div class="operation-inspection">
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab active" @click="handleInspection2">巡检管理</div> <div class="nav-tab active" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -8,7 +8,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<div class="header-container"> <div class="header-container">
<div class="header-actions"> <div class="header-actions">
<el-button type="primary" class="export-btn">筛选</el-button> <el-button type="primary" class="export-btn">筛选</el-button>
@ -127,14 +127,14 @@
<div class="space-y-4"> <div class="space-y-4">
<div> <div>
<div class="flex justify-between text-sm mb-1"> <div class="flex justify-between text-sm mb-1">
<span class="text-gray-600">完成率</span> <span class="text-gray-600">巡检完成率</span>
<span class="font-medium text-gray-800">{{ completionRate }}%</span> <span class="font-medium text-gray-800">{{ completionRate }}%</span>
</div> </div>
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden"> <div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
<div class="bg-blue-500 h-2 rounded-full transition-all duration-1500 ease-out" :style="{ width: completionRate + '%' }"></div> <div class="bg-blue-500 h-2 rounded-full transition-all duration-1500 ease-out" :style="{ width: completionRate + '%' }"></div>
</div> </div>
</div> </div>
<div> <!-- <div>
<div class="flex justify-between text-sm mb-1"> <div class="flex justify-between text-sm mb-1">
<span class="text-gray-600">解决率</span> <span class="text-gray-600">解决率</span>
<span class="font-medium text-gray-800">{{ resolutionRate }}%</span> <span class="font-medium text-gray-800">{{ resolutionRate }}%</span>
@ -142,10 +142,10 @@
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden"> <div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
<div class="bg-red-500 h-2 rounded-full transition-all duration-1500 ease-out" :style="{ width: resolutionRate + '%' }"></div> <div class="bg-red-500 h-2 rounded-full transition-all duration-1500 ease-out" :style="{ width: resolutionRate + '%' }"></div>
</div> </div>
</div> </div> -->
<div> <div>
<div class="flex justify-between text-sm mb-1"> <div class="flex justify-between text-sm mb-1">
<span class="text-gray-600">及时</span> <span class="text-gray-600">解决效</span>
<span class="font-medium text-gray-800">{{ timelinessRate }}%</span> <span class="font-medium text-gray-800">{{ timelinessRate }}%</span>
</div> </div>
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden"> <div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
@ -161,65 +161,8 @@
<!-- 发现问题种类 --> <!-- 发现问题种类 -->
<div class="py-4"> <div class="py-4">
<h3 class="section-title">发现问题种类</h3> <h3 class="section-title">发现问题种类</h3>
<div class="space-y-4"> <!-- 柱状图容器 -->
<div> <div id="problemTypesChart" class="bar-chart-container"></div>
<div class="flex justify-between text-sm mb-1">
<span class="text-gray-600">温度异常率</span>
<span class="text-gray-500">{{ problemTypes.temperature }}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
<div
class="bg-blue-500 h-2 rounded-full transition-all duration-1500 ease-out"
:style="{ width: problemTypes.temperature + '%' }"
></div>
</div>
</div>
<div>
<div class="flex justify-between text-sm mb-1">
<span class="text-gray-600">内存使用率</span>
<span class="text-gray-500">{{ problemTypes.memory }}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
<div
class="bg-blue-500 h-2 rounded-full transition-all duration-1500 ease-out"
:style="{ width: problemTypes.memory + '%' }"
></div>
</div>
</div>
<div>
<div class="flex justify-between text-sm mb-1">
<span class="text-gray-600">CPU负载</span>
<span class="text-gray-500">{{ problemTypes.cpu }}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
<div class="bg-blue-500 h-2 rounded-full transition-all duration-1500 ease-out" :style="{ width: problemTypes.cpu + '%' }"></div>
</div>
</div>
<div>
<div class="flex justify-between text-sm mb-1">
<span class="text-gray-600">响应时间</span>
<span class="text-gray-500">{{ problemTypes.responseTime }}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
<div
class="bg-blue-500 h-2 rounded-full transition-all duration-1500 ease-out"
:style="{ width: problemTypes.responseTime + '%' }"
></div>
</div>
</div>
<div>
<div class="flex justify-between text-sm mb-1">
<span class="text-gray-600">磁盘空间状态</span>
<span class="text-gray-500">{{ problemTypes.diskSpace }}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
<div
class="bg-blue-500 h-2 rounded-full transition-all duration-1500 ease-out"
:style="{ width: problemTypes.diskSpace + '%' }"
></div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -388,16 +331,17 @@ const avgCompletionTime = ref('45分钟');
// 问题类型数据 // 问题类型数据
const problemTypes = ref({ const problemTypes = ref({
temperature: 85, // 温度异常 temperature: 0, // 温度异常数量
memory: 62, // 内存使用率 memory: 0, // 内存使用率问题数量
cpu: 45, // CPU负载 cpu: 0, // CPU负载问题数量
responseTime: 30, // 响应时间 responseTime: 0, // 响应时间问题数量
diskSpace: 15 // 磁盘空间状态 diskSpace: 0 // 磁盘空间问题数量
}); });
// ECharts 图相关 // ECharts 图相关
const pieChartRef = ref(null); const pieChartRef = ref(null);
let pieChart = null; let pieChart = null;
let barChart = null;
// 计算平均完成度 // 计算平均完成度
const averageRate = computed(() => (completionRate.value + resolutionRate.value + timelinessRate.value) / 3); const averageRate = computed(() => (completionRate.value + resolutionRate.value + timelinessRate.value) / 3);
@ -426,7 +370,7 @@ const initPieChart = () => {
}, },
series: [ series: [
{ {
name: '进度指标', name: '指标对比',
type: 'pie', type: 'pie',
radius: ['40%', '70%'], radius: ['40%', '70%'],
avoidLabelOverlap: false, avoidLabelOverlap: false,
@ -442,20 +386,15 @@ const initPieChart = () => {
label: { label: {
show: true, show: true,
fontSize: 40, fontSize: 40,
fontWeight: 'bold', fontWeight: 'bold'
formatter: function (params) {
// 鼠标悬停时显示当前指标的百分比
return params.value + '%';
}
} }
}, },
labelLine: { labelLine: {
show: false show: false
}, },
data: [ data: [
{ value: completionRate.value, name: '完成率', itemStyle: { color: '#5470c6' } }, { value: completionRate.value, name: '巡检完成率', itemStyle: { color: '#409eff' } },
{ value: resolutionRate.value, name: '解决率', itemStyle: { color: '#f56c6c' } }, { value: timelinessRate.value, name: '解决率', itemStyle: { color: '#67c23a' } }
{ value: timelinessRate.value, name: '及时率', itemStyle: { color: '#67c23a' } }
] ]
} }
] ]
@ -506,11 +445,7 @@ const fetchDashboardData = async () => {
// 构建查询参数 // 构建查询参数
const queryParams = { const queryParams = {
projectId: 1, projectId: 1,
type: type, type: type
status: filterStatus.value !== 'all' ? filterStatus.value : undefined,
inspectionType: filterType.value !== 'all' ? filterType.value : undefined,
startTime: dateRange.value.length > 0 ? dateRange.value[0] : undefined,
endTime: dateRange.value.length > 0 ? dateRange.value[1] : undefined
}; };
// 调用接口获取数据 // 调用接口获取数据
@ -526,22 +461,26 @@ const fetchDashboardData = async () => {
solvedProblems.value = data.solvedProblemCount || 0; solvedProblems.value = data.solvedProblemCount || 0;
avgCompletionTime.value = data.averageCompletionTime ? `${data.averageCompletionTime}分钟` : '0分钟'; avgCompletionTime.value = data.averageCompletionTime ? `${data.averageCompletionTime}分钟` : '0分钟';
// 计算完成率、解决率、及时率 // 使用接口返回的xjwcl(巡检完成率)和jjxl(解决效率)
completionRate.value = data.finishInspectionCount && data.finishInspectionCount > 0 ? Math.round(Math.random() * 30 + 60) : 0; completionRate.value = data.xjwcl ? parseFloat(data.xjwcl) : 0;
resolutionRate.value = data.solvedProblemCount && data.problemCount ? Math.round((data.solvedProblemCount / data.problemCount) * 100) : 0; timelinessRate.value = data.jjxl ? parseFloat(data.jjxl) : 0;
timelinessRate.value = data.finishInspectionCount && data.finishInspectionCount > 0 ? Math.round(Math.random() * 30 + 50) : 0;
// 更新问题类型数据 // 由于接口不再返回解决率将其设置为0或保持原值
resolutionRate.value = 0;
// 更新问题类型数据 - 直接使用接口返回的数值,不再计算为百分比
problemTypes.value = { problemTypes.value = {
temperature: data.sbyxzt ? Math.min(100, Math.round(data.sbyxzt * 5)) : 0, // 设备运行状态映射为温度异常 temperature: data.sbyxzt || 0, // 设备运行状态类型问题数量
memory: data.ncsyl ? Math.min(100, data.ncsyl * 10) : 0, // 内存使用率 memory: data.ncsyl || 0, // 内存使用率类型问题数量
cpu: Math.round(Math.random() * 50 + 20), // CPU负载模拟数据 cpu: data.fwzt || 0, // 服务状态类型问题数量
responseTime: data.xysj ? Math.min(100, data.xysj * 5) : 0, // 响应时间 responseTime: data.xysj || 0, // 响应时间类型问题数量
diskSpace: data.cpsyl ? Math.min(100, data.cpsyl * 8) : 0 // 磁盘使用率 diskSpace: data.cpsyl || 0 // 磁盘使用率类型问题数量
}; };
// 更新饼图 // 更新饼图
initPieChart(); initPieChart();
// 更新柱状图
initBarChart();
} else { } else {
ElMessage.error(response.msg || '获取数据失败'); ElMessage.error(response.msg || '获取数据失败');
} }
@ -551,17 +490,115 @@ const fetchDashboardData = async () => {
} }
}; };
// 页面加载时获取数据 // 页面加载时直接获取数据
onMounted(() => { onMounted(() => {
fetchDashboardData(); fetchDashboardData();
}); });
// 初始化柱状图
const initBarChart = () => {
const chartDom = document.getElementById('problemTypesChart');
if (!chartDom) return;
// 销毁旧实例
if (barChart) {
barChart.dispose();
}
// 创建新实例
barChart = echarts.init(chartDom);
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: function (params) {
return params[0].name + ': ' + params[0].value + '个';
}
},
grid: {
left: '5%',
right: '5%',
bottom: '10%',
top: '5%',
containLabel: true
},
xAxis: {
type: 'value',
name: '问题数量',
axisLabel: {
formatter: '{value}个'
},
splitLine: {
lineStyle: {
type: 'dashed'
}
}
},
yAxis: {
type: 'category',
data: ['温度异常', '内存使用率', 'CPU负载', '响应时间', '磁盘空间'],
axisLabel: {
interval: 0
}
},
series: [
{
name: '问题数量',
type: 'bar',
barWidth: '40%',
data: [
problemTypes.value.temperature,
problemTypes.value.memory,
problemTypes.value.cpu,
problemTypes.value.responseTime,
problemTypes.value.diskSpace
],
itemStyle: {
color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{ offset: 0, color: '#5470c6' },
{ offset: 1, color: '#91cc75' }
]),
borderRadius: [0, 4, 4, 0]
},
label: {
show: true,
position: 'right',
formatter: '{c}个'
}
}
]
};
barChart.setOption(option);
// 响应式处理
const handleResize = () => {
if (barChart) {
barChart.resize();
}
};
window.addEventListener('resize', handleResize);
// 组件卸载时移除事件监听
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
};
// 组件卸载时销毁图表实例 // 组件卸载时销毁图表实例
onUnmounted(() => { onUnmounted(() => {
if (pieChart) { if (pieChart) {
pieChart.dispose(); pieChart.dispose();
pieChart = null; pieChart = null;
} }
if (barChart) {
barChart.dispose();
barChart = null;
}
}); });
// 导航方法 // 导航方法
@ -802,6 +839,17 @@ const handleInspectionManagement3 = () => {
margin: 0 auto; margin: 0 auto;
} }
/* 柱状图容器 */
.bar-chart-container {
width: 100%;
height: 350px;
margin: 0 auto;
background-color: #fafafa;
border-radius: 8px;
padding: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
/* 区域标题 */ /* 区域标题 */
.section-title { .section-title {
font-size: 14px; font-size: 14px;

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<div class="inspection-tasks"> <div class="inspection-tasks">
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab active" @click="handleInspection2">巡检管理</div> <div class="nav-tab active" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -9,7 +9,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 选项卡 --> <!-- 选项卡 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">
@ -133,7 +133,7 @@
</div> </div>
<!-- 添加新任务弹窗 --> <!-- 添加新任务弹窗 -->
<el-dialog v-model="createTaskDialogVisible" title="添加新任务" width="700px" :before-close="handleCancelCreateTask"> <el-dialog v-model="createTaskDialogVisible" title="添加新任务" width="750px" :before-close="handleCancelCreateTask">
<el-form ref="createTaskFormRef" :model="createTaskForm" :rules="createTaskRules" label-width="80px"> <el-form ref="createTaskFormRef" :model="createTaskForm" :rules="createTaskRules" label-width="80px">
<el-form-item label="任务名称" prop="taskName"> <el-form-item label="任务名称" prop="taskName">
<el-input v-model="createTaskForm.taskName" placeholder="输入任务名称" /> <el-input v-model="createTaskForm.taskName" placeholder="输入任务名称" />
@ -202,7 +202,7 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="问题类型" prop="problemType"> <!-- <el-form-item label="问题类型" prop="problemType">
<el-select v-model="createTaskForm.problemType" placeholder="选择问题类型"> <el-select v-model="createTaskForm.problemType" placeholder="选择问题类型">
<el-option label="磁盘使用率" value="1" /> <el-option label="磁盘使用率" value="1" />
<el-option label="内存使用率" value="2" /> <el-option label="内存使用率" value="2" />
@ -210,7 +210,7 @@
<el-option label="响应时间" value="4" /> <el-option label="响应时间" value="4" />
<el-option label="设备运行状态" value="5" /> <el-option label="设备运行状态" value="5" />
</el-select> </el-select>
</el-form-item> </el-form-item> -->
<!-- 步骤条 --> <!-- 步骤条 -->
<el-form-item label="执行步骤" class="form-item" style="width: 100%"> <el-form-item label="执行步骤" class="form-item" style="width: 100%">
@ -387,7 +387,7 @@
<div v-if="node.remark" class="step-remark">备注{{ node.remark }}</div> <div v-if="node.remark" class="step-remark">备注{{ node.remark }}</div>
</div> </div>
<div class="step-status" :class="getStatusClass(node.status)"> <div class="step-status" :class="getStatusClass(node.status)">
{{ getStepStatusText(node.status) }} {{ node.status === '2' ? '未完成' : '已完成' }}
</div> </div>
</div> </div>
</div> </div>
@ -543,7 +543,10 @@ const getTaskList = async () => {
const params = { const params = {
pageSize: pageSize.value, pageSize: pageSize.value,
pageNum: currentPage.value, pageNum: currentPage.value,
personId: 1 personId: 1,
taskType: taskStatus.value || undefined, // 任务状态
planType: planType.value || undefined, // 计划类型
personName: executor.value || undefined // 执行人
}; };
const response = await xjrenwulist(params); const response = await xjrenwulist(params);
@ -674,7 +677,7 @@ const createTaskForm = ref({
relatedPlan: '', relatedPlan: '',
executor: '', executor: '',
taskType: '1', // 默认待执行 taskType: '1', // 默认待执行
problemType: '', // problemType: '',
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }] // 任务步骤数组 steps: [{ name: '', intendedPurpose: '', intendedTime: '' }] // 任务步骤数组
}); });
@ -806,6 +809,7 @@ const handleSaveTask = async () => {
createTime: new Date().toISOString(), createTime: new Date().toISOString(),
updateTime: new Date().toISOString(), updateTime: new Date().toISOString(),
startTime: new Date().toISOString().slice(0, 19).replace('T', ' '),
params: { params: {
property1: 'string', property1: 'string',
property2: 'string' property2: 'string'
@ -820,7 +824,7 @@ const handleSaveTask = async () => {
personId: createTaskForm.value.executor !== 'all' ? createTaskForm.value.executor : 0, personId: createTaskForm.value.executor !== 'all' ? createTaskForm.value.executor : 0,
taskProgress: 0, taskProgress: 0,
taskType: createTaskForm.value.taskType, taskType: createTaskForm.value.taskType,
problemType: createTaskForm.value.problemType, // problemType: createTaskForm.value.problemType,
nodeIds: nodeIds // 添加步骤ID字符串与工单列表页面保持一致 nodeIds: nodeIds // 添加步骤ID字符串与工单列表页面保持一致
}; };
@ -841,7 +845,7 @@ const handleSaveTask = async () => {
relatedPlan: '', relatedPlan: '',
executor: '', executor: '',
taskType: '1', taskType: '1',
problemType: '', // problemType: '',
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }] steps: [{ name: '', intendedPurpose: '', intendedTime: '' }]
}; };
// 重新获取任务列表 // 重新获取任务列表
@ -935,7 +939,7 @@ const handleCancelCreateTask = () => {
relatedPlan: '', relatedPlan: '',
executor: '', executor: '',
taskType: '1', taskType: '1',
problemType: '', // problemType: '',
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }] steps: [{ name: '', intendedPurpose: '', intendedTime: '' }]
}; };
}; };

File diff suppressed because it is too large Load Diff