Переглянути джерело

局部通风机模块需求修改

bobo04052021@163.com 2 місяців тому
батько
коміт
ba5899c08b

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/assets/images/dischargeGas.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/assets/images/supplyAir.svg


+ 918 - 0
src/views/vent/monitorManager/fanLocalMonitor/components/dischargeGas.vue

@@ -0,0 +1,918 @@
+<template>
+  <BasicModal
+    @register="register"
+    title="智能排放瓦斯"
+    :maskStyle="{ backgroundColor: '#000000aa', backdropFilter: 'blur(3px)' }"
+    width="1200px"
+    v-bind="$attrs"
+    @ok="onSubmit"
+    :closeFunc="onCancel"
+    :canFullscreen="false"
+    :destroyOnClose="true"
+    :footer="null"
+    :maskClosable="false"
+  >
+    <div class="modal-box">
+      <div class="left-box" style="width: 550px; height: 300px"> </div>
+      <div class="right-box">
+        <!-- <div class="box-title">曲线方程</div> -->
+        <dv-decoration7 style="height: 20px">
+          <div class="box-title">风量数据</div>
+        </dv-decoration7>
+        <div class="info-lines">
+          <div class="right-box-inputs">
+            <div class="input-title" style="margin-top: 20px; line-height: 40px">出风口风量(m³/min): <span></span></div>
+            <div class="input-title" style="margin-top: 20px; line-height: 40px">局扇供风量(m³/min): <span></span></div>
+          </div>
+        </div>
+      </div>
+      <div class="tip-box"> </div>
+    </div>
+    <div class="chart-box">
+      <BarAndLine class="echarts-line" xAxisPropType="time" height="400px" />
+    </div>
+    <div class="setting-box">
+      <div class="right-inputs">
+        <div class="vent-flex-row">
+          <div class="input-title">出风口风量(m³/min):</div>
+          <InputNumber class="input-box" size="large" v-model:value="uQ1" />
+          <div class="input-title">局扇供风量(m³/min):</div>
+          <InputNumber class="input-box" size="large" v-model:value="uH" />
+        </div>
+      </div>
+    </div>
+  </BasicModal>
+</template>
+
+<script lang="ts" setup>
+//ts语法
+import { ref, onMounted, reactive, nextTick, computed } from 'vue';
+import echarts from '/@/utils/lib/echarts';
+import { option, initData, fanInfoData, chartsColumnList, echatsOption } from '../fanLocal.data';
+import { BasicModal, useModalInner } from '/@/components/Modal';
+import { useForm } from '/@/components/Form/index';
+import { Input, InputNumber } from 'ant-design-vue';
+import { Decoration7 as DvDecoration7 } from '@kjgl77/datav-vue3';
+import { message } from 'ant-design-vue';
+import { formatNum } from '/@/utils/ventutil';
+import BarAndLine from '/@/components/chart/BarAndLine.vue';
+import { cloneDeep } from 'lodash-es';
+import dayjs from 'dayjs';
+import { SendOutlined } from '@ant-design/icons-vue';
+
+const emit = defineEmits(['close', 'register', 'openModal']);
+const props = defineProps({
+  dataSource: {
+    type: Array,
+    default: () => [],
+  },
+  frequency: {
+    type: Number,
+    default: 30,
+  },
+  m3: {
+    type: Number,
+    default: 670.8,
+  },
+  // gasWarningMax: { type: Number, default: 0.5 },
+  // gasWarningVal: { type: Number, default: 0.6 },
+  // windQuantity: { type: Number, default: 635.84 },
+});
+type AssistanceItemType = {
+  angle: number;
+  Hz: number;
+  a: number;
+  b: number;
+  c: number;
+  min: number;
+  max: number;
+};
+
+// 注册 modal
+const [register, { closeModal }] = useModalInner((data) => {
+  nextTick(() => {
+    computeAssistance();
+    if (option['xAxis']) option['xAxis']['data'] = xData;
+    option['series'] = yDataList;
+    if (JSON.stringify(data) !== '{}') {
+      uQ1.value = Number(data['m3']);
+      uHz.value = Math.ceil(data['frequency']);
+      gasWarningVal.value = data['gasWarningVal'];
+      isComputeGas.value = true;
+      nextTick(() => {
+        computeUH(data['frequency'], data['m3']);
+        initEcharts();
+        setTimeout(() => {
+          // 根据频率计算uH
+          makeLine();
+        }, 2000);
+      });
+    } else {
+      initEcharts();
+      isComputeGas.value = false;
+    }
+  });
+});
+const loadding = ref<boolean>(false);
+const formShow = ref(false);
+const formType = ref('');
+const ChartRef = ref();
+const myChart = ref();
+const refresh = ref(true);
+const xDataMax = 1000;
+let xDataMin = 0;
+const xData: any[] = [];
+const yDataList: [] = [];
+let lineNum = 0;
+const lineEquation = ref<string[]>([]);
+const assistanceData = ref([]);
+const monitorData = ref([]);
+const gasWarningVal = ref(0);
+const gasWarningMax = ref(0.5);
+const isComputeGas = ref(false);
+const isStartCompute = ref(0);
+const uHz = ref(0);
+const uQ1 = ref(0);
+const uQ = computed(() => {
+  if (uQ1.value) {
+    if (gasWarningVal.value) {
+      return ((uQ1.value * gasWarningVal.value) / gasWarningMax.value) * 1.08;
+    }
+    return uQ1.value;
+  } else {
+    return 0;
+  }
+});
+const uH = ref<number | undefined>(undefined); //  - 1000
+const isHaCross = ref(true);
+const resultObj = ref<{ x: number; y: number; Hz: number } | null>(null);
+
+const [registerForm, {}] = useForm({
+  labelWidth: 120,
+  actionColOptions: {
+    span: 24,
+  },
+  compact: true,
+  showSubmitButton: true,
+  submitButtonOptions: {
+    text: '提交',
+    preIcon: '',
+  },
+  showResetButton: true,
+  resetButtonOptions: {
+    text: '关闭',
+    preIcon: '',
+  },
+  resetFunc: async () => {
+    formShow.value = false;
+  },
+});
+
+function computeAssistance() {
+  assistanceData.value = initData();
+  lineNum = 0;
+  const assistanceDataList = [];
+  const lineEquationList: string[] = [];
+  for (const key in assistanceData.value) {
+    const item = assistanceData.value[key];
+    assistanceDataList.push(item);
+    lineEquationList.push(
+      `H${parseInt(item['Hz'])} = ${item['a']}Q² ${Number(item['b']) > 0 ? '+' : '-'} ${Math.abs(Number(item['b'])).toFixed(4)}Q ${
+        Number(item['c']) > 0 ? '+' : '-'
+      } ${Math.abs(Number(item['c'])).toFixed(4)}`
+    );
+  }
+  lineEquation.value = lineEquationList;
+  lineNum = assistanceDataList.length;
+  xDataMin =
+    Math.min.apply(
+      Math,
+      assistanceDataList.map((item) => {
+        return item.min;
+      })
+    ) - 100;
+  // const xDataMax = Math.max.apply(Math, assistanceDataList.map(item => { return item.max }))
+  fanInfoData.flfw = `${xDataMin}~${xDataMax}`;
+  const computeItem = (item: AssistanceItemType) => {
+    const min = item.min;
+    const max = item.max;
+    const HList: number[] = [];
+    for (let i = xDataMin; i <= xDataMax; i++) {
+      if (i < min) {
+        HList.push(null);
+      } else if (i > max) {
+        HList.push(null);
+      } else {
+        HList.push(item.a * i * i + item.b * i + item.c);
+      }
+    }
+    return HList;
+  };
+
+  for (const key in assistanceData.value) {
+    const element: AssistanceItemType = assistanceData.value[key];
+    const yData: number[] = computeItem(element);
+    const series = {
+      type: 'line',
+      name: `${element['Hz']}Hz`,
+      smooth: true,
+      showSymbol: false,
+      symbol: 'none',
+      emphasis: {
+        focus: 'series',
+      },
+      itemStyle: { normal: { label: { show: true } } },
+      lineStyle: {
+        width: 1,
+        color: '#ffffff88',
+      },
+      zlevel: 0,
+      z: 1,
+      endLabel: {
+        show: true,
+        formatter: '{a}',
+        distance: 0,
+        color: '#39E9FE99',
+        backgroundColor: 'transparent',
+        padding: [3, 3, 2, 3],
+      },
+      data: yData,
+    };
+
+    yDataList.push(series);
+  }
+
+  for (let i = xDataMin; i <= xDataMax; i++) {
+    xData.push(i);
+  }
+}
+
+function computeUH(Hz: number, uQ: number) {
+  debugger;
+  const equation = assistanceData.value.find((item) => {
+    return Math.ceil(Hz) == item['Hz'];
+  });
+  if (equation) {
+    const uHMax = Math.round((equation['a'] * equation['min'] * equation['min'] + equation['b'] * equation['min'] + equation['c']) * 100) / 100;
+    const uHMin = Math.round((equation['a'] * equation['max'] * equation['max'] + equation['b'] * equation['max'] + equation['c']) * 100) / 100;
+    const uH1 = Math.round((equation['a'] * uQ * uQ + equation['b'] * uQ + equation['c']) * 100) / 100;
+    if (uH1 >= uHMin && uH1 <= uHMax) {
+      uH.value = uH1;
+      isHaCross.value = true;
+    } else {
+      isHaCross.value = false;
+    }
+  }
+}
+
+function computeRLine() {
+  console.log('计算后的风量为------------>', uQ.value);
+  if (uH.value && uQ.value) {
+    const R = uH.value / Number(uQ.value) / Number(uQ.value);
+    const yAxis: number[] = [];
+    for (let i = 0; i < xData.length; i++) {
+      const x = xData[i];
+      const y = R * x * x;
+      if (x == uQ.value) {
+        uH.value = y;
+      }
+      yAxis.push(y);
+    }
+    const series = {
+      name: 'R',
+      type: 'line',
+      smooth: true,
+      showSymbol: false,
+      zlevel: 0,
+      emphasis: {
+        focus: 'series',
+      },
+      itemStyle: { normal: { label: { show: true } } },
+      lineStyle: {
+        width: 2,
+        color: '#D0A343',
+      },
+      endLabel: {
+        show: true,
+        formatter: '{a}',
+        distance: 0,
+        color: '#D0A343',
+      },
+      data: yAxis,
+    };
+    yDataList[lineNum] = series;
+  }
+}
+
+function reSetLine() {
+  let minIndex = -1;
+  for (let i = 0; i < yDataList.length; i++) {
+    if (i !== lineNum && i != lineNum + 1) {
+      if (yDataList[i]['lineStyle']) yDataList[i]['lineStyle']['color'] = '#ffffff88';
+      if (yDataList[i]['lineStyle']) yDataList[i]['lineStyle']['width'] = 1;
+      if (yDataList[i]['endLabel'] && yDataList[i]['endLabel']['color']) {
+        yDataList[i]['endLabel']['color'] = '#39E9FE99';
+      }
+      if (yDataList[i]['endLabel'] && yDataList[i]['endLabel']['backgroundColor']) yDataList[i]['endLabel']['backgroundColor'] = 'transparent';
+      if (yDataList[i]['z']) yDataList[i]['z'] = 1;
+    }
+
+    if (resultObj.value && `${resultObj.value.Hz}Hz` == yDataList[i]['name']) {
+      minIndex = i;
+    }
+  }
+  if (minIndex != -1) {
+    yDataList[minIndex]['lineStyle']['color'] = '#9A60B4';
+    yDataList[minIndex]['lineStyle']['width'] = 2;
+    yDataList[minIndex]['endLabel']['color'] = '#9A60B4';
+    yDataList[minIndex]['endLabel']['backgroundColor'] = '#111';
+    yDataList[minIndex]['z'] = 999;
+  }
+}
+
+// 根据风量计算压差
+function computePa() {
+  debugger;
+  const R = uH.value / Number(uQ.value) / Number(uQ.value);
+  const pointX = Number(uQ.value);
+  const pointY = Number(uH.value);
+  type ItemType = {
+    x: number;
+    y: number;
+    Hz: number;
+  };
+  const paList = new Map<number, ItemType>(); // key 是最近距离
+  const getIntersectionPoint = (a, b, c, R, min, max) => {
+    const obj: { x: undefined | number; y: undefined | number; min: number; max: number } = { x: undefined, y: undefined, min: min, max: max };
+    // 计算二次方程的判别式
+    const discriminant = b * b - 4 * (a - R) * c;
+
+    if (discriminant > 0) {
+      // 有两个实根
+      const x1 = (-b + Math.sqrt(discriminant)) / (2 * (a - R));
+      const x2 = (-b - Math.sqrt(discriminant)) / (2 * (a - R));
+
+      const y1 = R * x1 * x1;
+      const y2 = R * x2 * x2;
+      if (x1 >= min && x1 <= max) {
+        obj.x = x1;
+        obj.y = y1;
+      } else {
+        obj.x = x2;
+        obj.y = y2;
+      }
+    } else if (discriminant === 0) {
+      // 有一个实根
+      const x = -b / (2 * (a - R));
+      const y = R * x * x;
+      if (x >= min && x <= max) {
+        obj.x = x;
+        obj.y = y;
+      }
+      // console.log(`唯一交点: (${x}, ${y})`);
+    } else {
+      // 没有实根,交点在虚数域
+      console.log('没有实数交点');
+      isHaCross.value = false;
+    }
+    return obj;
+  };
+
+  for (let key in assistanceData.value) {
+    const item: AssistanceItemType = assistanceData.value[key];
+    paList.set(item.Hz, getIntersectionPoint(item.a, item.b, item.c, R, item.min, item.max));
+  }
+
+  const min = (points: Map<number, ItemType>) => {
+    const targetX = uQ.value;
+    const targetY = uH.value;
+    let minDistance = Number.POSITIVE_INFINITY;
+    let closestPoint = null;
+    let keyVal = '';
+    // 遍历已知点数组,计算距离并更新最小距离和对应的点
+    for (const [key, point] of points) {
+      const distance = Math.sqrt((targetX - point.x) ** 2 + (targetY - point.y) ** 2);
+      if (distance < minDistance) {
+        minDistance = distance;
+        closestPoint = point;
+        keyVal = key;
+      }
+    }
+
+    if (closestPoint !== null) {
+      // console.log(`距离最小的点是 (${closestPoint.x}, ${closestPoint.y}), 距离为 ${minDistance}`);
+      if (closestPoint.x < closestPoint.min || closestPoint.x > closestPoint.max) {
+        resultObj.value = null;
+        isHaCross.value = false;
+        console.log('没有找到最小距离的点');
+      } else {
+        resultObj.value = { x: closestPoint.x, y: closestPoint.y, Hz: keyVal };
+        isHaCross.value = true;
+      }
+    } else {
+      resultObj.value = null;
+      isHaCross.value = false;
+      console.log('没有找到最小距离的点');
+    }
+  };
+  min(paList);
+}
+
+function computeR() {
+  if (uQ.value && uH.value) {
+    computeRLine();
+    computePa();
+    const x = resultObj.value && resultObj.value.x ? resultObj.value.x.toFixed(0) : -100;
+    const y = resultObj.value && resultObj.value.y ? Number(resultObj.value.y.toFixed(0)) : -100;
+    const series = {
+      type: 'effectScatter',
+      symbolSize: 5,
+      // symbolOffset:[1, 1],
+      showEffectOn: 'render',
+      // 涟漪特效相关配置。
+      rippleEffect: {
+        // 波纹的绘制方式,可选 'stroke' 和 'fill'。
+        brushType: 'stroke',
+      },
+      zlevel: 1,
+      z: 999,
+      itemStyle: {
+        color: '#C60000',
+      },
+      data: [[x, y]],
+    };
+    yDataList[lineNum + 1] = series;
+  }
+  // // 根据计算后的得到的频率,风量,瓦斯浓度
+  // getMonitor();
+}
+
+function getMonitor() {
+  clearTimeout(timer);
+  timer = undefined;
+  const n = 10;
+  const windQuantity = uQ1.value;
+  const obj = {
+    m3: windQuantity,
+    gas: gasWarningVal.value,
+    time: '',
+    Hz: uHz.value,
+  };
+  const monitorList: { m3: number; gas: number; Hz: number; time: string }[] = [];
+  for (let i = 0; i < n; i++) {
+    const item = cloneDeep(obj);
+    const m3Temp = (Math.random() * 2 - 1) * Math.random() * 20;
+    const gas = m3Temp * 0.0002;
+    item.time = dayjs(new Date().getTime() - (n + i) * 3000).format('HH:mm:ss');
+    item.m3 = (obj.m3 + m3Temp).toFixed(2);
+    item.gas = (obj.gas + gas).toFixed(4);
+    item.Hz = (obj.Hz + Math.random()).toFixed(2);
+    monitorList.unshift(item);
+  }
+  monitorData.value = cloneDeep(monitorList);
+
+  setTimeout(() => {
+    clearTimeout(timer);
+    timer = undefined;
+
+    timer = 0;
+    if (uQ1.value && uH.value && uQ.value) {
+      openTimer(cloneDeep(obj), 0);
+    } else {
+      openTimer(cloneDeep(obj), 0);
+    }
+  }, 1000);
+}
+
+function startCompute() {
+  setTimeout(() => {
+    message.success('指令下发成功!');
+    isStartCompute.value = 1;
+  }, 800);
+}
+
+function resetCompute() {
+  setTimeout(() => {
+    message.success('指令下发成功!');
+    isStartCompute.value = -1;
+    tempList.length = 0;
+    n = 0;
+  }, 800);
+}
+
+let timer = undefined;
+let n = 0;
+let tempList = [];
+function openTimer(obj: { m3: number; gas: number; Hz: number; time: string }) {
+  // 打开定时器
+  const monitorList = cloneDeep(monitorData.value);
+  if (timer !== undefined) {
+    timer = setTimeout(() => {
+      obj.m3 = Number(obj.m3);
+      obj.gas = Number(obj.gas);
+      obj.Hz = Number(obj.Hz);
+      const item = cloneDeep(obj);
+      item.time = dayjs(new Date().getTime()).format('HH:mm:ss');
+      if (resultObj.value && resultObj.value.x && resultObj.value.y && isStartCompute.value != 0) {
+        if (isStartCompute.value == 1 && Number(obj.m3) > uQ.value && Number(obj.gas) < gasWarningMax.value) {
+          isStartCompute.value = 0;
+          n = 0;
+        } else if (isStartCompute.value == -1 && Number(obj.m3) < uQ1.value) {
+          isStartCompute.value = 0;
+          n = 0;
+        }
+      }
+
+      if (!isStartCompute.value) {
+        const m3Temp = (Math.random() * 2 - 1) * Math.random() * Math.random() * 10;
+        const gas = m3Temp * 0.0002;
+        item.m3 = (obj.m3 + m3Temp).toFixed(2);
+        item.gas = (obj.gas + gas).toFixed(4);
+        item.Hz = (obj.Hz + Math.random()).toFixed(2);
+        n = 0;
+      } else {
+        if (n < 2) {
+          const item1 = cloneDeep(obj);
+          const m3Temp = Math.random() * Math.random() * 10;
+          const gas = m3Temp * 0.0002;
+          item1.m3 = (obj.m3 + m3Temp).toFixed(2);
+          item1.gas = (obj.gas + gas).toFixed(4);
+          item1.Hz = (obj.Hz + (Math.random() * 2 - 1) * Math.random()).toFixed(2);
+          tempList.push(item1);
+        }
+
+        // 计算  uQ1 -> uQ  gas -> gasWarningMax.value Hz -> resultObj.value.Hz
+        if (isStartCompute.value == 1) {
+          if (resultObj.value.Hz - obj.Hz > 0) {
+            item.Hz = (uHz.value + (2 * n * (resultObj.value.Hz - uHz.value)) / 20).toFixed(2);
+            if (resultObj.value.Hz - obj.Hz < 0) {
+              item.Hz = (resultObj.value.Hz + Math.random() * 0.5).toFixed(2);
+            }
+          } else {
+            // item.Hz = (resultObj.value.Hz + (Math.random() * 2 - 1) * Math.random() * 2).toFixed(2);
+            item.Hz = (resultObj.value.Hz + Math.random() * 0.5).toFixed(2);
+          }
+          item.m3 = (obj.m3 + (0.027 * n * item.Hz * (item.Hz - uHz.value) * (uQ.value - uQ1.value)) / 600).toFixed(2);
+          if (Number(item.m3) > uQ.value) {
+            item.m3 = (uQ.value + Math.random() * 10).toFixed(2);
+          }
+          item.gas = (
+            gasWarningVal.value -
+            (0.015 * item.Hz * n * (item.Hz - uHz.value) * (gasWarningVal.value - gasWarningMax.value)) / 200
+          ).toFixed(4);
+        } else {
+          // 复位
+          if (obj.Hz - uHz.value > 0) {
+            item.Hz = (obj.Hz - (2 * n * (resultObj.value.Hz - uHz.value)) / 30).toFixed(2);
+            if (item.Hz - uHz.value < 0) {
+              item.Hz = uHz.value - 0.3;
+            }
+          } else {
+            item.Hz = (uHz.value + (Math.random() * 2 - 1) * Math.random() * 2).toFixed(2);
+          }
+          item.m3 = (obj.m3 - (0.02 * n * n * item.Hz * (resultObj.value.Hz - uHz.value) * (uQ.value - uQ1.value)) / 400).toFixed(2);
+          if (item.m3 < uQ1.value) {
+            item.m3 = uQ1.value - Math.random() * 10;
+          }
+          item.gas = (obj.gas + (0.015 * item.Hz * n * (uHz.value - item.Hz) * (gasWarningVal.value - gasWarningMax.value)) / 800).toFixed(4);
+        }
+      }
+      if (monitorList.length >= 10) {
+        monitorList.shift();
+        const monitor = cloneDeep(item);
+        const data = tempList.shift();
+        if (data) {
+          item['m3'] = data['m3'];
+          item['gas'] = data['gas'];
+        }
+        tempList.push(monitor);
+        monitorList.push(item);
+      } else {
+        monitorList.push(item);
+      }
+      monitorData.value = monitorList;
+      // console.log('瓦斯监测数据-------------->', monitorData.value);
+      if (timer) {
+        if (!isStartCompute.value) {
+          // n++;
+          openTimer(cloneDeep(obj));
+        } else {
+          n++;
+          openTimer(cloneDeep(item));
+        }
+      }
+    }, 3000);
+  }
+}
+
+function edit(flag) {
+  if (flag == 'info') {
+    formType.value = '编辑风机信息';
+  }
+  if (flag == 'line') {
+    formType.value = '编辑特性曲线';
+  }
+  if (formShow.value == true) {
+    formShow.value = false;
+    nextTick(() => {
+      formShow.value = true;
+    });
+  } else {
+    formShow.value = true;
+  }
+}
+
+function onSubmit() {
+  emit('close');
+  closeModal();
+  ChartRef.value = null;
+  uQ.value = undefined;
+  uH.value = undefined;
+  formType.value = '';
+  refresh.value = true;
+  xData.length = 0;
+  yDataList.length = 0;
+  lineNum = 0;
+  lineEquation.value = [];
+  resultObj.value = null;
+  monitorData.value = [];
+  clearTimeout(timer);
+  timer = undefined;
+}
+
+async function onCancel() {
+  return new Promise((resolve) => {
+    if (isStartCompute.value == 0) {
+      onSubmit();
+      resolve(true);
+    } else {
+      message.warning('为保障矿井安全生产,请确保已复位!!!');
+      resolve(false);
+    }
+  });
+}
+
+function initEcharts() {
+  if (ChartRef.value) {
+    reSetLine();
+    myChart.value = echarts.init(ChartRef.value);
+    option && myChart.value.setOption(option);
+    refresh.value = false;
+    getMonitor();
+    nextTick(() => {
+      setTimeout(() => {
+        refresh.value = true;
+      }, 0);
+    });
+  }
+}
+
+function makeLine() {
+  if (uQ.value && uH.value) {
+    loadding.value = true;
+    setTimeout(() => {
+      computeR();
+      reSetLine();
+      option && myChart.value.setOption(option);
+      loadding.value = false;
+    }, 1200);
+  }
+}
+function handleSubmit() {
+  message.success('提交成功');
+  setTimeout(() => {
+    formShow.value = false;
+  }, 800);
+}
+onMounted(() => {
+  timer = undefined;
+});
+</script>
+
+<style scoped lang="less">
+.modal-box {
+  width: 100%;
+  height: 300px;
+  display: flex;
+  flex-direction: row;
+  background-color: #ffffff05;
+  padding: 20px 8px 0 8px;
+  border: 1px solid #00d8ff22;
+  position: relative;
+  // min-height: 600px;
+  .left-box {
+    width: 60%;
+    height: 300px;
+    background-image: url(../../../../../assets/images/dischargeGas.svg);
+    background-repeat: no-repeat;
+    background-size: 100%, 100%;
+  }
+  .box-title {
+    width: calc(100% - 40px);
+    text-align: center;
+    background-color: #1dc1f522;
+  }
+  .info-item {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 2px 0px;
+    margin: 10px 0;
+    line-height: 30px;
+    background-image: linear-gradient(to right, #39deff15, #3977e500);
+    &:first-child {
+      margin-top: 0;
+    }
+    .title {
+      width: 200px;
+      text-align: left;
+      padding-left: 20px;
+      color: #f1f1f1cc;
+    }
+    .value {
+      width: 150px;
+      color: #00d8ff;
+      padding-right: 20px;
+      text-align: right;
+    }
+  }
+  .right-box {
+    width: 40%;
+    .info-lines {
+      width: calc(100% - 2px);
+      height: 230px;
+      box-shadow: 0px 0px 50px #86baff08 inset;
+      overflow-y: auto;
+      margin-top: 5px;
+      .title {
+        width: 100%;
+        color: #f1f1f1cc;
+      }
+    }
+  }
+  .center-box {
+    flex: 1;
+    margin: 0 10px;
+    display: flex;
+    .info-echarts {
+      // background-color: #ffffff11;
+    }
+    .result-tip {
+      text-align: center;
+      background-color: #00000011;
+      line-height: 28px;
+      margin: 10px 50px 0 50px;
+      border: 1px solid #00d8ff22;
+      border-radius: 2px;
+    }
+  }
+  .tip-box {
+    width: 1040px;
+    height: 44px;
+    position: absolute;
+    top: 417px;
+    display: flex;
+    padding: 0 20px;
+    .title {
+      width: 142px;
+      height: 43px;
+      display: flex;
+      align-items: center;
+      padding-left: 30px;
+      background-image: url('@/assets/images/fanlocal-tip/tip-title.png');
+      position: relative;
+      &::before {
+        content: '';
+        display: inline-block;
+        position: absolute;
+        width: 31px;
+        height: 31px;
+        top: 5px;
+        left: -8px;
+        background-image: url('@/assets/images/fanlocal-tip/tip-icon.png');
+      }
+    }
+    .tip-container {
+      width: 898px;
+      height: 44px;
+      line-height: 44px;
+      display: flex;
+      background-image: url('@/assets/images/fanlocal-tip/tip-bg.png');
+      background-size: cover;
+    }
+  }
+}
+.chart-box {
+  width: 1170px;
+}
+.setting-box {
+  width: 1170px;
+  height: 70px;
+  margin: 10px 0;
+  background-color: #ffffff05;
+  border: 1px solid #00d8ff22;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+
+  .right-inputs {
+    width: 100%;
+    display: flex;
+    height: 40px;
+    margin: 0 10px;
+    justify-content: space-between;
+  }
+  .left-buttons {
+    display: flex;
+    height: 40px;
+
+    .btn {
+      margin: 0 10px;
+    }
+  }
+  .border-clip {
+    width: 1px;
+    height: 25px;
+    border-right: 1px solid #8b8b8b77;
+  }
+  .input-title {
+    max-width: 150px;
+  }
+  .input-box {
+    width: 220px !important;
+    background: transparent !important;
+    border-color: #00d8ff44 !important;
+    margin-right: 20px;
+    color: #fff !important;
+  }
+  .btn {
+    padding: 8px 20px;
+    position: relative;
+    border-radius: 2px;
+    color: #fff;
+    width: fit-content;
+    cursor: pointer;
+
+    &::before {
+      position: absolute;
+      display: block;
+      content: '';
+      width: calc(100% - 4px);
+      height: calc(100% - 4px);
+      top: 2px;
+      left: 2px;
+      border-radius: 2px;
+      z-index: -1;
+    }
+  }
+
+  .btn1 {
+    border: 1px solid #5cfaff;
+
+    &::before {
+      background-image: linear-gradient(#2effee92, #0cb1d592);
+    }
+
+    &:hover {
+      border: 1px solid #5cfaffaa;
+
+      &::before {
+        background-image: linear-gradient(#2effee72, #0cb1d572);
+      }
+    }
+  }
+}
+.is-open {
+  animation: open 0.5s;
+  animation-iteration-count: 1;
+  animation-fill-mode: forwards;
+  animation-timing-function: ease-in;
+}
+.is-close {
+  height: 0px;
+}
+
+@keyframes open {
+  0% {
+    height: 0px;
+  }
+  100% {
+    height: fit-content;
+  }
+}
+
+@keyframes close {
+  0% {
+    height: fit-content;
+  }
+  100% {
+    height: 0px;
+  }
+}
+:deep(.zxm-divider-inner-text) {
+  color: #cacaca88 !important;
+}
+:deep(.zxm-form-item) {
+  margin-bottom: 10px;
+}
+</style>

+ 274 - 0
src/views/vent/monitorManager/fanLocalMonitor/components/supplyAir.vue

@@ -0,0 +1,274 @@
+<template>
+  <a-modal title="按需供风联动" :visible="supplyAirModalVisible" @ok="handleOk" @cancel="handleCancel">
+    <div class="modal-box">
+      <div class="left-box" style="width: 550px; height: 300px"> </div>
+      <div class="center-box"> </div>
+      <div class="right-box">
+        <!-- <div class="box-title">曲线方程</div> -->
+        <dv-decoration7 style="height: 20px">
+          <div class="box-title">风量数据</div>
+        </dv-decoration7>
+        <div class="info-lines">
+          <div class="right-box-inputs">
+            <div class="input-title" style="margin-top: 20px; line-height: 40px">出风口风量(m³/min): <span></span></div>
+            <div class="input-title" style="margin-top: 20px; line-height: 40px">局扇供风量(m³/min): <span></span></div>
+          </div>
+        </div>
+      </div>
+      <div class="tip-box"> </div>
+    </div>
+    <div class="setting-box">
+      <div class="right-inputs">
+        <div class="vent-flex-row">
+          <div class="input-title">出风口风量(m³/min):</div>
+          <InputNumber class="input-box" size="large" v-model:value="uQ1" />
+          <div class="input-title">局扇供风量(m³/min):</div>
+          <InputNumber class="input-box" size="large" v-model:value="uH" />
+        </div>
+      </div>
+    </div>
+  </a-modal>
+</template>
+
+<script lang="ts" setup>
+import { ref, defineProps, defineEmits } from 'vue';
+
+const props = defineProps({
+  supplyAirModalVisible: Boolean,
+});
+
+const emit = defineEmits(['close']);
+
+function handleOk() {
+  emit('close');
+}
+
+function handleCancel() {
+  emit('close');
+}
+</script>
+
+<style scoped lang="less">
+.modal-box {
+  display: flex;
+  flex-direction: row;
+  background-color: #ffffff05;
+  padding: 20px 8px 0 8px;
+  border: 1px solid #00d8ff22;
+  position: relative;
+  // min-height: 600px;
+  .left-box {
+    height: 300px;
+    background-image: url(../../../../../assets/images/supplyAir.svg);
+    background-repeat: no-repeat;
+    background-size: 100%, 100%;
+  }
+  .box-title {
+    width: calc(100% - 40px);
+    text-align: center;
+    background-color: #1dc1f522;
+  }
+  .info-item {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 2px 0px;
+    margin: 10px 0;
+    line-height: 30px;
+    background-image: linear-gradient(to right, #39deff15, #3977e500);
+    &:first-child {
+      margin-top: 0;
+    }
+    .title {
+      width: 200px;
+      text-align: left;
+      padding-left: 20px;
+      color: #f1f1f1cc;
+    }
+    .value {
+      width: 150px;
+      color: #00d8ff;
+      padding-right: 20px;
+      text-align: right;
+    }
+  }
+  .right-box {
+    width: 320px;
+    .info-lines {
+      width: calc(100% - 2px);
+      height: 230px;
+      box-shadow: 0px 0px 50px #86baff08 inset;
+      overflow-y: auto;
+      margin-top: 5px;
+      .title {
+        width: 100%;
+        color: #f1f1f1cc;
+      }
+      .right-box-inputs {
+        height: 80px;
+      }
+    }
+  }
+  .center-box {
+    flex: 1;
+    margin: 0 10px;
+    display: flex;
+    .info-echarts {
+      // background-color: #ffffff11;
+    }
+    .result-tip {
+      text-align: center;
+      background-color: #00000011;
+      line-height: 28px;
+      margin: 10px 50px 0 50px;
+      border: 1px solid #00d8ff22;
+      border-radius: 2px;
+    }
+  }
+  .tip-box {
+    width: 1040px;
+    height: 44px;
+    position: absolute;
+    top: 417px;
+    display: flex;
+    padding: 0 20px;
+    .title {
+      width: 142px;
+      height: 43px;
+      display: flex;
+      align-items: center;
+      padding-left: 30px;
+      background-image: url('@/assets/images/fanlocal-tip/tip-title.png');
+      position: relative;
+      &::before {
+        content: '';
+        display: inline-block;
+        position: absolute;
+        width: 31px;
+        height: 31px;
+        top: 5px;
+        left: -8px;
+        background-image: url('@/assets/images/fanlocal-tip/tip-icon.png');
+      }
+    }
+    .tip-container {
+      width: 898px;
+      height: 44px;
+      line-height: 44px;
+      display: flex;
+      background-image: url('@/assets/images/fanlocal-tip/tip-bg.png');
+      background-size: cover;
+    }
+  }
+}
+.setting-box {
+  width: 1170px;
+  height: 70px;
+  margin: 10px 0;
+  background-color: #ffffff05;
+  border: 1px solid #00d8ff22;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+
+  .right-inputs {
+    width: 100%;
+    display: flex;
+    height: 40px;
+    margin: 0 10px;
+    justify-content: space-between;
+  }
+  .left-buttons {
+    display: flex;
+    height: 40px;
+
+    .btn {
+      margin: 0 10px;
+    }
+  }
+  .border-clip {
+    width: 1px;
+    height: 25px;
+    border-right: 1px solid #8b8b8b77;
+  }
+  .input-title {
+    max-width: 150px;
+  }
+  .input-box {
+    width: 220px !important;
+    background: transparent !important;
+    border-color: #00d8ff44 !important;
+    margin-right: 20px;
+    color: #fff !important;
+  }
+  .btn {
+    padding: 8px 20px;
+    position: relative;
+    border-radius: 2px;
+    color: #fff;
+    width: fit-content;
+    cursor: pointer;
+
+    &::before {
+      position: absolute;
+      display: block;
+      content: '';
+      width: calc(100% - 4px);
+      height: calc(100% - 4px);
+      top: 2px;
+      left: 2px;
+      border-radius: 2px;
+      z-index: -1;
+    }
+  }
+
+  .btn1 {
+    border: 1px solid #5cfaff;
+
+    &::before {
+      background-image: linear-gradient(#2effee92, #0cb1d592);
+    }
+
+    &:hover {
+      border: 1px solid #5cfaffaa;
+
+      &::before {
+        background-image: linear-gradient(#2effee72, #0cb1d572);
+      }
+    }
+  }
+}
+.is-open {
+  animation: open 0.5s;
+  animation-iteration-count: 1;
+  animation-fill-mode: forwards;
+  animation-timing-function: ease-in;
+}
+.is-close {
+  height: 0px;
+}
+
+@keyframes open {
+  0% {
+    height: 0px;
+  }
+  100% {
+    height: fit-content;
+  }
+}
+
+@keyframes close {
+  0% {
+    height: fit-content;
+  }
+  100% {
+    height: 0px;
+  }
+}
+:deep(.zxm-divider-inner-text) {
+  color: #cacaca88 !important;
+}
+:deep(.zxm-form-item) {
+  margin-bottom: 10px;
+}
+</style>

+ 1 - 1
src/views/vent/monitorManager/fanLocalMonitor/fanLocal.three.ts

@@ -357,7 +357,7 @@ export const addCssText = () => {
       fanLocalCSS3D.name = 'text9';
       fanLocalCSS3D.scale.set(0.07, 0.07, 0.07);
       fanLocalCSS3D.rotation.y = -Math.PI / 2;
-      fanLocalCSS3D.position.set(-18, 7.46, -35.28);
+      fanLocalCSS3D.position.set(-8, 7.46, -35.28);
       group.add(fanLocalCSS3D);
     }
   }

+ 82 - 25
src/views/vent/monitorManager/fanLocalMonitor/index.vue

@@ -172,7 +172,7 @@
       </div>
       <div class="top-right row">
         <template v-for="(item, index) in modalTypeArr.rightBtnArr" :key="index">
-          <div
+          <!-- <div
             v-if="
               (item.permission === 'fanLocal:gasAlarmSet' ||
                 item.permission === 'fanLocal:kkjc' ||
@@ -183,6 +183,17 @@
             :class="{ 'button-box': btnClick, 'button-disable': !btnClick }"
             @click="showModal(item)"
             >{{ item.value }}</div
+          > -->
+          <div
+            v-if="
+              item.permission === 'fanLocal:gasAlarmSet' ||
+              item.permission === 'fanLocal:kkjc' ||
+              item.permission === 'fanLocal:supplyAir' ||
+              item.permission === 'fanLocal:dischargeGas'
+            "
+            :class="{ 'button-box': btnClick, 'button-disable': !btnClick }"
+            @click="showModal(item)"
+            >{{ item.value }}</div
           >
         </template>
       </div>
@@ -226,22 +237,25 @@
               </template>
             </div>
           </div>
-          <div class="container-group" style="max-height: 250px; overflow-y: auto">
+          <div class="container-group">
             <div class="warning-header">
               <div class="header-item">
                 <div class="header-title">评价分析</div>
+                <!-- <div class="header-value">{{ selectData['warnLogNotOkCount'] }} </div> -->
               </div>
             </div>
-            <template v-if="selectData.deviceType">
-              <div class="container-item" v-for="(data, index) in leftColumns1" :key="index">
-                <div class="item-icon">
-                  <!-- <SvgIcon class="icon-style" size="18" name="temperature" /> -->
-                  <CaretRightOutlined class="icon-style" />
+            <div class="warning-group" style="max-height: 200px; overflow-y: auto">
+              <template v-if="selectData.deviceType">
+                <div class="container-item" v-for="(data, index) in leftColumns1" :key="index">
+                  <div class="item-icon">
+                    <!-- <SvgIcon class="icon-style" size="18" name="temperature" /> -->
+                    <CaretRightOutlined class="icon-style" />
+                  </div>
+                  <div class="item-name">{{ data.title }}</div>
+                  <div :class="['item-value', { 'text-red-bold': data.value === '超限' }]">{{ data.value ? data.value : '-' }}</div>
                 </div>
-                <div class="item-name">{{ data.title }}</div>
-                <div :class="['item-value', { 'text-red-bold': data.value === '超限' }]">{{ data.value ? data.value : '-' }}</div>
-              </div>
-            </template>
+              </template>
+            </div>
           </div>
         </div>
       </div>
@@ -283,7 +297,7 @@
                   </div>
                 </template>
               </div>
-              <div class="warning-group" style="max-height: 250px; overflow-y: auto">
+              <div class="warning-group">
                 <div class="warning-header">
                   <div class="header-item">
                     <div class="header-title">设备报警</div>
@@ -291,8 +305,7 @@
                   </div>
                 </div>
                 <template v-if="deviceType">
-                  <div style="height: 100%; text-align: center; color: #fff" v-if="rightColumns.length < 1"> 暂无数据 </div>
-                  <div v-else>
+                  <div v-if="rightColumns.length > 1">
                     <div v-for="(state, index) in rightColumns" :key="index">
                       <template v-if="state.dataIndex.endsWith('_w')">
                         <div class="warning-item">
@@ -356,6 +369,7 @@
                       </template>
                     </div>
                   </div>
+                  <div v-else style="height: 200px; overflow-y: auto; text-align: center; color: #fff; line-height: 200px"> 暂无报警 </div>
                 </template>
               </div>
             </div>
@@ -610,6 +624,22 @@
           <a-input-number size="small" v-model:value="gasWarningVal" :min="0" :max="1" :step="0.01" />
         </div>
       </div>
+      <div v-if="controlType == 'dischargeGas'">
+        <div class="vent-flex-row input-box">
+          <div class="label">设置瓦斯超限浓度:</div>
+          <a-input-number size="small" v-model:value="gas1WarningVal" :min="0" :max="0.8" :step="0.01" />
+        </div>
+        <div class="vent-flex-row input-box">
+          <div class="label">设置二氧化碳超限浓度:</div>
+          <a-input-number size="small" v-model:value="co2WarningVal" :min="0" :max="1.2" :step="0.01" />
+        </div>
+      </div>
+      <div v-if="controlType == 'supplyAir'">
+        <div class="vent-flex-row input-box">
+          <div class="label">设置目标风量:</div>
+          <a-input-number size="small" v-model:value="targetVolume" :min="0" :max="100000" :step="100" />
+        </div>
+      </div>
       <!-- 启动或停止 -->
       <div class="" v-if="controlType == 'startSmoke'"> </div>
       <div v-if="!globalConfig?.simulatedPassword" class="vent-flex-row input-box">
@@ -621,6 +651,8 @@
   <!-- 摄像头显示隐藏图标 -->
   <VideoCameraOutlined class="video-icon" :class="{ 'no-play': !showPlay }" @click="changePlay" />
   <ConditionAssistance @register="registerModalAssistance" :dataSource="historySource" />
+  <SupplyAir v-if="supplyAirModalVisible" @close="supplyAirModalVisible = false" />
+  <DischargeGas @register="registerModal3" />
   <DeviceBaseInfo @register="registerModal" :device-type="selectData['deviceType']" />
   <reportInfo @register="registerModal1" :editID="editID" :fileType="fileType" />
 </template>
@@ -654,6 +686,8 @@ import { message } from 'ant-design-vue';
 import { useCamera } from '/@/hooks/system/useCamera';
 import { CaretRightOutlined } from '@ant-design/icons-vue';
 import ConditionAssistance from './components/conditionAssistance.vue';
+import DischargeGas from './components/dischargeGas.vue';
+import SupplyAir from './components/supplyAir.vue';
 import reportInfo from '../comment/components/reportInfo.vue';
 import { save, reportList } from '../../reportManager/reportManager.api';
 import { usePermission } from '/@/hooks/web/usePermission';
@@ -671,6 +705,8 @@ const { hasPermission } = usePermission();
 const [registerModal, { openModal, closeModal }] = useModal();
 const [registerModal1, { openModal: openModal1, closeModal: closeModal1 }] = useModal();
 const [registerModalAssistance, { openModal: openAssistance, closeModal: closeAssistance }] = useModal();
+// const [registerModal2, { openModal: openModal2, closeModal: closeModal2 }] = useModal();
+const [registerModal3, { openModal: openModal3, closeModal: closeModal3 }] = useModal();
 const { currentRoute } = useRouter();
 const router = useRouter();
 const { createConfirm } = useMessage();
@@ -803,16 +839,16 @@ const modalTypeArr = reactive({
       value: '漏风率报警',
       permission: 'fanLocal:control',
     },
-    // {
-    //   key: 'dischargeGas',
-    //   value: '智能排放瓦斯',
-    //   permission: 'fanLocal:dischargeGas',
-    // },
-    // {
-    //   key: 'supplyAir',
-    //   value: '按需供风联动',
-    //   permission: 'fanLocal:supplyAir',
-    // },
+    {
+      key: 'dischargeGas',
+      value: '智能排放瓦斯',
+      permission: 'fanLocal:dischargeGas',
+    },
+    {
+      key: 'supplyAir',
+      value: '按需供风联动',
+      permission: 'fanLocal:supplyAir',
+    },
   ],
 });
 const sensorList = ref<any[]>([
@@ -835,6 +871,11 @@ const scroll = reactive({
 });
 const deviceTypeDicts = getDictItemsByCode('fanlocaltype');
 const gasWarningVal = ref(0.6); // 瓦斯最大报警值
+const gas1WarningVal = ref(0.8); // 瓦斯最大报警值
+const co2WarningVal = ref(1.2); // co2最大报警值
+const targetValueModalVisible = ref(false);
+const supplyAirModalVisible = ref(false);
+const dischargeGasModalVisible = ref(false);
 const playerRef = ref();
 const MonitorDataTable = ref();
 const modalSensor = ref(null);
@@ -1182,12 +1223,23 @@ async function getSelectRow(id) {
 // 打开并设置modal的标题
 function showModal(obj) {
   if (!btnClick.value) return;
+  console.log(obj.key, '测试测试');
   if (obj.key == 'kkjc') {
     gasWarningVal.value = 0.6;
     // 工况辅助决策
     openAssistance(true, {});
     return;
   }
+  // if (obj.key == 'supplyAir') {
+  //   openModal2(true, {});
+  //   return;
+  // }
+  // if (obj.key == 'dischargeGas') {
+  //   gas1WarningVal.value = 0.8; // 瓦斯最大报警值
+  //   co2WarningVal.value = 1.2; // co2最大报警值
+  //   openModal3(true, {});
+  //   return;
+  // }
   controlType.value = obj.key;
   modalTitle.value = obj.value;
   passWord.value = '';
@@ -1518,6 +1570,12 @@ function handleOk(control?) {
             message.error('指令下发失败');
           });
       }
+    } else if (handType === 'supplyAir') {
+      modalIsShow.value = false;
+      supplyAirModalVisible.value = true;
+    } else if (handType === 'dischargeGas') {
+      modalIsShow.value = false;
+      openModal3(true);
     }
   };
 }
@@ -1682,7 +1740,6 @@ onUnmounted(() => {
       .container-group {
         width: 314px;
         margin: 0px 4px;
-        padding: 10px 0;
         background: var(--container-group-bg);
         max-height: 440px;
         overflow-y: auto;

Деякі файли не було показано, через те що забагато файлів було змінено