|
|
@ -295,7 +295,7 @@ |
|
|
</template> |
|
|
</template> |
|
|
|
|
|
|
|
|
<script setup> |
|
|
<script setup> |
|
|
import { computed, onMounted, ref } from 'vue' |
|
|
|
|
|
|
|
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue' |
|
|
import { useRoute, useRouter } from 'vue-router' |
|
|
import { useRoute, useRouter } from 'vue-router' |
|
|
import { useDeviceStore } from '@/stores/modules/devicesStore' |
|
|
import { useDeviceStore } from '@/stores/modules/devicesStore' |
|
|
import { useUiStore } from '@/stores/modules/uiStore' |
|
|
import { useUiStore } from '@/stores/modules/uiStore' |
|
|
@ -345,6 +345,69 @@ const logColumns = [ |
|
|
const record = computed(() => devices.record(route.params.id)) |
|
|
const record = computed(() => devices.record(route.params.id)) |
|
|
const asset = computed(() => devices.asset(route.params.id)) |
|
|
const asset = computed(() => devices.asset(route.params.id)) |
|
|
const isDrone = computed(() => record.value?.kind === 'drone') |
|
|
const isDrone = computed(() => record.value?.kind === 'drone') |
|
|
|
|
|
|
|
|
|
|
|
const DETAIL_POLL_MS = 5000 |
|
|
|
|
|
let detailPollTimer = null |
|
|
|
|
|
let detailPollInflight = null |
|
|
|
|
|
let detailPollGen = 0 |
|
|
|
|
|
let detailDisposed = false |
|
|
|
|
|
|
|
|
|
|
|
function clearDetailPollTimer() { |
|
|
|
|
|
window.clearInterval(detailPollTimer) |
|
|
|
|
|
detailPollTimer = null |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async function pollCurrentDetail() { |
|
|
|
|
|
if (detailDisposed) return |
|
|
|
|
|
if (detailPollInflight) return |
|
|
|
|
|
const id = String(route.params.id || '') |
|
|
|
|
|
const rec = devices.record(id) |
|
|
|
|
|
if (!id || !rec) return |
|
|
|
|
|
const gen = detailPollGen |
|
|
|
|
|
const url = rec.kind === 'drone' ? urls.DRONE(id) : urls.DOCK(id) |
|
|
|
|
|
detailPollInflight = request.get(url) |
|
|
|
|
|
try { |
|
|
|
|
|
const detail = await detailPollInflight |
|
|
|
|
|
if (detailDisposed || gen !== detailPollGen) return |
|
|
|
|
|
if (String(route.params.id) !== id) return |
|
|
|
|
|
if (rec.kind === 'drone') devices.applyDroneDetail(id, detail || {}) |
|
|
|
|
|
else devices.applyDockDetail(id, detail || {}) |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
if (detailDisposed || gen !== detailPollGen) return |
|
|
|
|
|
console.warn('[device-detail] poll failed', e) |
|
|
|
|
|
} finally { |
|
|
|
|
|
detailPollInflight = null |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function startDetailPolling() { |
|
|
|
|
|
clearDetailPollTimer() |
|
|
|
|
|
if (detailDisposed || document.visibilityState === 'hidden') return |
|
|
|
|
|
detailPollTimer = window.setInterval(() => { |
|
|
|
|
|
void pollCurrentDetail() |
|
|
|
|
|
}, DETAIL_POLL_MS) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function stopDetailPolling() { |
|
|
|
|
|
clearDetailPollTimer() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function restartDetailPolling() { |
|
|
|
|
|
detailPollGen += 1 |
|
|
|
|
|
stopDetailPolling() |
|
|
|
|
|
void pollCurrentDetail() |
|
|
|
|
|
startDetailPolling() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function onDetailVisibility() { |
|
|
|
|
|
if (detailDisposed) return |
|
|
|
|
|
if (document.visibilityState === 'hidden') { |
|
|
|
|
|
stopDetailPolling() |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
void pollCurrentDetail() |
|
|
|
|
|
startDetailPolling() |
|
|
|
|
|
} |
|
|
const boundDrone = computed(() => (record.value?.kind === 'dock' ? devices.drones.find((d) => d.dockId === record.value.dockId) : null)) |
|
|
const boundDrone = computed(() => (record.value?.kind === 'dock' ? devices.drones.find((d) => d.dockId === record.value.dockId) : null)) |
|
|
const boundDock = computed(() => (record.value?.kind === 'drone' ? devices.docks.find((d) => d.dockId === record.value.dockId) : null)) |
|
|
const boundDock = computed(() => (record.value?.kind === 'drone' ? devices.docks.find((d) => d.dockId === record.value.dockId) : null)) |
|
|
const latestLog = computed(() => logs.value[0] || null) |
|
|
const latestLog = computed(() => logs.value[0] || null) |
|
|
@ -486,6 +549,8 @@ function chargeText(value) { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
onMounted(async () => { |
|
|
onMounted(async () => { |
|
|
|
|
|
detailDisposed = false |
|
|
|
|
|
document.addEventListener('visibilitychange', onDetailVisibility) |
|
|
try { |
|
|
try { |
|
|
await devices.load() |
|
|
await devices.load() |
|
|
if (!isAdmin.value) { |
|
|
if (!isAdmin.value) { |
|
|
@ -494,8 +559,24 @@ onMounted(async () => { |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
ui.toast(e.message || '加载设备失败') |
|
|
ui.toast(e.message || '加载设备失败') |
|
|
} |
|
|
} |
|
|
|
|
|
startDetailPolling() |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => { |
|
|
|
|
|
detailDisposed = true |
|
|
|
|
|
detailPollGen += 1 |
|
|
|
|
|
stopDetailPolling() |
|
|
|
|
|
document.removeEventListener('visibilitychange', onDetailVisibility) |
|
|
}) |
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
watch( |
|
|
|
|
|
() => String(route.params.id || ''), |
|
|
|
|
|
(next, prev) => { |
|
|
|
|
|
if (!next || next === prev) return |
|
|
|
|
|
restartDetailPolling() |
|
|
|
|
|
} |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const COMMAND_NAMES = { |
|
|
const COMMAND_NAMES = { |
|
|
'dock.open': '打开舱门', |
|
|
'dock.open': '打开舱门', |
|
|
|