Files
AUTO-MAS-test/frontend/src/views/scheduler/index.vue

348 lines
7.9 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="scheduler-main">
<!-- 页面头部 -->
<div class="scheduler-header">
<div class="header-left">
<h1 class="page-title">调度中心</h1>
</div>
<div class="header-actions">
<a-space size="middle">
<span class="power-label">任务完成后电源操作</span>
<a-select
v-model:value="powerAction"
style="width: 140px"
:disabled="!canChangePowerAction"
@change="onPowerActionChange"
size="large"
>
<a-select-option
v-for="(text, signal) in POWER_ACTION_TEXT"
:key="signal"
:value="signal"
>
{{ text }}
</a-select-option>
</a-select>
</a-space>
</div>
</div>
<!-- 调度台标签页 -->
<div class="scheduler-tabs">
<a-tabs
v-model:activeKey="activeSchedulerTab"
type="editable-card"
@edit="onSchedulerTabEdit"
:hide-add="false"
>
<a-tab-pane
v-for="tab in schedulerTabs"
:key="tab.key"
:closable="tab.closable && tab.status !== '运行'"
:data-tab-key="tab.key"
>
<template #tab>
<span class="tab-title">{{ tab.title }}</span>
<a-tag :color="TAB_STATUS_COLOR[tab.status]" size="small" class="tab-status">
{{ tab.status }}
</a-tag>
<a-tooltip v-if="tab.status === '运行'" title="运行中的调度台无法删除" placement="top">
<LockOutlined class="tab-lock-icon" />
</a-tooltip>
</template>
<!-- 任务控制与状态内容 -->
<div class="task-unified-card" :class="`status-${tab.status}`">
<!-- 任务控制栏 -->
<SchedulerTaskControl
v-model:selectedTaskId="tab.selectedTaskId"
v-model:selectedMode="tab.selectedMode"
:taskOptions="taskOptions"
:taskOptionsLoading="taskOptionsLoading"
:status="tab.status"
:disabled="tab.status === '运行'"
@start="startTask(tab)"
@stop="stopTask(tab)"
/>
<!-- 状态展示区域 -->
<div class="status-container">
<div class="overview-panel-container">
<TaskOverviewPanel
:ref="el => setOverviewRef(el, tab.key)"
/>
</div>
<div class="log-panel-container">
<SchedulerLogPanel
:log-content="tab.lastLogContent"
:tab-key="tab.key"
:is-log-at-bottom="tab.isLogAtBottom"
@scroll="onLogScroll(tab)"
@set-ref="setLogRef"
/>
</div>
</div>
</div>
</a-tab-pane>
<!-- 空状态 -->
<template #empty>
<div class="empty-tab-content">
<a-empty description="暂无调度台" />
</div>
</template>
</a-tabs>
</div>
<!-- 消息对话框 -->
<a-modal
v-model:open="messageModalVisible"
:title="currentMessage?.title || '系统消息'"
@ok="sendMessageResponse"
@cancel="cancelMessage"
>
<div v-if="currentMessage">
<p>{{ currentMessage.content }}</p>
<a-input
v-if="currentMessage.needInput"
v-model:value="messageResponse"
placeholder="请输入回复内容"
/>
</div>
</a-modal>
<!-- 电源操作倒计时弹窗已移至全局组件 GlobalPowerCountdown.vue -->
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'
import { LockOutlined } from '@ant-design/icons-vue'
import {
getPowerActionText,
POWER_ACTION_TEXT,
TAB_STATUS_COLOR,
} from './schedulerConstants'
import { useSchedulerLogic } from './useSchedulerLogic'
import SchedulerTaskControl from './SchedulerTaskControl.vue'
import SchedulerLogPanel from './SchedulerLogPanel.vue'
import TaskOverviewPanel from './TaskOverviewPanel.vue'
// 使用业务逻辑层
const {
// 状态
schedulerTabs,
activeSchedulerTab,
taskOptionsLoading,
taskOptions,
powerAction,
powerCountdownVisible,
powerCountdownData,
messageModalVisible,
currentMessage,
messageResponse,
// 计算属性
canChangePowerAction,
// Tab 管理
addSchedulerTab,
removeSchedulerTab,
// 任务操作
startTask,
stopTask,
// 日志操作
onLogScroll,
setLogRef,
// 电源操作
onPowerActionChange,
cancelPowerAction,
// 消息操作
sendMessageResponse,
cancelMessage,
// 初始化与清理
initialize,
loadTaskOptions,
cleanup,
// 新增:任务总览面板引用管理
setOverviewRef,
} = useSchedulerLogic()
// Tab 操作
const onSchedulerTabEdit = (targetKey: string | MouseEvent, action: 'add' | 'remove') => {
if (action === 'add') {
addSchedulerTab()
} else if (action === 'remove' && typeof targetKey === 'string') {
removeSchedulerTab(targetKey)
}
}
// 生命周期
onMounted(() => {
initialize() // 初始化TaskManager订阅
loadTaskOptions()
})
onUnmounted(() => {
cleanup()
})
</script>
<style scoped>
/* 页面容器 */
.scheduler-main {
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
background-color: var(--ant-color-bg-layout);
}
/* 页面头部样式 */
.scheduler-header {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 16px;
padding: 0 4px;
}
.header-left {
flex: 1;
}
.page-title {
margin: 0 0 8px 0;
font-size: 32px;
font-weight: 700;
color: var(--ant-color-text);
background: linear-gradient(135deg, var(--ant-color-primary), var(--ant-color-primary-hover));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.header-actions {
flex-shrink: 0;
display: flex;
align-items: center;
gap: 8px;
}
.power-label {
font-size: 14px;
color: var(--ant-color-text-secondary);
margin-right: 8px;
}
/* 标签页样式 */
.scheduler-tabs {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
background-color: var(--ant-color-bg-container);
border-radius: 8px;
padding: 12px;
}
.scheduler-tabs :deep(.ant-tabs) {
height: 100%;
display: flex;
flex-direction: column;
background-color: var(--ant-color-bg-container);
}
/* 任务卡片统一容器 */
.task-unified-card {
background-color: transparent;
box-shadow: none;
height: calc(100vh - 230px); /* 动态计算高度 */
display: flex;
flex-direction: column;
overflow: hidden;
}
/* 根据状态变化的样式 */
.task-unified-card.status-新建 {
background-color: transparent;
}
.task-unified-card.status-运行 {
background-color: transparent;
}
.task-unified-card.status-结束 {
background-color: transparent;
}
/* 状态容器 */
.status-container {
display: flex;
flex: 1;
overflow: hidden;
gap: 16px;
padding: 0;
margin: 0;
}
/* 任务总览面板容器 */
.overview-panel-container {
flex: 0 0 33.333333%; /* 占据1/3宽度 */
display: flex;
flex-direction: column;
overflow: hidden;
}
/* 日志面板容器 */
.log-panel-container {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* 电源操作倒计时弹窗样式已移至 GlobalPowerCountdown.vue */
/* 响应式 - 移动端适配 */
@media (max-width: 768px) {
.scheduler-main {
padding: 8px;
}
.scheduler-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.header-actions {
width: 100%;
justify-content: space-between;
}
.power-label {
display: none;
}
.status-container {
flex-direction: column;
}
.overview-panel-container,
.log-panel-container {
flex: 1;
width: 100%;
}
/* 移动端倒计时弹窗适配已移至 GlobalPowerCountdown.vue */
}
</style>