first commit
This commit is contained in:
484
src/views/dashboard1/pages/IntegratedCenterPage.vue
Normal file
484
src/views/dashboard1/pages/IntegratedCenterPage.vue
Normal file
@@ -0,0 +1,484 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from "vue";
|
||||
import monitorVideo from "../assets/循环背景动画.mp4";
|
||||
|
||||
interface DeviceItem {
|
||||
id: string;
|
||||
name: string;
|
||||
status: "空闲" | "离线" | "充电中";
|
||||
location: string;
|
||||
battery: string;
|
||||
task: string;
|
||||
linearSpeed: string;
|
||||
angularSpeed: string;
|
||||
}
|
||||
|
||||
interface ProjectItem {
|
||||
id: number;
|
||||
name: string;
|
||||
devices: DeviceItem[];
|
||||
}
|
||||
|
||||
interface NavPoint {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
interface NavScenario {
|
||||
routePath: string;
|
||||
cloudPoints: NavPoint[];
|
||||
checkPoints: Array<{ x: number; y: number; label: string }>;
|
||||
}
|
||||
|
||||
interface AlertItem {
|
||||
id: string;
|
||||
title: string;
|
||||
level: "高" | "中" | "低";
|
||||
location: string;
|
||||
time: string;
|
||||
}
|
||||
|
||||
const projectDevices: ProjectItem[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: "西江隧道",
|
||||
devices: [
|
||||
{
|
||||
id: "ROBOT-001",
|
||||
name: "巡检机器人 R1",
|
||||
status: "空闲",
|
||||
location: "东洞口至 K1+240",
|
||||
battery: "82%",
|
||||
task: "日常巡检",
|
||||
linearSpeed: "0.42m/s",
|
||||
angularSpeed: "0.08rad/s",
|
||||
},
|
||||
{
|
||||
id: "ROBOT-002",
|
||||
name: "巡检机器人 R2",
|
||||
status: "空闲",
|
||||
location: "K1+240 至 K2+120",
|
||||
battery: "76%",
|
||||
task: "设备复核",
|
||||
linearSpeed: "0.38m/s",
|
||||
angularSpeed: "0.06rad/s",
|
||||
},
|
||||
{
|
||||
id: "ROBOT-003",
|
||||
name: "巡检机器人 R3",
|
||||
status: "充电中",
|
||||
location: "中控区",
|
||||
battery: "48%",
|
||||
task: "回库维护",
|
||||
linearSpeed: "0.00m/s",
|
||||
angularSpeed: "0.00rad/s",
|
||||
},
|
||||
{
|
||||
id: "ROBOT-004",
|
||||
name: "巡检机器人 R4",
|
||||
status: "空闲",
|
||||
location: "K2+120 至西洞口",
|
||||
battery: "91%",
|
||||
task: "夜间待命",
|
||||
linearSpeed: "0.31m/s",
|
||||
angularSpeed: "0.04rad/s",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const recentAlerts: AlertItem[] = [
|
||||
{
|
||||
id: "AL-001",
|
||||
title: "线缆破损",
|
||||
level: "高",
|
||||
location: "西江隧道 K1+240",
|
||||
time: "10:24:18",
|
||||
},
|
||||
{
|
||||
id: "AL-002",
|
||||
title: "局部高温",
|
||||
level: "中",
|
||||
location: "西江隧道 泵房区",
|
||||
time: "10:18:42",
|
||||
},
|
||||
{
|
||||
id: "AL-003",
|
||||
title: "墙体裂缝",
|
||||
level: "低",
|
||||
location: "西江隧道 K1+860",
|
||||
time: "09:56:11",
|
||||
},
|
||||
];
|
||||
|
||||
const alertTabs = ["全部", "线缆破损", "局部高温", "墙体裂缝"] as const;
|
||||
const activeAlertTab = ref<(typeof alertTabs)[number]>("全部");
|
||||
const activeCameraMode = ref<"white" | "infrared">("white");
|
||||
const controlMode = ref<"人工接管" | "自主巡检">("人工接管");
|
||||
const targetLiftHeight = ref("1200");
|
||||
const liftRealtimeStatus = ref("待命");
|
||||
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 },
|
||||
],
|
||||
checkPoints: [
|
||||
{ x: 104, y: 280, label: "起点" },
|
||||
{ x: 268, y: 224, label: "高温检测点" },
|
||||
{ x: 480, y: 256, label: "泵房区" },
|
||||
{ x: 674, y: 214, label: "终点" },
|
||||
],
|
||||
};
|
||||
|
||||
const currentProject = computed(() => projectDevices[0]);
|
||||
const currentDevices = computed(() => currentProject.value.devices);
|
||||
const currentRobot = computed(() => currentDevices.value[0]);
|
||||
const currentScenario = computed(() => navScenario);
|
||||
|
||||
const filteredRecentAlerts = computed(() => {
|
||||
if (activeAlertTab.value === "全部") {
|
||||
return recentAlerts;
|
||||
}
|
||||
|
||||
return recentAlerts.filter((alert) => alert.title === activeAlertTab.value);
|
||||
});
|
||||
|
||||
function switchAlertTab(tab: (typeof alertTabs)[number]) {
|
||||
activeAlertTab.value = tab;
|
||||
}
|
||||
|
||||
function switchCameraMode(mode: "white" | "infrared") {
|
||||
activeCameraMode.value = mode;
|
||||
}
|
||||
|
||||
function toggleControlMode() {
|
||||
controlMode.value =
|
||||
controlMode.value === "人工接管" ? "自主巡检" : "人工接管";
|
||||
}
|
||||
|
||||
function runToTargetHeight() {
|
||||
const normalizedHeight = targetLiftHeight.value.replace(/[^\d]/g, "");
|
||||
|
||||
targetLiftHeight.value = normalizedHeight || "0";
|
||||
liftRealtimeStatus.value = "运行中";
|
||||
liftRealtimeHeight.value = targetLiftHeight.value;
|
||||
}
|
||||
|
||||
function getStatusClass(status: DeviceItem["status"]) {
|
||||
const classMap: Record<DeviceItem["status"], string> = {
|
||||
空闲: "status-idle",
|
||||
离线: "status-offline",
|
||||
充电中: "status-charging",
|
||||
};
|
||||
|
||||
return classMap[status];
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="center">
|
||||
<div class="left">
|
||||
<div class="card-1 device-card">
|
||||
<div class="title">设备列表</div>
|
||||
<div class="device-panel">
|
||||
<div class="project-name">{{ currentProject.name }}</div>
|
||||
<div class="device-list">
|
||||
<div v-for="device in currentDevices" :key="device.id" class="device-item">
|
||||
<div class="device-main">
|
||||
<div class="device-name">{{ device.name }}</div>
|
||||
<div :class="['device-status', getStatusClass(device.status)]">
|
||||
{{ device.status }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="device-meta">
|
||||
<span>{{ device.id }}</span>
|
||||
<span>{{ device.location }}</span>
|
||||
<span>{{ device.battery }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-2 alert-card">
|
||||
<div class="title">近期告警</div>
|
||||
<div class="alert-panel">
|
||||
<div class="alert-tabs">
|
||||
<button
|
||||
v-for="tab in alertTabs"
|
||||
:key="tab"
|
||||
:class="['alert-tab', { active: activeAlertTab === tab }]"
|
||||
type="button"
|
||||
@click="switchAlertTab(tab)"
|
||||
>
|
||||
{{ tab }}
|
||||
</button>
|
||||
</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>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="alert-empty">当前筛选下暂无告警</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="middle">
|
||||
<div class="nav-map-panel">
|
||||
<div class="nav-map-stage">
|
||||
<svg class="nav-map-svg" viewBox="0 0 760 420" preserveAspectRatio="xMidYMid meet">
|
||||
<g class="grid-layer">
|
||||
<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">
|
||||
<circle
|
||||
v-for="(point, index) in currentScenario.cloudPoints"
|
||||
:key="`point-${index}`"
|
||||
:cx="point.x"
|
||||
:cy="point.y"
|
||||
:r="index % 3 === 0 ? 3 : 2"
|
||||
/>
|
||||
</g>
|
||||
<path class="route-glow" :d="currentScenario.routePath" />
|
||||
<path class="route-line" :d="currentScenario.routePath" />
|
||||
<g class="checkpoint-layer">
|
||||
<g
|
||||
v-for="point in currentScenario.checkPoints"
|
||||
:key="point.label"
|
||||
class="checkpoint"
|
||||
:transform="`translate(${point.x}, ${point.y})`"
|
||||
>
|
||||
<circle r="7"></circle>
|
||||
<text x="12" y="4">{{ point.label }}</text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="robot-layer">
|
||||
<circle class="robot-shadow" r="18">
|
||||
<animateMotion dur="18s" repeatCount="indefinite" :path="currentScenario.routePath" />
|
||||
</circle>
|
||||
<circle class="robot-body" r="10">
|
||||
<animateMotion dur="18s" repeatCount="indefinite" rotate="auto" :path="currentScenario.routePath" />
|
||||
</circle>
|
||||
<path class="robot-head" d="M -3 -9 L 8 0 L -3 9 Z">
|
||||
<animateMotion dur="18s" repeatCount="indefinite" rotate="auto" :path="currentScenario.routePath" />
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<div class="card-1 video-card">
|
||||
<div class="card-header">
|
||||
<div class="title">视频监控</div>
|
||||
<div class="camera-switch header-switch">
|
||||
<button
|
||||
:class="['camera-btn', { active: activeCameraMode === 'white' }]"
|
||||
type="button"
|
||||
@click="switchCameraMode('white')"
|
||||
>
|
||||
白光
|
||||
</button>
|
||||
<button
|
||||
:class="['camera-btn', { active: activeCameraMode === 'infrared' }]"
|
||||
type="button"
|
||||
@click="switchCameraMode('infrared')"
|
||||
>
|
||||
红外
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="video-panel">
|
||||
<div class="video-screen">
|
||||
<video
|
||||
:class="['monitor-video', { infrared: activeCameraMode === 'infrared' }]"
|
||||
autoplay
|
||||
loop
|
||||
muted
|
||||
playsinline
|
||||
:src="monitorVideo"
|
||||
></video>
|
||||
<div class="scan-line"></div>
|
||||
<div class="video-overlay top-left">实时视频流</div>
|
||||
<div class="video-overlay top-right">
|
||||
{{ activeCameraMode === "white" ? "白光镜头" : "红外镜头" }}
|
||||
</div>
|
||||
<div class="video-overlay bottom-left">{{ currentRobot.id }}</div>
|
||||
<div class="video-overlay bottom-right">{{ currentRobot.location }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-2 control-card">
|
||||
<div class="card-header">
|
||||
<div class="title">远程控制</div>
|
||||
<button class="mode-toggle-btn" type="button" @click="toggleControlMode">
|
||||
<span :class="['mode-toggle-chip', { active: controlMode === '人工接管' }]">
|
||||
人工接管
|
||||
</span>
|
||||
<span :class="['mode-toggle-chip', { active: controlMode === '自主巡检' }]">
|
||||
自主巡检
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control-panel">
|
||||
<div class="control-status">
|
||||
<div class="status-item">
|
||||
<span class="status-label">控制对象</span>
|
||||
<span class="status-value">{{ currentRobot.id }}</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">当前模式</span>
|
||||
<span class="status-value">{{ controlMode }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-main">
|
||||
<div class="control-top-row">
|
||||
<div class="vehicle-section">
|
||||
<div class="section-title control-block-title">底盘驱动</div>
|
||||
<div class="vehicle-pad">
|
||||
<button class="drive-btn drive-up" type="button">
|
||||
<span class="drive-arrow">↑</span>
|
||||
<span class="drive-label">前进</span>
|
||||
</button>
|
||||
<button class="drive-btn drive-left" type="button">
|
||||
<span class="drive-arrow">←</span>
|
||||
<span class="drive-label">左转</span>
|
||||
</button>
|
||||
<div class="drive-core" aria-hidden="true"></div>
|
||||
<button class="drive-btn drive-right" type="button">
|
||||
<span class="drive-arrow">→</span>
|
||||
<span class="drive-label">右转</span>
|
||||
</button>
|
||||
<button class="drive-btn drive-down" type="button">
|
||||
<span class="drive-arrow">↓</span>
|
||||
<span class="drive-label">后退</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-section gimbal-section">
|
||||
<div class="section-title">云台控制</div>
|
||||
<div class="vehicle-pad gimbal-wheel">
|
||||
<button class="drive-btn drive-up" type="button">
|
||||
<span class="drive-arrow">↑</span>
|
||||
<span class="drive-label">上</span>
|
||||
</button>
|
||||
<button class="drive-btn drive-left" type="button">
|
||||
<span class="drive-arrow">←</span>
|
||||
<span class="drive-label">左</span>
|
||||
</button>
|
||||
<div class="drive-core" aria-hidden="true"></div>
|
||||
<button class="drive-btn drive-right" type="button">
|
||||
<span class="drive-arrow">→</span>
|
||||
<span class="drive-label">右</span>
|
||||
</button>
|
||||
<button class="drive-btn drive-down" type="button">
|
||||
<span class="drive-arrow">↓</span>
|
||||
<span class="drive-label">下</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-section lift-section">
|
||||
<div class="section-title">升降柱控制</div>
|
||||
<div class="lift-control-panel">
|
||||
<label class="lift-input-group">
|
||||
<span class="lift-input-label">目标高度</span>
|
||||
<div class="lift-input-shell">
|
||||
<input
|
||||
v-model="targetLiftHeight"
|
||||
class="lift-input"
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
/>
|
||||
<span class="lift-unit">mm</span>
|
||||
</div>
|
||||
</label>
|
||||
<button class="lift-submit-btn" type="button" @click="runToTargetHeight">
|
||||
运行到指定高度
|
||||
</button>
|
||||
<div class="lift-status-panel">
|
||||
<div class="lift-status-item">
|
||||
<span class="lift-status-label">实时状态</span>
|
||||
<span class="lift-status-value">{{ liftRealtimeStatus }}</span>
|
||||
</div>
|
||||
<div class="lift-status-item">
|
||||
<span class="lift-status-label">当前高度</span>
|
||||
<span class="lift-status-value">{{ liftRealtimeHeight }} mm</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user