LLM-test/other/数据看板边框.html

823 lines
28 KiB
HTML
Raw Normal View History

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