refactor: 重构电源操作倒计时逻辑,改为使用全屏弹窗并由后端控制

This commit is contained in:
2025-09-24 00:25:13 +08:00
parent 53d91fb3f8
commit 96c022c2ec
3 changed files with 301 additions and 66 deletions

View File

@@ -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>

View File

@@ -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 // 最多保留日志条数

View File

@@ -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,