独立渲染引擎就绪引擎就绪
<!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: #050914;
--panel-bg: rgba(10, 15, 30, 0.75);
--border-color: rgba(0, 240, 255, 0.2);
--text-main: #e0f2fe;
--text-dim: #7dd3fc;
--accent: #00f0ff;
--accent-glow: rgba(0, 240, 255, 0.6);
--warn: #fbbf24;
--danger: #ef4444;
--mesh-color: rgba(148, 163, 184, 0.3);
--pcb-color: #022c22;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-color);
color: var(--text-main);
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Microsoft YaHei';
overflow: hidden;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-image:
radial-gradient(circle at 50% 50%, rgba(0, 240, 255, 0.05) 0%, transparent 60%),
linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px);
background-size: 100% 100%, 40px 40px, 40px 40px;
}
/* 核心视觉容器 */
#visualization-container {
position: relative;
width: 100%;
max-width: 1600px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
/* 纯SVG物理仿真层 */
#simulation-svg {
position: absolute;
width: 70%;
height: 90%;
left: 5%;
filter: drop-shadow(0 0 20px rgba(0, 240, 255, 0.1));
}
/* 数据看板界面 */
.dashboard {
position: absolute;
right: 40px;
top: 50%;
transform: translateY(-50%);
width: 380px;
display: flex;
flex-direction: column;
gap: 20px;
z-index: 10;
}
.panel {
background: var(--panel-bg);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 20px;
backdrop-filter: blur(10px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5), inset 0 0 20px rgba(0, 240, 255, 0.05);
position: relative;
overflow: hidden;
}
.panel::before {
content: '';
position: absolute;
top: 0; left: 0; width: 100%; height: 2px;
background: linear-gradient(90deg, transparent, var(--accent), transparent);
}
.panel-header {
font-size: 14px;
font-weight: 600;
letter-spacing: 1px;
margin-bottom: 15px;
display: flex;
align-items: center;
justify-content: space-between;
color: var(--accent);
text-transform: uppercase;
}
.panel-header::before {
content: '';
display: inline-block;
width: 8px;
height: 8px;
background-color: var(--accent);
border-radius: 50%;
margin-right: 10px;
box-shadow: 0 0 10px var(--accent-glow);
animation: pulse-dot 2s infinite;
}
/* 热力图容器 */
#heatmap-container {
width: 100%;
aspect-ratio: 4/3;
background: rgba(0, 0, 0, 0.5);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 4px;
position: relative;
display: grid;
grid-template-columns: repeat(16, 1fr);
grid-template-rows: repeat(12, 1fr);
gap: 1px;
padding: 2px;
}
.heatmap-cell {
background-color: #000;
transition: background-color 0.3s ease;
border-radius: 1px;
}
/* 扫描线 (热力图上) */
#heatmap-scanner {
position: absolute;
top: 0;
left: 0;
width: calc(100% / 16);
height: 100%;
background: rgba(255, 255, 255, 0.1);
border-left: 2px solid var(--accent);
border-right: 1px solid rgba(0, 240, 255, 0.3);
pointer-events: none;
box-shadow: 0 0 15px var(--accent-glow);
transition: transform 0.05s linear;
}
/* 色阶图例 */
.legend {
margin-top: 15px;
display: flex;
flex-direction: column;
gap: 5px;
}
.legend-bar {
width: 100%;
height: 8px;
background: linear-gradient(90deg, #0000aa, #0088ff, #00ff00, #ffff00, #ff0000);
border-radius: 4px;
}
.legend-labels {
display: flex;
justify-content: space-between;
font-size: 11px;
color: var(--text-dim);
font-family: monospace;
}
/* 数据指标 */
.metrics-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.metric-box {
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255,255,255,0.05);
padding: 10px;
border-radius: 4px;
}
.metric-label {
font-size: 11px;
color: var(--text-dim);
margin-bottom: 4px;
}
.metric-value {
font-size: 16px;
font-family: monospace;
font-weight: bold;
color: var(--text-main);
}
.metric-value.highlight {
color: var(--accent);
text-shadow: 0 0 8px var(--accent-glow);
}
/* 状态指示器 */
.status-bar {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 12px;
margin-top: 10px;
padding-top: 10px;
border-top: 1px dashed rgba(255,255,255,0.1);
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #22c55e;
box-shadow: 0 0 8px #22c55e;
margin-right: 6px;
}
/* 绝对定位的浮动标签 (用于SVG物理区域解说) */
.floating-label {
position: absolute;
font-size: 12px;
color: var(--text-dim);
background: rgba(0,0,0,0.6);
border: 1px solid rgba(0,240,255,0.3);
padding: 6px 10px;
border-radius: 4px;
pointer-events: none;
backdrop-filter: blur(4px);
white-space: nowrap;
}
.floating-label::after {
content: '';
position: absolute;
width: 30px;
height: 1px;
background: var(--accent);
top: 50%;
left: -30px;
}
#label-sensor { top: 75%; left: 10%; }
#label-sensor::after { width: 100px; transform: rotate(-15deg); transform-origin: right center; left: 100%; top: auto; bottom: 50%; }
#label-mesh { top: 40%; left: 15%; }
#label-mesh::after { width: 60px; transform: rotate(15deg); transform-origin: left center; left: 100%; top: 50%; }
#label-bus { top: 85%; left: 35%; border-color: #a855f7; color: #d8b4fe; }
#label-bus::after { background: #a855f7; width: 40px; transform: rotate(-45deg); transform-origin: right center; left: 100%; top: 0; }
@keyframes pulse-dot {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(0.8); }
}
@keyframes flow-data {
0% { stroke-dashoffset: 20; }
100% { stroke-dashoffset: 0; }
}
</style>
</head>
<body>
<div id="visualization-container">
<!-- 物理系统等距视图 SVG -->
<svg id="simulation-svg" viewBox="0 0 1000 800" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 筛网材质 -->
<pattern id="mesh-pattern" width="20" height="20" patternUnits="userSpaceOnUse">
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="rgba(255,255,255,0.15)" stroke-width="1"/>
<path d="M 10 0 L 10 20 M 0 10 L 20 10" fill="none" stroke="rgba(255,255,255,0.05)" stroke-width="0.5"/>
</pattern>
<!-- 发光滤镜 -->
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="5" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="glow-strong" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="12" result="blur" />
<feComponentTransfer in="blur" result="glow">
<feFuncA type="linear" slope="1.5"/>
</feComponentTransfer>
<feMerge>
<feMergeNode in="glow" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<!-- 渐变定义 -->
<linearGradient id="liquid-grad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgba(0, 240, 255, 0.7)" />
<stop offset="100%" stop-color="rgba(0, 100, 255, 0.2)" />
</linearGradient>
</defs>
<!-- 整体进行等距变换 -->
<g transform="translate(500, 200) scale(1, 0.5) rotate(45)">
<!-- 底部:结构基座与CAN总线 -->
<rect x="-320" y="-220" width="640" height="440" fill="rgba(5, 10, 20, 0.8)" stroke="rgba(255,255,255,0.1)" rx="10"/>
<!-- 数据传输总线 (CAN Bus) -->
<path id="can-bus-line" d="M -300 240 L 300 240 L 350 290 L 600 290" fill="none" stroke="#a855f7" stroke-width="4" stroke-dasharray="10,5" filter="url(#glow)"/>
<circle cx="300" cy="240" r="4" fill="#a855f7" filter="url(#glow)"/>
<g id="data-pulses"></g>
<!-- 第2层:PCB传感器阵列 (2xN 实际渲染为更密集的矩阵以展示原理) -->
<rect x="-300" y="-200" width="600" height="400" fill="var(--pcb-color)" stroke="#064e3b" stroke-width="4" rx="5"/>
<!-- 传感器矩阵 (由JS动态生成填充) -->
<g id="sensor-matrix"></g>
<!-- 物理扫描线 (在PCB上方) -->
<rect id="physical-scanner" x="-300" y="-200" width="15" height="400" fill="rgba(0, 240, 255, 0.4)" filter="url(#glow-strong)"/>
<line id="physical-scanner-line" x1="-292.5" y1="-200" x2="-292.5" y2="200" stroke="#fff" stroke-width="2" filter="url(#glow)"/>
<!-- 悬浮层结构支撑 -->
<g stroke="rgba(255,255,255,0.2)" stroke-width="2" stroke-dasharray="5,5">
<line x1="-300" y1="-200" x2="-300" y2="-200" transform="translate(0, 0) scale(1, 2) rotate(-45)"/> <!-- 伪3D立柱,用绝对位移代替 -->
</g>
</g>
<!-- 绘制垂直连接柱 (连接PCB和筛网) -->
<path d="M 197 200 L 197 100 M 803 200 L 803 100 M 197 800 L 197 700 M 803 800 L 803 700" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="2" stroke-dasharray="4,4"/>
<g transform="translate(500, 100) scale(1, 0.5) rotate(45)">
<!-- 第3层:筛网 (Mesh) -->
<rect x="-320" y="-220" width="640" height="440" fill="url(#mesh-pattern)" stroke="rgba(148, 163, 184, 0.8)" stroke-width="3"/>
<!-- 筛网边框高光 -->
<rect x="-320" y="-220" width="640" height="440" fill="none" stroke="var(--accent)" stroke-width="1" filter="url(#glow)"/>
<!-- 第4层:动态液膜 (Liquid Film) - 由多条曲线构成的曲面 -->
<g id="liquid-surface-group" transform="translate(0, 0)"></g>
</g>
</svg>
<!-- 悬浮解说标签 -->
<div class="floating-label" id="label-mesh">工业振动筛网 (不适用严重金属粘结)</div>
<div class="floating-label" id="label-sensor">下方底置: 2×N 微波/电容分布阵列<br><span style="font-size:10px; color:#64748b;">空间分辨率: 100mm | 周期: 200ms</span></div>
<div class="floating-label" id="label-bus">CAN总线高速采集回传</div>
<!-- 右侧数据看板 -->
<div class="dashboard">
<div class="panel">
<div class="panel-header">实时液膜分布等高线映射</div>
<!-- 核心:二维液膜热力图 -->
<div id="heatmap-container">
<div id="heatmap-scanner"></div>
<!-- 单元格由JS生成 -->
</div>
<div class="legend">
<div class="legend-bar"></div>
<div class="legend-labels">
<span>薄 (介电常数低)</span>
<span>平衡态</span>
<span>堆积 (介电常数高)</span>
</div>
</div>
</div>
<div class="panel">
<div class="panel-header">阵列扫描控制终端 (PLC)</div>
<div class="metrics-grid">
<div class="metric-box">
<div class="metric-label">矩阵扫描周期 (ms)</div>
<div class="metric-value highlight">200.0</div>
</div>
<div class="metric-value metric-box">
<div class="metric-label">空间分辨率 (mm)</div>
<div class="metric-value">100.0</div>
</div>
<div class="metric-box">
<div class="metric-label">区域平衡度指标 (RBI)</div>
<div class="metric-value" id="rbi-value">0.85</div>
</div>
<div class="metric-box">
<div class="metric-label">当前扫描列 (Col)</div>
<div class="metric-value highlight" id="col-value">01</div>
</div>
</div>
<div class="status-bar">
<div style="display:flex; align-items:center;">
<div class="status-dot"></div>
<span>电容矩阵传感在线</span>
</div>
<span style="color:var(--text-dim); font-family:monospace;">SYS_RUNNING</span>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// 系统参数
const ROWS = 12; // 对应热力图高度
const COLS = 16; // 对应热力图宽度 (模拟2xN阵列的细分扫描)
const SENSOR_WIDTH = 600;
const SENSOR_HEIGHT = 400;
const CELL_W = SENSOR_WIDTH / COLS;
const CELL_H = SENSOR_HEIGHT / ROWS;
const SCAN_PERIOD = 2000; // 动画视觉周期设为2s,对应实际200ms,方便人眼观察
// DOM 元素
const sensorMatrixGroup = document.getElementById('sensor-matrix');
const liquidSurfaceGroup = document.getElementById('liquid-surface-group');
const heatmapContainer = document.getElementById('heatmap-container');
const heatmapScanner = document.getElementById('heatmap-scanner');
const physicalScanner = document.getElementById('physical-scanner');
const colValueEl = document.getElementById('col-value');
const rbiValueEl = document.getElementById('rbi-value');
const dataPulsesGroup = document.getElementById('data-pulses');
// 状态数据
let liquidData = Array.from({length: ROWS}, () => new Array(COLS).fill(0));
let heatmapCells = [];
let physicalSensors = [];
let time = 0;
let currentScanCol = 0;
// 颜色映射函数 (Jet Colormap: Blue -> Cyan -> Green -> Yellow -> Red)
function getHeatmapColor(value) {
// value: 0 to 1
let r = 0, g = 0, b = 0;
if (value < 0.25) {
r = 0; g = 4 * value; b = 1;
} else if (value < 0.5) {
r = 0; g = 1; b = 1 - 4 * (value - 0.25);
} else if (value < 0.75) {
r = 4 * (value - 0.5); g = 1; b = 0;
} else {
r = 1; g = 1 - 4 * (value - 0.75); b = 0;
}
return `rgb(${Math.floor(r*255)}, ${Math.floor(g*255)}, ${Math.floor(b*255)})`;
}
// 初始化物理传感器阵列 (SVG) 和 热力图网格 (HTML)
function initSystem() {
// 热力图扫描器置顶
heatmapContainer.appendChild(heatmapScanner);
for (let r = 0; r < ROWS; r++) {
physicalSensors[r] = [];
for (let c = 0; c < COLS; c++) {
// 1. 初始化热力图单元格
const cell = document.createElement('div');
cell.className = 'heatmap-cell';
heatmapContainer.appendChild(cell);
heatmapCells.push(cell);
// 2. 初始化物理传感器 (PCB上的焊盘/感应区)
// 为了贴合2xN的说明,我们在视觉上将它们分组
const isMainSensor = (r % 3 === 1);
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('x', -SENSOR_WIDTH/2 + c * CELL_W + 2);
rect.setAttribute('y', -SENSOR_HEIGHT/2 + r * CELL_H + 2);
rect.setAttribute('width', CELL_W - 4);
rect.setAttribute('height', CELL_H - 4);
rect.setAttribute('rx', 2);
rect.setAttribute('fill', isMainSensor ? '#047857' : '#022c22');
rect.setAttribute('stroke', isMainSensor ? '#10b981' : 'none');
rect.setAttribute('stroke-width', '0.5');
rect.style.transition = 'fill 0.1s, filter 0.1s';
sensorMatrixGroup.appendChild(rect);
physicalSensors[r][c] = rect;
}
}
// 初始化液膜表面路径 (每一行一条折线/曲线,堆叠表现厚度)
// 由于是在等距视图中,我们通过改变等距平面的Z轴(或者说相对位移)来表现高度
// 巧妙利用路径填充来模拟体积
}
// 生成动态液膜数据 (模拟非均匀分布的流动)
function updateLiquidData() {
time += 0.05;
let totalDepth = 0;
let maxDepth = 0.001;
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
// 混合多个正弦波模拟复杂的流体表面形态
const wave1 = Math.sin(c * 0.4 + time * 2);
const wave2 = Math.cos(r * 0.5 - time * 1.5);
const wave3 = Math.sin((c + r) * 0.3 + time);
// 制造一个局部堆积区 (矛盾点:分布不均)
const accumulation = Math.exp(-((c - 10)**2 + (r - 6)**2) / 10) * 0.8;
// 归一化到 0~1
let depth = (wave1 * 0.2 + wave2 * 0.2 + wave3 * 0.1 + 0.5) + accumulation;
depth = Math.max(0, Math.min(1, depth)); // 钳制在0-1
liquidData[r][c] = depth;
totalDepth += depth;
if(depth > maxDepth) maxDepth = depth;
}
}
// 计算区域平衡度指标 (RBI) - 简化的方差概念
const avg = totalDepth / (ROWS * COLS);
let variance = 0;
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
variance += Math.pow(liquidData[r][c] - avg, 2);
}
}
const rbi = 1 - Math.sqrt(variance / (ROWS * COLS));
rbiValueEl.innerText = rbi.toFixed(3);
rbiValueEl.style.color = rbi < 0.6 ? varwarn : (rbi > 0.8 ? 'var(--accent)' : 'var(--text-main)');
}
// 渲染物理液膜曲面 (基于数据生成SVG Path)
function renderLiquidSurface() {
liquidSurfaceGroup.innerHTML = '';
// 为了性能和效果,我们画条带或者多边形
for (let r = 0; r < ROWS; r++) {
let d = `M -${SENSOR_WIDTH/2} ${-SENSOR_HEIGHT/2 + r * CELL_H + CELL_H/2} `;
for (let c = 0; c < COLS; c++) {
const val = liquidData[r][c];
const x = -SENSOR_WIDTH/2 + c * CELL_W + CELL_W/2;
// 高度映射到等距投影的左上方向 (-x, -y 在视口直角坐标系中,但因为父级旋转,我们只需改变局部相对位置。
// 简化处理:由于父级包含 scale(1, 0.5) rotate(45),要在Z轴隆起,需要对坐标做逆运算。
// 更简单的视觉欺骗:沿着局部X/Y轴反向偏移产生“鼓起”效果。
// 真正的垂直鼓起需要沿 (-1, -1) 方向移动。
const heightOffset = val * 60; // 最大隆起高度
const px = x - heightOffset;
const py = -SENSOR_HEIGHT/2 + r * CELL_H + CELL_H/2 - heightOffset;
d += `L ${px} ${py} `;
}
// 闭合到底部形成体积感
d += `L ${SENSOR_WIDTH/2} ${-SENSOR_HEIGHT/2 + r * CELL_H + CELL_H/2} `;
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', d);
path.setAttribute('fill', 'none');
// 基于该行平均高度上色
const avgVal = liquidData[r].reduce((a,b)=>a+b,0)/COLS;
path.setAttribute('stroke', `rgba(0, 200, 255, ${0.3 + avgVal*0.5})`);
path.setAttribute('stroke-width', '4');
// path.style.filter = 'url(#glow)';
// 添加一层半透明填充制造水体感
const fillPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
const dFill = d + `L ${SENSOR_WIDTH/2} ${-SENSOR_HEIGHT/2 + (r+1) * CELL_H} L -${SENSOR_WIDTH/2} ${-SENSOR_HEIGHT/2 + (r+1) * CELL_H} Z`;
fillPath.setAttribute('d', dFill);
fillPath.setAttribute('fill', `rgba(0, 150, 255, ${0.1 + avgVal*0.2})`);
liquidSurfaceGroup.appendChild(fillPath);
liquidSurfaceGroup.appendChild(path);
}
}
// 发送数据脉冲动画 (模拟CAN总线传输)
function fireDataPulse() {
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
circle.setAttribute('cx', '-300');
circle.setAttribute('cy', '240');
circle.setAttribute('r', '5');
circle.setAttribute('fill', '#a855f7');
circle.setAttribute('filter', 'url(#glow-strong)');
dataPulsesGroup.appendChild(circle);
// 使用Web Animations API
const animation = circle.animate([
{ transform: 'translate(0, 0)' },
{ transform: 'translate(600px, 0)' }, // 沿X移动,然后折线部分简化处理或只展示前半段
{ transform: 'translate(650px, 50px)' },
{ transform: 'translate(900px, 50px)' }
], {
duration: 600,
easing: 'linear'
});
animation.onfinish = () => circle.remove();
}
// 主动画循环
let lastScanTime = 0;
function animate(timestamp) {
updateLiquidData();
renderLiquidSurface();
// 处理扫描逻辑
const scanProgress = (timestamp % SCAN_PERIOD) / SCAN_PERIOD; // 0 to 1
const targetCol = Math.floor(scanProgress * COLS);
// 物理扫描线平滑移动
const scanX = -SENSOR_WIDTH/2 + scanProgress * SENSOR_WIDTH;
physicalScanner.setAttribute('x', scanX);
document.getElementById('physical-scanner-line').setAttribute('x1', scanX + 7.5);
document.getElementById('physical-scanner-line').setAttribute('x2', scanX + 7.5);
// UI界面扫描线平滑移动
heatmapScanner.style.transform = `translateX(${scanProgress * 100 * COLS}%)`;
// 当扫描列切换时触发数据采集动作
if (targetCol !== currentScanCol) {
currentScanCol = targetCol;
colValueEl.innerText = (currentScanCol + 1).toString().padStart(2, '0');
fireDataPulse();
// 更新热力图颜色并点亮物理传感器
for (let r = 0; r < ROWS; r++) {
const val = liquidData[r][currentScanCol];
const color = getHeatmapColor(val);
// 1. 更新热力图该列
const cellIndex = r * COLS + currentScanCol;
heatmapCells[cellIndex].style.backgroundColor = color;
heatmapCells[cellIndex].style.boxShadow = `0 0 10px ${color}`;
// 渐隐尾迹效果
setTimeout(() => {
if(heatmapCells[cellIndex]) heatmapCells[cellIndex].style.boxShadow = 'none';
}, 300);
// 2. 物理传感器瞬间高亮 (模拟电容充放电测量)
const pSensor = physicalSensors[r][currentScanCol];
pSensor.setAttribute('fill', '#00f0ff');
pSensor.setAttribute('filter', 'url(#glow-strong)');
setTimeout(() => {
const isMainSensor = (r % 3 === 1);
pSensor.setAttribute('fill', isMainSensor ? '#047857' : '#022c22');
pSensor.removeAttribute('filter');
}, 150); // 短暂亮起后熄灭
}
}
requestAnimationFrame(animate);
}
// 启动
initSystem();
requestAnimationFrame(animate);
});
</script>
</body>
</html>
积分规则:每轮对话扣10分
等待动画代码生成...
