Browse Source

执法端-计划

dev-V1.0.1
庄锐波 5 months ago
parent
commit
c7042c054c
  1. 22
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnfCpReserveController.java
  2. 12
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpReserveService.java
  3. 17
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpSettingService.java
  4. 64
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpPlanServiceImpl.java
  5. 210
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpReserveServiceImpl.java
  6. 8
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpSettingServiceImpl.java
  7. 11
      zdxt-web-client/zdxt-efcode-enforcement-app/src/api/enforcement/reserve.js
  8. 172
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/reserve/cp_reserve_system.vue

22
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnfCpReserveController.java

@ -44,11 +44,25 @@ public class AppEnfCpReserveController extends EfcodeCpReserveController {
}
/**
* 同意预约
* 同意预约(系统匹配专用分支)
*
* @param id 预约记录ID
* @param opSetDate 第一个操作用户选择的日期(opOrder==0 时必传)
* @param opSetTime 第一个操作用户选择的时段(opOrder==0 时必传)
*/
@PutMapping("/approveBySystem/{id}")
public R<Void> approveBySystem(@PathVariable String id,
@RequestParam(required = false) String opSetDate,
@RequestParam(required = false) Long opSetTime) {
return cpReserveService.approveReserveBySystem(id, opSetDate, opSetTime);
}
/**
* 同意预约(手动匹配专用分支)
*/
@PutMapping("/approve/{id}")
public R<Void> approve(@PathVariable String id) {
return cpReserveService.approveReserve(id);
@PutMapping("/approveByManual/{id}")
public R<Void> approveByManual(@PathVariable String id) {
return cpReserveService.approveReserveByManual(id);
}
/**

12
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpReserveService.java

@ -89,9 +89,19 @@ public interface IEfcodeCpReserveService {
* 同意预约
*
* @param reserveId 预约记录ID
* @param opSetDate 第一个操作用户选择的日期(opOrder==0 时必传,格式 yyyy-MM-dd)
* @param opSetTime 第一个操作用户选择的时段(opOrder==0 时必传)
* @return 操作结果
*/
R<Void> approveReserve(String reserveId);
R<Void> approveReserveBySystem(String reserveId, String opSetDate, Long opSetTime);
/**
* 同意预约(手动匹配专用分支)
*
* @param reserveId 预约记录ID
* @return 操作结果
*/
R<Void> approveReserveByManual(String reserveId);
/**
* 拒绝预约

17
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpSettingService.java

@ -41,6 +41,11 @@ public interface IEfcodeCpSettingService {
*/
String SETTING_ID_MAX_INNER_UNIT_AC_PERSON_NUM = "CP_MAX_INNER_UNIT_AC_PERSON_NUM";
/**
* 设置项主键:系统匹配预约周数范围<=
*/
String SETTING_ID_SYSTEM_MATCH_WEEKS = "CP_SETTING_SYSTEM_MATCH_WEEKS";
/**
* 默认值:下阶段计划提交最晚时间
*/
@ -66,6 +71,11 @@ public interface IEfcodeCpSettingService {
*/
int DEFAULT_MAX_INNER_UNIT_AC_PERSON_NUM = 3;
/**
* 默认值:系统匹配预约周数范围
*/
int DEFAULT_SYSTEM_MATCH_WEEKS = 2;
/**
* 查询行政执法检查计划-设置
*
@ -109,6 +119,13 @@ public interface IEfcodeCpSettingService {
*/
Integer getMaxInnerUnitAcPersonNum();
/**
* 获取系统匹配预约周数范围
*
* @return 数值,未配置或无法解析时返回默认值 {@link #DEFAULT_SYSTEM_MATCH_WEEKS}
*/
Integer getSystemMatchWeeks();
/**
* 分页查询行政执法检查计划-设置列表
*

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

@ -38,9 +38,8 @@ 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.IEfcodeCpSettingService;
import com.zdxt.enforcementcode.service.IEfcodeCpTaskService;
import com.zdxt.enforcementcode.service.IEnterpriseInformationService;
import com.zdxt.enforcementcode.service.ISsztJbxx110497NewService;
@ -61,7 +60,7 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService {
private final EfcodeCpPlanMapper baseMapper;
private final EfcodeCpPlanUsersMapper efcodeCpPlanUsersMapper;
private final EfcodeCpReserveMapper efcodeCpReserveMapper;
private final EfcodeCpSettingMapper efcodeCpSettingMapper;
private final IEfcodeCpSettingService cpSettingService;
private final IEfcodeCpTaskService cpTaskService;
private final IEnterpriseInformationService enterpriseInformationService;
private final ISsztJbxx110497NewService ssztJbxx110497NewService;
@ -374,11 +373,7 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService {
}
// 条件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) {}
}
int manualMatchDays = cpSettingService.getManualMatchDays();
LocalDate planLocalDate = entity.getPlanDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
long daysUntilPlan = ChronoUnit.DAYS.between(LocalDate.now(), planLocalDate);
if (daysUntilPlan <= manualMatchDays) {
@ -590,23 +585,16 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService {
// 1. 日期校验:如果不是强制匹配,则判断当前天数是否等于 CP_SETTING_AUTO_MATCH_START
if (!forceMatch) {
String autoMatchStartDay = getSettingValue("CP_SETTING_AUTO_MATCH_START");
int autoMatchStart = cpSettingService.getAutoMatchStart();
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;
}
if (today != autoMatchStart) {
log.info("[CpPlan] 当前日期{}号,不等于设定的自动匹配日期{}号,跳过执行", today, autoMatchStart);
return resultMap;
}
}
// 2. 获取最大联合单位数量
int maxUnionUnit = 3; // 默认值
String maxUnionStr = getSettingValue("CP_MAX_UNION_UNIT");
if (StringUtils.isNotBlank(maxUnionStr)) {
maxUnionUnit = Integer.parseInt(maxUnionStr.trim());
}
int maxUnionUnit = cpSettingService.getMaxUnionUnit();
// 3. 查询待匹配的计划:plan_date > 当前时间 且 system_reserved=0, reserve_status=1, is_public=1, is_union=1
Date now = new Date();
@ -761,23 +749,20 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService {
// 1. 日期校验:如果不是强制匹配,则判断当前天数是否等于 CP_SETTING_AUTO_MATCH_START
if (!forceMatch) {
String autoMatchStartDay = getSettingValue("CP_SETTING_AUTO_MATCH_START");
int autoMatchStart = cpSettingService.getAutoMatchStart();
int today = LocalDate.now().getDayOfMonth();
if (StringUtils.isNotBlank(autoMatchStartDay)) {
int settingDay = Integer.parseInt(autoMatchStartDay.trim());
if (today != settingDay) {
log.info("[CpPlan-V2] 当前日期{}号,不等于设定的自动匹配日期{}号,跳过执行", today, settingDay);
return resultMap;
}
if (today != autoMatchStart) {
log.info("[CpPlan-V2] 当前日期{}号,不等于设定的自动匹配日期{}号,跳过执行", today, autoMatchStart);
return resultMap;
}
}
// 2. 获取最大联合单位数量
int maxUnionUnit = 3; // 默认值
String maxUnionStr = getSettingValue("CP_MAX_UNION_UNIT");
if (StringUtils.isNotBlank(maxUnionStr)) {
maxUnionUnit = Integer.parseInt(maxUnionStr.trim());
}
int maxUnionUnit = cpSettingService.getMaxUnionUnit();
// 2.1 获取系统匹配预约周数范围
int systemMatchWeeks = cpSettingService.getSystemMatchWeeks();
log.info("[CpPlan-V2] 系统匹配周数范围={}", systemMatchWeeks);
// 3. 查询待匹配的计划:plan_date > 当前时间 且 system_reserved=0, manual_reserved=0, reserve_status=1, is_public=1, is_union=1
Date now = new Date();
@ -808,10 +793,10 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService {
continue;
}
// 5. 计算计划所在的自然周(周一~周日)
// 5. 计算计划所在的时间范围:以计划所在自然周的周一为起始,向后扩展 systemMatchWeeks 周
LocalDate planLocalDate = plan.getPlanDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
LocalDate weekStartDate = planLocalDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
LocalDate weekEndDate = planLocalDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
LocalDate weekEndDate = weekStartDate.plusWeeks(systemMatchWeeks).minusDays(1); // 扩展N周后的周日
// 按"企业+周起始日"去重,同企业同周只处理一次
String processKey = enterpriseNumber + "_" + weekStartDate;
@ -938,15 +923,4 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService {
log.info("[CpPlan-V2] 计划自动匹配预约执行完毕,共匹配{}组联合预约", 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;
}
}

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

@ -382,9 +382,181 @@ public class EfcodeCpReserveServiceImpl implements IEfcodeCpReserveService {
/**
* 同意预约
* <p>
* opOrder==0 时为第一个操作的用户:
* - 必须传入 opSetDate、opSetTime(用户选择的时间)
* - 重新设置同组记录的 opOrder 为 1,2,3,4,5...
* - 更新当前记录及关联记录的 op_set_date、op_set_time
* - 生成 warringMessage,设置到关联记录(warring_is_pass=0)
* - 当前记录 warring_is_pass=1
* <p>
* opOrder>0 时为后续操作的用户:
* - 设置当前记录 warring_is_pass=1
* - 备份关联计划的 plan_date/plan_time 到 plan_source_date/plan_source_time
* - 将预约的 op_set_date/op_set_time 更新回关联计划的 plan_date/plan_time
* - 同步更新 efcode_cp_plan 的 reserve_status
*/
@Transactional(rollbackFor = Exception.class)
@Override
public R<Void> approveReserveBySystem(String reserveId, String opSetDate, Long opSetTime) {
EfcodeCpReserve reserve = baseMapper.selectById(reserveId);
if (reserve == null) {
return R.fail("预约记录不存在");
}
if (!Long.valueOf(3L).equals(reserve.getOpStatus())) {
return R.fail("该预约记录已被处理");
}
// 查询关联计划
EfcodeCpPlan myPlan = cpPlanMapper.selectById(reserve.getPlanId());
if (myPlan == null) {
return R.fail("关联计划不存在");
}
// 查询同组其他预约记录
List<EfcodeCpReserve> groupReserves = baseMapper.selectList(
Wrappers.<EfcodeCpReserve>lambdaQuery()
.eq(EfcodeCpReserve::getPlanGroupId, reserve.getPlanGroupId())
.ne(EfcodeCpReserve::getId, reserveId)
);
if (reserve.getOpOrder() != null && reserve.getOpOrder() == 0L) {
// ===== opOrder==0:第一个操作的用户 =====
if (StringUtils.isBlank(opSetDate) || opSetTime == null) {
return R.fail("请选择执行时间");
}
// 解析日期
Date selectedDate;
try {
selectedDate = new java.text.SimpleDateFormat("yyyy-MM-dd").parse(opSetDate);
} catch (Exception e) {
return R.fail("日期格式不正确");
}
// 1. 更新当前记录:同意 + 设置时间 + warring_is_pass=1
reserve.setOpStatus(1L);
reserve.setOpTime(new Date());
reserve.setOpSetDate(selectedDate);
reserve.setOpSetTime(opSetTime);
reserve.setWarringIsPass(1);
reserve.setOpOrder(1L); // 第一个操作用户排序号为1
baseMapper.updateById(reserve);
// 2. 生成 warringMessage
String timeDesc = opSetDate + " " + getPlanTimeText(opSetTime);
String warringMsg = "第一个操作的" + myPlan.getBureauDeptName() + "执法部门用户选择了" + timeDesc + "时间,请确认时间安排是否合适";
// 3. 更新关联记录:设置 op_set_date/op_set_time、warringMessage、warring_is_pass=0、重新分配 opOrder
int order = 2;
for (EfcodeCpReserve r : groupReserves) {
r.setOpSetDate(selectedDate);
r.setOpSetTime(opSetTime);
r.setWarringMessage(warringMsg);
r.setWarringIsPass(0);
r.setOpOrder((long) order++);
baseMapper.updateById(r);
}
// 4. 备份当前计划的 plan_date/plan_time 到 plan_source_date/plan_source_time,并更新为选定时间
EfcodeCpPlan planUpdate = new EfcodeCpPlan();
planUpdate.setId(myPlan.getId());
if (myPlan.getPlanSourceDate() == null) {
// 只有第一次才备份,避免多次覆盖
planUpdate.setPlanSourceDate(myPlan.getPlanDate());
planUpdate.setPlanSourceTime(myPlan.getPlanTime());
}
planUpdate.setPlanDate(selectedDate);
planUpdate.setPlanTime(opSetTime);
planUpdate.setReserveStatus(3L); // 预约完成
cpPlanMapper.updateById(planUpdate);
} else {
// ===== opOrder>0:后续操作的用户 =====
// 1. 设置当前记录:同意 + warring_is_pass=1
reserve.setOpStatus(1L);
reserve.setOpTime(new Date());
reserve.setWarringIsPass(1);
baseMapper.updateById(reserve);
// 2. 备份关联计划的 plan_date/plan_time 到 plan_source_date/plan_source_time
EfcodeCpPlan planUpdate = new EfcodeCpPlan();
planUpdate.setId(myPlan.getId());
if (myPlan.getPlanSourceDate() == null) {
// 只有第一次才备份,避免多次覆盖
planUpdate.setPlanSourceDate(myPlan.getPlanDate());
planUpdate.setPlanSourceTime(myPlan.getPlanTime());
}
// 3. 将预约的 op_set_date/op_set_time 更新回关联计划的 plan_date/plan_time
planUpdate.setPlanDate(reserve.getOpSetDate());
planUpdate.setPlanTime(reserve.getOpSetTime());
planUpdate.setReserveStatus(3L); // 预约完成
cpPlanMapper.updateById(planUpdate);
}
// 后置处理:检查是否所有同组记录都已同意,若是则生成任务
afterApproveCheck(reserve);
return R.ok();
}
/**
* 同意操作后置检查:检查同组是否全部同意,若是则生成任务
*/
private void afterApproveCheck(EfcodeCpReserve reserve) {
if (StringUtils.isBlank(reserve.getPlanGroupId())) {
return;
}
// 查询同组全部记录
List<EfcodeCpReserve> allGroupReserves = baseMapper.selectList(
Wrappers.<EfcodeCpReserve>lambdaQuery()
.eq(EfcodeCpReserve::getPlanGroupId, reserve.getPlanGroupId())
);
// 检查是否全部同意
boolean allApproved = allGroupReserves.stream()
.allMatch(r -> Long.valueOf(1L).equals(r.getOpStatus()));
if (!allApproved) {
log.info("[预约后置] planGroupId={} 尚有未同意的记录,等待其他用户操作", reserve.getPlanGroupId());
return;
}
// 全部同意:生成1条任务,所有同组计划共享该 task_id
String sharedTaskId = null;
for (EfcodeCpReserve r : allGroupReserves) {
EfcodeCpPlan plan = cpPlanMapper.selectById(r.getPlanId());
if (plan == null || StringUtils.isNotBlank(plan.getTaskId())) continue;
if (sharedTaskId == null) {
sharedTaskId = cpTaskService.forceGenerateTask(plan);
} else {
EfcodeCpPlan planUpdate = new EfcodeCpPlan();
planUpdate.setId(plan.getId());
planUpdate.setTaskId(sharedTaskId);
planUpdate.setReserveStatus(3L);
cpPlanMapper.updateById(planUpdate);
}
}
log.info("[预约后置] planGroupId={} 全部同意,生成任务taskId={},关联{}条计划",
reserve.getPlanGroupId(), sharedTaskId, allGroupReserves.size());
}
/**
* 时段文本
*/
private String getPlanTimeText(Long time) {
if (time == null) return "";
switch (time.intValue()) {
case 1: return "上午";
case 2: return "下午";
case 3: return "其他";
default: return "";
}
}
/**
* 同意预约(手动匹配专用分支)
*/
@Override
public R<Void> approveReserve(String reserveId) {
public R<Void> approveReserveByManual(String reserveId) {
EfcodeCpReserve reserve = baseMapper.selectById(reserveId);
if (reserve == null) {
return R.fail("预约记录不存在");
@ -436,42 +608,6 @@ public class EfcodeCpReserveServiceImpl implements IEfcodeCpReserveService {
cpPlanMapper.updateById(planUpdate);
log.info("[预约后置] 计划[{}] reserve_status 更新为 {}", reserve.getPlanId(), newPlanStatus);
}
// 同意操作后:检查同一 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) {
// 达标:只生成1条任务,所有同组计划共享该task_id
List<EfcodeCpReserve> groupReserves = baseMapper.selectList(
Wrappers.<EfcodeCpReserve>lambdaQuery()
.eq(EfcodeCpReserve::getPlanGroupId, reserve.getPlanGroupId())
.eq(EfcodeCpReserve::getOpStatus, 1L)
);
String sharedTaskId = null;
for (EfcodeCpReserve r : groupReserves) {
EfcodeCpPlan plan = cpPlanMapper.selectById(r.getPlanId());
if (plan == null || StringUtils.isNotBlank(plan.getTaskId())) continue;
if (sharedTaskId == null) {
// 用第一条计划生成唯一一条任务
sharedTaskId = cpTaskService.forceGenerateTask(plan);
} else {
// 其余计划直接回写同一个task_id
EfcodeCpPlan planUpdate = new EfcodeCpPlan();
planUpdate.setId(plan.getId());
planUpdate.setTaskId(sharedTaskId);
planUpdate.setReserveStatus(3L);
cpPlanMapper.updateById(planUpdate);
}
}
log.info("[预约后置] planGroupId={} 联合数量达标,生成1条任务taskId={},关联{}条计划", reserve.getPlanGroupId(), sharedTaskId, groupReserves.size());
}
}
}
/**

8
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpSettingServiceImpl.java

@ -84,6 +84,14 @@ public class EfcodeCpSettingServiceImpl implements IEfcodeCpSettingService {
return getIntSetting(SETTING_ID_MAX_INNER_UNIT_AC_PERSON_NUM, DEFAULT_MAX_INNER_UNIT_AC_PERSON_NUM);
}
/**
* 获取系统匹配预约周数范围
*/
@Override
public Integer getSystemMatchWeeks() {
return getIntSetting(SETTING_ID_SYSTEM_MATCH_WEEKS, DEFAULT_SYSTEM_MATCH_WEEKS);
}
/**
* 根据设置主键获取整型配置值,不存在或解析失败时返回默认值
*

11
zdxt-web-client/zdxt-efcode-enforcement-app/src/api/enforcement/reserve.js

@ -7,8 +7,15 @@ 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 approveReserve = (id, params, success, error) => {
let url = reserveBaseUrl + "/approveBySystem/" + id;
if (params) {
const qs = Object.entries(params).filter(([, v]) => v != null).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&');
if (qs) url += '?' + qs;
}
return _put(url, {}, success, error);
};
// 预约处理:拒绝
export const rejectReserve = (id, success, error) => _put(reserveBaseUrl + "/reject/" + id, {}, success, error);

172
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/reserve/cp_reserve_system.vue

@ -43,7 +43,7 @@
<div class="info-row">
<div class="label">计划时间:</div>
<div class="value">{{ item.planDate || '-' }} {{ getPlanTimeText(item.planTime) }}</div>
<div class="value">{{ formatDateShort(item.planDate) }} {{ getPlanTimeText(item.planTime) }}</div>
</div>
<div class="info-row">
@ -70,7 +70,7 @@
</div>
<div class="member-row">
<span class="member-label">计划时间:</span>
<span class="member-value">{{ formatDate(member.planDate) }} {{ getPlanTimeText(member.planTime) }}</span>
<span class="member-value">{{ formatDateShort(member.planDate) }} {{ getPlanTimeText(member.planTime) }}</span>
</div>
<div v-if="idx < groupMembers.length - 1" class="member-divider"></div>
</div>
@ -103,6 +103,30 @@
<van-empty v-if="!loading && reserveList.length === 0" description="暂无系统匹配记录" />
</van-list>
</van-pull-refresh>
<!-- 时间选择弹窗(opOrder==0 时弹出) -->
<van-action-sheet v-model:show="showTimePicker" title="请选择执行计划的时间">
<div class="time-picker-content">
<div
v-for="(opt, idx) in timeOptions"
:key="idx"
class="time-option"
:class="{ 'time-option-selected': selectedTimeIdx === idx }"
@click="selectedTimeIdx = idx"
>
<div class="time-option-unit">{{ opt.bureauDeptName }}</div>
<div class="time-option-date">{{ opt.dateLabel }}</div>
</div>
<div v-if="timeOptions.length === 0" class="time-option-empty">没有可选的时间</div>
<van-button
type="primary"
block
:disabled="selectedTimeIdx === null"
class="time-confirm-btn"
@click="onConfirmTimeSelection"
>确认选择</van-button>
</div>
</van-action-sheet>
</div>
</template>
@ -121,6 +145,12 @@ const pageNum = ref(1);
const pageSize = 10;
const filterStatus = ref(null); // null=全部, 1=已同意, 2=已拒绝, 3=待确认
// 时间选择相关
const showTimePicker = ref(false);
const timeOptions = ref([]); // [{ bureauDeptName, dateLabel, planDate, planTime }]
const selectedTimeIdx = ref(null);
const pendingApproveItem = ref(null); // 待同意的item缓存
const filterOptions = [
{ label: '全部', value: null },
{ label: '待确认', value: 3 },
@ -146,7 +176,7 @@ const getPlanTimeText = (time) => {
return map[time] || '';
};
// 格式化日期
// 格式化日期(带时分)
const formatDate = (dateStr) => {
if (!dateStr) return '-';
const d = new Date(dateStr);
@ -159,6 +189,17 @@ const formatDate = (dateStr) => {
return `${y}-${m}-${day} ${h}:${min}`;
};
// 格式化日期(仅日期)
const formatDateShort = (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');
return `${y}-${m}-${day}`;
};
// 加载数据
const onLoad = () => {
fetchList();
@ -251,19 +292,70 @@ const onShowMessage = (item) => {
// 同意
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 || '操作失败' });
}
if (item.opOrder === 0 || item.opOrder === null) {
// opOrder==0:第一个操作的用户,弹出时间选择
buildTimeOptions(item);
pendingApproveItem.value = item;
selectedTimeIdx.value = null;
showTimePicker.value = true;
} else {
// opOrder>0:后续用户,弹出 warringMessage 提示
const msg = item.warringMessage || '确定同意该系统匹配吗?';
showConfirmDialog({
title: '时间确认',
message: msg,
confirmButtonText: '确认同意',
cancelButtonText: '取消',
}).then(() => {
doApprove(item.id, null);
}).catch(() => {});
}
};
// 构建时间选项(从当前卡片+关联计划中提取可选的日期/时段)
const buildTimeOptions = (item) => {
const options = [];
// 当前卡片本身的计划时间
if (item.planDate) {
options.push({
bureauDeptName: item.bureauDeptName || '本单位',
dateLabel: formatDateShort(item.planDate) + ' ' + getPlanTimeText(item.planTime),
planDate: formatDateShort(item.planDate),
planTime: item.planTime,
});
}).catch(() => {});
}
// 关联计划的时间
for (const member of groupMembers.value) {
if (member.planDate) {
options.push({
bureauDeptName: member.bureauDeptName || '-',
dateLabel: formatDateShort(member.planDate) + ' ' + getPlanTimeText(member.planTime),
planDate: formatDateShort(member.planDate),
planTime: member.planTime,
});
}
}
timeOptions.value = options;
};
// 确认时间选择后提交同意
const onConfirmTimeSelection = () => {
if (selectedTimeIdx.value === null || !pendingApproveItem.value) return;
const selected = timeOptions.value[selectedTimeIdx.value];
showTimePicker.value = false;
doApprove(pendingApproveItem.value.id, { opSetDate: selected.planDate, opSetTime: selected.planTime });
};
// 执行同意接口调用
const doApprove = (id, params) => {
approveReserve(id, params, (res) => {
if (res.code === 200) {
showSuccessToast('已同意');
afterOperate();
} else {
showDialog({ title: '提示', message: res.msg || '操作失败' });
}
});
};
// 拒绝
@ -275,7 +367,7 @@ const onReject = (item) => {
rejectReserve(item.id, (res) => {
if (res.code === 200) {
showSuccessToast('已拒绝');
afterOperate(item, 'reject');
afterOperate();
} else {
showDialog({ title: '提示', message: res.msg || '操作失败' });
}
@ -284,9 +376,10 @@ const onReject = (item) => {
};
// 操作后置处理
const afterOperate = (item, action) => {
// TODO: 可在此处扩展后置逻辑,如通知、跳转等
const afterOperate = () => {
selectedId.value = null;
groupMembers.value = [];
pendingApproveItem.value = null;
onRefresh();
};
</script>
@ -532,4 +625,47 @@ const afterOperate = (item, action) => {
color: rgba(255, 255, 255, 0.6);
text-align: center;
}
.time-picker-content {
padding: 16px;
}
.time-option {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 16px;
margin-bottom: 10px;
border-radius: 10px;
background: #f5f7fa;
border: 2px solid transparent;
transition: all 0.2s;
&.time-option-selected {
background: #e8f4ff;
border-color: #1f75c9;
}
}
.time-option-unit {
font-size: 14px;
font-weight: 600;
color: #333;
}
.time-option-date {
font-size: 14px;
color: #666;
}
.time-option-empty {
text-align: center;
color: #999;
padding: 20px;
font-size: 14px;
}
.time-confirm-btn {
margin-top: 16px;
}
</style>

Loading…
Cancel
Save