|
|
@@ -0,0 +1,475 @@
|
|
|
+/**
|
|
|
+ * 瓦斯排放仿真 composable
|
|
|
+ *
|
|
|
+ * 核心架构:
|
|
|
+ * - 纯前端仿真,不修改 selectData,数据完全自包含
|
|
|
+ * - 以 1 仿真秒为步长连续推进,速度倍率仅控制 setInterval 间隔
|
|
|
+ * - 仿真时间 = 真实模拟用时(10min/调频周期),速度倍率压缩的是用户等待时间
|
|
|
+ *
|
|
|
+ * 状态机:IDLE → RUNNING → WINDING_DOWN(HOLD → REDUCE)→ COMPLETED
|
|
|
+ * 外加 PAUSED / ABORTED
|
|
|
+ *
|
|
|
+ * 7大业务阶段(日志 marker):
|
|
|
+ * 1.监测异常 2.启动排放 3.计算排放频率 4.调整频率
|
|
|
+ * 5.监测回风流 6.动态调整 7.停止排放
|
|
|
+ */
|
|
|
+import { ref, reactive, computed, watch, onUnmounted } from 'vue';
|
|
|
+import {
|
|
|
+ GasDischargeSimState,
|
|
|
+ GasDischargeConfig,
|
|
|
+ GasDischargeSimDataPoint,
|
|
|
+ GasDischargeEventLogEntry,
|
|
|
+ GAS_DISCHARGE_DEFAULT_CONFIG,
|
|
|
+ GAS_DISCHARGE_STEP_NAMES,
|
|
|
+ lookupFrequencyByGas1,
|
|
|
+} from '../fanLocal.data';
|
|
|
+
|
|
|
+export function useGasDischargeSimulation(selectData: Record<string, any>) {
|
|
|
+ // ======================== 响应式状态(暴露给 UI) ========================
|
|
|
+
|
|
|
+ const simState = ref<GasDischargeSimState>(GasDischargeSimState.IDLE); // 当前仿真阶段
|
|
|
+ const config = reactive<GasDischargeConfig>({ ...GAS_DISCHARGE_DEFAULT_CONFIG }); // 可运行中热修改的参数
|
|
|
+ const speed = ref(5); // 倍速 1/2/5/10,仅影响 setInterval 周期
|
|
|
+ const elapsedSimSec = ref(0); // 已流逝的仿真秒数(1秒/步,不受倍速影响)
|
|
|
+ const sustainedSec = ref(0); // 浓度持续达标的累积秒数
|
|
|
+
|
|
|
+ // 当前瞬时数据:仪表盘 + 实时数值以此为准
|
|
|
+ const currentData = reactive<GasDischargeSimDataPoint>({
|
|
|
+ simTime: 0,
|
|
|
+ realTime: '',
|
|
|
+ gas1: config.initialGas1,
|
|
|
+ gas2: config.initialGas2,
|
|
|
+ gas3: config.initialGas3,
|
|
|
+ co2: config.initialGas2 * config.co2EmissionRatio + 0.04,
|
|
|
+ frequencyHz: config.normalFrequencyHz,
|
|
|
+ airVolumeM3: config.frequencyK * (config.normalFrequencyHz / 50) * config.ratedAirVolume,
|
|
|
+ windSpeed: (config.frequencyK * (config.normalFrequencyHz / 50) * config.ratedAirVolume) / 60 / config.crossSection,
|
|
|
+ state: GasDischargeSimState.IDLE,
|
|
|
+ });
|
|
|
+
|
|
|
+ const timeSeries = ref<GasDischargeSimDataPoint[]>([]); // 趋势图用,最多 300 点
|
|
|
+ const eventLog = ref<GasDischargeEventLogEntry[]>([]); // 事件日志,最多 200 条
|
|
|
+ const runningFanPrefix = ref<'Fan1' | 'Fan2'>('Fan1'); // 识别当前运行的风机
|
|
|
+
|
|
|
+ // ======================== 内部变量(不暴露) ========================
|
|
|
+
|
|
|
+ let tickTimer: ReturnType<typeof setInterval> | null = null; // 主循环定时器
|
|
|
+ let eventIdCounter = 0; // 事件递增ID
|
|
|
+ let savedInitialFreq = 35; // 启动时保存的真实频率(作为降频终点)
|
|
|
+ let gas1History: number[] = []; // 工作面瓦斯历史,用于回风延迟模型
|
|
|
+ let lastGas2Warning = false; // 防止重复预警日志
|
|
|
+ let prevGas1 = 0; // 上周期工作面瓦斯(趋势判断用)
|
|
|
+ let prevGas2 = 0; // 上周期回风瓦斯
|
|
|
+ let gas2Peak = 0; // HOLD 阶段追踪的 gas2 峰值
|
|
|
+ let holdPhase = false; // 是否处于锁频等待阶段
|
|
|
+ let phase3Logged = false; // "计算排放频率" marker 是否已打
|
|
|
+
|
|
|
+ // ======================== 计算属性 ========================
|
|
|
+
|
|
|
+ const isRunning = computed(() => [GasDischargeSimState.RUNNING, GasDischargeSimState.WINDING_DOWN].includes(simState.value));
|
|
|
+ const stateLabel = computed(() => GAS_DISCHARGE_STEP_NAMES[simState.value]);
|
|
|
+
|
|
|
+ // ======================== 辅助函数 ========================
|
|
|
+
|
|
|
+ /** 秒数 → HH:MM:SS 显示 */
|
|
|
+ function formatElapsed(sec: number): string {
|
|
|
+ const h = Math.floor(sec / 3600),
|
|
|
+ m = Math.floor((sec % 3600) / 60),
|
|
|
+ s = Math.floor(sec % 60);
|
|
|
+ return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 日志时间戳 = baseTime + 仿真秒(本地时间,避免 UTC 时差) */
|
|
|
+ function realTimeStr(): string {
|
|
|
+ const d = new Date(config.baseTime);
|
|
|
+ d.setSeconds(d.getSeconds() + elapsedSimSec.value);
|
|
|
+ const p = (n: number) => String(n).padStart(2, '0');
|
|
|
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 添加事件日志,自动截断保持 200 条以内 */
|
|
|
+ function addEvent(type: GasDischargeEventLogEntry['eventType'], message: string) {
|
|
|
+ const entry: GasDischargeEventLogEntry = {
|
|
|
+ id: ++eventIdCounter,
|
|
|
+ simTime: elapsedSimSec.value,
|
|
|
+ realTime: realTimeStr(),
|
|
|
+ eventType: type,
|
|
|
+ message,
|
|
|
+ gas1: currentData.gas1,
|
|
|
+ gas2: currentData.gas2,
|
|
|
+ co2: currentData.co2,
|
|
|
+ frequencyHz: currentData.frequencyHz,
|
|
|
+ airVolumeM3: currentData.airVolumeM3,
|
|
|
+ windSpeed: currentData.windSpeed,
|
|
|
+ };
|
|
|
+ eventLog.value = eventLog.value.length >= 200 ? [...eventLog.value.slice(1), entry] : [...eventLog.value, entry];
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 状态切换并记录日志 */
|
|
|
+ function transitionTo(newState: GasDischargeSimState) {
|
|
|
+ const oldLabel = GAS_DISCHARGE_STEP_NAMES[simState.value];
|
|
|
+ simState.value = newState;
|
|
|
+ currentData.state = newState;
|
|
|
+ addEvent('state_change', `${oldLabel} → ${GAS_DISCHARGE_STEP_NAMES[newState]}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 回风延迟模型:取 delaySec 秒前的 gas1 值
|
|
|
+ * gas2(t) = gas1(t - delaySec) × decayFactor
|
|
|
+ */
|
|
|
+ function getDelayedGas1(delaySec: number): number {
|
|
|
+ const idx = Math.floor(delaySec);
|
|
|
+ if (idx >= gas1History.length) return gas1History[0] ?? config.initialGas1;
|
|
|
+ return gas1History[gas1History.length - 1 - idx];
|
|
|
+ }
|
|
|
+
|
|
|
+ // ======================== 仿真核心:每步 1 仿真秒 ========================
|
|
|
+
|
|
|
+ function simulationTick() {
|
|
|
+ const cfg = config; // 局部别名,减少模板字符串长度
|
|
|
+ const f = currentData.frequencyHz;
|
|
|
+
|
|
|
+ // -------- 1. 风量 --------
|
|
|
+ // 一阶惯性滞后模拟空气柱惯性 + 微小湍流噪声
|
|
|
+ const Q_target = cfg.frequencyK * (f / 50) * cfg.ratedAirVolume; // m³/min
|
|
|
+ const Q_prev = currentData.airVolumeM3 > 0 ? currentData.airVolumeM3 : Q_target;
|
|
|
+ const Q_actual = Q_prev + (Q_target - Q_prev) * 0.35; // 每步逼近35%
|
|
|
+ const noise = (Math.random() - 0.5) * Q_target * 0.02; // ±1% 湍流
|
|
|
+ currentData.airVolumeM3 = Math.round((Q_actual + noise) * 100) / 100;
|
|
|
+
|
|
|
+ // -------- 2. 风速 --------
|
|
|
+ // V = Q / 60 / A,安全约束 [minWindSpeed, maxWindSpeed]
|
|
|
+ const V = Q_actual / 60 / cfg.crossSection;
|
|
|
+ currentData.windSpeed = Math.round(V * 100) / 100;
|
|
|
+
|
|
|
+ // -------- 3. 工作面瓦斯稀释 --------
|
|
|
+ // dC/dt = (emissionRate - Q_s·(C - C_inlet)) / workfaceVolume
|
|
|
+ // 变化幅度 ×0.2 延长时间匹配 10min 调频周期
|
|
|
+ const Q_s = Q_actual / 60; // m³/s
|
|
|
+ const deltaC = ((cfg.emissionRate - Q_s * (currentData.gas1 - currentData.gas3)) / cfg.workfaceVolume) * 0.05;
|
|
|
+ currentData.gas1 = Math.max(0, currentData.gas1 + deltaC);
|
|
|
+
|
|
|
+ // -------- 4. CO₂ 独立稀释 --------
|
|
|
+ // CO₂ 涌出率 = 瓦斯涌出率 × 比率,与瓦斯独立但共享 Q 和 V
|
|
|
+ const co2Emission = cfg.emissionRate * cfg.co2EmissionRatio;
|
|
|
+ const deltaCo2 = (co2Emission - Q_s * (currentData.co2 - 0.04)) / cfg.workfaceVolume;
|
|
|
+ currentData.co2 = Math.max(0, currentData.co2 + deltaCo2);
|
|
|
+
|
|
|
+ // -------- 5. gas1 历史 --------
|
|
|
+ // 用于回风延迟模型的滑动窗口,保留最近 600 秒
|
|
|
+ gas1History.push(currentData.gas1);
|
|
|
+ if (gas1History.length > 600) gas1History.shift();
|
|
|
+
|
|
|
+ // -------- 6. 回风流瓦斯 --------
|
|
|
+ // gas2(t) = gas1(t - delaySec) × decayFactor
|
|
|
+ // delaySec = tunnelLength / windSpeed (风速越高延迟越短)
|
|
|
+ // 迫近 1.0% 时随机波动避免触碰红线;达标稳定后阻尼平滑
|
|
|
+ const delaySec = Math.floor(cfg.tunnelLength / (currentData.windSpeed || 1));
|
|
|
+ const rawGas2 = getDelayedGas1(delaySec) * cfg.decayFactor;
|
|
|
+ if (rawGas2 >= 0.9) {
|
|
|
+ currentData.gas2 = 0.85 + Math.random() * 0.12;
|
|
|
+ } else {
|
|
|
+ const newGas2 = Math.max(0, rawGas2);
|
|
|
+ const isStable = simState.value === GasDischargeSimState.WINDING_DOWN || holdPhase;
|
|
|
+ const damp = isStable && newGas2 < currentData.gas2 && newGas2 > 0.87 ? 0.13 : 0.7;
|
|
|
+ currentData.gas2 = currentData.gas2 + (newGas2 - currentData.gas2) * damp;
|
|
|
+ }
|
|
|
+
|
|
|
+ // -------- 7. 入口瓦斯 --------
|
|
|
+ currentData.gas3 = Math.max(0, cfg.initialGas3 + (Math.random() - 0.5) * 0.005);
|
|
|
+
|
|
|
+ // -------- 8. 预警(仅视觉)--------
|
|
|
+ if (currentData.gas2 >= 1.0 && !lastGas2Warning) {
|
|
|
+ addEvent('warning', `T2: ${currentData.gas2.toFixed(2)}%≥1.0%,注意监控`);
|
|
|
+ lastGas2Warning = true;
|
|
|
+ } else if (currentData.gas2 < 0.9) {
|
|
|
+ lastGas2Warning = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ======== 9. RUNNING 阶段:渐进分级调频 ========
|
|
|
+ // 每隔 cycleDurationMin 分钟检查一次
|
|
|
+ if (simState.value === GasDischargeSimState.RUNNING) {
|
|
|
+ if (elapsedSimSec.value % (cfg.cycleDurationMin * 60) === 0) {
|
|
|
+ const lookup = lookupFrequencyByGas1(currentData.gas1); // 查分段表
|
|
|
+ const targetFreq = lookup.frequency; // 本段目标频率
|
|
|
+ const step = lookup.stepHz; // 本段步进
|
|
|
+ const oldFreq = currentData.frequencyHz;
|
|
|
+
|
|
|
+ // 7大阶段-3: 首次打印"计算排放频率"
|
|
|
+ if (!phase3Logged) {
|
|
|
+ addEvent('info', `计算排放频率 T1=${currentData.gas1.toFixed(2)}% → 目标${targetFreq}Hz (${lookup.label}) 步进+${step}Hz`);
|
|
|
+ phase3Logged = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // gas2 迫近 0.9% 时加速逼近(effStep = 55% 差距),否则正常渐进
|
|
|
+ const effStep = currentData.gas2 >= 0.9 ? (targetFreq - f) * 0.55 : step;
|
|
|
+ // 风速上下限约束
|
|
|
+ const maxFreqByWind = f + (currentData.windSpeed >= cfg.maxWindSpeed ? 0 : effStep);
|
|
|
+ const minFreqByWind = f - (currentData.windSpeed <= cfg.minWindSpeed ? 0 : step);
|
|
|
+
|
|
|
+ if (f < targetFreq) {
|
|
|
+ currentData.frequencyHz = Math.min(targetFreq, maxFreqByWind);
|
|
|
+ } else if (f > targetFreq) {
|
|
|
+ currentData.frequencyHz = Math.max(targetFreq, minFreqByWind);
|
|
|
+ }
|
|
|
+ currentData.frequencyHz = Math.max(20, Math.min(50, currentData.frequencyHz));
|
|
|
+ currentData.frequencyHz = Math.round(currentData.frequencyHz * 100) / 100;
|
|
|
+
|
|
|
+ // 7大阶段-4/5/6: 实际频率变化时打日志
|
|
|
+ if (Math.abs(currentData.frequencyHz - oldFreq) > 0.05) {
|
|
|
+ addEvent('info', `监测回风流瓦斯 T2=${currentData.gas2.toFixed(2)}% < 1.0% 正常,动态调整频率`);
|
|
|
+ addEvent(
|
|
|
+ 'frequency_adjust',
|
|
|
+ `${oldFreq.toFixed(1)}→${currentData.frequencyHz.toFixed(1)}Hz (${lookup.label}) T1:${currentData.gas1.toFixed(2)}% T2:${currentData.gas2.toFixed(2)}%`
|
|
|
+ );
|
|
|
+ }
|
|
|
+ if (currentData.windSpeed >= cfg.maxWindSpeed) {
|
|
|
+ addEvent('speed_limit', `风速${currentData.windSpeed.toFixed(1)}m/s≥${cfg.maxWindSpeed}m/s,限制增频`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ======== 10. WINDING_DOWN 阶段 ========
|
|
|
+ // 子阶段 HOLD: 频率锁死,跟踪 gas2 峰值,等回落 0.2% 后进入 REDUCE
|
|
|
+ // 子阶段 REDUCE: 趋势驱动 -0.1Hz,完成条件后 COMPLETED
|
|
|
+ if (simState.value === GasDischargeSimState.WINDING_DOWN) {
|
|
|
+ // 首次进入:初始化 HOLD
|
|
|
+ if (!holdPhase && gas2Peak === 0) {
|
|
|
+ holdPhase = true;
|
|
|
+ gas2Peak = currentData.gas2;
|
|
|
+ prevGas1 = currentData.gas1;
|
|
|
+ prevGas2 = currentData.gas2;
|
|
|
+ addEvent('info', `进入保持阶段,频率${currentData.frequencyHz.toFixed(1)}Hz不变,等待T2峰值回落(T2Peak=${gas2Peak.toFixed(2)}%)`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 每帧更新 gas2 峰值
|
|
|
+ if (holdPhase && currentData.gas2 > gas2Peak) {
|
|
|
+ gas2Peak = currentData.gas2;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (elapsedSimSec.value % (cfg.cycleDurationMin * 60) === 0) {
|
|
|
+ const oldFreq = currentData.frequencyHz;
|
|
|
+ // 最低频率 = max(20, 风速下限对应的频率)
|
|
|
+ const minFreq = Math.max(20, (cfg.minWindSpeed * 60 * cfg.crossSection) / ((cfg.frequencyK * cfg.ratedAirVolume) / 50));
|
|
|
+
|
|
|
+ if (holdPhase) {
|
|
|
+ // HOLD: 频率不变,等待 gas2 从峰值回落 ≥0.2%
|
|
|
+ const drop = gas2Peak - currentData.gas2;
|
|
|
+ if (drop >= 0.2 && currentData.gas2 < 0.9) {
|
|
|
+ holdPhase = false;
|
|
|
+ addEvent(
|
|
|
+ 'info',
|
|
|
+ `T2峰值${gas2Peak.toFixed(2)}%回落${drop.toFixed(2)}≥0.2,降频中…(当前${currentData.frequencyHz.toFixed(1)}Hz→目标${savedInitialFreq}Hz) T1:${currentData.gas1.toFixed(2)}%`
|
|
|
+ );
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // REDUCE: gas1 < 0.3% 且趋势健康 → -0.1Hz
|
|
|
+ if (currentData.gas1 < 0.3) {
|
|
|
+ const delta1 = currentData.gas1 - prevGas1; // T1 本周期变化
|
|
|
+ const delta2 = currentData.gas2 - prevGas2; // T2 本周期变化
|
|
|
+ if (delta1 < 0 && delta2 < delta1) {
|
|
|
+ // T1↓ + T2↓↓(更快) → 安全降频
|
|
|
+ currentData.frequencyHz = Math.max(minFreq, savedInitialFreq, currentData.frequencyHz - 0.1);
|
|
|
+ currentData.frequencyHz = Math.round(currentData.frequencyHz * 100) / 100;
|
|
|
+ if (currentData.frequencyHz < oldFreq) {
|
|
|
+ addEvent(
|
|
|
+ 'frequency_adjust',
|
|
|
+ `降频(-0.1): ${oldFreq.toFixed(1)}→${currentData.frequencyHz.toFixed(1)}Hz [T1↓${Math.abs(delta1).toFixed(2)} T2↓${Math.abs(delta2).toFixed(2)}] T1:${currentData.gas1.toFixed(2)}% T2:${currentData.gas2.toFixed(2)}%`
|
|
|
+ );
|
|
|
+ }
|
|
|
+ } else if (delta1 >= 0) {
|
|
|
+ addEvent('info', `暂停降频: T1↑${delta1.toFixed(2)} T1:${currentData.gas1.toFixed(2)}% T2:${currentData.gas2.toFixed(2)}%`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ prevGas1 = currentData.gas1;
|
|
|
+ prevGas2 = currentData.gas2;
|
|
|
+
|
|
|
+ // COMPLETED 五条件:freq≤初始 && T1<0.3% && T2<0.2% && T2<T1 && CO2<1.5%
|
|
|
+ if (
|
|
|
+ !holdPhase &&
|
|
|
+ currentData.frequencyHz <= savedInitialFreq + 0.3 &&
|
|
|
+ currentData.gas1 < 0.3 &&
|
|
|
+ currentData.gas2 < 0.2 &&
|
|
|
+ currentData.gas2 < currentData.gas1 &&
|
|
|
+ currentData.co2 < 1.5
|
|
|
+ ) {
|
|
|
+ currentData.frequencyHz = savedInitialFreq;
|
|
|
+ transitionTo(GasDischargeSimState.COMPLETED);
|
|
|
+ // 7大阶段-7: 停止排放
|
|
|
+ addEvent('info', `停止排放 T1=${currentData.gas1.toFixed(2)}% T2=${currentData.gas2.toFixed(2)}% freq=${savedInitialFreq}Hz`);
|
|
|
+ stopTick();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ======== 11. 达标判断(RUNNING → WINDING_DOWN) ========
|
|
|
+ // T1<1.0% && T2<1.0% && T2≤T1 && CO2<1.5% 持续 stableDurationSec → 进入降频
|
|
|
+ if (simState.value === GasDischargeSimState.RUNNING) {
|
|
|
+ if (
|
|
|
+ currentData.gas1 < cfg.stopGasThreshold &&
|
|
|
+ currentData.gas2 < cfg.stopGasThreshold &&
|
|
|
+ currentData.gas2 <= currentData.gas1 &&
|
|
|
+ currentData.co2 < 1.5
|
|
|
+ ) {
|
|
|
+ sustainedSec.value++;
|
|
|
+ if (sustainedSec.value >= cfg.stableDurationSec) {
|
|
|
+ transitionTo(GasDischargeSimState.WINDING_DOWN);
|
|
|
+ addEvent(
|
|
|
+ 'info',
|
|
|
+ `达标: T1=${currentData.gas1.toFixed(2)}% T2=${currentData.gas2.toFixed(2)}% CO2=${currentData.co2.toFixed(2)}% 持续${cfg.stableDurationSec}s,进入保持阶段`
|
|
|
+ );
|
|
|
+ sustainedSec.value = 0;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ sustainedSec.value = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ======== 12. 推进 ========
|
|
|
+ const point: GasDischargeSimDataPoint = { ...currentData };
|
|
|
+ timeSeries.value = timeSeries.value.length >= 300 ? [...timeSeries.value.slice(1), point] : [...timeSeries.value, point];
|
|
|
+ elapsedSimSec.value++;
|
|
|
+ currentData.simTime = elapsedSimSec.value;
|
|
|
+ currentData.realTime = realTimeStr();
|
|
|
+ }
|
|
|
+
|
|
|
+ // ======================== 定时器 ========================
|
|
|
+ function startTick() {
|
|
|
+ stopTick();
|
|
|
+ tickTimer = setInterval(simulationTick, Math.max(50, 1000 / speed.value));
|
|
|
+ }
|
|
|
+ function stopTick() {
|
|
|
+ if (tickTimer) {
|
|
|
+ clearInterval(tickTimer);
|
|
|
+ tickTimer = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ watch(speed, () => {
|
|
|
+ if (isRunning.value) startTick();
|
|
|
+ });
|
|
|
+
|
|
|
+ // ======================== 对外方法 ========================
|
|
|
+
|
|
|
+ /** 启动仿真:读取真实频率作为起点,递进式预填充历史,开始渐进排放 */
|
|
|
+ function start() {
|
|
|
+ resetInternal();
|
|
|
+
|
|
|
+ // 识别当前运行的风机
|
|
|
+ const f1 = selectData.Fan1StartStatus == 1;
|
|
|
+ const f2 = selectData.Fan2StartStatus == 1;
|
|
|
+ runningFanPrefix.value = f2 && !f1 ? 'Fan2' : 'Fan1';
|
|
|
+
|
|
|
+ // 读取真实频率(Fan1fHz/Fan2fHz),兜底为配置默认值
|
|
|
+ const realFreq = Number(selectData[runningFanPrefix.value + 'fHz']);
|
|
|
+ savedInitialFreq = !isNaN(realFreq) && realFreq > 0 ? realFreq : config.normalFrequencyHz;
|
|
|
+
|
|
|
+ // 读取真实风量 windQuantity1,兜底公式计算
|
|
|
+ const realAirVolume = selectData['windQuantity1'];
|
|
|
+ const initAirVolume =
|
|
|
+ typeof realAirVolume === 'number' && realAirVolume > 0 ? realAirVolume : config.frequencyK * (savedInitialFreq / 50) * config.ratedAirVolume;
|
|
|
+
|
|
|
+ // 初始化当前数据
|
|
|
+ currentData.gas1 = config.initialGas1;
|
|
|
+ currentData.gas2 = config.initialGas2;
|
|
|
+ currentData.gas3 = config.initialGas3;
|
|
|
+ currentData.co2 = config.initialGas2 * config.co2EmissionRatio + 0.04;
|
|
|
+ currentData.frequencyHz = savedInitialFreq;
|
|
|
+ currentData.airVolumeM3 = initAirVolume;
|
|
|
+ currentData.windSpeed = currentData.airVolumeM3 / 60 / config.crossSection;
|
|
|
+ currentData.simTime = 0;
|
|
|
+ currentData.realTime = realTimeStr();
|
|
|
+
|
|
|
+ // 递进式预填充 gas1History:前半段正常浓度 → 后半段逐步累积到超限
|
|
|
+ // 上限约束:确保 gas2 = gas1 × decayFactor ≤ 1.0%
|
|
|
+ const delaySec = Math.floor(config.tunnelLength / (currentData.windSpeed || 1));
|
|
|
+ const steadyGas1 = config.initialGas2 / Math.max(0.01, config.decayFactor); // 正常状态对应瓦斯
|
|
|
+ const maxSafeGas1 = 1.0 / Math.max(0.01, config.decayFactor); // gas2≤1.0% 对应的 gas1 上限
|
|
|
+ const cappedInitialGas1 = Math.min(config.initialGas1, maxSafeGas1);
|
|
|
+ const halfDelay = Math.floor(delaySec / 2);
|
|
|
+ const rampSteps = delaySec - halfDelay || 1;
|
|
|
+ gas1History = [];
|
|
|
+ for (let i = 0; i <= delaySec; i++) {
|
|
|
+ if (i < halfDelay) {
|
|
|
+ gas1History.push(steadyGas1);
|
|
|
+ } else {
|
|
|
+ const rampVal = steadyGas1 + (cappedInitialGas1 - steadyGas1) * ((i - halfDelay) / rampSteps);
|
|
|
+ gas1History.push(Math.min(rampVal, maxSafeGas1));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ lastGas2Warning = false;
|
|
|
+
|
|
|
+ // 7大阶段-1/2
|
|
|
+ addEvent('info', `监测到瓦斯浓度异常 T1=${config.initialGas1}% > ${config.stopGasThreshold}%`);
|
|
|
+ addEvent('info', `启动瓦斯排放 当前频率=${savedInitialFreq}Hz 风机${runningFanPrefix.value === 'Fan1' ? '1#' : '2#'}`);
|
|
|
+ phase3Logged = false;
|
|
|
+ transitionTo(GasDischargeSimState.RUNNING);
|
|
|
+ startTick();
|
|
|
+ }
|
|
|
+
|
|
|
+ function pause() {
|
|
|
+ if (simState.value !== GasDischargeSimState.RUNNING) return;
|
|
|
+ stopTick();
|
|
|
+ transitionTo(GasDischargeSimState.PAUSED);
|
|
|
+ }
|
|
|
+ function resume() {
|
|
|
+ if (simState.value !== GasDischargeSimState.PAUSED) return;
|
|
|
+ transitionTo(GasDischargeSimState.RUNNING);
|
|
|
+ startTick();
|
|
|
+ }
|
|
|
+ function abort() {
|
|
|
+ stopTick();
|
|
|
+ if (simState.value !== GasDischargeSimState.IDLE) transitionTo(GasDischargeSimState.ABORTED);
|
|
|
+ simState.value = GasDischargeSimState.IDLE;
|
|
|
+ }
|
|
|
+ function updateConfig(partial: Partial<GasDischargeConfig>) {
|
|
|
+ Object.assign(config, partial);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 重置全部内部状态(不清配置) */
|
|
|
+ function resetInternal() {
|
|
|
+ stopTick();
|
|
|
+ elapsedSimSec.value = 0;
|
|
|
+ sustainedSec.value = 0;
|
|
|
+ eventIdCounter = 0;
|
|
|
+ gas1History = [];
|
|
|
+ lastGas2Warning = false;
|
|
|
+ prevGas1 = 0;
|
|
|
+ prevGas2 = 0;
|
|
|
+ gas2Peak = 0;
|
|
|
+ holdPhase = false;
|
|
|
+ phase3Logged = false;
|
|
|
+ timeSeries.value = [];
|
|
|
+ eventLog.value = [];
|
|
|
+ simState.value = GasDischargeSimState.IDLE;
|
|
|
+ currentData.state = GasDischargeSimState.IDLE;
|
|
|
+ }
|
|
|
+
|
|
|
+ onUnmounted(() => stopTick());
|
|
|
+
|
|
|
+ // ======================== 导出 ========================
|
|
|
+ return {
|
|
|
+ simState,
|
|
|
+ config,
|
|
|
+ speed,
|
|
|
+ currentData,
|
|
|
+ timeSeries,
|
|
|
+ eventLog,
|
|
|
+ elapsedSimSec,
|
|
|
+ sustainedSec,
|
|
|
+ runningFanPrefix,
|
|
|
+ isRunning,
|
|
|
+ stateLabel,
|
|
|
+ formatElapsed,
|
|
|
+ start,
|
|
|
+ pause,
|
|
|
+ resume,
|
|
|
+ abort,
|
|
|
+ updateConfig,
|
|
|
+ setSpeed: (s: number) => {
|
|
|
+ speed.value = s;
|
|
|
+ },
|
|
|
+ };
|
|
|
+}
|