wangmingyong 5 months ago
parent
commit
808e43ccf2
  1. 18
      script/sql/zfjd-refactoring-v1/013-计划任务表设计.sql
  2. 3
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnfAccordingController.java
  3. 51
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnfCpTaskController.java
  4. 51
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnfEnterpriseLawEnforcementController.java
  5. 16
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppNonEnforcementRegistrationController.java
  6. 106
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/EnforcementEducationServicesController.java
  7. 5
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/EfcodeCpPlan.java
  8. 5
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/EfcodeCpPlanBo.java
  9. 25
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/EnforcementEducationServicesBo.java
  10. 2
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/EnforcementRegistrationBo.java
  11. 13
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/EfcodeCpPlanVo.java
  12. 92
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/EfcodeCpTaskVo.java
  13. 19
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/EnforcementEducationServicesVo.java
  14. 30
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/EfcodeCpTaskMapper.java
  15. 4
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/NonEnforcementRegistrationMapper.java
  16. 11
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/CpPlanAutoMatchJob.java
  17. 9
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpPlanService.java
  18. 37
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpTaskService.java
  19. 19
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEnforcementEducationServicesService.java
  20. 11
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/INonEnforcementRegistrationService.java
  21. 258
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpPlanServiceImpl.java
  22. 50
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpReserveServiceImpl.java
  23. 65
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpTaskServiceImpl.java
  24. 138
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EnforcementEducationServicesServiceImpl.java
  25. 255
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/NonEnforcementRegistrationServiceImpl.java
  26. 81
      zdxt-modules/zdxt-enforcement-code/src/main/resources/mapper/enforcementcode/EfcodeCpTaskMapper.xml
  27. 2
      zdxt-web-client/zdxt-efcode-enforcement-app/src/api/enforcement/index.js
  28. 7
      zdxt-web-client/zdxt-efcode-enforcement-app/src/api/enforcement/plan.js
  29. 26
      zdxt-web-client/zdxt-efcode-enforcement-app/src/router/enforcement-app/enf-main-router.js
  30. 163
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/loginByPassword.vue
  31. 4
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/enforcementMy.vue
  32. 63
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_edit.vue
  33. 14
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_mine.vue
  34. 10
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_other.vue
  35. 392
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/task/cp_task_detail.vue
  36. 578
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/task/cp_task_mine.vue
  37. 6
      zdxt-web-client/zdxt-efcode-enforcement-app/vite-config/index.js
  38. 3
      zdxt-web-client/zdxt-efcode-enforcement-app/vite-env/.env.enforcement-dev
  39. 1
      zdxt-web-client/zdxt-efcode-enforcement-app/vite-env/.env.enforcement-prod
  40. 1
      zdxt-web-client/zdxt-efcode-enforcement-app/vite-env/.env.enforcement-test
  41. 354
      zdxt-web-server/zdxt-admin-web-server/src/test/java/com/zdxt/rest/test/cp/AutoMatchReserveTest.java

18
script/sql/zfjd-refactoring-v1/013-计划任务表设计.sql

@ -24,7 +24,9 @@ CREATE TABLE `efcode_cp_plan`
`is_union` tinyint NOT NULL DEFAULT 1 COMMENT '是否联合执法:1=是,0=否',
`union_bureau_dept_names` varchar(500) DEFAULT NULL COMMENT '联合执法单位(多个逗号分隔)',
`reserve_status` tinyint NOT NULL DEFAULT 0 COMMENT '预约状态:0=草稿,1=待预约(未发起联合),2=预约中,3=预约完成,4=重新预约中,5=待预约(还有位置可以加入)',
`reserve_status` tinyint NOT NULL DEFAULT 0 COMMENT '预约状态:0=草稿,1=待预约(未发起联合),2=预约中,3=预约完成,4=重新预约中,5=待预约(还有位置可以加入),6=待预约(人数已满)',
`task_id` varchar(50) NULL DEFAULT '' COMMENT '任务id:空串表示还没生成任务',
`system_reserved` tinyint NOT NULL DEFAULT 0 COMMENT '是否已经经过系统推荐预约:1=是,0=否',
`manual_reserved` tinyint NOT NULL DEFAULT 0 COMMENT '是否已经经过手动预约:1=是,0=否',
`bureau_dept_id` varchar(50) NOT NULL COMMENT '执法单位ID',
@ -181,14 +183,14 @@ CREATE TABLE `efcode_cp_setting`
CREATE TABLE `efcode_cp_log`
(
`id` varchar(50) NOT NULL COMMENT '主键ID' PRIMARY KEY,
`id` varchar(50) NOT NULL COMMENT '主键ID' PRIMARY KEY,
`record_id` varchar(50) NULL COMMENT '关联记录',
`table_name` varchar(50) NULL COMMENT '关联表',
`log_type` varchar(50) NULL COMMENT '日志类型',
`op_slog` varchar(2000) NULL COMMENT '短日志',
`op_llog` text NULL COMMENT '长日志',
`op_user_id` varchar(50) NOT NULL COMMENT '操作人ID',
`op_user_name` varchar(50) NOT NULL COMMENT '操作人姓名',
`op_user_id` varchar(50) NOT NULL COMMENT '操作人ID',
`op_user_name` varchar(50) NOT NULL COMMENT '操作人姓名',
`remark` varchar(500) NULL COMMENT '备注',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_dept` varchar(64) DEFAULT NULL COMMENT '创建人部门',
@ -199,7 +201,7 @@ CREATE TABLE `efcode_cp_log`
/*初始化数据*/
INSERT INTO `efcode_cp_setting` (`id`, `setting_name`, `setting_value`, `remark`, `create_by`, `create_dept`)
/*INSERT INTO `efcode_cp_setting` (`id`, `setting_name`, `setting_value`, `remark`, `create_by`, `create_dept`)
VALUES ('CP_SETTING_SUBMIT_DEADLINE', '下阶段计划提交最晚时间', '26', '每月几号前需提交下阶段计划<=', '0', '0'),
('CP_SETTING_AUTO_MATCH_START', '系统自动推荐匹配开始时间', '27', '系统自动推荐匹配的开始时间(号)=', '0', '0'),
('CP_SETTING_RESERVE_CANCEL_DURATION', '预约自动取消时长', '7', '发起预约后,如果多长时间没有处理,则自动取消',
@ -207,10 +209,10 @@ VALUES ('CP_SETTING_SUBMIT_DEADLINE', '下阶段计划提交最晚时间', '26',
('CP_SETTING_MANUAL_MATCH_DAYS', '手工匹配距计划开始天数', '3', '手工匹配距离计划开始的天数>=', '0', '0'),
('CP_MAX_UNION_UNIT', '单次联合单位最大数量', '3', '单次联合单位最大数量<=3', '0', '0'),
('CP_MAX_INNER_UNIT_AC_PERSON_NUM', '单位内部单次计划最多参与人数', '3', '单位内部单次计划最多参与人数<=3', '0',
'0');
'0');*/
/*创建计划任务*/
INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by,
/*INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by,
create_time, remark)
VALUES ('计划自动匹配预约',
'DEFAULT',
@ -221,4 +223,4 @@ VALUES ('计划自动匹配预约',
'0',
'admin',
NOW(),
'每天凌晨2点扫描待预约计划并自动匹配生成预约记录');
'每天凌晨2点扫描待预约计划并自动匹配生成预约记录');*/

3
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnfAccordingController.java

@ -41,7 +41,8 @@ public class AppEnfAccordingController extends BaseController {
/**
* 查询执依据告列表
*/
@SaCheckPermission("enforcementcode:zqyjjdm137344:list")
@SaIgnore
//@SaCheckPermission("enforcementcode:zqyjjdm137344:list")
@GetMapping("/list")
public TableDataInfo<Zqyjjdm137344Vo> list(Zqyjjdm137344Bo bo, PageQuery pageQuery) {
return zqyjjdm137344Service.queryPageList(bo, pageQuery);

51
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnfCpTaskController.java

@ -1,10 +1,18 @@
package com.zdxt.enforcementcode.controller.app.enforcement;
import com.zdxt.enforcementcode.controller.web.supervision.EfcodeCpTaskController;
import com.zdxt.enforcementcode.domain.vo.EfcodeCpTaskVo;
import com.zdxt.enforcementcode.service.IEfcodeCpTaskService;
import org.dromara.common.core.domain.R;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.List;
/**
* 行政执法检查计划-任务
*
@ -16,7 +24,50 @@ import org.springframework.web.bind.annotation.*;
@RequestMapping("/app-enf/cpTask")
public class AppEnfCpTaskController extends EfcodeCpTaskController {
@Autowired
private IEfcodeCpTaskService cpTaskService;
public AppEnfCpTaskController(IEfcodeCpTaskService efcodeCpTaskService) {
super(efcodeCpTaskService);
this.cpTaskService = efcodeCpTaskService;
}
/**
* 查询我的任务列表(指定日期)
*
* @param planDate 计划执行日期(可选,格式 yyyy-MM-dd)
* @return 任务列表
*/
@GetMapping("/myList")
public R<List<EfcodeCpTaskVo>> myList(
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date planDate) {
Long userId = LoginHelper.getUserId();
List<EfcodeCpTaskVo> list = cpTaskService.queryMyTaskList(userId, planDate);
return R.ok(list);
}
/**
* 查询指定月份中有任务的日期列表(用于日历打点)
*
* @param yearMonth 年月,格式如 "2026-03"
* @return 日期字符串列表
*/
@GetMapping("/myTaskDates")
public R<List<String>> myTaskDates(@RequestParam String yearMonth) {
Long userId = LoginHelper.getUserId();
List<String> dates = cpTaskService.queryMyTaskDates(userId, yearMonth);
return R.ok(dates);
}
/**
* 查询我的任务详情(通过 planId 查询任务+计划完整信息)
*
* @param planId 计划ID
* @return 任务详情
*/
@GetMapping("/myDetail")
public R<EfcodeCpTaskVo> myDetail(@RequestParam String planId) {
EfcodeCpTaskVo detail = cpTaskService.queryMyTaskDetail(planId);
return R.ok(detail);
}
}

51
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnfEnterpriseLawEnforcementController.java

@ -12,6 +12,7 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.ISysUserService;
import org.springframework.web.bind.annotation.*;
@ -40,27 +41,51 @@ public class AppEnfEnterpriseLawEnforcementController {
@SaIgnore
@PostMapping("/queryList")
@ResponseBody
public TableDataInfo<EnterpriseLawEnforcementVo> queryList(@RequestBody EnterpriseLawEnforcementBo bo, PageQuery pageQuery) {
TableDataInfo<EnterpriseLawEnforcementVo> rspData = enterpriseLawEnforcementService.selectEnterpriseLawEnforcementVoPageByBo(bo, pageQuery);
public TableDataInfo<EnterpriseLawEnforcementVo> queryList(@RequestBody EnterpriseLawEnforcementBo bo, @RequestBody PageQuery pageQuery) {
// 使用 DataPermissionHelper.ignore() 跳过数据权限检查,避免未登录时获取 LoginUser 报错
TableDataInfo<EnterpriseLawEnforcementVo> rspData = DataPermissionHelper.ignore(() ->
enterpriseLawEnforcementService.selectEnterpriseLawEnforcementVoPageByBo(bo, pageQuery)
);
List<EnterpriseLawEnforcementVo> list = rspData.getRows();
if (list == null || list.isEmpty()) {
return rspData;
}
for (EnterpriseLawEnforcementVo vo : list) {
List<SysUserVo> userBeans = new ArrayList<>();
if (StringUtils.isNotEmpty(vo.getLawEnforcerTwoId())) {
Long[] userIds = Convert.toLongArray(vo.getLawEnforcerTwoId());
List<Long> userIdList = java.util.Arrays.asList(userIds);
userBeans = sysUserService.selectUserByIds(userIdList, null);
try {
String[] userIdStrs = Convert.toStrArray(vo.getLawEnforcerTwoId());
List<Long> userIdList = new ArrayList<>();
for (String id : userIdStrs) {
try {
userIdList.add(Long.parseLong(id.trim()));
} catch (NumberFormatException e) {
log.warn("无效的用户ID格式: {}", id);
}
}
if (!userIdList.isEmpty()) {
userBeans = sysUserService.selectUserByIds(userIdList, null);
}
} catch (Exception e) {
log.error("解析执法人员ID失败: {}", vo.getLawEnforcerTwoId(), e);
}
}
String tmpUserShow = "";
for (SysUserVo ub : userBeans) {
String temp = ub.getLawCertificateNum();
if(StringUtils.isEmpty(temp)) {
temp = CommonUtil.encryptPhone(ub.getPhonenumber());
if (userBeans != null && !userBeans.isEmpty()) {
for (SysUserVo ub : userBeans) {
if (ub != null) {
String temp = ub.getLawCertificateNum();
if(StringUtils.isEmpty(temp)) {
temp = CommonUtil.encryptPhone(ub.getPhonenumber());
}
tmpUserShow += ub.getUserName() +" "+ temp + ",";
}
}
if (tmpUserShow.length() > 0) {
tmpUserShow = tmpUserShow.substring(0, tmpUserShow.length()-1);
}
tmpUserShow += ub.getUserName() +" "+ temp + ",";
}
if (tmpUserShow.length() > 0) {
tmpUserShow = tmpUserShow.substring(0, tmpUserShow.length()-1);
}
vo.setLawEnforcer2Show(tmpUserShow);

16
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppNonEnforcementRegistrationController.java

@ -1,16 +1,16 @@
package com.zdxt.enforcementcode.controller.app.enforcement;
import cn.dev33.satoken.annotation.SaIgnore;
import com.zdxt.enforcementcode.domain.bo.NonEnforcementRegistrationBo;
import com.zdxt.enforcementcode.domain.vo.NonEnforcementRegistrationVo;
import com.zdxt.enforcementcode.service.INonEnforcementRegistrationService;
import org.dromara.common.core.domain.R;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 执法端-非行政执法登记Controller
*
@ -34,21 +34,19 @@ public class AppNonEnforcementRegistrationController extends BaseController {
}
/**
* 执法端-非行政查询执法登记列表
* 执法端-非行政查询执法登记列表(历史活动) - 分页
*/
@SaIgnore
@GetMapping("/queryList")
public TableDataInfo queryList(NonEnforcementRegistrationBo bo)
public TableDataInfo<NonEnforcementRegistrationVo> queryList(NonEnforcementRegistrationBo bo, PageQuery pageQuery)
{
int total = enforcementRegistrationService.getListTotal(bo);
List<NonEnforcementRegistrationVo> list = enforcementRegistrationService.selectNonEnforcementRegistrationList(bo);
TableDataInfo<NonEnforcementRegistrationVo> rspData = new TableDataInfo<>(list, total);
rspData.setCode(0);
return rspData;
return enforcementRegistrationService.queryPageList(bo, pageQuery);
}
/**
* 执法端-非行政查询执法登记列表
*/
@SaIgnore
@GetMapping("/queryById")
public R<NonEnforcementRegistrationVo> queryById(String id)
{

106
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/EnforcementEducationServicesController.java

@ -0,0 +1,106 @@
package com.zdxt.enforcementcode.controller.web.supervision;
import java.util.List;
import cn.dev33.satoken.annotation.SaIgnore;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import com.zdxt.enforcementcode.domain.vo.EnforcementEducationServicesVo;
import com.zdxt.enforcementcode.domain.bo.EnforcementEducationServicesBo;
import com.zdxt.enforcementcode.service.IEnforcementEducationServicesService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 监督端-普法服务主表
*
* @author niujiyi
* @date 2026-05-26
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/web/enforcementcode/educationServices")
public class EnforcementEducationServicesController extends BaseController {
private final IEnforcementEducationServicesService enforcementEducationServicesService;
/**
* 监督端-查询普法服务主表列表
*/
//@SaCheckPermission("enforcementcode:educationServices:list")
@SaIgnore
@GetMapping("/list")
public TableDataInfo<EnforcementEducationServicesVo> list(EnforcementEducationServicesBo bo, PageQuery pageQuery) {
return enforcementEducationServicesService.queryPageList(bo, pageQuery);
}
/**
* 监督端-导出普法服务主表列表
*/
@SaCheckPermission("enforcementcode:educationServices:export")
@Log(title = "普法服务主表", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(EnforcementEducationServicesBo bo, HttpServletResponse response) {
List<EnforcementEducationServicesVo> list = enforcementEducationServicesService.queryList(bo);
ExcelUtil.exportExcel(list, "普法服务主表", EnforcementEducationServicesVo.class, response);
}
/**
* 监督端-获取普法服务主表详细信息
*
* @param id 主键
*/
//@SaCheckPermission("enforcementcode:educationServices:query")
@SaIgnore
@GetMapping("/details")
public R<EnforcementEducationServicesVo> details(String id) {
return R.ok(enforcementEducationServicesService.queryEducationServicesById(id));
}
/**
* 监督端-新增普法服务主表
*/
//@SaCheckPermission("enforcementcode:educationServices:add")
@SaIgnore
@Log(title = "普法服务主表", businessType = BusinessType.INSERT)
@PostMapping("/add")
public R<Void> add(@Validated(AddGroup.class) @RequestBody EnforcementEducationServicesBo bo) {
return toAjax(enforcementEducationServicesService.insertEducationServices(bo));
}
/**
* 监督端-修改普法服务主表
*/
//@SaCheckPermission("enforcementcode:educationServices:edit")
@Log(title = "普法服务主表", businessType = BusinessType.UPDATE)
@SaIgnore
@PostMapping("/edit")
public R<Void> edit(@RequestBody EnforcementEducationServicesBo bo) {
return toAjax(enforcementEducationServicesService.updateByBo(bo));
}
/**
* 监督端-删除普法服务主表
*
* @param ids 主键串
*/
//@SaCheckPermission("enforcementcode:educationServices:remove")
@SaIgnore
@GetMapping("/remove")
public R<Void> remove(String[] ids) {
return toAjax(enforcementEducationServicesService.deleteWithValidByIds(List.of(ids), true));
}
}

5
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/EfcodeCpPlan.java

@ -114,6 +114,11 @@ public class EfcodeCpPlan extends BaseEntity {
*/
private Long reserveStatus;
/**
* 任务id:空串表示还没生成任务
*/
private String taskId;
/**
* 是否已经经过系统推荐预约:1=是,0=否
*/

5
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/EfcodeCpPlanBo.java

@ -128,6 +128,11 @@ public class EfcodeCpPlanBo extends BaseEntity {
@NotNull(message = "预约状态:0=草稿,1=待预约,2=预约中,3=预约完成不能为空", groups = { AddGroup.class, EditGroup.class })
private Long reserveStatus;
/**
* 任务id:空串表示还没生成任务
*/
private String taskId;
/**
* 是否已经经过系统推荐预约:1=是,0=否
*/

25
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/EnforcementEducationServicesBo.java

@ -9,6 +9,9 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import java.util.Date;
import java.util.List;
/**
* 执法信息发布主业务对象 enforcement_education_services
*
@ -23,31 +26,26 @@ public class EnforcementEducationServicesBo extends BaseEntity {
/**
* 主键ID
*/
@NotBlank(message = "主键ID不能为空", groups = { EditGroup.class })
private String id;
/**
* 标题
*/
@NotBlank(message = "标题不能为空", groups = { AddGroup.class, EditGroup.class })
private String title;
/**
* 发布部门ID(多个用逗号分隔)
*/
@NotBlank(message = "发布部门ID(多个用逗号分隔)不能为空", groups = { AddGroup.class, EditGroup.class })
private String publishDeptIds;
/**
* 发布部门名称(冗余存储)
*/
@NotBlank(message = "发布部门名称(冗余存储)不能为空", groups = { AddGroup.class, EditGroup.class })
private String publishDeptNames;
/**
* 发布平台 0-全部 1-监督端 2-执法端 3-企业端
*/
@NotNull(message = "发布平台 0-全部 1-监督端 2-执法端 3-企业端不能为空", groups = { AddGroup.class, EditGroup.class })
private Integer targetPlatform;
/**
@ -58,7 +56,6 @@ public class EnforcementEducationServicesBo extends BaseEntity {
/**
* 来源类型 1|原生 2|转载
*/
@NotNull(message = "来源类型 1|原生 2|转载不能为空", groups = { AddGroup.class, EditGroup.class })
private Integer sourceType;
/**
@ -79,7 +76,6 @@ public class EnforcementEducationServicesBo extends BaseEntity {
/**
* 浏览次数
*/
@NotNull(message = "浏览次数不能为空", groups = { AddGroup.class, EditGroup.class })
private Integer viewCount;
/**
@ -105,7 +101,6 @@ public class EnforcementEducationServicesBo extends BaseEntity {
/**
* 是否删除 0=否 1=是
*/
@NotNull(message = "是否删除 0=否 1=是不能为空", groups = { AddGroup.class, EditGroup.class })
private Integer isDelete;
/**
@ -113,5 +108,19 @@ public class EnforcementEducationServicesBo extends BaseEntity {
*/
private String remark;
/**
* 附件列表
*/
private List<ZdxtFileBo> fileList;
/**
* 开始时间
*/
private Date startTime;
/**
* 结束时间
*/
private Date endTime;
}

2
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/EnforcementRegistrationBo.java

@ -208,7 +208,7 @@ public class EnforcementRegistrationBo extends BaseEntity {
private String bureauDeptName;
/**
*
*执法依据
*/
private String lawEnforcementBasisText;

13
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/EfcodeCpPlanVo.java

@ -145,6 +145,12 @@ public class EfcodeCpPlanVo implements Serializable {
@ExcelProperty(value = "预约状态:0=草稿,1=待预约,2=预约中,3=预约完成")
private Long reserveStatus;
/**
* 任务id:空串表示还没生成任务
*/
@ExcelProperty(value = "任务ID")
private String taskId;
/**
* 是否已经经过系统推荐预约:1=是,0=否
*/
@ -163,6 +169,13 @@ public class EfcodeCpPlanVo implements Serializable {
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private Long editEnable;
/**
* 是否可以删除:1=是,0=否(非数据库字段,后置计算)
* <p>规则:可编辑时可删除;不联合执法且执行时间未到时也可删除</p>
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private Long deleteEnable;
/**
* 执法单位ID
*/

92
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/EfcodeCpTaskVo.java

@ -46,5 +46,97 @@ public class EfcodeCpTaskVo implements Serializable {
@ExcelProperty(value = "执法单位ID")
private String bureauDeptId;
// ========== 以下为关联计划表字段,通过 SQL JOIN 填充,非数据库字段 ==========
/**
* 关联计划ID
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private String planId;
/**
* 计划执行日期
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private Date planDate;
/**
* 执行时段:1=上午,2=下午,3=其他
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private Long planTime;
/**
* 经过预约后最终的计划执行日期
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private Date planActualDate;
/**
* 经过预约后最终的执行时段:1=上午,2=下午,3=其他
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private Long planActualTime;
/**
* 执法事项
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private String enforceContent;
/**
* 被检查企业/机构名称
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private String enterpriseName;
/**
* 是否联合执法:1=是,0=否
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private Long isUnion;
/**
* 执法单位名称
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private String bureauDeptName;
/**
* 计划类别:1=日常计划,2=专项计划
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private Long planType;
/**
* 执法人员姓名(多个逗号分隔)
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private String enforceUserNames;
/**
* 被检查企业/机构信用代码
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private String enterpriseNumber;
/**
* 联合执法单位(多个逗号分隔)
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private String unionBureauDeptNames;
/**
* 预约状态:0=草稿,1=待预约,2=预约中,3=预约完成
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private Long reserveStatus;
/**
* 备注
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private String remark;
}

19
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/EnforcementEducationServicesVo.java

@ -7,7 +7,7 @@ import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
@ -60,12 +60,24 @@ public class EnforcementEducationServicesVo implements Serializable {
@ExcelProperty(value = "内容分类")
private Integer contentType;
/**
* 内容分类标签(字典翻译)
*/
@ExcelProperty(value = "内容分类名称")
private String contentTypeLabel;
/**
* 来源类型 1|原生 2|转载
*/
@ExcelProperty(value = "来源类型 1|原生 2|转载")
private Integer sourceType;
/**
* 发布平台标签(字典翻译)
*/
@ExcelProperty(value = "发布平台名称")
private String targetPlatformLabel;
/**
* 服务信息(富文本内容)
*/
@ -132,4 +144,9 @@ public class EnforcementEducationServicesVo implements Serializable {
private Integer isRead;
/**
* 附件信息
*/
@ExcelProperty(value = "附件信息")
private List<ZdxtFileVo> fileList;
}

30
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/EfcodeCpTaskMapper.java

@ -2,8 +2,12 @@ package com.zdxt.enforcementcode.mapper;
import com.zdxt.enforcementcode.domain.EfcodeCpTask;
import com.zdxt.enforcementcode.domain.vo.EfcodeCpTaskVo;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import java.util.Date;
import java.util.List;
/**
* 行政执法检查计划-任务Mapper接口
*
@ -12,4 +16,30 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
*/
public interface EfcodeCpTaskMapper extends BaseMapperPlus<EfcodeCpTask, EfcodeCpTaskVo> {
/**
* 查询我的任务列表(通过计划表关联,筛选当前用户参与的任务)
*
* @param userId 当前用户ID
* @param planDate 计划执行日期(可为null)
* @return 任务列表
*/
List<EfcodeCpTaskVo> selectMyTaskList(@Param("userId") Long userId, @Param("planDate") Date planDate);
/**
* 查询指定月份中有任务的日期列表(用于日历打点)
*
* @param userId 当前用户ID
* @param yearMonth 年月,格式如 "2026-03"
* @return 日期字符串列表,格式如 ["2026-03-02", "2026-03-10"]
*/
List<String> selectMyTaskDates(@Param("userId") Long userId, @Param("yearMonth") String yearMonth);
/**
* 查询我的任务详情(通过 planId 查询任务+计划完整信息)
*
* @param planId 计划ID
* @return 任务详情
*/
EfcodeCpTaskVo selectMyTaskDetail(@Param("planId") String planId);
}

4
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/NonEnforcementRegistrationMapper.java

@ -1,7 +1,9 @@
package com.zdxt.enforcementcode.mapper;
import com.zdxt.enforcementcode.domain.NonEnforcementRegistration;
import com.zdxt.enforcementcode.domain.bo.NonEnforcementRegistrationBo;
import com.zdxt.enforcementcode.domain.vo.NonEnforcementRegistrationVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.springframework.stereotype.Repository;
import java.util.List;
@ -13,7 +15,7 @@ import java.util.List;
* @date 2025-06-19
*/
@Repository
public interface NonEnforcementRegistrationMapper
public interface NonEnforcementRegistrationMapper extends BaseMapperPlus<NonEnforcementRegistration, NonEnforcementRegistrationVo>
{
/**
* 查询非行政执法登记

11
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/CpPlanAutoMatchJob.java

@ -20,9 +20,18 @@ public class CpPlanAutoMatchJob {
/**
* 任务执行入口(public,供 JobInvokeUtil 通过反射调用)
* <p>默认非强制模式:受 CP_SETTING_AUTO_MATCH_START 日期控制</p>
*/
public void execute() {
log.info("[CpPlanAutoMatchJob] 定时任务触发:计划自动匹配预约");
cpPlanService.autoMatchReserve();
cpPlanService.autoMatchReserve(false, true, true);
}
/**
* 强制执行入口(忽略日期校验,立即执行匹配)
*/
public void executeForce() {
log.info("[CpPlanAutoMatchJob] 强制触发:计划自动匹配预约");
cpPlanService.autoMatchReserve(true, true, true);
}
}

9
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpPlanService.java

@ -7,6 +7,8 @@ import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.zdxt.enforcementcode.domain.EfcodeCpReserve;
/**
* 行政执法检查计划-计划Service接口
@ -79,6 +81,11 @@ public interface IEfcodeCpPlanService {
/**
* 计划自动匹配预约(定时任务调用)
* <p>扫描待预约状态的计划,按规则自动匹配并生成预约记录</p>
*
* @param forceMatch 是否强制匹配(忽略 CP_SETTING_AUTO_MATCH_START 日期校验)
* @param updatePlan 是否更新计划记录(system_reserved、reserve_status)
* @param insertReserve 是否将预约记录插入数据库;为false时仅计算匹配结果不落库
* @return 按 planGroupId 分组的预约记录列表(key=planGroupId, value=该组预约记录)
*/
void autoMatchReserve();
Map<String, List<EfcodeCpReserve>> autoMatchReserve(boolean forceMatch, boolean updatePlan, boolean insertReserve);
}

37
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpTaskService.java

@ -5,7 +5,10 @@ import com.zdxt.enforcementcode.domain.bo.EfcodeCpTaskBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.zdxt.enforcementcode.domain.EfcodeCpPlan;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
@ -65,4 +68,38 @@ public interface IEfcodeCpTaskService {
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid);
/**
* 查询我的任务列表(通过计划表关联,筛选当前用户参与的任务)
*
* @param userId 当前用户ID
* @param planDate 计划执行日期(可为null)
* @return 任务列表
*/
List<EfcodeCpTaskVo> queryMyTaskList(Long userId, Date planDate);
/**
* 查询指定月份中有任务的日期列表(用于日历打点)
*
* @param userId 当前用户ID
* @param yearMonth 年月,格式如 "2026-03"
* @return 日期字符串列表
*/
List<String> queryMyTaskDates(Long userId, String yearMonth);
/**
* 查询我的任务详情(通过 planId 查询任务+计划完整信息)
*
* @param planId 计划ID
* @return 任务详情
*/
EfcodeCpTaskVo queryMyTaskDetail(String planId);
/**
* 强制创建任务(忽略其他条件),根据计划信息直接生成任务记录并回写 plan.task_id
*
* @param plan 来源计划
* @return 新建任务ID
*/
String forceGenerateTask(EfcodeCpPlan plan);
}

19
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEnforcementEducationServicesService.java

@ -23,7 +23,13 @@ public interface IEnforcementEducationServicesService {
* @return 执法信息发布主
*/
EnforcementEducationServicesVo queryById(String id);
/**
* 查询普法信息详情
*
* @param id 主键
* @return 详情
*/
EnforcementEducationServicesVo queryEducationServicesById(String id);
/**
* 分页查询执法信息发布主列表
*
@ -49,6 +55,14 @@ public interface IEnforcementEducationServicesService {
*/
Boolean insertByBo(EnforcementEducationServicesBo bo);
/**
* 监督端-新增执法信息发布主
*
* @param bo 执法信息发布主
* @return 是否新增成功
*/
Boolean insertEducationServices(EnforcementEducationServicesBo bo);
/**
* 修改执法信息发布主
*
@ -72,4 +86,7 @@ public interface IEnforcementEducationServicesService {
* @return 未读普法消息数量
*/
Long countUnreadEducationServices();
}

11
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/INonEnforcementRegistrationService.java

@ -3,6 +3,8 @@ package com.zdxt.enforcementcode.service;
import com.zdxt.enforcementcode.domain.bo.NonEnforcementRegistrationBo;
import com.zdxt.enforcementcode.domain.vo.NonEnforcementRegistrationVo;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import java.util.List;
@ -22,6 +24,15 @@ public interface INonEnforcementRegistrationService
*/
public NonEnforcementRegistrationVo selectNonEnforcementRegistrationById(String id);
/**
* 分页查询非行政执法登记列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 非行政执法登记分页列表
*/
TableDataInfo<NonEnforcementRegistrationVo> queryPageList(NonEnforcementRegistrationBo bo, PageQuery pageQuery);
/**
* 查询非行政执法登记列表
*

258
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpPlanServiceImpl.java

@ -19,14 +19,26 @@ import com.zdxt.enforcementcode.mapper.EfcodeCpPlanMapper;
import com.zdxt.enforcementcode.mapper.EfcodeCpPlanUsersMapper;
import com.zdxt.enforcementcode.service.IEfcodeCpPlanService;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.Collection;
import java.util.stream.Collectors;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.satoken.utils.LoginHelper;
import com.zdxt.enforcementcode.domain.EfcodeCpReserve;
import com.zdxt.enforcementcode.domain.EfcodeCpSetting;
import com.zdxt.enforcementcode.mapper.EfcodeCpReserveMapper;
import com.zdxt.enforcementcode.mapper.EfcodeCpSettingMapper;
import com.zdxt.enforcementcode.service.IEfcodeCpTaskService;
/**
* 行政执法检查计划-计划Service业务层处理
@ -41,6 +53,9 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService {
private final EfcodeCpPlanMapper baseMapper;
private final EfcodeCpPlanUsersMapper efcodeCpPlanUsersMapper;
private final EfcodeCpReserveMapper efcodeCpReserveMapper;
private final EfcodeCpSettingMapper efcodeCpSettingMapper;
private final IEfcodeCpTaskService cpTaskService;
/**
* 查询行政执法检查计划-计划
@ -118,8 +133,9 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService {
}
/**
* 后置计算 editEnable:判断记录是否可编辑
* <p>规则:预约状态为草稿(0)或待预约(1)时可编辑,其他状态不可编辑</p>
* 后置计算 editEnable / deleteEnable:判断记录是否可编辑、可删除
* <p>editEnable 规则:预约状态为草稿(0)或待预约(1)时可编辑,其他状态不可编辑</p>
* <p>deleteEnable 规则:可编辑时可删除;或者不联合执法(isUnion=0)且计划执行时间还未到时也可删除</p>
*
* @param records 查询结果列表
*/
@ -127,10 +143,22 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService {
if (records == null || records.isEmpty()) {
return;
}
LocalDate today = LocalDate.now();
for (EfcodeCpPlanVo vo : records) {
Long status = vo.getReserveStatus();
// 草稿(0)或待预约(1)可编辑,其他状态不可编辑
vo.setEditEnable(status != null && status <= 1L ? 1L : 0L);
boolean canEdit = status != null && status <= 1L;
vo.setEditEnable(canEdit ? 1L : 0L);
// 删除条件:可编辑 OR (不联合执法 AND 执行时间未到)
boolean canDelete = canEdit;
if (!canDelete && vo.getIsUnion() != null && vo.getIsUnion() == 0L && vo.getPlanDate() != null) {
LocalDate planLocalDate = vo.getPlanDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
if (planLocalDate.isAfter(today)) {
canDelete = true;
}
}
vo.setDeleteEnable(canDelete ? 1L : 0L);
}
}
@ -222,7 +250,27 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService {
* @param entity 已持久化的实体
*/
private void afterInsert(EfcodeCpPlanBo bo, EfcodeCpPlan entity) {
// TODO 后续扩展:如发送通知、记录日志等
// 条件1:不联合的计划,直接生成任务
if (entity.getIsUnion() != null && entity.getIsUnion() == 0L) {
log.info("[计划提交] 不联合计划,直接生成任务, planId={}", entity.getId());
cpTaskService.forceGenerateTask(entity);
return;
}
// 条件2:执行时间距今天 <= CP_SETTING_MANUAL_MATCH_DAYS,直接生成任务
if (entity.getPlanDate() != null) {
String manualMatchDaysStr = getSettingValue("CP_SETTING_MANUAL_MATCH_DAYS");
int manualMatchDays = 3; // 默认值
if (StringUtils.isNotBlank(manualMatchDaysStr)) {
try { manualMatchDays = Integer.parseInt(manualMatchDaysStr.trim()); } catch (NumberFormatException ignored) {}
}
LocalDate planLocalDate = entity.getPlanDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
long daysUntilPlan = ChronoUnit.DAYS.between(LocalDate.now(), planLocalDate);
if (daysUntilPlan <= manualMatchDays) {
log.info("[计划提交] 执行时间距今{}<={}(CP_SETTING_MANUAL_MATCH_DAYS),直接生成任务, planId={}",
daysUntilPlan, manualMatchDays, entity.getId());
cpTaskService.forceGenerateTask(entity);
}
}
}
/**
@ -333,26 +381,206 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService {
@Transactional(rollbackFor = Exception.class)
@Override
public Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
if (ids == null || ids.isEmpty()) {
return false;
}
// 查询待删除的计划记录
List<EfcodeCpPlan> plans = baseMapper.selectByIds(ids);
if (isValid) {
LocalDate today = LocalDate.now();
for (EfcodeCpPlan plan : plans) {
Long status = plan.getReserveStatus();
boolean canEdit = status != null && status <= 1L;
boolean canDelete = canEdit;
// 不联合执法且执行时间未到,也允许删除
if (!canDelete && plan.getIsUnion() != null && plan.getIsUnion() == 0L && plan.getPlanDate() != null) {
LocalDate planLocalDate = plan.getPlanDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
if (planLocalDate.isAfter(today)) {
canDelete = true;
}
}
if (!canDelete) {
throw new ServiceException("计划["
+ (plan.getEnterpriseName() != null ? plan.getEnterpriseName() : plan.getId())
+ "]当前状态不允许删除");
}
}
}
// 先级联删除计划参与人员表关联记录
if (ids != null && !ids.isEmpty()) {
efcodeCpPlanUsersMapper.delete(
Wrappers.<EfcodeCpPlanUsers>lambdaQuery().in(EfcodeCpPlanUsers::getPlanId, ids)
);
// 级联删除关联的任务记录
List<String> taskIds = plans.stream()
.map(EfcodeCpPlan::getTaskId)
.filter(StringUtils::isNotBlank)
.distinct()
.collect(Collectors.toList());
if (!taskIds.isEmpty()) {
log.info("[删除计划] 级联删除任务记录, taskIds={}", taskIds);
cpTaskService.deleteWithValidByIds(taskIds, false);
}
// 级联删除计划参与人员表关联记录
efcodeCpPlanUsersMapper.delete(
Wrappers.<EfcodeCpPlanUsers>lambdaQuery().in(EfcodeCpPlanUsers::getPlanId, ids)
);
return baseMapper.deleteByIds(ids) > 0;
}
/**
* 计划自动匹配预约(定时任务调用)
* <p>扫描待预约状态的计划,按规则自动匹配并生成预约记录</p>
*
* @param forceMatch 是否强制匹配(忽略 CP_SETTING_AUTO_MATCH_START 日期校验)
* @param updatePlan 是否更新计划记录(system_reserved、reserve_status)
* @param insertReserve 是否将预约记录插入数据库;为false时仅计算匹配结果不落库
* @return 按 planGroupId 分组的预约记录列表
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void autoMatchReserve() {
log.info("[CpPlan] 开始执行计划自动匹配预约...");
// TODO 后续实现:扫描待预约计划 → 按企业+日期+时段匹配 → 自动生成预约记录
log.info("[CpPlan] 计划自动匹配预约执行完毕");
public Map<String, List<EfcodeCpReserve>> autoMatchReserve(boolean forceMatch, boolean updatePlan, boolean insertReserve) {
log.info("[CpPlan] 开始执行计划自动匹配预约, forceMatch={}, updatePlan={}, insertReserve={}", forceMatch, updatePlan, insertReserve);
Map<String, List<EfcodeCpReserve>> resultMap = new HashMap<>();
// 1. 日期校验:如果不是强制匹配,则判断当前天数是否等于 CP_SETTING_AUTO_MATCH_START
if (!forceMatch) {
String autoMatchStartDay = getSettingValue("CP_SETTING_AUTO_MATCH_START");
int today = LocalDate.now().getDayOfMonth();
if (StringUtils.isNotBlank(autoMatchStartDay)) {
int settingDay = Integer.parseInt(autoMatchStartDay.trim());
if (today != settingDay) {
log.info("[CpPlan] 当前日期{}号,不等于设定的自动匹配日期{}号,跳过执行", today, settingDay);
return resultMap;
}
}
}
// 2. 获取最大联合单位数量
int maxUnionUnit = 3; // 默认值
String maxUnionStr = getSettingValue("CP_MAX_UNION_UNIT");
if (StringUtils.isNotBlank(maxUnionStr)) {
maxUnionUnit = Integer.parseInt(maxUnionStr.trim());
}
// 3. 查询待匹配的计划:plan_date > 当前时间 且 system_reserved=0, reserve_status=1, is_public=1, is_union=1
Date now = new Date();
LambdaQueryWrapper<EfcodeCpPlan> lqw = Wrappers.lambdaQuery();
lqw.gt(EfcodeCpPlan::getPlanDate, now);
lqw.eq(EfcodeCpPlan::getSystemReserved, 0L);
lqw.eq(EfcodeCpPlan::getReserveStatus, 1L);
lqw.eq(EfcodeCpPlan::getIsPublic, 1L);
lqw.eq(EfcodeCpPlan::getIsUnion, 1L);
lqw.eq(EfcodeCpPlan::getIsDelete, 0L);
lqw.orderByAsc(EfcodeCpPlan::getPlanDate);
List<EfcodeCpPlan> pendingPlans = baseMapper.selectList(lqw);
if (pendingPlans.isEmpty()) {
log.info("[CpPlan] 没有找到待匹配的计划记录,执行结束");
return resultMap;
}
log.info("[CpPlan] 共查询到{}条待匹配计划", pendingPlans.size());
// 4. 已处理过的企业信用代码集合(同一轮中已匹配过的不再重复处理)
Set<String> processedEnterprises = new HashSet<>();
int matchedCount = 0;
for (EfcodeCpPlan plan : pendingPlans) {
String enterpriseNumber = plan.getEnterpriseNumber();
if (StringUtils.isBlank(enterpriseNumber)) {
continue;
}
// 跳过本轮已处理的企业
if (processedEnterprises.contains(enterpriseNumber)) {
continue;
}
// 5. 查找一周内同企业、不同执法单位的其他待匹配计划
LocalDate planLocalDate = plan.getPlanDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
Date weekStart = Date.from(planLocalDate.minusDays(3).atStartOfDay(ZoneId.systemDefault()).toInstant());
Date weekEnd = Date.from(planLocalDate.plusDays(4).atStartOfDay(ZoneId.systemDefault()).toInstant());
LambdaQueryWrapper<EfcodeCpPlan> weekLqw = Wrappers.lambdaQuery();
weekLqw.ge(EfcodeCpPlan::getPlanDate, weekStart);
weekLqw.le(EfcodeCpPlan::getPlanDate, weekEnd);
weekLqw.eq(EfcodeCpPlan::getEnterpriseNumber, enterpriseNumber);
weekLqw.eq(EfcodeCpPlan::getSystemReserved, 0L);
weekLqw.eq(EfcodeCpPlan::getReserveStatus, 1L);
weekLqw.eq(EfcodeCpPlan::getIsPublic, 1L);
weekLqw.eq(EfcodeCpPlan::getIsUnion, 1L);
weekLqw.eq(EfcodeCpPlan::getIsDelete, 0L);
weekLqw.orderByAsc(EfcodeCpPlan::getPlanDate);
List<EfcodeCpPlan> samePlans = baseMapper.selectList(weekLqw);
// 6. 按不同执法单位去重,取不同 bureau_dept_id 的计划
List<EfcodeCpPlan> distinctUnitPlans = new ArrayList<>();
Set<String> seenBureauDeptIds = new HashSet<>();
for (EfcodeCpPlan sp : samePlans) {
if (!seenBureauDeptIds.contains(sp.getBureauDeptId())) {
seenBureauDeptIds.add(sp.getBureauDeptId());
distinctUnitPlans.add(sp);
}
// 限制最大联合数量
if (distinctUnitPlans.size() >= maxUnionUnit) {
break;
}
}
// 7. 如果只有一个单位的计划,说明没有可联合的,跳过
if (distinctUnitPlans.size() <= 1) {
log.debug("[CpPlan] 企业[{}]一周内只有1个单位的计划,跳过", enterpriseNumber);
continue;
}
// 8. 生成预约记录 - 使用同一个 plan_group_id
String planGroupId = "Temp"+"_"+UUID.randomUUID().toString().replace("-", "");
Date reserveTime = new Date();
List<EfcodeCpReserve> groupReserves = new ArrayList<>();
for (int i = 0; i < distinctUnitPlans.size(); i++) {
EfcodeCpPlan matchedPlan = distinctUnitPlans.get(i);
EfcodeCpReserve reserve = new EfcodeCpReserve();
reserve.setPlanId(matchedPlan.getId());
reserve.setPlanGroupId(planGroupId);
reserve.setReserveTime(reserveTime);
reserve.setOpStatus(3L); // 待确认
reserve.setOpOrder((long) (i + 1));
reserve.setOpMessage("");
reserve.setOpSetDate(matchedPlan.getPlanDate());
reserve.setOpSetTime(matchedPlan.getPlanTime());
reserve.setResFrom(1L); // 系统匹配
if (insertReserve) {
efcodeCpReserveMapper.insert(reserve);
}
groupReserves.add(reserve);
// 9. 更新计划: system_reserved=1, reserve_status=2
if (updatePlan) {
EfcodeCpPlan planUpdate = new EfcodeCpPlan();
planUpdate.setId(matchedPlan.getId());
planUpdate.setSystemReserved(1L);
planUpdate.setReserveStatus(2L);
baseMapper.updateById(planUpdate);
}
}
resultMap.put(planGroupId, groupReserves);
// 标记该企业已处理
processedEnterprises.add(enterpriseNumber);
matchedCount++;
log.info("[CpPlan] 企业[{}]匹配成功,联合{}个单位,planGroupId={}",
enterpriseNumber, distinctUnitPlans.size(), planGroupId);
}
log.info("[CpPlan] 计划自动匹配预约执行完毕,共匹配{}组联合预约", matchedCount);
return resultMap;
}
/**
* 获取 efcode_cp_setting 配置值
*
* @param settingId 设置ID
* @return 设置值
*/
private String getSettingValue(String settingId) {
EfcodeCpSetting setting = efcodeCpSettingMapper.selectById(settingId);
return setting != null ? setting.getSettingValue() : null;
}
}

50
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpReserveServiceImpl.java

@ -21,6 +21,7 @@ import com.zdxt.enforcementcode.mapper.EfcodeCpReserveMapper;
import com.zdxt.enforcementcode.mapper.EfcodeCpPlanMapper;
import com.zdxt.enforcementcode.service.IEfcodeCpReserveService;
import com.zdxt.enforcementcode.service.IEfcodeCpSettingService;
import com.zdxt.enforcementcode.service.IEfcodeCpTaskService;
import java.util.*;
import java.util.stream.Collectors;
@ -39,6 +40,7 @@ public class EfcodeCpReserveServiceImpl implements IEfcodeCpReserveService {
private final EfcodeCpReserveMapper baseMapper;
private final EfcodeCpPlanMapper cpPlanMapper;
private final IEfcodeCpSettingService cpSettingService;
private final IEfcodeCpTaskService cpTaskService;
/**
* 查询行政执法检查计划-预约
@ -242,6 +244,28 @@ public class EfcodeCpReserveServiceImpl implements IEfcodeCpReserveService {
// 插入1条记录
insertReserveRecord(myPlan.getId(), planGroupId, opMessage, targetPlan.getPlanDate(), targetPlan.getPlanTime(), 0L);
}
// ===== 插入预约记录后:更新两个计划的 reserve_status =====
// 1. 我的计划 → 2=预约中
EfcodeCpPlan myPlanUpdate = new EfcodeCpPlan();
myPlanUpdate.setId(myPlan.getId());
myPlanUpdate.setReserveStatus(2L);
cpPlanMapper.updateById(myPlanUpdate);
// 2. 对方计划 → 根据当前活跃预约数决定状态
// 活跃数 < maxUnionUnit → 5=待预约(还有位置可以加入)
// 活跃数 = maxUnionUnit → 6=待预约(人数已满)
long currentActiveCount = countActiveReserves(planGroupId);
long newTargetStatus = (currentActiveCount >= maxUnionUnit) ? 6L : 5L;
EfcodeCpPlan targetPlanUpdate = new EfcodeCpPlan();
targetPlanUpdate.setId(targetPlanId);
targetPlanUpdate.setReserveStatus(newTargetStatus);
cpPlanMapper.updateById(targetPlanUpdate);
log.info("[doReserve] 我的计划[{}]→reserve_status=2, 对方计划[{}]→reserve_status={}, 活跃数={}, 最大联合数={}",
myPlan.getId(), targetPlanId, newTargetStatus, currentActiveCount, maxUnionUnit);
return R.ok();
}
@ -356,7 +380,31 @@ public class EfcodeCpReserveServiceImpl implements IEfcodeCpReserveService {
* @param action 操作类型: "approve" 或 "reject"
*/
private void afterOperate(EfcodeCpReserve reserve, String action) {
// TODO: 可在此处扩展后置逻辑,如通知、状态同步等
log.info("预约记录[{}]执行{}操作后置处理, planGroupId={}", reserve.getId(), action, reserve.getPlanGroupId());
// 同意操作后:检查同一 planGroupId 已同意数量是否达到最大联合单位数
if ("approve".equals(action) && StringUtils.isNotBlank(reserve.getPlanGroupId())) {
int maxUnionUnit = cpSettingService.getMaxUnionUnit();
long approvedCount = baseMapper.selectCount(
Wrappers.<EfcodeCpReserve>lambdaQuery()
.eq(EfcodeCpReserve::getPlanGroupId, reserve.getPlanGroupId())
.eq(EfcodeCpReserve::getOpStatus, 1L) // 1=已同意
);
log.info("[预约后置] planGroupId={}, 已同意数量={}, 最大联合数={}", reserve.getPlanGroupId(), approvedCount, maxUnionUnit);
if (approvedCount >= maxUnionUnit) {
// 达标:为该分组所有计划生成任务
List<EfcodeCpReserve> groupReserves = baseMapper.selectList(
Wrappers.<EfcodeCpReserve>lambdaQuery()
.eq(EfcodeCpReserve::getPlanGroupId, reserve.getPlanGroupId())
.eq(EfcodeCpReserve::getOpStatus, 1L)
);
for (EfcodeCpReserve r : groupReserves) {
EfcodeCpPlan plan = cpPlanMapper.selectById(r.getPlanId());
if (plan != null && StringUtils.isBlank(plan.getTaskId())) {
cpTaskService.forceGenerateTask(plan);
}
}
log.info("[预约后置] planGroupId={} 联合数量达标,已为{}条计划生成任务", reserve.getPlanGroupId(), groupReserves.size());
}
}
}
}

65
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpTaskServiceImpl.java

@ -13,9 +13,12 @@ import org.springframework.stereotype.Service;
import com.zdxt.enforcementcode.domain.bo.EfcodeCpTaskBo;
import com.zdxt.enforcementcode.domain.vo.EfcodeCpTaskVo;
import com.zdxt.enforcementcode.domain.EfcodeCpTask;
import com.zdxt.enforcementcode.domain.EfcodeCpPlan;
import com.zdxt.enforcementcode.mapper.EfcodeCpTaskMapper;
import com.zdxt.enforcementcode.mapper.EfcodeCpPlanMapper;
import com.zdxt.enforcementcode.service.IEfcodeCpTaskService;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Collection;
@ -32,6 +35,7 @@ import java.util.Collection;
public class EfcodeCpTaskServiceImpl implements IEfcodeCpTaskService {
private final EfcodeCpTaskMapper baseMapper;
private final EfcodeCpPlanMapper cpPlanMapper;
/**
* 查询行政执法检查计划-任务
@ -116,6 +120,67 @@ public class EfcodeCpTaskServiceImpl implements IEfcodeCpTaskService {
//TODO 做一些数据校验,如唯一约束
}
/**
* 验证是否符合生成任务的条件
* <p>校验预约分组内所有记录是否满足生成执法任务的前置条件</p>
*
* @param planGroupId 计划分组ID
* @return true=符合生成条件, false=不符合
*/
public boolean checkCanGenerateTask(String planGroupId) {
// TODO 后续实现:校验分组内预约状态、时间窗口、人员配置等前置条件
return true;
}
/**
* 查询我的任务列表
*/
@Override
public List<EfcodeCpTaskVo> queryMyTaskList(Long userId, Date planDate) {
return baseMapper.selectMyTaskList(userId, planDate);
}
/**
* 查询指定月份中有任务的日期列表
*/
@Override
public List<String> queryMyTaskDates(Long userId, String yearMonth) {
return baseMapper.selectMyTaskDates(userId, yearMonth);
}
/**
* 查询我的任务详情
*/
@Override
public EfcodeCpTaskVo queryMyTaskDetail(String planId) {
return baseMapper.selectMyTaskDetail(planId);
}
/**
* 强制创建任务(忽略其他条件),根据计划信息直接生成任务记录并回写 plan.task_id
*
* @param plan 来源计划
* @return 新建任务ID
*/
@Override
public String forceGenerateTask(EfcodeCpPlan plan) {
// 1. 创建任务记录
EfcodeCpTask task = new EfcodeCpTask();
task.setTaskName(plan.getEnterpriseName() + " - 执法检查任务");
task.setBureauDeptId(plan.getBureauDeptId());
baseMapper.insert(task);
log.info("[强制创建任务] 新建任务 taskId={}, planId={}, 企业={}", task.getId(), plan.getId(), plan.getEnterpriseName());
// 2. 回写计划的 task_id,同时将 reserve_status 设为 3(预约完成/形成任务)
EfcodeCpPlan planUpdate = new EfcodeCpPlan();
planUpdate.setId(plan.getId());
planUpdate.setTaskId(task.getId());
planUpdate.setReserveStatus(3L);
cpPlanMapper.updateById(planUpdate);
return task.getId();
}
/**
* 校验并批量删除行政执法检查计划-任务信息
*

138
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EnforcementEducationServicesServiceImpl.java

@ -3,7 +3,11 @@ package com.zdxt.enforcementcode.service.impl;
import com.zdxt.enforcementcode.common.commonenum.MsgSourceTypeEnum;
import com.zdxt.enforcementcode.common.commonenum.PlatformTypeEnums;
import com.zdxt.enforcementcode.domain.bean.EnforcementEducationServices;
import com.zdxt.enforcementcode.domain.vo.ZdxtFileVo;
import com.zdxt.enforcementcode.service.ISysMessageViewLogService;
import com.zdxt.enforcementcode.domain.bo.ZdxtFileBo;
import com.zdxt.enforcementcode.service.IZdxtFileService;
import org.dromara.common.core.service.DictService;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
@ -43,6 +47,34 @@ public class EnforcementEducationServicesServiceImpl implements IEnforcementEduc
private final ISysMessageViewLogService sysMessageViewLogService;
private final IZdxtFileService zdxtFileService;
private final DictService dictService;
/**
* 查询执法信息发布主
*
* @param id 主键
* @return 执法信息发布主
*/
@Override
public EnforcementEducationServicesVo queryEducationServicesById(String id){
EnforcementEducationServicesVo vo = baseMapper.selectVoById(id);
if (vo != null) {
// 回显附件信息
ZdxtFileBo fileBo = new ZdxtFileBo();
fileBo.setBusinessId(id);
List<ZdxtFileVo> fileList = zdxtFileService.queryList(fileBo);
vo.setFileList(fileList);
// 设置字典标签
setDictLabels(vo);
}
return vo;
}
/**
* 查询执法信息发布主
*
@ -73,8 +105,10 @@ public class EnforcementEducationServicesServiceImpl implements IEnforcementEduc
LambdaQueryWrapper<EnforcementEducationServices> lqw = buildQueryWrapper(bo);
Page<EnforcementEducationServicesVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
// 为每条记录设置是否已读状态
// 为每条记录设置字典标签和已读状态
if (result.getRecords() != null && !result.getRecords().isEmpty()) {
setDictLabels(result.getRecords());
setReadStatus(result.getRecords());
}
@ -90,7 +124,50 @@ public class EnforcementEducationServicesServiceImpl implements IEnforcementEduc
@Override
public List<EnforcementEducationServicesVo> queryList(EnforcementEducationServicesBo bo) {
LambdaQueryWrapper<EnforcementEducationServices> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
List<EnforcementEducationServicesVo> list = baseMapper.selectVoList(lqw);
// 设置字典标签
if (list != null && !list.isEmpty()) {
setDictLabels(list);
}
return list;
}
/**
* 为单条记录设置字典标签
*
* @param vo 记录对象
*/
private void setDictLabels(EnforcementEducationServicesVo vo) {
if (vo == null) {
return;
}
// 内容分类标签
if (vo.getContentType() != null) {
String contentTypeLabel = dictService.getDictLabel("web_content_classification", vo.getContentType().toString());
vo.setContentTypeLabel(contentTypeLabel);
}
// 发布平台标签
if (vo.getTargetPlatform() != null) {
String targetPlatformLabel = dictService.getDictLabel("web_publishing_platform", vo.getTargetPlatform().toString());
vo.setTargetPlatformLabel(targetPlatformLabel);
}
}
/**
* 为列表设置字典标签
*
* @param records 记录列表
*/
private void setDictLabels(List<EnforcementEducationServicesVo> records) {
if (records == null || records.isEmpty()) {
return;
}
records.forEach(this::setDictLabels);
}
/**
@ -162,7 +239,7 @@ public class EnforcementEducationServicesServiceImpl implements IEnforcementEduc
lqw.orderByAsc(EnforcementEducationServices::getId);
lqw.eq(StringUtils.isNotBlank(bo.getTitle()), EnforcementEducationServices::getTitle, bo.getTitle());
lqw.eq(StringUtils.isNotBlank(bo.getPublishDeptIds()), EnforcementEducationServices::getPublishDeptIds, bo.getPublishDeptIds());
lqw.eq(StringUtils.isNotBlank(bo.getPublishDeptNames()), EnforcementEducationServices::getPublishDeptNames, bo.getPublishDeptNames());
lqw.like(StringUtils.isNotBlank(bo.getPublishDeptNames()), EnforcementEducationServices::getPublishDeptNames, bo.getPublishDeptNames());
lqw.eq(bo.getTargetPlatform() != null, EnforcementEducationServices::getTargetPlatform, bo.getTargetPlatform());
lqw.eq(bo.getContentType() != null, EnforcementEducationServices::getContentType, bo.getContentType());
lqw.eq(bo.getSourceType() != null, EnforcementEducationServices::getSourceType, bo.getSourceType());
@ -175,6 +252,16 @@ public class EnforcementEducationServicesServiceImpl implements IEnforcementEduc
lqw.like(StringUtils.isNotBlank(bo.getCreateByName()), EnforcementEducationServices::getCreateByName, bo.getCreateByName());
lqw.like(StringUtils.isNotBlank(bo.getUpdateByName()), EnforcementEducationServices::getUpdateByName, bo.getUpdateByName());
lqw.eq(bo.getIsDelete() != null, EnforcementEducationServices::getIsDelete, bo.getIsDelete());
// 创建时间范围筛选
if (bo.getStartTime() != null && bo.getEndTime() != null) {
lqw.between(EnforcementEducationServices::getCreateTime, bo.getStartTime(), bo.getEndTime());
} else if (bo.getStartTime() != null) {
lqw.ge(EnforcementEducationServices::getCreateTime, bo.getStartTime());
} else if (bo.getEndTime() != null) {
lqw.le(EnforcementEducationServices::getCreateTime, bo.getEndTime());
}
return lqw;
}
@ -195,6 +282,51 @@ public class EnforcementEducationServicesServiceImpl implements IEnforcementEduc
return flag;
}
/**
* 监督端-新增执法信息发布主
*
* @param bo 执法信息发布主
* @return 是否新增成功
*/
@Override
public Boolean insertEducationServices(EnforcementEducationServicesBo bo) {
EnforcementEducationServices add = MapstructUtils.convert(bo, EnforcementEducationServices.class);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
// 保存附件信息
if (bo.getFileList() != null && !bo.getFileList().isEmpty()) {
saveAttachmentFiles(bo.getFileList(), add.getId());
}
}
return flag;
}
/**
* 批量保存附件信息
*
* @param fileList 附件列表
* @param businessId 业务ID(普法服务主表ID)
*/
private void saveAttachmentFiles(List<ZdxtFileBo> fileList, String businessId) {
for (ZdxtFileBo fileBo : fileList) {
// 设置业务ID关联
fileBo.setBusinessId(businessId);
// 保存附件
try {
zdxtFileService.insertByBo(fileBo);
log.info("成功保存附件: businessId={}, fileName={}", businessId, fileBo.getFileName());
} catch (Exception e) {
log.error("保存附件失败: businessId={}, fileName={}, error={}",
businessId, fileBo.getFileName(), e.getMessage(), e);
}
}
}
/**
* 修改执法信息发布主
*

255
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/NonEnforcementRegistrationServiceImpl.java

@ -3,8 +3,10 @@ package com.zdxt.enforcementcode.service.impl;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zdxt.enforcementcode.common.annotation.DataScope;
import com.zdxt.enforcementcode.common.util.CommonUtil;
import com.zdxt.enforcementcode.domain.NonEnforcementRegistration;
import com.zdxt.enforcementcode.domain.ZdxtFile;
import com.zdxt.enforcementcode.domain.bo.NonEnforcementRegistrationBo;
import com.zdxt.enforcementcode.domain.vo.NonEnforcementRegistrationVo;
@ -19,6 +21,8 @@ import org.dromara.common.core.enums.FormatsType;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.vo.SysDictDataVo;
import org.dromara.system.domain.vo.SysUserVo;
@ -57,27 +61,35 @@ public class NonEnforcementRegistrationServiceImpl implements INonEnforcementReg
@Override
public NonEnforcementRegistrationVo selectNonEnforcementRegistrationById(String id)
{
NonEnforcementRegistrationVo nonReg = nonEnforcementRegistrationMapper.selectNonEnforcementRegistrationById(id);
if (nonReg.getId() != null) {
SysUserVo user = sysUserMapper.selectUserById(Long.valueOf(nonReg.getLawEnforcerId()));
String temp=user.getLawCertificateNum();
if(StringUtils.isEmpty(temp)){
temp= CommonUtil.encryptPhone(user.getPhonenumber());
NonEnforcementRegistrationVo nonReg = nonEnforcementRegistrationMapper.selectVoById(id);
if (nonReg != null && nonReg.getId() != null) {
// 处理主执法人员信息
if (StringUtils.isNotEmpty(nonReg.getLawEnforcerId())) {
SysUserVo user = sysUserMapper.selectUserById(Long.valueOf(nonReg.getLawEnforcerId()));
if (user != null) {
String temp = user.getLawCertificateNum();
if (StringUtils.isEmpty(temp)) {
temp = CommonUtil.encryptPhone(user.getPhonenumber());
}
nonReg.setLawEnforcer(user.getUserName() + " " + temp);
}
}
nonReg.setLawEnforcer(user.getUserName()+" "+temp);
if(StringUtils.isNotEmpty(nonReg.getLawEnforcerOther())){
List<SysUserVo> list = sysUserMapper.selectUserByIds(Convert.toStrArray(nonReg.getLawEnforcerIdOther()));
if(list.size()!=0){
if(list != null && list.size() != 0){
String tmpUserShow = "";
for (SysUserVo ub : list) {
String t = ub.getLawCertificateNum();
if(StringUtils.isEmpty(t)){
t=CommonUtil.encryptPhone(ub.getPhonenumber());
if (ub != null) {
String t = ub.getLawCertificateNum();
if(StringUtils.isEmpty(t)){
t = CommonUtil.encryptPhone(ub.getPhonenumber());
}
tmpUserShow += ub.getUserName() + " " + t + ",";
}
tmpUserShow += ub.getUserName() +" "+ t + ",";
}
if (tmpUserShow.length() > 0) {
tmpUserShow = tmpUserShow.substring(0, tmpUserShow.length()-1);
tmpUserShow = tmpUserShow.substring(0, tmpUserShow.length() - 1);
}
nonReg.setLawEnforcerOther(tmpUserShow);
}
@ -85,7 +97,7 @@ public class NonEnforcementRegistrationServiceImpl implements INonEnforcementReg
LambdaQueryWrapper<ZdxtFile> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ZdxtFile::getBusinessId, nonReg.getId());
List<ZdxtFile> zdxtFileCommons=zdxtFileMapper.selectList(queryWrapper);
List<ZdxtFile> zdxtFileCommons = zdxtFileMapper.selectList(queryWrapper);
if (zdxtFileCommons.size() > 0) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("fileList", zdxtFileCommons);
@ -95,6 +107,183 @@ public class NonEnforcementRegistrationServiceImpl implements INonEnforcementReg
return nonReg;
}
/**
* 分页查询非行政执法登记列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 非行政执法登记分页列表
*/
@Override
public TableDataInfo<NonEnforcementRegistrationVo> queryPageList(NonEnforcementRegistrationBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<NonEnforcementRegistration> lqw = buildQueryWrapper(bo);
Page<NonEnforcementRegistrationVo> result = nonEnforcementRegistrationMapper.selectVoPage(pageQuery.build(), lqw);
// 处理执法人员信息等附加数据
if (result.getRecords() != null && !result.getRecords().isEmpty()) {
enrichLawEnforcerInfo(result.getRecords());
}
return TableDataInfo.build(result);
}
/**
* 构建查询条件
*
* @param bo 查询条件
* @return LambdaQueryWrapper
*/
private LambdaQueryWrapper<NonEnforcementRegistration> buildQueryWrapper(NonEnforcementRegistrationBo bo) {
LambdaQueryWrapper<NonEnforcementRegistration> lqw = new LambdaQueryWrapper<>();
// 根据XML文件中的查询条件添加查询条件
if (bo != null) {
// 企业名称 - 模糊查询
lqw.like(StringUtils.isNotBlank(bo.getEnterpriseName()), NonEnforcementRegistration::getEnterpriseName, bo.getEnterpriseName());
// 企业编号 - 精确查询
lqw.eq(StringUtils.isNotBlank(bo.getEnterpriseNumber()), NonEnforcementRegistration::getEnterpriseNumber, bo.getEnterpriseNumber());
// 执法时间 - 精确查询
lqw.eq(bo.getEnforcementTime() != null, NonEnforcementRegistration::getEnforcementTime, bo.getEnforcementTime());
// 执法人员姓名 - 精确查询
lqw.eq(StringUtils.isNotBlank(bo.getLawEnforcer()), NonEnforcementRegistration::getLawEnforcer, bo.getLawEnforcer());
// 执法人员ID - 精确查询
lqw.eq(StringUtils.isNotBlank(bo.getLawEnforcerId()), NonEnforcementRegistration::getLawEnforcerId, bo.getLawEnforcerId());
// 其他执法人员姓名 - 精确查询
lqw.eq(StringUtils.isNotBlank(bo.getLawEnforcerOther()), NonEnforcementRegistration::getLawEnforcerOther, bo.getLawEnforcerOther());
// 其他执法人员ID - 精确查询
lqw.eq(StringUtils.isNotBlank(bo.getLawEnforcerIdOther()), NonEnforcementRegistration::getLawEnforcerIdOther, bo.getLawEnforcerIdOther());
// 执法类别 - 精确查询
lqw.eq(bo.getLawEnforcementType() != null, NonEnforcementRegistration::getLawEnforcementType, bo.getLawEnforcementType());
// 执法类别文本 - 精确查询
lqw.eq(StringUtils.isNotBlank(bo.getLawEnforcementTypeText()), NonEnforcementRegistration::getLawEnforcementTypeText, bo.getLawEnforcementTypeText());
// 执法事项 - 精确查询
lqw.eq(bo.getLawEnforcementItem() != null, NonEnforcementRegistration::getLawEnforcementItem, bo.getLawEnforcementItem());
// 执法事项文本 - 精确查询
lqw.eq(StringUtils.isNotBlank(bo.getLawEnforcementItemText()), NonEnforcementRegistration::getLawEnforcementItemText, bo.getLawEnforcementItemText());
// 执法依据 - 精确查询
lqw.eq(StringUtils.isNotBlank(bo.getLawEnforcementBasis()), NonEnforcementRegistration::getLawEnforcementBasis, bo.getLawEnforcementBasis());
// 执法依据文本 - 精确查询
lqw.eq(StringUtils.isNotBlank(bo.getLawEnforcementBasisText()), NonEnforcementRegistration::getLawEnforcementBasisText, bo.getLawEnforcementBasisText());
// 执法结果 - 精确查询 (Long类型)
lqw.eq(bo.getReslut() != null, NonEnforcementRegistration::getReslut, bo.getReslut());
// 状态 - 精确查询 (Long类型)
lqw.eq(bo.getStatus() != null, NonEnforcementRegistration::getStatus, bo.getStatus());
// 是否删除 - 精确查询 (Long类型)
lqw.eq(bo.getHasDelete() != null, NonEnforcementRegistration::getHasDelete, bo.getHasDelete());
// 是否投诉 - 精确查询 (Long类型)
lqw.eq(bo.getHasComplaint() != null, NonEnforcementRegistration::getHasComplaint, bo.getHasComplaint());
// 是否提交 - 精确查询 (Long类型)
lqw.eq(bo.getHasSubmit() != null, NonEnforcementRegistration::getHasSubmit, bo.getHasSubmit());
// 是否评价 - 精确查询 (Long类型)
lqw.eq(bo.getHasEvaluate() != null, NonEnforcementRegistration::getHasEvaluate, bo.getHasEvaluate());
// 预告ID - 精确查询
lqw.eq(StringUtils.isNotBlank(bo.getLawId()), NonEnforcementRegistration::getLawId, bo.getLawId());
// 预告文本 - 精确查询
lqw.eq(StringUtils.isNotBlank(bo.getLawText()), NonEnforcementRegistration::getLawText, bo.getLawText());
// 执法主体部门ID - 精确查询
lqw.eq(StringUtils.isNotBlank(bo.getBureauDeptId()), NonEnforcementRegistration::getBureauDeptId, bo.getBureauDeptId());
// 执法主体部门名称 - 模糊查询
lqw.like(StringUtils.isNotBlank(bo.getBureauDeptName()), NonEnforcementRegistration::getBureauDeptName, bo.getBureauDeptName());
// 统一信用代码 - 精确查询
lqw.eq(StringUtils.isNotBlank(bo.getCreditcode()), NonEnforcementRegistration::getCreditcode, bo.getCreditcode());
// 用户ID - 精确查询
lqw.eq(StringUtils.isNotBlank(bo.getUserId()), NonEnforcementRegistration::getUserId, bo.getUserId());
// 部门ID - 精确查询
lqw.eq(StringUtils.isNotBlank(bo.getDeptId()), NonEnforcementRegistration::getDeptId, bo.getDeptId());
// searchValue - 多字段模糊查询
if (StringUtils.isNotBlank(bo.getSearchValue())) {
lqw.and(wrapper -> wrapper
.like(NonEnforcementRegistration::getLawEnforcementItemText, bo.getSearchValue())
.or().like(NonEnforcementRegistration::getBureauDeptName, bo.getSearchValue())
.or().like(NonEnforcementRegistration::getLawEnforcer, bo.getSearchValue())
.or().like(NonEnforcementRegistration::getLawEnforcerOther, bo.getSearchValue())
);
}
// dateType - 时间范围查询
if (bo.getDateType() != null) {
if (bo.getDateType() == 1) {
// 本月
lqw.apply("DATE_FORMAT(enforcement_time, '%Y-%m') = DATE_FORMAT(CURDATE(), '%Y-%m')");
} else if (bo.getDateType() == 2) {
// 本季度
lqw.apply("DATE_FORMAT(enforcement_time, '%Y-%m') >= CONCAT(YEAR(CURDATE()), '-', " +
"CASE WHEN QUARTER(CURDATE()) IN (1) THEN '01' " +
"WHEN QUARTER(CURDATE()) IN (2) THEN '04' " +
"WHEN QUARTER(CURDATE()) IN (3) THEN '07' " +
"ELSE '10' END) " +
"and DATE_FORMAT(enforcement_time, '%Y-%m') <= CONCAT(YEAR(CURDATE()), '-', " +
"CASE " +
"WHEN QUARTER(CURDATE()) IN (1) THEN '03' " +
"WHEN QUARTER(CURDATE()) IN (2) THEN '06' " +
"WHEN QUARTER(CURDATE()) IN (3) THEN '09' " +
"ELSE '12' " +
"END)");
} else if (bo.getDateType() == 3) {
// 本年
lqw.apply("DATE_FORMAT(enforcement_time, '%Y') = DATE_FORMAT(CURDATE(), '%Y')");
}
}
// dateSearch - 时间范围查询
if (StringUtils.isNotBlank(bo.getDateSearch())) {
lqw.apply("DATE_FORMAT(enforcement_time, '%Y-%m') = {0}", bo.getDateSearch());
}
// 按执法时间倒序排序
lqw.orderByDesc(NonEnforcementRegistration::getEnforcementTime);
}
return lqw;
}
/**
* 补充执法人员信息
*
* @param list 记录列表
*/
private void enrichLawEnforcerInfo(List<NonEnforcementRegistrationVo> list) {
if (list == null || list.isEmpty()) {
return;
}
// 执法人员 没有执法证件号展示加密手机号
list.forEach(li -> {
// 处理主执法人员信息
if (StringUtils.isNotEmpty(li.getLawEnforcerId())) {
SysUserVo sysUserVo = sysUserMapper.selectUserById(Long.valueOf(li.getLawEnforcerId()));
if (sysUserVo != null) {
String lawCN = sysUserVo.getLawCertificateNum();
if (StringUtils.isEmpty(lawCN)) {
lawCN = CommonUtil.encryptPhone(sysUserVo.getPhonenumber());
}
li.setLawEnforcer(sysUserVo.getUserName() + " " + lawCN);
} else {
li.setLawEnforcer(li.getLawEnforcer());
}
}
// 处理其他执法人员信息
List<String> lawTwoList = new ArrayList<>();
if (StringUtils.isNotEmpty(li.getLawEnforcerOther()) && StringUtils.isNotEmpty(li.getLawEnforcerIdOther())) {
List<SysUserVo> userBeans = sysUserMapper.selectUserByIds(Convert.toStrArray(li.getLawEnforcerIdOther()));
if (userBeans != null && !userBeans.isEmpty()) {
userBeans.forEach(u -> {
if (u != null) {
String temp = u.getLawCertificateNum();
if (StringUtils.isEmpty(temp)) {
temp = CommonUtil.encryptPhone(u.getPhonenumber());
}
lawTwoList.add(u.getUserName() + " " + temp);
}
});
li.setLawEnforcerOtherList(lawTwoList);
}
}
});
}
/**
* 查询非行政执法登记列表
*
@ -104,41 +293,11 @@ public class NonEnforcementRegistrationServiceImpl implements INonEnforcementReg
@Override
public List<NonEnforcementRegistrationVo> selectNonEnforcementRegistrationList(NonEnforcementRegistrationBo bo)
{
List<NonEnforcementRegistrationVo> list=nonEnforcementRegistrationMapper.selectNonEnforcementRegistrationList(bo);
if(list.size()!=0){
// 执法人员 没有执法证件号展示加密手机号
list.forEach(li->{
if (StringUtils.isNotEmpty(li.getLawEnforcerId())) {
SysUserVo sysUserVo = sysUserMapper.selectUserById(Long.valueOf(li.getLawEnforcerId()));
if (sysUserVo != null) {
String lawCN = sysUserVo.getLawCertificateNum();
if(StringUtils.isEmpty(lawCN)){
lawCN = CommonUtil.encryptPhone(sysUserVo.getPhonenumber());
}
li.setLawEnforcer(sysUserVo.getUserName() + " " + lawCN);
} else {
li.setLawEnforcer(li.getLawEnforcer());
}
}
List<NonEnforcementRegistrationVo> list = nonEnforcementRegistrationMapper.selectNonEnforcementRegistrationList(bo);
// 使用公共方法处理执法人员信息
enrichLawEnforcerInfo(list);
List<String> lawTwoList = new ArrayList<>();
if (StringUtils.isNotEmpty(li.getLawEnforcerOther()) && StringUtils.isNotEmpty(li.getLawEnforcerIdOther())) {
List<SysUserVo> userBeans = sysUserMapper.selectUserByIds(Convert.toStrArray(li.getLawEnforcerIdOther()));
if (userBeans != null && !userBeans.isEmpty()) {
userBeans.forEach(u->{
if (u != null) {
String temp = u.getLawCertificateNum();
if(StringUtils.isEmpty(temp)){
temp = CommonUtil.encryptPhone(u.getPhonenumber());
}
lawTwoList.add(u.getUserName() + " " + temp);
}
});
li.setLawEnforcerOtherList(lawTwoList);
}
}
});
}
return list;
}

81
zdxt-modules/zdxt-enforcement-code/src/main/resources/mapper/enforcementcode/EfcodeCpTaskMapper.xml

@ -3,4 +3,85 @@
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zdxt.enforcementcode.mapper.EfcodeCpTaskMapper">
<!-- 我的任务列表:通过 plan.task_id 关联任务表,筛选当前用户参与的任务 -->
<select id="selectMyTaskList" resultType="com.zdxt.enforcementcode.domain.vo.EfcodeCpTaskVo">
SELECT
t.id,
t.task_name,
t.bureau_dept_id,
p.id AS plan_id,
p.plan_date,
p.plan_time,
p.plan_actual_date,
p.plan_actual_time,
p.enforce_content,
p.enterprise_name,
p.is_union,
p.bureau_dept_name
FROM efcode_cp_task t
INNER JOIN efcode_cp_plan p ON p.task_id = t.id
<where>
p.is_delete = 0
AND p.reserve_status IN (1, 4, 5)
AND (
p.create_by = #{userId}
OR EXISTS (
SELECT 1 FROM efcode_cp_plan_users pu
WHERE pu.plan_id = p.id AND pu.user_id = #{userId}
)
)
<if test="planDate != null">
AND p.plan_date = #{planDate}
</if>
</where>
ORDER BY p.plan_time ASC, p.plan_date ASC
</select>
<!-- 查询指定月份中有任务的日期列表(用于日历打点) -->
<select id="selectMyTaskDates" resultType="java.lang.String">
SELECT DISTINCT DATE_FORMAT(p.plan_date, '%Y-%m-%d') AS task_date
FROM efcode_cp_task t
INNER JOIN efcode_cp_plan p ON p.task_id = t.id
<where>
p.is_delete = 0
AND (
p.create_by = #{userId}
OR EXISTS (
SELECT 1 FROM efcode_cp_plan_users pu
WHERE pu.plan_id = p.id AND pu.user_id = #{userId}
)
)
AND DATE_FORMAT(p.plan_date, '%Y-%m') = #{yearMonth}
</where>
ORDER BY task_date ASC
</select>
<!-- 我的任务详情:通过 planId 查询任务+计划完整信息 -->
<select id="selectMyTaskDetail" resultType="com.zdxt.enforcementcode.domain.vo.EfcodeCpTaskVo">
SELECT
t.id,
t.task_name,
t.bureau_dept_id,
p.id AS plan_id,
p.plan_type,
p.plan_date,
p.plan_time,
p.plan_actual_date,
p.plan_actual_time,
p.enforce_content,
p.enterprise_name,
p.enterprise_number,
p.enforce_user_names,
p.is_union,
p.union_bureau_dept_names,
p.bureau_dept_name,
p.reserve_status,
p.remark
FROM efcode_cp_plan p
INNER JOIN efcode_cp_task t ON t.id = p.task_id
WHERE p.id = #{planId}
AND p.is_delete = 0
</select>
</mapper>

2
zdxt-web-client/zdxt-efcode-enforcement-app/src/api/enforcement/index.js

@ -5,7 +5,7 @@ const baseUrl = "/app-enf";
// region 认证模块
export const enforcementLogin = (params, success, error) => _post(baseUrl + "/login/szyptLogin", params, success, error);
// export const enforcementLogin = (params, success, error) => _post(baseUrl + "/login/do", params, success, error);
export const enforcementLoginByPassword = (params, success, error) => _post(baseUrl + "/login/do", params, success, error);
// endregion
// region 企业模块

7
zdxt-web-client/zdxt-efcode-enforcement-app/src/api/enforcement/plan.js

@ -32,7 +32,7 @@ export const doReservePlan = (params, success, error) => _post(planBaseUrl + "/r
// region 计划-设置接口
const settingBaseUrl = "/api/app-enf/cpSetting";
const settingBaseUrl = "/app-enf/cpSetting";
// 下阶段计划提交最晚时间(每月几号前需提交下阶段计划<=)
export const getSubmitDeadline = (success, error) => _get(settingBaseUrl + "/submitDeadline", {}, success, error);
@ -40,11 +40,14 @@ export const getSubmitDeadline = (success, error) => _get(settingBaseUrl + "/sub
// 单位内部单次计划最多参与人数(>=3)
export const getMaxInnerUnitAcPersonNum = (success, error) => _get(settingBaseUrl + "/maxInnerUnitAcPersonNum", {}, success, error);
// 手工匹配距计划开始天数
export const getManualMatchDays = (success, error) => _get(settingBaseUrl + "/manualMatchDays", {}, success, error);
// endregion
// region 企业检索接口
const enterpriseSearchBaseUrl = "/api/enforcementcode/enterpriseSearch";
const enterpriseSearchBaseUrl = "/enforcementcode/enterpriseSearch";
// 按企业名称联合检索(>=3字符)
export const searchUnionByEnterpriseName = (enterpriseName, success, error) =>

26
zdxt-web-client/zdxt-efcode-enforcement-app/src/router/enforcement-app/enf-main-router.js

@ -9,6 +9,14 @@ const routes = [
isKeepAlive: false,
},
},
{
path: "/enforcement/loginByPassword",
name: "enforcementLoginByPassword",
component: () => import("@/views/enforcement/loginByPassword.vue"),
meta: {
isKeepAlive: false,
},
},
// 执法端首页
{
path: "/enforcement/enforcementHome",
@ -99,6 +107,24 @@ const routes = [
isKeepAlive: false,
},
},
// 执法端-我的任务列表
{
path: "/enforcement/task",
name: "cpTaskMine",
component: () => import("@/views/enforcement/my/task/cp_task_mine.vue"),
meta: {
isKeepAlive: false,
},
},
// 执法端-任务详情
{
path: "/enforcement/task/detail",
name: "cpTaskDetail",
component: () => import("@/views/enforcement/my/task/cp_task_detail.vue"),
meta: {
isKeepAlive: false,
},
},
// 执法端-预约处理列表
{
path: "/enforcement/reserve",

163
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/loginByPassword.vue

@ -0,0 +1,163 @@
<template>
<div class="login-page">
<div class="login-content">
<van-nav-bar>
<template #title>
<div class="title">
执法端登录
</div>
</template>
</van-nav-bar>
<van-form @submit="onSubmit">
<van-field v-model="loginParam.secretName" name="secretName" label="用户名" placeholder="用户名"
:rules="[{ required: true, message: '请填写用户名' }]"/>
<van-field v-model="loginParam.secretKey" type="password" name="secretKey" label="密码"
placeholder="密码"
:rules="[{ required: true, message: '请填写密码' }]"/>
<div style="margin: 0.42rem">
<van-button block type="primary" native-type="submit">登录</van-button>
</div>
</van-form>
<div style="margin: 0.42rem 0.42rem 0; text-align: center;">
<van-button plain type="default" size="small" @click="showTestPicker = true"
style="background-color: darkred;color: white">选择测试用户
</van-button>
</div>
<van-action-sheet v-model:show="showTestPicker" title="选择测试用户">
<div class="test-enterprise-list">
<van-cell
v-for="(item, index) in testUsers"
:key="index"
:title="item.name"
:label="'账号:' + item.account"
is-link
@click="selectTestUser(item)"
/>
</div>
</van-action-sheet>
</div>
</div>
</template>
<script setup>
import {reactive, ref} from 'vue';
import {enforcementLogin, enforcementLoginByPassword} from '../../api/enforcement';
import CryptoJS from "crypto-js";
import {showFailToast} from 'vant';
import 'vant/es/toast/style';
const {useCommon} = $globalStore;
const router = useRouter();
const loginParam = reactive({
secretName: "",
secretKey: ""
})
const showTestPicker = ref(false);
const testUsers = [
{account: 'liuqianlin', name: '刘倩琳', password: 'szsf@zfjd'},
{account: 'baining', name: '白宁', password: 'szsf@zfjd'},
{account: 'caizifan', name: '蔡梓帆', password: 'szsf@zfjd'},
{account: 'chenchong4', name: '陈冲', password: 'szsf@zfjd'},
{account: 'chenggaozhan', name: '程高展', password: 'szsf@zfjd'},
{account: 'chenhaitao2', name: '陈海涛', password: 'szsf@zfjd'},
{account: 'chenhanyu', name: '陈汉羽', password: 'szsf@zfjd'}
];
const selectTestUser = (item) => {
loginParam.secretName = item.account;
loginParam.secretKey = item.password;
showTestPicker.value = false;
// 直接触发登录
doLogin(item.account, item.password);
}
const doLogin = (secretName, secretKey) => {
const encryptedKey = Encrypt(secretKey);
enforcementLoginByPassword({"secretName": secretName, "secretKey": encryptedKey}, response => {
if (response.code === 200) {
// 验证关键组织信息
if (!response.data.deptId) {
showFailToast("该用户未关联部门,无法登录");
return;
}
if (!response.data.organId) {
showFailToast("该用户未关联认证机构,无法登录");
return;
}
if (!response.data.bureauUnitId) {
showFailToast("该用户未关联执法单位,无法登录");
return;
}
// 执法端登录成功 → 赋值用户基本信息
useCommon.SET_USER_PHONENUMBER(response.data.phonenumber || "");
useCommon.SET_USER_ID(response.data.userId);
useCommon.SET_USER_NAME(response.data.userName || "");
useCommon.SET_NICK_NAME(response.data.nickName || "");
useCommon.SET_TOKEN(response.data.token);
useCommon.SET_DEPT_ID(response.data.deptId);
useCommon.SET_DEPT_NAME(response.data.deptName || "");
useCommon.SET_ORGAN_ID(response.data.organId);
useCommon.SET_ORGAN_NAME(response.data.organName || "");
useCommon.SET_BUREAU_UNIT_ID(response.data.bureauUnitId);
useCommon.SET_BUREAU_UNIT_NAME(response.data.bureauUnitName || "");
console.log("enforcement login success", useCommon.userId);
router.push({name: "enforcementHome"});
} else {
showFailToast(response.msg || "登录失败");
}
}, error => {
showFailToast(error?.msg || "登录失败,请检查网络");
})
};
const onSubmit = async () => {
doLogin(loginParam.secretName, loginParam.secretKey);
};
const Encrypt = (passWord) => {
var keyStr = '1234567898745621';
var ivStr = keyStr;
var key = CryptoJS.enc.Utf8.parse(keyStr);
var iv = CryptoJS.enc.Utf8.parse(ivStr);
var srcs = CryptoJS.enc.Utf8.parse(passWord);
var encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
});
return CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
}
</script>
<style lang="less" scoped>
.login-page {
width: 100%;
height: 100%;
display: flex;
overflow: hidden;
background-image: url("~@/assets/images/login.jpg");
background-repeat: no-repeat;
background-size: cover;
opacity: 0.9;
.login-content {
margin: auto;
padding: 0.27rem 0.27rem 0.52rem 0.27rem;
background-color: #fff;
border-radius: 0.27rem;
box-shadow: 0 0 0.27rem rgba(0, 0, 0, 0.2);
opacity: 0.9;
.title {
font-size: 0.52rem;
font-weight: 600;
}
}
}
.test-enterprise-list {
max-height: 60vh;
overflow-y: auto;
padding: 0.2rem;
}
</style>

4
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/enforcementMy.vue

@ -119,13 +119,15 @@ const settingItem = {
const onMenuClick = (item) => {
switch (item.key) {
case 'notice':
case 'task':
// 跳至消息中心待办 tab,并带类型过滤参数
router.push({
path: '/enforcement/message',
query: { tab: 'todo', type: item.key },
});
break;
case 'task':
router.push({ path: '/enforcement/task' });
break;
case 'appointment':
router.push({ path: '/enforcement/reserve' });
break;

63
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_edit.vue

@ -228,8 +228,8 @@
<script setup name="cpPlanEdit">
import { useRouter, useRoute } from 'vue-router';
import { showToast, showSuccessToast } from 'vant';
import { addPlan, editPlan, getPlanDetail, getSubmitDeadline, getMaxInnerUnitAcPersonNum, searchUnionByEnterpriseName, searchBureauUnitUsers } from '@/api/enforcement/plan';
import { showToast, showSuccessToast, showConfirmDialog } from 'vant';
import { addPlan, editPlan, getPlanDetail, getSubmitDeadline, getManualMatchDays, getMaxInnerUnitAcPersonNum, searchUnionByEnterpriseName, searchBureauUnitUsers } from '@/api/enforcement/plan';
const router = useRouter();
const route = useRoute();
@ -243,6 +243,9 @@ const showDatePicker = ref(false);
// 下阶段计划提交最晚时间(每月几号前需提交下阶段计划)
const submitDeadline = ref(26);
// 手工匹配距计划开始天数
const manualMatchDays = ref(3);
// 计算日期范围:根据计划类别、提交截止日返回 min/max
const dateRange = computed(() => {
const today = new Date();
@ -355,6 +358,8 @@ onMounted(() => {
loadSubmitDeadline();
// 获取单位内部单次计划最多参与人数
loadMaxPersonNum();
// 获取手工匹配距计划开始天数
loadManualMatchDays();
if (isEdit.value) {
loadDetail(route.query.id);
} else {
@ -412,6 +417,18 @@ const loadMaxPersonNum = () => {
});
};
// 拉取手工匹配距计划开始天数设置
const loadManualMatchDays = () => {
getManualMatchDays((res) => {
if (res && res.code === 200 && res.data != null) {
const val = Number(res.data);
if (!isNaN(val) && val > 0) {
manualMatchDays.value = val;
}
}
});
};
// 拉取提交截止日设置
const loadSubmitDeadline = () => {
getSubmitDeadline((res) => {
@ -646,6 +663,48 @@ const validate = () => {
const onSubmit = () => {
if (!validate()) return;
// 检柡是否需要确认Dialog
const isUnion = form.value.isUnion;
const planDateStr = form.value.planDate;
// 条件1:不联合 -> 提示将直接生成任务
if (isUnion === 0 || isUnion === '0') {
showConfirmDialog({
title: '提示',
message: '不联合的计划将直接生成任务,且不能再改为联合,是否确认提交?',
confirmButtonText: '确认提交',
cancelButtonText: '取消',
}).then(() => {
doSubmit();
}).catch(() => {});
return;
}
// 条件2:执行时间 <= 手工匹配天数 -> 提示时间不足
if (planDateStr) {
const planDate = new Date(planDateStr);
const today = new Date();
today.setHours(0, 0, 0, 0);
const diffDays = Math.ceil((planDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
if (diffDays <= manualMatchDays.value) {
showConfirmDialog({
title: '提示',
message: `执行时间小于可预约的最短时间(${manualMatchDays.value}天),提交后将直接生成任务,是否确认提交?`,
confirmButtonText: '确认提交',
cancelButtonText: '取消',
}).then(() => {
doSubmit();
}).catch(() => {});
return;
}
}
// 无需确认,直接提交
doSubmit();
};
// 实际执行提交
const doSubmit = () => {
submitting.value = true;
// ===== 提交前兜底:补齐后端BO @NotBlank/@NotNull 强校验字段 =====

14
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_mine.vue

@ -42,7 +42,7 @@
:class="{ 'pending-card': item.reserveStatus === 1 }"
>
<div class="card-header">
<div class="card-type">{{ item.isUnion === 1 ? '联合执法' : '普通执法' }}</div>
<div class="card-type">{{ item.isUnion === 1 ? '联合执法' : '非联合执法' }}</div>
<div class="status blue">
{{ getStatusText(item.reserveStatus) }}
</div>
@ -65,7 +65,7 @@
<div class="info-row">
<div class="label dark">计划时间:</div>
<div class="value gray">
{{ item.planDate }} {{ getPlanTimeText(item.planTime) }}
{{ (item.planDate || '').substring(0, 10) }} {{ getPlanTimeText(item.planTime) }}
</div>
</div>
@ -74,8 +74,9 @@
<div class="value gray">{{ item.enterpriseName }}</div>
</div>
<div v-if="item.editEnable !== 0" class="card-actions">
<div v-if="item.editEnable !== 0 || item.deleteEnable !== 0" class="card-actions">
<div
v-if="item.editEnable !== 0"
class="action-item blue-action"
@click="onEditPlan(item)"
>
@ -83,6 +84,7 @@
<span>编辑</span>
</div>
<div
v-if="item.deleteEnable !== 0"
class="action-item blue-action"
@click="onDeletePlan(item)"
>
@ -443,8 +445,12 @@ const onDeletePlan = (item) => {
}
.card-type {
font-size: 17px;
font-size: 14px;
font-weight: 500;
color: #fff !important;
background: linear-gradient(135deg, #1890ff, #0b63d1);
padding: 6px 10px;
border-radius: 10px;
}
.status {

10
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_other.vue

@ -21,7 +21,7 @@
class="plan-card normal-card pending-card"
>
<div class="card-header">
<div class="card-type">{{ item.isUnion === 1 ? '联合执法' : '普通执法' }}</div>
<div class="card-type">{{ item.isUnion === 1 ? '联合执法' : '非联合执法' }}</div>
<div class="status blue">待预约</div>
</div>
@ -42,7 +42,7 @@
<div class="info-row">
<div class="label dark">计划时间:</div>
<div class="value gray">
{{ item.planDate }} {{ getPlanTimeText(item.planTime) }}
{{ (item.planDate || '').substring(0, 10) }} {{ getPlanTimeText(item.planTime) }}
</div>
</div>
@ -437,8 +437,12 @@ const onConfirmReserve = () => {
}
.card-type {
font-size: 17px;
font-size: 14px;
font-weight: 500;
color: #fff !important;
background: linear-gradient(135deg, #1890ff, #0b63d1);
padding: 6px 10px;
border-radius: 10px;
}
.status {

392
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/task/cp_task_detail.vue

@ -0,0 +1,392 @@
<template>
<div class="detail-page">
<!-- 顶部导航 -->
<div class="nav-bar">
<div class="nav-back" @click="onBack">
<van-icon name="arrow-left" size="20" color="#333" />
</div>
<div class="nav-title">任务详情</div>
<div class="nav-right"></div>
</div>
<!-- 加载中 -->
<div v-if="loading" class="detail-loading">
<van-loading size="24" color="#2382cf" />
</div>
<!-- 详情内容 -->
<div v-else-if="detail" class="detail-content">
<!-- 顶部标题区 -->
<div class="header-card">
<div class="header-top">
<span class="plan-type-tag">【{{ planTypeText }}】</span>
<span v-if="detail.isUnion === 1" class="union-badge">联合执法</span>
</div>
<span class="header-title">{{ detail.enforceContent }}</span>
</div>
<!-- 基本信息卡片 -->
<div class="detail-card">
<div class="card-header">
<div class="header-icon-wrap green">
<van-icon name="info-o" size="15" color="#fff" />
</div>
<span>执法计划</span>
</div>
<div class="info-list">
<div class="info-item">
<span class="info-label">计划日期</span>
<span class="info-value">{{ formatDate(detail.planDate) }}</span>
</div>
<div class="info-item">
<span class="info-label">执行时段</span>
<span class="info-value">{{ timeRangeText }}</span>
</div>
<div v-if="detail.planActualDate && (formatDate(detail.planActualDate) !== formatDate(detail.planDate) || detail.planActualTime !== detail.planTime)" class="info-item highlight">
<span class="info-label">实际日期</span>
<span class="info-value">{{ formatDate(detail.planActualDate) }} {{ actualTimeRangeText }}</span>
</div>
</div>
</div>
<!-- 企业信息卡片 -->
<div class="detail-card">
<div class="card-header">
<div class="header-icon-wrap orange">
<van-icon name="wap-home-o" size="15" color="#fff" />
</div>
<span>检查对象</span>
</div>
<div class="info-list">
<div class="info-item">
<span class="info-label">企业名称</span>
<span class="info-value bold">{{ detail.enterpriseName || '-' }}</span>
</div>
<div v-if="detail.enterpriseNumber" class="info-item">
<span class="info-label">信用代码</span>
<span class="info-value bold">{{ detail.enterpriseNumber }}</span>
</div>
</div>
</div>
<!-- 执法人员卡片 -->
<div class="detail-card">
<div class="card-header">
<div class="header-icon-wrap purple">
<van-icon name="friends-o" size="15" color="#fff" />
</div>
<span>执法人员</span>
</div>
<div class="info-list">
<div class="info-item">
<span class="info-label">执法单位</span>
<span class="info-value">{{ detail.bureauDeptName || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">执法人员</span>
<span class="info-value">{{ detail.enforceUserNames || '-' }}</span>
</div>
<div v-if="detail.isUnion === 1 && detail.unionBureauDeptNames" class="info-item">
<span class="info-label">联合单位</span>
<span class="info-value">{{ detail.unionBureauDeptNames }}</span>
</div>
</div>
</div>
<!-- 备注卡片 -->
<div v-if="detail.remark" class="detail-card">
<div class="card-header">
<div class="header-icon-wrap gray">
<van-icon name="edit" size="15" color="#fff" />
</div>
<span>备注</span>
</div>
<div class="content-block">
{{ detail.remark }}
</div>
</div>
</div>
<!-- 空状态 -->
<div v-else class="detail-empty">
<van-empty description="未找到任务信息" />
</div>
</div>
</template>
<script setup name="cpTaskDetail">
import { useRouter, useRoute } from 'vue-router';
import { getMyTaskDetail } from '@/api/enforcement/task';
const router = useRouter();
const route = useRoute();
const loading = ref(true);
const detail = ref(null);
// 返回
const onBack = () => {
router.back();
};
// 格式化日期
const formatDate = (d) => {
if (!d) return '-';
if (typeof d === 'string') return d.substring(0, 10);
const date = new Date(d);
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${y}-${m}-${day}`;
};
// 时段文字
const getTimeText = (time) => {
if (time === 1) return '上午 (09:00-12:00)';
if (time === 2) return '下午 (14:00-18:00)';
return '其他';
};
const timeRangeText = computed(() => getTimeText(detail.value?.planTime));
const actualTimeRangeText = computed(() => getTimeText(detail.value?.planActualTime));
// 计划类型
const planTypeText = computed(() => {
const t = detail.value?.planType;
if (t === 1) return '日常计划';
if (t === 2) return '专项计划';
return '其他';
});
const statusClass = computed(() => {
const s = detail.value?.reserveStatus;
if (s === 3) return 'status-done';
if (s === 2) return 'status-progress';
if (s === 1) return 'status-pending';
return 'status-draft';
});
// 加载详情
const loadDetail = () => {
const planId = route.query.planId;
if (!planId) {
loading.value = false;
return;
}
loading.value = true;
getMyTaskDetail(planId, (res) => {
loading.value = false;
if (res.code === 200 && res.data) {
detail.value = res.data;
}
}, () => {
loading.value = false;
});
};
onMounted(() => {
loadDetail();
});
</script>
<style lang="less" scoped>
.detail-page {
display: flex;
flex-direction: column;
height: 100%;
background: #f4f7fc;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
/* 导航栏 */
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
background: linear-gradient(135deg, #2382cf, #1a6db5);
flex-shrink: 0;
.nav-back {
width: 32px;
cursor: pointer;
:deep(.van-icon) {
color: #fff !important;
}
}
.nav-title {
font-size: 17px;
font-weight: 600;
color: #fff;
}
.nav-right {
width: 32px;
}
}
.detail-loading {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.detail-empty {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
/* 详情内容 */
.detail-content {
flex: 1;
overflow-y: auto;
padding: 0 14px 24px;
}
/* 顶部标题区 */
.header-card {
background: linear-gradient(135deg, #2382cf, #1a6db5);
border-radius: 16px 16px 16px 16px;
padding: 20px 18px 22px;
margin: 8px 0px;
color: #fff;
.header-top {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.plan-type-tag {
font-size: 14px;
padding: 6px 10px;
border-radius: 10px;
background: rgba(255,255,255,0.2);
color: #fff;
font-weight: 500;
}
.union-badge {
font-size: 11px;
padding: 2px 10px;
border-radius: 10px;
background: linear-gradient(135deg, #f5a623, #f7c948);
color: #fff;
font-weight: 600;
}
.header-title {
font-size: 18px;
font-weight: 700;
line-height: 1.4;
margin-bottom: 8px;
}
.header-meta {
display: flex;
align-items: center;
gap: 5px;
font-size: 13px;
color: rgba(255,255,255,0.85);
}
}
/* 卡片 */
.detail-card {
background: #fff;
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(35, 130, 207, 0.06);
}
/* 卡片头部 */
.card-header {
display: flex;
align-items: center;
gap: 8px;
font-size: 15px;
font-weight: 600;
color: #1a1a2e;
margin-bottom: 14px;
.header-icon-wrap {
width: 26px;
height: 26px;
border-radius: 7px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
&.blue { background: linear-gradient(135deg, #2382cf, #5ba8e6); }
&.green { background: linear-gradient(135deg, #36b37e, #57d9a3); }
&.orange { background: linear-gradient(135deg, #f5a623, #f7c948); }
&.purple { background: linear-gradient(135deg, #7b68ee, #a78bfa); }
&.gray { background: linear-gradient(135deg, #8e99a4, #b0bac5); }
}
}
/* 信息列表 */
.info-list {
.info-item {
display: flex;
padding: 8px 0;
border-bottom: 1px dashed #f2f3f5;
&:last-child {
border-bottom: none;
padding-bottom: 0;
}
&:first-child {
padding-top: 0;
}
&.highlight {
background: #fef9ed;
margin: 4px -8px;
padding: 8px;
border-radius: 6px;
border-bottom: none;
}
.info-label {
flex-shrink: 0;
width: 68px;
font-size: 13px;
color: #8c8c8c;
line-height: 1.6;
}
.info-value {
flex: 1;
font-size: 13px;
color: #333;
word-break: break-all;
line-height: 1.6;
&.bold {
font-weight: 600;
color: #1a1a2e;
}
&.mono {
font-family: 'SF Mono', 'Menlo', monospace;
font-size: 12px;
color: #666;
letter-spacing: 0.3px;
}
}
}
}
/* 内容块 */
.content-block {
font-size: 13px;
color: #444;
line-height: 1.8;
word-break: break-all;
white-space: pre-wrap;
background: #f9fafb;
padding: 12px;
border-radius: 8px;
border-left: 3px solid #2382cf;
}
</style>

578
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/task/cp_task_mine.vue

@ -0,0 +1,578 @@
<template>
<div class="task-page">
<!-- 顶部导航 -->
<div class="nav-bar">
<div class="nav-back" @click="onBack">
<van-icon name="arrow-left" size="20" color="#333" />
</div>
<div class="nav-title">我的任务</div>
<div class="nav-right"></div>
</div>
<!-- 月份切换 -->
<div class="month-switch">
<div class="month-arrow" @click="onPrevMonth">
<van-icon name="arrow-left" size="16" color="#2382cf" />
</div>
<div class="month-text" @click="showMonthPicker = true">
{{ currentYear }}年{{ currentMonth }}月
</div>
<div class="month-arrow" @click="onNextMonth">
<van-icon name="arrow" size="16" color="#2382cf" />
</div>
</div>
<!-- 日历 -->
<div class="calendar">
<div class="week-header">
<div class="week-cell" v-for="w in weekDays" :key="w">{{ w }}</div>
</div>
<div class="date-grid">
<div
v-for="(cell, idx) in calendarCells"
:key="idx"
class="date-cell"
:class="{
'other-month': !cell.isCurrentMonth,
'is-today': cell.isToday,
'is-selected': cell.isSelected,
}"
@click="onSelectDate(cell)"
>
<span class="date-num">{{ cell.day }}</span>
<span class="date-lunar">{{ cell.lunar }}</span>
<span v-if="cell.hasDot && cell.isCurrentMonth" class="date-dot"></span>
</div>
</div>
</div>
<!-- 任务数量 -->
<div class="task-count">
今天共 <span class="count-num">{{ taskList.length }}</span> 个任务
</div>
<!-- 任务列表 -->
<div class="task-list-wrap">
<div v-if="loading" class="task-loading">
<van-loading size="24" color="#2382cf" />
</div>
<div v-else-if="taskList.length === 0" class="task-empty">
<van-empty description="当天暂无任务" />
</div>
<div v-else class="task-list">
<div
v-for="item in taskList"
:key="item.id"
class="task-card"
@click="onTaskClick(item)"
>
<div class="task-time-col">
<div class="time-start">{{ getTimeStart(item) }}</div>
<div class="time-end">{{ getTimeEnd(item) }}</div>
</div>
<div class="task-info-col">
<div class="task-title-row">
<span class="task-title">{{ item.enforceContent }}</span>
<span v-if="item.isUnion === 1" class="union-badge">联</span>
</div>
<div class="task-detail">
<van-icon name="wap-home-o" size="14" />
<span>{{ item.enterpriseName }}</span>
</div>
<div class="task-detail">
<van-icon name="clock-o" size="14" color="#999" />
<span>{{ formatPlanDate(item) }} {{ getTimeRange(item) }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- 月份选择弹窗 -->
<van-popup v-model:show="showMonthPicker" position="bottom" round>
<van-date-picker
v-model="monthPickerValue"
title="选择月份"
:columns-type="['year', 'month']"
@confirm="onMonthPickerConfirm"
@cancel="showMonthPicker = false"
/>
</van-popup>
</div>
</template>
<script setup name="cpTaskMine">
import { useRouter } from 'vue-router';
import { getMyTaskList, getMyTaskDates } from '@/api/enforcement/task';
const router = useRouter();
// ========== 日历相关 ==========
const weekDays = ['日', '一', '二', '三', '四', '五', '六'];
const today = new Date();
const currentYear = ref(today.getFullYear());
const currentMonth = ref(today.getMonth() + 1);
const selectedDate = ref(new Date(today.getFullYear(), today.getMonth(), today.getDate()));
const taskDatesSet = ref(new Set());
// 月份选择器
const showMonthPicker = ref(false);
const monthPickerValue = ref([
String(today.getFullYear()),
String(today.getMonth() + 1).padStart(2, '0'),
]);
// 简易农历(仅用于展示,不做精确计算)
const lunarDays = [
'', '初一', '初二', '初三', '初四', '初五', '初六', '初七', '初八', '初九', '初十',
'十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十',
'廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '三十',
];
const getLunarDay = (date) => {
// 简化处理:使用日期的日数模拟农历显示
const d = date.getDate();
return lunarDays[d] || '';
};
// 生成日历单元格
const calendarCells = computed(() => {
const year = currentYear.value;
const month = currentMonth.value;
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const startWeekDay = firstDay.getDay(); // 0=Sunday
const totalDays = lastDay.getDate();
const cells = [];
// 上个月补位
const prevMonthLastDay = new Date(year, month - 1, 0).getDate();
for (let i = startWeekDay - 1; i >= 0; i--) {
const day = prevMonthLastDay - i;
const d = new Date(year, month - 2, day);
cells.push({
day,
date: d,
lunar: getLunarDay(d),
isCurrentMonth: false,
isToday: false,
isSelected: false,
hasDot: false,
});
}
// 当月
for (let day = 1; day <= totalDays; day++) {
const d = new Date(year, month - 1, day);
const dateStr = formatDateStr(d);
const isToday = dateStr === formatDateStr(today);
const isSelected = dateStr === formatDateStr(selectedDate.value);
cells.push({
day,
date: d,
lunar: getLunarDay(d),
isCurrentMonth: true,
isToday,
isSelected,
hasDot: taskDatesSet.value.has(dateStr),
});
}
// 下个月补位(补满6行)
const remaining = 42 - cells.length;
for (let day = 1; day <= remaining; day++) {
const d = new Date(year, month, day);
cells.push({
day,
date: d,
lunar: getLunarDay(d),
isCurrentMonth: false,
isToday: false,
isSelected: false,
hasDot: false,
});
}
return cells;
});
const formatDateStr = (d) => {
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${y}-${m}-${day}`;
};
const formatYearMonth = () => {
return `${currentYear.value}-${String(currentMonth.value).padStart(2, '0')}`;
};
// 切换月份
const onPrevMonth = () => {
if (currentMonth.value === 1) {
currentYear.value--;
currentMonth.value = 12;
} else {
currentMonth.value--;
}
loadTaskDates();
};
const onNextMonth = () => {
if (currentMonth.value === 12) {
currentYear.value++;
currentMonth.value = 1;
} else {
currentMonth.value++;
}
loadTaskDates();
};
// 月份选择器确认
const onMonthPickerConfirm = ({ selectedValues }) => {
currentYear.value = parseInt(selectedValues[0]);
currentMonth.value = parseInt(selectedValues[1]);
showMonthPicker.value = false;
loadTaskDates();
};
// 选择日期
const onSelectDate = (cell) => {
if (!cell.isCurrentMonth) return;
selectedDate.value = cell.date;
loadTaskList();
};
// 返回
const onBack = () => {
router.back();
};
// 点击任务卡片跳转详情
const onTaskClick = (item) => {
router.push({ path: '/enforcement/task/detail', query: { planId: item.planId } });
};
// ========== 任务列表 ==========
const taskList = ref([]);
const loading = ref(false);
// 时段转时间显示
const getTimeStart = (item) => {
const time = item.planActualTime || item.planTime;
if (time === 1) return '09:00';
if (time === 2) return '14:00';
return '00:00';
};
const getTimeEnd = (item) => {
const time = item.planActualTime || item.planTime;
if (time === 1) return '12:00';
if (time === 2) return '18:00';
return '23:59';
};
const getTimeRange = (item) => {
return `${getTimeStart(item)}-${getTimeEnd(item)}`;
};
const formatPlanDate = (item) => {
const d = item.planActualDate || item.planDate;
if (!d) return '';
if (typeof d === 'string') return d.substring(0, 10);
const date = new Date(d);
return formatDateStr(date);
};
// 加载任务日期(月份打点)
const loadTaskDates = () => {
const ym = formatYearMonth();
getMyTaskDates(ym, (res) => {
if (res.code === 200 && res.data) {
taskDatesSet.value = new Set(res.data);
}
});
// 如果选中日期不在当月,自动选中当月1号
if (selectedDate.value.getFullYear() !== currentYear.value
|| selectedDate.value.getMonth() + 1 !== currentMonth.value) {
selectedDate.value = new Date(currentYear.value, currentMonth.value - 1, 1);
}
loadTaskList();
};
// 加载任务列表
const loadTaskList = () => {
loading.value = true;
const dateStr = formatDateStr(selectedDate.value);
getMyTaskList(
{ planDate: dateStr },
(res) => {
loading.value = false;
if (res.code === 200 && res.data) {
taskList.value = res.data;
} else {
taskList.value = [];
}
},
() => {
loading.value = false;
taskList.value = [];
}
);
};
// 初始化
onMounted(() => {
loadTaskDates();
});
</script>
<style lang="less" scoped>
.task-page {
display: flex;
flex-direction: column;
height: 100%;
background: #f0f6ff;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
/* 导航栏 */
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: #fff;
flex-shrink: 0;
.nav-back {
width: 32px;
cursor: pointer;
}
.nav-title {
font-size: 18px;
font-weight: 600;
color: #1a1a2e;
}
.nav-right {
width: 32px;
}
}
/* 月份切换 */
.month-switch {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
padding: 8px 0;
background: #fff;
flex-shrink: 0;
.month-arrow {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.month-text {
font-size: 15px;
font-weight: 600;
color: #2382cf;
cursor: pointer;
}
}
/* 日历 */
.calendar {
background: #fff;
padding: 0 8px 8px;
flex-shrink: 0;
}
.week-header {
display: grid;
grid-template-columns: repeat(7, 1fr);
text-align: center;
padding: 4px 0;
.week-cell {
font-size: 11px;
color: #999;
font-weight: 500;
}
}
.date-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px 0;
}
.date-cell {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 3px 0;
min-height: 38px;
cursor: pointer;
position: relative;
border-radius: 6px;
transition: background 0.15s;
.date-num {
font-size: 13px;
font-weight: 600;
color: #333;
line-height: 1.3;
}
.date-lunar {
font-size: 9px;
color: #999;
line-height: 1.1;
}
.date-dot {
width: 4px;
height: 4px;
border-radius: 50%;
background: #2382cf;
margin-top: 1px;
}
&.other-month {
.date-num { color: #ccc; }
.date-lunar { color: #ddd; }
}
&.is-today:not(.is-selected) {
.date-num {
color: #2382cf;
font-weight: 700;
}
}
&.is-selected {
background: #2382cf;
.date-num { color: #fff; font-weight: 700; }
.date-lunar { color: rgba(255,255,255,0.8); }
.date-dot { background: #fff; }
}
}
/* 任务计数 */
.task-count {
padding: 12px 16px 8px;
font-size: 14px;
color: #666;
flex-shrink: 0;
.count-num {
font-size: 18px;
font-weight: 700;
color: #2382cf;
}
}
/* 任务列表 */
.task-list-wrap {
flex: 1;
overflow-y: auto;
padding: 0 12px 16px;
}
.task-loading {
display: flex;
justify-content: center;
padding: 40px 0;
}
.task-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.task-card {
display: flex;
background: #fff;
border-radius: 14px;
padding: 16px;
box-shadow: 0 2px 10px rgba(44, 116, 190, 0.07);
gap: 14px;
}
.task-time-col {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: 48px;
flex-shrink: 0;
.time-start, .time-end {
font-size: 15px;
font-weight: 700;
color: #333;
line-height: 1.6;
}
}
.task-info-col {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.task-title-row {
display: flex;
align-items: center;
gap: 6px;
}
.task-title {
font-size: 15px;
font-weight: 600;
color: #1a1a2e;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
.union-badge {
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
border-radius: 4px;
background: #2382cf;
color: #fff;
font-size: 12px;
font-weight: 600;
flex-shrink: 0;
}
.task-detail {
display: flex;
align-items: center;
gap: 4px;
font-size: 13px;
color: #393838;
overflow: hidden;
span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>

6
zdxt-web-client/zdxt-efcode-enforcement-app/vite-config/index.js

@ -25,7 +25,7 @@ const loadPluginModules = async () => {
// 基本配置
const baseCfg = (viteEnv = {}) => {
const { VITE_BASE_URL, VITE_PORT } = viteEnv;
const { VITE_BASE_URL, VITE_PORT, VITE_PROXY_TARGET } = viteEnv;
return {
base: './',
envDir: "vite-env",
@ -43,12 +43,10 @@ const baseCfg = (viteEnv = {}) => {
cors: true,
proxy: {
'/api': {
// target: 'http://localhost:17000',
target: 'http://139.186.148.21:8080',
target: VITE_PROXY_TARGET || 'http://localhost:17000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
}
},
build: {

3
zdxt-web-client/zdxt-efcode-enforcement-app/vite-env/.env.enforcement-dev

@ -1,5 +1,6 @@
# 执法端开发环境
VITE_PORT = 12002
VITE_BASE_URL = '/api/enf-ep'
VITE_DEFAULT_LOGIN = '/enforcement/login'
VITE_PROXY_TARGET = 'http://localhost:17000'
VITE_DEFAULT_LOGIN = '/enforcement/loginByPassword'
VITE_APP_ENV = 'dev'

1
zdxt-web-client/zdxt-efcode-enforcement-app/vite-env/.env.enforcement-prod

@ -1,5 +1,6 @@
# 执法端生产环境
VITE_PORT = 12002
VITE_BASE_URL = 'http://localhost:17000/enf-ep'
VITE_PROXY_TARGET = 'http://localhost:17000'
VITE_DEFAULT_LOGIN = '/enforce/login'
VITE_APP_ENV = 'prod'

1
zdxt-web-client/zdxt-efcode-enforcement-app/vite-env/.env.enforcement-test

@ -1,5 +1,6 @@
# 执法端测试环境
VITE_PORT = 12002
VITE_BASE_URL = '/api'
VITE_PROXY_TARGET = 'http://139.186.148.21:8080'
VITE_DEFAULT_LOGIN = '/enforcement/login'
VITE_APP_ENV = 'test'

354
zdxt-web-server/zdxt-admin-web-server/src/test/java/com/zdxt/rest/test/cp/AutoMatchReserveTest.java

@ -0,0 +1,354 @@
package com.zdxt.rest.test.cp;
import cn.dev33.satoken.context.mock.SaTokenContextMockUtil;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zdxt.enforcementcode.domain.EfcodeCpPlan;
import com.zdxt.enforcementcode.domain.EfcodeCpReserve;
import com.zdxt.enforcementcode.mapper.EfcodeCpPlanMapper;
import com.zdxt.enforcementcode.mapper.EfcodeCpReserveMapper;
import com.zdxt.enforcementcode.service.IEfcodeCpPlanService;
import org.dromara.ZdxtAdminApplication;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
/**
* 计划自动匹配预约 autoMatchReserve 方法测试
* <p>
* 测试场景:
* 1. 强制模式下执行自动匹配(绕过日期校验)
* 2. 验证匹配结果:同企业不同单位的计划被联合
* 3. 验证计划状态更新:system_reserved=1, reserve_status=2
* 4. 验证预约记录生成:相同 plan_group_id 关联多条记录
* </p>
*/
@SpringBootTest(classes = ZdxtAdminApplication.class)
@AutoConfigureMockMvc
@ActiveProfiles({"dev", "dev-cust-zrb"})
@DisplayName("autoMatchReserve 自动匹配预约测试")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Tag("dev")
public class AutoMatchReserveTest {
@Autowired
private IEfcodeCpPlanService cpPlanService;
@Autowired
private EfcodeCpPlanMapper cpPlanMapper;
@Autowired
private EfcodeCpReserveMapper cpReserveMapper;
@BeforeEach
public void setUp() {
SaTokenContextMockUtil.setMockContext();
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
mockRequest.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(mockRequest));
}
@AfterEach
public void tearDown() {
try {
StpUtil.logout();
} catch (Exception ignored) {
}
RequestContextHolder.resetRequestAttributes();
SaTokenContextMockUtil.clearContext();
}
@DisplayName("0. 插入1000条适合匹配的计划数据")
@Test
@Order(0)
public void testInsert1000MatchablePlans() {
// 模拟登录(避免自动填充 createBy 报空)
StpUtil.login(1);
// 准备数据:200个企业 × 每企业5个不同执法单位 = 1000条计划
int enterpriseCount = 200;
int unitsPerEnterprise = 5;
Random random = new Random();
// 模拟执法单位ID池(确保不同单位)
String[] bureauDeptIds = {
"BUREAU_TEST_A", "BUREAU_TEST_B", "BUREAU_TEST_C",
"BUREAU_TEST_D", "BUREAU_TEST_E", "BUREAU_TEST_F",
"BUREAU_TEST_G", "BUREAU_TEST_H"
};
String[] bureauDeptNames = {
"测试执法局A", "测试执法局B", "测试执法局C",
"测试执法局D", "测试执法局E", "测试执法局F",
"测试执法局G", "测试执法局H"
};
// 基准日期:从明天开始
LocalDate baseDate = LocalDate.now().plusDays(1);
int insertedCount = 0;
for (int e = 0; e < enterpriseCount; e++) {
String enterpriseNumber = String.format("91440300TEST%06d", e);
String enterpriseName = "测试企业" + e + "号";
String enterpriseId = "ENT_TEST_" + e;
// 同一企业的计划日期在基准日期附3天内(确保一周内可匹配)
Date planDate = Date.from(
baseDate.plusDays(random.nextInt(3))
.atStartOfDay(ZoneId.systemDefault()).toInstant()
);
for (int u = 0; u < unitsPerEnterprise; u++) {
int bureauIdx = (e + u) % bureauDeptIds.length;
EfcodeCpPlan plan = new EfcodeCpPlan();
plan.setId(UUID.randomUUID().toString().replace("-", ""));
plan.setPlanType(random.nextBoolean() ? 1L : 2L);
plan.setPlanDate(planDate);
plan.setPlanTime((long) (random.nextInt(3) + 1)); // 1=上午,2=下午,3=其他
plan.setPlanActualDate(plan.getPlanDate());
plan.setPlanActualTime(plan.getPlanTime()); // 1=上午,2=下午,3=其他
plan.setEnforceContent("自动匹配测试-执法事项" + e + "-" + u);
plan.setEnterpriseName(enterpriseName);
plan.setEnterpriseNumber(enterpriseNumber);
plan.setEnterpriseId(enterpriseId);
plan.setEnforceUserNames("测试人员" + u);
plan.setEnforceUserIds(String.valueOf(1000 + u));
plan.setEnforceUserCodes("CODE" + u);
plan.setEnforceUserTels("1380000" + String.format("%04d", u));
plan.setIsPublic(1L);
plan.setIsUnion(1L);
plan.setReserveStatus(1L); // 待预约
plan.setSystemReserved(0L); // 未经系统匹配
plan.setManualReserved(0L);
plan.setBureauDeptId(bureauDeptIds[bureauIdx]);
plan.setBureauDeptName(bureauDeptNames[bureauIdx]);
plan.setOrganId(plan.getBureauDeptId());
plan.setOrganName(plan.getBureauDeptName());
plan.setDeptId(plan.getBureauDeptId());
plan.setDeptName(plan.getBureauDeptName());
plan.setIsDelete(0L);
plan.setCreateBy(1L);
plan.setCreateTime(new Date());
plan.setUpdateBy(1L);
plan.setUpdateTime(new Date());
cpPlanMapper.insert(plan);
insertedCount++;
}
}
System.out.println("========== 插入匹配测试数据完成 ==========");
System.out.println("共插入计划数: " + insertedCount);
System.out.println("涉及企业数: " + enterpriseCount);
System.out.println("每企业不同单位数: " + unitsPerEnterprise);
System.out.println("预期可匹配企业组数: " + enterpriseCount + " (每企业都有>=2个单位)");
System.out.println("===========================================");
Assertions.assertEquals(1000, insertedCount, "应插入恰好1000条计划");
}
@DisplayName("1. 查看当前待匹配计划数据概况")
@Test
@Order(1)
public void testCheckPendingPlans() {
// 查询符合自动匹配条件的计划
LambdaQueryWrapper<EfcodeCpPlan> lqw = Wrappers.lambdaQuery();
lqw.gt(EfcodeCpPlan::getPlanDate, new Date());
lqw.eq(EfcodeCpPlan::getSystemReserved, 0L);
lqw.eq(EfcodeCpPlan::getReserveStatus, 1L);
lqw.eq(EfcodeCpPlan::getIsPublic, 1L);
lqw.eq(EfcodeCpPlan::getIsUnion, 1L);
lqw.eq(EfcodeCpPlan::getIsDelete, 0L);
List<EfcodeCpPlan> pendingPlans = cpPlanMapper.selectList(lqw);
System.out.println("========== 待匹配计划数据概况 ==========");
System.out.println("符合条件的计划总数: " + pendingPlans.size());
// 按企业信用代码分组统计
Map<String, List<EfcodeCpPlan>> groupByEnterprise = pendingPlans.stream()
.filter(p -> p.getEnterpriseNumber() != null)
.collect(Collectors.groupingBy(EfcodeCpPlan::getEnterpriseNumber));
int potentialMatchCount = 0;
for (Map.Entry<String, List<EfcodeCpPlan>> entry : groupByEnterprise.entrySet()) {
// 统计不同执法单位数量
long distinctUnits = entry.getValue().stream()
.map(EfcodeCpPlan::getBureauDeptId)
.distinct()
.count();
if (distinctUnits >= 2) {
potentialMatchCount++;
System.out.println(" 企业[" + entry.getKey() + "] - 有" + distinctUnits + "个不同单位的计划,可匹配");
}
}
System.out.println("潜在可匹配的企业数: " + potentialMatchCount);
System.out.println("=======================================");
}
@DisplayName("2. 强制执行 autoMatchReserve 并验证结果")
@Test
@Order(2)
public void testAutoMatchReserveForceMode() {
// 执行前:统计当前系统匹配预约记录数
long beforeReserveCount = cpReserveMapper.selectCount(
Wrappers.<EfcodeCpReserve>lambdaQuery().eq(EfcodeCpReserve::getResFrom, 1L)
);
System.out.println("执行前系统匹配预约记录数: " + beforeReserveCount);
// 执行前:统计已被系统预约的计划数
long beforeMatchedPlanCount = cpPlanMapper.selectCount(
Wrappers.<EfcodeCpPlan>lambdaQuery()
.eq(EfcodeCpPlan::getSystemReserved, 1L)
.eq(EfcodeCpPlan::getIsDelete, 0L)
);
System.out.println("执行前已系统匹配计划数: " + beforeMatchedPlanCount);
// 执行自动匹配(强制模式,忽略日期校验)
System.out.println("\n========== 开始执行 autoMatchReserve(true) ==========");
cpPlanService.autoMatchReserve(true, true, true);
System.out.println("========== 执行完毕 ==========\n");
// 执行后:统计系统匹配预约记录数
long afterReserveCount = cpReserveMapper.selectCount(
Wrappers.<EfcodeCpReserve>lambdaQuery().eq(EfcodeCpReserve::getResFrom, 1L)
);
System.out.println("执行后系统匹配预约记录数: " + afterReserveCount);
System.out.println("本次新增预约记录数: " + (afterReserveCount - beforeReserveCount));
// 执行后:统计已被系统预约的计划数
long afterMatchedPlanCount = cpPlanMapper.selectCount(
Wrappers.<EfcodeCpPlan>lambdaQuery()
.eq(EfcodeCpPlan::getSystemReserved, 1L)
.eq(EfcodeCpPlan::getIsDelete, 0L)
);
System.out.println("执行后已系统匹配计划数: " + afterMatchedPlanCount);
System.out.println("本次新增匹配计划数: " + (afterMatchedPlanCount - beforeMatchedPlanCount));
// 验证:如果有符合条件的数据,则应产生匹配结果
if (afterReserveCount > beforeReserveCount) {
System.out.println("\n[验证通过] 自动匹配产生了新的预约记录");
} else {
System.out.println("\n[提示] 未产生新预约记录(可能没有符合条件的跨单位同企业计划)");
}
}
@DisplayName("3. 验证匹配后的 plan_group_id 分组一致性")
@Test
@Order(3)
public void testVerifyPlanGroupConsistency() {
// 查询所有系统匹配生成的预约记录
LambdaQueryWrapper<EfcodeCpReserve> lqw = Wrappers.lambdaQuery();
lqw.eq(EfcodeCpReserve::getResFrom, 1L);
lqw.eq(EfcodeCpReserve::getOpStatus, 3L); // 待确认
lqw.orderByDesc(EfcodeCpReserve::getCreateTime);
List<EfcodeCpReserve> reserves = cpReserveMapper.selectList(lqw);
if (reserves.isEmpty()) {
System.out.println("[跳过] 无系统匹配的预约记录,无法验证分组一致性");
return;
}
// 按 plan_group_id 分组
Map<String, List<EfcodeCpReserve>> groupMap = reserves.stream()
.collect(Collectors.groupingBy(EfcodeCpReserve::getPlanGroupId));
System.out.println("========== plan_group_id 分组验证 ==========");
System.out.println("共有 " + groupMap.size() + " 组联合预约");
int validGroups = 0;
for (Map.Entry<String, List<EfcodeCpReserve>> entry : groupMap.entrySet()) {
List<EfcodeCpReserve> group = entry.getValue();
// 每组至少2条记录(2个不同单位)
if (group.size() >= 2) {
validGroups++;
// 验证同组内关联的计划属于不同执法单位
Set<String> planIds = group.stream()
.map(EfcodeCpReserve::getPlanId)
.collect(Collectors.toSet());
List<EfcodeCpPlan> plans = cpPlanMapper.selectByIds(planIds);
Set<String> bureauDeptIds = plans.stream()
.map(EfcodeCpPlan::getBureauDeptId)
.collect(Collectors.toSet());
System.out.println(" groupId=" + entry.getKey()
+ " | 记录数=" + group.size()
+ " | 涉及单位数=" + bureauDeptIds.size());
// 断言:同组关联的计划应来自不同单位
Assertions.assertTrue(bureauDeptIds.size() >= 2,
"同一planGroupId下应关联至少2个不同执法单位的计划");
}
}
System.out.println("有效分组数(>=2条记录): " + validGroups);
System.out.println("=============================================");
}
@DisplayName("4. 验证匹配后计划状态已更新")
@Test
@Order(4)
public void testVerifyPlanStatusUpdated() {
// 查询已被系统匹配的计划
LambdaQueryWrapper<EfcodeCpPlan> lqw = Wrappers.lambdaQuery();
lqw.eq(EfcodeCpPlan::getSystemReserved, 1L);
lqw.eq(EfcodeCpPlan::getReserveStatus, 2L); // 预约中
lqw.eq(EfcodeCpPlan::getIsDelete, 0L);
List<EfcodeCpPlan> matchedPlans = cpPlanMapper.selectList(lqw);
System.out.println("========== 计划状态验证 ==========");
System.out.println("system_reserved=1 且 reserve_status=2 的计划数: " + matchedPlans.size());
if (!matchedPlans.isEmpty()) {
// 验证每条计划都满足状态要求
for (EfcodeCpPlan plan : matchedPlans) {
Assertions.assertEquals(1L, plan.getSystemReserved(),
"计划[" + plan.getId() + "]的system_reserved应为1");
Assertions.assertEquals(2L, plan.getReserveStatus(),
"计划[" + plan.getId() + "]的reserve_status应为2(预约中)");
}
System.out.println("[验证通过] 所有已匹配计划状态正确");
} else {
System.out.println("[提示] 无已匹配计划数据");
}
System.out.println("==================================");
}
@DisplayName("5. 再次执行 autoMatchReserve 验证幂等性(不重复匹配)")
@Test
@Order(5)
public void testAutoMatchReserveIdempotent() {
// 先统计当前预约记录数
long beforeCount = cpReserveMapper.selectCount(
Wrappers.<EfcodeCpReserve>lambdaQuery().eq(EfcodeCpReserve::getResFrom, 1L)
);
// 再次执行
System.out.println("========== 二次执行 autoMatchReserve(true) 验证幂等性 ==========");
cpPlanService.autoMatchReserve(true, true, true);
long afterCount = cpReserveMapper.selectCount(
Wrappers.<EfcodeCpReserve>lambdaQuery().eq(EfcodeCpReserve::getResFrom, 1L)
);
long newRecords = afterCount - beforeCount;
System.out.println("二次执行新增预约记录数: " + newRecords);
// 已匹配过的计划不应再次匹配(system_reserved已设为1)
// 但如果有新的符合条件的数据也可能产生新记录
System.out.println("[说明] 已匹配的计划(system_reserved=1)不会被重复匹配");
System.out.println("================================================================");
}
}
Loading…
Cancel
Save