|
@@ -0,0 +1,489 @@
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { computed, onMounted, ref, watch } from 'vue'
|
|
|
|
|
+import { clientGet } from '@/utils/request.ts'
|
|
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
|
|
+import * as echarts from 'echarts'
|
|
|
|
|
+import type { BarSeriesOption, LineSeriesOption, PieSeriesOption } from 'echarts/charts'
|
|
|
|
|
+import type { ComposeOption } from 'echarts/core'
|
|
|
|
|
+import { BarChart3, Calendar, DollarSign, TrendingUp } from 'lucide-vue-next'
|
|
|
|
|
+
|
|
|
|
|
+interface ExistYearResponse extends BaseResponse {
|
|
|
|
|
+ data: number[]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface YearlyStatisticsResponse extends BaseResponse {
|
|
|
|
|
+ data: Record<string, number>
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface QuarterlyStatisticsResponse extends BaseResponse {
|
|
|
|
|
+ data: Record<string, number>
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface MonthlyStatisticsResponse extends BaseResponse {
|
|
|
|
|
+ data: Record<string, number>
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 状态管理
|
|
|
|
|
+const availableYears = ref<number[]>([])
|
|
|
|
|
+const selectedYear = ref<number | null>(null)
|
|
|
|
|
+const yearlyData = ref<Record<string, number>>({})
|
|
|
|
|
+const quarterlyData = ref<Record<string, number>>({})
|
|
|
|
|
+const monthlyData = ref<Record<string, number>>({})
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+
|
|
|
|
|
+// 图表实例
|
|
|
|
|
+const yearlyChartRef = ref<HTMLElement | null>(null)
|
|
|
|
|
+const quarterlyChartRef = ref<HTMLElement | null>(null)
|
|
|
|
|
+const monthlyChartRef = ref<HTMLElement | null>(null)
|
|
|
|
|
+let yearlyChart: echarts.ECharts | null = null
|
|
|
|
|
+let quarterlyChart: echarts.ECharts | null = null
|
|
|
|
|
+let monthlyChart: echarts.ECharts | null = null
|
|
|
|
|
+
|
|
|
|
|
+// 计算统计数据
|
|
|
|
|
+const totalYearlyAmount = computed(() => {
|
|
|
|
|
+ return Object.values(yearlyData.value).reduce((sum, val) => sum + val, 0)
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const currentYearAmount = computed(() => {
|
|
|
|
|
+ if (!selectedYear.value) return 0
|
|
|
|
|
+ return yearlyData.value[selectedYear.value] || 0
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const totalQuarterlyAmount = computed(() => {
|
|
|
|
|
+ return Object.values(quarterlyData.value).reduce((sum, val) => sum + val, 0)
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const averageMonthlyAmount = computed(() => {
|
|
|
|
|
+ const values = Object.values(monthlyData.value)
|
|
|
|
|
+ if (values.length === 0) return 0
|
|
|
|
|
+ return values.reduce((sum, val) => sum + val, 0) / values.length
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 获取已经存在的年份
|
|
|
|
|
+const getExistingYears = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await clientGet<null, ExistYearResponse>('/arefund/getExistingRefundYears')
|
|
|
|
|
+ if (res.code !== 200) {
|
|
|
|
|
+ ElMessage.error(res.msg)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ availableYears.value = res.data.sort((a, b) => b - a)
|
|
|
|
|
+ if (availableYears.value.length > 0 && !selectedYear.value) {
|
|
|
|
|
+ selectedYear.value = availableYears.value[0]
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(error)
|
|
|
|
|
+ ElMessage.error('获取年份数据失败')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 计算年份的退据金额
|
|
|
|
|
+const getCalculateYearlyStatistics = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await clientGet<null, YearlyStatisticsResponse>(
|
|
|
|
|
+ '/arefund/calculateYearlyRefundStatistics',
|
|
|
|
|
+ )
|
|
|
|
|
+ if (res.code !== 200) {
|
|
|
|
|
+ ElMessage.error(res.msg)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ yearlyData.value = res.data
|
|
|
|
|
+ renderYearlyChart()
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(error)
|
|
|
|
|
+ ElMessage.error('获取年度统计失败')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 传入年份返回对应的季度的数据
|
|
|
|
|
+const getCalculateQuarterlyStatistics = async (year: number) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await clientGet<{ year: number }, QuarterlyStatisticsResponse>(
|
|
|
|
|
+ '/arefund/calculateQuarterlyRefundStatistics',
|
|
|
|
|
+ {
|
|
|
|
|
+ params: { year },
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+ if (res.code !== 200) {
|
|
|
|
|
+ ElMessage.error(res.msg)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ quarterlyData.value = res.data
|
|
|
|
|
+ renderQuarterlyChart()
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(error)
|
|
|
|
|
+ ElMessage.error('获取季度统计失败')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 传入年份返回对应的月份的数据
|
|
|
|
|
+const getCalculateMonthlyStatistics = async (year: number) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await clientGet<{ year: number }, MonthlyStatisticsResponse>(
|
|
|
|
|
+ '/arefund/calculateMonthlyRefundStatistics',
|
|
|
|
|
+ {
|
|
|
|
|
+ params: { year },
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+ if (res.code !== 200) {
|
|
|
|
|
+ ElMessage.error(res.msg)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ monthlyData.value = res.data
|
|
|
|
|
+ renderMonthlyChart()
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(error)
|
|
|
|
|
+ ElMessage.error('获取月度统计失败')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 渲染年度统计图表
|
|
|
|
|
+const renderYearlyChart = () => {
|
|
|
|
|
+ if (!yearlyChartRef.value) return
|
|
|
|
|
+
|
|
|
|
|
+ if (!yearlyChart) {
|
|
|
|
|
+ yearlyChart = echarts.init(yearlyChartRef.value)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const years = Object.keys(yearlyData.value).sort()
|
|
|
|
|
+ const values = years.map((year) => yearlyData.value[year])
|
|
|
|
|
+
|
|
|
|
|
+ const option: ComposeOption<BarSeriesOption> = {
|
|
|
|
|
+ title: {
|
|
|
|
|
+ text: '年度退据金额统计',
|
|
|
|
|
+ left: 'center',
|
|
|
|
|
+ textStyle: {
|
|
|
|
|
+ fontSize: 16,
|
|
|
|
|
+ fontWeight: 'bold',
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: 'axis',
|
|
|
|
|
+ formatter: '{b}: ¥{c}',
|
|
|
|
|
+ },
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ data: years,
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ rotate: 0,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ yAxis: {
|
|
|
|
|
+ type: 'value',
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ formatter: '¥{value}',
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ data: values,
|
|
|
|
|
+ type: 'bar',
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
+ { offset: 0, color: '#10b981' },
|
|
|
|
|
+ { offset: 1, color: '#34d399' },
|
|
|
|
|
+ ]),
|
|
|
|
|
+ },
|
|
|
|
|
+ label: {
|
|
|
|
|
+ show: true,
|
|
|
|
|
+ position: 'top',
|
|
|
|
|
+ formatter: '¥{c}',
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ grid: {
|
|
|
|
|
+ left: '3%',
|
|
|
|
|
+ right: '4%',
|
|
|
|
|
+ bottom: '3%',
|
|
|
|
|
+ containLabel: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ yearlyChart.setOption(option)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 渲染季度统计图表
|
|
|
|
|
+const renderQuarterlyChart = () => {
|
|
|
|
|
+ if (!quarterlyChartRef.value) return
|
|
|
|
|
+
|
|
|
|
|
+ if (!quarterlyChart) {
|
|
|
|
|
+ quarterlyChart = echarts.init(quarterlyChartRef.value)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const quarters = Object.keys(quarterlyData.value).sort()
|
|
|
|
|
+ const values = quarters.map((q) => quarterlyData.value[q])
|
|
|
|
|
+
|
|
|
|
|
+ const option: ComposeOption<PieSeriesOption> = {
|
|
|
|
|
+ title: {
|
|
|
|
|
+ text: `${selectedYear.value}年季度退据金额统计`,
|
|
|
|
|
+ left: 'center',
|
|
|
|
|
+ textStyle: {
|
|
|
|
|
+ fontSize: 16,
|
|
|
|
|
+ fontWeight: 'bold',
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: 'item',
|
|
|
|
|
+ formatter: '{b}: ¥{c} ({d}%)',
|
|
|
|
|
+ },
|
|
|
|
|
+ legend: {
|
|
|
|
|
+ orient: 'vertical',
|
|
|
|
|
+ left: 'left',
|
|
|
|
|
+ top: 'middle',
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'pie',
|
|
|
|
|
+ radius: ['40%', '70%'],
|
|
|
|
|
+ avoidLabelOverlap: false,
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ borderRadius: 10,
|
|
|
|
|
+ borderColor: '#fff',
|
|
|
|
|
+ borderWidth: 2,
|
|
|
|
|
+ },
|
|
|
|
|
+ label: {
|
|
|
|
|
+ show: true,
|
|
|
|
|
+ formatter: '{b}: ¥{c}',
|
|
|
|
|
+ },
|
|
|
|
|
+ emphasis: {
|
|
|
|
|
+ label: {
|
|
|
|
|
+ show: true,
|
|
|
|
|
+ fontSize: 16,
|
|
|
|
|
+ fontWeight: 'bold',
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ data: quarters.map((q, index) => ({
|
|
|
|
|
+ value: values[index],
|
|
|
|
|
+ name: q,
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: ['#10b981', '#06b6d4', '#8b5cf6', '#f59e0b'][index % 4],
|
|
|
|
|
+ },
|
|
|
|
|
+ })),
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ quarterlyChart.setOption(option)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 渲染月度统计图表
|
|
|
|
|
+const renderMonthlyChart = () => {
|
|
|
|
|
+ if (!monthlyChartRef.value) return
|
|
|
|
|
+
|
|
|
|
|
+ if (!monthlyChart) {
|
|
|
|
|
+ monthlyChart = echarts.init(monthlyChartRef.value)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const months = Object.keys(monthlyData.value).sort()
|
|
|
|
|
+ const values = months.map((m) => monthlyData.value[m])
|
|
|
|
|
+
|
|
|
|
|
+ const option: ComposeOption<LineSeriesOption> = {
|
|
|
|
|
+ title: {
|
|
|
|
|
+ text: `${selectedYear.value}年月度退据金额统计`,
|
|
|
|
|
+ left: 'center',
|
|
|
|
|
+ textStyle: {
|
|
|
|
|
+ fontSize: 16,
|
|
|
|
|
+ fontWeight: 'bold',
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: 'axis',
|
|
|
|
|
+ formatter: '{b}: ¥{c}',
|
|
|
|
|
+ },
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ data: months.map((m) => m.split('-')[1] + '月'),
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ rotate: 45,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ yAxis: {
|
|
|
|
|
+ type: 'value',
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ formatter: '¥{value}',
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ data: values,
|
|
|
|
|
+ type: 'line',
|
|
|
|
|
+ smooth: true,
|
|
|
|
|
+ areaStyle: {
|
|
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
+ { offset: 0, color: 'rgba(16, 185, 129, 0.3)' },
|
|
|
|
|
+ { offset: 1, color: 'rgba(16, 185, 129, 0.05)' },
|
|
|
|
|
+ ]),
|
|
|
|
|
+ },
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: '#10b981',
|
|
|
|
|
+ },
|
|
|
|
|
+ lineStyle: {
|
|
|
|
|
+ width: 3,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ grid: {
|
|
|
|
|
+ left: '3%',
|
|
|
|
|
+ right: '4%',
|
|
|
|
|
+ bottom: '10%',
|
|
|
|
|
+ containLabel: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ monthlyChart.setOption(option)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 加载数据
|
|
|
|
|
+const loadData = async () => {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ await getCalculateYearlyStatistics()
|
|
|
|
|
+ if (selectedYear.value) {
|
|
|
|
|
+ await Promise.all([
|
|
|
|
|
+ getCalculateQuarterlyStatistics(selectedYear.value),
|
|
|
|
|
+ getCalculateMonthlyStatistics(selectedYear.value),
|
|
|
|
|
+ ])
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 监听年份变化
|
|
|
|
|
+watch(selectedYear, (newYear) => {
|
|
|
|
|
+ if (newYear) {
|
|
|
|
|
+ loadData()
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 初始化
|
|
|
|
|
+onMounted(async () => {
|
|
|
|
|
+ await getExistingYears()
|
|
|
|
|
+ await loadData()
|
|
|
|
|
+
|
|
|
|
|
+ // 响应式调整图表大小
|
|
|
|
|
+ window.addEventListener('resize', () => {
|
|
|
|
|
+ yearlyChart?.resize()
|
|
|
|
|
+ quarterlyChart?.resize()
|
|
|
|
|
+ monthlyChart?.resize()
|
|
|
|
|
+ })
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 格式化金额
|
|
|
|
|
+const formatAmount = (amount: number) => {
|
|
|
|
|
+ return new Intl.NumberFormat('zh-CN', {
|
|
|
|
|
+ style: 'currency',
|
|
|
|
|
+ currency: 'CNY',
|
|
|
|
|
+ }).format(amount)
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="min-h-screen bg-gradient-to-br from-emerald-50 via-white to-cyan-50 p-6">
|
|
|
|
|
+ <div class="max-full mx-auto">
|
|
|
|
|
+ <div class="mb-8">
|
|
|
|
|
+ <h1 class="text-4xl font-bold text-gray-900 mb-2 flex items-center gap-3">
|
|
|
|
|
+ <BarChart3 class="w-10 h-10 text-emerald-600" />
|
|
|
|
|
+ </h1>
|
|
|
|
|
+ <h1 class="text-gray-600">退据统计</h1>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="mb-6 flex items-center gap-4">
|
|
|
|
|
+ <Calendar class="w-5 h-5 text-emerald-600" />
|
|
|
|
|
+ <span class="text-gray-700 font-medium">选择年份:</span>
|
|
|
|
|
+ <el-select v-model="selectedYear" placeholder="请选择年份" size="large" class="w-48!">
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="year in availableYears"
|
|
|
|
|
+ :key="year"
|
|
|
|
|
+ :label="`${year}年`"
|
|
|
|
|
+ :value="year"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="bg-white rounded-2xl shadow-lg p-6 border-l-4 border-emerald-500 hover:shadow-xl transition-shadow"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="flex items-center justify-between mb-4">
|
|
|
|
|
+ <div class="bg-emerald-100 p-3 rounded-xl">
|
|
|
|
|
+ <DollarSign class="w-6 h-6 text-emerald-600" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <TrendingUp class="w-5 h-5 text-green-500" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <h3 class="text-gray-600 text-sm font-medium mb-1">总退据金额</h3>
|
|
|
|
|
+ <p class="text-3xl font-bold text-gray-900">{{ formatAmount(totalYearlyAmount) }}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="bg-white rounded-2xl shadow-lg p-6 border-l-4 border-cyan-500 hover:shadow-xl transition-shadow"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="flex items-center justify-between mb-4">
|
|
|
|
|
+ <div class="bg-cyan-100 p-3 rounded-xl">
|
|
|
|
|
+ <Calendar class="w-6 h-6 text-cyan-600" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <h3 class="text-gray-600 text-sm font-medium mb-1">{{ selectedYear }}年金额</h3>
|
|
|
|
|
+ <p class="text-3xl font-bold text-gray-900">{{ formatAmount(currentYearAmount) }}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="bg-white rounded-2xl shadow-lg p-6 border-l-4 border-green-500 hover:shadow-xl transition-shadow"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="flex items-center justify-between mb-4">
|
|
|
|
|
+ <div class="bg-green-100 p-3 rounded-xl">
|
|
|
|
|
+ <BarChart3 class="w-6 h-6 text-green-600" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <h3 class="text-gray-600 text-sm font-medium mb-1">季度总金额</h3>
|
|
|
|
|
+ <p class="text-3xl font-bold text-gray-900">{{ formatAmount(totalQuarterlyAmount) }}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="bg-white rounded-2xl shadow-lg p-6 border-l-4 border-amber-500 hover:shadow-xl transition-shadow"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="flex items-center justify-between mb-4">
|
|
|
|
|
+ <div class="bg-amber-100 p-3 rounded-xl">
|
|
|
|
|
+ <TrendingUp class="w-6 h-6 text-amber-600" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <h3 class="text-gray-600 text-sm font-medium mb-1">月均金额</h3>
|
|
|
|
|
+ <p class="text-3xl font-bold text-gray-900">{{ formatAmount(averageMonthlyAmount) }}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-loading="loading" class="space-y-6">
|
|
|
|
|
+ <div class="bg-white rounded-2xl shadow-lg p-6 hover:shadow-xl transition-shadow">
|
|
|
|
|
+ <div ref="yearlyChartRef" class="w-full h-96"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
|
|
|
+ <div class="bg-white rounded-2xl shadow-lg p-6 hover:shadow-xl transition-shadow">
|
|
|
|
|
+ <div ref="quarterlyChartRef" class="w-full h-96"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="bg-white rounded-2xl shadow-lg p-6 hover:shadow-xl transition-shadow">
|
|
|
|
|
+ <div ref="monthlyChartRef" class="w-full h-96"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+/* Element Plus 样式覆盖 */
|
|
|
|
|
+:deep(.el-select) {
|
|
|
|
|
+ --el-select-border-color-hover: #4f46e5;
|
|
|
|
|
+ --el-select-input-focus-border-color: #4f46e5;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-select__wrapper) {
|
|
|
|
|
+ border-radius: 0.75rem;
|
|
|
|
|
+ box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-select__wrapper:hover) {
|
|
|
|
|
+ box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|