|
@@ -0,0 +1,500 @@
|
|
|
+<script setup>
|
|
|
+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';
|
|
|
+
|
|
|
+let echart = null;
|
|
|
+let tempTabItemOneKey = 0;
|
|
|
+let tempTabItemTwoKey = 'jzxsOne';
|
|
|
+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 echartOptions = [
|
|
|
+ { label: "进水流量", value: 0, style: "font-size: 12px" },
|
|
|
+ { label: "#1好氧池硝酸盐", value: 1, style: "font-size: 12px" },
|
|
|
+ { label: "#2好氧池硝酸盐", value: 2, style: "font-size: 12px" },
|
|
|
+ { label: "#1缺氧池氨氮", value: 3, style: "font-size: 12px" },
|
|
|
+ { label: "#2缺氧池氨氮", value: 4, style: "font-size: 12px" },
|
|
|
+ { label: "进水COD", value: 5, style: "font-size: 12px" },
|
|
|
+ { label: "进水总氮", value: 6, style: "font-size: 12px" },
|
|
|
+ { label: "碳源投加量", value: 7, style: "font-size: 12px" }
|
|
|
+]
|
|
|
+
|
|
|
+const coefficientOptions = [
|
|
|
+ { label: "基准系数", value: 'jzxsOne', style: "font-size: 12px" },
|
|
|
+ { label: "修正系数", value: 'xzxsOne', style: "font-size: 12px" },
|
|
|
+ { label: "水量分配系数", value: 'slfpxsOne', style: "font-size: 12px" },
|
|
|
+ { label: "碳源当量", value: 'tydlOne', style: "font-size: 12px" },
|
|
|
+ { label: "转换系数", value: 'zhxsOne', style: "font-size: 12px" },
|
|
|
+ { label: "稀释倍数", value: 'sxpsOne', style: "font-size: 12px" },
|
|
|
+ { label: "密度", value: 'yymdOne', style: "font-size: 12px" },
|
|
|
+]
|
|
|
+
|
|
|
+const seriesName = computed(() => {
|
|
|
+ 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, type) => {
|
|
|
+ const option = {
|
|
|
+ backgroundColor: '#FFF',
|
|
|
+ title: {
|
|
|
+ show: !data.length,
|
|
|
+ text: '暂无数据',
|
|
|
+ x: 'center',
|
|
|
+ y: 'center',
|
|
|
+ textStyle: {
|
|
|
+ fontSize: 14,
|
|
|
+ fontWeight: 'normal',
|
|
|
+ }
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ top: '40px',
|
|
|
+ bottom: '50px',
|
|
|
+ left: '5%',
|
|
|
+ right: '5%',
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ label: {
|
|
|
+ show: true
|
|
|
+ },
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ boundaryGap: false,
|
|
|
+ axisLine: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ axisTick: {
|
|
|
+ show: false,
|
|
|
+ alignWithLabel: true
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ formatter: function (value) {
|
|
|
+ return dayjs(value).format('YYYY/MM/DD')
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data: data.map(({ time }) => time)
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ axisLine: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ show: true,
|
|
|
+ lineStyle: {
|
|
|
+ type: 'dashed',
|
|
|
+ color: '#E5E5E5'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisTick: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ splitArea: {
|
|
|
+ show: false,
|
|
|
+ color: '#fff'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: seriesName.value,
|
|
|
+ showSymbol: false,
|
|
|
+ smooth: true,
|
|
|
+ type: 'line',
|
|
|
+ symbolSize: 10,
|
|
|
+ lineStyle: {
|
|
|
+ color: '#17a6fa',
|
|
|
+ shadowBlur: 12,
|
|
|
+ shadowColor: 'rgba(0, 0, 0, 0.12)',
|
|
|
+ shadowOffsetX: 0,
|
|
|
+ shadowOffsetY: 4,
|
|
|
+ width: 2,
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ color: '#4080FF',
|
|
|
+ borderWidth: 3,
|
|
|
+ borderColor: '#4080FF'
|
|
|
+ },
|
|
|
+ areaStyle: {
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
|
|
+ offset: 0,
|
|
|
+ color: 'rgba(0, 136, 212, 0.2)'
|
|
|
+ }, {
|
|
|
+ offset: 1,
|
|
|
+ color: 'rgba(0, 136, 212, 0)'
|
|
|
+ }], false),
|
|
|
+ },
|
|
|
+ data: data.map(({ val }) => val)
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+ return option;
|
|
|
+}
|
|
|
+
|
|
|
+const onSwitchEchart = (item) => {
|
|
|
+ const echartData = echartDataSource.value[item.value];
|
|
|
+ isEmpty.value = !!echartData.length
|
|
|
+ echart.setOption(getEchartOptions(echartData));
|
|
|
+}
|
|
|
+
|
|
|
+// 水务相关数据格式化
|
|
|
+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: '在线仪表',
|
|
|
+ HY: '连续检测',
|
|
|
+ YC: '预测'
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ // tabs.value = Object.keys(echartData).map((key, index) => {
|
|
|
+ // if (index === 0) {
|
|
|
+ // tabActive.value = key + '-' + selectValue.value + '-' + index;
|
|
|
+ // }
|
|
|
+ // if (echartData[key].length) {
|
|
|
+ // return ({ label: enumSource[key], value: key });
|
|
|
+ // }
|
|
|
+ // }).filter(Boolean);
|
|
|
+
|
|
|
+ // console.log( "tabs.value", tabs.value );
|
|
|
+
|
|
|
+ echartDataSource.value = echartData;
|
|
|
+
|
|
|
+ onSwitchEchart({ value: tabActive.value.substring(0, tabActive.value.indexOf('-')) });
|
|
|
+}
|
|
|
+
|
|
|
+// 系数相关数据
|
|
|
+const intiCoefficientEchartData = async () => {
|
|
|
+ const [timeBegin, timeEnd] = datePickerValue.value || [];
|
|
|
+ const { data } = await controlApi.getBoardEchartList({ 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));
|
|
|
+}
|
|
|
+
|
|
|
+// 日期范围限制
|
|
|
+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();
|
|
|
+}
|
|
|
+
|
|
|
+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-card_view">
|
|
|
+ <div class="title">
|
|
|
+ <div class="left-inner">
|
|
|
+ <span class="text">数据看板</span>
|
|
|
+ </div>
|
|
|
+ <div class="right-inner">
|
|
|
+ <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>
|
|
|
+ <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"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.echart-card_view {
|
|
|
+ display: flex;
|
|
|
+ flex-flow: column;
|
|
|
+ height: calc(100% - 284px);
|
|
|
+ padding: 0px 16px 0 25px;
|
|
|
+ border-radius: 10px;
|
|
|
+
|
|
|
+ .title {
|
|
|
+ flex-shrink: 0;
|
|
|
+ @include flex(x, center, between);
|
|
|
+ padding-bottom: 16px;
|
|
|
+
|
|
|
+ .left-inner {
|
|
|
+ @include flex(x, center, start);
|
|
|
+
|
|
|
+ .text {
|
|
|
+ color: #1A2029;
|
|
|
+ font-size: 15px;
|
|
|
+ font-style: normal;
|
|
|
+ font-weight: 500;
|
|
|
+ line-height: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tabs {
|
|
|
+ width: 240px;
|
|
|
+ margin-left: 16px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .right-inner {
|
|
|
+ @include flex(x, center, start);
|
|
|
+
|
|
|
+ .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: calc(100% - 72px);
|
|
|
+
|
|
|
+ .echart,
|
|
|
+ .empty {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .empty {
|
|
|
+ @include flex(x, center, center);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|
|
|
+
|
|
|
+<style lang="scss">
|
|
|
+.echart-card_view {
|
|
|
+ .tabs {
|
|
|
+ .n-tabs-tab--active {
|
|
|
+ .n-tabs-tab__label {
|
|
|
+ color: #2454FF;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .n-tabs-tab__label {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #333333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .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-label {
|
|
|
+ border: 0;
|
|
|
+ border-radius: 0;
|
|
|
+ background: #F2F3F5 !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .n-base-selection__state-border,
|
|
|
+ .n-base-selection__border {
|
|
|
+ border: 0 !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .n-base-selection .n-base-selection__border,
|
|
|
+ .n-base-selection .n-base-selection__state-border {
|
|
|
+ box-shadow: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .n-base-suffix__arrow {
|
|
|
+ color: #4E5969;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .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>
|