修改完毕

This commit is contained in:
2026-06-29 15:36:19 +08:00
parent d8c0885c84
commit fe0d4304f8
7 changed files with 2262 additions and 868 deletions

View File

@@ -1,6 +1,10 @@
<script lang="ts" setup>
import { computed, ref } from "vue";
import monitorVideo from "../assets/循环背景动画.mp4";
import { recentAlertPhotoSources } from "../shared/alertPhotos";
const monitorVideo = "https://wuhuimg.oss-cn-shanghai.aliyuncs.com/video/output.mp4";
type DriveMode = "阿克曼" | "旋转" | "横移";
interface DeviceItem {
id: string;
@@ -26,7 +30,11 @@ interface NavPoint {
interface NavScenario {
routePath: string;
cloudPoints: NavPoint[];
lanePath: string;
upperEdgePath: string;
lowerEdgePath: string;
upperWallPoints: NavPoint[];
lowerWallPoints: NavPoint[];
checkPoints: Array<{ x: number; y: number; label: string }>;
}
@@ -34,8 +42,8 @@ interface AlertItem {
id: string;
title: string;
level: "高" | "中" | "低";
location: string;
time: string;
photo: string;
}
const projectDevices: ProjectItem[] = [
@@ -92,28 +100,35 @@ const recentAlerts: AlertItem[] = [
id: "AL-001",
title: "线缆破损",
level: "高",
location: "西江隧道 K1+240",
time: "10:24:18",
time: "2026-06-26 10:24:18",
photo: recentAlertPhotoSources["线缆破损"],
},
{
id: "AL-002",
title: "局部高温",
level: "中",
location: "西江隧道 泵房区",
time: "10:18:42",
time: "2026-06-26 10:18:42",
photo: recentAlertPhotoSources["局部高温"],
},
{
id: "AL-003",
title: "墙体裂缝",
level: "低",
location: "西江隧道 K1+860",
time: "09:56:11",
time: "2026-06-26 09:56:11",
photo: recentAlertPhotoSources["墙体裂缝"],
},
];
const alertTabs = ["全部", "线缆破损", "局部高温", "墙体裂缝"] as const;
const activeAlertTab = ref<(typeof alertTabs)[number]>("全部");
const activeCameraMode = ref<"white" | "infrared">("white");
const driveModeTabs: DriveMode[] = ["阿克曼", "旋转", "横移"];
const activeDriveMode = ref<DriveMode>("阿克曼");
const presetPointOptions = Array.from(
{ length: 10 },
(_, index) => `预置点${index + 1}`,
);
const selectedPresetPoint = ref("预置点1");
const controlMode = ref<"人工接管" | "自主巡检">("人工接管");
const targetLiftHeight = ref("1200");
const liftRealtimeStatus = ref("待命");
@@ -121,38 +136,73 @@ const liftRealtimeHeight = ref("1180");
const navScenario: NavScenario = {
routePath:
"M 72 310 C 118 282, 164 265, 212 248 S 312 205, 360 216 S 446 270, 504 248 S 612 188, 690 210",
cloudPoints: [
{ x: 52, y: 120 },
{ x: 86, y: 132 },
{ x: 128, y: 148 },
{ x: 164, y: 138 },
{ x: 204, y: 118 },
{ x: 252, y: 104 },
{ x: 304, y: 120 },
{ x: 346, y: 142 },
{ x: 392, y: 168 },
{ x: 442, y: 162 },
{ x: 494, y: 134 },
{ x: 548, y: 116 },
{ x: 602, y: 132 },
{ x: 660, y: 152 },
{ x: 94, y: 254 },
{ x: 150, y: 236 },
{ x: 214, y: 220 },
{ x: 278, y: 208 },
{ x: 338, y: 214 },
{ x: 400, y: 236 },
{ x: 468, y: 258 },
{ x: 530, y: 238 },
{ x: 592, y: 214 },
{ x: 656, y: 226 },
"M 106 318 L 154 306 L 208 292 L 256 278 L 306 264 L 358 258 L 414 258 L 468 248 L 522 241 L 578 234 L 638 226",
lanePath:
"M 82 270 L 136 258 L 190 244 L 244 230 L 296 216 L 350 208 L 408 208 L 464 198 L 518 190 L 576 182 L 642 174 L 656 188 L 650 278 L 586 286 L 528 294 L 472 302 L 414 312 L 358 312 L 304 320 L 252 334 L 198 348 L 142 360 L 88 370 L 74 356 Z",
upperEdgePath:
"M 82 270 L 136 258 L 190 244 L 244 230 L 296 216 L 350 208 L 408 208 L 464 198 L 518 190 L 576 182 L 642 174",
lowerEdgePath:
"M 88 370 L 142 360 L 198 348 L 252 334 L 304 320 L 358 312 L 414 312 L 472 302 L 528 294 L 586 286 L 650 278",
upperWallPoints: [
{ x: 64, y: 164 },
{ x: 84, y: 150 },
{ x: 104, y: 142 },
{ x: 126, y: 136 },
{ x: 148, y: 132 },
{ x: 172, y: 126 },
{ x: 196, y: 122 },
{ x: 224, y: 118 },
{ x: 252, y: 116 },
{ x: 280, y: 112 },
{ x: 312, y: 110 },
{ x: 344, y: 108 },
{ x: 378, y: 108 },
{ x: 412, y: 110 },
{ x: 448, y: 112 },
{ x: 484, y: 114 },
{ x: 520, y: 116 },
{ x: 556, y: 120 },
{ x: 590, y: 126 },
{ x: 622, y: 134 },
{ x: 650, y: 144 },
{ x: 676, y: 158 },
{ x: 116, y: 170 },
{ x: 214, y: 156 },
{ x: 326, y: 146 },
{ x: 446, y: 148 },
{ x: 566, y: 160 },
{ x: 638, y: 172 },
],
lowerWallPoints: [
{ x: 72, y: 380 },
{ x: 96, y: 392 },
{ x: 122, y: 404 },
{ x: 150, y: 410 },
{ x: 180, y: 416 },
{ x: 214, y: 420 },
{ x: 248, y: 422 },
{ x: 284, y: 424 },
{ x: 322, y: 426 },
{ x: 360, y: 426 },
{ x: 400, y: 424 },
{ x: 440, y: 420 },
{ x: 480, y: 414 },
{ x: 520, y: 408 },
{ x: 558, y: 400 },
{ x: 594, y: 392 },
{ x: 628, y: 384 },
{ x: 660, y: 374 },
{ x: 700, y: 366 },
{ x: 126, y: 376 },
{ x: 238, y: 392 },
{ x: 354, y: 396 },
{ x: 474, y: 388 },
{ x: 590, y: 372 },
],
checkPoints: [
{ x: 104, y: 280, label: "起点" },
{ x: 268, y: 224, label: "高温检测点" },
{ x: 480, y: 256, label: "泵房区" },
{ x: 674, y: 214, label: "终点" },
{ x: 106, y: 318, label: "起点" },
{ x: 358, y: 258, label: "途经点" },
{ x: 638, y: 226, label: "终点" },
],
};
@@ -177,11 +227,19 @@ function switchCameraMode(mode: "white" | "infrared") {
activeCameraMode.value = mode;
}
function switchDriveMode(mode: DriveMode) {
activeDriveMode.value = mode;
}
function toggleControlMode() {
controlMode.value =
controlMode.value === "人工接管" ? "自主巡检" : "人工接管";
}
function jumpToPresetPoint() {
selectedPresetPoint.value = selectedPresetPoint.value;
}
function runToTargetHeight() {
const normalizedHeight = targetLiftHeight.value.replace(/[^\d]/g, "");
@@ -218,7 +276,6 @@ function getStatusClass(status: DeviceItem["status"]) {
</div>
<div class="device-meta">
<span>{{ device.id }}</span>
<span>{{ device.location }}</span>
<span>{{ device.battery }}</span>
</div>
</div>
@@ -242,14 +299,24 @@ function getStatusClass(status: DeviceItem["status"]) {
</div>
<template v-if="filteredRecentAlerts.length">
<div v-for="alert in filteredRecentAlerts" :key="alert.id" class="alert-item">
<div class="alert-main">
<div class="alert-title">{{ alert.title }}</div>
<div class="alert-level">{{ alert.level }}</div>
</div>
<div class="alert-meta">
<span>{{ alert.id }}</span>
<span>{{ alert.location }}</span>
<span>{{ alert.time }}</span>
<div class="alert-content">
<img class="alert-thumbnail" :src="alert.photo" :alt="alert.title" />
<div class="alert-info">
<div class="alert-summary">
<div class="alert-field">
<div class="alert-label">告警类型</div>
<div class="alert-title">{{ alert.title }}</div>
</div>
<div class="alert-field alert-field-right">
<div class="alert-label">告警等级</div>
<div :class="['alert-level', `level-${alert.level}`]">{{ alert.level }}</div>
</div>
</div>
<div class="alert-time-row">
<span class="alert-label">告警时间</span>
<span class="alert-time">{{ alert.time }}</span>
</div>
</div>
</div>
</div>
</template>
@@ -266,13 +333,25 @@ function getStatusClass(status: DeviceItem["status"]) {
<line v-for="line in 7" :key="`h-${line}`" :x1="40" :y1="line * 52" :x2="720" :y2="line * 52" />
<line v-for="line in 12" :key="`v-${line}`" :x1="line * 56" :y1="40" :x2="line * 56" :y2="380" />
</g>
<g class="cloud-layer">
<path class="corridor-lane" :d="currentScenario.lanePath" />
<path class="corridor-edge" :d="currentScenario.upperEdgePath" />
<path class="corridor-edge" :d="currentScenario.lowerEdgePath" />
<g class="wall-layer wall-upper">
<circle
v-for="(point, index) in currentScenario.cloudPoints"
:key="`point-${index}`"
v-for="(point, index) in currentScenario.upperWallPoints"
:key="`upper-wall-${index}`"
:cx="point.x"
:cy="point.y"
:r="index % 3 === 0 ? 3 : 2"
:r="index % 4 === 0 ? 3.4 : 2.4"
/>
</g>
<g class="wall-layer wall-lower">
<circle
v-for="(point, index) in currentScenario.lowerWallPoints"
:key="`lower-wall-${index}`"
:cx="point.x"
:cy="point.y"
:r="index % 5 === 0 ? 3.2 : 2.3"
/>
</g>
<path class="route-glow" :d="currentScenario.routePath" />
@@ -300,35 +379,14 @@ function getStatusClass(status: DeviceItem["status"]) {
</path>
</g>
</svg>
<div class="nav-map-overlay top-left">激光雷达点云</div>
<div class="nav-map-overlay top-right">路径跟踪</div>
<div class="nav-map-overlay top-left">隧道点云走廊</div>
<div class="nav-map-overlay top-right">窄通道路径跟踪</div>
<div class="nav-map-overlay bottom-left">
机器人编号{{ currentRobot.id }} / {{ currentRobot.task }}
</div>
<div class="nav-map-overlay bottom-right">
线速度 {{ currentRobot.linearSpeed }} / 角速度 {{ currentRobot.angularSpeed }}
</div>
</div>
<div class="nav-map-footer">
<div class="footer-item">
<span class="footer-label">当前位置</span>
<span class="footer-value">{{ currentRobot.location }}</span>
</div>
<div class="footer-item">
<span class="footer-label">电量</span>
<span class="footer-value">{{ currentRobot.battery }}</span>
</div>
<div class="footer-item">
<span class="footer-label">任务状态</span>
<span class="footer-value">{{ currentRobot.task }}</span>
</div>
<div class="footer-item">
<span class="footer-label">实时线速度</span>
<span class="footer-value">{{ currentRobot.linearSpeed }}</span>
</div>
<div class="footer-item">
<span class="footer-label">实时角速度</span>
<span class="footer-value">{{ currentRobot.angularSpeed }}</span>
电量 {{ currentRobot.battery }} / 线速度 {{ currentRobot.linearSpeed }} / 角速度
{{ currentRobot.angularSpeed }}
</div>
</div>
</div>
@@ -371,7 +429,6 @@ function getStatusClass(status: DeviceItem["status"]) {
{{ activeCameraMode === "white" ? "白光镜头" : "红外镜头" }}
</div>
<div class="video-overlay bottom-left">{{ currentRobot.id }}</div>
<div class="video-overlay bottom-right">{{ currentRobot.location }}</div>
</div>
</div>
</div>
@@ -403,6 +460,17 @@ function getStatusClass(status: DeviceItem["status"]) {
<div class="control-top-row">
<div class="vehicle-section">
<div class="section-title control-block-title">底盘驱动</div>
<div class="drive-mode-tabs">
<button
v-for="mode in driveModeTabs"
:key="mode"
:class="['drive-mode-tab', { active: activeDriveMode === mode }]"
type="button"
@click="switchDriveMode(mode)"
>
{{ mode }}
</button>
</div>
<div class="vehicle-pad">
<button class="drive-btn drive-up" type="button">
<span class="drive-arrow"></span>
@@ -425,6 +493,22 @@ function getStatusClass(status: DeviceItem["status"]) {
</div>
<div class="control-section gimbal-section">
<div class="section-title">云台控制</div>
<div class="gimbal-preset-panel">
<label class="gimbal-preset-group">
<select v-model="selectedPresetPoint" class="gimbal-preset-select">
<option
v-for="preset in presetPointOptions"
:key="preset"
:value="preset"
>
{{ preset }}
</option>
</select>
</label>
<button class="gimbal-preset-btn" type="button" @click="jumpToPresetPoint">
跳转
</button>
</div>
<div class="vehicle-pad gimbal-wheel">
<button class="drive-btn drive-up" type="button">
<span class="drive-arrow"></span>
@@ -482,3 +566,4 @@ function getStatusClass(status: DeviceItem["status"]) {
</div>
</div>
</template>