|
@@ -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>
|