ソースを参照

feat: 碳源投加需求需改

sunxiao 5 ヶ月 前
コミット
00e348ea10

+ 1 - 1
.env.development

@@ -1,4 +1,4 @@
 # 请求地址
-VITE_BASE_URL=http://10.0.0.28:8888
+VITE_BASE_URL=http://10.1.100.119:8888/
 # 请求前缀
 VITE_BASE_PREFIX=''

+ 16 - 0
src/api/control.js

@@ -50,5 +50,21 @@ export const controlApi = {
    * 数据分析 table
    */
   getTableList: ({ pageNum, pageSize, timeBegin = '', timeEnd = '' }) => http.get(`/front/bigModel/smartAdd/homePageList?pageNum=${pageNum}&pageSize=${pageSize}&timeBegin=${timeBegin}&timeEnd=${timeEnd}`),
+
+  /**
+   * 获取最大最小值配置
+   */
+  getMinMaxVal: () => http.get(`front/bigModel/smartAdd/addConfigDetail`),
+  
+    /**
+   * 更新最大最小值配置
+   */
+  putMinMaxVal: (data) => http.put(`front/bigModel/smartAdd/updateConfig`, data),
+
+  /**
+   * 碳源投加 获取echart记录 - 数据看板 - 新
+   */
+  getAllEchartData: (params) => http.get(`/front/bigModel/smartAdd/charListAll`, { params }),
   
+    
 }

+ 65 - 28
src/views/control/MedicinalView.vue

@@ -14,6 +14,7 @@ const message = useMessage();
 const isVisibleBtn = ref(true);
 const isVisibleUpdateInfo = ref(false);
 const systemStatus = ref(0);
+const minAndMaxValue = ref({});
 const resultNumberSet = ref({
   flowNum1: 0,
   flowNum2: 0,
@@ -190,9 +191,19 @@ function getTotalNum() {
   const stepNorthOne = (((2 * hycXsyTwo - htfksdTwo) + ((qycAdTwo + qycYxyTwo) * xzxsTwo - htfksdTwo)) * (jzxsTwo - 1)) * (jsLlTwo * slfpxsTwo) / 1000;
   const stepNorthTwo = (stepNorthOne * kzxsTwo - (jsLlTwo * slfpxsTwo * jsCodTwo * zhxsTwo / 1000)) / tydlTwo;
   const stepNorthThree = stepNorthTwo / yymdTwo / 1000 * sxpsTwo;
+  
+  let r11 = Number((stepSouthThree < 0 || !stepSouthThree) ? 0 : stepSouthThree) || 0;
+  let r22 = Number((stepNorthThree < 0 || !stepNorthThree) ? 0 : stepNorthThree) || 0;
+
+  let r1 = Number((r11 * 1000).toFixed(3));
+  let r2 = Number((r22 * 1000).toFixed(3));
+  
+  const {maxAddAmount, minAddAmount } = minAndMaxValue.value;
 
-  const r1 = Number((stepSouthThree < 0 || !stepSouthThree) ? 0 : stepSouthThree.toFixed(3)) || 0;
-  const r2 = Number((stepNorthThree < 0 || !stepNorthThree) ? 0 : stepNorthThree.toFixed(3)) || 0;
+  if ( r1 > maxAddAmount ) r1  = maxAddAmount;
+  if ( r1 < minAddAmount ) r1  = minAddAmount;
+  if ( r2 > maxAddAmount ) r2  = maxAddAmount;
+  if ( r2 < minAddAmount ) r2  = minAddAmount;
 
   return [r1, r2, stepSouthTwo, stepNorthTwo];
 }
@@ -214,7 +225,6 @@ const onConfirmUpdate = async () => {
   
   resultNumberSet.value.doseNum1 = resultNumberSet.value.updateNum1;
   resultNumberSet.value.doseNum2 = resultNumberSet.value.updateNum2;
-
 }
 
 const handleAutoSwitch = (val) => {
@@ -237,36 +247,60 @@ const handleMedicateAmount = () => {
   const [r1, r2] = getTotalNum();
   
   if ( type == 0 ) {
-    if ( !dataSourceParams.value.typeOne && dataSourceParams.value.medicineAmountOne != updateNum1) {
-      resultNumberSet.value.updateNum1 = dataSourceParams.value.medicineAmountOne;
-      isVisibleUpdateInfo.value = true;
-      message.warning("南池有新的投放方案, 请查看");
-    } else if (r1 != updateNum1 && dataSourceParams.value.typeOne) {
+    // && dataSourceParams.value.medicineAmountOne != updateNum1
+    if ( !dataSourceParams.value.typeOne ) {
+      const medicineAmountOne = dataSourceParams.value.medicineAmountOne
+      if ( medicineAmountOne && medicineAmountOne != 0 ) {
+        resultNumberSet.value.updateNum1 = dataSourceParams.value.medicineAmountOne;
+        isVisibleUpdateInfo.value = true;
+        message.warning("北池有新的投放方案, 请查看");
+      } else {
+        resultNumberSet.value.updateNum1 = dataSourceParams.value.medicineAmountOne;
+        // isVisibleUpdateInfo.value = true;
+      }
+
+      // if (r1 != updateNum1 && dataSourceParams.value.typeOne)
+    } else {
       resultNumberSet.value.updateNum1 = r1;
       isVisibleUpdateInfo.value = true;
       dataSourceParams.value.tdbOne = tdb;
-      message.warning("南池有新的投放方案, 请查看");
+      message.warning("池有新的投放方案, 请查看");
     }
   }
 
   if ( type == 1 ) {
-    if (!dataSourceParams.value.typeTwo && dataSourceParams.value.medicineAmountTwo != updateNum2) {
-      resultNumberSet.value.updateNum2 = dataSourceParams.value.medicineAmountTwo;
-      isVisibleUpdateInfo.value = true;
-      message.warning("北池有新的投放方案, 请查看");
-    } else if (r2 != updateNum2 && dataSourceParams.value.typeTwo) {
+    // && dataSourceParams.value.medicineAmountTwo != updateNum2
+    if (!dataSourceParams.value.typeTwo) {
+      const medicineAmountTwo = dataSourceParams.value.medicineAmountTwo;
+      if (medicineAmountTwo && medicineAmountTwo != 0 ) {
+        resultNumberSet.value.updateNum2 = dataSourceParams.value.medicineAmountTwo;
+        isVisibleUpdateInfo.value = true;
+        message.warning("南池有新的投放方案, 请查看");
+      } else {
+        resultNumberSet.value.updateNum2 = dataSourceParams.value.medicineAmountTwo;
+        // isVisibleUpdateInfo.value = true;
+      }
+      // if (r2 != updateNum2 && dataSourceParams.value.typeTwo)
+    } else  {
       resultNumberSet.value.updateNum2 = r2;
       isVisibleUpdateInfo.value = true;
       dataSourceParams.value.tdbTwo = tdb;
-      message.warning("北池有新的投放方案, 请查看");
+      message.warning("池有新的投放方案, 请查看");
     }
   }
 }
 
 onMounted(async () => {
+
+  const { data: valSet } = await controlApi.getMinMaxVal();
+  
+  minAndMaxValue.value = valSet;
+
   // 获取最后一条记录  getSystemStatus
   await controlApi.getBaseData().then(({ data }) => {
     const {
+      addTypeOne,
+      addTypeTwo,
       typeOne = 0,
       typeTwo = 0,
       jsLlTypeOne, jsLlOne, jsCodTypeOne, jsCodOne, hycXsyTypeOne, hycXsyOne, qycYxyTypeOne, qycYxyOne, qycAdTypeOne, qycAdOne, jsTnTypeOne, jsTnOne,
@@ -279,6 +313,8 @@ onMounted(async () => {
       tytjTransientLLOne, tytjTransientLLTwo
     } = data;
     
+    systemStatus.value = { activeTwo: addTypeOne, activeTwo: addTypeTwo };
+
     // 系数
     baseSourceParams.value = {
       ...data
@@ -335,10 +371,10 @@ onMounted(async () => {
   })
 
   // 获取是否允许投药开关 - 锡林浩特
-  controlApi.getSystemStatus().then(({ data }) => {
-    // 0不允许  1允许
-    systemStatus.value = { ...data };
-  });
+  // controlApi.getSystemStatus().then(({ data }) => {
+  //   // 0不允许  1允许
+  //   systemStatus.value = { ...data };
+  // });
 })
 
 </script>
@@ -352,13 +388,6 @@ onMounted(async () => {
             <BaseTitle title="智能投加计算"></BaseTitle>
             <n-scrollbar class="scrollbar" style="height: 100%;">
               <div class="form-content">
-
-                <!-- <BaseCard title="投加运行方式">
-                  <span class="status-bar">
-                    <i>{{ systemStatus === 1 ? '启用智适应碳源投加' : '停用智适应碳源投加' }}</i>
-                  </span>
-                </BaseCard> -->
-             
                 <BaseCard title="选择池组手自动方式">
                   <n-tabs justify-content="space-between" type="line" :bar-width="0" tab-style="width: 200px;"
                     tab-class="custom-tab_item" animated :on-update:value="onUpdateTab" :value="dataSourceParams.type">
@@ -373,13 +402,15 @@ onMounted(async () => {
 
                       <div v-show="dataSourceParams.typeOne">
                         <div class="space-y-[12px] mb-[20px]">
-                          <BaseChooseItem 
+                          <BaseChooseItem
                             tab-key="south"
                             title="进水流量"
                             unit="m³"
                             @on-update="handleMedicateAmount"
                             v-model:type="dataSourceParams.jsLlTypeOne"
-                            v-model:value1="dataSourceParams.jsLlOne" 
+                            v-model:value1="dataSourceParams.jsLlOne"
+                            :min="minAndMaxValue.minJsll"
+                            :max="minAndMaxValue.maxJsll"
                             :btn-group="[
                               { label: '手动', value1: '', value2: '' },
                               { label: '仪表', value1: waterConfigParams.jslYB }
@@ -391,6 +422,8 @@ onMounted(async () => {
                             @on-update="handleMedicateAmount"
                             v-model:type="dataSourceParams.jsCodTypeOne"
                             v-model:value1="dataSourceParams.jsCodOne"
+                            :min="minAndMaxValue.minJsCod"
+                            :max="minAndMaxValue.maxJsCod"
                             :btn-group="[
                               { label: '手动', value1: '', value2: '' },
                               { label: '仪表', value1: waterConfigParams.jsCodYB },
@@ -516,6 +549,8 @@ onMounted(async () => {
                             @on-update="handleMedicateAmount"
                             v-model:type="dataSourceParams.jsLlTypeTwo"
                             v-model:value1="dataSourceParams.jsLlTwo"
+                            :min="minAndMaxValue.minJsll"
+                            :max="minAndMaxValue.maxJsll"
                             :btn-group="[
                               { label: '手动', value1: '', value2: '' },
                               { label: '仪表', value1: waterConfigParams.jslYB }
@@ -527,6 +562,8 @@ onMounted(async () => {
                             @on-update="handleMedicateAmount"
                             v-model:type="dataSourceParams.jsCodTypeTwo"
                             v-model:value1="dataSourceParams.jsCodTwo"
+                            :min="minAndMaxValue.minCod"
+                            :max="minAndMaxValue.manCod"
                             :btn-group="[
                               { label: '手动', value1: '', value2: '' },
                               { label: '仪表', value1: waterConfigParams.jsCodYB },

+ 11 - 2
src/views/control/components/BaseChooseItem.vue

@@ -1,5 +1,5 @@
 <script setup>
-import { ref, computed, watch, watchEffect } from 'vue';
+import { ref, computed, watchEffect } from 'vue';
 import { useMessage } from 'naive-ui';
 
 import BaseButton from './BaseButton.vue';
@@ -37,6 +37,14 @@ const props = defineProps({
   tabKey: {
     type: String,
     requured: true
+  },
+  min: {
+    type: Number,
+    default: 0
+  },
+  max: {
+    type: Number,
+    default: 999999
   }
 })
 
@@ -107,12 +115,13 @@ const changeActive = (item, index) => {
           <li class="w-[60px]" v-if="isDouble">{{ twoPoolNum }}</li>
         </ul>
       </div>
-
       <BaseInput
         v-show="(modelType !== undefined && !modelType) || btnGroup.length === 1"
         default-value=""
         :placeholder="'请输入' + props.title"
         :unit="unit"
+        :max="max"
+        :min="min"
         v-model="inpVal"
         @click:confirm="onInpConfirm"
         @click:cancel="onInpCancel"

+ 11 - 3
src/views/control/components/BaseInput.vue

@@ -38,6 +38,14 @@ const props = defineProps({
   readonly: {
     type: Boolean,
     default: false
+  },
+  min: {
+    type: Number,
+    default: 0
+  },
+  max: {
+    type: Number,
+    default: 999999
   }
 })
 
@@ -92,9 +100,9 @@ const handleInpValue = (event, type) => {
       round
       style="width: 100%;"
       :readonly="readonly"
-      :precision="3"
-      :max="99999.99"
-      :min="0"
+      :precision="2"
+      :max="max"
+      :min="min"
       :placeholder="placeholder"
       :show-button="false"
       :on-update:value="onInput"

+ 500 - 0
src/views/control/components/TheEchartPanel copy.vue

@@ -0,0 +1,500 @@
+<script setup>
+import { ref, computed, onMounted, unref, onUnmounted } from 'vue';
+import { NTabs, NTab, NSelect, NDatePicker } from "naive-ui";
+import * as echarts from 'echarts';
+import { startOfDay } from "date-fns/esm"
+import { controlApi } from "@/api/control"
+import dayjs from 'dayjs';
+
+let echart = null;
+let tempTabItemOneKey = 0;
+let tempTabItemTwoKey = 'jzxsOne';
+const datePickerValue = ref(null);
+const dateRangeRef = ref(null);
+const tabs = ref([]);
+const tabActive = ref(null);
+const selectValue = ref(0);
+const coefficientDataSource = ref([]);
+const echartDataSource = ref({});
+const echartRef = ref(null);
+const isEmpty = ref(false);
+const activeIndex = ref(0);
+
+const tabList = ['水质', '系数'];
+
+const selectOptions = ref([]);
+
+const echartOptions = [
+  { label: "进水流量", value: 0, style: "font-size: 12px" },
+  { label: "#1好氧池硝酸盐", value: 1, style: "font-size: 12px" },
+  { label: "#2好氧池硝酸盐", value: 2, style: "font-size: 12px" },
+  { label: "#1缺氧池氨氮", value: 3, style: "font-size: 12px" },
+  { label: "#2缺氧池氨氮", value: 4, style: "font-size: 12px" },
+  { label: "进水COD", value: 5, style: "font-size: 12px" },
+  { label: "进水总氮", value: 6, style: "font-size: 12px" },
+  { label: "碳源投加量", value: 7, style: "font-size: 12px" }
+]
+
+const coefficientOptions = [
+  { label: "基准系数", value: 'jzxsOne', style: "font-size: 12px" },
+  { label: "修正系数", value: 'xzxsOne', style: "font-size: 12px" },
+  { label: "水量分配系数", value: 'slfpxsOne', style: "font-size: 12px" },
+  { label: "碳源当量", value: 'tydlOne', style: "font-size: 12px" },
+  { label: "转换系数", value: 'zhxsOne', style: "font-size: 12px" },
+  { label: "稀释倍数", value: 'sxpsOne', style: "font-size: 12px" },
+  { label: "密度", value: 'yymdOne', style: "font-size: 12px" },
+]
+
+const seriesName = computed(() => {
+  let name = '';
+  if ( activeIndex.value === 0) {
+    name = echartOptions.find(({ value }) => selectValue.value === value).label
+  } else {
+    name = coefficientOptions.find(item => item.value === selectValue.value).label
+  }
+  return name
+})
+
+// 切换tab选项
+const handleSwitchTab = (index) => {
+  if ( activeIndex.value === index ) return;
+  activeIndex.value = index;
+  if ( !index ) {
+    // echart
+    tempTabItemTwoKey = selectValue.value;
+    selectValue.value = tempTabItemOneKey;
+    selectOptions.value = echartOptions;
+    datePickerValue.value = null;
+    initWaterEchartData();
+  } else {
+     // 系数
+    tempTabItemOneKey = selectValue.value;
+    selectValue.value = tempTabItemTwoKey;
+    selectOptions.value = coefficientOptions;
+    datePickerValue.value = null;
+    intiCoefficientEchartData();
+  }
+}
+
+// select option change
+const handleSelectOptions = (val) => {
+  selectValue.value = val;
+
+  activeIndex.value === 0 ? initWaterEchartData() : intiCoefficientEchartData();
+}
+
+const windowResize = () => echart.resize();
+
+const getEchartOptions = (data, type) => {
+  const option = {
+    backgroundColor: '#FFF',
+    title: {
+      show: !data.length,
+      text: '暂无数据',
+      x: 'center',
+      y: 'center',
+      textStyle: {
+        fontSize: 14,
+        fontWeight: 'normal',
+      }
+    },
+    grid: {
+      top: '40px',
+      bottom: '50px',
+      left: '5%',
+      right: '5%',
+    },
+    tooltip: {
+      trigger: 'axis',
+      label: {
+        show: true
+      },
+    },
+    xAxis: {
+      boundaryGap: false,
+      axisLine: {
+        show: false
+      },
+      splitLine: {
+        show: false
+      },
+      axisTick: {
+        show: false,
+        alignWithLabel: true
+      },
+      axisLabel: {
+        formatter: function (value) {
+          return dayjs(value).format('YYYY/MM/DD')
+        }
+      },
+      data: data.map(({ time }) => time)
+    },
+    yAxis: {
+      axisLine: {
+        show: false
+      },
+      splitLine: {
+        show: true,
+        lineStyle: {
+          type: 'dashed',
+          color: '#E5E5E5'
+        }
+      },
+      axisTick: {
+        show: false
+      },
+      splitArea: {
+        show: false,
+        color: '#fff'
+      }
+    },
+    series: [
+      {
+        name: seriesName.value,
+        showSymbol: false,
+        smooth: true,
+        type: 'line',
+        symbolSize: 10,
+        lineStyle: {
+          color: '#17a6fa',
+          shadowBlur: 12,
+          shadowColor: 'rgba(0, 0, 0, 0.12)',
+          shadowOffsetX: 0,
+          shadowOffsetY: 4,
+          width: 2,
+        },
+        itemStyle: {
+          color: '#4080FF',
+          borderWidth: 3,
+          borderColor: '#4080FF'
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+            offset: 0,
+            color: 'rgba(0, 136, 212, 0.2)'
+          }, {
+            offset: 1,
+            color: 'rgba(0, 136, 212, 0)'
+          }], false),
+        },
+        data: data.map(({ val }) => val)
+      }
+    ]
+  };
+  return option;
+}
+
+const onSwitchEchart = (item) => {
+  const echartData = echartDataSource.value[item.value];
+  isEmpty.value = !!echartData.length
+  echart.setOption(getEchartOptions(echartData));
+}
+
+// 水务相关数据格式化
+const initWaterEchartData = async () => {
+  const [tBegin, tEnd] = datePickerValue.value || [];
+
+  const timeBegin = tBegin ? dayjs(tBegin).format('YYYY-MM-DD') : null;
+  const timeEnd = tEnd ? dayjs(tEnd).format('YYYY-MM-DD') : null;
+  const { data: echartData } = await controlApi.getEchartData(unref(selectValue), { timeBegin, timeEnd });
+
+  const enumSource = {
+    YB: '在线仪表',
+    HY: '连续检测',
+    YC: '预测'
+  };
+  
+  
+
+  // tabs.value = Object.keys(echartData).map((key, index) => {
+  //   if (index === 0) {
+  //     tabActive.value = key + '-' + selectValue.value + '-' + index;
+  //   }
+  //   if (echartData[key].length) {
+  //     return ({ label: enumSource[key], value: key });
+  //   }
+  // }).filter(Boolean);
+
+  // console.log( "tabs.value", tabs.value );
+
+  echartDataSource.value = echartData;
+
+  onSwitchEchart({ value: tabActive.value.substring(0, tabActive.value.indexOf('-')) });
+}
+
+// 系数相关数据
+const intiCoefficientEchartData = async () => {
+  const [timeBegin, timeEnd] = datePickerValue.value || [];
+  const { data } = await controlApi.getBoardEchartList({ timeBegin, timeEnd });
+  coefficientDataSource.value = data;
+  
+  const d = data.map(item => ({
+    time: dayjs(item.createTime).format('YYYY/MM/DD HH'),
+    val: item[selectValue.value]
+  }));
+
+  echart.setOption(getEchartOptions(d));
+}
+
+// 日期范围限制
+const isRangeDateDisabled = (ts, type, range) => {
+  const d = 864e5;
+  if (type === "start" && range !== null) {
+    return startOfDay(range[1]).valueOf() - startOfDay(ts).valueOf() >= d * 10;
+  }
+  if (type === "end" && range !== null) {
+    return startOfDay(ts).valueOf() - startOfDay(range[0]).valueOf() >= d * 10;
+  }
+  return false;
+}
+
+const onDatePickerConfirm = (ts) => {
+  datePickerValue.value = ts.map(t => dayjs(t).format('YYYY-MM-DD'));
+  activeIndex.value === 0 ? initWaterEchartData() : intiCoefficientEchartData();
+}
+
+const onDatePickerClear = () => {
+  datePickerValue.value = null;
+  activeIndex.value === 0 ? initWaterEchartData() : intiCoefficientEchartData();
+}
+
+onMounted(async () => {
+  selectOptions.value = echartOptions;
+
+  echart = echarts.init(echartRef.value, 'light');
+
+  await initWaterEchartData();
+
+  window.addEventListener("resize", windowResize);
+
+})
+
+onUnmounted(() => {
+  window.removeEventListener("resize", windowResize);
+  echart && echart.dispose();
+})
+
+</script>
+
+<template>
+  <div class="echart-card_view">
+    <div class="title">
+      <div class="left-inner">
+        <span class="text">数据看板</span>
+      </div>
+      <div class="right-inner">
+        <ul class="custom-radio-group">
+          <li :class="{ active: activeIndex === index }" v-for="item, index in tabList" :key="item"
+            @click="handleSwitchTab(index)">{{ item }}</li>
+        </ul>
+      </div>
+    </div>
+
+    <div class="select-wrapper">
+      <div>
+        <n-tabs
+          animated
+          type="segment"
+          size="small"
+          class="tabs"
+          style="width: 200px;"
+          v-model:value="tabActive"
+          v-show="activeIndex === 0"
+        >
+          <n-tab
+            v-for="item, index in tabs"
+            :key="item.value"
+            :name="item.value + '-' + selectValue + '-' + index"
+            @click="onSwitchEchart(item)"
+          >{{ item.label }}</n-tab>
+        </n-tabs>
+      </div>
+      <div class="flex space-x-[10px]">
+        <NDatePicker
+          clearable
+          class="w-[300px]"
+          size="small"
+          type="daterange"
+          ref="dateRangeRef"
+          :is-date-disabled="isRangeDateDisabled"
+          :on-confirm="onDatePickerConfirm"
+          :on-clear="onDatePickerClear"
+          v-model:formatted-value="datePickerValue"
+        ></NDatePicker>
+        <NSelect
+          class="w-[150px]"
+          :options="selectOptions"
+          :value="selectValue"
+          :on-update:value="handleSelectOptions"
+          size="small" />
+      </div>
+    </div>
+
+    <div class="echart-wrapper">
+      <div class="echart" ref="echartRef"></div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.echart-card_view {
+  display: flex;
+  flex-flow: column;
+  height: calc(100% - 284px);
+  padding: 0px 16px 0 25px;
+  border-radius: 10px;
+
+  .title {
+    flex-shrink: 0;
+    @include flex(x, center, between);
+    padding-bottom: 16px;
+
+    .left-inner {
+      @include flex(x, center, start);
+
+      .text {
+        color: #1A2029;
+        font-size: 15px;
+        font-style: normal;
+        font-weight: 500;
+        line-height: 24px;
+      }
+
+      .tabs {
+        width: 240px;
+        margin-left: 16px;
+      }
+    }
+
+    .right-inner {
+      @include flex(x, center, start);
+
+      .custom-radio-group {
+        @include flex(x, center, center);
+        width: 104px;
+        height: 24px;
+        border-radius: 4px;
+        border: 1px solid #D3D7DD;
+
+        li {
+          width: 50%;
+          font-size: 12px;
+          text-align: center;
+          line-height: 24px;
+          color: #333;
+          cursor: pointer;
+
+          &:nth-child(1) {
+            border-right: 1px solid #D3D7DD;
+          }
+        }
+
+        li.active {
+          color: #2454FF;
+        }
+      }
+    }
+  }
+
+  .select-wrapper {
+    @include flex(x, center, between);
+    height: 32px;
+  }
+
+  .echart-wrapper {
+    height: calc(100% - 72px);
+
+    .echart,
+    .empty {
+      width: 100%;
+      height: 100%;
+    }
+
+    .empty {
+      @include flex(x, center, center);
+    }
+  }
+}
+</style>
+
+<style lang="scss">
+.echart-card_view {
+  .tabs {
+    .n-tabs-tab--active {
+      .n-tabs-tab__label {
+        color: #2454FF;
+      }
+    }
+
+    .n-tabs-tab__label {
+      font-size: 12px;
+      color: #333333;
+    }
+  }
+
+  .n-base-selection .n-base-selection-label .n-base-selection-input {
+    font-size: 12px;
+    color: #333333 !important;
+  }
+
+  .right-inner {
+    .n-base-selection__border {
+      border: 0;
+    }
+
+    .n-base-selection-label {
+      border: 0;
+      border-radius: 0;
+      background: #F2F3F5 !important;
+    }
+
+    .n-base-selection__state-border,
+    .n-base-selection__border {
+      border: 0 !important;
+    }
+
+    .n-base-selection .n-base-selection__border,
+    .n-base-selection .n-base-selection__state-border {
+      box-shadow: none;
+    }
+
+    .n-base-suffix__arrow {
+      color: #4E5969;
+    }
+  }
+
+  .n-input--pair {
+    background: #f0f1f3 !important;
+  }
+
+  .n-base-selection-input__content {
+    font-size: 12px;
+  }
+
+  .n-base-selection {
+    background: #f0f1f3 !important;
+  }
+
+  .n-base-selection-label {
+    background: #f0f1f3;
+    border-radius: 3px;
+  }
+
+  .n-base-selection__border,
+  .n-base-selection__state-border {
+    display: none !important;
+  }
+
+  .n-date-picker {
+    .n-input__input-el,
+    .n-input__placeholder {
+      font-size: 12px !important;
+    }
+  }
+
+  .n-base-selection:not(.n-base-selection--disabled).n-base-selection--active .n-base-selection-label {
+    background: #f0f1f3 !important;
+  }
+}
+
+</style>

+ 252 - 140
src/views/control/components/TheEchartPanel.vue

@@ -1,6 +1,6 @@
 <script setup>
-import { ref, computed, onMounted, unref, onUnmounted } from 'vue';
-import { NTabs, NTab, NSelect, NDatePicker } from "naive-ui";
+import { ref, computed, onMounted, nextTick, onUnmounted } from 'vue';
+import { NSpin, NSelect, NDatePicker } from "naive-ui";
 import * as echarts from 'echarts';
 import { startOfDay } from "date-fns/esm"
 import { controlApi } from "@/api/control"
@@ -11,20 +11,33 @@ let tempTabItemOneKey = 0;
 let tempTabItemTwoKey = 'jzxsOne';
 const datePickerValue = ref(null);
 const dateRangeRef = ref(null);
-const tabs = ref([]);
-const tabActive = ref(null);
-const selectValue = ref(0);
+const selectValue = ref(['0']);
 const coefficientDataSource = ref([]);
 const echartDataSource = ref({});
 const echartRef = ref(null);
-const isEmpty = ref(false);
 const activeIndex = ref(0);
+const show = ref(true);
 
 const tabList = ['水质', '系数'];
 
 const selectOptions = ref([]);
 
-const echartOptions = [
+const selectEnum = {
+  0:  '进水流量',
+  1:  '北池-好氧池硝酸盐',
+  2:  '南池-好氧池硝酸盐',
+  3:  '北池-缺氧池氨氮',
+  4:  '南池-缺氧池氨氮',
+  5:  '进水COD-连续检测',
+  6:  '进水COD-在线仪表',
+  7:  '进水总氮',
+  8:  '碳源投加量-北池-计算投药量',
+  9:  '碳源投加量-南池-计算投药量',
+  10: '碳源投加量-北池-组态反馈',
+  11: '碳源投加量-南池-组态反馈'
+}
+
+let echartOptions = [
   { label: "进水流量", value: 0, style: "font-size: 12px" },
   { label: "#1好氧池硝酸盐", value: 1, style: "font-size: 12px" },
   { label: "#2好氧池硝酸盐", value: 2, style: "font-size: 12px" },
@@ -45,6 +58,14 @@ const coefficientOptions = [
   { label: "密度", value: 'yymdOne', style: "font-size: 12px" },
 ]
 
+const selectThemeOverrides = {
+  peers: {
+    InternalSelection: {
+      borderRadius: '4px'
+    }
+  },
+}
+
 const seriesName = computed(() => {
   let name = '';
   if ( activeIndex.value === 0) {
@@ -65,7 +86,7 @@ const handleSwitchTab = (index) => {
     selectValue.value = tempTabItemOneKey;
     selectOptions.value = echartOptions;
     datePickerValue.value = null;
-    initWaterEchartData();
+    initWaterEchart();
   } else {
      // 系数
     tempTabItemOneKey = selectValue.value;
@@ -77,15 +98,131 @@ const handleSwitchTab = (index) => {
 }
 
 // select option change
-const handleSelectOptions = (val) => {
-  selectValue.value = val;
+const handleSelectOptions = (selectOptionList) => {
+
+  selectValue.value = selectOptionList;
+  
+  activeIndex.value === 0 ? initWaterEchart() : intiCoefficientEchartData();
+
+}
+
+const initWaterEchart = async () => {
+  
+  const tempArr = [];
+
+  const tempResult = selectValue.value.map(key => {
+    const data = echartDataSource.value[key] || [];
+
+    tempArr.push(data);
+    return ({
+      name: selectEnum[key],
+      data
+    });
+  });
+
+  const xAxis = tempArr.flat(Infinity).reduce((acc, curr) => {
+    if (!acc.some(item => item.time === curr.time)) {
+      acc.push(curr);
+    }
+    return acc;
+  }, []).filter(Boolean).map(item => item.time.trim()).sort((a, b) => dayjs(a).valueOf() - dayjs(b).valueOf());
+  
+  const options = getWaterEchartOptions({ data: tempResult, xAxis });
+
+  echart.setOption(options, true);
 
-  activeIndex.value === 0 ? initWaterEchartData() : intiCoefficientEchartData();
 }
 
 const windowResize = () => echart.resize();
 
-const getEchartOptions = (data, type) => {
+const getWaterEchartOptions = ({ data, xAxis = [] }) => {
+
+  const series = data.map(item => {
+    const d1 = item.data.map(item => {
+      return [
+        item.time.trim(),
+        item.val ? item.val.toFixed(2) : 0
+      ]
+    });
+    return {
+        name: item.name,
+        showSymbol: false,
+        smooth: true,
+        type: 'line',
+        symbolSize: 10,
+        data: d1,
+      }
+  })
+
+  const option = {
+    backgroundColor: '#FFF',
+    title: {
+      show: !xAxis.length,
+      text: '暂无数据',
+      x: 'center',
+      y: 'center',
+      textStyle: {
+        fontSize: 14,
+        fontWeight: 'normal',
+      }
+    },
+    grid: {
+      top: '40px',
+      bottom: '50px',
+      left: '5%',
+      right: '5%',
+    },
+    tooltip: {
+      trigger: 'axis',
+      label: {
+        show: true
+      },
+    },
+    xAxis: {
+      type: 'time',
+      boundaryGap: false,
+      axisLine: {
+        show: false
+      },
+      splitLine: {
+        show: false
+      },
+      axisTick: {
+        show: false,
+        alignWithLabel: true
+      },
+      axisLabel: {
+        formatter: function (value) {
+          return dayjs(value).format('YYYY/MM/DD')
+        }
+      },
+      // data: xAxis
+    },
+    yAxis: {
+      axisLine: {
+        show: false
+      },
+      splitLine: {
+        show: true,
+        lineStyle: {
+          type: 'dashed',
+          color: '#E5E5E5'
+        }
+      },
+      axisTick: {
+        show: false
+      },
+      splitArea: {
+        show: false,
+        color: '#fff'
+      }
+    },
+    series
+  };
+  return option;
+}
+
+const getEchartOptions = (data) => {
   const option = {
     backgroundColor: '#FFF',
     title: {
@@ -184,42 +321,24 @@ const getEchartOptions = (data, type) => {
   return option;
 }
 
-const onSwitchEchart = (item) => {
-  const echartData = echartDataSource.value[item.value];
-  isEmpty.value = !!echartData.length
-  echart.setOption(getEchartOptions(echartData));
-}
-
 // 水务相关数据格式化
 const initWaterEchartData = async () => {
+  show.value = true;
   const [tBegin, tEnd] = datePickerValue.value || [];
 
   const timeBegin = tBegin ? dayjs(tBegin).format('YYYY-MM-DD') : null;
   const timeEnd = tEnd ? dayjs(tEnd).format('YYYY-MM-DD') : null;
-  const { data: echartData } = await controlApi.getEchartData(unref(selectValue), { timeBegin, timeEnd });
-
-  const enumSource = {
-    YB: '在线仪表',
-    HY: '连续检测',
-    YC: '预测'
-  };
-
-  tabs.value = Object.keys(echartData).map((key, index) => {
-    if (index === 0) {
-      tabActive.value = key + '-' + selectValue.value + '-' + index;
-    }
-    if (echartData[key].length) {
-      return ({ label: enumSource[key], value: key });
-    }
-  }).filter(Boolean);
-
-  echartDataSource.value = echartData;
-
-  onSwitchEchart({ value: tabActive.value.substring(0, tabActive.value.indexOf('-')) });
+  const { data } = await controlApi.getAllEchartData({ timeBegin, timeEnd });
+  
+  show.value = false;
+  echartDataSource.value = data;
+  
+  initWaterEchart();
 }
 
 // 系数相关数据
 const intiCoefficientEchartData = async () => {
+  show.value = true;
   const [timeBegin, timeEnd] = datePickerValue.value || [];
   const { data } = await controlApi.getBoardEchartList({ timeBegin, timeEnd });
   coefficientDataSource.value = data;
@@ -228,8 +347,8 @@ const intiCoefficientEchartData = async () => {
     time: dayjs(item.createTime).format('YYYY/MM/DD HH'),
     val: item[selectValue.value]
   }));
-
-  echart.setOption(getEchartOptions(d));
+  show.value = false;
+  echart.setOption(getEchartOptions(d), true);
 }
 
 // 日期范围限制
@@ -255,10 +374,13 @@ const onDatePickerClear = () => {
 }
 
 onMounted(async () => {
-  selectOptions.value = echartOptions;
+  echartOptions = Object.entries(selectEnum).map(([key, value]) => {
+    return { label: value, value: key, style: "font-size: 12px" }
+  });
 
-  echart = echarts.init(echartRef.value, 'light');
+  selectOptions.value = echartOptions;
 
+  echart = echarts.init(document.querySelector('#echartRef'), 'light');
   await initWaterEchartData();
 
   window.addEventListener("resize", windowResize);
@@ -287,47 +409,33 @@ onUnmounted(() => {
     </div>
 
     <div class="select-wrapper">
-      <div>
-        <n-tabs
-          animated
-          type="segment"
-          size="small"
-          class="tabs"
-          style="width: 200px;"
-          v-model:value="tabActive"
-          v-show="activeIndex === 0"
-        >
-          <n-tab
-            v-for="item, index in tabs"
-            :key="item.value"
-            :name="item.value + '-' + selectValue + '-' + index"
-            @click="onSwitchEchart(item)"
-          >{{ item.label }}</n-tab>
-        </n-tabs>
-      </div>
-      <div class="flex space-x-[10px]">
-        <NDatePicker
-          clearable
-          class="w-[300px]"
-          size="small"
-          type="daterange"
-          ref="dateRangeRef"
-          :is-date-disabled="isRangeDateDisabled"
-          :on-confirm="onDatePickerConfirm"
-          :on-clear="onDatePickerClear"
-          v-model:formatted-value="datePickerValue"
-        ></NDatePicker>
-        <NSelect
-          class="w-[150px]"
-          :options="selectOptions"
-          :value="selectValue"
-          :on-update:value="handleSelectOptions"
-          size="small" />
-      </div>
+      <NDatePicker
+        clearable
+        class="w-[300px]"
+        size="small"
+        type="daterange"
+        ref="dateRangeRef"
+        :is-date-disabled="isRangeDateDisabled"
+        :on-confirm="onDatePickerConfirm"
+        :on-clear="onDatePickerClear"
+        v-model:formatted-value="datePickerValue"
+      ></NDatePicker>
+      <NSelect
+        :multiple="activeIndex != 1"
+        class="w-[200px]"
+        :options="selectOptions"
+        :value="selectValue"
+        :on-update:value="handleSelectOptions"
+        :theme-overrides="selectThemeOverrides"
+        size="small"
+        max-tag-count="responsive"
+      />
     </div>
-
     <div class="echart-wrapper">
-      <div class="echart" ref="echartRef"></div>
+      <div class="echart w-full h-full" ref="echartRef" id="echartRef" :style="{ zIndex: show ? 0 : 100 }"></div>
+      <n-spin :show="show" class="h-full w-full" :style="{position: 'absolute', top: '0%', left: '0%', zIndex: show ? 100 : 0}">
+        <span></span>
+      </n-spin>
     </div>
   </div>
 </template>
@@ -398,8 +506,12 @@ onUnmounted(() => {
   }
 
   .echart-wrapper {
+    position: relative;
     height: calc(100% - 72px);
-
+    .echart {
+      position: relative;
+      z-index: 10;
+    }
     .echart,
     .empty {
       width: 100%;
@@ -428,69 +540,69 @@ onUnmounted(() => {
     }
   }
 
-  .n-base-selection .n-base-selection-label .n-base-selection-input {
-    font-size: 12px;
-    color: #333333 !important;
-  }
-
-  .right-inner {
-    .n-base-selection__border {
-      border: 0;
-    }
-
-    .n-base-selection-label {
-      border: 0;
-      border-radius: 0;
-      background: #F2F3F5 !important;
-    }
-
-    .n-base-selection__state-border,
-    .n-base-selection__border {
-      border: 0 !important;
-    }
-
-    .n-base-selection .n-base-selection__border,
-    .n-base-selection .n-base-selection__state-border {
-      box-shadow: none;
-    }
-
-    .n-base-suffix__arrow {
-      color: #4E5969;
-    }
-  }
+  // .n-base-selection .n-base-selection-label .n-base-selection-input {
+  //   font-size: 12px;
+  //   color: #333333 !important;
+  // }
+
+  // .right-inner {
+  //   .n-base-selection__border {
+  //     border: 0;
+  //   }
+
+  //   .n-base-selection-label {
+  //     border: 0;
+  //     border-radius: 0;
+  //     background: #F2F3F5 !important;
+  //   }
+
+  //   .n-base-selection__state-border,
+  //   .n-base-selection__border {
+  //     border: 0 !important;
+  //   }
+
+  //   .n-base-selection .n-base-selection__border,
+  //   .n-base-selection .n-base-selection__state-border {
+  //     box-shadow: none;
+  //   }
+
+  //   .n-base-suffix__arrow {
+  //     color: #4E5969;
+  //   }
+  // }
 
   .n-input--pair {
     background: #f0f1f3 !important;
   }
 
-  .n-base-selection-input__content {
-    font-size: 12px;
-  }
-
-  .n-base-selection {
-    background: #f0f1f3 !important;
-  }
-
-  .n-base-selection-label {
-    background: #f0f1f3;
-    border-radius: 3px;
-  }
-
-  .n-base-selection__border,
-  .n-base-selection__state-border {
-    display: none !important;
-  }
-
-  .n-date-picker {
-    .n-input__input-el,
-    .n-input__placeholder {
-      font-size: 12px !important;
-    }
-  }
-
-  .n-base-selection:not(.n-base-selection--disabled).n-base-selection--active .n-base-selection-label {
-    background: #f0f1f3 !important;
-  }
+  // .n-base-selection-input__content {
+  //   font-size: 12px;
+  // }
+
+  // .n-base-selection {
+  //   background: #f0f1f3 !important;
+  // }
+
+  // .n-base-selection-label {
+  //   background: #f0f1f3;
+  //   border-radius: 3px;
+  // }
+
+  // .n-base-selection__border,
+  // .n-base-selection__state-border {
+  //   display: none !important;
+  // }
+
+  // .n-date-picker {
+  //   .n-input__input-el,
+  //   .n-input__placeholder {
+  //     font-size: 12px !important;
+  //   }
+  // }
+
+  // .n-base-selection:not(.n-base-selection--disabled).n-base-selection--active .n-base-selection-label {
+  //   background: #f0f1f3 !important;
+  // }
 }
 
 </style>

+ 8 - 9
src/views/control/components/TheResultPanel.vue

@@ -1,9 +1,8 @@
 <script setup>
-import { useMessage, NNumberAnimation } from 'naive-ui';
+import { NNumberAnimation } from 'naive-ui';
 import BaseTitle from './BaseTitle.vue';
 import { SvgIcon } from '@/components';
 
-const message = useMessage();
 const isVisibleBtn = defineModel();
 const modelSystemStatus = defineModel('system');
 const emit = defineEmits(['on-click', 'on-update']);
@@ -16,7 +15,7 @@ const props = defineProps({
 });
 
 const emitEvent = (type) => {
-  emit('on-click', type);
+  // emit('on-click', type);
 };
 const emitUpdate = async () => {
   emit('on-update');
@@ -31,7 +30,7 @@ const emitUpdate = async () => {
         <ul class="update-message space-x-[16px]" v-show="isVisibleBtn">
           <li class="flex space-x-[4px]">
             <SvgIcon name="control-icon-warning" size="16"></SvgIcon>
-            <span>有新投放方案,北池加药量计算为:{{ nums.updateNum1 || 0 }}m³/h, 南池加药量计算为:{{ nums.updateNum2 || 0}}m³/h,是否更新?</span>
+            <span>有新投放方案,北池加药量计算为:{{ nums.updateNum1 || 0 }}L/h, 南池加药量计算为:{{ nums.updateNum2 || 0}}L/h,是否更新?</span>
           </li>
           <li class="space-x-[10px]">
             <span class="text-[#ed742f] cursor-pointer" @click="emitUpdate">更新投放量</span>
@@ -45,7 +44,7 @@ const emitUpdate = async () => {
             <ul class="board-inner">
               <li class="board-item">
                 <span class="label">北池</span>
-                <h4>碳源投加瞬时流量(/h)</h4>
+                <h4>碳源投加瞬时流量(L/h)</h4>
                 <span class="num">
                   <NNumberAnimation :from="0" :to="nums.flowNum1" :duration="1000"
                     :precision="3"></NNumberAnimation>
@@ -53,7 +52,7 @@ const emitUpdate = async () => {
               </li>
               <li class="line"></li>
               <li class="board-item">
-                <h4>系统加药量(/h)</h4>
+                <h4>系统加药量(L/h)</h4>
                 <span class="num">
                   <NNumberAnimation :from="0" :to="nums.doseNum1" :duration="1000"
                     :precision="3"></NNumberAnimation>
@@ -77,7 +76,7 @@ const emitUpdate = async () => {
             <ul class="board-inner">
               <li class="board-item">
                 <span class="label">南池</span>
-                <h4>碳源投加瞬时流量(/h)</h4>
+                <h4>碳源投加瞬时流量(L/h)</h4>
                 <span class="num">
                   <NNumberAnimation :from="0" :to="nums.flowNum2" :duration="1000"
                     :precision="3"></NNumberAnimation>
@@ -85,7 +84,7 @@ const emitUpdate = async () => {
               </li>
               <li class="line"></li>
               <li class="board-item">
-                <h4>系统加药量(/h)</h4>
+                <h4>系统加药量(L/h)</h4>
                 <span class="num">
                   <NNumberAnimation :from="0" :to="nums.doseNum2" :duration="1000"
                     :precision="3"></NNumberAnimation>
@@ -230,7 +229,7 @@ const emitUpdate = async () => {
     font-size: 10px;
     font-weight: bold;
     color: #fff;
-    cursor: pointer;
+    // cursor: pointer;
     transition: all 0.5s;
 
     .inner {

+ 91 - 6
src/views/user/index.vue

@@ -1,14 +1,48 @@
 <script setup>
-import { reactive } from 'vue';
+import { reactive, ref, onMounted } from 'vue';
+import { NForm, NFormItem, NInputNumber, NButton, useMessage } from 'naive-ui';
 import { TheChatView, contactUs, userEdit } from "@/components/index";
-import { screenApi } from "@/api/screen"
+import { screenApi } from "@/api/screen";
+import { controlApi } from "@/api/control";
+
+const message = useMessage();
+
 const data = reactive({
   tab_action: 0,
-  tabs: ['账号管理', '关于我们'],
+  tabs: ['账号管理', '自定义投药量', '关于我们'],
   user: {}
 })
+const inputThemeOverrides = {
+  peers: {
+    Input: {
+      text: '#333',
+      border: '1px solid #d7d9e5',
+      borderRadius: '8px',
+      borderHover: '1px solid #2454FF',
+      borderFocus: '1px solid #2454FF',
+      boxShadowFocus: '0 0 0 2px rgba(36, 84, 255, 0.2)'
+    }
+  }
+}
+
+const formOptions = [
+  { 
+    label: '进水流量:',
+    keys: ['minJsll', 'maxJsll']
+  },
+  { 
+    label: '进水COD:',
+    keys: ['minJsCod', 'maxJsCod']
+  },
+  { 
+    label: '投药量:',
+    keys: ['minAddAmount', 'maxAddAmount']
+  },
+]
+
+const formValue = ref({});
 const changeTab = (index) => {
-  if (index === 1) {
+  if (index === 2) {
     window.open('https://www.sequoialibra.com/', '_blank');
   } else {
     data.tab_action = index
@@ -20,7 +54,20 @@ const getInfo = () => {
     data.user = res.user
   })
 }
-getInfo()
+
+
+const updateConfig = () => {
+  controlApi.putMinMaxVal(formValue.value).then(res => {
+    message.success(res.msg);
+  });
+}
+
+onMounted(() => {
+  controlApi.getMinMaxVal().then(({ data }) => {
+    formValue.value = data;
+  })
+})
+
 </script>
 
 <template>
@@ -40,8 +87,46 @@ getInfo()
 
         <!-- 账号管理 -->
         <userEdit v-show="data.tab_action == 0" :user="data.user"></userEdit>
+    
+        <!-- 自定义投药设置 -->
+          <NForm label-width="110px" label-align="left" class="pl-[40px]" v-show="data.tab_action == 1">
+            <NFormItem :label="item.label" path="user.name" label-placement="left" v-for="item in formOptions" :key="item.label">
+              <div class="flex space-x-4">
+                <NInputNumber
+                  class="custom-input-number"
+                  placeholder="最小值" 
+                  v-model:value="formValue[item.keys[0]]"
+                  :show-button="false"
+                  :theme-overrides="inputThemeOverrides"
+                  :min="-90000000"
+                  :max="90000000"
+                >
+                  <template #suffix>
+                    <div class="unit" v-if="unit">{{ unit }}</div>
+                  </template>
+                </NInputNumber>
+                <NInputNumber
+                  class="custom-input-number"
+                  placeholder="最大值" 
+                  v-model:value="formValue[item.keys[1]]"
+                  :show-button="false"
+                  :theme-overrides="inputThemeOverrides"
+                  :min="-90000000"
+                  :max="90000000"
+                >
+                  <template #suffix>
+                    <div class="unit" v-if="unit">{{ unit }}</div>
+                  </template>
+                </NInputNumber>
+              </div>
+            </NFormItem>
+            <NFormItem>
+              <NButton type="info" class="w-[100px]" @click="updateConfig">更新</NButton>
+            </NFormItem>
+          </NForm>
+
         <!-- 联系我们 -->
-        <contactUs v-show="data.tab_action == 1"></contactUs>
+        <contactUs v-show="data.tab_action == 2"></contactUs>
       </div>
 
     </div>

+ 1 - 1
src/views/xlht/AnalyseView.vue

@@ -169,7 +169,7 @@ onUnmounted(() => {
               </p>
             </li>
             <li class="board-item">
-              <p>累计化验次数</p>
+              <p>累积化验指标数</p>
               <p class="content space-x-[6px]">
                 <span class="num-text">
                   <NNumberAnimation :from="0" :to="boardData.totals" :duration="1500"></NNumberAnimation>

+ 10 - 0
src/views/xlht/config.js

@@ -1,3 +1,5 @@
+const formatTofixed = val => val != 0 && !val ? '' : val.toFixed(2);
+
 export const columns = [
   {
     title: "检测时间",
@@ -9,41 +11,49 @@ export const columns = [
     title: "进水总流量",
     key: "scJsZll",
     width: 100,
+    render: ({ scJsZll }) => formatTofixed(scJsZll)
   },
   {
     title: "进水COD",
     key: "scJsCod",
     width: 100,
+    render: ({ scJsCod }) => formatTofixed(scJsCod)
   },
   {
     title: "1#好氧硝酸盐",
     key: "jqr1HyXiaodan",
     width: 120,
+    render: ({ scJsZll }) => formatTofixed(scJsZll)
   },
   {
     title: "2#好氧硝酸盐",
     key: "jqr2HyXiaodan",
     width: 120,
+    render: ({ jqr2HyXiaodan }) => formatTofixed(jqr2HyXiaodan)
   },
   {
     title: "1#缺氧氨氮",
     key: "jqr1QyAndan",
     width: 120,
+    render: ({ jqr1QyAndan }) => formatTofixed(jqr1QyAndan)
   },
   {
     title: "2#缺氧氨氮",
     key: "jqr2QyAndan",
     width: 120,
+    render: ({ jqr2QyAndan }) => formatTofixed(jqr2QyAndan)
   },
   {
     title: "1#缺氧硝酸盐",
     key: "jqr1QyXiaodan",
     width: 120,
+    render: ({ jqr1QyXiaodan }) => formatTofixed(jqr1QyXiaodan)
   },
   {
     title: "2#缺氧硝酸盐",
     key: "jqr2QyXiaodan",
     width: 120,
+    render: ({ jqr2QyXiaodan }) => formatTofixed(jqr2QyXiaodan)
   },
 ];