fix(ui): 修复调度中心切换路由后信息丢失的问题

This commit is contained in:
Alirea
2025-09-24 15:52:52 +08:00
parent cda50ea134
commit 59de0ec510
4 changed files with 151 additions and 22 deletions

View File

@@ -11,7 +11,7 @@
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ref } from 'vue'
import TaskTree from '@/components/TaskTree.vue'
interface User {
@@ -103,13 +103,12 @@ const handleWSMessage = (message: WSMessage) => {
// 处理 task_list 数据
else if (message.data?.task_list && Array.isArray(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()}`,
name: task.name || '未知任务',
status: task.status || '等待',
user_list: task.user_list || []
}))
taskData.value = convertedData
console.log('转换后的 taskData:', taskData.value)
// 更新展开状态
@@ -158,7 +157,7 @@ defineExpose({
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--ant-color-text-heading);
color: var(--ant-color-text);
}
.overview-content {

View File

@@ -158,7 +158,6 @@ import { LockOutlined } from '@ant-design/icons-vue'
import {
getPowerActionText,
POWER_ACTION_TEXT,
type SchedulerTab,
TAB_STATUS_COLOR,
} from './schedulerConstants'
import { useSchedulerLogic } from './useSchedulerLogic'

View File

@@ -4,6 +4,20 @@ import { PowerIn } from '@/api/models/PowerIn'
// 调度台状态
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> = {
: 'default',
@@ -69,6 +83,8 @@ export interface SchedulerTab {
logs: LogEntry[]
isLogAtBottom: boolean
lastLogContent: string
// 新增:任务总览快照(用于路由返回时快速恢复显示)
overviewData?: Script[]
}
export interface TaskMessage {

View File

@@ -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 { Service } from '@/api/services/Service'
import { TaskCreateIn } from '@/api/models/TaskCreateIn'
import { PowerIn } from '@/api/models/PowerIn'
import { useWebSocket } from '@/composables/useWebSocket'
import type { ComboBoxItem } from '@/api/models/ComboBoxItem'
import type { QueueItem } from './schedulerConstants'
import type { QueueItem, Script } from './schedulerConstants'
import {
getPowerActionText,
LOG_MAX_LENGTH,
type LogEntry,
type SchedulerTab,
type TaskMessage,
} from './schedulerConstants'
@@ -310,10 +308,10 @@ export function useSchedulerLogic() {
try {
await Service.stopTaskApiDispatchStopPost({ taskId: tab.websocketId })
// 不再取消订阅保持WebSocket连接以便接收结束信号
// 只需发送停止请求等待后端通过WebSocket发送结束信号
// 移除了提示消息"已发送停止任务请求,等待任务完成确认"
// 因为任务状态变更必须由明确外部信号驱动,不应该使用提示消息来表示等待状态
// 本地乐观刷新:将“运行/运行中”改为“等待”,立即回放到总览面板
refreshOverviewDisplay(tab, { optimisticStop: true })
// 等待后端通过 WebSocket 发送真实结束/更新信号进行最终同步
saveTabsToStorage(schedulerTabs.value)
} catch (error) {
console.error('停止任务失败:', error)
@@ -358,7 +356,7 @@ export function useSchedulerLogic() {
break
case 'Info':
console.log('[Scheduler] 处理Info消息:', data)
handleInfoMessage(tab, data)
handleInfoMessage(data)
break
case 'Message':
console.log('[Scheduler] 处理Message消息:', data)
@@ -382,14 +380,14 @@ export function useSchedulerLogic() {
}
// 尝试处理可能的错误/警告/信息
if (data.Error || data.Warning || data.Info) {
handleInfoMessage(tab, data)
handleInfoMessage(data)
}
}
}
}
const handleUpdateMessage = (tab: SchedulerTab, data: any) => {
// 直接将 WebSocket 消息传递给 TaskOverviewPanel
// 直接将 WebSocket 消息传递给 TaskOverviewPanel
const overviewPanel = overviewRefs.value.get(tab.key)
if (overviewPanel && overviewPanel.handleWSMessage) {
const wsMessage = {
@@ -401,12 +399,66 @@ export function useSchedulerLogic() {
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初始化消息
if (data.task_dict && Array.isArray(data.task_dict)) {
// 初始化任务队列 - 保持原始状态
const newTaskQueue = data.task_dict.map((item: any) => ({
name: item.name || '未知任务',
status: item.status || '等待', // 使用实际状态,而不是强制设置为等待
status: item.status || '等待',
}));
// 初始化用户队列(仅包含运行状态下的用户)
@@ -414,7 +466,6 @@ export function useSchedulerLogic() {
data.task_dict.forEach((taskItem: any) => {
if (taskItem.user_list && Array.isArray(taskItem.user_list)) {
taskItem.user_list.forEach((user: any) => {
// 只有在用户状态为运行时才添加到用户队列中
if (user.status === '运行') {
newUserQueue.push({
name: `${taskItem.name}-${user.name}`,
@@ -450,7 +501,6 @@ export function useSchedulerLogic() {
// 处理日志 - 直接显示完整日志内容,覆盖上次显示的内容
if (data.log) {
if (typeof data.log === 'string') {
// 直接替换日志内容,不添加时间戳,不保留历史记录
tab.lastLogContent = data.log;
} else if (typeof data.log === 'object') {
if (data.log.Error) tab.lastLogContent = data.log.Error;
@@ -462,7 +512,7 @@ export function useSchedulerLogic() {
saveTabsToStorage(schedulerTabs.value)
}
const handleInfoMessage = (tab: SchedulerTab, data: any) => {
const handleInfoMessage = (data: any) => {
if (data.Error) {
notification.error({ message: '任务错误', description: data.Error })
} else if (data.Warning) {
@@ -550,6 +600,20 @@ export function useSchedulerLogic() {
if (el) {
overviewRefs.value.set(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 {
overviewRefs.value.delete(key)
}
@@ -725,6 +789,18 @@ export function useSchedulerLogic() {
// 订阅Main消息用于接收全局消息如电源操作倒计时
subscribeToMainMessages()
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消息处理全局消息
@@ -742,7 +818,7 @@ export function useSchedulerLogic() {
if (type === 'Message' && data && data.type === 'Countdown') {
// 收到倒计时消息启动前端60秒倒计时
console.log('[Scheduler] 收到倒计时消息启动60秒倒计时:', data)
console.log('[Scheduler] 收到倒计时消息,启动前端60秒倒计时:', data)
startPowerCountdown(data)
} else if (type === 'Update' && data && data.PowerSign !== undefined) {
// 收到电源操作更新消息,更新显示
@@ -772,6 +848,44 @@ export function useSchedulerLogic() {
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 {
// 状态
schedulerTabs,
@@ -819,3 +933,4 @@ export function useSchedulerLogic() {
setOverviewRef,
}
}