<template>
|
<div class="container">
|
<div class="dashboard">
|
<div class="card">
|
<div class="card-header">
|
<h2 class="card-title">设备概览</h2>
|
<i class="fas fa-microchip card-icon"></i>
|
</div>
|
<div class="stats-grid">
|
<div class="stat-item">
|
<div class="stat-label">设备状态</div>
|
<div class="stat-value">{{ deviceStatus.online ? '在线' : '离线' }}</div>
|
<div class="stat-label">{{ deviceStatus.online ? '运行正常' : '设备异常' }}</div>
|
</div>
|
<div class="stat-item">
|
<div class="stat-label">注册人数</div>
|
<div class="stat-value">{{ deviceStats.recognitions }}</div>
|
<div class="stat-label">人次</div>
|
</div>
|
<div class="stat-item">
|
<div class="stat-label">白名单数</div>
|
<div class="stat-value">{{ whitelist.face }} / {{ whitelist.password }} / {{ whitelist.card }}</div>
|
<div class="stat-label">人脸/密码/卡片</div>
|
</div>
|
<div class="stat-item">
|
<div class="stat-label">通行记录总数</div>
|
<div class="stat-value">{{ systemStatus.memory }}</div>
|
<div class="stat-label">条数</div>
|
</div>
|
</div>
|
</div>
|
|
<div class="card">
|
<div class="card-header">
|
<h2 class="card-title">CPU使用率</h2>
|
<i class="fas fa-tachometer-alt card-icon"></i>
|
</div>
|
<div class="chart-container" id="cpuChart"></div>
|
</div>
|
|
<div class="card">
|
<div class="card-header">
|
<h2 class="card-title">内存使用率</h2>
|
<i class="fas fa-memory card-icon"></i>
|
</div>
|
<div class="chart-container" id="memoryChart"></div>
|
</div>
|
</div>
|
|
<div class="main-content">
|
<div class="card">
|
<div class="card-header">
|
<h2 class="card-title">设备运行日志</h2>
|
<i class="fas fa-clipboard-list card-icon"></i>
|
</div>
|
<div class="logs-container">
|
<div v-for="log in logs" :key="log.id" :class="['log-item', log.type]">
|
<div class="log-time">{{ log.time }}</div>
|
<div class="log-message">{{ log.message }}</div>
|
</div>
|
</div>
|
</div>
|
|
<div class="card">
|
<div class="card-header">
|
<h2 class="card-title">设备信息</h2>
|
<i class="fas fa-server card-icon"></i>
|
</div>
|
<div class="device-info-container">
|
<div class="device-status">
|
<div :class="['status-indicator', deviceStatus.online ? 'status-online pulse' : 'status-offline']"></div>
|
<div class="device-details">
|
<!-- <div class="device-name">{{ deviceInfo.name }}</div> -->
|
<div class="device-id">设备SN: {{ deviceInfo.id }} | IP: {{ deviceInfo.ip }}</div>
|
</div>
|
</div>
|
|
<div class="info-grid">
|
<div class="info-item">
|
<div class="info-label">设备型号</div>
|
<div class="info-value">{{ deviceInfo.model }}</div>
|
</div>
|
<div class="info-item">
|
<div class="info-label">固件版本</div>
|
<div class="info-value">{{ deviceInfo.firmware }}</div>
|
</div>
|
<div class="info-item">
|
<div class="info-label">运行时间</div>
|
<div class="info-value">{{ deviceInfo.uptime }}</div>
|
</div>
|
<div class="info-item">
|
<div class="info-label">存储空间</div>
|
<div class="info-value">{{ deviceInfo.storage }}</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
<script>
|
import * as echarts from "echarts";
|
export default {
|
data () {
|
return {
|
currentTime: '',
|
deviceStatus: {
|
online: true
|
},
|
userCount: 888,
|
whitelist: {
|
face: 99,
|
password: 88,
|
card: 66
|
},
|
deviceStats: {
|
recognitions: 1247
|
},
|
systemStatus: {
|
cpu: 45,
|
memory: 62
|
},
|
deviceInfo: {
|
name: '人脸识别终端-A01',
|
id: 'FRD-2023-A01',
|
ip: '192.168.1.101',
|
model: 'FRD-X3000',
|
firmware: 'v2.5.3',
|
uptime: '15天 8小时 32分钟',
|
storage: '78% (128GB/164GB)',
|
network: '稳定',
|
lastMaintenance: '2023-10-28'
|
},
|
logs: [
|
{ id: 1, time: '2023-11-15 14:23:45', message: '人脸识别成功 - 用户: 张三', type: 'normal' },
|
{ id: 2, time: '2023-11-15 14:22:30', message: 'CPU使用率超过80%', type: 'warning' },
|
{ id: 3, time: '2023-11-15 14:21:15', message: '网络连接短暂中断,已恢复', type: 'error' },
|
{ id: 4, time: '2023-11-15 14:20:05', message: '系统重启完成', type: 'normal' },
|
{ id: 5, time: '2023-11-15 14:19:50', message: '内存使用率超过阈值', type: 'warning' },
|
{ id: 6, time: '2023-11-15 14:18:30', message: '识别引擎更新完成', type: 'normal' },
|
{ id: 7, time: '2023-11-15 14:17:15', message: '人脸识别成功 - 用户: 李四', type: 'normal' },
|
{ id: 8, time: '2023-11-15 14:16:20', message: '数据库备份完成', type: 'normal' }
|
],
|
cpuChart: null,
|
memoryChart: null,
|
cpuData: [],
|
memoryData: [],
|
timeData: []
|
}
|
},
|
mounted () {
|
this.updateTime();
|
setInterval(this.updateTime, 1000);
|
this.initCharts();
|
this.simulateDataUpdate();
|
// 模拟实时数据更新
|
setInterval(this.updateCharts, 2000);
|
setInterval(this.addRandomLog, 5000);
|
setInterval(this.updateDeviceStats, 3000);
|
},
|
methods: {
|
updateTime () {
|
const now = new Date();
|
this.currentTime = now.toLocaleString('zh-CN', {
|
year: 'numeric',
|
month: '2-digit',
|
day: '2-digit',
|
hour: '2-digit',
|
minute: '2-digit',
|
second: '2-digit',
|
hour12: false
|
});
|
},
|
initCharts () {
|
// 初始化CPU图表
|
this.cpuChart = echarts.init(document.getElementById('cpuChart'));
|
const cpuOption = {
|
tooltip: {
|
trigger: 'axis',
|
formatter: '{b}<br/>CPU: {c}%'
|
},
|
grid: {
|
top: '15%',
|
left: '3%',
|
right: '4%',
|
bottom: '10%',
|
containLabel: true
|
},
|
xAxis: {
|
type: 'category',
|
boundaryGap: false,
|
data: this.timeData,
|
axisLine: {
|
lineStyle: {
|
color: '#a0a0ff'
|
}
|
}
|
},
|
yAxis: {
|
type: 'value',
|
max: 100,
|
axisLine: {
|
lineStyle: {
|
color: '#a0a0ff'
|
}
|
},
|
splitLine: {
|
lineStyle: {
|
color: 'rgba(160, 160, 255, 0.1)'
|
}
|
}
|
},
|
series: [{
|
name: 'CPU使用率',
|
type: 'line',
|
smooth: true,
|
symbol: 'circle',
|
symbolSize: 8,
|
lineStyle: {
|
width: 3,
|
color: '#1890ff'
|
},
|
itemStyle: {
|
color: '#1890ff',
|
borderColor: '#fff',
|
borderWidth: 2
|
},
|
areaStyle: {
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
{ offset: 0, color: 'rgba(24, 144, 255, 0.5)' },
|
{ offset: 1, color: 'rgba(24, 144, 255, 0.1)' }
|
])
|
},
|
data: this.cpuData
|
}]
|
};
|
this.cpuChart.setOption(cpuOption);
|
|
// 初始化内存图表
|
this.memoryChart = echarts.init(document.getElementById('memoryChart'));
|
const memoryOption = {
|
tooltip: {
|
formatter: '{a} <br/>{b} : {c}%'
|
},
|
series: [
|
{
|
name: '内存使用率',
|
type: 'gauge',
|
radius: '90%',
|
center: ['50%', '60%'],
|
progress: {
|
show: true,
|
width: 20,
|
itemStyle: {
|
color: {
|
type: 'linear',
|
x: 0,
|
y: 0,
|
x2: 0,
|
y2: 1,
|
colorStops: [{
|
offset: 0, color: '#40e0d0'
|
}, {
|
offset: 1, color: '#ff0080'
|
}]
|
}
|
}
|
},
|
axisLine: {
|
lineStyle: {
|
width: 20,
|
color: [
|
[0.3, '#40e0d0'],
|
[0.7, '#ff8c00'],
|
[1, '#ff0080']
|
]
|
}
|
},
|
axisTick: {
|
distance: -20,
|
length: 8,
|
lineStyle: {
|
color: '#fff',
|
width: 2
|
}
|
},
|
splitLine: {
|
distance: -20,
|
length: 20,
|
lineStyle: {
|
color: '#fff',
|
width: 3
|
}
|
},
|
axisLabel: {
|
distance: -20,
|
color: '#fff',
|
fontSize: 14
|
},
|
anchor: {
|
show: true,
|
size: 15,
|
showAbove: true,
|
itemStyle: {
|
borderWidth: 4,
|
borderColor: '#1890ff'
|
}
|
},
|
detail: {
|
valueAnimation: true,
|
formatter: '{value}%',
|
color: '#fff',
|
fontSize: 24,
|
offsetCenter: [0, '30%']
|
},
|
title: {
|
// 单独设置名称的样式
|
show: true,
|
offsetCenter: [0, '50%'], // 名称位置 [水平偏移, 垂直偏移]
|
color: '#fff', // 名称颜色改为白色
|
fontSize: 12,
|
fontWeight: 'bold'
|
},
|
data: [
|
{
|
value: this.systemStatus.memory,
|
name: '已用/总(M):353/780'
|
}
|
]
|
}
|
]
|
};
|
this.memoryChart.setOption(memoryOption);
|
},
|
simulateDataUpdate () {
|
// 初始化一些模拟数据 - 使用真实时间
|
const now = new Date();
|
for (let i = 10; i >= 0; i--) {
|
const time = new Date(now.getTime() - i * 2000);
|
this.timeData.push(this.formatTime(time));
|
this.cpuData.push(Math.floor(Math.random() * 30) + 30);
|
this.memoryData.push(Math.floor(Math.random() * 20) + 50);
|
}
|
},
|
updateCharts () {
|
// 更新CPU图表数据 - 使用当前真实时间
|
const now = new Date();
|
const currentTime = this.formatTime(now);
|
|
this.timeData.push(currentTime);
|
this.timeData.shift();
|
|
const newCpuValue = Math.floor(Math.random() * 30) + 30;
|
this.cpuData.push(newCpuValue);
|
this.cpuData.shift();
|
this.systemStatus.cpu = newCpuValue;
|
|
// 更新内存图表数据
|
const newMemoryValue = Math.floor(Math.random() * 20) + 50;
|
this.systemStatus.memory = newMemoryValue;
|
|
this.cpuChart.setOption({
|
xAxis: {
|
data: this.timeData
|
},
|
series: [{
|
data: this.cpuData
|
}]
|
});
|
|
this.memoryChart.setOption({
|
series: [{
|
data: [{
|
value: newMemoryValue,
|
name: '已用/总(M):353/780'
|
}]
|
}]
|
});
|
},
|
// 格式化时间为 HH:MM:SS 格式
|
formatTime (date) {
|
const hours = date.getHours().toString().padStart(2, '0');
|
const minutes = date.getMinutes().toString().padStart(2, '0');
|
const seconds = date.getSeconds().toString().padStart(2, '0');
|
return `${hours}:${minutes}:${seconds}`;
|
},
|
addRandomLog () {
|
const logTypes = ['normal', 'warning', 'error'];
|
const messages = [
|
'人脸识别成功 - 用户: 王五',
|
'设备温度异常',
|
'网络连接恢复',
|
'存储空间不足警告',
|
'识别准确率下降',
|
'系统备份完成',
|
'安全策略已更新',
|
'设备固件升级可用',
|
'数据库优化完成',
|
'人脸库更新完成'
|
];
|
|
const now = new Date();
|
const timeStr = now.getFullYear() + '-' +
|
(now.getMonth() + 1).toString().padStart(2, '0') + '-' +
|
now.getDate().toString().padStart(2, '0') + ' ' +
|
now.getHours().toString().padStart(2, '0') + ':' +
|
now.getMinutes().toString().padStart(2, '0') + ':' +
|
now.getSeconds().toString().padStart(2, '0');
|
|
const randomType = logTypes[Math.floor(Math.random() * logTypes.length)];
|
const randomMessage = messages[Math.floor(Math.random() * messages.length)];
|
|
this.logs.unshift({
|
id: this.logs.length + 1,
|
time: timeStr,
|
message: randomMessage,
|
type: randomType
|
});
|
|
// 保持日志数量不超过15条
|
if (this.logs.length > 15) {
|
this.logs.pop();
|
}
|
},
|
updateDeviceStats () {
|
// 随机增加识别次数
|
if (Math.random() > 0.7) {
|
this.deviceStats.recognitions += Math.floor(Math.random() * 3) + 1;
|
}
|
|
// 随机改变设备在线状态(小概率)
|
if (Math.random() > 0.95) {
|
this.deviceStatus.online = !this.deviceStatus.online;
|
}
|
},
|
beforeDestroy () {
|
if (this.cpuChart) {
|
this.cpuChart.dispose();
|
}
|
if (this.memoryChart) {
|
this.memoryChart.dispose();
|
}
|
}
|
}
|
};
|
</script>
|
<style lang="less">
|
.container {
|
max-width: 1400px;
|
margin: 0 auto;
|
padding: 20px;
|
}
|
|
.dashboard {
|
display: grid;
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
gap: 25px;
|
margin-bottom: 30px;
|
}
|
|
.card {
|
background: rgba(16, 16, 48, 0.7);
|
border-radius: 15px;
|
padding: 25px;
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
border: 1px solid rgba(64, 224, 208, 0.2);
|
transition: all 0.3s ease;
|
position: relative;
|
overflow: hidden;
|
}
|
|
.card::before {
|
content: '';
|
position: absolute;
|
top: 0;
|
left: 0;
|
width: 100%;
|
height: 4px;
|
background: linear-gradient(90deg, var(--themeColor), #ff8c00, #ff0080);
|
}
|
|
.card:hover {
|
transform: translateY(-10px);
|
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.4);
|
border-color: rgba(24, 144, 255, 0.5);
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 20px;
|
}
|
|
.card-title {
|
font-size: 20px;
|
font-weight: 600;
|
color: var(--themeColor);
|
}
|
|
.card-icon {
|
font-size: 24px;
|
color: #ff8c00;
|
}
|
|
.stats-grid {
|
display: grid;
|
grid-template-columns: repeat(2, 1fr);
|
gap: 15px;
|
}
|
|
.stat-item {
|
text-align: center;
|
padding: 15px;
|
background: rgba(32, 32, 64, 0.5);
|
border-radius: 10px;
|
transition: all 0.3s ease;
|
}
|
|
.stat-item:hover {
|
background: rgba(32, 32, 64, 0.8);
|
transform: scale(1.05);
|
}
|
|
.stat-value {
|
font-size: 24px;
|
font-weight: 700;
|
margin: 10px 0;
|
color: #ff8c00;
|
}
|
|
.stat-label {
|
font-size: 14px;
|
color: #a0a0ff;
|
}
|
|
.chart-container {
|
height: 300px;
|
margin-top: 10px;
|
}
|
|
.main-content {
|
display: grid;
|
grid-template-columns: 2fr 1fr;
|
gap: 25px;
|
}
|
|
.logs-container {
|
max-height: 400px;
|
overflow-y: auto;
|
padding-right: 10px;
|
}
|
|
.logs-container::-webkit-scrollbar {
|
width: 8px;
|
}
|
|
.logs-container::-webkit-scrollbar-track {
|
background: rgba(32, 32, 64, 0.5);
|
border-radius: 10px;
|
}
|
|
.logs-container::-webkit-scrollbar-thumb {
|
background: linear-gradient(180deg, var(--themeColor), #ff8c00);
|
border-radius: 10px;
|
}
|
|
.log-item {
|
padding: 15px;
|
margin-bottom: 15px;
|
background: rgba(32, 32, 64, 0.5);
|
border-radius: 10px;
|
border-left: 4px solid var(--themeColor);;
|
transition: all 0.3s ease;
|
}
|
|
.log-item:hover {
|
background: rgba(32, 32, 64, 0.8);
|
transform: translateX(5px);
|
}
|
|
.log-item.warning {
|
border-left-color: #ff8c00;
|
}
|
|
.log-item.error {
|
border-left-color: #ff0080;
|
}
|
|
.log-time {
|
font-size: 12px;
|
color: #a0a0ff;
|
margin-bottom: 5px;
|
}
|
|
.log-message {
|
font-size: 14px;
|
color: #ffffff;
|
}
|
|
.device-info-container {
|
display: flex;
|
flex-direction: column;
|
gap: 15px;
|
}
|
|
.device-status {
|
display: flex;
|
align-items: center;
|
padding: 15px;
|
background: rgba(32, 32, 64, 0.5);
|
border-radius: 10px;
|
transition: all 0.3s ease;
|
}
|
|
.device-status:hover {
|
background: rgba(32, 32, 64, 0.8);
|
transform: translateX(5px);
|
}
|
|
.status-indicator {
|
width: 12px;
|
height: 12px;
|
border-radius: 50%;
|
margin-right: 15px;
|
}
|
|
.status-online {
|
background: var(--themeColor);
|
box-shadow: 0 0 10px var(--themeColor);
|
}
|
|
.status-offline {
|
background: #ff0080;
|
box-shadow: 0 0 10px #ff0080;
|
}
|
|
.device-details {
|
flex: 1;
|
}
|
|
.device-name {
|
font-weight: 600;
|
margin-bottom: 5px;
|
font-size: 18px;
|
}
|
|
.device-id {
|
font-size: 14px;
|
color: #a0a0ff;
|
}
|
|
@media (max-width: 992px) {
|
.main-content {
|
grid-template-columns: 1fr;
|
}
|
}
|
|
@media (max-width: 768px) {
|
.dashboard {
|
grid-template-columns: 1fr;
|
}
|
|
header {
|
flex-direction: column;
|
gap: 15px;
|
}
|
}
|
|
.pulse {
|
animation: pulse 2s infinite;
|
}
|
|
@keyframes pulse {
|
0% {
|
box-shadow: 0 0 0 0 rgba(24, 144, 255, 0.7);
|
}
|
|
70% {
|
box-shadow: 0 0 0 10px rgba(64, 224, 208, 0);
|
}
|
|
100% {
|
box-shadow: 0 0 0 0 rgba(64, 224, 208, 0);
|
}
|
}
|
|
.glow {
|
text-shadow: 0 0 10px currentColor;
|
}
|
|
.info-grid {
|
display: grid;
|
grid-template-columns: repeat(2, 1fr);
|
gap: 15px;
|
margin-top: 15px;
|
}
|
|
.info-item {
|
padding: 12px;
|
background: rgba(32, 32, 64, 0.5);
|
border-radius: 8px;
|
font-size: 14px;
|
}
|
|
.info-label {
|
color: #a0a0ff;
|
margin-bottom: 5px;
|
}
|
|
.info-value {
|
font-weight: 600;
|
color: #ff8c00;
|
}
|
</style>
|