Files
maintenance_system/src/views/zhinengxunjian/shiyanrenwu.vue
2025-09-30 17:57:19 +08:00

1928 lines
61 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-input v-model="keyword" placeholder="关键字(任务名/测试对象/执行人)" clearable @keyup.enter="handleSearch" />
</div>
<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="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" icon="Search" class="search-btn" @click="handleSearch"> 搜索 </el-button>
<el-button icon="Refresh" class="create-btn" @click="resetFilters"> 重置 </el-button>
<el-button type="primary" 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 v-if="task.status === '3'" class="failed-task-details">
<div class="detail-item">
<span class="detail-label">失败时间</span>
<span class="detail-value">{{ task.failTime || '未记录' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">试验阶段</span>
<span class="detail-value">{{ task.testStage || '未记录' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">执行人</span>
<span class="detail-value">{{ task.executor }}</span>
</div>
<div class="detail-item failed-reason-item">
<span class="detail-label">失败原因</span>
<span class="detail-value failed-reason">{{ task.originalData?.failReason || '未填写' }}</span>
</div>
</div>
<!-- 其他状态的卡片展示 -->
<div v-else>
<div class="detail-item">
<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'" class="task-result">
<span class="detail-label">结果</span>
<span class="detail-value" :class="task.resultClass">{{ task.result }}</span>
</div>
</div>
</div>
<div class="task-actions">
<!-- 失败卡片的特殊操作按钮 -->
<div v-if="task.status === '3'" class="failed-task-actions">
<el-button type="text" class="action-btn view-btn" @click="handleView(task)"> 详情 </el-button>
<el-button type="primary" :class="task.actionClass" @click="handleAction(task)">
{{ task.actionText }}
</el-button>
</div>
<!-- 其他状态的操作按钮 -->
<div v-else>
<el-button type="text" class="action-btn view-btn" @click="handleView(task)"> 详情 </el-button>
<el-button type="primary" :class="task.actionClass" @click="handleAction(task)">
{{ task.actionText }}
</el-button>
</div>
</div>
</div>
</div>
<!-- 分页区域 -->
<div 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="750px" :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-item label="执行步骤" prop="steps">
<div class="steps-container">
<div class="step-item" v-for="(step, index) in createTaskForm.steps" :key="index">
<div class="step-number">{{ index + 1 }}</div>
<el-input v-model="step.name" placeholder="输入步骤名称" style="flex: 1; margin-right: 10px" />
<el-input v-model="step.intendedPurpose" placeholder="输入预期目的" style="flex: 1; margin-right: 10px" />
<el-date-picker
v-model="step.intendedTime"
type="datetime"
placeholder="选择计划时间"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm"
style="width: 180px; margin-right: 10px"
/>
<el-button v-if="createTaskForm.steps.length > 1" type="text" @click="removeStep(index)" style="color: #f56c6c"> 删除 </el-button>
</div>
<el-button type="text" class="add-step-btn" @click="addStep">添加步骤</el-button>
</div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<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="getTaskStatusClass(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.phonenumber }}</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>
<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.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' ? '未执行' : node.status === '3' ? '失败' : '已完成' }}
</div>
</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>
<!-- 日志弹窗 -->
<el-dialog v-model="logsDialogVisible" title="任务执行日志" width="700px" :close-on-click-modal="false">
<div v-if="!logsLoading" class="logs-container">
<div v-if="logsData.length > 0" class="logs-list">
<div v-for="(log, index) in logsData" :key="index" class="log-item">
<div class="log-time">{{ log.timestamp || '-' }}</div>
<div class="log-content">{{ log.content || '-' }}</div>
</div>
</div>
<div v-else class="no-logs">
<el-empty description="暂无执行日志" />
</div>
</div>
<div v-else class="loading-logs">
<el-skeleton :count="5" class="log-skeleton" />
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="logsDialogVisible = false">关闭</el-button>
</span>
</template>
</el-dialog>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } 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';
import { addjiedian, updatejiedian } from '@/api/zhinengxunjian/jiedian/index';
// 引入Element Plus组件提示/空状态/骨架屏/弹窗)
import { ElMessage, ElEmpty, ElSkeleton, ElForm, ElMessageBox, ElDialog } from 'element-plus';
// 日志弹窗相关变量
const logsDialogVisible = ref(false);
const logsData = ref([]);
const logsLoading = ref(false);
/**
* 根据任务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 keyword = ref(''); // 关键词
/**
* 将节点数据按模块分组
* @param {Array} nodes - 节点数据数组
* @returns {Array} 分组后的模块数组
*/
const groupNodesByModule = (nodes) => {
if (!nodes || !Array.isArray(nodes)) {
return [];
}
// 这里简单地将所有节点放在一个默认模块下实际应用中可以根据节点数据的module字段进行分组
const defaultGroup = {
module: '测试步骤',
items: nodes
};
return [defaultGroup];
};
const executor = ref('all'); // 执行人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} 样式类名
*/
/**
* 获取步骤状态对应的样式类
* @param {string|number} status - 步骤状态码
* @returns {string} 样式类名
*/
const getStatusClass = (status) => {
// 处理可能的数字输入
const statusStr = status?.toString() || '';
const statusClassMap = {
'1': 'status-pending', // 待执行
'2': 'status-unknown', // 未完成 - 灰色
'3': 'status-failed', // 失败 - 红色
'4': 'status-executing', // 执行中
'5': 'status-completed' // 已完成 - 绿色
};
return statusClassMap[statusStr] || 'status-unknown';
};
/**
* 格式化日期时间(用于步骤条)
* @param {string} dateTime - 日期时间字符串
* @returns {string} 格式化后的日期时间
*/
const formatDateTime = (dateTime) => {
if (!dateTime) return '-';
try {
const date = new Date(dateTime);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
} catch (error) {
return dateTime;
}
};
/**
* 获取步骤状态文本
* @param {string|number} status - 步骤状态码
* @returns {string} 状态文本
*/
const getStepStatusText = (status) => {
const statusStr = status?.toString() || '';
const statusMap = {
'1': '待执行',
'2': '执行中',
'3': '失败',
'4': '已延期'
};
return statusMap[statusStr] || '未知状态';
};
// 创建任务弹窗
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
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }] // 步骤数据数组
});
// 创建任务表单规则
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' }]
};
// 添加步骤
const addStep = () => {
createTaskForm.value.steps.push({ name: '', intendedPurpose: '', intendedTime: '' });
};
// 删除步骤
const removeStep = (index) => {
// 确保至少保留一个步骤
if (createTaskForm.value.steps.length <= 1) {
ElMessage.warning('至少需要保留一个步骤');
return;
}
createTaskForm.value.steps.splice(index, 1);
};
// 构建timeInfo字符串
const getTaskTimeInfoString = () => {
const timeInfoArray = [];
// 处理工作时间段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 users = response.rows || [];
// 将用户数据转换为所需格式包含id和userName以适配模板和getUserById函数
userList.value = users.map((user) => ({
id: user.userId, // 用于标识和查找
userName: user.userName // 显示名称
}));
// 调试信息,确认数据格式正确
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. 构造接口请求参数严格匹配返回的JSON数据结构
const requestParams = {
pageNum: currentPage.value,
pageSize: pageSize.value,
projectId: 1, // 项目ID必需字段
status: taskStatus.value || undefined, // 任务状态对应JSON中的status字段
testPlanId: planType.value || undefined, // 关联计划ID对应JSON中的testPlanId字段
person: executor.value === 'all' ? undefined : executor.value // 执行人ID对应JSON中的person字段
};
// 2. 调用接口已引入的syrenwulist函数
const response = await syrenwulist(requestParams);
// 根据实际接口返回格式:{ code: 200, rows: [], total: 0 }
if (response.code === 200) {
// 3. 接口数据映射为页面展示格式
const mapped = (response.rows || []).map((item) => mapApiToView(item));
// 4. 前端关键词过滤
const kw = keyword.value.trim();
const filtered = kw
? mapped.filter((t) =>
[t.title, t.target, t.executor, t.relatedPlan, t.statusText]
.filter(Boolean)
.some((v) => String(v).toLowerCase().includes(kw.toLowerCase()))
)
: mapped;
tasks.value = filtered;
total.value = kw ? filtered.length : response.total || filtered.length; // 同步总条数
} 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: 'status-unknown',
actionText: '重新安排',
actionClass: 'reschedule-btn',
result: '-'
},
'3': {
statusText: '失败',
cardClass: 'card-failed',
tagClass: 'status-failed',
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);
}
// 格式化失败时间
const formatFailTime = (timeStr) => {
if (timeStr) {
const date = new Date(timeStr);
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(
date.getHours()
).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
}
return '未记录';
};
// 生成试验阶段信息
const getTestStage = () => {
try {
// 优先查找nodes数组中处于执行中或失败的节点来确定当前试验阶段
if (apiData && apiData.nodes && Array.isArray(apiData.nodes)) {
// 优先查找失败状态的节点根据需求优先显示status为3的数据
const failedNode = apiData.nodes.find((node) => {
if (!node || node.status === undefined) return false;
return node.status === '3' || node.status === 3;
});
// 如果有失败的节点根据code判断阶段
if (failedNode && failedNode.code !== undefined) {
const stepName = failedNode.name || '未命名步骤';
return `${failedNode.code}步(${stepName})`;
}
// 查找执行中状态的节点
const executingNode = apiData.nodes.find((node) => {
if (!node || node.status === undefined) return false;
return node.status === '2' || node.status === 2;
});
// 如果有执行中的节点根据code判断阶段
if (executingNode && executingNode.code !== undefined) {
const stepName = executingNode.name || '未命名步骤';
return `${executingNode.code}步(${stepName})`;
}
// 查找已完成的节点,确定最后完成的阶段
const completedNodes = apiData.nodes.filter((node) => {
if (!node || node.status === undefined) return false;
return node.status === '4' || node.status === 4;
});
if (completedNodes.length > 0) {
// 按code排序取最大的code
completedNodes.sort((a, b) => Number(b.code) - Number(a.code));
if (completedNodes[0].code !== undefined) {
const stepName = completedNodes[0].name || '未命名步骤';
return `${completedNodes[0].code}步(${stepName})`;
}
}
}
// 如果没有找到符合条件的nodes数据检查是否有明确的试验阶段信息
if (apiData && apiData.testStage) {
return apiData.testStage;
}
// 如果没有明确的阶段信息,尝试从关联计划中获取
if (apiData && apiData.testPlan && apiData.testPlan.stage) {
return apiData.testPlan.stage;
}
} catch (error) {
console.error('获取试验阶段信息失败:', error);
}
return '未记录';
};
return {
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, // 保存原始数据,用于后续操作
// 失败卡片特有字段
failTime: formatFailTime(apiData.failTime),
testStage: getTestStage()
};
};
// ============== 3. 筛选与分页事件 ==============
/**
* 搜索按钮点击:重置页码并重新请求数据
*/
const handleSearch = () => {
currentPage.value = 1; // 筛选时重置到第一页
getTaskList();
};
// 重置筛选条件
const resetFilters = () => {
taskStatus.value = '';
planType.value = '';
executor.value = 'all';
keyword.value = '';
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
};
// 声明resultType变量提升作用域
let resultType = null;
// 3. 根据任务状态只修改状态相关的字段
if (task.status === '4') {
// 执行中 → 完成:使用弹窗确认结果
try {
// 保持原有结构
} catch (error) {
console.error('捕获到异常:', error);
}
try {
const confirmResult = await ElMessageBox.confirm('请选择试验结果', '完成试验', {
confirmButtonText: '正常',
cancelButtonText: '异常',
type: 'warning',
distinguishCancelAndClose: true
});
// 用户点击确认(正常)
updateParams.status = '5';
updateParams.progress = 100;
updateParams.testFinal = '正常';
resultType = 'normal'; // 现在在外部作用域中定义
} catch (error) {
if (error === 'cancel') {
// 用户点击取消(异常),弹出失败原因输入框
try {
const failReasonResult = await ElMessageBox.prompt('请输入失败原因', '试验异常', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
inputPlaceholder: '请详细描述失败原因...',
inputValidator: (value) => {
if (!value || value.trim() === '') {
return '失败原因不能为空';
}
return true;
}
});
// 用户输入了失败原因并确认
updateParams.status = '3';
updateParams.progress = '';
updateParams.testFinal = '异常';
updateParams.failReason = failReasonResult.value; // 绑定失败原因参数
updateParams.failTime = formatLocalDateTime(new Date()); // 记录失败时间
resultType = 'abnormal';
// 将第一条未完成的步骤状态改为3失败
if (taskDetails.nodes && Array.isArray(taskDetails.nodes)) {
const firstUnfinishedNode = taskDetails.nodes.find((node) => {
return node.status === '2' || node.status === 2;
});
if (firstUnfinishedNode) {
// 使用updatejiedian接口更新节点状态构造完整的节点信息数组
const nodeUpdateParams = [
{
...firstUnfinishedNode,
status: '3',
updateTime: new Date().toISOString(),
// 确保包含所有必需字段
createDept: firstUnfinishedNode.createDept || 0,
createBy: firstUnfinishedNode.createBy || 0,
createTime: firstUnfinishedNode.createTime || new Date().toISOString(),
updateBy: firstUnfinishedNode.updateBy || 0,
params: firstUnfinishedNode.params || {
property1: 'string',
property2: 'string'
},
module: firstUnfinishedNode.module || 'string',
orderId: firstUnfinishedNode.orderId || 0,
code: firstUnfinishedNode.code || 0,
name: firstUnfinishedNode.name || 'string',
intendedPurpose: firstUnfinishedNode.intendedPurpose || 'string',
intendedTime: firstUnfinishedNode.intendedTime || new Date().toISOString(),
finishTime: firstUnfinishedNode.finishTime || '',
remark: firstUnfinishedNode.remark || ''
}
];
await updatejiedian(nodeUpdateParams);
// 更新本地数据以反映最新状态
firstUnfinishedNode.status = '3';
}
}
} catch (innerError) {
// 用户取消了失败原因输入
return;
}
} else {
// 关闭弹窗,不执行操作
return;
}
}
} else {
// 其他状态处理保持不变
switch (task.status) {
case '1': // 待执行 → 开始执行状态改为4
updateParams.status = '4';
updateParams.progress = 0; // 初始进度10%
// 设置开始时间为当前时间使用本地时间而非UTC时间
updateParams.planBeginTime = formatLocalDateTime(new Date());
break;
case '2': // 已延期 → 重新安排状态改为1重置时间
updateParams.status = '1';
updateParams.beginTime = formatLocalDateTime(new Date());
break;
case '3': // 失败 → 重试状态改为1
updateParams.status = '1';
// 清空失败相关字段,使用适合各字段数据类型的默认值
updateParams.failReason = null;
updateParams.failTime = null; // 时间类型字段使用null
updateParams.failPhase = null; // 整数类型字段使用0
// 将失败的步骤状态改回2未完成
if (taskDetails.nodes && Array.isArray(taskDetails.nodes)) {
const failedNodes = taskDetails.nodes.filter((node) => {
return node.status === '3' || node.status === 3;
});
// 构造包含所有失败节点的完整信息数组
const nodeUpdateParams = failedNodes.map((failedNode) => ({
...failedNode,
status: '2',
updateTime: new Date().toISOString(),
// 确保包含所有必需字段
createDept: failedNode.createDept || 0,
createBy: failedNode.createBy || 0,
createTime: failedNode.createTime || new Date().toISOString(),
updateBy: failedNode.updateBy || 0,
params: failedNode.params || {
property1: 'string',
property2: 'string'
},
module: failedNode.module || 'string',
orderId: failedNode.orderId || 0,
code: failedNode.code || 0,
name: failedNode.name || 'string',
intendedPurpose: failedNode.intendedPurpose || 'string',
intendedTime: failedNode.intendedTime || new Date().toISOString(),
finishTime: failedNode.finishTime || '',
remark: failedNode.remark || ''
}));
// 一次性调用updatejiedian接口更新所有节点
await updatejiedian(nodeUpdateParams);
// 更新本地数据以反映最新状态
for (const failedNode of failedNodes) {
failedNode.status = '2';
}
}
break;
default:
return;
}
}
// 对于执行中状态('4')的任务,预先设置好时间字段
if (task.status === '4') {
// 根据结果类型设置相应的时间使用本地时间而非UTC时间
if (resultType === 'normal') {
updateParams.planFinishTime = formatLocalDateTime(new Date());
} else if (resultType === 'abnormal') {
updateParams.failTime = formatLocalDateTime(new Date());
}
}
// 调用更新接口(只调用一次)
const response = await updatesyrenwu(updateParams);
if (response.code === 200) {
ElMessage.success(`任务${task.actionText}成功`);
getTaskList(); // 刷新任务列表
} else {
ElMessage.error(`任务${task.actionText}失败:` + response.msg);
}
} catch (error) {
ElMessage.error(`操作异常:` + error.message);
}
};
/**
* 格式化本地日期时间为 'YYYY-MM-DD HH:mm' 格式
* @param {Date} date - 日期对象
* @returns {string} 格式化后的日期时间字符串
*/
const formatLocalDateTime = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
};
/**
* 打开创建任务弹窗
*/
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 hasEmptyStep = createTaskForm.value.steps.some((step) => !step.name.trim() || !step.intendedPurpose.trim());
if (hasEmptyStep) {
ElMessage.warning('请填写完整所有步骤信息');
return;
}
// 处理步骤数据
let nodeIds = '';
if (createTaskForm.value.steps && createTaskForm.value.steps.length > 0) {
// 过滤非空步骤并映射为所需格式
const validSteps = createTaskForm.value.steps
.filter((step) => step.name.trim() && step.intendedPurpose.trim())
.map((step, index) => ({
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
params: {},
module: 3,
code: index + 1,
name: step.name,
intendedPurpose: step.intendedPurpose,
intendedTime: step.intendedTime ? new Date(step.intendedTime).toISOString() : new Date().toISOString(),
finishTime: '',
remark: '',
status: 2
}));
if (validSteps.length > 0) {
try {
// 调用addjiedian接口获取nodeIds
const jiedianResponse = await addjiedian(validSteps);
if (jiedianResponse.code === 200 && jiedianResponse.msg) {
nodeIds = jiedianResponse.msg; // 直接使用字符串格式,不转换为数组
}
} catch (error) {
console.error('添加节点失败:', error);
ElMessage.error('添加执行步骤失败');
return;
}
}
}
const createParams = {
createDept: 0, // 可根据实际情况从全局状态获取
createBy: 0, // 可根据实际情况从全局状态获取当前用户ID
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: '', // 计划开始时间(新增时为空)
progress: 0, // 初始进度0%
failReason: '',
failTime: '', // 失败时间(新增时为空)
failPhase: '',
faileAnalyze: '',
faileTips: '',
testLongTime: 0,
testFinal: '',
finalInfo: '',
pauseFor: '',
pauseTime: '', // 暂停时间(新增时为空)
planFinishTime: '', // 计划完成时间(新增时为空)
nodeIds: nodeIds // 步骤节点ID数组
};
// 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: '',
workTimeRange1: null,
workTimeRange2: null,
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }]
};
};
// ============== 5. 导航栏跳转逻辑(保持原有) ==============
const handleInspectionManagement1 = () => {
router.push('/znxj/sygl/shiyanguanli');
};
const handleInspectionManagement2 = () => {
router.push('/znxj/sygl/shiyanrenwu');
};
const handleInspectionManagement3 = () => {
router.push('/znxj/sygl/shiyanjilu');
};
const handleInspection1 = () => {
router.push('/znxj/rili');
};
const handleInspection2 = () => {
router.push('/znxj/xjgl/InspectionManagement');
};
const handleInspection3 = () => {
router.push('/znxj/sygl/shiyanguanli');
};
const handleInspection4 = () => {
router.push('/znxj/bxgl/baoxiuguanli');
};
const handleInspection5 = () => {
router.push('/znxj/qxgl/qiangxiuguanli');
};
const handleInspection6 = () => {
router.push('/znxj/gdgl/gongdanliebiao');
};
const handleInspection7 = () => {
router.push('/znxj/ywzz/renyuanzhuangtai');
};
// ============== 6. 页面初始化(加载任务列表和用户列表) ==============
onMounted(() => {
getUsersList();
getExperimentPlanList();
getTaskList();
});
// ============== 7. 分页数据(直接使用接口返回的分页列表) ==============
const pagedTasks = computed(() => {
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>
<style scoped>
@import url('./css/step-bars.css');
@import url('./css/detail-dialog.css');
/* 原有样式不变,新增无数据提示样式 */
.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-failed {
box-shadow: 0 4px 16px rgba(255, 77, 79, 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;
}
.card-failed::before {
background-color: #ff4d4f;
}
/* 卡片悬停效果 */
.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;
}
.tag-failed {
background-color: #fff2f0;
color: #ff4d4f;
border-color: #ffccc7;
}
.tag-incomplete {
background-color: #f5f5f5;
color: #999;
border-color: #d9d9d9;
}
.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;
}
/* 失败卡片特殊样式 */
.failed-task-details {
margin-bottom: 16px;
}
.failed-reason-item {
padding-top: 8px;
border-top: 1px dashed #f0f2f5;
}
.failed-reason {
color: #f53f3f;
font-weight: 500;
}
.failed-task-actions {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 10px;
}
/* 分页区域样式 */
.pagination-section {
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;
}
/* 日志弹窗样式 */
.logs-container {
max-height: 400px;
overflow-y: auto;
}
.logs-list {
padding: 10px 0;
}
.log-item {
padding: 12px 0;
border-bottom: 1px solid #f0f2f5;
}
.log-item:last-child {
border-bottom: none;
}
.log-time {
font-size: 12px;
color: #86909c;
margin-bottom: 4px;
}
.log-content {
font-size: 14px;
color: #1d2129;
line-height: 1.6;
}
.no-logs {
text-align: center;
padding: 60px 0;
}
.log-skeleton {
margin: 12px 0;
}
/* 任务详情弹窗样式 */
.task-detail-container {
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-completed {
color: #67c23a;
}
.status-delayed {
color: #f56c6c;
}
.status-unknown {
color: #909399;
}
.status-failed {
color: #f56c6c;
}
/* 响应式设计 */
@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>