fix(ui): 修复调度中心切换路由后信息丢失的问题
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref } from 'vue'
|
||||||
import TaskTree from '@/components/TaskTree.vue'
|
import TaskTree from '@/components/TaskTree.vue'
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
@@ -103,13 +103,12 @@ const handleWSMessage = (message: WSMessage) => {
|
|||||||
// 处理 task_list 数据
|
// 处理 task_list 数据
|
||||||
else if (message.data?.task_list && Array.isArray(message.data.task_list)) {
|
else if (message.data?.task_list && Array.isArray(message.data.task_list)) {
|
||||||
console.log('更新任务列表 (task_list):', message.data.task_list)
|
console.log('更新任务列表 (task_list):', message.data.task_list)
|
||||||
const convertedData = message.data.task_list.map((task: any) => ({
|
taskData.value = message.data.task_list.map((task: any) => ({
|
||||||
script_id: task.id || task.script_id || `task_${Date.now()}`,
|
script_id: task.id || task.script_id || `task_${Date.now()}`,
|
||||||
name: task.name || '未知任务',
|
name: task.name || '未知任务',
|
||||||
status: task.status || '等待',
|
status: task.status || '等待',
|
||||||
user_list: task.user_list || []
|
user_list: task.user_list || []
|
||||||
}))
|
}))
|
||||||
taskData.value = convertedData
|
|
||||||
console.log('转换后的 taskData:', taskData.value)
|
console.log('转换后的 taskData:', taskData.value)
|
||||||
|
|
||||||
// 更新展开状态
|
// 更新展开状态
|
||||||
@@ -158,7 +157,7 @@ defineExpose({
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--ant-color-text-heading);
|
color: var(--ant-color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.overview-content {
|
.overview-content {
|
||||||
|
|||||||
@@ -158,7 +158,6 @@ import { LockOutlined } from '@ant-design/icons-vue'
|
|||||||
import {
|
import {
|
||||||
getPowerActionText,
|
getPowerActionText,
|
||||||
POWER_ACTION_TEXT,
|
POWER_ACTION_TEXT,
|
||||||
type SchedulerTab,
|
|
||||||
TAB_STATUS_COLOR,
|
TAB_STATUS_COLOR,
|
||||||
} from './schedulerConstants'
|
} from './schedulerConstants'
|
||||||
import { useSchedulerLogic } from './useSchedulerLogic'
|
import { useSchedulerLogic } from './useSchedulerLogic'
|
||||||
|
|||||||
@@ -4,6 +4,20 @@ import { PowerIn } from '@/api/models/PowerIn'
|
|||||||
// 调度台状态
|
// 调度台状态
|
||||||
export type SchedulerStatus = '新建' | '运行' | '结束'
|
export type SchedulerStatus = '新建' | '运行' | '结束'
|
||||||
|
|
||||||
|
// 新增:任务总览数据类型
|
||||||
|
export interface User {
|
||||||
|
user_id: string
|
||||||
|
status: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Script {
|
||||||
|
script_id: string
|
||||||
|
status: string
|
||||||
|
name: string
|
||||||
|
user_list: User[]
|
||||||
|
}
|
||||||
|
|
||||||
// 状态颜色映射
|
// 状态颜色映射
|
||||||
export const TAB_STATUS_COLOR: Record<SchedulerStatus, string> = {
|
export const TAB_STATUS_COLOR: Record<SchedulerStatus, string> = {
|
||||||
新建: 'default',
|
新建: 'default',
|
||||||
@@ -69,6 +83,8 @@ export interface SchedulerTab {
|
|||||||
logs: LogEntry[]
|
logs: LogEntry[]
|
||||||
isLogAtBottom: boolean
|
isLogAtBottom: boolean
|
||||||
lastLogContent: string
|
lastLogContent: string
|
||||||
|
// 新增:任务总览快照(用于路由返回时快速恢复显示)
|
||||||
|
overviewData?: Script[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TaskMessage {
|
export interface TaskMessage {
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import { computed, nextTick, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { message, Modal, notification } from 'ant-design-vue'
|
import { message, Modal, notification } from 'ant-design-vue'
|
||||||
import { Service } from '@/api/services/Service'
|
import { Service } from '@/api/services/Service'
|
||||||
import { TaskCreateIn } from '@/api/models/TaskCreateIn'
|
import { TaskCreateIn } from '@/api/models/TaskCreateIn'
|
||||||
import { PowerIn } from '@/api/models/PowerIn'
|
import { PowerIn } from '@/api/models/PowerIn'
|
||||||
import { useWebSocket } from '@/composables/useWebSocket'
|
import { useWebSocket } from '@/composables/useWebSocket'
|
||||||
import type { ComboBoxItem } from '@/api/models/ComboBoxItem'
|
import type { ComboBoxItem } from '@/api/models/ComboBoxItem'
|
||||||
import type { QueueItem } from './schedulerConstants'
|
import type { QueueItem, Script } from './schedulerConstants'
|
||||||
import {
|
import {
|
||||||
getPowerActionText,
|
getPowerActionText,
|
||||||
LOG_MAX_LENGTH,
|
|
||||||
type LogEntry,
|
|
||||||
type SchedulerTab,
|
type SchedulerTab,
|
||||||
type TaskMessage,
|
type TaskMessage,
|
||||||
} from './schedulerConstants'
|
} from './schedulerConstants'
|
||||||
@@ -310,10 +308,10 @@ export function useSchedulerLogic() {
|
|||||||
try {
|
try {
|
||||||
await Service.stopTaskApiDispatchStopPost({ taskId: tab.websocketId })
|
await Service.stopTaskApiDispatchStopPost({ taskId: tab.websocketId })
|
||||||
|
|
||||||
// 不再取消订阅,保持WebSocket连接以便接收结束信号
|
// 本地乐观刷新:将“运行/运行中”改为“等待”,立即回放到总览面板
|
||||||
// 只需发送停止请求,等待后端通过WebSocket发送结束信号
|
refreshOverviewDisplay(tab, { optimisticStop: true })
|
||||||
// 移除了提示消息"已发送停止任务请求,等待任务完成确认"
|
|
||||||
// 因为任务状态变更必须由明确外部信号驱动,不应该使用提示消息来表示等待状态
|
// 等待后端通过 WebSocket 发送真实结束/更新信号进行最终同步
|
||||||
saveTabsToStorage(schedulerTabs.value)
|
saveTabsToStorage(schedulerTabs.value)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('停止任务失败:', error)
|
console.error('停止任务失败:', error)
|
||||||
@@ -358,7 +356,7 @@ export function useSchedulerLogic() {
|
|||||||
break
|
break
|
||||||
case 'Info':
|
case 'Info':
|
||||||
console.log('[Scheduler] 处理Info消息:', data)
|
console.log('[Scheduler] 处理Info消息:', data)
|
||||||
handleInfoMessage(tab, data)
|
handleInfoMessage(data)
|
||||||
break
|
break
|
||||||
case 'Message':
|
case 'Message':
|
||||||
console.log('[Scheduler] 处理Message消息:', data)
|
console.log('[Scheduler] 处理Message消息:', data)
|
||||||
@@ -382,14 +380,14 @@ export function useSchedulerLogic() {
|
|||||||
}
|
}
|
||||||
// 尝试处理可能的错误/警告/信息
|
// 尝试处理可能的错误/警告/信息
|
||||||
if (data.Error || data.Warning || data.Info) {
|
if (data.Error || data.Warning || data.Info) {
|
||||||
handleInfoMessage(tab, data)
|
handleInfoMessage(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUpdateMessage = (tab: SchedulerTab, data: any) => {
|
const handleUpdateMessage = (tab: SchedulerTab, data: any) => {
|
||||||
// 直接将 WebSocket 消息传递给 TaskOverviewPanel
|
// 直接将 WebSocket 消消息传递给 TaskOverviewPanel
|
||||||
const overviewPanel = overviewRefs.value.get(tab.key)
|
const overviewPanel = overviewRefs.value.get(tab.key)
|
||||||
if (overviewPanel && overviewPanel.handleWSMessage) {
|
if (overviewPanel && overviewPanel.handleWSMessage) {
|
||||||
const wsMessage = {
|
const wsMessage = {
|
||||||
@@ -401,12 +399,66 @@ export function useSchedulerLogic() {
|
|||||||
overviewPanel.handleWSMessage(wsMessage)
|
overviewPanel.handleWSMessage(wsMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同步维护 任务总览 快照 overviewData(用于路由返回后的快速恢复)
|
||||||
|
try {
|
||||||
|
if (data.task_dict && Array.isArray(data.task_dict)) {
|
||||||
|
// 完整脚本+用户数据,直接保存
|
||||||
|
tab.overviewData = (data.task_dict as Script[]).map(s => ({ ...s, user_list: s.user_list ? [...s.user_list] : [] }))
|
||||||
|
} else if (data.user_list && Array.isArray(data.user_list)) {
|
||||||
|
// 只有用户列表更新,合并到现有快照或创建默认脚本
|
||||||
|
if (!tab.overviewData || tab.overviewData.length === 0) {
|
||||||
|
const users = data.user_list
|
||||||
|
const userStatuses = users.map((u: any) => u.status)
|
||||||
|
let scriptStatus = '等待'
|
||||||
|
if (userStatuses.includes('异常') || userStatuses.includes('失败')) scriptStatus = '异常'
|
||||||
|
else if (userStatuses.includes('运行中')) scriptStatus = '运行中'
|
||||||
|
else if (userStatuses.length > 0 && userStatuses.every((s: string) => s === '已完成')) scriptStatus = '已完成'
|
||||||
|
tab.overviewData = [
|
||||||
|
{
|
||||||
|
script_id: 'default-script',
|
||||||
|
name: '新 MAA 脚本',
|
||||||
|
status: scriptStatus,
|
||||||
|
user_list: users,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
const users = data.user_list
|
||||||
|
const userStatuses = users.map((u: any) => u.status)
|
||||||
|
let scriptStatus = '等待'
|
||||||
|
if (userStatuses.includes('异常') || userStatuses.includes('失败')) scriptStatus = '异常'
|
||||||
|
else if (userStatuses.includes('运行中')) scriptStatus = '运行中'
|
||||||
|
else if (userStatuses.length > 0 && userStatuses.every((s: string) => s === '已完成')) scriptStatus = '已完成'
|
||||||
|
// 更新第一个脚本
|
||||||
|
tab.overviewData = [
|
||||||
|
{
|
||||||
|
...(tab.overviewData[0] || { script_id: 'default-script', name: '新 MAA 脚本' }),
|
||||||
|
status: scriptStatus,
|
||||||
|
user_list: users,
|
||||||
|
},
|
||||||
|
...tab.overviewData.slice(1),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
} else if (data.task_list && Array.isArray(data.task_list)) {
|
||||||
|
// 将 task_list 转为 Script[]
|
||||||
|
const converted: Script[] = data.task_list.map((task: any) => ({
|
||||||
|
script_id: task.id || task.script_id || `task_${Date.now()}`,
|
||||||
|
name: task.name || '未知任务',
|
||||||
|
status: task.status || '等待',
|
||||||
|
user_list: task.user_list || [],
|
||||||
|
}))
|
||||||
|
tab.overviewData = converted
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[Scheduler] 维护 overviewData 快照时出现问题:', e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 队列与日志 显示
|
||||||
// 处理task_dict初始化消息
|
// 处理task_dict初始化消息
|
||||||
if (data.task_dict && Array.isArray(data.task_dict)) {
|
if (data.task_dict && Array.isArray(data.task_dict)) {
|
||||||
// 初始化任务队列 - 保持原始状态
|
// 初始化任务队列 - 保持原始状态
|
||||||
const newTaskQueue = data.task_dict.map((item: any) => ({
|
const newTaskQueue = data.task_dict.map((item: any) => ({
|
||||||
name: item.name || '未知任务',
|
name: item.name || '未知任务',
|
||||||
status: item.status || '等待', // 使用实际状态,而不是强制设置为等待
|
status: item.status || '等待',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 初始化用户队列(仅包含运行状态下的用户)
|
// 初始化用户队列(仅包含运行状态下的用户)
|
||||||
@@ -414,7 +466,6 @@ export function useSchedulerLogic() {
|
|||||||
data.task_dict.forEach((taskItem: any) => {
|
data.task_dict.forEach((taskItem: any) => {
|
||||||
if (taskItem.user_list && Array.isArray(taskItem.user_list)) {
|
if (taskItem.user_list && Array.isArray(taskItem.user_list)) {
|
||||||
taskItem.user_list.forEach((user: any) => {
|
taskItem.user_list.forEach((user: any) => {
|
||||||
// 只有在用户状态为运行时才添加到用户队列中
|
|
||||||
if (user.status === '运行') {
|
if (user.status === '运行') {
|
||||||
newUserQueue.push({
|
newUserQueue.push({
|
||||||
name: `${taskItem.name}-${user.name}`,
|
name: `${taskItem.name}-${user.name}`,
|
||||||
@@ -450,7 +501,6 @@ export function useSchedulerLogic() {
|
|||||||
// 处理日志 - 直接显示完整日志内容,覆盖上次显示的内容
|
// 处理日志 - 直接显示完整日志内容,覆盖上次显示的内容
|
||||||
if (data.log) {
|
if (data.log) {
|
||||||
if (typeof data.log === 'string') {
|
if (typeof data.log === 'string') {
|
||||||
// 直接替换日志内容,不添加时间戳,不保留历史记录
|
|
||||||
tab.lastLogContent = data.log;
|
tab.lastLogContent = data.log;
|
||||||
} else if (typeof data.log === 'object') {
|
} else if (typeof data.log === 'object') {
|
||||||
if (data.log.Error) tab.lastLogContent = data.log.Error;
|
if (data.log.Error) tab.lastLogContent = data.log.Error;
|
||||||
@@ -462,7 +512,7 @@ export function useSchedulerLogic() {
|
|||||||
saveTabsToStorage(schedulerTabs.value)
|
saveTabsToStorage(schedulerTabs.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleInfoMessage = (tab: SchedulerTab, data: any) => {
|
const handleInfoMessage = (data: any) => {
|
||||||
if (data.Error) {
|
if (data.Error) {
|
||||||
notification.error({ message: '任务错误', description: data.Error })
|
notification.error({ message: '任务错误', description: data.Error })
|
||||||
} else if (data.Warning) {
|
} else if (data.Warning) {
|
||||||
@@ -550,6 +600,20 @@ export function useSchedulerLogic() {
|
|||||||
if (el) {
|
if (el) {
|
||||||
overviewRefs.value.set(key, el)
|
overviewRefs.value.set(key, el)
|
||||||
console.log('设置 TaskOverviewPanel 引用:', key, el)
|
console.log('设置 TaskOverviewPanel 引用:', key, el)
|
||||||
|
// 若当前 tab 有 overviewData 快照,立即回放到子组件,保证路由切回时立现
|
||||||
|
const tab = schedulerTabs.value.find(t => t.key === key)
|
||||||
|
if (tab?.overviewData && el.handleWSMessage) {
|
||||||
|
const wsMessage = {
|
||||||
|
type: 'Update',
|
||||||
|
id: tab.websocketId,
|
||||||
|
data: { task_dict: tab.overviewData },
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
el.handleWSMessage(wsMessage)
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[Scheduler] 回放 overviewData 到面板时异常:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
overviewRefs.value.delete(key)
|
overviewRefs.value.delete(key)
|
||||||
}
|
}
|
||||||
@@ -725,6 +789,18 @@ export function useSchedulerLogic() {
|
|||||||
// 订阅Main消息(用于接收全局消息如电源操作倒计时)
|
// 订阅Main消息(用于接收全局消息如电源操作倒计时)
|
||||||
subscribeToMainMessages()
|
subscribeToMainMessages()
|
||||||
console.log('[Scheduler] 已订阅Main消息')
|
console.log('[Scheduler] 已订阅Main消息')
|
||||||
|
|
||||||
|
// 新增:为已有的“运行中”标签恢复 WebSocket 订阅,防止路由切换返回后不再更新
|
||||||
|
try {
|
||||||
|
schedulerTabs.value.forEach(tab => {
|
||||||
|
if (tab.status === '运行' && tab.websocketId) {
|
||||||
|
console.log('[Scheduler] 初始化阶段为运行中的标签恢复订阅:', { key: tab.key, websocketId: tab.websocketId })
|
||||||
|
subscribeToTask(tab)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[Scheduler] 恢复订阅时出现问题:', e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 订阅Main消息,处理全局消息
|
// 订阅Main消息,处理全局消息
|
||||||
@@ -742,7 +818,7 @@ export function useSchedulerLogic() {
|
|||||||
|
|
||||||
if (type === 'Message' && data && data.type === 'Countdown') {
|
if (type === 'Message' && data && data.type === 'Countdown') {
|
||||||
// 收到倒计时消息,启动前端60秒倒计时
|
// 收到倒计时消息,启动前端60秒倒计时
|
||||||
console.log('[Scheduler] 收到倒计时消息,启动60秒倒计时:', data)
|
console.log('[Scheduler] 收到倒计时消息,启动前端60秒倒计时:', data)
|
||||||
startPowerCountdown(data)
|
startPowerCountdown(data)
|
||||||
} else if (type === 'Update' && data && data.PowerSign !== undefined) {
|
} else if (type === 'Update' && data && data.PowerSign !== undefined) {
|
||||||
// 收到电源操作更新消息,更新显示
|
// 收到电源操作更新消息,更新显示
|
||||||
@@ -772,6 +848,44 @@ export function useSchedulerLogic() {
|
|||||||
savePowerActionToStorage(powerAction.value)
|
savePowerActionToStorage(powerAction.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 在面板上刷新任务总览显示(支持停止后的乐观更新)
|
||||||
|
const refreshOverviewDisplay = (tab: SchedulerTab, options?: { optimisticStop?: boolean }) => {
|
||||||
|
try {
|
||||||
|
const panel = overviewRefs.value.get(tab.key)
|
||||||
|
if (!tab.overviewData || !panel || !panel.handleWSMessage) return
|
||||||
|
|
||||||
|
let nextData: Script[] = tab.overviewData
|
||||||
|
|
||||||
|
if (options?.optimisticStop) {
|
||||||
|
// 将“运行/运行中”的用户标记为“等待”,并据此推导脚本状态
|
||||||
|
nextData = tab.overviewData.map(scr => {
|
||||||
|
const nextUsers = (scr.user_list || []).map(u => {
|
||||||
|
if (u.status === '运行' || u.status === '运行中') {
|
||||||
|
return { ...u, status: '等待' }
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
})
|
||||||
|
// 推导脚本状态
|
||||||
|
const statuses = nextUsers.map(u => u.status)
|
||||||
|
let s = '等待'
|
||||||
|
if (statuses.includes('异常') || statuses.includes('失败')) s = '异常'
|
||||||
|
else if (statuses.includes('运行') || statuses.includes('运行中')) s = '运行中'
|
||||||
|
else if (statuses.length > 0 && statuses.every(st => st === '已完成')) s = '已完成'
|
||||||
|
else if (statuses.includes('等待')) s = '等待'
|
||||||
|
return { ...scr, user_list: nextUsers, status: s }
|
||||||
|
})
|
||||||
|
// 保存回快照
|
||||||
|
tab.overviewData = nextData
|
||||||
|
}
|
||||||
|
|
||||||
|
// 立即回放
|
||||||
|
const wsMessage = { type: 'Update', id: tab.websocketId, data: { task_dict: nextData } }
|
||||||
|
panel.handleWSMessage(wsMessage)
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[Scheduler] 刷新任务总览显示失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 状态
|
// 状态
|
||||||
schedulerTabs,
|
schedulerTabs,
|
||||||
@@ -818,4 +932,5 @@ export function useSchedulerLogic() {
|
|||||||
// 任务总览面板引用管理
|
// 任务总览面板引用管理
|
||||||
setOverviewRef,
|
setOverviewRef,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user