476 lines
16 KiB
Vue
476 lines
16 KiB
Vue
|
|
<template>
|
|||
|
|
<div class="page-container">
|
|||
|
|
<div class="left">
|
|||
|
|
<div class="header">
|
|||
|
|
<div>任务队列</div>
|
|||
|
|
<el-button type="primary" @click="addTask">添加任务</el-button>
|
|||
|
|
</div>
|
|||
|
|
<div class="taskList-container">
|
|||
|
|
<div class="task-item" v-for="task in runTaskList">
|
|||
|
|
<div class="task-content">
|
|||
|
|
<div class="task-title">{{ task.taskName }}</div>
|
|||
|
|
<div class="task-actions">
|
|||
|
|
<SvgIcon class="icon" v-if="task.status === '1'" name="pause" :size="24" color="#FFB300" @click="pauseTask(task)" />
|
|||
|
|
<SvgIcon class="icon" v-if="task.status === '3' || task.status === '5'" name="start" :size="24" color="#00FF88" @click="startTask(task)" />
|
|||
|
|
<SvgIcon class="icon" v-if="task.status != '5'" name="stop" :size="24" color="#d81e06" @click="stopTask(task)" />
|
|||
|
|
<SvgIcon class="icon" v-if="task.status === '5'" name="remove" :size="24" color="#FFB300" @click="removeTask(task)" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="robot">机器人:{{ task.robotName }}</div>
|
|||
|
|
<el-progress :color="customColorMethod" :percentage="60" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="right" ref="topContainerRef">
|
|||
|
|
<div class="header">
|
|||
|
|
<div class="label" :class="{ active: activePanel === 'current' }" @click="activePanel = 'current'">当前任务
|
|||
|
|
</div>
|
|||
|
|
<div class="label" :class="{ active: activePanel === 'history' }" @click="activePanel = 'history'">历史记录
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<div v-if="activePanel === 'current'">
|
|||
|
|
<!-- 当前任务内容 -->
|
|||
|
|
<div class="task">
|
|||
|
|
<div class="task-header">
|
|||
|
|
<div class="task-info">
|
|||
|
|
<div class="title">厂区巡检</div>
|
|||
|
|
<div class="id">ID: 123456</div>
|
|||
|
|
</div>
|
|||
|
|
<div>执行中</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="robot">
|
|||
|
|
<div>机器人:xxxx</div>
|
|||
|
|
<div>IP: 192.168.1.100</div>
|
|||
|
|
<div>开始时间: 2026-06-01 10:00:00</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="progress-label">执行进度</div>
|
|||
|
|
<el-progress :color="customColorMethod" :percentage="60" />
|
|||
|
|
<taskSteps />
|
|||
|
|
<div class="run-log-container">
|
|||
|
|
<div>执行日志</div>
|
|||
|
|
<div class="log-container">
|
|||
|
|
<div class="log" v-for="value, index in 4">
|
|||
|
|
<div class="time">11:02:06</div>
|
|||
|
|
<div class="context">移动机器人至点位{{ index + 1 }}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div v-else-if="activePanel === 'history'">
|
|||
|
|
<div :style="containerHeight">
|
|||
|
|
<el-table v-loading="loading" height="100%" :data="tableDataList">
|
|||
|
|
<el-table-column align="center" label="任务名称" prop="taskName" show-overflow-tooltip />
|
|||
|
|
<el-table-column align="center" label="任务状态" prop="status">
|
|||
|
|
<template #default="scope">
|
|||
|
|
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
|
|||
|
|
{{ taskStatusMap[scope.row.status] || '未知状态' }}
|
|||
|
|
</el-tag>
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column align="center" label="任务类型" prop="taskType">
|
|||
|
|
<template #default="scope">
|
|||
|
|
<el-tag :type="scope.row.taskType === '1' ? 'primary' : 'success'">
|
|||
|
|
{{ scope.row.taskType === '1' ? '巡检任务' : '讲解任务' }}
|
|||
|
|
</el-tag>
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column align="center" label="机器人" prop="robotName" show-overflow-tooltip />
|
|||
|
|
<el-table-column align="center" label="开始时间" prop="startTime">
|
|||
|
|
<template #default="scope">
|
|||
|
|
{{ formatDate(scope.row.startTime) }}
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column align="center" label="结束时间" prop="endTime">
|
|||
|
|
<template #default="scope">
|
|||
|
|
{{ formatDate(scope.row.endTime) }}
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column align="center" class-name="small-padding fixed-width" label="操作"
|
|||
|
|
width="260">
|
|||
|
|
<template #default="scope">
|
|||
|
|
<el-button icon="Edit" link type="primary" @click="handleUpdate(scope.row)">
|
|||
|
|
日志
|
|||
|
|
</el-button>
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
</el-table>
|
|||
|
|
|
|||
|
|
<pagination v-show="total > 0" v-model:limit="queryParams.pageSize"
|
|||
|
|
v-model:page="queryParams.pageNum" :total="total" @pagination="getList" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- 添加任务弹窗 -->
|
|||
|
|
<el-dialog title="添加任务" v-model="open" width="500px" append-to-body>
|
|||
|
|
<el-form ref="runTaskRef" :model="form" :rules="rules" label-width="100px">
|
|||
|
|
<el-form-item label="选择任务" prop="taskId">
|
|||
|
|
<el-select v-model="form.taskId" placeholder="请选择任务" clearable style="width: 100%">
|
|||
|
|
<el-option v-for="task in taskList" :key="task.id" :label="task.taskName" :value="task.id" />
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="选择机器人" prop="robotId">
|
|||
|
|
<el-select v-model="form.robotId" placeholder="请选择机器人" clearable style="width: 100%">
|
|||
|
|
<el-option v-for="robot in robotList" :key="robot.id" :label="robot.robotName"
|
|||
|
|
:value="robot.id" />
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-form>
|
|||
|
|
<template #footer>
|
|||
|
|
<div class="dialog-footer">
|
|||
|
|
<el-button type="primary" @click="confirmTaskDialog">确 定</el-button>
|
|||
|
|
<el-button @click="cancelTaskDialog">取 消</el-button>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
</el-dialog>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
<script setup>
|
|||
|
|
import SvgIcon from "@/components/SvgIcon";
|
|||
|
|
import taskSteps from "./task-steps.vue";
|
|||
|
|
import { useContainerHeight } from "@/hooks/tableHeight";
|
|||
|
|
import { getTaskList } from "@/api/inspection/task";
|
|||
|
|
import { getRobotList } from "@/api/inspection/robot";
|
|||
|
|
import { onMounted } from "vue";
|
|||
|
|
import { addRunTask, getRunTaskList, startRunTask, pauseRunTask, stopRunTask, resumeRunTask, deleteRunTask } from "@/api/inspection/runTask";
|
|||
|
|
import { ElMessage, ElMessageBox } from "element-plus";
|
|||
|
|
import { formatDate } from "@/utils/index";
|
|||
|
|
|
|||
|
|
const topContainerRef = ref();
|
|||
|
|
const containerHeight = useContainerHeight(topContainerRef);
|
|||
|
|
|
|||
|
|
const taskStatusMap = {
|
|||
|
|
'0': '已完成',
|
|||
|
|
'1': '执行中',
|
|||
|
|
'2': '失败',
|
|||
|
|
'3': '已暂停',
|
|||
|
|
'4': '已终止',
|
|||
|
|
'5': '未执行',
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const form = reactive({
|
|||
|
|
taskId: null,
|
|||
|
|
robotId: null,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const rules = reactive({
|
|||
|
|
taskId: [{ required: true, message: '请选择任务', trigger: 'change' }],
|
|||
|
|
robotId: [{ required: true, message: '请选择机器人', trigger: 'change' }],
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const taskList = ref([]);
|
|||
|
|
const robotList = ref([]);
|
|||
|
|
|
|||
|
|
const runTaskList = ref([]);
|
|||
|
|
|
|||
|
|
const getTaskData = () => {
|
|||
|
|
getTaskList({
|
|||
|
|
pageNum: 1,
|
|||
|
|
pageSize: 10000,
|
|||
|
|
}).then((res) => {
|
|||
|
|
taskList.value = res.rows;
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const getRobotData = () => {
|
|||
|
|
getRobotList({
|
|||
|
|
pageNum: 1,
|
|||
|
|
pageSize: 10000,
|
|||
|
|
}).then((res) => {
|
|||
|
|
robotList.value = res.rows;
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const open = ref(false);
|
|||
|
|
|
|||
|
|
const addTask = () => {
|
|||
|
|
open.value = true;
|
|||
|
|
}
|
|||
|
|
const runTaskRef = ref();
|
|||
|
|
const confirmTaskDialog = () => {
|
|||
|
|
runTaskRef.value.validate((valid) => {
|
|||
|
|
if (valid) {
|
|||
|
|
addRunTask(form).then(() => {
|
|||
|
|
ElMessage.success("任务添加成功");
|
|||
|
|
getList();
|
|||
|
|
open.value = false;
|
|||
|
|
runTaskRef.value.resetFields();
|
|||
|
|
}).catch(() => {
|
|||
|
|
ElMessage.error("任务添加失败");
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const cancelTaskDialog = () => {
|
|||
|
|
open.value = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const getList = () => {
|
|||
|
|
loading.value = true;
|
|||
|
|
getRunTaskList({
|
|||
|
|
statusList: ['1', '3', '5'],
|
|||
|
|
}).then((res) => {
|
|||
|
|
if (res.code === 200) {
|
|||
|
|
runTaskList.value = res.rows;
|
|||
|
|
}
|
|||
|
|
loading.value = false;
|
|||
|
|
}).catch(() => {
|
|||
|
|
loading.value = false;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const customColorMethod = (percentage) => {
|
|||
|
|
if (percentage < 30) {
|
|||
|
|
return '#6f7ad3'
|
|||
|
|
}
|
|||
|
|
if (percentage < 70) {
|
|||
|
|
return '#1989fa'
|
|||
|
|
}
|
|||
|
|
return '#67c23a'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const activePanel = ref('current');
|
|||
|
|
|
|||
|
|
const tableDataList = ref([]);
|
|||
|
|
const loading = ref(false);
|
|||
|
|
const total = ref(0);
|
|||
|
|
|
|||
|
|
const queryParams = reactive({
|
|||
|
|
pageNum: 1,
|
|||
|
|
pageSize: 10,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const startTask = async (task) => {
|
|||
|
|
if (task.status === '3') {
|
|||
|
|
let res = await resumeRunTask(task.id);
|
|||
|
|
if (res.code === 200) {
|
|||
|
|
ElMessage.success("任务已恢复");
|
|||
|
|
getList();
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
let res = await startRunTask(task.id);
|
|||
|
|
if (res.code === 200) {
|
|||
|
|
ElMessage.success("任务已开始");
|
|||
|
|
getList();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const pauseTask = async (task) => {
|
|||
|
|
let res = await pauseRunTask(task.id);
|
|||
|
|
if (res.code === 200) {
|
|||
|
|
ElMessage.success("任务已暂停");
|
|||
|
|
getList();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const stopTask = async (task) => {
|
|||
|
|
let res = await stopRunTask(task.id);
|
|||
|
|
if (res.code === 200) {
|
|||
|
|
ElMessage.success("任务已停止");
|
|||
|
|
getList();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const removeTask = async (task) => {
|
|||
|
|
ElMessageBox.confirm('确定要删除该任务吗?', '提示', {
|
|||
|
|
confirmButtonText: '确定',
|
|||
|
|
cancelButtonText: '取消',
|
|||
|
|
type: 'warning',
|
|||
|
|
}).then(async () => {
|
|||
|
|
let res = await deleteRunTask(task.id);
|
|||
|
|
if (res.code === 200) {
|
|||
|
|
ElMessage.success("任务已删除");
|
|||
|
|
getList();
|
|||
|
|
}
|
|||
|
|
}).catch(() => {
|
|||
|
|
// 取消删除
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const getHistoryList = () => {
|
|||
|
|
loading.value = true;
|
|||
|
|
getRunTaskList({
|
|||
|
|
statusList: ['0', '2', '4'],
|
|||
|
|
pageNum: queryParams.pageNum,
|
|||
|
|
pageSize: queryParams.pageSize,
|
|||
|
|
}).then((res) => {
|
|||
|
|
if (res.code === 200) {
|
|||
|
|
tableDataList.value = res.rows;
|
|||
|
|
total.value = res.total;
|
|||
|
|
}
|
|||
|
|
loading.value = false;
|
|||
|
|
}).catch(() => {
|
|||
|
|
loading.value = false;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
onMounted(() => {
|
|||
|
|
getTaskData();
|
|||
|
|
getRobotData();
|
|||
|
|
getList();
|
|||
|
|
getHistoryList();
|
|||
|
|
})
|
|||
|
|
</script>
|
|||
|
|
<style lang="scss" scoped>
|
|||
|
|
.page-container {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
display: flex;
|
|||
|
|
gap: 12px;
|
|||
|
|
|
|||
|
|
.left {
|
|||
|
|
width: 25%;
|
|||
|
|
height: 100%;
|
|||
|
|
background: #fff;
|
|||
|
|
padding: 16px;
|
|||
|
|
|
|||
|
|
.header {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: center;
|
|||
|
|
padding-bottom: 16px;
|
|||
|
|
border-bottom: 1px solid #eee;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.taskList-container {
|
|||
|
|
margin-top: 16px;
|
|||
|
|
height: calc(100% - 100px);
|
|||
|
|
overflow-y: auto;
|
|||
|
|
|
|||
|
|
.task-item {
|
|||
|
|
padding: 12px;
|
|||
|
|
background: #fafafa;
|
|||
|
|
box-shadow: inset 0 0 0 1px #eee;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
margin-bottom: 16px;
|
|||
|
|
|
|||
|
|
.task-content {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: center;
|
|||
|
|
|
|||
|
|
.task-title {
|
|||
|
|
font-size: 16px;
|
|||
|
|
font-weight: bold;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.task-actions {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 8px;
|
|||
|
|
|
|||
|
|
.icon {
|
|||
|
|
cursor: pointer;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.robot {
|
|||
|
|
margin-top: 8px;
|
|||
|
|
color: #666;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.right {
|
|||
|
|
flex: 1;
|
|||
|
|
padding: 16px;
|
|||
|
|
background: #fff;
|
|||
|
|
overflow: hidden;
|
|||
|
|
|
|||
|
|
.header {
|
|||
|
|
display: flex;
|
|||
|
|
margin-bottom: 16px;
|
|||
|
|
border-bottom: 1px solid #eee;
|
|||
|
|
|
|||
|
|
.label {
|
|||
|
|
font-size: 16px;
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: #333;
|
|||
|
|
cursor: pointer;
|
|||
|
|
box-shadow: inset 0 0 0 1px #eee;
|
|||
|
|
padding: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.active {
|
|||
|
|
color: #409eff;
|
|||
|
|
box-shadow: inset 0 0 0 1px #409eff;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.task {
|
|||
|
|
padding: 16px;
|
|||
|
|
background: #fafafa;
|
|||
|
|
border-radius: 20px;
|
|||
|
|
|
|||
|
|
.task-header {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: center;
|
|||
|
|
|
|||
|
|
.title {
|
|||
|
|
font-size: 18px;
|
|||
|
|
font-weight: bold;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.id {
|
|||
|
|
color: #666;
|
|||
|
|
font-size: 14px;
|
|||
|
|
margin: 8px 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.robot {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 50px;
|
|||
|
|
color: #666;
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.progress-label {
|
|||
|
|
margin-top: 12px;
|
|||
|
|
color: #666;
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.run-log-container {
|
|||
|
|
padding: 16px;
|
|||
|
|
|
|||
|
|
.log-container {
|
|||
|
|
margin-top: 12px;
|
|||
|
|
max-height: 150px;
|
|||
|
|
overflow-y: auto;
|
|||
|
|
background: #fff;
|
|||
|
|
border: 1px solid #eee;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
padding: 12px;
|
|||
|
|
|
|||
|
|
.log {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 12px;
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
|
|||
|
|
.time {
|
|||
|
|
color: #999;
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.context {
|
|||
|
|
color: #333;
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|