<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'; 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 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: '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(() => { 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: '6%', right: '6%', }, 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 type ? dayjs(value).format('YYYY/MM/DD') : value } }, 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); echartDataSource.value = echartData; onSwitchEchart({ value: tabActive.value.substring(0, tabActive.value.indexOf('-')) }); } // 系数相关数据 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)); } // 日期范围限制 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> <!-- 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"></div> </div> </div> </template> <style lang="scss" scoped> .echart-card_view { display: flex; flex-flow: column; height: calc(100% - 256px); 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>