|
|
|
@ -60,6 +60,100 @@ |
|
|
|
最后在线 {{ dock.updated }}<span>网络中断</span> |
|
|
|
</div> |
|
|
|
</article> |
|
|
|
|
|
|
|
<div class="modal-backdrop" :class="{ open: dockStatusOpen }" :aria-hidden="!dockStatusOpen" @click.self="closeDockStatus"> |
|
|
|
<div class="modal dock-status-dialog" role="dialog" aria-modal="true" aria-labelledby="dockStatusTitle"> |
|
|
|
<header class="dock-status-header"> |
|
|
|
<div> |
|
|
|
<span>机巢运行状态</span> |
|
|
|
<h3 id="dockStatusTitle">{{ dockStatusTitle }}</h3> |
|
|
|
</div> |
|
|
|
<button class="icon-button" type="button" title="关闭详情" @click="closeDockStatus"> |
|
|
|
<svg><use href="#i-x" /></svg> |
|
|
|
</button> |
|
|
|
</header> |
|
|
|
<div class="dock-status-body"> |
|
|
|
<div class="dock-status-grid"> |
|
|
|
<div> |
|
|
|
<span>设备连接</span> |
|
|
|
<strong :class="{ 'green-text': dockStatusSummary.online }">{{ dockStatusSummary.connection }}</strong> |
|
|
|
<small>{{ dockStatusSummary.connectionHint }}</small> |
|
|
|
</div> |
|
|
|
<div> |
|
|
|
<span>无人机状态</span> |
|
|
|
<strong>{{ dockStatusSummary.drone }}</strong> |
|
|
|
<small>{{ dockStatusSummary.droneHint }}</small> |
|
|
|
</div> |
|
|
|
<div> |
|
|
|
<span>舱门状态</span> |
|
|
|
<strong>{{ dockStatusSummary.door }}</strong> |
|
|
|
<small>{{ dockStatusSummary.doorHint }}</small> |
|
|
|
</div> |
|
|
|
<div> |
|
|
|
<span>充电模块</span> |
|
|
|
<strong :class="{ 'green-text': chargeTone }">{{ dockStatusSummary.charge }}</strong> |
|
|
|
<small>{{ dockStatusSummary.chargeHint }}</small> |
|
|
|
</div> |
|
|
|
<div> |
|
|
|
<span>控制模式</span> |
|
|
|
<strong>{{ dockStatusSummary.mode }}</strong> |
|
|
|
<small>{{ dockStatusSummary.modeHint }}</small> |
|
|
|
</div> |
|
|
|
<div> |
|
|
|
<span>当前告警</span> |
|
|
|
<strong :class="{ 'green-text': !dockStatusSummary.alarmCount }">{{ dockStatusSummary.alarm }}</strong> |
|
|
|
<small>{{ dockStatusSummary.alarmHint }}</small> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<section class="dock-state-section"> |
|
|
|
<div class="dock-state-heading"> |
|
|
|
<h4>状态</h4> |
|
|
|
<span>PLC 实时反馈 · {{ selectedDock?.updated || record?.updated || '刚刚' }}</span> |
|
|
|
</div> |
|
|
|
<div class="dock-state-board"> |
|
|
|
<article> |
|
|
|
<h5>舱门状态</h5> |
|
|
|
<ul> |
|
|
|
<li :class="{ active: isClosed(doorValue('left')) }"><i></i>左顶门关</li> |
|
|
|
<li :class="{ active: doorValue('left') === 'open' }"><i></i>左顶门开</li> |
|
|
|
<li :class="{ active: isClosed(doorValue('right')) }"><i></i>右顶门关</li> |
|
|
|
<li :class="{ active: doorValue('right') === 'open' }"><i></i>右顶门开</li> |
|
|
|
</ul> |
|
|
|
</article> |
|
|
|
<article> |
|
|
|
<h5>杆件状态</h5> |
|
|
|
<ul> |
|
|
|
<li :class="{ active: isTight(centeringValue('leftRight')) }"><i></i>左右归中闭合</li> |
|
|
|
<li :class="{ active: centeringValue('leftRight') === 'loose' }"><i></i>左右归中打开</li> |
|
|
|
<li :class="{ active: isTight(centeringValue('frontBack')) }"><i></i>前后归中闭合</li> |
|
|
|
<li :class="{ active: centeringValue('frontBack') === 'loose' }"><i></i>前后归中打开</li> |
|
|
|
</ul> |
|
|
|
</article> |
|
|
|
<article> |
|
|
|
<h5>系统状态</h5> |
|
|
|
<ul> |
|
|
|
<li :class="{ active: isTrue(rt('emergencyStop')) }"><i :class="{ warning: isTrue(rt('emergencyStop')) }"></i>急停</li> |
|
|
|
<li :class="{ active: isAutoMode }"><i></i>自动模式</li> |
|
|
|
<li :class="{ active: !dockStatusSummary.alarmCount }"><i></i>无报警</li> |
|
|
|
<li :class="{ active: isTrue(rt('resetDone', 'resetComplete')) }"><i></i>复位完成</li> |
|
|
|
</ul> |
|
|
|
</article> |
|
|
|
<article> |
|
|
|
<h5>充电与在位</h5> |
|
|
|
<ul> |
|
|
|
<li :class="{ active: chargeTone }"><i></i>充电中</li> |
|
|
|
<li :class="{ active: String(rt('chargingState') || '').toLowerCase() === 'idle' }"><i></i>充电空闲</li> |
|
|
|
<li :class="{ active: isTrue(rt('dronePresent')) }"><i></i>无人机在位</li> |
|
|
|
<li :class="{ active: isTrue(rt('powerOnline', 'powerOn')) }"><i></i>电源在线</li> |
|
|
|
</ul> |
|
|
|
</article> |
|
|
|
</div> |
|
|
|
</section> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
</template> |
|
|
|
|
|
|
|
<div v-if="!filteredDocks.length" class="table-empty" style="height:120px;display:grid;place-items:center;color:var(--muted);font-size:10px">暂无匹配设备</div> |
|
|
|
@ -131,7 +225,7 @@ |
|
|
|
<section v-if="!isDrone" class="status-section"> |
|
|
|
<div class="section-title"> |
|
|
|
<h3>运行状态</h3> |
|
|
|
<button class="text-button" type="button" @click="goDetail">查看详情</button> |
|
|
|
<button class="text-button" type="button" @click="openDockStatus">查看详情</button> |
|
|
|
</div> |
|
|
|
<div class="metric-grid"> |
|
|
|
<div><svg><use href="#i-battery" /></svg><span>无人机电量</span><strong>{{ selectedDrone?.battery || '--' }}</strong></div> |
|
|
|
@ -279,6 +373,7 @@ const livePlayerRef = ref(null) |
|
|
|
const filter = ref('all') |
|
|
|
const search = ref('') |
|
|
|
const controlExpanded = ref(false) |
|
|
|
const dockStatusOpen = ref(false) |
|
|
|
const selectedDockIds = reactive(new Set()) |
|
|
|
|
|
|
|
let mapInstance = null |
|
|
|
@ -461,6 +556,7 @@ function teardownMap() { |
|
|
|
} |
|
|
|
|
|
|
|
onMounted(async () => { |
|
|
|
window.addEventListener('keydown', onGlobalKeydown) |
|
|
|
try { |
|
|
|
await devices.load() |
|
|
|
if (route.query.deviceId && devices.record(route.query.deviceId)) selectedId.value = String(route.query.deviceId) |
|
|
|
@ -473,6 +569,7 @@ onMounted(async () => { |
|
|
|
}) |
|
|
|
|
|
|
|
onUnmounted(() => { |
|
|
|
window.removeEventListener('keydown', onGlobalKeydown) |
|
|
|
closeLive() |
|
|
|
teardownMap() |
|
|
|
}) |
|
|
|
@ -633,6 +730,18 @@ function isClosed(value) { |
|
|
|
return value === 'closed' || value === 'close' |
|
|
|
} |
|
|
|
|
|
|
|
function centeringValue(key) { |
|
|
|
return rtObj('centering')[key] |
|
|
|
} |
|
|
|
|
|
|
|
function isTight(value) { |
|
|
|
return value === 'tight' |
|
|
|
} |
|
|
|
|
|
|
|
function isTrue(value) { |
|
|
|
return value === true || value === 'true' || value === '1' || value === 1 |
|
|
|
} |
|
|
|
|
|
|
|
function chargeText(value) { |
|
|
|
const key = String(value || '').toLowerCase() |
|
|
|
if (key === 'charging') return '充电中' |
|
|
|
@ -654,6 +763,66 @@ const doorSummary = computed(() => { |
|
|
|
const dockControlMode = computed(() => record.value?.mode || displayValue(rt('controlMode'))) |
|
|
|
|
|
|
|
|
|
|
|
const isAutoMode = computed(() => { |
|
|
|
const mode = String(rt('controlMode') || record.value?.mode || '').toLowerCase() |
|
|
|
return mode === 'auto' || mode === 'automatic' || mode.includes('自动') |
|
|
|
}) |
|
|
|
|
|
|
|
const dockStatusTitle = computed(() => { |
|
|
|
if (!isDrone.value) return selectedAsset.value?.name || '机巢' |
|
|
|
return selectedDock.value?.name || selectedDroneParentName.value || '机巢' |
|
|
|
}) |
|
|
|
|
|
|
|
const dockStatusSummary = computed(() => { |
|
|
|
const dock = selectedDock.value |
|
|
|
const drone = selectedDrone.value |
|
|
|
const online = !!dock?.online |
|
|
|
const alarms = Array.isArray(dock?.alarms) ? dock.alarms : [] |
|
|
|
const alarmCount = alarms.length |
|
|
|
const left = doorValue('left') |
|
|
|
const right = doorValue('right') |
|
|
|
let doorHint = '门状态未知' |
|
|
|
if (isClosed(left) && isClosed(right)) doorHint = '左门、右门反馈正常' |
|
|
|
else if (left === 'open' || right === 'open') doorHint = '存在开启舱门' |
|
|
|
const droneLabel = drone |
|
|
|
? (drone.statusClass === 'mission' ? (drone.status || '任务中') : (drone.status || '在巢待命')) |
|
|
|
: '未绑定' |
|
|
|
const droneHint = drone |
|
|
|
? `${drone.name || drone.code || '--'}${drone.battery && drone.battery !== '--' ? ` · 电量 ${drone.battery}` : ''}` |
|
|
|
: '暂无绑定无人机' |
|
|
|
return { |
|
|
|
online, |
|
|
|
connection: online ? '在线' : (dock?.status || '离线'), |
|
|
|
connectionHint: online ? `心跳正常 · ${dock?.updated || '刚刚'}` : (dock?.updated ? `最后在线 ${dock.updated}` : '设备离线'), |
|
|
|
drone: droneLabel, |
|
|
|
droneHint, |
|
|
|
door: doorSummary.value, |
|
|
|
doorHint, |
|
|
|
charge: chargeText(rt('chargingState')), |
|
|
|
chargeHint: isTrue(rt('dronePresent')) ? '电池在位' : '在位状态未知', |
|
|
|
mode: dockControlMode.value, |
|
|
|
modeHint: `手自动切换:${dockControlMode.value}`, |
|
|
|
alarmCount, |
|
|
|
alarm: alarmCount ? `${alarmCount} 条` : '无告警', |
|
|
|
alarmHint: alarmCount ? alarms.slice(0, 2).join('、') : '急停未触发 · 报警总信号正常' |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
function openDockStatus() { |
|
|
|
if (!selectedId.value) return |
|
|
|
dockStatusOpen.value = true |
|
|
|
} |
|
|
|
|
|
|
|
function closeDockStatus() { |
|
|
|
dockStatusOpen.value = false |
|
|
|
} |
|
|
|
|
|
|
|
function onGlobalKeydown(event) { |
|
|
|
if (event.key === 'Escape' && dockStatusOpen.value) closeDockStatus() |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function reload() { |
|
|
|
try { |
|
|
|
await devices.load() |
|
|
|
|