独立渲染引擎就绪引擎就绪
<!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-color: #0b0f19;
--panel-bg: #131a2a;
--border-color: #1f2937;
--text-main: #e2e8f0;
--text-muted: #94a3b8;
--accent-blue: #3b82f6;
--accent-cyan: #06b6d4;
--accent-orange: #f97316;
--accent-red: #ef4444;
--accent-green: #10b981;
--font-main: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-color);
color: var(--text-main);
font-family: var(--font-main);
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
background-image:
linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
background-size: 30px 30px;
background-position: center center;
}
header {
padding: 16px 24px;
background: rgba(11, 15, 25, 0.8);
border-bottom: 1px solid var(--border-color);
backdrop-filter: blur(10px);
display: flex;
justify-content: space-between;
align-items: center;
z-index: 10;
}
.title-block h1 {
font-size: 16px;
font-weight: 600;
letter-spacing: 0.5px;
color: var(--accent-cyan);
}
.title-block p {
font-size: 12px;
color: var(--text-muted);
margin-top: 4px;
}
.ifr-badge {
background: rgba(6, 182, 212, 0.1);
border: 1px solid rgba(6, 182, 212, 0.3);
color: var(--accent-cyan);
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
display: flex;
align-items: center;
gap: 6px;
}
.ifr-badge::before {
content: '';
display: block;
width: 8px;
height: 8px;
background: var(--accent-cyan);
border-radius: 50%;
box-shadow: 0 0 8px var(--accent-cyan);
animation: pulse-dot 2s infinite;
}
main {
flex: 1;
display: flex;
padding: 24px;
gap: 24px;
position: relative;
}
.panel {
background: var(--panel-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 20px;
display: flex;
flex-direction: column;
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
position: relative;
overflow: hidden;
}
/* 物理结构视图 */
.physical-view {
flex: 1;
min-width: 400px;
}
/* 数据图表视图 */
.digital-view {
flex: 1;
min-width: 400px;
}
.panel-title {
font-size: 14px;
font-weight: 600;
color: var(--text-muted);
margin-bottom: 16px;
display: flex;
justify-content: space-between;
}
.status-text {
font-family: var(--font-mono);
font-size: 12px;
color: var(--accent-green);
}
.svg-container {
flex: 1;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
svg {
width: 100%;
height: 100%;
max-height: 100%;
}
/* 数据面板覆盖物 */
.data-overlay {
position: absolute;
bottom: 20px;
left: 20px;
right: 20px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
pointer-events: none;
}
.data-box {
background: rgba(0, 0, 0, 0.4);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 6px;
padding: 8px 12px;
backdrop-filter: blur(4px);
}
.data-label {
font-size: 10px;
color: var(--text-muted);
text-transform: uppercase;
}
.data-value {
font-family: var(--font-mono);
font-size: 16px;
font-weight: 600;
color: var(--text-main);
margin-top: 4px;
display: flex;
align-items: baseline;
gap: 4px;
}
.data-unit {
font-size: 10px;
color: var(--text-muted);
}
/* 动画定义 */
@keyframes pulse-dot {
0% { transform: scale(0.95); opacity: 0.5; }
50% { transform: scale(1.1); opacity: 1; }
100% { transform: scale(0.95); opacity: 0.5; }
}
@keyframes dash {
to { stroke-dashoffset: 0; }
}
@keyframes transmit {
0% { opacity: 0; transform: translateX(-5px) scale(0.8); }
50% { opacity: 1; transform: translateX(0) scale(1.1); }
100% { opacity: 0; transform: translateX(5px) scale(0.8); }
}
/* 动态样式类 (由JS控制) */
.glow-active {
filter: drop-shadow(0 0 8px currentColor);
}
.state-idle .data-value { color: var(--accent-cyan); }
.state-accel .data-value { color: var(--accent-orange); }
.state-peak .data-value { color: var(--accent-red); }
.state-decel .data-value { color: var(--accent-green); }
/* 解释说明文字约束 */
.legend-overlay {
position: absolute;
top: 20px;
right: 20px;
font-size: 12px;
background: rgba(11, 15, 25, 0.85);
border: 1px solid var(--border-color);
padding: 10px;
border-radius: 6px;
pointer-events: none;
max-width: 200px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 6px;
color: var(--text-muted);
}
.legend-item:last-child { margin-bottom: 0; }
.legend-color {
width: 12px; height: 12px; border-radius: 3px;
}
</style>
</head>
<body>
<header>
<div class="title-block">
<h1>基于 TRIZ IFR 的无气泵传感器检测方案</h1>
<p>利用发动机本身作为压力源,消除内置气泵,实现轻量化与真实工况复现。</p>
</div>
<div class="ifr-badge">
最终理想解 (IFR) 状态演示
</div>
</header>
<main>
<!-- 左侧:物理系统原理视图 -->
<div class="panel physical-view">
<div class="panel-title">
<span>物理采集端 (现场机舱)</span>
<span class="status-text" id="engine-status">IDLE STANDBY</span>
</div>
<div class="svg-container">
<svg viewBox="0 0 500 500" preserveAspectRatio="xMidYMid meet" id="physical-svg">
<!-- 定义渐变和滤镜 -->
<defs>
<radialGradient id="engine-glow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#f97316" stop-opacity="0.3" id="glow-stop"/>
<stop offset="100%" stop-color="transparent" stop-opacity="0"/>
</radialGradient>
<linearGradient id="pipe-grad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#1e293b"/>
<stop offset="100%" stop-color="#0f172a"/>
</linearGradient>
<filter id="neon-blue" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="3" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<!-- 背景引擎区域暗示 -->
<circle cx="150" cy="350" r="120" fill="url(#engine-glow)" id="engine-heat"/>
<!-- 发动机进气歧管 (压力源) -->
<g transform="translate(50, 300)">
<path d="M 0,20 Q 50,-10 100,20 T 200,20" fill="none" stroke="#334155" stroke-width="40" stroke-linecap="round"/>
<path d="M 0,20 Q 50,-10 100,20 T 200,20" fill="none" stroke="url(#pipe-grad)" stroke-width="38" stroke-linecap="round"/>
<!-- 内部气流/压力指示 -->
<path d="M 10,20 Q 50,0 100,20 T 190,20" fill="none" stroke="#06b6d4" stroke-width="12" stroke-linecap="round" id="airflow-indicator" opacity="0.6"/>
<text x="100" y="60" fill="#64748b" font-size="12" font-family="monospace" text-anchor="middle">发动机进气歧管 (内源动力)</text>
</g>
<!-- 三通接头与气路 -->
<g transform="translate(150, 290)">
<!-- 主管道连接处 -->
<path d="M 0,0 L 0,-120" fill="none" stroke="#475569" stroke-width="16"/>
<path d="M 0,0 L 0,-120" fill="none" stroke="#1e293b" stroke-width="14"/>
<!-- 压力传递核心线 -->
<path d="M 0,-10 L 0,-110" fill="none" stroke="#06b6d4" stroke-width="6" id="pressure-line" class="glow-active"/>
<!-- 三通分流 -->
<path d="M -60,-120 L 60,-120" fill="none" stroke="#475569" stroke-width="12" stroke-linecap="round"/>
<path d="M -58,-120 L 58,-120" fill="none" stroke="#1e293b" stroke-width="10" stroke-linecap="round"/>
<path d="M 0,-110 L 0,-120 M 0,-120 L -50,-120 M 0,-120 L 50,-120" fill="none" stroke="#06b6d4" stroke-width="4" id="pressure-split" class="glow-active"/>
<text x="20" y="-80" fill="#94a3b8" font-size="10" font-family="monospace">免拆三通</text>
</g>
<!-- 参考压力传感器 (左) -->
<g transform="translate(70, 150)">
<rect x="0" y="0" width="40" height="40" rx="4" fill="#1e293b" stroke="#06b6d4" stroke-width="2"/>
<circle cx="20" cy="20" r="10" fill="#0f172a" stroke="#06b6d4" stroke-width="1"/>
<text x="20" y="-10" fill="#06b6d4" font-size="11" text-anchor="middle" font-weight="bold">参考传感器</text>
<text x="20" y="55" fill="#64748b" font-size="9" text-anchor="middle">高精度</text>
<!-- 信号线 -->
<path d="M 20,0 L 20,-30 L 120,-30" fill="none" stroke="#06b6d4" stroke-width="2" stroke-dasharray="4 2" class="signal-line"/>
</g>
<!-- 被测传感器 (右) -->
<g transform="translate(190, 150)">
<rect x="0" y="0" width="40" height="40" rx="4" fill="#1e293b" stroke="#f97316" stroke-width="2"/>
<polygon points="20,10 30,25 10,25" fill="#0f172a" stroke="#f97316" stroke-width="1"/>
<text x="20" y="-10" fill="#f97316" font-size="11" text-anchor="middle" font-weight="bold">被测传感器</text>
<text x="20" y="55" fill="#64748b" font-size="9" text-anchor="middle">待标定</text>
<!-- 信号线 -->
<path d="M 20,0 L 20,-20 L -20,-20" fill="none" stroke="#f97316" stroke-width="2" stroke-dasharray="4 2" class="signal-line"/>
</g>
<!-- 采集主机 (烟盒大小) -->
<g transform="translate(140, 50)">
<rect x="0" y="0" width="100" height="70" rx="6" fill="#131a2a" stroke="#475569" stroke-width="2"/>
<rect x="10" y="10" width="80" height="20" rx="2" fill="#0f172a" stroke="#1e293b"/>
<text x="50" y="24" fill="#10b981" font-size="10" font-family="monospace" text-anchor="middle">DAQ SYNC</text>
<text x="50" y="50" fill="#e2e8f0" font-size="12" text-anchor="middle" font-weight="bold">无泵采集主机</text>
<circle cx="85" cy="50" r="3" fill="#10b981" id="power-led" class="glow-active"/>
<!-- 蓝牙发射波 -->
<g transform="translate(110, 35)" stroke="#3b82f6" fill="none" stroke-width="2" stroke-linecap="round">
<path d="M 0,-10 Q 10,0 0,10" class="bt-wave" style="animation: transmit 1.5s infinite 0s;"/>
<path d="M 5,-20 Q 20,0 5,20" class="bt-wave" style="animation: transmit 1.5s infinite 0.3s;"/>
<path d="M 10,-30 Q 30,0 10,30" class="bt-wave" style="animation: transmit 1.5s infinite 0.6s;"/>
</g>
</g>
</svg>
</div>
<div class="data-overlay">
<div class="data-box" id="box-rpm">
<div class="data-label">发动机转速 (模拟油门)</div>
<div class="data-value" id="val-rpm">800 <span class="data-unit">RPM</span></div>
</div>
<div class="data-box" id="box-kpa">
<div class="data-label">流形绝对压力</div>
<div class="data-value" id="val-kpa">101.3 <span class="data-unit">kPa</span></div>
</div>
<div class="data-box" id="box-vol">
<div class="data-label">被测传感电压</div>
<div class="data-value" id="val-vol">1.05 <span class="data-unit">V</span></div>
</div>
</div>
</div>
<!-- 右侧:数字化诊断视图 -->
<div class="panel digital-view">
<div class="panel-title">
<span>实时特性曲线 (诊断端)</span>
<span class="status-text" style="color: var(--accent-blue);">BT CONNECTED</span>
</div>
<div class="svg-container">
<svg viewBox="0 0 500 500" preserveAspectRatio="xMidYMid meet">
<!-- 图表背景网格 -->
<g stroke="#1e293b" stroke-width="1">
<line x1="50" y1="50" x2="50" y2="450" />
<line x1="125" y1="50" x2="125" y2="450" stroke-dasharray="2 4"/>
<line x1="200" y1="50" x2="200" y2="450" stroke-dasharray="2 4"/>
<line x1="275" y1="50" x2="275" y2="450" stroke-dasharray="2 4"/>
<line x1="350" y1="50" x2="350" y2="450" stroke-dasharray="2 4"/>
<line x1="425" y1="50" x2="425" y2="450" stroke-dasharray="2 4"/>
<line x1="50" y1="450" x2="450" y2="450" />
<line x1="50" y1="350" x2="450" y2="350" stroke-dasharray="2 4"/>
<line x1="50" y1="250" x2="450" y2="250" stroke-dasharray="2 4"/>
<line x1="50" y1="150" x2="450" y2="150" stroke-dasharray="2 4"/>
<line x1="50" y1="50" x2="450" y2="50" stroke-dasharray="2 4"/>
</g>
<!-- 坐标轴标签 -->
<g fill="#64748b" font-size="10" font-family="monospace">
<!-- X轴 (参考压力 kPa) -->
<text x="455" y="465" text-anchor="end">参考压力 (kPaA)</text>
<text x="50" y="465" text-anchor="middle">100</text>
<text x="200" y="465" text-anchor="middle">200</text>
<text x="350" y="465" text-anchor="middle">300</text>
<!-- Y轴 (电压 V) -->
<text x="40" y="45" text-anchor="end">输出电压 (V)</text>
<text x="40" y="450" text-anchor="end">1.0</text>
<text x="40" y="350" text-anchor="end">2.0</text>
<text x="40" y="250" text-anchor="end">3.0</text>
<text x="40" y="150" text-anchor="end">4.0</text>
<text x="40" y="50" text-anchor="end">5.0</text>
</g>
<!-- 理想参考曲线 (虚线) -->
<path d="M 50,450 L 425,75" fill="none" stroke="#334155" stroke-width="2" stroke-dasharray="5 5" />
<!-- 动态绘制的实际曲线 -->
<!-- 加速上升段 (红色) -->
<path id="graph-path-up" d="M 50,450" fill="none" stroke="#ef4444" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" filter="url(#neon-blue)"/>
<!-- 减速下降段 (绿色,展现迟滞现象) -->
<path id="graph-path-down" d="" fill="none" stroke="#10b981" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" filter="url(#neon-blue)"/>
<!-- 当前采样点 -->
<circle id="graph-point" cx="50" cy="450" r="5" fill="#e2e8f0" stroke="#3b82f6" stroke-width="2" class="glow-active"/>
<!-- 动态虚线定位器 -->
<g id="crosshair" stroke="#e2e8f0" stroke-width="1" stroke-dasharray="2 2" opacity="0.3">
<line id="ch-v" x1="50" y1="50" x2="50" y2="450" />
<line id="ch-h" x1="50" y1="450" x2="450" y2="450" />
</g>
</svg>
</div>
<div class="legend-overlay">
<div class="legend-item">
<div class="legend-color" style="background: #334155; border: 1px dashed #64748b;"></div>
<span>标准出厂标定线</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #ef4444;"></div>
<span>加速(增压)上行轨迹</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #10b981;"></div>
<span>减速(泄压)下行轨迹</span>
</div>
<div class="legend-item" style="font-size: 10px; margin-top: 8px; border-top: 1px solid #1f2937; padding-top: 8px;">
* 曲线开口反映传感器迟滞失真
</div>
</div>
</div>
</main>
<script>
document.addEventListener("DOMContentLoaded", () => {
// DOM Elements
const pathUp = document.getElementById('graph-path-up');
const pathDown = document.getElementById('graph-path-down');
const graphPoint = document.getElementById('graph-point');
const chV = document.getElementById('ch-v');
const chH = document.getElementById('ch-h');
const valRpm = document.getElementById('val-rpm');
const valKpa = document.getElementById('val-kpa');
const valVol = document.getElementById('val-vol');
const statusText = document.getElementById('engine-status');
const airflowIndicator = document.getElementById('airflow-indicator');
const pressureLine = document.getElementById('pressure-line');
const pressureSplit = document.getElementById('pressure-split');
const engineHeat = document.getElementById('engine-heat');
const glowStop = document.getElementById('glow-stop');
// Animation Configuration
const cycleDuration = 12000; // 12 seconds per cycle
let startTime = null;
// Physical parameters
const minRpm = 800;
const maxRpm = 2500;
const minKpa = 100; // Atmospheric pressure
const maxKpa = 300; // Turbo boost
// Graph Mapping (50,450 to 450,50)
// X: 100kPa -> 50px, 300kPa -> 350px. Formula: x = 50 + (kpa - 100) * (300/200) = 50 + (kpa-100)*1.5
// Y: 1V -> 450px, 4V -> 150px. Formula: y = 450 - (vol - 1) * (300/3) = 450 - (vol-1)*100
function mapX(kpa) { return 50 + (kpa - 100) * 1.5; }
function mapY(vol) { return 450 - (vol - 1.0) * 100; }
// Sensor Simulation with hysteresis
function simulateVoltage(kpa, isAscending) {
// Ideal linear: 1V at 100kPa, 4V at 300kPa -> v = 1 + (kpa-100)*3/200
let idealV = 1.0 + (kpa - 100) * 0.015;
// Add hysteresis (lag): lower voltage when going up, higher when going down
let hysteresis = isAscending ? -0.08 : 0.12;
if (kpa < 110) hysteresis *= (kpa - 100)/10; // smooth start
if (kpa > 290) hysteresis *= (300 - kpa)/10; // smooth end
return Math.max(0.5, Math.min(4.5, idealV + hysteresis + (Math.random()*0.02 - 0.01))); // Add tiny noise
}
let pathUpD = "M 50,450 ";
let pathDownD = "";
let lastKpa = minKpa;
function animate(timestamp) {
if (!startTime) startTime = timestamp;
const elapsed = timestamp - startTime;
const progress = (elapsed % cycleDuration) / cycleDuration;
let phase = '';
let rpm = minRpm, kpa = minKpa, vol = 1.0;
let isAscending = true;
// State Machine (Time distribution)
// 0.0 - 0.15: Idle (800 RPM)
// 0.15 - 0.40: Rapid Accel (800 -> 2500 RPM)
// 0.40 - 0.55: Hold Peak (2500 RPM)
// 0.55 - 0.85: Decel (2500 -> 800 RPM)
// 0.85 - 1.00: Idle settling
if (progress < 0.15) {
phase = 'IDLE';
// New cycle reset
if (progress < 0.02) {
pathUpD = "M 50,450 ";
pathDownD = "";
pathUp.setAttribute('d', pathUpD);
pathDown.setAttribute('d', pathDownD);
}
kpa = minKpa + (Math.random()*2-1); // slight fluctuation
} else if (progress < 0.40) {
phase = 'ACCEL';
let t = (progress - 0.15) / 0.25;
// Ease-in-out curve for RPM/Pressure
let ease = t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
rpm = minRpm + (maxRpm - minRpm) * ease;
kpa = minKpa + (maxKpa - minKpa) * ease;
} else if (progress < 0.55) {
phase = 'PEAK HOLD';
rpm = maxRpm + (Math.random()*20-10);
kpa = maxKpa + (Math.random()*5-2.5);
isAscending = false; // Prepare for turn
} else if (progress < 0.85) {
phase = 'DECEL / COAST';
isAscending = false;
let t = (progress - 0.55) / 0.30;
// Exponential decay for pressure drop
let ease = 1 - Math.pow(1 - t, 3);
rpm = maxRpm - (maxRpm - minRpm) * ease;
kpa = maxKpa - (maxKpa - minKpa) * ease;
} else {
phase = 'IDLE RETURN';
isAscending = false;
kpa = minKpa + (Math.random()*2-1);
}
vol = simulateVoltage(kpa, isAscending);
// Update DOM UI
valRpm.innerHTML = `${Math.round(rpm)} <span class="data-unit">RPM</span>`;
valKpa.innerHTML = `${kpa.toFixed(1)} <span class="data-unit">kPaA</span>`;
valVol.innerHTML = `${vol.toFixed(3)} <span class="data-unit">V</span>`;
statusText.innerText = phase;
// Color coding based on phase
document.body.className = phase === 'ACCEL' ? 'state-accel' :
phase === 'PEAK HOLD' ? 'state-peak' :
phase === 'DECEL / COAST' ? 'state-decel' : 'state-idle';
// Update Physical Visuals
// Color temperature mapping for airflow (Cyan -> Orange -> Red)
let pressureRatio = (kpa - minKpa) / (maxKpa - minKpa);
let r = Math.round(6 + pressureRatio * 233); // 06 -> EF
let g = Math.round(182 - pressureRatio * 114); // B6 -> 44
let b = Math.round(212 - pressureRatio * 144); // D4 -> 44
let color = `rgb(${r},${g},${b})`;
airflowIndicator.setAttribute('stroke', color);
pressureLine.setAttribute('stroke', color);
pressureSplit.setAttribute('stroke', color);
glowStop.setAttribute('stop-color', color);
engineHeat.setAttribute('r', 120 + pressureRatio * 30);
// Stroke dasharray animation speed based on RPM
airflowIndicator.setAttribute('stroke-dasharray', `${10 + pressureRatio*20} ${10 + pressureRatio*5}`);
airflowIndicator.style.strokeDashoffset = -(elapsed * (0.05 + pressureRatio*0.15)) + 'px';
// Update Graph
let x = mapX(kpa);
let y = mapY(vol);
graphPoint.setAttribute('cx', x);
graphPoint.setAttribute('cy', y);
chV.setAttribute('x1', x); chV.setAttribute('x2', x);
chH.setAttribute('y1', y); chH.setAttribute('y2', y);
// Build path string based on phase
if (phase === 'ACCEL' || (phase === 'IDLE' && progress > 0.1)) {
pathUpD += ` L ${x.toFixed(1)},${y.toFixed(1)}`;
pathUp.setAttribute('d', pathUpD);
if (pathDownD === "") pathDownD = `M ${x.toFixed(1)},${y.toFixed(1)}`; // Sync start point for down path
} else if (phase === 'PEAK HOLD' || phase === 'DECEL / COAST' || phase === 'IDLE RETURN') {
// Start new segment if transitioning
if(pathDownD === "" || pathDownD.startsWith("M") && pathDownD.length < 20) {
pathDownD = `M ${x.toFixed(1)},${y.toFixed(1)}`;
} else {
pathDownD += ` L ${x.toFixed(1)},${y.toFixed(1)}`;
}
pathDown.setAttribute('d', pathDownD);
}
lastKpa = kpa;
requestAnimationFrame(animate);
}
// Start animation loop
requestAnimationFrame(animate);
});
</script>
</body>
</html>
积分规则:每轮对话扣10分
等待动画代码生成...
