feat(agency): 新增应急响应和护理记录功能模块

添加紧急响应中心和护理记录档案两个新路由页面
实现全局状态管理中的告警数据结构和模拟数据
完善机构工作台的机器人调度和任务管理功能
重构仪表盘地图视图为可切换的网格和地图模式
优化告警卡片和区块链存证信息的展示效果
This commit is contained in:
祀梦
2026-01-12 00:42:51 +08:00
parent d22b65f6d3
commit d32985721d
8 changed files with 1530 additions and 128 deletions

View File

@@ -4,39 +4,213 @@
<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">REC</div>
<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: {{ fps }} | LATENCY: {{ latency }}ms
帧率: {{ 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">
<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>
<!-- 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-20"
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: 40px 40px;">
<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>
<!-- Skeleton / Person Placeholder -->
<div class="relative transition-all duration-500" :class="{'transform rotate-90 translate-y-20': currentLevel === 'L4'}">
<!-- Head -->
<div class="w-16 h-16 border-2 rounded-full flex items-center justify-center relative z-10"
:class="statusColorClass">
<span class="text-xs font-mono text-white opacity-50">{{ confidence }}%</span>
<!-- 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>
<!-- Body -->
<div class="w-1 h-32 mx-auto relative z-0" :class="statusBgClass"></div>
<!-- Arms -->
<div class="absolute top-20 left-1/2 -translate-x-1/2 w-40 h-1" :class="statusBgClass"></div>
<!-- Legs -->
<div class="absolute bottom-0 left-1/2 -translate-x-1/2 w-32 h-1 flex justify-between">
<div class="w-1 h-32 transform -rotate-12 origin-top" :class="statusBgClass"></div>
<div class="w-1 h-32 transform rotate-12 origin-top" :class="statusBgClass"></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-8 border border-dashed opacity-50 animate-pulse" :class="statusBorderClass">
<div class="absolute -top-6 left-0 text-xs font-mono" :class="statusTextClass">
<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>
@@ -48,9 +222,13 @@
<!-- 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 v-for="(log, i) in logs" :key="i" class="mb-1">
<span class="text-gray-500">[{{ log.time }}]</span>
<span :class="log.color" class="ml-2">{{ log.message }}</span>
<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>
@@ -82,19 +260,22 @@
<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 bg-green-500"></div>
<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 text-green-400">Done</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="poseColor"></div>
<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" :class="poseTextColor">{{ poseStatus }}</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="riskColor"></div>
<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" :class="riskTextColor">{{ riskScore }}</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>
@@ -122,15 +303,15 @@
<!-- 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 Status:</span>
<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">Connected</span>
<span class="text-xs text-blue-400 font-mono font-bold uppercase">已连接</span>
</div>
<div class="text-xs font-mono text-gray-500">
Last Hash: {{ lastHash }}
上链哈希: {{ lastHash }}
</div>
</div>
</div>
@@ -138,11 +319,13 @@
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
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)
@@ -151,10 +334,101 @@ 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 - No changes needed as they depend on currentLevel)
// 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') => {
@@ -168,48 +442,96 @@ const setLevel = (level) => {
// Update Global Store
if (level === 'L1') {
store.triggerAlert('L1', '系统恢复正常')
addLog('System state normalized.', 'success')
addLog('系统状态已恢复正常,继续执行预定巡护任务。', 'success')
} else if (level === 'L2') {
store.triggerAlert('L2', '检测到长时间静止')
addLog('Anomaly detected: No motion for 180s.', 'warning')
addLog('Initiating approach sequence...', 'info')
addLog('异常警告:检测到目标在当前区域静止超过180秒。', 'warning')
} else if (level === 'L3') {
store.triggerAlert('L3', '检测到呼救声')
addLog('Audio event detected: "Help"', 'warning')
addLog('Risk score escalated to 0.78', 'warning')
addLog('语音事件:实时捕捉到疑似“救命”或“帮帮我”的音频特征。', 'warning')
} else if (level === 'L4') {
store.triggerAlert('L4', '检测到跌倒事件')
addLog('CRITICAL EVENT: FALL DETECTED', 'error')
addLog('Generating privacy-preserving hash...', 'info')
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(`Evidence committed to blockchain: ${lastHash.value}`, 'success')
addLog('Emergency contact (120) notified via API', 'error')
addLog(`存证已上链:${lastHash.value}`, 'success')
addLog('已通过 API 通知紧急联系人及医疗中心。', 'error')
}, 800)
}
}
// Loop for background logs
// Simulation Loops
let interval
let confidenceInterval
onMounted(() => {
addLog('System initialized. Model: YOLOv8-Nano loaded.', 'success')
addLog('Privacy Engine: Active (Local Only).', 'success')
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('Routine scan complete. No objects found.', 'info')
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;
}