init: 智慧农业大数据可视化控制中心 v0.1.0(Electron + React 19 + TS 重写版)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,217 @@
|
||||
<!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>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
width: 100vw; height: 100vh; overflow: hidden;
|
||||
font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
|
||||
color: #d7f2ff;
|
||||
background:
|
||||
radial-gradient(1200px 600px at 20% -10%, rgba(16,120,90,.28), transparent 60%),
|
||||
radial-gradient(1000px 500px at 90% 110%, rgba(10,90,140,.3), transparent 60%),
|
||||
linear-gradient(160deg, #07121d 0%, #0a1f2e 55%, #081827 100%);
|
||||
display: flex; flex-direction: column;
|
||||
}
|
||||
/* 顶部标题栏 */
|
||||
header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 14px 28px;
|
||||
background: linear-gradient(180deg, rgba(10,60,80,.55), rgba(6,20,32,.2));
|
||||
border-bottom: 1px solid rgba(64,224,180,.25);
|
||||
box-shadow: 0 4px 24px rgba(0,0,0,.35);
|
||||
}
|
||||
header h1 {
|
||||
font-size: 26px; letter-spacing: 4px; font-weight: 700;
|
||||
background: linear-gradient(90deg, #6ff7d0, #38d9a9, #7dd3fc);
|
||||
-webkit-background-clip: text; background-clip: text; color: transparent;
|
||||
text-shadow: 0 0 30px rgba(56,217,169,.25);
|
||||
}
|
||||
header .sub { font-size: 15px; color: #8fd9c0; }
|
||||
header .clock { font-size: 15px; color: #a9d8ec; font-family: Consolas, monospace; }
|
||||
|
||||
main {
|
||||
flex: 1; display: grid; gap: 14px; padding: 14px 18px 12px;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
grid-template-rows: auto auto 1fr;
|
||||
}
|
||||
.panel {
|
||||
position: relative; padding: 12px 14px;
|
||||
background: linear-gradient(180deg, rgba(18,52,72,.55), rgba(8,24,40,.45));
|
||||
border: 1px solid rgba(64,180,224,.22);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.panel::before {
|
||||
content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px;
|
||||
background: linear-gradient(90deg, transparent, #2dd4bf, transparent);
|
||||
}
|
||||
.panel-title {
|
||||
display: flex; align-items: center; gap: 8px; font-size: 15px; color: #8fe8ff;
|
||||
margin-bottom: 10px; letter-spacing: 2px;
|
||||
}
|
||||
.panel-title::before { content: ""; width: 4px; height: 14px; background: #2dd4bf; border-radius: 2px; }
|
||||
|
||||
/* 核心指标 */
|
||||
.stats { grid-column: span 4; display: flex; gap: 10px; align-items: center; }
|
||||
.stat {
|
||||
flex: 1; text-align: center; padding: 12px 4px;
|
||||
background: rgba(16,56,76,.45); border: 1px solid rgba(64,180,224,.18); border-radius: 6px;
|
||||
}
|
||||
.stat .num { font-size: 26px; font-weight: 700; color: #5ef0c0; font-family: Consolas, monospace; }
|
||||
.stat .lab { font-size: 12px; color: #9fb8c8; margin-top: 4px; }
|
||||
|
||||
/* 环境监测卡 */
|
||||
.env { grid-column: span 4; display: flex; gap: 10px; }
|
||||
.env .mini {
|
||||
flex: 1; text-align: center; padding: 10px 2px;
|
||||
background: rgba(16,56,76,.4); border: 1px solid rgba(64,180,224,.16); border-radius: 6px;
|
||||
}
|
||||
.env .mini .v { font-size: 20px; font-weight: 700; color: #ffd479; font-family: Consolas, monospace; }
|
||||
.env .mini .t { font-size: 11px; color: #93adbf; margin-top: 3px; }
|
||||
|
||||
/* 中下区域 */
|
||||
.mid { grid-column: span 4; }
|
||||
.chart { width: 100%; height: 150px; }
|
||||
|
||||
.list-row {
|
||||
display: flex; justify-content: space-between; padding: 6px 4px;
|
||||
font-size: 12px; border-bottom: 1px dashed rgba(80,160,200,.15); color: #bcd4e4;
|
||||
}
|
||||
.list-row .tag {
|
||||
padding: 1px 8px; border-radius: 10px; font-size: 11px;
|
||||
}
|
||||
.tag.ok { background: rgba(45,212,191,.18); color: #5ef0c0; }
|
||||
.tag.warn { background: rgba(255,180,80,.18); color: #ffc36b; }
|
||||
.tag.err { background: rgba(255,90,90,.18); color: #ff7a7a; }
|
||||
|
||||
/* 预警滚动 */
|
||||
.alert-list { grid-column: span 6; overflow: hidden; height: 100%; }
|
||||
.alert-item {
|
||||
display: flex; gap: 10px; padding: 5px 4px; font-size: 12.5px;
|
||||
border-bottom: 1px dashed rgba(80,160,200,.15); color: #ffd9a8;
|
||||
}
|
||||
.alert-item b { color: #ff9d5c; font-weight: 600; }
|
||||
|
||||
/* 底部跑马灯 */
|
||||
footer {
|
||||
height: 34px; line-height: 34px; overflow: hidden; white-space: nowrap;
|
||||
font-size: 13px; color: #7fd9c0; border-top: 1px solid rgba(64,224,180,.2);
|
||||
background: rgba(6,18,30,.6); padding-left: 18px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div>
|
||||
<h1>智慧农业大数据可视化平台</h1>
|
||||
<div class="sub" id="screenTitle">示例大屏 · 农业数据总览</div>
|
||||
</div>
|
||||
<div class="clock" id="clock">2026-08-23 09:30:00</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="panel stats">
|
||||
<div class="stat"><div class="num" data-count="12856">12,856</div><div class="lab">今日产量 (kg)</div></div>
|
||||
<div class="stat"><div class="num" data-count="342">342</div><div class="lab">在线设备</div></div>
|
||||
<div class="stat"><div class="num" data-count="7">7</div><div class="lab">活跃预警</div></div>
|
||||
<div class="stat"><div class="num" data-count="12600">12,600</div><div class="lab">灌溉面积 (亩)</div></div>
|
||||
</section>
|
||||
|
||||
<section class="panel env">
|
||||
<div class="mini"><div class="v">26.4°C</div><div class="t">棚内温度</div></div>
|
||||
<div class="mini"><div class="v">68%</div><div class="t">空气湿度</div></div>
|
||||
<div class="mini"><div class="v">12.3</div><div class="t">光照 klx</div></div>
|
||||
<div class="mini"><div class="v">486</div><div class="t">CO₂ ppm</div></div>
|
||||
</section>
|
||||
|
||||
<section class="panel mid">
|
||||
<div class="panel-title">近7日土壤墒情</div>
|
||||
<svg class="chart" viewBox="0 0 420 150" preserveAspectRatio="none">
|
||||
<defs>
|
||||
<linearGradient id="barG" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#38d9a9"/><stop offset="1" stop-color="#0a4a68"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<line x1="0" y1="20" x2="420" y2="20" stroke="rgba(120,200,255,.12)"/>
|
||||
<line x1="0" y1="70" x2="420" y2="70" stroke="rgba(120,200,255,.12)"/>
|
||||
<line x1="0" y1="120" x2="420" y2="120" stroke="rgba(120,200,255,.12)"/>
|
||||
<rect x="25" y="55" width="38" height="85" rx="3" fill="url(#barG)"/>
|
||||
<rect x="85" y="35" width="38" height="105" rx="3" fill="url(#barG)"/>
|
||||
<rect x="145" y="60" width="38" height="80" rx="3" fill="url(#barG)"/>
|
||||
<rect x="205" y="25" width="38" height="115" rx="3" fill="url(#barG)"/>
|
||||
<rect x="265" y="45" width="38" height="95" rx="3" fill="url(#barG)"/>
|
||||
<rect x="325" y="70" width="38" height="70" rx="3" fill="url(#barG)"/>
|
||||
</svg>
|
||||
</section>
|
||||
|
||||
<section class="panel mid">
|
||||
<div class="panel-title">近7日温度趋势</div>
|
||||
<svg class="chart" viewBox="0 0 420 150" preserveAspectRatio="none">
|
||||
<polyline points="20,95 87,70 154,80 221,45 288,60 355,35 410,50"
|
||||
fill="none" stroke="#5ef0c0" stroke-width="2.5"/>
|
||||
<polyline points="20,120 87,105 154,110 221,85 288,100 355,80 410,92"
|
||||
fill="none" stroke="#7dd3fc" stroke-width="2"/>
|
||||
<circle cx="221" cy="45" r="4" fill="#5ef0c0"/>
|
||||
<circle cx="355" cy="35" r="4" fill="#5ef0c0"/>
|
||||
</svg>
|
||||
</section>
|
||||
|
||||
<section class="panel mid">
|
||||
<div class="panel-title">设备在线率</div>
|
||||
<div style="display:flex;align-items:center;gap:18px;height:130px;">
|
||||
<svg width="120" height="120" viewBox="0 0 120 120">
|
||||
<circle cx="60" cy="60" r="46" fill="none" stroke="rgba(80,160,200,.18)" stroke-width="12"/>
|
||||
<circle cx="60" cy="60" r="46" fill="none" stroke="#38d9a9" stroke-width="12"
|
||||
stroke-dasharray="251" stroke-dashoffset="38" transform="rotate(-90 60 60)" stroke-linecap="round"/>
|
||||
<text x="60" y="66" text-anchor="middle" fill="#5ef0c0" font-size="20" font-weight="700">85%</text>
|
||||
</svg>
|
||||
<div style="font-size:12px;line-height:2;color:#bcd4e4;">
|
||||
<div>● 在线 <span style="color:#5ef0c0">342</span></div>
|
||||
<div>● 离线 <span style="color:#ff9d5c">60</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel alert-list">
|
||||
<div class="panel-title">作物生长预警</div>
|
||||
<div class="alert-item"><b>06:30</b> 3号温室土壤湿度低于阈值,已自动启动滴灌。</div>
|
||||
<div class="alert-item"><b>08:15</b> 西区光照不足,建议开启补光灯。</div>
|
||||
<div class="alert-item"><b>10:02</b> 5号水泵电机温度偏高,请安排巡检。</div>
|
||||
<div class="alert-item"><b>12:40</b> 东区虫情监测发现异常,已推送防治方案。</div>
|
||||
<div class="alert-item"><b>14:10</b> 数据中心网络波动,已切换备用链路。</div>
|
||||
</section>
|
||||
|
||||
<section class="panel mid">
|
||||
<div class="panel-title">设备状态</div>
|
||||
<div class="list-row"><span>滴灌系统</span><span class="tag ok">运行中</span></div>
|
||||
<div class="list-row"><span>环境传感器</span><span class="tag ok">正常</span></div>
|
||||
<div class="list-row"><span>植保无人机</span><span class="tag warn">待命</span></div>
|
||||
<div class="list-row"><span>水肥一体机</span><span class="tag ok">运行中</span></div>
|
||||
<div class="list-row"><span>监控摄像头</span><span class="tag err">1路离线</span></div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>【系统运行正常】示范基地总览 · 数据每 60 秒自动刷新 · 最后同步:刚刚</footer>
|
||||
|
||||
<script>
|
||||
// 从 URL 读取 title 参数(由编辑器传入)
|
||||
(function () {
|
||||
var p = new URLSearchParams(location.search);
|
||||
var t = p.get('title');
|
||||
if (t) document.getElementById('screenTitle').textContent = '示例大屏 · ' + t;
|
||||
var n = p.get('no');
|
||||
if (n) document.getElementById('screenTitle').textContent = '示例大屏 · ' + n + ' - ' + document.getElementById('screenTitle').textContent;
|
||||
var c = document.getElementById('clock');
|
||||
function tick() {
|
||||
var d = new Date();
|
||||
var pad = function (x) { return x < 10 ? '0' + x : x; };
|
||||
c.textContent = d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
|
||||
}
|
||||
tick(); setInterval(tick, 1000);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
const http = require('http');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = __dirname;
|
||||
const PORT = 8777;
|
||||
|
||||
const MIME = {
|
||||
'.html': 'text/html; charset=utf-8',
|
||||
'.js': 'text/javascript; charset=utf-8',
|
||||
'.css': 'text/css; charset=utf-8',
|
||||
'.json': 'application/json; charset=utf-8',
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.ico': 'image/x-icon',
|
||||
};
|
||||
|
||||
http.createServer((req, res) => {
|
||||
let urlPath = decodeURIComponent(req.url.split('?')[0]);
|
||||
if (urlPath === '/') urlPath = '/index.html';
|
||||
const filePath = path.join(ROOT, urlPath);
|
||||
if (!filePath.startsWith(ROOT)) {
|
||||
res.writeHead(403); res.end('Forbidden'); return;
|
||||
}
|
||||
fs.readFile(filePath, (err, data) => {
|
||||
if (err) {
|
||||
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||
res.end('404 Not Found: ' + urlPath);
|
||||
return;
|
||||
}
|
||||
res.writeHead(200, { 'Content-Type': MIME[path.extname(filePath)] || 'application/octet-stream' });
|
||||
res.end(data);
|
||||
});
|
||||
}).listen(PORT, () => {
|
||||
console.log(`Static server running at http://localhost:${PORT}`);
|
||||
});
|
||||
Reference in New Issue
Block a user