823 lines
28 KiB
HTML
823 lines
28 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>全球实时数据监控大屏</title>
|
|
<style>
|
|
:root {
|
|
--bg-color: #0b0f19;
|
|
--card-bg: rgba(20, 30, 48, 0.6);
|
|
--card-border: rgba(64, 224, 208, 0.3);
|
|
--text-main: #ffffff;
|
|
--text-muted: #8b9bb4;
|
|
--accent-cyan: #00f2ff;
|
|
--accent-purple: #bd00ff;
|
|
--accent-green: #00ff88;
|
|
--accent-orange: #ff9d00;
|
|
--font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
body {
|
|
background-color: var(--bg-color);
|
|
background-image:
|
|
radial-gradient(circle at 50% 50%, rgba(0, 40, 80, 0.3) 0%, transparent 70%),
|
|
linear-gradient(0deg, rgba(0,0,0,0.2) 1px, transparent 1px),
|
|
linear-gradient(90deg, rgba(0,0,0,0.2) 1px, transparent 1px);
|
|
background-size: 100% 100%, 40px 40px, 40px 40px;
|
|
color: var(--text-main);
|
|
font-family: var(--font-family);
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
/* --- Header --- */
|
|
header {
|
|
height: 70px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 0 30px;
|
|
border-bottom: 1px solid var(--card-border);
|
|
background: rgba(11, 15, 25, 0.9);
|
|
box-shadow: 0 0 20px rgba(0, 242, 255, 0.1);
|
|
z-index: 10;
|
|
}
|
|
|
|
.header-title {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
letter-spacing: 2px;
|
|
text-transform: uppercase;
|
|
background: linear-gradient(90deg, var(--accent-cyan), #fff);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
text-shadow: 0 0 10px rgba(0, 242, 255, 0.3);
|
|
}
|
|
|
|
.header-info {
|
|
display: flex;
|
|
gap: 20px;
|
|
font-family: 'Courier New', Courier, monospace;
|
|
color: var(--accent-cyan);
|
|
font-size: 14px;
|
|
}
|
|
|
|
.btn-fullscreen {
|
|
background: transparent;
|
|
border: 1px solid var(--accent-cyan);
|
|
color: var(--accent-cyan);
|
|
padding: 5px 15px;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
text-transform: uppercase;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.btn-fullscreen:hover {
|
|
background: var(--accent-cyan);
|
|
color: #000;
|
|
box-shadow: 0 0 15px var(--accent-cyan);
|
|
}
|
|
|
|
/* --- Main Layout --- */
|
|
main {
|
|
flex: 1;
|
|
padding: 20px;
|
|
display: grid;
|
|
grid-template-columns: 25% 50% 25%;
|
|
grid-template-rows: 15% 45% 40%;
|
|
gap: 20px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Responsive adjustments */
|
|
@media (max-width: 1200px) {
|
|
main {
|
|
grid-template-columns: 1fr 1fr;
|
|
grid-template-rows: auto;
|
|
overflow-y: auto;
|
|
}
|
|
.center-panel {
|
|
grid-column: span 2;
|
|
order: -1;
|
|
}
|
|
}
|
|
@media (max-width: 768px) {
|
|
main {
|
|
grid-template-columns: 1fr;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.center-panel {
|
|
height: 400px;
|
|
}
|
|
header {
|
|
padding: 0 15px;
|
|
}
|
|
.header-title { font-size: 18px; }
|
|
}
|
|
|
|
/* --- Cards & Panels --- */
|
|
.card {
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: relative;
|
|
backdrop-filter: blur(5px);
|
|
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
|
|
}
|
|
|
|
/* Decorative corners */
|
|
.card::before, .card::after {
|
|
content: '';
|
|
position: absolute;
|
|
width: 10px;
|
|
height: 10px;
|
|
border: 2px solid var(--accent-cyan);
|
|
transition: all 0.3s;
|
|
}
|
|
.card::before { top: -1px; left: -1px; border-right: none; border-bottom: none; }
|
|
.card::after { bottom: -1px; right: -1px; border-left: none; border-top: none; }
|
|
|
|
.card:hover::before, .card:hover::after {
|
|
width: 100%;
|
|
height: 100%;
|
|
border-color: rgba(0, 242, 255, 0.1);
|
|
z-index: -1;
|
|
}
|
|
|
|
.card-title {
|
|
font-size: 16px;
|
|
color: var(--text-main);
|
|
margin-bottom: 10px;
|
|
border-left: 3px solid var(--accent-cyan);
|
|
padding-left: 10px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
/* --- KPI Metrics (Top of Center) --- */
|
|
.kpi-container {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 20px;
|
|
grid-column: 2 / 3;
|
|
grid-row: 1 / 2;
|
|
}
|
|
|
|
.kpi-card {
|
|
background: linear-gradient(135deg, rgba(255,255,255,0.05), rgba(255,255,255,0.01));
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
padding: 15px;
|
|
border-radius: 6px;
|
|
text-align: center;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
}
|
|
|
|
.kpi-label { color: var(--text-muted); font-size: 12px; margin-bottom: 5px; }
|
|
.kpi-value {
|
|
font-size: 32px;
|
|
font-weight: bold;
|
|
font-family: 'Courier New', monospace;
|
|
}
|
|
.kpi-unit { font-size: 14px; margin-left: 2px; }
|
|
.c-cyan { color: var(--accent-cyan); text-shadow: 0 0 10px rgba(0, 242, 255, 0.4); }
|
|
.c-purple { color: var(--accent-purple); text-shadow: 0 0 10px rgba(189, 0, 255, 0.4); }
|
|
.c-green { color: var(--accent-green); text-shadow: 0 0 10px rgba(0, 255, 136, 0.4); }
|
|
|
|
/* --- Chart Containers --- */
|
|
.chart-wrapper {
|
|
flex: 1;
|
|
position: relative;
|
|
width: 100%;
|
|
min-height: 0; /* Important for flex/grid children */
|
|
}
|
|
|
|
canvas {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
/* --- Specific Panels --- */
|
|
/* Center Map Panel */
|
|
.center-map {
|
|
grid-column: 2 / 3;
|
|
grid-row: 2 / 3;
|
|
background: transparent;
|
|
border: none;
|
|
padding: 0;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.center-map-bg {
|
|
position: absolute;
|
|
top: 50%; left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
width: 80%;
|
|
height: 80%;
|
|
border: 1px dashed rgba(0, 242, 255, 0.2);
|
|
border-radius: 50%;
|
|
animation: rotate 60s linear infinite;
|
|
}
|
|
|
|
@keyframes rotate { from { transform: translate(-50%, -50%) rotate(0deg); } to { transform: translate(-50%, -50%) rotate(360deg); } }
|
|
|
|
/* Bottom Center: Traffic Line Chart */
|
|
.traffic-panel {
|
|
grid-column: 2 / 3;
|
|
grid-row: 3 / 4;
|
|
}
|
|
|
|
/* Left Side Panels */
|
|
.left-top { grid-column: 1 / 2; grid-row: 1 / 3; }
|
|
.left-bottom { grid-column: 1 / 2; grid-row: 3 / 4; }
|
|
|
|
/* Right Side Panels */
|
|
.right-top { grid-column: 3 / 4; grid-row: 1 / 3; }
|
|
.right-bottom { grid-column: 3 / 4; grid-row: 3 / 4; }
|
|
|
|
/* --- Scrolling List --- */
|
|
.scroll-list {
|
|
overflow: hidden;
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: relative;
|
|
}
|
|
|
|
.scroll-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 8px 0;
|
|
border-bottom: 1px solid rgba(255,255,255,0.05);
|
|
font-size: 13px;
|
|
animation: fadeIn 0.5s ease-out;
|
|
}
|
|
.scroll-item span:nth-child(2) { color: var(--accent-green); font-family: monospace; }
|
|
.scroll-item span:nth-child(3) { color: var(--text-muted); font-size: 12px; }
|
|
|
|
@keyframes fadeIn { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: translateX(0); } }
|
|
|
|
/* Tooltip style for canvas (custom div) */
|
|
#tooltip {
|
|
position: absolute;
|
|
background: rgba(0, 0, 0, 0.8);
|
|
border: 1px solid var(--accent-cyan);
|
|
padding: 5px 10px;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transition: opacity 0.2s;
|
|
z-index: 100;
|
|
box-shadow: 0 0 10px rgba(0,0,0,0.5);
|
|
}
|
|
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Header -->
|
|
<header>
|
|
<div class="header-title">GLOBAL DATA <span style="font-size:0.6em; color:var(--text-muted)">HUB</span></div>
|
|
<div class="header-info">
|
|
<span id="current-time">00:00:00</span>
|
|
<span>|</span>
|
|
<span id="system-status">System: Online</span>
|
|
</div>
|
|
<button class="btn-fullscreen" onclick="toggleFullScreen()">Full Screen</button>
|
|
</header>
|
|
|
|
<!-- Main Grid Layout -->
|
|
<main>
|
|
<!-- Left Column -->
|
|
<section class="card left-top">
|
|
<div class="card-title">
|
|
<span>销售类别占比</span>
|
|
<span style="font-size:12px; color:var(--accent-cyan)">Live</span>
|
|
</div>
|
|
<div class="chart-wrapper">
|
|
<canvas id="barChart"></canvas>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card left-bottom">
|
|
<div class="card-title">实时交易日志</div>
|
|
<div class="scroll-list" id="log-list">
|
|
<!-- JS will populate this -->
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Center Column -->
|
|
<div class="kpi-container">
|
|
<div class="kpi-card">
|
|
<div class="kpi-label">今日总访问量</div>
|
|
<div class="kpi-value c-cyan"><span id="kpi-1">0</span></div>
|
|
</div>
|
|
<div class="kpi-card">
|
|
<div class="kpi-label">实时交易额 (USD)</div>
|
|
<div class="kpi-value c-purple">$<span id="kpi-2">0</span></div>
|
|
</div>
|
|
<div class="kpi-card">
|
|
<div class="kpi-label">活跃节点</div>
|
|
<div class="kpi-value c-green"><span id="kpi-3">0</span></div>
|
|
</div>
|
|
</div>
|
|
|
|
<section class="center-map">
|
|
<div class="center-map-bg"></div>
|
|
<div class="chart-wrapper">
|
|
<canvas id="mapChart"></canvas>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card traffic-panel">
|
|
<div class="card-title">全网流量趋势监控</div>
|
|
<div class="chart-wrapper">
|
|
<canvas id="lineChart"></canvas>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Right Column -->
|
|
<section class="card right-top">
|
|
<div class="card-title">设备接入分布</div>
|
|
<div class="chart-wrapper">
|
|
<canvas id="pieChart"></canvas>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card right-bottom">
|
|
<div class="card-title">服务器负载</div>
|
|
<div class="chart-wrapper">
|
|
<canvas id="gaugeChart"></canvas>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<!-- Global Tooltip -->
|
|
<div id="tooltip"></div>
|
|
|
|
<script>
|
|
/**
|
|
* ------------------------------------------------------------------
|
|
* Utility Functions & State
|
|
* ------------------------------------------------------------------
|
|
*/
|
|
const tooltip = document.getElementById('tooltip');
|
|
|
|
function formatDate() {
|
|
const now = new Date();
|
|
return now.toLocaleTimeString('zh-CN', { hour12: false });
|
|
}
|
|
setInterval(() => document.getElementById('current-time').innerText = formatDate(), 1000);
|
|
|
|
// Random Integer Generator
|
|
const randomInt = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
|
|
|
|
/**
|
|
* ------------------------------------------------------------------
|
|
* Mini Chart Engine (Canvas)
|
|
* A lightweight library to draw charts without external dependencies.
|
|
* ------------------------------------------------------------------
|
|
*/
|
|
class MiniChart {
|
|
constructor(canvasId) {
|
|
this.canvas = document.getElementById(canvasId);
|
|
this.ctx = this.canvas.getContext('2d');
|
|
this.width = 0;
|
|
this.height = 0;
|
|
this.dpr = window.devicePixelRatio || 1;
|
|
this.resize();
|
|
|
|
window.addEventListener('resize', () => {
|
|
this.resize();
|
|
if(this.lastData) this.draw(this.lastData);
|
|
});
|
|
|
|
// Interaction
|
|
this.canvas.addEventListener('mousemove', (e) => this.handleMouseMove(e));
|
|
this.canvas.addEventListener('mouseleave', () => {
|
|
tooltip.style.opacity = 0;
|
|
});
|
|
}
|
|
|
|
resize() {
|
|
const rect = this.canvas.parentElement.getBoundingClientRect();
|
|
this.width = rect.width;
|
|
this.height = rect.height;
|
|
this.canvas.width = this.width * this.dpr;
|
|
this.canvas.height = this.height * this.dpr;
|
|
this.ctx.scale(this.dpr, this.dpr);
|
|
}
|
|
|
|
clear() {
|
|
this.ctx.clearRect(0, 0, this.width, this.height);
|
|
}
|
|
|
|
handleMouseMove(e) {
|
|
// To be implemented by subclasses
|
|
}
|
|
|
|
showTooltip(x, y, text) {
|
|
tooltip.style.left = (e => e.pageX + 10)(e) + 'px'; // simple closure access to event
|
|
tooltip.style.top = (e => e.pageY + 10)(e) + 'px';
|
|
tooltip.innerHTML = text;
|
|
tooltip.style.opacity = 1;
|
|
}
|
|
}
|
|
|
|
// 1. Bar Chart (Left Top)
|
|
class BarChart extends MiniChart {
|
|
draw(data) {
|
|
this.lastData = data;
|
|
this.clear();
|
|
const { ctx, width, height } = this;
|
|
const padding = 30;
|
|
const chartWidth = width - padding * 2;
|
|
const chartHeight = height - padding * 2;
|
|
const maxVal = Math.max(...data.values) * 1.2;
|
|
const barWidth = (chartWidth / data.values.length) * 0.5;
|
|
const spacing = (chartWidth / data.values.length);
|
|
|
|
// Draw Axes
|
|
ctx.beginPath();
|
|
ctx.strokeStyle = 'rgba(255,255,255,0.1)';
|
|
ctx.moveTo(padding, padding);
|
|
ctx.lineTo(padding, height - padding);
|
|
ctx.lineTo(width - padding, height - padding);
|
|
ctx.stroke();
|
|
|
|
// Draw Bars
|
|
data.values.forEach((val, i) => {
|
|
const barHeight = (val / maxVal) * chartHeight;
|
|
const x = padding + (i * spacing) + spacing/2 - barWidth/2;
|
|
const y = height - padding - barHeight;
|
|
|
|
// Gradient
|
|
const gradient = ctx.createLinearGradient(0, y, 0, height - padding);
|
|
gradient.addColorStop(0, '#00f2ff');
|
|
gradient.addColorStop(1, 'rgba(0, 242, 255, 0.1)');
|
|
|
|
ctx.fillStyle = gradient;
|
|
ctx.fillRect(x, y, barWidth, barHeight);
|
|
|
|
// Label
|
|
ctx.fillStyle = '#8b9bb4';
|
|
ctx.font = '10px Arial';
|
|
ctx.textAlign = 'center';
|
|
ctx.fillText(data.labels[i], x + barWidth/2, height - padding + 15);
|
|
});
|
|
}
|
|
|
|
handleMouseMove(e) {
|
|
// Simplified interaction logic
|
|
const rect = this.canvas.getBoundingClientRect();
|
|
const x = e.clientX - rect.left;
|
|
// Basic hit test logic could be added here
|
|
}
|
|
}
|
|
|
|
// 2. Line Chart (Center Bottom)
|
|
class LineChart extends MiniChart {
|
|
draw(data) {
|
|
this.lastData = data;
|
|
this.clear();
|
|
const { ctx, width, height } = this;
|
|
const padding = 30;
|
|
const chartW = width - padding * 2;
|
|
const chartH = height - padding * 2;
|
|
|
|
// Grid lines
|
|
ctx.strokeStyle = 'rgba(255,255,255,0.05)';
|
|
ctx.beginPath();
|
|
for(let i=0; i<5; i++) {
|
|
let y = padding + (i * (chartH/4));
|
|
ctx.moveTo(padding, y);
|
|
ctx.lineTo(width-padding, y);
|
|
}
|
|
ctx.stroke();
|
|
|
|
const maxVal = 100;
|
|
const stepX = chartW / (data.length - 1);
|
|
|
|
// Draw Path
|
|
ctx.beginPath();
|
|
ctx.strokeStyle = '#bd00ff';
|
|
ctx.lineWidth = 2;
|
|
|
|
data.forEach((val, i) => {
|
|
const x = padding + i * stepX;
|
|
const y = height - padding - (val / maxVal) * chartH;
|
|
if (i === 0) ctx.moveTo(x, y);
|
|
else ctx.lineTo(x, y);
|
|
});
|
|
ctx.stroke();
|
|
|
|
// Fill Area under line
|
|
ctx.lineTo(padding + (data.length - 1) * stepX, height - padding);
|
|
ctx.lineTo(padding, height - padding);
|
|
ctx.fillStyle = 'rgba(189, 0, 255, 0.1)';
|
|
ctx.fill();
|
|
|
|
// Draw Points
|
|
data.forEach((val, i) => {
|
|
const x = padding + i * stepX;
|
|
const y = height - padding - (val / maxVal) * chartH;
|
|
ctx.beginPath();
|
|
ctx.fillStyle = '#fff';
|
|
ctx.arc(x, y, 3, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
});
|
|
}
|
|
}
|
|
|
|
// 3. Donut Chart (Right Top)
|
|
class PieChart extends MiniChart {
|
|
draw(data) {
|
|
this.lastData = data;
|
|
this.clear();
|
|
const { ctx, width, height } = this;
|
|
const centerX = width / 2;
|
|
const centerY = height / 2;
|
|
const radius = Math.min(width, height) / 2 - 20;
|
|
const innerRadius = radius * 0.6;
|
|
|
|
const total = data.values.reduce((a, b) => a + b, 0);
|
|
let startAngle = -Math.PI / 2;
|
|
|
|
data.values.forEach((val, i) => {
|
|
const sliceAngle = (val / total) * 2 * Math.PI;
|
|
const endAngle = startAngle + sliceAngle;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(centerX, centerY); // Needed for correct arc fill sometimes, but arc handles it usually
|
|
ctx.arc(centerX, centerY, radius, startAngle, endAngle);
|
|
ctx.closePath();
|
|
|
|
ctx.fillStyle = data.colors[i];
|
|
ctx.fill();
|
|
|
|
// Inner Circle for Donut effect
|
|
ctx.globalCompositeOperation = 'destination-out';
|
|
ctx.beginPath();
|
|
ctx.arc(centerX, centerY, innerRadius, 0, 2*Math.PI);
|
|
ctx.fill();
|
|
ctx.globalCompositeOperation = 'source-over';
|
|
|
|
startAngle = endAngle;
|
|
});
|
|
|
|
// Center Text
|
|
ctx.fillStyle = '#fff';
|
|
ctx.font = 'bold 16px Arial';
|
|
ctx.textAlign = 'center';
|
|
ctx.textBaseline = 'middle';
|
|
ctx.fillText('Devices', centerX, centerY - 10);
|
|
ctx.fillStyle = '#8b9bb4';
|
|
ctx.font = '12px Arial';
|
|
ctx.fillText('100%', centerX, centerY + 10);
|
|
}
|
|
}
|
|
|
|
// 4. Map/Particle System (Center)
|
|
class MapChart extends MiniChart {
|
|
constructor(id) {
|
|
super(id);
|
|
this.particles = [];
|
|
this.initParticles();
|
|
}
|
|
|
|
initParticles() {
|
|
for(let i=0; i<60; i++) {
|
|
this.particles.push({
|
|
x: Math.random() * this.width,
|
|
y: Math.random() * this.height,
|
|
vx: (Math.random() - 0.5) * 0.5,
|
|
vy: (Math.random() - 0.5) * 0.5,
|
|
size: Math.random() * 2 + 1
|
|
});
|
|
}
|
|
}
|
|
|
|
animate() {
|
|
this.clear();
|
|
const { ctx, width, height } = this;
|
|
const centerX = width / 2;
|
|
const centerY = height / 2;
|
|
|
|
// Draw World Map Abstract (Dots)
|
|
ctx.fillStyle = 'rgba(0, 242, 255, 0.2)';
|
|
for(let i=0; i<20; i++) {
|
|
// Abstract shapes representing continents
|
|
let cx = centerX + Math.cos(i * 0.5) * (width/3);
|
|
let cy = centerY + Math.sin(i * 0.5) * (height/4);
|
|
ctx.beginPath();
|
|
ctx.arc(cx, cy, Math.random() * 20 + 10, 0, Math.PI*2);
|
|
ctx.fill();
|
|
}
|
|
|
|
// Animate Particles (Traffic)
|
|
this.particles.forEach(p => {
|
|
p.x += p.vx;
|
|
p.y += p.vy;
|
|
|
|
// Bounce
|
|
if(p.x < 0 || p.x > width) p.vx *= -1;
|
|
if(p.y < 0 || p.y > height) p.vy *= -1;
|
|
|
|
ctx.beginPath();
|
|
ctx.fillStyle = '#00ff88';
|
|
ctx.arc(p.x, p.y, p.size, 0, Math.PI*2);
|
|
ctx.fill();
|
|
|
|
// Draw connections
|
|
this.particles.forEach(p2 => {
|
|
const dist = Math.hypot(p.x - p2.x, p.y - p2.y);
|
|
if (dist < 50) {
|
|
ctx.beginPath();
|
|
ctx.strokeStyle = `rgba(0, 255, 136, ${1 - dist/50})`;
|
|
ctx.lineWidth = 0.5;
|
|
ctx.moveTo(p.x, p.y);
|
|
ctx.lineTo(p2.x, p2.y);
|
|
ctx.stroke();
|
|
}
|
|
});
|
|
});
|
|
|
|
requestAnimationFrame(() => this.animate());
|
|
}
|
|
}
|
|
|
|
// 5. Gauge/Meter (Right Bottom)
|
|
class GaugeChart extends MiniChart {
|
|
draw(percent) {
|
|
this.clear();
|
|
const { ctx, width, height } = this;
|
|
const cx = width / 2;
|
|
const cy = height * 0.8;
|
|
const radius = Math.min(width, height) / 2 - 10;
|
|
|
|
// Background Arc
|
|
ctx.beginPath();
|
|
ctx.arc(cx, cy, radius, Math.PI, 0);
|
|
ctx.strokeStyle = 'rgba(255,255,255,0.1)';
|
|
ctx.lineWidth = 15;
|
|
ctx.lineCap = 'round';
|
|
ctx.stroke();
|
|
|
|
// Value Arc
|
|
const endAngle = Math.PI + (Math.PI * (percent / 100));
|
|
ctx.beginPath();
|
|
ctx.arc(cx, cy, radius, Math.PI, endAngle);
|
|
|
|
// Color based on load
|
|
let color = '#00ff88';
|
|
if(percent > 60) color = '#ff9d00';
|
|
if(percent > 85) color = '#ff0055';
|
|
|
|
ctx.strokeStyle = color;
|
|
ctx.lineWidth = 15;
|
|
ctx.stroke();
|
|
|
|
// Text
|
|
ctx.fillStyle = '#fff';
|
|
ctx.textAlign = 'center';
|
|
ctx.font = 'bold 24px Courier New';
|
|
ctx.fillText(Math.floor(percent) + '%', cx, cy - 20);
|
|
ctx.font = '12px Arial';
|
|
ctx.fillStyle = '#8b9bb4';
|
|
ctx.fillText('CPU Load', cx, cy + 10);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ------------------------------------------------------------------
|
|
* Data Management & Simulation Logic
|
|
* ------------------------------------------------------------------
|
|
*/
|
|
|
|
// Initialize Charts
|
|
const barChart = new BarChart('barChart');
|
|
const lineChart = new LineChart('lineChart');
|
|
const pieChart = new PieChart('pieChart');
|
|
const mapChart = new MapChart('mapChart');
|
|
const gaugeChart = new GaugeChart('gaugeChart');
|
|
|
|
// Map Animation Loop
|
|
mapChart.animate();
|
|
|
|
// Initial Data
|
|
const categories = ['电子', '家居', '服饰', '美妆', '食品'];
|
|
const pieColors = ['#00f2ff', '#bd00ff', '#00ff88', '#ff9d00', '#fff'];
|
|
let lineData = Array(20).fill(0).map(() => randomInt(20, 50));
|
|
|
|
// KPI Values State
|
|
let kpi1 = 124500;
|
|
let kpi2 = 89300;
|
|
|
|
// 1. Update Bar Chart (Random every 3s)
|
|
function updateBar() {
|
|
const data = {
|
|
labels: categories,
|
|
values: categories.map(() => randomInt(10, 100))
|
|
};
|
|
barChart.draw(data);
|
|
}
|
|
|
|
// 2. Update Pie Chart (Rarely)
|
|
function updatePie() {
|
|
const data = {
|
|
labels: ['Mobile', 'Desktop', 'Tablet'],
|
|
values: [randomInt(30, 70), randomInt(20, 40), randomInt(5, 20)],
|
|
colors: pieColors
|
|
};
|
|
pieChart.draw(data);
|
|
}
|
|
|
|
// 3. Update Line Chart (Real-time push)
|
|
function updateLine() {
|
|
lineData.shift();
|
|
lineData.push(randomInt(20, 90));
|
|
lineChart.draw(lineData);
|
|
}
|
|
|
|
// 4. Update Gauge Chart
|
|
function updateGauge() {
|
|
const load = randomInt(20, 95);
|
|
gaugeChart.draw(load);
|
|
}
|
|
|
|
// 5. Update Log List
|
|
function addLogEntry() {
|
|
const list = document.getElementById('log-list');
|
|
const cities = ['New York', 'Beijing', 'London', 'Tokyo', 'Shanghai', 'Berlin'];
|
|
const city = cities[randomInt(0, cities.length-1)];
|
|
const price = randomInt(100, 5000);
|
|
|
|
const div = document.createElement('div');
|
|
div.className = 'scroll-item';
|
|
div.innerHTML = `
|
|
<span>Order #${randomInt(1000,9999)} [${city}]</span>
|
|
<span>+$${price}</span>
|
|
<span>Just now</span>
|
|
`;
|
|
|
|
list.prepend(div);
|
|
if(list.children.length > 8) list.lastElementChild.remove();
|
|
}
|
|
|
|
// 6. Update KPIs (Number Rolling Effect)
|
|
function updateKPIs() {
|
|
kpi1 += randomInt(10, 50);
|
|
kpi2 += randomInt(50, 200);
|
|
|
|
document.getElementById('kpi-1').innerText = kpi1.toLocaleString();
|
|
document.getElementById('kpi-2').innerText = kpi2.toLocaleString();
|
|
document.getElementById('kpi-3').innerText = randomInt(400, 450);
|
|
}
|
|
|
|
// --- Start Loops ---
|
|
setInterval(updateBar, 3000);
|
|
setInterval(updatePie, 10000);
|
|
setInterval(updateLine, 1000); // Fast update for line chart
|
|
setInterval(updateGauge, 2000);
|
|
setInterval(addLogEntry, 1500);
|
|
setInterval(updateKPIs, 1000);
|
|
|
|
// --- Full Screen Toggle ---
|
|
function toggleFullScreen() {
|
|
if (!document.fullscreenElement) {
|
|
document.documentElement.requestFullscreen();
|
|
} else {
|
|
if (document.exitFullscreen) {
|
|
document.exitFullscreen();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initial Draw Calls
|
|
updateBar();
|
|
updatePie();
|
|
updateLine();
|
|
updateGauge();
|
|
|
|
</script>
|
|
</body>
|
|
</html> |