Forráskód Böngészése

搞定节假日分析功能

wukai 4 hónapja
szülő
commit
b474930ee7
2 módosított fájl, 709 hozzáadás és 21 törlés
  1. 39 21
      src/api/biz/anal.js
  2. 670 0
      src/views/biz/anal/holiday.vue

+ 39 - 21
src/api/biz/anal.js

@@ -2,36 +2,54 @@ import request from '@/utils/request'
 
 // 查询高频出入境列表
 export function listHighFrequencyInOut(params) {
-  return request({
-    url: '/biz/anal/high',
-    method: 'get',
-    params: params
-  })
+    return request({
+        url: '/biz/anal/high',
+        method: 'get',
+        params: params
+    })
 }
 
 // 查询低频出入境列表
 export function listLowFrequencyInOut(params) {
-  return request({
-    url: '/biz/anal/low',
-    method: 'get',
-    params: params
-  })
+    return request({
+        url: '/biz/anal/low',
+        method: 'get',
+        params: params
+    })
 }
 
 // 查询高频出入境统计信息
 export function getInOutStats(params) {
-  return request({
-    url: '/biz/anal/stats',
-    method: 'get',
-    params: params
-  })
+    return request({
+        url: '/biz/anal/stats',
+        method: 'get',
+        params: params
+    })
 }
 
 // 根据证件号码查询出入境记录
 export function getRecords(params) {
-  return request({
-    url: '/biz/anal/records',
-    method: 'get',
-    params: params
-  })
-}
+    return request({
+        url: '/biz/anal/records',
+        method: 'get',
+        params: params
+    })
+}
+
+// 查询节假日出入境明细数据
+export function getHolidayInOutDetails(params) {
+    return request({
+        url: '/biz/anal/holiday',
+        method: 'get',
+        params: params
+    })
+}
+
+// 查询节假日出入境统计数据
+export function getHolidayInOutStats(params) {
+    return request({
+        url: '/biz/anal/holidayStats',
+        method: 'get',
+        params: params
+    })
+}

+ 670 - 0
src/views/biz/anal/holiday.vue

@@ -0,0 +1,670 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryRef" :inline="true" label-width="80px">
+      <el-form-item label="年份" prop="year">
+        <div style="display: flex; align-items: center;">
+          <el-button icon="ArrowLeft" @click="changeYear(-1)" style="margin-right: 10px;"></el-button>
+          <el-date-picker
+            v-model="queryParams.year"
+            type="year"
+            value-format="YYYY"
+            placeholder="请选择年份"
+            @change="handleQuery"
+            style="width: 130px">
+          </el-date-picker>
+          <el-button icon="ArrowRight" @click="changeYear(1)" style="margin-left: 10px;"></el-button>
+        </div>
+      </el-form-item>
+    </el-form>
+
+    <!-- 图表和统计卡片布局 -->
+    <div class="chart-and-stats-container">
+      <div class="chart-section">
+        <h3 class="section-title">节假日出入境统计</h3>
+        <div ref="chartRef" class="chart-container"></div>
+      </div>
+      <div class="stats-section">
+        <!-- 节假日统计信息列表 -->
+        <el-row :gutter="20" class="mb20">
+          <el-col :span="24">
+            <h3 class="section-title">节假日详情</h3>
+          </el-col>
+          <!-- 使用el-table替代卡片列表,以适应不确定数量的节假日 -->
+          <el-col :span="24">
+            <el-table
+              :data="holidayStats"
+              style="width: 100%"
+              highlight-current-row
+              @row-click="selectHoliday"
+              :row-class-name="tableRowClassName"
+            >
+              <el-table-column prop="holidayName" label="节假日名称" width="150"/>
+              <el-table-column prop="inOutCount" label="出入境次数" width="100"/>
+              <el-table-column prop="startDate" label="开始时间" width="120"/>
+              <el-table-column prop="endDate" label="结束时间" width="120"/>
+            </el-table>
+          </el-col>
+        </el-row>
+      </div>
+    </div>
+
+    <!-- 表格标题显示当前节日 -->
+    <div class="table-header">
+      <h3>
+        明细数据 -
+        <span class="selected-holiday">{{ selectedHolidayName || '请选择节假日' }}</span>
+      </h3>
+    </div>
+
+    <el-table v-loading="loading" :data="list" style="margin-top: 20px;" @row-click="handleRowClick">
+      <el-table-column label="姓名" align="center" prop="fullName" width="300"/>
+      <el-table-column label="性别" align="center" prop="genderCn"/>
+      <el-table-column label="出生日期" align="center" prop="birthDate" width="100">
+        <template #default="scope">
+          <span>{{ parseTime(scope.row.birthDate, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="国家/地区" align="center" prop="countryName"/>
+      <el-table-column label="民族" align="center" prop="ethnicityName"/>
+      <el-table-column label="出入标识" align="center" prop="inOutFlag"/>
+      <el-table-column label="出入时间" align="center" prop="inOutTime" width="180">
+        <template #default="scope">
+          <span>{{ parseTime(scope.row.inOutTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="出入口岸" align="center" prop="portCode">
+        <template #default="scope">
+          <span>{{ scope.row.portCode}}-{{scope.row.portName}}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="前往地/出发地" align="center" prop="destinationName"/>
+      <el-table-column label="节假日" align="center" prop="holidayName"/>
+      <el-table-column label="导入时间" align="center" prop="createTime" width="150">
+        <template #default="scope">
+          <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 出入境记录明细对话框 -->
+    <el-dialog :title="detailTitle" v-model="recordDetailOpen" width="900px" append-to-body>
+      <el-form :model="detailForm" label-width="120px" disabled>
+        <!-- 个人信息 -->
+        <div style="font-weight: bold; margin: 10px 0 15px 0; border-left: 4px solid #409eff; padding-left: 8px; color: #409eff;">个人信息</div>
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="人员类别:">
+              <span>{{ detailForm.personnelCategoryName }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="姓名:">
+              <span>{{ detailForm.fullName }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="性别:">
+              <span>{{ detailForm.genderCn }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="出生日期:">
+              <span>{{ parseTime(detailForm.birthDate, '{y}-{m}-{d}') }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="国家/地区:">
+              <span>{{ detailForm.countryCode }}-{{ detailForm.countryName }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="民族:">
+              <span>{{ detailForm.ethnicityName }}</span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 证件信息 -->
+        <div style="font-weight: bold; margin: 10px 0 15px 0; border-left: 4px solid #409eff; padding-left: 8px; color: #409eff;">证件信息</div>
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="证件类别:">
+              <span>{{ detailForm.idTypeName }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="证件号码:">
+              <span>{{ detailForm.idNumber }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="签证类型:">
+              <span>{{ detailForm.visaTypeName }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="签证号码:">
+              <span>{{ detailForm.visaNumber }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="停留期:">
+              <span>{{ detailForm.stayDuration }}</span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 出入境信息 -->
+        <div style="font-weight: bold; margin: 10px 0 15px 0; border-left: 4px solid #409eff; padding-left: 8px; color: #409eff;">出入境信息</div>
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="出入标识:">
+              <span>{{ detailForm.inOutFlag }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="出入时间:">
+              <span>{{ parseTime(detailForm.inOutTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="出入口岸:">
+              <span>{{ detailForm.portCode }}-{{ detailForm.portName }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="交通方式:">
+              <span>{{ detailForm.transportMode }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="交通工具:">
+              <span>{{ detailForm.transportVehicle }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="前往地/出发地:">
+              <span>{{ detailForm.destinationCode }}-{{ detailForm.destinationName }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="出入境事由:">
+              <span>{{ detailForm.reasonName }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="发证机关:">
+              <span>{{ detailForm.issuingAuthorityCode }}-{{ detailForm.issuingAuthorityName }}</span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 其他信息 -->
+        <div style="font-weight: bold; margin: 10px 0 15px 0; border-left: 4px solid #409eff; padding-left: 8px; color: #409eff;">其他信息</div>
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="自助通道标记:">
+              <span>{{ detailForm.selfServiceFlag }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="后台补录标记:">
+              <span>{{ detailForm.backfillFlag }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="创建时间:">
+              <span>{{ parseTime(detailForm.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="疑难字说明:">
+              <span>{{ detailForm.remark }}</span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="recordDetailOpen = false">关 闭</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      v-model:page="queryParams.pageNum"
+      v-model:limit="queryParams.pageSize"
+      @pagination="handlePagination"
+      layout="total, sizes, prev, pager, next, jumper"
+    />
+  </div>
+</template>
+
+<script setup name="Holiday">
+import { getHolidayInOutDetails, getHolidayInOutStats } from "@/api/biz/anal";
+import { ArrowLeft, ArrowRight } from "@element-plus/icons-vue";
+import { ElMessage, ElMessageBox } from 'element-plus';
+import * as echarts from 'echarts';
+
+const { proxy } = getCurrentInstance();
+
+const list = ref([]);
+const loading = ref(false);
+const holidayStats = ref([]);
+
+const total = ref(0);
+
+// 出入境记录明细相关
+const recordDetailOpen = ref(false);
+const detailTitle = ref("详细信息");
+const detailForm = ref({});
+
+// 当前选中的节假日
+const selectedHolidayName = ref("");
+
+// 图表相关
+const chartRef = ref(null);
+let chartInstance = null;
+
+const data = reactive({
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    year: new Date().getFullYear().toString()
+  }
+});
+
+const { queryParams } = toRefs(data);
+
+/** 初始化图表 */
+function initChart() {
+  if (chartRef.value) {
+    chartInstance = echarts.init(chartRef.value);
+
+    const chartData = holidayStats.value.map(item => ({
+      name: item.holidayName,
+      value: item.inOutCount
+    }));
+
+    const option = {
+      tooltip: {
+        trigger: 'item',
+        formatter: '{a} <br/>{b}: {c} ({d}%)'
+      },
+      legend: {
+        orient: 'horizontal',
+        top: 'bottom'
+      },
+      series: [
+        {
+          name: '出入境次数',
+          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'
+            }
+          },
+          labelLine: {
+            show: true,
+            length: 10,
+            length2: 10
+          },
+          data: chartData
+        }
+      ]
+    };
+
+    chartInstance.setOption(option);
+
+    // 添加点击事件
+    chartInstance.on('click', function(params) {
+      const clickedStat = holidayStats.value.find(item => item.holidayName === params.name);
+      if (clickedStat) {
+        selectHoliday(clickedStat);
+      }
+    });
+  }
+}
+
+/** 更新图表 */
+function updateChart() {
+  if (chartInstance) {
+    const chartData = holidayStats.value.map(item => ({
+      name: item.holidayName,
+      value: item.inOutCount
+    }));
+
+    const option = {
+      tooltip: {
+        trigger: 'item',
+        formatter: '{a} <br/>{b}: {c} ({d}%)'
+      },
+      legend: {
+        orient: 'horizontal',
+        top: 'bottom'
+      },
+      series: [
+        {
+          name: '出入境次数',
+          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'
+            }
+          },
+          labelLine: {
+            show: true,
+            length: 10,
+            length2: 10
+          },
+          data: chartData
+        }
+      ]
+    };
+
+    chartInstance.setOption(option, true);
+  }
+}
+
+/** 查询节假日出入境统计数据 */
+function getStats() {
+  const params = {
+    year: queryParams.value.year
+  };
+
+  getHolidayInOutStats(params).then(response => {
+    holidayStats.value = response.data || [];
+
+    // 初始化图表
+    nextTick(() => {
+      initChart();
+    });
+
+    // 如果有节假日数据,找到第一个统计数据不为0的记录
+    if (holidayStats.value.length > 0) {
+      // 找到第一个统计数据不为0的记录
+      const firstNonZeroHoliday = holidayStats.value.find(stat => stat.inOutCount > 0);
+      // 如果没有找到统计数据不为0的记录,则使用第一条记录
+      const selectedHoliday = firstNonZeroHoliday || holidayStats.value[0];
+
+      selectedHolidayName.value = selectedHoliday.holidayName;
+
+      // 更新查询参数
+      queryParams.value.startDate = selectedHoliday.startDate;
+      queryParams.value.endDate = selectedHoliday.endDate;
+
+      // 使用找到的节假日的开始结束时间查询明细数据
+      getList(selectedHoliday.startDate, selectedHoliday.endDate);
+    } else {
+      // 如果没有节假日数据,清空表格并弹出提示
+      list.value = [];
+      total.value = 0;
+      
+      // 弹出模态框提示信息,阻止其他操作
+      ElMessageBox.alert(`${queryParams.value.year}年度节假日未配置,请先到参数配置/节假日配置中配置 ${queryParams.value.year} 年度的节假日。`, '提示', {
+        type: 'warning',
+        confirmButtonText: '确定',
+        closeOnClickModal: false, // 点击遮罩层不关闭
+        closeOnPressEscape: false // 按ESC键不关闭
+      });
+    }
+  }).catch(() => {
+    holidayStats.value = [];
+    list.value = [];
+    total.value = 0;
+  });
+}
+
+/** 查询节假日出入境明细数据 - 只更新表格数据,不重新加载统计信息 */
+function getList(startDate, endDate) {
+  loading.value = true;
+
+  // 准备查询参数
+  const params = {
+    year: queryParams.value.year,
+    startDate: startDate,
+    endDate: endDate
+  };
+
+  // 添加分页参数
+  params.pageNum = queryParams.value.pageNum;
+  params.pageSize = queryParams.value.pageSize;
+
+  // 调用明细数据接口
+  getHolidayInOutDetails(params).then(response => {
+    // 处理明细数据,添加节假日名称
+    const processedList = (response.rows || []).map(row => ({
+      ...row,
+      holidayName: getHolidayNameByDate(startDate, endDate)
+    }));
+
+    list.value = processedList;
+    total.value = response.total || 0;
+
+    loading.value = false;
+  }).catch(() => {
+    list.value = [];
+    total.value = 0;
+    loading.value = false;
+  });
+}
+
+/** 根据日期获取节假日名称 */
+function getHolidayNameByDate(startDate, endDate) {
+  const holiday = holidayStats.value.find(stat =>
+    stat.startDate === startDate && stat.endDate === endDate
+  );
+  return holiday ? holiday.holidayName : '';
+}
+
+/** 处理分页事件 - 只更新表格数据,不重新加载统计信息 */
+function handlePagination() {
+  // 分页只更新表格数据,不重新获取统计信息
+  loading.value = true;
+
+  // 准备查询参数
+  const params = {
+    year: queryParams.value.year,
+    startDate: queryParams.value.startDate,
+    endDate: queryParams.value.endDate,
+    pageNum: queryParams.value.pageNum,
+    pageSize: queryParams.value.pageSize
+  };
+
+  getHolidayInOutDetails(params).then(response => {
+    // 直接更新list,不进行其他操作
+    list.value = response.rows || [];
+    total.value = response.total || 0;
+
+    loading.value = false;
+  }).catch(() => {
+    list.value = [];
+    total.value = 0;
+    loading.value = false;
+  });
+}
+
+/** 搜索按钮操作 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getStats(); // 先获取统计信息
+}
+
+/** 重置按钮操作 */
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  queryParams.value.year = new Date().getFullYear().toString();
+  handleQuery();
+}
+
+/** 处理表格行点击事件 */
+function handleRowClick(row) {
+  detailForm.value = { ...row };
+  detailTitle.value = `详细信息 - ${row.fullName}`;
+  recordDetailOpen.value = true;
+}
+
+/** 改变年份 */
+function changeYear(step) {
+  const currentYear = parseInt(queryParams.value.year);
+  queryParams.value.year = (currentYear + step).toString();
+  handleQuery();
+}
+
+/** 选择节假日 */
+function selectHoliday(row) {
+  selectedHolidayName.value = row.holidayName;
+
+  // 更新查询参数
+  queryParams.value.startDate = row.startDate;
+  queryParams.value.endDate = row.endDate;
+
+  // 查询该节假日的明细数据
+  getList(row.startDate, row.endDate);
+}
+
+/** 表格行样式设置 */
+function tableRowClassName({ row }) {
+  return selectedHolidayName.value === row.holidayName ? 'selected-row' : '';
+}
+
+// 初始化时直接查询,使用reactive中设置的默认值
+onMounted(() => {
+  handleQuery();
+});
+
+onUnmounted(() => {
+  if (chartInstance) {
+    chartInstance.dispose();
+  }
+});
+</script>
+
+<style scoped>
+.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;
+}
+
+.chart-and-stats-container {
+  display: flex;
+  gap: 20px;
+  margin-bottom: 20px;
+}
+
+.chart-section {
+  flex: 0.7; /* 缩小图表区域,占用70%的宽度 */
+  min-width: 0; /* 允许flex项目收缩到其内容的固有尺寸以下 */
+  display: flex;
+  flex-direction: column;
+  border: 1px solid #e4e7ed;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+  padding: 15px;
+  background-color: #fafafa;
+  height: 400px; /* 固定高度 */
+}
+
+.stats-section {
+  flex: 0.3; /* 统计区域占用30%的宽度 */
+  display: flex;
+  flex-direction: column;
+  border: 1px solid #e4e7ed;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+  padding: 15px;
+  background-color: #fafafa;
+  height: 400px; /* 设置固定高度 */
+}
+
+.chart-container {
+  width: 100%;
+  height: 400px;
+  border: 1px solid #e4e7ed;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+  flex: 1;
+}
+
+.chart-container {
+  width: 100%;
+  height: 320px; /* 调整为固定高度,留出标题空间 */
+  border: 1px solid #e4e7ed;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+}
+
+.section-title {
+  margin: 0 0 15px 0;
+  color: #303133;
+  font-size: 16px;
+  font-weight: 600;
+  border-bottom: 2px solid #409eff;
+  padding-bottom: 8px;
+}
+
+.table-header {
+  margin-top: 20px;
+}
+
+.selected-holiday {
+  color: #409eff;
+  font-weight: bold;
+  font-size: 16px;
+}
+
+/* 高亮选中的表格行 */
+:deep(.selected-row) {
+  background-color: #f5f7fa !important;
+  color: #409eff;
+  font-weight: bold;
+}
+
+/* 为表格添加边框和阴影 */
+:deep(.el-table) {
+  border: 1px solid #e4e7ed;
+  border-radius: 8px;
+}
+</style>