refactor: 重构电源操作倒计时逻辑,改为使用全屏弹窗并由后端控制
This commit is contained in:
@@ -112,29 +112,43 @@
|
|||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<!-- 电源操作倒计时模态框 -->
|
<!-- 电源操作倒计时全屏弹窗 -->
|
||||||
<a-modal
|
<div v-if="powerCountdownVisible" class="power-countdown-overlay">
|
||||||
v-model:open="powerCountdownVisible"
|
<div class="power-countdown-container">
|
||||||
title="电源操作确认"
|
<div class="countdown-content">
|
||||||
:closable="false"
|
|
||||||
:maskClosable="false"
|
|
||||||
@cancel="cancelPowerAction"
|
|
||||||
>
|
|
||||||
<template #footer>
|
|
||||||
<a-button @click="cancelPowerAction">取消</a-button>
|
|
||||||
</template>
|
|
||||||
<div class="power-countdown">
|
|
||||||
<div class="warning-icon">⚠️</div>
|
<div class="warning-icon">⚠️</div>
|
||||||
<div>
|
<h2 class="countdown-title">{{ powerCountdownData.title || `${getPowerActionText(powerAction)}倒计时` }}</h2>
|
||||||
<p>
|
<p class="countdown-message">
|
||||||
所有任务已完成,系统将在 <strong>{{ powerCountdown }}</strong> 秒后执行:<strong>{{
|
{{ powerCountdownData.message || `程序将在倒计时结束后执行 ${getPowerActionText(powerAction)} 操作` }}
|
||||||
getPowerActionText(powerAction)
|
|
||||||
}}</strong>
|
|
||||||
</p>
|
</p>
|
||||||
<a-progress :percent="(10 - powerCountdown) * 10" :show-info="false" />
|
<div class="countdown-timer" v-if="powerCountdownData.countdown !== undefined">
|
||||||
|
<span class="countdown-number">{{ powerCountdownData.countdown }}</span>
|
||||||
|
<span class="countdown-unit">秒</span>
|
||||||
|
</div>
|
||||||
|
<div class="countdown-timer" v-else>
|
||||||
|
<span class="countdown-text">等待后端倒计时...</span>
|
||||||
|
</div>
|
||||||
|
<a-progress
|
||||||
|
v-if="powerCountdownData.countdown !== undefined"
|
||||||
|
:percent="Math.max(0, Math.min(100, (60 - powerCountdownData.countdown) / 60 * 100))"
|
||||||
|
:show-info="false"
|
||||||
|
:stroke-color="(powerCountdownData.countdown || 0) <= 10 ? '#ff4d4f' : '#1890ff'"
|
||||||
|
:stroke-width="8"
|
||||||
|
class="countdown-progress"
|
||||||
|
/>
|
||||||
|
<div class="countdown-actions">
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
@click="cancelPowerAction"
|
||||||
|
class="cancel-button"
|
||||||
|
>
|
||||||
|
取消操作
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -161,7 +175,7 @@ const {
|
|||||||
taskOptions,
|
taskOptions,
|
||||||
powerAction,
|
powerAction,
|
||||||
powerCountdownVisible,
|
powerCountdownVisible,
|
||||||
powerCountdown,
|
powerCountdownData,
|
||||||
messageModalVisible,
|
messageModalVisible,
|
||||||
currentMessage,
|
currentMessage,
|
||||||
messageResponse,
|
messageResponse,
|
||||||
@@ -333,6 +347,128 @@ onUnmounted(() => {
|
|||||||
overflow: hidden;
|
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) {
|
@media (max-width: 768px) {
|
||||||
.scheduler-main {
|
.scheduler-main {
|
||||||
@@ -363,5 +499,28 @@ onUnmounted(() => {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -37,7 +37,7 @@ export const POWER_ACTION_TEXT: Record<PowerIn.signal, string> = {
|
|||||||
[PowerIn.signal.SHUTDOWN_FORCE]: '强制关机',
|
[PowerIn.signal.SHUTDOWN_FORCE]: '强制关机',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getPowerActionText = (action: PowerIn.signal) => POWER_ACTION_TEXT[action] || '无动<EFBFBD><EFBFBD>'
|
export const getPowerActionText = (action: PowerIn.signal) => POWER_ACTION_TEXT[action] || '无动作'
|
||||||
|
|
||||||
// 日志相关
|
// 日志相关
|
||||||
export const LOG_MAX_LENGTH = 2000 // 最多保留日志条数
|
export const LOG_MAX_LENGTH = 2000 // 最多保留日志条数
|
||||||
|
|||||||
@@ -95,7 +95,12 @@ export function useSchedulerLogic() {
|
|||||||
// 电源操作 - 从本地存储加载或使用默认值
|
// 电源操作 - 从本地存储加载或使用默认值
|
||||||
const powerAction = ref<PowerIn.signal>(loadPowerActionFromStorage())
|
const powerAction = ref<PowerIn.signal>(loadPowerActionFromStorage())
|
||||||
const powerCountdownVisible = ref(false)
|
const powerCountdownVisible = ref(false)
|
||||||
const powerCountdown = ref(10)
|
const powerCountdownData = ref<{
|
||||||
|
title?: string
|
||||||
|
message?: string
|
||||||
|
countdown?: number
|
||||||
|
}>({})
|
||||||
|
// 前端自己的60秒倒计时
|
||||||
let powerCountdownTimer: ReturnType<typeof setInterval> | null = null
|
let powerCountdownTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
|
||||||
// 消息弹窗
|
// 消息弹窗
|
||||||
@@ -333,6 +338,13 @@ export function useSchedulerLogic() {
|
|||||||
|
|
||||||
console.log('[Scheduler] 收到WebSocket消息:', { id, type, data, tabId: tab.websocketId })
|
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) {
|
if (id && id !== tab.websocketId) {
|
||||||
console.log('[Scheduler] 消息ID不匹配,忽略消息:', { messageId: id, tabId: tab.websocketId })
|
console.log('[Scheduler] 消息ID不匹配,忽略消息:', { messageId: id, tabId: tab.websocketId })
|
||||||
@@ -461,6 +473,14 @@ export function useSchedulerLogic() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleMessageDialog = (tab: SchedulerTab, data: any) => {
|
const handleMessageDialog = (tab: SchedulerTab, data: any) => {
|
||||||
|
// 处理倒计时消息
|
||||||
|
if (data.type === 'Countdown') {
|
||||||
|
console.log('[Scheduler] 收到倒计时消息,启动60秒倒计时:', data)
|
||||||
|
startPowerCountdown(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理普通消息对话框
|
||||||
if (data.title && data.content) {
|
if (data.title && data.content) {
|
||||||
currentMessage.value = {
|
currentMessage.value = {
|
||||||
title: data.title,
|
title: data.title,
|
||||||
@@ -498,18 +518,16 @@ export function useSchedulerLogic() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message.success('任务完成')
|
message.success('任务完成')
|
||||||
checkAllTasksCompleted()
|
|
||||||
saveTabsToStorage(schedulerTabs.value)
|
saveTabsToStorage(schedulerTabs.value)
|
||||||
|
|
||||||
// 触发Vue的响应式更新
|
// 触发Vue的响应式更新
|
||||||
schedulerTabs.value = [...schedulerTabs.value]
|
schedulerTabs.value = [...schedulerTabs.value]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data && data.power && data.power !== 'NoAction') {
|
// 移除自动处理电源信号的逻辑,电源操作完全由后端WebSocket的倒计时消息控制
|
||||||
powerAction.value = data.power as PowerIn.signal
|
// if (data && data.power && data.power !== 'NoAction') {
|
||||||
savePowerActionToStorage(powerAction.value)
|
// // 不再自己处理电源信号
|
||||||
startPowerCountdown()
|
// }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onLogScroll = (tab: SchedulerTab) => {
|
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
|
powerAction.value = value
|
||||||
savePowerActionToStorage(value)
|
savePowerActionToStorage(value)
|
||||||
|
|
||||||
|
// 调用API设置电源操作
|
||||||
|
try {
|
||||||
|
await Service.setPowerApiDispatchSetPowerPost({ signal: value })
|
||||||
|
console.log('[Scheduler] 电源操作设置成功:', value)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('设置电源操作失败:', error)
|
||||||
|
message.error('设置电源操作失败')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const startPowerCountdown = () => {
|
// 启动60秒倒计时
|
||||||
if (powerAction.value === PowerIn.signal.NO_ACTION) return
|
const startPowerCountdown = (data: any) => {
|
||||||
|
// 清除之前的计时器
|
||||||
|
if (powerCountdownTimer) {
|
||||||
|
clearInterval(powerCountdownTimer)
|
||||||
|
powerCountdownTimer = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示倒计时弹窗
|
||||||
powerCountdownVisible.value = true
|
powerCountdownVisible.value = true
|
||||||
powerCountdown.value = 10
|
|
||||||
|
|
||||||
|
// 设置倒计时数据,从60秒开始
|
||||||
|
powerCountdownData.value = {
|
||||||
|
title: data.title || `${getPowerActionText(powerAction.value)}倒计时`,
|
||||||
|
message: data.message || `程序将在倒计时结束后执行 ${getPowerActionText(powerAction.value)} 操作`,
|
||||||
|
countdown: 60
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动每秒倒计时
|
||||||
powerCountdownTimer = setInterval(() => {
|
powerCountdownTimer = setInterval(() => {
|
||||||
powerCountdown.value--
|
if (powerCountdownData.value.countdown && powerCountdownData.value.countdown > 0) {
|
||||||
if (powerCountdown.value <= 0) {
|
powerCountdownData.value.countdown--
|
||||||
|
console.log('[Scheduler] 倒计时:', powerCountdownData.value.countdown)
|
||||||
|
|
||||||
|
// 倒计时结束
|
||||||
|
if (powerCountdownData.value.countdown <= 0) {
|
||||||
if (powerCountdownTimer) {
|
if (powerCountdownTimer) {
|
||||||
clearInterval(powerCountdownTimer)
|
clearInterval(powerCountdownTimer)
|
||||||
powerCountdownTimer = null
|
powerCountdownTimer = null
|
||||||
}
|
}
|
||||||
powerCountdownVisible.value = false
|
powerCountdownVisible.value = false
|
||||||
executePowerAction()
|
console.log('[Scheduler] 倒计时结束,弹窗关闭')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
const executePowerAction = async () => {
|
// 移除自动执行电源操作,由后端完全控制
|
||||||
try {
|
// const executePowerAction = async () => {
|
||||||
await Service.powerTaskApiDispatchPowerPost({ signal: powerAction.value })
|
// // 不再自己执行电源操作,完全由后端控制
|
||||||
message.success(`${getPowerActionText(powerAction.value)}命令已发送`)
|
// }
|
||||||
} catch (error) {
|
|
||||||
console.error('执行电源操作失败:', error)
|
|
||||||
message.error('执行电源操作失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelPowerAction = () => {
|
const cancelPowerAction = async () => {
|
||||||
|
// 清除倒计时器
|
||||||
if (powerCountdownTimer) {
|
if (powerCountdownTimer) {
|
||||||
clearInterval(powerCountdownTimer)
|
clearInterval(powerCountdownTimer)
|
||||||
powerCountdownTimer = null
|
powerCountdownTimer = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关闭倒计时弹窗
|
||||||
powerCountdownVisible.value = false
|
powerCountdownVisible.value = false
|
||||||
powerCountdown.value = 10
|
|
||||||
|
// 调用取消电源操作的API
|
||||||
|
try {
|
||||||
|
await Service.cancelPowerTaskApiDispatchCancelPowerPost()
|
||||||
|
message.success('已取消电源操作')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('取消电源操作失败:', error)
|
||||||
|
message.error('取消电源操作失败')
|
||||||
|
}
|
||||||
|
|
||||||
// 注意:这里不重置 powerAction,保留用户选择
|
// 注意:这里不重置 powerAction,保留用户选择
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkAllTasksCompleted = () => {
|
// 移除自动检查任务完成的逻辑,完全由后端控制
|
||||||
const hasRunningTasks = schedulerTabs.value.some(tab => tab.status === '运行')
|
// const checkAllTasksCompleted = () => {
|
||||||
|
// // 不再自己检查任务完成状态,完全由后端WebSocket消息控制
|
||||||
if (!hasRunningTasks && powerAction.value !== PowerIn.signal.NO_ACTION) {
|
// }
|
||||||
startPowerCountdown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 消息弹窗操作
|
// 消息弹窗操作
|
||||||
const sendMessageResponse = () => {
|
const sendMessageResponse = () => {
|
||||||
@@ -637,16 +686,43 @@ export function useSchedulerLogic() {
|
|||||||
// 订阅TaskManager消息
|
// 订阅TaskManager消息
|
||||||
subscribeToTaskManager()
|
subscribeToTaskManager()
|
||||||
console.log('[Scheduler] 已订阅TaskManager消息')
|
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 = () => {
|
const cleanup = () => {
|
||||||
|
// 清理倒计时器
|
||||||
if (powerCountdownTimer) {
|
if (powerCountdownTimer) {
|
||||||
clearInterval(powerCountdownTimer)
|
clearInterval(powerCountdownTimer)
|
||||||
|
powerCountdownTimer = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消订阅TaskManager
|
// 取消订阅TaskManager和Main
|
||||||
ws.unsubscribe('TaskManager')
|
ws.unsubscribe('TaskManager')
|
||||||
|
ws.unsubscribe('Main')
|
||||||
|
|
||||||
schedulerTabs.value.forEach(tab => {
|
schedulerTabs.value.forEach(tab => {
|
||||||
if (tab.websocketId) {
|
if (tab.websocketId) {
|
||||||
@@ -666,7 +742,7 @@ export function useSchedulerLogic() {
|
|||||||
taskOptions,
|
taskOptions,
|
||||||
powerAction,
|
powerAction,
|
||||||
powerCountdownVisible,
|
powerCountdownVisible,
|
||||||
powerCountdown,
|
powerCountdownData,
|
||||||
messageModalVisible,
|
messageModalVisible,
|
||||||
currentMessage,
|
currentMessage,
|
||||||
messageResponse,
|
messageResponse,
|
||||||
|
|||||||
Reference in New Issue
Block a user