sunxiao 7 місяців тому
батько
коміт
0a60787ef4

+ 13 - 0
src/api/control.js

@@ -0,0 +1,13 @@
+import http from "@/utils/request";
+
+export const controlApi = {
+  /**
+   * 碳源投加 获取echart记录 - 数据看板
+   */
+  getEchartData: (type) => http.get(`/front/bigModel/smartAdd/charList/${type}`),
+  
+  /**
+   * 碳源投加 基础数值数据
+   */
+  getNumValue: () => http.get(`/front/bigModel/smartAdd/dataInfo`),
+}

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

@@ -76,7 +76,7 @@ const menuOptions = [
       {
         label: '智能投药',
         icon: renderChildrenIcon({ name: 'menu-cost-drug' }),
-        key: 'medicate',
+        key: 'medicinal',
       },
       {
         label: '精准曝气',

+ 3 - 3
src/router/index.js

@@ -117,9 +117,9 @@ const constantRouterMap = [
         }
       },
       {
-        path: 'medicate',
-        name: 'MedicateView',
-        component: () => import('@/views/control/MedicateView.vue'),
+        path: 'medicinal',
+        name: 'MedicinalView',
+        component: () => import('@/views/control/MedicinalView.vue'),
         meta: {
           title: '智能投药'
         }

+ 5 - 0
src/utils/tools.js

@@ -143,3 +143,8 @@ export const debounce = (func, wait) => {
   };
 }
 
+export const objectCopy = (obj) => {
+  const newObj = {};
+  Object.entries(obj).map(([key, value]) => newObj[key] = value);
+  return newObj;
+}

+ 0 - 326
src/views/control/MedicateView.vue

@@ -1,326 +0,0 @@
-<script setup>
-import { ref } from 'vue';
-import { NScrollbar } from 'naive-ui';
-import { TheChatView, SvgIcon } from '@/components';
-
-import BaseButton from './components/BaseButton.vue';
-import BaseTitle from './components/BaseTitle.vue';
-import BaseRadioCard from './components/BaseRadioCard.vue';
-import BaseCard from './components/BaseCard.vue';
-import BaseRadioGroup from './components/BaseRadioGroup.vue';
-import BaseChooseItem from './components/BaseChooseItem.vue';
-import BaseInput from './components/BaseInput.vue';
-
-import TheResultCard from './components/TheResultCard.vue';
-import EchartPanel from './components/EchartPanel.vue';
-
-const isVisibleBtn = ref(true);
-
-</script>
-
-<template>
-  <section class="flex items-start h-full">
-    <TheChatView leftTitle="智适应碳源投加" :isChatSlot="false" :isFooter="false">
-      <template #control>
-        <div class="control-container space-x-[12px]">
-          <div class="left-section">
-
-            <BaseTitle title="智能投加计算">
-              <template #right>
-                <BaseButton>重置</BaseButton>
-                <BaseButton type="gradual">投加计算</BaseButton>
-              </template>
-            </BaseTitle>
-
-            <n-scrollbar class="scrollbar" style="height: 100%;">
-              <div class="form-content">
-                <BaseCard title="选择加药泵">
-                  <BaseRadioCard></BaseRadioCard>
-                </BaseCard>
-
-                <BaseCard title="选择运行方式">
-                  <BaseRadioGroup :data="['自动', '手动']"></BaseRadioGroup>
-                </BaseCard>
-
-                <BaseCard title="选择池组手自动方式">
-                  <BaseRadioGroup :data="['自动', '手动']"></BaseRadioGroup>
-                </BaseCard>
-
-                <BaseCard title="确定智适应碳源设置">
-                  <BaseRadioGroup :data="['1号池', '2号池']"></BaseRadioGroup>
-                </BaseCard>
-                
-                <BaseCard title="设定数据来源及取数方式">
-                  <div class="space-y-[12px]">
-                    <BaseChooseItem
-                      title="进水流量"
-                      :btn-group="[
-                        { label: '手动', key: 'hand' }
-                      ]"
-                      unit="m³"
-                    ></BaseChooseItem>
-  
-                    <BaseChooseItem
-                      title="进水COD"
-                      :btn-group="[
-                        { label: '手动', key: 'hand' },
-                        { label: '仪表', key: 'laboratory' },
-                        { label: '化验', key: 'assay' },
-                      ]"
-                      unit="mg/L"
-                    ></BaseChooseItem>
-  
-                    <BaseChooseItem
-                      title="好氧池硝酸盐"
-                      :btn-group="[
-                        { label: '手动', key: 'hand' },
-                        { label: '化验', key: 'forecast' },
-                        { label: '预测', key: 'forecast' },
-                      ]"
-                      unit="mg/L"
-                    ></BaseChooseItem>
-  
-                    <BaseChooseItem
-                      title="缺氧池硝酸盐"
-                      :btn-group="[
-                        { label: '手动', key: 'hand' },
-                        { label: '化验', key: 'forecast' },
-                      ]"
-                      unit="mg/L"
-                    ></BaseChooseItem>
-  
-                    <BaseChooseItem
-                      title="缺氧池氨氮"
-                      :btn-group="[
-                        { label: '手动', key: 'hand' },
-                        { label: '化验', key: 'forecast' },
-                      ]"
-                      unit="mg/L"
-                    ></BaseChooseItem>
-  
-                    <BaseChooseItem
-                      title="进水总氮"
-                      :btn-group="[
-                        { label: '手动', key: 'hand' },
-                        { label: '仪表', key: 'laboratory' }
-                      ]"
-                      unit="mg/L"
-                    ></BaseChooseItem>
-                  </div>
-                </BaseCard>
-
-                <BaseCard title="设定数据来源及取数方式" style="margin: 0" tips="建议使用默认值,非必要不修改">
-                  <template #titleRight>
-                    <div>
-                      <div
-                        class="flex items-center space-x-[4px] cursor-pointer"
-                        v-show="isVisibleBtn"
-                        @click="isVisibleBtn = false
-                      ">
-                        <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-                          <path d="M2.33337 14H14.3334" stroke="#2454FF" stroke-linecap="round" stroke-linejoin="round" />
-                          <path d="M3.66663 8.90663V11.3333H6.10569L13 4.43603L10.565 2L3.66663 8.90663Z" stroke="#2454FF"
-                            stroke-linejoin="round" />
-                        </svg>
-                        <span style="color: #2454FF">编辑</span>
-                      </div>
-                      <ul class="flex items-center text-[13px] space-x-[8px] cursor-pointer" v-show="!isVisibleBtn">
-                        <li class="cursor-pointer">确定</li>
-                        <li class="cursor-pointer text-[#B0B7C0]" @click="isVisibleBtn = true">取消</li>
-                      </ul>
-                    </div>
-                  </template>
-                  <ul class="data-source-list space-y-[12px]">
-                    <li class="data-soruce-item">
-                      <span>后反馈设定:</span>
-                      <span class="unit" v-show="isVisibleBtn">12.23 mg/L</span>
-                      <BaseInput unit="mg/L" size='small' :isNeedFlotBtn="false" v-show="!isVisibleBtn"></BaseInput>
-                    </li>
-                    <li class="data-soruce-item">
-                      <span>基准系数:</span>
-                      <span class="unit" v-show="isVisibleBtn">12.23</span>
-                      <BaseInput size='small' type="text" :isNeedFlotBtn="false" v-show="!isVisibleBtn"></BaseInput>
-                    </li>
-                    <li class="data-soruce-item">
-                      <span>修正系数:</span>
-                      <span class="unit" v-show="isVisibleBtn">12.23</span>
-                      <BaseInput size='small' type="text" :isNeedFlotBtn="false" v-show="!isVisibleBtn"></BaseInput>
-                    </li>
-                    <li class="data-soruce-item">
-                      <span>控制系数:</span>
-                      <span class="unit" v-show="isVisibleBtn">12.23</span>
-                      <BaseInput size='small' type="text" :isNeedFlotBtn="false" v-show="!isVisibleBtn"></BaseInput>
-                    </li>
-                    <li class="data-soruce-item">
-                      <span>水量分配系数:</span>
-                      <span class="unit" v-show="isVisibleBtn">12.23</span>
-                      <BaseInput size='small' type="text" :isNeedFlotBtn="false" v-show="!isVisibleBtn"></BaseInput>
-                    </li>
-                    <li class="data-soruce-item">
-                      <span>碳源当量:</span>
-                      <span class="unit" v-show="isVisibleBtn">12.23</span>
-                      <BaseInput size='small' type="text" :isNeedFlotBtn="false" v-show="!isVisibleBtn"></BaseInput>
-                    </li>
-                    <li class="data-soruce-item">
-                      <span>转换系数:</span>
-                      <span class="unit" v-show="isVisibleBtn">12.23</span>
-                      <BaseInput size='small' type="text" :isNeedFlotBtn="false" v-show="!isVisibleBtn"></BaseInput>
-                    </li>
-                    <li class="data-soruce-item">
-                      <span>稀释配属:</span>
-                      <span class="unit" v-show="isVisibleBtn">12.23</span>
-                      <BaseInput size='small' type="text" :isNeedFlotBtn="false" v-show="!isVisibleBtn"></BaseInput>
-                    </li>
-                    <li class="data-soruce-item">
-                      <span>药剂密度:</span>
-                      <span class="unit" v-show="isVisibleBtn">12.23</span>
-                      <BaseInput size='small' type="text" :isNeedFlotBtn="false" v-show="!isVisibleBtn"></BaseInput>
-                    </li>
-                    <li class="data-soruce-item">
-                      <span>最小启动流量:</span>
-                      <span class="unit" v-show="isVisibleBtn">12.23</span>
-                      <BaseInput size='small' type="text" :isNeedFlotBtn="false" v-show="!isVisibleBtn"></BaseInput>
-                    </li>
-                    <li class="data-soruce-item">
-                      <span>碳氮比:</span>
-                      <span class="unit" v-show="isVisibleBtn">12.23</span>
-                      <BaseInput size='small' type="text" :isNeedFlotBtn="false" v-show="!isVisibleBtn"></BaseInput>
-                    </li>
-                  </ul>
-                </BaseCard>
-              </div>
-            </n-scrollbar>
-          </div>
-          <div class="right-section">
-            <TheResultCard></TheResultCard>
-            <EchartPanel></EchartPanel>
-          </div>
-        </div>
-      </template>
-    </TheChatView>
-  </section>
-</template>
-
-<style lang="scss" scoped>
-.control-container {
-  @include flex(x, start, start);
-  height: 100%;
-
-  .left-section {
-    display: flex;
-    flex-flow: column;
-    width: 400px;
-    height: 100%;
-    border-radius: 10px;
-    background: #fff;
-
-    .scrollbar {
-      height: 100%;
-    }
-
-    .form-content {
-      padding: 24px 16px;
-    }
-  }
-
-  .data-source-list {
-    .data-soruce-item {
-      @include flex(x, center, between);
-      .inp-inner {
-        width: 112px;
-      }
-      .unit {
-        font-family: "D-DIN-PRO-700-Bold";
-        font-weight: bold;
-        font-size: 14px;
-        color: #333;
-      }
-    }
-  }
-}
-
-
-.right-section {
-  display: flex;
-  flex-flow: column;
-  width: 100%;
-  height: 100%;
-
-  .top {
-    flex-shrink: 1;
-    height: 214px;
-    border-radius: 8px;
-    border: 0.5px solid #FFF;
-    background: linear-gradient(90deg, #E0E8FC 0%, #F2F4FF 100%);
-  }
-
-  .bottom {
-    height: 100%;
-    background: pink;
-  }
-
-}
-
-
-// 通用区域的样式
-
-.btn {
-  width: 80px;
-  height: 32px;
-  border-radius: 4px;
-  border: 1px solid #D3D7DD;
-  text-align: center;
-  font-size: 14px;
-  line-height: 32px;
-  color: #1A2029;
-}
-
-.btn-primary {
-  border: 0;
-  background: var(--Linear, linear-gradient(270deg, #3BD6E3 0%, #019AFE 100%));
-  font-weight: bold;
-  color: #fff;
-}
-
-.btn-info {
-  width: 44px;
-  height: 28px;
-  border-radius: 4px;
-  border: 1px solid #D3D7DD;
-  background: #fff;
-  font-size: 12px;
-  text-align: center;
-  line-height: 28px;
-  color: #1A2029;
-  cursor: pointer;
-}
-
-.btn-info_active {
-  color: #2454FF;
-  border: 1px solid #2454FF;
-  background: #EBF0FF;
-}
-
-.radio {
-  display: block;
-  width: 12px;
-  height: 12px;
-  border-radius: 100%;
-  border: 1px solid #ccc;
-  cursor: pointer;
-}
-
-.radio_big {
-  width: 16px;
-  height: 16px;
-}
-
-.radio-active {
-  transition: all .1s;
-  border: 3px solid #2454FF;
-}
-
-.radio_big.radio-active {
-  border: 4px solid #2454FF;
-}
-</style>

+ 461 - 0
src/views/control/MedicinalView.vue

@@ -0,0 +1,461 @@
+<script setup>
+import { ref, onMounted, watch } from 'vue';
+import { NScrollbar, useMessage } from 'naive-ui';
+import { objectCopy } from '@/utils/tools';
+import { TheChatView } from '@/components';
+
+import { controlApi } from "@/api/control";
+
+import BaseButton from './components/BaseButton.vue';
+import BaseTitle from './components/BaseTitle.vue';
+import BaseRadioCard from './components/BaseRadioCard.vue';
+import BaseCard from './components/BaseCard.vue';
+import BaseRadioGroup from './components/BaseRadioGroup.vue';
+import BaseChooseItem from './components/BaseChooseItem.vue';
+import BaseInput from './components/BaseInput.vue';
+
+import TheResultPanel from './components/TheResultPanel.vue';
+import TheEchartPanel from './components/TheEchartPanel.vue';
+
+const message = useMessage();
+const isVisibleBtn = ref(true);
+const dataSource = ref({});
+const chooseItemRef = ref([]);
+
+const columnData = [
+  { label: '后反馈设定',    key: 'hfksd' },
+  { label: '基准系数',      key: 'jzxs' },
+  { label: '修正系数',      key: 'xzxs' },
+  { label: '控制系数',      key: 'kzxs' },
+  { label: '水量分配系数',  key: 'sffpxs' },
+  { label: '碳源当量',      key: 'tydl' },
+  { label: '转换系数',      key: 'zhxs' },
+  { label: '稀释配属',      key: 'xsbs' },
+  { label: '药剂密度',      key: 'yjmd' },
+  { label: '最小启动流量',   key: 'zxqdll' },
+  { label: '碳氮比',        key: 'tdb' }
+]
+
+// 基础参数 - 按钮选择
+const paramData = ref({
+  pump: 0,           // 加药泵
+  running: 0,        // 运行方式
+  pond:0,            // 池组手自动方式
+  setting: 0,        // 智适应碳源设置 1# 2#
+  jslYB: null,        // 进水流量
+  jscod: null,       // 进水COD
+  hycxsy: null,      // 好氧池硝酸盐
+  qycxsy: null,      // 缺氧池硝酸盐
+  qycad: null,       // 缺氧池氨氮
+  jszd: null         // 进水总氮
+})
+
+// 基础系数 - input输入
+const factorData = ref({
+  hfksd: 12.00,   // 后反馈设置定
+  jzxs: 3.00,     // 基准系数
+  xzxs: 1.00,     // 修正系数
+  kzxs: 5.20,     // 控制系数
+  sffpxs: 1.00,   // 水量分配系数
+  tydl: 0.90,     // 碳源当量
+  zhxs: 0.90,     // 转换系数
+  xsbs: 1.00,     // 稀释倍数
+  yjmd: 1.00,     // 药剂密度
+  zxqdll: 0.02,   // 最小启动流量
+  tdb: 3.54       // 碳氮比
+})
+
+const originParamData = objectCopy(paramData.value);
+const originFactorData = objectCopy(factorData.value);
+
+const factorInpData = ref(objectCopy(factorData.value));
+
+const doseNum = ref(null);
+const flowNum = ref(null);
+
+watch(() => paramData.value.setting, () => {
+  handelReset("变化了");
+});
+
+// 重置
+const handelReset = () => {
+
+  if ( !isVisibleBtn.value ) {
+    return message.warning("设定参数系数的值未保存")
+  }
+
+  chooseItemRef.value.forEach(item => item.resetInpVal() );
+
+  paramData.value = objectCopy({...originParamData, setting: paramData.value.setting});
+  factorData.value = objectCopy(originFactorData);
+  factorInpData.value = objectCopy(originFactorData);
+
+  doseNum.value = '';
+  flowNum.value = '';
+}
+
+// 计算最终结果
+const handleResult = () => {
+
+  if ( !isVisibleBtn.value ) {
+    return message.warning("设定参数系数的值未保存")
+  }
+
+  const codeSetEnum = {
+    jslYB: '进水流量',
+    jscod: '进水COD',
+    hycxsy: '好氧池硝酸盐',
+    qycxsy: '缺氧池硝酸盐',
+    qycad: '缺氧池氨氮',
+    jszd: '进水总氮',
+
+    hfksd: '后反馈设置',
+    jzxs: '基准系数',
+    xzxs: '修正系数',
+    kzxs: '控制系数',
+    sffpxs: '水量分配系数',
+    tydl: '碳源当量',
+    zhxs: '转换系数',
+    xsbs: '稀释倍数',
+    yjmd: '药剂密度',
+    zxqdll: '最小启动流量',
+    tdb: '碳氮比'
+  }
+  const whitelist = [ 'pump', 'running', 'pond', 'setting' ];
+  const mergeData = { ...paramData.value, ...factorData.value };
+  const keyList = Object.keys(mergeData);
+  let isErrorItem = null;
+
+  for (let i = 0; i < keyList.length; i++) {
+    const key = keyList[i];
+    const val = mergeData[key]
+    
+    if (!val && !whitelist.includes(key)) {
+      isErrorItem = { key, val, label: codeSetEnum[key]  }
+      break
+    }
+  }
+
+  if ( isErrorItem ) {
+    return message.warning(`${isErrorItem.label}未填写`)
+  }
+
+  const stepOne = ((( 2 * paramData.value.hycxsy - factorData.value.hfksd)+((paramData.value.qycad + paramData.value.qycxsy) * factorData.value.xzxs - factorData.value.hfksd)) * (factorData.value.jzxs - 1)) * (paramData.value.jslYB * factorData.value.sffpxs) / 1000
+
+  const setpTwo = ( stepOne * factorData.value.kzxs - ( paramData.value.jslYB * factorData.value.sffpxs * paramData.value.jscod * factorData.value.zhxs / 1000)) / factorData.value.tydl
+
+  const setpThree = setpTwo / factorData.value.yjmd / 1000 * factorData.value.xsbs
+
+  doseNum.value = setpThree.toFixed(3);
+  flowNum.value = paramData.value.jslYB;
+}
+
+const onEditConfirm = () => {
+  const keys = Object.keys(factorInpData.value);
+  let isError = false;
+
+  for (let i = 0; i < keys.length; i++) {
+    const key = keys[i];
+    const val = factorInpData.value[key];
+    if ( !val || val === Infinity) {
+      message.warning("数据来源填写有误,请检查")
+      isError = true;
+      break;
+    }
+  }
+
+  if ( isError ) return;
+
+  isVisibleBtn.value = true;
+  factorData.value = JSON.parse(JSON.stringify(factorInpData.value));
+}
+
+const onEditCancel = () => {
+  isVisibleBtn.value = true;
+  factorInpData.value = JSON.parse(JSON.stringify(factorData.value));
+}
+
+onMounted(async () => {
+  const { data } = await controlApi.getNumValue();
+  let result = {};
+  Object.entries(data).forEach(([key, val]) => {
+    result[key] = val ? Number(val.toFixed(2)) : val;
+  });
+  dataSource.value = result;
+})
+
+</script>
+
+<template>
+  <section class="flex items-start h-full">
+    <TheChatView leftTitle="智适应碳源投加" :isChatSlot="false" :isFooter="false">
+      <template #control>
+        <div class="control-container space-x-[12px]">
+          <div class="left-section">
+
+            <BaseTitle title="智能投加计算">
+              <template #right>
+                <BaseButton @click="handelReset">重置</BaseButton>
+                <BaseButton type="gradual" @on-click="handleResult">投加计算</BaseButton>
+              </template>
+            </BaseTitle>
+
+            <n-scrollbar class="scrollbar" style="height: 100%;">
+              <div class="form-content">
+                <BaseCard title="选择加药泵">
+                  <BaseRadioCard v-model="paramData.pump"></BaseRadioCard>
+                </BaseCard>
+
+                <BaseCard title="选择运行方式">
+                  <BaseRadioGroup :data="['自动', '手动']" v-model="paramData.running"></BaseRadioGroup>
+                </BaseCard>
+
+                <BaseCard title="选择池组手自动方式">
+                  <BaseRadioGroup :data="['自动', '手动']" v-model="paramData.pond"></BaseRadioGroup>
+                </BaseCard>
+
+                <BaseCard title="确定智适应碳源设置">
+                  <BaseRadioGroup :data="['1号池', '2号池']" v-model="paramData.setting"></BaseRadioGroup>
+                </BaseCard>
+                
+                <BaseCard :title="paramData.setting == 0  ? '1号池设定数据来源' : '2号池设定数据来源'">
+                  <div class="space-y-[12px]">
+                    <BaseChooseItem
+                      title="进水流量"
+                      v-model="paramData.jslYB"
+                      :btn-group="[
+                        { label: '手动', key: 'hand', value: '' },
+                        { label: '仪表', key: 'laboratory', value: dataSource.jslYB },
+                      ]"
+                      unit="m³"
+                      :ref="el=> chooseItemRef[0] = el"
+                    ></BaseChooseItem>
+
+                    <BaseChooseItem
+                      title="进水COD"
+                      v-model="paramData.jscod"
+                      :btn-group="[
+                        { label: '手动', key: 'hand' },
+                        { label: '仪表', key: 'laboratory', value: dataSource.jsCodYB },
+                        { label: '化验', key: 'assay', value: dataSource.jsCodHY },
+                      ]"
+                      unit="mg/L"
+                      :ref="el=> chooseItemRef[1] = el"
+                    ></BaseChooseItem>
+  
+                    <BaseChooseItem
+                      title="好氧池硝酸盐"
+                      v-model="paramData.hycxsy"
+                      :btn-group="[
+                        { label: '手动', key: 'hand', value: '' },
+                        { label: '化验', key: 'forecast', value: dataSource[paramData.setting === 0 ? 'hyXsyHYOne' : 'hyXsyHYTwo'] },
+                        { label: '预测', key: 'forecast', value: dataSource[paramData.setting === 0 ? 'hyXsyYCOne' : 'hyXsyYCTwo'] },
+                      ]"
+                      unit="mg/L"
+                      :ref="el=> chooseItemRef[2] = el"
+                    ></BaseChooseItem>
+  
+                    <BaseChooseItem
+                      title="缺氧池硝酸盐"
+                      v-model="paramData.qycxsy"
+                      :btn-group="[
+                        { label: '手动', key: 'hand', value: ''},
+                        { label: '化验', key: 'forecast', value: dataSource[paramData.setting === 0 ? 'qyXsyHYOne' : 'qyXsyHYTwo'] },
+                      ]"
+                      unit="mg/L"
+                      :ref="el=> chooseItemRef[3] = el"
+                    ></BaseChooseItem>
+  
+                    <BaseChooseItem
+                      title="缺氧池氨氮"
+                      v-model="paramData.qycad"
+                      :btn-group="[
+                        { label: '手动', key: 'hand', value:'' },
+                        { label: '化验', key: 'forecast', value: dataSource[paramData.setting === 0 ? 'qyAdHYOne' : 'qyAdHYTwo'] },
+                      ]"
+                      unit="mg/L"
+                      :ref="el=> chooseItemRef[4] = el"
+                    ></BaseChooseItem>
+  
+                    <BaseChooseItem
+                      title="进水总氮"
+                      v-model="paramData.jszd"
+                      :btn-group="[
+                        { label: '手动', key: 'hand', value: '' },
+                        { label: '仪表', key: 'laboratory', value: dataSource.jsTnYB}
+                      ]"
+                      unit="mg/L"
+                      :ref="el=> chooseItemRef[5] = el"
+                    ></BaseChooseItem>
+                  </div>
+                </BaseCard>
+
+                <BaseCard title="设定参数系数" style="margin: 0" tips="建议使用默认值,非必要不修改">
+                  <template #titleRight>
+                    <div>
+                      <div
+                        class="flex items-center space-x-[4px] cursor-pointer text-[#2454FF] text-[13px]"
+                        v-show="isVisibleBtn"
+                        @click="isVisibleBtn = false
+                      ">
+                        <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+                          <path d="M2.33337 14H14.3334" stroke="#2454FF" stroke-linecap="round" stroke-linejoin="round" />
+                          <path d="M3.66663 8.90663V11.3333H6.10569L13 4.43603L10.565 2L3.66663 8.90663Z" stroke="#2454FF"
+                            stroke-linejoin="round" />
+                        </svg>
+                        <span>编辑</span>
+                      </div>
+                      <ul class="flex items-center text-[13px] space-x-[8px] cursor-pointer" v-show="!isVisibleBtn">
+                        <li class="cursor-pointer" @click="onEditConfirm" style="color: #2454FF">确定</li>
+                        <li class="cursor-pointer text-[#B0B7C0]" @click="onEditCancel">取消</li>
+                      </ul>
+                    </div>
+                  </template>
+                  <ul class="data-source-list space-y-[12px]">
+                    <li class="data-soruce-item" v-for="item, index in columnData">
+                      <span>{{ item.label }}:</span>
+                      <span class="unit" v-show="isVisibleBtn">
+                        {{ factorData[item.key].toFixed(2) }}
+                        {{ index === 0 ? 'mg/L' : '' }}
+                      </span>
+                      <div style="width: 140px;" v-show="!isVisibleBtn">
+                        <BaseInput :unit="index === 0 ? 'mg/L' : '' " size='small' :isNeedFlotBtn="false"  v-model="factorInpData[item.key]" isCenter placeholder=""></BaseInput>
+                      </div>
+                    </li>
+                  </ul>
+                </BaseCard>
+              </div>
+            </n-scrollbar>
+          </div>
+          <div class="right-section">
+            <TheResultPanel :doseNum="doseNum" :flowNum="flowNum"></TheResultPanel>
+            <TheEchartPanel></TheEchartPanel>
+          </div>
+        </div>
+      </template>
+    </TheChatView>
+  </section>
+</template>
+
+<style lang="scss" scoped>
+.control-container {
+  @include flex(x, start, start);
+  height: 100%;
+
+  .left-section {
+    display: flex;
+    flex-flow: column;
+    width: 400px;
+    height: 100%;
+    border-radius: 10px;
+    background: #fff;
+
+    .scrollbar {
+      height: 100%;
+    }
+
+    .form-content {
+      padding: 24px 16px;
+    }
+  }
+
+  .data-source-list {
+    .data-soruce-item {
+      @include flex(x, center, between);
+      .inp-inner {
+        width: 112px;
+      }
+      .unit {
+        font-family: "D-DIN-PRO-700-Bold";
+        font-weight: bold;
+        font-size: 14px;
+        color: #333;
+      }
+    }
+  }
+}
+
+.right-section {
+  display: flex;
+  flex-flow: column;
+  width: 100%;
+  height: 100%;
+
+  .top {
+    flex-shrink: 1;
+    height: 214px;
+    border-radius: 8px;
+    border: 0.5px solid #FFF;
+    background: linear-gradient(90deg, #E0E8FC 0%, #F2F4FF 100%);
+  }
+
+  .bottom {
+    height: 100%;
+    background: pink;
+  }
+
+}
+
+
+// 通用区域的样式
+
+.btn {
+  width: 80px;
+  height: 32px;
+  border-radius: 4px;
+  border: 1px solid #D3D7DD;
+  text-align: center;
+  font-size: 14px;
+  line-height: 32px;
+  color: #1A2029;
+}
+
+.btn-primary {
+  border: 0;
+  background: var(--Linear, linear-gradient(270deg, #3BD6E3 0%, #019AFE 100%));
+  font-weight: bold;
+  color: #fff;
+}
+
+.btn-info {
+  width: 44px;
+  height: 28px;
+  border-radius: 4px;
+  border: 1px solid #D3D7DD;
+  background: #fff;
+  font-size: 12px;
+  text-align: center;
+  line-height: 28px;
+  color: #1A2029;
+  cursor: pointer;
+}
+
+.btn-info_active {
+  color: #2454FF;
+  border: 1px solid #2454FF;
+  background: #EBF0FF;
+}
+
+.radio {
+  display: block;
+  width: 12px;
+  height: 12px;
+  border-radius: 100%;
+  border: 1px solid #ccc;
+  cursor: pointer;
+}
+
+.radio_big {
+  width: 16px;
+  height: 16px;
+}
+
+.radio-active {
+  transition: all .1s;
+  border: 3px solid #2454FF;
+}
+
+.radio_big.radio-active {
+  border: 4px solid #2454FF;
+}
+</style>

+ 77 - 26
src/views/control/components/BaseChooseItem.vue

@@ -1,16 +1,23 @@
 <script setup>
-import { ref } from 'vue';
+import { ref, computed, unref } from 'vue';
+import { useMessage } from 'naive-ui';
 
 import BaseButton from './BaseButton.vue';
 import BaseInput from './BaseInput.vue';
 
-defineProps({
+const message = useMessage();
+
+const activeIndex = ref(-1);
+const inpVal = ref();
+const modelValue = defineModel();
+
+const props = defineProps({
   title: {
     type: String,
     default: ''
   },
   btnGroup: {
-    type: Boolean,
+    type: Array,
     default: false
   },
   unit: {
@@ -19,11 +26,50 @@ defineProps({
   }
 })
 
-const activeIndex = ref(0);
+const currentNumValue = computed(() => {
+  const curIndex = props.btnGroup.length === 1 ? 0 : unref(activeIndex.value);
+  return curIndex < 0
+    ? "" : curIndex === 0
+      ? modelValue.value: props.btnGroup[curIndex].value
+});
+
+const onInpCancel = (val) => {
+  inpVal.value = val;
+}
+
+const onInpConfirm = (num) => {
+  if (num === Infinity || num === -Infinity) {
+    modelValue.value = null
+    return message.warning(`${props.title}的数值填写有误, 请检查`);
+  }
+  modelValue.value = num;
+}
 
-const changeActive = (index) => {
+const onInput = (val) => {
+  inpVal.value = val
+}
+
+const onBlur = () => {
+  if (inpVal.value === Infinity || inpVal.value === -Infinity) {
+    modelValue.value = null;
+    inpVal.value = null;
+    return message.warning(`${props.title}的数值填写有误, 请检查`);
+  }
+}
+
+const changeActive = (item, index) => {
   activeIndex.value = index;
+  modelValue.value = index != 0 ? item.value : (inpVal.value || null);
 }
+
+const resetInpVal = () => {
+  inpVal.value = null;
+  activeIndex.value = -1;
+}
+
+defineExpose({
+  resetInpVal
+})
 </script>
 
 <template>
@@ -32,19 +78,24 @@ const changeActive = (index) => {
     <div class="choose-inner">
       <div class="top-box">
         <ul class="btn-group space-x-[4px]">
-          <BaseButton
-            type="info"
-            :key="item.key"
-            :isActive="activeIndex === index"
-            v-for="item, index in btnGroup"
-            @click="changeActive(index)"
-          >
+          <BaseButton type="info" :key="item.key" :isActive="activeIndex === index || btnGroup.length === 1" v-for="item, index in btnGroup"
+            @click="changeActive(item, index)">
             {{ item.label }}
           </BaseButton>
         </ul>
-        <span class="unit">4018.00 {{ unit }}</span>
+        <span class="unit">{{ currentNumValue }} {{ unit }}</span>
       </div>
-      <BaseInput v-show="!activeIndex" :unit="unit"></BaseInput>
+      <BaseInput
+        v-show="!activeIndex || btnGroup.length === 1"
+        default-value=""
+        :placeholder="'请输入' + props.title"
+        :unit="unit"
+        v-model="modelValue"
+        @click:confirm="onInpConfirm"
+        @click:cancel="onInpCancel"
+        @on-input="onInput"
+        @on-blur="onBlur"
+      ></BaseInput>
     </div>
   </div>
 </template>
@@ -58,16 +109,17 @@ const changeActive = (index) => {
     flex-shrink: 1;
     line-height: 28px;
   }
+
   .choose-inner {
     width: 100%;
-  
+
     .top-box {
       @include flex(x, center, between);
-  
+
       .btn-group {
         @include flex(x, center, center);
       }
-  
+
       .unit {
         font-family: "D-DIN-PRO-700-Bold";
         font-weight: bold;
@@ -75,12 +127,12 @@ const changeActive = (index) => {
         color: #333;
       }
     }
-  
+
     .bottom-box {
       position: relative;
       @include flex(x, center, between);
       margin-top: 4px;
-  
+
       .inp {
         width: 100%;
         height: 28px;
@@ -90,12 +142,12 @@ const changeActive = (index) => {
         background: #fff;
         outline: none;
         font-size: 12px;
-  
+
         &:focus {
           border: 1px solid #2454FF;
         }
       }
-  
+
       .unit {
         flex-shrink: 1;
         width: 46px;
@@ -109,27 +161,27 @@ const changeActive = (index) => {
         font-size: 12px;
         font-weight: bold;
       }
-  
+
       .inp-flot_group {
         position: absolute;
         @include flex(x, center, center);
         right: 50px;
         top: 50%;
         transform: translateY(-50%);
-  
+
         li {
           width: 16px;
           height: 16px;
           border-radius: 100%;
           color: #DFE2E6;
           cursor: pointer;
-  
+
           svg,
           svg path {
             fill: #e0e2e6;
             stroke: #e0e2e6;
           }
-  
+
           &:hover svg {
             fill: #b3c4e3;
             stroke: #b3c4e3;
@@ -139,5 +191,4 @@ const changeActive = (index) => {
     }
   }
 }
-
 </style>

+ 62 - 29
src/views/control/components/BaseInput.vue

@@ -1,8 +1,13 @@
 <script setup>
 import { ref, computed } from 'vue';
+import { NInputNumber } from 'naive-ui';
 import { SvgIcon } from '@/components';
 
 const props = defineProps({
+  placeholder: {
+    type: String,
+    default: () => ""
+  },
   size: {
     type: String,
     default: ''
@@ -22,17 +27,22 @@ const props = defineProps({
   isNeedFlotBtn: {
     type: Boolean,
     default: true
+  },
+  isCenter: {
+    type: Boolean,
+    default: false
   }
 })
 
-const inpRef = ref(null);
-const inpValue = ref('');
 const isFocusStatus = ref(false);
-const activeIndex = ref(0);
+
+const emit = defineEmits(['click:confirm', 'on-input', 'on-blur', 'click:cancel']);
+const modelValue = defineModel();
 
 const domClassName = computed(() => {
   return [
     'input_wrapper',
+    {'input_text_center': props.isCenter },
     'input-' + props.type + "_" + props.size,
     'inpit_' + props.size
   ]
@@ -42,36 +52,50 @@ const onFocus = () => {
   isFocusStatus.value = true;
 }
 
-const onBlur = () => {
+const onBlur = (ev) => {
   isFocusStatus.value = false;
+  emit('on-blur');
+}
+
+const onInput = (value) => {
+  emit('on-input', value);
+  modelValue.value = value;
 }
 
 const handleInpValue = (event, type) => {
-  if ( type === 'confirm' ) {
-    
+  if (type === 'confirm') {
+    emit('click:confirm', modelValue.value);
   }
-  if ( type === 'cancel' ) {
+  if (type === 'cancel') {
     event.preventDefault();
-    inpValue.value = '';
+    modelValue.value = null;
+    emit('click:cancel', null);
   }
 }
+
 </script>
 
 <template>
-  <div :class="domClassName">
-    <input
-      ref="inpRef"
-      v-model="inpValue"
-      type="text"
-      class="inp"
-      @focus="onFocus"
-      @blur="onBlur"
+  <div :class="[domClassName, 'base-input-wrapper']">
+    <NInputNumber
+      size="small"
+      round
+      style="width: 100%;"
+      :placeholder="placeholder"
+      :on-blur="onBlur"
+      :on-focus="onFocus"
+      :on-update:value="onInput"
+      :show-button="false"
+      :value="modelValue"
     >
-    <div class="unit" v-if="unit">{{ unit }}</div>
+      <template #suffix>
+        <div class="unit" v-if="unit">{{ unit }}</div>
+      </template>
+    </NInputNumber>
     <ul class="inp-flot_group space-x-[4px]" v-show="isFocusStatus && isNeedFlotBtn">
-      <li>
-        <SvgIcon name="control-icon-confirm" size="16" @click="handleInpValue($event, 'confirm')"></SvgIcon>
-      </li>
+      <!-- <li>
+        <SvgIcon name="control-icon-confirm" size="16" @mousedown="handleInpValue($event, 'confirm')"></SvgIcon>
+      </li> -->
       <li>
         <SvgIcon name="control-icon-cancel" size="16" @mousedown="handleInpValue($event, 'cancel')"></SvgIcon>
       </li>
@@ -79,6 +103,7 @@ const handleInpValue = (event, type) => {
   </div>
 </template>
 
+
 <style lang="scss" scoped>
 .input_wrapper {
   @include flex(x, center, between);
@@ -117,7 +142,7 @@ const handleInpValue = (event, type) => {
   .inp-flot_group {
     @include flex(x, center, center);
     position: absolute;
-    right: 50px;
+    right: 66px;
     top: 50%;
     transform: translateY(-50%);
 
@@ -141,18 +166,26 @@ const handleInpValue = (event, type) => {
     }
   }
 }
+</style>
 
-.inpit_small, .input-text_small {
-  width: 112px;
-  .inp {
-    text-align: center;
-    padding: 0 10px;
+<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_small {
-  .inp {
-    border-radius: 4px;
+.input_text_center {
+  .n-input-wrapper {
+    .n-input__input-el {
+      padding-right: 10px;
+      text-align: center;
+    }
   }
 }
 </style>

+ 4 - 2
src/views/control/components/BaseRadioCard.vue

@@ -2,6 +2,8 @@
 import { ref } from 'vue';
 import { SvgIcon } from '@/components';
 
+const modelValue = defineModel();
+
 const defaultData = [
   { label: '1号加药泵', key: 'first' },
   { label: '2号加药泵', key: 'second' },
@@ -10,12 +12,12 @@ const defaultData = [
 
 const activeIndex = ref(0);
 
-const chageActive = (index) => activeIndex.value = index;
+const chageActive = (index) => modelValue.value = index;
 </script>
 
 <template>
   <ul class="radio-card_group space-x-[10px]">
-    <li :class="['radio-card_item', { 'card_item_active': index === activeIndex }]" v-for="item, index in defaultData"
+    <li :class="['radio-card_item', { 'card_item_active': index === modelValue }]" v-for="item, index in defaultData"
       :key="item.key" @click="chageActive(index)">
       <div class="radio-wrapper">
         <SvgIcon name="control-icon-pump" size="16" fillColor="#2454FF" />

+ 4 - 5
src/views/control/components/BaseRadioGroup.vue

@@ -1,18 +1,17 @@
 <script setup>
 import { ref } from 'vue';
 
-defineProps({
+const props = defineProps({
   data: {
     type: Array,
     default: () => []
   }
 })
 
-const activeIndex = ref(0);
+const modelValue = defineModel();
 
 const changeActive = (index) => {
-  activeIndex.value = index;
-  console.log(index);
+  modelValue.value = index;
 }
 
 </script>
@@ -24,7 +23,7 @@ const changeActive = (index) => {
         :class="[
           'radio',
           'radio_big',
-          {'radio-active': activeIndex === index}
+          {'radio-active': modelValue === index}
         ]"
         
       ></span>

+ 6 - 2
src/views/control/components/BaseTitle.vue

@@ -1,6 +1,10 @@
 <script setup>
 
 defineProps({
+  title: {
+    type: String,
+    default: ''
+  },
   type: {
     type: String,
     default: 'first'
@@ -19,7 +23,7 @@ defineProps({
       <svg xmlns="http://www.w3.org/2000/svg" width="12" height="24" viewBox="0 0 12 24" fill="none" v-else>
         <path d="M7.74366 10.4333C8.43696 9.03333 8.78361 7.76667 8.78361 6.6V6.1C8.78361 5.06667 8.58552 4.26667 8.16459 3.7C7.74366 3.13333 7.19892 2.83333 6.48086 2.83333C5.7628 2.83333 5.21807 3.1 4.8219 3.66667C4.42573 4.23333 4.22764 5.1 4.22764 6.23333V7.16667H1.94966V6.1C1.94966 4.26667 2.37059 2.8 3.18769 1.66667C4.02956 0.566667 5.14379 0 6.53039 0C7.42177 0 8.23887 0.266667 8.93217 0.8C9.62547 1.3 10.1702 2.03333 10.5416 2.96667C10.9378 3.9 11.1111 4.93333 11.1111 6.03333V6.6C11.1111 7.6 10.9873 8.5 10.7149 9.4C10.4673 10.2667 10.0959 11.1667 9.57595 12.1L2.94652 21.1667H9.16145V24H0V21.6L7.74366 10.4333Z" fill="#2454FF"/>
       </svg>
-      <span class="text">智能投加计算</span>
+      <span class="text">{{ title }}</span>
     </div>
     <div class="btn-group space-x-[8px]">
       <slot name="right"/>
@@ -44,7 +48,7 @@ defineProps({
     .text {
       height: 100%;
       padding-left: 13px;
-      margin-left: -3px;
+      margin-left: -6px;
       background: linear-gradient(90deg, rgba(36, 84, 255, 0.10) -0.94%, rgba(36, 84, 255, 0.00) 95.3%);
       font-size: 15px;
       font-weight: bold;

+ 0 - 161
src/views/control/components/EchartPanel.vue

@@ -1,161 +0,0 @@
-<script setup>
-import { ref, unref, computed, onMounted } from 'vue';
-import { NTabs, NTab, NSelect } from "naive-ui";
-import * as echarts from 'echarts';
-const selectValue = ref('water');
-
-const echartRef = ref(null);
-
-const options = [
-  {
-    label: "信义污水厂信义污水厂",
-    value: 'water',
-    style: "font-size: 12px"
-  },
-  {
-    label: "数据看板数据看板",
-    value: 'water1',
-    style: "font-size: 12px"
-  }
-]
-
-onMounted(() => {
-  const option = {
-    xAxis: {
-      type: 'category',
-      data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
-    },
-    yAxis: {
-      type: 'value'
-    },
-    series: [
-      {
-        data: [150, 230, 224, 218, 135, 147, 260],
-        type: 'line'
-      }
-    ]
-  };
-
-  const echart = echarts.init(echartRef.value, 'light');
-
-  echart.setOption(option);
-})
-</script>
-
-<template>
-  <div class="echart-panel-wrapper">
-    <div class="title">
-      <div class="left-inner">
-        <span class="text">数据看板</span>
-        <n-tabs type="segment" animated size="small" class="tabs">
-          <n-tab name="在线仪表"></n-tab>
-          <n-tab name="连续检测"></n-tab>
-          <n-tab name="预测"></n-tab>
-        </n-tabs>
-      </div>
-      <div class="right-inner">
-        <NSelect v-model:value="selectValue" class="w-[180px]" size="small" :options="options"
-          :consistent-menu-width="false" />
-      </div>
-    </div>
-    <div class="echart-wrapper">
-      <div class="echart" ref="echartRef">echart</div>
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-.echart-panel-wrapper {
-  display: flex;
-  flex-flow: column;
-  height: 100%;
-  padding: 25px 16px 0 25px;
-  margin-top: 12px;
-  border-radius: 10px;
-  background: #FFF;
-
-  .title {
-    flex-shrink: 0;
-    @include flex(x, center, between);
-
-    .left-inner {
-      @include flex(x, center, start);
-
-      .text {
-        color: #1A2029;
-        font-size: 15px;
-        font-style: normal;
-        font-weight: 500;
-        line-height: 24px;
-      }
-
-      .tabs {
-        width: 240px;
-        margin-left: 16px;
-      }
-    }
-
-    .right-inner {
-      @include flex(x, center, start);
-    }
-  }
-
-  .echart-wrapper {
-    height: 100%;
-    padding-top: 16px;
-
-    .echart {
-      width: 100%;
-      height: 100%;
-    }
-  }
-}
-</style>
-
-<style lang="scss">
-.echart-panel-wrapper {
-  .tabs {
-    .n-tabs-tab--active {
-      .n-tabs-tab__label {
-        color: #2454FF;
-      }
-    }
-
-    .n-tabs-tab__label {
-      font-size: 12px;
-      color: #333333;
-    }
-  }
-
-  .right-inner {
-    .n-base-selection__border {
-      border: 0;
-    }
-
-    .n-base-selection-input__content {
-      font-size: 12px;
-      color: #1A2029;
-    }
-
-    .n-base-selection-label {
-      border: 0;
-      border-radius: 0;
-      background: #F2F3F5 !important;
-    }
-
-    .n-base-selection__state-border,
-    .n-base-selection__border {
-      border: 0 !important;
-    }
-
-    .n-base-selection .n-base-selection__border,
-    .n-base-selection .n-base-selection__state-border {
-      box-shadow: none;
-    }
-
-    .n-base-suffix__arrow {
-      color: #4E5969;
-    }
-  }
-}
-</style>

+ 285 - 0
src/views/control/components/TheEchartPanel.vue

@@ -0,0 +1,285 @@
+<script setup>
+import { ref, watch, computed, onMounted, unref, onUnmounted } from 'vue';
+import { NTabs, NTab, NSelect } from "naive-ui";
+import * as echarts from 'echarts';
+import { controlApi } from "@/api/control"
+
+let echart = null;
+const tabs = ref([]);
+const tabActive = ref(null);
+const selectValue = ref(0);
+const echartDataSource = ref({});
+const echartRef = ref(null);
+const isEmpty = ref(false);
+
+const selectOptions = [
+  { label: "进水流量", value: 0, style: "font-size: 12px" },
+  { label: "#1好氧池硝酸盐", value: 1, style: "font-size: 12px" },
+  { label: "#2好氧池硝酸盐", value: 2, style: "font-size: 12px" },
+  { label: "#1缺氧池氨氮", value: 3, style: "font-size: 12px" },
+  { label: "#2缺氧池氨氮", value: 4, style: "font-size: 12px" },
+  { label: "进水COD", value: 5, style: "font-size: 12px" },
+  { label: "进水总氮", value: 6, style: "font-size: 12px" },
+  { label: "碳源投加量", value: 7, style: "font-size: 12px" }
+]
+
+const seriesName = computed(() => {
+  return selectOptions.find(({ value }) => selectValue.value === value).label
+})
+
+const windowResize = () => echart.resize();
+
+const getEchartOptions = (data) => {
+  const option = {
+    backgroundColor: '#FFF',
+    grid: {
+      top: '20px',
+      bottom: '50px',
+      left: '8%',
+      right: '20px'
+    },
+    tooltip: {
+      trigger: 'axis',
+      label: {
+        show: true
+      },
+    },
+    xAxis: {
+      boundaryGap: false,
+      axisLine: {
+        show: false
+      },
+      splitLine: {
+        show: false
+      },
+      axisTick: {
+        show: false,
+        alignWithLabel: true
+      },
+      data: data.map(({ time }) => time)
+    },
+    yAxis: {
+      axisLine: {
+        show: false
+      },
+      splitLine: {
+        show: true,
+        lineStyle: {
+          type: 'dashed',
+          color: '#E5E5E5'
+        }
+      },
+      axisTick: {
+        show: false
+      },
+      splitArea: {
+        show: false,
+        color: '#fff'
+      }
+    },
+    series: [
+      {
+        name: seriesName.value,
+        showSymbol: false,
+        smooth: true,
+        type: 'line',
+        symbolSize: 10,
+        lineStyle: {
+          color: '#17a6fa',
+          shadowBlur: 12,
+          shadowColor: 'rgba(0, 0, 0, 0.12)',
+          shadowOffsetX: 0,
+          shadowOffsetY: 4,
+          width: 2,
+        },
+        itemStyle: {
+          color: '#4080FF',
+          borderWidth: 3,
+          borderColor: '#4080FF'
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+            offset: 0,
+            color: 'rgba(0, 136, 212, 0.2)'
+          }, {
+            offset: 1,
+            color: 'rgba(0, 136, 212, 0)'
+          }], false),
+        },
+        data: data.map(({ val }) => val)
+      }
+    ]
+  };
+  return option;
+}
+
+const createEchart = (data) => {
+  echart.setOption(getEchartOptions(data));
+}
+
+const onSwitchEchart = (item) => {
+  const echartData = echartDataSource.value[item.value];
+  isEmpty.value = !!echartData.length
+  createEchart(echartData);
+}
+
+const initEchartData = async () => {
+  const { data: echartData } = await controlApi.getEchartData(unref(selectValue));
+
+  const enumSource = {
+    YB: '在线仪表',
+    HY: '连续检测',
+    YC: '预测'
+  };
+
+  tabs.value = Object.keys(echartData).map((key, index) => {
+    if (index === 0) {
+      tabActive.value = key + '-' + selectValue.value + '-' + index;
+    }
+    if ( echartData[key].length ) {
+      return ({ label: enumSource[key], value: key });
+    }
+  }).filter(Boolean);
+
+  echartDataSource.value = echartData;
+
+  onSwitchEchart({ value: tabActive.value.substring(0, tabActive.value.indexOf('-')) });
+}
+
+watch(selectValue, initEchartData)
+
+onMounted(async () => {
+
+  initEchartData();
+
+  echart = echarts.init(echartRef.value, 'light');
+
+  window.addEventListener("resize", windowResize);
+})
+
+onUnmounted(() => {
+  window.removeEventListener("resize", windowResize);
+  echart && echart.dispose();
+})
+</script>
+
+<template>
+  <div class="echart-panel-wrapper">
+    <div class="title">
+      <div class="left-inner">
+        <span class="text">数据看板</span>
+        <n-tabs type="segment" animated size="small" class="tabs" v-model:value="tabActive">
+          <n-tab v-for="item, index in tabs" :key="item.value" :name="item.value + '-' + selectValue + '-' + index"
+            @click="onSwitchEchart(item)">{{ item.label }}</n-tab>
+        </n-tabs>
+      </div>
+      <div class="right-inner">
+        <NSelect v-model:value="selectValue" class="w-[180px]" size="small" :options="selectOptions"
+          :consistent-menu-width="false" />
+      </div>
+    </div>
+    <div class="echart-wrapper">
+      <div class="echart" ref="echartRef">echart</div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.echart-panel-wrapper {
+  display: flex;
+  flex-flow: column;
+  height: 100%;
+  padding: 25px 16px 0 25px;
+  margin-top: 12px;
+  border-radius: 10px;
+  background: #FFF;
+
+  .title {
+    flex-shrink: 0;
+    @include flex(x, center, between);
+
+    .left-inner {
+      @include flex(x, center, start);
+
+      .text {
+        color: #1A2029;
+        font-size: 15px;
+        font-style: normal;
+        font-weight: 500;
+        line-height: 24px;
+      }
+
+      .tabs {
+        width: 240px;
+        margin-left: 16px;
+      }
+    }
+
+    .right-inner {
+      @include flex(x, center, start);
+    }
+  }
+
+  .echart-wrapper {
+    height: 100%;
+    padding-top: 16px;
+
+    .echart, .empty {
+      width: 100%;
+      height: 100%;
+    }
+
+    .empty {
+      @include flex(x, center, center);
+    }
+  }
+}
+</style>
+
+<style lang="scss">
+.echart-panel-wrapper {
+  .tabs {
+    .n-tabs-tab--active {
+      .n-tabs-tab__label {
+        color: #2454FF;
+      }
+    }
+
+    .n-tabs-tab__label {
+      font-size: 12px;
+      color: #333333;
+    }
+  }
+
+  .right-inner {
+    .n-base-selection__border {
+      border: 0;
+    }
+
+    .n-base-selection-input__content {
+      font-size: 12px;
+      color: #1A2029;
+    }
+
+    .n-base-selection-label {
+      border: 0;
+      border-radius: 0;
+      background: #F2F3F5 !important;
+    }
+
+    .n-base-selection__state-border,
+    .n-base-selection__border {
+      border: 0 !important;
+    }
+
+    .n-base-selection .n-base-selection__border,
+    .n-base-selection .n-base-selection__state-border {
+      box-shadow: none;
+    }
+
+    .n-base-suffix__arrow {
+      color: #4E5969;
+    }
+  }
+}
+</style>

+ 28 - 8
src/views/control/components/TheResultCard.vue → src/views/control/components/TheResultPanel.vue

@@ -1,27 +1,43 @@
 <script setup>
 import BaseTitle from './BaseTitle.vue';
-import { TheChatView, SvgIcon } from '@/components';
+import { SvgIcon } from '@/components';
+
+const emit = defineEmits(['on-click']);
+const props = defineProps({
+  flowNum: {
+    type: Number,
+    default: 0
+  },
+  doseNum: {
+    type: Number,
+    default: ''
+  }
+});
+
+const emitEvent = () => emit('on-click');
 </script>
 
 <template>
   <div class="result-card_view">
-    <BaseTitle title="智能投加计算" type="second"></BaseTitle>
+    <BaseTitle title="智能投加计算结果" type="second"></BaseTitle>
     <div class="content">
       <div class="reult-list space-y-[4px]">
-        <div class="title">请您完成左侧「1.智能投加计算」</div>
-        <!-- <div class="desc">核算完成后为您生成建议碳源投加量</div> -->
-        <ul class="text">
+        <div class="title">
+          {{ !doseNum ? '请您完成左侧「1.智能投加计算」' : '根据投加设定,计算结果如下:' }}
+          </div>
+        <div class="desc" v-show="!doseNum">核算完成后为您生成建议碳源投加量</div>
+        <ul class="text" v-show="doseNum">
           <li>
             <span>仪表瞬时流量:</span>
-            <i>2827 m³</i>
+            <i>{{ flowNum }} m³</i>
           </li>
           <li>
             <span>系统加药量:</span>
-            <i>40 m3/h</i>
+            <i>{{ doseNum }} m3/h</i>
           </li>
         </ul>
       </div>
-      <div class="round-btn">
+      <div class="round-btn" @click="emitEvent">
         <div class="inner space-y-[4px]">
           <SvgIcon name="control-icon-result-btn" size="24" />
           <span>一键投放</span>
@@ -56,6 +72,9 @@ import { TheChatView, SvgIcon } from '@/components';
 
     .reult-list {
       padding-top: 7px;
+      overflow: hiddden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
       .title {
         font-size: 20px;
         background: linear-gradient(92.49deg, #5ABBF2 12.24%, #2454FF 36.2%);
@@ -94,6 +113,7 @@ import { TheChatView, SvgIcon } from '@/components';
       font-weight: bold;
       color: #fff;
       cursor: pointer;
+      transition: all 0.5s;
 
       .inner {
         @include flex(x, center, center);