Files
maintenance_system/src/views/zhinengxunjian/xunjianrenwu.vue
2025-09-29 18:49:04 +08:00

1588 lines
46 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 active" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @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="pending"></el-option>
<el-option label="执行中" value="executing"></el-option>
<el-option label="已延期" value="delayed"></el-option>
<el-option label="已完成" value="completed"></el-option>
</el-select>
</div>
<div class="filter-item">
<el-select v-model="planType" placeholder="全部计划">
<el-option label="每日巡检计划" value="daily"></el-option>
<el-option label="每周巡检计划" value="weekly"></el-option>
<el-option label="每月巡检计划" value="monthly"></el-option>
</el-select>
</div>
<div class="filter-item">
<el-input v-model="executor" placeholder="执行人"></el-input>
</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>
<!-- 任务卡片列表 -->
<div class="task-cards">
<div class="task-card" v-for="(task, index) in pagedTasks" :key="index" :class="task.statusClass">
<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 === 'delayed'" class="delay-reason">
<span class="detail-label">延期原因</span>
<span class="detail-value">{{ task.delayReason }}</span>
</div>
<div v-if="task.status === 'executing'" 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" stroke-width="6" :stroke-color="task.progressColor"></el-progress>
</div>
</div>
<div v-if="task.status === 'completed'" 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 v-if="pagedTasks.length === 0 && tasks.length === 0" class="no-results">
<div class="no-results-icon">
<i class="el-icon-search"></i>
</div>
<div class="no-results-text">未找到符合条件的任务</div>
<el-button type="text" @click="resetFilters">清除筛选条件</el-button>
</div>
</div>
<!-- 分页区域 -->
<div class="pagination-section" v-if="tasks.length > 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="关联计划">
<el-select v-model="createTaskForm.relatedPlan" placeholder="选择关联计划">
<el-option v-for="plan in planList" :key="plan.value" :label="plan.label" :value="plan.value" />
</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.value" :label="user.label" :value="user.value" />
</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="taskType" style="display: none">
<el-select v-model="createTaskForm.taskType" placeholder="选择任务类型">
<el-option label="待执行" value="1" />
<el-option label="已延期" value="2" />
<el-option label="执行中" value="3" />
<el-option label="已完成" value="4" />
</el-select>
</el-form-item>
<!-- <el-form-item label="问题类型" prop="problemType">
<el-select v-model="createTaskForm.problemType" placeholder="选择问题类型">
<el-option label="磁盘使用率" value="1" />
<el-option label="内存使用率" value="2" />
<el-option label="服务状态" value="3" />
<el-option label="响应时间" value="4" />
<el-option label="设备运行状态" value="5" />
</el-select>
</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 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" :before-close="handleCloseDetailDialog">
<div v-if="detailData" class="task-detail-container">
<!-- 加载状态骨架屏 -->
<div v-if="isDetailLoading" class="skeleton-loading">
<div class="skeleton-card">
<div class="skeleton-header"></div>
<div class="skeleton-content">
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
</div>
</div>
<div class="skeleton-card">
<div class="skeleton-header"></div>
<div class="skeleton-content">
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
</div>
</div>
<div class="skeleton-card">
<div class="skeleton-header"></div>
<div class="skeleton-content">
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
</div>
</div>
</div>
<!-- 任务基本信息卡片 -->
<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.taskType)">{{ getStatusText(detailData.taskType) }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">巡检对象</span>
<span class="info-value">{{ detailData.inspectionObject || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">完成进度</span>
<span class="info-value">{{ detailData.taskProgress || 0 }}%</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 full-width">
<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 class="info-row">
<div class="info-item">
<span class="info-label">姓名</span>
<span class="info-value">{{ detailData.person?.userName || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">联系电话</span>
<span class="info-value">{{ detailData.person?.phonenumber || '-' }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">性别</span>
<span class="info-value">{{ detailData.person?.sex === '1' ? '男' : detailData.person?.sex === '2' ? '女' : '-' }}</span>
</div>
</div>
</div>
</div>
<!-- 关联计划信息卡片 -->
<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.plan?.planName || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">计划编号</span>
<span class="info-value">{{ detailData.plan?.id || '-' }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">巡检频率</span>
<span class="info-value">{{ detailData.plan?.inspectionFrequency || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">计划时长</span>
<span class="info-value">{{ detailData.plan?.duration || '-' }}分钟</span>
</div>
</div>
<div class="info-row">
<div class="info-item full-width">
<span class="info-label">计划时间</span>
<span class="info-value">{{
detailData.plan?.beginTime && detailData.plan?.endTime ? `${detailData.plan.beginTime}${detailData.plan.endTime}` : '-'
}}</span>
</div>
</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.taskType === '2' || detailData.taskType === 2" class="detail-card">
<h3 class="card-title">延期信息</h3>
<div class="card-content">
<div class="info-row">
<div class="info-item full-width">
<span class="info-label">延期原因</span>
<span class="info-value fail-reason">{{ detailData.delayReason || '未说明原因' }}</span>
</div>
</div>
</div>
</div>
</div>
<div v-else class="loading-state">
<i class="el-icon-loading el-icon--loading"></i>
<span>加载中...</span>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleCloseDetailDialog">关闭</el-button>
</span>
</template>
</el-dialog>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import router from '@/router';
import { xjrenwuDetail, xjrenwulist, addxjrenwu, updatexjrenwu } from '@/api/zhinengxunjian/xunjian/renwu';
import { xunjianUserlist, xunjianlist } from '@/api/zhinengxunjian/xunjian/index';
import { addjiedian } from '@/api/zhinengxunjian/jiedian/index';
import { ElMessage, ElLoading, ElForm } from 'element-plus';
import { formatDate } from '@/utils/index';
// 筛选条件
const taskStatus = ref('');
const planType = ref('');
const executor = ref('');
const keyword = ref('');
// 任务数据 - 初始为空数组通过API获取
const tasks = ref([]);
// 任务详情弹窗相关变量
const detailDialogVisible = ref(false);
const detailData = ref(null);
const isDetailLoading = ref(false);
// 根据任务状态获取对应的文本
const getStatusText = (status) => {
// 处理可能的数字输入
const statusStr = status?.toString() || '';
const statusMap = {
'1': '待执行',
'2': '已延期',
'3': '执行中',
'4': '已完成'
};
return statusMap[statusStr] || '未知状态';
};
// 根据任务状态获取对应的样式类
const getStatusClass = (status) => {
// 处理可能的数字输入
const statusStr = status?.toString() || '';
const statusClassMap = {
'1': 'status-pending',
'2': 'status-delayed',
'3': 'status-executing',
'4': 'status-completed'
};
return statusClassMap[statusStr] || 'status-unknown';
};
// 格式化日期时间
const formatDateTime = (dateTime) => {
if (!dateTime) return '-';
try {
const date = new Date(dateTime);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
} catch (error) {
return dateTime;
}
};
// 获取步骤状态文本
const getStepStatusText = (status) => {
const statusStr = status?.toString() || '';
const statusMap = {
'1': '待执行',
'2': '执行中',
'3': '已完成',
'4': '已延期'
};
return statusMap[statusStr] || '未知状态';
};
// 状态映射配置
const statusConfig = {
pending: {
statusText: '待执行',
statusClass: 'card-pending',
tagClass: 'tag-pending',
actionText: '开始执行',
actionClass: 'start-btn'
},
delayed: {
statusText: '已延期',
statusClass: 'card-delayed',
tagClass: 'tag-delayed',
actionText: '重新安排',
actionClass: 'reschedule-btn'
},
executing: {
statusText: '执行中',
statusClass: 'card-executing',
tagClass: 'tag-executing',
actionText: '完成',
actionClass: 'complete-btn'
},
completed: {
statusText: '已完成',
statusClass: 'card-completed',
tagClass: 'tag-completed',
actionText: '查看报告',
actionClass: 'report-btn'
}
};
// 获取巡检任务数据
const getTaskList = async () => {
// 显示加载状态
const loading = ElLoading.service({
lock: true,
text: '加载任务中...',
background: 'rgba(255, 255, 255, 0.7)'
});
try {
// 构建搜索参数,包含所有筛选条件
const params = {
pageSize: pageSize.value,
pageNum: currentPage.value,
projectId: 1
};
const response = await xjrenwulist(params);
if (response.code === 200 && response.rows) {
const mapped = response.rows.map((item) => {
// 获取原始数据中的id
const taskId = item.id || '';
if (!taskId) {
console.warn('当前任务缺少id数据', item);
}
// 根据taskType获取对应的status
let taskStatus = 'pending';
if (item.taskType === '1' || item.taskType === 1) {
taskStatus = 'pending';
} else if (item.taskType === '2' || item.taskType === 2) {
taskStatus = 'delayed';
} else if (item.taskType === '3' || item.taskType === 3) {
taskStatus = 'executing';
} else if (item.taskType === '4' || item.taskType === 4) {
taskStatus = 'completed';
}
const config = statusConfig[taskStatus] || statusConfig.pending;
// 构建任务对象
const task = {
id: taskId,
title: item.taskName || '未命名任务',
status: taskStatus,
statusText: config.statusText,
statusClass: config.statusClass,
tagClass: config.tagClass,
planTime: item.beginTime || '',
target: item.inspectionObject || '',
executor: item.person?.userName || '未知用户',
relatedPlan: item.plan?.planName || '未知计划',
actionText: config.actionText,
actionClass: config.actionClass,
rawData: item
};
// 补充其他状态字段
if (task.status === 'delayed') {
task.delayReason = item.delayReason || '未说明原因';
} else if (task.status === 'executing') {
task.progress = item.taskProgress || 0;
task.progressColor = '#FF7D00';
} else if (task.status === 'completed') {
task.result = item.result || '正常';
task.resultClass = item.result === '异常' ? 'result-abnormal' : 'result-normal';
}
return task;
});
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;
}
} catch (error) {
console.error('获取巡检任务数据失败:', error);
ElMessage.error('获取任务列表失败');
} finally {
// 关闭加载状态
loading.close();
}
};
// 页面加载时获取数据
onMounted(() => {
getTaskList();
});
// 分页相关
const currentPage = ref(1);
const pageSize = ref(8);
const total = ref(tasks.value.length);
// 状态排序映射
const statusOrder = {
pending: 0, // 待完成
delayed: 1, // 已延期
executing: 2, // 执行中
completed: 3 // 已完成
};
// 分页处理后的数据(含排序)
const pagedTasks = computed(() => {
// 先按状态排序
const sortedTasks = [...tasks.value].sort((a, b) => {
return statusOrder[a.status] - statusOrder[b.status];
});
// 再进行分页
const startIndex = (currentPage.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
return sortedTasks.slice(startIndex, endIndex);
});
// 搜索处理
const handleSearch = () => {
currentPage.value = 1; // 重置到第一页
getTaskList(); // 重新获取数据
};
// 重置筛选条件
const resetFilters = () => {
taskStatus.value = '';
planType.value = '';
executor.value = '';
currentPage.value = 1;
getTaskList();
};
// 创建任务弹窗相关
const createTaskDialogVisible = ref(false);
const createTaskForm = ref({
taskName: '',
inspectionTarget: '',
timeRange: [],
workTimeRange1: null,
workTimeRange2: null,
relatedPlan: '',
executor: '',
taskType: '1', // 默认待执行
// problemType: '',
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' }],
taskType: [{ required: true, message: '请选择任务类型', trigger: 'change' }]
};
// 创建任务
const handleCreateTask = () => {
openCreateTaskDialog();
};
// 重置步骤表单
const resetStepForm = () => {
Object.keys(stepForm).forEach((key) => {
stepForm[key] = '';
});
currentStep.value = 0;
if (stepFormRef.value) stepFormRef.value.resetFields();
if (deviceFormRef.value) deviceFormRef.value.resetFields();
if (faultFormRef.value) faultFormRef.value.resetFields();
};
// 构建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 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);
};
// 保存任务
const handleSaveTask = async () => {
// 表单验证
if (!createTaskForm.value.taskName || !createTaskForm.value.inspectionTarget || !createTaskForm.value.timeRange) {
ElMessage.warning('请填写必要的任务信息');
return;
}
// 验证所有步骤
const hasEmptyStep = createTaskForm.value.steps.some((step) => !step.name.trim() || !step.intendedPurpose.trim());
if (hasEmptyStep) {
ElMessage.warning('请填写完整所有步骤信息');
return;
}
try {
// 获取timeInfo字符串
const taskTimeInfo = getTaskTimeInfoString();
if (!taskTimeInfo) {
ElMessage.warning('请至少填写一个工作时间段');
return;
}
// 准备步骤数据,与工单列表页面保持一致的格式
const stepsData = createTaskForm.value.steps
.filter((step) => step.name.trim() && step.intendedPurpose.trim())
.map((step, index) => ({
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
params: {},
module: 2,
code: index + 1,
name: step.name,
intendedPurpose: step.intendedPurpose,
intendedTime: step.intendedTime ? new Date(step.intendedTime).toISOString() : new Date().toISOString(),
finishTime: '',
remark: '',
status: 2
}));
// 调用添加节点接口,直接传递步骤数组
const jiedianResponse = await addjiedian(stepsData);
if (jiedianResponse.code !== 200) {
ElMessage.error('创建步骤失败');
return;
}
// 获取返回的ids实际返回格式中msg字段包含ids字符串data为null
let nodeIds = '';
if (jiedianResponse.code === 200 && jiedianResponse.msg) {
nodeIds = jiedianResponse.msg;
} else {
ElMessage.warning('未获取到有效的步骤ID');
return;
}
// 构建接口所需的数据结构
const apiData = {
projectId: 1,
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
startTime: formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss'),
params: {
property1: 'string',
property2: 'string'
},
taskName: createTaskForm.value.taskName,
inspectionObject: createTaskForm.value.inspectionTarget,
beginTime: createTaskForm.value.timeRange[0] ? new Date(createTaskForm.value.timeRange[0]).toISOString() : '',
endTime: createTaskForm.value.timeRange[1] ? new Date(createTaskForm.value.timeRange[1]).toISOString() : '',
timeInfo: taskTimeInfo,
planId: createTaskForm.value.relatedPlan !== 'all' ? getPlanId(createTaskForm.value.relatedPlan) : 0,
personId: createTaskForm.value.executor !== 'all' ? createTaskForm.value.executor : 0,
taskProgress: 0,
taskType: createTaskForm.value.taskType,
// problemType: createTaskForm.value.problemType,
nodeIds: nodeIds // 添加步骤ID字符串与工单列表页面保持一致
};
// 调用新增任务接口
const response = await addxjrenwu(apiData);
if (response.code === 200) {
ElMessage.success('任务创建成功');
// 关闭弹窗
createTaskDialogVisible.value = false;
// 重置表单
createTaskForm.value = {
taskName: '',
inspectionTarget: '',
timeRange: [],
workTimeRange1: null,
workTimeRange2: null,
relatedPlan: '',
executor: '',
taskType: '1',
// problemType: '',
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }]
};
// 重新获取任务列表
getTaskList();
} else {
ElMessage.error(`创建失败: ${response.msg || '未知错误'}`);
}
} catch (error) {
console.error('保存任务失败:', error);
ElMessage.error('创建任务失败,请重试');
}
};
// 辅助函数获取计划ID
const getPlanId = (planValue) => {
if (planValue === 'all') {
return 0;
}
return planValue;
};
// 负责人列表
const userList = ref([]);
// 计划列表
const planList = ref([]);
// 获取负责人列表
const getUsersList = async () => {
try {
const response = await xunjianUserlist();
// 适配新接口格式检查code为200且rows为数组
const userRows = response.code === 200 && response.rows && Array.isArray(response.rows) ? response.rows : [];
userList.value = userRows
.filter((item) => item && typeof item === 'object')
.map((item) => ({
label: item.userName || '未知用户',
value: String(item.userId || '') // 使用userId作为唯一标识
}));
if (userList.value.length === 0) {
userList.value = [{ label: '默认用户', value: 'default' }];
}
} catch (error) {
console.error('获取负责人列表失败:', error);
userList.value = [{ label: '默认用户', value: 'default' }];
}
};
// 获取计划列表
const getPlansList = async () => {
try {
const response = await xunjianlist();
const planRows =
response?.data?.rows && Array.isArray(response.data.rows)
? response.data.rows
: response?.rows && Array.isArray(response.rows)
? response.rows
: Array.isArray(response)
? response
: [];
planList.value = planRows
.filter((item) => item && typeof item === 'object')
.map((item) => ({
label: item.planName || `计划${item.id}`,
value: item.id.toString()
}));
} catch (error) {
console.error('获取计划列表失败:', error);
}
};
// 打开创建任务弹窗
const openCreateTaskDialog = async () => {
await Promise.all([getUsersList(), getPlansList()]);
createTaskDialogVisible.value = true;
};
// 取消创建任务
const handleCancelCreateTask = () => {
createTaskDialogVisible.value = false;
// 重置表单
createTaskForm.value = {
taskName: '',
inspectionTarget: '',
timeRange: [],
workTimeRange1: null,
workTimeRange2: null,
relatedPlan: '',
executor: '',
taskType: '1',
// problemType: '',
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }]
};
};
// 分页事件
const handleSizeChange = (val) => {
pageSize.value = val;
currentPage.value = 1;
getTaskList(); // 分页大小改变时重新获取数据
};
const handleCurrentChange = (val) => {
currentPage.value = val;
getTaskList(); // 页码改变时重新获取数据
};
// 查看任务详情
const handleView = async (task) => {
try {
if (!task || !task.id) {
ElMessage.warning('任务ID不存在请刷新页面重试');
console.error('任务缺少有效id任务数据', task);
return;
}
isDetailLoading.value = true;
detailData.value = null;
const response = await xjrenwuDetail(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('获取任务详情出错,请重试');
} finally {
isDetailLoading.value = false;
}
};
// 关闭详情弹窗
const handleCloseDetailDialog = () => {
detailDialogVisible.value = false;
detailData.value = null;
};
const handleInspectionManagement1 = () => {
router.push('/znxj/xjgl/InspectionManagement');
};
const handleInspectionManagement2 = () => {
router.push('/znxj/xjgl/xunjianrenwu');
};
const handleInspectionManagement3 = () => {
router.push('/znxj/xjgl/xunjianjihua');
};
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');
};
// 处理任务操作按钮点击事件
const handleAction = async (task) => {
try {
if (!task || !task.id) {
ElMessage.warning('任务ID不存在请刷新页面重试');
console.error('任务缺少有效id任务数据', task);
return;
}
// 如果是待执行状态,点击开始执行按钮
if (task.status === 'pending' && task.actionText === '开始执行') {
const taskIndex = tasks.value.findIndex((t) => t.id === task.id);
if (taskIndex === -1) {
ElMessage.warning('未找到该任务数据');
return;
}
const originalTask = tasks.value[taskIndex];
const updateData = {
...originalTask.rawData,
id: task.id,
startTime: formatDate(new Date().toString()),
taskType: '3', // 3表示执行中
status: 'executing',
taskProgress: 0
};
const response = await updatexjrenwu(updateData);
if (response.code === 200) {
ElMessage.success('任务已开始执行');
getTaskList();
} else {
const errorMsg = response.msg || '未知错误';
console.error(`更新任务状态失败: ${errorMsg}`);
ElMessage.error(`更新任务状态失败:${errorMsg}`);
}
}
// 如果是执行中状态,点击完成按钮
else if (task.status === 'executing' && task.actionText === '完成') {
const taskIndex = tasks.value.findIndex((t) => t.id === task.id);
if (taskIndex === -1) {
ElMessage.warning('未找到该任务数据');
return;
}
const originalTask = tasks.value[taskIndex];
const finishTime = formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss');
const updateData = {
...originalTask.rawData,
id: task.id,
taskType: '4', // 4表示已完成
status: 'completed',
taskProgress: 100,
finishTime: finishTime
};
const response = await updatexjrenwu(updateData);
if (response.code === 200) {
ElMessage.success('任务已完成');
getTaskList();
} else {
const errorMsg = response.msg || '未知错误';
console.error(`更新任务状态失败: ${errorMsg}`);
ElMessage.error(`更新任务状态失败:${errorMsg}`);
}
} else {
console.log('其他操作:', task.actionText);
}
} catch (error) {
console.error('处理任务操作失败:', error);
ElMessage.error('操作失败,请重试');
}
};
</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;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
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::before {
background-color: #1677ff;
}
.card-delayed::before {
background-color: #ff4d4f;
}
.card-executing::before {
background-color: #fa8c16;
}
.card-completed::before {
background-color: #52c41a;
}
/* 卡片阴影效果 */
.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);
}
.task-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
}
.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;
}
/* 状态标签样式 */
.task-status {
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
}
/* 标签状态类 */
.tag-pending {
background-color: #e6f7ff;
color: #1677ff;
border: 1px solid #91d5ff;
}
.tag-delayed {
background-color: #fff2f0;
color: #ff4d4f;
border: 1px solid #ffccc7;
}
.tag-executing {
background-color: #fffbe6;
color: #fa8c16;
border: 1px solid #ffe58f;
}
.tag-completed {
background-color: #f6ffed;
color: #52c41a;
border: 1px solid #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;
z-index: 10;
}
.action-btn {
font-size: 13px;
padding: 4px 10px;
}
.view-btn {
color: #165dff;
}
.report-btn,
.start-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 {
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-results {
grid-column: 1 / -1;
text-align: center;
padding: 60px 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
}
.no-results-icon {
font-size: 48px;
color: #c9cdd4;
margin-bottom: 20px;
}
.no-results-text {
font-size: 16px;
color: #86909c;
margin-bottom: 20px;
}
/* 响应式设计 */
@media (max-width: 1200px) {
.task-cards {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
}
@media (max-width: 768px) {
.inspection-tasks {
padding: 16px;
}
.filter-container {
flex-direction: column;
align-items: stretch;
}
.task-cards {
grid-template-columns: 1fr;
}
}
/* 状态颜色样式 */
.status-pending {
color: #e6a23c;
}
.status-executing {
color: #409eff;
}
.status-completed {
color: #67c23a;
}
.status-delayed {
color: #f56c6c;
}
.status-unknown {
color: #909399;
}
/* 加载状态样式 */
.loading-state {
text-align: center;
padding: 80px 20px;
color: #6c757d;
font-size: 14px;
}
.loading-state i {
display: block;
font-size: 48px;
margin-bottom: 16px;
color: #1677ff;
}
/* 骨架屏加载 */
.skeleton-loading {
display: flex;
flex-direction: column;
gap: 20px;
}
.skeleton-card {
background-color: #f2f2f2;
border-radius: 8px;
padding: 20px;
animation: skeleton-loading 1.5s infinite;
}
.skeleton-header {
height: 24px;
width: 140px;
background-color: #e0e0e0;
border-radius: 4px;
margin-bottom: 16px;
}
.skeleton-content {
display: flex;
flex-direction: column;
gap: 12px;
}
.skeleton-row {
height: 20px;
background-color: #e0e0e0;
border-radius: 4px;
}
.skeleton-row:nth-child(1) {
width: 70%;
}
.skeleton-row:nth-child(2) {
width: 90%;
}
.skeleton-row:nth-child(3) {
width: 60%;
}
@keyframes skeleton-loading {
0% {
opacity: 0.6;
}
50% {
opacity: 0.3;
}
100% {
opacity: 0.6;
}
}
/* 响应式设计 */
@media (max-width: 768px) {
.info-item {
min-width: 100%;
}
.info-row {
gap: 12px;
}
}
/* 弹窗按钮样式 */
.dialog-footer .el-button {
padding: 10px 24px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
}
.dialog-footer .el-button:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}
.step-content {
padding: 30px 20px;
background-color: #fafafa;
border-radius: 8px;
margin-top: 20px;
}
</style>