sunxiao 1 долоо хоног өмнө
parent
commit
24049d3d64

+ 4 - 4
src/App.vue

@@ -41,10 +41,10 @@ const themeOverrides: GlobalThemeOverrides = {
   },
   Input: {
     border: '0px',
-    borderHover: '0px',
-    borderFocus: '0px',
-    borderFocusWarning: '0px',
-    boxShadowFocus: 'none',
+    // borderHover: '0px',
+    // borderFocus: '0px',
+    // borderFocusWarning: '0px',
+    // boxShadowFocus: 'none',
     fontSizeMedium: '15px',
     textColor: '#1A2029',
     placeholderColor: 'rgba(158, 158, 158, 0.6)',

+ 11 - 0
src/api/control.js

@@ -35,4 +35,15 @@ export const controlApi = {
    * 碳源投加 系统是否可以投放 - 更新
    */
   putSystemStatus: data => http.put(`/front/bigModel/smartAdd/updateSwitchStatus`, data),
+
+  /**
+   * 碳源投加 数据看板 - left
+   */
+  getLeftEChartData: params => http.get(`/front/bigModel/smartAdd/charListNew`, { params }),
+
+
+  /**
+   * 碳源投加 数据看板 - right
+   */
+  getRightEChartData: params => http.get(`/front/bigModel/smartAdd/addMedicineRecordList`, { params }),
 }

+ 13 - 0
src/assets/images/control/icon-start.svg

@@ -0,0 +1,13 @@
+<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_52_12662)">
+<path d="M0 14C0 17.713 1.475 21.274 4.10051 23.8995C6.72601 26.525 10.287 28 14 28C17.713 28 21.274 26.525 23.8995 23.8995C26.525 21.274 28 17.713 28 14C28 10.287 26.525 6.72601 23.8995 4.10051C21.274 1.475 17.713 0 14 0C10.287 0 6.72601 1.475 4.10051 4.10051C1.475 6.72601 0 10.287 0 14Z" fill="#2454FF" fill-opacity="0.3"/>
+<path d="M1.88184 14.0004C1.88184 15.5918 2.19529 17.1676 2.8043 18.6379C3.41332 20.1082 4.30596 21.4441 5.43127 22.5694C6.55658 23.6948 7.89251 24.5874 9.3628 25.1964C10.8331 25.8054 12.4089 26.1189 14.0004 26.1189C15.5918 26.1189 17.1676 25.8054 18.6379 25.1964C20.1082 24.5874 21.4441 23.6948 22.5694 22.5694C23.6948 21.4441 24.5874 20.1082 25.1964 18.6379C25.8054 17.1676 26.1189 15.5918 26.1189 14.0004C26.1189 12.4089 25.8054 10.8331 25.1964 9.3628C24.5874 7.89251 23.6948 6.55658 22.5694 5.43127C21.4441 4.30596 20.1082 3.41332 18.6379 2.8043C17.1676 2.19529 15.5918 1.88184 14.0004 1.88184C12.4089 1.88184 10.8331 2.19529 9.3628 2.8043C7.89251 3.41332 6.55658 4.30596 5.43127 5.43127C4.30596 6.55658 3.41332 7.89251 2.8043 9.3628C2.19529 10.8331 1.88184 12.4089 1.88184 14.0004Z" fill="#2454FF"/>
+<path opacity="0.1" d="M0 14C0 21.7284 6.26667 27.9951 14 27.9951V0C6.26667 0 0 6.26667 0 14Z" fill="#FEFFFF"/>
+<path d="M11.1064 8.46875V19.5255C11.1064 19.5601 11.146 19.5848 11.1756 19.565L20.7509 14.0342C20.7805 14.0144 20.7805 13.97 20.7509 13.9552L11.1756 8.42431C11.146 8.40949 11.1064 8.43418 11.1064 8.46875Z" fill="white"/>
+</g>
+<defs>
+<clipPath id="clip0_52_12662">
+<rect width="28" height="28" fill="white"/>
+</clipPath>
+</defs>
+</svg>

BIN
src/assets/images/control/icon-warning.png


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

@@ -113,12 +113,14 @@ defineExpose({ targetScrollDom });
   }
 
   .chat-wrapper {
+    min-height: 600px;
     border: 1px solid #fff;
     background: linear-gradient(180deg, rgba(238, 253, 255, 0.5) 0%, rgba(231, 243, 252, 0.5) 100%);
 
     .control-main {
       // flex: 1;
-      height: calc(100vh - 124px);
+      height: calc(100vh - 100px);
+      min-height: 550px;
       padding: 0 24px 24px 24px;
     }
 

+ 18 - 2
src/views/control/CirclePanel.vue

@@ -1,17 +1,33 @@
 <script setup>
+import { NNumberAnimation } from 'naive-ui';
 
+defineProps({
+  medicineAmount: {
+    type: Number,
+    default: 0
+  },
+  tytjTransientLL: {
+    type: Number,
+    default: 0
+  }
+})
 </script>
 
 <template>
   <div class="circle-panel-container">
     <h4 class="title">智能加药量</h4>
     <div class="intellect-number">
-      <p class="num">100.80</p>
+      <p class="num">
+        <NNumberAnimation :from="0" :to="medicineAmount || 0" :duration="1000" :precision="0"></NNumberAnimation>
+      </p>
       <p class="unit">L/h</p>
     </div>
     <p class="instant-number space-x-[14px]">
       <span class="label">瞬时加药量</span>
-      <span class="value">97.40L/h</span>
+      <span class="value">
+        <NNumberAnimation :from="0" :to="tytjTransientLL || 0" :duration="1000" :precision="0"></NNumberAnimation>
+        L/h
+      </span>
     </p>
   </div>
 </template>

+ 508 - 0
src/views/control/DrawerSetting.vue

@@ -0,0 +1,508 @@
+<script setup>
+import { onMounted, ref, computed } from 'vue';
+import { NDrawer, NDrawerContent, NTabs, NTabPane, useMessage } from 'naive-ui';
+
+import { controlApi } from "@/api/control";
+
+import BaseCard from './components/BaseCard.vue';
+import BaseRadioCard from './components/BaseRadioCard.vue';
+import BaseChooseItem from './components/BaseChooseItem.vue';
+import BaseInput from './components/BaseInput.vue';
+
+const message = useMessage();
+
+const showModel = defineModel("show");
+const emit = defineEmits(['on-update']);
+
+// 基础配置参数
+const baseSourceParams = ref({
+  numberBeng: null,
+  type: 0
+})
+
+// 基础数据
+const dataSourceParams = ref({
+  auto: {},
+  onePool: {},
+  twoPool: {},
+  worker: {
+    medicineAmount: null
+  }
+})
+
+// 自定义分类的key
+const tabKeyEnum = {
+  0: 'auto',
+  1: 'onePool',
+  2: 'twoPool',
+  3: 'worker'
+}
+
+const tabActiveKey = computed(() => tabKeyEnum[baseSourceParams.value.type]);
+
+// water实时数据
+const waterConfigParams = ref({})
+
+// tabs - 更新当前选中项目
+const onUpdateTab = (index) => {
+  baseSourceParams.value.type = index;
+}
+
+const handleTabsBeforeLeave = (tabName) => {
+  if (tabName == 3) {
+    message.error('该功能未开启')
+  }
+  return tabName != 3;
+}
+
+// 更新参数配置
+const handleUpdateParams = async () => {
+
+  await controlApi.postAddRecord({
+    ...dataSourceParams.value[tabKeyEnum[baseSourceParams.value.type]],
+    ...baseSourceParams.value,
+  });
+
+  message.success("系统加药量,更新成功");
+
+  emit('on-update')
+}
+
+const initWaterParamter = async () => {
+  // 获取历史记录
+  await controlApi.getBaseData().then(({ data }) => {
+    const {
+      // 加药泵
+      numberBeng = 0, 
+      // 池组的运行方式
+      type = 0,
+      // 基础参数
+      htfksd, jzxs, xzxs, kzxs, slfpxs, tydl, zhxs, sxps, yymd, zxqdll,
+      // 人工投放数据
+      medicineAmount,
+      // 组态的状态
+      addType,
+
+      tytjTransientLL
+    } = data;
+    
+    baseSourceParams.value = { 
+      ...baseSourceParams.value,
+      numberBeng, type,
+      htfksd, jzxs, xzxs, kzxs, slfpxs, tydl, zhxs, sxps, yymd, zxqdll,
+      // 组态的状态
+      addType
+    };
+
+    dataSourceParams.value[tabActiveKey.value] = data;
+
+  })
+
+  // 获取实时数据
+  controlApi.getNumValue().then(({ data }) => {
+    waterConfigParams.value = data;
+    const params = {
+      jsLlType: 1,
+      jsLlOne: data.jslYB,
+      jsLlTwo: data.jslYB,
+      jsCodType: 1,
+      jsCodOne: data.jsCodYB,
+      jsCodTwo: data.jsCodYB,
+      hycXsyType: 1,
+      hycXsyOne: data.hyXsyHYOne,
+      hycXsyTwo: data.hyXsyHYTwo,
+      qycYxyType: 1,
+      qycYxyOne: data.qyXsyHYOne,
+      qycYxyTwo: data.qyXsyHYTwo,
+      qycAdType: 1,
+      qycAdOne: data.qyAdHYOne,
+      qycAdTwo: data.qyAdHYTwo,
+      jsTnType: 1,
+      jsTnOne: data.jsTnYB,
+      jsTnTwo: data.jsTnYB
+    }
+
+    Object.keys(dataSourceParams.value).forEach(key => {
+      const item = dataSourceParams.value[key];
+      if ( !Object.keys(item).length ) {
+        dataSourceParams.value[key] = { ...params };
+      }
+    })
+  })
+}
+
+const onDrawerAfterdEnter = () => {
+  initWaterParamter();
+}
+</script>
+
+<template>
+  <n-drawer v-model:show="showModel" :width="420" :on-after-enter="onDrawerAfterdEnter">
+    <n-drawer-content title="参数设置" closable body-content-style="padding: 24px 16px 40px 16px;" :header-style="{ padding: '16px 12px', fontSize: '15px', color: '#1A2029' }">
+      <div class="h-full relative">
+        <BaseCard title="选择加药泵">
+          <BaseRadioCard v-model="baseSourceParams.numberBeng"></BaseRadioCard>
+        </BaseCard>
+        <BaseCard title="选择池组手自动方式" class="chiose-mode_card">
+          <n-tabs
+            justify-content="space-between"
+            type="line"
+            tab-style="min-width: 89px;"
+            tab-class="custom-tab_item"
+            pane-class="custom-tab_pane"
+            animated
+            :bar-width="0"
+            :on-update:value="onUpdateTab"
+            :value="baseSourceParams.type"
+            @before-leave="handleTabsBeforeLeave"
+          >
+            <n-tab-pane :name="0" tab="自动">
+              <div class="panel-header_main">
+                <p>设置数据来源</p>
+                <p class="space-x-[20px] text-center">
+                  <span>1号池</span>
+                  <span>2号池</span>
+                </p>
+              </div>
+              <div class="space-y-[12px]">
+                <BaseChooseItem
+                  tab-key="auto"
+                  title="进水流量"
+                  unit="m³"
+                  isDouble
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.auto.jsLlType"
+                  v-model:value1="dataSourceParams.auto.jsLlOne"
+                  v-model:value2="dataSourceParams.auto.jsLlTwo"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '仪表', value1: waterConfigParams.jslYB, value2: waterConfigParams.jslYB }
+                  ]"
+                ></BaseChooseItem>
+                <BaseChooseItem
+                  tab-key="auto"
+                  title="进水COD"
+                  unit="mg/L"
+                  isDouble
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.auto.jsCodType"
+                  v-model:value1="dataSourceParams.auto.jsCodOne"
+                  v-model:value2="dataSourceParams.auto.jsCodTwo"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '仪表', value1: waterConfigParams.jsCodYB, value2: waterConfigParams.jsCodYB },
+                    { label: '化验', value1: waterConfigParams.jsCodHY, value2: waterConfigParams.jsCodHY }
+                  ]"
+                ></BaseChooseItem>
+                <BaseChooseItem
+                  tab-key="auto"
+                  title="好氧池硝酸盐"
+                  unit="mg/L"
+                  isDouble
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.auto.hycXsyType"
+                  v-model:value1="dataSourceParams.auto.hycXsyOne"
+                  v-model:value2="dataSourceParams.auto.hycXsyTwo"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '化验', value1: waterConfigParams.hyXsyHYOne, value2: waterConfigParams.hyXsyHYTwo },
+                    { label: '预测', value1: waterConfigParams.hyXsyYCOne, value2: waterConfigParams.hyXsyYCTwo, disabled: true }
+                  ]"
+                ></BaseChooseItem>
+                <BaseChooseItem
+                  tab-key="auto"
+                  title="缺氧池硝酸盐"
+                  unit="mg/L"
+                  isDouble
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.auto.qycYxyType"
+                  v-model:value1="dataSourceParams.auto.qycYxyOne"
+                  v-model:value2="dataSourceParams.auto.qycYxyTwo"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '化验', value1: waterConfigParams.qyXsyHYOne, value2: waterConfigParams.qyXsyHYTwo }
+                  ]"
+                ></BaseChooseItem>
+                <BaseChooseItem
+                  tab-key="auto"
+                  title="缺氧池氨氮"
+                  unit="mg/L"
+                  isDouble
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.auto.qycAdType"
+                  v-model:value1="dataSourceParams.auto.qycAdOne"
+                  v-model:value2="dataSourceParams.auto.qycAdTwo"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '化验', value1: waterConfigParams.qyAdHYOne, value2: waterConfigParams.qyAdHYTwo }
+                  ]"
+                ></BaseChooseItem>
+                <BaseChooseItem
+                  tab-key="auto"
+                  title="进水总氮"
+                  unit="mg/L"
+                  isDouble
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.auto.jsTnType"
+                  v-model:value1="dataSourceParams.auto.jsTnOne"
+                  v-model:value2="dataSourceParams.auto.jsTnTwo"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '仪表', value1: waterConfigParams.jsTnYB, value2: waterConfigParams.jsTnYB }
+                  ]"
+                ></BaseChooseItem>
+              </div>
+            </n-tab-pane>
+            <n-tab-pane :name="1" tab="1号池">
+              <div class="panel-header_main">
+                <p>设置数据来源</p>
+                <p class="space-x-[20px] text-center">
+                  <span>1号池</span>
+                </p>
+              </div>
+              <div class="space-y-[12px]">
+                <BaseChooseItem
+                  tab-key="onePool"
+                  title="进水流量"
+                  unit="m³"
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.onePool.jsLlType"
+                  v-model:value1="dataSourceParams.onePool.jsLlOne"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '仪表', value1: waterConfigParams.jslYB, value2: waterConfigParams.jslYB }
+                  ]"
+                ></BaseChooseItem>
+                <BaseChooseItem
+                  tab-key="onePool"
+                  title="进水COD"
+                  unit="mg/L"
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.onePool.jsCodType"
+                  v-model:value1="dataSourceParams.onePool.jsCodOne"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '仪表', value1: waterConfigParams.jsCodYB, value2: waterConfigParams.jsCodYB },
+                    { label: '化验', value1: waterConfigParams.jsCodHY, value2: waterConfigParams.jsCodHY }
+                  ]"
+                ></BaseChooseItem>
+                <BaseChooseItem
+                  tab-key="onePool"
+                  title="好氧池硝酸盐"
+                  unit="mg/L"
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.onePool.hycXsyType"
+                  v-model:value1="dataSourceParams.onePool.hycXsyOne"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '化验', value1: waterConfigParams.hyXsyHYOne, value2: waterConfigParams.hyXsyHYTwo },
+                    { label: '预测', value1: waterConfigParams.hyXsyYCOne, value2: waterConfigParams.hyXsyYCTwo, disabled: true }
+                  ]"
+                ></BaseChooseItem>
+                <BaseChooseItem
+                  tab-key="onePool"
+                  title="缺氧池硝酸盐"
+                  unit="mg/L"
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.onePool.qycYxyType"
+                  v-model:value1="dataSourceParams.onePool.qycYxyOne"
+                  v-model:value2="dataSourceParams.onePool.qycYxyTwo"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '化验', value1: waterConfigParams.qyXsyHYOne, value2: waterConfigParams.qyXsyHYTwo }
+                  ]"
+                ></BaseChooseItem>
+                <BaseChooseItem
+                  tab-key="onePool"
+                  title="缺氧池氨氮"
+                  unit="mg/L"
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.onePool.qycAdType"
+                  v-model:value1="dataSourceParams.onePool.qycAdOne"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '化验', value1: waterConfigParams.qyAdHYOne, value2: waterConfigParams.qyAdHYTwo }
+                  ]"
+                ></BaseChooseItem>
+                <BaseChooseItem
+                  tab-key="onePool"
+                  title="进水总氮"
+                  unit="mg/L"
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.onePool.jsTnType"
+                  v-model:value1="dataSourceParams.onePool.jsTnOne"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '仪表', value1: waterConfigParams.jsTnYB, value2: waterConfigParams.jsTnYB }
+                  ]"
+                ></BaseChooseItem>
+              </div>
+            </n-tab-pane>
+            <n-tab-pane :name="2" tab="2号池">
+              <div class="panel-header_main">
+                <p>设置数据来源</p>
+                <p class="space-x-[20px] text-center">
+                  <span>2号池</span>
+                </p>
+              </div>
+              <div class="space-y-[12px]">
+                <BaseChooseItem
+                  tab-key="twoPool"
+                  title="进水流量"
+                  unit="m³"
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.twoPool.jsLlType"
+                  v-model:value1="dataSourceParams.twoPool.jsLlTwo"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '仪表', value1: waterConfigParams.jslYB, value2: waterConfigParams.jslYB }
+                  ]"
+                ></BaseChooseItem>
+                <BaseChooseItem
+                  tab-key="twoPool"
+                  title="进水COD"
+                  unit="mg/L"
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.twoPool.jsCodType"
+                  v-model:value1="dataSourceParams.twoPool.jsCodTwo"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '仪表', value1: waterConfigParams.jsCodYB, value2: waterConfigParams.jsCodYB },
+                    { label: '化验', value1: waterConfigParams.jsCodHY, value2: waterConfigParams.jsCodHY }
+                  ]"
+                ></BaseChooseItem>
+                <BaseChooseItem
+                  tab-key="twoPool"
+                  title="好氧池硝酸盐"
+                  unit="mg/L"
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.twoPool.hycXsyType"
+                  v-model:value1="dataSourceParams.twoPool.hycXsyTwo"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '化验', value1: waterConfigParams.hyXsyHYTwo },
+                    { label: '预测', value1: waterConfigParams.hyXsyYCTwo, disabled: true }
+                  ]"
+                ></BaseChooseItem>
+                <BaseChooseItem
+                  tab-key="twoPool"
+                  title="缺氧池硝酸盐"
+                  unit="mg/L"
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.twoPool.qycYxyType"
+                  v-model:value1="dataSourceParams.twoPool.qycYxyTwo"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '化验', value1: waterConfigParams.qyXsyHYTwo }
+                  ]"
+                ></BaseChooseItem>
+                <BaseChooseItem
+                  tab-key="twoPool"
+                  title="缺氧池氨氮"
+                  unit="mg/L"
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.twoPool.qycAdType"
+                  v-model:value1="dataSourceParams.twoPool.qycAdTwo"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '化验', value1: waterConfigParams.qyAdHYTwo }
+                  ]"
+                ></BaseChooseItem>
+                <BaseChooseItem
+                  tab-key="twoPool"
+                  title="进水总氮"
+                  unit="mg/L"
+                  @on-update="handleMedicateAmount"
+                  v-model:type="dataSourceParams.twoPool.jsTnType"
+                  v-model:value1="dataSourceParams.twoPool.jsTnTwo"
+                  :btn-group="[
+                    { label: '手动', value1: '', value2: '', disabled: true },
+                    { label: '仪表', value1: waterConfigParams.jsTnYB }
+                  ]"
+                ></BaseChooseItem>
+              </div>
+            </n-tab-pane>
+            <n-tab-pane :name="3" tab="人工投放" disabled>
+              <div class="panel-header_main">
+                <p>设置数据来源</p>
+                <p class="space-x-[20px] text-center">
+                  <span>人工投放</span>
+                </p>
+              </div>
+              <div class="w-full flex items-center justify-between">
+                <span>人工投放:</span>
+                <div class="w-[200px]">
+                  <BaseInput
+                    :isCloseIcon="false"
+                    v-model="dataSourceParams.worker.medicineAmount"
+                    @on-blur="handleMedicateAmount"
+                  ></BaseInput>
+                </div>
+              </div>
+            </n-tab-pane>
+          </n-tabs>
+        </BaseCard>
+        <div class="btn-wrap">
+          <div class="update-btn" @click="handleUpdateParams">更新参数设置</div>
+        </div>
+      </div>
+    </n-drawer-content>
+  </n-drawer>
+</template>
+
+<style lang="scss" scoped>
+.btn-wrap {
+  @include flex(x, center, center);
+  margin-top: 170px;
+  color: #FFF;
+  text-align: center;
+  font-size: 14px;
+  font-weight: 500;
+  line-height: 48px;
+  .update-btn {
+    width: 148px;
+    height: 48px;
+    border-radius: 40px;
+    background: linear-gradient(to right, rgba(36, 84, 255, 1), rgba(39, 224, 255, 1));
+    cursor: pointer;
+  }
+}
+
+// 选择池组手自动方式
+.chiose-mode_card {
+  :deep(.custom-tab_item) {
+    @include flex (x, center, center);
+    width: 90px;
+    height: 36px;
+    padding: 0 !important;
+    border-radius: 4px;
+    border: 1px solid #D3D7DD;
+    background: #fff;
+  }
+  
+  :deep(.n-tabs-tab--active) {
+    border-color: #2454FF;
+    background: #EBF0FF;
+  }
+  
+  :deep(.n-tabs-nav-scroll-content) {
+    border: 0px !important;
+  }
+
+  :deep(.custom-tab_pane) {
+    height: 280px;
+  }
+
+  .panel-header_main {
+    @include flex(x, center, between);
+    padding-bottom: 10px;
+    color: #86909C;
+
+    span {
+      display: inline-block;
+      width: 60px;
+    }
+  }
+}
+
+</style>

+ 77 - 0
src/views/control/DrawerWarning.vue

@@ -0,0 +1,77 @@
+<script setup>
+import { onMounted, ref, computed } from 'vue';
+import { NDrawer, NDrawerContent, NTabs, NTabPane, useMessage } from 'naive-ui';
+
+
+import { controlApi } from "@/api/control";
+
+const showModel = defineModel("show");
+
+
+const onDrawerAfterdEnter =() => {
+  // 这个可能用不到
+}
+</script>
+
+<template>
+  <n-drawer v-model:show="showModel" :width="420" :placement="placement" :on-after-enter="onDrawerAfterdEnter">
+    <n-drawer-content title="系统报警" closable body-content-style="padding: 24px 16px 40px 16px;" :header-style="{ padding: '16px 12px', fontSize: '15px', color: '#1A2029' }">
+      <div class="warning-list h-full relative space-y-[10px]">
+        <div class="warning-item space-x-[8px]">
+          <img src="@/assets/images/control/icon-warning.png" alt="" class="w-[18px] h-[18px]">
+          <div class="main space-x-[4px]">
+            <h4 class="title">反硝化异常报警</h4>
+            <ul class="content">
+              <li>・好氧池硝酸盐超管控值(北池)</li>
+              <li>・好氧池硝酸盐控制目标偏移过大(北池)</li>
+            </ul>
+            <p class="desc">排查现场工况/调整控制参数,非碳源量的问题,请切手动控制</p>
+          </div>
+        </div>
+        <div class="warning-item space-x-[8px]">
+          <img src="@/assets/images/control/icon-warning.png" alt="" class="w-[18px] h-[18px]">
+          <div class="main space-x-[4px]">
+            <h4 class="title">反硝化异常报警</h4>
+            <ul class="content">
+              <li>・好氧池硝酸盐超管控值(北池)</li>
+              <li>・好氧池硝酸盐控制目标偏移过大(北池)</li>
+            </ul>
+            <p class="desc">排查现场工况/调整控制参数,非碳源量的问题,请切手动控制</p>
+          </div>
+        </div>
+      </div>
+    </n-drawer-content>
+  </n-drawer>
+</template>
+
+<style lang="scss" scoped>
+.warning-item {
+  @include flex(x, start, start);
+  padding: 16px 0 16px 16px;
+  border-radius: 10px;
+  border-left: 2px solid #FF4920;
+  background: linear-gradient(90deg, #FFF1F1 0%, #FFFCFC 100%), #F3F5FA;
+
+  .title {
+    @include flex(x, center, start);
+    color: #1D2129;
+    font-size: 14px;
+    font-weight: bold;
+    line-height: 22px;
+  }
+
+  .content {
+    color: #4E5969;
+    font-size: 12px;
+    font-weight: 400;
+    line-height: 18px;
+  }
+
+  .desc {
+    color: #86909C;
+    font-size: 10px;
+    font-weight: 400;
+    line-height: 16px;
+  }
+}
+</style>

+ 413 - 0
src/views/control/EchartLeft.vue

@@ -0,0 +1,413 @@
+<script setup>
+import { ref, onMounted, nextTick, onUnmounted } from "vue";
+import { NSpin, NSelect, NDatePicker, useMessage } from "naive-ui";
+import dayjs from "dayjs";
+import * as echarts from 'echarts';
+
+import { controlApi } from "@/api/control";
+
+import { startOfDay } from "date-fns/esm"
+import BaseTitle from './components/BaseTitle.vue';
+
+let leftEchartInstance = null;
+
+const selectValue = ref(['jsSlq']);
+const echartDataSource = ref([]);
+
+
+const datePickerValue = ref(null);
+
+const selectOptions = ref([
+  { label: "进水流量", value: 'jsSlq', style: "font-size: 12px" },
+  { label: "#1好氧池硝酸盐", value: 'no3Hlj1Jqr', style: "font-size: 12px" },
+  { label: "#2好氧池硝酸盐", value: 'no3Hlj2Jqr', style: "font-size: 12px" },
+  { label: "#1缺氧池氨氮", value: 'nh31Jqr', style: "font-size: 12px" },
+  { label: "#2缺氧池氨氮", value: 'nh32Jqr', style: "font-size: 12px" },
+  { label: "进水COD-仪表", value: 'jsCod', style: "font-size: 12px" },
+  { label: "进水COD-检测", value: 'codYb', style: "font-size: 12px" },
+  { label: "进水总氮TN", value: 'jsTn', style: "font-size: 12px" }
+])
+
+const selectOptionsEnum = {
+  jsSlq: '进水流量',
+  no3Hlj1Jqr: '#1好氧池硝酸盐',
+  no3Hlj2Jqr: '#2好氧池硝酸盐',
+  nh31Jqr: '#1缺氧池氨氮',
+  nh32Jqr: '#2缺氧池氨氮',
+  jsCod: '进水COD-仪表',
+  jsCod: '进水COD-检测',
+  codYb: '进水总氮TN',
+  jsTn: '进水总氮TN'
+}
+
+// 日期范围限制
+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 datePickerThemeOverrides = {
+  peers: {
+    Input: {
+      borderRadius: '2px',
+    }
+  },
+}
+
+// 重置下拉框样式
+const selectThemeOverrides = {
+  peers: {
+    InternalSelection: {
+      borderRadius: '2px',
+      border: '0px'
+    }
+  },
+}
+
+const getEchartOptions = (list) => {
+
+  const series = list.map(({ name, data }) => {
+    const d1 = data.map(({ time, value }) => {
+      return [
+        time,
+        value
+      ]
+    })
+
+    return {
+      name,
+      showSymbol: false,
+      smooth: true,
+      type: 'line',
+      symbolSize: 10,
+      data: d1,
+    }
+  })
+
+  const option = {
+    legend: {
+      x: 'center',
+      y: 'top',
+      show: true,
+      left: '10px',
+      top: '16px',
+      itemWidth: 6,
+      itemGap: 20,
+      textStyle: {
+        color: '#556677',
+      },
+      // data: ['直接登录平台', '扫码登录平台', '总'],
+    },
+    title: {
+      show: !list.length,
+      text: '暂无数据',
+      x: 'center',
+      y: 'center',
+      textStyle: {
+        fontSize: 14,
+        fontWeight: 'normal',
+      }
+    },
+    grid: {
+      top: '60px',
+      bottom: '30px',
+      left: '8%',
+      right: '5%',
+      // containLabel: true  // 这个啥?看下面
+    },
+    tooltip: {
+      trigger: 'axis',
+      label: {
+        show: true
+      },
+    },
+    xAxis: {
+      type: 'time',
+      boundaryGap: ['5%', '5%'],
+      axisLine: {
+        show: false
+      },
+      splitLine: {
+        show: false
+      },
+      axisTick: {
+        show: false,
+      },
+      axisLabel: {
+        formatter: function (value) {
+          return dayjs(value).format('MM-DD')
+        }
+      }
+    },
+    yAxis: {
+      axisLine: {
+        show: false
+      },
+      splitLine: {
+        show: true,
+        lineStyle: {
+          type: 'dashed',
+          color: '#E5E5E5'
+        }
+      },
+      axisTick: {
+        show: false
+      },
+      splitArea: {
+        show: false,
+        color: '#fff'
+      },
+      axisLabel: {
+        formatter: function (value) {
+          return value.toFixed(0)
+        }
+      }
+    },
+    series
+  };
+  return option;
+}
+
+// 右侧图表
+const getRightEchartOptions = (list) => {
+  const series = list.map(({ name, data }) => {
+    const d1 = data.map(({ time, value }) => {
+      return [
+        time,
+        value
+      ]
+    })
+
+    return {
+      name,
+      showSymbol: false,
+      smooth: true,
+      type: 'line',
+      symbolSize: 10,
+      data: d1,
+    }
+  })
+
+  const option = {
+    legend: {
+      x: 'center',
+      y: 'top',
+      show: true,
+      left: '10px',
+      top: '16px',
+      itemWidth: 6,
+      itemGap: 20,
+      textStyle: {
+        color: '#556677',
+      },
+      // data: ['直接登录平台', '扫码登录平台', '总'],
+    },
+    title: {
+      show: !list.length,
+      text: '暂无数据',
+      x: 'center',
+      y: 'center',
+      textStyle: {
+        fontSize: 14,
+        fontWeight: 'normal',
+      }
+    },
+    grid: {
+      top: '60px',
+      bottom: '30px',
+      left: '8%',
+      right: '5%',
+      // containLabel: true
+    },
+    tooltip: {
+      trigger: 'axis',
+      label: {
+        show: true
+      },
+    },
+    xAxis: {
+      type: 'time',
+      boundaryGap: ['5%', '5%'],
+      axisLine: {
+        show: false
+      },
+      splitLine: {
+        show: false
+      },
+      axisTick: {
+        show: false,
+      },
+      axisLabel: {
+        formatter: function (value) {
+          return dayjs(value).format('MM-DD')
+        }
+      }
+    },
+    yAxis: {
+      axisLine: {
+        show: false
+      },
+      splitLine: {
+        show: true,
+        lineStyle: {
+          type: 'dashed',
+          color: '#E5E5E5'
+        }
+      },
+      axisTick: {
+        show: false
+      },
+      splitArea: {
+        show: false,
+        color: '#fff'
+      },
+      axisLabel: {
+        formatter: function (value) {
+          return value.toFixed(0)
+        }
+      }
+    },
+    series
+  };
+  return option;
+}
+
+// 下拉框选择事件
+const handleSelectOptions = (selectedList) => {
+  selectValue.value = selectedList;
+
+  selectOptions.value = selectOptions.value.map(item => {
+    return ({
+      ...item,
+      disabled: selectedList.length >= 3 ? !selectedList.includes(item.value) : false
+    })
+  });
+
+  const list = selectedList.map((key) => {
+    const data = echartDataSource.value.map(item => {
+      const val = item[key];
+      return {
+        time: item.testHour,
+        value: val ? val.toFixed(2) : 0
+      }
+    })
+    return {
+      name: selectOptionsEnum[key],
+      data
+    }
+  })
+
+  const options = getEchartOptions(list);
+
+  leftEchartInstance.setOption(options, true);
+}
+
+const onDatePickerConfirm = (ts) => {
+  datePickerValue.value = ts.map(t => dayjs(t).format('YYYY/MM/DD'));
+  initEchartData();
+}
+
+const initEchartData = async() => {
+  const [ timeBegin, timeEnd ] = datePickerValue.value || [];
+  
+  await controlApi.getLeftEChartData({ timeBegin, timeEnd }).then(({ data }) => {
+    echartDataSource.value = data;
+    handleSelectOptions(selectValue.value);
+  })
+}
+
+const windowResize = () => {
+  leftEchartInstance.resize();
+}
+
+onMounted(async () => {
+
+  await nextTick(() => {
+    leftEchartInstance = echarts.init(document.querySelector('#echartRef'), 'light');
+  });
+
+  await initEchartData();
+
+  window.addEventListener("resize", windowResize);
+})
+
+onUnmounted(() => {
+  window.removeEventListener("resize", windowResize);
+  leftEchartInstance && leftEchartInstance.dispose();
+})
+</script>
+
+<template>
+  <div class="left-echart-container">
+    <div class="header">
+      <BaseTitle title="数据看板" type="second" class="flex-shrink-0"></BaseTitle>
+      <div class="flex items-center space-x-[4px]">
+        <NDatePicker
+          class="w-[250px]"
+          clearable
+          size="small"
+          type="daterange"
+          ref="dateRangeRef"
+          value-format="yyyy/MM/dd"
+          :is-date-disabled="isRangeDateDisabled"
+          :on-confirm="onDatePickerConfirm"
+          :on-clear="onDatePickerClear"
+          v-model:formatted-value="datePickerValue"
+          :theme-overrides="datePickerThemeOverrides"
+        ></NDatePicker>
+        <NSelect
+          class="w-[220px]"
+          multiple
+          :options="selectOptions"
+          :value="selectValue"
+          :on-update:value="handleSelectOptions"
+          :theme-overrides="selectThemeOverrides"
+          size="small"
+          max-tag-count="responsive"
+        />
+      </div>
+    </div>
+    <div class="content">
+      <!-- :style="{ zIndex: show ? 0 : 100 }" -->
+      <div class="echart w-full h-full" id="echartRef"></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>
+
+<style lang="scss" scoped>
+.left-echart-container {
+  width: 50%;
+  height: 100%;
+  padding: 14px;
+  border-radius: 10px;
+  background: rgba(255, 255, 255, 0.60);
+  backdrop-filter: blur(10px);
+  overflow: hidden;
+
+  .header {
+    @include flex(x, center, between);
+    height: 28px;
+  }
+
+  .content {
+    height: calc(100% - 28px);
+    padding-top: 8px;
+
+    .echarts {
+      height: 100%;
+      background: pink;
+      background-clip: padding-box;
+    }
+  }
+}
+</style>

+ 209 - 0
src/views/control/EchartRight.vue

@@ -0,0 +1,209 @@
+<script setup>
+import { ref, onMounted, onUnmounted, nextTick } from "vue";
+import dayjs from "dayjs";
+import * as echarts from 'echarts';
+
+import { controlApi } from "@/api/control";
+
+import BaseTitle from './components/BaseTitle.vue';
+
+let rightEchartInstance = ref(null);
+
+const getEchartOptions = (list) => {
+
+  const series = list.map(({ name, data }) => {
+    const d1 = data.map(({ time, value }) => {
+      return [
+        time,
+        value
+      ]
+    })
+
+    return {
+      name,
+      showSymbol: false,
+      smooth: true,
+      type: 'line',
+      symbolSize: 10,
+      data: d1,
+    }
+  })
+
+  const option = {
+    // legend: {
+    //   x: 'center',
+    //   y: 'top',
+    //   show: true,
+    //   left: '10px',
+    //   top: '16px',
+    //   itemWidth: 6,
+    //   itemGap: 20,
+    //   textStyle: {
+    //     color: '#556677',
+    //   },
+    // },
+    title: {
+      show: !list.length,
+      text: '暂无数据',
+      x: 'center',
+      y: 'center',
+      textStyle: {
+        fontSize: 14,
+        fontWeight: 'normal',
+      }
+    },
+    grid: {
+      top: '60px',
+      bottom: '30px',
+      left: '8%',
+      right: '5%',
+      // containLabel: true 
+    },
+    tooltip: {
+      trigger: 'axis',
+      label: {
+        show: true
+      },
+    },
+    xAxis: {
+      type: 'time',
+      boundaryGap: ['5%', '5%'],
+      axisLine: {
+        show: false
+      },
+      splitLine: {
+        show: false
+      },
+      axisTick: {
+        show: false,
+      },
+      axisLabel: {
+        formatter: function (value) {
+          return dayjs(value).format('MM-DD')
+        }
+      }
+    },
+    yAxis: {
+      axisLine: {
+        show: false
+      },
+      splitLine: {
+        show: true,
+        lineStyle: {
+          type: 'dashed',
+          color: '#E5E5E5'
+        }
+      },
+      axisTick: {
+        show: false
+      },
+      splitArea: {
+        show: false,
+        color: '#fff'
+      },
+      axisLabel: {
+        formatter: function (value) {
+          return value.toFixed(0)
+        }
+      }
+    },
+    series
+  };
+  return option;
+}
+
+const windowEchartResize = () => {
+  rightEchartInstance.resize();
+}
+
+onMounted(async () => {
+
+  await nextTick(() => {
+    rightEchartInstance = echarts.init(document.querySelector('#echartRight'), 'light');
+  });
+
+  controlApi.getRightEChartData({timeBegin: '2025/03/01', timeEnd: '2025/04/22'}).then(({ data }) => {
+    console.log("getRightEChartData", data);
+    const options = getEchartOptions([]);
+    rightEchartInstance.setOption(options, true);
+  })
+
+  window.addEventListener("resize", windowEchartResize);
+})
+
+onUnmounted(() => {
+  window.removeEventListener("resize", windowEchartResize);
+  rightEchartInstance && rightEchartInstance.dispose();
+})
+</script>
+
+<template>
+  <div class="left-echart-container">
+    <ul class="header space-x-[12px]">
+      <li class="legend space-x-[4px]">
+        <span class="circle color-green"></span>
+        <span class="legend-name">智能投药量</span>
+      </li>
+      <li class="legend space-x-[4px]">
+        <span class="circle color-blue"></span>
+        <span class="legend-name">即时加药量</span>
+      </li>
+    </ul>
+
+    <div class="content">
+      <div class="echarts" id="echartRight"></div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.left-echart-container {
+  width: 50%;
+  height: 100%;
+  padding: 14px;
+  border-radius: 10px;
+  background: rgba(255, 255, 255, 0.60);
+  backdrop-filter: blur(10px);
+  overflow: hidden;
+
+  .header {
+    @include flex(x, center, end);
+    height: 28px;
+    
+    .legend {
+      @include flex(x, center, center);
+
+      .circle {
+        width: 8px;
+        height: 8px;
+        border-radius: 50%;
+      }
+
+      .legend-name {
+        color: #1A2029;
+        font-size: 12px;
+        font-weight: 400;
+        line-height: 20px;
+      }
+ 
+      .color-green {
+        background: linear-gradient(90deg, #43FF1E 0.95%, #24FFDB 57.74%);
+      }
+
+      .color-blue {
+        background: linear-gradient(90deg, #1EE7FF 0.95%, #249AFF 90.28%);
+      }
+    }
+  }
+
+  .content {
+    height: calc(100% - 28px);
+    padding-top: 8px;
+
+    .echarts {
+      height: 100%;
+      background-clip: padding-box;
+    }
+  }
+}
+</style>

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

@@ -1,6 +1,6 @@
 <script setup>
 import { ref, onMounted, computed, unref, watch } from 'vue';
-import { NScrollbar, useMessage, NTabs, NTabPane, c } from 'naive-ui';
+import { NScrollbar, useMessage, NTabs, NTabPane, NProgress } from 'naive-ui';
 import { TheChatView } from '@/components';
 import { controlApi } from "@/api/control";
 import BaseTitle from './components/BaseTitle.vue';
@@ -13,372 +13,176 @@ import TheEchartPanel from './components/TheEchartPanel.vue';
 
 import NumberPanel from './NumberPanel.vue';
 import CirclePanel from './CirclePanel.vue';
+import EchartLeft from './EchartLeft.vue'
+import EchartRight from './EchartRight.vue'
+import DrawerSetting from './DrawerSetting.vue';
+import ParamterCard from './ParamterCard.vue';
+import DrawerWarning from './DrawerWarning.vue';
+
+let isProcessing = false;
 
 const message = useMessage();
-const isVisibleBtn = ref(true);
-const isVisibleUpdateInfo = ref(false);
+const processNumber = ref(0);
 const systemStatus = ref(0);
+const configurationStatus = ref(1);
+const medicineAmount = ref(0);
+const tytjTransientLL = ref(0);
 
-const columnData = ref([
-  { label: '后反馈设定', key: 'htfksd', value: '' },
-  { label: '基准系数', key: 'jzxs', value: '' },
-  { label: '修正系数', key: 'xzxs', value: '' },
-  { label: '控制系数', key: 'kzxs', value: '' },
-  { label: '水量分配系数', key: 'slfpxs', value: '' },
-  { label: '碳源当量', key: 'tydl', value: '' },
-  { label: '转换系数', key: 'zhxs', value: '' },
-  { label: '稀释倍数', key: 'sxps', value: '' },
-  { label: '药剂密度', key: 'yymd', value: '' },
-  { label: '最小启动流量', key: 'zxqdll', value: '' },
-  { label: '碳氮比', key: 'tdb', value: '' }
-])
-
-const doseNum = ref(null);
-const flowNum = ref(null);
-const updateNum = ref(null);
-
-const tabKeyEnum = {
-  0: 'auto',
-  1: 'onePool',
-  2: 'twoPool',
-  3: 'worker'
-}
+const paramterDrawerVisible = ref(false);
+const warningDrawerVisible = ref(false);
 
-// water实时数据
-const waterConfigParams = ref({});
+const paramterValue = ref({});
 
-// 基础数据
-const dataSourceParams = ref({
-  auto: {},
-  onePool: {},
-  twoPool: {},
-  worker: {
-    medicineAmount: null
-  }
-})
+const systemSwitchType = computed(() => configurationStatus.value === 0 && systemStatus.value === 1);
 
-// 系数
-const baseSourceParams = ref({
-  numberBeng: 0,
-  type: 0
-})
+// 开始投放
+const handleSystemStatus = () => {
 
-// 当前Tab选中的key
-const tabActiveKey = computed(() => tabKeyEnum[baseSourceParams.value.type]);
-// 计算碳氮比
-const tdbNum = computed(() => {
-  const type = tabActiveKey.value;
+  if ( isProcessing ) return;
 
-  const [ r1, r2, rOne, rTwo] = getTotalNum();
- 
-  const { 
-    jsLlOne, jsLlTwo,
-    jsCodOne, jsCodTwo,
-    jsTnOne, jsTnTwo
-  } = dataSourceParams.value[type];
+  isProcessing = true;
 
-  const {
-    tydl, zhxs
-  } = baseSourceParams.value
- 
-  if ( type === 'auto' || type === 'worker' ) {
-    const rNum = Math.max( r1, r2 );
-    const jsll = rNum == r1 ? jsLlOne: jsLlTwo;
-    const cod = rNum == r1 ? jsCodOne: jsCodTwo;
-    const jsTn = rNum == r1 ? jsTnOne: jsTnTwo;
-    const n = rNum == r1 ? rOne : rOne
-    return Number(((n*1000/jsll*tydl+cod*zhxs*tydl)/jsTn).toFixed(2));
-  }
-  if ( type === 'onePool' ) {
-    return Number(((rOne*1000/jsLlOne*tydl+jsCodOne*zhxs*tydl)/jsTnOne).toFixed(2));
-  }
-  if ( type === 'twoPool' ) {
-    return Number(((rTwo*1000/jsLlTwo*tydl+jsCodTwo*zhxs*tydl)/jsTnTwo).toFixed(2));
+  if ( configurationStatus.value == 1 ) {
+    return message.warning('当前组态未启用,无法投放');
   }
-})
-watch(() => tdbNum.value , tdb => {
-  columnData.value[columnData.value.length - 1].value = tdb;
-})
-
-// 编辑系数 - confirm
-const onEditConfirm = () => {
-  isVisibleBtn.value = true;
-  columnData.value = columnData.value.map(item => {
-    Object.entries(baseSourceParams.value).forEach(([key, value]) => {
-      if (key === item.key) {
-        item.value = value;
-      }
-    })
-    return item;
-  })
 
-  handleMedicateAmount();
-}
-
-// 编辑系数 - 取消
-const onEditCancel = () => {
-  isVisibleBtn.value = true;
-
-  columnData.value.map(({ key, value }) => {
-    baseSourceParams.value[key] = value;
-  })
-}
-
-const onFinalResult = () => {
   const addStatus = systemStatus.value === 0 ? 1 : 0;
 
   controlApi.putSystemStatus({ addStatus });
 
-  systemStatus.value = addStatus;
-
-  message.warning(addStatus === 0 ? '当前投药状态:已停用' : '当前投药状态:投放中');
-
+  operateNumbers(addStatus === 0 ? "decrement" : "increment", () => {
+    systemStatus.value = addStatus;
+    isProcessing = false
+    message.warning(addStatus === 0 ? '当前投药状态:已停用' : '当前投药状态:投放中');
+  })
 }
 
-const onUpdateTab = (index) => {
-  const currentData = dataSourceParams.value[tabKeyEnum[index]];
-  baseSourceParams.value.type = index;
-  if ( !Object.keys(currentData).length ) {
-    isVisibleUpdateInfo.value = false;
-    return;
-  }
-  isVisibleBtn.value = true;
-  handleMedicateAmount();
+// 更新参数设置
+const handleUpdateParams = () => {
+  paramterDrawerVisible.value = false;
+  initData();
 }
 
-function getTotalNum() {
-  const {
-    hycXsyOne = 0, hycXsyTwo = 0,
-    qycAdOne = 0, qycAdTwo = 0,
-    qycYxyOne = 0, qycYxyTwo = 0,
-    jsLlOne = 0, jsLlTwo = 0,
-    jsCodOne = 0, jsCodTwo = 0
-  } = dataSourceParams.value[tabActiveKey.value];
-
-  const {
-    htfksd, xzxs, kzxs, slfpxs, zhxs, tydl, jzxs, yymd, sxps
-  } = baseSourceParams.value;
+const getNumVal = (type, n1, n2, activeIndex) => {
 
-  const rOne1 = (((2*hycXsyOne-htfksd)+((qycAdOne+qycYxyOne)*xzxs-htfksd))*(jzxs-1))*(jsLlOne*slfpxs)/1000;
-  const rOne2 = (rOne1*kzxs-(jsLlOne*slfpxs*jsCodOne*zhxs/1000))/tydl;
-  const rOne3 = rOne2/yymd/1000*sxps
+  if ( type == 0 ) {
+    return Math.max(n1, n2);
+  }
 
-  const rTwo1 = (((2*hycXsyTwo-htfksd)+((qycAdTwo+qycYxyTwo)*xzxs-htfksd))*(jzxs-1))*(jsLlTwo*slfpxs)/1000;
-  const rTwo2 = (rTwo1*kzxs-(jsLlTwo*slfpxs*jsCodTwo*zhxs/1000))/tydl;
-  const rTwo3 = rTwo2/yymd/1000*sxps
+  if ( type == 1 ) {
+    return n1 || n1 == 0 ? Number(n1.toFixed(2)) : '--'
+  }
 
-  const r1 = Number((rOne3 < 0 || !rOne3) ? 0 : rOne3.toFixed(3)) || 0;
-  const r2 = Number((rTwo3 < 0 || !rTwo3) ? 0 : rTwo3.toFixed(3)) || 0;
+  if ( type == 2 ) {
+    return n2 || n2 == 0 ? Number(n2.toFixed(2)) : '--'
+  }
 
-  return [ r1, r2, rOne2, rTwo2 ];
 }
 
-const onConfirmUpdate = async () => {
-  await controlApi.postAddRecord({
-    ...dataSourceParams.value[tabKeyEnum[baseSourceParams.value.type]],
-    ...baseSourceParams.value,
-  });
+const operateNumbers = (operationType, callBack) => {
+  const totalTime = 3000; 
+  const steps = 100; 
+  const intervalTime = totalTime / steps; 
 
-  isVisibleUpdateInfo.value = false;
+  let currentNumber = operationType === 'decrement' ? 100 : 1;
 
-  message.success("系统加药量,更新成功");
+  processNumber.value = currentNumber;
 
-  doseNum.value = updateNum.value;
-}
+  const intervalId = setInterval(() => {
+    operationType === 'increment' ? currentNumber++ : currentNumber--
 
-// 更新投药结果
-const handleMedicateAmount = () => {
-  const type = tabActiveKey.value;
-  const lastNum = unref(updateNum);
-  const tdb = tdbNum.value;
-
-  if ( type === 'worker' ) {
-    const medicineNum = dataSourceParams.value.worker.medicineAmount;
-    if ( medicineNum || medicineNum == 0 ) {
-      updateNum.value = medicineNum;
-      isVisibleUpdateInfo.value = true;
-      baseSourceParams.value.tdb = tdb;
-      message.warning("有新投放方案, 请查看")
-    } else {
-      updateNum.value = null;
-      isVisibleUpdateInfo.value = false;
+    processNumber.value = currentNumber
+
+    if ((operationType === 'increment' && currentNumber > 100) ||
+      (operationType === 'decrement' && currentNumber < 0)) {
+      clearInterval(intervalId); 
+      callBack && callBack();
     }
-    return;
-  }
+  }, intervalTime);
+} 
 
-  const [r1, r2] = getTotalNum();
-  const maxR = Math.max( r1, r2 );
+const initData = async () => {
+  const { data } = await controlApi.getBaseData();
+  const {
+    addType,
 
-  if ( type === 'auto' && maxR !== lastNum) {
-    updateNum.value = maxR;
-    isVisibleUpdateInfo.value = true;
-    baseSourceParams.value.tdb = tdb;
-    message.warning("有新的投放方案, 请查看");
+    numberBeng, type,
+    jsLlOne, jsLlTwo,
+    jsCodOne, jsCodTwo,
+    jsTnOne, jsTnTwo,
+    hycXsyOne, hycXsyTwo,
+
+    qycYxyOne, qycYxyTwo,
+    qycAdOne, qycAdTwo,
+
+    // 系统加药量
+    medicineAmount,
+    // 瞬时加药量
+    tytjTransientLL
+  } = data;
+
+  const numberBengEunm = {
+    0: '1号加药泵',
+    1: '2号加药泵',
+    2: '3号加药泵'
   }
 
-  if ( type === 'onePool' && r1 != lastNum) {
-    updateNum.value = r1;
-    isVisibleUpdateInfo.value = true;
-    baseSourceParams.value.tdb = tdb;
-    message.warning("有新的投放方案, 请查看");
+  const typeEnum = {
+    0: '自动',
+    1: '1号池',
+    2: '2号池',
+    3: '手动'
   }
 
-  if ( type === 'twoPool' && r2 != lastNum ) {
-    updateNum.value = r2;
-    isVisibleUpdateInfo.value = true;
-    baseSourceParams.value.tdb = tdb;
-    message.warning("有新的投放方案, 请查看");
-  }
+  paramterValue.value = {
+    device: `${numberBengEunm[numberBeng]} / ${typeEnum[type]}`,
+    jsll: getNumVal(type, jsLlOne, jsLlTwo) + ' m³/h',
+    cod: getNumVal(type, jsCodOne, jsCodTwo) + ' mg/L',
+    jszd: getNumVal(type, jsTnOne, jsTnTwo) + ' mg/L',
+    hycxsy: getNumVal(type, hycXsyOne, hycXsyTwo) + ' mg/L',
+    hycad: '没有',
+    qycxsy: getNumVal(type, qycYxyOne, qycYxyTwo) + ' mg/L',
+    qycan: getNumVal(type, qycAdOne, qycAdTwo) + ' mg/L',
+  };
+
+  configurationStatus.value = addType;
 
 }
 
 onMounted(async () => {
-  // 获取最后一条记录  getSystemStatus
-  await controlApi.getBaseData().then(({ data }) => {
-    const {
-      numberBeng = 0, type = 0,
-      htfksd, jzxs, xzxs, kzxs, slfpxs, tydl, zhxs, sxps, yymd, zxqdll,
-      medicineAmount,
-      addType,
-      tytjTransientLL
-    } = data;
- 
-    baseSourceParams.value = { 
-      ...baseSourceParams.value,
-      numberBeng, type,
-      htfksd, jzxs, xzxs, kzxs, slfpxs, tydl, zhxs, sxps, yymd, zxqdll,
-      addType
-    };
-    
-    updateNum.value = medicineAmount;
-
-    doseNum.value = medicineAmount;
-
-    flowNum.value = tytjTransientLL;
-
-    dataSourceParams.value[tabActiveKey.value] = data;
-
-  })
-
- 
-  // 获取实时数据
-  controlApi.getNumValue().then(({ data }) => {
-
-    Object.entries(data).forEach(([key, val]) => {
-      data[key] = val;
-    });
-
-    // water实时数据
-    waterConfigParams.value = data;
-
-    // 重新计算碳氮比
-    const tdb = tdbNum.value;
-    baseSourceParams.value.tdb = tdb;
- 
-    columnData.value = columnData.value.map(item => {
-      item.value = item.key === 'tdb' ? tdb : dataSourceParams.value[tabActiveKey.value][item.key];
-      return item;
-    })
-
-    const params = {
-      jsLlType: 1,
-      jsLlOne: data.jslYB,
-      jsLlTwo: data.jslYB,
-      jsCodType: 1,
-      jsCodOne: data.jsCodYB,
-      jsCodTwo: data.jsCodYB,
-      hycXsyType: 1,
-      hycXsyOne: data.hyXsyHYOne,
-      hycXsyTwo: data.hyXsyHYTwo,
-      qycYxyType: 1,
-      qycYxyOne: data.qyXsyHYOne,
-      qycYxyTwo: data.qyXsyHYTwo,
-      qycAdType: 1,
-      qycAdOne: data.qyAdHYOne,
-      qycAdTwo: data.qyAdHYTwo,
-      jsTnType: 1,
-      jsTnOne: data.jsTnYB,
-      jsTnTwo: data.jsTnYB
-    }
-
-    Object.keys(dataSourceParams.value).forEach(key => {
-      const item = dataSourceParams.value[key];
-      if ( !Object.keys(item).length ) {
-        dataSourceParams.value[key] = { ...params };
-      }
-    })
-  })
+  // 初始化
+  await initData();
 
   // 获取是否允许投药开关
-  controlApi.getSystemStatus().then(({ data }) => {
+  await controlApi.getSystemStatus().then(({ data }) => {
     // 0不允许  1允许
     systemStatus.value = data;
   });
 
+  processNumber.value = systemSwitchType.value ? 100 : 0;
 })
-
 </script>
 
 <template>
   <section class="flex items-start h-full">
     <TheChatView leftTitle="智适应碳源投加" :isChatSlot="false" :isFooter="false">
       <template #control>
-        <div class="control-container space-y-[16px]">
+        <div class="control-container">
           <div class="arg-section">
 
             <div class="left-card space-y-[16px]">
               <BaseTitle title="智能投加计算"></BaseTitle>
-              <ul class="paramter-list">
-                <li class="paramter-item">
-                  <span class="label">加药设备</span>
-                  <span class="value">1号加药泵 / 1号池</span>
-                </li>
-                <li class="line"></li>
-                <li class="paramter-item">
-                  <span class="label">进水流量</span>
-                  <span class="value">1号加药泵 / 1号池</span>
-                </li>
-                <li class="line"></li>
-                <li class="paramter-item">
-                  <span class="label">进水COD</span>
-                  <span class="value">1号加药泵 / 1号池</span>
-                </li>
-                <li class="line"></li>
-                <li class="paramter-item">
-                  <span class="label">进水总氮</span>
-                  <span class="value">1号加药泵 / 1号池</span>
-                </li>
-                <li class="line"></li>
-                <li class="paramter-item">
-                  <span class="label">好氧池硝酸盐</span>
-                  <span class="value">1号加药泵 / 1号池</span>
-                </li>
-                <li class="line"></li>
-                <li class="paramter-item">
-                  <span class="label">好氧池氨氮</span>
-                  <span class="value">1号加药泵 / 1号池</span>
-                </li>
-                <li class="line"></li>
-                <li class="paramter-item">
-                  <span class="label">缺氧池硝酸盐</span>
-                  <span class="value">1号加药泵 / 1号池</span>
-                </li>
-                <li class="line"></li>
-                <li class="paramter-item">
-                  <span class="label">缺氧池氨氮</span>
-                  <span class="value">1号加药泵 / 1号池</span>
-                </li>
-              </ul>
+              <ParamterCard :data="paramterValue"></ParamterCard>
             </div>
 
             <div class="right-card">
               <div class="header">
                 <h4 class="title">智能系统参数</h4>
                 <ul class="btn-list space-x-[8px]">
-                  <li class="item">
+                  <li class="item" @click="paramterDrawerVisible = true">
                     <span>参数设置</span>
                   </li>
-                  <li class="item">
+                  <li class="item" @click="warningDrawerVisible = true">
                     <span>系统告警</span>
                     <span class="waring-circle-icon"></span>
                   </li>
@@ -387,38 +191,59 @@ onMounted(async () => {
               <div class="result-content">
                 <div class="number_card space-x-[20px]">
                   <NumberPanel direction="left" title="智能控制系数" :value="123"></NumberPanel>
-                  <CirclePanel></CirclePanel>
+                  <CirclePanel :medicineAmount="medicineAmount" :tytjTransientLL="tytjTransientLL"></CirclePanel>
                   <NumberPanel direction="right" title="硝酸盐智能设定" unit="mg/L" :value="22"></NumberPanel>
                 </div>
                 <div class="progress_card space-y-[8px]">
                   <span class="time">模型更新时间: 2025-04-11 12:11:11</span>
-                  <div class="progress"></div>
-                  <span class="tips">Libra智能投药中...</span>
+                  <div class="progress">
+                    <NProgress
+                      processing
+                      type="line"
+                      :border-radius="0"
+                      :percentage="processNumber"
+                      :show-indicator="false"
+                      :height="10"
+                      :color="'red'"
+                      rail-color="transparent"
+                      class="custom-progress"
+                    ></NProgress>
+                  </div>
+                  <span class="tips">Libra智能{{ systemSwitchType ? "投药中" : "未启用" }}...</span>
                 </div>
                 <div class="play_card">
-                  <div class="play-btn space-x-[12px]">
-                    <span class="icon"></span>
-                    <span>暂停</span>
+                  <div class="play-btn space-x-[6px]" @click="handleSystemStatus">
+                    <span :class="[systemSwitchType ? 'icon_end' : 'icon_start' ]"></span>
+                    <span>{{ systemSwitchType ? "暂停" : "开始" }}</span>
                   </div>
                 </div>
               </div>
             </div>
-
           </div>
-          <div class="echart-section">
-
+          <div class="echart-section space-x-[20px]">
+            <EchartLeft></EchartLeft>
+            <EchartRight></EchartRight>
           </div>
         </div>
       </template>
     </TheChatView>
   </section>
+
+  <!-- 参数设置 - 抽屉 -->
+  <DrawerSetting
+    v-model:show="paramterDrawerVisible"
+    @on-update="handleUpdateParams"
+  ></DrawerSetting>
+
+  <!-- 系统告警 - 抽屉 -->
+  <DrawerWarning
+    v-model:show="warningDrawerVisible"
+  ></DrawerWarning>
 </template>
 
 <style lang="scss" scoped>
 .control-container {
-  // @include flex(x, start, start);
   height: 100%;
-
   .arg-section {
     @include flex(x, start, start);
     height: 57%;
@@ -427,6 +252,7 @@ onMounted(async () => {
     border-radius: 10px;
     background: url(@/assets/images/control/bg-top.png) left center no-repeat;
     background-size: 878px 100% ;
+    overflow: hidden;
 
     .left-card {      
       flex-shrink: 0;  
@@ -434,34 +260,6 @@ onMounted(async () => {
       height: 100%;
       display: flex;
       flex-flow: column;
-
-      .paramter-list {
-        flex: 1;
-        display: flex;
-        justify-content: space-between;
-        flex-flow: column;
-        padding: 14px;
-        border: 1px solid #fff;
-        border-radius: 10px;
-        background: rgba(255, 255, 255, 0.20);
-        backdrop-filter: blur(5px);
-        font-size: 13px;
-        color: #585858;
-        line-height: 22px;
-
-        .line {
-          width: 100%;
-          height: 1px;
-          background: #EEE;
-        }
-        .paramter-item {
-          @include flex(x, center, between);
-          .value {
-            color: #1A2029;
-            font-weight: bold;
-          }
-        }
-      }
     }
 
     .right-card {
@@ -513,72 +311,12 @@ onMounted(async () => {
         @include flex(y, center, around);
         height: calc(100% - 40px);
         padding: 0px 86px 0 86px;
-        // background: #0059FF;
         background-clip: padding-box;
         
         .number_card {
           display: flex;
           align-items: center;
           justify-content: center;
-          
-          .left, .right {
-            text-align: center;
-            .left-animate {
-              background: url("@/assets/images/control/number-card-left.png") center center no-repeat;
-              .number {
-                right: 30px;
-              }
-            }
-            .right-animate {
-              background: url("@/assets/images/control/number-card-right.png") center center no-repeat;
-              .number {
-                @include flex(y, start, center);
-                left: 30px;
-                span {
-                  color: #000;
-                  font-family: "PingFang SC";
-                  font-size: 12px;
-                  font-style: normal;
-                  font-weight: 400;
-                  line-height: normal;
-                }
-              }
-            }
-            .animate-card {
-              position: relative;
-              width: 194px;
-              height: 164px;
-              // background-size: 140px 100%;
-              background-size: cover;
-
-              .number {
-                position: absolute;
-                top: 30px;
-                color: #1A2029;
-                // font-family: "DIN-RegularAlternate";
-                font-family: "D-DIN-PRO-700-Bold";
-                font-size: 52px;
-                font-style: normal;
-                font-weight: 700;
-              }
-            }
-            .sub-title {
-              color: #333;
-              font-family: "PingFang SC";
-              font-size: 12px;
-              font-style: normal;
-              font-weight: 400;
-              line-height: 20px;
-            }
-          }
-
-          .middel {
-            width: 186px;
-            height: 186px;
-            background: url("@/assets/images/control/number-card-middle.svg") center center no-repeat,
-            url("@/assets/images/control/number-card-middle2.svg") center bottom no-repeat;
-            background-size: contain;
-          }
         }
 
         .progress_card {
@@ -608,7 +346,8 @@ onMounted(async () => {
           .progress {
             width: 165px;
             height: 10px;
-            background: red;
+            background: #cce7ec;
+            box-shadow: 0px 5px 5px #ccc;
           }
         }
 
@@ -616,8 +355,6 @@ onMounted(async () => {
           @include flex(x, center, center);
           .play-btn {
             @include flex(x, center, center);
-            // width: 136px;
-            // height: 64px;
             width: 120px;
             height: 48px;
             background: url("@/assets/images/control/bg-play-btn.png") center center no-repeat;
@@ -627,13 +364,20 @@ onMounted(async () => {
             font-weight: 500;
             line-height: 16px;
             cursor: pointer;
-            .icon {
+            user-select: none;
+            .icon_end, .icon_start {
               display: block;
               width: 28px;
               height: 28px;
+            }
+            .icon_end {
               background: url("@/assets/images/control/icon-end.svg") center center no-repeat;
               background-size: cover;
             }
+            .icon_start {
+              background: url("@/assets/images/control/icon-start.svg") center center no-repeat;
+              background-size: cover;
+            }
           }
         }
       }
@@ -641,32 +385,38 @@ onMounted(async () => {
   }
 
   .echart-section {
+    @include flex(x, start, start);
     height: 43%;
-    background: red;
+    padding-top: 16px;
   }
 
 }
-
-
-
 </style>
 
 <style lang="scss">
-.custom-tab_item {
-  @include flex (x, center, center);
-  height: 35px;
-  border-radius: 4px;
-  background: #F3F5FA;
-
-  &.n-tabs-tab--active {
-    transition: none !important;
-    border-radius: 4px;
-    transition: none !important;
-    background: url('https://static.fuxicarbon.com/bigModel/pc/tab-border-item-2x.png') -3px 0px no-repeat, linear-gradient(180deg, #F1F3FE 0%, #FFF 100%);
-    background-size: 107% 100%;
+.progress {
+  .custom-progress {
+    .n-progress-graph-line-fill {
+      background: linear-gradient(to right, rgba(0, 101, 253, 1), rgba(0, 239, 234, 1)) !important;
+    }
   }
 }
 
+// .custom-tab_item {
+//   @include flex (x, center, center);
+//   height: 35px;
+//   border-radius: 4px;
+//   background: #F3F5FA;
+
+//   &.n-tabs-tab--active {
+//     transition: none !important;
+//     border-radius: 4px;
+//     transition: none !important;
+//     background: url('https://static.fuxicarbon.com/bigModel/pc/tab-border-item-2x.png') -3px 0px no-repeat, linear-gradient(180deg, #F1F3FE 0%, #FFF 100%);
+//     background-size: 107% 100%;
+//   }
+// }
+
 .control-container .left-section {
   .n-tabs-nav-scroll-content {
     padding-bottom: 10px;

+ 5 - 4
src/views/control/NumberPanel.vue

@@ -51,6 +51,7 @@ const props = defineProps({
       font-size: 52px;
       font-style: normal;
       font-weight: 700;
+      line-height: 52px;
     }
   }
 
@@ -71,7 +72,7 @@ const props = defineProps({
     background-size: auto auto, auto auto, auto auto, 100% 110%;
 
     .number {
-      top: 30px;
+      top: 50px;
       right: 0;
     }
 
@@ -84,17 +85,17 @@ const props = defineProps({
     background: url("@/assets/images/control/line-right-1.svg") right center no-repeat,
     url("@/assets/images/control/line-right-2.svg") left bottom no-repeat,
     url("@/assets/images/control/line-right-3.svg") left 158px no-repeat,
-    url("@/assets/images/control/line-right-4.png") 10px -4px no-repeat;
+    url("@/assets/images/control/line-right-4.png") -10px -4px no-repeat;
     background-size: auto auto, auto auto, auto auto, 100% 110%;
 
     .number {
-      top: 30px;
+      top: 50px;
       left: 0;
     }
     
     .unit {
       position: absolute;
-      top: 96px;
+      top: 105px;
       left: 4px;
       color: #000;
       font-size: 12px;

+ 86 - 0
src/views/control/ParamterCard.vue

@@ -0,0 +1,86 @@
+<script setup>
+defineProps({
+  data: {
+    type: Object,
+    default: () => {}
+  }
+})
+</script>
+
+<template>
+  <ul class="paramter-list">
+    <li class="paramter-item">
+      <span class="label">加药设备</span>
+      <span class="value">{{ data.device }}</span>
+    </li>
+    <li class="line"></li>
+    <li class="paramter-item">
+      <span class="label">进水流量</span>
+      <span class="value">{{ data.jsll }}</span>
+    </li>
+    <li class="line"></li>
+    <li class="paramter-item">
+      <span class="label">进水COD</span>
+      <span class="value">{{ data.cod }}</span>
+    </li>
+    <li class="line"></li>
+    <li class="paramter-item">
+      <span class="label">进水总氮</span>
+      <span class="value">{{ data.jszd }}</span>
+    </li>
+    <li class="line"></li>
+    <li class="paramter-item">
+      <span class="label">好氧池硝酸盐</span>
+      <span class="value">{{ data.hycxsy }}</span>
+    </li>
+    <li class="line"></li>
+    <li class="paramter-item">
+      <span class="label">好氧池氨氮</span>
+      <span class="value">{{ data.hycad }}</span>
+    </li>
+    <li class="line"></li>
+    <li class="paramter-item">
+      <span class="label">缺氧池硝酸盐</span>
+      <span class="value">{{ data.qycxsy }}</span>
+    </li>
+    <li class="line"></li>
+    <li class="paramter-item">
+      <span class="label">缺氧池氨氮</span>
+      <span class="value">{{ data.qycan }}</span>
+    </li>
+  </ul>
+</template>
+
+<style lang="scss" scoped>
+.paramter-list {
+  flex: 1;
+  display: flex;
+  justify-content: space-between;
+  flex-flow: column;
+  padding: 14px;
+  border: 1px solid #fff;
+  border-radius: 10px;
+  background: rgba(255, 255, 255, 0.20);
+  backdrop-filter: blur(5px);
+  font-size: 13px;
+  color: #585858;
+  line-height: 22px;
+
+  .line {
+    width: 100%;
+    height: 1px;
+    background: #EEE;
+  }
+
+  .paramter-item {
+    @include flex(x, center, between);
+
+    .value {
+      font-size: 13px;
+      font-family: "D-DIN-PRO-700-Bold";
+      color: #1A2029;
+      font-weight: bold;
+    }
+  }
+}
+</style>

+ 14 - 1
src/views/control/components/BaseButton.vue

@@ -7,6 +7,10 @@ defineProps({
   isActive: {
     type: Boolean,
     default: false
+  },
+  disabled: {
+    type: Boolean,
+    default: false
   }
 })
 
@@ -21,8 +25,10 @@ const emitClickEvent = () => emit('on-click');
     :class="[
       'custom-button',
       'button-type_' + type, 
-      {'type_active': isActive}
+      {'type_active': isActive},
+      {'btn_disabled': disabled}
     ]"
+    :disabled="disabled"
     @click="emitClickEvent"
   >
     <slot></slot>
@@ -70,4 +76,11 @@ const emitClickEvent = () => emit('on-click');
     background: #EBF0FF;
   }
 }
+
+.btn_disabled {
+  border: 1px solid #D3D7DD;
+  background: #F5F5F5;
+  color: #D3D7DD;
+  cursor: not-allowed;
+}
 </style>

+ 5 - 1
src/views/control/components/BaseCard.vue

@@ -1,6 +1,7 @@
 <script setup>
 import { NTooltip } from 'naive-ui';
 import { SvgIcon } from '@/components';
+
 defineProps({
   title: {
     type: String,
@@ -35,7 +36,7 @@ defineProps({
 
 <style lang="scss" scoped>
 .base-card_view {
-  margin-bottom: 24px;
+  margin-bottom: 27px;
 
   .title-wrapper {
     @include flex(x, center, between);
@@ -44,11 +45,14 @@ defineProps({
     .title {
       @include flex(x, center, start);
       height: 30px;
+      width: 100%;
+      padding: 0 9px;
       line-height: 30px;
       border-radius: 4px;
       font-size: 14px;
       font-weight: bold;
       color: #1A2029;
+      background: linear-gradient(90deg, #F6F7F9 0%, #FFF 100%);
     }
   }
 

+ 35 - 25
src/views/control/components/BaseChooseItem.vue

@@ -1,6 +1,6 @@
 <script setup>
-import { ref, computed, watch, watchEffect } from 'vue';
-import { useMessage } from 'naive-ui';
+import { ref, computed, watchEffect } from 'vue';
+import { useMessage, NTooltip } from 'naive-ui';
 
 import BaseButton from './BaseButton.vue';
 import BaseInput from './BaseInput.vue';
@@ -37,20 +37,20 @@ const props = defineProps({
   tabKey: {
     type: String,
     requured: true
+  },
+  min: {
+    type: Number,
+    default: 0
+  },
+  max: {
+    type: Number,
+    default: 999999
   }
 })
 
 watchEffect(() => {
   if ( modelType.value === 0 ) {
-    if ( props.tabKey == 'auto' ) {
-      inpVal.value = modelValue1.value;
-    }
-    if ( props.tabKey == 'onePool' ) {
-      inpVal.value = modelValue1.value;
-    }
-    if ( props.tabKey == 'twoPool' ) {
-      inpVal.value = modelValue1.value;
-    }
+    inpVal.value = modelValue1.value;
   }
 })
 
@@ -99,28 +99,38 @@ const changeActive = (item, index) => {
     <span class="label-inner">{{ title }}:</span>
     <div class="choose-inner">
       <div class="top-box">
-        <ul class="btn-group space-x-[4px]">
-          <BaseButton
-            v-for="item, index in btnGroup"
-            type="info"
-            :key="index"
-            :isActive="modelType === index || btnGroup.length === 1"
-            @click="changeActive(item, index)"
-          >
-            {{ item.label }}
-          </BaseButton>
-        </ul>
+        <div class="btn-group space-x-[4px]">
+          <div v-for="item, index in btnGroup">
+            <n-tooltip :disabled="!item.disabled">
+              <template #trigger>
+                <div>
+                  <BaseButton
+                    type="info"
+                    :key="index"
+                    :isActive="modelType === index || btnGroup.length === 1"
+                    :disabled="item.disabled"
+                    @click="changeActive(item, index)"
+                  >
+                    {{ item.label }}
+                  </BaseButton>
+                </div>
+              </template>
+              <span class="text-[12px]">该功能未开启</span>
+            </n-tooltip>
+          </div>
+        </div>
         <ul class="num-group flex space-x-[20px] text-center">
           <li class="w-[60px]">{{ onePoolNum }}</li>
           <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"
@@ -136,8 +146,8 @@ const changeActive = (item, index) => {
   @include flex(x, start, start);
 
   .label-inner {
-    width: 160px;
-    flex-shrink: 1;
+    width: 100px;
+    flex-shrink: 0;
     line-height: 28px;
   }
 

+ 30 - 20
src/views/control/components/BaseInput.vue

@@ -55,6 +55,15 @@ const domClassName = computed(() => {
   ]
 })
 
+const inputThemeOverrides = {
+  peers: {
+    Input: {
+      border: '1px solid #d7d9e5',
+      borderRadius: '2px',
+    }
+  }
+}
+
 const onFocus = () => {
   isFocusStatus.value = true;
 }
@@ -79,13 +88,13 @@ const handleInpValue = (event, type) => {
     emit('click:cancel', null);
   }
 }
+
 </script>
 
 <template>
   <div :class="[domClassName, 'base-input-wrapper']">
     <NInputNumber
       size="small"
-      round
       style="width: 100%;"
       :readonly="readonly"
       :precision="2"
@@ -97,6 +106,7 @@ const handleInpValue = (event, type) => {
       :on-blur="onBlur"
       :on-focus="onFocus"
       :value="modelValue"
+      :theme-overrides="inputThemeOverrides" 
     >
       <template #suffix>
         <div class="unit" v-if="unit">{{ unit }}</div>
@@ -180,23 +190,23 @@ const handleInpValue = (event, type) => {
 </style>
 
 <style lang="scss">
-.base-input-wrapper {
-  .n-input-wrapper {
-    padding-right: 0px;
-    border: 1px solid #E6EAEE;
-    border-radius: 4px;
-    .n-input__input-el, .n-input__placeholder {
-      font-size: 12px;
-    }
-  }
-}
-
-.input_text_center {
-  .n-input-wrapper {
-    .n-input__input-el {
-      padding-right: 10px;
-      text-align: center;
-    }
-  }
-}
+// .base-input-wrapper {
+//   .n-input-wrapper {
+//     padding-right: 0px;
+//     border: 1px solid #E6EAEE;
+//     border-radius: 4px;
+//     .n-input__input-el, .n-input__placeholder {
+//       font-size: 12px;
+//     }
+//   }
+// }
+
+// .input_text_center {
+//   .n-input-wrapper {
+//     .n-input__input-el {
+//       padding-right: 10px;
+//       text-align: center;
+//     }
+//   }
+// }
 </style>

+ 144 - 0
src/views/control/config/echart.js

@@ -0,0 +1,144 @@
+export 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,
+      }
+  })
+
+  if ( selectValue.value.includes('22') ) {
+    const data = xAxis.map(time => [time, props.htfksdOne]);
+    series.push({
+      name: '北池-后反馈设定',
+      showSymbol: false,
+      smooth: true,
+      type: 'line',
+      symbolSize: 10,
+      lineStyle: {
+        color: 'red'
+      },
+      itemStyle: {
+        color: 'red'
+      },
+      data
+    });
+  }
+
+  if (selectValue.value.includes('23')) {
+    const data = xAxis.map(time => [time, props.htfksdTwo]);
+    series.push({
+      name: '南池-后反馈设定',
+      showSymbol: false,
+      smooth: true,
+      type: 'line',
+      symbolSize: 10,
+      lineStyle: {
+        color: 'red'
+      },
+      itemStyle: {
+        color: 'red'
+      },
+      data
+    });
+  }
+
+  const option = {
+    backgroundColor: '#FFF',
+    legend: {
+      x: 'center',
+      y: 'top',
+      show: true,
+      left: '10px',
+      top: '16px',
+      itemWidth: 6,
+      itemGap: 20,
+      textStyle: {
+        color: '#556677',
+      },
+      // data: ['直接登录平台', '扫码登录平台', '总'],
+    },
+    title: {
+      show: !xAxis.length,
+      text: '暂无数据',
+      x: 'center',
+      y: 'center',
+      textStyle: {
+        fontSize: 14,
+        fontWeight: 'normal',
+      }
+    },
+    grid: {
+      top: '60px',
+      bottom: '50px',
+      left: '5%',
+      right: '5%',
+    },
+    tooltip: {
+      trigger: 'axis',
+      label: {
+        show: true
+      },
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: ['5%', '5%'],
+      axisLine: {
+        show: false
+      },
+      splitLine: {
+        show: false
+      },
+      axisTick: {
+        show: false,
+        // alignWithLabel: true
+      },
+      axisLabel: {
+        // margin: 10,
+        // showMaxLabel: true,
+        // rotate: 1,
+        formatter: function (value) {
+          return dayjs(value).format('YYYY/MM/DD')
+        }
+      },
+      // interval: 0, // 0 表示强制显示所有标签,'auto' 表示自动间隔
+    },
+    yAxis: {
+      axisLine: {
+        show: false
+      },
+      splitLine: {
+        show: true,
+        lineStyle: {
+          type: 'dashed',
+          color: '#E5E5E5'
+        }
+      },
+      axisTick: {
+        show: false
+      },
+      splitArea: {
+        show: false,
+        color: '#fff'
+      },
+      axisLabel: {
+        formatter: function (value) {
+          return value.toFixed(0)
+        }
+      }
+    },
+    series
+  };
+  return option;
+}