refactor: 重构电源操作倒计时逻辑,改为使用全屏弹窗并由后端控制
This commit is contained in:
@@ -112,29 +112,43 @@
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
<!-- 电源操作倒计时模态框 -->
|
||||
<a-modal
|
||||
v-model:open="powerCountdownVisible"
|
||||
title="电源操作确认"
|
||||
: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>
|
||||
<p>
|
||||
所有任务已完成,系统将在 <strong>{{ powerCountdown }}</strong> 秒后执行:<strong>{{
|
||||
getPowerActionText(powerAction)
|
||||
}}</strong>
|
||||
<!-- 电源操作倒计时全屏弹窗 -->
|
||||
<div v-if="powerCountdownVisible" class="power-countdown-overlay">
|
||||
<div class="power-countdown-container">
|
||||
<div class="countdown-content">
|
||||
<div class="warning-icon">⚠️</div>
|
||||
<h2 class="countdown-title">{{ powerCountdownData.title || `${getPowerActionText(powerAction)}倒计时` }}</h2>
|
||||
<p class="countdown-message">
|
||||
{{ powerCountdownData.message || `程序将在倒计时结束后执行 ${getPowerActionText(powerAction)} 操作` }}
|
||||
</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>
|
||||
</a-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -37,7 +37,7 @@ export const POWER_ACTION_TEXT: Record<PowerIn.signal, string> = {
|
||||
[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 // 最多保留日志条数
|
||||
|
||||
@@ -95,7 +95,12 @@ export function useSchedulerLogic() {
|
||||
// 电源操作 - 从本地存储加载或使用默认值
|
||||
const powerAction = ref<PowerIn.signal>(loadPowerActionFromStorage())
|
||||
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
|
||||
|
||||
// 消息弹窗
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user