Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:55:46 +08:00
commit b710247ba7
27 changed files with 6516 additions and 0 deletions

View File

@@ -0,0 +1,651 @@
<!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>
/* --- 全局重置与基础样式 --- */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: "Microsoft YaHei", Arial, sans-serif;
background-color: #050a15;
background-image:
radial-gradient(circle at 50% 50%, #0d1a35 0%, #050a15 100%),
linear-gradient(rgba(0, 240, 255, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 240, 255, 0.03) 1px, transparent 1px);
background-size: 100% 100%, 30px 30px, 30px 30px;
color: #fff;
height: 100vh;
overflow: hidden; /* 防止滚动,全屏展示 */
display: flex;
flex-direction: column;
}
/* --- 颜色变量 --- */
:root {
--primary-color: #00f0ff;
--secondary-color: #0088ff;
--warn-color: #ffcc00;
--danger-color: #ff4d4d;
--bg-panel: rgba(10, 25, 50, 0.6);
--border-panel: rgba(0, 240, 255, 0.2);
}
/* --- 顶部 Header --- */
header {
height: 8vh;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
background: linear-gradient(to bottom, rgba(0,20,40,0.9), transparent);
border-bottom: 2px solid var(--border-panel);
position: relative;
}
header::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 100%;
height: 2px;
background: linear-gradient(90deg, transparent, var(--primary-color), transparent);
box-shadow: 0 0 10px var(--primary-color);
}
.header-title {
font-size: 1.8rem;
font-weight: bold;
letter-spacing: 2px;
text-shadow: 0 0 10px var(--secondary-color);
background: linear-gradient(to bottom, #fff, #aaddff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.header-time {
font-family: 'Courier New', monospace;
font-size: 1.2rem;
color: var(--primary-color);
}
/* --- 主体布局 --- */
main {
flex: 1;
display: grid;
grid-template-columns: 28% 44% 28%; /* 左 中 右 布局 */
gap: 15px;
padding: 15px;
}
/* --- 通用面板样式 --- */
.panel {
background: var(--bg-panel);
border: 1px solid var(--border-panel);
border-radius: 4px;
padding: 15px;
display: flex;
flex-direction: column;
position: relative;
backdrop-filter: blur(5px);
}
/* 面板四角装饰 */
.panel::before, .panel::after,
.panel-corner::before, .panel-corner::after {
content: "";
position: absolute;
width: 10px;
height: 10px;
border-color: var(--primary-color);
border-style: solid;
transition: all 0.3s ease;
}
.panel::before { top: -1px; left: -1px; border-width: 2px 0 0 2px; }
.panel::after { top: -1px; right: -1px; border-width: 2px 2px 0 0; }
.panel-corner { position: absolute; bottom: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
.panel-corner::before { bottom: -1px; left: -1px; border-width: 0 0 2px 2px; }
.panel-corner::after { bottom: -1px; right: -1px; border-width: 0 2px 2px 0; }
.panel:hover {
box-shadow: 0 0 15px rgba(0, 240, 255, 0.1);
border-color: rgba(0, 240, 255, 0.5);
}
.panel-title {
font-size: 1.1rem;
color: #fff;
border-left: 4px solid var(--primary-color);
padding-left: 10px;
margin-bottom: 15px;
font-weight: 600;
}
/* --- 左侧内容 --- */
.chart-box {
flex: 1;
position: relative;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
/* 自定义进度条列表 */
.progress-list {
width: 100%;
display: flex;
flex-direction: column;
gap: 15px;
}
.progress-item {
width: 100%;
}
.p-label {
display: flex;
justify-content: space-between;
font-size: 0.9rem;
margin-bottom: 5px;
color: #ccc;
}
.p-bar-bg {
width: 100%;
height: 8px;
background: rgba(255,255,255,0.1);
border-radius: 4px;
overflow: hidden;
}
.p-bar-fill {
height: 100%;
background: linear-gradient(90deg, var(--secondary-color), var(--primary-color));
width: 0%; /* 动画用 JS 控制 */
transition: width 1s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
}
.p-bar-fill::after {
content: '';
position: absolute;
right: 0;
top: 0;
height: 100%;
width: 2px;
background: #fff;
box-shadow: 0 0 5px #fff;
}
/* 环形图 (CSS实现) */
.donut-chart {
width: 120px;
height: 120px;
border-radius: 50%;
background: conic-gradient(var(--primary-color) 0% calc(var(--val) * 1%), rgba(255,255,255,0.1) calc(var(--val) * 1%) 100%);
position: relative;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 0 20px rgba(0, 240, 255, 0.2);
}
.donut-chart::before {
content: "";
width: 80%;
height: 80%;
background: #0d1a35;
border-radius: 50%;
position: absolute;
}
.donut-text {
position: relative;
z-index: 2;
text-align: center;
}
.donut-value { font-size: 1.4rem; font-weight: bold; color: #fff; }
.donut-label { font-size: 0.8rem; color: #aaa; }
.device-stats {
display: flex;
justify-content: space-around;
width: 100%;
margin-top: 10px;
}
.stat-item { text-align: center; }
.stat-val { font-size: 1.2rem; font-weight: bold; }
.stat-val.online { color: #00ff00; }
.stat-val.offline { color: #888; }
.stat-val.warning { color: var(--warn-color); }
.stat-desc { font-size: 0.8rem; color: #aaa; margin-top: 2px; }
/* --- 中间内容 --- */
.center-column {
display: flex;
flex-direction: column;
gap: 15px;
}
.kpi-board {
display: flex;
justify-content: space-between;
gap: 10px;
height: 15vh;
}
.kpi-card {
flex: 1;
background: rgba(0, 136, 255, 0.1);
border: 1px solid rgba(0, 136, 255, 0.3);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
overflow: hidden;
}
.kpi-card::after {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(0,240,255,0.1) 0%, transparent 70%);
animation: rotate 10s linear infinite;
}
@keyframes rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.kpi-num {
font-size: 2.5rem;
font-weight: bold;
color: var(--primary-color);
z-index: 1;
font-family: 'Courier New', monospace;
text-shadow: 0 0 10px var(--primary-color);
}
.kpi-title {
font-size: 1rem;
color: #ccc;
z-index: 1;
margin-top: 5px;
}
.map-container {
flex: 1;
border: 1px solid var(--border-panel);
background: rgba(0,0,0,0.2);
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
/* 模拟 3D 地图/雷达效果 */
.radar-grid {
width: 100%;
height: 100%;
position: absolute;
background:
linear-gradient(rgba(0, 240, 255, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 240, 255, 0.1) 1px, transparent 1px);
background-size: 50px 50px;
perspective: 500px;
transform: perspective(1000px) rotateX(60deg) scale(1.5);
animation: gridMove 20s linear infinite;
}
@keyframes gridMove { from { background-position: 0 0; } to { background-position: 0 50px; } }
.factory-model {
position: relative;
width: 300px;
height: 300px;
z-index: 10;
}
/* 简单的 CSS 旋转动画模拟中心产线 */
.circle-ring {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
border: 2px solid var(--primary-color);
box-shadow: 0 0 15px var(--primary-color);
}
.r1 { width: 100px; height: 100px; border-style: dashed; animation: spin 10s linear infinite; }
.r2 { width: 180px; height: 180px; border-color: rgba(0,240,255,0.3); border-width: 1px; animation: spinReverse 15s linear infinite; }
.r3 { width: 60px; height: 60px; background: rgba(0,240,255,0.1); display: flex; align-items: center; justify-content: center;}
.factory-icon { font-size: 2rem; }
@keyframes spin { 100% { transform: translate(-50%, -50%) rotate(360deg); } }
@keyframes spinReverse { 100% { transform: translate(-50%, -50%) rotate(-360deg); } }
/* --- 右侧内容 --- */
.alert-list {
width: 100%;
overflow: hidden;
flex: 1;
}
.alert-item {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid rgba(255,255,255,0.05);
font-size: 0.9rem;
animation: fadeIn 0.5s ease;
}
.alert-time { color: #888; margin-right: 10px; font-size: 0.8rem;}
.alert-content { flex: 1; }
.alert-level {
padding: 2px 6px;
border-radius: 2px;
font-size: 0.75rem;
margin-right: 8px;
}
.level-high { background: rgba(255, 77, 77, 0.2); color: var(--danger-color); border: 1px solid var(--danger-color); }
.level-warn { background: rgba(255, 204, 0, 0.2); color: var(--warn-color); border: 1px solid var(--warn-color); }
/* SVG 折线图容器 */
.svg-chart-container {
width: 100%;
height: 100%;
position: relative;
}
svg {
width: 100%;
height: 100%;
overflow: visible;
}
.chart-line {
fill: none;
stroke: var(--primary-color);
stroke-width: 2;
vector-effect: non-scaling-stroke;
}
.chart-area {
fill: rgba(0, 240, 255, 0.1);
stroke: none;
}
.chart-grid line {
stroke: rgba(255,255,255,0.1);
stroke-width: 1;
}
.chart-axis text {
fill: #888;
font-size: 10px;
}
/* 响应式调整 */
@media (max-width: 1200px) {
main { grid-template-columns: 30% 70%; grid-template-rows: auto auto; }
.center-column { grid-row: 1 / 2; grid-column: 2 / 3; }
.right-column { grid-row: 2 / 3; grid-column: 1 / 3; display: flex; gap: 15px; height: 30vh;}
.left-column { grid-row: 1 / 2; grid-column: 1 / 2; }
}
@media (max-width: 768px) {
main { display: flex; flex-direction: column; overflow-y: auto; }
body { overflow: auto; height: auto; }
.kpi-board { flex-wrap: wrap; height: auto; }
.kpi-card { width: 48%; margin-bottom: 10px; height: 100px; }
.map-container { height: 300px; }
}
</style>
</head>
<body>
<header>
<div class="header-logo">🏭 SMART FACTORY</div>
<div class="header-title">智能制造MES生产看板</div>
<div class="header-time" id="clock">00:00:00</div>
</header>
<main>
<!-- 左列:设备与效率 -->
<section class="panel left-column">
<div class="panel-corner"></div>
<div class="panel-title">设备综合效率 (OEE)</div>
<div class="chart-box" style="flex-direction: column; justify-content: space-around;">
<div class="device-stats">
<div class="stat-item">
<div class="donut-chart" style="--val:85">
<div class="donut-text">
<div class="donut-value">85%</div>
<div class="donut-label">OEE</div>
</div>
</div>
</div>
</div>
<div class="device-stats" style="margin-top: 20px;">
<div class="stat-item">
<div class="stat-val online">42</div>
<div class="stat-desc">运行中</div>
</div>
<div class="stat-item">
<div class="stat-val warning">3</div>
<div class="stat-desc">待机</div>
</div>
<div class="stat-item">
<div class="stat-val offline">1</div>
<div class="stat-desc">故障</div>
</div>
</div>
</div>
</section>
<section class="panel left-column">
<div class="panel-corner"></div>
<div class="panel-title">车间产出进度</div>
<div class="chart-box">
<div class="progress-list">
<div class="progress-item">
<div class="p-label"><span>A线 - 电子组装</span><span>92%</span></div>
<div class="p-bar-bg"><div class="p-bar-fill" style="width: 92%;"></div></div>
</div>
<div class="progress-item">
<div class="p-label"><span>B线 - 注塑成型</span><span>78%</span></div>
<div class="p-bar-bg"><div class="p-bar-fill" style="width: 78%; background: linear-gradient(90deg, #ffcc00, #ff9900);"></div></div>
</div>
<div class="progress-item">
<div class="p-label"><span>C线 - 包装质检</span><span>45%</span></div>
<div class="p-bar-bg"><div class="p-bar-fill" style="width: 45%;"></div></div>
</div>
<div class="progress-item">
<div class="p-label"><span>D线 - 激光焊接</span><span>88%</span></div>
<div class="p-bar-bg"><div class="p-bar-fill" style="width: 88%;"></div></div>
</div>
</div>
</div>
</section>
<!-- 中列:核心监控 -->
<section class="center-column">
<div class="kpi-board">
<div class="kpi-card">
<div class="kpi-num" id="kpi-total">24,592</div>
<div class="kpi-title">今日总产量 (PCS)</div>
</div>
<div class="kpi-card">
<div class="kpi-num" style="color: #00ff00;">99.2%</div>
<div class="kpi-title">良品率</div>
</div>
<div class="kpi-card">
<div class="kpi-num" style="color: #ffcc00;" id="kpi-cycle">4.2s</div>
<div class="kpi-title">平均节拍</div>
</div>
</div>
<div class="panel map-container" style="flex:1;">
<div class="panel-corner"></div>
<div class="radar-grid"></div>
<div class="factory-model">
<div class="circle-ring r1"></div>
<div class="circle-ring r2"></div>
<div class="circle-ring r3">
<span class="factory-icon">⚙️</span>
</div>
</div>
<div style="position:absolute; bottom:20px; left:20px; font-size:0.9rem; color:#aaa;">
<div>主轴转速: <span style="color:#fff;" id="motor-speed">1200</span> RPM</div>
<div>核心温度: <span style="color:#fff;" id="motor-temp">45.2</span> °C</div>
</div>
<div style="position:absolute; top:20px; right:20px; text-align:right;">
<div style="color:var(--primary-color); border:1px solid var(--primary-color); padding: 5px 10px; font-size:0.8rem;">
SYSTEM: ONLINE
</div>
</div>
</div>
</section>
<!-- 右列:趋势与告警 -->
<section class="panel right-column">
<div class="panel-corner"></div>
<div class="panel-title">实时产量趋势 (24H)</div>
<div class="chart-box" id="line-chart-box">
<!-- SVG Chart will be injected here by JS -->
</div>
</section>
<section class="panel right-column">
<div class="panel-corner"></div>
<div class="panel-title">实时告警信息</div>
<div class="chart-box" style="align-items: flex-start;">
<div class="alert-list" id="alert-container">
<!-- Alerts injected by JS -->
<div class="alert-item">
<span class="alert-time">10:42</span>
<span class="alert-level level-warn">警告</span>
<span class="alert-content">B线 3号机台 温度偏高</span>
</div>
<div class="alert-item">
<span class="alert-time">09:15</span>
<span class="alert-level level-high">故障</span>
<span class="alert-content">C线 缺料停机报警</span>
</div>
</div>
</div>
</section>
</main>
<script>
// --- 1. 时钟功能 ---
function updateClock() {
const now = new Date();
const timeString = now.toLocaleTimeString('zh-CN', { hour12: false });
const dateString = now.toLocaleDateString('zh-CN');
document.getElementById('clock').innerHTML = `${dateString} ${timeString}`;
}
setInterval(updateClock, 1000);
updateClock();
// --- 2. 模拟数据动态跳动 ---
function fluctuateData() {
// 更新产量
const totalEl = document.getElementById('kpi-total');
let currentTotal = parseInt(totalEl.innerText.replace(/,/g, ''));
currentTotal += Math.floor(Math.random() * 5); // 随机增加
totalEl.innerText = currentTotal.toLocaleString();
// 更新节拍
const cycleEl = document.getElementById('kpi-cycle');
cycleEl.innerText = (4.0 + Math.random() * 0.5).toFixed(1) + 's';
// 更新机器参数
document.getElementById('motor-speed').innerText = 1200 + Math.floor(Math.random() * 50 - 25);
document.getElementById('motor-temp').innerText = (45 + Math.random() * 2).toFixed(1);
}
setInterval(fluctuateData, 2000);
// --- 3. 绘制 SVG 折线图 (无需 Canvas 库) ---
function drawLineChart() {
const container = document.getElementById('line-chart-box');
const width = container.clientWidth;
const height = container.clientHeight;
// 生成模拟数据 (24小时)
const dataPoints = [];
for (let i = 0; i < 24; i++) {
dataPoints.push(50 + Math.random() * 40); // 50-90之间的数值
}
const maxVal = 100;
const stepX = width / (dataPoints.length - 1);
// 构建路径点
let pointsStr = "";
let areaPointsStr = `0,${height} `; // 闭合区域起点
dataPoints.forEach((val, index) => {
const x = index * stepX;
const y = height - (val / maxVal * height); // 坐标翻转
pointsStr += `${x},${y} `;
areaPointsStr += `${x},${y} `;
});
areaPointsStr += `${width},${height}`; // 闭合区域终点
const svgContent = `
<svg viewBox="0 0 ${width} ${height}" preserveAspectRatio="none">
<!-- 网格线 -->
<g class="chart-grid">
<line x1="0" y1="${height * 0.25}" x2="${width}" y2="${height * 0.25}" />
<line x1="0" y1="${height * 0.5}" x2="${width}" y2="${height * 0.5}" />
<line x1="0" y1="${height * 0.75}" x2="${width}" y2="${height * 0.75}" />
</g>
<!-- 面积 -->
<polygon points="${areaPointsStr}" class="chart-area" />
<!-- 折线 -->
<polyline points="${pointsStr}" class="chart-line" />
</svg>
`;
container.innerHTML = svgContent;
}
// 窗口大小改变时重绘图表
window.addEventListener('resize', drawLineChart);
setTimeout(drawLineChart, 100); // 初始化绘制
// --- 4. 模拟告警滚动 ---
const alerts = [
{ level: 'warn', text: 'A线 气压波动异常' },
{ level: 'high', text: 'AGV 小车受阻 #04' },
{ level: 'warn', text: '物料库存预警: 螺丝M4' },
{ level: 'normal', text: '班次产量已达标' }
];
function addRandomAlert() {
const container = document.getElementById('alert-container');
const randomAlert = alerts[Math.floor(Math.random() * alerts.length)];
const now = new Date();
const timeStr = `${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}`;
const div = document.createElement('div');
div.className = 'alert-item';
let levelClass = '';
let levelText = '信息';
if (randomAlert.level === 'warn') { levelClass = 'level-warn'; levelText = '警告'; }
if (randomAlert.level === 'high') { levelClass = 'level-high'; levelText = '故障'; }
div.innerHTML = `
<span class="alert-time">${timeStr}</span>
<span class="alert-level ${levelClass}">${levelText}</span>
<span class="alert-content">${randomAlert.text}</span>
`;
container.insertBefore(div, container.firstChild);
// 保持列表长度
if (container.children.length > 6) {
container.removeChild(container.lastChild);
}
}
setInterval(addRandomAlert, 5000);
</script>
</body>
</html>