Files
maintenance_system/src/views/zhinengxunjian/shiyanrenwu.vue
2025-09-22 15:42:13 +08:00

1426 lines
42 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<div class="inspection-tasks">
<!-- 导航栏 -->
<div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab active" @click="handleInspection3">试验管理</div>
<div class="nav-tab" @click="handleInspection4">报修管理</div>
<div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div>
</div>
<!-- 选项卡 -->
<div class="tabs-wrapper">
<div style="display: flex; align-items: center; gap: 10px">
<el-button type="primary" @click="handleInspectionManagement1">试验计划</el-button>
<el-button type="primary" @click="handleInspectionManagement2">试验任务</el-button>
<el-button type="primary" @click="handleInspectionManagement3">试验记录</el-button>
</div>
</div>
<!-- 筛选栏 -->
<div class="filter-bar">
<div class="filter-container">
<div class="filter-item">
<el-select v-model="taskStatus" placeholder="任务状态">
<el-option label="待执行" value="1"></el-option>
<el-option label="执行中" value="4"></el-option>
<el-option label="已延期" value="2"></el-option>
<!-- 接口暂停对应页面已延期 -->
<el-option label="已完成" value="5"></el-option>
<el-option label="失败" value="3"></el-option>
</el-select>
</div>
<div class="filter-item">
<el-select v-model="planType" placeholder="全部计划">
<el-option label="每日巡检计划" value="1"></el-option>
<!-- 对应接口testPlanId -->
<el-option label="每周巡检计划" value="2"></el-option>
<el-option label="每月巡检计划" value="3"></el-option>
</el-select>
</div>
<div class="filter-item">
<el-select v-model="executor" placeholder="执行人">
<el-option label="全部人员" value="all"></el-option>
<el-option v-for="user in userList" :key="user.id" :label="user.userName" :value="user.id"></el-option>
</el-select>
</div>
<div class="filter-actions">
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
<el-button type="primary" icon="el-icon-plus" class="create-btn" @click="handleCreateTask"> 手动创建任务 </el-button>
</div>
</div>
</div>
<!-- 加载状态 -->
<el-skeleton v-if="loading" :count="8" :columns="3" class="mb-4"></el-skeleton>
<!-- 无数据提示 -->
<div v-else-if="total === 0" class="no-data">
<el-empty description="暂无试验任务数据"></el-empty>
</div>
<!-- 任务卡片列表 -->
<div class="task-cards" v-else>
<div class="task-card" v-for="task in pagedTasks" :key="task.id" :class="task.cardClass">
<div class="task-header">
<div class="task-title">{{ task.title }}</div>
<div class="task-status" :class="task.tagClass">{{ task.statusText }}</div>
</div>
<div class="task-details">
<div class="detail-item">
<span class="detail-label">计划时间</span>
<span class="detail-value">{{ task.planTime }}</span>
</div>
<div class="detail-item">
<span class="detail-label">测试对象</span>
<span class="detail-value">{{ task.target }}</span>
</div>
<div class="detail-item">
<span class="detail-label">执行人</span>
<span class="detail-value">{{ task.executor }}</span>
</div>
<div class="detail-item">
<span class="detail-label">关联计划</span>
<span class="detail-value">{{ task.relatedPlan }}</span>
</div>
<!-- 已延期暂停原因 -->
<div v-if="task.status === '2'" class="delay-reason">
<span class="detail-label">延期原因</span>
<span class="detail-value">{{ task.delayReason || '未填写' }}</span>
</div>
<!-- 执行中进度 -->
<div v-if="task.status === '4'" class="progress-container">
<span class="detail-label">完成进度</span>
<div class="progress-bar" style="flex: 1; padding-left: 0; margin-top: 0">
<el-progress :percentage="task.progress || 0" stroke-width="6" :stroke-color="task.progressColor"></el-progress>
</div>
</div>
<!-- 已完成/失败结果 -->
<div v-if="task.status === '5' || task.status === '3'" class="task-result">
<span class="detail-label">结果</span>
<span class="detail-value" :class="task.resultClass">{{ task.result }}</span>
</div>
</div>
<div class="task-actions">
<el-button type="text" class="action-btn view-btn" @click="handleView(task)"> 详情 </el-button>
<el-button type="primary" :class="task.actionClass" @click="handleAction(task)">
{{ task.actionText }}
</el-button>
</div>
</div>
</div>
<!-- 分页区域 -->
<div class="pagination-section" v-if="total > 0">
<div class="pagination-controls">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[8, 12, 16, 20]"
:page-size="pageSize"
layout="prev, pager, next, jumper, ->, total"
:total="total"
background
>
</el-pagination>
</div>
</div>
<!-- 添加新任务弹窗 -->
<el-dialog v-model="createTaskDialogVisible" title="添加新任务" width="500px" :before-close="handleCancelCreateTask">
<el-form ref="createTaskFormRef" :model="createTaskForm" :rules="createTaskRules" label-width="80px">
<el-form-item label="任务名称" prop="taskName">
<el-input v-model="createTaskForm.taskName" placeholder="输入任务名称" />
</el-form-item>
<el-form-item label="巡检对象" prop="inspectionTarget">
<el-input v-model="createTaskForm.inspectionTarget" placeholder="输入巡检内容" />
</el-form-item>
<el-form-item label="时间" prop="timeRange">
<el-date-picker
v-model="createTaskForm.timeRange"
type="datetimerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm"
placeholder="选择时间范围"
/>
</el-form-item>
<el-form-item label="关联计划" prop="relatedPlan">
<el-select v-model="createTaskForm.relatedPlan" placeholder="选择关联计划">
<el-option v-for="plan in experimentPlanList" :key="plan.id" :label="plan.planName" :value="plan.id" />
</el-select>
</el-form-item>
<el-form-item label="执行人" prop="executor">
<el-select v-model="createTaskForm.executor" placeholder="选择执行人">
<el-option v-for="user in userList" :key="user.id" :label="user.userName" :value="user.id" />
</el-select>
</el-form-item>
<el-form-item label="开始时间" prop="workTimeRange1">
<el-time-picker
v-model="createTaskForm.workTimeRange1"
type="timerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
format="HH:mm"
value-format="HH:mm"
class="form-input"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="结束时间" prop="workTimeRange2">
<el-time-picker
v-model="createTaskForm.workTimeRange2"
type="timerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
format="HH:mm"
value-format="HH:mm"
class="form-input"
style="width: 100%"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleCancelCreateTask">取消</el-button>
<el-button type="primary" @click="handleSaveTask">保存任务</el-button>
</span>
</template>
</el-dialog>
<!-- 任务详情弹窗 -->
<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="card-content">
<div class="info-row">
<div class="info-item">
<span class="info-label">任务名称</span>
<span class="info-value">{{ detailData.taskName }}</span>
</div>
<div class="info-item">
<span class="info-label">任务状态</span>
<span class="info-value" :class="getStatusClass(detailData.status)">
{{ getStatusText(detailData.status) }}
</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">测试对象</span>
<span class="info-value">{{ detailData.testObject }}</span>
</div>
<div class="info-item">
<span class="info-label">完成进度</span>
<span class="info-value">{{ detailData.progress }}%</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">开始时间</span>
<span class="info-value">{{ detailData.beginTime }}</span>
</div>
<div class="info-item">
<span class="info-label">结束时间</span>
<span class="info-value">{{ detailData.endTime }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">工作时间段</span>
<span class="info-value">{{ detailData.timeInfo ? detailData.timeInfo.replace(/,/g, '—') : '-' }}</span>
</div>
</div>
</div>
</div>
<!-- 执行人信息卡片 -->
<div class="detail-card">
<h3 class="card-title">执行人信息</h3>
<div class="card-content">
<div v-if="detailData.personInfo" class="info-row">
<div class="info-item">
<span class="info-label">姓名</span>
<span class="info-value">{{ detailData.personInfo.userName }}</span>
</div>
<div class="info-item">
<span class="info-label">联系电话</span>
<span class="info-value">{{ detailData.personInfo.phone }}</span>
</div>
</div>
<div v-if="detailData.personInfo" class="info-row">
<div class="info-item">
<span class="info-label">性别</span>
<span class="info-value">{{ detailData.personInfo.sex === '1' ? '男' : '女' }}</span>
</div>
<div class="info-item">
<span class="info-label">民族</span>
<span class="info-value">{{ detailData.personInfo.nation }}</span>
</div>
</div>
<div v-else class="no-info">暂无执行人信息</div>
</div>
</div>
<!-- 关联计划信息卡片 -->
<div class="detail-card">
<h3 class="card-title">关联计划信息</h3>
<div class="card-content">
<div v-if="detailData.testPlan" class="info-row">
<div class="info-item">
<span class="info-label">计划名称</span>
<span class="info-value">{{ detailData.testPlan.planName }}</span>
</div>
<div class="info-item">
<span class="info-label">计划编号</span>
<span class="info-value">{{ detailData.testPlan.planCode }}</span>
</div>
</div>
<div v-if="detailData.testPlan" class="info-row">
<div class="info-item">
<span class="info-label">计划时间</span>
<span class="info-value">{{ detailData.testPlan.beginTime }} {{ detailData.testPlan.endTime }}</span>
</div>
</div>
<div v-if="detailData.testPlan && detailData.testPlan.testDevice" class="info-row">
<div class="info-item full-width">
<span class="info-label">测试设备</span>
<span class="info-value">{{ detailData.testPlan.testDevice }}</span>
</div>
</div>
<div v-else class="no-info">暂无关联计划信息</div>
</div>
</div>
<!-- 任务执行结果信息卡片如果有 -->
<div v-if="detailData.testFinal || detailData.failReason" class="detail-card">
<h3 class="card-title">执行结果信息</h3>
<div class="card-content">
<div v-if="detailData.testFinal" class="info-row">
<div class="info-item full-width">
<span class="info-label">测试结果</span>
<span class="info-value">{{ detailData.testFinal }}</span>
</div>
</div>
<div v-if="detailData.failReason" class="info-row">
<div class="info-item full-width">
<span class="info-label">失败原因</span>
<span class="info-value fail-reason">{{ detailData.failReason }}</span>
</div>
</div>
</div>
</div>
</div>
<div v-else class="loading-details">
<el-skeleton :count="6" :columns="2" />
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="detailDialogVisible = false">关闭</el-button>
</span>
</template>
</el-dialog>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, getCurrentInstance } from 'vue';
import router from '@/router';
// 引入已定义的接口函数
import { syrenwulist, syrenwuDetail, addsyrenwu, updatesyrenwu } from '@/api/zhinengxunjian/shiyan/renwu';
import { shiyanlist } from '@/api/zhinengxunjian/shiyan';
import { xunjianUserlist } from '@/api/zhinengxunjian/xunjian/index';
// 引入Element Plus组件提示/空状态/骨架屏/弹窗)
import { ElMessage, ElEmpty, ElSkeleton, ElForm, ElMessageBox } from 'element-plus';
/**
* 根据任务ID获取完整的任务详情数据
* @param {string|number} taskId - 任务ID
* @returns {Promise<Object|null>} 完整的任务数据对象或null获取失败
*/
const getTaskDetails = async (taskId) => {
try {
const response = await syrenwuDetail(taskId);
if (response.code === 200 && response.data) {
// 直接返回接口数据,不做映射转换
return response.data;
} else {
const errorMsg = response.msg || '未知错误';
console.error(`获取任务详情失败: ${errorMsg}`);
ElMessage.warning(`获取任务详情失败:${errorMsg}`);
return null;
}
} catch (error) {
console.error('任务详情请求异常:', error);
ElMessage.error('任务详情请求异常:' + error.message);
return null;
}
};
// ============== 1. 基础状态管理 ==============
// 加载状态
const loading = ref(false);
// 筛选条件(与接口参数对应)
const taskStatus = ref(''); // 任务状态1=待执行2=暂停已延期3=失败4=执行中5=已完成
const planType = ref(''); // 关联计划ID1=每日2=每周3=每月
const executor = ref('all'); // 执行人IDall=全部
// 用户列表通过xunjianUserlist接口获取
const userList = ref([]);
// 试验计划列表通过shiyanlist接口获取
const experimentPlanList = ref([]);
// 分页参数与接口pageNum/pageSize对应
const currentPage = ref(1); // 当前页
const pageSize = ref(8); // 每页条数
const total = ref(0); // 总条数
// 任务列表(接口返回数据)
const tasks = ref([]);
// 详情弹窗相关
const detailDialogVisible = ref(false);
const detailData = ref(null);
/**
* 获取任务状态文本
* @param {string} status - 任务状态码
* @returns {string} 状态文本
*/
const getStatusText = (status) => {
const statusMap = {
'1': '待执行',
'2': '已延期',
'3': '失败',
'4': '执行中',
'5': '已完成'
};
return statusMap[status] || '未知状态';
};
/**
* 获取任务状态对应的样式类
* @param {string} status - 任务状态码
* @returns {string} 样式类名
*/
const getStatusClass = (status) => {
const statusClassMap = {
'1': 'status-pending',
'2': 'status-delayed',
'3': 'status-failed',
'4': 'status-running',
'5': 'status-completed'
};
return statusClassMap[status] || '';
};
// 创建任务弹窗
const createTaskDialogVisible = ref(false);
const createTaskFormRef = ref(null);
const createTaskForm = ref({
taskName: '', // 任务名称接口taskName
inspectionTarget: '', // 巡检对象接口testObject
timeRange: [], // 时间范围(开始=beginTime结束=endTime
relatedPlan: '', // 关联计划ID接口testPlanId
executor: '', // 执行人ID接口person
workTimeRange1: null, // 工作时间段1
workTimeRange2: null // 工作时间段2
});
// 创建任务表单规则
const createTaskRules = {
taskName: [{ required: true, message: '请输入任务名称', trigger: 'blur' }],
inspectionTarget: [{ required: true, message: '请输入巡检对象', trigger: 'blur' }],
timeRange: [{ required: true, message: '请选择时间范围', trigger: 'change' }],
workTimeRange1: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
relatedPlan: [{ required: true, message: '请选择关联计划', trigger: 'change' }],
executor: [{ required: true, message: '请选择执行人', trigger: 'change' }]
};
// 构建timeInfo字符串
const getTaskTimeInfoString = () => {
const timeInfoArray = [];
// 处理工作时间段1
if (createTaskForm.value.workTimeRange1) {
if (Array.isArray(createTaskForm.value.workTimeRange1)) {
timeInfoArray.push(createTaskForm.value.workTimeRange1.join('-'));
} else if (typeof createTaskForm.value.workTimeRange1 === 'string') {
timeInfoArray.push(createTaskForm.value.workTimeRange1);
}
}
// 处理工作时间段2
if (createTaskForm.value.workTimeRange2) {
if (Array.isArray(createTaskForm.value.workTimeRange2)) {
timeInfoArray.push(createTaskForm.value.workTimeRange2.join('-'));
} else if (typeof createTaskForm.value.workTimeRange2 === 'string') {
timeInfoArray.push(createTaskForm.value.workTimeRange2);
}
}
return timeInfoArray.join(',');
};
const getUsersList = async () => {
try {
const response = await xunjianUserlist({});
if (response.code === 200) {
// 从任务数据中提取用户信息
const usersMap = new Map(); // 使用Map确保id唯一
const tasks = response.rows || [];
tasks.forEach((task) => {
// 提取personInfo中的用户信息
if (task.personInfo && task.personInfo.id && task.personInfo.userName) {
usersMap.set(task.personInfo.id, {
id: task.personInfo.id,
userName: task.personInfo.userName
});
}
// 提取testPlan.persons中的用户信息
if (task.testPlan && task.testPlan.persons && Array.isArray(task.testPlan.persons)) {
task.testPlan.persons.forEach((person) => {
if (person.id && person.userName) {
usersMap.set(person.id, {
id: person.id,
userName: person.userName
});
}
});
}
});
// 将Map转换为下拉选择器需要的格式{ label, value }
userList.value = Array.from(usersMap.values()).map((user) => ({
label: user.userName, // 显示在下拉框中的文本
value: user.id // 选中后的值
}));
// 调试信息,确认数据格式正确
console.log('处理后的用户列表:', userList.value);
}
} catch (error) {
console.error('获取用户列表失败:', error);
// 出错时提供默认选项,避免下拉框为空
userList.value = [{ label: '默认用户', value: '' }];
}
};
// 获取试验计划列表
const getExperimentPlanList = async () => {
try {
const response = await shiyanlist({});
if (response.code === 200) {
// 根据实际接口返回格式处理数据
experimentPlanList.value = response.rows || response.data?.rows || [];
}
} catch (error) {
console.error('获取试验计划列表失败:', error);
ElMessage.error('获取试验计划列表失败');
}
};
// ============== 2. 接口请求核心逻辑 ==============
/**
* 调用syrenwulist接口获取试验任务列表
* 接口参数参考createDept数据结构包含分页、筛选、排序
*/
const getTaskList = async () => {
try {
loading.value = true;
// 1. 构造接口请求参数严格匹配createDept结构
const requestParams = {
pageNum: currentPage.value,
pageSize: pageSize.value,
projectId: 1, // 项目ID必需字段需从全局状态/路由获取真实值)
status: taskStatus.value || undefined, // 任务状态(为空不传递)
testPlanId: planType.value || undefined, // 关联计划ID筛选条件
person: executor.value === 'all' ? undefined : executor.value // 执行人ID筛选条件
};
// 2. 调用接口已引入的syrenwulist函数
const response = await syrenwulist(requestParams);
// 根据实际接口返回格式:{ code: 200, rows: [], total: 0 }
if (response.code === 200) {
// 3. 接口数据映射为页面展示格式
tasks.value = (response.rows || []).map((item) => mapApiToView(item));
total.value = response.total || 0; // 同步总条数
} else {
ElMessage.error('获取任务列表失败:' + (response.msg || '未知错误'));
tasks.value = [];
total.value = 0;
}
} catch (error) {
ElMessage.error('接口请求异常:' + error.message);
tasks.value = [];
total.value = 0;
} finally {
loading.value = false;
}
};
/**
* 接口数据 → 页面展示数据映射
* @param {Object} apiData - 接口返回的单条任务数据
* @returns {Object} 页面所需的任务格式
*/
const mapApiToView = (apiData) => {
// 1. 状态映射接口status1-5→ 页面状态文本/样式
const statusConfigMap = {
'1': {
statusText: '待执行',
cardClass: 'card-pending',
tagClass: 'tag-pending',
actionText: '开始执行',
actionClass: 'start-btn',
result: '-'
},
'2': {
statusText: '已延期',
cardClass: 'card-delayed',
tagClass: 'tag-delayed',
actionText: '重新安排',
actionClass: 'reschedule-btn',
result: '-'
},
'3': {
statusText: '失败',
cardClass: 'card-delayed',
tagClass: 'tag-delayed',
actionText: '重试',
actionClass: 'reschedule-btn',
result: '失败',
resultClass: 'result-abnormal'
},
'4': {
statusText: '执行中',
cardClass: 'card-executing',
tagClass: 'tag-executing',
actionText: '完成',
actionClass: 'complete-btn',
result: '-',
progressColor: '#FF7D00'
},
'5': {
statusText: '已完成',
cardClass: 'card-completed',
tagClass: 'tag-completed',
actionText: '查看报告',
actionClass: 'report-btn',
result: apiData.testFinal || '正常',
resultClass: apiData.testFinal === '异常' ? 'result-abnormal' : 'result-normal'
}
};
const statusConfig = statusConfigMap[apiData.status] || statusConfigMap['1'];
// 2. 执行人ID → 姓名映射(通过用户列表动态查找)
const getUserById = (userId) => {
const user = userList.value.find((u) => String(u.id) === String(userId));
return user ? user.userName : '未知人员';
};
// 3. 关联计划ID → 计划名称映射(通过试验计划列表动态查找)
const getPlanNameById = (planId) => {
const plan = experimentPlanList.value.find((p) => String(p.id) === String(planId));
return plan ? plan.planName : '无';
};
// 4. 格式化时间接口date-time → 页面展示格式)
const formatTime = (timeStr) => (timeStr ? timeStr.replace('T', ' ').split('.')[0] : '未设置');
// 优先从apiData.testPlan中获取关联计划名称
let relatedPlanName = '无';
if (apiData.testPlan && apiData.testPlan.planName) {
relatedPlanName = apiData.testPlan.planName;
} else if (apiData.testPlanId) {
relatedPlanName = getPlanNameById(apiData.testPlanId);
}
// 优先从apiData.personInfo中获取执行人姓名
let executorName = '未知人员';
if (apiData.personInfo && apiData.personInfo.userName) {
executorName = apiData.personInfo.userName;
} else if (apiData.person) {
executorName = getUserById(apiData.person);
}
return {
id: apiData.id, // 任务IDv-for的key唯一标识
title: apiData.taskName || '未命名任务', // 任务名称
status: apiData.status, // 原始状态值(用于条件判断)
statusText: statusConfig.statusText,
cardClass: statusConfig.cardClass,
tagClass: statusConfig.tagClass,
planTime: formatTime(apiData.beginTime), // 计划开始时间
target: apiData.testObject || '未指定', // 巡检对象(测试对象)
executor: executorName, // 执行人姓名
relatedPlan: relatedPlanName, // 关联计划名称
delayReason: apiData.pauseFor || '未填写', // 延期(暂停)原因
progress: apiData.progress || 0, // 执行进度(仅执行中显示)
progressColor: statusConfig.progressColor,
result: statusConfig.result,
resultClass: statusConfig.resultClass,
actionText: statusConfig.actionText,
actionClass: statusConfig.actionClass,
testFinal: apiData.testFinal, // 结果(用于详情页)
originalData: apiData // 保存原始数据,用于后续操作
};
};
// ============== 3. 筛选与分页事件 ==============
/**
* 搜索按钮点击:重置页码并重新请求数据
*/
const handleSearch = () => {
currentPage.value = 1; // 筛选时重置到第一页
getTaskList();
};
/**
* 每页条数变化
* @param {number} val - 新的每页条数
*/
const handleSizeChange = (val) => {
pageSize.value = val;
currentPage.value = 1; // 条数变化时重置页码
getTaskList();
};
/**
* 当前页码变化
* @param {number} val - 新的页码
*/
const handleCurrentChange = (val) => {
currentPage.value = val;
getTaskList();
};
// ============== 4. 任务操作逻辑(详情/执行/创建) ==============
/**
* 查看任务详情
* @param {Object} task - 任务数据
*/
const handleView = async (task) => {
try {
if (!task || !task.id) {
ElMessage.warning('任务ID不存在请刷新页面重试');
console.error('任务缺少有效id任务数据', task);
return;
}
const response = await syrenwuDetail(task.id);
if (response.code === 200 && response.data) {
detailData.value = response.data;
detailDialogVisible.value = true;
} else {
const errorMsg = response.msg || '未知错误';
console.error(`获取任务详情失败: ${errorMsg}`);
ElMessage.error(`获取任务详情失败:${errorMsg}`);
}
} catch (error) {
console.error('获取任务详情出错:', error);
ElMessage.error('获取任务详情出错,请重试');
}
};
/**
* 任务操作(开始执行/重新安排/完成等)
* @param {Object} task - 任务数据
*/
const handleAction = async (task) => {
try {
// 1. 获取当前任务的完整数据
const taskDetails = await getTaskDetails(task.id);
if (!taskDetails) {
ElMessage.error('获取任务详情失败');
return;
}
// 2. 创建包含所有参数的更新对象,确保所有字段都被包含
let updateParams = {
...taskDetails, // 包含所有原始字段
id: task.id
};
// 3. 根据任务状态只修改状态相关的字段
if (task.status === '4') {
// 执行中 → 完成:使用弹窗确认结果
try {
const confirmResult = await ElMessageBox.confirm('请选择试验结果', '完成试验', {
confirmButtonText: '正常',
cancelButtonText: '异常',
type: 'warning',
distinguishCancelAndClose: true
});
// 用户点击确认(正常)
updateParams.status = '5';
updateParams.progress = 100;
updateParams.testFinal = '正常';
} catch (error) {
if (error === 'cancel') {
// 用户点击取消(异常)
updateParams.status = '5';
updateParams.progress = 100;
updateParams.testFinal = '异常';
} else {
// 关闭弹窗,不执行操作
return;
}
}
} else {
// 其他状态处理保持不变
switch (task.status) {
case '1': // 待执行 → 开始执行状态改为4
updateParams.status = '4';
updateParams.progress = 10; // 初始进度10%
break;
case '2': // 已延期 → 重新安排状态改为1重置时间
updateParams.status = '1';
updateParams.beginTime = new Date().toISOString().slice(0, 16).replace('T', ' ');
break;
case '3': // 失败 → 重试状态改为1
updateParams.status = '1';
break;
default:
return;
}
}
// 调用更新接口
const response = await updatesyrenwu(updateParams);
if (response.code === 200) {
ElMessage.success(`任务${task.actionText}成功`);
getTaskList(); // 刷新任务列表
} else {
ElMessage.error(`任务${task.actionText}失败:` + response.msg);
}
} catch (error) {
ElMessage.error(`操作异常:` + error.message);
}
};
/**
* 打开创建任务弹窗
*/
const handleCreateTask = async () => {
try {
// 打开弹窗前先获取用户列表和试验计划列表
await Promise.all([getUsersList(), getExperimentPlanList()]);
createTaskDialogVisible.value = true;
} catch (error) {
console.error('打开创建任务弹窗失败:', error);
}
};
/**
* 保存新任务调用addsyrenwu接口
*/
const handleSaveTask = async () => {
const formRef = createTaskFormRef.value;
if (!formRef) return;
// 1. 表单验证
formRef.validate(async (isValid) => {
if (isValid) {
try {
// 2. 构造创建任务的接口参数,严格按照要求的结构
const now = new Date();
// 获取timeInfo字符串
const taskTimeInfo = getTaskTimeInfoString();
if (!taskTimeInfo) {
ElMessage.warning('请至少填写一个工作时间段');
return;
}
const createParams = {
createDept: 0, // 可根据实际情况从全局状态获取
createBy: 0, // 可根据实际情况从全局状态获取当前用户ID
createTime: now.toISOString(),
updateBy: 0,
updateTime: now.toISOString(),
params: {
property1: 'string',
property2: 'string'
},
projectId: 1, // 必需字段需替换为真实项目ID
taskName: createTaskForm.value.taskName, // 任务名称
testObject: createTaskForm.value.inspectionTarget, // 测试对象(巡检对象)
beginTime: createTaskForm.value.timeRange[0], // 开始时间
endTime: createTaskForm.value.timeRange[1], // 结束时间
timeInfo: taskTimeInfo, // 使用统一的时间信息生成函数
person: createTaskForm.value.executor, // 执行人ID必需
status: '1', // 初始状态:待执行(必需)
testPlanId: createTaskForm.value.relatedPlan, // 关联计划ID必需
testSetting: '', // 测试设置
planBeginTime: createTaskForm.value.timeRange[0], // 计划开始时间
progress: 0, // 初始进度0%
failReason: '',
failTime: now.toISOString(),
failPhase: 0,
faileAnalyze: '',
faileTips: '',
testLongTime: 0,
testFinal: '',
finalInfo: '',
pauseFor: '',
pauseTime: now.toISOString(),
planFinishTime: createTaskForm.value.timeRange[1] // 计划完成时间
};
// 3. 调用创建接口
const response = await addsyrenwu(createParams);
if (response.code === 200) {
ElMessage.success('任务创建成功');
createTaskDialogVisible.value = false;
getTaskList(); // 刷新任务列表
// 4. 重置表单
createTaskForm.value = {
taskName: '',
inspectionTarget: '',
timeRange: [],
relatedPlan: '',
executor: ''
};
formRef.resetFields(); // 重置表单验证状态
} else {
ElMessage.error('创建失败:' + response.msg);
}
} catch (error) {
ElMessage.error('创建异常:' + error.message);
}
}
});
};
/**
* 取消创建任务
*/
const handleCancelCreateTask = () => {
createTaskDialogVisible.value = false;
// 重置表单
createTaskFormRef.value?.resetFields();
createTaskForm.value = {
taskName: '',
inspectionTarget: '',
timeRange: [],
relatedPlan: '',
executor: ''
};
};
// ============== 5. 导航栏跳转逻辑(保持原有) ==============
const handleInspectionManagement1 = () => {
router.push('/rili/shiyanguanli');
};
const handleInspectionManagement2 = () => {
router.push('/rili/shiyanrenwu');
};
const handleInspectionManagement3 = () => {
router.push('/rili/shiyanjilu');
};
const handleInspection1 = () => {
router.push('/rili/rili');
};
const handleInspection2 = () => {
router.push('/rili/InspectionManagement');
};
const handleInspection3 = () => {
router.push('/rili/shiyanguanli');
};
const handleInspection4 = () => {
router.push('/rili/baoxiuguanli');
};
const handleInspection5 = () => {
router.push('/rili/qiangxiuguanli');
};
const handleInspection6 = () => {
router.push('/rili/gongdanliebiao');
};
const handleInspection7 = () => {
router.push('/rili/renyuanzhuangtai');
};
// ============== 6. 页面初始化(加载任务列表和用户列表) ==============
onMounted(() => {
getUsersList();
getExperimentPlanList();
getTaskList();
});
// ============== 7. 分页数据(直接使用接口返回的分页列表) ==============
const pagedTasks = computed(() => {
return tasks.value;
});
</script>
<style scoped>
/* 原有样式不变,新增无数据提示样式 */
.inspection-tasks {
padding: 20px;
background-color: #f5f7fa;
min-height: 100vh;
}
/* 选项卡样式 */
.tabs-wrapper {
background-color: #fff;
padding: 20px;
border-radius: 8px;
margin-bottom: 16px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
/* 筛选栏样式 */
.filter-bar {
background-color: #fff;
border-radius: 8px;
margin-bottom: 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
padding: 16px 24px;
}
.filter-container {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 16px;
width: 100%;
}
.filter-item {
flex-shrink: 0;
}
.filter-bar .el-select {
width: 180px;
height: 36px;
}
.filter-actions {
margin-left: auto;
display: flex;
gap: 10px;
}
.search-btn,
.create-btn {
height: 36px;
border-radius: 4px;
}
/* 任务卡片样式 */
.task-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(310px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.task-card {
background-color: #fff;
border-radius: 8px;
padding: 16px 16px 60px 24px;
position: relative;
overflow: hidden;
min-height: 280px;
transition: all 0.3s ease;
}
/* 卡片左侧状态竖线 */
.task-card::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 6px;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.05);
}
/* 卡片阴影样式 */
.card-pending {
box-shadow: 0 4px 16px rgba(22, 119, 255, 0.15);
}
.card-delayed {
box-shadow: 0 4px 16px rgba(255, 77, 79, 0.15);
}
.card-executing {
box-shadow: 0 4px 16px rgba(250, 140, 22, 0.15);
}
.card-completed {
box-shadow: 0 4px 16px rgba(82, 196, 26, 0.15);
}
/* 左侧状态线颜色 */
.card-pending::before {
background-color: #1677ff;
}
.card-delayed::before {
background-color: #ff4d4f;
}
.card-executing::before {
background-color: #fa8c16;
}
.card-completed::before {
background-color: #52c41a;
}
/* 卡片悬停效果 */
.task-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1), 0 4px 16px rgba(0, 0, 0, 0.05);
}
.task-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #f0f2f5;
}
.task-title {
font-size: 16px;
font-weight: 500;
color: #1d2129;
line-height: 1.4;
}
/* 状态标签样式 */
.task-status {
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
border: 1px solid transparent;
}
.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;
}
.task-details {
margin-bottom: 16px;
}
.detail-item {
display: flex;
margin-bottom: 10px;
font-size: 13px;
}
.detail-label {
flex: 0 0 80px;
color: #86909c;
}
.detail-value {
flex: 1;
color: #4e5969;
word-break: break-all;
}
.delay-reason {
display: flex;
margin: 10px 0;
font-size: 13px;
padding-top: 8px;
border-top: 1px dashed #f0f2f5;
}
.progress-container {
display: flex;
align-items: center;
margin: 10px 0;
padding-top: 8px;
border-top: 1px dashed #f0f2f5;
}
.progress-bar {
flex: 1;
padding-left: 10px;
margin-top: 0;
}
.task-result {
display: flex;
margin: 10px 0;
font-size: 13px;
padding-top: 8px;
border-top: 1px dashed #f0f2f5;
}
.result-normal {
color: #00b42a;
}
.result-abnormal {
color: #f53f3f;
}
.task-actions {
display: flex;
justify-content: flex-end;
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;
}
.action-btn {
font-size: 13px;
padding: 4px 10px;
}
.view-btn {
color: #165dff;
}
.start-btn {
background-color: #165dff;
border-color: #165dff;
}
.reschedule-btn {
background-color: #ff7d00;
border-color: #ff7d00;
}
.complete-btn {
background-color: #00b42a;
border-color: #00b42a;
}
.report-btn {
background-color: #86909c;
border-color: #86909c;
}
/* 分页区域样式 */
.pagination-section {
display: flex;
justify-content: center;
margin-top: 20px;
}
/* 导航栏样式 */
.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 {
padding: 12px 24px;
cursor: pointer;
transition: all 0.3s ease;
border-radius: 4px;
font-size: 14px;
color: #606266;
border-right: 1px solid #f0f0f0;
flex: 1;
text-align: center;
}
.nav-tab:last-child {
border-right: none;
}
.nav-tab:hover {
color: #409eff;
background-color: #ecf5ff;
}
.nav-tab.active {
background-color: #409eff;
color: #fff;
box-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
}
/* 无数据提示样式 */
.no-data {
text-align: center;
padding: 40px 0;
background-color: #fff;
border-radius: 8px;
margin-bottom: 30px;
}
/* 任务详情弹窗样式 */
.task-detail-container {
max-height: 600px;
overflow-y: auto;
}
.detail-card {
margin-bottom: 20px;
padding: 20px;
background-color: #fafafa;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.card-title {
margin: 0 0 16px 0;
padding-bottom: 12px;
border-bottom: 2px solid #409eff;
font-size: 16px;
font-weight: 600;
color: #303133;
}
.card-content {
display: flex;
flex-direction: column;
gap: 12px;
}
.info-row {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.info-item {
flex: 1;
min-width: 280px;
}
.info-item.full-width {
min-width: 100%;
}
.info-label {
display: inline-block;
width: 100px;
color: #606266;
font-weight: 500;
}
.info-value {
color: #303133;
word-break: break-word;
}
.fail-reason {
color: #f56c6c;
}
.no-info {
color: #909399;
font-style: italic;
padding: 10px 0;
}
.loading-details {
padding: 20px 0;
}
.status-pending {
color: #e6a23c;
}
.status-running {
color: #409eff;
}
.status-completed {
color: #67c23a;
}
.status-delayed {
color: #f56c6c;
}
.status-failed {
color: #909399;
}
/* 响应式设计 */
@media (max-width: 1200px) {
.task-cards {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
}
@media (max-width: 768px) {
.info-item {
min-width: 100%;
}
.info-row {
gap: 12px;
}
}
@media (max-width: 768px) {
.inspection-tasks {
padding: 16px;
}
.filter-container {
flex-direction: column;
align-items: stretch;
}
.task-cards {
grid-template-columns: 1fr;
}
}
</style>