|
|
@@ -0,0 +1,756 @@
|
|
|
+<template>
|
|
|
+ <div class="flex flex-col gap-4 h-full">
|
|
|
+ <div class="grid grid-cols-1 gap-4 overflow-hidden max-sm:gap-2 lg:grid-cols-12 flex-1">
|
|
|
+ <div class="col-span-1 lg:col-span-5">
|
|
|
+ <NCard content-style="padding: 0;">
|
|
|
+ <div
|
|
|
+ class="rounded bg-naive-card px-5 pt-5 pb-3 transition-[background-color]"
|
|
|
+ style="height: 270px"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ ref="monthlyRadarChart"
|
|
|
+ class="h-full"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </NCard>
|
|
|
+ </div>
|
|
|
+ <div class="col-span-1 lg:col-span-7">
|
|
|
+ <NCard content-style="padding: 0;">
|
|
|
+ <div
|
|
|
+ class="rounded bg-naive-card p-5 transition-[background-color]"
|
|
|
+ style="height: 270px; position: relative"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ ref="highestRevenueChart"
|
|
|
+ class="h-full"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </NCard>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="grid grid-cols-1 gap-4 overflow-hidden max-sm:gap-2 lg:grid-cols-12 flex-1">
|
|
|
+ <div class="col-span-1 lg:col-span-12">
|
|
|
+ <NCard content-style="padding: 0;">
|
|
|
+ <div
|
|
|
+ class="rounded bg-naive-card px-5 pt-5 pb-4.5 shadow-xs transition-[background-color]"
|
|
|
+ style="height: 270px"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ ref="revenueChart"
|
|
|
+ class="h-full"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </NCard>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import chroma from 'chroma-js'
|
|
|
+import * as echarts from 'echarts'
|
|
|
+
|
|
|
+import { toRefsPreferencesStore } from '@/stores'
|
|
|
+const { isDark, themeColor } = toRefsPreferencesStore()
|
|
|
+import { NCard } from 'naive-ui'
|
|
|
+import { onMounted, onUnmounted, ref } from 'vue'
|
|
|
+
|
|
|
+import twc from '@/utils/tailwindColor'
|
|
|
+
|
|
|
+import type { ECharts } from 'echarts'
|
|
|
+
|
|
|
+const revenueChart = ref<HTMLDivElement | null>(null)
|
|
|
+let revenueChartInstance: ECharts | null = null
|
|
|
+let revenueChartResizeHandler: (() => void) | null = null
|
|
|
+
|
|
|
+const monthlyRadarChart = ref<HTMLDivElement | null>(null)
|
|
|
+let monthlyRadarChartInstance: ECharts | null = null
|
|
|
+let monthlyRadarChartResizeHandler: (() => void) | null = null
|
|
|
+
|
|
|
+const highestRevenueChart = ref<HTMLDivElement | null>(null)
|
|
|
+let highestRevenueChartInstance: ECharts | null = null
|
|
|
+let highestRevenueChartResizeHandler: (() => void) | null = null
|
|
|
+let collapseResizeTimeout: ReturnType<typeof setTimeout> | null = null
|
|
|
+
|
|
|
+const createTooltipConfig = (formatter?: any) => ({
|
|
|
+ trigger: 'axis',
|
|
|
+ backgroundColor: isDark.value ? twc.neutral[750] : '#fff',
|
|
|
+ borderWidth: 0,
|
|
|
+ padding: 8,
|
|
|
+ extraCssText: 'box-shadow: none;',
|
|
|
+ textStyle: {
|
|
|
+ color: isDark.value ? twc.neutral[400] : twc.neutral[600],
|
|
|
+ fontSize: 12,
|
|
|
+ },
|
|
|
+ axisPointer: {
|
|
|
+ type: 'none',
|
|
|
+ },
|
|
|
+ ...(formatter && { formatter }),
|
|
|
+})
|
|
|
+
|
|
|
+const chartDataManager = {
|
|
|
+ get30DayLine: () => {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ // 30天活动分布
|
|
|
+ resolve([
|
|
|
+ {
|
|
|
+ label: '1月',
|
|
|
+ value: [111, 333, 444, 555],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '2月',
|
|
|
+ value: [222, 444, 555, 666],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '3月',
|
|
|
+ value: [333, 555, 666, 777],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '4月',
|
|
|
+ value: [444, 666, 777, 888],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '5月',
|
|
|
+ value: [555, 777, 888, 999],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '6月',
|
|
|
+ value: [555, 777, 888, 999],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '7月',
|
|
|
+ value: [555, 777, 888, 999],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '8月',
|
|
|
+ value: [666, 888, 999, 1000],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '9月',
|
|
|
+ value: [777, 999, 1000, 1100],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '10月',
|
|
|
+ value: [555, 777, 888, 999],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '11月',
|
|
|
+ value: [444, 666, 777, 888],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '12月',
|
|
|
+ value: [555, 777, 888, 999],
|
|
|
+ },
|
|
|
+ ])
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ getLowestWeekLine: () => {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ resolve([
|
|
|
+ {
|
|
|
+ label: '周一',
|
|
|
+ value: [1000, 5000, 8000, 1000],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '周二',
|
|
|
+ value: [4000, 6000, 9000, 1000],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '周三',
|
|
|
+ value: [3000, 7000, 10000, 5000],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '周四',
|
|
|
+ value: [5000, 8000, 12000, 6000],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '周五',
|
|
|
+ value: [4000, 6000, 9000, 1000],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '周六',
|
|
|
+ value: [6000, 8000, 10000, 2000],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '周日',
|
|
|
+ value: [7000, 9000, 600, 3000],
|
|
|
+ },
|
|
|
+ ])
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ getCurrentDayData: () => {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ resolve([
|
|
|
+ {
|
|
|
+ label: '1月',
|
|
|
+ value: [111],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '2月',
|
|
|
+ value: [222],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '3月',
|
|
|
+ value: [333],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '4月',
|
|
|
+ value: [444],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '5月',
|
|
|
+ value: [555],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '6月',
|
|
|
+ value: [555],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '7月',
|
|
|
+ value: [555],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '8月',
|
|
|
+ value: [666],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '9月',
|
|
|
+ value: [777],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '10月',
|
|
|
+ value: [555],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '11月',
|
|
|
+ value: [444],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '12月',
|
|
|
+ value: [555],
|
|
|
+ },
|
|
|
+ ])
|
|
|
+ })
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+async function initRevenueChart() {
|
|
|
+ if (!revenueChart.value) return
|
|
|
+
|
|
|
+ const lineData = (await chartDataManager.getLowestWeekLine()) as Array<{
|
|
|
+ label: string
|
|
|
+ value: number
|
|
|
+ }>
|
|
|
+ const lineX = lineData.map((item) => item.label)
|
|
|
+ const lineY = lineData.map((item) => item.value)
|
|
|
+ const legend = [
|
|
|
+ {
|
|
|
+ name: '大型鸟类',
|
|
|
+ color: '#EF4444', // 红色
|
|
|
+ data: lineY.map((data: any) => data[0]),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '中型鸟类',
|
|
|
+ color: '#F59E0B', // 黄色
|
|
|
+ data: lineY.map((data: any) => data[1]),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '小型鸟类',
|
|
|
+ color: '#3B82F6', // 蓝色
|
|
|
+ data: lineY.map((data: any) => data[2]),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '未知',
|
|
|
+ color: '#10B981', // 绿色
|
|
|
+ data: lineY.map((data: any) => data[3]),
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const chart = echarts.init(revenueChart.value)
|
|
|
+
|
|
|
+ const option = {
|
|
|
+ title: [
|
|
|
+ {
|
|
|
+ text: '近期活动趋势',
|
|
|
+ left: 0,
|
|
|
+ top: 0,
|
|
|
+ textStyle: {
|
|
|
+ fontSize: 18,
|
|
|
+ color: isDark.value ? twc.neutral[400] : twc.neutral[600],
|
|
|
+ fontWeight: 'normal',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ tooltip: createTooltipConfig((params: any) => {
|
|
|
+ const date = params[0].axisValue
|
|
|
+ let result = `<div>${date}数据</div>`
|
|
|
+ params.forEach((item: any) => {
|
|
|
+ const value = item.value.toLocaleString()
|
|
|
+ result += `
|
|
|
+ <div style="display: flex; align-items: center; margin-top: 4px;">
|
|
|
+ <span style="display:inline-block; margin-right:4px; width:10px; height:10px; border-radius:50%; background-color:${item.color};"></span>
|
|
|
+ <span style="margin-right: 10px">${item.seriesName}</span>
|
|
|
+ <span>${value}</span>
|
|
|
+ </div>
|
|
|
+ `
|
|
|
+ })
|
|
|
+ return result
|
|
|
+ }),
|
|
|
+ legend: {
|
|
|
+ top: 6,
|
|
|
+ right: 22,
|
|
|
+ itemGap: 16,
|
|
|
+ icon: 'circle',
|
|
|
+ itemWidth: 12,
|
|
|
+ itemHeight: 12,
|
|
|
+ textStyle: {
|
|
|
+ color: isDark.value ? twc.neutral[400] : twc.neutral[600],
|
|
|
+ fontSize: 14,
|
|
|
+ },
|
|
|
+ data: legend.map((item) => item.name),
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ left: 20,
|
|
|
+ right: 20,
|
|
|
+ top: 72,
|
|
|
+ bottom: 0,
|
|
|
+ containLabel: true,
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ boundaryGap: false,
|
|
|
+ data: lineX,
|
|
|
+ axisLine: { show: false },
|
|
|
+ axisTick: { show: false },
|
|
|
+ axisLabel: {
|
|
|
+ color: isDark.value ? twc.neutral[400] : twc.neutral[600],
|
|
|
+ fontSize: 12,
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ show: true,
|
|
|
+ lineStyle: {
|
|
|
+ type: 'dashed',
|
|
|
+ color: isDark.value ? 'rgba(255, 255, 255, 0.08)' : 'rgba(0, 0, 0, 0.08)',
|
|
|
+ width: 1,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ min: 0,
|
|
|
+ axisLine: { show: false },
|
|
|
+ axisTick: { show: false },
|
|
|
+ axisLabel: {
|
|
|
+ color: isDark.value ? twc.neutral[400] : twc.neutral[600],
|
|
|
+ fontSize: 11,
|
|
|
+ formatter(value: number) {
|
|
|
+ if (value === 0) return '0'
|
|
|
+ return `${(value / 1000).toFixed(0)},000`
|
|
|
+ },
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ show: true,
|
|
|
+ lineStyle: {
|
|
|
+ type: 'dashed',
|
|
|
+ color: isDark.value ? 'rgba(255, 255, 255, 0.08)' : 'rgba(0, 0, 0, 0.08)',
|
|
|
+ width: 1,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ series: legend.map((line, idx) => ({
|
|
|
+ name: line.name,
|
|
|
+ type: 'line',
|
|
|
+ data: line.data,
|
|
|
+ symbol: 'circle',
|
|
|
+ showSymbol: false,
|
|
|
+ smooth: true,
|
|
|
+ lineStyle: {
|
|
|
+ color: line.color,
|
|
|
+ width: idx < 2 ? 2 : 1,
|
|
|
+ type: idx < 2 ? 'solid' : 'dashed',
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ color: line.color,
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ focus: 'series',
|
|
|
+ symbolSize: 6,
|
|
|
+ itemStyle: {
|
|
|
+ color: line.color,
|
|
|
+ borderColor: line.color,
|
|
|
+ borderWidth: 2,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ areaStyle: {
|
|
|
+ color: {
|
|
|
+ type: 'linear',
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ x2: 0,
|
|
|
+ y2: 1,
|
|
|
+ colorStops: [
|
|
|
+ { offset: 0, color: chroma(line.color).alpha(0.12).hex() },
|
|
|
+ { offset: 1, color: chroma(line.color).alpha(0.02).hex() },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ })),
|
|
|
+
|
|
|
+ animationDuration: 1000,
|
|
|
+ animationEasing: 'cubicOut' as const,
|
|
|
+ animationDelay(idx: number) {
|
|
|
+ return idx * 100
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ chart.setOption(option)
|
|
|
+
|
|
|
+ revenueChartInstance = chart
|
|
|
+ revenueChartResizeHandler = () => chart.resize()
|
|
|
+ window.addEventListener('resize', revenueChartResizeHandler, { passive: true })
|
|
|
+}
|
|
|
+
|
|
|
+async function initMonthlyRadarChart() {
|
|
|
+ if (!monthlyRadarChart.value) return
|
|
|
+
|
|
|
+ // const now = new Date()
|
|
|
+ // const currentDay = now.getDay()
|
|
|
+
|
|
|
+ const currentDayData = (await chartDataManager.getCurrentDayData()) as Array<{
|
|
|
+ label: string
|
|
|
+ value: number
|
|
|
+ }>
|
|
|
+ const lineX = currentDayData.map((item) => item.label)
|
|
|
+ const lineY = currentDayData.map((item) => item.value)
|
|
|
+ const legend = [
|
|
|
+ {
|
|
|
+ name: '大型鸟类',
|
|
|
+ color: themeColor.value,
|
|
|
+ data: lineY.map((data: any) => data[0]),
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const chart = echarts.init(monthlyRadarChart.value)
|
|
|
+
|
|
|
+ const option = {
|
|
|
+ title: [
|
|
|
+ {
|
|
|
+ text: '月度检测趋势',
|
|
|
+ left: 0,
|
|
|
+ top: 0,
|
|
|
+ textStyle: {
|
|
|
+ fontSize: 18,
|
|
|
+ color: isDark.value ? twc.neutral[400] : twc.neutral[600],
|
|
|
+ fontWeight: 'normal',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ tooltip: createTooltipConfig((params: any) => {
|
|
|
+ const date = params[0].axisValue
|
|
|
+ let result = `<div>${date}数据</div>`
|
|
|
+ params.forEach((item: any) => {
|
|
|
+ const value = item.value.toLocaleString()
|
|
|
+ result += `
|
|
|
+ <div style="display: flex; align-items: center; margin-top: 4px;">
|
|
|
+ <span style="display:inline-block; margin-right:4px; width:10px; height:10px; border-radius:50%; background-color:${item.color};"></span>
|
|
|
+ <span style="margin-right: 10px">${item.seriesName}</span>
|
|
|
+ <span>${value}</span>
|
|
|
+ </div>
|
|
|
+ `
|
|
|
+ })
|
|
|
+ return result
|
|
|
+ }),
|
|
|
+ legend: {
|
|
|
+ show: false,
|
|
|
+ top: 6,
|
|
|
+ right: 22,
|
|
|
+ itemGap: 16,
|
|
|
+ icon: 'circle',
|
|
|
+ itemWidth: 12,
|
|
|
+ itemHeight: 12,
|
|
|
+ textStyle: {
|
|
|
+ color: isDark.value ? twc.neutral[400] : twc.neutral[600],
|
|
|
+ fontSize: 14,
|
|
|
+ },
|
|
|
+ data: legend.map((item) => item.name),
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ left: 20,
|
|
|
+ right: 20,
|
|
|
+ top: 72,
|
|
|
+ bottom: 0,
|
|
|
+ containLabel: true,
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ boundaryGap: false,
|
|
|
+ data: lineX,
|
|
|
+ axisLine: { show: false },
|
|
|
+ axisTick: { show: false },
|
|
|
+ axisLabel: {
|
|
|
+ color: isDark.value ? twc.neutral[400] : twc.neutral[600],
|
|
|
+ fontSize: 12,
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ show: true,
|
|
|
+ lineStyle: {
|
|
|
+ type: 'dashed',
|
|
|
+ color: isDark.value ? 'rgba(255, 255, 255, 0.08)' : 'rgba(0, 0, 0, 0.08)',
|
|
|
+ width: 1,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ min: 0,
|
|
|
+ axisLine: { show: false },
|
|
|
+ axisTick: { show: false },
|
|
|
+ axisLabel: {
|
|
|
+ color: isDark.value ? twc.neutral[400] : twc.neutral[600],
|
|
|
+ fontSize: 11,
|
|
|
+ formatter(value: number) {
|
|
|
+ if (value === 0) return '0'
|
|
|
+ return `${(value / 1000).toFixed(0)},000`
|
|
|
+ },
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ show: true,
|
|
|
+ lineStyle: {
|
|
|
+ type: 'dashed',
|
|
|
+ color: isDark.value ? 'rgba(255, 255, 255, 0.08)' : 'rgba(0, 0, 0, 0.08)',
|
|
|
+ width: 1,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ series: legend.map((line, idx) => ({
|
|
|
+ name: line.name,
|
|
|
+ type: 'line',
|
|
|
+ data: line.data,
|
|
|
+ symbol: 'circle',
|
|
|
+ showSymbol: false,
|
|
|
+ smooth: true,
|
|
|
+ lineStyle: {
|
|
|
+ color: line.color,
|
|
|
+ width: idx < 2 ? 2 : 1,
|
|
|
+ type: idx < 2 ? 'solid' : 'dashed',
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ color: line.color,
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ focus: 'series',
|
|
|
+ symbolSize: 6,
|
|
|
+ itemStyle: {
|
|
|
+ color: line.color,
|
|
|
+ borderColor: line.color,
|
|
|
+ borderWidth: 2,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ areaStyle: {
|
|
|
+ color: {
|
|
|
+ type: 'linear',
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ x2: 0,
|
|
|
+ y2: 1,
|
|
|
+ colorStops: [
|
|
|
+ { offset: 0, color: chroma(line.color).alpha(0.12).hex() },
|
|
|
+ { offset: 1, color: chroma(line.color).alpha(0.02).hex() },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ })),
|
|
|
+
|
|
|
+ animationDuration: 1000,
|
|
|
+ animationEasing: 'cubicOut' as const,
|
|
|
+ animationDelay(idx: number) {
|
|
|
+ return idx * 100
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ chart.setOption(option)
|
|
|
+ monthlyRadarChartInstance = chart
|
|
|
+ monthlyRadarChartResizeHandler = () => chart.resize()
|
|
|
+ window.addEventListener('resize', monthlyRadarChartResizeHandler, { passive: true })
|
|
|
+}
|
|
|
+
|
|
|
+async function initHighestRevenueChart() {
|
|
|
+ if (!highestRevenueChart.value) return
|
|
|
+
|
|
|
+ const thirtyDayLine = (await chartDataManager.get30DayLine()) as Array<{
|
|
|
+ label: string
|
|
|
+ value: number
|
|
|
+ }>
|
|
|
+ const xAxisData = thirtyDayLine.map((item) => item.label)
|
|
|
+ const seriesData = thirtyDayLine.map((item) => item.value)
|
|
|
+
|
|
|
+ const legend = [
|
|
|
+ {
|
|
|
+ name: '大型鸟类',
|
|
|
+ color: '#EF4444', // 红色
|
|
|
+ data: seriesData.map((data: any) => data[0]),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '中型鸟类',
|
|
|
+ color: '#F59E0B', // 黄色
|
|
|
+ data: seriesData.map((data: any) => data[1]),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '小型鸟类',
|
|
|
+ color: '#3B82F6', // 蓝色
|
|
|
+ data: seriesData.map((data: any) => data[2]),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '未知',
|
|
|
+ color: '#10B981', // 绿色
|
|
|
+ data: seriesData.map((data: any) => data[3]),
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const chart = echarts.init(highestRevenueChart.value)
|
|
|
+
|
|
|
+ const option = {
|
|
|
+ title: {
|
|
|
+ text: '按体型分类趋势',
|
|
|
+ textStyle: {
|
|
|
+ fontSize: 15,
|
|
|
+ color: isDark.value ? twc.neutral[400] : twc.neutral[600],
|
|
|
+ fontWeight: 'normal',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ axisPointer: { type: 'none' },
|
|
|
+ backgroundColor: isDark.value ? twc.neutral[750] : '#fff',
|
|
|
+ borderWidth: 0,
|
|
|
+ padding: 8,
|
|
|
+ extraCssText: 'box-shadow: none;',
|
|
|
+ textStyle: {
|
|
|
+ color: isDark.value ? twc.neutral[400] : twc.neutral[600],
|
|
|
+ fontSize: 12,
|
|
|
+ },
|
|
|
+ formatter(params: any) {
|
|
|
+ const date = params[0].axisValue
|
|
|
+ let result = `<div>${date}</div>`
|
|
|
+ params.forEach((item: any) => {
|
|
|
+ result += `
|
|
|
+ <div style="display: flex; align-items: center; margin-top: 4px;">
|
|
|
+ <span style="display:inline-block; margin-right:4px; width:10px; height:10px; border-radius:50%; background-color:${item.color};"></span>
|
|
|
+ <span style="margin-right: 10px">${item.seriesName}</span>
|
|
|
+ <span>${item.value.toLocaleString()}</span>
|
|
|
+ </div>
|
|
|
+ `
|
|
|
+ })
|
|
|
+ return result
|
|
|
+ },
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ left: '3%',
|
|
|
+ right: '4%',
|
|
|
+ bottom: '3%',
|
|
|
+ containLabel: true,
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ top: 6,
|
|
|
+ right: 22,
|
|
|
+ itemGap: 16,
|
|
|
+ icon: 'circle',
|
|
|
+ itemWidth: 12,
|
|
|
+ itemHeight: 12,
|
|
|
+ textStyle: {
|
|
|
+ color: isDark.value ? twc.neutral[400] : twc.neutral[600],
|
|
|
+ fontSize: 14,
|
|
|
+ },
|
|
|
+ data: legend.map((item) => item.name),
|
|
|
+ },
|
|
|
+ xAxis: [
|
|
|
+ {
|
|
|
+ type: 'category',
|
|
|
+ data: xAxisData,
|
|
|
+ axisTick: {
|
|
|
+ alignWithLabel: true,
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ show: true,
|
|
|
+ lineStyle: {
|
|
|
+ type: 'dashed',
|
|
|
+ color: isDark.value ? 'rgba(255, 255, 255, 0.08)' : 'rgba(0, 0, 0, 0.08)',
|
|
|
+ width: 1,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ yAxis: [
|
|
|
+ {
|
|
|
+ type: 'value',
|
|
|
+ splitLine: {
|
|
|
+ show: true,
|
|
|
+ lineStyle: {
|
|
|
+ type: 'dashed',
|
|
|
+ color: isDark.value ? 'rgba(255, 255, 255, 0.08)' : 'rgba(0, 0, 0, 0.08)',
|
|
|
+ width: 1,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ series: legend.map((item) => ({
|
|
|
+ emphasis: {
|
|
|
+ focus: 'series',
|
|
|
+ },
|
|
|
+ stack: '总量',
|
|
|
+ name: item.name,
|
|
|
+ type: 'bar',
|
|
|
+ barWidth: '60%',
|
|
|
+ color: item.color,
|
|
|
+ data: item.data,
|
|
|
+ })),
|
|
|
+ }
|
|
|
+
|
|
|
+ chart.setOption(option)
|
|
|
+ highestRevenueChartInstance = chart
|
|
|
+ highestRevenueChartResizeHandler = () => chart.resize()
|
|
|
+ window.addEventListener('resize', highestRevenueChartResizeHandler, { passive: true })
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ initRevenueChart()
|
|
|
+ initMonthlyRadarChart()
|
|
|
+ initHighestRevenueChart()
|
|
|
+})
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ if (revenueChartInstance) {
|
|
|
+ if (revenueChartResizeHandler) {
|
|
|
+ window.removeEventListener('resize', revenueChartResizeHandler)
|
|
|
+ revenueChartResizeHandler = null
|
|
|
+ }
|
|
|
+ revenueChartInstance.dispose()
|
|
|
+ revenueChartInstance = null
|
|
|
+ }
|
|
|
+
|
|
|
+ if (monthlyRadarChartInstance) {
|
|
|
+ if (monthlyRadarChartResizeHandler) {
|
|
|
+ window.removeEventListener('resize', monthlyRadarChartResizeHandler)
|
|
|
+ monthlyRadarChartResizeHandler = null
|
|
|
+ }
|
|
|
+ monthlyRadarChartInstance.dispose()
|
|
|
+ monthlyRadarChartInstance = null
|
|
|
+ }
|
|
|
+
|
|
|
+ if (highestRevenueChartInstance) {
|
|
|
+ if (highestRevenueChartResizeHandler) {
|
|
|
+ window.removeEventListener('resize', highestRevenueChartResizeHandler)
|
|
|
+ highestRevenueChartResizeHandler = null
|
|
|
+ }
|
|
|
+ highestRevenueChartInstance.dispose()
|
|
|
+ highestRevenueChartInstance = null
|
|
|
+ }
|
|
|
+
|
|
|
+ if (collapseResizeTimeout !== null) {
|
|
|
+ clearTimeout(collapseResizeTimeout)
|
|
|
+ collapseResizeTimeout = null
|
|
|
+ }
|
|
|
+})
|
|
|
+</script>
|