Browse Source

feat: 图例默认不选中, 新增icon

sunxiao 3 months ago
parent
commit
59b3eb881c

+ 1 - 1
.env.development

@@ -1,5 +1,5 @@
 # 请求地址
-VITE_BASE_URL=http://192.168.40.21:8080
+VITE_BASE_URL=http://192.168.40.18:8080
 # VITE_BASE_URL=http://192.168.9.54:8080
 # 请求前缀
 VITE_BASE_PREFIX='' 

+ 18 - 0
src/api/data.js

@@ -0,0 +1,18 @@
+import http from "@/utils/request";
+
+export const dataApi = {
+  /**
+   * list data
+   */
+  getList: params => http.get(`/business/robot/list`, { params }),
+
+  /**
+   * list echart
+   */
+  getForecastEchart: params => http.get(`/business/comparison/echartsList`, { params, onDownloadProgress: true }),
+
+  /**
+   * list table
+   */
+  getForecastTable: params => http.get(`/business/comparison/list`, { params }),
+}

+ 4 - 0
src/assets/svgs/menu/data-analyse-active.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
+  <path d="M2.08325 9.99998C2.08325 14.3722 5.62767 17.9166 9.99992 17.9166V10.8333C9.99992 10.3731 10.373 9.99998 10.8333 9.99998H17.9166C17.9166 5.62773 14.3722 2.08331 9.99992 2.08331C5.62767 2.08331 2.08325 5.62773 2.08325 9.99998Z" fill="#1A2029" stroke="#1A2029" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
+  <path d="M17.5 12.5H12.5V17.5H17.5V12.5Z" stroke="#1A2029" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 4 - 0
src/assets/svgs/menu/data-analyse.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
+<path d="M2.08325 9.99992C2.08325 14.3722 5.62767 17.9166 9.99992 17.9166V10.8333C9.99992 10.373 10.373 9.99992 10.8333 9.99992H17.9166C17.9166 5.62767 14.3722 2.08325 9.99992 2.08325C5.62767 2.08325 2.08325 5.62767 2.08325 9.99992Z" stroke="#1A2029" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M17.5 12.5H12.5V17.5H17.5V12.5Z" stroke="#1A2029" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 7 - 0
src/assets/svgs/menu/data-forecast-active.svg

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
+<path d="M2.5 17.4999H17.5" stroke="#1A2029" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M2.5 10.7499L5.5 11.4999V15.2499H2.5V10.7499Z" fill="#1A2029" stroke="#1A2029" stroke-width="1.3" stroke-linejoin="round"/>
+<path d="M8.5 9.99994L11.5 8.49994V15.2499H8.5V9.99994Z" fill="#1A2029" stroke="#1A2029" stroke-width="1.3" stroke-linejoin="round"/>
+<path d="M14.5 7L17.5 5.5V15.25H14.5V7Z" fill="#1A2029" stroke="#1A2029" stroke-width="1.3" stroke-linejoin="round"/>
+<path d="M2.5 7.75006L5.5 8.50006L17.5 2.50006H13.75" stroke="#1A2029" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 7 - 0
src/assets/svgs/menu/data-forecast.svg

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
+<path d="M2.5 17.5H17.5" stroke="#1A2029" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M2.5 10.75L5.5 11.5V15.25H2.5V10.75Z" stroke="#1A2029" stroke-width="1.3" stroke-linejoin="round"/>
+<path d="M8.5 10L11.5 8.5V15.25H8.5V10Z" stroke="#1A2029" stroke-width="1.3" stroke-linejoin="round"/>
+<path d="M14.5 7L17.5 5.5V15.25H14.5V7Z" stroke="#1A2029" stroke-width="1.3" stroke-linejoin="round"/>
+<path d="M2.5 7.75L5.5 8.5L17.5 2.5H13.75" stroke="#1A2029" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 54 - 0
src/assets/svgs/menu/data.svg

@@ -0,0 +1,54 @@
+<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
+<circle cx="15" cy="15" r="15" fill="url(#paint0_linear_1780_2169)"/>
+<path d="M7.96084 14.3003C7.3508 12.0236 7.04578 10.8853 7.56355 9.98847C8.08131 9.09167 9.21967 8.78665 11.4964 8.17661L13.5222 7.6338V7.6338C14.1052 7.47757 14.3967 7.39946 14.6473 7.384C15.6496 7.32219 16.5916 7.86603 17.0392 8.76492C17.1511 8.98968 17.2277 9.27571 17.381 9.84775V9.84775L18.7258 14.8668C19.3359 17.1435 19.6409 18.2818 19.1231 19.1786C18.6054 20.0754 17.467 20.3805 15.1903 20.9905L14.9473 21.0556C12.6706 21.6657 11.5322 21.9707 10.6354 21.4529C9.73864 20.9351 9.43362 19.7968 8.82357 17.5201L7.96084 14.3003Z" fill="url(#paint1_linear_1780_2169)"/>
+<g filter="url(#filter0_b_1780_2169)">
+<path d="M10.8333 14.1668C10.8333 11.8097 10.8333 10.6312 11.5655 9.89898C12.2977 9.16675 13.4762 9.16675 15.8333 9.16675H16.2622C17.2841 9.16675 17.795 9.16675 18.2544 9.35705C18.7139 9.54735 19.0751 9.90864 19.7977 10.6312L19.9999 10.8334L20.2021 11.0356C20.9247 11.7582 21.286 12.1195 21.4763 12.5789C21.6666 13.0383 21.6666 13.5493 21.6666 14.5711V18.3334C21.6666 20.6904 21.6666 21.8689 20.9344 22.6012C20.2021 23.3334 19.0236 23.3334 16.6666 23.3334H15.8332C13.4762 23.3334 12.2977 23.3334 11.5655 22.6012C10.8333 21.8689 10.8333 20.6904 10.8333 18.3334V14.1668Z" fill="url(#paint2_linear_1780_2169)" fill-opacity="0.4"/>
+<path d="M15.8332 23.5417H16.6666H16.683C17.8476 23.5417 18.7429 23.5418 19.4375 23.4484C20.1414 23.3537 20.6704 23.1598 21.0817 22.7485C21.4929 22.3372 21.6869 21.8082 21.7815 21.1044C21.8749 20.4097 21.8749 19.5145 21.8749 18.3499V18.3334V14.5711L21.8749 14.52C21.875 13.5471 21.875 12.9971 21.6688 12.4992C21.4625 12.0012 21.0736 11.6124 20.3856 10.9245L20.3494 10.8883L20.1472 10.6861L19.945 10.4839L19.9089 10.4477C19.221 9.75975 18.8321 9.37083 18.3342 9.16457C17.8362 8.95832 17.2862 8.95835 16.3134 8.95841C16.2964 8.95841 16.2794 8.95841 16.2622 8.95841H15.8333L15.8168 8.95841C14.6522 8.95841 13.7569 8.95841 13.0623 9.0518C12.3584 9.14644 11.8294 9.34039 11.4182 9.75167C11.0069 10.1629 10.8129 10.6919 10.7183 11.3958C10.6249 12.0904 10.6249 12.9857 10.6249 14.1503L10.6249 14.1668V18.3334L10.6249 18.3498C10.6249 19.5145 10.6249 20.4097 10.7183 21.1044C10.8129 21.8082 11.0069 22.3372 11.4182 22.7485C11.8294 23.1598 12.3584 23.3537 13.0623 23.4484C13.7569 23.5418 14.6522 23.5417 15.8168 23.5417H15.8332Z" stroke="url(#paint3_linear_1780_2169)" stroke-width="0.416667"/>
+</g>
+<path d="M17.5 11.1786C17.5 10.3224 17.5 9.89426 17.7572 9.78772C18.0144 9.68118 18.3172 9.9839 18.9226 10.5893L19.5833 11.2501L20.2441 11.9108C20.8495 12.5163 21.1522 12.819 21.0457 13.0762C20.9392 13.3334 20.511 13.3334 19.6548 13.3334H19.1667C18.381 13.3334 17.9882 13.3334 17.7441 13.0893C17.5 12.8453 17.5 12.4524 17.5 11.6667V11.1786Z" fill="url(#paint4_linear_1780_2169)"/>
+<g filter="url(#filter1_d_1780_2169)">
+<path d="M13.3333 13.3333H15.8333M13.3333 16.2499H19.1666M13.3333 19.1666H19.1666" stroke="url(#paint5_linear_1780_2169)" stroke-width="1.25" stroke-linecap="round" shape-rendering="crispEdges"/>
+</g>
+<defs>
+<filter id="filter0_b_1780_2169" x="7.08317" y="5.41667" width="18.3334" height="21.6667" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feGaussianBlur in="BackgroundImageFix" stdDeviation="1.66667"/>
+<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_1780_2169"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_1780_2169" result="shape"/>
+</filter>
+<filter id="filter1_d_1780_2169" x="11.8749" y="11.8749" width="10.4166" height="10.4166" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dx="0.833333" dy="0.833333"/>
+<feGaussianBlur stdDeviation="0.833333"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0.28 0 0 0 0 0.46 0 0 0 0 1 0 0 0 0.4 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1780_2169"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1780_2169" result="shape"/>
+</filter>
+<linearGradient id="paint0_linear_1780_2169" x1="27" y1="10.5" x2="4.5" y2="22" gradientUnits="userSpaceOnUse">
+<stop stop-color="#26BEFF"/>
+<stop offset="1" stop-color="#01AEDD"/>
+</linearGradient>
+<linearGradient id="paint1_linear_1780_2169" x1="7.67057" y1="10.765" x2="23.4699" y2="19.1494" gradientUnits="userSpaceOnUse">
+<stop stop-color="white"/>
+<stop offset="1" stop-color="white" stop-opacity="0.5"/>
+</linearGradient>
+<linearGradient id="paint2_linear_1780_2169" x1="16.2499" y1="23.3334" x2="16.2499" y2="9.23049" gradientUnits="userSpaceOnUse">
+<stop stop-color="white"/>
+<stop offset="1" stop-color="#5BD6FF" stop-opacity="0.7"/>
+</linearGradient>
+<linearGradient id="paint3_linear_1780_2169" x1="11.6666" y1="10.0001" x2="23.5334" y2="19.55" gradientUnits="userSpaceOnUse">
+<stop offset="0.181329" stop-color="white"/>
+<stop offset="1" stop-color="white" stop-opacity="0.7"/>
+</linearGradient>
+<linearGradient id="paint4_linear_1780_2169" x1="17.7579" y1="9.63863" x2="21.7853" y2="14.5154" gradientUnits="userSpaceOnUse">
+<stop stop-color="white"/>
+<stop offset="1" stop-color="white" stop-opacity="0.5"/>
+</linearGradient>
+<linearGradient id="paint5_linear_1780_2169" x1="13.6944" y1="13.9939" x2="19.3327" y2="20.8213" gradientUnits="userSpaceOnUse">
+<stop stop-color="white"/>
+<stop offset="1" stop-color="white" stop-opacity="0.5"/>
+</linearGradient>
+</defs>
+</svg>

+ 14 - 2
src/components/Layout/TheMenu.vue

@@ -127,8 +127,20 @@ const updateMenOptions = () => {
       },
       {
         label: () => renderLabel('数据分析'),
-        icon: renderIcon({ name: 'menu-work' }),
-        key: '/data'
+        icon: renderIcon({ name: 'menu-data' }),
+        key: '/data',
+        children: [
+          {
+            label: '智能化验室数据',
+            icon: renderChildrenIcon({ name: 'menu-data-analyse' }),
+            key: '/laboratory',
+          },
+          {
+            label: '预测数据',
+            icon: renderChildrenIcon({ name: 'menu-data-forecast' }),
+            key: '/comparison',
+          },
+        ]
       },
       {
         label: () => renderLabel('用户中心'),

+ 1 - 0
src/components/Layout/ThePublicLayout.vue

@@ -114,6 +114,7 @@ onUnmounted(() => clearInterval(initWraingListData))
 
   .main-container {
     flex: 1;
+    // width: 100%;
     height: 100%;
     // background: orange;
   }

+ 10 - 2
src/router/index.js

@@ -133,11 +133,19 @@ const constantRouterMap = [
         }
       },
       {
-        path: 'data',
+        path: 'laboratory',
         name: 'DataView',
         component: () => import('@/views/data/DataView.vue'),
         meta: {
-          title: '智适应碳源投加'
+          title: '智能国标化验室数据'
+        }
+      },
+      {
+        path: 'comparison',
+        name: 'ComparisonView',
+        component: () => import('@/views/data/ComparisonView.vue'),
+        meta: {
+          title: '预测数据'
         }
       },
     ]

+ 249 - 0
src/views/data/ComparisonView.vue

@@ -0,0 +1,249 @@
+<script setup>
+import { onMounted, ref, onUnmounted, computed } from 'vue';
+import { NDatePicker, NScrollbar, NDataTable, NPagination, NSelect } from 'naive-ui';
+import dayjs from 'dayjs';
+import { TheChatView } from '@/components';
+import { dataApi } from '@/api/data';
+import * as echarts from 'echarts';
+import { ForecastColumns, getEchartMultiLineOption } from './config';
+
+let echart = null;
+const formData = ref({ pageNum: 1 });
+const pageCount = ref(0);
+const echartRef = ref(null);
+const tableData = ref([]);
+const timeRangeValue = ref(null);
+const selectValue = ref('xsy1');
+
+const selectOptions = [
+  { label: "#1NO₃⁻", value: 'xsy1' },
+  { label: "#2NO₃⁻", value: 'xsy2' },
+  { label: "PO₄³⁻ ", value: 'zlsy' },
+  { label: "NH₃-N ", value: 'nh3' },
+  { label: "COD ", value: 'cod' },
+  { label: "SS ", value: 'ss' },
+  { label: "#1缺氧硝酸盐 ", value: 'qyxsy1' },
+  { label: "#2缺氧硝酸盐 ", value: 'qyxsy2' },
+  { label: "#1缺氧氨氮 ", value: 'qynh31' },
+  { label: "#2缺氧氨氮 ", value: 'qynh32' }
+]
+
+const dateThemeOverrides = {
+  peers: {
+    Input: {
+      placeholderColor: '#86909C',
+    }
+  }
+}
+
+const dateRange = computed(() => {
+  const [ begin, end ] = timeRangeValue.value || [];
+  let timeBegin = '';
+  let timeEnd = '';
+
+  if (begin) {
+    const endHour = dayjs(end).format('HH');
+    timeBegin = dayjs(begin).format('YYYY/MM/DD HH');
+    timeEnd = endHour === '00' ? dayjs(end).format('YYYY/MM/DD') + ' 24' : dayjs(end).format('YYYY/MM/DD HH')
+  }
+
+  const params = { timeBegin, timeEnd };
+
+  delete params.daterange;
+
+  return params
+})
+
+const onDatePickerConfirm = (timeRange) => {
+  timeRangeValue.value = timeRange;
+  initEchartData();
+  getTableList();
+}
+
+const onDatePickerClear = () => {
+  timeRangeValue.value = null;
+  initEchartData();
+  getTableList();
+}
+
+const handlePageChange = (page) => {
+  formData.value.pageNum = page;
+  getTableList();
+}
+
+// 初始化ecahrt数据
+const initEchartData = async () => {
+  dataApi.getForecastEchart({ category: selectValue.value, ...dateRange.value }).then(({ data }) => {
+
+    Object.keys(data).forEach(key => {
+      const len = data[key].data.length;
+      if ( !data[key].data[len - 1] ) {
+        data[key].data.pop()
+      }
+    })
+
+    const reusltData = data.hs.data.splice(data.real.data.length, data.hs.data.length - data.real.data.length);
+    let xAxisData = [];
+    xAxisData = [...data.yy.time];
+
+    const echartData = [
+      { name: '预测值', val: [...data.hs.data, ...reusltData] },
+      { name: '真实值', val: data.real.data }
+    ]
+
+    const specificData = new Array(data.hs.data.length).fill(null).concat( reusltData );
+
+    const option = getEchartMultiLineOption({ xAxisData, echartData, specificData:specificData });
+
+    echart.setOption(option);
+
+  })
+}
+
+const windowResize = () => echart.resize();
+
+// 请求数据
+const getTableList = async () => {
+  const { rows, total } = await dataApi.getForecastTable({ category: selectValue.value, ...formData.value, ...dateRange.value });
+  const whiteList = {
+    xsy1: '#1NO₃⁻',
+    xsy2: '#2NO₃⁻',
+    zlsy: 'PO₄³⁻',
+    nh3: 'NH₃',
+    cod: 'COD',
+    ss: 'SS',
+    qyxsy1: '#1缺氧硝酸盐',
+    qyxsy2: '#2缺氧硝酸盐',
+    qynh31: '#1缺氧氨氮',
+    qynh32: '#2缺氧氨氮'
+  }
+
+  tableData.value = rows.map(item => ({ ...item, category: whiteList[item.category] }));
+  pageCount.value = total;
+}
+
+onMounted(() => {
+  getTableList();
+  initEchartData();
+  echart = echarts.init(echartRef.value, 'light');
+  window.addEventListener("resize", windowResize);
+})
+
+onUnmounted(() => {
+  window.removeEventListener("resize", windowResize);
+  echart && echart.dispose();
+})
+
+const handleSelectOptions = (val) => {
+  selectValue.value = val;
+  getTableList();
+  initEchartData();
+}
+</script>
+
+<template>
+  <section class="comparison-wrapper flex items-start h-full">
+    <TheChatView leftTitle="数据分析" :isChatSlot="false" :isFooter="false">
+      <template #control>
+        <div class="comparison-container">
+          <div class="header">
+            <h4 class="title">预测数据</h4>
+            <div class="flex items-center space-x-[10px]">
+              <NSelect class="w-[150px]" :options="selectOptions" :value="selectValue" :on-update:value="handleSelectOptions" size="small" />
+              <NDatePicker type="daterange" size="small" clearable input-readonly :themeOverrides="dateThemeOverrides" :on-confirm="onDatePickerConfirm" :on-clear="onDatePickerClear" />
+            </div>
+          </div>
+          <div class="main">
+            <NScrollbar style="height: 100%;">
+              <div class="echart-box">
+                <p class="title">数据趋势图「小时」</p>
+                <div class="h-full w-full" ref="echartRef"></div>
+              </div>
+              <div class="table-box w-full">
+                <p class="title">表格数据</p>
+                <div class="pt-[12px]">
+                  <n-data-table size="small" :columns="ForecastColumns" :data="tableData" :bordered="false" :scroll-x="1700"
+                    layout="table" />
+                </div>
+                <div class="flex justify-center mt-[14px] mb-[10px]">
+                  <NPagination v-model:page="formData.pageNum" :item-count="pageCount"
+                    :on-update:page="handlePageChange"></NPagination>
+                </div>
+              </div>
+            </NScrollbar>
+          </div>
+        </div>
+      </template>
+    </TheChatView>
+  </section>
+</template>
+
+<style lang="scss" scoped>
+.comparison-wrapper {
+  width: calc(100vw - 240px);
+}
+.comparison-container {
+  display: flex;
+  flex-flow: column;
+  height: 100%;
+  border-radius: 10px;
+  background: #fff;
+
+  :deep(.n-input--stateful) {
+    background: #F2F3F5 !important;
+  }
+
+  :deep(.n-base-selection-label) {
+    border: 0;
+    border-radius: 3px;
+    background: #F2F3F5 !important;
+  }
+
+  :deep(.n-base-selection__border) {
+    border: none;
+  }
+  
+  :deep(.n-base-selection__state-border),
+  :deep(.n-base-selection__border) {
+    display: none;
+    border: 0 !important;
+  }
+  :deep(.n-base-selection-input__content) {
+    color: #333;
+  }
+}
+
+.header {
+  flex-shrink: 0;
+  height: 60px;
+  padding: 16px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+
+  .title {
+    color: #333;
+    font-weight: bold;
+    font-size: 15px;
+  }
+}
+
+.main {
+  flex: 1;
+  height: 100%;
+  padding: 0 16px;
+  overflow: hidden;
+
+  .echart-box {
+    height: 300px;
+    background: #fff;
+  }
+
+  .table-box {
+    padding-top: 20px;
+  }
+}
+</style>
+
+<style></style>

+ 198 - 3
src/views/data/DataView.vue

@@ -1,11 +1,167 @@
-<script setup></script>
+<script setup>
+import { onMounted, ref, onUnmounted, computed } from 'vue';
+import { NDatePicker, NScrollbar, NDataTable, NPagination } from 'naive-ui';
+import dayjs from 'dayjs';
+import { TheChatView } from '@/components';
+import { dataApi } from '@/api/data';
+import * as echarts from 'echarts';
+import { columns, getEchartLineOptions } from './config';
+
+let echart = null;
+const formData = ref({pageNum: 1});
+const pageCount = ref(0);
+const echartRef = ref(null);
+const tableData = ref([]);
+const timeRangeValue = ref(null);
+
+const dateThemeOverrides = {
+  peers: {
+    Input: {
+      placeholderColor: '#86909C',
+    }
+  }
+}
+
+const dateRange = computed(() => {
+  let timeBegin = '';
+  let timeEnd = '';
+  if ( timeRangeValue.value && timeRangeValue.value.length ) {
+    const [begin, end] = timeRangeValue.value;
+    timeBegin = dayjs(begin).format('YYYY/MM/DD');
+    timeEnd = dayjs(end).format('YYYY/MM/DD');
+  }
+  return {
+    timeBegin,
+    timeEnd
+  }
+})
+
+const onDatePickerConfirm = (timeRange) => {
+  timeRangeValue.value = timeRange;
+  initEchartData();
+  getTableList();
+}
+
+const onDatePickerClear = () => {
+  timeRangeValue.value = null;
+  initEchartData();
+  getTableList();
+}
+
+const handlePageChange = (page) => {
+  formData.value.pageNum = page;
+  getTableList();
+}
+
+// 初始化ecahrt数据
+const initEchartData = async() => {
+  const { rows:data } = await dataApi.getList({pageNum: 1, pageSize: 2000, ...dateRange.value});
+
+  const xAxisData = [];
+  const seriesData1 = [];
+  const seriesData2 = [];
+  const seriesData3 = [];
+  const seriesData4 = [];
+  const seriesData5 = [];
+  const seriesData6 = [];
+  const seriesData7 = [];
+  const seriesData8 = [];
+
+  data.reverse().map(item => {
+    if ( item.vdate ) {
+      xAxisData.push(dayjs(item.vdateTime.trim()).format('YYYY/MM/DD'));
+
+      let qyckxsyAll1 = '';
+      let qyckxsyAll2 = '';
+
+      if ( item.qyckxsyAll ) {
+        const [ qy1, qy2 ] = JSON.parse(item.qyckxsyAll);
+        qyckxsyAll1 = qy1?.toFixed(2);
+        qyckxsyAll2 = qy2?.toFixed(2);
+      }
+
+      seriesData1.push(item.jsll?.toFixed(2));
+      seriesData2.push(item.no3Hlj1Jqr?.toFixed(2));
+      seriesData3.push(item.no3Hlj2Jqr?.toFixed(2));
+      seriesData4.push(item.nh31Jqr?.toFixed(2));
+      seriesData5.push(item.nh32Jqr?.toFixed(2));
+      seriesData6.push(qyckxsyAll1);
+      seriesData7.push(qyckxsyAll2);
+      seriesData8.push(item.tpRccJqr?.toFixed(2));
+    }
+  })
+
+  const seriesData = [
+    { name: "进水流量", data: seriesData1 },
+    { name: "#1好氧池硝酸盐", data: seriesData2 },
+    { name: "#2好氧池硝酸盐", data: seriesData3 },
+    { name: "#1缺氧氨氮", data: seriesData4 },
+    { name: "#2缺氧氨氮", data: seriesData5 },
+    { name: "#1缺氧硝酸盐", data: seriesData6 },
+    { name: "#2缺氧硝酸盐", data: seriesData7 },
+    { name: "二沉池正鳞酸盐", data: seriesData8 },
+  ]
+
+  const option = getEchartLineOptions({ xAxisData, seriesData: seriesData });
+  echart.setOption(option);
+}
+
+const windowResize = () => echart.resize();
+
+// 请求数据
+const getTableList = async() => {
+  const { rows, total } = await dataApi.getList({...formData.value, ...dateRange.value});
+  tableData.value = rows;
+  pageCount.value = total;
+}
+
+onMounted(() => {
+  initEchartData();
+  getTableList();
+  echart = echarts.init(echartRef.value, 'light');
+  window.addEventListener("resize", windowResize);
+})
+
+onUnmounted(() => {
+  window.removeEventListener("resize", windowResize);
+  echart && echart.dispose();
+})
+</script>
 
 <template>
   <section class="flex items-start h-full">
     <TheChatView leftTitle="数据分析" :isChatSlot="false" :isFooter="false">
       <template #control>
         <div class="data-container">
-          123123123
+          <div class="header">
+            <h4 class="title">智能化验室数据</h4>
+            <nDatePicker type="daterange" size="small" clearable input-readonly :themeOverrides="dateThemeOverrides" :on-confirm="onDatePickerConfirm" :on-clear="onDatePickerClear"/>
+          </div>
+
+          <div class="main">
+            <NScrollbar style="height: 100%;">
+              <div class="echart-box">
+                <p class="title">检测数据趋势图「小时」</p>
+                <div class="h-full w-full" ref="echartRef"></div>
+              </div>
+              <div class="table-box w-full">
+                <p class="title">表格数据</p>
+                <div class="pt-[12px]">
+                  <n-data-table
+                    size="small"
+                    :columns="columns"
+                    :data="tableData"
+                    :bordered="false"
+                    :scroll-x="700"
+                    layout="table"
+                  />
+                </div>
+                <div class="flex justify-center mt-[14px] mb-[10px]">
+                  <NPagination v-model:page="formData.pageNum" :item-count="pageCount" :on-update:page="handlePageChange"></NPagination>
+                </div>
+              </div>
+            </NScrollbar>
+          </div>
         </div>
       </template>
     </TheChatView>
@@ -14,7 +170,46 @@
 
 <style lang="scss" scoped>
 .data-container{
+  display: flex;
+  flex-flow: column;
+  // max-width: 1200px;
   height: 100%;
-  background: pink;
+  border-radius: 10px;
+  background: #fff;
+
+  :deep(.n-input--stateful) {
+    background: #F2F3F5 !important;
+  }
+}
+
+.header {
+  flex-shrink: 0;
+  height: 60px;
+  padding: 16px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  .title {
+    color: #333;
+    font-weight: bold;
+    font-size: 15px;
+  }
+}
+
+.main {
+  flex:1;
+  height: 100%;
+  padding: 0 16px;
+  overflow: hidden;
+
+  .echart-box {
+    height: 300px;
+    background: #fff;
+  }
+
+  .table-box {
+    padding-top: 20px;
+  }
 }
 </style>

+ 403 - 0
src/views/data/config.js

@@ -0,0 +1,403 @@
+import dayjs from "dayjs";
+const formatTofixed = val => val != 0 && !val ? '' : val.toFixed(2);
+// const formatTofixed = val => val
+
+export const columns = [
+  {
+    title: "检测日期",
+    key: "vdateTime",
+    width: 80,
+    fixed: "left",
+    render: ({ vdateTime }) => dayjs(vdateTime).format("YYYY-MM-DD")
+  },
+  {
+    title: "检测小时",
+    key: "testHour",
+    width: 60,
+    render: ({ testHour }) => dayjs(testHour).format("HH")
+  },
+  {
+    title: "进水流量",
+    key: "jsll",
+    width: 100,
+    render: ({ jsll }) => formatTofixed(jsll)
+  },
+  {
+    title: "#1好氧池硝酸盐",
+    key: "no3Hlj1Jqr",
+    width: 100,
+    render: ({ no3Hlj1Jqr }) => formatTofixed(no3Hlj1Jqr)
+  },
+  {
+    title: "#2好氧池硝酸盐",
+    key: "no3Hlj2Jqr",
+    width: 100,
+    render: ({ no3Hlj2Jqr }) => formatTofixed(no3Hlj2Jqr)
+  },
+  {
+    title: "#1缺氧氨氮",
+    key: "nh31Jqr",
+    width: 100,
+    render: ({ nh31Jqr }) => formatTofixed(nh31Jqr)
+  },
+  {
+    title: "#2缺氧氨氮",
+    key: "nh32Jqr",
+    width: 100,
+    render: ({ nh32Jqr }) => formatTofixed(nh32Jqr)
+  },
+  {
+    title: "#1缺氧硝酸盐",
+    key: "qyckxsyAll1",
+    width: 100,
+    render: ({ qyckxsyAll }) => {
+      let num = ''
+      if ( qyckxsyAll ) {
+        const n  = JSON.parse(qyckxsyAll)[0];
+        num = formatTofixed(n);
+      }
+      return num;
+    }
+  },
+  {
+    title: "#2缺氧硝酸盐",
+    key: "qyckxsyAll2",
+    width: 100,
+    render: ({ qyckxsyAll }) =>{
+      let num = ''
+      if ( qyckxsyAll ) {
+        const n  = JSON.parse(qyckxsyAll)[1];
+        num = formatTofixed(n);
+      }
+      return num;
+    }
+  },
+  // {
+  //   title: "好氧池正磷酸盐",
+  //   key: "hyzlsyAll",
+  //   width: 120,
+  //   render: ({ hyzlsyAll }) => formatTofixed(hyzlsyAll)
+  // },
+  {
+    title: "二沉池正鳞酸盐",
+    key: "tpRccJqr",
+    width: 100,
+    render: ({ tpRccJqr }) => formatTofixed(tpRccJqr)
+  },
+];
+
+export const ForecastColumns = [
+  {
+    title: "时间",
+    key: "remark",
+    width: 140,
+    fixed: "left",
+    // render: ({ vdateTime }) => dayjs(vdateTime).format("YYYY-MM-DD")
+  },
+  {
+    title: "预测类型",
+    key: "category",
+    width: 120,
+  },
+  {
+    title: "预测1时间",
+    key: "forecastTimeOne",
+    width: 130,
+    // render: ({ forecastTimeOne }) => dayjs(forecastTimeOne).format("HH")
+  },
+  {
+    title: "实际1小时值",
+    key: "realOne",
+    width: 100,
+    render: ({ realOne }) => formatTofixed(realOne)
+  },
+  {
+    title: "预测1小时值",
+    key: "hsForecastOne",
+    width: 100,
+    render: ({ hsForecastOne }) => formatTofixed(hsForecastOne)
+  },
+  {
+    title: "预测1小时误差值",
+    key: "hsOneSubtract",
+    width: 130,
+    render: ({ hsOneSubtract }) => formatTofixed(hsOneSubtract)
+  },
+  {
+    title: "预测1小时百分比误差",
+    key: "hsErrorRateOneStr",
+    width: 160,
+  },
+  {
+    title: "预测2时间",
+    key: "forecastTimeTwo",
+    width: 130,
+  },
+  {
+    title: "实际2小时值",
+    key: "realTwo",
+    width: 100,
+    render: ({ realTwo }) => formatTofixed(realTwo)
+  },
+  {
+    title: "预测2小时值",
+    key: "hsForecastTwo",
+    width: 100,
+    render: ({ hsForecastTwo }) => formatTofixed(hsForecastTwo)
+  },
+  {
+    title: "预测2误差值",
+    key: "hsTwoSubtract",
+    width: 100,
+    render: ({ hsTwoSubtract }) => formatTofixed(hsTwoSubtract)
+  },
+  {
+    title: "预测2小时百分比误差",
+    key: "hsErrorRateTwoStr",
+    width: 160,
+  },
+  {
+    title: "预测3时间",
+    key: "forecastTimeThree",
+    width: 130,
+    // render: ({ forecastTimeThree }) => dayjs(forecastTimeThree).format("HH")
+  },
+  {
+    title: "实际3小时值",
+    key: "realThree",
+    width: 100,
+    render: ({ realThree }) => formatTofixed(realThree)
+  },
+  {
+    title: "预测3小时值",
+    key: "hsForecastThree",
+    width: 100,
+    render: ({ hsForecastThree }) => formatTofixed(hsForecastThree)
+  },
+  {
+    title: "预测3误差值",
+    key: "hsThreeSubtract",
+    width: 160,
+    render: ({ hsThreeSubtract }) => formatTofixed(hsThreeSubtract)
+  },
+  {
+    title: "预测3小时百分比误差",
+    key: "hsErrorRateThreeStr",
+    width: 160,
+  },
+];
+
+export const getEchartLineOptions = ({ xAxisData, seriesData }) => {
+  const colorList = [
+    "#FF6737",
+    "#00AB84",
+    "#85E822",
+    "#21CCFF",
+    "#FFE122",
+    "#313CA9",
+    "#F023FF",
+    "#FFD22E",
+    "#2181FF",
+    "#F22",
+    "#37DDE0",
+  ];
+
+  const series = seriesData.map((item, index) => {
+    return {
+      name: item.name,
+      type: "line",
+      smooth: true,
+      showSymbol: false,
+      itemStyle: {
+        opacity: 0.8,
+        color: colorList[index],
+      },
+      // tooltip: {
+      //   valueFormatter: function (value) {
+      //     return value;
+      //   },
+      // },
+      data: item.data,
+    };
+  });
+
+  const option = {
+    grid: {
+      top: 80,
+      bottom: 50,
+      left: 40,
+      right: 30,
+    },
+    tooltip: {
+      trigger: "axis",
+      // confine: true,
+      appendToBody: true,
+    },
+    title: {
+      show: !xAxisData.length,
+      text: '暂无数据',
+      x: 'center',
+      y: 'center',
+      textStyle: {
+        fontSize: 14,
+        fontWeight: 'normal',
+      }
+    },
+    legend: [
+      {
+        show: !!xAxisData.length,
+        data: ["进水流量", "#1好氧池硝酸盐", "#1缺氧氨氮", "#1缺氧硝酸盐"],
+        top: 10,
+        left: 10,
+        icon: "rect",
+        itemWidth: 10,
+        itemHeight: 10,
+        textStyle: {
+          color: "rgba(0,0,0,0.65)",
+        },
+        selected: {
+          '进水流量':false
+        }
+      },
+      {
+        show: !!xAxisData.length,
+        data: ["二沉池正鳞酸盐", "#2好氧池硝酸盐", "#2缺氧氨氮", "#2缺氧硝酸盐"],
+        top: 35,
+        left: 10,
+        icon: "rect",
+        itemWidth: 10,
+        itemHeight: 10,
+        icon: "rect",
+        itemWidth: 10,
+        itemHeight: 10,
+        textStyle: {
+          color: "rgba(0,0,0,0.65)",
+        },
+      },
+    ],
+    xAxis: [
+      {
+        type: "category",
+        data: xAxisData || [],
+        boundaryGap: false,
+        axisTick: {
+          show: false,
+        },
+        axisLine: {
+          lineStyle: {
+            color: "#DAE0E1",
+          },
+        },
+        axisLabel: {
+          align: "center",
+          color: "#828282",
+          margin: 10,
+        },
+      },
+    ],
+    yAxis: [
+      {
+        scale: false,
+        minInterval: 1,
+        backgroundColor: "#FFFFFF",
+        splitLine: {
+          lineStyle: {
+            color: ["#DAE0E1"],
+            shadowColor: "#DAE0E1",
+            shadowBlur: 1,
+            opacity: 1,
+            width: 1,
+            type: "dashed",
+          },
+        },
+        axisLine: {
+          show: false,
+        },
+        axisLabel: {
+          align: "center",
+          color: "#828282",
+          margin: 20,
+        },
+      },
+    ],
+    series
+  };
+  return option;
+};
+
+
+export const getEchartMultiLineOption = ({ xAxisData, echartData, specificData }) => {
+  const colors = ["#5B8FF9", "#82c370", "#ffbf59"];
+  const series = echartData.map(({ name, val: data }, index) => ({
+    name,
+    data,
+    type: "line",
+    showSymbol: false,
+    lineStyle: {
+      width: 2,
+      color: colors[index],
+    },
+    smooth: true,
+    xAxisIndex: 0,
+  }));
+
+  series.push({
+    name: '预测值',
+    tooltip: {
+      show: false
+    },
+    type: "line",
+    symbolSize: 8,
+    itemStyle: {
+      color: "red",
+    },
+    itemStyle: {
+      color: colors[0],
+    },
+    xAxisIndex: 0,
+    data: specificData,
+  });
+
+  const option = {
+    dataZoom: [{
+      bottom: 10,
+      height: 20,
+      show: true,
+      start: 0,
+      end: 100,
+      xAxisIndex: [0],
+    }],
+    grid: {
+      left: "5%",
+      top: "10%",
+      right: "12%",
+      bottom: '18%',
+      containLabel: true,
+    },
+    legend: {
+      left: "left",
+      icon: "circle",
+      orient: "vertical",
+      left: "88%",
+      top: "center",
+      itemWidth: 8,
+      itemHeight: 8,
+      padding: [0, 30]
+    },
+    tooltip: {
+      trigger: "axis",
+    },
+    xAxis: [
+      {
+        type: "category",
+        boundaryGap: false,
+        data: xAxisData,
+      },
+    ],
+    yAxis: {
+      type: "value",
+    },
+    series,
+  };
+  return option;
+};