Przeglądaj źródła

feat: 锡林浩特碳源修改

sunxiao 5 miesięcy temu
rodzic
commit
bb0df9f85f

+ 6 - 4
src/App.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
 import { RouterView } from 'vue-router';
-import { NConfigProvider, NMessageProvider, zhCN, dateZhCN } from 'naive-ui';
+import { NConfigProvider, NMessageProvider, zhCN, dateZhCN, NNotificationProvider } from 'naive-ui';
 import type { GlobalThemeOverrides } from 'naive-ui';
 
 const primaryColor = '#1A2029';
@@ -69,8 +69,10 @@ const themeOverrides: GlobalThemeOverrides = {
 
 <template>
   <NConfigProvider :theme-overrides="themeOverrides" :locale="zhCN" :date-locale="dateZhCN">
-    <NMessageProvider>
-      <RouterView />
-    </NMessageProvider>
+    <NNotificationProvider>
+      <NMessageProvider>
+        <RouterView />
+      </NMessageProvider>
+    </NNotificationProvider>
   </NConfigProvider>
 </template>

+ 16 - 3
src/components/BaseNumberInput/index.vue

@@ -21,6 +21,18 @@ const props = defineProps({
   disabled: {
     type: Boolean,
     default: false
+  },
+  min: {
+    type: Number,
+    default: -999999
+  },
+  max: {
+    type: Number,
+    default: 999999
+  },
+  precision: {
+    type: Number,
+    default: 2
   }
 });
 
@@ -42,15 +54,15 @@ const inputThemeOverrides = {
 }
 
 const emitInpVal = (value) => {
-  emit('on-input', { value, name: props.name });
   modelValue.value = value;
+  emit('on-input', { value, name: props.name });
 };
 
 </script>
 
 <template>
-  <NInputNumber :placeholder="placeholder" :show-button="false" :disabled="disabled" v-model:value="modelValue"
-    :theme-overrides="inputThemeOverrides" class="custom-input-number" :min="-90000000" :max="90000000" :on-update:value="emitInpVal">
+  <NInputNumber :precision="precision" :placeholder="placeholder" :show-button="false" :disabled="disabled" v-model:value="modelValue"
+    :theme-overrides="inputThemeOverrides" class="custom-input-number" :min="min" :max="max" :on-update:value="emitInpVal">
     <template #suffix>
       <div class="unit" v-if="unit">{{ unit }}</div>
     </template>
@@ -60,6 +72,7 @@ const emitInpVal = (value) => {
 <style lang="scss" scoped>
 .unit {
   flex-shrink: 1;
+  min-width: 46px;
   height: 100%;
   padding: 0 8px;
   border-radius: 0px 8px 8px 0px;

+ 1 - 1
src/components/Layout/TheChatView.vue

@@ -108,7 +108,7 @@ defineExpose({ targetScrollDom });
     .control-main {
       // flex: 1;
       height: calc(100vh - 124px);
-      padding: 0 24px 24px 24px;
+      padding: 0 24px 0px 24px;
     }
 
     .chat-main {

+ 1 - 1
src/components/Layout/TheLogo.vue

@@ -21,7 +21,7 @@ const handleClick = () => router.push('/');
       <div class="w-[28px] h-[28px]">
         <SvgIcon name="common-logo" size="28"></SvgIcon>
       </div>
-      <span class="block w-[80px] font-[10px] text-left">人工智能运营体 锡林浩特水务集团</span>
+      <span class="block w-[82px] font-[10px] text-left">人工智能运营体<br>锡林浩特水务集团</span>
     </div>
     <!-- 图标 -->
     <div class="icon-group flex items-center justify-center"  @click="changeCollapse" v-show="!subMenuCollapse">

+ 170 - 32
src/views/control/MedicinalView.vue

@@ -1,6 +1,6 @@
 <script setup>
 import { ref, onMounted, computed, unref, watch } from 'vue';
-import { NScrollbar, useMessage, NTabs, NTabPane, NSwitch } from 'naive-ui';
+import { NScrollbar, useMessage, NTabs, NTabPane, NSwitch, useNotification, NButton } from 'naive-ui';
 import { TheChatView } from '@/components';
 import { controlApi } from "@/api/control";
 import BaseTitle from './components/BaseTitle.vue';
@@ -10,6 +10,8 @@ import BaseInput from './components/BaseInput.vue';
 import TheResultPanel from './components/TheResultPanel.vue';
 import TheEchartPanel from './components/TheEchartPanel.vue';
 
+const warningList = ref([]);
+const notification = useNotification()
 const message = useMessage();
 const isVisibleBtn = ref(true);
 const isVisibleUpdateInfo = ref(false);
@@ -21,7 +23,9 @@ const resultNumberSet = ref({
   doseNum1: 0,
   doseNum2: 0,
   updateNum1: 0,
-  updateNum2: 0
+  updateNum2: 0,
+  currentResultNum1: 0,
+  currentResultNum2: 0
 });
 
 const railStyle = ({ focused,checked }) => {
@@ -50,7 +54,7 @@ const southColumnData = ref([
   { label: '转换系数', key: 'zhxsOne', value: null },
   { label: '稀释倍数', key: 'sxpsOne', value: null },
   { label: '药剂密度', key: 'yymdOne', value: null },
-  { label: '最小启动流量', key: 'zxqdllOne', value: null },
+  // { label: '最小启动流量', key: 'zxqdllOne', value: null },
   { label: '碳氮比', key: 'tdbOne', value: null }
 ]);
 
@@ -64,7 +68,7 @@ const northColumnData = ref([
   { label: '转换系数', key: 'zhxsTwo', value: null },
   { label: '稀释倍数', key: 'sxpsTwo', value: null },
   { label: '药剂密度', key: 'yymdTwo', value: null },
-  { label: '最小启动流量', key: 'zxqdllTwo', value: null },
+  // { label: '最小启动流量', key: 'zxqdllTwo', value: null },
   { label: '碳氮比', key: 'tdbTwo', value: null }
 ]);
 
@@ -170,7 +174,7 @@ const onFinalResult = async (type) => {
 // 切换tabs
 const onUpdateTab = (index) => {
   dataSourceParams.value.type = index;
-  setTimeout(() => handleMedicateAmount())
+  // setTimeout(() => handleMedicateAmount())
 }
 
 function getTotalNum() {
@@ -183,22 +187,29 @@ function getTotalNum() {
     htfksdOne, jzxsOne, xzxsOne, kzxsOne, slfpxsOne, tydlOne, zhxsOne, sxpsOne, yymdOne,
     htfksdTwo, jzxsTwo, xzxsTwo, kzxsTwo, slfpxsTwo, tydlTwo, zhxsTwo, sxpsTwo, yymdTwo,
   } = dataSourceParams.value;
+  
+  const oneStep = ((qycAdOne + qycYxyOne) * xzxsOne - htfksdOne) > 0 ? ((qycAdOne + qycYxyOne) * xzxsOne - htfksdOne) : 0;
+  const twoStep = ((qycAdTwo + qycYxyTwo) * xzxsTwo - htfksdTwo) > 0 ? ((qycAdTwo + qycYxyTwo) * xzxsTwo - htfksdTwo) : 0;
 
-  const stepSouthOne = (((2 * hycXsyOne - htfksdOne) + ((qycAdOne + qycYxyOne) * xzxsOne - htfksdOne)) * (jzxsOne - 1)) * (jsLlOne * slfpxsOne) / 1000;
-  const stepSouthTwo = (stepSouthOne * kzxsOne - (jsLlOne * slfpxsOne * jsCodOne * zhxsOne / 1000)) / tydlOne;
+  const stepSouthOne = (((2 * hycXsyOne - htfksdOne) + (oneStep)) * (jzxsOne - 1)) * (jsLlOne * slfpxsOne) / 1000;
+  const stepSouthTwo = Number(((stepSouthOne * kzxsOne - (jsLlOne * slfpxsOne * jsCodOne * zhxsOne / 1000).toFixed(10)) / tydlOne).toFixed(10));
   const stepSouthThree = stepSouthTwo / yymdOne / 1000 * sxpsOne;
 
-  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 stepNorthOne = (((2 * hycXsyTwo - htfksdTwo) + (twoStep)) * (jzxsTwo - 1)) * (jsLlTwo * slfpxsTwo) / 1000;
+  const stepNorthTwo = Number(((stepNorthOne * kzxsTwo - (jsLlTwo * slfpxsTwo * jsCodTwo * zhxsTwo / 1000).toFixed(10)) / tydlTwo).toFixed(10));
   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 { maxAddAmount, minAddAmount } = minAndMaxValue.value;
+  
+  resultNumberSet.value.currentResultNum1 = Number((stepSouthThree * 1000).toFixed(3));
+  resultNumberSet.value.currentResultNum2 = Number((stepNorthThree * 1000).toFixed(3));
 
   if ( r1 > maxAddAmount ) r1  = maxAddAmount;
   if ( r1 < minAddAmount ) r1  = minAddAmount;
@@ -225,6 +236,9 @@ const onConfirmUpdate = async () => {
   
   resultNumberSet.value.doseNum1 = resultNumberSet.value.updateNum1;
   resultNumberSet.value.doseNum2 = resultNumberSet.value.updateNum2;
+
+  resultNumberSet.value.currentResultNum1 = resultNumberSet.value.updateNum1;
+  resultNumberSet.value.currentResultNum2 = resultNumberSet.value.updateNum2;
 }
 
 const handleAutoSwitch = (val) => {
@@ -245,48 +259,148 @@ const handleMedicateAmount = () => {
   const tdb = tdbNum.value;
 
   const [r1, r2] = getTotalNum();
-  
+
+  waringTips();
+
   if ( type == 0 ) {
-    if ( !dataSourceParams.value.typeOne && dataSourceParams.value.medicineAmountOne != updateNum1 ) {
+    // && 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("北池有新的投放方案, 请查看");
+        // message.warning("北池有新的投放方案, 请查看");
       } else {
         resultNumberSet.value.updateNum1 = dataSourceParams.value.medicineAmountOne;
         // isVisibleUpdateInfo.value = true;
       }
-    } else if (r1 != updateNum1 && dataSourceParams.value.typeOne) {
+      // r1 != updateNum1 &&
+    } else if ( dataSourceParams.value.typeOne) {
       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) {
+    // && 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("南池有新的投放方案, 请查看");
+        // message.warning("南池有新的投放方案, 请查看");
       } else {
         resultNumberSet.value.updateNum2 = dataSourceParams.value.medicineAmountTwo;
         // isVisibleUpdateInfo.value = true;
       }
-    } else if (r2 != updateNum2 && dataSourceParams.value.typeTwo) {
+      // r2 != updateNum2 &&
+    } else if ( dataSourceParams.value.typeTwo) {
       resultNumberSet.value.updateNum2 = r2;
       isVisibleUpdateInfo.value = true;
       dataSourceParams.value.tdbTwo = tdb;
-      message.warning("南池有新的投放方案, 请查看");
+      // message.warning("南池有新的投放方案, 请查看");
     }
   }
 }
 
-onMounted(async () => {
+// 查看系统警报
+const onSystemWarning = () => {
+  warningList.value.forEach(item => {
+    notification.warning({...item})
+  })
+}
+
+const isEmpty = (val) => {
+  return !(val == null || val == undefined || val == '')
+}
+
+const waringTips = () => {
+  const { kzmbplbjz, hycxsygkz, xhycbjz, jylpybjz, minAddAmount } = minAndMaxValue.value;
+
+  const {
+    hycXsyOne, htfksdOne, hycXsyTwo, htfksdTwo, 
+    qycYxyOne, qycAdOne, qycYxyTwo, qycAdTwo,
+    // 加药偏移量 北池
+    addDifferenceOne,
+    // 加药偏移量 南池
+    addDifferenceTwo
+  } = dataSourceParams.value;
+  
+  
+  const tipsEnum = {
+    oneTips: {
+      title: '反硝化异常报警',
+      content: '排查现场工况/调整控制参数,非碳源量的问题,请切手动控制',
+    },
+    twoTips: {
+      title: '硝化异常报警',
+      content: '排查进水水质、曝气系统、活性污泥系统等,请切手动运行',
+    },
+    threeTips: {
+      title: '加药量偏移报警',
+      content: '排查现场碳源储罐液位、加药泵和流量计等,确保运行正常',
+    }
+  }
+  
+  const result = {
+    oneTips: [],
+    twoTips: [],
+    threeTips: []
+  }
 
+  if ( isEmpty(hycXsyOne) && isEmpty(htfksdOne) && isEmpty(kzmbplbjz) ) {
+    if ( (hycXsyOne - htfksdOne) > kzmbplbjz || hycXsyOne > hycxsygkz) {
+      result.oneTips.push('北池');
+    }
+  }
+  
+  if ( isEmpty(hycXsyTwo) && isEmpty(htfksdTwo) && isEmpty(kzmbplbjz) ) {
+    if ( (hycXsyTwo - htfksdTwo) > kzmbplbjz || hycXsyTwo > hycxsygkz) {
+      result.oneTips.push('南池');
+    }
+  }
+  
+  if ( isEmpty(hycXsyOne) && isEmpty(qycYxyOne) && isEmpty(qycAdOne) ) {
+    if (qycYxyOne + qycAdOne - hycXsyOne > xhycbjz) {
+      result.twoTips.push('北池');
+    }
+  }
+
+  if ( isEmpty(hycXsyTwo) && isEmpty(qycYxyTwo) && isEmpty(qycAdTwo) ) {
+    if (qycYxyTwo + qycAdTwo - hycXsyTwo > xhycbjz) {
+      result.twoTips.push('南池');
+    }
+  }
+
+  if ( isEmpty(addDifferenceOne) && isEmpty(jylpybjz) && isEmpty(minAddAmount)) {
+    if (addDifferenceOne > minAddAmount && addDifferenceOne > jylpybjz) {
+      result.threeTips.push('北池');
+    }
+  }
+ 
+  if ( isEmpty(addDifferenceTwo) && isEmpty(jylpybjz) && isEmpty(minAddAmount)) {
+    if (addDifferenceTwo > minAddAmount && addDifferenceTwo > jylpybjz) {
+      result.threeTips.push('南池');
+    }
+  }
+  
+  warningList.value = Object.entries(result).map(([key, value]) => {
+    if ( value.length ) {
+      return {
+        ...tipsEnum[key],
+        title: tipsEnum[key].title + "(" + value.join(' | ') + ")",
+        duration: 30 * 1000,
+        keepAliveOnHover: true,
+      }
+    }
+  }).filter(Boolean);
+
+}
+
+onMounted(async () => {
+  
   const { data: valSet } = await controlApi.getMinMaxVal();
   
   minAndMaxValue.value = valSet;
@@ -305,9 +419,12 @@ onMounted(async () => {
       // 投加量
       medicineAmountOne, medicineAmountTwo,
       // 瞬时流量
-      tytjTransientLLOne, tytjTransientLLTwo
+      tytjTransientLLOne, tytjTransientLLTwo,
+
+      addDifferenceOne,
+      addDifferenceTwo
     } = data;
-    
+
     systemStatus.value = { activeTwo: addTypeOne, activeTwo: addTypeTwo };
 
     // 系数
@@ -321,7 +438,9 @@ onMounted(async () => {
       flowNum1: tytjTransientLLOne,
       flowNum2: tytjTransientLLTwo,
       doseNum1: medicineAmountOne,
-      doseNum2: medicineAmountTwo
+      doseNum2: medicineAmountTwo,    
+      currentResultNum1: medicineAmountOne,
+      currentResultNum2: medicineAmountTwo
     }
 
     // 数据源
@@ -339,7 +458,12 @@ onMounted(async () => {
 
       medicineAmountOne: typeOne === 0 ? 0 : medicineAmountOne,
       medicineAmountTwo: typeTwo === 0 ? 0 : medicineAmountTwo,
+
+      addDifferenceOne,
+      addDifferenceTwo
     };
+
+    waringTips();
   })
  
   // 获取实时数据
@@ -669,15 +793,23 @@ onMounted(async () => {
             </n-scrollbar>
           </div>
           <div class="right-section">
-            <TheResultPanel
-              :nums="resultNumberSet"
-              v-model:system="systemStatus"
-              v-model="isVisibleUpdateInfo"
-              @on-click="onFinalResult"
-              @on-update="onConfirmUpdate"
-            >
-            </TheResultPanel>
-            <TheEchartPanel></TheEchartPanel>
+            <BaseTitle title="智能投加计算结果" type="second">
+              <template #right>
+                <NButton strong secondary type="warning" @click="onSystemWarning" v-show="warningList.length">查看系统警报</NButton>
+              </template>
+            </BaseTitle>
+            <div class="right-section-content">
+              <TheResultPanel
+                :nums="resultNumberSet"
+                :minAndMaxNum="minAndMaxValue"
+                v-model:system="systemStatus"
+                v-model="isVisibleUpdateInfo"
+                @on-click="onFinalResult"
+                @on-update="onConfirmUpdate"
+              >
+              </TheResultPanel>
+              <TheEchartPanel v-model:change="isVisibleUpdateInfo"></TheEchartPanel>
+            </div>
           </div>
         </div>
       </template>
@@ -781,6 +913,12 @@ onMounted(async () => {
   border-radius: 8px;
   background: #fff;
   overflow: hidden;
+
+  .right-section-content {
+    display: flex;
+    flex-flow: column;
+    height: calc(100% - 75px);
+  }
 }
 
 

+ 20 - 4
src/views/control/components/TheEchartPanel.vue

@@ -1,11 +1,14 @@
 <script setup>
-import { ref, computed, onMounted, nextTick, onUnmounted } from 'vue';
+import { ref, computed, onMounted, watch, 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"
 import dayjs from 'dayjs';
 
+
+const isDomSizeChange = defineModel('change');
+
 let echart = null;
 let tempTabItemOneKey = 0;
 let tempTabItemTwoKey = 'jzxsOne';
@@ -33,8 +36,8 @@ const selectEnum = {
   7:  '进水总氮',
   8:  '碳源投加量-北池-计算投药量',
   9:  '碳源投加量-南池-计算投药量',
-  10: '碳源投加量-北池-组态反馈',
-  11: '碳源投加量-南池-组态反馈'
+  10: '碳源投加量-北池-反馈流量',
+  11: '碳源投加量-南池-反馈流量'
 }
 
 let echartOptions = [
@@ -66,10 +69,13 @@ const selectThemeOverrides = {
   },
 }
 
+
+
 const seriesName = computed(() => {
   let name = '';
   if ( activeIndex.value === 0) {
     name = echartOptions.find(({ value }) => selectValue.value === value).label
+    console.log("name", name);
   } else {
     name = coefficientOptions.find(item => item.value === selectValue.value).label
   }
@@ -215,6 +221,11 @@ const getWaterEchartOptions = ({ data, xAxis = [] }) => {
       splitArea: {
         show: false,
         color: '#fff'
+      },
+      axisLabel: {
+        formatter: function (value) {
+          return value.toFixed(0)
+        }
       }
     },
     series
@@ -373,6 +384,10 @@ const onDatePickerClear = () => {
   activeIndex.value === 0 ? initWaterEchartData() : intiCoefficientEchartData();
 }
 
+watch(() => isDomSizeChange.value, (val) => {
+  setTimeout(() => windowResize(), 200)
+});
+
 onMounted(async () => {
   echartOptions = Object.entries(selectEnum).map(([key, value]) => {
     return { label: value, value: key, style: "font-size: 12px" }
@@ -442,9 +457,10 @@ onUnmounted(() => {
 
 <style lang="scss" scoped>
 .echart-card_view {
+  flex: 1;
   display: flex;
   flex-flow: column;
-  height: calc(100% - 284px);
+  // height: calc(100% - 284px);
   padding: 0px 16px 0 25px;
   border-radius: 10px;
 

+ 118 - 87
src/views/control/components/TheResultPanel.vue

@@ -1,6 +1,6 @@
 <script setup>
+import { computed } from 'vue';
 import { NNumberAnimation } from 'naive-ui';
-import BaseTitle from './BaseTitle.vue';
 import { SvgIcon } from '@/components';
 
 const isVisibleBtn = defineModel();
@@ -11,9 +11,40 @@ const props = defineProps({
   nums: {
     type: Object,
     default: () => ({})
+  },
+  minAndMaxNum: {
+    type: Object,
+    default: () => ({})
   }
 });
 
+const getTipsText = (name, updateNum, doseNum) => {
+  const { minAddAmount, maxAddAmount } = props.minAndMaxNum;
+  const { currentResultNum1, currentResultNum2 } = props.nums;
+  
+  const num = name === '北池' ? currentResultNum1 : currentResultNum2;
+
+  if ( updateNum === doseNum ) {
+    return `${name}设定后,系统加药量未发生变化`;
+  }
+  if ( updateNum >= maxAddAmount ) {
+    return `${name}设定系统加药量计算值为${num}L/h,已达到最大值${maxAddAmount}L/h,按照最大值投放`;
+  }
+  if ( updateNum <= minAddAmount ) {
+    return `${name}设定系统加药量计算值为${num}L/h,已达到最小值${minAddAmount}L/h,按照最小值投放`;
+  }
+  
+  return `${name}有新投放方案,加药量计算为:${updateNum}L/h`
+}
+
+const noticeTextVal = computed(() => {
+  const { updateNum1, doseNum1, updateNum2, doseNum2 } = props.nums;
+  return {
+    southVal: getTipsText('北池', updateNum1, doseNum1),
+    northVal: getTipsText('南池', updateNum2, doseNum2)
+  }
+})
+
 const emitEvent = (type) => {
   // emit('on-click', type);
 };
@@ -23,84 +54,87 @@ const emitUpdate = async () => {
 </script>
 
 <template>
-  <div>
-    <BaseTitle title="智能投加计算结果" type="second"></BaseTitle>
-    <div class="result-card_view">
-      <div class="update-message-box">
-        <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 }}L/h, 南池加药量计算:{{ nums.updateNum2 || 0}}L/h,是否更新?</span>
-          </li>
-          <li class="space-x-[10px]">
-            <span class="text-[#ed742f] cursor-pointer" @click="emitUpdate">更新投放</span>
-            <span class="text-[#88909b] cursor-pointer" @click="isVisibleBtn = false">取消</span>
-          </li>
-        </ul>
+  <div class="result-card_view">
+    <div class="update-message-box space-x-[30px]" v-show="isVisibleBtn">
+      <ul class="update-message">
+        <li class="flex items-center space-x-[4px]">
+          <SvgIcon name="control-icon-warning" size="14"></SvgIcon>
+          <span>{{ noticeTextVal.southVal }}</span>
+          <!-- <span>北池加药量计算:{{ nums.updateNum1 || 0 }}L/h</span> -->
+        </li>
+        <li class="flex items-center space-x-[4px]">
+          <SvgIcon name="control-icon-warning" size="14"></SvgIcon>
+          <span>{{ noticeTextVal.northVal }}</span>
+          <!-- <span>南池加药量计算:{{ nums.updateNum2 || 0}}L/h,是否更新?</span> -->
+        </li>
+      </ul>
+      <div class="space-x-[10px]">
+        <span class="text-[#ed742f] cursor-pointer" @click="emitUpdate">更新投放</span>
+        <span class="text-[#88909b] cursor-pointer" @click="isVisibleBtn = false">取消</span>
       </div>
-      <div class="result-card">
-        <div class="result-inner space-x-[8px]">
-          <div class="result-card_item">
-            <ul class="board-inner">
-              <li class="board-item">
-                <span class="label">北池</span>
-                <h4>碳源投加瞬时流量(L/h)</h4>
-                <span class="num">
-                  <NNumberAnimation :from="0" :to="nums.flowNum1" :duration="1000"
-                    :precision="3"></NNumberAnimation>
-                </span>
-              </li>
-              <li class="line"></li>
-              <li class="board-item">
-                <h4>系统加药量(L/h)</h4>
-                <span class="num">
-                  <NNumberAnimation :from="0" :to="nums.doseNum1" :duration="1000"
-                    :precision="3"></NNumberAnimation>
-                </span>
-              </li>
-            </ul>
-
-            <div class="btn-card">
-              <div :class="['round-btn']" @click="emitEvent('one')">
-                <div class="circle1" v-show="modelSystemStatus.activeOne === 1"></div>
-                <div class="circle2" v-show="modelSystemStatus.activeOne === 1"></div>
-                <div class="circle3" v-show="modelSystemStatus.activeOne === 1"></div>
-                <div class="inner space-y-[4px]">
-                  <SvgIcon name="control-icon-result-btn" size="14" />
-                  <span>{{ modelSystemStatus.activeOne === 1 ? "系统投放" : "组态投放" }}</span>
-                </div>
+    </div>
+    <div class="result-card">
+      <div class="result-inner space-x-[8px]">
+        <div class="result-card_item">
+          <ul class="board-inner">
+            <li class="board-item">
+              <span class="label">北池</span>
+              <h4>碳源投加瞬时流量(L/h)</h4>
+              <span class="num">
+                <NNumberAnimation :from="0" :to="nums.flowNum1" :duration="1000"
+                  :precision="3"></NNumberAnimation>
+              </span>
+            </li>
+            <li class="line"></li>
+            <li class="board-item">
+              <h4>系统加药量(L/h)</h4>
+              <span class="num">
+                <NNumberAnimation :from="0" :to="nums.doseNum1" :duration="1000"
+                  :precision="3"></NNumberAnimation>
+              </span>
+            </li>
+          </ul>
+
+          <div class="btn-card">
+            <div :class="['round-btn']" @click="emitEvent('one')">
+              <div class="circle1" v-show="modelSystemStatus.activeOne === 1"></div>
+              <div class="circle2" v-show="modelSystemStatus.activeOne === 1"></div>
+              <div class="circle3" v-show="modelSystemStatus.activeOne === 1"></div>
+              <div class="inner space-y-[4px]">
+                <SvgIcon name="control-icon-result-btn" size="14" />
+                <span>{{ modelSystemStatus.activeOne === 1 ? "系统投放" : "组态投放" }}</span>
               </div>
             </div>
           </div>
-          <div class="result-card_item">
-            <ul class="board-inner">
-              <li class="board-item">
-                <span class="label">南池</span>
-                <h4>碳源投加瞬时流量(L/h)</h4>
-                <span class="num">
-                  <NNumberAnimation :from="0" :to="nums.flowNum2" :duration="1000"
-                    :precision="3"></NNumberAnimation>
-                </span>
-              </li>
-              <li class="line"></li>
-              <li class="board-item">
-                <h4>系统加药量(L/h)</h4>
-                <span class="num">
-                  <NNumberAnimation :from="0" :to="nums.doseNum2" :duration="1000"
-                    :precision="3"></NNumberAnimation>
-                </span>
-              </li>
-            </ul>
-            
-            <div class="btn-card">
-              <div :class="['round-btn']" @click="emitEvent('two')">
-                <div class="circle1" v-show="modelSystemStatus.activeTwo === 1"></div>
-                <div class="circle2" v-show="modelSystemStatus.activeTwo === 1"></div>
-                <div class="circle3" v-show="modelSystemStatus.activeTwo === 1"></div>
-                <div class="inner space-y-[4px]">
-                  <SvgIcon name="control-icon-result-btn" size="14" />
-                  <span>{{ modelSystemStatus.activeTwo === 1 ? "系统投放" : "组态投放" }}</span>
-                </div>
+        </div>
+        <div class="result-card_item">
+          <ul class="board-inner">
+            <li class="board-item">
+              <span class="label">南池</span>
+              <h4>碳源投加瞬时流量(L/h)</h4>
+              <span class="num">
+                <NNumberAnimation :from="0" :to="nums.flowNum2" :duration="1000"
+                  :precision="3"></NNumberAnimation>
+              </span>
+            </li>
+            <li class="line"></li>
+            <li class="board-item">
+              <h4>系统加药量(L/h)</h4>
+              <span class="num">
+                <NNumberAnimation :from="0" :to="nums.doseNum2" :duration="1000"
+                  :precision="3"></NNumberAnimation>
+              </span>
+            </li>
+          </ul>
+          
+          <div class="btn-card">
+            <div :class="['round-btn']" @click="emitEvent('two')">
+              <div class="circle1" v-show="modelSystemStatus.activeTwo === 1"></div>
+              <div class="circle2" v-show="modelSystemStatus.activeTwo === 1"></div>
+              <div class="circle3" v-show="modelSystemStatus.activeTwo === 1"></div>
+              <div class="inner space-y-[4px]">
+                <SvgIcon name="control-icon-result-btn" size="14" />
+                <span>{{ modelSystemStatus.activeTwo === 1 ? "系统投放" : "组态投放" }}</span>
               </div>
             </div>
           </div>
@@ -113,32 +147,29 @@ const emitUpdate = async () => {
 <style lang="scss" scoped>
 .result-card_view {
   position: relative;
-  height: 210px;
   padding: 0px 16px 24px 16px;
 
   .update-message-box {
-    height: 37px;
-  }
-
-  .update-message {
     display: flex;
     align-items: center;
-    width: 100%;
-    height: 100%;
-    padding-left: 16px;
-    padding-bottom: 7px;
-    flex-shrink: 0;
-    border-radius: 8px 8px 0px 0px;
+    justify-content: flex-start;
+    padding: 0 10px;
+    border-radius: 8px;
     border: 1px solid #FFE9CE;
     background: #FFF5E5;
     font-size: 12px;
+  }
+
+  .update-message {
+    height: 100%;
+    flex-shrink: 0;
     color: #7E604F;
   }
 
   .result-card {
     @include flex(x, center, between);
     padding: 16px 16px 16px 16px;
-    margin-top: -9px;
+    // margin-top: -9px;
     border: 1px solid #fff;
     border-radius: 8px;
     background: url('@/assets/images/control/bg-control-top.png') center right no-repeat, linear-gradient(90deg, #E0E8FC 0%, #F2F4FF 100%);

+ 116 - 80
src/views/user/index.vue

@@ -1,43 +1,67 @@
 <script setup>
 import { reactive, ref, onMounted } from 'vue';
-import { NForm, NFormItem, NInputNumber, NButton, useMessage } from 'naive-ui';
+import { NScrollbar, NCheckbox, NInputNumber, NButton, useMessage } from 'naive-ui';
 import { TheChatView, contactUs, userEdit } from "@/components/index";
 import { screenApi } from "@/api/screen";
 import { controlApi } from "@/api/control";
+import { BaseNumberInput } from '@/components';
 
 const message = useMessage();
 
 const data = reactive({
-  tab_action: 0,
-  tabs: ['账号管理', '自定义投药量', '关于我们'],
+  tab_action: 1,
+  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']
+const stopAddFlag = ref(false);
+
+const customRenderData = [
+  {
+    title: '进水流量',
+    children: [
+      { key: 'minJsll', label: '限定最小值', unit: 'm³/h', min: 0, max: 999999 },
+      { key: 'maxJsll', label: '限定最大值', unit: 'm³/h', min: 0, max: 999999  }
+    ]
+  },
+  {
+    title: '进水COD',
+    children: [
+      { key: 'minJsCod', label: '限定最小值', unit: 'mg/h', min: 0, max: 999999  },
+      { key: 'maxJsCod', label: '限定最大值', unit: 'mg/h', min: 0, max: 999999  }
+    ]
   },
-  { 
-    label: '进水COD:',
-    keys: ['minJsCod', 'maxJsCod']
+  {
+    title: '投药量',
+    children: [
+      { key: 'minAddAmount', label: '限定最小值', unit: 'L/h', min: 0, max: 999999  },
+      { key: 'maxAddAmount', label: '限定最大值', unit: 'L/h', min: 0, max: 999999  }
+    ]
   },
-  { 
-    label: '投药量:',
-    keys: ['minAddAmount', 'maxAddAmount']
+  {
+    title: '控制目标偏离报警值',
+    children: [
+      { key: 'kzmbplbjz', label: '大于设定值触发报警', unit: 'mg/L', min: 0, max: 999999 },
+    ]
   },
+  {
+    title: '好氧池硝酸盐管控值',
+    children: [
+      { key: 'hycxsygkz', label: '大于设定值触发报警', unit: 'mg/L', min: 0, max: 999999 },
+    ]
+  },
+  {
+    title: '硝化异常报警值',
+    children: [
+      { key: 'xhycbjz', label: '大于设定值触发报警', unit: 'mg/L', min: 0, max: 999999 },
+    ]
+  },
+  {
+    title: '加药量偏移报警值',
+    children: [
+      { key: 'jylpybjz', label: '大于设定值触发报警', unit: 'L/h', min: 0, max: 999999 },
+    ]
+  }
 ]
 
 const formValue = ref({});
@@ -57,6 +81,27 @@ const getInfo = () => {
 
 
 const updateConfig = () => {
+
+  const whitelist = [
+    'minJsll', 'maxJsll',
+    'minJsCod', 'maxJsCod',
+    'minAddAmount', 'maxAddAmount',
+    'kzmbplbjz',
+    'hycxsygkz',
+    'xhycbjz',
+    'jylpybjz'
+  ]
+  
+  const result = whitelist.every(key => {
+    const value = formValue.value[key]
+    return value !== null && value !== undefined && value !== ''
+  })
+
+
+  if (!result) {
+    return message.warning('请填写完整');
+  }
+  
   controlApi.putMinMaxVal(formValue.value).then(res => {
     message.success(res.msg);
   });
@@ -71,65 +116,50 @@ onMounted(() => {
 </script>
 
 <template>
-  <TheChatView ref="scrollRef" class="user-wraper">
-
-    <div class="user-warp">
-      <div class="user-warp-title">个人中心</div>
-      <div class="user-warp-box">
-
-        <div class="header">
-          <span
-            :class="['tab', data.tab_action == index ? 'action' : '']"
-            v-for="item, index in data.tabs"
-            @click="changeTab(index)"
-          >{{ item }}</span>
+  <TheChatView ref="scrollRef" class="user-wraper" :isChatSlot='false'>
+    <template #control>
+      <div class="user-warp">
+        <div class="user-warp-title">个人中心</div>
+        <div class="user-warp-box">
+          <div class="header">
+            <span :class="['tab', data.tab_action == index ? 'action' : '']" v-for="item, index in data.tabs"
+              @click="changeTab(index)">{{ item }}</span>
+          </div>
+          <NScrollbar style="height: calc(100% - 46px);">
+
+            <!-- 账号管理 -->
+            <userEdit v-show="data.tab_action == 0" :user="data.user"></userEdit>
+       
+            <!-- 自定义投药量 -->
+            <div class="pl-[40px]" v-show="data.tab_action == 1">
+              <div class="mb-[16px] text-[#1A2029] text-[14px]" v-for="item in customRenderData" :key="item.title">
+                <h4 class="mb-[8px]">{{ item.title }}</h4>
+                <ul class="flex items-center space-x-[24px]">
+                  <li class="w-[264px]" v-for="val in item.children">
+                    <BaseNumberInput :unit="val.unit" v-model:value="formValue[val.key]" :min="val.min" :max="val.max"></BaseNumberInput>
+                    <span class="text-[#B0B7C0] text-[12px]">{{ val.label }}</span>
+                  </li>
+                  <li v-if="item.title === '投药量'">
+                    <NCheckbox v-model="stopAddFlag" disabled>小于最小值停止投放</NCheckbox>
+                  </li>
+                </ul>
+              </div>
+              <div
+                class="w-[88px] h-[32px] bg-[#2454ff] rounded-[6px] text-[#fff] text-center text-[14px] leading-[32px] cursor-pointer"
+                @click="updateConfig"
+              >
+                <span>更新配置</span>
+              </div>
+            </div>
+
+            <!-- 联系我们 -->
+            <contactUs v-show="data.tab_action == 2"></contactUs>
+          </NScrollbar>
         </div>
 
-        <!-- 账号管理 -->
-        <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 == 2"></contactUs>
       </div>
+    </template>
 
-    </div>
 
   </TheChatView>
 </template>
@@ -154,7 +184,13 @@ onMounted(() => {
 </style>
 <style lang="scss" scoped>
 .user-warp {
+  height: 100%;
   margin: 0 60px;
+  overflow: hidden;
+
+  .user-warp-box {
+    height: calc(100% - 70px);
+  }
 
   &-title {
     line-height: 40px;