You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

152 lines
8.0 KiB

<template>
<section class="control-page">
<header class="control-page-header">
<h1>远程控制</h1>
<span>对机巢与无人机下发远程指令</span>
</header>
<div class="control-workspace">
<aside class="control-device-list">
<div class="control-list-heading">
<div><h2>设备列表</h2><span>选择要控制的设备</span></div>
<button class="icon-button" @click="ui.toast('刷新设备')"><svg><use href="#i-refresh" /></svg></button>
</div>
<div class="control-device-items">
<button
v-for="d in controlDevices"
:key="d.id"
class="control-device-item"
:class="{ active: selectedId === d.id }"
@click="selectedId = d.id"
>
<span class="control-device-icon"><svg><use :href="d.kind === 'drone' ? '#i-plane' : '#i-home'" /></svg></span>
<div><strong>{{ d.name }}</strong><small>{{ d.code }} · {{ d.location || d.binding }}</small></div>
<em class="status" :class="d.statusClass">{{ d.status }}</em>
</button>
</div>
</aside>
<main class="control-main">
<template v-if="selected">
<header class="control-selected-header">
<div>
<h2>{{ selected.name }}</h2>
<p>{{ selected.kind === 'drone' ? '巡检无人机' : '机巢设备' }} · {{ selected.code }}</p>
</div>
<em class="status" :class="selected.statusClass">{{ selected.status }}</em>
</header>
<div class="control-status-band">
<template v-if="selected.kind === 'drone'">
<div><span>剩余电量</span><strong>{{ num(selected.battery) }}%</strong></div>
<div><span>飞行模式</span><strong>{{ selected.mode }}</strong></div>
<div><span>定位状态</span><strong>{{ selected.gps }}</strong></div>
<div><span>当前任务</span><strong>{{ selected.mission }}</strong></div>
<div><span>更新时间</span><strong>{{ selected.updated }}</strong></div>
</template>
<template v-else>
<div><span>绑定状态</span><strong>{{ selected.bindingState === 'bound' ? '已绑定' : '未绑定' }}</strong></div>
<div><span>绑定设备</span><strong>{{ selected.binding }}</strong></div>
<div><span>工作模式</span><strong>{{ selected.mode }}</strong></div>
<div><span>软件版本</span><strong>{{ selected.version }}</strong></div>
<div><span>通信协议</span><strong>{{ selected.protocol }}</strong></div>
</template>
</div>
<section class="control-alert-section">
<div class="control-alert-content" :class="selected.statusClass">
<span class="control-alert-icon" :class="{ ok: selected.statusClass === 'online' }"><svg><use href="#i-alert" /></svg></span>
<div>
<strong>{{ alertTitle }}</strong>
<small>{{ alertDesc }}</small>
</div>
<button class="plain-command" @click="ui.toast('查看详情')">详情</button>
</div>
</section>
<section class="control-section">
<div class="data-section-title"><h3 style="margin:0;font-size:12px">控制指令</h3><span>下发前将进行安全检查</span></div>
<div class="control-command-grid" v-if="selected.kind === 'drone'">
<button class="control-command primary" @click="run('起飞', '无人机将立即起飞进入待命状态。')"><svg><use href="#i-plane" /></svg><span><strong>起飞</strong><small>开始任务</small></span></button>
<button class="control-command landing" @click="run('降落', '无人机将就地降落并停止任务。')"><svg><use href="#i-plane" /></svg><span><strong>降落</strong><small>结束任务</small></span></button>
<button class="control-command" @click="run('一键返航', '无人机将返回绑定机巢。')"><b>R</b><span><strong>一键返航</strong><small>自动回巢</small></span></button>
<button class="control-command" @click="run('悬停', '无人机将在当前位置悬停。')"><b>H</b><span><strong>悬停</strong><small>保持位置</small></span></button>
<button class="control-command danger" @click="run('紧急停止', '立即中止飞行,无人机原地悬停待命。')"><b>E</b><span><strong>紧急停止</strong><small>中止任务</small></span></button>
</div>
<div class="control-command-grid" v-else>
<button class="control-command primary" @click="run('一键起飞', '无人机将从机巢起飞并进入待命状态。')"><svg><use href="#i-plane" /></svg><span><strong>一键起飞</strong><small>自动开舱</small></span></button>
<button class="control-command" @click="run('打开舱门', '将打开机巢舱门。')"><b>01</b><span><strong>打开舱门</strong><small>开合机构</small></span></button>
<button class="control-command" @click="run('关闭舱门', '将关闭机巢舱门。')"><b>02</b><span><strong>关闭舱门</strong><small>开合机构</small></span></button>
<button class="control-command" @click="run('机巢复位', '机巢将执行复位自检。')"><b>03</b><span><strong>机巢复位</strong><small>状态复位</small></span></button>
<button class="control-command" @click="run('设备自检', '对机巢各子系统执行自检。')"><b>04</b><span><strong>设备自检</strong><small>健康诊断</small></span></button>
</div>
</section>
<section class="control-section control-note">
<svg><use href="#i-shield" /></svg>
<span><strong>安全提示</strong><small>所有远程指令均会经过设备在线校验与指令互斥检查,确认通过后才会下发执行。</small></span>
</section>
</template>
<div v-else class="table-empty" style="height:100%;display:grid;place-items:center;color:var(--muted);font-size:12px">请在左侧选择设备</div>
</main>
</div>
</section>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { useDeviceStore } from '../store/devices'
import { useUiStore } from '../store/ui'
const route = useRoute()
const devices = useDeviceStore()
const ui = useUiStore()
const controlDevices = computed(() => [...devices.drones, ...devices.docks])
const selectedId = ref(route.params.id || null)
const selected = computed(() => (selectedId.value ? devices.record(selectedId.value) : null))
onMounted(async () => {
try {
await devices.load()
if (!selectedId.value && devices.docks.length) selectedId.value = devices.docks[0].id
} catch (e) {
ui.toast(e.message || '加载设备失败')
}
})
const alertTitle = computed(() => {
if (!selected.value) return ''
if (selected.value.statusClass === 'offline') return '设备离线,无法下发指令'
if (selected.value.statusClass === 'alarm') return '设备存在未处理告警'
if (selected.value.statusClass === 'mission') return '设备正在执行任务'
return '设备状态正常,可下发指令'
})
const alertDesc = computed(() => {
if (!selected.value) return ''
if (selected.value.statusClass === 'offline') return '请检查通信链路与供电状态'
if (selected.value.statusClass === 'alarm') return '建议先处理告警再执行控制操作'
if (selected.value.statusClass === 'mission') return '部分指令将在当前任务结束后生效'
return '所有子系统运行正常'
})
function num(v) {
if (v == null) return '--'
return String(v).replace('%', '')
}
async function run(title, desc) {
const rec = selected.value
if (!rec) return
const ok = await ui.confirm(title, desc)
if (!ok) return
try {
await devices.sendCommand(rec, title)
ui.toast(`${title}指令已下发`)
} catch (e) {
ui.toast(e.message || '指令下发失败')
}
}
</script>