|
|
@@ -1,8 +1,45 @@
|
|
|
<template>
|
|
|
<div class="app-container">
|
|
|
- <!-- 统计数据卡片 -->
|
|
|
- <el-row :gutter="20" class="mb20">
|
|
|
+ <!-- 年份选择器和统计数据卡片 -->
|
|
|
+ <el-row :gutter="30" class="mb20">
|
|
|
<el-col :span="4">
|
|
|
+ <el-card class="year-selector-card">
|
|
|
+ <div class="year-selector-content">
|
|
|
+ <div class="year-title">
|
|
|
+ <el-icon><Calendar /></el-icon>
|
|
|
+ <span>年度查询</span>
|
|
|
+ </div>
|
|
|
+ <div class="year-controls">
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ @click="changeYear(-1)"
|
|
|
+ class="year-nav-btn"
|
|
|
+ :disabled="queryParams.year <= 2020"
|
|
|
+ :icon="ArrowLeft"
|
|
|
+ >
|
|
|
+ </el-button>
|
|
|
+ <el-date-picker
|
|
|
+ v-model="queryParams.year"
|
|
|
+ type="year"
|
|
|
+ value-format="YYYY"
|
|
|
+ placeholder="请选择年份"
|
|
|
+ @change="handleYearChange"
|
|
|
+ style="width: 100px"
|
|
|
+ >
|
|
|
+ </el-date-picker>
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ @click="changeYear(1)"
|
|
|
+ class="year-nav-btn"
|
|
|
+ :disabled="queryParams.year >= new Date().getFullYear()"
|
|
|
+ :icon="ArrowRight"
|
|
|
+ >
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="3">
|
|
|
<el-card class="stat-card total-people">
|
|
|
<div class="stat-item">
|
|
|
<div class="stat-number">{{ statistics.totalPeople }}</div>
|
|
|
@@ -18,7 +55,7 @@
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
- <el-col :span="4">
|
|
|
+ <el-col :span="3">
|
|
|
<el-card class="stat-card avg-frequency">
|
|
|
<div class="stat-item">
|
|
|
<div class="stat-number">{{ statistics.avgFrequency }}</div>
|
|
|
@@ -26,7 +63,7 @@
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
- <el-col :span="4">
|
|
|
+ <el-col :span="3">
|
|
|
<el-card class="stat-card max-frequency">
|
|
|
<div class="stat-item">
|
|
|
<div class="stat-number">{{ statistics.maxFrequency }}</div>
|
|
|
@@ -34,7 +71,7 @@
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
- <el-col :span="4">
|
|
|
+ <el-col :span="3">
|
|
|
<el-card class="stat-card night-entries">
|
|
|
<div class="stat-item">
|
|
|
<div class="stat-number">{{ statistics.nightPeople }}</div>
|
|
|
@@ -161,7 +198,7 @@
|
|
|
<script setup name="Index">
|
|
|
import * as echarts from 'echarts'
|
|
|
import { onMounted, onUnmounted, ref, reactive, toRefs, nextTick } from 'vue'
|
|
|
-import { House, TrendCharts, Moon, Calendar, Lightning, Document, Timer } from '@element-plus/icons-vue'
|
|
|
+import { House, TrendCharts, Moon, Calendar, Lightning, Document, Timer, ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
|
|
|
import { useRouter } from 'vue-router'
|
|
|
import { getInOutStats, listHighFrequencyInOut, getNightInOutStats, getHolidayInOutStats } from "@/api/biz/anal";
|
|
|
|
|
|
@@ -193,6 +230,12 @@ const data = reactive({
|
|
|
|
|
|
const { queryParams } = toRefs(data);
|
|
|
|
|
|
+// 年份选项,从2020年到未来1年
|
|
|
+const currentYear = new Date().getFullYear();
|
|
|
+const yearOptions = ref(
|
|
|
+ Array.from({ length: 10 }, (_, i) => (currentYear - 4 + i).toString()) // 从当前年份前4年开始,到后5年结束
|
|
|
+);
|
|
|
+
|
|
|
/** 查询数据 */
|
|
|
function getList() {
|
|
|
// 准备查询参数 - 直接使用当前年份
|
|
|
@@ -210,8 +253,6 @@ function getList() {
|
|
|
getHolidayInOutStats(params), // 节假日统计
|
|
|
listHighFrequencyInOut(params) // 高频数据用于频次分布
|
|
|
]).then(responses => {
|
|
|
- console.log('API响应数据:', responses); // 调试信息
|
|
|
-
|
|
|
// 处理基础统计信息
|
|
|
const statsResponse = responses[0];
|
|
|
if (statsResponse.code === 200 && statsResponse.data) {
|
|
|
@@ -231,11 +272,9 @@ function getList() {
|
|
|
|
|
|
// 处理节假日统计信息
|
|
|
const holidayResponse = responses[2];
|
|
|
- console.log('节假日统计响应:', holidayResponse); // 调试信息
|
|
|
if (holidayResponse.code === 200) {
|
|
|
// 节假日统计数据结构为数组,包含节假日名称、出入境次数等信息
|
|
|
const holidayData = Array.isArray(holidayResponse.data) ? holidayResponse.data : [];
|
|
|
- console.log('节假日统计数据:', holidayData); // 调试信息
|
|
|
|
|
|
// 计算总人数
|
|
|
const totalHolidayCount = holidayData.reduce((sum, item) => sum + (item.inOutCount || 0), 0);
|
|
|
@@ -272,136 +311,139 @@ function getList() {
|
|
|
function renderPieChart(params) {
|
|
|
if (!pieChartContainer.value) return;
|
|
|
|
|
|
- const myChart = echarts.init(pieChartContainer.value);
|
|
|
-
|
|
|
- // 调用后端接口获取频次分布数据
|
|
|
- getInOutStats(params).then(response => {
|
|
|
- let distributionData = [];
|
|
|
-
|
|
|
- if (response.code === 200 && response.data) {
|
|
|
- const stats = response.data;
|
|
|
-
|
|
|
- // 检查是否有频次分布数据
|
|
|
- if (stats.frequencyDistribution && typeof stats.frequencyDistribution === 'object') {
|
|
|
- const dist = stats.frequencyDistribution;
|
|
|
- distributionData = [];
|
|
|
-
|
|
|
- // 将对象格式转换为饼图需要的数组格式,并按区间排序
|
|
|
- const rangeOrder = ['1-5', '5-10', '10-20', '20-30', '30+'];
|
|
|
-
|
|
|
- // 先处理预定义的区间
|
|
|
- for (const range of rangeOrder) {
|
|
|
- if (dist.hasOwnProperty(range)) {
|
|
|
- // 根据区间范围选择颜色
|
|
|
- let color;
|
|
|
- if (range.startsWith('1-5')) color = '#5470c6';
|
|
|
- else if (range.startsWith('5-10')) color = '#91cc75';
|
|
|
- else if (range.startsWith('10-20')) color = '#fac858';
|
|
|
- else if (range.startsWith('20-30')) color = '#ee6666';
|
|
|
- else if (range.startsWith('30')) color = '#73c0de';
|
|
|
- else color = '#9da4b0';
|
|
|
-
|
|
|
- distributionData.push({
|
|
|
- value: dist[range],
|
|
|
- name: range,
|
|
|
- itemStyle: { color: color }
|
|
|
- });
|
|
|
+ // 确保DOM已经渲染
|
|
|
+ nextTick(() => {
|
|
|
+ const myChart = echarts.init(pieChartContainer.value);
|
|
|
+
|
|
|
+ // 调用后端接口获取基础统计信息,其中包含频次分布数据
|
|
|
+ getInOutStats(params).then(response => {
|
|
|
+ let distributionData = [];
|
|
|
+
|
|
|
+ if (response.code === 200 && response.data) {
|
|
|
+ const stats = response.data;
|
|
|
+
|
|
|
+ // 检查是否有频次分布数据
|
|
|
+ if (stats.frequencyDistribution && typeof stats.frequencyDistribution === 'object') {
|
|
|
+ const dist = stats.frequencyDistribution;
|
|
|
+
|
|
|
+ // 将对象格式转换为饼图需要的数组格式,并按区间排序
|
|
|
+ const rangeOrder = ['1-5', '5-10', '10-20', '20-30', '30+'];
|
|
|
+
|
|
|
+ // 先处理预定义的区间
|
|
|
+ for (const range of rangeOrder) {
|
|
|
+ if (dist.hasOwnProperty(range)) {
|
|
|
+ // 根据区间范围选择颜色
|
|
|
+ let color;
|
|
|
+ if (range.startsWith('1-5')) color = '#5470c6';
|
|
|
+ else if (range.startsWith('5-10')) color = '#91cc75';
|
|
|
+ else if (range.startsWith('10-20')) color = '#fac858';
|
|
|
+ else if (range.startsWith('20-30')) color = '#ee6666';
|
|
|
+ else if (range.startsWith('30')) color = '#73c0de';
|
|
|
+ else color = '#9da4b0';
|
|
|
+
|
|
|
+ distributionData.push({
|
|
|
+ value: dist[range],
|
|
|
+ name: range,
|
|
|
+ itemStyle: { color: color }
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- // 处理不在预定义区间中的其他区间
|
|
|
- for (const [range, count] of Object.entries(dist)) {
|
|
|
- if (!rangeOrder.includes(range)) {
|
|
|
- let color;
|
|
|
- if (range.startsWith('1-5')) color = '#5470c6';
|
|
|
- else if (range.startsWith('5-10')) color = '#91cc75';
|
|
|
- else if (range.startsWith('10-20')) color = '#fac858';
|
|
|
- else if (range.startsWith('20-30')) color = '#ee6666';
|
|
|
- else if (range.startsWith('30')) color = '#73c0de';
|
|
|
- else color = '#9da4b0';
|
|
|
-
|
|
|
- distributionData.push({
|
|
|
- value: count,
|
|
|
- name: range,
|
|
|
- itemStyle: { color: color }
|
|
|
- });
|
|
|
+ // 处理不在预定义区间中的其他区间
|
|
|
+ for (const [range, count] of Object.entries(dist)) {
|
|
|
+ if (!rangeOrder.includes(range)) {
|
|
|
+ let color;
|
|
|
+ if (range.startsWith('1-5')) color = '#5470c6';
|
|
|
+ else if (range.startsWith('5-10')) color = '#91cc75';
|
|
|
+ else if (range.startsWith('10-20')) color = '#fac858';
|
|
|
+ else if (range.startsWith('20-30')) color = '#ee6666';
|
|
|
+ else if (range.startsWith('30')) color = '#73c0de';
|
|
|
+ else color = '#9da4b0';
|
|
|
+
|
|
|
+ distributionData.push({
|
|
|
+ value: count,
|
|
|
+ name: range,
|
|
|
+ itemStyle: { color: color }
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- const option = {
|
|
|
- tooltip: {
|
|
|
- trigger: 'item',
|
|
|
- formatter: '{a} <br/>{b}: {c}人 ({d}%)'
|
|
|
- },
|
|
|
- legend: {
|
|
|
- orient: 'vertical',
|
|
|
- left: 'right'
|
|
|
- },
|
|
|
- series: [{
|
|
|
- name: '人数',
|
|
|
- type: 'pie',
|
|
|
- radius: [20, 120], // 设置内外半径,形成南丁格尔图效果
|
|
|
- center: ['50%', '50%'],
|
|
|
- roseType: 'area', // 启用南丁格尔图模式
|
|
|
- itemStyle: {
|
|
|
- borderRadius: 8
|
|
|
+ const option = {
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item',
|
|
|
+ formatter: '{a} <br/>{b}: {c}人 ({d}%)'
|
|
|
},
|
|
|
- label: {
|
|
|
- show: true,
|
|
|
- formatter: '{b}: {c}',
|
|
|
- fontSize: 10,
|
|
|
- position: 'outside'
|
|
|
+ legend: {
|
|
|
+ orient: 'vertical',
|
|
|
+ left: 'right'
|
|
|
},
|
|
|
- data: distributionData,
|
|
|
- emphasis: {
|
|
|
+ series: [{
|
|
|
+ name: '人数',
|
|
|
+ type: 'pie',
|
|
|
+ radius: [20, 120], // 设置内外半径,形成南丁格尔图效果
|
|
|
+ center: ['50%', '50%'],
|
|
|
+ roseType: 'area', // 启用南丁格尔图模式
|
|
|
itemStyle: {
|
|
|
- shadowBlur: 10,
|
|
|
- shadowOffsetX: 0,
|
|
|
- shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
+ borderRadius: 8
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ formatter: '{b}: {c}',
|
|
|
+ fontSize: 10,
|
|
|
+ position: 'outside'
|
|
|
+ },
|
|
|
+ data: distributionData,
|
|
|
+ emphasis: {
|
|
|
+ itemStyle: {
|
|
|
+ shadowBlur: 10,
|
|
|
+ shadowOffsetX: 0,
|
|
|
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- }]
|
|
|
- };
|
|
|
-
|
|
|
- myChart.setOption(option);
|
|
|
- }).catch(() => {
|
|
|
- const option = {
|
|
|
- tooltip: {
|
|
|
- trigger: 'item'
|
|
|
- },
|
|
|
- legend: {
|
|
|
- orient: 'vertical',
|
|
|
- left: 'right'
|
|
|
- },
|
|
|
- series: [{
|
|
|
- name: '人数',
|
|
|
- type: 'pie',
|
|
|
- radius: [20, 120],
|
|
|
- center: ['50%', '50%'],
|
|
|
- roseType: 'area',
|
|
|
- itemStyle: {
|
|
|
- borderRadius: 8
|
|
|
+ }]
|
|
|
+ };
|
|
|
+
|
|
|
+ myChart.setOption(option);
|
|
|
+ }).catch((error) => {
|
|
|
+ console.error('获取饼图数据失败:', error);
|
|
|
+ const option = {
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item'
|
|
|
},
|
|
|
- label: {
|
|
|
- show: true,
|
|
|
- formatter: '{b}: {c}',
|
|
|
- fontSize: 10,
|
|
|
- position: 'outside'
|
|
|
+ legend: {
|
|
|
+ orient: 'vertical',
|
|
|
+ left: 'right'
|
|
|
},
|
|
|
- data: [],
|
|
|
- emphasis: {
|
|
|
+ series: [{
|
|
|
+ name: '人数',
|
|
|
+ type: 'pie',
|
|
|
+ radius: [20, 120],
|
|
|
+ center: ['50%', '50%'],
|
|
|
+ roseType: 'area',
|
|
|
itemStyle: {
|
|
|
- shadowBlur: 10,
|
|
|
- shadowOffsetX: 0,
|
|
|
- shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
+ borderRadius: 8
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ formatter: '{b}: {c}',
|
|
|
+ fontSize: 10,
|
|
|
+ position: 'outside'
|
|
|
+ },
|
|
|
+ data: [],
|
|
|
+ emphasis: {
|
|
|
+ itemStyle: {
|
|
|
+ shadowBlur: 10,
|
|
|
+ shadowOffsetX: 0,
|
|
|
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- }]
|
|
|
- };
|
|
|
+ }]
|
|
|
+ };
|
|
|
|
|
|
- myChart.setOption(option);
|
|
|
+ myChart.setOption(option);
|
|
|
+ });
|
|
|
});
|
|
|
}
|
|
|
|
|
|
@@ -409,134 +451,151 @@ function renderPieChart(params) {
|
|
|
function renderNightChart(params) {
|
|
|
if (!nightChartContainer.value) return;
|
|
|
|
|
|
- const myChart = echarts.init(nightChartContainer.value);
|
|
|
-
|
|
|
- getNightInOutStats(params).then(response => {
|
|
|
- let xAxisData = [];
|
|
|
- let seriesData = [];
|
|
|
-
|
|
|
- if (response.code === 200 && response.data) {
|
|
|
- const stats = response.data;
|
|
|
-
|
|
|
- // 使用趋势数据
|
|
|
- if (stats.trendData && Array.isArray(stats.trendData)) {
|
|
|
- // 按年查询,显示每月趋势
|
|
|
- xAxisData = stats.trendData.map(item => {
|
|
|
- if (item.period) {
|
|
|
- const parts = item.period.split('-');
|
|
|
- if (parts.length === 2) {
|
|
|
- return parts[1] + '月';
|
|
|
+ // 确保DOM已经渲染
|
|
|
+ nextTick(() => {
|
|
|
+ const myChart = echarts.init(nightChartContainer.value);
|
|
|
+
|
|
|
+ getNightInOutStats(params).then(response => {
|
|
|
+ let xAxisData = [];
|
|
|
+ let seriesData = [];
|
|
|
+
|
|
|
+ if (response.code === 200 && response.data) {
|
|
|
+ const stats = response.data;
|
|
|
+
|
|
|
+ // 使用趋势数据
|
|
|
+ if (stats.trendData && Array.isArray(stats.trendData)) {
|
|
|
+ // 按年查询,显示每月趋势
|
|
|
+ xAxisData = stats.trendData.map(item => {
|
|
|
+ if (item.period) {
|
|
|
+ const parts = item.period.split('-');
|
|
|
+ if (parts.length === 2) {
|
|
|
+ return parts[1] + '月';
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- return '未知';
|
|
|
- });
|
|
|
+ return '未知';
|
|
|
+ });
|
|
|
|
|
|
- // 获取人数或次数数据
|
|
|
- seriesData = stats.trendData.map(item => item.inOutCount !== undefined ? item.inOutCount : 0);
|
|
|
+ // 获取人数或次数数据
|
|
|
+ seriesData = stats.trendData.map(item => item.inOutCount !== undefined ? item.inOutCount : 0);
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- const option = {
|
|
|
- tooltip: {
|
|
|
- trigger: 'axis',
|
|
|
- axisPointer: {
|
|
|
- type: 'shadow'
|
|
|
- },
|
|
|
- formatter: (params) => {
|
|
|
- const param = params[0];
|
|
|
- if (param.name === '暂无数据') {
|
|
|
- return '暂无数据';
|
|
|
+ const option = {
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ axisPointer: {
|
|
|
+ type: 'shadow'
|
|
|
+ },
|
|
|
+ formatter: (params) => {
|
|
|
+ const param = params[0];
|
|
|
+ if (param.name === '暂无数据') {
|
|
|
+ return '暂无数据';
|
|
|
+ }
|
|
|
+ const date = xAxisData[param.dataIndex];
|
|
|
+ const value = param.value;
|
|
|
+ return `${date}<br/>夜间出入境人数:${value}`;
|
|
|
}
|
|
|
- const date = xAxisData[param.dataIndex];
|
|
|
- const value = param.value;
|
|
|
- return `${date}<br/>夜间出入境人数:${value}`;
|
|
|
- }
|
|
|
- },
|
|
|
- grid: {
|
|
|
- show: false
|
|
|
- },
|
|
|
- xAxis: {
|
|
|
- type: 'category',
|
|
|
- data: xAxisData,
|
|
|
- axisLabel: {
|
|
|
- rotate: 45,
|
|
|
- fontSize: 12
|
|
|
},
|
|
|
- boundaryGap: false
|
|
|
- },
|
|
|
- yAxis: {
|
|
|
- type: 'value',
|
|
|
- name: '人数',
|
|
|
- show: true
|
|
|
- },
|
|
|
- series: [{
|
|
|
- data: seriesData,
|
|
|
- type: 'line',
|
|
|
- smooth: true,
|
|
|
- symbol: 'emptyCircle',
|
|
|
- symbolSize: 6,
|
|
|
- lineStyle: {
|
|
|
- color: '#73c0de',
|
|
|
- width: 2
|
|
|
+ grid: {
|
|
|
+ show: false
|
|
|
},
|
|
|
- itemStyle: {
|
|
|
- color: '#3ba272'
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: xAxisData,
|
|
|
+ axisLabel: {
|
|
|
+ rotate: 45,
|
|
|
+ fontSize: 12
|
|
|
+ },
|
|
|
+ boundaryGap: false
|
|
|
},
|
|
|
- areaStyle: {
|
|
|
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
- { offset: 0, color: 'rgba(115, 192, 222, 0.3)' },
|
|
|
- { offset: 1, color: 'rgba(115, 192, 222, 0.05)' }
|
|
|
- ])
|
|
|
- }
|
|
|
- }]
|
|
|
- };
|
|
|
-
|
|
|
- myChart.setOption(option);
|
|
|
- }).catch(() => {
|
|
|
- const option = {
|
|
|
- tooltip: {
|
|
|
- trigger: 'axis',
|
|
|
- axisPointer: {
|
|
|
- type: 'shadow'
|
|
|
- }
|
|
|
- },
|
|
|
- grid: {
|
|
|
- show: false
|
|
|
- },
|
|
|
- xAxis: {
|
|
|
- type: 'category',
|
|
|
- data: ['暂无数据'],
|
|
|
- axisLabel: {
|
|
|
- rotate: 45,
|
|
|
- fontSize: 12
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ name: '人数',
|
|
|
+ show: true
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ data: seriesData,
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ symbol: 'emptyCircle',
|
|
|
+ symbolSize: 6,
|
|
|
+ lineStyle: {
|
|
|
+ color: '#73c0de',
|
|
|
+ width: 2
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ color: '#3ba272'
|
|
|
+ },
|
|
|
+ areaStyle: {
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: 'rgba(115, 192, 222, 0.3)' },
|
|
|
+ { offset: 1, color: 'rgba(115, 192, 222, 0.05)' }
|
|
|
+ ])
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ };
|
|
|
+
|
|
|
+ myChart.setOption(option);
|
|
|
+ }).catch(() => {
|
|
|
+ const option = {
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ axisPointer: {
|
|
|
+ type: 'shadow'
|
|
|
+ }
|
|
|
},
|
|
|
- boundaryGap: false
|
|
|
- },
|
|
|
- yAxis: {
|
|
|
- type: 'value',
|
|
|
- name: '人数',
|
|
|
- show: true
|
|
|
- },
|
|
|
- series: [{
|
|
|
- data: [0],
|
|
|
- type: 'line',
|
|
|
- smooth: true,
|
|
|
- symbol: 'emptyCircle',
|
|
|
- symbolSize: 6,
|
|
|
- lineStyle: {
|
|
|
- color: '#73c0de',
|
|
|
- width: 2
|
|
|
+ grid: {
|
|
|
+ show: false
|
|
|
},
|
|
|
- itemStyle: {
|
|
|
- color: '#3ba272'
|
|
|
- }
|
|
|
- }]
|
|
|
- };
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: ['暂无数据'],
|
|
|
+ axisLabel: {
|
|
|
+ rotate: 45,
|
|
|
+ fontSize: 12
|
|
|
+ },
|
|
|
+ boundaryGap: false
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ name: '人数',
|
|
|
+ show: true
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ data: [0],
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ symbol: 'emptyCircle',
|
|
|
+ symbolSize: 6,
|
|
|
+ lineStyle: {
|
|
|
+ color: '#73c0de',
|
|
|
+ width: 2
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ color: '#3ba272'
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ };
|
|
|
|
|
|
- myChart.setOption(option);
|
|
|
+ myChart.setOption(option);
|
|
|
+ });
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+/** 处理年份变化 */
|
|
|
+function handleYearChange() {
|
|
|
+ getList();
|
|
|
+}
|
|
|
+
|
|
|
+/** 年份变更处理函数 */
|
|
|
+function changeYear(offset) {
|
|
|
+ const newYear = parseInt(queryParams.value.year) + offset;
|
|
|
+ if (newYear >= 2020 && newYear <= new Date().getFullYear()) {
|
|
|
+ queryParams.value.year = newYear.toString();
|
|
|
+ getList();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/** 跳转到指定页面 */
|
|
|
function goToPage(path) {
|
|
|
router.push(path)
|
|
|
@@ -567,6 +626,87 @@ onUnmounted(() => {
|
|
|
background-color: #f5f7f9;
|
|
|
}
|
|
|
|
|
|
+.year-selector-card {
|
|
|
+ border-radius: 12px;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ height: 100px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: linear-gradient(135deg, #ecf5ff 0%, #d2e6ff 100%);
|
|
|
+ color: #409EFF;
|
|
|
+ border: 2px solid #b3d8ff;
|
|
|
+}
|
|
|
+
|
|
|
+.year-selector-card:hover {
|
|
|
+ transform: translateY(-3px);
|
|
|
+ box-shadow: 0 6px 16px rgba(64, 158, 255, 0.3);
|
|
|
+ border-color: #409EFF;
|
|
|
+}
|
|
|
+
|
|
|
+.year-selector-content {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 100%;
|
|
|
+ padding: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.year-title {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #409EFF;
|
|
|
+ margin-bottom: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+.year-title .el-icon {
|
|
|
+ color: #409EFF;
|
|
|
+}
|
|
|
+
|
|
|
+.year-controls {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 8px;
|
|
|
+ flex-wrap: nowrap;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.year-select {
|
|
|
+ width: 70px;
|
|
|
+}
|
|
|
+
|
|
|
+.year-nav-btn {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #409EFF;
|
|
|
+ padding: 4px;
|
|
|
+ border-radius: 50%;
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background-color: #ecf5ff;
|
|
|
+ border: 1px solid #b3d8ff;
|
|
|
+}
|
|
|
+
|
|
|
+.year-nav-btn:hover {
|
|
|
+ background-color: #409EFF;
|
|
|
+ color: white;
|
|
|
+ border-color: #409EFF;
|
|
|
+}
|
|
|
+
|
|
|
+.year-nav-btn.is-disabled {
|
|
|
+ color: #c0c4cc;
|
|
|
+ border-color: #dcdfe6;
|
|
|
+}
|
|
|
+
|
|
|
.stat-card {
|
|
|
text-align: center;
|
|
|
border-radius: 12px;
|
|
|
@@ -810,7 +950,7 @@ onUnmounted(() => {
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
}
|
|
|
|
|
|
- .el-col-4 {
|
|
|
+ .el-col-3 {
|
|
|
width: 50%;
|
|
|
margin-bottom: 15px;
|
|
|
}
|