CMVR-BID/src/views/inspection/runTask/index.vue
zhanghao 5777d64821 refactor: update styles to use CSS variables for consistency and theming
- Replaced hardcoded colors and styles with CSS variables in multiple components including `index.vue`, `lock.vue`, `login.vue`, and `register.vue`.
- Improved layout and spacing in forms and containers for better responsiveness.
- Removed unused particle background animation from `lock.vue`.
- Added new SCSS file for Element Plus theme customization.
- Introduced a minimalist CSS file for global styles and variables.
2026-08-27 15:40:05 +08:00

825 lines
30 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 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)">
<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.stop="pauseTask(task)" />
<SvgIcon class="icon" v-if="task.status === '3' || task.status === '5'" name="start"
:size="24" color="#00FF88" @click.stop="startTask(task)" />
<SvgIcon class="icon" v-if="task.status != '5'" name="stop" :size="24" color="#d81e06"
@click.stop="stopTask(task)" />
<SvgIcon class="icon" v-if="task.status === '5'" name="remove" :size="24" color="#FFB300"
@click.stop="removeTask(task)" />
</div>
</div>
<div class="robot">主要机器人:{{ task.robotName || '执行时安排' }}</div>
<el-progress :color="customColorMethod"
:percentage="parseFloat(taskRunningInfo[task.id]?.progress?.toFixed(2)) || 0" />
</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 ref="contextContainerRef" class="context-container">
<div v-if="activePanel === 'current'">
<!-- 当前任务区域只展示左侧任务队列中选中的任务,避免同时渲染所有执行中任务。 -->
<div v-if="selectedRunTask" :key="selectedRunTask.id" class="task"
:data-task-id="selectedRunTask.id">
<div class="task-header">
<div class="header-container">
<div class="task-info">
<div class="title">{{ selectedRunTask.taskName }}</div>
<div class="id">ID: {{ selectedRunTask.taskId }}</div>
</div>
<div class="robot">
<div>主要机器人:{{ selectedRunTask.robotName || '按流程角色安排' }}</div>
<div>开始时间: {{ formatDate(selectedRunTask.startTime) }}</div>
</div>
</div>
<div>
<el-tag :type="selectedRunTask.status === '3' ? 'primary' : 'warning'">
{{ taskStatusMap[selectedRunTask.status] || '未知状态' }}
</el-tag>
</div>
</div>
<div class="progress-label">执行进度</div>
<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" />
<div ref="taskLogContainerRef" class="run-log-container">
<div ref="taskLogTitleRef">执行日志</div>
<taskLog :height="taskLogHeight" :task-id="selectedRunTask.taskId"
:task-status="selectedRunTask.status"
:points="pointList_runningTask[selectedRunTask.taskId]"
:list="taskLogList[selectedRunTask.id]" />
</div>
</div>
<el-empty v-else description="暂无任务" />
</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>
<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="handleOpenLog(scope.row)">
日志
</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>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNum" :total="total" @pagination="getHistoryList" />
</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="remark">
<el-input type="textarea" v-model="form.remark" />
</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>
<el-dialog title="任务执行日志" v-model="logDialogVisible" width="800px" append-to-body>
<taskLog v-loading="logDialogLoading" :task-id="logDialogTaskId" :task-status="logDialogTaskStatus"
:list="logDialogData" :height="500" />
</el-dialog>
<el-dialog title="本次执行安排" v-model="startDialogVisible" width="820px" append-to-body>
<div class="start-dialog-content">
<div class="start-section-title">机器人和设备</div>
<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" />
</div>
<template #footer>
<el-button @click="startDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="startSubmitting" @click="confirmStartTask">开始执行</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import SvgIcon from "@/components/SvgIcon";
import taskSteps from "./task-steps.vue";
import taskLog from "./task-log.vue";
import { useContainerHeight } from "@/hooks/tableHeight";
import { getTaskList, getWaypointByTaskId } from "@/api/inspection/task";
import { computed, onMounted, onUnmounted } from "vue";
import { useElementBounding, useElementSize } from '@vueuse/core'
import { addRunTask, getRunTaskList, startRunTask, rerunRunTask, pauseRunTask, stopRunTask, resumeRunTask, deleteRunTask, getTaskLogListApi } from "@/api/inspection/runTask";
import { ElMessage, ElMessageBox } from "element-plus";
import { formatDate } from "@/utils/index";
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'
const topContainerRef = ref();
const containerHeight = useContainerHeight(topContainerRef);
const contextContainerRef = ref(null)
const taskLogContainerRef = ref(null)
const taskLogTitleRef = ref(null)
// 通过元素边界实时获取当前任务内容区底部和日志容器顶部,避免依赖固定的 460px。
const { bottom: contextContainerBottom } = useElementBounding(contextContainerRef)
const { top: taskLogContainerTop } = useElementBounding(taskLogContainerRef)
const { height: taskLogTitleHeight } = useElementSize(taskLogTitleRef)
const TASK_LOG_MARGIN_TOP = 12
const CURRENT_TASK_BOTTOM_PADDING = 16
const MIN_TASK_LOG_HEIGHT = 150
/**
* 计算当前任务页面中日志组件可以使用的剩余高度。
*
* 计算基准是右侧内容区的底部,而不是窗口高度,因此页面外层布局、浏览器窗口
* 尺寸或右侧面板高度发生变化时都能得到正确结果。日志容器顶部到内容区底部之间,
* 还需要扣除“执行日志”标题高度、taskLog 组件自身的上间距,以及当前任务卡片的
* 底部内边距,确保日志不会覆盖任务卡片边界。元素尚未挂载时使用最低高度作为初始值,
* 待 VueUse 完成首次测量后会自动更新为实际剩余高度。
*/
const taskLogHeight = computed(() => {
const remainingHeight = contextContainerBottom.value
- taskLogContainerTop.value
- taskLogTitleHeight.value
- TASK_LOG_MARGIN_TOP
- CURRENT_TASK_BOTTOM_PADDING
return Math.max(MIN_TASK_LOG_HEIGHT, Math.floor(remainingHeight))
})
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('')
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),
}))
const getTaskData = () => {
getTaskList({
pageNum: 1,
pageSize: 10000,
}).then((res) => {
taskList.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;
}
/**
* 获取任务队列并维护当前选中的任务:
* 1. 页面首次加载时默认选中队列第一条任务;
* 2. 队列刷新后,如果原任务仍存在,则保留用户当前选择;
* 3. 如果原任务已经结束或被删除,则选择刷新后队列中的第一条任务;
* 4. 如果任务队列为空,则清空选择并由模板展示空状态。
*/
const getList = () => {
return getRunTaskList({
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 ?? ''
}
for (let i = 0; i < runTaskList.value.length; i++) {
getPointByTaskId(runTaskList.value[i].taskId)
getTaskLogList(runTaskList.value[i].id)
}
}
})
}
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 = ''
}
}
const pointList_runningTask = ref({})
/**
* 获取正在执行任务的点位列表
* @param taskId
*/
const getPointByTaskId = async (taskId) => {
const res = await getWaypointByTaskId(taskId)
if (res.code === 200) {
pointList_runningTask.value[taskId] = res.data
}
}
const taskLogList = ref({})
/**
* 获取正在执行任务的执行日志
* @param taskInstanceId
*/
const getTaskLogList = async (taskInstanceId) => {
const res = await fetchAllPagedRows(getTaskLogListApi, { taskInstanceId })
if (res.code === 200) {
taskLogList.value[taskInstanceId] = res.rows
}
}
/**
* 自定义进度条值不同区间所显示的颜色
* @param percentage
*/
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,
});
/**
* 开始执行任务
* @param task
*/
const startTask = async (task) => {
if (String(task.status) === '3') {
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() || {}
})
if (res.code === 200) {
ElMessage.success('任务已开始')
startDialogVisible.value = false
getList()
}
} finally {
startSubmitting.value = false
}
}
/**
* 暂停执行任务
* @param task
*/
const pauseTask = async (task) => {
let res = await pauseRunTask(task.id);
if (res.code === 200) {
ElMessage.success("任务已暂停");
getList();
}
}
/**
* 停止执行任务
* @param task
*/
const stopTask = async (task) => {
let res = await stopRunTask(task.id);
if (res.code === 200) {
ElMessage.success("任务已停止");
getList();
}
}
/**
* 删除执行任务
* @param task
*/
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 = normalizeTaskStatus(res.rows);
total.value = res.total;
}
loading.value = false;
}).catch(() => {
loading.value = false;
});
}
const taskRunningInfo = ref({})
const socketChannelCallbacks = ref({})
const channel = 'InspectionTaskInstance';
/**
* 订阅websocket
*/
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)
if (data.progress == 100) {
activeStep += 1
}
taskRunningInfo.value[data.taskInstanceId] = {
...data,
activeStep
}
if (!taskLogList.value[data.taskInstanceId]) {
taskLogList.value[data.taskInstanceId] = []
}
taskLogList.value[data.taskInstanceId].push({
...data
})
} else {
// 终态到达后清理内存中的实时快照,再从接口刷新当前队列与历史列表,保证页面以持久化结果为准。
delete taskRunningInfo.value[data.taskInstanceId]
delete taskLogList.value[data.taskInstanceId]
getList()
getHistoryList()
}
} 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);
const logDialogVisible = ref(false)
const logDialogData = ref([])
// 历史日志组件需要任务定义 ID 查询该任务绑定的点位,并用任务终态推导未单独记录状态的点位结果。
const logDialogTaskId = ref('')
const logDialogTaskStatus = ref(null)
const logDialogLoading = ref(false)
let logDialogRequestId = 0
const handleOpenLog = async (row) => {
const requestId = ++logDialogRequestId
logDialogVisible.value = true
// 先切换任务上下文,再请求日志;点位请求和日志请求可以并行,避免弹窗一直沿用上一次任务的数据。
logDialogTaskId.value = row.taskId
logDialogTaskStatus.value = row.status
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
}
}
const refreshAfterSocketReconnect = () => {
getList()
getHistoryList()
}
onMounted(() => {
getTaskData();
getList();
getHistoryList();
subscribeDebounced(true)
socket?.on('open', refreshAfterSocketReconnect)
})
onUnmounted(() => {
subscribeDebounced(false)
socket?.off('open', refreshAfterSocketReconnect)
})
</script>
<style lang="scss" scoped>
.page-container {
width: 100%;
height: 100%;
display: flex;
gap: 12px;
.left {
width: 25%;
height: 100%;
background: var(--min-bg-white);
padding: 16px;
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 16px;
border-bottom: 1px solid var(--min-border-light);
}
.taskList-container {
margin-top: 16px;
height: calc(100% - 100px);
overflow-y: auto;
.task-item {
padding: 12px;
background: var(--min-bg-page);
box-shadow: inset 0 0 0 1px var(--min-border-light);
border-radius: var(--min-radius-base);
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);
}
.task-content {
display: flex;
justify-content: space-between;
align-items: center;
.task-title {
font-size: 16px;
font-weight: 600;
}
.task-actions {
display: flex;
gap: 8px;
.icon {
cursor: pointer;
}
}
}
.robot {
margin-top: 8px;
color: var(--min-text-secondary);
}
}
}
}
.right {
flex: 1;
padding: 16px;
background: var(--min-bg-white);
overflow: hidden;
.header {
display: flex;
margin-bottom: 16px;
border-bottom: 1px solid var(--min-border-light);
.label {
font-size: 16px;
font-weight: 600;
color: var(--min-text-regular);
cursor: pointer;
box-shadow: inset 0 0 0 1px var(--min-border-light);
padding: 10px;
}
.active {
color: var(--min-primary);
box-shadow: inset 0 -2px 0 var(--min-primary);
}
}
.context-container {
height: calc(100% - 40px);
overflow: auto;
}
.task {
padding: 16px;
background: var(--min-bg-page);
border-radius: var(--min-radius-base);
margin-bottom: 12px;
.task-header {
display: flex;
justify-content: space-between;
align-items: center;
.task-info {
display: flex;
gap: 40px;
.title {
font-size: 18px;
font-weight: 600;
}
.id {
color: var(--min-text-secondary);
font-size: 14px;
margin: 8px 0;
}
}
.robot {
display: flex;
align-items: center;
gap: 50px;
color: var(--min-text-secondary);
font-size: 14px;
}
}
.progress-label {
margin-top: 12px;
color: var(--min-text-secondary);
font-size: 14px;
font-weight: 600;
}
}
}
}
.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>