独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>超前预判防跑浆控制系统 (IFR)</title>
<style>
:root {
--bg-dark: #0a0d14;
--panel-bg: rgba(16, 22, 32, 0.85);
--grid-color: rgba(45, 60, 85, 0.2);
--cyan-glow: #00f0ff;
--alert-red: #ff3366;
--warn-amber: #ffb000;
--text-main: #e2e8f0;
--text-muted: #94a3b8;
--pipe-color: #1e293b;
--mud-color: #d97706;
--mech-metal: #64748b;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: var(--bg-dark);
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
color: var(--text-main);
}
/* Container setup for center focus */
#animation-container {
position: relative;
width: 100%;
max-width: 1200px;
aspect-ratio: 16 / 9;
background: radial-gradient(circle at center, #111827 0%, #030712 100%);
border: 1px solid rgba(255, 255, 255, 0.05);
box-shadow: 0 20px 50px rgba(0,0,0,0.5);
overflow: hidden;
}
/* Corner Data Panels - strict adherence to small size and non-obstruction */
.data-panel {
position: absolute;
background: var(--panel-bg);
border: 1px solid rgba(0, 240, 255, 0.2);
backdrop-filter: blur(10px);
padding: 12px 16px;
border-radius: 8px;
z-index: 10;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
}
#plc-dashboard {
top: 20px;
right: 20px;
min-width: 200px;
}
#legend-panel {
bottom: 20px;
left: 20px;
max-width: 280px;
}
.panel-title {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--cyan-glow);
margin-bottom: 8px;
border-bottom: 1px solid rgba(0, 240, 255, 0.2);
padding-bottom: 4px;
}
.data-row {
display: flex;
justify-content: space-between;
margin: 6px 0;
font-size: 12px;
}
.data-label { color: var(--text-muted); }
.data-value {
font-family: 'Courier New', Courier, monospace;
font-weight: bold;
transition: color 0.3s;
}
.desc-text {
font-size: 11px;
line-height: 1.5;
color: var(--text-muted);
}
.highlight { color: var(--cyan-glow); font-weight: bold; }
/* SVG Elements Styling */
svg {
width: 100%;
height: 100%;
display: block;
}
/* Animations */
.data-stream {
stroke-dasharray: 4 8;
animation: flowData 1s linear infinite;
}
.mud-flow {
stroke-dasharray: 10 15;
animation: flowData 0.5s linear infinite reverse;
transition: stroke-width 0.5s, stroke-dasharray 0.5s;
}
@keyframes flowData {
to { stroke-dashoffset: -24; }
}
@keyframes pulseGlow {
0%, 100% { filter: drop-shadow(0 0 4px var(--cyan-glow)); }
50% { filter: drop-shadow(0 0 12px var(--cyan-glow)); }
}
@keyframes alertGlow {
0%, 100% { filter: drop-shadow(0 0 6px var(--alert-red)); }
50% { filter: drop-shadow(0 0 16px var(--alert-red)); }
}
/* Dynamic Classes controlled by JS */
.status-normal .indicator { fill: var(--cyan-glow); }
.status-normal .data-value.dqdt { color: var(--cyan-glow); }
.status-pre .indicator { fill: var(--warn-amber); }
.status-pre .data-value.dqdt { color: var(--warn-amber); }
.status-pre .sensor-ring { stroke: var(--warn-amber); filter: drop-shadow(0 0 8px var(--warn-amber)); }
.status-emergency .indicator { fill: var(--alert-red); animation: alertGlow 1s infinite; }
.status-emergency .data-value.dqdt { color: var(--alert-red); }
.status-emergency .sensor-ring { stroke: var(--alert-red); animation: alertGlow 0.5s infinite; }
/* Smooth transitions for mechanical parts */
#shaker-screen {
transform-origin: 750px 420px;
transition: transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
}
#weir-plate {
transition: transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
}
#servo-rod {
transition: transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
}
#mud-pool {
transition: d 0.6s ease-in-out, fill 0.6s;
}
.data-line {
transition: stroke 0.3s;
}
#system-status-msg {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
font-size: 14px;
font-weight: bold;
letter-spacing: 2px;
padding: 6px 16px;
border-radius: 20px;
background: rgba(0, 240, 255, 0.1);
border: 1px solid var(--cyan-glow);
color: var(--cyan-glow);
transition: all 0.3s;
z-index: 10;
pointer-events: none;
}
</style>
</head>
<body>
<div id="animation-container" class="status-normal">
<!-- Top Center Status Badge -->
<div id="system-status-msg">系统状态:平稳运行</div>
<!-- Top Right PLC Data -->
<div id="plc-dashboard" class="data-panel">
<div class="panel-title">PLC 预测控制中枢</div>
<div class="data-row">
<span class="data-label">实时流量 (Q):</span>
<span class="data-value" id="val-flow">45.0 L/s</span>
</div>
<div class="data-row">
<span class="data-label">流量变率 (dQ/dt):</span>
<span class="data-value dqdt" id="val-dqdt">0.02 L/s²</span>
</div>
<div class="data-row">
<span class="data-label">进料压力:</span>
<span class="data-value" id="val-pressure">102 kPa</span>
</div>
<hr style="border: 0; border-top: 1px solid rgba(255,255,255,0.1); margin: 8px 0;">
<div class="data-row">
<span class="data-label">预警阈值:</span>
<span class="data-value" style="color:var(--text-muted)">0.6 L/s²</span>
</div>
<div class="data-row">
<span class="data-label">伺服抬升量:</span>
<span class="data-value" id="val-lift">0 mm</span>
</div>
<div class="data-row">
<span class="data-label">堰板开度:</span>
<span class="data-value" id="val-weir">100%</span>
</div>
</div>
<!-- Bottom Left Legend / IFR description -->
<div id="legend-panel" class="data-panel">
<div class="panel-title">TRIZ 最终理想解 (IFR)</div>
<div class="desc-text">
利用<span class="highlight">流量前兆特征 (dQ/dt)</span>,在跑浆发生前实现超前干预。<br>
• <b style="color:var(--cyan-glow)">正常:</b> 流量平稳,全开度<br>
• <b style="color:var(--warn-amber)">预调节:</b> dQ/dt>0.6,提前2s抬升30mm,关堰板至80%<br>
• <b style="color:var(--alert-red)">紧急兜底:</b> 液位超限,全行程抬升120mm,关至40%<br>
<i style="opacity:0.7; margin-top:4px; display:block;">* 巧妙利用现有资源,极小化系统改变实现零跑浆。</i>
</div>
</div>
<!-- Main SVG Canvas -->
<svg viewBox="0 0 1000 562.5" preserveAspectRatio="xMidYMid meet">
<defs>
<filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="3" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="var(--grid-color)" stroke-width="0.5"/>
</pattern>
<!-- Mud Gradient -->
<linearGradient id="mud-grad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#f59e0b" stop-opacity="0.9"/>
<stop offset="100%" stop-color="#b45309" stop-opacity="0.9"/>
</linearGradient>
<!-- Metal Gradient -->
<linearGradient id="metal" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#94a3b8"/>
<stop offset="50%" stop-color="#475569"/>
<stop offset="100%" stop-color="#1e293b"/>
</linearGradient>
</defs>
<!-- Background Grid -->
<rect width="100%" height="100%" fill="url(#grid)" />
<!-- Static Background Structures -->
<!-- Pipeline Base -->
<path d="M 0 250 L 150 250 L 150 350 L 0 350 Z" fill="var(--pipe-color)" stroke="#334155" stroke-width="2"/>
<path d="M 150 250 C 200 250, 220 280, 250 300 L 250 380 C 200 360, 150 350, 150 350 Z" fill="var(--pipe-color)" stroke="#334155" stroke-width="2"/>
<!-- Tank Base -->
<path d="M 250 380 L 250 480 L 800 480 L 800 420 L 750 420 L 750 460 L 270 460 Z" fill="#1e293b" stroke="#475569" stroke-width="2"/>
<!-- ================= MUD DYNAMICS ================= -->
<!-- Incoming Mud Stream -->
<path id="mud-stream" d="M 0 300 L 150 300 C 180 300, 210 320, 230 350 L 250 400" fill="none" stroke="var(--mud-color)" stroke-width="20" stroke-linecap="round" class="mud-flow"/>
<!-- Dynamic Mud Pool -->
<!-- Start state: small pool -->
<path id="mud-pool" d="M 250 460 L 250 440 Q 500 440, 750 420 L 750 460 Z" fill="url(#mud-grad)" />
<!-- ================= MECHANICAL PARTS ================= -->
<!-- Shaker Screen (Rotates) -->
<g id="shaker-screen">
<line x1="250" y1="420" x2="750" y2="420" stroke="#cbd5e1" stroke-width="6" stroke-linecap="round"/>
<!-- Mesh pattern -->
<path d="M 300 420 L 300 430 M 350 420 L 350 430 M 400 420 L 400 430 M 450 420 L 450 430 M 500 420 L 500 430 M 550 420 L 550 430 M 600 420 L 600 430 M 650 420 L 650 430 M 700 420 L 700 430" stroke="#64748b" stroke-width="2"/>
<!-- Hinge -->
<circle cx="750" cy="420" r="8" fill="#e2e8f0" stroke="#475569" stroke-width="3"/>
</g>
<!-- Servo Mechanism -->
<g id="servo-group" transform="translate(250, 480)">
<rect x="-15" y="0" width="30" height="60" fill="url(#metal)" rx="4"/>
<!-- Servo Rod (moves up/down based on screen lift) -->
<rect id="servo-rod" x="-6" y="-60" width="12" height="60" fill="#cbd5e1"/>
<circle cx="0" cy="30" r="8" fill="var(--cyan-glow)" opacity="0.5"/>
<!-- Encoder detail -->
<rect x="15" y="10" width="10" height="20" fill="#334155" rx="2"/>
<circle cx="20" cy="20" r="3" fill="var(--cyan-glow)"/>
</g>
<!-- Weir Plate (Moves Down) -->
<g id="weir-group" transform="translate(230, 220)">
<rect x="0" y="0" width="10" height="80" fill="#475569" rx="2"/>
<rect id="weir-plate" x="-2" y="40" width="14" height="60" fill="url(#metal)" rx="2"/>
</g>
<!-- ================= SENSORS & PLC ================= -->
<!-- Electromagnetic Flowmeter (DN200) -->
<g id="sensor-flow" transform="translate(100, 300)">
<rect x="-20" y="-60" width="40" height="120" fill="none" stroke="#64748b" stroke-width="4" rx="4"/>
<ellipse class="sensor-ring" cx="0" cy="0" rx="10" ry="40" fill="none" stroke="var(--cyan-glow)" stroke-width="3"/>
<rect x="-15" y="-75" width="30" height="15" fill="#334155" rx="2"/>
<circle class="indicator" cx="0" cy="-67" r="3"/>
<text x="0" y="-85" font-size="10" fill="var(--text-muted)" text-anchor="middle">电磁流量计</text>
</g>
<!-- Pressure Transmitter -->
<g id="sensor-pressure" transform="translate(60, 250)">
<rect x="-10" y="-30" width="20" height="30" fill="#334155" rx="2"/>
<line x1="0" y1="0" x2="0" y2="10" stroke="#cbd5e1" stroke-width="3"/>
<circle class="indicator" cx="0" cy="-15" r="3"/>
<text x="0" y="-38" font-size="10" fill="var(--text-muted)" text-anchor="middle">压力变送器</text>
</g>
<!-- Level Sensor (Alarm Line) -->
<g id="sensor-level" transform="translate(350, 350)">
<rect x="-8" y="-20" width="16" height="20" fill="#334155" rx="2"/>
<line x1="0" y1="0" x2="0" y2="40" stroke="#94a3b8" stroke-width="2" stroke-dasharray="2 2"/>
<!-- Alarm Threshold Line -->
<line x1="-30" y1="40" x2="150" y2="40" stroke="var(--alert-red)" stroke-width="1" stroke-dasharray="4 4" opacity="0.6"/>
<text x="60" y="35" font-size="10" fill="var(--alert-red)" opacity="0.8">紧急动作液位阈值</text>
<circle class="indicator" cx="0" cy="-10" r="3"/>
</g>
<!-- PLC Unit (Abstracted representation connected to dashboard) -->
<g id="plc-unit" transform="translate(500, 150)">
<rect x="-40" y="-30" width="80" height="60" fill="rgba(15, 23, 42, 0.8)" stroke="var(--cyan-glow)" stroke-width="1" rx="6" filter="url(#glow-cyan)"/>
<text x="0" y="-5" font-size="12" fill="#fff" text-anchor="middle" font-weight="bold">PLC</text>
<text x="0" y="12" font-size="9" fill="var(--cyan-glow)" text-anchor="middle">预测模型</text>
<circle class="indicator" cx="-25" cy="-15" r="3"/>
<circle class="indicator" cx="-15" cy="-15" r="3"/>
<circle class="indicator" cx="-5" cy="-15" r="3"/>
</g>
<!-- ================= DATA PATHS (Animated) ================= -->
<!-- Sensors to PLC -->
<path id="path-flow-plc" d="M 100 225 L 100 150 L 460 150" fill="none" stroke="var(--cyan-glow)" stroke-width="1.5" class="data-line data-stream" opacity="0.6"/>
<path id="path-level-plc" d="M 350 330 L 350 160 L 460 160" fill="none" stroke="var(--cyan-glow)" stroke-width="1.5" class="data-line data-stream" opacity="0.6"/>
<!-- PLC to Actuators -->
<!-- To Servo -->
<path id="path-plc-servo" d="M 540 160 L 780 160 L 780 500 L 280 500 L 280 490" fill="none" stroke="var(--warn-amber)" stroke-width="1.5" class="data-line data-stream" opacity="0.6"/>
<!-- To Weir -->
<path id="path-plc-weir" d="M 500 120 L 500 80 L 235 80 L 235 220" fill="none" stroke="var(--warn-amber)" stroke-width="1.5" class="data-line data-stream" opacity="0.6"/>
</svg>
</div>
<script>
// State Management for the Animation Loop
// States:
// 0: Normal
// 1: Spike (dQ/dt > 0.6) -> Pre-action
// 2: Extreme Spike -> Level Alarm -> Emergency
// 3: Recovery -> back to Normal
const elements = {
container: document.getElementById('animation-container'),
statusMsg: document.getElementById('system-status-msg'),
valFlow: document.getElementById('val-flow'),
valDqdt: document.getElementById('val-dqdt'),
valPressure: document.getElementById('val-pressure'),
valLift: document.getElementById('val-lift'),
valWeir: document.getElementById('val-weir'),
screen: document.getElementById('shaker-screen'),
weirPlate: document.getElementById('weir-plate'),
servoRod: document.getElementById('servo-rod'),
mudPool: document.getElementById('mud-pool'),
mudStream: document.getElementById('mud-stream'),
pathFlowPlc: document.getElementById('path-flow-plc'),
pathLevelPlc: document.getElementById('path-level-plc'),
};
let time = 0;
// Physical simulation parameters (visual representation)
let currentLift = 0; // 0, 30, 120 mm
let targetLift = 0;
let currentWeir = 100; // 100, 80, 40 %
let targetWeir = 100;
// Animation sequencer
function updateState() {
const t = time % 16; // 16 seconds loop
if (t === 0) {
// NORMAL STATE
setMode('normal', '系统状态:平稳运行', 'var(--cyan-glow)', 'var(--cyan-glow)');
updateData(45.0, 0.02, 102, 0, 100);
animateMechanics(0, 100, 440);
updateMudStream(20, '10 15');
}
else if (t === 3) {
// SPIKE DETECTED
setMode('pre', '检测到流量突增前兆!', 'var(--warn-amber)', 'var(--warn-amber)');
updateData(75.5, 0.85, 128, 0, 100);
updateMudStream(35, '5 8');
}
else if (t === 4) {
// PRE-ACTION (IFR Core)
setMode('pre', '预调节启动:提前干预!', 'var(--warn-amber)', 'var(--warn-amber)');
updateData(78.2, 0.65, 130, 30, 80);
animateMechanics(30, 80, 410); // Screen pivots up, weir closes slightly, mud rises but controlled
}
else if (t === 8) {
// EXTREME SURGE (Alarm hit)
setMode('emergency', '液位触警:紧急兜底启动!', 'var(--alert-red)', 'var(--alert-red)');
updateData(95.0, 1.20, 145, 30, 80);
updateMudStream(45, '2 4');
elements.pathLevelPlc.style.stroke = 'var(--alert-red)';
}
else if (t === 9) {
// EMERGENCY ACTION
updateData(92.0, 0.10, 140, 120, 40);
animateMechanics(120, 40, 390); // Screen max lift, weir closes to 40%
}
else if (t === 13) {
// RECOVERY
setMode('normal', '工况平稳:延时复位中', 'var(--cyan-glow)', 'var(--cyan-glow)');
updateData(50.0, -0.40, 105, 120, 40);
updateMudStream(25, '10 15');
elements.pathLevelPlc.style.stroke = 'var(--cyan-glow)';
}
else if (t === 14) {
// GRADUAL RESET
updateData(46.0, -0.05, 102, 0, 100);
animateMechanics(0, 100, 440);
}
time++;
}
function setMode(modeClass, msg, color, strokeColor) {
elements.container.className = `status-${modeClass}`;
elements.statusMsg.textContent = msg;
elements.statusMsg.style.color = color;
elements.statusMsg.style.borderColor = color;
elements.statusMsg.style.background = `rgba(${modeClass === 'emergency' ? '255,51,102' : (modeClass === 'pre' ? '255,176,0' : '0,240,255')}, 0.1)`;
elements.pathFlowPlc.style.stroke = strokeColor;
}
function updateData(flow, dqdt, press, lift, weir) {
// Add tiny random fluctuations to make it look alive
const jitter = () => (Math.random() * 0.4 - 0.2);
elements.valFlow.textContent = (flow + jitter()).toFixed(1) + ' L/s';
elements.valDqdt.textContent = (dqdt + (jitter()*0.1)).toFixed(2) + ' L/s²';
elements.valPressure.textContent = Math.round(press + jitter()*2) + ' kPa';
elements.valLift.textContent = lift + ' mm';
elements.valWeir.textContent = weir + '%';
}
function updateMudStream(width, dasharray) {
elements.mudStream.style.strokeWidth = width;
elements.mudStream.style.strokeDasharray = dasharray;
}
function animateMechanics(liftMm, weirPct, mudHeight) {
// Map 0-120mm lift to rotation angle
// Screen pivot is at x=750, y=420. Front is at x=250.
// Length = 500. Lift of 120mm means sin(theta) = 120/500 -> theta ~ 13.8 deg.
// We'll scale visual rotation slightly for better aesthetics.
const maxAngle = 10;
const angle = (liftMm / 120) * maxAngle;
elements.screen.style.transform = `rotate(${angle}deg)`;
// Servo rod moves down as screen front moves up (visually linking them)
const rodVisualShift = (liftMm / 120) * -30;
elements.servoRod.style.transform = `translateY(${rodVisualShift}px)`;
// Weir plate closes
// 100% open = 0 translate. 40% open = 60% closed = move down visually
const weirShift = (100 - weirPct) * 0.5; // visual mapping
elements.weirPlate.style.transform = `translateY(${weirShift}px)`;
// Mud pool physics
// As screen rotates up, mud pools higher at the back or maintains level.
// d="M 250 460 L 250 {mudHeight} Q 500 {mudHeight}, 750 420 L 750 460 Z"
// Adjust the front corner to match screen lift
const frontY = 460 - (liftMm / 120) * 35;
elements.mudPool.setAttribute('d', `M 250 460 L 250 ${mudHeight} Q 500 ${mudHeight + 10}, 750 420 L 750 460 Z`);
// Mud color shifts slightly darker when deep/surging
if(mudHeight < 400) {
elements.mudPool.style.fill = 'url(#mud-grad)';
elements.mudPool.style.opacity = '1';
} else {
elements.mudPool.style.fill = 'url(#mud-grad)';
elements.mudPool.style.opacity = '0.9';
}
}
// Auto-start loop on load
document.addEventListener("DOMContentLoaded", () => {
// Initial setup
updateState();
// Run sequencer every 1 second
setInterval(updateState, 1000);
// Add fast jitter loop for realistic sensor readouts (without changing state)
setInterval(() => {
if(time % 16 !== 0 && time % 16 !== 4 && time % 16 !== 9) { // Don't override exact frame changes
// Add tiny visual flutter to text
const curFlow = parseFloat(elements.valFlow.textContent);
elements.valFlow.textContent = (curFlow + (Math.random()*0.2-0.1)).toFixed(1) + ' L/s';
}
}, 300);
});
</script>
</body>
</html>
积分规则:每轮对话扣10分
等待动画代码生成...
