Browse Source

监督端-查询统计

dev-v1.0.2
庄锐波 5 months ago
parent
commit
305f2191ea
  1. 84
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/report/StatisticsInspectionPlanController.java
  2. 14
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/EnforcementInspectionProgramBo.java
  3. 37
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/report/InspectionPlanDetailVo.java
  4. 26
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/report/InspectionPlanExportVo.java
  5. 28
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/report/InspectionPlanGroupVo.java
  6. 22
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/report/InspectionPlanOverviewVo.java
  7. 50
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/report/InspectionPlanRankVo.java
  8. 18
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/ComplaintStatisticsMapper.java
  9. 29
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/EnforcementInspectionProgramMapper.java
  10. 39
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/report/IStatisticsInspectionPlanReport.java
  11. 59
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/report/impl/StatisticsComplaintReportImpl.java
  12. 200
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/report/impl/StatisticsInspectionPlanReportImpl.java
  13. 6
      zdxt-modules/zdxt-enforcement-code/src/main/resources/mapper/enforcementcode/report/ComplaintStatisticsReportMapper.xml
  14. 174
      zdxt-modules/zdxt-enforcement-code/src/main/resources/mapper/enforcementcode/report/EnforcementInspectionProgramReportMapper.xml
  15. 141
      zdxt-web-client/zdxt-admin-plus-ui/src/api/efcode/report/statistics-inspection-plan.ts
  16. 488
      zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/report/statistics_enforcement_complaint/index.vue
  17. 458
      zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/report/statistics_inspection_plan/index.vue

84
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/report/StatisticsInspectionPlanController.java

@ -0,0 +1,84 @@
package com.zdxt.enforcementcode.controller.web.supervision.report;
import com.zdxt.enforcementcode.domain.bo.EnforcementInspectionProgramBo;
import com.zdxt.enforcementcode.domain.vo.report.*;
import com.zdxt.enforcementcode.report.IStatisticsInspectionPlanReport;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 检查计划统计报表 Controller
*
* @author zdxt
*/
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/web/supervision/report/statistics-inspection-plan")
public class StatisticsInspectionPlanController {
private final IStatisticsInspectionPlanReport reportService;
/** Tab1概览 */
@GetMapping("/overview")
public R<InspectionPlanOverviewVo> overview(EnforcementInspectionProgramBo bo) {
return R.ok(reportService.overview(bo));
}
/** Tab1主表格:按部门分组 */
@GetMapping("/groupList")
public TableDataInfo<InspectionPlanGroupVo> groupList(EnforcementInspectionProgramBo bo, PageQuery pageQuery) {
return reportService.groupList(bo, pageQuery);
}
/** Tab1明细列表 */
@GetMapping("/detailList")
public TableDataInfo<InspectionPlanDetailVo> detailList(EnforcementInspectionProgramBo bo, PageQuery pageQuery) {
return reportService.detailList(bo, pageQuery);
}
/** Tab2-年度排行TOP10 */
@GetMapping("/rankByYear")
public R<List<InspectionPlanRankVo>> rankByYear(EnforcementInspectionProgramBo bo) {
return R.ok(reportService.rankByYear(bo));
}
/** Tab2-季度排行TOP10 */
@GetMapping("/rankByQuarter")
public R<List<InspectionPlanRankVo>> rankByQuarter(EnforcementInspectionProgramBo bo) {
return R.ok(reportService.rankByQuarter(bo));
}
/** Tab2-月度排行TOP10 */
@GetMapping("/rankByMonth")
public R<List<InspectionPlanRankVo>> rankByMonth(EnforcementInspectionProgramBo bo) {
return R.ok(reportService.rankByMonth(bo));
}
/** Tab1导出 */
@PostMapping("/exportGroupList")
public void exportGroupList(EnforcementInspectionProgramBo bo, HttpServletResponse response) {
try {
List<InspectionPlanExportVo> list = reportService.exportGroupList(bo);
ExcelUtil.exportExcel(list, "检查计划统计", InspectionPlanExportVo.class, response);
} catch (Exception e) {
log.error("导出检查计划统计Excel异常", e);
response.reset();
response.setContentType("application/json; charset=utf-8");
try {
response.getWriter().write("{\"code\":500,\"msg\":\"导出失败: " + e.getMessage() + "\"}");
} catch (Exception ignored) {
}
}
}
}

14
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/EnforcementInspectionProgramBo.java

@ -134,6 +134,20 @@ public class EnforcementInspectionProgramBo extends BaseEntity {
*/
private String dataScopeId;
// ===== 报表查询条件字段 =====
/** 年(报表搜索) */
private String year;
/** 月(报表搜索) */
private String month;
/** 自定义开始时间 */
private String beginTime;
/** 自定义结束时间 */
private String endTime;
/** 明细弹窗时的计划类型(year/quarter/month),避免与实体 timeType 冲突 */
private String reportTimeType;
/** 报表搜索用季度(String类型,区别于实体的Long quarter) */
private String reportQuarter;
/**
* 文件对象
*/

37
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/report/InspectionPlanDetailVo.java

@ -0,0 +1,37 @@
package com.zdxt.enforcementcode.domain.vo.report;
import lombok.Data;
/**
* 检查计划统计 - Tab1明细VO
*/
@Data
public class InspectionPlanDetailVo {
/** 主键 */
private String id;
/** 计划名称(拼接后) */
private String planName;
/** 执法单位 */
private String bureauDeptName;
/** 执法区域 */
private String enforcementArea;
/** 发布时间(格式化后的字符串) */
private String enforcementTime;
/** 计划类型(year/quarter/month),用于Java层拼接名称 */
private String timeType;
/** 月份 */
private Long mouth;
/** 计划年度 */
private String planYear;
/** 季度 */
private Long quarter;
}

26
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/report/InspectionPlanExportVo.java

@ -0,0 +1,26 @@
package com.zdxt.enforcementcode.domain.vo.report;
import cn.idev.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* 检查计划统计 - Tab1导出VO
*/
@Data
public class InspectionPlanExportVo {
@ExcelProperty("执法单位")
private String bureauDeptName;
@ExcelProperty("总数")
private Integer total;
@ExcelProperty("年度计划")
private Integer yearTotal;
@ExcelProperty("季度计划")
private Integer quarterTotal;
@ExcelProperty("月度计划")
private Integer monthTotal;
}

28
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/report/InspectionPlanGroupVo.java

@ -0,0 +1,28 @@
package com.zdxt.enforcementcode.domain.vo.report;
import lombok.Data;
/**
* 检查计划统计 - Tab1分组统计VO
*/
@Data
public class InspectionPlanGroupVo {
/** 执法单位名称 */
private String bureauDeptName;
/** 局级部门ID */
private String bureauDeptId;
/** 总数 */
private Integer total;
/** 年度计划数 */
private Integer yearTotal;
/** 季度计划数 */
private Integer quarterTotal;
/** 月度计划数 */
private Integer monthTotal;
}

22
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/report/InspectionPlanOverviewVo.java

@ -0,0 +1,22 @@
package com.zdxt.enforcementcode.domain.vo.report;
import lombok.Data;
/**
* 检查计划统计 - Tab1概览VO
*/
@Data
public class InspectionPlanOverviewVo {
/** 计划总数(yearTotal + quarterTotal + monthTotal) */
private Integer total;
/** 年度计划数 */
private Integer yearTotal;
/** 季度计划数 */
private Integer quarterTotal;
/** 月度计划数 */
private Integer monthTotal;
}

50
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/report/InspectionPlanRankVo.java

@ -0,0 +1,50 @@
package com.zdxt.enforcementcode.domain.vo.report;
import lombok.Data;
/**
* 检查计划统计 - Tab2排行VO(含各季度/月份明细)
*/
@Data
public class InspectionPlanRankVo {
/** 执法单位名称 */
private String bureauDeptName;
/** 合计 */
private Integer total;
/** 第1季度 */
private Integer quarterTotal1;
/** 第2季度 */
private Integer quarterTotal2;
/** 第3季度 */
private Integer quarterTotal3;
/** 第4季度 */
private Integer quarterTotal4;
/** 1月 */
private Integer monthTotal1;
/** 2月 */
private Integer monthTotal2;
/** 3月 */
private Integer monthTotal3;
/** 4月 */
private Integer monthTotal4;
/** 5月 */
private Integer monthTotal5;
/** 6月 */
private Integer monthTotal6;
/** 7月 */
private Integer monthTotal7;
/** 8月 */
private Integer monthTotal8;
/** 9月 */
private Integer monthTotal9;
/** 10月 */
private Integer monthTotal10;
/** 11月 */
private Integer monthTotal11;
/** 12月 */
private Integer monthTotal12;
}

18
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/ComplaintStatisticsMapper.java

@ -18,21 +18,27 @@ import java.util.List;
public interface ComplaintStatisticsMapper extends BaseMapperPlus<ComplaintStatistics, ComplaintStatisticsVo> {
/** Tab1:按部门分组统计 */
List<ComplaintReportGroupVo> selectComplaintGroupByDept(@Param("bo") ComplaintStatisticsBo bo);
List<ComplaintReportGroupVo> selectComplaintGroupByDept(@Param("bo") ComplaintStatisticsBo bo,
@Param("dataFilter") String dataFilter);
/** Tab1:投诉明细列表 */
List<ComplaintReportDetailVo> selectComplaintDetailList(@Param("bo") ComplaintStatisticsBo bo);
List<ComplaintReportDetailVo> selectComplaintDetailList(@Param("bo") ComplaintStatisticsBo bo,
@Param("dataFilter") String dataFilter);
/** Tab1概览:投诉总数 */
int selectComplaintCount(@Param("bo") ComplaintStatisticsBo bo);
int selectComplaintCount(@Param("bo") ComplaintStatisticsBo bo,
@Param("dataFilter") String dataFilter);
/** Tab1概览:被投诉对象数 */
int selectComplaintDeptCount(@Param("bo") ComplaintStatisticsBo bo);
int selectComplaintDeptCount(@Param("bo") ComplaintStatisticsBo bo,
@Param("dataFilter") String dataFilter);
/** Tab2:查全量投诉列表(含enforce_type)用于Java层投诉类型分组 */
List<ComplaintStatistics> selectComplaintListForTypeStat(@Param("bo") ComplaintStatisticsBo bo);
List<ComplaintStatistics> selectComplaintListForTypeStat(@Param("bo") ComplaintStatisticsBo bo,
@Param("dataFilter") String dataFilter);
/** Tab2:查全量投诉列表(含enterprise_address)用于Java层区域/街道分组 */
List<ComplaintStatistics> selectComplaintListForAreaStat(@Param("bo") ComplaintStatisticsBo bo);
List<ComplaintStatistics> selectComplaintListForAreaStat(@Param("bo") ComplaintStatisticsBo bo,
@Param("dataFilter") String dataFilter);
}

29
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/EnforcementInspectionProgramMapper.java

@ -4,6 +4,10 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zdxt.enforcementcode.domain.EnforcementInspectionProgram;
import com.zdxt.enforcementcode.domain.bo.EnforcementInspectionProgramBo;
import com.zdxt.enforcementcode.domain.vo.EnforcementInspectionProgramVo;
import com.zdxt.enforcementcode.domain.vo.report.InspectionPlanDetailVo;
import com.zdxt.enforcementcode.domain.vo.report.InspectionPlanGroupVo;
import com.zdxt.enforcementcode.domain.vo.report.InspectionPlanOverviewVo;
import com.zdxt.enforcementcode.domain.vo.report.InspectionPlanRankVo;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
@ -71,4 +75,29 @@ public interface EnforcementInspectionProgramMapper extends BaseMapperPlus<Enfor
*/
List<String> selectAccessibleDeptIds(@Param("currentDeptId") String currentDeptId);
// ===== 报表查询方法 =====
/** Tab1: 分组统计 */
List<InspectionPlanGroupVo> selectPlanGroupByDept(@Param("bo") EnforcementInspectionProgramBo bo,
@Param("dataFilter") String dataFilter);
/** Tab1: 概览汇总 */
InspectionPlanOverviewVo selectPlanOverview(@Param("bo") EnforcementInspectionProgramBo bo,
@Param("dataFilter") String dataFilter);
/** Tab1: 明细列表 */
List<InspectionPlanDetailVo> selectPlanDetailList(@Param("bo") EnforcementInspectionProgramBo bo,
@Param("dataFilter") String dataFilter);
/** Tab2: 年度排行TOP10 */
List<InspectionPlanRankVo> selectPlanRankByYear(@Param("bo") EnforcementInspectionProgramBo bo,
@Param("dataFilter") String dataFilter);
/** Tab2: 季度排行TOP10 */
List<InspectionPlanRankVo> selectPlanRankByQuarter(@Param("bo") EnforcementInspectionProgramBo bo,
@Param("dataFilter") String dataFilter);
/** Tab2: 月度排行TOP10 */
List<InspectionPlanRankVo> selectPlanRankByMonth(@Param("bo") EnforcementInspectionProgramBo bo,
@Param("dataFilter") String dataFilter);
}

39
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/report/IStatisticsInspectionPlanReport.java

@ -0,0 +1,39 @@
package com.zdxt.enforcementcode.report;
import com.zdxt.enforcementcode.domain.bo.EnforcementInspectionProgramBo;
import com.zdxt.enforcementcode.domain.vo.report.InspectionPlanDetailVo;
import com.zdxt.enforcementcode.domain.vo.report.InspectionPlanExportVo;
import com.zdxt.enforcementcode.domain.vo.report.InspectionPlanGroupVo;
import com.zdxt.enforcementcode.domain.vo.report.InspectionPlanOverviewVo;
import com.zdxt.enforcementcode.domain.vo.report.InspectionPlanRankVo;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import java.util.List;
/**
* 检查计划统计报表服务接口
*/
public interface IStatisticsInspectionPlanReport {
/** Tab1概览:计划总数 + 年度/季度/月度 */
InspectionPlanOverviewVo overview(EnforcementInspectionProgramBo bo);
/** Tab1主表格:按部门分组统计(含手动分页) */
TableDataInfo<InspectionPlanGroupVo> groupList(EnforcementInspectionProgramBo bo, PageQuery pageQuery);
/** Tab1明细列表:某部门某类型的计划明细(含手动分页) */
TableDataInfo<InspectionPlanDetailVo> detailList(EnforcementInspectionProgramBo bo, PageQuery pageQuery);
/** Tab2-年度排行TOP10 */
List<InspectionPlanRankVo> rankByYear(EnforcementInspectionProgramBo bo);
/** Tab2-季度排行TOP10 */
List<InspectionPlanRankVo> rankByQuarter(EnforcementInspectionProgramBo bo);
/** Tab2-月度排行TOP10 */
List<InspectionPlanRankVo> rankByMonth(EnforcementInspectionProgramBo bo);
/** Tab1导出 */
List<InspectionPlanExportVo> exportGroupList(EnforcementInspectionProgramBo bo);
}

59
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/report/impl/StatisticsComplaintReportImpl.java

@ -17,6 +17,11 @@ import org.dromara.system.service.ISysDeptService;
import org.dromara.system.domain.vo.SysDeptVo;
import org.springframework.stereotype.Service;
import cn.hutool.core.collection.CollUtil;
import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.satoken.utils.LoginHelper;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
@ -29,7 +34,7 @@ import java.util.stream.Collectors;
* - Tab1: 按 complaint_department_id 分组统计 + JOIN sys_dept 翻译名称
* - Tab2: Java层按 enforceType/enterprise_address 分组,字典翻译
* <p>
* 不使用 DataFilterUtil 进行数据过滤
* 数据权限:非超级用户使用 sys_role_dept 过滤 complaint_department_id
*
* @author zdxt
*/
@ -46,10 +51,11 @@ public class StatisticsComplaintReportImpl implements IStatisticsComplaintReport
public ComplaintReportOverviewVo overview(ComplaintStatisticsBo bo) {
// 处理被投诉对象模糊搜索
fillDeptIdListByName(bo);
String dataFilter = buildComplaintDataFilter();
ComplaintReportOverviewVo vo = new ComplaintReportOverviewVo();
vo.setComplaintCount(complaintStatisticsMapper.selectComplaintCount(bo));
vo.setComplaintDeptCount(complaintStatisticsMapper.selectComplaintDeptCount(bo));
vo.setComplaintCount(complaintStatisticsMapper.selectComplaintCount(bo, dataFilter));
vo.setComplaintDeptCount(complaintStatisticsMapper.selectComplaintDeptCount(bo, dataFilter));
return vo;
}
@ -57,9 +63,10 @@ public class StatisticsComplaintReportImpl implements IStatisticsComplaintReport
public TableDataInfo<ComplaintReportGroupVo> groupList(ComplaintStatisticsBo bo, PageQuery pageQuery) {
// 处理被投诉对象模糊搜索
fillDeptIdListByName(bo);
String dataFilter = buildComplaintDataFilter();
// 全量查询分组数据
List<ComplaintReportGroupVo> allList = complaintStatisticsMapper.selectComplaintGroupByDept(bo);
List<ComplaintReportGroupVo> allList = complaintStatisticsMapper.selectComplaintGroupByDept(bo, dataFilter);
// 手动分页
int pageNum = pageQuery.getPageNum();
@ -76,8 +83,9 @@ public class StatisticsComplaintReportImpl implements IStatisticsComplaintReport
@Override
public TableDataInfo<ComplaintReportDetailVo> detailList(ComplaintStatisticsBo bo, PageQuery pageQuery) {
String dataFilter = buildComplaintDataFilter();
// 全量查询明细
List<ComplaintReportDetailVo> allList = complaintStatisticsMapper.selectComplaintDetailList(bo);
List<ComplaintReportDetailVo> allList = complaintStatisticsMapper.selectComplaintDetailList(bo, dataFilter);
// 字典翻译 enforceType -> enforceTypeText
Map<String, String> caseTypeDict = dictService.getAllDictByDictType("caseType");
@ -104,7 +112,8 @@ public class StatisticsComplaintReportImpl implements IStatisticsComplaintReport
@Override
public List<ComplaintReportTypeStatVo> typeStatList(ComplaintStatisticsBo bo) {
// 复刻旧系统 type() 逻辑
List<ComplaintStatistics> list = complaintStatisticsMapper.selectComplaintListForTypeStat(bo);
String dataFilter = buildComplaintDataFilter();
List<ComplaintStatistics> list = complaintStatisticsMapper.selectComplaintListForTypeStat(bo, dataFilter);
Map<String, String> caseTypeDict = dictService.getAllDictByDictType("caseType");
int totalCount = list.size();
@ -130,7 +139,8 @@ public class StatisticsComplaintReportImpl implements IStatisticsComplaintReport
@Override
public List<ComplaintReportTypeStatVo> areaStatList(ComplaintStatisticsBo bo) {
// 复刻旧系统 qyComplaintList() 逻辑
List<ComplaintStatistics> list = complaintStatisticsMapper.selectComplaintListForAreaStat(bo);
String dataFilter = buildComplaintDataFilter();
List<ComplaintStatistics> list = complaintStatisticsMapper.selectComplaintListForAreaStat(bo, dataFilter);
int totalCount = list.size();
// 获取区域字典 address_type
@ -183,7 +193,8 @@ public class StatisticsComplaintReportImpl implements IStatisticsComplaintReport
String areaPrefix = buildAreaPrefix(areaLabel);
// 获取全量投诉数据(含地址)
List<ComplaintStatistics> allList = complaintStatisticsMapper.selectComplaintListForAreaStat(bo);
String dataFilter = buildComplaintDataFilter();
List<ComplaintStatistics> allList = complaintStatisticsMapper.selectComplaintListForAreaStat(bo, dataFilter);
// 先过滤出该区的记录
List<ComplaintStatistics> areaList = allList.stream()
@ -230,7 +241,8 @@ public class StatisticsComplaintReportImpl implements IStatisticsComplaintReport
@Override
public List<ComplaintReportExportVo> exportGroupList(ComplaintStatisticsBo bo) {
fillDeptIdListByName(bo);
List<ComplaintReportGroupVo> allList = complaintStatisticsMapper.selectComplaintGroupByDept(bo);
String dataFilter = buildComplaintDataFilter();
List<ComplaintReportGroupVo> allList = complaintStatisticsMapper.selectComplaintGroupByDept(bo, dataFilter);
return allList.stream().map(item -> {
ComplaintReportExportVo exportVo = new ComplaintReportExportVo();
exportVo.setComplaintDepartment(item.getComplaintDepartment());
@ -260,6 +272,35 @@ public class StatisticsComplaintReportImpl implements IStatisticsComplaintReport
// ===== 私有方法 =====
/**
* 构建投诉数据权限过滤SQL。
* <p>超级管理员不过滤,非超级用户按 sys_role_dept 过滤 complaint_department_id。
*
* @return "AND (...)" SQL片段 或 空串
*/
private String buildComplaintDataFilter() {
if (LoginHelper.isSuperAdmin()) {
return "";
}
LoginUser loginUser = LoginHelper.getLoginUser();
if (loginUser == null || CollUtil.isEmpty(loginUser.getRoles())) {
// 无角色用户,返回不可能条件
return "AND 1 = 0";
}
List<RoleDTO> roles = loginUser.getRoles();
StringBuilder sb = new StringBuilder("AND (");
for (int i = 0; i < roles.size(); i++) {
if (i > 0) {
sb.append(" OR ");
}
sb.append("complaint_department_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = ")
.append(roles.get(i).getRoleId())
.append(" )");
}
sb.append(")");
return sb.toString();
}
/**
* 处理被投诉对象模糊搜索:通过 complaintDepartment 查询 sys_dept 获取 deptIdList
*/

200
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/report/impl/StatisticsInspectionPlanReportImpl.java

@ -0,0 +1,200 @@
package com.zdxt.enforcementcode.report.impl;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zdxt.enforcementcode.common.util.DataFilterUtil;
import com.zdxt.enforcementcode.domain.bo.EnforcementInspectionProgramBo;
import com.zdxt.enforcementcode.domain.vo.report.*;
import com.zdxt.enforcementcode.mapper.EnforcementInspectionProgramMapper;
import com.zdxt.enforcementcode.report.IStatisticsInspectionPlanReport;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 检查计划统计报表 Service实现
* <p>
* 复刻旧系统 EnforcementInspectionProgramServiceImpl 逻辑:
* - Tab1(统计+概览+明细):数据权限 + 已发布(is_publish=1)对所有人可见
* - Tab2(排行TOP10):仅数据权限过滤(不含is_publish扩展)
*
* @author zdxt
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class StatisticsInspectionPlanReportImpl implements IStatisticsInspectionPlanReport {
private final EnforcementInspectionProgramMapper programMapper;
@Override
public InspectionPlanOverviewVo overview(EnforcementInspectionProgramBo bo) {
String dataFilter = buildDataFilterWithPublish();
InspectionPlanOverviewVo vo = programMapper.selectPlanOverview(bo, dataFilter);
if (vo == null) {
vo = new InspectionPlanOverviewVo();
vo.setYearTotal(0);
vo.setQuarterTotal(0);
vo.setMonthTotal(0);
}
// total = yearTotal + quarterTotal + monthTotal
vo.setTotal(vo.getYearTotal() + vo.getQuarterTotal() + vo.getMonthTotal());
return vo;
}
@Override
public TableDataInfo<InspectionPlanGroupVo> groupList(EnforcementInspectionProgramBo bo, PageQuery pageQuery) {
String dataFilter = buildDataFilterWithPublish();
// 全量查询
List<InspectionPlanGroupVo> allList = programMapper.selectPlanGroupByDept(bo, dataFilter);
// 手动分页
int pageNum = pageQuery.getPageNum();
int pageSize = pageQuery.getPageSize();
int total = allList.size();
int fromIndex = Math.min((pageNum - 1) * pageSize, total);
int toIndex = Math.min(fromIndex + pageSize, total);
List<InspectionPlanGroupVo> pageRecords = allList.subList(fromIndex, toIndex);
Page<InspectionPlanGroupVo> page = new Page<>(pageNum, pageSize, total);
page.setRecords(pageRecords);
return TableDataInfo.build(page);
}
@Override
public TableDataInfo<InspectionPlanDetailVo> detailList(EnforcementInspectionProgramBo bo, PageQuery pageQuery) {
String dataFilter = buildDataFilterWithPublish();
// 全量查询
List<InspectionPlanDetailVo> allList = programMapper.selectPlanDetailList(bo, dataFilter);
// Java层拼接计划名称(复刻旧系统格式:局名+年+类型描述+计划名称)
for (InspectionPlanDetailVo detail : allList) {
detail.setPlanName(buildPlanDisplayName(detail));
}
// 手动分页
int pageNum = pageQuery.getPageNum();
int pageSize = pageQuery.getPageSize();
int total = allList.size();
int fromIndex = Math.min((pageNum - 1) * pageSize, total);
int toIndex = Math.min(fromIndex + pageSize, total);
List<InspectionPlanDetailVo> pageRecords = allList.subList(fromIndex, toIndex);
Page<InspectionPlanDetailVo> page = new Page<>(pageNum, pageSize, total);
page.setRecords(pageRecords);
return TableDataInfo.build(page);
}
@Override
public List<InspectionPlanRankVo> rankByYear(EnforcementInspectionProgramBo bo) {
// Tab2排行不加 is_publish 特殊逻辑(对齐旧系统行为)
String dataFilter = buildDataFilter();
return programMapper.selectPlanRankByYear(bo, dataFilter);
}
@Override
public List<InspectionPlanRankVo> rankByQuarter(EnforcementInspectionProgramBo bo) {
String dataFilter = buildDataFilter();
return programMapper.selectPlanRankByQuarter(bo, dataFilter);
}
@Override
public List<InspectionPlanRankVo> rankByMonth(EnforcementInspectionProgramBo bo) {
String dataFilter = buildDataFilter();
return programMapper.selectPlanRankByMonth(bo, dataFilter);
}
@Override
public List<InspectionPlanExportVo> exportGroupList(EnforcementInspectionProgramBo bo) {
String dataFilter = buildDataFilterWithPublish();
List<InspectionPlanGroupVo> allList = programMapper.selectPlanGroupByDept(bo, dataFilter);
return allList.stream().map(g -> {
InspectionPlanExportVo export = new InspectionPlanExportVo();
export.setBureauDeptName(g.getBureauDeptName());
export.setTotal(g.getTotal());
export.setYearTotal(g.getYearTotal());
export.setQuarterTotal(g.getQuarterTotal());
export.setMonthTotal(g.getMonthTotal());
return export;
}).collect(Collectors.toList());
}
// ===== 私有方法 =====
/**
* 构建数据权限过滤条件(含 is_publish=1 扩展)。
* <p>用于Tab1(统计+概览+明细),已发布计划对所有人可见。
* <p>超管/全数据角色返回空串(不过滤)。
*
* @return "AND (...)" SQL片段 或 空串
*/
private String buildDataFilterWithPublish() {
String filter = DataFilterUtil.buildFilterSql("p", "enforcement_department_id", true, true);
if (filter == null) {
// 超管/全数据角色 → 不过滤
return "";
}
// 非超管:将原始过滤条件扩展为 (原条件 OR is_publish=1)
// filter 格式类似: (p.enforcement_department_id IN (SELECT ...))
return "AND (" + filter + " OR p.is_publish = 1)";
}
/**
* 构建数据权限过滤条件(不含 is_publish 扩展)。
* <p>用于Tab2排行,仅展示权限范围内的数据。
*
* @return "AND ..." SQL片段 或 空串
*/
private String buildDataFilter() {
String filter = DataFilterUtil.buildFilterSql("p", "enforcement_department_id", true, true);
if (filter == null) {
return "";
}
return "AND " + filter;
}
/**
* 拼接计划显示名称(复刻旧系统格式)。
* <p>格式:局名+年+季度/月份+计划名称
*/
private String buildPlanDisplayName(InspectionPlanDetailVo detail) {
StringBuilder sb = new StringBuilder();
if (StrUtil.isNotBlank(detail.getBureauDeptName())) {
sb.append(detail.getBureauDeptName());
}
if (StrUtil.isNotBlank(detail.getPlanYear())) {
sb.append(detail.getPlanYear()).append("年");
}
String timeType = detail.getTimeType();
if ("quarter".equals(timeType) && detail.getQuarter() != null) {
sb.append(getQuarterText(detail.getQuarter()));
} else if ("month".equals(timeType) && detail.getMouth() != null) {
sb.append(detail.getMouth()).append("月");
}
if (StrUtil.isNotBlank(detail.getPlanName())) {
sb.append(detail.getPlanName());
}
return sb.toString();
}
/**
* 季度数字转文本
*/
private String getQuarterText(Long quarter) {
if (quarter == null) return "";
switch (quarter.intValue()) {
case 1: return "第一季度";
case 2: return "第二季度";
case 3: return "第三季度";
case 4: return "第四季度";
default: return "";
}
}
}

6
zdxt-modules/zdxt-enforcement-code/src/main/resources/mapper/enforcementcode/report/ComplaintStatisticsReportMapper.xml

@ -46,6 +46,7 @@
AND c.enterprise_address LIKE CONCAT('%', #{bo.enterpriseAddress}, '%')
</if>
<include refid="reportTimeCondition"/>
${dataFilter}
GROUP BY c.complaint_department_id
ORDER BY total DESC
</select>
@ -69,6 +70,7 @@
AND c.enterprise_address LIKE CONCAT('%', #{bo.enterpriseAddress}, '%')
</if>
<include refid="reportTimeCondition"/>
${dataFilter}
ORDER BY c.complaint_time DESC
</select>
@ -91,6 +93,7 @@
AND enterprise_address LIKE CONCAT('%', #{bo.enterpriseAddress}, '%')
</if>
<include refid="reportTimeCondition"/>
${dataFilter}
</select>
<!-- Tab1概览:被投诉对象数 -->
@ -112,6 +115,7 @@
AND enterprise_address LIKE CONCAT('%', #{bo.enterpriseAddress}, '%')
</if>
<include refid="reportTimeCondition"/>
${dataFilter}
</select>
<!-- Tab2:查全量投诉列表(含enforce_type)用于Java层投诉类型分组 -->
@ -122,6 +126,7 @@
AND complaint_department IS NOT NULL
AND enforce_type IS NOT NULL
<include refid="reportTimeCondition"/>
${dataFilter}
</select>
<!-- Tab2:查全量投诉列表(含enterprise_address)用于Java层区域/街道分组 -->
@ -137,6 +142,7 @@
AND c.complaint_department IS NOT NULL
AND c.enforce_type IS NOT NULL
<include refid="reportTimeCondition"/>
${dataFilter}
</select>
</mapper>

174
zdxt-modules/zdxt-enforcement-code/src/main/resources/mapper/enforcementcode/report/EnforcementInspectionProgramReportMapper.xml

@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zdxt.enforcementcode.mapper.EnforcementInspectionProgramMapper">
<!-- 通用基础条件片段 -->
<sql id="reportBaseCondition">
AND p.bureau_dept_id IS NOT NULL
AND p.time_type IS NOT NULL AND p.time_type != '0'
AND (p.plan_year IS NOT NULL OR p.quarter IS NOT NULL OR p.mouth IS NOT NULL)
</sql>
<!-- 通用时间条件片段 -->
<sql id="reportTimeCondition">
<if test="bo.dateSearch == 'year' and bo.year != null and bo.year != ''">
AND YEAR(p.enforcement_time) = #{bo.year}
</if>
<if test="bo.dateSearch == 'quarter' and bo.year != null and bo.year != '' and bo.reportQuarter != null and bo.reportQuarter != ''">
AND YEAR(p.enforcement_time) = #{bo.year} AND QUARTER(p.enforcement_time) = #{bo.reportQuarter}
</if>
<if test="bo.dateSearch == 'month' and bo.year != null and bo.year != '' and bo.month != null and bo.month != ''">
AND YEAR(p.enforcement_time) = #{bo.year} AND MONTH(p.enforcement_time) = #{bo.month}
</if>
<if test="bo.dateSearch == 'day' and bo.beginTime != null and bo.beginTime != ''">
AND DATE_FORMAT(p.enforcement_time,'%Y-%m-%d') &gt;= STR_TO_DATE(#{bo.beginTime},'%Y-%m-%d')
</if>
<if test="bo.dateSearch == 'day' and bo.endTime != null and bo.endTime != ''">
AND DATE_FORMAT(p.enforcement_time,'%Y-%m-%d') &lt;= STR_TO_DATE(#{bo.endTime},'%Y-%m-%d')
</if>
</sql>
<!-- 通用搜索条件片段 -->
<sql id="reportSearchCondition">
<if test="bo.bureauDeptName != null and bo.bureauDeptName != ''">
AND p.bureau_dept_name LIKE CONCAT('%', #{bo.bureauDeptName}, '%')
</if>
</sql>
<!-- 数据权限片段 -->
<sql id="reportDataFilter">
${dataFilter}
</sql>
<!-- Tab1:概览汇总 -->
<select id="selectPlanOverview" resultType="com.zdxt.enforcementcode.domain.vo.report.InspectionPlanOverviewVo">
SELECT
IFNULL(SUM(CASE WHEN p.time_type = 'year' THEN 1 ELSE 0 END), 0) AS yearTotal,
IFNULL(SUM(CASE WHEN p.time_type = 'quarter' THEN 1 ELSE 0 END), 0) AS quarterTotal,
IFNULL(SUM(CASE WHEN p.time_type = 'month' THEN 1 ELSE 0 END), 0) AS monthTotal
FROM enforcement_inspection_program p
WHERE 1=1
<include refid="reportBaseCondition"/>
<include refid="reportSearchCondition"/>
<include refid="reportTimeCondition"/>
<include refid="reportDataFilter"/>
</select>
<!-- Tab1:分组统计 -->
<select id="selectPlanGroupByDept" resultType="com.zdxt.enforcementcode.domain.vo.report.InspectionPlanGroupVo">
SELECT
p.bureau_dept_name AS bureauDeptName,
p.bureau_dept_id AS bureauDeptId,
COUNT(*) AS total,
SUM(CASE WHEN p.time_type = 'year' THEN 1 ELSE 0 END) AS yearTotal,
SUM(CASE WHEN p.time_type = 'quarter' THEN 1 ELSE 0 END) AS quarterTotal,
SUM(CASE WHEN p.time_type = 'month' THEN 1 ELSE 0 END) AS monthTotal
FROM enforcement_inspection_program p
WHERE 1=1
<include refid="reportBaseCondition"/>
<include refid="reportSearchCondition"/>
<include refid="reportTimeCondition"/>
<include refid="reportDataFilter"/>
GROUP BY p.bureau_dept_id, p.bureau_dept_name
ORDER BY total DESC
</select>
<!-- Tab1:明细列表 -->
<select id="selectPlanDetailList" resultType="com.zdxt.enforcementcode.domain.vo.report.InspectionPlanDetailVo">
SELECT p.id, p.plan_name AS planName, p.bureau_dept_name AS bureauDeptName,
p.enforcement_area AS enforcementArea,
DATE_FORMAT(p.enforcement_time, '%Y-%m-%d %H:%i:%s') AS enforcementTime,
p.time_type AS timeType, p.mouth, p.plan_year AS planYear, p.quarter
FROM enforcement_inspection_program p
WHERE 1=1
<include refid="reportBaseCondition"/>
<if test="bo.bureauDeptId != null and bo.bureauDeptId != ''">
AND p.bureau_dept_id = #{bo.bureauDeptId}
</if>
<if test="bo.reportTimeType != null and bo.reportTimeType != ''">
AND p.time_type = #{bo.reportTimeType}
</if>
<include refid="reportSearchCondition"/>
<include refid="reportTimeCondition"/>
<include refid="reportDataFilter"/>
ORDER BY p.enforcement_time DESC
</select>
<!-- Tab2:年度排行TOP10 -->
<select id="selectPlanRankByYear" resultType="com.zdxt.enforcementcode.domain.vo.report.InspectionPlanRankVo">
SELECT
p.bureau_dept_name AS bureauDeptName,
SUM(CASE WHEN p.time_type = 'year' THEN 1 ELSE 0 END) AS total
FROM enforcement_inspection_program p
WHERE 1=1
AND p.bureau_dept_id IS NOT NULL
AND p.time_type = 'year'
AND p.plan_year IS NOT NULL
<if test="bo.year != null and bo.year != ''">
AND YEAR(p.enforcement_time) = #{bo.year}
</if>
<include refid="reportDataFilter"/>
GROUP BY p.bureau_dept_id, p.bureau_dept_name
ORDER BY total DESC
LIMIT 10
</select>
<!-- Tab2:季度排行TOP10(含各季度明细) -->
<select id="selectPlanRankByQuarter" resultType="com.zdxt.enforcementcode.domain.vo.report.InspectionPlanRankVo">
SELECT
p.bureau_dept_name AS bureauDeptName,
SUM(CASE WHEN p.time_type = 'quarter' THEN 1 ELSE 0 END) AS total,
SUM(CASE WHEN p.quarter = 1 THEN 1 ELSE 0 END) AS quarterTotal1,
SUM(CASE WHEN p.quarter = 2 THEN 1 ELSE 0 END) AS quarterTotal2,
SUM(CASE WHEN p.quarter = 3 THEN 1 ELSE 0 END) AS quarterTotal3,
SUM(CASE WHEN p.quarter = 4 THEN 1 ELSE 0 END) AS quarterTotal4
FROM enforcement_inspection_program p
WHERE 1=1
AND p.bureau_dept_id IS NOT NULL
AND p.time_type = 'quarter'
AND p.plan_year IS NOT NULL
AND p.quarter IS NOT NULL
<if test="bo.year != null and bo.year != ''">
AND YEAR(p.enforcement_time) = #{bo.year}
</if>
<include refid="reportDataFilter"/>
GROUP BY p.bureau_dept_id, p.bureau_dept_name
ORDER BY total DESC
LIMIT 10
</select>
<!-- Tab2:月度排行TOP10(含各月明细) -->
<select id="selectPlanRankByMonth" resultType="com.zdxt.enforcementcode.domain.vo.report.InspectionPlanRankVo">
SELECT
p.bureau_dept_name AS bureauDeptName,
SUM(CASE WHEN p.time_type = 'month' THEN 1 ELSE 0 END) AS total,
SUM(CASE WHEN p.mouth = 1 THEN 1 ELSE 0 END) AS monthTotal1,
SUM(CASE WHEN p.mouth = 2 THEN 1 ELSE 0 END) AS monthTotal2,
SUM(CASE WHEN p.mouth = 3 THEN 1 ELSE 0 END) AS monthTotal3,
SUM(CASE WHEN p.mouth = 4 THEN 1 ELSE 0 END) AS monthTotal4,
SUM(CASE WHEN p.mouth = 5 THEN 1 ELSE 0 END) AS monthTotal5,
SUM(CASE WHEN p.mouth = 6 THEN 1 ELSE 0 END) AS monthTotal6,
SUM(CASE WHEN p.mouth = 7 THEN 1 ELSE 0 END) AS monthTotal7,
SUM(CASE WHEN p.mouth = 8 THEN 1 ELSE 0 END) AS monthTotal8,
SUM(CASE WHEN p.mouth = 9 THEN 1 ELSE 0 END) AS monthTotal9,
SUM(CASE WHEN p.mouth = 10 THEN 1 ELSE 0 END) AS monthTotal10,
SUM(CASE WHEN p.mouth = 11 THEN 1 ELSE 0 END) AS monthTotal11,
SUM(CASE WHEN p.mouth = 12 THEN 1 ELSE 0 END) AS monthTotal12
FROM enforcement_inspection_program p
WHERE 1=1
AND p.bureau_dept_id IS NOT NULL
AND p.time_type = 'month'
AND p.plan_year IS NOT NULL
AND p.mouth IS NOT NULL
<if test="bo.year != null and bo.year != ''">
AND YEAR(p.enforcement_time) = #{bo.year}
</if>
<include refid="reportDataFilter"/>
GROUP BY p.bureau_dept_id, p.bureau_dept_name
ORDER BY total DESC
LIMIT 10
</select>
</mapper>

141
zdxt-web-client/zdxt-admin-plus-ui/src/api/efcode/report/statistics-inspection-plan.ts

@ -0,0 +1,141 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
const BASE_URL = '/web/supervision/report/statistics-inspection-plan';
/** 查询参数 */
export interface InspectionPlanQuery {
pageNum?: number;
pageSize?: number;
/** 执法单位(模糊搜索) */
bureauDeptName?: string;
/** 局级部门ID(明细查询用) */
bureauDeptId?: string;
/** 时段类型:year/quarter/month/day */
dateSearch?: string;
/** 年 */
year?: string;
/** 报表搜索用季度 */
reportQuarter?: string;
/** 月 */
month?: string;
/** 自定义开始时间 */
beginTime?: string;
/** 自定义结束时间 */
endTime?: string;
/** 明细弹窗时的计划类型(year/quarter/month) */
reportTimeType?: string;
}
/** 概览VO */
export interface InspectionPlanOverviewVo {
total: number;
yearTotal: number;
quarterTotal: number;
monthTotal: number;
}
/** 分组统计VO */
export interface InspectionPlanGroupVo {
bureauDeptName: string;
bureauDeptId: string;
total: number;
yearTotal: number;
quarterTotal: number;
monthTotal: number;
}
/** 明细VO */
export interface InspectionPlanDetailVo {
id: string;
planName: string;
bureauDeptName: string;
enforcementArea: string;
enforcementTime: string;
}
/** 排行VO */
export interface InspectionPlanRankVo {
bureauDeptName: string;
total: number;
quarterTotal1?: number;
quarterTotal2?: number;
quarterTotal3?: number;
quarterTotal4?: number;
monthTotal1?: number;
monthTotal2?: number;
monthTotal3?: number;
monthTotal4?: number;
monthTotal5?: number;
monthTotal6?: number;
monthTotal7?: number;
monthTotal8?: number;
monthTotal9?: number;
monthTotal10?: number;
monthTotal11?: number;
monthTotal12?: number;
}
/** Tab1概览 */
export const getOverview = (params: InspectionPlanQuery): AxiosPromise => {
return request({
url: `${BASE_URL}/overview`,
method: 'get',
params
});
};
/** Tab1分组列表 */
export const getGroupList = (params: InspectionPlanQuery): AxiosPromise => {
return request({
url: `${BASE_URL}/groupList`,
method: 'get',
params
});
};
/** Tab1明细列表 */
export const getDetailList = (params: InspectionPlanQuery): AxiosPromise => {
return request({
url: `${BASE_URL}/detailList`,
method: 'get',
params
});
};
/** Tab2-年度排行TOP10 */
export const getRankByYear = (params: InspectionPlanQuery): AxiosPromise => {
return request({
url: `${BASE_URL}/rankByYear`,
method: 'get',
params
});
};
/** Tab2-季度排行TOP10 */
export const getRankByQuarter = (params: InspectionPlanQuery): AxiosPromise => {
return request({
url: `${BASE_URL}/rankByQuarter`,
method: 'get',
params
});
};
/** Tab2-月度排行TOP10 */
export const getRankByMonth = (params: InspectionPlanQuery): AxiosPromise => {
return request({
url: `${BASE_URL}/rankByMonth`,
method: 'get',
params
});
};
/** Tab1导出 */
export const exportGroupList = (data: InspectionPlanQuery): AxiosPromise<Blob> => {
return request({
url: `${BASE_URL}/exportGroupList`,
method: 'post',
data,
responseType: 'blob'
});
};

488
zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/report/statistics_enforcement_complaint/index.vue

@ -1,11 +1,497 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted, onUnmounted, nextTick } from 'vue';
import { ElMessage } from 'element-plus';
import * as echarts from 'echarts';
import { getDicts } from '@/api/system/dict/data';
import {
getOverview, getGroupList, getDetailList,
getTypeStatList, getAreaStatList, getStreetStatList,
exportGroupList, exportTypeStat, exportAreaStat, exportStreetStat
} from '@/api/efcode/report/statistics-complaint';
import type { ComplaintQuery, ComplaintGroupVo, ComplaintDetailVo, ComplaintTypeStatVo } from '@/api/efcode/report/statistics-complaint';
// 当前Tab
const activeTab = ref('tab1');
// ======================== 通用时段查询 ========================
const timeParams = reactive({
year: new Date().getFullYear().toString(),
quarter: undefined as string | undefined,
month: undefined as string | undefined,
beginTime: undefined as string | undefined,
endTime: undefined as string | undefined,
timeType: 'year' as string
});
const yearOptions = computed(() => {
const current = new Date().getFullYear();
return Array.from({ length: 10 }, (_, i) => String(current - i));
});
const quarterOptions = ['1', '2', '3', '4'];
const monthOptions = Array.from({ length: 12 }, (_, i) => String(i + 1));
const handleTimeTypeChange = (val: string) => {
timeParams.quarter = undefined;
timeParams.month = undefined;
timeParams.beginTime = undefined;
timeParams.endTime = undefined;
if (val !== 'year' && val !== 'quarter' && val !== 'month' && val !== 'day') {
timeParams.year = new Date().getFullYear().toString();
}
};
// 构建时段参数
const buildTimeQuery = () => ({
year: timeParams.year,
quarter: timeParams.quarter,
month: timeParams.month,
beginTime: timeParams.beginTime,
endTime: timeParams.endTime,
timeType: timeParams.timeType
});
// ======================== Tab1: 执法投诉统计 ========================
const loading1 = ref(false);
const tab1Query = reactive({
complaintDepartment: undefined as string | undefined,
enforceType: undefined as number | string | undefined,
enterpriseAddress: undefined as string | undefined
});
// 概览
const overviewData = reactive({ complaintCount: 0, complaintDeptCount: 0 });
// 主表格(分组)
const groupTableData = ref<ComplaintGroupVo[]>([]);
const groupTotal = ref(0);
const groupPageNum = ref(1);
const groupPageSize = ref(10);
// 明细弹窗
const detailDialogVisible = ref(false);
const detailTitle = ref('');
const detailTableData = ref<ComplaintDetailVo[]>([]);
const detailTotal = ref(0);
const detailPageNum = ref(1);
const detailPageSize = ref(10);
const currentDeptId = ref('');
// 字典
const caseTypeOptions = ref<any[]>([]);
const addressTypeOptions = ref<any[]>([]);
// ======================== Tab2: 投诉类型统计 ========================
const loading2 = ref(false);
const tab2TypeSelect = ref('type'); // type / '' (全市) / 具体区域dict_value
const tab2StatList = ref<ComplaintTypeStatVo[]>([]);
const tab2Total = computed(() => tab2StatList.value.reduce((sum, item) => sum + item.count, 0));
// 饼图
const pieChartRef = ref<HTMLElement>();
let pieChart: echarts.ECharts | null = null;
// Tab2 类型选择选项
const tab2TypeOptions = computed(() => {
const opts: { label: string; value: string }[] = [
{ label: '投诉类型', value: 'type' },
{ label: '全市', value: '' }
];
addressTypeOptions.value.forEach((item: any) => {
opts.push({ label: item.dictLabel, value: item.dictValue });
});
return opts;
});
// ======================== 生命周期 ========================
onMounted(() => {
loadDicts();
loadTab1Data();
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
pieChart?.dispose();
});
const handleResize = () => {
pieChart?.resize();
};
// ======================== 字典 ========================
const loadDicts = async () => {
try {
const [caseTypeRes, areaRes] = await Promise.all([
getDicts('caseType'),
getDicts('address_type')
]);
caseTypeOptions.value = caseTypeRes.data || [];
addressTypeOptions.value = areaRes.data || [];
} catch (e) {
console.error('加载字典失败', e);
}
};
// ======================== Tab1: 数据加载 ========================
const loadTab1Data = async () => {
loading1.value = true;
try {
const params: ComplaintQuery = {
...tab1Query,
...buildTimeQuery(),
pageNum: groupPageNum.value,
pageSize: groupPageSize.value
};
const [overviewRes, groupRes] = await Promise.all([
getOverview(params),
getGroupList(params)
]);
const ov = overviewRes.data || {};
overviewData.complaintCount = Number(ov.complaintCount) || 0;
overviewData.complaintDeptCount = Number(ov.complaintDeptCount) || 0;
groupTableData.value = groupRes.rows || [];
groupTotal.value = groupRes.total || 0;
} finally {
loading1.value = false;
}
};
/** Tab1搜索 */
const handleTab1Query = () => {
groupPageNum.value = 1;
loadTab1Data();
};
/** Tab1重置 */
const resetTab1Query = () => {
tab1Query.complaintDepartment = undefined;
tab1Query.enforceType = undefined;
tab1Query.enterpriseAddress = undefined;
timeParams.year = new Date().getFullYear().toString();
timeParams.quarter = undefined;
timeParams.month = undefined;
timeParams.beginTime = undefined;
timeParams.endTime = undefined;
timeParams.timeType = 'year';
groupPageNum.value = 1;
loadTab1Data();
};
/** Tab1翻页 */
const handleGroupPageChange = () => {
loadTab1Data();
};
/** 点击投诉数量→弹出明细 */
const handleShowDetail = (row: ComplaintGroupVo) => {
if (row.total <= 0) return;
currentDeptId.value = row.complaintDepartmentId;
detailTitle.value = `${row.complaintDepartment} — 投诉明细`;
detailPageNum.value = 1;
detailDialogVisible.value = true;
loadDetailData();
};
/** 加载明细数据 */
const loadDetailData = async () => {
try {
const params: ComplaintQuery = {
...tab1Query,
...buildTimeQuery(),
complaintDepartmentId: currentDeptId.value,
pageNum: detailPageNum.value,
pageSize: detailPageSize.value
};
const res = await getDetailList(params);
detailTableData.value = res.rows || [];
detailTotal.value = res.total || 0;
} catch (e) {
detailTableData.value = [];
detailTotal.value = 0;
}
};
/** 明细翻页 */
const handleDetailPageChange = () => {
loadDetailData();
};
/** Tab1导出 */
const handleExportTab1 = async () => {
try {
const params: ComplaintQuery = { ...tab1Query, ...buildTimeQuery() };
const res = await exportGroupList(params);
downloadBlob(res, '执法投诉统计.xlsx');
} catch (e) {
ElMessage.error('导出失败');
}
};
// ======================== Tab2: 数据加载 ========================
const loadTab2Data = async () => {
loading2.value = true;
try {
const params: ComplaintQuery = { ...buildTimeQuery(), type: tab2TypeSelect.value };
let res;
if (tab2TypeSelect.value === 'type') {
res = await getTypeStatList(params);
} else if (tab2TypeSelect.value === '') {
res = await getAreaStatList(params);
} else {
res = await getStreetStatList(params);
}
tab2StatList.value = res.data || [];
await nextTick();
renderPieChart();
} finally {
loading2.value = false;
}
};
/** Tab2搜索 */
const handleTab2Query = () => {
loadTab2Data();
};
/** Tab2重置 */
const resetTab2Query = () => {
timeParams.year = new Date().getFullYear().toString();
timeParams.quarter = undefined;
timeParams.month = undefined;
timeParams.beginTime = undefined;
timeParams.endTime = undefined;
timeParams.timeType = 'year';
tab2TypeSelect.value = 'type';
loadTab2Data();
};
/** Tab2导出 */
const handleExportTab2 = async () => {
try {
const params: ComplaintQuery = { ...buildTimeQuery(), type: tab2TypeSelect.value };
let res;
if (tab2TypeSelect.value === 'type') {
res = await exportTypeStat(params);
} else if (tab2TypeSelect.value === '') {
res = await exportAreaStat(params);
} else {
res = await exportStreetStat(params);
}
downloadBlob(res, '投诉类型统计.xlsx');
} catch (e) {
ElMessage.error('导出失败');
}
};
/** 饼图渲染 */
const renderPieChart = () => {
if (!pieChartRef.value) return;
if (!pieChart) {
pieChart = echarts.init(pieChartRef.value);
}
const data = tab2StatList.value.map(item => ({ name: item.name, value: item.count }));
const option: echarts.EChartsOption = {
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
legend: { orient: 'vertical', left: 'left', top: 'middle', type: 'scroll' },
series: [{
type: 'pie',
radius: ['35%', '65%'],
center: ['60%', '50%'],
data,
label: { formatter: '{b}: {c} ({d}%)' },
emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } }
}]
};
pieChart.setOption(option, true);
};
/** Tab切换 */
const handleTabChange = (tab: string) => {
if (tab === 'tab2' && tab2StatList.value.length === 0) {
loadTab2Data();
}
};
// ======================== 通用工具 ========================
const downloadBlob = (res: any, filename: string) => {
const blob = new Blob([res as any], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
window.URL.revokeObjectURL(url);
};
</script>
<template>
<div class="p-2">
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
<!-- ==================== Tab1: 执法投诉统计 ==================== -->
<el-tab-pane label="执法投诉统计" name="tab1">
<!-- 搜索区域 -->
<el-card shadow="hover" class="mb-[10px]">
<el-form :inline="true" label-width="90px">
<el-form-item label="被投诉对象">
<el-input v-model="tab1Query.complaintDepartment" placeholder="请输入被投诉对象" clearable style="width: 160px" @keyup.enter="handleTab1Query" />
</el-form-item>
<el-form-item label="投诉类型">
<el-select v-model="tab1Query.enforceType" placeholder="请选择投诉类型" clearable style="width: 150px">
<el-option v-for="item in caseTypeOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
</el-select>
</el-form-item>
<el-form-item label="涉事地址">
<el-input v-model="tab1Query.enterpriseAddress" placeholder="请输入涉事地址" clearable style="width: 160px" @keyup.enter="handleTab1Query" />
</el-form-item>
<el-form-item label="时段类型">
<el-select v-model="timeParams.timeType" style="width: 100px" @change="handleTimeTypeChange">
<el-option label="按年" value="year" />
<el-option label="按季" value="quarter" />
<el-option label="按月" value="month" />
<el-option label="自定义" value="day" />
</el-select>
</el-form-item>
<el-form-item label="年份">
<el-select v-model="timeParams.year" placeholder="选择年份" clearable style="width: 100px">
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
</el-select>
</el-form-item>
<el-form-item v-if="timeParams.timeType === 'quarter'" label="季度">
<el-select v-model="timeParams.quarter" placeholder="选择季度" clearable style="width: 90px">
<el-option v-for="q in quarterOptions" :key="q" :label="'第' + q + '季度'" :value="q" />
</el-select>
</el-form-item>
<el-form-item v-if="timeParams.timeType === 'month'" label="月份">
<el-select v-model="timeParams.month" placeholder="选择月份" clearable style="width: 90px">
<el-option v-for="m in monthOptions" :key="m" :label="m + '月'" :value="m" />
</el-select>
</el-form-item>
<el-form-item v-if="timeParams.timeType === 'day'" label="开始时间">
<el-date-picker v-model="timeParams.beginTime" type="date" value-format="YYYY-MM-DD" placeholder="开始日期" style="width: 140px" />
</el-form-item>
<el-form-item v-if="timeParams.timeType === 'day'" label="结束时间">
<el-date-picker v-model="timeParams.endTime" type="date" value-format="YYYY-MM-DD" placeholder="结束日期" style="width: 140px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleTab1Query">搜索</el-button>
<el-button icon="Refresh" @click="resetTab1Query">重置</el-button>
<el-button type="warning" icon="Download" @click="handleExportTab1">导出</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 概览 -->
<el-card shadow="hover" class="mb-[10px]">
<div style="display: flex; align-items: center; gap: 24px; font-size: 14px;">
<span>投诉数量:<strong style="color: #f56c6c;">{{ overviewData.complaintCount }}</strong></span>
<span>被投诉对象数:<strong style="color: #e6a23c;">{{ overviewData.complaintDeptCount }}</strong></span>
</div>
</el-card>
<!-- 主表格 -->
<el-card shadow="hover" v-loading="loading1">
<el-table :data="groupTableData" border stripe>
<el-table-column label="被投诉对象" prop="complaintDepartment" min-width="260" :show-overflow-tooltip="true" />
<el-table-column label="投诉数量" width="140" align="center">
<template #default="{ row }">
<el-link type="primary" :underline="false" @click="handleShowDetail(row)">{{ row.total }}</el-link>
</template>
</el-table-column>
</el-table>
<pagination v-show="groupTotal > 0"
v-model:page="groupPageNum" v-model:limit="groupPageSize"
:total="groupTotal" @pagination="handleGroupPageChange" />
</el-card>
<!-- 明细弹窗 -->
<el-dialog v-model="detailDialogVisible" :title="detailTitle" width="700px" destroy-on-close>
<el-table :data="detailTableData" border stripe max-height="400">
<el-table-column label="被投诉对象" prop="complaintDepartment" min-width="160" :show-overflow-tooltip="true" />
<el-table-column label="投诉类型" prop="enforceTypeText" width="140" align="center" />
<el-table-column label="投诉时间" prop="complaintTime" width="170" align="center" />
</el-table>
<pagination v-show="detailTotal > 0"
v-model:page="detailPageNum" v-model:limit="detailPageSize"
:total="detailTotal" @pagination="handleDetailPageChange" />
</el-dialog>
</el-tab-pane>
<!-- ==================== Tab2: 投诉类型统计 ==================== -->
<el-tab-pane label="投诉类型统计" name="tab2">
<!-- 搜索区域 -->
<el-card shadow="hover" class="mb-[10px]">
<el-form :inline="true" label-width="90px">
<el-form-item label="时段类型">
<el-select v-model="timeParams.timeType" style="width: 100px" @change="handleTimeTypeChange">
<el-option label="按年" value="year" />
<el-option label="按季" value="quarter" />
<el-option label="按月" value="month" />
<el-option label="自定义" value="day" />
</el-select>
</el-form-item>
<el-form-item label="年份">
<el-select v-model="timeParams.year" placeholder="选择年份" clearable style="width: 100px">
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
</el-select>
</el-form-item>
<el-form-item v-if="timeParams.timeType === 'quarter'" label="季度">
<el-select v-model="timeParams.quarter" placeholder="选择季度" clearable style="width: 90px">
<el-option v-for="q in quarterOptions" :key="q" :label="'第' + q + '季度'" :value="q" />
</el-select>
</el-form-item>
<el-form-item v-if="timeParams.timeType === 'month'" label="月份">
<el-select v-model="timeParams.month" placeholder="选择月份" clearable style="width: 90px">
<el-option v-for="m in monthOptions" :key="m" :label="m + '月'" :value="m" />
</el-select>
</el-form-item>
<el-form-item v-if="timeParams.timeType === 'day'" label="开始时间">
<el-date-picker v-model="timeParams.beginTime" type="date" value-format="YYYY-MM-DD" placeholder="开始日期" style="width: 140px" />
</el-form-item>
<el-form-item v-if="timeParams.timeType === 'day'" label="结束时间">
<el-date-picker v-model="timeParams.endTime" type="date" value-format="YYYY-MM-DD" placeholder="结束日期" style="width: 140px" />
</el-form-item>
<el-form-item label="类型切换">
<el-select v-model="tab2TypeSelect" style="width: 140px" @change="loadTab2Data">
<el-option v-for="opt in tab2TypeOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleTab2Query">搜索</el-button>
<el-button icon="Refresh" @click="resetTab2Query">重置</el-button>
<el-button type="warning" icon="Download" @click="handleExportTab2">导出</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 概览 -->
<el-card shadow="hover" class="mb-[10px]">
<div style="display: flex; align-items: center; gap: 24px; font-size: 14px;">
<span>总数量:<strong style="color: #409eff;">{{ tab2Total }}</strong></span>
<span>占比总计:<strong>100%</strong></span>
</div>
</el-card>
<!-- 表格 + 饼图 -->
<el-row :gutter="16" v-loading="loading2">
<el-col :span="12">
<el-card shadow="hover">
<el-table :data="tab2StatList" border stripe>
<el-table-column label="名称" prop="name" min-width="160" :show-overflow-tooltip="true" />
<el-table-column label="数量" prop="count" width="100" align="center" />
<el-table-column label="占比" prop="percentage" width="100" align="center" />
</el-table>
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="hover">
<div ref="pieChartRef" style="width: 100%; height: 380px;"></div>
</el-card>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</div>
</template>
<style scoped lang="scss">
</style>

458
zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/report/statistics_inspection_plan/index.vue

@ -1,11 +1,467 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted, onUnmounted, nextTick } from 'vue';
import { ElMessage } from 'element-plus';
import * as echarts from 'echarts';
import {
getOverview, getGroupList, getDetailList,
getRankByYear, getRankByQuarter, getRankByMonth,
exportGroupList
} from '@/api/efcode/report/statistics-inspection-plan';
import type {
InspectionPlanQuery, InspectionPlanOverviewVo, InspectionPlanGroupVo,
InspectionPlanDetailVo, InspectionPlanRankVo
} from '@/api/efcode/report/statistics-inspection-plan';
// 当前Tab
const activeTab = ref('tab1');
// ======================== 通用时段查询(Tab1) ========================
const timeParams = reactive({
year: new Date().getFullYear().toString(),
reportQuarter: undefined as string | undefined,
month: undefined as string | undefined,
beginTime: undefined as string | undefined,
endTime: undefined as string | undefined,
dateSearch: 'year' as string
});
const yearOptions = computed(() => {
const current = new Date().getFullYear();
return Array.from({ length: 10 }, (_, i) => String(current - i));
});
const quarterOptions = ['1', '2', '3', '4'];
const monthOptions = Array.from({ length: 12 }, (_, i) => String(i + 1));
const handleTimeTypeChange = (val: string) => {
timeParams.reportQuarter = undefined;
timeParams.month = undefined;
timeParams.beginTime = undefined;
timeParams.endTime = undefined;
};
// 构建Tab1时段参数
const buildTimeQuery = (): Partial<InspectionPlanQuery> => ({
year: timeParams.year,
reportQuarter: timeParams.reportQuarter,
month: timeParams.month,
beginTime: timeParams.beginTime,
endTime: timeParams.endTime,
dateSearch: timeParams.dateSearch
});
// ======================== Tab1: 执法计划统计 ========================
const loading1 = ref(false);
const tab1Query = reactive({ bureauDeptName: undefined as string | undefined });
// 概览
const overviewData = reactive<InspectionPlanOverviewVo>({ total: 0, yearTotal: 0, quarterTotal: 0, monthTotal: 0 });
// 主表格(分组)
const groupTableData = ref<InspectionPlanGroupVo[]>([]);
const groupTotal = ref(0);
const groupPageNum = ref(1);
const groupPageSize = ref(10);
// 明细弹窗
const detailDialogVisible = ref(false);
const detailTitle = ref('');
const detailTableData = ref<InspectionPlanDetailVo[]>([]);
const detailTotal = ref(0);
const detailPageNum = ref(1);
const detailPageSize = ref(10);
const currentBureauDeptId = ref('');
const currentReportTimeType = ref('');
// ======================== Tab2: 执法计划排行 ========================
const loading2 = ref(false);
const tab2Year = ref(new Date().getFullYear().toString());
const rankYearData = ref<InspectionPlanRankVo[]>([]);
const rankQuarterData = ref<InspectionPlanRankVo[]>([]);
const rankMonthData = ref<InspectionPlanRankVo[]>([]);
// ECharts 引用
const yearChartRef = ref<HTMLElement>();
const quarterChartRef = ref<HTMLElement>();
const monthChartRef = ref<HTMLElement>();
let yearChart: echarts.ECharts | null = null;
let quarterChart: echarts.ECharts | null = null;
let monthChart: echarts.ECharts | null = null;
// ======================== 生命周期 ========================
onMounted(() => {
loadTab1Data();
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
yearChart?.dispose();
quarterChart?.dispose();
monthChart?.dispose();
});
const handleResize = () => {
yearChart?.resize();
quarterChart?.resize();
monthChart?.resize();
};
// ======================== Tab1: 数据加载 ========================
const loadTab1Data = async () => {
loading1.value = true;
try {
const params: InspectionPlanQuery = {
...tab1Query,
...buildTimeQuery(),
pageNum: groupPageNum.value,
pageSize: groupPageSize.value
};
const [overviewRes, groupRes] = await Promise.all([
getOverview(params),
getGroupList(params)
]);
const ov = overviewRes.data || {};
overviewData.total = Number(ov.total) || 0;
overviewData.yearTotal = Number(ov.yearTotal) || 0;
overviewData.quarterTotal = Number(ov.quarterTotal) || 0;
overviewData.monthTotal = Number(ov.monthTotal) || 0;
groupTableData.value = groupRes.rows || [];
groupTotal.value = groupRes.total || 0;
} finally {
loading1.value = false;
}
};
/** Tab1搜索 */
const handleTab1Query = () => {
groupPageNum.value = 1;
loadTab1Data();
};
/** Tab1重置 */
const resetTab1Query = () => {
tab1Query.bureauDeptName = undefined;
timeParams.year = new Date().getFullYear().toString();
timeParams.reportQuarter = undefined;
timeParams.month = undefined;
timeParams.beginTime = undefined;
timeParams.endTime = undefined;
timeParams.dateSearch = 'year';
groupPageNum.value = 1;
loadTab1Data();
};
/** Tab1翻页 */
const handleGroupPageChange = () => {
loadTab1Data();
};
/** 点击数量→弹出明细 */
const handleShowDetail = (row: InspectionPlanGroupVo, type: string) => {
const count = type === 'year' ? row.yearTotal : type === 'quarter' ? row.quarterTotal : row.monthTotal;
if (!count || count <= 0) return;
currentBureauDeptId.value = row.bureauDeptId;
currentReportTimeType.value = type;
const typeLabel = type === 'year' ? '年度计划' : type === 'quarter' ? '季度计划' : '月度计划';
detailTitle.value = `${row.bureauDeptName} — ${typeLabel}明细`;
detailPageNum.value = 1;
detailDialogVisible.value = true;
loadDetailData();
};
/** 加载明细数据 */
const loadDetailData = async () => {
try {
const params: InspectionPlanQuery = {
...tab1Query,
...buildTimeQuery(),
bureauDeptId: currentBureauDeptId.value,
reportTimeType: currentReportTimeType.value,
pageNum: detailPageNum.value,
pageSize: detailPageSize.value
};
const res = await getDetailList(params);
detailTableData.value = res.rows || [];
detailTotal.value = res.total || 0;
} catch (e) {
detailTableData.value = [];
detailTotal.value = 0;
}
};
/** 明细翻页 */
const handleDetailPageChange = () => {
loadDetailData();
};
/** Tab1导出 */
const handleExportTab1 = async () => {
try {
const params: InspectionPlanQuery = { ...tab1Query, ...buildTimeQuery() };
const res = await exportGroupList(params);
downloadBlob(res, '检查计划统计.xlsx');
} catch (e) {
ElMessage.error('导出失败');
}
};
// ======================== Tab2: 数据加载 ========================
const loadTab2Data = async () => {
loading2.value = true;
try {
const params: InspectionPlanQuery = { year: tab2Year.value };
const [yearRes, quarterRes, monthRes] = await Promise.all([
getRankByYear(params),
getRankByQuarter(params),
getRankByMonth(params)
]);
rankYearData.value = yearRes.data || [];
rankQuarterData.value = quarterRes.data || [];
rankMonthData.value = monthRes.data || [];
await nextTick();
renderYearChart();
renderQuarterChart();
renderMonthChart();
} finally {
loading2.value = false;
}
};
/** Tab2搜索 */
const handleTab2Query = () => {
loadTab2Data();
};
/** Tab2重置 */
const resetTab2Query = () => {
tab2Year.value = new Date().getFullYear().toString();
loadTab2Data();
};
// ======================== ECharts 渲染 ========================
const renderYearChart = () => {
if (!yearChartRef.value) return;
if (!yearChart) yearChart = echarts.init(yearChartRef.value);
const names = rankYearData.value.map(item => item.bureauDeptName);
const values = rankYearData.value.map(item => item.total);
const option: echarts.EChartsOption = {
title: { text: '年度计划排行 TOP10', left: 'center', textStyle: { fontSize: 14 } },
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: { type: 'category', data: names, axisLabel: { rotate: 30, fontSize: 11 } },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: values, barMaxWidth: 40, itemStyle: { color: '#409eff' } }]
};
yearChart.setOption(option, true);
};
const renderQuarterChart = () => {
if (!quarterChartRef.value) return;
if (!quarterChart) quarterChart = echarts.init(quarterChartRef.value);
const names = rankQuarterData.value.map(item => item.bureauDeptName);
const colors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c'];
const series = [1, 2, 3, 4].map((q, idx) => ({
name: `第${q}季度`,
type: 'bar' as const,
stack: 'quarter',
barMaxWidth: 40,
data: rankQuarterData.value.map(item => (item as any)[`quarterTotal${q}`] || 0),
itemStyle: { color: colors[idx] }
}));
const option: echarts.EChartsOption = {
title: { text: '季度计划排行 TOP10', left: 'center', textStyle: { fontSize: 14 } },
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: { bottom: 0, data: ['第1季度', '第2季度', '第3季度', '第4季度'] },
grid: { left: '3%', right: '4%', bottom: '12%', containLabel: true },
xAxis: { type: 'category', data: names, axisLabel: { rotate: 30, fontSize: 11 } },
yAxis: { type: 'value' },
series
};
quarterChart.setOption(option, true);
};
const renderMonthChart = () => {
if (!monthChartRef.value) return;
if (!monthChart) monthChart = echarts.init(monthChartRef.value);
const names = rankMonthData.value.map(item => item.bureauDeptName);
const colors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#909399', '#b37feb',
'#36cfc9', '#ff85c0', '#ffc53d', '#73d13d', '#40a9ff', '#ff7a45'];
const series = Array.from({ length: 12 }, (_, i) => ({
name: `${i + 1}月`,
type: 'bar' as const,
stack: 'month',
barMaxWidth: 40,
data: rankMonthData.value.map(item => (item as any)[`monthTotal${i + 1}`] || 0),
itemStyle: { color: colors[i] }
}));
const option: echarts.EChartsOption = {
title: { text: '月度计划排行 TOP10', left: 'center', textStyle: { fontSize: 14 } },
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: { bottom: 0, data: Array.from({ length: 12 }, (_, i) => `${i + 1}月`), type: 'scroll' },
grid: { left: '3%', right: '4%', bottom: '12%', containLabel: true },
xAxis: { type: 'category', data: names, axisLabel: { rotate: 30, fontSize: 11 } },
yAxis: { type: 'value' },
series
};
monthChart.setOption(option, true);
};
/** Tab切换 */
const handleTabChange = (tab: string) => {
if (tab === 'tab2' && rankYearData.value.length === 0) {
loadTab2Data();
}
};
// ======================== 通用工具 ========================
const downloadBlob = (res: any, filename: string) => {
const blob = new Blob([res as any], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
window.URL.revokeObjectURL(url);
};
</script>
<template>
<div class="p-2">
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
<!-- ==================== Tab1: 执法计划统计 ==================== -->
<el-tab-pane label="执法计划统计" name="tab1">
<!-- 搜索区域 -->
<el-card shadow="hover" class="mb-[10px]">
<el-form :inline="true" label-width="90px">
<el-form-item label="执法单位">
<el-input v-model="tab1Query.bureauDeptName" placeholder="请输入执法单位" clearable style="width: 160px" @keyup.enter="handleTab1Query" />
</el-form-item>
<el-form-item label="时段类型">
<el-select v-model="timeParams.dateSearch" style="width: 100px" @change="handleTimeTypeChange">
<el-option label="按年" value="year" />
<el-option label="按季" value="quarter" />
<el-option label="按月" value="month" />
<el-option label="自定义" value="day" />
</el-select>
</el-form-item>
<el-form-item label="年份">
<el-select v-model="timeParams.year" placeholder="选择年份" clearable style="width: 100px">
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
</el-select>
</el-form-item>
<el-form-item v-if="timeParams.dateSearch === 'quarter'" label="季度">
<el-select v-model="timeParams.reportQuarter" placeholder="选择季度" clearable style="width: 90px">
<el-option v-for="q in quarterOptions" :key="q" :label="'第' + q + '季度'" :value="q" />
</el-select>
</el-form-item>
<el-form-item v-if="timeParams.dateSearch === 'month'" label="月份">
<el-select v-model="timeParams.month" placeholder="选择月份" clearable style="width: 90px">
<el-option v-for="m in monthOptions" :key="m" :label="m + '月'" :value="m" />
</el-select>
</el-form-item>
<el-form-item v-if="timeParams.dateSearch === 'day'" label="开始时间">
<el-date-picker v-model="timeParams.beginTime" type="date" value-format="YYYY-MM-DD" placeholder="开始日期" style="width: 140px" />
</el-form-item>
<el-form-item v-if="timeParams.dateSearch === 'day'" label="结束时间">
<el-date-picker v-model="timeParams.endTime" type="date" value-format="YYYY-MM-DD" placeholder="结束日期" style="width: 140px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleTab1Query">搜索</el-button>
<el-button icon="Refresh" @click="resetTab1Query">重置</el-button>
<el-button type="warning" icon="Download" @click="handleExportTab1">导出</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 概览 -->
<el-card shadow="hover" class="mb-[10px]">
<div style="display: flex; align-items: center; gap: 24px; font-size: 14px;">
<span>计划总数:<strong style="color: #409eff;">{{ overviewData.total }}</strong></span>
<span>年度计划:<strong style="color: #67c23a;">{{ overviewData.yearTotal }}</strong></span>
<span>季度计划:<strong style="color: #e6a23c;">{{ overviewData.quarterTotal }}</strong></span>
<span>月度计划:<strong style="color: #f56c6c;">{{ overviewData.monthTotal }}</strong></span>
</div>
</el-card>
<!-- 主表格 -->
<el-card shadow="hover" v-loading="loading1">
<el-table :data="groupTableData" border stripe>
<el-table-column label="执法单位" prop="bureauDeptName" min-width="260" :show-overflow-tooltip="true" />
<el-table-column label="总数" prop="total" width="100" align="center" />
<el-table-column label="年度计划" width="120" align="center">
<template #default="{ row }">
<el-link type="primary" :underline="false" @click="handleShowDetail(row, 'year')">{{ row.yearTotal }}</el-link>
</template>
</el-table-column>
<el-table-column label="季度计划" width="120" align="center">
<template #default="{ row }">
<el-link type="primary" :underline="false" @click="handleShowDetail(row, 'quarter')">{{ row.quarterTotal }}</el-link>
</template>
</el-table-column>
<el-table-column label="月度计划" width="120" align="center">
<template #default="{ row }">
<el-link type="primary" :underline="false" @click="handleShowDetail(row, 'month')">{{ row.monthTotal }}</el-link>
</template>
</el-table-column>
</el-table>
<pagination v-show="groupTotal > 0"
v-model:page="groupPageNum" v-model:limit="groupPageSize"
:total="groupTotal" @pagination="handleGroupPageChange" />
</el-card>
<!-- 明细弹窗 -->
<el-dialog v-model="detailDialogVisible" :title="detailTitle" width="750px" destroy-on-close>
<el-table :data="detailTableData" border stripe max-height="400">
<el-table-column label="计划名称" prop="planName" min-width="280" :show-overflow-tooltip="true" />
<el-table-column label="执法单位" prop="bureauDeptName" width="160" :show-overflow-tooltip="true" />
<el-table-column label="执法区域" prop="enforcementArea" width="120" align="center" />
<el-table-column label="发布时间" prop="enforcementTime" width="170" align="center" />
</el-table>
<pagination v-show="detailTotal > 0"
v-model:page="detailPageNum" v-model:limit="detailPageSize"
:total="detailTotal" @pagination="handleDetailPageChange" />
</el-dialog>
</el-tab-pane>
<!-- ==================== Tab2: 执法计划排行 ==================== -->
<el-tab-pane label="执法计划排行" name="tab2">
<!-- 搜索区域 -->
<el-card shadow="hover" class="mb-[10px]">
<el-form :inline="true" label-width="90px">
<el-form-item label="年份">
<el-select v-model="tab2Year" placeholder="选择年份" style="width: 120px">
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleTab2Query">搜索</el-button>
<el-button icon="Refresh" @click="resetTab2Query">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 三个柱状图 -->
<el-row :gutter="16" v-loading="loading2">
<el-col :span="24" class="mb-[16px]">
<el-card shadow="hover">
<div ref="yearChartRef" style="width: 100%; height: 350px;"></div>
</el-card>
</el-col>
<el-col :span="24" class="mb-[16px]">
<el-card shadow="hover">
<div ref="quarterChartRef" style="width: 100%; height: 380px;"></div>
</el-card>
</el-col>
<el-col :span="24">
<el-card shadow="hover">
<div ref="monthChartRef" style="width: 100%; height: 400px;"></div>
</el-card>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</div>
</template>
<style scoped lang="scss">
</style>

Loading…
Cancel
Save