|
|
@@ -0,0 +1,911 @@
|
|
|
+<template>
|
|
|
+ <div class="app-container">
|
|
|
+ <el-form :model="queryParams" ref="queryRef" :inline="true" label-width="68px">
|
|
|
+ <el-form-item label="查询方式" prop="queryType">
|
|
|
+ <el-select v-model="queryParams.queryType" placeholder="请选择查询方式" clearable style="width: 150px;" @change="handleQuery">
|
|
|
+ <el-option label="按年查询" value="year" />
|
|
|
+ <el-option label="按月查询" value="month" />
|
|
|
+ <el-option label="按时段查询" value="range" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="queryParams.queryType === 'year'" label="年份" prop="year">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="queryParams.year"
|
|
|
+ type="year"
|
|
|
+ value-format="YYYY"
|
|
|
+ placeholder="请选择年份"
|
|
|
+ @change="handleQuery">
|
|
|
+ </el-date-picker>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="queryParams.queryType === 'month'" label="月份" prop="month">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="queryParams.month"
|
|
|
+ type="month"
|
|
|
+ value-format="YYYY-MM"
|
|
|
+ placeholder="请选择月份"
|
|
|
+ @change="handleQuery">
|
|
|
+ </el-date-picker>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="queryParams.queryType === 'range'" label="时段" prop="dateRange">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="queryParams.dateRange"
|
|
|
+ type="daterange"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ range-separator="至"
|
|
|
+ start-placeholder="开始日期"
|
|
|
+ end-placeholder="结束日期"
|
|
|
+ @change="handleQuery">
|
|
|
+ </el-date-picker>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ <!-- 统计数据卡片 -->
|
|
|
+ <el-row :gutter="20" class="mb20">
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card total-people">
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-number">{{ statistics.totalPeople }}</div>
|
|
|
+ <div class="stat-label">总人数</div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card total-entries">
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-number">{{ statistics.totalEntries }}</div>
|
|
|
+ <div class="stat-label">总次数</div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card avg-frequency">
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-number">{{ statistics.avgFrequency }}</div>
|
|
|
+ <div class="stat-label">平均频次</div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card max-frequency">
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-number">{{ statistics.maxFrequency }}</div>
|
|
|
+ <div class="stat-label">最高频次</div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row :gutter="10" style="margin-bottom: 20px;">
|
|
|
+ <div style="height: 500px; width: 49%; margin-right: 1%;">
|
|
|
+ <div class="table_caption" style="height: 30px; line-height: 30px;">高频出入境统计图(Top10)</div>
|
|
|
+ <div style="height: 470px; width: 100%; border: 1px solid #ededed; background: #fff;" ref="top10">
|
|
|
+ <div id="top10Chart" style="height: 100%; width: 100%;"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div style="height: 500px; width: 49%;">
|
|
|
+ <div class="table_caption" style="height: 30px; line-height: 30px;">出入境频次分布</div>
|
|
|
+ <div style="height: 470px; width: 100%; border: 1px solid #ededed; background: #fff;" ref="pieChartContainer">
|
|
|
+ <div id="pieChart" style="height: 100%; width: 100%;"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-table v-loading="loading" :data="list" style="margin-top: 20px;">
|
|
|
+ <el-table-column label="序号" type="index" width="50" align="center" />
|
|
|
+ <el-table-column label="姓名" prop="fullName" align="center" />
|
|
|
+ <el-table-column label="证件号码" prop="idNumber" align="center" />
|
|
|
+ <el-table-column label="出入境次数" prop="count" align="center" />
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <pagination
|
|
|
+ v-show="total>0"
|
|
|
+ :total="total"
|
|
|
+ v-model:page="queryParams.pageNum"
|
|
|
+ v-model:limit="queryParams.pageSize"
|
|
|
+ @pagination="handlePagination"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup name="High">
|
|
|
+import * as echarts from 'echarts'
|
|
|
+import { listHighFrequencyInOut, getInOutStats } from "@/api/biz/anal";
|
|
|
+
|
|
|
+const { proxy } = getCurrentInstance();
|
|
|
+
|
|
|
+const list = ref([]);
|
|
|
+const loading = ref(false);
|
|
|
+
|
|
|
+const total = ref(0);
|
|
|
+const barChartContainer = ref(null);
|
|
|
+const pieChartContainer = ref(null);
|
|
|
+const top10 = ref(null); // 添加Top10容器引用
|
|
|
+
|
|
|
+const statistics = ref({
|
|
|
+ totalPeople: 0,
|
|
|
+ totalEntries: 0,
|
|
|
+ avgFrequency: 0,
|
|
|
+ maxFrequency: 0
|
|
|
+});
|
|
|
+
|
|
|
+const data = reactive({
|
|
|
+ queryParams: {
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ queryType: 'year',
|
|
|
+ year: new Date().getFullYear().toString(),
|
|
|
+ month: `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}`, // 当前年月
|
|
|
+ dateRange: [
|
|
|
+ new Date(new Date().setDate(new Date().getDate() - 6)).toISOString().split('T')[0], // 7天前
|
|
|
+ new Date().toISOString().split('T')[0] // 今天
|
|
|
+ ]
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const { queryParams } = toRefs(data);
|
|
|
+
|
|
|
+// 监听查询方式变化,设置默认值
|
|
|
+watch(() => queryParams.value.queryType, (newVal) => {
|
|
|
+ if (newVal === 'month' && (!queryParams.value.month || queryParams.value.month === null)) {
|
|
|
+ // 当选择按月查询时,默认选中当月
|
|
|
+ const now = new Date();
|
|
|
+ queryParams.value.month = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
|
|
+ // 立即查询
|
|
|
+ handleQuery();
|
|
|
+ } else if (newVal === 'range' && (!queryParams.value.dateRange || queryParams.value.dateRange.length === 0)) {
|
|
|
+ // 当选择按时段查询时,默认最近7天
|
|
|
+ const now = new Date();
|
|
|
+ const startDate = new Date();
|
|
|
+ startDate.setDate(now.getDate() - 6); // 最近7天
|
|
|
+
|
|
|
+ const startStr = `${startDate.getFullYear()}-${String(startDate.getMonth() + 1).padStart(2, '0')}-${String(startDate.getDate()).padStart(2, '0')}`;
|
|
|
+ const endStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
|
+
|
|
|
+ queryParams.value.dateRange = [startStr, endStr];
|
|
|
+ // 立即查询
|
|
|
+ handleQuery();
|
|
|
+ } else if (newVal === 'year') {
|
|
|
+ // 当选择按年查询时,也立即查询
|
|
|
+ handleQuery();
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+/** 查询高频出入境列表 */
|
|
|
+function getList() {
|
|
|
+ loading.value = true;
|
|
|
+
|
|
|
+ // 准备查询参数
|
|
|
+ const params = {};
|
|
|
+ if (queryParams.value.queryType === 'year') {
|
|
|
+ params.year = queryParams.value.year;
|
|
|
+ } else if (queryParams.value.queryType === 'month') {
|
|
|
+ params.month = queryParams.value.month;
|
|
|
+ } else if (queryParams.value.queryType === 'range') {
|
|
|
+ if (queryParams.value.dateRange && queryParams.value.dateRange.length === 2) {
|
|
|
+ params.startDate = queryParams.value.dateRange[0];
|
|
|
+ params.endDate = queryParams.value.dateRange[1];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 同时调用列表接口和统计接口
|
|
|
+ Promise.all([
|
|
|
+ listHighFrequencyInOut(params),
|
|
|
+ getInOutStats(params)
|
|
|
+ ]).then(responses => {
|
|
|
+ // 处理列表数据
|
|
|
+ const listResponse = responses[0];
|
|
|
+ list.value = listResponse.rows || [];
|
|
|
+ total.value = listResponse.total || 0;
|
|
|
+
|
|
|
+ // 处理统计信息
|
|
|
+ const statsResponse = responses[1];
|
|
|
+ if (statsResponse.code === 200 && statsResponse.data) {
|
|
|
+ const stats = statsResponse.data;
|
|
|
+ statistics.value = {
|
|
|
+ totalPeople: stats.totalPeople || 0,
|
|
|
+ totalEntries: stats.totalCount || 0, // 后端返回的是totalCount字段
|
|
|
+ avgFrequency: Number(stats.avgFrequency).toFixed(2) || 0,
|
|
|
+ maxFrequency: stats.maxFrequency || 0
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ // 如果新接口调用失败,使用原有方式计算统计数据
|
|
|
+ calculateStatistics();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新图表
|
|
|
+ nextTick(() => {
|
|
|
+ renderBarChart();
|
|
|
+ renderPieChart();
|
|
|
+ renderTop10Chart(); // 添加Top10图表渲染
|
|
|
+ });
|
|
|
+
|
|
|
+ loading.value = false;
|
|
|
+ }).catch(() => {
|
|
|
+ loading.value = false;
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+/** 处理分页事件 */
|
|
|
+function handlePagination() {
|
|
|
+ // 分页只更新表格数据,不重新获取统计和图表数据
|
|
|
+ loading.value = true;
|
|
|
+
|
|
|
+ // 准备查询参数(仅用于表格数据)
|
|
|
+ const params = {};
|
|
|
+ if (queryParams.value.queryType === 'year') {
|
|
|
+ params.year = queryParams.value.year;
|
|
|
+ } else if (queryParams.value.queryType === 'month') {
|
|
|
+ params.month = queryParams.value.month;
|
|
|
+ } else if (queryParams.value.queryType === 'range') {
|
|
|
+ if (queryParams.value.dateRange && queryParams.value.dateRange.length === 2) {
|
|
|
+ params.startDate = queryParams.value.dateRange[0];
|
|
|
+ params.endDate = queryParams.value.dateRange[1];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加分页参数
|
|
|
+ params.pageNum = queryParams.value.pageNum;
|
|
|
+ params.pageSize = queryParams.value.pageSize;
|
|
|
+
|
|
|
+ listHighFrequencyInOut(params).then(response => {
|
|
|
+ list.value = response.rows || [];
|
|
|
+ total.value = response.total || 0;
|
|
|
+
|
|
|
+ loading.value = false;
|
|
|
+ }).catch(() => {
|
|
|
+ loading.value = false;
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+/** 计算统计数据 */
|
|
|
+function calculateStatistics() {
|
|
|
+ if (!list.value || list.value.length === 0) {
|
|
|
+ statistics.value = {
|
|
|
+ totalPeople: 0,
|
|
|
+ totalEntries: 0,
|
|
|
+ avgFrequency: 0,
|
|
|
+ maxFrequency: 0
|
|
|
+ };
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ let totalEntries = 0;
|
|
|
+ let maxFreq = 0;
|
|
|
+
|
|
|
+ list.value.forEach(item => {
|
|
|
+ totalEntries += item.count || 0;
|
|
|
+ if ((item.count || 0) > maxFreq) {
|
|
|
+ maxFreq = item.count;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ statistics.value = {
|
|
|
+ totalPeople: list.value.length,
|
|
|
+ totalEntries: totalEntries,
|
|
|
+ avgFrequency: list.value.length > 0 ? (totalEntries / list.value.length).toFixed(2) : 0,
|
|
|
+ maxFrequency: maxFreq
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+/** 从前端数据计算频次分布 */
|
|
|
+function calculateFrequencyDistributionFromList() {
|
|
|
+ // 按频次区间分组
|
|
|
+ const ranges = [
|
|
|
+ { name: '1-5次', count: 0, color: '#5470c6' },
|
|
|
+ { name: '6-10次', count: 0, color: '#91cc75' },
|
|
|
+ { name: '11-20次', count: 0, color: '#fac858' },
|
|
|
+ { name: '21-50次', count: 0, color: '#ee6666' },
|
|
|
+ { name: '50次以上', count: 0, color: '#73c0de' }
|
|
|
+ ];
|
|
|
+
|
|
|
+ list.value.forEach(item => {
|
|
|
+ const freq = item.count || 0;
|
|
|
+ if (freq >= 1 && freq <= 5) {
|
|
|
+ ranges[0].count++;
|
|
|
+ } else if (freq > 5 && freq <= 10) {
|
|
|
+ ranges[1].count++;
|
|
|
+ } else if (freq > 10 && freq <= 20) {
|
|
|
+ ranges[2].count++;
|
|
|
+ } else if (freq > 20 && freq <= 50) {
|
|
|
+ ranges[3].count++;
|
|
|
+ } else if (freq > 50) {
|
|
|
+ ranges[4].count++;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return ranges.filter(range => range.count > 0).map(range => ({
|
|
|
+ value: range.count,
|
|
|
+ name: range.name,
|
|
|
+ itemStyle: { color: range.color }
|
|
|
+ }));
|
|
|
+}
|
|
|
+
|
|
|
+/** 渲染柱状图 */
|
|
|
+function renderBarChart() {
|
|
|
+ if (!barChartContainer.value) return;
|
|
|
+
|
|
|
+ const myChart = echarts.init(barChartContainer.value);
|
|
|
+
|
|
|
+ const names = list.value.slice(0, 10).map(item => item.fullName || '未知');
|
|
|
+ const frequencies = list.value.slice(0, 10).map(item => item.count || 0);
|
|
|
+
|
|
|
+ const option = {
|
|
|
+ title: {
|
|
|
+ text: '前10名高频出入境人员',
|
|
|
+ left: 'center'
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ axisPointer: {
|
|
|
+ type: 'shadow'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: names,
|
|
|
+ axisLabel: {
|
|
|
+ rotate: 45,
|
|
|
+ fontSize: 12
|
|
|
+ }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ name: '出入境次数'
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ data: frequencies,
|
|
|
+ type: 'bar',
|
|
|
+ itemStyle: {
|
|
|
+ color: '#409eff'
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ };
|
|
|
+
|
|
|
+ myChart.setOption(option);
|
|
|
+}
|
|
|
+
|
|
|
+/** 渲染饼图 */
|
|
|
+function renderPieChart() {
|
|
|
+ if (!pieChartContainer.value) return;
|
|
|
+
|
|
|
+ const myChart = echarts.init(pieChartContainer.value);
|
|
|
+
|
|
|
+ // 准备查询参数
|
|
|
+ const params = {};
|
|
|
+ if (queryParams.value.queryType === 'year') {
|
|
|
+ params.year = queryParams.value.year;
|
|
|
+ } else if (queryParams.value.queryType === 'month') {
|
|
|
+ params.month = queryParams.value.month;
|
|
|
+ } else if (queryParams.value.queryType === 'range') {
|
|
|
+ if (queryParams.value.dateRange && queryParams.value.dateRange.length === 2) {
|
|
|
+ params.startDate = queryParams.value.dateRange[0];
|
|
|
+ params.endDate = queryParams.value.dateRange[1];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 调用后端接口获取频次分布数据
|
|
|
+ getInOutStats(params).then(response => {
|
|
|
+ let distributionData = [];
|
|
|
+
|
|
|
+ if (response.code === 200 && response.data) {
|
|
|
+ const stats = response.data;
|
|
|
+
|
|
|
+ // 检查是否有频次分布数据,优先使用后端返回的分布数据
|
|
|
+ if (stats.frequencyDistribution && typeof stats.frequencyDistribution === 'object') {
|
|
|
+ // 后端返回的是对象格式的频次分布数据,例如 {"1-5": 1023, "5-10": 1276, ...}
|
|
|
+ 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'; // 30+
|
|
|
+ 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'; // 30+
|
|
|
+ else color = '#9da4b0'; // 默认颜色
|
|
|
+
|
|
|
+ distributionData.push({
|
|
|
+ value: count,
|
|
|
+ name: range,
|
|
|
+ itemStyle: { color: color }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 如果后端没有返回频次分布数据,使用统计数据中的信息或者前端计算
|
|
|
+ // 尝试使用已有的统计数据创建分布
|
|
|
+ distributionData = calculateFrequencyDistributionFromList();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 如果API调用失败,使用前端计算
|
|
|
+ distributionData = calculateFrequencyDistributionFromList();
|
|
|
+ }
|
|
|
+
|
|
|
+ const option = {
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item'
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ orient: 'vertical',
|
|
|
+ left: 'right'
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ name: '人数',
|
|
|
+ type: 'pie',
|
|
|
+ radius: '50%',
|
|
|
+ data: distributionData,
|
|
|
+ emphasis: {
|
|
|
+ itemStyle: {
|
|
|
+ shadowBlur: 10,
|
|
|
+ shadowOffsetX: 0,
|
|
|
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ };
|
|
|
+
|
|
|
+ myChart.setOption(option);
|
|
|
+ }).catch(() => {
|
|
|
+ // 错误处理,使用前端计算方式
|
|
|
+ const distributionData = calculateFrequencyDistributionFromList();
|
|
|
+
|
|
|
+ const option = {
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item'
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ orient: 'vertical',
|
|
|
+ left: 'right'
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ name: '人数',
|
|
|
+ type: 'pie',
|
|
|
+ radius: '50%',
|
|
|
+ data: distributionData,
|
|
|
+ emphasis: {
|
|
|
+ itemStyle: {
|
|
|
+ shadowBlur: 10,
|
|
|
+ shadowOffsetX: 0,
|
|
|
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ };
|
|
|
+
|
|
|
+ myChart.setOption(option);
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+/** 渲染Top10图表 */
|
|
|
+function renderTop10Chart() {
|
|
|
+ if (!top10.value) return;
|
|
|
+
|
|
|
+ const myChart = echarts.init(top10.value);
|
|
|
+
|
|
|
+ const top10Data = list.value.slice(0, 10);
|
|
|
+ let data1 = [];
|
|
|
+ let data2 = [];
|
|
|
+ let data3 = [];
|
|
|
+
|
|
|
+ for (let index = 0; index < top10Data.length; index++) {
|
|
|
+ const element = top10Data[index];
|
|
|
+ data3.push(element.count || 0);
|
|
|
+ data2.push(index+1);
|
|
|
+ data1.push({
|
|
|
+ name: element.fullName || '未知',
|
|
|
+ value: element.count || 0
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ let data4 = [100, 100, 100, 100, 100, 100, 100, 100, 100, 100];
|
|
|
+ let style = {
|
|
|
+ width: 24,
|
|
|
+ height: 18,
|
|
|
+ padding: [2, 2, 0, 0],
|
|
|
+ fontSize: 12,
|
|
|
+ align: "center",
|
|
|
+ color: "#ffffff"
|
|
|
+ };
|
|
|
+
|
|
|
+ var option = {
|
|
|
+ tooltip: {
|
|
|
+ show: true,
|
|
|
+ trigger: "item",
|
|
|
+ padding: [8, 15],
|
|
|
+ backgroundColor: "rgba(12, 51, 115,0.8)",
|
|
|
+ borderColor: "rgba(3, 11, 44, 0.5)",
|
|
|
+ textStyle: {
|
|
|
+ color: "rgba(255, 255, 255, 1)",
|
|
|
+ fontFamily: "SourceHanSansCN-Normal",
|
|
|
+ fontSize: 14
|
|
|
+ },
|
|
|
+ formatter: function (params) {
|
|
|
+ if(params.seriesIndex==0) return "";
|
|
|
+ const name = params.name;
|
|
|
+ const value = params.value;
|
|
|
+ return `${name}<br/>出入境次数:${value}`;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ left: "10%",
|
|
|
+ right: "8%",
|
|
|
+ top: "1%",
|
|
|
+ bottom: "2%"
|
|
|
+ },
|
|
|
+ xAxis: [
|
|
|
+ {
|
|
|
+ splitLine: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ type: "value",
|
|
|
+ show: false,
|
|
|
+ axisLine: {
|
|
|
+ show: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ yAxis: [
|
|
|
+ {
|
|
|
+ show: true,
|
|
|
+ splitLine: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ axisLine: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ type: "category",
|
|
|
+ axisTick: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ axisPointer: { show: false },
|
|
|
+ triggerEvent: false,
|
|
|
+ inverse: true,
|
|
|
+ data: data2,
|
|
|
+ axisLabel: {
|
|
|
+ show: true,
|
|
|
+ margin: 20,
|
|
|
+ textStyle: {
|
|
|
+ color: "rgba(255, 255, 255, 1)",
|
|
|
+ fontFamily: "SourceHanSansCN-Normal",
|
|
|
+ fontSize: 14
|
|
|
+ },
|
|
|
+ formatter: function (params, index) {
|
|
|
+ const id = index + 1;
|
|
|
+ if (id < 4) {
|
|
|
+ return [`{rank${id}|${id}}`].join("\n");
|
|
|
+ } else {
|
|
|
+ return [`{rank|${id}}`].join("\n");
|
|
|
+ }
|
|
|
+ },
|
|
|
+ rich: {
|
|
|
+ rank1: {
|
|
|
+ ...style,
|
|
|
+ backgroundColor: {
|
|
|
+ image:
|
|
|
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAASCAYAAABB7B6eAAAAAXNSR0IArs4c6QAAARZJREFUOE/tlb1KA0EUhc+5u41F1lbB1tY0Gt8iPoD2eQFtLXwAU/sMIoKQQd0tUghiqW1awZQWQiSGTLzjzrKbymKmCRkYhjt/3zDnzhza/VYXIlegbIEJQEGzTf9i0TGtZVz1+VjGsGmP9/kdaoW2k324zcGljT3o3wBdP6YptpcB1p06DAA0BdeAUisvfCWy17CPweMpgblelYocVIMyC2+x8XnM6+dJLAAAecH0pxsP4DJTBjEBT5jOjmIBbvA1OeFw+B0egOQS5uEsWhat4ks+yOYQ0a80xG9qaQrNz6rQdjZfQe4FArzRFO0m4DDb/XWZC0B2nKFASnOp+YEzG41TQFIA3nh0bmVA75gl58zzUR2wADMYXqBI8VUUAAAAAElFTkSuQmCC"
|
|
|
+ }
|
|
|
+ },
|
|
|
+ rank2: {
|
|
|
+ ...style,
|
|
|
+ backgroundColor: {
|
|
|
+ image:
|
|
|
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAASCAYAAABB7B6eAAAAAXNSR0IArs4c6QAAAPFJREFUOE/tlTFKA0EUhr9/UuUGK9imTW4SD6CNlc2GgJgubBYPoLWHEEHwBjaWprUVIsQ+m2YnZHYdZtdydxrxwcDMMLwP5r33/7I5U9ADKEHQXLY6m9Z9eHZ79+4LuNKMZ4KQXWmDlLi7bgAHUcpJC2CsS9wPAKU+W5XSrv4Bvsg/Nbxny41yyihfVFfgiT3numbXew18J8IbYhoPULX8SzwAvFJwFgvwyJALXVL0D4A7vllE66K/OMmZKTH1eHRXU6vUibuPo1y/I417UtO1UiZNwJIRA3OL7Kk3l1C6f5lL7RsNE3KG80lJpjkfIeAAkjJvqT1lfksAAAAASUVORK5CYII="
|
|
|
+ }
|
|
|
+ },
|
|
|
+ rank3: {
|
|
|
+ ...style,
|
|
|
+ backgroundColor: {
|
|
|
+ image:
|
|
|
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAASCAYAAABB7B6eAAAAAXNSR0IArs4c6QAAAPBJREFUOE/tlT9OwzAUh7+fYWfpAFIZOrBSpblHegC69wLtihAHoDOHQAgabsDSsV1ZKzUnaDvFla00JFG31AviWZZly3rfs98/2ZQE8Yq4xokBVJnHfXU9dSYyDGMN+PR6CpFN2XjlTmk7gDMsU8xNE2BLi9sDUFya6jnuBf+AemAYZnww0RN5kC8q/PlOhwfdsju7DzzAhTEsEElIgAN9hQR8s2UYCvDGFSP12IcAvBAxlQJF0d/M5Bz50b6aGqwGRRaU5XrOEsP9WQBipZh+vVzPuUM8Y+iWZfvYUNzNi0bzaTab38xdc8mjIn6qgAME+UlV+Na7MwAAAABJRU5ErkJggg=="
|
|
|
+ }
|
|
|
+ },
|
|
|
+ rank: {
|
|
|
+ ...style,
|
|
|
+ backgroundColor: {
|
|
|
+ image:
|
|
|
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAASCAYAAABB7B6eAAAAAXNSR0IArs4c6QAAAQdJREFUOE/tlb9KxEAQh7/fWvkGCra2+hK59ixWbLSx8gW0FfEBFHOVna0JhyD4BjaWprUVvM5SDzQjm0tCTsQmt424zbDsn29mmPmNBiMbCi4RKxIgCFbuF/vz2QTHQe51S2cpSe1F9ec9AcGpSea1OgcYpGat1/0iqKLOfOVmu/QP+F4QiPOs4JATlSFPC09RABrc2Ae7+Y7eogDqonmYlgxjAkI/3cUDOO4/p2zFATjGy6/sXe3rPQbg7LrgKFoV/cFOTlIrJVR1ZE+xw2G5D5LXEbsktUeJjQUBitxrc15NL2wdxylizTUR1AMldKRrhsvSLML2TrMP/s7ePZs4Hm/rqQv4AucUYfTiSAZXAAAAAElFTkSuQmCC"
|
|
|
+ }
|
|
|
+ },
|
|
|
+ a: {
|
|
|
+ padding: [0, 0, 35, 10]
|
|
|
+ },
|
|
|
+
|
|
|
+ b: {
|
|
|
+ backgroundColor: {
|
|
|
+ type: "linear",
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ x2: 1,
|
|
|
+ y2: 0,
|
|
|
+ colorStops: [
|
|
|
+ {
|
|
|
+ offset: 0,
|
|
|
+ color: "#29DFF6"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ offset: 1,
|
|
|
+ color: "#00A8FF"
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ global: false
|
|
|
+ },
|
|
|
+ color: "#D1E7FF",
|
|
|
+ width: 60,
|
|
|
+ height: 60,
|
|
|
+ padding: [5, 0, 0, 0],
|
|
|
+ borderRadius: 30,
|
|
|
+ fontSize: 14,
|
|
|
+ align: "center",
|
|
|
+ fontWeight: 500
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: "category",
|
|
|
+ inverse: true,
|
|
|
+ axisTick: "none",
|
|
|
+ axisLine: "none",
|
|
|
+ show: true,
|
|
|
+ position: "right",
|
|
|
+ axisLabel: {
|
|
|
+ inside: true,
|
|
|
+ padding: [-24, 0, 0, 0],
|
|
|
+ margin: 0,
|
|
|
+ show: true,
|
|
|
+ textStyle: {
|
|
|
+ verticalAlign: "top",
|
|
|
+ color: "#445482",
|
|
|
+ fontFamily: "SourceHanSansCN-Normal",
|
|
|
+ fontSize: 14
|
|
|
+ },
|
|
|
+ formatter: function (value) {
|
|
|
+ return value + "";
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data: data3
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: "背景",
|
|
|
+ type: "bar",
|
|
|
+ barWidth: 12,
|
|
|
+ barGap: "-100%",
|
|
|
+ data: data4,
|
|
|
+ itemStyle: {
|
|
|
+ normal: {
|
|
|
+ color: "#D1E7FF",
|
|
|
+ barBorderRadius: 10
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ show: true,
|
|
|
+ name: "",
|
|
|
+ type: "bar",
|
|
|
+ data: data1,
|
|
|
+ barWidth: 12,
|
|
|
+ showBackground: false,
|
|
|
+ backgroundStyle: {
|
|
|
+ color: {
|
|
|
+ type: "linear",
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ x2: 1,
|
|
|
+ y2: 1,
|
|
|
+ colorStops: [
|
|
|
+ {
|
|
|
+ offset: 0,
|
|
|
+ color: "#D1E7FF"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ offset: 1,
|
|
|
+ color: "#D1E7FF"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ offset: [5, -17],
|
|
|
+ color: "#132852",
|
|
|
+ fontFamily: "SourceHanSansCN-Normal",
|
|
|
+ fontSize: 14,
|
|
|
+ fontWeight: 500,
|
|
|
+ position: "left",
|
|
|
+ align: "left",
|
|
|
+ formatter: function (params) {
|
|
|
+ return params.data.name;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ barBorderRadius: [5, 5, 5, 5],
|
|
|
+ color: {
|
|
|
+ type: "linear",
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ x2: 1,
|
|
|
+ y2: 0,
|
|
|
+ colorStops: [
|
|
|
+ {
|
|
|
+ offset: 0,
|
|
|
+ color: "#29DFF6"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ offset: 1,
|
|
|
+ color: "#00A8FF"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ dataZoom: [
|
|
|
+ {
|
|
|
+ width: 15,
|
|
|
+ yAxisIndex: [0, 1],
|
|
|
+ show: false,
|
|
|
+ type: "slider"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+
|
|
|
+ myChart.setOption(option);
|
|
|
+}
|
|
|
+
|
|
|
+/** 搜索按钮操作 */
|
|
|
+function handleQuery() {
|
|
|
+ queryParams.value.pageNum = 1;
|
|
|
+ getList();
|
|
|
+}
|
|
|
+
|
|
|
+/** 重置按钮操作 */
|
|
|
+function resetQuery() {
|
|
|
+ proxy.resetForm("queryRef");
|
|
|
+ queryParams.value.queryType = 'year';
|
|
|
+ queryParams.value.year = new Date().getFullYear().toString();
|
|
|
+ queryParams.value.month = null;
|
|
|
+ queryParams.value.dateRange = null;
|
|
|
+ handleQuery();
|
|
|
+}
|
|
|
+
|
|
|
+// 页面加载时获取数据
|
|
|
+onMounted(() => {
|
|
|
+ // 初始化时直接查询,使用reactive中设置的默认值
|
|
|
+ handleQuery();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* 统计卡片样式 */
|
|
|
+.stat-card {
|
|
|
+ text-align: center;
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card:hover {
|
|
|
+ transform: translateY(-3px);
|
|
|
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
|
|
|
+}
|
|
|
+
|
|
|
+/* 不同统计卡片的颜色 */
|
|
|
+.stat-card.total-people {
|
|
|
+ background: linear-gradient(135deg, #e6f7ff 0%, #bae7ff 100%);
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card.total-entries {
|
|
|
+ background: linear-gradient(135deg, #f6ffed 0%, #d9f7be 100%);
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card.avg-frequency {
|
|
|
+ background: linear-gradient(135deg, #fffbe6 0%, #fff5a0 100%);
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card.max-frequency {
|
|
|
+ background: linear-gradient(135deg, #fff2f0 0%, #ffccc7 100%);
|
|
|
+}
|
|
|
+
|
|
|
+.stat-item {
|
|
|
+ padding: 15px 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-number {
|
|
|
+ font-size: 22px;
|
|
|
+ font-weight: bold;
|
|
|
+ margin-bottom: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 不同统计数字的颜色 */
|
|
|
+.stat-number {
|
|
|
+ color: #1890ff;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card:nth-child(2) .stat-number {
|
|
|
+ color: #52c41a;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card:nth-child(3) .stat-number {
|
|
|
+ color: #faad14;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card:nth-child(4) .stat-number {
|
|
|
+ color: #f5222d;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-container {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+#chart, #pieChart {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.table_caption {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ text-align: center;
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.app-container {
|
|
|
+ padding: 15px;
|
|
|
+}
|
|
|
+
|
|
|
+.el-row {
|
|
|
+ margin-bottom: 15px;
|
|
|
+}
|
|
|
+
|
|
|
+.el-table {
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
|
+ border-radius: 8px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.el-card {
|
|
|
+ border-radius: 8px;
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+</style>
|