CMVR-BID/src/views/inspection/runTask/index.vue

781 lines
27 KiB
Vue
Raw Normal View History

2026-06-04 16:41:29 +08:00
<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 v-for="task in runTaskList" :key="task.id" class="task-item"
:class="{ 'is-selected': isSelectedTask(task) }"
:aria-current="isSelectedTask(task) ? 'true' : undefined" @click="selectTask(task)">
2026-06-04 16:41:29 +08:00
<div class="task-content">
<div class="task-title">{{ task.taskName }}</div>
<div class="task-actions">
2026-06-05 14:50:29 +08:00
<SvgIcon class="icon" v-if="task.status === '1'" name="pause" :size="24" color="#FFB300"
@click.stop="pauseTask(task)" />
2026-06-05 14:50:29 +08:00
<SvgIcon class="icon" v-if="task.status === '3' || task.status === '5'" name="start"
:size="24" color="#00FF88" @click.stop="startTask(task)" />
2026-06-05 14:50:29 +08:00
<SvgIcon class="icon" v-if="task.status != '5'" name="stop" :size="24" color="#d81e06"
@click.stop="stopTask(task)" />
2026-06-05 14:50:29 +08:00
<SvgIcon class="icon" v-if="task.status === '5'" name="remove" :size="24" color="#FFB300"
@click.stop="removeTask(task)" />
2026-06-04 16:41:29 +08:00
</div>
</div>
<div class="robot">主要机器人:{{ task.robotName || '执行时安排' }}</div>
2026-06-05 16:53:40 +08:00
<el-progress :color="customColorMethod"
:percentage="parseFloat(taskRunningInfo[task.id]?.progress?.toFixed(2)) || 0" />
2026-06-04 16:41:29 +08:00
</div>
</div>
</div>
2026-06-05 14:50:29 +08:00
<div class="right" ref="topContainerRef">
2026-06-04 16:41:29 +08:00
<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>
2026-06-05 14:50:29 +08:00
<div class="context-container">
2026-06-04 16:41:29 +08:00
<div v-if="activePanel === 'current'">
<!-- 当前任务区域只展示左侧任务队列中选中的任务,避免同时渲染所有执行中任务。 -->
<div v-if="selectedRunTask" :key="selectedRunTask.id" class="task"
:data-task-id="selectedRunTask.id">
2026-06-04 16:41:29 +08:00
<div class="task-header">
2026-06-05 14:50:29 +08:00
<div class="header-container">
<div class="task-info">
<div class="title">{{ selectedRunTask.taskName }}</div>
<div class="id">ID: {{ selectedRunTask.taskId }}</div>
2026-06-05 14:50:29 +08:00
</div>
<div class="robot">
<div>主要机器人:{{ selectedRunTask.robotName || '按流程角色安排' }}</div>
<div>开始时间: {{ formatDate(selectedRunTask.startTime) }}</div>
2026-06-05 14:50:29 +08:00
</div>
</div>
<div>
<el-tag :type="selectedRunTask.status === '3' ? 'primary' : 'warning'">
{{ taskStatusMap[selectedRunTask.status] || '未知状态' }}
2026-06-05 14:50:29 +08:00
</el-tag>
2026-06-04 16:41:29 +08:00
</div>
</div>
2026-06-05 14:50:29 +08:00
2026-06-04 16:41:29 +08:00
<div class="progress-label">执行进度</div>
2026-06-05 14:50:29 +08:00
<el-progress :color="customColorMethod"
:percentage="parseFloat(taskRunningInfo[selectedRunTask.id]?.progress?.toFixed(2)) || 0" />
<taskSteps :list="pointList_runningTask[selectedRunTask.taskId]"
:activeStep="taskRunningInfo[selectedRunTask.id]?.activeStep || 0" />
2026-06-04 16:41:29 +08:00
<div class="run-log-container">
<div>执行日志</div>
<taskLog :list="taskLogList[selectedRunTask.id]" />
2026-06-04 16:41:29 +08:00
</div>
</div>
<el-empty v-else description="暂无任务" />
2026-06-04 16:41:29 +08:00
</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>
<template #default="scope">{{ scope.row.robotName || '按流程角色安排' }}</template>
</el-table-column>
2026-06-04 16:41:29 +08:00
<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">
2026-06-05 17:18:47 +08:00
<el-button icon="Edit" link type="primary" @click="handleOpenLog(scope.row)">
2026-06-04 16:41:29 +08:00
日志
</el-button>
<el-button v-if="['0', '2'].includes(scope.row.status)" icon="RefreshRight" link
type="primary" :loading="rerunningId === scope.row.id" @click="handleRerun(scope.row)">
再次执行
</el-button>
2026-06-04 16:41:29 +08:00
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:limit="queryParams.pageSize"
2026-06-05 14:50:29 +08:00
v-model:page="queryParams.pageNum" :total="total" @pagination="getHistoryList" />
2026-06-04 16:41:29 +08:00
</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%">
2026-06-04 16:41:29 +08:00
<el-option v-for="task in taskList" :key="task.id" :label="task.taskName" :value="task.id" />
</el-select>
</el-form-item>
2026-07-06 11:11:59 +08:00
<el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="form.remark" />
</el-form-item>
2026-06-04 16:41:29 +08:00
</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>
2026-06-05 17:18:47 +08:00
<el-dialog title="任务执行日志" v-model="logDialogVisible" width="500px" append-to-body>
<taskLog v-loading="logDialogLoading" :list="logDialogData" :height="300" />
2026-06-05 17:18:47 +08:00
</el-dialog>
<el-dialog title="本次执行安排" v-model="startDialogVisible" width="820px" append-to-body>
2026-08-12 13:32:00 +08:00
<div class="start-dialog-content">
<div class="start-section-title">机器人和设备</div>
2026-08-12 13:32:00 +08:00
<RuntimeAssignmentsEditor
ref="runtimeEditorRef"
v-model="runtimeAssignments"
:roles="runtimeRoles"
:required-map-id="runtimeMapId"
/>
<div class="start-section-title start-section-title--params">运行参数</div>
<RuntimeParamsEditor ref="runtimeParamsEditorRef" :items="runtimeParamItems" />
2026-08-12 13:32:00 +08:00
</div>
<template #footer>
<el-button @click="startDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="startSubmitting" @click="confirmStartTask">开始执行</el-button>
</template>
</el-dialog>
2026-06-04 16:41:29 +08:00
</div>
</template>
<script setup>
import SvgIcon from "@/components/SvgIcon";
import taskSteps from "./task-steps.vue";
2026-06-05 16:53:40 +08:00
import taskLog from "./task-log.vue";
2026-06-04 16:41:29 +08:00
import { useContainerHeight } from "@/hooks/tableHeight";
2026-06-05 14:50:29 +08:00
import { getTaskList, getWaypointByTaskId } from "@/api/inspection/task";
import { computed, onMounted, onUnmounted } from "vue";
import { addRunTask, getRunTaskList, startRunTask, rerunRunTask, pauseRunTask, stopRunTask, resumeRunTask, deleteRunTask, getTaskLogListApi } from "@/api/inspection/runTask";
2026-06-04 16:41:29 +08:00
import { ElMessage, ElMessageBox } from "element-plus";
import { formatDate } from "@/utils/index";
2026-06-05 14:50:29 +08:00
import { debounce } from 'lodash'; // 引入 lodash 的防抖函数
import { fetchAllPagedRows } from '@/utils/pagedRequest'
import RuntimeAssignmentsEditor from '@/views/flow/components/RuntimeAssignmentsEditor.vue'
import RuntimeParamsEditor from '@/views/flow/components/RuntimeParamsEditor.vue'
import { getFlowParamsApi } from '@/api/device/terminal.js'
import {
createRuntimeAssignments,
firstAssignedRobotId,
loadFlowItemScopedRuntimeRoles,
} from '@/utils/flowExecution'
2026-06-04 16:41:29 +08:00
const topContainerRef = ref();
const containerHeight = useContainerHeight(topContainerRef);
2026-06-05 14:50:29 +08:00
const socket = inject('ws');
const startDialogVisible = ref(false)
const startSubmitting = ref(false)
const pendingStartTask = ref(null)
const runtimeRoles = ref([])
const runtimeAssignments = ref([])
const runtimeEditorRef = ref()
const runtimeMapId = ref('')
const runtimeParamItems = ref([])
const runtimeParamsEditorRef = ref()
const selectedTaskId = ref('')
const rerunningId = ref('')
2026-06-04 16:41:29 +08:00
const taskStatusMap = {
'0': '已完成',
'1': '执行中',
'2': '失败',
'3': '已暂停',
'4': '已终止',
'5': '未执行',
}
const form = reactive({
taskId: null,
});
const rules = reactive({
taskId: [{ required: true, message: '请选择任务', trigger: 'change' }],
});
const taskList = ref([]);
const runTaskList = ref([]);
// 接口和 WebSocket 可能分别以数字、字符串返回任务实例 ID,统一转换后再判断是否为选中任务。
const isSelectedTask = (task) => String(task?.id ?? '') === String(selectedTaskId.value)
// 当前任务详情直接从完整任务队列中取得,因此未执行、执行中和已暂停任务都可以被选中查看。
const selectedRunTask = computed(() => runTaskList.value.find(isSelectedTask) || null)
/**
* 点击左侧任务卡片时切换右侧当前任务详情,并从历史记录面板返回当前任务面板。
* 卡片内的启动、暂停、停止和删除图标均使用 .stop 阻止冒泡,执行操作时不会意外改变当前选择。
*
* @param {Object} task 左侧任务队列中被点击的任务实例
*/
const selectTask = (task) => {
selectedTaskId.value = task.id
activePanel.value = 'current'
}
const normalizeTaskStatus = (tasks = []) => tasks.map(item => ({
...item,
status: String(item.status),
}))
2026-06-04 16:41:29 +08:00
const getTaskData = () => {
getTaskList({
pageNum: 1,
pageSize: 10000,
}).then((res) => {
taskList.value = res.rows;
})
}
const open = ref(false);
const addTask = () => {
open.value = true;
}
2026-06-05 14:50:29 +08:00
2026-06-04 16:41:29 +08:00
const runTaskRef = ref();
2026-06-05 16:53:40 +08:00
/**
* 确定添加执行任务
*/
2026-06-04 16:41:29 +08:00
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;
}
2026-06-05 14:50:29 +08:00
/**
* 获取任务队列并维护当前选中的任务:
* 1. 页面首次加载时默认选中队列第一条任务;
* 2. 队列刷新后,如果原任务仍存在,则保留用户当前选择;
* 3. 如果原任务已经结束或被删除,则选择刷新后队列中的第一条任务;
* 4. 如果任务队列为空,则清空选择并由模板展示空状态。
2026-06-05 14:50:29 +08:00
*/
2026-06-04 16:41:29 +08:00
const getList = () => {
return getRunTaskList({
2026-06-04 16:41:29 +08:00
statusList: ['1', '3', '5'],
}).then((res) => {
if (res.code === 200) {
const tasks = normalizeTaskStatus(res.rows)
runTaskList.value = tasks;
if (!tasks.some(isSelectedTask)) {
selectedTaskId.value = tasks[0]?.id ?? ''
}
2026-06-05 14:50:29 +08:00
for (let i = 0; i < runTaskList.value.length; i++) {
getPointByTaskId(runTaskList.value[i].taskId)
2026-06-05 16:53:40 +08:00
getTaskLogList(runTaskList.value[i].id)
2026-06-05 14:50:29 +08:00
}
2026-06-04 16:41:29 +08:00
}
2026-06-05 14:50:29 +08:00
})
}
const handleRerun = async (task) => {
rerunningId.value = task.id
try {
const res = await rerunRunTask(task.id)
if (res.code === 200) {
selectedTaskId.value = String(res.data || '')
activePanel.value = 'current'
await getList()
await nextTick()
document.querySelector(`[data-task-id="${selectedTaskId.value}"]`)?.scrollIntoView({ behavior: 'smooth', block: 'center' })
ElMessage.success('已按上次参数创建并启动新任务')
}
} finally {
rerunningId.value = ''
}
}
2026-06-05 16:53:40 +08:00
const pointList_runningTask = ref({})
/**
* 获取正在执行任务的点位列表
* @param taskId
*/
2026-06-05 14:50:29 +08:00
const getPointByTaskId = async (taskId) => {
const res = await getWaypointByTaskId(taskId)
if (res.code === 200) {
2026-06-05 16:53:40 +08:00
pointList_runningTask.value[taskId] = res.data
2026-06-05 14:50:29 +08:00
}
2026-06-04 16:41:29 +08:00
}
2026-06-05 16:53:40 +08:00
const taskLogList = ref({})
/**
* 获取正在执行任务的执行日志
* @param taskInstanceId
*/
const getTaskLogList = async (taskInstanceId) => {
const res = await fetchAllPagedRows(getTaskLogListApi, { taskInstanceId })
2026-06-05 16:53:40 +08:00
if (res.code === 200) {
taskLogList.value[taskInstanceId] = res.rows
}
}
/**
* 自定义进度条值不同区间所显示的颜色
* @param percentage
*/
2026-06-04 16:41:29 +08:00
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,
});
2026-06-05 16:53:40 +08:00
/**
* 开始执行任务
* @param task
*/
2026-06-04 16:41:29 +08:00
const startTask = async (task) => {
if (String(task.status) === '3') {
2026-06-04 16:41:29 +08:00
let res = await resumeRunTask(task.id);
if (res.code === 200) {
ElMessage.success("任务已恢复");
getList();
}
} else {
const taskDefinition = taskList.value.find(item => item.id === task.taskId)
if (!taskDefinition?.taskConfigId) {
ElMessage.warning('当前任务没有关联流程编排')
return
}
try {
const paramsResponse = await getFlowParamsApi(taskDefinition.taskConfigId)
const paramItems = paramsResponse.rows || []
const roles = await loadFlowItemScopedRuntimeRoles(paramItems)
runtimeAssignments.value = createRuntimeAssignments(roles, task.robotIdentity)
runtimeMapId.value = taskDefinition.mapId || ''
runtimeRoles.value = roles
runtimeParamItems.value = paramItems
pendingStartTask.value = task
startDialogVisible.value = true
} catch (error) {
ElMessage.error(error.message || '工作流执行资源加载失败')
}
}
}
const confirmStartTask = async () => {
const assignmentError = runtimeEditorRef.value?.validate()
if (assignmentError) {
ElMessage.warning(assignmentError)
return
}
const paramsValid = await runtimeParamsEditorRef.value?.validate()
if (paramsValid === false) {
ElMessage.warning('请完整填写运行参数')
return
}
startSubmitting.value = true
try {
const robotId = firstAssignedRobotId(runtimeAssignments.value)
const res = await startRunTask(pendingStartTask.value.id, {
robotId,
runtimeAssignments: runtimeAssignments.value,
runParams: runtimeParamsEditorRef.value?.getRunParams() || {}
})
2026-06-04 16:41:29 +08:00
if (res.code === 200) {
ElMessage.success('任务已开始')
startDialogVisible.value = false
getList()
2026-06-04 16:41:29 +08:00
}
} finally {
startSubmitting.value = false
2026-06-04 16:41:29 +08:00
}
}
2026-06-05 16:53:40 +08:00
/**
* 暂停执行任务
* @param task
*/
2026-06-04 16:41:29 +08:00
const pauseTask = async (task) => {
let res = await pauseRunTask(task.id);
if (res.code === 200) {
ElMessage.success("任务已暂停");
getList();
}
}
2026-06-05 16:53:40 +08:00
/**
* 停止执行任务
* @param task
*/
2026-06-04 16:41:29 +08:00
const stopTask = async (task) => {
let res = await stopRunTask(task.id);
if (res.code === 200) {
ElMessage.success("任务已停止");
getList();
}
}
2026-06-05 16:53:40 +08:00
/**
* 删除执行任务
* @param task
*/
2026-06-04 16:41:29 +08:00
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(() => {
// 取消删除
});
}
2026-06-05 14:50:29 +08:00
/**
* 获取历史执行任务
*/
2026-06-04 16:41:29 +08:00
const getHistoryList = () => {
loading.value = true;
getRunTaskList({
statusList: ['0', '2', '4'],
pageNum: queryParams.pageNum,
pageSize: queryParams.pageSize,
}).then((res) => {
if (res.code === 200) {
tableDataList.value = normalizeTaskStatus(res.rows);
2026-06-04 16:41:29 +08:00
total.value = res.total;
}
loading.value = false;
}).catch(() => {
loading.value = false;
});
}
2026-06-05 14:50:29 +08:00
const taskRunningInfo = ref({})
const socketChannelCallbacks = ref({})
const channel = 'InspectionTaskInstance';
2026-06-05 16:53:40 +08:00
/**
* 订阅websocket
*/
2026-06-05 14:50:29 +08:00
const subscribeDebounced = debounce(async (sub) => {
if (!socket) {
console.warn(`执行任务订阅失败: socket=${!!socket}`);
return;
}
socket.send({
type: 'channel_subscription',
action: sub ? 'subscribe' : 'unsubscribe',
channel,
});
if (sub) {
await nextTick();
// 清理旧回调
if (socketChannelCallbacks.value[channel]) {
socket.off(channel, socketChannelCallbacks.value[channel]);
}
const callback = (data) => {
if (data !== '500') {
// WebSocket 可能把状态作为字符串发送,先转成数字再判断,避免 '0' !== 0 导致终态被误当成运行日志。
// 0=完成、2=失败、4=终止,这三种状态都会结束当前实例,不再追加实时日志和进度。
const status = Number(data.status)
const terminal = [0, 2, 4].includes(status)
if (!terminal) {
const steps = pointList_runningTask.value[data.taskId] || []
let activeStep = Number.isInteger(data.itemOrder) && data.itemOrder > 0
? data.itemOrder - 1
: Math.max(steps.findIndex(item => item.detectItemId === data.itemId), 0)
2026-06-05 14:50:29 +08:00
if (data.progress == 100) {
activeStep += 1
}
2026-06-05 16:53:40 +08:00
taskRunningInfo.value[data.taskInstanceId] = {
2026-06-05 14:50:29 +08:00
...data,
activeStep
}
2026-06-05 16:53:40 +08:00
if (!taskLogList.value[data.taskInstanceId]) {
taskLogList.value[data.taskInstanceId] = []
}
taskLogList.value[data.taskInstanceId].push({
...data
})
2026-06-05 14:50:29 +08:00
} else {
// 终态到达后清理内存中的实时快照,再从接口刷新当前队列与历史列表,保证页面以持久化结果为准。
2026-06-05 16:53:40 +08:00
delete taskRunningInfo.value[data.taskInstanceId]
delete taskLogList.value[data.taskInstanceId]
2026-06-05 14:50:29 +08:00
getList()
2026-07-06 11:11:59 +08:00
getHistoryList()
2026-06-05 14:50:29 +08:00
}
} else {
// 错误处理:取消订阅后重新订阅
subscribeDebounced(false);
subscribeDebounced(true);
}
};
socket.on(channel, callback);
socketChannelCallbacks.value[channel] = callback;
} else {
if (socketChannelCallbacks.value[channel]) {
socket.off(channel, socketChannelCallbacks.value[channel]);
delete socketChannelCallbacks.value[channel];
}
}
}, 300);
2026-06-05 17:18:47 +08:00
const logDialogVisible = ref(false)
const logDialogData = ref([])
const logDialogLoading = ref(false)
let logDialogRequestId = 0
2026-06-05 17:18:47 +08:00
const handleOpenLog = async (row) => {
const requestId = ++logDialogRequestId
2026-06-05 17:18:47 +08:00
logDialogVisible.value = true
logDialogData.value = []
logDialogLoading.value = true
try {
const res = await fetchAllPagedRows(getTaskLogListApi, { taskInstanceId: row.id })
if (requestId === logDialogRequestId && res.code === 200) {
logDialogData.value = res.rows
}
} finally {
if (requestId === logDialogRequestId) logDialogLoading.value = false
2026-06-05 17:18:47 +08:00
}
}
const refreshAfterSocketReconnect = () => {
getList()
getHistoryList()
}
2026-06-04 16:41:29 +08:00
onMounted(() => {
getTaskData();
getList();
getHistoryList();
2026-06-05 14:50:29 +08:00
subscribeDebounced(true)
socket?.on('open', refreshAfterSocketReconnect)
2026-06-05 14:50:29 +08:00
})
onUnmounted(() => {
subscribeDebounced(false)
socket?.off('open', refreshAfterSocketReconnect)
2026-06-04 16:41:29 +08:00
})
</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;
cursor: pointer;
transition: background-color 0.2s ease, box-shadow 0.2s ease;
&:hover {
background: var(--el-fill-color-light);
}
&.is-selected {
background: var(--el-color-primary-light-9);
box-shadow: inset 0 0 0 2px var(--el-color-primary);
}
2026-06-04 16:41:29 +08:00
.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;
}
}
2026-06-05 14:50:29 +08:00
.context-container {
height: calc(100% - 40px);
overflow: auto;
}
2026-06-04 16:41:29 +08:00
.task {
padding: 16px;
background: #fafafa;
border-radius: 20px;
2026-06-05 14:50:29 +08:00
margin-bottom: 12px;
2026-06-04 16:41:29 +08:00
.task-header {
display: flex;
justify-content: space-between;
align-items: center;
2026-06-05 14:50:29 +08:00
.task-info {
display: flex;
gap: 40px;
.title {
font-size: 18px;
font-weight: bold;
}
.id {
color: #666;
font-size: 14px;
margin: 8px 0;
}
2026-06-04 16:41:29 +08:00
}
2026-06-05 14:50:29 +08:00
.robot {
display: flex;
align-items: center;
gap: 50px;
2026-06-04 16:41:29 +08:00
color: #666;
font-size: 14px;
}
}
2026-06-05 14:50:29 +08:00
2026-06-04 16:41:29 +08:00
.progress-label {
margin-top: 12px;
color: #666;
font-size: 14px;
2026-06-05 14:50:29 +08:00
font-weight: bold;
2026-06-04 16:41:29 +08:00
}
}
}
}
2026-08-12 13:32:00 +08:00
.start-dialog-content {
max-height: 65vh;
padding-right: 6px;
overflow-y: auto;
}
.start-section-title {
margin: 16px 0 10px;
color: var(--el-text-color-primary);
font-size: 15px;
font-weight: 600;
}
.start-section-title--params {
margin-top: 22px;
}
</style>