first commit
This commit is contained in:
134
src/views/LandingPage.vue
Normal file
134
src/views/LandingPage.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gradient-to-br from-slate-900 via-blue-900 to-slate-900 flex items-center justify-center p-4">
|
||||
<div class="max-w-6xl w-full grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
||||
|
||||
<!-- Left: Intro -->
|
||||
<div class="space-y-8 text-white">
|
||||
<div class="inline-flex items-center space-x-2 bg-blue-500/10 border border-blue-400/30 rounded-full px-4 py-1.5 backdrop-blur-sm">
|
||||
<span class="relative flex h-2 w-2">
|
||||
<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-sm font-medium text-blue-300">基于区块链+隐私计算的新一代养老协作网络</span>
|
||||
</div>
|
||||
|
||||
<h1 class="text-5xl lg:text-7xl font-extrabold tracking-tight leading-tight">
|
||||
智护链
|
||||
<span class="block text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-teal-300">SmartCare Chain</span>
|
||||
</h1>
|
||||
|
||||
<p class="text-xl text-slate-300 max-w-lg leading-relaxed">
|
||||
以人为中心,以行为为存证。连接家庭、机构与监管的信任桥梁,让每一次护理都值得信赖。
|
||||
</p>
|
||||
|
||||
<div class="flex items-center space-x-4 pt-4">
|
||||
<button @click="router.push('/login')" class="px-8 py-4 bg-blue-600 hover:bg-blue-500 text-white rounded-xl font-bold text-lg shadow-lg shadow-blue-600/30 transition-all transform hover:-translate-y-1">
|
||||
立即启动系统
|
||||
</button>
|
||||
<a href="#" class="px-8 py-4 bg-slate-800/50 hover:bg-slate-800 text-white rounded-xl font-semibold text-lg border border-slate-700 backdrop-blur transition-all">
|
||||
查看技术白皮书
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-6 pt-8 border-t border-slate-700/50">
|
||||
<div>
|
||||
<div class="text-3xl font-bold text-white">0s</div>
|
||||
<div class="text-sm text-slate-400 mt-1">隐私计算延迟</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-3xl font-bold text-white">100%</div>
|
||||
<div class="text-sm text-slate-400 mt-1">链上存证率</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-3xl font-bold text-white">L4</div>
|
||||
<div class="text-sm text-slate-400 mt-1">最高防护等级</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right: Role Cards -->
|
||||
<div class="relative">
|
||||
<!-- Decoration BG -->
|
||||
<div class="absolute -inset-4 bg-gradient-to-r from-blue-500 to-purple-600 rounded-2xl blur-2xl opacity-20 animate-pulse"></div>
|
||||
|
||||
<div class="relative bg-slate-800/80 backdrop-blur-xl border border-slate-700 rounded-2xl p-8 shadow-2xl">
|
||||
<h3 class="text-xl font-semibold text-white mb-6 flex items-center">
|
||||
<span class="mr-2">🚀</span> 快速进入演示终端
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div v-for="role in roles" :key="role.id"
|
||||
@click="navigateTo(role.path)"
|
||||
class="group relative p-4 rounded-xl border border-slate-600/50 bg-slate-700/30 hover:bg-blue-600/20 hover:border-blue-500/50 cursor-pointer transition-all duration-300 overflow-hidden">
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-transparent to-white/5 opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
||||
|
||||
<div class="relative z-10 flex items-start space-x-4">
|
||||
<div class="p-3 rounded-lg bg-slate-800 group-hover:bg-blue-500/20 text-2xl transition-colors">
|
||||
{{ role.icon }}
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-white font-bold group-hover:text-blue-300 transition-colors">{{ role.name }}</h4>
|
||||
<p class="text-xs text-slate-400 mt-1 leading-snug">{{ role.desc }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 pt-6 border-t border-slate-700 flex justify-between items-center text-xs text-slate-500">
|
||||
<span>System Status: <span class="text-green-400">Online</span></span>
|
||||
<span>Build v2.0.1 (Dev)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const roles = [
|
||||
{
|
||||
id: 'client',
|
||||
name: '家属/用户端',
|
||||
desc: '查看亲人状态,管理隐私权限,参与时间银行互助。',
|
||||
icon: '👨👩👧',
|
||||
path: '/client/dashboard'
|
||||
},
|
||||
{
|
||||
id: 'robot',
|
||||
name: '机器人端 (Edge)',
|
||||
desc: '模拟边缘计算节点,展示AI识别与L1-L4决策逻辑。',
|
||||
icon: '🤖',
|
||||
path: '/robot/monitor'
|
||||
},
|
||||
{
|
||||
id: 'agency',
|
||||
name: '机构/护理端',
|
||||
desc: '多机协同指挥,接收报警并进行链上签名确认。',
|
||||
icon: '🏥',
|
||||
path: '/agency/workspace'
|
||||
},
|
||||
{
|
||||
id: 'admin',
|
||||
name: '政府监管端',
|
||||
desc: '全域态势感知大屏,审计溯源与纠纷仲裁。',
|
||||
icon: '🏛️',
|
||||
path: '/admin/dashboard'
|
||||
},
|
||||
{
|
||||
id: 'blockchain',
|
||||
name: '区块链浏览器',
|
||||
desc: '查看分布式账本,验证智能合约自动执行逻辑。',
|
||||
icon: '🔗',
|
||||
path: '/blockchain/explorer'
|
||||
}
|
||||
]
|
||||
|
||||
const navigateTo = (path) => {
|
||||
router.push(path)
|
||||
}
|
||||
</script>
|
||||
62
src/views/LoginPage.vue
Normal file
62
src/views/LoginPage.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50 flex items-center justify-center p-4">
|
||||
<div class="max-w-md w-full bg-white rounded-2xl shadow-xl overflow-hidden">
|
||||
<!-- Header -->
|
||||
<div class="bg-trust-blue px-8 py-10 text-center relative overflow-hidden">
|
||||
<div class="absolute top-0 left-0 w-full h-full opacity-10 bg-[url('https://www.transparenttextures.com/patterns/cubes.png')]"></div>
|
||||
<h2 class="text-3xl font-bold text-white relative z-10">身份认证</h2>
|
||||
<p class="text-blue-200 mt-2 text-sm relative z-10">连接至智护链可信协作网络</p>
|
||||
</div>
|
||||
|
||||
<!-- Form -->
|
||||
<div class="p-8">
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">账号 / 终端ID</label>
|
||||
<input type="text" v-model="username" placeholder="输入 ID (如 user01, robot01)"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">安全密钥</label>
|
||||
<input type="password" placeholder="••••••••"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all" />
|
||||
</div>
|
||||
|
||||
<button @click="handleLogin" class="w-full bg-trust-blue hover:bg-blue-800 text-white font-bold py-3 rounded-lg shadow-lg shadow-blue-900/20 transition-all transform active:scale-95">
|
||||
安全登录
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 text-center text-xs text-gray-400">
|
||||
<p>基于 FISCO BCOS 区块链身份认证服务 (DID)</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const username = ref('')
|
||||
|
||||
const handleLogin = () => {
|
||||
// 简单的路由分发逻辑,模拟登录
|
||||
const id = username.value.toLowerCase()
|
||||
if (id.includes('robot')) {
|
||||
router.push('/robot/monitor')
|
||||
} else if (id.includes('agency')) {
|
||||
router.push('/agency/workspace')
|
||||
} else if (id.includes('admin')) {
|
||||
router.push('/admin/dashboard')
|
||||
} else if (id.includes('chain')) {
|
||||
router.push('/blockchain/explorer')
|
||||
} else {
|
||||
// 默认跳转到用户端
|
||||
router.push('/client/dashboard')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
112
src/views/admin/Dashboard.vue
Normal file
112
src/views/admin/Dashboard.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div class="h-full flex flex-col space-y-6">
|
||||
<!-- Top KPI Cards -->
|
||||
<div class="grid grid-cols-4 gap-6">
|
||||
<div v-for="kpi in kpis" :key="kpi.label" class="bg-slate-800/50 backdrop-blur border border-slate-700 p-4 rounded-xl">
|
||||
<div class="text-sm text-slate-400">{{ kpi.label }}</div>
|
||||
<div class="text-2xl font-bold text-white mt-1">{{ kpi.value }}</div>
|
||||
<div class="text-xs mt-2" :class="kpi.trendUp ? 'text-green-400' : 'text-red-400'">
|
||||
{{ kpi.trendUp ? '↑' : '↓' }} {{ kpi.trend }} vs last week
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Vis Area -->
|
||||
<div class="flex-1 grid grid-cols-3 gap-6 min-h-0">
|
||||
<!-- Map Area (Center) -->
|
||||
<div class="col-span-2 bg-slate-800/30 border border-slate-700 rounded-xl relative overflow-hidden group">
|
||||
<!-- Decoration Grid -->
|
||||
<div class="absolute inset-0 opacity-20"
|
||||
style="background-image: radial-gradient(#4f46e5 1px, transparent 1px); background-size: 20px 20px;">
|
||||
</div>
|
||||
|
||||
<!-- Mock Map Content -->
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<div class="relative w-3/4 h-3/4 border-2 border-indigo-500/30 rounded-full animate-pulse-slow flex items-center justify-center">
|
||||
<div class="w-2/3 h-2/3 border border-indigo-400/20 rounded-full"></div>
|
||||
<!-- Hotspots -->
|
||||
<div class="absolute top-1/4 right-1/4 w-3 h-3 bg-red-500 rounded-full shadow-[0_0_15px_rgba(239,68,68,0.8)] animate-ping"></div>
|
||||
<div class="absolute bottom-1/3 left-1/3 w-2 h-2 bg-green-500 rounded-full shadow-[0_0_10px_rgba(34,197,94,0.8)]"></div>
|
||||
</div>
|
||||
<div class="absolute bottom-4 right-4 bg-black/60 px-3 py-1 rounded text-xs text-indigo-300 border border-indigo-500/30">
|
||||
Real-time Monitoring Active
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Panel: Audit & Alerts -->
|
||||
<div class="col-span-1 flex flex-col space-y-6">
|
||||
<!-- Recent Alerts -->
|
||||
<div class="flex-1 bg-slate-800/50 border border-slate-700 rounded-xl p-4 overflow-hidden flex flex-col">
|
||||
<h3 class="text-sm font-bold text-slate-300 mb-3 uppercase tracking-wider">实时告警流 (Alert Stream)</h3>
|
||||
<div class="flex-1 overflow-y-auto space-y-2 pr-1 custom-scrollbar">
|
||||
<div v-for="alert in store.alerts" :key="alert.id" class="bg-slate-700/50 p-3 rounded border-l-2 animate-slide-in" :class="alert.level === 'L4' ? 'border-red-500' : 'border-orange-500'">
|
||||
<div class="flex justify-between items-start">
|
||||
<span class="text-xs font-bold text-white">{{ alert.type }}</span>
|
||||
<span class="text-[10px] text-slate-400">{{ alert.time }}</span>
|
||||
</div>
|
||||
<div class="text-xs text-slate-300 mt-1">{{ alert.desc }}</div>
|
||||
</div>
|
||||
<div v-if="store.alerts.length === 0" class="text-center text-xs text-slate-500 py-4">暂无实时告警</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Audit Log -->
|
||||
<div class="h-1/3 bg-slate-800/50 border border-slate-700 rounded-xl p-4">
|
||||
<h3 class="text-sm font-bold text-slate-300 mb-3 uppercase tracking-wider">区块链审计日志 (Audit Log)</h3>
|
||||
<div class="space-y-2">
|
||||
<div v-for="log in store.auditLogs" :key="log.hash" class="text-xs font-mono flex justify-between items-center p-2 hover:bg-white/5 rounded transition-colors cursor-pointer animate-fade-in">
|
||||
<span class="text-green-400">● Verified</span>
|
||||
<span class="text-slate-500 truncate w-32">{{ log.hash }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useGlobalStore } from '../../stores/global'
|
||||
|
||||
const store = useGlobalStore()
|
||||
|
||||
const kpis = ref([
|
||||
{ label: '覆盖社区数', value: '128', trend: '12%', trendUp: true },
|
||||
{ label: '在线机器人', value: '1,042', trend: '5%', trendUp: true },
|
||||
{ label: '今日告警处理', value: '89', trend: '2%', trendUp: false },
|
||||
{ label: '服务满意度', value: '98.5%', trend: '0.5%', trendUp: true },
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@keyframes slideIn {
|
||||
from { opacity: 0; transform: translateX(-10px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
.animate-slide-in {
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
.animate-fade-in {
|
||||
animation: fadeIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
.animate-pulse-slow {
|
||||
animation: pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: #475569;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
124
src/views/agency/Workspace.vue
Normal file
124
src/views/agency/Workspace.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- Active Tasks (Kanban Style) -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
|
||||
<!-- L1/L2 Routine -->
|
||||
<div class="bg-gray-50 rounded-xl p-4 border border-gray-200">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="font-bold text-gray-700">常规巡护 (Routine)</h3>
|
||||
<span class="bg-gray-200 text-gray-600 text-xs px-2 py-1 rounded-full font-bold">12</span>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<div v-for="i in 3" :key="i" class="bg-white p-3 rounded-lg shadow-sm border border-gray-100 cursor-move hover:shadow-md transition-shadow">
|
||||
<div class="flex justify-between items-start">
|
||||
<span class="text-sm font-medium text-gray-800">Room 30{{i}} - 晚间巡查</span>
|
||||
<span class="w-2 h-2 rounded-full bg-green-500"></span>
|
||||
</div>
|
||||
<div class="mt-2 text-xs text-gray-500 flex justify-between">
|
||||
<span>Robot-0{{i}}</span>
|
||||
<span>10m ago</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- L3 Attention Needed -->
|
||||
<div class="bg-orange-50 rounded-xl p-4 border border-orange-100">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="font-bold text-orange-800">需关注 (Attention)</h3>
|
||||
<span class="bg-orange-200 text-orange-800 text-xs px-2 py-1 rounded-full font-bold">2</span>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<div class="bg-white p-3 rounded-lg shadow-sm border-l-4 border-orange-400 cursor-pointer hover:shadow-md transition-shadow">
|
||||
<div class="flex justify-between items-start">
|
||||
<span class="text-sm font-bold text-gray-800">Room 402 - 异常静止</span>
|
||||
<span class="text-xs bg-orange-100 text-orange-600 px-1.5 py-0.5 rounded">L2</span>
|
||||
</div>
|
||||
<p class="text-xs text-gray-600 mt-2">AI 检测到老人超过 3 小时未移动,请确认。</p>
|
||||
<div class="mt-3 flex space-x-2">
|
||||
<button class="flex-1 bg-orange-100 hover:bg-orange-200 text-orange-700 text-xs py-1.5 rounded transition-colors">呼叫机器人</button>
|
||||
<button class="flex-1 bg-white border border-gray-200 hover:bg-gray-50 text-gray-600 text-xs py-1.5 rounded transition-colors">人工介入</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- L4 Critical -->
|
||||
<div class="bg-red-50 rounded-xl p-4 border border-red-100">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="font-bold text-red-800">紧急告警 (Critical)</h3>
|
||||
<span class="bg-red-200 text-red-800 text-xs px-2 py-1 rounded-full font-bold animate-pulse">1</span>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<div class="bg-white p-3 rounded-lg shadow-lg border-l-4 border-red-600 relative overflow-hidden">
|
||||
<div class="absolute top-0 right-0 p-1">
|
||||
<span class="flex h-3 w-3 relative">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-3 w-3 bg-red-500"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-start pr-4">
|
||||
<span class="text-sm font-bold text-gray-900">Room 101 - 跌倒告警</span>
|
||||
<span class="text-xs bg-red-100 text-red-600 px-1.5 py-0.5 rounded font-bold">L4</span>
|
||||
</div>
|
||||
<div class="mt-2 bg-gray-50 p-2 rounded border border-gray-100">
|
||||
<div class="flex items-center space-x-2 text-xs text-gray-500">
|
||||
<span>🔗 证据已上链</span>
|
||||
<span class="font-mono text-gray-400">0x8a...9c</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="w-full mt-3 bg-red-600 hover:bg-red-700 text-white text-xs font-bold py-2 rounded shadow-sm transition-colors flex items-center justify-center">
|
||||
<span class="mr-1">⚡</span> 立即响应 (Sign to Confirm)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Robot Fleet Map (Simplified) -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<h3 class="font-bold text-gray-800 mb-4">机器人部署概览</h3>
|
||||
<div class="h-64 bg-gray-100 rounded-lg relative overflow-hidden flex items-center justify-center">
|
||||
<div class="absolute inset-0 grid grid-cols-6 grid-rows-4 gap-1 p-4 opacity-30">
|
||||
<div v-for="n in 24" :key="n" class="border border-gray-300 rounded"></div>
|
||||
</div>
|
||||
<div class="text-gray-400 text-sm">Interactive Map Placeholder</div>
|
||||
|
||||
<!-- Mock Robot Dots -->
|
||||
<div class="absolute top-1/4 left-1/4 w-4 h-4 bg-green-500 rounded-full border-2 border-white shadow-md" title="Robot-01"></div>
|
||||
<div class="absolute top-1/2 left-1/2 w-4 h-4 bg-orange-500 rounded-full border-2 border-white shadow-md animate-bounce" title="Robot-05 (Busy)"></div>
|
||||
<div class="absolute bottom-1/4 right-1/4 w-4 h-4 bg-green-500 rounded-full border-2 border-white shadow-md" title="Robot-03"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { useGlobalStore } from '../../stores/global'
|
||||
|
||||
const store = useGlobalStore()
|
||||
|
||||
const attentionTasks = computed(() => store.alerts.filter(a => a.level === 'L2' || a.level === 'L3'))
|
||||
const criticalTasks = computed(() => store.alerts.filter(a => a.level === 'L4'))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.animate-fade-in {
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes bounceIn {
|
||||
0% { opacity: 0; transform: scale(0.9); }
|
||||
50% { opacity: 1; transform: scale(1.02); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
.animate-bounce-in {
|
||||
animation: bounceIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
</style>
|
||||
93
src/views/blockchain/Explorer.vue
Normal file
93
src/views/blockchain/Explorer.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<!-- Network Stats -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div v-for="stat in stats" :key="stat.label" class="bg-[#1e293b]/50 border border-slate-700 p-4 rounded-lg">
|
||||
<div class="text-xs text-slate-400 uppercase tracking-wide">{{ stat.label }}</div>
|
||||
<div class="text-xl font-mono text-white mt-1">{{ stat.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Latest Blocks (Cube Stream) -->
|
||||
<div class="relative h-48 bg-[#1e293b]/30 border border-slate-700 rounded-xl overflow-hidden flex items-center px-4 space-x-4">
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-[#0f172a] via-transparent to-[#0f172a] z-10 pointer-events-none"></div>
|
||||
|
||||
<div v-for="block in blocks" :key="block.height"
|
||||
class="min-w-[140px] h-32 bg-gradient-to-br from-blue-900 to-slate-900 border border-blue-500/30 rounded-lg p-3 flex flex-col justify-between transform transition-all hover:scale-105 hover:border-blue-400 cursor-pointer group shadow-lg shadow-blue-900/20">
|
||||
<div class="flex justify-between items-start">
|
||||
<span class="text-xs text-blue-300 font-mono">#{{ block.height }}</span>
|
||||
<span class="w-2 h-2 rounded-full bg-green-400 animate-pulse"></span>
|
||||
</div>
|
||||
<div class="text-[10px] text-slate-400 font-mono truncate">
|
||||
{{ block.hash }}
|
||||
</div>
|
||||
<div class="text-xs text-white">
|
||||
{{ block.txs }} txs
|
||||
<span class="block text-[10px] text-slate-500">{{ block.time }} ago</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Transactions Table -->
|
||||
<div class="bg-[#1e293b]/50 border border-slate-700 rounded-xl overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-slate-700 flex justify-between items-center">
|
||||
<h3 class="text-white font-bold">Latest Transactions</h3>
|
||||
<button class="text-xs text-blue-400 hover:text-blue-300">View All ></button>
|
||||
</div>
|
||||
<table class="w-full text-left text-sm text-slate-400">
|
||||
<thead class="bg-slate-800/50 text-xs uppercase">
|
||||
<tr>
|
||||
<th class="px-6 py-3">Tx Hash</th>
|
||||
<th class="px-6 py-3">Type</th>
|
||||
<th class="px-6 py-3">From</th>
|
||||
<th class="px-6 py-3">To</th>
|
||||
<th class="px-6 py-3">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-700">
|
||||
<tr v-for="tx in txs" :key="tx.hash" class="hover:bg-slate-700/30 transition-colors">
|
||||
<td class="px-6 py-4 font-mono text-blue-400">{{ tx.hash }}</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="px-2 py-1 rounded text-xs border" :class="getTypeClass(tx.type)">{{ tx.type }}</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 font-mono truncate max-w-[100px]">{{ tx.from }}</td>
|
||||
<td class="px-6 py-4 font-mono truncate max-w-[100px]">{{ tx.to }}</td>
|
||||
<td class="px-6 py-4 text-green-400 text-xs">Success</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const stats = ref([
|
||||
{ label: 'Block Height', value: '12,403,921' },
|
||||
{ label: 'Total Txs', value: '89,201,332' },
|
||||
{ label: 'Nodes Active', value: '4/4' },
|
||||
{ label: 'Avg Block Time', value: '1.02s' },
|
||||
])
|
||||
|
||||
const blocks = ref([
|
||||
{ height: 12403921, hash: '0x8f2a...9b1c', txs: 12, time: '2s' },
|
||||
{ height: 12403920, hash: '0x3d4e...1f9a', txs: 45, time: '3s' },
|
||||
{ height: 12403919, hash: '0x7a8b...2c3d', txs: 8, time: '4s' },
|
||||
{ height: 12403918, hash: '0x1c2d...3e4f', txs: 156, time: '5s' },
|
||||
{ height: 12403917, hash: '0x5g6h...7i8j', txs: 23, time: '6s' },
|
||||
])
|
||||
|
||||
const txs = ref([
|
||||
{ hash: '0x8f2a...9b1c', type: 'Evidence', from: 'Robot-082', to: 'Contract:Evidence' },
|
||||
{ hash: '0x3d4e...1f9a', type: 'TokenTransfer', from: 'Family-User', to: 'Agency-Wallet' },
|
||||
{ hash: '0x7a8b...2c3d', type: 'Alert', from: 'Robot-015', to: 'Contract:Alert' },
|
||||
{ hash: '0x9c0d...1e2f', type: 'Evidence', from: 'Robot-099', to: 'Contract:Evidence' },
|
||||
])
|
||||
|
||||
const getTypeClass = (type) => {
|
||||
if (type === 'Evidence') return 'bg-blue-900/30 text-blue-300 border-blue-800'
|
||||
if (type === 'Alert') return 'bg-red-900/30 text-red-300 border-red-800'
|
||||
return 'bg-green-900/30 text-green-300 border-green-800'
|
||||
}
|
||||
</script>
|
||||
137
src/views/client/Dashboard.vue
Normal file
137
src/views/client/Dashboard.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- Top Stats -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="bg-white p-4 rounded-xl shadow-sm border border-gray-100 flex items-center space-x-4">
|
||||
<div class="p-3 bg-orange-100 text-vitality-orange rounded-full">
|
||||
❤️
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-gray-500">今日健康分</div>
|
||||
<div class="text-xl font-bold text-gray-800">92</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white p-4 rounded-xl shadow-sm border border-gray-100 flex items-center space-x-4">
|
||||
<div class="p-3 bg-blue-100 text-trust-blue rounded-full">
|
||||
🛡️
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-gray-500">隐私保护</div>
|
||||
<div class="text-xl font-bold text-gray-800">开启</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Live Status Card -->
|
||||
<div class="bg-white rounded-2xl shadow-lg border overflow-hidden transition-all duration-300"
|
||||
:class="statusCardClass">
|
||||
<div class="p-4 border-b border-gray-100 flex justify-between items-center">
|
||||
<h3 class="font-bold flex items-center" :class="statusTextClass">
|
||||
<span class="w-2 h-2 rounded-full mr-2 animate-pulse" :class="statusDotClass"></span>
|
||||
实时状态:王大爷
|
||||
</h3>
|
||||
<span class="text-xs bg-gray-100 text-gray-500 px-2 py-1 rounded">实时同步中</span>
|
||||
</div>
|
||||
|
||||
<div class="p-6 flex flex-col items-center">
|
||||
<!-- Status Icon -->
|
||||
<div class="w-32 h-32 rounded-full flex items-center justify-center mb-4 relative transition-all duration-500"
|
||||
:class="iconBgClass">
|
||||
<div class="absolute inset-0 border-4 rounded-full animate-ping opacity-20" :class="iconBorderClass"></div>
|
||||
<span class="text-5xl">{{ statusIcon }}</span>
|
||||
</div>
|
||||
<h4 class="text-xl font-bold text-gray-900">{{ statusTitle }}</h4>
|
||||
<p class="text-sm text-gray-500 mt-1" :class="{'text-red-500 font-bold': isCritical}">
|
||||
{{ statusDesc }}
|
||||
</p>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="grid grid-cols-3 gap-4 w-full mt-8">
|
||||
<button class="flex flex-col items-center justify-center p-3 rounded-xl bg-gray-50 hover:bg-orange-50 text-gray-600 hover:text-vitality-orange transition-colors">
|
||||
<span class="text-xl mb-1">📞</span>
|
||||
<span class="text-xs">语音通话</span>
|
||||
</button>
|
||||
<button class="flex flex-col items-center justify-center p-3 rounded-xl bg-gray-50 hover:bg-orange-50 text-gray-600 hover:text-vitality-orange transition-colors">
|
||||
<span class="text-xl mb-1">💊</span>
|
||||
<span class="text-xs">提醒吃药</span>
|
||||
</button>
|
||||
<button class="flex flex-col items-center justify-center p-3 rounded-xl bg-gray-50 hover:bg-orange-50 text-gray-600 hover:text-vitality-orange transition-colors">
|
||||
<span class="text-xl mb-1">🤖</span>
|
||||
<span class="text-xs">召唤机器人</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blockchain Evidence List -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100">
|
||||
<div class="p-4 border-b border-gray-100">
|
||||
<h3 class="font-bold text-gray-800">最近可信存证</h3>
|
||||
</div>
|
||||
<div class="divide-y divide-gray-100">
|
||||
<div v-for="item in store.auditLogs" :key="item.hash" class="p-4 hover:bg-gray-50 transition-colors">
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="flex items-start space-x-3">
|
||||
<div class="mt-1 p-1.5 rounded bg-blue-50 text-trust-blue text-xs">
|
||||
🔗
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-medium text-gray-900">{{ item.desc }}</div>
|
||||
<div class="text-xs text-gray-500 mt-0.5">{{ item.time }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="text-xs font-mono text-gray-400 bg-gray-100 px-1.5 py-0.5 rounded truncate w-24">
|
||||
{{ item.hash }}
|
||||
</div>
|
||||
<div class="text-[10px] text-green-600 mt-1">已上链确认</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-3 text-center border-t border-gray-100">
|
||||
<button class="text-sm text-trust-blue hover:text-blue-700 font-medium">查看完整区块链浏览器 ></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { useGlobalStore } from '../../stores/global'
|
||||
|
||||
const store = useGlobalStore()
|
||||
|
||||
// Computed Status based on Global Store
|
||||
const isCritical = computed(() => store.robotStatus.level === 'L4')
|
||||
|
||||
const statusCardClass = computed(() => isCritical.value ? 'border-red-500 shadow-red-100' : 'border-gray-100')
|
||||
const statusTextClass = computed(() => isCritical.value ? 'text-red-600' : 'text-gray-800')
|
||||
const statusDotClass = computed(() => {
|
||||
const map = { L1: 'bg-green-500', L2: 'bg-yellow-500', L3: 'bg-orange-500', L4: 'bg-red-600' }
|
||||
return map[store.robotStatus.level]
|
||||
})
|
||||
|
||||
const iconBgClass = computed(() => isCritical.value ? 'bg-red-50' : 'bg-orange-50')
|
||||
const iconBorderClass = computed(() => isCritical.value ? 'border-red-200' : 'border-orange-100')
|
||||
|
||||
const statusIcon = computed(() => {
|
||||
const map = { L1: '🚶', L2: '🛌', L3: '🗣️', L4: '🆘' }
|
||||
return map[store.robotStatus.level]
|
||||
})
|
||||
|
||||
const statusTitle = computed(() => {
|
||||
const map = { L1: '正在客厅散步', L2: '疑似静止不动', L3: '检测到呼救声', L4: '检测到跌倒!' }
|
||||
return map[store.robotStatus.level]
|
||||
})
|
||||
|
||||
const statusDesc = computed(() => {
|
||||
const map = {
|
||||
L1: 'AI 识别置信度: 99.2%',
|
||||
L2: '已持续 3 小时,建议关注',
|
||||
L3: '正在分析音频语义...',
|
||||
L4: '紧急!证据已上链,请立即处理!'
|
||||
}
|
||||
return map[store.robotStatus.level]
|
||||
})
|
||||
</script>
|
||||
52
src/views/client/TimeBank.vue
Normal file
52
src/views/client/TimeBank.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- Balance Card -->
|
||||
<div class="bg-gradient-to-r from-orange-400 to-pink-500 rounded-2xl p-6 text-white shadow-lg relative overflow-hidden">
|
||||
<div class="absolute -right-6 -top-6 w-32 h-32 bg-white opacity-10 rounded-full blur-2xl"></div>
|
||||
<div class="relative z-10">
|
||||
<div class="text-sm opacity-90 mb-1">我的智护通证 (Token)</div>
|
||||
<div class="text-4xl font-bold font-mono tracking-tight">1,250.00</div>
|
||||
<div class="mt-6 flex space-x-3">
|
||||
<button class="flex-1 bg-white/20 hover:bg-white/30 backdrop-blur py-2 rounded-lg text-sm font-medium transition-colors">
|
||||
兑换服务
|
||||
</button>
|
||||
<button class="flex-1 bg-white text-orange-600 hover:bg-orange-50 py-2 rounded-lg text-sm font-medium transition-colors shadow-sm">
|
||||
去赚取
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tasks List -->
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-gray-800 mb-4 px-1">社区互助任务</h3>
|
||||
<div class="space-y-3">
|
||||
<div v-for="task in tasks" :key="task.id" class="bg-white p-4 rounded-xl shadow-sm border border-gray-100 flex justify-between items-center">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center text-xl">
|
||||
{{ task.icon }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium text-gray-900">{{ task.title }}</div>
|
||||
<div class="text-xs text-gray-500">{{ task.distance }} • {{ task.time }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-end">
|
||||
<span class="text-vitality-orange font-bold text-lg">+{{ task.reward }}</span>
|
||||
<button class="px-3 py-1 bg-gray-900 text-white text-xs rounded-md mt-1 hover:bg-gray-700">接单</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const tasks = ref([
|
||||
{ id: 1, title: '陪伴李奶奶聊天', icon: '👵', distance: '300m', time: '30分钟', reward: 50 },
|
||||
{ id: 2, title: '协助购买日用品', icon: '🛒', distance: '500m', time: '1小时', reward: 100 },
|
||||
{ id: 3, title: '教张大爷使用手机', icon: '📱', distance: '1.2km', time: '45分钟', reward: 80 },
|
||||
])
|
||||
</script>
|
||||
232
src/views/robot/Monitor.vue
Normal file
232
src/views/robot/Monitor.vue
Normal file
@@ -0,0 +1,232 @@
|
||||
<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">REC</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
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Visual Area (Simulated Pose Estimation) -->
|
||||
<div class="w-full h-full flex items-center justify-center relative overflow-hidden group">
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
</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>
|
||||
|
||||
<!-- 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">
|
||||
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 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>
|
||||
</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 bg-green-500"></div>
|
||||
<span class="text-sm text-gray-300">目标检测 (YOLOv8)</span>
|
||||
<span class="ml-auto 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>
|
||||
<span class="text-sm text-gray-300">姿态分析 (PosePipe)</span>
|
||||
<span class="ml-auto text-xs" :class="poseTextColor">{{ poseStatus }}</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>
|
||||
<span class="text-sm text-gray-300">风险评估 (RiskEval)</span>
|
||||
<span class="ml-auto text-xs" :class="riskTextColor">{{ riskScore }}</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 Status:</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>
|
||||
</div>
|
||||
<div class="text-xs font-mono text-gray-500">
|
||||
Last Hash: {{ lastHash }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useDateFormat, useNow } from '@vueuse/core'
|
||||
import { useGlobalStore } from '../../stores/global'
|
||||
|
||||
const store = useGlobalStore()
|
||||
|
||||
// State
|
||||
const currentLevel = computed(() => store.robotStatus.level)
|
||||
const fps = ref(30)
|
||||
const latency = ref(12)
|
||||
const logs = ref([])
|
||||
const confidence = ref(98)
|
||||
|
||||
// Mock Data Generators
|
||||
const lastHash = ref(store.robotStatus.hash || 'Waiting...')
|
||||
|
||||
// ... (Computed Styles & Status - No changes needed as they depend on currentLevel)
|
||||
|
||||
// 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('System state normalized.', 'success')
|
||||
} else if (level === 'L2') {
|
||||
store.triggerAlert('L2', '检测到长时间静止')
|
||||
addLog('Anomaly detected: No motion for 180s.', 'warning')
|
||||
addLog('Initiating approach sequence...', 'info')
|
||||
} else if (level === 'L3') {
|
||||
store.triggerAlert('L3', '检测到呼救声')
|
||||
addLog('Audio event detected: "Help"', 'warning')
|
||||
addLog('Risk score escalated to 0.78', 'warning')
|
||||
} else if (level === 'L4') {
|
||||
store.triggerAlert('L4', '检测到跌倒事件')
|
||||
addLog('CRITICAL EVENT: FALL DETECTED', 'error')
|
||||
addLog('Generating privacy-preserving hash...', 'info')
|
||||
setTimeout(() => {
|
||||
lastHash.value = store.robotStatus.hash
|
||||
addLog(`Evidence committed to blockchain: ${lastHash.value}`, 'success')
|
||||
addLog('Emergency contact (120) notified via API', 'error')
|
||||
}, 800)
|
||||
}
|
||||
}
|
||||
|
||||
// Loop for background logs
|
||||
let interval
|
||||
onMounted(() => {
|
||||
addLog('System initialized. Model: YOLOv8-Nano loaded.', 'success')
|
||||
addLog('Privacy Engine: Active (Local Only).', 'success')
|
||||
|
||||
interval = setInterval(() => {
|
||||
fps.value = Math.floor(Math.random() * 5) + 28
|
||||
latency.value = Math.floor(Math.random() * 5) + 10
|
||||
if (currentLevel.value === 'L1' && Math.random() > 0.8) {
|
||||
addLog('Routine scan complete. No objects found.', 'info')
|
||||
}
|
||||
}, 2000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(interval)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.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>
|
||||
Reference in New Issue
Block a user