Przeglądaj źródła

feat: 合并碳源投加相关代码

sunxiao 6 miesięcy temu
rodzic
commit
3dbf2f628f

+ 26 - 1
src/api/control.js

@@ -4,10 +4,35 @@ export const controlApi = {
   /**
    * 碳源投加 获取echart记录 - 数据看板
    */
-  getEchartData: (type) => http.get(`/front/bigModel/smartAdd/charList/${type}`),
+  getEchartData: (type, params) => http.get(`/front/bigModel/smartAdd/charList/${type}`, { params }),
   
   /**
    * 碳源投加 基础数值数据
    */
   getNumValue: () => http.get(`/front/bigModel/smartAdd/dataInfo`),
+
+  /**
+   * 碳源投加 基础数值数据 - 新
+   */
+  getBaseData: () => http.get(`/business/medicineParamSetting/getLatestRecord`),
+  
+  /**
+   * 碳源投加 配置记录
+   */
+  getEchartList: params => http.get(`/business/medicineParamSetting/getList`, { params }),
+  
+  /**
+   * 碳源投加 配置记录
+   */
+  postAddRecord: data => http.post(`/business/medicineParamSetting`, data),
+
+  /**
+   * 碳源投加 系统是否可以投放 - 获取
+   */
+  getSystemStatus: () => http.get(`/front/bigModel/smartAdd/getSwitchStatus`),
+  
+  /**
+   * 碳源投加 系统是否可以投放 - 更新
+   */
+  putSystemStatus: data => http.put(`/front/bigModel/smartAdd/updateSwitchStatus`, data),
 }

Plik diff jest za duży
+ 2 - 0
src/assets/svgs/control/icon-pump-active.svg


Plik diff jest za duży
+ 0 - 2
src/assets/svgs/control/icon-pump.svg


+ 12 - 0
src/assets/svgs/control/icon-warning.svg

@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none">
+  <g clip-path="url(#clip0_3344_3853)">
+    <path d="M7.00008 12.8337C8.6109 12.8337 10.0692 12.1807 11.1249 11.1251C12.1805 10.0695 12.8334 8.61114 12.8334 7.00033C12.8334 5.38951 12.1805 3.93118 11.1249 2.87553C10.0692 1.81991 8.6109 1.16699 7.00008 1.16699C5.38927 1.16699 3.93093 1.81991 2.87529 2.87553C1.81967 3.93118 1.16675 5.38951 1.16675 7.00033C1.16675 8.61114 1.81967 10.0695 2.87529 11.1251C3.93093 12.1807 5.38927 12.8337 7.00008 12.8337Z" fill="#FF9902" stroke="#FF9902" stroke-width="1.16667" stroke-linejoin="round"/>
+    <path fill-rule="evenodd" clip-rule="evenodd" d="M6.99992 10.7913C7.40262 10.7913 7.72909 10.4649 7.72909 10.0622C7.72909 9.65947 7.40262 9.33301 6.99992 9.33301C6.59721 9.33301 6.27075 9.65947 6.27075 10.0622C6.27075 10.4649 6.59721 10.7913 6.99992 10.7913Z" fill="white"/>
+    <path d="M7 3.5V8.16667" stroke="white" stroke-width="1.16667" stroke-linecap="round" stroke-linejoin="round"/>
+  </g>
+  <defs>
+    <clipPath id="clip0_3344_3853">
+      <rect width="14" height="14" fill="white"/>
+    </clipPath>
+  </defs>
+</svg>

+ 107 - 25
src/components/Chat/ChatText.vue

@@ -1,12 +1,11 @@
-<script setup>
-import { computed } from 'vue';
+<script setup lang="jsx">
+import { computed, ref, watchEffect } from 'vue';
 import MarkdownIt from 'markdown-it';
 import hljs from 'highlight.js';
 import mila from 'markdown-it-link-attributes';
-// import markdownItMath from 'markdown-it-math';
-// import mdKatex from '@iktakahiro/markdown-it-katex';
 import markdownItTexmath from 'markdown-it-texmath';
-import katex from 'katex'
+import katex from 'katex';
+import * as echarts from 'echarts';
 import markdownItSub from 'markdown-it-sub';
 import markdownItSup from 'markdown-it-sup';
 
@@ -17,17 +16,21 @@ const props = defineProps({
   }
 })
 
+const echartOptions = {};
+const echartInstance = {};
+
 const highlightBlock = (str, lang) => {
   return `
     <code class="hljs code-block-body ${lang}">${str}</code>
   `
 }
+
 const mdi = new MarkdownIt({
-  // html: true,
-  // linkify: true,
-  // breaks: true,
-  // typographer: true,
-  highlight (code, language) {
+  html: true,
+  linkify: true,
+  breaks: true,
+  typographer: true,
+  highlight(code, language) {
     const validLang = !!(language && hljs.getLanguage(language))
     if (validLang) {
       const lang = language ?? ''
@@ -39,7 +42,6 @@ const mdi = new MarkdownIt({
 
 mdi.use(mila, { attrs: { target: '_blank', rel: 'noopener' } });
 
-
 mdi.use(markdownItTexmath, {
   engine: katex,
   delimiters: ['dollars', 'brackets', 'doxygen', 'gitlab', 'julia', 'kramdown', 'beg_end'],
@@ -56,6 +58,98 @@ const transformText = (text) => {
   });
 }
 
+mdi.renderer.rules.echart_block = (tokens, idx, options, env, self) => {
+  const token = tokens[idx];
+  const optionsString = token.content.trim();
+  try {
+    const optionsObj = JSON.parse(optionsString);
+
+    echartOptions[`chart-${idx}`] = { id: idx, code: optionsString, option: optionsObj, isExecute: false }
+
+    return `<div id="chart-${idx}"></div>`;
+  } catch (e) {
+    console.error('Invalid ECharts options:', optionsString);
+    return '';
+  }
+};
+
+mdi.block.ruler.after('fence', 'echart_block', function (state, startLine, endLine, silent) {
+  const startStr = '@@@echarts_b';
+  const endStr = '@@@echarts_e';
+
+  if (startLine + 1 < state.lineMax && state.src.slice(state.bMarks[startLine] + state.tShift[startLine], state.eMarks[startLine]) === startStr) {
+
+    let pos = state.bMarks[startLine++] + state.tShift[startLine];
+    let endPos = state.bMarks[startLine] + state.blkIndent;
+
+    let nextLine = startLine;
+    for (; nextLine < endLine; nextLine++) {
+      endPos = state.bMarks[nextLine] + state.tShift[nextLine];
+      if (state.src.slice(endPos, endPos + endStr.length) === endStr) {
+        break;
+      }
+    }
+
+    if (nextLine < endLine) {
+      const str = state.getLines(startLine, nextLine, state.tShift[startLine], false);
+      const token = state.push('echart_block', 'div', 0);
+      token.content = str;
+      token.map = [startLine, nextLine + 1];
+
+      state.line = nextLine + 1;
+
+      return true;
+    }
+  }
+
+  return false;
+});
+
+const updateCharts = () => {
+  Object.keys(echartOptions).forEach((key) => {
+    if (echartInstance[key]) return;
+    setTimeout(() => {
+      echartInstance[key] = echarts.init(document.getElementById(key), null, { width: 800, height: 400 });
+      echartInstance[key].setOption(echartOptions[key].option);
+    }, 100)
+  });
+}
+
+const markdownText = ref([]);
+
+function splitStringByChartDiv(str) {
+  const regex = /<div id="chart-\d+"><\/div>/g;
+
+  let result = [];
+  let lastIndex = 0;
+
+  let match;
+  while ((match = regex.exec(str)) !== null) {
+    if (lastIndex !== regex.lastIndex - match[0].length) {
+      result.push(str.slice(lastIndex, regex.lastIndex - match[0].length));
+    }
+    result.push(match[0]);
+    lastIndex = regex.lastIndex;
+  }
+
+  if (lastIndex < str.length) {
+    result.push(str.slice(lastIndex));
+  }
+
+  return result;
+}
+
+watchEffect(() => {
+  const value = mdi.render(props.content);
+
+  const result = splitStringByChartDiv(value)
+
+  markdownText.value = result
+
+  updateCharts();
+})
+
+// 原本的逻辑,暂时不用
 const text = computed(() => {
   let value = props.content ?? ""
   value = value.replace(/\((\w+_\w+)\)/g, '($$$1$)');
@@ -69,11 +163,11 @@ const text = computed(() => {
 
 <template>
   <div class="markdown-body text-[15px] break-all" v-if="content">
-    <div v-html="text"></div>
+    <div v-for="item, index in markdownText" :key="index" v-html="item"></div>
   </div>
 </template>
 
-<style>
+<style lang="scss">
 .markdown-body p:last-child {
   margin-bottom: 0;
 }
@@ -105,17 +199,5 @@ const text = computed(() => {
     margin-bottom: 0;
   }
 
-  /* img {
-    margin-top: 20px;
-  }
-
-  table {
-
-    thead {
-      strong {
-        font-size: 12px;
-      }
-    }
-  } */
 }
 </style>

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

@@ -78,18 +78,18 @@ const menuOptions = [
         icon: renderChildrenIcon({ name: 'menu-cost-drug' }),
         key: 'medicinal',
       },
+      // {
+      //   label: '精准曝气',
+      //   icon: renderChildrenIcon({ name: 'menu-cost-accurate' }),
+      //   key: 'normal-2',
+      // },
+      // {
+      //   label: '微生物镜检',
+      //   icon: renderChildrenIcon({ name: 'menu-cost-microorganism' }),
+      //   key: 'normal-3',
+      // },
       {
-        label: '精准曝气',
-        icon: renderChildrenIcon({ name: 'menu-cost-accurate' }),
-        key: 'normal-2',
-      },
-      {
-        label: '微生物镜检',
-        icon: renderChildrenIcon({ name: 'menu-cost-microorganism' }),
-        key: 'normal-3',
-      },
-      {
-        label: '碳排放智能体',
+        label: '碳排放管理',
         icon: renderChildrenIcon({ name: 'menu-carbon-emission' }),
         key: '/carbon',
       },

+ 7 - 4
src/components/Layout/userTop.vue

@@ -29,13 +29,16 @@ const changeVoiceStatus = () => {
 </script>
 
 <template>
-  <BasePopover placement="bottom" content="数字人设置">
+  <!-- 客服icon -->
+  <!-- <BasePopover placement="bottom" content="数字人设置">
     <div class="avatar rounded-full w-[24px] h-[24px]">
       <img src="@/assets/images/chat/img-avatar.png" alt="" class="cursor-pointer">
     </div>
-  </BasePopover>
-  <SvgIcon :name="voiceName" size="24" class="cursor-pointer" @click="changeVoiceStatus"></SvgIcon>
-  <div class="h-[24px] border-r-[1px] border-color-[#D3D0E1]"></div>
+  </BasePopover> -->
+  <!-- 声音icon -->
+  <!-- <SvgIcon :name="voiceName" size="24" class="cursor-pointer" @click="changeVoiceStatus"></SvgIcon> -->
+   <!-- 分割线 -->
+  <!-- <div class="h-[24px] border-r-[1px] border-color-[#D3D0E1]"></div> -->
   <NSelect v-model:value="selectValue" class="w-[114px]" size="medium" :options="options"
     :consistent-menu-width="false" />
   <TheUserAvatar></TheUserAvatar>

+ 647 - 0
src/views/analyse/WorkOrder-back.vue

@@ -0,0 +1,647 @@
+<script setup>
+import { ref, unref, computed, onUnmounted, h } from 'vue';
+import { useMessage, NDatePicker, NTabs, NTab, NRadioGroup, NRadio, NCheckboxGroup, NCheckbox, NDataTable } from 'naive-ui';
+import { BaseButton, RecodeCardItem, TheSubMenu, TheChatView, ChatWelcome, SvgIcon } from '@/components';
+import { ChatAsk, ChatAnswer } from '@/components/Chat';
+import { orderApi } from "@/api/order";
+import { chatApi } from '@/api/chat';
+import { formatEchart, isNumberComprehensive } from '@/utils/format';
+import { ORDER_OPTION_ENUM } from '@/utils/enum';
+import { getOrderAreaOptions } from './config/echartOptions'
+import * as echarts from 'echarts';
+import dayjs from 'dayjs';
+
+import { useInfinite, useScroll, useChat } from '@/composables';
+
+const { recordList, isFetching, onScrolltolower, onReset } = useInfinite('/front/bigModel/qa/pageList', { module: 1 });
+const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll();
+const { chatDataSource, addChat, updateChat, clearChat, updateById } = useChat();
+
+let controller = new AbortController();
+let timer = null;
+
+const ANSWER_ID_KEY = '@@id@@';
+
+const chartInstance = {};
+
+const message = useMessage();
+const reportDate = ref();
+
+const workOrderParams = ref({
+  timeBegin: null,
+  timeEnd: null,
+  whichWay: 1,
+  checkGroup: [],
+})
+
+const switchActive = ref(false);
+const tabActive = ref("daily");
+
+const isLoading = ref(false);
+const recordActive = ref(null);
+
+const currenSessionId = ref(null);
+
+const isExistInHistory = computed(() => (recordList.value.findIndex(({ sessionId: sId }) => sId === unref(currenSessionId)) === -1));
+const isChart = computed(() => tabActive.value == 'customDaily');
+const chatDataSourceItem = computed(() => unref(chatDataSource)[chatDataSource.value.length - 1]);
+
+const resetFormData = () => {
+  workOrderParams.value = {
+    timeBegin: null,
+    timeEnd: null,
+    whichWay: 1,
+    checkGroup: [],
+  }
+}
+
+const resetState = () => {
+  tabActive.value = 'daily';
+
+  currenSessionId.value = null;
+
+  recordActive.value = null;
+
+  reportDate.value = null;
+
+  resetFormData();
+
+  clearChat();
+}
+
+// 新建对话
+const handleCreateDialog = async () => {
+  message.destroyAll();
+
+  if (unref(isLoading)) {
+    return message.warning('当前对话生成中');
+  }
+
+  if (!unref(chatDataSource).length) {
+    return message.info('已切换最新会话');
+  }
+
+  resetState();
+}
+
+// 查询对话详情
+const handleChatDetail = async ({ sessionId }) => {
+  isLoading.value = false;
+
+  controller.abort();
+
+  if(currenSessionId.value === sessionId) return;
+
+  recordActive.value = sessionId;
+
+  const { data } = await chatApi.getAnswerHistoryDetail({ sessionId });
+
+  let echartData = [];
+  let whichWay = null;
+  chatDataSource.value = data.map(item => {
+    if ( item.remark ) {
+      const remark = JSON.parse(item.remark);
+      whichWay = remark.whichWay;
+      echartData = item.echartWithTableData = formatData(remark);
+    }
+
+    return { ...item, loading: false };
+  })
+ 
+  echartData.length && createEchart(echartData, whichWay);
+
+  currenSessionId.value = sessionId;
+
+  resetFormData();
+
+  scrollToBottom();
+}
+
+// 请求
+const onRegenerate = async (question, options) => {
+  controller = new AbortController();
+
+  const sessionId = unref(currenSessionId);
+  const params = {
+    data: {
+      sessionId,
+      showVal: question,
+      question,
+      module: 1,
+      isStrong: Number(unref(switchActive)),
+      reportDate: reportDate.value,
+      ...options,
+    },
+    signal: controller.signal,
+    onDownloadProgress: ({ event }) => {
+      const xhr = event.target;
+      const { responseText } = xhr;
+      const [answer] = responseText.split(ANSWER_ID_KEY);
+
+      updateChat({
+        ...chatDataSourceItem.value,
+        sessionId,
+        showVal: question,
+        answer,
+        loading: true,
+        delayLoading: false,
+        innerLoading: false
+      })
+
+      scrollToBottomIfAtBottom();
+    }
+  }
+
+  try {
+    const { data } = await chatApi.getChatStream(params);
+
+    const [answer, id] = data.split(ANSWER_ID_KEY);
+
+    updateChat({
+      ...chatDataSourceItem.value,
+      id,
+      sessionId,
+      showVal: question,
+      answer,
+      loading: false,
+      delayLoading: false,
+      innerLoading: false
+    })
+
+    scrollToBottomIfAtBottom();
+  }
+  catch (error) {
+    console.log("取消了请求 - catch", error);
+  }
+  finally {
+    isLoading.value = false;
+    onReset();
+  }
+}
+
+// 提交问题
+const handleSubmit = async (question, params) => {
+
+  if (unref(isExistInHistory)) {
+    const { data: sessionId } = await chatApi.getChatSessionTag();
+    currenSessionId.value = sessionId;
+  }
+
+  isLoading.value = true;
+
+  const option = {
+    sessionId: unref(currenSessionId),
+    showVal: question,
+    answer: '',
+    loading: true,
+    delayLoading: true,
+    echartWithTableData: []
+  }
+
+  if (isChart.value) {
+    const { data } = await orderApi.postOrderChart(params);
+    const reuslt = formatData({...data, whichWay: workOrderParams.value.whichWay});
+
+    option.echartWithTableData = reuslt;
+  }
+
+  addChat({
+    ...option,
+    innerLoading: true,
+  });
+
+  timer = setTimeout(() => {
+
+    updateChat({
+      ...chatDataSourceItem.value,
+      delayLoading: true,
+      innerLoading: false,
+    })
+
+    createEchart(option.echartWithTableData, workOrderParams.value.whichWay);
+      
+    scrollToBottom();
+
+    onRegenerate(question, params);
+    
+  }, 2 * 1000);
+}
+
+// 处理推荐问题
+const handleCreateOrder = async () => {
+  const { timeBegin, timeEnd, whichWay, checkGroup } = unref(workOrderParams);
+  const startDateTime = dayjs(timeBegin);
+  const endDateTime = dayjs(timeEnd);
+  let params = {};
+
+  let question = `${reportDate.value}智慧工单分析报告`;
+
+  if (tabActive.value === 'customDaily') {
+    const thirtyDaysBeforeEndTime = endDateTime.subtract(31, 'day');
+    const isStartAfterThirtyDaysBeforeEnd = startDateTime.isAfter(thirtyDaysBeforeEndTime);
+
+    if (!timeBegin) return message.warning('请选择开始日期');
+    if (!timeEnd) return message.warning('请选择结束日期');
+    if (!isStartAfterThirtyDaysBeforeEnd) return message.warning('只可生成一个月区间的工单报告');
+    if (!checkGroup.length) return message.warning('请至少选择一项指标项');
+    
+    params = {
+      timeBegin,
+      timeEnd,
+      whichWay
+    }
+    checkGroup.forEach(key => params[key] = true);
+
+    question = `${startDateTime.format("MM月DD日")}-${endDateTime.format("MM月DD日")}的在线仪表的日报工单`
+
+  } else {
+
+    if (!reportDate.value) return message.warning('请选择时间');
+
+  }
+
+  handleSubmit(question, params)
+}
+
+// 创建echart图形
+const createEchart = (echartData, whichWay) => {
+  setTimeout(() => {
+    echartData.forEach(({ key, xAxisData, yAxisData,  }) => {
+      const dom = document.getElementById(key);
+      chartInstance[key] = echarts.init(dom, null, { width: 680, height: 300 });
+    
+      const option = getOrderAreaOptions({ xAxisData, yAxisData, whichWay });
+    
+      chartInstance[key].setOption(option);
+    })
+  })
+}
+
+// 格式化 echart 和 table 数据
+const formatData = (data) => {
+  const titleEnum = {
+    jsGroup: '进水指标',
+    csGroup: '出水指标',
+    hyGroup: '化验指标'
+  }
+  const { whichWay, jsGroup, csGroup, hyGroup } = data;
+  return Object.entries({jsGroup, csGroup, hyGroup}).map(([key, value]) => {
+    if ( value.length ) {
+      const [ xAxisData, yAxisData ] = formatEchart(value);
+      const [ item ] = value; 
+      const columns = Object.keys(item).map(k => {
+        const wihteKeyList = ['jsSlq', 'csSlqc'];
+        let unit = '';
+
+        if(wihteKeyList.includes(k)) {
+          unit = whichWay === 1? '(m³/h)' : '(m³/d)';
+        } else if (k !== 'time') {
+          unit= '(mg/L)';
+        }
+
+        return {
+          title: ORDER_OPTION_ENUM[k] + unit,
+          key: k,
+          width: '150px',
+          align: 'center'
+        }
+      })
+      const data = value.map(item => {
+        Object.entries(item).forEach(([k, v]) => {
+          !v && v!=0 && (item[k] = '-');
+          if (isNumberComprehensive(v)) {
+            item[k] = v ? Number(v.toFixed(2)) : 0
+          }
+        })
+        return item;
+      })
+
+      return {
+        key: key,
+        title: titleEnum[key],
+        xAxisData,
+        yAxisData,
+        columns,
+        data
+      }
+    }
+  }).filter(Boolean);
+}
+
+// 返回
+const handleback = async () => {
+  clearInterval(timer);
+  await chatApi.getStopChatStream(currenSessionId.value);
+  resetState();
+}
+
+// 删除历史对话
+const handeChatDelete = async (id) => {
+  await chatApi.deleteHistory(id);
+  onReset();
+  clearChat();
+  message.success('删除成功');
+}
+
+const handleTabChange = (val) => {
+  tabActive.value = val;
+}
+
+const dateStartDisabled = (timestamp) => {
+  const { timeEnd } = workOrderParams.value;
+  if (timeEnd) {
+    const currentDate = dayjs(timestamp);
+    const endDateValue = dayjs(timeEnd);
+    return !currentDate.isBefore(endDateValue);
+  }
+}
+
+const dateEndDisabled = (timestamp) => {
+  const { timeBegin } = workOrderParams.value;
+  if (timeBegin) {
+    const currentDate = dayjs(timestamp);
+    const endDateValue = dayjs(timeBegin);
+    return !currentDate.isAfter(endDateValue);
+  }
+}
+
+onUnmounted(() => {
+  controller.abort();
+  Object.keys(chartInstance).forEach(key => chartInstance[key].clear());
+})
+</script>
+
+<template>
+  <section class="flex items-start h-full">
+
+    <TheSubMenu title="智能工单" @scrollToLower="onScrolltolower" :loading="isFetching">
+      <template #top>
+        <div class="create-btn px-[11px] pb-[22px]">
+          <BaseButton @click="handleCreateDialog" icon-name="tool-add-circle">新建工单</BaseButton>
+        </div>
+      </template>
+
+      <div class="pr-[4px] text-[#5e5e5e]">
+        <p v-show="!recordList.length" class="pt-[30px] text-[12px] text-[#999] text-center">暂无工单数据</p>
+        <RecodeCardItem v-for="item, index in recordList" :key="item.sessionId + index" :title="item.showVal"
+          :time="item.createTime" :data-item="item"
+          :class="{ 'recode-card-item_active': recordActive === item.sessionId }" @on-click="handleChatDetail"
+          @on-delete="handeChatDelete" />
+      </div>
+    </TheSubMenu>
+
+    <TheChatView ref="scrollRef" :is-footer="false" :is-back-btn="!!chatDataSource.length" @on-click-back="handleback">
+      <ChatWelcome title="您好,我是LibraAI智慧工单助手" :sub-title="[
+        '基于大语言模型的智能工单分析助手,可以为您实现数据分析及数据解读',
+        '选择日期并为您生成日报分析'
+      ]" v-if="!chatDataSource.length" />
+
+      <div class="conversation-item" v-show="chatDataSource.length">
+        <div v-for="item in chatDataSource" :key="item.sessionId">
+          <ChatAsk :content="item.showVal" :sessionId="item.sessionId"></ChatAsk>
+          <ChatAnswer
+            :id="item.id"
+            :content="item.answer"
+            :loading="item.loading"
+            :delay-loading="item.delayLoading"
+            :isSatisfied="item.isSatisfied"
+            :toggleVisibleIcons="false"
+            loadingText="数据分析中,由于数据查询需要一些时间,请您耐心等候..."
+            @on-click-icon="params => updateById(params)"
+          >
+            <main v-show="!item.innerLoading">
+              <div 
+                class="area_inner"
+                v-for="(item, index) in item.echartWithTableData"
+                :key="index"
+              >
+                <div class="echart-warpper" >
+                  <span class="mb-[10px] #1A2029 font-bold">{{ item.title }}</span>
+                  <div :id="item.key" class="w-[680px] h-[300px]"></div>
+                </div>
+                
+                <div class="w-[680px]" style="max-width: 680px; overflow-x: auto;">
+                  <NDataTable
+                    bordered
+                    size="small"
+                    :scroll-x="item.columns.length > 4 ? 1200: 670"
+                    :max-height="250"
+                    :single-line="false"
+                    :columns="item.columns"
+                    :data="item.data"
+                  >
+                    <template #empty>
+                      <span class="leading-[32px]">暂无数据</span>
+                    </template>
+                  </NDataTable>
+                </div>
+              </div>
+            </main>
+          </ChatAnswer>
+        </div>
+      </div>
+
+      <div class="order-container px-[60px] py-[30px] mt-[36px] rounded-[10px] bg-[#fff]" v-if="!chatDataSource.length">
+        <div
+          class="flex items-center justify-start space-x-[24px] pb-[20px] border-b-[1px] border-solid border-[#F1F1F1]">
+          <n-tabs type="segment" animated style="width: 188px;" size="large" :on-update:value="handleTabChange">
+            <n-tab name="daily">日报工单</n-tab>
+            <n-tab name="customDaily">自定义工单</n-tab>
+          </n-tabs>
+          <span class="text-[12px] text-[#8F959C]">
+            {{ tabActive === 'daily' ? '选择日期后为您生成日报工单' : '选择内容后为您生成工单' }}
+          </span>
+        </div>
+
+        <main class="order pt-[20px] text-[#1A2029]" v-show="tabActive === 'daily'">
+          <div class="flex items-center justify-start text-[16px] space-x-[16px]">
+            <span class="font-bold">选择时间</span>
+            <div class="w-[164px] border-[1px] border-[#EFEFF0] rounded-[8px] overflow-hidden">
+              <NDatePicker placeholder="选择日期" :readonly="true" v-model:formatted-value="reportDate"
+                value-format="yyyy-MM-dd">
+                <template #date-icon>
+                  <SvgIcon name="tool-arrow-bottom"></SvgIcon>
+                </template>
+              </NDatePicker>
+            </div>
+          </div>
+
+          <dl class="pt-[20px] text-[#1A2029] space-y-[16px]">
+            <dt class="text-[16px] font-bold leading-[22px]">报告内容</dt>
+            <dd>1、水质数据、生化数据情况</dd>
+            <dd>2、各项指标数据分析</dd>
+            <dd>3、对于工艺调整方面、数据趋势方面的措施与建议</dd>
+          </dl>
+        </main>
+
+        <main class="order pt-[20px] text-[#1A2029]" v-show="tabActive === 'customDaily'">
+          <div class="flex items-center justify-start mb-[20px] text-[16px] space-x-[16px]">
+            <span class="font-bold">选择时间</span>
+            <div class="w-[164px] border-[1px] border-[#EFEFF0] rounded-[8px] overflow-hidden">
+              <NDatePicker placeholder="开始日期" :readonly="true" v-model:formatted-value="workOrderParams.timeBegin"
+                value-format="yyyy-MM-dd" :is-date-disabled="dateStartDisabled">
+                <template #date-icon>
+                  <SvgIcon name="tool-arrow-bottom"></SvgIcon>
+                </template>
+              </NDatePicker>
+            </div>
+            <span class="text-[16px] text-[#8F959C]">至</span>
+            <div class="w-[164px] border-[1px] border-[#EFEFF0] rounded-[8px] overflow-hidden">
+              <NDatePicker placeholder="结束日期" :readonly="true" v-model:formatted-value="workOrderParams.timeEnd"
+                value-format="yyyy-MM-dd" :is-date-disabled="dateEndDisabled">
+                <template #date-icon>
+                  <SvgIcon name="tool-arrow-bottom"></SvgIcon>
+                </template>
+              </NDatePicker>
+            </div>
+          </div>
+
+          <div class="flex items-center justify-start mb-[20px] text-[16px] space-x-[16px]">
+            <span class="font-bold">统计方式</span>
+            <n-radio-group v-model:value="workOrderParams.whichWay" name="radiogroup2">
+              <n-radio :value="1">在线仪表</n-radio>
+              <n-radio :value="0">日报</n-radio>
+            </n-radio-group>
+          </div>
+
+          <div class="flex items-center justify-start mb-[20px] text-[16px] space-x-[16px]">
+            <span class="font-bold">进水指标</span>
+            <n-checkbox-group v-model:value="workOrderParams.checkGroup" name="radiogroup2">
+              <n-checkbox value="jsSlq">进水水量</n-checkbox>
+              <n-checkbox value="jsCod">COD</n-checkbox>
+              <n-checkbox value="jsTn">总氮</n-checkbox>
+              <n-checkbox value="jsTp">总磷</n-checkbox>
+              <n-checkbox value="jsNh3">氨氮</n-checkbox>
+              <n-checkbox value="jsSs">SS</n-checkbox>
+            </n-checkbox-group>
+          </div>
+
+          <div class="flex items-center justify-start mb-[20px] text-[16px] space-x-[16px]">
+            <span class="font-bold">出水指标</span>
+            <n-checkbox-group v-model:value="workOrderParams.checkGroup" name="radiogroup2">
+              <n-checkbox value="csSlqc">出水水量</n-checkbox>
+              <n-checkbox value="csCod">COD</n-checkbox>
+              <n-checkbox value="csTn">总氮</n-checkbox>
+              <n-checkbox value="csTp">总磷</n-checkbox>
+              <n-checkbox value="csNh3">氨氮</n-checkbox>
+              <n-checkbox value="csSs">SS</n-checkbox>
+            </n-checkbox-group>
+          </div>
+
+          <div class="flex items-start justify-start mb-[20px] text-[16px] space-x-[16px]">
+            <span class="font-bold">化验指标</span>
+            <div class="w-[500px]">
+              <n-checkbox-group v-model:value="workOrderParams.checkGroup" name="radiogroup2">
+                <n-checkbox value="no3Hlj1Jqr">1#好氧池硝酸盐</n-checkbox>
+                <n-checkbox value="no3Hlj2Jqr">2#好氧池硝酸盐</n-checkbox>
+                <n-checkbox value="nh31Jqr">1#缺氧氨氮</n-checkbox>
+                <n-checkbox value="nh32Jqr">2#缺氧氨氮</n-checkbox>
+                <n-checkbox value="no3Qyc1Jqr">1#缺氧池硝酸盐</n-checkbox>
+                <n-checkbox value="no3Qyc2Jqr">2#缺氧池硝酸盐</n-checkbox>
+                <n-checkbox value="tpRccJqr">二沉池正磷酸盐</n-checkbox>
+              </n-checkbox-group>
+            </div>
+          </div>
+
+          <dl class="text-[#1A2029] space-y-[16px]">
+            <dt class="text-[16px] font-bold leading-[22px]">报告内容</dt>
+            <dd>1、数据情况</dd>
+            <dd>2、各项指标数据分析及措施与建议</dd>
+          </dl>
+        </main>
+
+        <footer class="pt-[24px]">
+          <button class="btn-primary" @click="handleCreateOrder">立即生成</button>
+        </footer>
+      </div>
+
+    </TheChatView>
+  </section>
+</template>
+
+<style scoped lang="scss">
+.n-input__input-el {
+  font-size: 12px !important;
+}
+
+.area_inner {
+  display: flex;
+  align-items: center;
+  flex-flow: column;
+  padding-bottom: 20px;
+  border-radius: 8px;
+  margin-bottom: 20px;
+  background: #eff8fc;
+}
+
+.list-item {
+  margin-bottom: 16px;
+
+  .title {
+    font-size: 14px;
+    font-weight: bold;
+    line-height: 20px;
+  }
+
+  .item-top {
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: 10px;
+
+    .num {
+      font-size: 12px;
+      font-weight: 400;
+      color: #B0B7C0;
+    }
+  }
+}
+
+
+.btn-primary {
+  width: 88px;
+  height: 32px;
+  border-radius: 6px;
+  background-color: #2454FF;
+  font-size: 14px;
+  line-height: 32px;
+  color: #fff;
+
+  &:hover {
+    background: #1D43CC;
+  }
+}
+
+.echart-warpper {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-flow: column;
+  width: 100%;
+  padding: 20px 0;
+  border-radius: 8px;
+}
+</style>
+
+<style lang="scss">
+.order {
+
+  .n-input__input-el,
+  .n-input__placeholder {
+    font-size: 14px;
+  }
+}
+
+.order-container {
+  .n-tabs-tab__label {
+    font-size: 14px !important;
+    font-weight: bold;
+    color: #5E5E5E;
+  }
+
+  .n-tabs-tab--active {
+    .n-tabs-tab__label {
+      color: #1A2029;
+    }
+  }
+}
+</style>

+ 609 - 261
src/views/control/MedicinalView.vue

@@ -1,194 +1,269 @@
 <script setup>
-import { ref, onMounted, watch } from 'vue';
-import { NScrollbar, useMessage } from 'naive-ui';
-import { objectCopy } from '@/utils/tools';
+import { ref, onMounted, computed, unref } from 'vue';
+import { NScrollbar, useMessage, NTabs, NTabPane, c } from 'naive-ui';
 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         // 进水总氮
+const isVisibleUpdateInfo = ref(false);
+const systemStatus = 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'
+}
+
+// water实时数据
+const waterConfigParams = ref({});
+
+// 基础数据
+const dataSourceParams = ref({
+  auto: {},
+  onePool: {},
+  twoPool: {},
+  worker: {
+    medicineAmount: null
+  }
 })
 
-// 基础系数 - input输入
-const factorData = ref({
-  hfksd: 12.00,   // 后反馈设置定
-  jzxs: 3.10,     // 基准系数
-  xzxs: 1.00,     // 修正系数
-  kzxs: 5.20,     // 控制系数
-  sffpxs: 1.00,   // 水量分配系数
-  tydl: 0.90,     // 碳源当量
-  zhxs: 0.50,     // 转换系数
-  xsbs: 1.00,     // 稀释倍数
-  yjmd: 1.10,     // 药剂密度
-  zxqdll: 0.02,   // 最小启动流量
-  tdb: 3.54       // 碳氮比
+// 系数
+const baseSourceParams = ref({
+  numberBeng: 0,
+  type: 0
 })
 
-const originParamData = objectCopy(paramData.value);
-const originFactorData = objectCopy(factorData.value);
+// 当前Tab选中的key
+const tabActiveKey = computed(() => tabKeyEnum[baseSourceParams.value.type]);
 
-const factorInpData = ref(objectCopy(factorData.value));
+// 编辑系数 - 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;
+  })
 
-const doseNum = ref(null);
-const flowNum = ref(null);
+  handleMedicateAmount();
+}
 
-watch(() => paramData.value.setting, () => {
-  handelReset("变化了");
-});
+// 编辑系数 - 取消
+const onEditCancel = () => {
+  isVisibleBtn.value = true;
 
-// 重置
-const handelReset = () => {
+  columnData.value.map(({ key, value }) => {
+    baseSourceParams.value[key] = value;
+  })
+}
 
-  if ( !isVisibleBtn.value ) {
-    return message.warning("设定参数系数的值未保存")
-  }
+const onFinalResult = () => {
+  const addStatus = systemStatus.value === 0 ? 1 : 0;
 
-  chooseItemRef.value.forEach(item => item.resetInpVal() );
+  controlApi.putSystemStatus({ addStatus });
 
-  paramData.value = objectCopy({...originParamData, setting: paramData.value.setting});
-  factorData.value = objectCopy(originFactorData);
-  factorInpData.value = objectCopy(originFactorData);
+  systemStatus.value = addStatus;
 
-  doseNum.value = '';
-  flowNum.value = '';
-}
+  message.warning(addStatus === 0 ? '当前投药状态:已停用' : '当前投药状态:投放中');
 
-// 计算最终结果
-const handleResult = () => {
+}
 
-  if ( !isVisibleBtn.value ) {
-    return message.warning("设定参数系数的值未保存")
+const onUpdateTab = (index) => {
+  const currentData = dataSourceParams.value[tabKeyEnum[index]];
+  baseSourceParams.value.type = index;
+  if ( !Object.keys(currentData).length ) {
+    isVisibleUpdateInfo.value = false;
+    return;
   }
+  handleMedicateAmount();
+}
 
-  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
-    }
-  }
+const 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];
 
-  if ( isErrorItem ) {
-    return message.warning(`${isErrorItem.label}未填写`)
-  }
+  const {
+    htfksd, xzxs, kzxs, slfpxs, zhxs, tydl, jzxs, yymd, sxps
+  } = baseSourceParams.value;
 
-  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 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
 
-  const setpTwo = ( stepOne * factorData.value.kzxs - ( paramData.value.jslYB * factorData.value.sffpxs * paramData.value.jscod * factorData.value.zhxs / 1000)) / factorData.value.tydl
+  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
 
-  const setpThree = setpTwo / factorData.value.yjmd / 1000 * factorData.value.xsbs
+  const r1 = Number((rOne3 < 0 || !rOne3) ? 0 : rOne3.toFixed(3)) || 0;
+  const r2 = Number((rTwo3 < 0 || !rTwo3) ? 0 : rTwo3.toFixed(3)) || 0;
 
-  doseNum.value = setpThree.toFixed(3);
-  flowNum.value = paramData.value.jslYB;
+  return [ r1, r2 ];
 }
 
-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;
+const onConfirmUpdate = async () => {
+  await controlApi.postAddRecord({
+    ...dataSourceParams.value[tabKeyEnum[baseSourceParams.value.type]],
+    ...baseSourceParams.value,
+  });
+
+  isVisibleUpdateInfo.value = false;
+
+  message.success("系统加药量,更新成功");
+
+  doseNum.value = updateNum.value;
+}
+
+// 更新投药结果
+const handleMedicateAmount = () => {
+  const type = tabActiveKey.value;
+  const lastNum = unref(updateNum);
+
+  if ( tabActiveKey.value === 'worker' ) {
+    const medicineNum = dataSourceParams.value.worker.medicineAmount;
+    if ( medicineNum || medicineNum == 0 ) {
+      updateNum.value = medicineNum;
+      isVisibleUpdateInfo.value = true;
+      message.warning("有新投放方案, 请查看")
+    } else {
+      updateNum.value = null;
+      isVisibleUpdateInfo.value = false;
     }
+    return;
   }
 
-  if ( isError ) return;
+  const [r1, r2] = getTotalNum();
+  const maxR = Math.max( r1, r2 );
 
-  isVisibleBtn.value = true;
-  factorData.value = JSON.parse(JSON.stringify(factorInpData.value));
-}
+  if ( type === 'auto' && maxR !== lastNum) {
+    updateNum.value = maxR;
+    isVisibleUpdateInfo.value = true;
+    message.warning("有新的投放方案, 请查看");
+  }
 
-const onEditCancel = () => {
-  isVisibleBtn.value = true;
-  factorInpData.value = JSON.parse(JSON.stringify(factorData.value));
-}
+  if ( type === 'onePool' && r1 != lastNum ) {
+    updateNum.value = r1;
+    isVisibleUpdateInfo.value = true;
+    message.warning("有新的投放方案, 请查看");
+  }
 
-const onFinalResult = () => {
-  if ( !flowNum.value ) {
-    return message.warning('您还未完成投加计算,请完成后再试');
+  if ( type === 'twoPool' && r2 != lastNum ) {
+    updateNum.value = r2;
+    isVisibleUpdateInfo.value = true;
+    message.warning("有新的投放方案, 请查看");
   }
-  message.warning('您还未接入组态系统,请接入后再试');
+
 }
 
 onMounted(async () => {
-  const { data } = await controlApi.getNumValue();
-  let result = {};
-  Object.entries(data).forEach(([key, val]) => {
-    result[key] = val ? Number(val.toFixed(2)) : val;
+  // 获取最后一条记录  getSystemStatus
+  await controlApi.getBaseData().then(({ data }) => {
+    const {
+      numberBeng = 0, type = 0,
+      htfksd, jzxs, xzxs, kzxs, slfpxs, tydl, zhxs, sxps, yymd, zxqdll, tdb,
+      medicineAmount,
+      addType,
+      tytjTransientLL
+    } = data;
+ 
+    baseSourceParams.value = { 
+      ...baseSourceParams.value,
+      numberBeng, type,
+      htfksd, jzxs, xzxs, kzxs, slfpxs, tydl, zhxs, sxps, yymd, zxqdll, tdb,
+      addType
+    };
+
+    updateNum.value = medicineAmount;
+
+    doseNum.value = medicineAmount;
+
+    flowNum.value = tytjTransientLL;
+
+    dataSourceParams.value[tabActiveKey.value] = data;
+    
+    columnData.value = columnData.value.map(item => {
+      item.value = data[item.key];
+      return item;
+    })
+  })
+
+  // 获取实时数据
+  controlApi.getNumValue().then(({ data }) => {
+
+    Object.entries(data).forEach(([key, val]) => {
+      data[key] = val;
+    });
+    // water实时数据
+    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 };
+      }
+    })
+  })
+
+  // 获取是否允许投药开关
+  controlApi.getSystemStatus().then(({ data }) => {
+    // 0不允许  1允许
+    systemStatus.value = data;
   });
-  dataSource.value = result;
 })
 
 </script>
@@ -199,116 +274,323 @@ onMounted(async () => {
       <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>
-
+            <BaseTitle title="智能投加计算"></BaseTitle>
             <n-scrollbar class="scrollbar" style="height: 100%;">
               <div class="form-content">
+
                 <BaseCard title="选择加药泵">
-                  <BaseRadioCard v-model="paramData.pump"></BaseRadioCard>
+                  <BaseRadioCard v-model="baseSourceParams.numberBeng"></BaseRadioCard>
                 </BaseCard>
 
-                <BaseCard title="选择运行方式">
-                  <BaseRadioGroup :data="['自动', '手动']" v-model="paramData.running"></BaseRadioGroup>
+                <BaseCard title="投加运行方式">
+                  <span class="status-bar">
+                    <i>{{ baseSourceParams.addType === 0 ? '启用智适应碳源投加' : '手动碳源投加' }}</i>
+                  </span>
                 </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>
+                  <n-tabs justify-content="space-between" type="line" :bar-width="40"
+                    tab-style="min-width: 89px;" tab-class="custom-tab_item" animated :on-update:value="onUpdateTab" :value="baseSourceParams.type">
+                    <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: '' },
+                            { 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: '' },
+                            { 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: '' },
+                            { label: '化验', value1: waterConfigParams.hyXsyHYOne, value2: waterConfigParams.hyXsyHYTwo },
+                            { label: '预测', value1: waterConfigParams.hyXsyYCOne, value2: waterConfigParams.hyXsyYCTwo }
+                          ]"
+                        ></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: '' },
+                            { 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: '' },
+                            { 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: '' },
+                            { 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: '' },
+                            { 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: '' },
+                            { 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: '' },
+                            { label: '化验', value1: waterConfigParams.hyXsyHYOne, value2: waterConfigParams.hyXsyHYTwo },
+                            { label: '预测', value1: waterConfigParams.hyXsyYCOne, value2: waterConfigParams.hyXsyYCTwo }
+                          ]"
+                        ></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: '' },
+                            { 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: '' },
+                            { 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: '' },
+                            { 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: '' },
+                            { 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: '' },
+                            { 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: '' },
+                            { label: '化验', value1: waterConfigParams.hyXsyHYOne, value2: waterConfigParams.hyXsyHYTwo },
+                            { label: '预测', value1: waterConfigParams.hyXsyYCOne, value2: waterConfigParams.hyXsyYCTwo }
+                          ]"
+                        ></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: '' },
+                            { label: '化验', value1: waterConfigParams.qyXsyHYOne, value2: 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: '' },
+                            { label: '化验', value1: waterConfigParams.qyAdHYOne, value2: 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: '' },
+                            { label: '仪表', value1: waterConfigParams.jsTnYB, value2: waterConfigParams.jsTnYB }
+                          ]"
+                        ></BaseChooseItem>
+                      </div>
+                    </n-tab-pane>
+
+                    <n-tab-pane :name="3" tab="人工投放">
+                      <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>
-
                 <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
-                      ">
+                      <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"
+                          <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>
@@ -318,15 +600,17 @@ onMounted(async () => {
                       </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) }}
+                        {{ item.value }}
                         {{ 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>
+                        <BaseInput :unit="index === 0 ? 'mg/L' : ''" size='small' :isNeedFlotBtn="false"
+                          v-model="baseSourceParams[item.key]" isCenter placeholder="" :readonly="index === columnData.length - 1"></BaseInput>
                       </div>
                     </li>
                   </ul>
@@ -335,8 +619,17 @@ onMounted(async () => {
             </n-scrollbar>
           </div>
           <div class="right-section">
-            <TheResultPanel :doseNum="doseNum" :flowNum="flowNum" @on-click="onFinalResult"></TheResultPanel>
-            <TheEchartPanel></TheEchartPanel>
+            <TheResultPanel
+              :updateNum="updateNum"
+              :flowNum="flowNum"
+              :doseNum="doseNum"
+              :configuration-status="baseSourceParams.addType"
+              v-model:system="systemStatus"
+              v-model="isVisibleUpdateInfo"
+              @on-click="onFinalResult"
+              @on-update="onConfirmUpdate"
+            ></TheResultPanel>
+            <TheEchartPanel></TheEchartPanel> 
           </div>
         </div>
       </template>
@@ -352,30 +645,62 @@ onMounted(async () => {
   .left-section {
     display: flex;
     flex-flow: column;
-    width: 400px;
+    flex-shrink: 0;
+    width: 440px;
     height: 100%;
     border-radius: 10px;
     background: #fff;
 
+    .status-bar {
+      display: inline-block;
+      padding: 4px 14px;
+      margin-left: 20px;
+      border-top: 0.5px solid rgba(212, 241, 255, 0.00);
+      border-bottom: 0.5px solid rgba(212, 241, 255, 0.00);
+      background: linear-gradient(90deg, rgba(240, 250, 255, 0.00) 0%, #E9F8FF 27.27%, rgba(240, 250, 255, 0.00) 100%);
+
+      i {
+        line-height: 24px;
+        background: linear-gradient(92deg, #5ABBF2 12.24%, #2454FF 63.2%);
+        background-clip: text;
+        -webkit-background-clip: text;
+        -webkit-text-fill-color: transparent;
+        font-weight: bold;
+      }
+    }
+
     .scrollbar {
       height: 100%;
     }
 
     .form-content {
-      padding: 24px 16px;
+      padding: 0 16px 24px 16px;
     }
+
+    .panel-header_main {
+      @include flex(x, center, between);
+      margin-bottom: 12px;
+      color: #86909C;
+      span {
+        display: inline-block;
+        width: 60px;
+      }
+    }
+
   }
 
   .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;
+        font-size: 12px;
         color: #333;
       }
     }
@@ -383,29 +708,29 @@ onMounted(async () => {
 }
 
 .right-section {
-  display: flex;
-  flex-flow: column;
   width: 100%;
   height: 100%;
+  border-radius: 8px;
+  background: #fff;
+  overflow: hidden;
 
-  .top {
-    flex-shrink: 1;
-    height: 214px;
-    border-radius: 8px;
-    border: 0.5px solid #FFF;
-    background: linear-gradient(90deg, #E0E8FC 0%, #F2F4FF 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;
-  }
+  // .bottom {
+  //   height: 100%;
+  //   background: pink;
+  // }
 
 }
 
 
 // 通用区域的样式
-
 .btn {
   width: 80px;
   height: 32px;
@@ -465,4 +790,27 @@ onMounted(async () => {
 .radio_big.radio-active {
   border: 4px solid #2454FF;
 }
+</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%;
+  }
+}
+
+.control-container .left-section {
+  .n-tabs-nav-scroll-content {
+    padding-bottom: 10px;
+  }
+}
 </style>

+ 0 - 2
src/views/control/components/BaseCard.vue

@@ -44,10 +44,8 @@ defineProps({
     .title {
       @include flex(x, center, start);
       height: 30px;
-      padding-left: 9px;
       line-height: 30px;
       border-radius: 4px;
-      background: linear-gradient(90deg, #F6F7F9 0%, #FFF 100%);
       font-size: 14px;
       font-weight: bold;
       color: #1A2029;

+ 61 - 92
src/views/control/components/BaseChooseItem.vue

@@ -1,5 +1,5 @@
 <script setup>
-import { ref, computed, unref } from 'vue';
+import { ref, computed, watch, watchEffect } from 'vue';
 import { useMessage } from 'naive-ui';
 
 import BaseButton from './BaseButton.vue';
@@ -11,6 +11,12 @@ const activeIndex = ref(-1);
 const inpVal = ref();
 const modelValue = defineModel();
 
+const modelType = defineModel('type');
+const modelValue1 = defineModel('value1');
+const modelValue2 = defineModel('value2');
+
+const emit = defineEmits(['on-update']);
+
 const props = defineProps({
   title: {
     type: String,
@@ -23,15 +29,33 @@ const props = defineProps({
   unit: {
     type: String,
     default: ''
+  },
+  isDouble: {
+    type: Boolean,
+    default: false
+  },
+  tabKey: {
+    type: String,
+    requured: true
   }
 })
 
-const currentNumValue = computed(() => {
-  const curIndex = props.btnGroup.length === 1 ? 0 : unref(activeIndex.value);
-  return curIndex < 0
-    ? "" : curIndex === 0
-      ? modelValue.value: props.btnGroup[curIndex].value
-});
+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;
+    }
+  }
+})
+
+const onePoolNum = computed(() => (modelValue1.value && modelValue1.value !=0 ) ?  Number((modelValue1.value || 0).toFixed(2)) : '--');
+const twoPoolNum = computed(() => (modelValue2.value && modelValue2.value !=0 ) ?  Number((modelValue2.value || 0).toFixed(2)) : '--');
 
 const onInpCancel = (val) => {
   inpVal.value = val;
@@ -46,30 +70,28 @@ const onInpConfirm = (num) => {
 }
 
 const onInput = (val) => {
-  inpVal.value = val
+  modelValue1.value = val;
+  modelValue2.value = val;
 }
 
-const onBlur = () => {
-  if (inpVal.value === Infinity || inpVal.value === -Infinity) {
-    modelValue.value = null;
-    inpVal.value = null;
-    return message.warning(`${props.title}的数值填写有误, 请检查`);
+const onEmitEvent = () => {
+  if ( activeIndex.value === 0 ) {
+    if ( inpVal.value ) {
+      emit('on-update');
+    }
+  } else {
+    emit('on-update');
   }
 }
 
 const changeActive = (item, index) => {
   activeIndex.value = index;
-  modelValue.value = index != 0 ? item.value : (inpVal.value || null);
-}
-
-const resetInpVal = () => {
-  inpVal.value = null;
-  activeIndex.value = -1;
+  modelType.value = index;
+  modelValue1.value = index != 0 ? item.value1 : inpVal.value;
+  modelValue2.value = index != 0 ? item.value2 : inpVal.value;
+  onEmitEvent();
 }
 
-defineExpose({
-  resetInpVal
-})
 </script>
 
 <template>
@@ -78,23 +100,32 @@ defineExpose({
     <div class="choose-inner">
       <div class="top-box">
         <ul class="btn-group space-x-[4px]">
-          <BaseButton type="info" :key="item.key" :isActive="activeIndex === index || btnGroup.length === 1" v-for="item, index in btnGroup"
-            @click="changeActive(item, index)">
+          <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>
-        <span class="unit">{{ currentNumValue }} {{ unit }}</span>
+        <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="!activeIndex || btnGroup.length === 1"
+        v-show="(modelType !== undefined && !modelType) || btnGroup.length === 1"
         default-value=""
         :placeholder="'请输入' + props.title"
         :unit="unit"
-        v-model="modelValue"
+        v-model="inpVal"
         @click:confirm="onInpConfirm"
         @click:cancel="onInpCancel"
         @on-input="onInput"
-        @on-blur="onBlur"
+        @on-blur="onEmitEvent"
       ></BaseInput>
     </div>
   </div>
@@ -120,73 +151,11 @@ defineExpose({
         @include flex(x, center, center);
       }
 
-      .unit {
+      .num-group {
         font-family: "D-DIN-PRO-700-Bold";
         font-weight: bold;
-        font-size: 14px;
-        color: #333;
-      }
-    }
-
-    .bottom-box {
-      position: relative;
-      @include flex(x, center, between);
-      margin-top: 4px;
-
-      .inp {
-        width: 100%;
-        height: 28px;
-        padding: 0px 56px 0 10px;
-        border-radius: 4px 0px 0px 4px;
-        border: 1px solid #E6EAEE;
-        background: #fff;
-        outline: none;
-        font-size: 12px;
-
-        &:focus {
-          border: 1px solid #2454FF;
-        }
-      }
-
-      .unit {
-        flex-shrink: 1;
-        width: 46px;
-        height: 28px;
-        border-radius: 0px 4px 4px 0px;
-        border: 1px solid #E6EAEE;
-        border-left: 0;
-        background: #F0F2F5;
-        text-align: center;
-        line-height: 28px;
         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;
-          }
-        }
+        color: #333;
       }
     }
   }

+ 15 - 5
src/views/control/components/BaseInput.vue

@@ -31,6 +31,14 @@ const props = defineProps({
   isCenter: {
     type: Boolean,
     default: false
+  },
+  isCloseIcon: {
+    type: Boolean,
+    default: true
+  },
+  readonly: {
+    type: Boolean,
+    default: false
   }
 })
 
@@ -72,7 +80,6 @@ const handleInpValue = (event, type) => {
     emit('click:cancel', null);
   }
 }
-
 </script>
 
 <template>
@@ -81,11 +88,14 @@ const handleInpValue = (event, type) => {
       size="small"
       round
       style="width: 100%;"
+      :precision="2"
+      :max="99999.99"
+      :min="0"
       :placeholder="placeholder"
+      :show-button="false"
+      :on-update:value="onInput"
       :on-blur="onBlur"
       :on-focus="onFocus"
-      :on-update:value="onInput"
-      :show-button="false"
       :value="modelValue"
     >
       <template #suffix>
@@ -96,9 +106,9 @@ const handleInpValue = (event, type) => {
       <!-- <li>
         <SvgIcon name="control-icon-confirm" size="16" @mousedown="handleInpValue($event, 'confirm')"></SvgIcon>
       </li> -->
-      <li>
+      <!-- <li v-if="isCloseIcon">
         <SvgIcon name="control-icon-cancel" size="16" @mousedown="handleInpValue($event, 'cancel')"></SvgIcon>
-      </li>
+      </li> -->
     </ul>
   </div>
 </template>

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

@@ -10,8 +10,6 @@ const defaultData = [
   { label: '3号加药泵', key: 'third' },
 ]
 
-const activeIndex = ref(0);
-
 const chageActive = (index) => modelValue.value = index;
 </script>
 
@@ -20,7 +18,7 @@ const chageActive = (index) => modelValue.value = index;
     <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" />
+        <SvgIcon :name="index === modelValue ? 'control-icon-pump-active' : 'control-icon-pump'" size="16" fillColor="#2454FF" />
         <span class="radio radio-active"></span>
       </div>
       <p class="text">{{ item.label }}</p>
@@ -38,7 +36,7 @@ const chageActive = (index) => modelValue.value = index;
 
   .radio-card_item {
     @include flex(y, start, between);
-    width: 100px;
+    width: 120px;
     height: 48px;
     padding: 6px;
     border-radius: 4px;

+ 3 - 4
src/views/control/components/BaseTitle.vue

@@ -14,12 +14,11 @@ defineProps({
 </script>
 
 <template>
-  <div class="header" :style="[{ 'border-bottom': type === 'first' ? '1px solid #eee' : 'none' }]">
+  <div class="header">
     <div class="title">
       <svg xmlns="http://www.w3.org/2000/svg" width="8" height="24" viewBox="0 0 8 24" fill="none" v-if="type === 'first'">
         <path d="M0 6.86197V3.24507L4.21274 0H7.11111L4.45741 24H1.35684L4.01053 3.85352L0 6.86197Z" fill="#2454FF" />
       </svg>
-
       <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>
@@ -36,8 +35,7 @@ defineProps({
   @include flex(x, center, between);
   height: 75px;
   flex-shrink: 1;
-  padding: 24px 16px 18px 16px;
-  border-bottom: 1px solid #EEE;
+  padding: 24px 16px 24px 16px;
   color: #1A2029;
 
   .title {
@@ -52,6 +50,7 @@ defineProps({
       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;
+      line-height: 24px
     }
   }
 

+ 257 - 41
src/views/control/components/TheEchartPanel.vue

@@ -1,18 +1,32 @@
 <script setup>
-import { ref, watch, computed, onMounted, unref, onUnmounted } from 'vue';
-import { NTabs, NTab, NSelect } from "naive-ui";
+import { ref, computed, onMounted, unref, onUnmounted } from 'vue';
+import { NTabs, NTab, NSelect, NDatePicker } from "naive-ui";
 import * as echarts from 'echarts';
+import { startOfDay } from "date-fns/esm"
 import { controlApi } from "@/api/control"
+import dayjs from 'dayjs';
+
+const modelValue = defineModel("height");
 
 let echart = null;
+let tempTabItemOneKey = 0;
+let tempTabItemTwoKey = 'jzxs';
+const datePickerValue = ref(null);
+const dateRangeRef = ref(null);
 const tabs = ref([]);
 const tabActive = ref(null);
 const selectValue = ref(0);
+const coefficientDataSource = ref([]);
 const echartDataSource = ref({});
 const echartRef = ref(null);
 const isEmpty = ref(false);
+const activeIndex = ref(0);
+
+const tabList = ['水质', '系数'];
+
+const selectOptions = ref([]);
 
-const selectOptions = [
+const echartOptions = [
   { label: "进水流量", value: 0, style: "font-size: 12px" },
   { label: "#1好氧池硝酸盐", value: 1, style: "font-size: 12px" },
   { label: "#2好氧池硝酸盐", value: 2, style: "font-size: 12px" },
@@ -23,20 +37,74 @@ const selectOptions = [
   { label: "碳源投加量", value: 7, style: "font-size: 12px" }
 ]
 
+const coefficientOptions = [
+  { label: "基准系数", value: 'jzxs', style: "font-size: 12px" },
+  { label: "修正系数", value: 'xzxs', style: "font-size: 12px" },
+  { label: "水量分配系数", value: 'slfpxs', style: "font-size: 12px" },
+  { label: "碳源当量", value: 'tydl', style: "font-size: 12px" },
+  { label: "转换系数", value: 'zhxs', style: "font-size: 12px" },
+  { label: "稀释倍数", value: 'sxps', style: "font-size: 12px" },
+  { label: "密度", value: 'yymd', style: "font-size: 12px" },
+]
+
 const seriesName = computed(() => {
-  return selectOptions.find(({ value }) => selectValue.value === value).label
+  let name = '';
+  if ( activeIndex.value === 0) {
+    name = echartOptions.find(({ value }) => selectValue.value === value).label
+  } else {
+    name = coefficientOptions.find(item => item.value === selectValue.value).label
+  }
+  return name
 })
 
+// 切换tab选项
+const handleSwitchTab = (index) => {
+  if ( activeIndex.value === index ) return;
+  activeIndex.value = index;
+  if ( !index ) {
+    // echart
+    tempTabItemTwoKey = selectValue.value;
+    selectValue.value = tempTabItemOneKey;
+    selectOptions.value = echartOptions;
+    datePickerValue.value = null;
+    initWaterEchartData();
+  } else {
+     // 系数
+    tempTabItemOneKey = selectValue.value;
+    selectValue.value = tempTabItemTwoKey;
+    selectOptions.value = coefficientOptions;
+    datePickerValue.value = null;
+    intiCoefficientEchartData();
+  }
+}
+
+// select option change
+const handleSelectOptions = (val) => {
+  selectValue.value = val;
+
+  activeIndex.value === 0 ? initWaterEchartData() : intiCoefficientEchartData();
+}
+
 const windowResize = () => echart.resize();
 
-const getEchartOptions = (data) => {
+const getEchartOptions = (data, type) => {
   const option = {
     backgroundColor: '#FFF',
+    title: {
+      show: !data.length,
+      text: '暂无数据',
+      x: 'center',
+      y: 'center',
+      textStyle: {
+        fontSize: 14,
+        fontWeight: 'normal',
+      }
+    },
     grid: {
-      top: '20px',
+      top: '40px',
       bottom: '50px',
-      left: '8%',
-      right: '20px'
+      left: '6%',
+      right: '6%',
     },
     tooltip: {
       trigger: 'axis',
@@ -56,6 +124,11 @@ const getEchartOptions = (data) => {
         show: false,
         alignWithLabel: true
       },
+      axisLabel: {
+        formatter: function (value) {
+          return type ? dayjs(value).format('YYYY/MM/DD') : value
+        }
+      },
       data: data.map(({ time }) => time)
     },
     yAxis: {
@@ -113,18 +186,19 @@ const getEchartOptions = (data) => {
   return option;
 }
 
-const createEchart = (data) => {
-  echart.setOption(getEchartOptions(data));
-}
-
 const onSwitchEchart = (item) => {
   const echartData = echartDataSource.value[item.value];
   isEmpty.value = !!echartData.length
-  createEchart(echartData);
+  echart.setOption(getEchartOptions(echartData));
 }
 
-const initEchartData = async () => {
-  const { data: echartData } = await controlApi.getEchartData(unref(selectValue));
+// 水务相关数据格式化
+const initWaterEchartData = async () => {
+  const [tBegin, tEnd] = datePickerValue.value || [];
+
+  const timeBegin = tBegin ? dayjs(tBegin).format('YYYY/MM/DD') : null;
+  const timeEnd = tEnd ? dayjs(tEnd).format('YYYY/MM/DD') : null;
+  const { data: echartData } = await controlApi.getEchartData(unref(selectValue), { timeBegin, timeEnd });
 
   const enumSource = {
     YB: '在线仪表',
@@ -136,7 +210,7 @@ const initEchartData = async () => {
     if (index === 0) {
       tabActive.value = key + '-' + selectValue.value + '-' + index;
     }
-    if ( echartData[key].length ) {
+    if (echartData[key].length) {
       return ({ label: enumSource[key], value: key });
     }
   }).filter(Boolean);
@@ -146,57 +220,135 @@ const initEchartData = async () => {
   onSwitchEchart({ value: tabActive.value.substring(0, tabActive.value.indexOf('-')) });
 }
 
-watch(selectValue, initEchartData)
+// 系数相关数据
+const intiCoefficientEchartData = async () => {
+  const [timeBegin, timeEnd] = datePickerValue.value || [];
+  const { data } = await controlApi.getEchartList({ timeBegin, timeEnd });
+  coefficientDataSource.value = data;
+  
+  const d = data.map(item => ({
+    time: dayjs(item.createTime).format('YYYY/MM/DD HH'),
+    val: item[selectValue.value]
+  }));
+
+  echart.setOption(getEchartOptions(d));
+}
 
-onMounted(async () => {
+// 日期范围限制
+const isRangeDateDisabled = (ts, type, range) => {
+  const d = 864e5;
+  if (type === "start" && range !== null) {
+    return startOfDay(range[1]).valueOf() - startOfDay(ts).valueOf() >= d * 10;
+  }
+  if (type === "end" && range !== null) {
+    return startOfDay(ts).valueOf() - startOfDay(range[0]).valueOf() >= d * 10;
+  }
+  return false;
+}
+
+const onDatePickerConfirm = (ts) => {
+  datePickerValue.value = ts.map(t => dayjs(t).format('YYYY-MM-DD'));
+  activeIndex.value === 0 ? initWaterEchartData() : intiCoefficientEchartData();
+}
 
-  initEchartData();
+const onDatePickerClear = () => {
+  datePickerValue.value = null;
+  activeIndex.value === 0 ? initWaterEchartData() : intiCoefficientEchartData();
+}
+
+onMounted(async () => {
+  selectOptions.value = echartOptions;
 
   echart = echarts.init(echartRef.value, 'light');
 
+  await initWaterEchartData();
+
   window.addEventListener("resize", windowResize);
+
 })
 
 onUnmounted(() => {
   window.removeEventListener("resize", windowResize);
   echart && echart.dispose();
 })
+
 </script>
 
 <template>
-  <div class="echart-panel-wrapper">
+  <div class="echart-card_view">
     <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" />
+        <ul class="custom-radio-group">
+          <li :class="{ active: activeIndex === index }" v-for="item, index in tabList" :key="item"
+            @click="handleSwitchTab(index)">{{ item }}</li>
+        </ul>
       </div>
     </div>
+
+    <div class="select-wrapper">
+      <div>
+        <n-tabs
+          animated
+          type="segment"
+          size="small"
+          class="tabs"
+          style="width: 200px;"
+          v-model:value="tabActive"
+          v-show="activeIndex === 0"
+        >
+          <n-tab
+            v-for="item, index in tabs"
+            :key="item.value"
+            :name="item.value + '-' + selectValue + '-' + index"
+            @click="onSwitchEchart(item)"
+          >{{ item.label }}</n-tab>
+        </n-tabs>
+      </div>
+      <div class="flex space-x-[10px]">
+        <NDatePicker
+          clearable
+          class="w-[300px]"
+          size="small"
+          type="daterange"
+          ref="dateRangeRef"
+          :is-date-disabled="isRangeDateDisabled"
+          :on-confirm="onDatePickerConfirm"
+          :on-clear="onDatePickerClear"
+          v-model:formatted-value="datePickerValue"
+        ></NDatePicker>
+        <!-- v-model:formatted-value="datePickerValue" -->
+        <!--  -->
+        <!-- v-model:value="selectValue" -->
+        <NSelect
+          class="w-[150px]"
+          :options="selectOptions"
+          :value="selectValue"
+          :on-update:value="handleSelectOptions"
+          size="small" />
+      </div>
+    </div>
+
     <div class="echart-wrapper">
-      <div class="echart" ref="echartRef">echart</div>
+      <div class="echart" ref="echartRef"></div>
     </div>
   </div>
 </template>
 
 <style lang="scss" scoped>
-.echart-panel-wrapper {
+.echart-card_view {
   display: flex;
   flex-flow: column;
-  height: 100%;
-  padding: 25px 16px 0 25px;
-  margin-top: 12px;
+  height: calc(100% - 256px);
+  padding: 0px 16px 0 25px;
   border-radius: 10px;
-  background: #FFF;
 
   .title {
     flex-shrink: 0;
     @include flex(x, center, between);
+    padding-bottom: 16px;
 
     .left-inner {
       @include flex(x, center, start);
@@ -217,14 +369,44 @@ onUnmounted(() => {
 
     .right-inner {
       @include flex(x, center, start);
+
+      .custom-radio-group {
+        @include flex(x, center, center);
+        width: 104px;
+        height: 24px;
+        border-radius: 4px;
+        border: 1px solid #D3D7DD;
+
+        li {
+          width: 50%;
+          font-size: 12px;
+          text-align: center;
+          line-height: 24px;
+          color: #333;
+          cursor: pointer;
+
+          &:nth-child(1) {
+            border-right: 1px solid #D3D7DD;
+          }
+        }
+
+        li.active {
+          color: #2454FF;
+        }
+      }
     }
   }
 
+  .select-wrapper {
+    @include flex(x, center, between);
+    height: 32px;
+  }
+
   .echart-wrapper {
-    height: 100%;
-    padding-top: 16px;
+    height: calc(100% - 72px);
 
-    .echart, .empty {
+    .echart,
+    .empty {
       width: 100%;
       height: 100%;
     }
@@ -237,7 +419,7 @@ onUnmounted(() => {
 </style>
 
 <style lang="scss">
-.echart-panel-wrapper {
+.echart-card_view {
   .tabs {
     .n-tabs-tab--active {
       .n-tabs-tab__label {
@@ -251,16 +433,16 @@ onUnmounted(() => {
     }
   }
 
+  .n-base-selection .n-base-selection-label .n-base-selection-input {
+    font-size: 12px;
+    color: #333333 !important;
+  }
+
   .right-inner {
     .n-base-selection__border {
       border: 0;
     }
 
-    .n-base-selection-input__content {
-      font-size: 12px;
-      color: #1A2029;
-    }
-
     .n-base-selection-label {
       border: 0;
       border-radius: 0;
@@ -281,5 +463,39 @@ onUnmounted(() => {
       color: #4E5969;
     }
   }
+
+  .n-input--pair {
+    background: #f0f1f3 !important;
+  }
+
+  .n-base-selection-input__content {
+    font-size: 12px;
+  }
+
+  .n-base-selection {
+    background: #f0f1f3 !important;
+  }
+
+  .n-base-selection-label {
+    background: #f0f1f3;
+    border-radius: 3px;
+  }
+
+  .n-base-selection__border,
+  .n-base-selection__state-border {
+    display: none !important;
+  }
+
+  .n-date-picker {
+    .n-input__input-el,
+    .n-input__placeholder {
+      font-size: 12px !important;
+    }
+  }
+
+  .n-base-selection:not(.n-base-selection--disabled).n-base-selection--active .n-base-selection-label {
+    background: #f0f1f3 !important;
+  }
 }
+
 </style>

+ 228 - 99
src/views/control/components/TheResultPanel.vue

@@ -1,46 +1,105 @@
 <script setup>
+import { computed } from 'vue';
+import { useMessage, NNumberAnimation } from 'naive-ui';
 import BaseTitle from './BaseTitle.vue';
 import { SvgIcon } from '@/components';
 
-const emit = defineEmits(['on-click']);
+const message = useMessage();
+const isVisibleBtn = defineModel();
+const modelSystemStatus = defineModel('system');
+const emit = defineEmits(['on-click', 'on-update']);
+
 const props = defineProps({
   flowNum: {
     type: Number,
     default: 0
   },
+  updateNum: {
+    type: Number,
+    default: 0
+  },
   doseNum: {
     type: Number,
-    default: ''
+    default: 0
+  },
+  configurationStatus: {
+    type: Number,
+    default: 1
   }
 });
 
-const emitEvent = () => emit('on-click');
+const systemSwitchType = computed(() => props.configurationStatus === 0 && modelSystemStatus.value === 1);
+
+const emitEvent = () => {
+  if ( props.configurationStatus === 1 ) {
+    return message.warning('当前组态未启用,无法投放');
+  } else {
+    emit('on-click');
+  }
+
+};
+const emitUpdate = async () => {
+  emit('on-update');
+};
 </script>
 
 <template>
-  <div class="result-card_view">
+  <div>
     <BaseTitle title="智能投加计算结果" type="second"></BaseTitle>
-    <div class="content">
-      <div class="reult-list space-y-[4px]">
-        <div class="title">
-          {{ !doseNum ? '请您完成左侧「1.智能投加计算」' : '根据投加设定,计算结果如下:' }}
-          </div>
-        <div class="desc" v-show="!doseNum">核算完成后为您生成建议碳源投加量</div>
-        <ul class="text" v-show="doseNum">
-          <li>
-            <span>仪表瞬时流量:</span>
-            <i>{{ flowNum }} m³</i>
+    <div class="result-card_view">
+      <div class="update-message-box">
+        <ul class="update-message space-x-[16px]" v-show="isVisibleBtn">
+          <li class="flex space-x-[4px]">
+            <SvgIcon name="control-icon-warning" size="16"></SvgIcon>
+            <span>有新投放方案,系统加药量计算为:{{ updateNum }}m³/h,是否更新?</span>
           </li>
-          <li>
-            <span>系统加药量:</span>
-            <i>{{ doseNum }} m3/h</i>
+          <li class="space-x-[10px]">
+            <span class="text-[#ed742f] cursor-pointer" @click="emitUpdate">更新投放量</span>
+            <span class="text-[#88909b] cursor-pointer" @click="isVisibleBtn = false">取消</span>
           </li>
         </ul>
       </div>
-      <div :class="['round-btn', { active: doseNum }]" @click="emitEvent">
-        <div class="inner space-y-[4px]">
-          <SvgIcon name="control-icon-result-btn" size="24" />
-          <span>一键投放</span>
+      <div class="result-card">
+        <div class="result-inner space-x-[8px]">
+          <div class="result-card_item">
+            <div class="h-full flex flex-col justify-between">
+              <h4>
+                <span>碳源投加瞬时流量</span>
+                <span class="block w-[12px] h-[3px] mt-[4px] bg-[#1D2129]"></span>
+              </h4>
+              <p class="num-group space-x-[4px]">
+                <span class="num">
+                  <NNumberAnimation :from="0" :to="flowNum" :duration="1000" :precision="flowNum < 1 ? 3 : 3"></NNumberAnimation>
+                </span>
+                <span class="text-[12px] text-[#86909C]">m³/h</span>
+              </p>
+            </div>
+          </div>
+          <div class="result-card_item">
+            <div class="h-full flex flex-col justify-between">
+              <h4>
+                <span>系统加药量</span>
+                <span class="block w-[12px] h-[3px] mt-[4px] bg-[#1D2129]"></span>
+              </h4>
+              <p class="num-group space-x-[4px]">
+                <span class="num">
+                  <NNumberAnimation :from="0" :to="doseNum" :duration="1000" :precision="3"></NNumberAnimation>
+                </span>
+                <span class="text-[12px] text-[#86909C]">m³/h</span>
+              </p>
+            </div>
+          </div>
+        </div>
+        <div class="btn-card">
+          <div :class="['round-btn', {disable: configurationStatus === 1}]" @click="emitEvent">
+            <div class="circle1" v-show="systemSwitchType"></div>
+            <div class="circle2" v-show="systemSwitchType"></div>
+            <div class="circle3" v-show="systemSwitchType"></div>
+            <div class="inner space-y-[4px]">
+              <SvgIcon name="control-icon-result-btn" size="24" />
+              <span>{{ systemSwitchType ? "投放中" : "未启用" }}</span>
+            </div>
+          </div>
         </div>
       </div>
     </div>
@@ -48,98 +107,168 @@ const emitEvent = () => emit('on-click');
 </template>
 
 <style lang="scss" scoped>
+
 .result-card_view {
-  display: flex;
-  flex-flow: column;
-  flex-shrink: 0;
-  width: 100%;
-  height: 214px;
-  padding: 0px 16px 20px 16px;
-  border-radius: 8px;
-  border: 1px solid #fff;
-  background: url('@/assets/images/control/bg-control-top.png') center right no-repeat, linear-gradient(90deg, #E0E8FC 0%, #F2F4FF 100%);
-  background-size: auto 90%, auto;
-  background-position: top right;
-  overflow: hidden;
-
-  .content {
-    @include flex(x, start, between);
+  position: relative;
+  height: 190px;
+  padding: 0px 16px 24px 16px;
+
+  .update-message-box {
+    height: 37px;
+  }
+
+  .update-message {
+    display: flex;
+    align-items: center;
+    width: 100%;
     height: 100%;
-    padding: 23px 153px 23px 54px;
+    padding-left: 16px;
+    padding-bottom: 7px;
+    flex-shrink: 0;
+    border-radius: 8px 8px 0px 0px;
+    border: 1px solid #FFE9CE;
+    background: #FFF5E5;
+    font-size: 12px;
+    color: #7E604F;
+  }
+
+  .result-card {
+    @include flex(x, center, between);
+    padding: 16px 35px 16px 16px;
+    margin-top: -9px;
+    border: 1px solid #fff;
     border-radius: 8px;
-    background: linear-gradient(90deg, rgba(255, 255, 255, 0.80) 0%, rgba(255, 255, 255, 0.50) 100%);
-    backdrop-filter: blur(2px);
-
-    .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%);
-        -webkit-background-clip: text;
-        -webkit-text-fill-color: transparent;
-        line-height: 24px;
-        font-size: 15px;
-        font-weight: bold;
-      }
+    background: url('@/assets/images/control/bg-control-top.png') center right no-repeat, linear-gradient(90deg, #E0E8FC 0%, #F2F4FF 100%);
+    background-size: auto 100%, auto;
+    background-position: 50% 10%;
 
-      .desc {
-        font-size: 14px;
-        color: #1A2029;
-      }
+    .result-inner {
+      border-radius: 8px;
+      @include flex(x, center, start);
 
-      .text {
-        line-height: 24px;
-        font-weight: 500;
-        font-size: 14px;
-        color: #1A2029;
+      .result-card_item {
+        width: 254px;
+        height: 104px;
+        padding: 20px 0 20px 20px;
+        border-radius: inherit;
+        border: 1px solid #FFF;
+        background: #fff;
+        background: linear-gradient(90deg, #FFF 50%, rgba(255, 255, 255, 0.50) 100%);
+        backdrop-filter: blur(2px);
 
-        li:nth-child(2) span{
-          letter-spacing: 2.8px;
+        .num {
+          color: #1A2029;
+          font-family: D-DIN-PRO-700-Bold;
+          font-size: 24px;
+          font-weight: bold;
+          line-height: 0;
         }
       }
     }
 
-    .round-btn {
+    & .result-card_item:nth-child(2) {
+      border: 1px solid #FFF;
+      background: linear-gradient(90deg, #FFF 50%, rgba(255, 255, 255, 0.50) 100%);
+      backdrop-filter: blur(2px);
+    }
+  }
+
+  .round-btn {
+    position: relative;
+    @include flex(x, center, center);
+    width: 88px;
+    height: 88px;
+    border: 2px solid #E6EFFE;
+    border-radius: 50%;
+    background: #898EFE;
+    font-size: 10px;
+    font-weight: bold;
+    color: #fff;
+    cursor: pointer;
+    transition: all 0.5s;
+
+    .inner {
+      position: relative;
       @include flex(x, center, center);
-      width: 88px;
-      height: 88px;
-      border: 2px solid #E6EFFE;
-      border-radius: 50%;
-      background: #D7DDFF;
-      font-size: 10px;
-      font-weight: bold;
-      color: #fff;
-      cursor: pointer;
-      transition: all 0.5s;
-
-      .inner {
-        @include flex(x, center, center);
-        flex-flow: column;
-        width: 74px;
-        height: 74px;
-        padding: 14px;
-        border-radius: 100%;
-        background: #8E9EFB;
-        transition: all 0.3s;
-      }
+      flex-flow: column;
+      width: 74px;
+      height: 74px;
+      padding: 14px;
+      border-radius: 100%;
+      background: #2454FF;
+      transition: all 0.3s;
+    }
+  }
 
-      &:hover {
-        // background: #898EFE;
-        .inner {
-          // background: #2454FF;
-        }
-      }
+  .disable {
+    cursor: not-allowed;
+  }
+
+  // .active {
+  //   transition: all 0.5s;
+  //   background: #898EFE;
+
+  //   .inner {
+  //     background: #2454FF;
+  //   }
+  // }
+
+  .circle1,
+  .circle2,
+  .circle3 {
+    position: absolute;
+    width: 40px;
+    height: 40px;
+    background: rgba(137, 142, 254, 1);
+    border: 1px solid rgba(137, 142, 254, 0.85);
+    border-radius: 999px;
+  }
+
+  .circle1,
+  .circle2,
+  .circle3 {
+    animation-name: circleChange;
+    animation-duration: 3s;
+    animation-iteration-count: infinite;
+    animation-timing-function: linear;
+  }
+
+  .circle1 {
+    animation-delay: 0.5s;
+  }
+
+  .circle2 {
+    animation-delay: 1.5s;
+  }
+
+  .circle3 {
+    animation-delay: 2.5s;
+  }
+
+  @keyframes circleChange {
+    0% {
+      transform: scale(2);
+      opacity: 0.95;
     }
 
-    .active {
-      transition: all 0.5s;
-      background: #898EFE;
-      .inner {
-        background: #2454FF;
-      }
+    // 25% {
+    //   transform: scale(1.8);
+    //   opacity: 0.75;
+    // }
+
+    // 50% {
+    //   transform: scale(2);
+    //   opacity: 0.5;
+    // }
+
+    // 75% {
+    //   transform: scale(2.4);
+    //   opacity: 0.25;
+    // }
+
+    100% {
+      transform: scale(3);
+      opacity: 0.05;
     }
   }
 }

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików