Jelajahi Sumber

调整首页,取消版权

wukai 3 bulan lalu
induk
melakukan
158b5a8d4f
7 mengubah file dengan 392 tambahan dan 252 penghapusan
  1. 1 1
      .env.development
  2. 2 2
      .env.production
  3. 2 2
      .env.staging
  4. 1 1
      index.html
  5. 1 1
      package.json
  6. 380 240
      src/views/index.vue
  7. 5 5
      src/views/login.vue

+ 1 - 1
.env.development

@@ -4,5 +4,5 @@ VITE_APP_TITLE = 出入境智能分析系统
 # 开发环境配置
 VITE_APP_ENV = 'development'
 
-# 出入境人员智能分析系统/开发环境
+# 出入境智能分析系统/开发环境
 VITE_APP_BASE_API = '/dev-api'

+ 2 - 2
.env.production

@@ -1,10 +1,10 @@
 # 页面标题
-VITE_APP_TITLE = 出入境人员智能分析系统
+VITE_APP_TITLE = 出入境智能分析系统
 
 # 生产环境配置
 VITE_APP_ENV = 'production'
 
-# 出入境人员智能分析系统/生产环境
+# 出入境智能分析系统/生产环境
 VITE_APP_BASE_API = '/prod-api'
 
 # 是否在打包时开启压缩,支持 gzip 和 brotli

+ 2 - 2
.env.staging

@@ -1,10 +1,10 @@
 # 页面标题
-VITE_APP_TITLE = 出入境人员智能分析系统
+VITE_APP_TITLE = 出入境智能分析系统
 
 # 生产环境配置
 VITE_APP_ENV = 'staging'
 
-# 出入境人员智能分析系统/生产环境
+# 出入境智能分析系统/生产环境
 VITE_APP_BASE_API = '/stage-api'
 
 # 是否在打包时开启压缩,支持 gzip 和 brotli

+ 1 - 1
index.html

@@ -7,7 +7,7 @@
   <meta name="renderer" content="webkit">
   <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
   <link rel="icon" href="/favicon.ico">
-  <title>出入境人员智能分析系统</title>
+  <title>出入境智能分析系统</title>
   <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
   <style>
     html,

+ 1 - 1
package.json

@@ -1,7 +1,7 @@
 {
   "name": "ruoyi",
   "version": "3.8.9",
-  "description": "出入境人员智能分析系统",
+  "description": "出入境智能分析系统",
   "author": "聚聚通",
   "license": "MIT",
   "type": "module",

+ 380 - 240
src/views/index.vue

@@ -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;
   }

+ 5 - 5
src/views/login.vue

@@ -58,9 +58,9 @@
       </el-form-item>
     </el-form>
     <!--  底部  -->
-    <div class="el-login-footer">
-      <span>Copyright © 2018-2025 ruoyi.vip All Rights Reserved.</span>
-    </div>
+<!--    <div class="el-login-footer">-->
+<!--      <span>Copyright © 2018-2025 ruoyi.vip All Rights Reserved.</span>-->
+<!--    </div>-->
   </div>
 </template>
 
@@ -76,8 +76,8 @@ const router = useRouter();
 const { proxy } = getCurrentInstance();
 
 const loginForm = ref({
-  username: "admin",
-  password: "admin123",
+  username: "",
+  password: "",
   rememberMe: false,
   code: "",
   uuid: ""