first commit

This commit is contained in:
祀梦
2026-01-11 22:51:06 +08:00
commit 52f0ab6366
35 changed files with 4884 additions and 0 deletions

134
src/views/LandingPage.vue Normal file
View 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
View 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>

View 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>

View 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>

View 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>

View 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>

View 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
View 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>