From 96c022c2ec9bf694a550bd3973b732c923a618f0 Mon Sep 17 00:00:00 2001 From: AoXuan Date: Wed, 24 Sep 2025 00:25:13 +0800 Subject: [PATCH 1/2] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E7=94=B5?= =?UTF-8?q?=E6=BA=90=E6=93=8D=E4=BD=9C=E5=80=92=E8=AE=A1=E6=97=B6=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E6=94=B9=E4=B8=BA=E4=BD=BF=E7=94=A8=E5=85=A8?= =?UTF-8?q?=E5=B1=8F=E5=BC=B9=E7=AA=97=E5=B9=B6=E7=94=B1=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/views/scheduler/index.vue | 201 ++++++++++++++++-- .../src/views/scheduler/schedulerConstants.ts | 2 +- .../src/views/scheduler/useSchedulerLogic.ts | 164 ++++++++++---- 3 files changed, 301 insertions(+), 66 deletions(-) diff --git a/frontend/src/views/scheduler/index.vue b/frontend/src/views/scheduler/index.vue index efea200..8cbf7a3 100644 --- a/frontend/src/views/scheduler/index.vue +++ b/frontend/src/views/scheduler/index.vue @@ -112,29 +112,43 @@ - - - -
-
⚠️
-
-

- 所有任务已完成,系统将在 {{ powerCountdown }} 秒后执行:{{ - getPowerActionText(powerAction) - }} + +

+
+
+
⚠️
+

{{ powerCountdownData.title || `${getPowerActionText(powerAction)}倒计时` }}

+

+ {{ powerCountdownData.message || `程序将在倒计时结束后执行 ${getPowerActionText(powerAction)} 操作` }}

- +
+ {{ powerCountdownData.countdown }} + +
+
+ 等待后端倒计时... +
+ +
+ + 取消操作 + +
- +
@@ -161,7 +175,7 @@ const { taskOptions, powerAction, powerCountdownVisible, - powerCountdown, + powerCountdownData, messageModalVisible, currentMessage, messageResponse, @@ -333,6 +347,128 @@ onUnmounted(() => { overflow: hidden; } +/* 电源操作倒计时全屏弹窗样式 */ +.power-countdown-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(8px); + z-index: 9999; + display: flex; + align-items: center; + justify-content: center; + animation: fadeIn 0.3s ease-out; +} + +.power-countdown-container { + background: var(--ant-color-bg-container); + border-radius: 16px; + padding: 48px; + box-shadow: 0 24px 48px rgba(0, 0, 0, 0.2); + text-align: center; + max-width: 500px; + width: 90%; + animation: slideIn 0.3s ease-out; +} + +.countdown-content .warning-icon { + font-size: 64px; + margin-bottom: 24px; + display: block; + animation: pulse 2s infinite; +} + +.countdown-title { + font-size: 28px; + font-weight: 600; + color: var(--ant-color-text); + margin: 0 0 16px 0; +} + +.countdown-message { + font-size: 16px; + color: var(--ant-color-text-secondary); + margin: 0 0 32px 0; + line-height: 1.5; +} + +.countdown-timer { + display: flex; + align-items: baseline; + justify-content: center; + margin-bottom: 32px; +} + +.countdown-number { + font-size: 72px; + font-weight: 700; + color: var(--ant-color-primary); + line-height: 1; + margin-right: 8px; + font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace; +} + +.countdown-unit { + font-size: 24px; + color: var(--ant-color-text-secondary); + font-weight: 500; +} + +.countdown-text { + font-size: 24px; + color: var(--ant-color-text-secondary); + font-weight: 500; +} + +.countdown-progress { + margin-bottom: 32px; +} + +.countdown-actions { + display: flex; + justify-content: center; +} + +.cancel-button { + padding: 12px 32px; + height: auto; + font-size: 16px; + font-weight: 500; +} + +/* 动画效果 */ +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(-20px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +@keyframes pulse { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.1); + } +} + /* 响应式 - 移动端适配 */ @media (max-width: 768px) { .scheduler-main { @@ -363,5 +499,28 @@ onUnmounted(() => { flex: 1; width: 100%; } + + /* 移动端倒计时弹窗适配 */ + .power-countdown-container { + padding: 32px 24px; + margin: 16px; + } + + .countdown-title { + font-size: 24px; + } + + .countdown-number { + font-size: 56px; + } + + .countdown-unit { + font-size: 20px; + } + + .countdown-content .warning-icon { + font-size: 48px; + margin-bottom: 16px; + } } \ No newline at end of file diff --git a/frontend/src/views/scheduler/schedulerConstants.ts b/frontend/src/views/scheduler/schedulerConstants.ts index cd97af4..17f204e 100644 --- a/frontend/src/views/scheduler/schedulerConstants.ts +++ b/frontend/src/views/scheduler/schedulerConstants.ts @@ -37,7 +37,7 @@ export const POWER_ACTION_TEXT: Record = { [PowerIn.signal.SHUTDOWN_FORCE]: '强制关机', } -export const getPowerActionText = (action: PowerIn.signal) => POWER_ACTION_TEXT[action] || '无动��' +export const getPowerActionText = (action: PowerIn.signal) => POWER_ACTION_TEXT[action] || '无动作' // 日志相关 export const LOG_MAX_LENGTH = 2000 // 最多保留日志条数 diff --git a/frontend/src/views/scheduler/useSchedulerLogic.ts b/frontend/src/views/scheduler/useSchedulerLogic.ts index d9598a4..fa546a4 100644 --- a/frontend/src/views/scheduler/useSchedulerLogic.ts +++ b/frontend/src/views/scheduler/useSchedulerLogic.ts @@ -95,7 +95,12 @@ export function useSchedulerLogic() { // 电源操作 - 从本地存储加载或使用默认值 const powerAction = ref(loadPowerActionFromStorage()) const powerCountdownVisible = ref(false) - const powerCountdown = ref(10) + const powerCountdownData = ref<{ + title?: string + message?: string + countdown?: number + }>({}) + // 前端自己的60秒倒计时 let powerCountdownTimer: ReturnType | null = null // 消息弹窗 @@ -333,6 +338,13 @@ export function useSchedulerLogic() { console.log('[Scheduler] 收到WebSocket消息:', { id, type, data, tabId: tab.websocketId }) + // 处理全局消息(如电源操作倒计时) + if (id === 'Main' && type === 'Message' && data?.type === 'Countdown') { + console.log('[Scheduler] 收到全局倒计时消息:', data) + handleMessageDialog(tab, data) + return + } + // 只处理与当前标签页相关的消息 if (id && id !== tab.websocketId) { console.log('[Scheduler] 消息ID不匹配,忽略消息:', { messageId: id, tabId: tab.websocketId }) @@ -461,6 +473,14 @@ export function useSchedulerLogic() { } const handleMessageDialog = (tab: SchedulerTab, data: any) => { + // 处理倒计时消息 + if (data.type === 'Countdown') { + console.log('[Scheduler] 收到倒计时消息,启动60秒倒计时:', data) + startPowerCountdown(data) + return + } + + // 处理普通消息对话框 if (data.title && data.content) { currentMessage.value = { title: data.title, @@ -498,18 +518,16 @@ export function useSchedulerLogic() { } message.success('任务完成') - checkAllTasksCompleted() saveTabsToStorage(schedulerTabs.value) // 触发Vue的响应式更新 schedulerTabs.value = [...schedulerTabs.value] } - if (data && data.power && data.power !== 'NoAction') { - powerAction.value = data.power as PowerIn.signal - savePowerActionToStorage(powerAction.value) - startPowerCountdown() - } + // 移除自动处理电源信号的逻辑,电源操作完全由后端WebSocket的倒计时消息控制 + // if (data && data.power && data.power !== 'NoAction') { + // // 不再自己处理电源信号 + // } } const onLogScroll = (tab: SchedulerTab) => { @@ -538,57 +556,88 @@ export function useSchedulerLogic() { } // 电源操作 - const onPowerActionChange = (value: PowerIn.signal) => { + const onPowerActionChange = async (value: PowerIn.signal) => { powerAction.value = value savePowerActionToStorage(value) - } - const startPowerCountdown = () => { - if (powerAction.value === PowerIn.signal.NO_ACTION) return - - powerCountdownVisible.value = true - powerCountdown.value = 10 - - powerCountdownTimer = setInterval(() => { - powerCountdown.value-- - if (powerCountdown.value <= 0) { - if (powerCountdownTimer) { - clearInterval(powerCountdownTimer) - powerCountdownTimer = null - } - powerCountdownVisible.value = false - executePowerAction() - } - }, 1000) - } - - const executePowerAction = async () => { + // 调用API设置电源操作 try { - await Service.powerTaskApiDispatchPowerPost({ signal: powerAction.value }) - message.success(`${getPowerActionText(powerAction.value)}命令已发送`) + await Service.setPowerApiDispatchSetPowerPost({ signal: value }) + console.log('[Scheduler] 电源操作设置成功:', value) } catch (error) { - console.error('执行电源操作失败:', error) - message.error('执行电源操作失败') + console.error('设置电源操作失败:', error) + message.error('设置电源操作失败') } } - const cancelPowerAction = () => { + // 启动60秒倒计时 + const startPowerCountdown = (data: any) => { + // 清除之前的计时器 if (powerCountdownTimer) { clearInterval(powerCountdownTimer) powerCountdownTimer = null } + + // 显示倒计时弹窗 + powerCountdownVisible.value = true + + // 设置倒计时数据,从60秒开始 + powerCountdownData.value = { + title: data.title || `${getPowerActionText(powerAction.value)}倒计时`, + message: data.message || `程序将在倒计时结束后执行 ${getPowerActionText(powerAction.value)} 操作`, + countdown: 60 + } + + // 启动每秒倒计时 + powerCountdownTimer = setInterval(() => { + if (powerCountdownData.value.countdown && powerCountdownData.value.countdown > 0) { + powerCountdownData.value.countdown-- + console.log('[Scheduler] 倒计时:', powerCountdownData.value.countdown) + + // 倒计时结束 + if (powerCountdownData.value.countdown <= 0) { + if (powerCountdownTimer) { + clearInterval(powerCountdownTimer) + powerCountdownTimer = null + } + powerCountdownVisible.value = false + console.log('[Scheduler] 倒计时结束,弹窗关闭') + } + } + }, 1000) + } + + // 移除自动执行电源操作,由后端完全控制 + // const executePowerAction = async () => { + // // 不再自己执行电源操作,完全由后端控制 + // } + + const cancelPowerAction = async () => { + // 清除倒计时器 + if (powerCountdownTimer) { + clearInterval(powerCountdownTimer) + powerCountdownTimer = null + } + + // 关闭倒计时弹窗 powerCountdownVisible.value = false - powerCountdown.value = 10 + + // 调用取消电源操作的API + try { + await Service.cancelPowerTaskApiDispatchCancelPowerPost() + message.success('已取消电源操作') + } catch (error) { + console.error('取消电源操作失败:', error) + message.error('取消电源操作失败') + } + // 注意:这里不重置 powerAction,保留用户选择 } - const checkAllTasksCompleted = () => { - const hasRunningTasks = schedulerTabs.value.some(tab => tab.status === '运行') - - if (!hasRunningTasks && powerAction.value !== PowerIn.signal.NO_ACTION) { - startPowerCountdown() - } - } + // 移除自动检查任务完成的逻辑,完全由后端控制 + // const checkAllTasksCompleted = () => { + // // 不再自己检查任务完成状态,完全由后端WebSocket消息控制 + // } // 消息弹窗操作 const sendMessageResponse = () => { @@ -637,16 +686,43 @@ export function useSchedulerLogic() { // 订阅TaskManager消息 subscribeToTaskManager() console.log('[Scheduler] 已订阅TaskManager消息') + + // 订阅Main消息(用于接收全局消息如电源操作倒计时) + subscribeToMainMessages() + console.log('[Scheduler] 已订阅Main消息') + } + + // 订阅Main消息,处理全局消息 + const subscribeToMainMessages = () => { + ws.subscribe('Main', { + onMessage: (message) => handleMainMessage(message) + }) + } + + const handleMainMessage = (wsMessage: any) => { + if (!wsMessage || typeof wsMessage !== 'object') return + + const { type, data } = wsMessage + console.log('[Scheduler] 收到Main消息:', { type, data }) + + if (type === 'Message' && data && data.type === 'Countdown') { + // 收到倒计时消息,启动前端60秒倒计时 + console.log('[Scheduler] 收到倒计时消息,启动60秒倒计时:', data) + startPowerCountdown(data) + } } // 清理函数 const cleanup = () => { + // 清理倒计时器 if (powerCountdownTimer) { clearInterval(powerCountdownTimer) + powerCountdownTimer = null } - // 取消订阅TaskManager + // 取消订阅TaskManager和Main ws.unsubscribe('TaskManager') + ws.unsubscribe('Main') schedulerTabs.value.forEach(tab => { if (tab.websocketId) { @@ -666,7 +742,7 @@ export function useSchedulerLogic() { taskOptions, powerAction, powerCountdownVisible, - powerCountdown, + powerCountdownData, messageModalVisible, currentMessage, messageResponse, From d3382104269324a312daef771e284f723a9360e3 Mon Sep 17 00:00:00 2001 From: AoXuan Date: Wed, 24 Sep 2025 00:47:34 +0800 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20=E6=B7=BB=E5=8A=A0=E7=94=B5?= =?UTF-8?q?=E6=BA=90=E6=93=8D=E4=BD=9C=E6=98=BE=E7=A4=BA=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E6=94=AF=E6=8C=81=E4=BB=8E=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E6=8E=A5=E6=94=B6=E7=8A=B6=E6=80=81=E5=B9=B6=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=9C=AC=E5=9C=B0=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/scheduler/useSchedulerLogic.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/frontend/src/views/scheduler/useSchedulerLogic.ts b/frontend/src/views/scheduler/useSchedulerLogic.ts index fa546a4..b330152 100644 --- a/frontend/src/views/scheduler/useSchedulerLogic.ts +++ b/frontend/src/views/scheduler/useSchedulerLogic.ts @@ -570,6 +570,41 @@ export function useSchedulerLogic() { } } + // 更新电源操作显示(不发送API请求) + const updatePowerActionDisplay = (powerSign: string) => { + // 将后端的PowerSign转换为前端的PowerIn.signal枚举值 + let newPowerAction: PowerIn.signal = PowerIn.signal.NO_ACTION + + switch (powerSign) { + case 'NoAction': + newPowerAction = PowerIn.signal.NO_ACTION + break + case 'KillSelf': + newPowerAction = PowerIn.signal.KILL_SELF + break + case 'Sleep': + newPowerAction = PowerIn.signal.SLEEP + break + case 'Hibernate': + newPowerAction = PowerIn.signal.HIBERNATE + break + case 'Shutdown': + newPowerAction = PowerIn.signal.SHUTDOWN + break + case 'ShutdownForce': + newPowerAction = PowerIn.signal.SHUTDOWN_FORCE + break + default: + console.warn('[Scheduler] 未知的PowerSign值:', powerSign) + return + } + + // 更新显示状态和本地存储,但不发送API请求 + powerAction.value = newPowerAction + savePowerActionToStorage(newPowerAction) + console.log('[Scheduler] 电源操作显示已更新为:', newPowerAction) + } + // 启动60秒倒计时 const startPowerCountdown = (data: any) => { // 清除之前的计时器 @@ -709,6 +744,10 @@ export function useSchedulerLogic() { // 收到倒计时消息,启动前端60秒倒计时 console.log('[Scheduler] 收到倒计时消息,启动60秒倒计时:', data) startPowerCountdown(data) + } else if (type === 'Update' && data && data.PowerSign !== undefined) { + // 收到电源操作更新消息,更新显示 + console.log('[Scheduler] 收到电源操作更新消息:', data.PowerSign) + updatePowerActionDisplay(data.PowerSign) } }