17 changed files with 1856 additions and 17 deletions
@ -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) { |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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 ""; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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') >= 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') <= 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> |
||||
@ -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' |
||||
|
}); |
||||
|
}; |
||||
@ -1,11 +1,497 @@ |
|||||
<script setup lang="ts"> |
<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> |
</script> |
||||
|
|
||||
<template> |
<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> |
</template> |
||||
|
|
||||
<style scoped lang="scss"> |
<style scoped lang="scss"> |
||||
|
|
||||
</style> |
</style> |
||||
|
|||||
@ -1,11 +1,467 @@ |
|||||
<script setup lang="ts"> |
<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> |
</script> |
||||
|
|
||||
<template> |
<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> |
</template> |
||||
|
|
||||
<style scoped lang="scss"> |
<style scoped lang="scss"> |
||||
|
|
||||
</style> |
</style> |
||||
|
|||||
Loading…
Reference in new issue