26 changed files with 2130 additions and 13 deletions
@ -0,0 +1,105 @@ |
|||
package com.zdxt.enforcementcode.controller; |
|||
|
|||
import java.util.List; |
|||
|
|||
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.EfcodeCpLogVo; |
|||
import com.zdxt.enforcementcode.domain.bo.EfcodeCpLogBo; |
|||
import com.zdxt.enforcementcode.service.IEfcodeCpLogService; |
|||
import org.dromara.common.mybatis.core.page.TableDataInfo; |
|||
|
|||
/** |
|||
* 行政执法检查计划-日志 |
|||
* |
|||
* @author Lion Li |
|||
* @date 2026-05-25 |
|||
*/ |
|||
@Validated |
|||
@RequiredArgsConstructor |
|||
@RestController |
|||
@RequestMapping("/enforcementcode/cpLog") |
|||
public class EfcodeCpLogController extends BaseController { |
|||
|
|||
private final IEfcodeCpLogService efcodeCpLogService; |
|||
|
|||
/** |
|||
* 查询行政执法检查计划-日志列表 |
|||
*/ |
|||
@SaCheckPermission("enforcementcode:cpLog:list") |
|||
@GetMapping("/list") |
|||
public TableDataInfo<EfcodeCpLogVo> list(EfcodeCpLogBo bo, PageQuery pageQuery) { |
|||
return efcodeCpLogService.queryPageList(bo, pageQuery); |
|||
} |
|||
|
|||
/** |
|||
* 导出行政执法检查计划-日志列表 |
|||
*/ |
|||
@SaCheckPermission("enforcementcode:cpLog:export") |
|||
@Log(title = "行政执法检查计划-日志", businessType = BusinessType.EXPORT) |
|||
@PostMapping("/export") |
|||
public void export(EfcodeCpLogBo bo, HttpServletResponse response) { |
|||
List<EfcodeCpLogVo> list = efcodeCpLogService.queryList(bo); |
|||
ExcelUtil.exportExcel(list, "行政执法检查计划-日志", EfcodeCpLogVo.class, response); |
|||
} |
|||
|
|||
/** |
|||
* 获取行政执法检查计划-日志详细信息 |
|||
* |
|||
* @param id 主键 |
|||
*/ |
|||
@SaCheckPermission("enforcementcode:cpLog:query") |
|||
@GetMapping("/{id}") |
|||
public R<EfcodeCpLogVo> getInfo(@NotNull(message = "主键不能为空") |
|||
@PathVariable String id) { |
|||
return R.ok(efcodeCpLogService.queryById(id)); |
|||
} |
|||
|
|||
/** |
|||
* 新增行政执法检查计划-日志 |
|||
*/ |
|||
@SaCheckPermission("enforcementcode:cpLog:add") |
|||
@Log(title = "行政执法检查计划-日志", businessType = BusinessType.INSERT) |
|||
@RepeatSubmit() |
|||
@PostMapping() |
|||
public R<Void> add(@Validated(AddGroup.class) @RequestBody EfcodeCpLogBo bo) { |
|||
return toAjax(efcodeCpLogService.insertByBo(bo)); |
|||
} |
|||
|
|||
/** |
|||
* 修改行政执法检查计划-日志 |
|||
*/ |
|||
@SaCheckPermission("enforcementcode:cpLog:edit") |
|||
@Log(title = "行政执法检查计划-日志", businessType = BusinessType.UPDATE) |
|||
@RepeatSubmit() |
|||
@PutMapping() |
|||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody EfcodeCpLogBo bo) { |
|||
return toAjax(efcodeCpLogService.updateByBo(bo)); |
|||
} |
|||
|
|||
/** |
|||
* 删除行政执法检查计划-日志 |
|||
* |
|||
* @param ids 主键串 |
|||
*/ |
|||
@SaCheckPermission("enforcementcode:cpLog:remove") |
|||
@Log(title = "行政执法检查计划-日志", businessType = BusinessType.DELETE) |
|||
@DeleteMapping("/{ids}") |
|||
public R<Void> remove(@NotEmpty(message = "主键不能为空") |
|||
@PathVariable String[] ids) { |
|||
return toAjax(efcodeCpLogService.deleteWithValidByIds(List.of(ids), true)); |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
package com.zdxt.enforcementcode.domain; |
|||
|
|||
import org.dromara.common.mybatis.core.domain.BaseEntity; |
|||
import com.baomidou.mybatisplus.annotation.*; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
|
|||
import java.io.Serial; |
|||
|
|||
/** |
|||
* 行政执法检查计划-日志对象 efcode_cp_log |
|||
* |
|||
* @author Lion Li |
|||
* @date 2026-05-25 |
|||
*/ |
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@TableName("efcode_cp_log") |
|||
public class EfcodeCpLog extends BaseEntity { |
|||
|
|||
@Serial |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
/** |
|||
* 主键ID |
|||
*/ |
|||
@TableId(value = "id") |
|||
private String id; |
|||
|
|||
/** |
|||
* 关联记录 |
|||
*/ |
|||
private String recordId; |
|||
|
|||
/** |
|||
* 关联表 |
|||
*/ |
|||
private String tableName; |
|||
|
|||
/** |
|||
* 日志类型 |
|||
*/ |
|||
private String logType; |
|||
|
|||
/** |
|||
* 短日志 |
|||
*/ |
|||
private String opSlog; |
|||
|
|||
/** |
|||
* 长日志 |
|||
*/ |
|||
private String opLlog; |
|||
|
|||
/** |
|||
* 操作人ID |
|||
*/ |
|||
private String opUserId; |
|||
|
|||
/** |
|||
* 操作人姓名 |
|||
*/ |
|||
private String opUserName; |
|||
|
|||
/** |
|||
* 备注 |
|||
*/ |
|||
private String remark; |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
package com.zdxt.enforcementcode.domain.bo; |
|||
|
|||
import com.zdxt.enforcementcode.domain.EfcodeCpLog; |
|||
import org.dromara.common.mybatis.core.domain.BaseEntity; |
|||
import org.dromara.common.core.validate.AddGroup; |
|||
import org.dromara.common.core.validate.EditGroup; |
|||
import io.github.linpeilie.annotations.AutoMapper; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import jakarta.validation.constraints.*; |
|||
|
|||
/** |
|||
* 行政执法检查计划-日志业务对象 efcode_cp_log |
|||
* |
|||
* @author Lion Li |
|||
* @date 2026-05-25 |
|||
*/ |
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@AutoMapper(target = EfcodeCpLog.class, reverseConvertGenerate = false) |
|||
public class EfcodeCpLogBo extends BaseEntity { |
|||
|
|||
/** |
|||
* 主键ID |
|||
*/ |
|||
@NotBlank(message = "主键ID不能为空", groups = { EditGroup.class }) |
|||
private String id; |
|||
|
|||
/** |
|||
* 关联记录 |
|||
*/ |
|||
private String recordId; |
|||
|
|||
/** |
|||
* 关联表 |
|||
*/ |
|||
private String tableName; |
|||
|
|||
/** |
|||
* 日志类型 |
|||
*/ |
|||
private String logType; |
|||
|
|||
/** |
|||
* 短日志 |
|||
*/ |
|||
private String opSlog; |
|||
|
|||
/** |
|||
* 长日志 |
|||
*/ |
|||
private String opLlog; |
|||
|
|||
/** |
|||
* 操作人ID |
|||
*/ |
|||
@NotBlank(message = "操作人ID不能为空", groups = { AddGroup.class, EditGroup.class }) |
|||
private String opUserId; |
|||
|
|||
/** |
|||
* 操作人姓名 |
|||
*/ |
|||
@NotBlank(message = "操作人姓名不能为空", groups = { AddGroup.class, EditGroup.class }) |
|||
private String opUserName; |
|||
|
|||
/** |
|||
* 备注 |
|||
*/ |
|||
private String remark; |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
package com.zdxt.enforcementcode.domain.vo; |
|||
|
|||
import com.zdxt.enforcementcode.domain.EfcodeCpLog; |
|||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated; |
|||
import cn.idev.excel.annotation.ExcelProperty; |
|||
import org.dromara.common.excel.annotation.ExcelDictFormat; |
|||
import org.dromara.common.excel.convert.ExcelDictConvert; |
|||
import io.github.linpeilie.annotations.AutoMapper; |
|||
import lombok.Data; |
|||
|
|||
import java.io.Serial; |
|||
import java.io.Serializable; |
|||
import java.util.Date; |
|||
|
|||
|
|||
|
|||
/** |
|||
* 行政执法检查计划-日志视图对象 efcode_cp_log |
|||
* |
|||
* @author Lion Li |
|||
* @date 2026-05-25 |
|||
*/ |
|||
@Data |
|||
@ExcelIgnoreUnannotated |
|||
@AutoMapper(target = EfcodeCpLog.class) |
|||
public class EfcodeCpLogVo implements Serializable { |
|||
|
|||
@Serial |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
/** |
|||
* 主键ID |
|||
*/ |
|||
@ExcelProperty(value = "主键ID") |
|||
private String id; |
|||
|
|||
/** |
|||
* 关联记录 |
|||
*/ |
|||
@ExcelProperty(value = "关联记录") |
|||
private String recordId; |
|||
|
|||
/** |
|||
* 关联表 |
|||
*/ |
|||
@ExcelProperty(value = "关联表") |
|||
private String tableName; |
|||
|
|||
/** |
|||
* 日志类型 |
|||
*/ |
|||
@ExcelProperty(value = "日志类型") |
|||
private String logType; |
|||
|
|||
/** |
|||
* 短日志 |
|||
*/ |
|||
@ExcelProperty(value = "短日志") |
|||
private String opSlog; |
|||
|
|||
/** |
|||
* 长日志 |
|||
*/ |
|||
@ExcelProperty(value = "长日志") |
|||
private String opLlog; |
|||
|
|||
/** |
|||
* 操作人ID |
|||
*/ |
|||
@ExcelProperty(value = "操作人ID") |
|||
private String opUserId; |
|||
|
|||
/** |
|||
* 操作人姓名 |
|||
*/ |
|||
@ExcelProperty(value = "操作人姓名") |
|||
private String opUserName; |
|||
|
|||
/** |
|||
* 备注 |
|||
*/ |
|||
@ExcelProperty(value = "备注") |
|||
private String remark; |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
package com.zdxt.enforcementcode.mapper; |
|||
|
|||
import com.zdxt.enforcementcode.domain.EfcodeCpLog; |
|||
import com.zdxt.enforcementcode.domain.vo.EfcodeCpLogVo; |
|||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; |
|||
|
|||
/** |
|||
* 行政执法检查计划-日志Mapper接口 |
|||
* |
|||
* @author Lion Li |
|||
* @date 2026-05-25 |
|||
*/ |
|||
public interface EfcodeCpLogMapper extends BaseMapperPlus<EfcodeCpLog, EfcodeCpLogVo> { |
|||
|
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
package com.zdxt.enforcementcode.quartz.job; |
|||
|
|||
import com.zdxt.enforcementcode.service.IEfcodeCpPlanService; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
/** |
|||
* 计划自动匹配预约 - 定时任务 |
|||
* <p>定期扫描待预约状态的计划,按规则自动匹配并生成预约记录</p> |
|||
* |
|||
* @author zrb |
|||
*/ |
|||
@Slf4j |
|||
@Component |
|||
@RequiredArgsConstructor |
|||
public class CpPlanAutoMatchJob { |
|||
|
|||
private final IEfcodeCpPlanService cpPlanService; |
|||
|
|||
/** |
|||
* 任务执行入口(public,供 JobInvokeUtil 通过反射调用) |
|||
*/ |
|||
public void execute() { |
|||
log.info("[CpPlanAutoMatchJob] 定时任务触发:计划自动匹配预约"); |
|||
cpPlanService.autoMatchReserve(); |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
package com.zdxt.enforcementcode.quartz.job; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.quartz.JobExecutionContext; |
|||
import org.springframework.scheduling.quartz.QuartzJobBean; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import java.time.LocalDateTime; |
|||
import java.time.format.DateTimeFormatter; |
|||
|
|||
/** |
|||
* 演示定时任务 - 每秒打印当前时间 |
|||
* |
|||
* @author ruoyi |
|||
*/ |
|||
@Slf4j |
|||
@Component |
|||
public class ENFDemoTimeJob extends QuartzJobBean { |
|||
|
|||
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); |
|||
|
|||
@Override |
|||
protected void executeInternal(JobExecutionContext context) { |
|||
execute(); |
|||
} |
|||
|
|||
/** |
|||
* 任务执行入口(public,供 JobInvokeUtil 通过反射调用) |
|||
*/ |
|||
public void execute() { |
|||
log.info("[Quartz Demo] 当前时间: {}", LocalDateTime.now().format(FORMATTER)); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
package com.zdxt.enforcementcode.service; |
|||
|
|||
import com.zdxt.enforcementcode.domain.vo.EfcodeCpLogVo; |
|||
import com.zdxt.enforcementcode.domain.bo.EfcodeCpLogBo; |
|||
import org.dromara.common.mybatis.core.page.TableDataInfo; |
|||
import org.dromara.common.mybatis.core.page.PageQuery; |
|||
|
|||
import java.util.Collection; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 行政执法检查计划-日志Service接口 |
|||
* |
|||
* @author Lion Li |
|||
* @date 2026-05-25 |
|||
*/ |
|||
public interface IEfcodeCpLogService { |
|||
|
|||
/** |
|||
* 查询行政执法检查计划-日志 |
|||
* |
|||
* @param id 主键 |
|||
* @return 行政执法检查计划-日志 |
|||
*/ |
|||
EfcodeCpLogVo queryById(String id); |
|||
|
|||
/** |
|||
* 分页查询行政执法检查计划-日志列表 |
|||
* |
|||
* @param bo 查询条件 |
|||
* @param pageQuery 分页参数 |
|||
* @return 行政执法检查计划-日志分页列表 |
|||
*/ |
|||
TableDataInfo<EfcodeCpLogVo> queryPageList(EfcodeCpLogBo bo, PageQuery pageQuery); |
|||
|
|||
/** |
|||
* 查询符合条件的行政执法检查计划-日志列表 |
|||
* |
|||
* @param bo 查询条件 |
|||
* @return 行政执法检查计划-日志列表 |
|||
*/ |
|||
List<EfcodeCpLogVo> queryList(EfcodeCpLogBo bo); |
|||
|
|||
/** |
|||
* 新增行政执法检查计划-日志 |
|||
* |
|||
* @param bo 行政执法检查计划-日志 |
|||
* @return 是否新增成功 |
|||
*/ |
|||
Boolean insertByBo(EfcodeCpLogBo bo); |
|||
|
|||
/** |
|||
* 修改行政执法检查计划-日志 |
|||
* |
|||
* @param bo 行政执法检查计划-日志 |
|||
* @return 是否修改成功 |
|||
*/ |
|||
Boolean updateByBo(EfcodeCpLogBo bo); |
|||
|
|||
/** |
|||
* 校验并批量删除行政执法检查计划-日志信息 |
|||
* |
|||
* @param ids 待删除的主键集合 |
|||
* @param isValid 是否进行有效性校验 |
|||
* @return 是否删除成功 |
|||
*/ |
|||
Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid); |
|||
} |
|||
@ -0,0 +1,138 @@ |
|||
package com.zdxt.enforcementcode.service.impl; |
|||
|
|||
import org.dromara.common.core.utils.MapstructUtils; |
|||
import org.dromara.common.core.utils.StringUtils; |
|||
import org.dromara.common.mybatis.core.page.TableDataInfo; |
|||
import org.dromara.common.mybatis.core.page.PageQuery; |
|||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Service; |
|||
import com.zdxt.enforcementcode.domain.bo.EfcodeCpLogBo; |
|||
import com.zdxt.enforcementcode.domain.vo.EfcodeCpLogVo; |
|||
import com.zdxt.enforcementcode.domain.EfcodeCpLog; |
|||
import com.zdxt.enforcementcode.mapper.EfcodeCpLogMapper; |
|||
import com.zdxt.enforcementcode.service.IEfcodeCpLogService; |
|||
|
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Collection; |
|||
|
|||
/** |
|||
* 行政执法检查计划-日志Service业务层处理 |
|||
* |
|||
* @author Lion Li |
|||
* @date 2026-05-25 |
|||
*/ |
|||
@Slf4j |
|||
@RequiredArgsConstructor |
|||
@Service |
|||
public class EfcodeCpLogServiceImpl implements IEfcodeCpLogService { |
|||
|
|||
private final EfcodeCpLogMapper baseMapper; |
|||
|
|||
/** |
|||
* 查询行政执法检查计划-日志 |
|||
* |
|||
* @param id 主键 |
|||
* @return 行政执法检查计划-日志 |
|||
*/ |
|||
@Override |
|||
public EfcodeCpLogVo queryById(String id){ |
|||
return baseMapper.selectVoById(id); |
|||
} |
|||
|
|||
/** |
|||
* 分页查询行政执法检查计划-日志列表 |
|||
* |
|||
* @param bo 查询条件 |
|||
* @param pageQuery 分页参数 |
|||
* @return 行政执法检查计划-日志分页列表 |
|||
*/ |
|||
@Override |
|||
public TableDataInfo<EfcodeCpLogVo> queryPageList(EfcodeCpLogBo bo, PageQuery pageQuery) { |
|||
LambdaQueryWrapper<EfcodeCpLog> lqw = buildQueryWrapper(bo); |
|||
Page<EfcodeCpLogVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw); |
|||
return TableDataInfo.build(result); |
|||
} |
|||
|
|||
/** |
|||
* 查询符合条件的行政执法检查计划-日志列表 |
|||
* |
|||
* @param bo 查询条件 |
|||
* @return 行政执法检查计划-日志列表 |
|||
*/ |
|||
@Override |
|||
public List<EfcodeCpLogVo> queryList(EfcodeCpLogBo bo) { |
|||
LambdaQueryWrapper<EfcodeCpLog> lqw = buildQueryWrapper(bo); |
|||
return baseMapper.selectVoList(lqw); |
|||
} |
|||
|
|||
private LambdaQueryWrapper<EfcodeCpLog> buildQueryWrapper(EfcodeCpLogBo bo) { |
|||
Map<String, Object> params = bo.getParams(); |
|||
LambdaQueryWrapper<EfcodeCpLog> lqw = Wrappers.lambdaQuery(); |
|||
lqw.orderByAsc(EfcodeCpLog::getId); |
|||
lqw.eq(StringUtils.isNotBlank(bo.getRecordId()), EfcodeCpLog::getRecordId, bo.getRecordId()); |
|||
lqw.like(StringUtils.isNotBlank(bo.getTableName()), EfcodeCpLog::getTableName, bo.getTableName()); |
|||
lqw.eq(StringUtils.isNotBlank(bo.getLogType()), EfcodeCpLog::getLogType, bo.getLogType()); |
|||
lqw.eq(StringUtils.isNotBlank(bo.getOpSlog()), EfcodeCpLog::getOpSlog, bo.getOpSlog()); |
|||
lqw.eq(StringUtils.isNotBlank(bo.getOpLlog()), EfcodeCpLog::getOpLlog, bo.getOpLlog()); |
|||
lqw.eq(StringUtils.isNotBlank(bo.getOpUserId()), EfcodeCpLog::getOpUserId, bo.getOpUserId()); |
|||
lqw.like(StringUtils.isNotBlank(bo.getOpUserName()), EfcodeCpLog::getOpUserName, bo.getOpUserName()); |
|||
return lqw; |
|||
} |
|||
|
|||
/** |
|||
* 新增行政执法检查计划-日志 |
|||
* |
|||
* @param bo 行政执法检查计划-日志 |
|||
* @return 是否新增成功 |
|||
*/ |
|||
@Override |
|||
public Boolean insertByBo(EfcodeCpLogBo bo) { |
|||
EfcodeCpLog add = MapstructUtils.convert(bo, EfcodeCpLog.class); |
|||
validEntityBeforeSave(add); |
|||
boolean flag = baseMapper.insert(add) > 0; |
|||
if (flag) { |
|||
bo.setId(add.getId()); |
|||
} |
|||
return flag; |
|||
} |
|||
|
|||
/** |
|||
* 修改行政执法检查计划-日志 |
|||
* |
|||
* @param bo 行政执法检查计划-日志 |
|||
* @return 是否修改成功 |
|||
*/ |
|||
@Override |
|||
public Boolean updateByBo(EfcodeCpLogBo bo) { |
|||
EfcodeCpLog update = MapstructUtils.convert(bo, EfcodeCpLog.class); |
|||
validEntityBeforeSave(update); |
|||
return baseMapper.updateById(update) > 0; |
|||
} |
|||
|
|||
/** |
|||
* 保存前的数据校验 |
|||
*/ |
|||
private void validEntityBeforeSave(EfcodeCpLog entity){ |
|||
//TODO 做一些数据校验,如唯一约束
|
|||
} |
|||
|
|||
/** |
|||
* 校验并批量删除行政执法检查计划-日志信息 |
|||
* |
|||
* @param ids 待删除的主键集合 |
|||
* @param isValid 是否进行有效性校验 |
|||
* @return 是否删除成功 |
|||
*/ |
|||
@Override |
|||
public Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid) { |
|||
if(isValid){ |
|||
//TODO 做一些业务上的校验,判断是否需要校验
|
|||
} |
|||
return baseMapper.deleteByIds(ids) > 0; |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<!DOCTYPE mapper |
|||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
|||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="com.zdxt.enforcementcode.mapper.EfcodeCpLogMapper"> |
|||
</mapper> |
|||
@ -0,0 +1,16 @@ |
|||
import { _get, _post, _put } from "@/http/index"; |
|||
|
|||
// region 预约处理接口
|
|||
|
|||
const reserveBaseUrl = "/app-enf/cpReserve"; |
|||
|
|||
// 预约列表(分页,支持 resFrom 过滤:0=手动预约,1=系统匹配)
|
|||
export const getReserveList = (params, success, error) => _get(reserveBaseUrl + "/list", params, success, error); |
|||
|
|||
// 预约处理:同意
|
|||
export const approveReserve = (id, success, error) => _put(reserveBaseUrl + "/approve/" + id, {}, success, error); |
|||
|
|||
// 预约处理:拒绝
|
|||
export const rejectReserve = (id, success, error) => _put(reserveBaseUrl + "/reject/" + id, {}, success, error); |
|||
|
|||
// endregion
|
|||
@ -0,0 +1,102 @@ |
|||
<template> |
|||
<div class="reserve-page app_content"> |
|||
<!-- 顶部导航 --> |
|||
<van-nav-bar |
|||
title="预约处理" |
|||
left-arrow |
|||
@click-left="onBack" |
|||
/> |
|||
|
|||
<!-- Tab 切换 --> |
|||
<van-tabs |
|||
v-model:active="activeTab" |
|||
title-active-color="#111" |
|||
title-inactive-color="#666" |
|||
color="#2f8bd3" |
|||
line-width="36px" |
|||
line-height="2px" |
|||
:border="false" |
|||
> |
|||
<van-tab title="手动预约" name="manual"> |
|||
<cp-reserve-manual /> |
|||
</van-tab> |
|||
<van-tab title="系统匹配" name="system"> |
|||
<cp-reserve-system /> |
|||
</van-tab> |
|||
</van-tabs> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup name="cpReserveList"> |
|||
import { useRouter } from 'vue-router'; |
|||
import CpReserveManual from './cp_reserve_manual.vue'; |
|||
import CpReserveSystem from './cp_reserve_system.vue'; |
|||
|
|||
const router = useRouter(); |
|||
const activeTab = ref('manual'); |
|||
|
|||
const onBack = () => { |
|||
router.back(); |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.reserve-page { |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: 100%; |
|||
background: #fff; |
|||
position: relative; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
:deep(.van-nav-bar) { |
|||
background: #347bc3; |
|||
flex-shrink: 0; |
|||
|
|||
.van-nav-bar__title { |
|||
color: #fff; |
|||
font-size: 18px; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
.van-icon { |
|||
color: #fff; |
|||
} |
|||
} |
|||
|
|||
:deep(.van-tabs) { |
|||
display: flex; |
|||
flex-direction: column; |
|||
flex: 1; |
|||
min-height: 0; |
|||
} |
|||
|
|||
:deep(.van-tabs__wrap) { |
|||
height: 54px; |
|||
background: #fff; |
|||
border-bottom: 1px solid #e4edf7; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
:deep(.van-tab) { |
|||
font-size: 16px; |
|||
} |
|||
|
|||
:deep(.van-tab--active) { |
|||
font-weight: 600; |
|||
} |
|||
|
|||
:deep(.van-tabs__content), |
|||
:deep(.van-tabs__track), |
|||
:deep(.van-tab__panel-wrapper), |
|||
:deep(.van-tab__panel) { |
|||
flex: 1; |
|||
min-height: 0; |
|||
height: 100%; |
|||
} |
|||
|
|||
:deep(.van-tabs__content) { |
|||
overflow: hidden; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,457 @@ |
|||
<template> |
|||
<div class="reserve-tab"> |
|||
<!-- 状态过滤 --> |
|||
<div class="filter-bar"> |
|||
<div |
|||
v-for="opt in filterOptions" |
|||
:key="opt.value" |
|||
class="filter-item" |
|||
:class="{ active: filterStatus === opt.value }" |
|||
@click="onFilterChange(opt.value)" |
|||
> |
|||
{{ opt.label }} |
|||
</div> |
|||
</div> |
|||
|
|||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh"> |
|||
<van-list |
|||
v-model:loading="loading" |
|||
:finished="finished" |
|||
finished-text="没有更多了" |
|||
@load="onLoad" |
|||
> |
|||
<div class="reserve-list"> |
|||
<section |
|||
v-for="item in reserveList" |
|||
:key="item.id" |
|||
class="reserve-card" |
|||
:class="{ 'approved-card': item.opStatus === 1, 'rejected-card': item.opStatus === 2, 'pending-card': item.opStatus === 3, 'selected-card': selectedId === item.id }" |
|||
@click="onSelectCard(item)" |
|||
> |
|||
<div class="card-header"> |
|||
<div class="card-label"></div> |
|||
<div class="status" :class="getStatusClass(item.opStatus)"> |
|||
{{ getStatusText(item.opStatus) }} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="plan-title">{{ item.enforceContent || '执法事项' }}</div> |
|||
|
|||
<div class="info-row"> |
|||
<div class="label">企业名称:</div> |
|||
<div class="value">{{ item.enterpriseName || '-' }}</div> |
|||
</div> |
|||
|
|||
<div class="info-row"> |
|||
<div class="label">计划时间:</div> |
|||
<div class="value">{{ item.planDate || '-' }} {{ getPlanTimeText(item.planTime) }}</div> |
|||
</div> |
|||
|
|||
<div class="info-row"> |
|||
<div class="label">执法单位:</div> |
|||
<div class="value">{{ item.bureauDeptName || '-' }}</div> |
|||
</div> |
|||
|
|||
<div class="info-row"> |
|||
<div class="label">预约时间:</div> |
|||
<div class="value">{{ formatDate(item.reserveTime) }}</div> |
|||
</div> |
|||
|
|||
<!-- 选中且待确认状态才显示操作按钮 --> |
|||
<div v-if="item.opStatus === 3 && selectedId === item.id" class="card-actions"> |
|||
<div class="actions-left"> |
|||
<div v-if="item.opMessage" class="action-item message-action" @click.stop="onShowMessage(item)"> |
|||
<van-icon name="chat-o" /> |
|||
<span>查看留言</span> |
|||
</div> |
|||
</div> |
|||
<div class="actions-right"> |
|||
<div class="action-item reject-action" @click.stop="onReject(item)"> |
|||
<van-icon name="cross" /> |
|||
<span>拒绝</span> |
|||
</div> |
|||
<div class="action-item approve-action" @click.stop="onApprove(item)"> |
|||
<van-icon name="success" /> |
|||
<span>同意</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</section> |
|||
</div> |
|||
|
|||
<!-- 空状态 --> |
|||
<van-empty v-if="!loading && reserveList.length === 0" description="暂无手动预约记录" /> |
|||
</van-list> |
|||
</van-pull-refresh> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup name="cpReserveManual"> |
|||
import { showConfirmDialog, showSuccessToast, showDialog } from 'vant'; |
|||
import { getReserveList, approveReserve, rejectReserve } from '@/api/enforcement/reserve'; |
|||
|
|||
const selectedId = ref(null); |
|||
const reserveList = ref([]); |
|||
const loading = ref(false); |
|||
const finished = ref(false); |
|||
const refreshing = ref(false); |
|||
const pageNum = ref(1); |
|||
const pageSize = 10; |
|||
const filterStatus = ref(null); // null=全部, 1=已同意, 2=已拒绝, 3=待确认 |
|||
|
|||
const filterOptions = [ |
|||
{ label: '全部', value: null }, |
|||
{ label: '待确认', value: 3 }, |
|||
{ label: '已同意', value: 1 }, |
|||
{ label: '已拒绝', value: 2 }, |
|||
]; |
|||
|
|||
// 状态文本映射 |
|||
const getStatusText = (status) => { |
|||
const map = { 1: '已同意', 2: '已拒绝', 3: '待确认', 4: '已取消' }; |
|||
return map[status] || '未知'; |
|||
}; |
|||
|
|||
// 状态样式 |
|||
const getStatusClass = (status) => { |
|||
const map = { 1: 'green', 2: 'red', 3: 'blue', 4: 'gray' }; |
|||
return map[status] || ''; |
|||
}; |
|||
|
|||
// 时段文本 |
|||
const getPlanTimeText = (time) => { |
|||
const map = { 1: '上午', 2: '下午', 3: '其他' }; |
|||
return map[time] || ''; |
|||
}; |
|||
|
|||
// 格式化日期 |
|||
const formatDate = (dateStr) => { |
|||
if (!dateStr) return '-'; |
|||
const d = new Date(dateStr); |
|||
if (isNaN(d.getTime())) return dateStr; |
|||
const y = d.getFullYear(); |
|||
const m = String(d.getMonth() + 1).padStart(2, '0'); |
|||
const day = String(d.getDate()).padStart(2, '0'); |
|||
const h = String(d.getHours()).padStart(2, '0'); |
|||
const min = String(d.getMinutes()).padStart(2, '0'); |
|||
return `${y}-${m}-${day} ${h}:${min}`; |
|||
}; |
|||
|
|||
// 加载数据 |
|||
const onLoad = () => { |
|||
fetchList(); |
|||
}; |
|||
|
|||
// 选中卡片(再次点击取消选中) |
|||
const onSelectCard = (item) => { |
|||
if (item.opStatus !== 3) return; // 非待确认状态不可选 |
|||
selectedId.value = selectedId.value === item.id ? null : item.id; |
|||
}; |
|||
|
|||
// 下拉刷新 |
|||
const onRefresh = () => { |
|||
pageNum.value = 1; |
|||
finished.value = false; |
|||
reserveList.value = []; |
|||
fetchList(); |
|||
}; |
|||
|
|||
// 过滤切换 |
|||
const onFilterChange = (val) => { |
|||
filterStatus.value = val; |
|||
onRefresh(); |
|||
}; |
|||
|
|||
// 请求列表 |
|||
const fetchList = () => { |
|||
loading.value = true; |
|||
const params = { pageNum: pageNum.value, pageSize, resFrom: 0 }; |
|||
if (filterStatus.value !== null) { |
|||
params.opStatus = filterStatus.value; |
|||
} |
|||
getReserveList( |
|||
params, |
|||
(res) => { |
|||
refreshing.value = false; |
|||
loading.value = false; |
|||
if (res.code === 200) { |
|||
const rows = res.rows || []; |
|||
if (pageNum.value === 1) { |
|||
reserveList.value = rows; |
|||
} else { |
|||
reserveList.value.push(...rows); |
|||
} |
|||
if (rows.length < pageSize) { |
|||
finished.value = true; |
|||
} else { |
|||
pageNum.value++; |
|||
} |
|||
} else { |
|||
finished.value = true; |
|||
} |
|||
}, |
|||
() => { |
|||
refreshing.value = false; |
|||
loading.value = false; |
|||
finished.value = true; |
|||
} |
|||
); |
|||
}; |
|||
|
|||
// 查看留言 |
|||
const onShowMessage = (item) => { |
|||
showDialog({ |
|||
title: '预约留言', |
|||
message: item.opMessage, |
|||
confirmButtonText: '关闭', |
|||
}); |
|||
}; |
|||
|
|||
// 同意 |
|||
const onApprove = (item) => { |
|||
showConfirmDialog({ |
|||
title: '提示', |
|||
message: '确定同意该预约申请吗?', |
|||
}).then(() => { |
|||
approveReserve(item.id, (res) => { |
|||
if (res.code === 200) { |
|||
showSuccessToast('已同意'); |
|||
afterOperate(item, 'approve'); |
|||
} else { |
|||
showDialog({ title: '提示', message: res.msg || '操作失败' }); |
|||
} |
|||
}); |
|||
}).catch(() => {}); |
|||
}; |
|||
|
|||
// 拒绝 |
|||
const onReject = (item) => { |
|||
showConfirmDialog({ |
|||
title: '提示', |
|||
message: '确定拒绝该预约申请吗?', |
|||
}).then(() => { |
|||
rejectReserve(item.id, (res) => { |
|||
if (res.code === 200) { |
|||
showSuccessToast('已拒绝'); |
|||
afterOperate(item, 'reject'); |
|||
} else { |
|||
showDialog({ title: '提示', message: res.msg || '操作失败' }); |
|||
} |
|||
}); |
|||
}).catch(() => {}); |
|||
}; |
|||
|
|||
// 操作后置处理 |
|||
const afterOperate = (item, action) => { |
|||
// TODO: 可在此处扩展后置逻辑,如通知、跳转等 |
|||
selectedId.value = null; |
|||
onRefresh(); |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.reserve-tab { |
|||
height: 100%; |
|||
overflow-y: auto; |
|||
padding: 12px; |
|||
padding-top: 12px; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.filter-bar { |
|||
display: flex; |
|||
gap: 10px; |
|||
margin-bottom: 14px; |
|||
} |
|||
|
|||
.filter-item { |
|||
padding: 5px 14px; |
|||
font-size: 13px; |
|||
font-weight: 500; |
|||
border-radius: 16px; |
|||
background: #f0f4f8; |
|||
color: #666; |
|||
transition: all 0.2s; |
|||
|
|||
&.active { |
|||
background: #1f75c9; |
|||
color: #fff; |
|||
} |
|||
} |
|||
|
|||
.reserve-list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 14px; |
|||
} |
|||
|
|||
.reserve-card { |
|||
background: #fff; |
|||
border-radius: 14px; |
|||
padding: 16px; |
|||
box-shadow: 0 4px 16px rgba(44, 116, 190, 0.12), 0 1px 4px rgba(0, 0, 0, 0.06); |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.approved-card { |
|||
border: 1.5px solid rgba(7, 193, 96, 0.4); |
|||
} |
|||
|
|||
.rejected-card { |
|||
border: 1.5px solid rgba(238, 10, 36, 0.35); |
|||
} |
|||
|
|||
.pending-card { |
|||
border: 1.5px solid rgba(35, 130, 207, 0.35); |
|||
} |
|||
|
|||
.selected-card { |
|||
background: radial-gradient(circle at 78% 5%, rgba(65, 188, 255, 0.75) 0, rgba(65, 188, 255, 0.25) 20%, transparent 42%), |
|||
linear-gradient(135deg, #064ec6 0%, #0b63d9 48%, #268ee5 100%) !important; |
|||
border-color: transparent; |
|||
|
|||
.card-header .card-label { |
|||
background: rgba(255, 255, 255, 0.15); |
|||
color: rgba(255, 255, 255, 0.85); |
|||
} |
|||
|
|||
.card-header .status { |
|||
color: #f0b66c !important; |
|||
} |
|||
|
|||
.plan-title { |
|||
color: #fff; |
|||
} |
|||
|
|||
.info-row .label { |
|||
color: #fff; |
|||
} |
|||
|
|||
.info-row .value { |
|||
color: rgba(255, 255, 255, 0.7); |
|||
|
|||
&.msg { |
|||
color: rgba(255, 255, 255, 0.8); |
|||
} |
|||
} |
|||
|
|||
.card-actions { |
|||
border-top-color: rgba(255, 255, 255, 0.18); |
|||
} |
|||
|
|||
.message-action { |
|||
color: rgba(255, 255, 255, 0.85); |
|||
} |
|||
|
|||
.approve-action { |
|||
color: #fff; |
|||
} |
|||
|
|||
.reject-action { |
|||
color: rgba(255, 255, 255, 0.8); |
|||
} |
|||
} |
|||
|
|||
.card-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 14px; |
|||
} |
|||
|
|||
.card-label { |
|||
font-size: 13px; |
|||
font-weight: 500; |
|||
color: #666; |
|||
background: #f0f6ff; |
|||
padding: 3px 10px; |
|||
border-radius: 10px; |
|||
} |
|||
|
|||
.status { |
|||
font-size: 14px; |
|||
font-weight: 500; |
|||
|
|||
&.blue { color: #1f75c9; } |
|||
&.green { color: #07c160; } |
|||
&.red { color: #ee0a24; } |
|||
&.gray { color: #999; } |
|||
} |
|||
|
|||
.plan-title { |
|||
font-size: 16px; |
|||
font-weight: 700; |
|||
color: #111; |
|||
line-height: 1.5; |
|||
margin-bottom: 12px; |
|||
white-space: nowrap; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
} |
|||
|
|||
.info-row { |
|||
display: flex; |
|||
align-items: flex-start; |
|||
margin-bottom: 8px; |
|||
font-size: 14px; |
|||
line-height: 1.5; |
|||
} |
|||
|
|||
.label { |
|||
width: 72px; |
|||
flex-shrink: 0; |
|||
color: #333; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.value { |
|||
flex: 1; |
|||
color: #8a8a8a; |
|||
|
|||
&.msg { |
|||
color: #666; |
|||
font-style: italic; |
|||
} |
|||
} |
|||
|
|||
.card-actions { |
|||
margin-top: 14px; |
|||
padding-top: 12px; |
|||
border-top: 1px solid rgba(0, 0, 0, 0.06); |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
} |
|||
|
|||
.actions-left { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.actions-right { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 24px; |
|||
} |
|||
|
|||
.message-action { |
|||
color: #1f75c9; |
|||
} |
|||
|
|||
.action-item { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 4px; |
|||
font-size: 14px; |
|||
cursor: pointer; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.approve-action { |
|||
color: #07c160; |
|||
} |
|||
|
|||
.reject-action { |
|||
color: #ee0a24; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,442 @@ |
|||
<template> |
|||
<div class="reserve-tab"> |
|||
<!-- 状态过滤 --> |
|||
<div class="filter-bar"> |
|||
<div |
|||
v-for="opt in filterOptions" |
|||
:key="opt.value" |
|||
class="filter-item" |
|||
:class="{ active: filterStatus === opt.value }" |
|||
@click="onFilterChange(opt.value)" |
|||
> |
|||
{{ opt.label }} |
|||
</div> |
|||
</div> |
|||
|
|||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh"> |
|||
<van-list |
|||
v-model:loading="loading" |
|||
:finished="finished" |
|||
finished-text="没有更多了" |
|||
@load="onLoad" |
|||
> |
|||
<div class="reserve-list"> |
|||
<section |
|||
v-for="item in reserveList" |
|||
:key="item.id" |
|||
class="reserve-card" |
|||
:class="{ 'approved-card': item.opStatus === 1, 'rejected-card': item.opStatus === 2, 'pending-card': item.opStatus === 3, 'selected-card': selectedId === item.id }" |
|||
@click="onSelectCard(item)" |
|||
> |
|||
<div class="card-header"> |
|||
<div class="status" :class="getStatusClass(item.opStatus)"> |
|||
{{ getStatusText(item.opStatus) }} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="plan-title">{{ item.enforceContent || '执法事项' }}</div> |
|||
|
|||
<div class="info-row"> |
|||
<div class="label">企业名称:</div> |
|||
<div class="value">{{ item.enterpriseName || '-' }}</div> |
|||
</div> |
|||
|
|||
<div class="info-row"> |
|||
<div class="label">计划时间:</div> |
|||
<div class="value">{{ item.planDate || '-' }} {{ getPlanTimeText(item.planTime) }}</div> |
|||
</div> |
|||
|
|||
<div class="info-row"> |
|||
<div class="label">执法单位:</div> |
|||
<div class="value">{{ item.bureauDeptName || '-' }}</div> |
|||
</div> |
|||
|
|||
<div class="info-row"> |
|||
<div class="label">匹配时间:</div> |
|||
<div class="value">{{ formatDate(item.reserveTime) }}</div> |
|||
</div> |
|||
|
|||
<!-- 选中且待确认状态才显示操作按钮 --> |
|||
<div v-if="item.opStatus === 3 && selectedId === item.id" class="card-actions"> |
|||
<div class="actions-left"> |
|||
<div v-if="item.opMessage" class="action-item message-action" @click.stop="onShowMessage(item)"> |
|||
<van-icon name="chat-o" /> |
|||
<span>查看备注</span> |
|||
</div> |
|||
</div> |
|||
<div class="actions-right"> |
|||
<div class="action-item reject-action" @click.stop="onReject(item)"> |
|||
<van-icon name="cross" /> |
|||
<span>拒绝</span> |
|||
</div> |
|||
<div class="action-item approve-action" @click.stop="onApprove(item)"> |
|||
<van-icon name="success" /> |
|||
<span>同意</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</section> |
|||
</div> |
|||
|
|||
<!-- 空状态 --> |
|||
<van-empty v-if="!loading && reserveList.length === 0" description="暂无系统匹配记录" /> |
|||
</van-list> |
|||
</van-pull-refresh> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup name="cpReserveSystem"> |
|||
import { showConfirmDialog, showSuccessToast, showDialog } from 'vant'; |
|||
import { getReserveList, approveReserve, rejectReserve } from '@/api/enforcement/reserve'; |
|||
|
|||
const selectedId = ref(null); |
|||
const reserveList = ref([]); |
|||
const loading = ref(false); |
|||
const finished = ref(false); |
|||
const refreshing = ref(false); |
|||
const pageNum = ref(1); |
|||
const pageSize = 10; |
|||
const filterStatus = ref(null); // null=全部, 1=已同意, 2=已拒绝, 3=待确认 |
|||
|
|||
const filterOptions = [ |
|||
{ label: '全部', value: null }, |
|||
{ label: '待确认', value: 3 }, |
|||
{ label: '已同意', value: 1 }, |
|||
{ label: '已拒绝', value: 2 }, |
|||
]; |
|||
|
|||
// 状态文本映射 |
|||
const getStatusText = (status) => { |
|||
const map = { 1: '已同意', 2: '已拒绝', 3: '待确认', 4: '已取消' }; |
|||
return map[status] || '未知'; |
|||
}; |
|||
|
|||
// 状态样式 |
|||
const getStatusClass = (status) => { |
|||
const map = { 1: 'green', 2: 'red', 3: 'blue', 4: 'gray' }; |
|||
return map[status] || ''; |
|||
}; |
|||
|
|||
// 时段文本 |
|||
const getPlanTimeText = (time) => { |
|||
const map = { 1: '上午', 2: '下午', 3: '其他' }; |
|||
return map[time] || ''; |
|||
}; |
|||
|
|||
// 格式化日期 |
|||
const formatDate = (dateStr) => { |
|||
if (!dateStr) return '-'; |
|||
const d = new Date(dateStr); |
|||
if (isNaN(d.getTime())) return dateStr; |
|||
const y = d.getFullYear(); |
|||
const m = String(d.getMonth() + 1).padStart(2, '0'); |
|||
const day = String(d.getDate()).padStart(2, '0'); |
|||
const h = String(d.getHours()).padStart(2, '0'); |
|||
const min = String(d.getMinutes()).padStart(2, '0'); |
|||
return `${y}-${m}-${day} ${h}:${min}`; |
|||
}; |
|||
|
|||
// 加载数据 |
|||
const onLoad = () => { |
|||
fetchList(); |
|||
}; |
|||
|
|||
// 选中卡片(再次点击取消选中) |
|||
const onSelectCard = (item) => { |
|||
if (item.opStatus !== 3) return; // 非待确认状态不可选 |
|||
selectedId.value = selectedId.value === item.id ? null : item.id; |
|||
}; |
|||
|
|||
// 下拉刷新 |
|||
const onRefresh = () => { |
|||
pageNum.value = 1; |
|||
finished.value = false; |
|||
reserveList.value = []; |
|||
fetchList(); |
|||
}; |
|||
|
|||
// 过滤切换 |
|||
const onFilterChange = (val) => { |
|||
filterStatus.value = val; |
|||
onRefresh(); |
|||
}; |
|||
|
|||
// 请求列表 |
|||
const fetchList = () => { |
|||
loading.value = true; |
|||
const params = { pageNum: pageNum.value, pageSize, resFrom: 1 }; |
|||
if (filterStatus.value !== null) { |
|||
params.opStatus = filterStatus.value; |
|||
} |
|||
getReserveList( |
|||
params, |
|||
(res) => { |
|||
refreshing.value = false; |
|||
loading.value = false; |
|||
if (res.code === 200) { |
|||
const rows = res.rows || []; |
|||
if (pageNum.value === 1) { |
|||
reserveList.value = rows; |
|||
} else { |
|||
reserveList.value.push(...rows); |
|||
} |
|||
if (rows.length < pageSize) { |
|||
finished.value = true; |
|||
} else { |
|||
pageNum.value++; |
|||
} |
|||
} else { |
|||
finished.value = true; |
|||
} |
|||
}, |
|||
() => { |
|||
refreshing.value = false; |
|||
loading.value = false; |
|||
finished.value = true; |
|||
} |
|||
); |
|||
}; |
|||
|
|||
// 查看备注 |
|||
const onShowMessage = (item) => { |
|||
showDialog({ |
|||
title: '备注信息', |
|||
message: item.opMessage, |
|||
confirmButtonText: '关闭', |
|||
}); |
|||
}; |
|||
|
|||
// 同意 |
|||
const onApprove = (item) => { |
|||
showConfirmDialog({ |
|||
title: '提示', |
|||
message: '确定同意该系统匹配吗?', |
|||
}).then(() => { |
|||
approveReserve(item.id, (res) => { |
|||
if (res.code === 200) { |
|||
showSuccessToast('已同意'); |
|||
afterOperate(item, 'approve'); |
|||
} else { |
|||
showDialog({ title: '提示', message: res.msg || '操作失败' }); |
|||
} |
|||
}); |
|||
}).catch(() => {}); |
|||
}; |
|||
|
|||
// 拒绝 |
|||
const onReject = (item) => { |
|||
showConfirmDialog({ |
|||
title: '提示', |
|||
message: '确定拒绝该系统匹配吗?', |
|||
}).then(() => { |
|||
rejectReserve(item.id, (res) => { |
|||
if (res.code === 200) { |
|||
showSuccessToast('已拒绝'); |
|||
afterOperate(item, 'reject'); |
|||
} else { |
|||
showDialog({ title: '提示', message: res.msg || '操作失败' }); |
|||
} |
|||
}); |
|||
}).catch(() => {}); |
|||
}; |
|||
|
|||
// 操作后置处理 |
|||
const afterOperate = (item, action) => { |
|||
// TODO: 可在此处扩展后置逻辑,如通知、跳转等 |
|||
selectedId.value = null; |
|||
onRefresh(); |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.reserve-tab { |
|||
height: 100%; |
|||
overflow-y: auto; |
|||
padding: 12px; |
|||
padding-top: 12px; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.filter-bar { |
|||
display: flex; |
|||
gap: 10px; |
|||
margin-bottom: 14px; |
|||
} |
|||
|
|||
.filter-item { |
|||
padding: 5px 14px; |
|||
font-size: 13px; |
|||
font-weight: 500; |
|||
border-radius: 16px; |
|||
background: #f0f4f8; |
|||
color: #666; |
|||
transition: all 0.2s; |
|||
|
|||
&.active { |
|||
background: #1f75c9; |
|||
color: #fff; |
|||
} |
|||
} |
|||
|
|||
.reserve-list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 14px; |
|||
} |
|||
|
|||
.reserve-card { |
|||
background: #fff; |
|||
border-radius: 14px; |
|||
padding: 16px; |
|||
box-shadow: 0 4px 16px rgba(44, 116, 190, 0.12), 0 1px 4px rgba(0, 0, 0, 0.06); |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.approved-card { |
|||
border: 1.5px solid rgba(7, 193, 96, 0.4); |
|||
} |
|||
|
|||
.rejected-card { |
|||
border: 1.5px solid rgba(238, 10, 36, 0.35); |
|||
} |
|||
|
|||
.pending-card { |
|||
border: 1.5px solid rgba(35, 130, 207, 0.35); |
|||
} |
|||
|
|||
.selected-card { |
|||
background: radial-gradient(circle at 78% 5%, rgba(65, 188, 255, 0.75) 0, rgba(65, 188, 255, 0.25) 20%, transparent 42%), |
|||
linear-gradient(135deg, #064ec6 0%, #0b63d9 48%, #268ee5 100%) !important; |
|||
border-color: transparent; |
|||
|
|||
.card-header .status { |
|||
color: #f0b66c !important; |
|||
} |
|||
|
|||
.plan-title { |
|||
color: #fff; |
|||
} |
|||
|
|||
.info-row .label { |
|||
color: #fff; |
|||
} |
|||
|
|||
.info-row .value { |
|||
color: rgba(255, 255, 255, 0.7); |
|||
|
|||
&.msg { |
|||
color: rgba(255, 255, 255, 0.8); |
|||
} |
|||
} |
|||
|
|||
.card-actions { |
|||
border-top-color: rgba(255, 255, 255, 0.18); |
|||
} |
|||
|
|||
.message-action { |
|||
color: rgba(255, 255, 255, 0.85); |
|||
} |
|||
|
|||
.approve-action { |
|||
color: #fff; |
|||
} |
|||
|
|||
.reject-action { |
|||
color: rgba(255, 255, 255, 0.8); |
|||
} |
|||
} |
|||
|
|||
.card-header { |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
align-items: center; |
|||
margin-bottom: 14px; |
|||
} |
|||
|
|||
.status { |
|||
font-size: 14px; |
|||
font-weight: 500; |
|||
|
|||
&.blue { color: #1f75c9; } |
|||
&.green { color: #07c160; } |
|||
&.red { color: #ee0a24; } |
|||
&.gray { color: #999; } |
|||
} |
|||
|
|||
.plan-title { |
|||
font-size: 16px; |
|||
font-weight: 700; |
|||
color: #111; |
|||
line-height: 1.5; |
|||
margin-bottom: 12px; |
|||
white-space: nowrap; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
} |
|||
|
|||
.info-row { |
|||
display: flex; |
|||
align-items: flex-start; |
|||
margin-bottom: 8px; |
|||
font-size: 14px; |
|||
line-height: 1.5; |
|||
} |
|||
|
|||
.label { |
|||
width: 72px; |
|||
flex-shrink: 0; |
|||
color: #333; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.value { |
|||
flex: 1; |
|||
color: #8a8a8a; |
|||
|
|||
&.msg { |
|||
color: #666; |
|||
font-style: italic; |
|||
} |
|||
} |
|||
|
|||
.card-actions { |
|||
margin-top: 14px; |
|||
padding-top: 12px; |
|||
border-top: 1px solid rgba(0, 0, 0, 0.06); |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
} |
|||
|
|||
.actions-left { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.actions-right { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 24px; |
|||
} |
|||
|
|||
.message-action { |
|||
color: #1f75c9; |
|||
} |
|||
|
|||
.action-item { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 4px; |
|||
font-size: 14px; |
|||
cursor: pointer; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.approve-action { |
|||
color: #07c160; |
|||
} |
|||
|
|||
.reject-action { |
|||
color: #ee0a24; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,220 @@ |
|||
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 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.util.*; |
|||
import java.util.stream.Collectors; |
|||
|
|||
/** |
|||
* 预约记录批量生成测试 |
|||
* <p> |
|||
* 基于已创建的计划数据,为多个用户交替创建 efcode_cp_reserve 预约记录。 |
|||
* 模拟场景:用户A的计划被用户B预约,用户B的计划被用户C预约...循环交替。 |
|||
* 预约状态交替:待确认(3)、已同意(1)、已拒绝(2) |
|||
* 预约来源交替:手动预约(0)、系统匹配(1) |
|||
* </p> |
|||
*/ |
|||
@SpringBootTest(classes = ZdxtAdminApplication.class) |
|||
@AutoConfigureMockMvc |
|||
@ActiveProfiles({"dev", "dev-cust-zrb"}) |
|||
@DisplayName("AppEnfCpReserve 批量生成预约记录测试") |
|||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) |
|||
@Tag("dev") |
|||
public class AppEnfCpReserveTest { |
|||
|
|||
@Autowired |
|||
private EfcodeCpPlanMapper cpPlanMapper; |
|||
|
|||
@Autowired |
|||
private EfcodeCpReserveMapper cpReserveMapper; |
|||
|
|||
/** |
|||
* 测试账号对应的 userId(需从数据库匹配) |
|||
* 这里通过查询 plan 表的 create_by 字段获取对应用户ID |
|||
*/ |
|||
private static final String[] TEST_ACCOUNTS = {"liuqianlin", "baining", "caizifan", "chenchong4"}; |
|||
|
|||
/** 每个用户最多取多少条计划来生成预约 */ |
|||
private static final int MAX_PLANS_PER_USER = 50; |
|||
|
|||
/** 预约留言模板 */ |
|||
private static final String[] MESSAGES = { |
|||
"希望联合执法,请确认时间", |
|||
"我单位有相关检查需求,申请联合", |
|||
"建议合并检查,减轻企业负担", |
|||
"时间安排一致,申请联合执法", |
|||
"", // 部分无留言
|
|||
"请尽快确认预约", |
|||
"", |
|||
"我方已做好准备,等待确认" |
|||
}; |
|||
|
|||
@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("基于已有计划批量生成交替预约记录") |
|||
@Test |
|||
@Order(1) |
|||
public void testBatchCreateReserves() { |
|||
// 1. 收集每个用户的计划(通过 enforce_user_names LIKE 账号中文名识别)
|
|||
// 实际通过 create_by 字段分组获取不同用户的计划
|
|||
Map<Long, List<EfcodeCpPlan>> userPlansMap = loadUserPlansMap(); |
|||
|
|||
List<Long> userIds = new ArrayList<>(userPlansMap.keySet()); |
|||
Assertions.assertTrue(userIds.size() >= 2, "至少需要2个用户的计划数据才能交叉预约"); |
|||
|
|||
System.out.println("========== 检测到 " + userIds.size() + " 个用户的计划数据 =========="); |
|||
for (Long uid : userIds) { |
|||
System.out.println(" userId=" + uid + ", 计划数=" + userPlansMap.get(uid).size()); |
|||
} |
|||
|
|||
Random random = new Random(42); // 固定种子,可重复
|
|||
int totalCreated = 0; |
|||
int reserveIndex = 0; |
|||
|
|||
// 2. 交替为每个用户的计划创建来自其他用户的预约
|
|||
for (int ownerIdx = 0; ownerIdx < userIds.size(); ownerIdx++) { |
|||
Long ownerUserId = userIds.get(ownerIdx); |
|||
List<EfcodeCpPlan> ownerPlans = userPlansMap.get(ownerUserId); |
|||
|
|||
// 限制数量
|
|||
int planCount = Math.min(ownerPlans.size(), MAX_PLANS_PER_USER); |
|||
|
|||
for (int planIdx = 0; planIdx < planCount; planIdx++) { |
|||
EfcodeCpPlan targetPlan = ownerPlans.get(planIdx); |
|||
|
|||
// 选择预约发起者:循环选取其他用户
|
|||
int reserverIdx = (ownerIdx + 1 + planIdx) % userIds.size(); |
|||
if (reserverIdx == ownerIdx) { |
|||
reserverIdx = (reserverIdx + 1) % userIds.size(); |
|||
} |
|||
Long reserverUserId = userIds.get(reserverIdx); |
|||
List<EfcodeCpPlan> reserverPlans = userPlansMap.get(reserverUserId); |
|||
|
|||
// 找预约者匹配的计划(同日期+同时段+同企业),如果没有就用预约者的任意一条计划
|
|||
EfcodeCpPlan reserverPlan = findMatchingPlan(reserverPlans, targetPlan); |
|||
if (reserverPlan == null && !reserverPlans.isEmpty()) { |
|||
reserverPlan = reserverPlans.get(planIdx % reserverPlans.size()); |
|||
} |
|||
if (reserverPlan == null) continue; |
|||
|
|||
// 3. 确定 planGroupId
|
|||
String planGroupId = targetPlan.getId(); |
|||
|
|||
// 4. 交替 opStatus: 3(待确认), 1(已同意), 2(已拒绝), 3, 1, 2...
|
|||
long opStatus = getAlternatingStatus(reserveIndex); |
|||
// 5. 交替 resFrom: 0(手动), 1(系统)
|
|||
long resFrom = reserveIndex % 2; |
|||
// 6. 留言
|
|||
String message = MESSAGES[reserveIndex % MESSAGES.length]; |
|||
|
|||
// 插入目标计划方的记录(计划拥有者)
|
|||
insertReserve(targetPlan.getId(), planGroupId, "", targetPlan.getPlanDate(), |
|||
targetPlan.getPlanTime(), resFrom, opStatus); |
|||
|
|||
// 插入预约发起方的记录
|
|||
insertReserve(reserverPlan.getId(), planGroupId, message, targetPlan.getPlanDate(), |
|||
targetPlan.getPlanTime(), resFrom, 3L); // 发起方始终待确认
|
|||
|
|||
totalCreated += 2; |
|||
reserveIndex++; |
|||
} |
|||
|
|||
System.out.println("[userId=" + ownerUserId + "] 已为其 " + planCount + " 条计划生成预约记录"); |
|||
} |
|||
|
|||
System.out.println("========== 预约记录生成完成 =========="); |
|||
System.out.println("总计插入: " + totalCreated + " 条预约记录"); |
|||
Assertions.assertTrue(totalCreated > 0, "应至少生成1条预约记录"); |
|||
} |
|||
|
|||
/** |
|||
* 从数据库加载各用户的计划,按 create_by 分组 |
|||
* 只取未删除、reserve_status=1(待预约)的计划 |
|||
*/ |
|||
private Map<Long, List<EfcodeCpPlan>> loadUserPlansMap() { |
|||
LambdaQueryWrapper<EfcodeCpPlan> lqw = Wrappers.lambdaQuery(); |
|||
lqw.eq(EfcodeCpPlan::getIsDelete, 0L); |
|||
lqw.eq(EfcodeCpPlan::getReserveStatus, 1L); // 待预约状态
|
|||
lqw.like(EfcodeCpPlan::getEnforceContent, "批量测试执法事项"); // 只取测试数据
|
|||
lqw.orderByAsc(EfcodeCpPlan::getPlanDate); |
|||
List<EfcodeCpPlan> allPlans = cpPlanMapper.selectList(lqw); |
|||
|
|||
return allPlans.stream() |
|||
.filter(p -> p.getCreateBy() != null) |
|||
.collect(Collectors.groupingBy(EfcodeCpPlan::getCreateBy)); |
|||
} |
|||
|
|||
/** |
|||
* 在预约者的计划列表中查找与目标计划匹配的计划(同日期+同时段+同企业) |
|||
*/ |
|||
private EfcodeCpPlan findMatchingPlan(List<EfcodeCpPlan> plans, EfcodeCpPlan target) { |
|||
if (plans == null || plans.isEmpty()) return null; |
|||
for (EfcodeCpPlan p : plans) { |
|||
if (p.getPlanDate() != null && p.getPlanDate().equals(target.getPlanDate()) |
|||
&& Objects.equals(p.getPlanTime(), target.getPlanTime()) |
|||
&& Objects.equals(p.getEnterpriseId(), target.getEnterpriseId())) { |
|||
return p; |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* 交替状态: 3, 1, 2, 3, 1, 2... |
|||
*/ |
|||
private long getAlternatingStatus(int index) { |
|||
int mod = index % 3; |
|||
return mod == 0 ? 3L : (mod == 1 ? 1L : 2L); |
|||
} |
|||
|
|||
/** |
|||
* 插入一条预约记录 |
|||
*/ |
|||
private void insertReserve(String planId, String planGroupId, String opMessage, |
|||
Date opSetDate, Long opSetTime, Long resFrom, Long opStatus) { |
|||
EfcodeCpReserve record = new EfcodeCpReserve(); |
|||
record.setPlanId(planId); |
|||
record.setPlanGroupId(planGroupId); |
|||
record.setReserveTime(new Date()); |
|||
record.setOpStatus(opStatus); |
|||
record.setOpOrder(0L); |
|||
record.setOpMessage(opMessage); |
|||
record.setOpSetDate(opSetDate); |
|||
record.setOpSetTime(opSetTime); |
|||
record.setResFrom(resFrom); |
|||
cpReserveMapper.insert(record); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue