重构项目中的"时间银行"模块,统一更名为"互助中心",涉及前端路由、组件、文档及多处文本替换。新增互助中心页面功能,包括: 1. 通证兑换服务弹窗 2. 互助任务列表展示 3. 区块链存证交互流程 同时优化护理记录页面,增加筛选、导出功能,完善监管端仪表盘交互
597 lines
28 KiB
Vue
597 lines
28 KiB
Vue
<template>
|
||
<div class="h-full grid grid-cols-12 gap-0">
|
||
<!-- Left: Vision Stream & AI Detection -->
|
||
<div class="col-span-8 bg-black relative border-r border-gray-800">
|
||
<!-- Live Stream Overlay -->
|
||
<div class="absolute top-4 left-4 z-10 flex space-x-2">
|
||
<div class="px-2 py-1 bg-red-600/80 text-white text-xs font-bold rounded animate-pulse">录制中</div>
|
||
<div class="px-2 py-1 bg-gray-800/80 text-green-400 text-xs font-mono border border-green-500/30">
|
||
帧率: {{ fps }} | 延迟: {{ latency }}ms
|
||
</div>
|
||
<div class="px-2 py-1 bg-blue-600/80 text-white text-xs font-bold rounded">
|
||
当前场景: {{ scenes[currentScene].name }}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Main Visual Area (Simulated Pose Estimation) -->
|
||
<div class="w-full h-full flex items-center justify-center relative overflow-hidden group transition-colors duration-1000"
|
||
:class="scenes[currentScene].color">
|
||
<!-- Scene Selector -->
|
||
<div class="absolute top-16 left-4 z-10 flex flex-col space-y-2">
|
||
<button v-for="(scene, key) in scenes" :key="key"
|
||
@click="currentScene = key"
|
||
class="px-3 py-1.5 text-xs border rounded-sm backdrop-blur-md transition-all duration-300 flex items-center space-x-2"
|
||
:class="currentScene === key ? 'bg-green-500 text-black border-green-500 shadow-[0_0_10px_rgba(34,197,94,0.5)]' : 'bg-black/50 text-green-500/50 border-green-500/20 hover:border-green-500/50'">
|
||
<span>{{ scene.icon }}</span>
|
||
<span>{{ scene.name }}</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Manual Control Toggle -->
|
||
<div class="absolute top-16 right-4 z-10">
|
||
<button @click="toggleManualMode"
|
||
class="px-4 py-2 text-xs font-bold border rounded-lg backdrop-blur-md transition-all flex items-center space-x-2"
|
||
:class="isManualMode ? 'bg-orange-500 text-white border-orange-500 animate-pulse' : 'bg-black/50 text-white/50 border-white/20 hover:border-white/50'">
|
||
<span>🎮</span>
|
||
<span>{{ isManualMode ? '人工接管中' : '自动巡航模式' }}</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Manual Control Pad -->
|
||
<div v-if="isManualMode" class="absolute bottom-8 left-1/2 -translate-x-1/2 z-20 flex flex-col items-center space-y-2 animate-fade-in-up">
|
||
<button class="w-12 h-12 bg-white/10 hover:bg-white/30 backdrop-blur border border-white/20 rounded-lg flex items-center justify-center text-white text-xl active:bg-orange-500 active:scale-95 transition-all">
|
||
▲
|
||
</button>
|
||
<div class="flex space-x-4">
|
||
<button class="w-12 h-12 bg-white/10 hover:bg-white/30 backdrop-blur border border-white/20 rounded-lg flex items-center justify-center text-white text-xl active:bg-orange-500 active:scale-95 transition-all">
|
||
◀
|
||
</button>
|
||
<button class="w-12 h-12 bg-red-500/80 hover:bg-red-500 backdrop-blur border border-red-400 rounded-full flex items-center justify-center text-white text-xl active:scale-95 transition-all shadow-[0_0_15px_rgba(239,68,68,0.5)]">
|
||
🛑
|
||
</button>
|
||
<button class="w-12 h-12 bg-white/10 hover:bg-white/30 backdrop-blur border border-white/20 rounded-lg flex items-center justify-center text-white text-xl active:bg-orange-500 active:scale-95 transition-all">
|
||
▶
|
||
</button>
|
||
</div>
|
||
<button class="w-12 h-12 bg-white/10 hover:bg-white/30 backdrop-blur border border-white/20 rounded-lg flex items-center justify-center text-white text-xl active:bg-orange-500 active:scale-95 transition-all">
|
||
▼
|
||
</button>
|
||
</div>
|
||
|
||
<!-- SVG Filters for Edge Detection Effect -->
|
||
<svg class="absolute w-0 h-0">
|
||
<filter id="edge-detection">
|
||
<feConvolveMatrix kernelMatrix="-1 -1 -1 -1 8 -1 -1 -1 -1" />
|
||
</filter>
|
||
</svg>
|
||
|
||
<!-- Grid Background -->
|
||
<div class="absolute inset-0 opacity-10"
|
||
style="background-image: linear-gradient(rgba(0, 255, 0, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(0, 255, 0, 0.1) 1px, transparent 1px); background-size: 80px 80px;">
|
||
</div>
|
||
|
||
<!-- Scene Elements (Simplified Props) -->
|
||
<div v-for="(el, i) in scenes[currentScene].elements" :key="i"
|
||
class="absolute border border-white/10 bg-white/5 rounded-lg backdrop-blur-sm group/el transition-all duration-700 hover:bg-white/10"
|
||
:style="{ left: el.x + '%', top: el.y + '%', width: (120 * el.scale) + 'px', height: (80 * el.scale) + 'px', transform: 'perspective(500px) rotateX(15deg)' }">
|
||
<div class="absolute inset-0 flex flex-col items-center justify-center font-mono italic">
|
||
<span class="text-2xl mb-1 opacity-50 group-hover/el:opacity-100 transition-opacity">{{ el.icon }}</span>
|
||
<span class="text-[10px] text-white/40 group-hover/el:text-white/80">{{ el.type }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Target Tracking Simulation (Multiple Boxes) -->
|
||
<div v-for="target in targets" :key="target.id"
|
||
class="absolute border border-green-500/30 transition-all duration-1000"
|
||
:style="{ left: target.x + '%', top: target.y + '%', width: '120px', height: '180px', transform: 'translate(-50%, -50%)', opacity: target.active ? 0.3 : 0.1 }">
|
||
<div class="absolute -top-4 left-0 text-[8px] font-mono text-green-500/50">{{ target.id }}</div>
|
||
<div class="absolute top-0 left-0 w-2 h-2 border-t border-l border-green-500"></div>
|
||
<div class="absolute top-0 right-0 w-2 h-2 border-t border-r border-green-500"></div>
|
||
<div class="absolute bottom-0 left-0 w-2 h-2 border-b border-l border-green-500"></div>
|
||
<div class="absolute bottom-0 right-0 w-2 h-2 border-b border-r border-green-500"></div>
|
||
</div>
|
||
|
||
<!-- Radar Scanning UI -->
|
||
<div class="absolute top-4 right-4 w-32 h-32 border border-green-500/20 rounded-full overflow-hidden hidden md:block">
|
||
<div class="absolute inset-0 border border-green-500/10 rounded-full scale-75"></div>
|
||
<div class="absolute inset-0 border border-green-500/10 rounded-full scale-50"></div>
|
||
<div class="absolute top-1/2 left-0 w-full h-px bg-green-500/20"></div>
|
||
<div class="absolute left-1/2 top-0 w-px h-full bg-green-500/20"></div>
|
||
<div class="absolute inset-0 origin-center animate-radar-spin bg-gradient-to-tr from-green-500/20 to-transparent"></div>
|
||
<div class="absolute top-1/4 left-1/3 w-1.5 h-1.5 bg-green-400 rounded-full shadow-[0_0_8px_rgba(74,222,128,1)] animate-pulse"></div>
|
||
</div>
|
||
|
||
<!-- System Status Floating Panel -->
|
||
<div class="absolute top-4 right-40 hidden lg:flex flex-col space-y-2 bg-black/40 backdrop-blur-md p-3 border border-white/10 rounded font-mono text-[10px]">
|
||
<div class="text-white/60 mb-1 border-b border-white/10 pb-1 flex justify-between">
|
||
<span>系统负载</span>
|
||
<span class="text-green-500">正常</span>
|
||
</div>
|
||
<div class="grid grid-cols-2 gap-x-4 gap-y-2">
|
||
<div class="flex items-center justify-between space-x-2">
|
||
<span class="text-white/40">CPU:</span>
|
||
<span class="text-white/80">{{ systemStats.cpu }}%</span>
|
||
</div>
|
||
<div class="flex items-center justify-between space-x-2">
|
||
<span class="text-white/40">内存:</span>
|
||
<span class="text-white/80">{{ systemStats.mem }}%</span>
|
||
</div>
|
||
<div class="flex items-center justify-between space-x-2">
|
||
<span class="text-white/40">温度:</span>
|
||
<span class="text-white/80">{{ systemStats.temp }}°C</span>
|
||
</div>
|
||
<div class="flex items-center justify-between space-x-2">
|
||
<span class="text-white/40">电量:</span>
|
||
<span class="text-white/80">{{ systemStats.battery }}%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Environmental Sensors (Floating) -->
|
||
<div class="absolute bottom-4 right-4 space-y-3 font-mono text-[10px] bg-black/40 backdrop-blur-md p-4 border border-white/10 rounded-lg">
|
||
<div class="text-white/60 mb-2 border-b border-white/10 pb-1 text-center">多维传感器数据</div>
|
||
<div class="space-y-2 min-w-[140px]">
|
||
<div class="flex items-center justify-between space-x-3">
|
||
<span class="text-green-400/70">环境温度:</span>
|
||
<div class="flex items-center space-x-2">
|
||
<span class="text-white">24.5°C</span>
|
||
<div class="w-12 h-1 bg-gray-800 rounded-full overflow-hidden">
|
||
<div class="w-3/5 h-full bg-green-500"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-center justify-between space-x-3">
|
||
<span class="text-blue-400/70">空气湿度:</span>
|
||
<div class="flex items-center space-x-2">
|
||
<span class="text-white">45%</span>
|
||
<div class="w-12 h-1 bg-gray-800 rounded-full overflow-hidden">
|
||
<div class="w-2/5 h-full bg-blue-500"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-center justify-between space-x-3">
|
||
<span class="text-yellow-400/70">噪音水平:</span>
|
||
<div class="flex items-center space-x-2">
|
||
<span class="text-white">-42dB</span>
|
||
<div class="w-12 h-1 bg-gray-800 rounded-full overflow-hidden">
|
||
<div class="h-full bg-yellow-500" :style="{width: currentLevel === 'L3' ? '90%' : '30%'}"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-center justify-between space-x-3">
|
||
<span class="text-orange-400/70">CO2浓度:</span>
|
||
<div class="flex items-center space-x-2">
|
||
<span class="text-white">{{ environmentDetails.co2 }}ppm</span>
|
||
<div class="w-12 h-1 bg-gray-800 rounded-full overflow-hidden">
|
||
<div class="w-4/5 h-full bg-orange-500"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Skeleton / Person Placeholder (SVG Based for better animation) -->
|
||
<div class="relative transition-all duration-1000 ease-in-out"
|
||
:class="{
|
||
'transform rotate-[85deg] translate-y-24 translate-x-4': currentLevel === 'L4',
|
||
'translate-y-12': currentLevel === 'L2'
|
||
}">
|
||
|
||
<!-- Virtual Chair for L2 -->
|
||
<svg v-if="currentLevel === 'L2'" width="120" height="120" viewBox="0 0 120 120" class="absolute top-24 left-0 opacity-40">
|
||
<path d="M30,80 L90,80 M40,80 L40,110 M80,80 L80,110 M30,80 L30,40" stroke="currentColor" stroke-width="2" fill="none" class="text-gray-500" />
|
||
</svg>
|
||
|
||
<svg width="120" height="240" viewBox="0 0 120 240" class="overflow-visible">
|
||
<!-- Glow Effect -->
|
||
<defs>
|
||
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
|
||
<feGaussianBlur stdDeviation="4" result="blur" />
|
||
<feComposite in="SourceGraphic" in2="blur" operator="over" />
|
||
</filter>
|
||
</defs>
|
||
|
||
<g :class="statusTextClass" filter="url(#glow)">
|
||
<!-- Head with Breathing and L2 Tilt -->
|
||
<g class="transition-all duration-1000"
|
||
:style="{
|
||
transform: currentLevel === 'L2' ? 'translate(60px, 40px) rotate(45deg) translate(-60px, -40px)' : '',
|
||
animation: currentLevel === 'L1' ? 'breathing 3s ease-in-out infinite' : ''
|
||
}">
|
||
<circle cx="60" cy="40" r="18" fill="none" stroke="currentColor" stroke-width="2" class="animate-pulse" />
|
||
<text x="60" y="42" text-anchor="middle" font-size="8" fill="currentColor" opacity="0.6" font-family="monospace">
|
||
{{ confidence }}%
|
||
</text>
|
||
</g>
|
||
|
||
<!-- Neck -->
|
||
<line x1="60" y1="58" x2="60" y2="65" stroke="currentColor" stroke-width="2" />
|
||
|
||
<!-- Torso / Spine (Breathing) -->
|
||
<path :d="currentLevel === 'L2' ? 'M60,65 Q70,90 60,110' : 'M60,65 Q65,100 60,130'"
|
||
fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"
|
||
class="transition-all duration-1000"
|
||
:style="{ animation: currentLevel === 'L1' ? 'breathing-torso 3s ease-in-out infinite' : '' }" />
|
||
|
||
<!-- Shoulders & Arms -->
|
||
<g class="transition-transform duration-1000"
|
||
:style="currentLevel === 'L3' ? 'transform: translateY(-5px)' : (currentLevel === 'L2' ? 'transform: translateY(5px)' : '')">
|
||
<line x1="30" y1="75" x2="90" y2="75" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
||
<!-- Left Arm -->
|
||
<path :d="currentLevel === 'L3' ? 'M30,75 L15,45' : (currentLevel === 'L2' ? 'M30,75 L25,105 L45,100' : 'M30,75 L20,115')"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="transition-all duration-1000" />
|
||
<!-- Right Arm -->
|
||
<path :d="currentLevel === 'L3' ? 'M90,75 L105,45' : (currentLevel === 'L2' ? 'M90,75 L95,105 L75,100' : 'M90,75 L100,115')"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="transition-all duration-1000" />
|
||
</g>
|
||
|
||
<!-- Hips & Legs (Sitting for L2) -->
|
||
<g class="transition-all duration-1000">
|
||
<line :x1="currentLevel === 'L2' ? 45 : 45" y1="130" :x2="currentLevel === 'L2' ? 75 : 75" y2="130" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
||
<!-- Left Leg -->
|
||
<path :d="currentLevel === 'L4' ? 'M45,130 L30,170 L50,210' : (currentLevel === 'L2' ? 'M45,110 L25,115 L20,160' : 'M45,130 L40,180 L45,230')"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="transition-all duration-1000" />
|
||
<!-- Right Leg -->
|
||
<path :d="currentLevel === 'L4' ? 'M75,130 L95,175 L85,215' : (currentLevel === 'L2' ? 'M75,110 L95,115 L100,160' : 'M75,130 L80,180 L75,230')"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="transition-all duration-1000" />
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
|
||
<!-- Bounding Box -->
|
||
<div class="absolute -inset-10 border border-dashed opacity-40 animate-pulse transition-all duration-500"
|
||
:class="statusBorderClass">
|
||
<div class="absolute -top-6 left-0 text-[10px] font-mono whitespace-nowrap" :class="statusTextClass">
|
||
ID: PERSON_01 [{{ currentLevel }}]
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Privacy Mask Effect (Scanline) -->
|
||
<div class="absolute inset-0 pointer-events-none bg-gradient-to-b from-transparent via-green-500/10 to-transparent h-8 w-full animate-scan"></div>
|
||
</div>
|
||
|
||
<!-- Bottom Console Log -->
|
||
<div class="absolute bottom-0 left-0 w-full h-48 bg-black/90 border-t border-gray-800 p-4 font-mono text-xs overflow-y-auto custom-scrollbar">
|
||
<div class="flex items-center space-x-2 text-white/40 mb-2 border-b border-white/5 pb-1">
|
||
<span class="animate-pulse">●</span>
|
||
<span>系统实时审计日志 (SYSTEM AUDIT LOG)</span>
|
||
</div>
|
||
<div v-for="(log, i) in logs" :key="i" class="mb-1 flex items-start space-x-2">
|
||
<span class="text-gray-500 shrink-0">[{{ log.time }}]</span>
|
||
<span :class="log.color" class="break-all">{{ log.message }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Right: AI Logic & Control -->
|
||
<div class="col-span-4 bg-gray-900 flex flex-col">
|
||
<!-- Header -->
|
||
<div class="p-4 border-b border-gray-800 bg-gray-800/50">
|
||
<h2 class="text-white font-bold flex items-center">
|
||
<span class="mr-2 text-xl">🧠</span> 决策引擎 (Brain)
|
||
</h2>
|
||
</div>
|
||
|
||
<!-- Status Panel -->
|
||
<div class="p-6 flex-1 space-y-8">
|
||
<!-- Current Level Indicator -->
|
||
<div class="text-center p-6 rounded-2xl border-2 transition-all duration-300"
|
||
:class="levelBorderClass">
|
||
<div class="text-sm text-gray-400 uppercase tracking-widest mb-2">当前风险等级</div>
|
||
<div class="text-6xl font-black transition-all duration-300" :class="statusTextClass">
|
||
{{ currentLevel }}
|
||
</div>
|
||
<div class="mt-4 text-lg font-medium text-white">{{ currentStatusText }}</div>
|
||
</div>
|
||
|
||
<!-- AI Logic Tree Visualization -->
|
||
<div class="space-y-4">
|
||
<h3 class="text-xs text-gray-500 uppercase font-bold tracking-wider">推理逻辑链 (Inference Chain)</h3>
|
||
|
||
<div class="flex flex-col space-y-2">
|
||
<div class="flex items-center space-x-3 p-3 rounded bg-gray-800/50 border border-gray-700">
|
||
<div class="w-2 h-2 rounded-full" :class="inferenceStats.yolo.color"></div>
|
||
<span class="text-sm text-gray-300">目标检测 (YOLOv8)</span>
|
||
<span class="ml-auto text-xs font-mono text-gray-500">{{ inferenceStats.yolo.time }}ms</span>
|
||
<span class="ml-2 text-xs text-green-400">Done</span>
|
||
</div>
|
||
<div class="flex items-center space-x-3 p-3 rounded bg-gray-800/50 border border-gray-700">
|
||
<div class="w-2 h-2 rounded-full" :class="inferenceStats.pose.color"></div>
|
||
<span class="text-sm text-gray-300">姿态分析 (PosePipe)</span>
|
||
<span class="ml-auto text-xs font-mono text-gray-500">{{ inferenceStats.pose.time }}ms</span>
|
||
<span class="ml-2 text-xs text-green-400">Done</span>
|
||
</div>
|
||
<div class="flex items-center space-x-3 p-3 rounded bg-gray-800/50 border border-gray-700">
|
||
<div class="w-2 h-2 rounded-full" :class="inferenceStats.risk.color"></div>
|
||
<span class="text-sm text-gray-300">风险评估 (RiskEval)</span>
|
||
<span class="ml-auto text-xs font-mono text-gray-500">{{ inferenceStats.risk.time }}ms</span>
|
||
<span class="ml-2 text-xs text-green-400">Done</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Action Panel (Demo Controls) -->
|
||
<div class="pt-6 border-t border-gray-800">
|
||
<h3 class="text-xs text-gray-500 uppercase font-bold tracking-wider mb-4">模拟触发 (Simulate Trigger)</h3>
|
||
<div class="grid grid-cols-2 gap-3">
|
||
<button @click="setLevel('L1')" class="px-4 py-3 rounded bg-gray-800 hover:bg-gray-700 text-green-400 text-xs font-bold border border-gray-700 transition-colors">
|
||
L1 正常巡护
|
||
</button>
|
||
<button @click="setLevel('L2')" class="px-4 py-3 rounded bg-gray-800 hover:bg-gray-700 text-yellow-400 text-xs font-bold border border-gray-700 transition-colors">
|
||
L2 异常静止
|
||
</button>
|
||
<button @click="setLevel('L3')" class="px-4 py-3 rounded bg-gray-800 hover:bg-gray-700 text-orange-400 text-xs font-bold border border-gray-700 transition-colors">
|
||
L3 呼救检测
|
||
</button>
|
||
<button @click="setLevel('L4')" class="px-4 py-3 rounded bg-red-900/50 hover:bg-red-800 text-red-400 text-xs font-bold border border-red-800 transition-colors animate-pulse">
|
||
L4 跌倒告警
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Blockchain Status Footer -->
|
||
<div class="p-4 bg-gray-800 border-t border-gray-700 flex justify-between items-center">
|
||
<div class="flex items-center space-x-2">
|
||
<span class="text-xs text-gray-400">TrustLink 状态:</span>
|
||
<span class="flex h-2 w-2 relative">
|
||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75"></span>
|
||
<span class="relative inline-flex rounded-full h-2 w-2 bg-blue-500"></span>
|
||
</span>
|
||
<span class="text-xs text-blue-400 font-mono font-bold uppercase">已连接</span>
|
||
</div>
|
||
<div class="text-xs font-mono text-gray-500">
|
||
上链哈希: {{ lastHash }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||
import { useDateFormat, useNow } from '@vueuse/core'
|
||
import { useGlobalStore } from '../../stores/global'
|
||
import { useBlockchainStore } from '../../stores/blockchain'
|
||
|
||
const store = useGlobalStore()
|
||
const blockchainStore = useBlockchainStore()
|
||
|
||
// State
|
||
const currentLevel = computed(() => store.robotStatus.level)
|
||
const isManualMode = ref(false)
|
||
|
||
const toggleManualMode = () => {
|
||
isManualMode.value = !isManualMode.value
|
||
if (isManualMode.value) {
|
||
addLog('操作:已切换至人工遥控模式,自动巡航暂停。', 'info')
|
||
} else {
|
||
addLog('操作:恢复自动巡航模式。', 'info')
|
||
}
|
||
}
|
||
|
||
const fps = ref(30)
|
||
const latency = ref(12)
|
||
const logs = ref([])
|
||
const confidence = ref(98)
|
||
|
||
// Inference Stats
|
||
const inferenceStats = ref({
|
||
yolo: { time: 4.2, status: 'Done', color: 'bg-green-500' },
|
||
pose: { time: 8.5, status: 'Done', color: 'bg-green-500' },
|
||
risk: { time: 2.1, status: 'Done', color: 'bg-green-500' }
|
||
})
|
||
|
||
// Targets for tracking simulation
|
||
const targets = ref([
|
||
{ id: 'PERSON_01', x: 50, y: 50, active: true },
|
||
{ id: 'OBJECT_04', x: 20, y: 30, active: false },
|
||
{ id: 'OBJECT_09', x: 80, y: 70, active: false }
|
||
])
|
||
|
||
const currentScene = ref('hospital') // 'hospital', 'office', 'warehouse'
|
||
|
||
const scenes = {
|
||
hospital: {
|
||
name: '医院走廊',
|
||
color: 'from-blue-900/30 to-slate-900/50',
|
||
elements: [
|
||
{ type: '智能病床', x: 15, y: 65, scale: 1.2, icon: '🏥' },
|
||
{ type: '输液架', x: 82, y: 45, scale: 0.8, icon: '🧪' },
|
||
{ type: '消毒机', x: 5, y: 40, scale: 0.7, icon: '🛡️' },
|
||
{ type: '导视牌', x: 90, y: 20, scale: 0.6, icon: '🪧' }
|
||
]
|
||
},
|
||
office: {
|
||
name: '现代办公室',
|
||
color: 'from-emerald-900/20 to-stone-900/40',
|
||
elements: [
|
||
{ type: '升降桌', x: 75, y: 60, scale: 1.1, icon: '🖥️' },
|
||
{ type: '人体工学椅', x: 68, y: 65, scale: 0.9, icon: '💺' },
|
||
{ type: '绿植', x: 10, y: 70, scale: 1.0, icon: '🌿' },
|
||
{ type: '饮水机', x: 92, y: 50, scale: 0.8, icon: '💧' }
|
||
]
|
||
},
|
||
warehouse: {
|
||
name: '自动化仓库',
|
||
color: 'from-orange-900/20 to-zinc-900/50',
|
||
elements: [
|
||
{ type: '重型货架', x: 85, y: 35, scale: 1.4, icon: '📦' },
|
||
{ type: '自动分拣机', x: 10, y: 75, scale: 1.3, icon: '🤖' },
|
||
{ type: '搬运托盘', x: 40, y: 85, scale: 0.9, icon: '🛒' },
|
||
{ type: '监控立柱', x: 5, y: 25, scale: 1.1, icon: '📹' }
|
||
]
|
||
}
|
||
}
|
||
|
||
// 机器人系统状态
|
||
const systemStats = ref({
|
||
cpu: 42,
|
||
mem: 65,
|
||
temp: 38,
|
||
battery: 85,
|
||
wifi: -45
|
||
})
|
||
|
||
// 环境详细传感器
|
||
const environmentDetails = ref({
|
||
co2: 420,
|
||
oxygen: 20.9,
|
||
voc: 0.12,
|
||
pressure: 101.3
|
||
})
|
||
|
||
// Mock Data Generators
|
||
const lastHash = ref(store.robotStatus.hash || 'Waiting...')
|
||
|
||
// Computed Styles & Status
|
||
const statusColorClass = computed(() => {
|
||
const map = { L1: 'border-green-500', L2: 'border-yellow-500', L3: 'border-orange-500', L4: 'border-red-500' }
|
||
return map[currentLevel.value] || 'border-green-500'
|
||
})
|
||
const statusBgClass = computed(() => {
|
||
const map = { L1: 'bg-green-500', L2: 'bg-yellow-500', L3: 'bg-orange-500', L4: 'bg-red-500' }
|
||
return map[currentLevel.value] || 'bg-green-500'
|
||
})
|
||
const statusBorderClass = computed(() => {
|
||
const map = { L1: 'border-green-500/50', L2: 'border-yellow-500/50', L3: 'border-orange-500/50', L4: 'border-red-500/50' }
|
||
return map[currentLevel.value] || 'border-green-500/50'
|
||
})
|
||
const statusTextClass = computed(() => {
|
||
const map = { L1: 'text-green-500', L2: 'text-yellow-500', L3: 'text-orange-500', L4: 'text-red-500' }
|
||
return map[currentLevel.value] || 'text-green-500'
|
||
})
|
||
const levelBorderClass = computed(() => {
|
||
const map = { L1: 'border-green-500/20 bg-green-500/5', L2: 'border-yellow-500/20 bg-yellow-500/5', L3: 'border-orange-500/20 bg-orange-500/5', L4: 'border-red-500/20 bg-red-500/5' }
|
||
return map[currentLevel.value] || 'border-green-500/20 bg-green-500/5'
|
||
})
|
||
|
||
const currentStatusText = computed(() => {
|
||
const map = { L1: '巡视中 - 环境安全', L2: '观察中 - 行为异常', L3: '警告 - 检测到求救', L4: '紧急 - 跌倒告警' }
|
||
return map[currentLevel.value] || '系统就绪'
|
||
})
|
||
|
||
// Methods
|
||
const addLog = (msg, level = 'info') => {
|
||
const time = useDateFormat(useNow(), 'HH:mm:ss.SSS').value
|
||
const colors = { info: 'text-gray-400', warning: 'text-yellow-400', error: 'text-red-400', success: 'text-green-400' }
|
||
logs.value.unshift({ time, message: msg, color: colors[level] })
|
||
if (logs.value.length > 50) logs.value.pop()
|
||
}
|
||
|
||
const setLevel = (level) => {
|
||
// Update Global Store
|
||
if (level === 'L1') {
|
||
store.triggerAlert('L1', '系统恢复正常')
|
||
addLog('系统状态已恢复正常,继续执行预定巡护任务。', 'success')
|
||
} else if (level === 'L2') {
|
||
store.triggerAlert('L2', '检测到长时间静止')
|
||
addLog('异常警告:检测到目标在当前区域静止超过180秒。', 'warning')
|
||
} else if (level === 'L3') {
|
||
store.triggerAlert('L3', '检测到呼救声')
|
||
addLog('语音事件:实时捕捉到疑似“救命”或“帮帮我”的音频特征。', 'warning')
|
||
} else if (level === 'L4') {
|
||
store.triggerAlert('L4', '检测到跌倒事件')
|
||
addLog('紧急:视觉算法确认目标发生跌倒行为,立即启动应急响应。', 'error')
|
||
|
||
// Blockchain Linkage
|
||
const txHash = '0x' + Math.random().toString(16).slice(2, 10) + '...' + Math.random().toString(16).slice(2, 6)
|
||
blockchainStore.transactions.unshift({
|
||
id: txHash,
|
||
type: '告警',
|
||
from: 'Robot_01',
|
||
to: '应急响应中心',
|
||
amount: '0.00',
|
||
status: '已确认',
|
||
time: '刚刚'
|
||
})
|
||
|
||
addLog('正在生成隐私保护哈希存证...', 'info')
|
||
setTimeout(() => {
|
||
lastHash.value = store.robotStatus.hash
|
||
addLog(`存证已上链:${lastHash.value}`, 'success')
|
||
addLog('已通过 API 通知紧急联系人及医疗中心。', 'error')
|
||
}, 800)
|
||
}
|
||
}
|
||
|
||
// Simulation Loops
|
||
let interval
|
||
let confidenceInterval
|
||
onMounted(() => {
|
||
addLog('系统初始化完成。视觉模型:YOLOv8-Nano 已加载。', 'success')
|
||
addLog('隐私保护引擎:运行中(本地处理模式)。', 'success')
|
||
|
||
interval = setInterval(() => {
|
||
fps.value = Math.floor(Math.random() * 5) + 28
|
||
latency.value = Math.floor(Math.random() * 5) + 10
|
||
|
||
// Randomize system stats
|
||
systemStats.value.cpu = 35 + Math.floor(Math.random() * 15)
|
||
systemStats.value.mem = 60 + Math.floor(Math.random() * 10)
|
||
systemStats.value.temp = 36 + Math.floor(Math.random() * 5)
|
||
|
||
// Randomize inference times
|
||
inferenceStats.value.yolo.time = (Math.random() * 2 + 3).toFixed(1)
|
||
inferenceStats.value.pose.time = (Math.random() * 5 + 6).toFixed(1)
|
||
inferenceStats.value.risk.time = (Math.random() * 1 + 1.5).toFixed(1)
|
||
|
||
// Move targets slightly
|
||
targets.value.forEach(t => {
|
||
t.x += (Math.random() - 0.5) * 2
|
||
t.y += (Math.random() - 0.5) * 2
|
||
// Keep within bounds
|
||
t.x = Math.max(10, Math.min(90, t.x))
|
||
t.y = Math.max(10, Math.min(90, t.y))
|
||
})
|
||
|
||
if (currentLevel.value === 'L1' && Math.random() > 0.8) {
|
||
addLog('常规扫描完成。未发现异常目标。', 'info')
|
||
}
|
||
}, 2000)
|
||
|
||
confidenceInterval = setInterval(() => {
|
||
const base = currentLevel.value === 'L1' ? 98 : (currentLevel.value === 'L4' ? 99 : 85)
|
||
confidence.value = base + Math.floor(Math.random() * 3) - 1
|
||
}, 500)
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
clearInterval(interval)
|
||
clearInterval(confidenceInterval)
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
@keyframes breathing {
|
||
0%, 100% { transform: translateY(0); }
|
||
50% { transform: translateY(-3px); }
|
||
}
|
||
|
||
@keyframes breathing-torso {
|
||
0%, 100% { transform: scaleY(1); }
|
||
50% { transform: scaleY(1.02); }
|
||
}
|
||
|
||
.custom-scrollbar::-webkit-scrollbar {
|
||
width: 4px;
|
||
}
|
||
.custom-scrollbar::-webkit-scrollbar-track {
|
||
background: #111;
|
||
}
|
||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||
background: #333;
|
||
}
|
||
|
||
@keyframes scan {
|
||
0% { top: 0%; opacity: 0; }
|
||
10% { opacity: 1; }
|
||
90% { opacity: 1; }
|
||
100% { top: 100%; opacity: 0; }
|
||
}
|
||
.animate-scan {
|
||
animation: scan 3s linear infinite;
|
||
}
|
||
</style>
|