2 changed files with 195 additions and 169 deletions
@ -0,0 +1,183 @@ |
|||
import { computed, reactive } from 'vue' |
|||
import request from '@/utils/http' |
|||
import * as urls from '@/config/urls' |
|||
|
|||
const COMMAND_STATUS = { |
|||
sent: { name: '已下发', step: 2, result: '等待应答', terminal: false }, |
|||
acked: { name: '已确认', step: 3, result: '执行成功', terminal: true, ok: true }, |
|||
timeout: { name: '已超时', step: 3, result: '已超时', terminal: true, ok: false }, |
|||
terminal: { name: '执行失败', step: 3, result: '执行失败', terminal: true, ok: false }, |
|||
} |
|||
|
|||
export function useCommandProgress({ getRecord, ui, devices, reload }) { |
|||
const commandProgress = reactive({ |
|||
open: false, |
|||
title: '', |
|||
deviceName: '', |
|||
commandId: '', |
|||
status: '', // sent/acked/timeout/terminal
|
|||
result: '', |
|||
step: 1, // 1 确认下发 2 已下发 3 终态
|
|||
error: '', |
|||
buttonTitle: '', |
|||
}) |
|||
|
|||
let commandPollTimer = null |
|||
let commandAutoCloseTimer = null |
|||
let commandPollFails = 0 |
|||
let commandPollInFlight = false |
|||
let commandProgressEpoch = 0 |
|||
|
|||
function buttonStatusText(title) { |
|||
if (commandProgress.buttonTitle !== title || !commandProgress.status) return '状态:待执行' |
|||
const meta = COMMAND_STATUS[commandProgress.status] |
|||
return meta ? `状态:${meta.name}` : '状态:待执行' |
|||
} |
|||
|
|||
const commandProgressTerminalLabel = computed(() => { |
|||
if (commandProgress.status === 'acked') return '已确认' |
|||
return COMMAND_STATUS[commandProgress.status]?.name || '执行结果' |
|||
}) |
|||
|
|||
function clearCommandTimers() { |
|||
if (commandPollTimer) { |
|||
clearInterval(commandPollTimer) |
|||
commandPollTimer = null |
|||
} |
|||
if (commandAutoCloseTimer) { |
|||
clearTimeout(commandAutoCloseTimer) |
|||
commandAutoCloseTimer = null |
|||
} |
|||
} |
|||
|
|||
function closeCommandProgress() { |
|||
clearCommandTimers() |
|||
commandProgressEpoch += 1 |
|||
commandProgress.open = false |
|||
} |
|||
|
|||
function applyCommandStatus(cmd) { |
|||
const status = cmd?.status || 'sent' |
|||
const meta = COMMAND_STATUS[status] || COMMAND_STATUS.sent |
|||
commandProgress.status = status |
|||
commandProgress.step = meta.step |
|||
commandProgress.result = cmd?.ackResultCode || meta.result |
|||
commandProgress.error = '' |
|||
return meta |
|||
} |
|||
|
|||
function scheduleAutoClose() { |
|||
clearTimeout(commandAutoCloseTimer) |
|||
commandAutoCloseTimer = setTimeout(() => { |
|||
closeCommandProgress() |
|||
}, 5000) |
|||
} |
|||
|
|||
async function pollCommandOnce() { |
|||
if (!commandProgress.commandId || commandPollInFlight) return |
|||
const epoch = commandProgressEpoch |
|||
commandPollInFlight = true |
|||
try { |
|||
const cmd = await request.get(urls.COMMAND(commandProgress.commandId)) |
|||
if (epoch !== commandProgressEpoch) return |
|||
commandPollFails = 0 |
|||
const meta = applyCommandStatus(cmd) |
|||
if (meta.terminal) { |
|||
clearInterval(commandPollTimer) |
|||
commandPollTimer = null |
|||
scheduleAutoClose() |
|||
} |
|||
} catch (e) { |
|||
if (epoch !== commandProgressEpoch) return |
|||
commandPollFails += 1 |
|||
const status = e?.status ?? e?.response?.status |
|||
if (status === 403 || status === 404 || commandPollFails >= 3) { |
|||
clearInterval(commandPollTimer) |
|||
commandPollTimer = null |
|||
commandProgress.error = |
|||
status === 403 |
|||
? '无权限查看进度,请稍后在操作记录确认' |
|||
: status === 404 |
|||
? '未找到指令记录' |
|||
: (e.message || '进度查询失败') |
|||
scheduleAutoClose() |
|||
} |
|||
} finally { |
|||
commandPollInFlight = false |
|||
} |
|||
} |
|||
|
|||
function startCommandProgress(cmd, title, deviceName) { |
|||
clearCommandTimers() |
|||
commandPollInFlight = false |
|||
commandProgressEpoch += 1 |
|||
commandPollFails = 0 |
|||
commandProgress.open = true |
|||
commandProgress.title = title |
|||
commandProgress.deviceName = deviceName || '--' |
|||
commandProgress.commandId = String(cmd?.id || '') |
|||
commandProgress.buttonTitle = title |
|||
commandProgress.step = 1 |
|||
commandProgress.error = '' |
|||
applyCommandStatus(cmd || { status: 'sent' }) |
|||
|
|||
if (!commandProgress.commandId) { |
|||
commandProgress.error = '未返回指令 ID,无法跟踪进度' |
|||
scheduleAutoClose() |
|||
return |
|||
} |
|||
|
|||
const ttl = Number(cmd?.ttlMs) > 0 ? Number(cmd.ttlMs) : 30000 |
|||
const deadline = Date.now() + Math.max(ttl, 30000) + 5000 |
|||
|
|||
commandPollTimer = setInterval(async () => { |
|||
if (Date.now() > deadline) { |
|||
clearInterval(commandPollTimer) |
|||
commandPollTimer = null |
|||
commandProgressEpoch += 1 |
|||
if (!COMMAND_STATUS[commandProgress.status]?.terminal) { |
|||
commandProgress.error = '等待超时,请稍后在操作记录查看' |
|||
commandProgress.step = 3 |
|||
} |
|||
scheduleAutoClose() |
|||
return |
|||
} |
|||
await pollCommandOnce() |
|||
}, 1500) |
|||
|
|||
// 立即查一次,避免干等 1.5s
|
|||
pollCommandOnce() |
|||
} |
|||
|
|||
async function runCommand(title, desc) { |
|||
const rec = getRecord() |
|||
if (!rec) return |
|||
if (!rec.online) { |
|||
ui.toast('设备当前离线,无法下发指令') |
|||
return |
|||
} |
|||
if (rec.kind === 'drone' && rec.bindingState !== 'bound') { |
|||
ui.toast('无人机未绑定机巢,无法下发指令') |
|||
return |
|||
} |
|||
const ok = await ui.confirm(title, desc) |
|||
if (!ok) return |
|||
try { |
|||
const cmd = await devices.sendCommand(rec, title) |
|||
ui.toast(`${title}指令已下发`) |
|||
startCommandProgress(cmd, title, rec.name) |
|||
await reload() |
|||
} catch (e) { |
|||
ui.toast(e.message || '指令下发失败') |
|||
} |
|||
} |
|||
|
|||
return { |
|||
commandProgress, |
|||
commandProgressTerminalLabel, |
|||
buttonStatusText, |
|||
startCommandProgress, |
|||
closeCommandProgress, |
|||
runCommand, |
|||
} |
|||
} |
|||
Loading…
Reference in new issue