fix: 调度中心ws订阅的相关方法在初始化时暴露
This commit is contained in:
136
frontend/src/views/scheduler/schedulerHandlers.ts
Normal file
136
frontend/src/views/scheduler/schedulerHandlers.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
// schedulerHandlers.ts
|
||||
// 提供在调度中心 UI 未加载前也能工作的消息处理实现。
|
||||
// 核心策略:将重要事件保存到 localStorage(或内存队列),并暴露注册点供 UI 在挂载时接收并回放。
|
||||
|
||||
// no types needed here to avoid circular/unused imports
|
||||
|
||||
const PENDING_TABS_KEY = 'scheduler-pending-tabs'
|
||||
const PENDING_COUNTDOWN_KEY = 'scheduler-pending-countdown'
|
||||
const POWER_ACTION_KEY = 'scheduler-power-action'
|
||||
|
||||
type UIHooks = {
|
||||
onNewTab?: (tab: any) => void
|
||||
onCountdown?: (data: any) => void
|
||||
}
|
||||
|
||||
let uiHooks: UIHooks = {}
|
||||
|
||||
export function registerSchedulerUI(hooks: UIHooks) {
|
||||
uiHooks = { ...uiHooks, ...hooks }
|
||||
}
|
||||
|
||||
// helper: push pending tab id to localStorage
|
||||
function pushPendingTab(taskId: string) {
|
||||
try {
|
||||
const raw = localStorage.getItem(PENDING_TABS_KEY)
|
||||
const arr = raw ? JSON.parse(raw) : []
|
||||
if (!arr.includes(taskId)) {
|
||||
arr.push(taskId)
|
||||
localStorage.setItem(PENDING_TABS_KEY, JSON.stringify(arr))
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
function popPendingTabs(): string[] {
|
||||
try {
|
||||
const raw = localStorage.getItem(PENDING_TABS_KEY)
|
||||
if (!raw) return []
|
||||
localStorage.removeItem(PENDING_TABS_KEY)
|
||||
return JSON.parse(raw)
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function storePendingCountdown(data: any) {
|
||||
try {
|
||||
localStorage.setItem(PENDING_COUNTDOWN_KEY, JSON.stringify(data))
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
function consumePendingCountdownAndClear(): any | null {
|
||||
try {
|
||||
const raw = localStorage.getItem(PENDING_COUNTDOWN_KEY)
|
||||
if (!raw) return null
|
||||
localStorage.removeItem(PENDING_COUNTDOWN_KEY)
|
||||
return JSON.parse(raw)
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function savePowerAction(value: string) {
|
||||
try {
|
||||
localStorage.setItem(POWER_ACTION_KEY, value)
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// 导出:供 useWebSocket 在模块加载时就能使用的处理函数
|
||||
export function handleTaskManagerMessage(wsMessage: any) {
|
||||
if (!wsMessage || typeof wsMessage !== 'object') return
|
||||
const { type, data } = wsMessage
|
||||
try {
|
||||
if (type === 'Signal' && data && data.newTask) {
|
||||
const taskId = String(data.newTask)
|
||||
// 将任务 ID 写入 pending 队列,UI 在挂载时会回放
|
||||
pushPendingTab(taskId)
|
||||
|
||||
// 如果 UI 已注册回调,则立即通知
|
||||
if (uiHooks.onNewTab) {
|
||||
try {
|
||||
uiHooks.onNewTab({ title: `调度台自动-${taskId}`, websocketId: taskId })
|
||||
} catch (e) {
|
||||
console.warn('[SchedulerHandlers] onNewTab handler error:', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[SchedulerHandlers] handleTaskManagerMessage error:', e)
|
||||
}
|
||||
}
|
||||
|
||||
export function handleMainMessage(wsMessage: any) {
|
||||
if (!wsMessage || typeof wsMessage !== 'object') return
|
||||
const { type, data } = wsMessage
|
||||
try {
|
||||
if (type === 'Message' && data && data.type === 'Countdown') {
|
||||
// 存储倒计时消息,供 UI 回放
|
||||
storePendingCountdown(data)
|
||||
if (uiHooks.onCountdown) {
|
||||
try {
|
||||
uiHooks.onCountdown(data)
|
||||
} catch (e) {
|
||||
console.warn('[SchedulerHandlers] onCountdown handler error:', e)
|
||||
}
|
||||
}
|
||||
} else if (type === 'Update' && data && data.PowerSign !== undefined) {
|
||||
// 保存电源操作显示值(供 UI 加载时读取)
|
||||
savePowerAction(String(data.PowerSign))
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[SchedulerHandlers] handleMainMessage error:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// UI 在挂载时调用,消费并回放 pending 数据
|
||||
export function consumePendingTabIds(): string[] {
|
||||
return popPendingTabs()
|
||||
}
|
||||
|
||||
export function consumePendingCountdown(): any | null {
|
||||
return consumePendingCountdownAndClear()
|
||||
}
|
||||
|
||||
export default {
|
||||
handleTaskManagerMessage,
|
||||
handleMainMessage,
|
||||
registerSchedulerUI,
|
||||
consumePendingTabIds,
|
||||
consumePendingCountdown,
|
||||
}
|
||||
@@ -3,7 +3,8 @@ 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, setTaskManagerHandler, setMainMessageHandler } from '@/composables/useWebSocket'
|
||||
import { useWebSocket, ExternalWSHandlers } from '@/composables/useWebSocket'
|
||||
import schedulerHandlers from './schedulerHandlers'
|
||||
import type { ComboBoxItem } from '@/api/models/ComboBoxItem'
|
||||
import type { QueueItem, Script } from './schedulerConstants'
|
||||
import {
|
||||
@@ -184,7 +185,7 @@ export function useSchedulerLogic() {
|
||||
title: options?.title || `调度台${tabCounter}`,
|
||||
closable: true,
|
||||
status: validStatus,
|
||||
selectedTaskId: null,
|
||||
selectedTaskId: options?.websocketId || null,
|
||||
selectedMode: TaskCreateIn.mode.AutoMode,
|
||||
websocketId: options?.websocketId || null,
|
||||
taskQueue: [],
|
||||
@@ -767,10 +768,61 @@ export function useSchedulerLogic() {
|
||||
// 初始化函数
|
||||
const initialize = () => {
|
||||
// 设置全局WebSocket的消息处理函数
|
||||
setTaskManagerHandler(handleTaskManagerMessage)
|
||||
setMainMessageHandler(handleMainMessage)
|
||||
// 通过 import 的 ExternalWSHandlers 直接注册处理函数,保证导入方能够永久引用并调用
|
||||
ExternalWSHandlers.taskManagerMessage = handleTaskManagerMessage
|
||||
ExternalWSHandlers.mainMessage = handleMainMessage
|
||||
console.log('[Scheduler] 已设置全局WebSocket消息处理函数')
|
||||
|
||||
// 注册 UI hooks 到 schedulerHandlers,使其能在 schedulerHandlers 检测到 pending 时回放到当前 UI
|
||||
try {
|
||||
schedulerHandlers.registerSchedulerUI({
|
||||
onNewTab: (tab) => {
|
||||
try {
|
||||
// 创建并订阅新调度台
|
||||
const newTab = addSchedulerTab({ title: tab.title, status: '运行', websocketId: tab.websocketId })
|
||||
subscribeToTask(newTab)
|
||||
saveTabsToStorage(schedulerTabs.value)
|
||||
} catch (e) {
|
||||
console.warn('[Scheduler] registerSchedulerUI onNewTab error:', e)
|
||||
}
|
||||
},
|
||||
onCountdown: (data) => {
|
||||
try {
|
||||
// 直接启动前端倒计时
|
||||
startPowerCountdown(data)
|
||||
} catch (e) {
|
||||
console.warn('[Scheduler] registerSchedulerUI onCountdown error:', e)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 回放 pending tabs(如果有的话)
|
||||
const pending = schedulerHandlers.consumePendingTabIds()
|
||||
if (pending && pending.length > 0) {
|
||||
pending.forEach((taskId: string) => {
|
||||
try {
|
||||
const newTab = addSchedulerTab({ title: `调度台${taskId}`, status: '运行', websocketId: taskId })
|
||||
subscribeToTask(newTab)
|
||||
} catch (e) {
|
||||
console.warn('[Scheduler] replay pending tab error:', e)
|
||||
}
|
||||
})
|
||||
saveTabsToStorage(schedulerTabs.value)
|
||||
}
|
||||
|
||||
// 回放 pending countdown(如果有的话)
|
||||
const pendingCountdown = schedulerHandlers.consumePendingCountdown()
|
||||
if (pendingCountdown) {
|
||||
try {
|
||||
startPowerCountdown(pendingCountdown)
|
||||
} catch (e) {
|
||||
console.warn('[Scheduler] replay pending countdown error:', e)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Scheduler] schedulerHandlers registration failed:', e)
|
||||
}
|
||||
|
||||
// 新增:为已有的“运行中”标签恢复 WebSocket 订阅,防止路由切换返回后不再更新
|
||||
try {
|
||||
schedulerTabs.value.forEach(tab => {
|
||||
@@ -811,8 +863,7 @@ export function useSchedulerLogic() {
|
||||
}
|
||||
|
||||
// 清理全局WebSocket的消息处理函数
|
||||
setTaskManagerHandler(() => {})
|
||||
setMainMessageHandler(() => {})
|
||||
// 不再清理或重置导出的处理函数,保持使用者注册的处理逻辑永久有效
|
||||
|
||||
schedulerTabs.value.forEach(tab => {
|
||||
if (tab.websocketId) {
|
||||
|
||||
Reference in New Issue
Block a user