Sfoglia il codice sorgente

搞定夜间出入境和其他参数配置功能

wukai 4 mesi fa
parent
commit
a4ff7e6642
4 ha cambiato i file con 1046 aggiunte e 0 eliminazioni
  1. 18 0
      src/api/biz/anal.js
  2. 44 0
      src/api/biz/other.js
  3. 765 0
      src/views/biz/anal/night.vue
  4. 219 0
      src/views/biz/other/index.vue

+ 18 - 0
src/api/biz/anal.js

@@ -53,3 +53,21 @@ export function getHolidayInOutStats(params) {
         params: params
     })
 }
+
+// 查询夜间出入境统计数据
+export function getNightInOutStats(params) {
+    return request({
+        url: '/biz/anal/night/stats',
+        method: 'get',
+        params: params
+    })
+}
+
+// 查询夜间出入境明细数据
+export function getNightInOutDetail(params) {
+    return request({
+        url: '/biz/anal/night/details',
+        method: 'get',
+        params: params
+    })
+}

+ 44 - 0
src/api/biz/other.js

@@ -0,0 +1,44 @@
+import request from '@/utils/request'
+
+// 查询其他参数配置列表
+export function listOther(query) {
+    return request({
+        url: '/biz/other/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 查询其他参数配置详细
+export function getOther(cfgId) {
+    return request({
+        url: '/biz/other/' + cfgId,
+        method: 'get'
+    })
+}
+
+// 新增其他参数配置
+export function addOther(data) {
+    return request({
+        url: '/biz/other',
+        method: 'post',
+        data: data
+    })
+}
+
+// 修改其他参数配置
+export function updateOther(data) {
+    return request({
+        url: '/biz/other',
+        method: 'put',
+        data: data
+    })
+}
+
+// 删除其他参数配置
+export function delOther(cfgId) {
+    return request({
+        url: '/biz/other/' + cfgId,
+        method: 'delete'
+    })
+}

+ 765 - 0
src/views/biz/anal/night.vue

@@ -0,0 +1,765 @@
+<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: 100%;">
+        <div class="table_caption" style="height: 30px; line-height: 30px;">夜间出入境人员数量趋势图</div>
+        <div style="height: 470px; width: 100%; border: 1px solid #ededed; background: #fff;" ref="trendChartContainer">
+          <div id="trendChart" style="height: 100%; width: 100%;"></div>
+        </div>
+      </div>
+    </el-row>
+
+    <!-- 明细数据表格 -->
+    <el-table v-loading="loading" :data="detailsList" style="margin-top: 20px;" @row-click="handleRowClick">
+      <el-table-column label="序号" type="index" width="50" align="center" />
+      <el-table-column label="姓名" prop="fullName" align="center" 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="createTime" width="150">
+        <template #default="scope">
+          <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </template>
+      </el-table-column>
+    </el-table>
+    <div style="margin-top: 10px;">
+      <div v-if="detailsList.length === 0 && !loading" style="margin-top: 10px; color: #f56c6c;">
+        <el-icon><Warning /></el-icon> 暂无数据,请检查API返回结果
+      </div>
+    </div>
+
+    <!-- 出入境记录明细对话框 -->
+    <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"
+    />
+    <div v-if="total === 0 && !loading" style="text-align: center; padding: 20px; color: #909399;">
+      暂无数据
+    </div>
+  </div>
+</template>
+
+<script setup name="Night">
+import * as echarts from 'echarts'
+import { getNightInOutStats, getNightInOutDetail } from "@/api/biz/anal";
+import { getCurrentInstance, nextTick, onMounted, onUnmounted, reactive, ref, toRefs, watch } from 'vue';
+import Pagination from "@/components/Pagination/index.vue";
+import { parseTime, resetForm } from "@/utils/ruoyi";
+import { Pointer, Warning } from "@element-plus/icons-vue";
+
+const { proxy } = getCurrentInstance();
+
+const detailsList = ref([]);
+const loading = ref(false);
+const total = ref(0);
+const trendChartContainer = ref(null);
+
+const statistics = ref({
+  totalPeople: 0,
+  totalEntries: 0,
+  avgFrequency: 0,
+  maxFrequency: 0
+});
+
+// 出入境记录明细相关
+const recordDetailOpen = ref(false);
+const recordDetailLoading = ref(false);
+const detailTitle = ref("详细信息");
+const detailForm = ref({});
+
+const data = reactive({
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    queryType: 'year',  // 默认查询类型为年
+    year: new Date().getFullYear().toString(),  // 默认年份
+    month: null,  // 初始不设置月份
+    dateRange: null  // 初始不设置日期范围
+  }
+});
+
+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();
+  }
+}, { immediate: true });
+
+/** 查询夜间出入境数据 */
+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([
+    getNightInOutStats(params),
+    getNightInOutDetail({ ...params, pageNum: queryParams.value.pageNum, pageSize: queryParams.value.pageSize })
+  ]).then(responses => {
+    // 处理统计数据和趋势数据
+    const statsResponse = responses[0];
+    // 处理明细数据
+    const detailResponse = responses[1];
+
+
+
+    if (statsResponse.code === 200 && statsResponse.data) {
+      const data = statsResponse.data;
+      // 处理趋势数据
+      const trendData = data.trendData || [];
+      
+      // 直接使用后端返回的统计数据
+      statistics.value = {
+        totalPeople: data.totalPeople || 0,
+        totalEntries: data.totalCount || 0,
+        avgFrequency: Number(data.avgFrequency).toFixed(2) || 0,
+        maxFrequency: data.maxFrequency || 0
+      };
+      
+      // 确保在DOM更新后再渲染图表
+      nextTick(() => {
+        renderTrendChart(trendData, queryParams.value.queryType);
+      });
+    } else {
+      // 如果统计数据API调用失败,初始化空数据
+      statistics.value = {
+        totalPeople: 0,
+        totalEntries: 0,
+        avgFrequency: 0,
+        maxFrequency: 0
+      };
+      
+      // 即使统计数据失败,也尝试渲染空的图表
+      nextTick(() => {
+        renderTrendChart([], queryParams.value.queryType);
+      });
+    }
+
+    if (detailResponse.code === 200) {
+      // 处理分页数据 - 使用rows和total字段
+      // 根据API响应结构,数据直接在响应对象中,而非data字段下
+      detailsList.value = detailResponse.rows || [];
+      total.value = detailResponse.total || 0;
+      
+
+    } else {
+      // 如果明细数据API调用失败,初始化空数据
+      detailsList.value = [];
+      total.value = 0;
+    }
+
+    loading.value = false;
+  }).catch(error => {
+    console.error('获取夜间出入境数据失败:', error);
+    // 初始化空数据
+    detailsList.value = [];
+    total.value = 0;
+    statistics.value = {
+      totalPeople: 0,
+      totalEntries: 0,
+      avgFrequency: 0,
+      maxFrequency: 0
+    };
+    
+    // 确保即使出错也渲染空图表
+    nextTick(() => {
+      renderTrendChart([], queryParams.value.queryType);
+    });
+    
+    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;
+  
+
+
+  getNightInOutDetail(params).then(response => {
+    if (response.code === 200) {
+      // 处理分页数据 - 使用rows和total字段
+      // 根据API响应结构,数据直接在响应对象中,而非data字段下
+      detailsList.value = response.rows || [];
+      total.value = response.total || 0;
+    } else {
+      detailsList.value = [];
+      total.value = 0;
+    }
+    
+    loading.value = false;
+  }).catch((error) => {
+    console.error('分页API调用失败:', error); // 添加错误调试信息
+    detailsList.value = [];
+    total.value = 0;
+    loading.value = false;
+  });
+}
+
+/** 根据趋势数据计算统计数据 */
+function calculateStatisticsFromTrendData(trendData, details) {
+  let totalPeople = 0;
+  let totalEntries = details.length || 0;
+  let maxFreq = 0;
+  let avgFrequency = 0;
+
+  // 计算总人数(去重后的证件号码数量)
+  const uniquePersons = new Set();
+  details.forEach(item => {
+    if (item.idNumber) {
+      uniquePersons.add(item.idNumber);
+    }
+  });
+  totalPeople = uniquePersons.size;
+
+  // 计算每个证件号码的出入境次数,找出最高频次
+  const personCountMap = {};
+  details.forEach(item => {
+    if (item.idNumber) {
+      personCountMap[item.idNumber] = (personCountMap[item.idNumber] || 0) + 1;
+    }
+  });
+
+  if (Object.keys(personCountMap).length > 0) {
+    maxFreq = Math.max(...Object.values(personCountMap));
+    avgFrequency = totalEntries / totalPeople;
+  }
+
+  // 如果趋势数据包含总数信息,也可以使用
+  if (trendData && trendData.length > 0) {
+    // 累加趋势数据中的人数
+    const trendTotal = trendData.reduce((sum, item) => sum + (item.personCount || item.count || 0), 0);
+    // 使用较大的值作为总人数(可能趋势数据和明细数据统计方式不同)
+    totalPeople = Math.max(totalPeople, trendTotal);
+  }
+
+  return {
+    totalPeople: totalPeople,
+    totalEntries: totalEntries,
+    avgFrequency: Number(avgFrequency).toFixed(2) || 0,
+    maxFrequency: maxFreq
+  };
+}
+
+/** 渲染趋势图 */
+function renderTrendChart(trendData, queryType) {
+  if (!trendChartContainer.value) return;
+
+  // 如果已有图表实例,先销毁
+  if (trendChartContainer.value.__echarts_instance) {
+    echarts.getInstanceByDom(trendChartContainer.value)?.dispose();
+  }
+
+  const myChart = echarts.init(trendChartContainer.value);
+
+  // 保存图表实例引用
+  trendChartContainer.value.__echarts_instance = myChart;
+
+  let xAxisData = [];
+  let seriesData = [];
+
+  if (trendData && trendData.length > 0) {
+    // 根据查询类型确定X轴标签
+    if (queryType === 'year') {
+      // 按年查询,显示每月趋势,使用period字段
+      xAxisData = trendData.map(item => {
+        // period格式可能是 "2023-01" 格式
+        if (item.period) {
+          // 检查是否包含日期分隔符,提取月份
+          if (item.period.includes('-')) {
+            const parts = item.period.split('-');
+            if (parts.length === 2) {
+              return parts[1] + '月'; // 如 "01月"
+            }
+          }
+          return item.period + '月';
+        } else {
+          return '未知';
+        }
+      });
+    } else if (queryType === 'month') {
+      // 按月查询,显示当月每天趋势
+      xAxisData = trendData.map(item => {
+        if (item.period) {
+          // 如果是完整日期格式 "YYYY-MM-DD",取日部分
+          if (item.period.includes('-')) {
+            const parts = item.period.split('-');
+            if (parts.length === 3) {
+              return parts[2] + '日'; // 如 "01日"
+            }
+          }
+          return item.period + '日';
+        } else {
+          return '未知';
+        }
+      });
+    } else {
+      // 按时段查询,显示按天趋势
+      xAxisData = trendData.map(item => {
+        if (item.period) {
+          // 如果是完整日期格式 "YYYY-MM-DD",显示 MM-DD
+          if (item.period.includes('-')) {
+            const parts = item.period.split('-');
+            if (parts.length === 3) {
+              return parts[1] + '-' + parts[2]; // 如 "05-15"
+            }
+          }
+          return item.period;
+        } else {
+          return '未知';
+        }
+      });
+    }
+
+    // 获取人数或次数数据 - 使用inOutCount字段
+    seriesData = trendData.map(item => {
+      // 使用新的字段名inOutCount
+      return item.inOutCount !== undefined ? item.inOutCount : 0;
+    });
+  } else {
+    // 如果没有趋势数据,显示提示信息
+    xAxisData = ['暂无数据'];
+    seriesData = [0];
+  }
+
+  const option = {
+    title: {
+      text: getTrendChartTitle(queryType),
+      left: 'center'
+    },
+    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}`;
+      }
+    },
+    xAxis: {
+      type: 'category',
+      data: xAxisData,
+      axisLabel: {
+        rotate: 45,
+        fontSize: 12
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '人数'
+    },
+    series: [{
+      data: seriesData,
+      type: 'line',
+      smooth: true,
+      itemStyle: {
+        color: '#5470c6'
+      },
+      areaStyle: {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: 'rgba(84, 112, 198, 0.4)' },
+          { offset: 1, color: 'rgba(84, 112, 198, 0.1)' }
+        ])
+      }
+    }]
+  };
+
+  myChart.setOption(option);
+}
+
+/** 获取趋势图标题 */
+function getTrendChartTitle(queryType) {
+  if (queryType === 'year') {
+    return `年度夜间出入境人员月度趋势图 (年: ${queryParams.value.year})`;
+  } else if (queryType === 'month') {
+    return `月度夜间出入境人员每日趋势图 (月: ${queryParams.value.month})`;
+  } else {
+    const start = queryParams.value.dateRange ? queryParams.value.dateRange[0] : '';
+    const end = queryParams.value.dateRange ? queryParams.value.dateRange[1] : '';
+    return `指定时段夜间出入境人员趋势图 (${start} 至 ${end})`;
+  }
+}
+
+/** 搜索按钮操作 */
+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();
+}
+
+/** 处理表格行点击事件 */
+function handleRowClick(row) {
+  // 显示详细信息
+  detailForm.value = { ...row };
+  detailTitle.value = `${row.fullName || '未知'} 详细信息`;
+  recordDetailOpen.value = true;
+}
+
+// 在组件挂载后,根据默认查询类型执行查询
+onMounted(() => {
+
+  // 确保在DOM更新后再执行查询
+  nextTick(() => {
+    handleQuery();
+  });
+});
+
+// 在组件卸载时清理图表实例
+onUnmounted(() => {
+  if (trendChartContainer.value && trendChartContainer.value.__echarts_instance) {
+    echarts.getInstanceByDom(trendChartContainer.value)?.dispose();
+    trendChartContainer.value.__echarts_instance = null;
+  }
+});
+
+</script>
+
+<style lang="scss" scoped>
+.app-container {
+  padding: 20px;
+}
+
+.stat-card {
+  height: 80px;
+  text-align: center;
+
+  .stat-item {
+    .stat-number {
+      font-size: 24px;
+      font-weight: bold;
+      color: #409eff;
+      margin-bottom: 5px;
+    }
+
+    .stat-label {
+      font-size: 14px;
+      color: #606266;
+    }
+  }
+
+  &.total-people { .stat-number { color: #409eff; } }
+  &.total-entries { .stat-number { color: #67c21a; } }
+  &.avg-frequency { .stat-number { color: #e6a23c; } }
+  &.max-frequency { .stat-number { color: #f56c6c; } }
+}
+
+.mb20 {
+  margin-bottom: 20px;
+}
+
+.table_caption {
+  font-weight: bold;
+  text-align: left;
+  padding-left: 10px;
+  background-color: #f5f7fa;
+}
+</style>

+ 219 - 0
src/views/biz/other/index.vue

@@ -0,0 +1,219 @@
+<template>
+  <div class="app-container">
+    <div class="form-wrapper">
+      <el-form ref="otherRef" :model="form" :rules="rules" label-width="120px" class="config-form">
+        <el-form-item label="夜间时段配置" prop="cfg1">
+          <div style="display: flex; align-items: center;">
+            <el-input-number
+              v-model="startHour"
+              :min="18"
+              :max="24"
+              :step="1"
+              placeholder="开始时"
+              style="width: 100px; margin-right: 10px;"
+            />
+            <span style="margin: 0 10px;">-</span>
+            <el-input-number
+              v-model="endHour"
+              :min="1"
+              :max="8"
+              :step="1"
+              placeholder="结束时"
+              style="width: 100px;"
+            />
+          </div>
+          <div style="margin-top: 5px; color: #909399; font-size: 12px;">最终格式: {{ formatTimeDisplay }}</div>
+        </el-form-item>
+        <el-form-item label="短期定义天数" prop="cfg2">
+          <el-input-number v-model.number="form.cfg2" :min="0" :step="1" placeholder="请输入短期定义天数" />
+        </el-form-item>
+        <el-form-item label="境外滞留天数" prop="cfg3">
+          <el-input-number v-model.number="form.cfg3" :min="0" :step="1" placeholder="请输入境外滞留天数" />
+        </el-form-item>
+        <!--
+        <el-form-item label="配置4" prop="cfg4">
+          <el-input v-model="form.cfg4" placeholder="请输入配置4" />
+        </el-form-item>
+        <el-form-item label="配置5" prop="cfg5">
+          <el-input v-model="form.cfg5" placeholder="请输入配置5" />
+        </el-form-item>
+        <el-form-item label="配置6" prop="cfg6">
+          <el-input v-model="form.cfg6" placeholder="请输入配置6" />
+        </el-form-item>
+        <el-form-item label="配置7" prop="cfg7">
+          <el-input v-model="form.cfg7" placeholder="请输入配置7" />
+        </el-form-item>
+        <el-form-item label="配置8" prop="cfg8">
+          <el-input v-model="form.cfg8" placeholder="请输入配置8" />
+        </el-form-item>
+        <el-form-item label="配置9" prop="cfg9">
+          <el-input v-model="form.cfg9" placeholder="请输入配置9" />
+        </el-form-item>
+        <el-form-item label="配置10" prop="cfg10">
+          <el-input v-model="form.cfg10" placeholder="请输入配置10" />
+        </el-form-item>
+        <el-form-item label="配置11" prop="cfg11">
+          <el-input v-model="form.cfg11" placeholder="请输入配置11" />
+        </el-form-item>
+        <el-form-item label="配置12" prop="cfg12">
+          <el-input v-model="form.cfg12" placeholder="请输入配置12" />
+        </el-form-item>
+        <el-form-item label="配置13" prop="cfg13">
+          <el-input v-model="form.cfg13" placeholder="请输入配置13" />
+        </el-form-item>
+        <el-form-item label="配置14" prop="cfg14">
+          <el-input v-model="form.cfg14" placeholder="请输入配置14" />
+        </el-form-item>
+        <el-form-item label="配置15" prop="cfg15">
+          <el-input v-model="form.cfg15" placeholder="请输入配置15" />
+        </el-form-item>
+        <el-form-item label="配置16" prop="cfg16">
+          <el-input v-model="form.cfg16" placeholder="请输入配置16" />
+        </el-form-item>
+        <el-form-item label="配置17" prop="cfg17">
+          <el-input v-model="form.cfg17" placeholder="请输入配置17" />
+        </el-form-item>
+        <el-form-item label="配置18" prop="cfg18">
+          <el-input v-model="form.cfg18" placeholder="请输入配置18" />
+        </el-form-item>
+        <el-form-item label="配置19" prop="cfg19">
+          <el-input v-model="form.cfg19" placeholder="请输入配置19" />
+        </el-form-item>
+        <el-form-item label="配置20" prop="cfg20">
+          <el-input v-model="form.cfg20" placeholder="请输入配置20" />
+        </el-form-item>
+        -->
+        <el-form-item style="text-align: center; margin-top: 20px;">
+          <el-button type="primary" @click="submitForm" style="width: 120px;">确 定</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+  </div>
+</template>
+
+<script setup name="Other">
+import { listOther, getOther, delOther, addOther, updateOther } from "@/api/biz/other";
+
+const { proxy } = getCurrentInstance();
+
+const loading = ref(true);
+const title = ref("修改其他参数配置");
+
+// 新增开始和结束小时变量
+const startHour = ref(22);
+const endHour = ref(4);
+
+// 计算时间显示格式
+const formatTimeDisplay = computed(() => {
+  const start = String(startHour.value).padStart(2, '0');
+  const end = String(endHour.value).padStart(2, '0');
+  return `${start}:00-${end}:00`;
+});
+
+// 验证夜间时段格式的函数
+const validateNightTime = (rule, value, callback) => {
+  if (!value) {
+    callback(new Error('夜间时段不能为空'));
+    return;
+  }
+  // 验证格式如 22:00-04:00
+  const timePattern = /^([01]\d|2[0-3]):([0-5]\d)-([01]\d|2[0-3]):([0-5]\d)$/;
+  if (!timePattern.test(value)) {
+    callback(new Error('请输入正确的时间段格式,如: 22:00-04:00'));
+  } else {
+    callback();
+  }
+};
+
+const data = reactive({
+  form: {
+    cfgId: 1  // 默认设置cfgId为1
+  },
+  rules: {
+    cfg1: [
+      { validator: validateNightTime, trigger: 'blur' }
+    ],
+    // 取消cfg2和cfg3的验证
+  }
+});
+
+const { form, rules } = toRefs(data);
+
+// 页面初始化时加载数据
+onMounted(() => {
+  const _cfgId = 1;  // 固定使用cfgId为1
+  getOther(_cfgId).then(response => {
+    form.value = response.data;
+    // 解析cfg1中的时间范围到开始和结束小时
+    if (form.value.cfg1 && form.value.cfg1.includes('-')) {
+      const [start, end] = form.value.cfg1.split('-');
+      const [startHourStr, startMinuteStr] = start.split(':');
+      const [endHourStr, endMinuteStr] = end.split(':');
+      startHour.value = parseInt(startHourStr);
+      endHour.value = parseInt(endHourStr);
+    } else {
+      // 默认值:22:00-04:00
+      startHour.value = 22;
+      endHour.value = 4;
+    }
+    loading.value = false;
+  });
+});
+
+// 表单重置功能改为使用当前值重新加载
+function handleReset() {
+  const _cfgId = 1;  // 固定使用cfgId为1
+  getOther(_cfgId).then(response => {
+    form.value = response.data;
+    // 解析cfg1中的时间范围到开始和结束小时
+    if (form.value.cfg1 && form.value.cfg1.includes('-')) {
+      const [start, end] = form.value.cfg1.split('-');
+      const [startHourStr, startMinuteStr] = start.split(':');
+      const [endHourStr, endMinuteStr] = end.split(':');
+      startHour.value = parseInt(startHourStr);
+      endHour.value = parseInt(endHourStr);
+    } else {
+      // 默认值:22:00-04:00
+      startHour.value = 22;
+      endHour.value = 4;
+    }
+  });
+}
+
+/** 提交按钮 */
+function submitForm() {
+  // 在提交前,将开始和结束小时合并成cfg1的值
+  const start = String(startHour.value).padStart(2, '0');
+  const end = String(endHour.value).padStart(2, '0');
+  form.value.cfg1 = `${start}:00-${end}:00`;
+
+  proxy.$refs["otherRef"].validate(valid => {
+    if (valid) {
+      if (form.value.cfgId != null) {
+        updateOther(form.value).then(response => {
+          proxy.$modal.msgSuccess("修改成功");
+        });
+      } else {
+        addOther(form.value).then(response => {
+          proxy.$modal.msgSuccess("新增成功");
+        });
+      }
+    }
+  });
+}
+</script>
+
+<style scoped>
+.form-wrapper {
+  max-width: 600px;
+  margin: 0 auto;
+  padding: 20px;
+  background: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+.config-form {
+  padding: 20px;
+}
+</style>