场地预约问题
This commit is contained in:
@@ -5,6 +5,9 @@ import io.v.nutz.activity.models.ActivitySiteInfo;
|
||||
import io.v.nutz.activity.services.SiteInfoService;
|
||||
import io.v.nutz.annontation.ViReturn;
|
||||
import io.v.nutz.base.utils.PageUtil;
|
||||
import cn.wizzer.framework.base.Result;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.dao.Cnd;
|
||||
@@ -62,18 +65,28 @@ public class SiteManageController {
|
||||
return siteInfoService.listPage(pageNumber, pageSize, cnd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param activitySiteInfo data为场地表单,reserve_deadline传yyyy-MM-dd或空值(不限制)
|
||||
* @return 保存成功沿用ViReturn默认成功结构,校验失败返回Result错误信息
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object doAdd(@Param("data") ActivitySiteInfo activitySiteInfo) {
|
||||
siteInfoService.insert(activitySiteInfo);
|
||||
return null;
|
||||
String error = siteInfoService.saveSite(activitySiteInfo, false);
|
||||
return error == null ? null : Result.error(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param activitySiteInfo data为带id的场地表单,reserve_deadline空值表示清除截止日期
|
||||
* @return 保存成功沿用ViReturn默认成功结构,校验失败返回Result错误信息
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object doEdit(@Param("data") ActivitySiteInfo activitySiteInfo) {
|
||||
siteInfoService.updateIgnoreNull(activitySiteInfo);
|
||||
return null;
|
||||
String error = siteInfoService.saveSite(activitySiteInfo, true);
|
||||
return error == null ? null : Result.error(error);
|
||||
}
|
||||
|
||||
@At
|
||||
|
||||
@@ -7,6 +7,9 @@ import io.v.nutz.activity.models.ActivitySiteInfo;
|
||||
import io.v.nutz.activity.models.ActivitySiteReserve;
|
||||
import io.v.nutz.activity.services.ActivityCommonService;
|
||||
import io.v.nutz.activity.services.SiteInfoService;
|
||||
import io.v.nutz.sys.services.SysHolidayService;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import io.v.nutz.activity.services.SiteReserveService;
|
||||
import io.v.nutz.annontation.ViReturn;
|
||||
import io.v.nutz.base.utils.PageUtil;
|
||||
@@ -53,6 +56,19 @@ public class SiteReserveController {
|
||||
@Inject
|
||||
private SiteInfoService siteInfoService;
|
||||
|
||||
@Inject
|
||||
private SysHolidayService sysHolidayService;
|
||||
|
||||
/**
|
||||
* 无请求参数。返回日历对象:days为yyyy-MM-dd节假日列表,years为已同步年份列表。
|
||||
* PC和手机使用同一本地数据源,预约请求不访问外部接口。
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
public Object holidayCalendar() {
|
||||
return sysHolidayService.getCalendar();
|
||||
}
|
||||
|
||||
@Inject
|
||||
private SiteReserveService siteReserveService;
|
||||
|
||||
@@ -152,8 +168,14 @@ public class SiteReserveController {
|
||||
return fetch.getReserve_state() > stateCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param activitySiteReserve data提交预约表单,包含site_id及起止时间等原有字段
|
||||
* @param days days提交yyyy-MM-dd日期数组,必须整批通过日期限制后才保存
|
||||
* @return 校验失败返回Result错误对象;成功保持原接口由ViReturn包装的结果结构
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object doAdd(@Param("data") ActivitySiteReserve activitySiteReserve, @Param("days") String[] days) {
|
||||
|
||||
String r = R.UU32();
|
||||
@@ -167,10 +189,12 @@ public class SiteReserveController {
|
||||
}
|
||||
|
||||
ActivitySiteInfo siteInfo = siteInfoService.fetch(activitySiteReserve.getSite_id());
|
||||
// 在第一条记录写入前校验全部日期,避免后面的节假日导致前面的日期已被保存。
|
||||
String dateError = siteInfoService.validateReserveDays(siteInfo, days == null ? null : Arrays.asList(days));
|
||||
if (dateError != null) {
|
||||
return Result.error(dateError);
|
||||
}
|
||||
for (String d : days) {
|
||||
if (siteInfoService.isInCloseDates(siteInfo, d)) {
|
||||
return Result.error(siteInfoService.getCloseDateMessage(siteInfo));
|
||||
}
|
||||
activitySiteReserve.setReserve_day(d);
|
||||
//查询这个人是否已经预约这个时间段
|
||||
int count = siteReserveService.count("activity_site_reserve", Cnd.NEW().and("reserve_person_id", "=", ShiroUtil.getPrincipalProperty("id"))
|
||||
|
||||
@@ -57,6 +57,11 @@ public class ActivitySiteInfo {
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
private List close_dates;
|
||||
|
||||
@Column
|
||||
@Comment("最后可预约日期,yyyy-MM-dd,含当天;空值表示不限制")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 10)
|
||||
private String reserve_deadline;
|
||||
|
||||
@Column
|
||||
@Comment("创建人")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
|
||||
@@ -6,6 +6,22 @@ import io.v.nutz.activity.models.ActivitySiteInfo;
|
||||
|
||||
public interface SiteInfoService extends BaseService<ActivitySiteInfo> {
|
||||
|
||||
/**
|
||||
* 校验并保存场地,清空截止日期时显式保存空字符串,避免忽略null更新保留旧值。
|
||||
* @param siteInfo 场地表单,reserve_deadline为yyyy-MM-dd,null或空串表示不限制
|
||||
* @param edit true表示编辑已有场地(必须提供id),false表示新增
|
||||
* @return null表示保存成功,非空字符串为校验失败原因,校验失败不写入
|
||||
*/
|
||||
String saveSite(ActivitySiteInfo siteInfo, boolean edit);
|
||||
|
||||
/**
|
||||
* 在写入预约记录前检查整批日期,截止日期和关闭时段始终生效,workday开启时额外排除周末及节假日。
|
||||
* @param siteInfo 从数据库读取的场地,不能使用客户端传入的开关
|
||||
* @param days 预约日期列表,每项必须为 yyyy-MM-dd
|
||||
* @return null 表示全部日期允许预约,否则返回第一条拒绝原因
|
||||
*/
|
||||
String validateReserveDays(ActivitySiteInfo siteInfo, java.util.List<String> days);
|
||||
|
||||
/**
|
||||
* 判断指定预约日期是否落在场地关闭时段内。
|
||||
*
|
||||
|
||||
@@ -4,9 +4,14 @@ package io.v.nutz.activity.services.impl;
|
||||
import cn.wizzer.framework.base.service.BaseServiceImpl;
|
||||
import io.v.nutz.activity.models.ActivitySiteInfo;
|
||||
import io.v.nutz.activity.services.SiteInfoService;
|
||||
import io.v.nutz.sys.services.SysHolidayService;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
@@ -20,6 +25,85 @@ import java.util.List;
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class SiteInfoServiceImpl extends BaseServiceImpl<ActivitySiteInfo> implements SiteInfoService {
|
||||
|
||||
@Inject
|
||||
private SysHolidayService sysHolidayService;
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public String saveSite(ActivitySiteInfo siteInfo, boolean edit) {
|
||||
if (siteInfo == null || (edit && Strings.isBlank(siteInfo.getId()))) {
|
||||
return "场地信息不完整,请刷新后重试";
|
||||
}
|
||||
String deadline = Strings.sNull(siteInfo.getReserve_deadline()).trim();
|
||||
if (!deadline.isEmpty()) {
|
||||
try {
|
||||
if (!LocalDate.parse(deadline).toString().equals(deadline)) {
|
||||
return "预约截止日期格式必须为yyyy-MM-dd";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return "预约截止日期格式必须为yyyy-MM-dd";
|
||||
}
|
||||
}
|
||||
// 日期框清空返回null;转为空串后updateIgnoreNull才能真正清除原来的截止日期。
|
||||
siteInfo.setReserve_deadline(deadline);
|
||||
if (edit) {
|
||||
updateIgnoreNull(siteInfo);
|
||||
} else {
|
||||
insert(siteInfo);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String validateReserveDays(ActivitySiteInfo siteInfo, List<String> days) {
|
||||
if (siteInfo == null) {
|
||||
return "场地不存在,请刷新后重试";
|
||||
}
|
||||
if (days == null || days.isEmpty()) {
|
||||
return "请选择预约日期";
|
||||
}
|
||||
LocalDate deadline = null;
|
||||
if (!Strings.isBlank(siteInfo.getReserve_deadline())) {
|
||||
try {
|
||||
deadline = LocalDate.parse(siteInfo.getReserve_deadline());
|
||||
} catch (Exception e) {
|
||||
return "场地预约截止日期配置异常,请联系管理员";
|
||||
}
|
||||
}
|
||||
// 整批只加载一次日历;未开启限定工作日时,不依赖节假日同步状态。
|
||||
NutMap calendar = Boolean.TRUE.equals(siteInfo.getWorkday()) ? sysHolidayService.getCalendar() : null;
|
||||
for (String day : days) {
|
||||
LocalDate date;
|
||||
try {
|
||||
date = LocalDate.parse(day);
|
||||
if (!date.toString().equals(day)) {
|
||||
return "预约日期格式必须为yyyy-MM-dd";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return "预约日期格式必须为yyyy-MM-dd";
|
||||
}
|
||||
// 截止当天仍可预约,只有晚于截止日才拒绝;与限定工作日开关无关。
|
||||
if (deadline != null && date.isAfter(deadline)) {
|
||||
return "该场地最晚可预约至" + deadline + ",请选择截止日期当天或之前的日期";
|
||||
}
|
||||
if (isInCloseDates(siteInfo, day)) {
|
||||
return getCloseDateMessage(siteInfo);
|
||||
}
|
||||
if (calendar != null) {
|
||||
if (date.getDayOfWeek().getValue() >= 6) {
|
||||
return day + "为周末,该场地仅限工作日预约";
|
||||
}
|
||||
if (!((List<?>) calendar.get("years")).contains(date.getYear())) {
|
||||
return date.getYear() + "年节假日尚未同步,暂不能预约,请联系管理员";
|
||||
}
|
||||
if (((List<?>) calendar.get("days")).contains(day)) {
|
||||
return day + "为节假日,该场地仅限工作日预约";
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public SiteInfoServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ import io.v.nutz.activity.models.ActivitySiteInfo;
|
||||
import io.v.nutz.activity.models.ActivitySiteReserve;
|
||||
import io.v.nutz.activity.services.ActivityCommonService;
|
||||
import io.v.nutz.activity.services.SiteInfoService;
|
||||
import io.v.nutz.sys.services.SysHolidayService;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import io.v.nutz.activity.services.SiteReserveService;
|
||||
import io.v.nutz.annontation.ViReturn;
|
||||
import io.v.nutz.base.utils.SendMsg;
|
||||
@@ -50,6 +53,19 @@ public class SiteInfoMobileController {
|
||||
@Inject
|
||||
private SiteInfoService siteInfoService;
|
||||
|
||||
@Inject
|
||||
private SysHolidayService sysHolidayService;
|
||||
|
||||
/**
|
||||
* 无请求参数。返回日历对象:days为yyyy-MM-dd节假日列表,years为已同步年份列表。
|
||||
* PC和手机使用同一本地数据源,预约请求不访问外部接口。
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
public Object holidayCalendar() {
|
||||
return sysHolidayService.getCalendar();
|
||||
}
|
||||
|
||||
@Inject
|
||||
private SiteReserveService siteReserveService;
|
||||
|
||||
@@ -238,9 +254,16 @@ public class SiteInfoMobileController {
|
||||
return hours;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param siteId 预约场地ID,限制开关以数据库场地配置为准
|
||||
* @param times JSON数组,每项包含day(yyyy-MM-dd)、start_time、end_time
|
||||
* @param message 预约事由
|
||||
* @return 校验失败返回Result错误对象;成功保持原接口由ViReturn包装的结果结构
|
||||
*/
|
||||
@At("/reserveDo")
|
||||
@Ok("json:full")
|
||||
@ViReturn
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object reserveDo(String siteId, String times, String message) {
|
||||
String moduleName = activityCommonService.findModuleNameByActivityId(siteId);
|
||||
Integer stateCode = activityCommonService.findStartStateCodeByModuleName(moduleName);
|
||||
@@ -254,13 +277,17 @@ public class SiteInfoMobileController {
|
||||
|
||||
ActivitySiteInfo siteInfo = siteInfoService.fetch(siteId);
|
||||
List<NutMap> nutMaps = Json.fromJsonAsList(NutMap.class, times);
|
||||
// 校验整批日期后再构造和保存预约,客户端传来的日期不能绕过工作日限制。
|
||||
List<String> reserveDays = nutMaps == null ? null : nutMaps.stream()
|
||||
.map(item -> item == null ? null : item.getString("day")).collect(Collectors.toList());
|
||||
String dateError = siteInfoService.validateReserveDays(siteInfo, reserveDays);
|
||||
if (dateError != null) {
|
||||
return Result.error(dateError);
|
||||
}
|
||||
String str = "";
|
||||
String r = R.UU32();
|
||||
for (NutMap item : nutMaps) {
|
||||
String reserveDay = item.getString("day");
|
||||
if (siteInfoService.isInCloseDates(siteInfo, reserveDay)) {
|
||||
return Result.error(siteInfoService.getCloseDateMessage(siteInfo));
|
||||
}
|
||||
ActivitySiteReserve as = new ActivitySiteReserve();
|
||||
as.setSqid(r);
|
||||
as.setSite_id(siteId);
|
||||
|
||||
@@ -268,17 +268,15 @@ layout("/mobile/platform.html"){
|
||||
times: [],
|
||||
weekList: [],
|
||||
calendarVisible: false,
|
||||
selectDate: null,
|
||||
selectDate: moment(),
|
||||
//已经预约的记录
|
||||
alreadyReserveRecord: [],
|
||||
holidays: [
|
||||
'2025-01-01',
|
||||
'2025-01-28','2025-01-29','2025-01-30','2025-01-31','2025-02-01','2025-02-02','2025-02-03','2025-02-04',
|
||||
'2025-04-04', '2025-04-05', '2025-04-06',
|
||||
'2025-05-01', '2025-05-02', '2025-05-03', '2025-05-04', '2025-05-05',
|
||||
'2025-05-31', '2025-06-01', '2025-06-02',
|
||||
'2025-10-01', '2025-10-02', '2025-10-03', '2025-10-04', '2025-10-05', '2025-10-06', '2025-10-07', '2025-10-08'
|
||||
],
|
||||
holidays: [],
|
||||
holidayYears: [],
|
||||
holidayReady: false,
|
||||
holidayLoading: false,
|
||||
reserveDataLoading: false,
|
||||
reservationLoading: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -306,17 +304,64 @@ layout("/mobile/platform.html"){
|
||||
calendarFormatter() {
|
||||
return (day) => {
|
||||
// 休息日不能选择
|
||||
if(this.site.workday === true) {
|
||||
let week = day.date.getDay()
|
||||
if (week === 0 || week === 6) {
|
||||
day.type = 'disabled'
|
||||
}
|
||||
if (this.getReserveDateMessage(moment(day.date).format('YYYY-MM-DD'), this.site)) {
|
||||
day.type = 'disabled'
|
||||
}
|
||||
return day
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 后端返回days日期数组及years已同步年份,加载失败时禁止按空日历放行。
|
||||
loadHolidayCalendar() {
|
||||
this.holidayReady = false
|
||||
this.holidayLoading = true
|
||||
return $.post('/mobile/activity/site/info/holidayCalendar', {})
|
||||
.then((res) => {
|
||||
if (res.code !== 0 || !res.data || !Array.isArray(res.data.days) || !Array.isArray(res.data.years)) {
|
||||
return $.Deferred().reject('节假日日历加载失败,请稍后重试').promise()
|
||||
}
|
||||
this.holidays = res.data.days
|
||||
this.holidayYears = res.data.years
|
||||
this.holidayReady = true
|
||||
})
|
||||
.always(() => {
|
||||
this.holidayLoading = false
|
||||
})
|
||||
},
|
||||
// 截止日期独立于工作日开关:截止当天允许,超过截止日禁止;原有假期规则继续生效。
|
||||
getReserveDateMessage(day, siteData) {
|
||||
if (!siteData) {
|
||||
return ''
|
||||
}
|
||||
const date = moment(day, 'YYYY-MM-DD', true)
|
||||
if (!date.isValid()) {
|
||||
return '预约日期格式不正确'
|
||||
}
|
||||
if (siteData.reserve_deadline) {
|
||||
const deadline = moment(siteData.reserve_deadline, 'YYYY-MM-DD', true)
|
||||
if (!deadline.isValid()) {
|
||||
return '场地预约截止日期配置异常,请联系管理员'
|
||||
}
|
||||
if (date.isAfter(deadline, 'day')) {
|
||||
return '该场地最晚可预约至' + siteData.reserve_deadline + ',请选择截止日期当天或之前的日期'
|
||||
}
|
||||
}
|
||||
if (siteData.workday !== true) {
|
||||
return ''
|
||||
}
|
||||
if (date.day() === 0 || date.day() === 6) {
|
||||
return day + '为周末,该场地仅限工作日预约'
|
||||
}
|
||||
if (!this.holidayReady) {
|
||||
return '节假日日历尚未加载,请稍后重试'
|
||||
}
|
||||
if (!this.holidayYears.includes(date.year())) {
|
||||
return date.year() + '年节假日尚未同步,暂不能预约,请联系管理员'
|
||||
}
|
||||
return this.holidays.includes(day) ? day + '为节假日,该场地仅限工作日预约' : ''
|
||||
},
|
||||
|
||||
close(f) {
|
||||
if (this.times.length === 1) {
|
||||
vant.Toast('必须保留一个时间段')
|
||||
@@ -333,23 +378,18 @@ layout("/mobile/platform.html"){
|
||||
//这里要排除掉休息日
|
||||
setWeekList(startDate) {
|
||||
const wk = []
|
||||
wk.push(moment(startDate))
|
||||
let count = 1
|
||||
while (wk.length < 7) {
|
||||
const addDate = moment(startDate).add(count, 'd')
|
||||
//这里不是休息日才能加到数组里
|
||||
if (this.site.workday === true) {
|
||||
if(
|
||||
addDate.day() !== 0 &&
|
||||
addDate.day() !== 6 &&
|
||||
!this.holidays.includes(moment(addDate).format('YYYY-MM-DD'))
|
||||
) {
|
||||
wk.push(addDate)
|
||||
}
|
||||
} else {
|
||||
wk.push(addDate)
|
||||
// 包含起始日期逐天校验;设定上限,跨入尚未同步年度时不会无限查找。
|
||||
for (let count = 0; count < 366 && wk.length < 7; count++) {
|
||||
const day = moment(startDate).add(count, 'd')
|
||||
if (this.site.reserve_deadline && day.format('YYYY-MM-DD') > this.site.reserve_deadline) {
|
||||
break
|
||||
}
|
||||
if (this.site.workday === true && (!this.holidayReady || !this.holidayYears.includes(day.year()))) {
|
||||
break
|
||||
}
|
||||
if (!this.getReserveDateMessage(day.format('YYYY-MM-DD'), this.site)) {
|
||||
wk.push(day)
|
||||
}
|
||||
count++
|
||||
}
|
||||
this.weekList = wk
|
||||
},
|
||||
@@ -398,7 +438,8 @@ layout("/mobile/platform.html"){
|
||||
return day >= start && day <= end
|
||||
},
|
||||
showCloseDateMessage() {
|
||||
vant.Toast(this.getCloseReserveMessage(this.site))
|
||||
const message = this.getReserveDateMessage(this.selectDate.format('YYYY-MM-DD'), this.site)
|
||||
vant.Toast(message || this.getCloseReserveMessage(this.site))
|
||||
},
|
||||
reserve() {
|
||||
if (this.times.length === 0) {
|
||||
@@ -407,6 +448,11 @@ layout("/mobile/platform.html"){
|
||||
}
|
||||
for (let i = 0; i < this.times.length; i++) {
|
||||
let time = this.times[i]
|
||||
const workdayMessage = this.getReserveDateMessage(time.day, this.site)
|
||||
if (workdayMessage) {
|
||||
vant.Toast(workdayMessage)
|
||||
return
|
||||
}
|
||||
if (this.isInCloseDates(time.day, this.site)) {
|
||||
vant.Toast(this.getCloseReserveMessage(this.site))
|
||||
this.times.splice(i, 1)
|
||||
@@ -452,6 +498,11 @@ layout("/mobile/platform.html"){
|
||||
this.reserveLoading = false
|
||||
},
|
||||
chooseTime(item) {
|
||||
const workdayMessage = this.getReserveDateMessage(this.selectDate.format('YYYY-MM-DD'), this.site)
|
||||
if (workdayMessage) {
|
||||
vant.Toast(workdayMessage)
|
||||
return
|
||||
}
|
||||
if (this.isInCloseDates(this.selectDate.format('YYYY-MM-DD'), this.site)) {
|
||||
vant.Toast(this.getCloseReserveMessage(this.site))
|
||||
return
|
||||
@@ -477,7 +528,8 @@ layout("/mobile/platform.html"){
|
||||
return 3
|
||||
}
|
||||
|
||||
if (this.isInCloseDates(this.selectDate.format('YYYY-MM-DD'), this.site)) {
|
||||
if (this.getReserveDateMessage(this.selectDate.format('YYYY-MM-DD'), this.site)
|
||||
|| this.isInCloseDates(this.selectDate.format('YYYY-MM-DD'), this.site)) {
|
||||
return 4
|
||||
}
|
||||
|
||||
@@ -492,66 +544,86 @@ layout("/mobile/platform.html"){
|
||||
}
|
||||
},
|
||||
|
||||
async onLoad() {
|
||||
onLoad() {
|
||||
this.loading = true
|
||||
const resp = await $.post('/mobile/activity/site/info/getDetail', {id: this.site_id})
|
||||
if (resp.code === 0) {
|
||||
this.site = resp.data
|
||||
if (this.site.close_dates && typeof this.site.close_dates === 'string') {
|
||||
this.$set(this.site, 'close_dates', JSON.parse(this.site.close_dates))
|
||||
} else if (!this.site.close_dates) {
|
||||
this.$set(this.site, 'close_dates', [])
|
||||
}
|
||||
}
|
||||
this.loading = false
|
||||
},
|
||||
async getReserve() {
|
||||
const resp = await $.post('/mobile/activity/site/info/getReserve', {
|
||||
siteId: this.site_id,
|
||||
day: moment(this.selectDate).format('YYYY-MM-DD'),
|
||||
})
|
||||
if (resp.code === 0) {
|
||||
resp.data.forEach(item => {
|
||||
item.isClick = true
|
||||
return $.post('/mobile/activity/site/info/getDetail', {id: this.site_id})
|
||||
.then((resp) => {
|
||||
if (resp.code !== 0 || !resp.data) {
|
||||
return $.Deferred().reject('场地信息加载失败').promise()
|
||||
}
|
||||
this.site = resp.data
|
||||
if (typeof this.site.close_dates === 'string') {
|
||||
this.$set(this.site, 'close_dates', JSON.parse(this.site.close_dates))
|
||||
} else if (!this.site.close_dates) {
|
||||
this.$set(this.site, 'close_dates', [])
|
||||
}
|
||||
return this.site.workday === true ? this.loadHolidayCalendar() : null
|
||||
})
|
||||
.always(() => {
|
||||
this.loading = false
|
||||
})
|
||||
this.timeData = resp.data
|
||||
}
|
||||
},
|
||||
async getMyReservation() {
|
||||
const resp = await $.post('/mobile/activity/site/info/getMyReservation', {
|
||||
site_id: this.site_id
|
||||
getReserve() {
|
||||
// siteId为场地ID、day为选中日期;返回场次及占用状态数组。
|
||||
this.reserveDataLoading = true
|
||||
return $.post('/mobile/activity/site/info/getReserve', {
|
||||
siteId: this.site_id,
|
||||
day: this.selectDate.format('YYYY-MM-DD')
|
||||
}).then((resp) => {
|
||||
if (resp.code !== 0 || !Array.isArray(resp.data)) {
|
||||
return $.Deferred().reject().promise()
|
||||
}
|
||||
resp.data.forEach(item => this.$set(item, 'isClick', true))
|
||||
this.timeData = resp.data
|
||||
}).always(() => {
|
||||
this.reserveDataLoading = false
|
||||
})
|
||||
if (resp.code === 0) {
|
||||
this.alreadyReserveRecord = resp.data.map(v => v.reserve_day + " " + v.start_time + "-" + v.end_time)
|
||||
}
|
||||
},
|
||||
//获取最新的一个工作日设为当前时间
|
||||
getMyReservation() {
|
||||
// site_id为当前场地ID;预约日期和起止时间合并为前端已选状态标识。
|
||||
this.reservationLoading = true
|
||||
return $.post('/mobile/activity/site/info/getMyReservation', {site_id: this.site_id})
|
||||
.then((resp) => {
|
||||
if (resp.code !== 0 || !Array.isArray(resp.data)) {
|
||||
return $.Deferred().reject().promise()
|
||||
}
|
||||
this.alreadyReserveRecord = resp.data.map(v => v.reserve_day + ' ' + v.start_time + '-' + v.end_time)
|
||||
}).always(() => {
|
||||
this.reservationLoading = false
|
||||
})
|
||||
},
|
||||
// 场地与日历加载后再确定默认日期,普通周一至周五也始终返回有效日期。
|
||||
getLatestWorkDay() {
|
||||
//1.首先判断今天是不是工作日
|
||||
const weekNum = moment().day()
|
||||
let selectDate = null
|
||||
if(this.site.workday === true) {
|
||||
if (weekNum === 0) {
|
||||
//周日
|
||||
selectDate = moment().add(1, 'd')
|
||||
} else if (weekNum === 6) {
|
||||
//周六
|
||||
selectDate = moment().add(2, 'd')
|
||||
const today = moment()
|
||||
for (let offset = 0; offset < 366; offset++) {
|
||||
const date = moment(today).add(offset, 'd')
|
||||
if (this.site.reserve_deadline && date.format('YYYY-MM-DD') > this.site.reserve_deadline) {
|
||||
break
|
||||
}
|
||||
if (this.site.workday === true && (!this.holidayReady || !this.holidayYears.includes(date.year()))) {
|
||||
break
|
||||
}
|
||||
if (!this.getReserveDateMessage(date.format('YYYY-MM-DD'), this.site)) {
|
||||
return date
|
||||
}
|
||||
} else {
|
||||
selectDate = moment()
|
||||
}
|
||||
return selectDate
|
||||
// 年度未同步时保留显示日期,但选择与后端提交都保持禁止。
|
||||
return today
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
created() {
|
||||
this.site_id = getQueryString('id')
|
||||
this.selectDate = this.getLatestWorkDay()
|
||||
await this.onLoad()
|
||||
this.setWeekList(this.selectDate)
|
||||
await this.getReserve()
|
||||
await this.getMyReservation()
|
||||
|
||||
this.onLoad().then(() => {
|
||||
this.selectDate = this.getLatestWorkDay()
|
||||
this.setWeekList(this.selectDate)
|
||||
const message = this.getReserveDateMessage(this.selectDate.format('YYYY-MM-DD'), this.site)
|
||||
if (message) {
|
||||
vant.Toast(message)
|
||||
}
|
||||
return this.getReserve()
|
||||
}).then(() => this.getMyReservation(), () => {
|
||||
vant.Toast('场地或节假日数据加载失败,请刷新重试')
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -187,6 +187,19 @@ layout("/layouts/platform.html"){
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="预约截止日期" prop="reserve_deadline">
|
||||
<!-- 截止日期含当天,清空表示不限制最晚预约日期。 -->
|
||||
<el-date-picker
|
||||
:value="formData.reserve_deadline"
|
||||
@input="value => this.$set(formData, 'reserve_deadline', value || '')"
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
placeholder="不设置则不限制"
|
||||
clearable>
|
||||
</el-date-picker>
|
||||
<span style="color: #909399; margin-left: 12px">截止当天仍可预约,之后不可预约</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="关闭时段" label-width="110px">
|
||||
<el-date-picker
|
||||
v-model="formData.close_dates"
|
||||
@@ -238,7 +251,7 @@ layout("/layouts/platform.html"){
|
||||
|
||||
</el-form>
|
||||
<div style="float: right;margin: 20px 0">
|
||||
<el-button type="primary" :disabled="subDis" @click="operation">确 定</el-button>
|
||||
<el-button type="primary" :disabled="formLoading" @click="operation">确 定</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -258,10 +271,11 @@ layout("/layouts/platform.html"){
|
||||
activityTypeList: [],
|
||||
tabKey: '',
|
||||
tableLoading: false,
|
||||
subDis: false,
|
||||
formLoading: false,
|
||||
tableData: [],
|
||||
formData: {
|
||||
open_hours: [{}],
|
||||
reserve_deadline: '',
|
||||
close_dates: []
|
||||
},
|
||||
pageForm: {
|
||||
@@ -307,50 +321,71 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
});
|
||||
},
|
||||
async operation() {
|
||||
let method = this.formData.id ? "/doEdit" : "/doAdd"
|
||||
this.$refs["addForm"].validate(async (valid) => {
|
||||
if (valid) {
|
||||
this.subDis = true
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
text: '正在提交...',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
});
|
||||
|
||||
const {code, data, msg} = await $.post(location.href + method, {data: JSON.stringify(this.formData)})
|
||||
if (code === 0) {
|
||||
this.pageData()
|
||||
this.$message.success(msg);
|
||||
this.$refs.guava.index()
|
||||
} else {
|
||||
this.$message.error(msg);
|
||||
}
|
||||
loading.close()
|
||||
this.subDis = false
|
||||
operation() {
|
||||
const method = this.formData.id ? '/doEdit' : '/doAdd'
|
||||
this.$refs['addForm'].validate((valid) => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
});
|
||||
// 清空日期时仍提交空字符串,确保后端删除旧的截止配置。
|
||||
this.$set(this.formData, 'reserve_deadline', this.formData.reserve_deadline || '')
|
||||
this.formLoading = true
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
text: '正在提交...',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
// data包含原有场地字段及reserve_deadline;code为0表示保存成功,msg为结果提示。
|
||||
$.post(location.href + method, {data: JSON.stringify(this.formData)})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.pageData()
|
||||
this.$message.success(res.msg)
|
||||
this.$refs.guava.index()
|
||||
} else {
|
||||
this.$message.error(res.msg)
|
||||
}
|
||||
}, () => {
|
||||
this.$message.error('场地保存失败,请稍后重试')
|
||||
}).always(() => {
|
||||
loading.close()
|
||||
this.formLoading = false
|
||||
})
|
||||
})
|
||||
},
|
||||
async findOneSite(id) {
|
||||
const {data} = await $.get("/platform/activity/site/mange/findOneSite", {id: id})
|
||||
if (!data.close_dates) {
|
||||
this.$set(data, 'close_dates', [])
|
||||
}
|
||||
return data
|
||||
findOneSite(id) {
|
||||
// id为场地ID;data为场地实体,旧场地没有截止日期时按不限处理。
|
||||
this.formLoading = true
|
||||
return $.post('/platform/activity/site/mange/findOneSite', {id: id})
|
||||
.then((res) => {
|
||||
if (res.code !== 0 || !res.data) {
|
||||
return $.Deferred().reject().promise()
|
||||
}
|
||||
this.$set(res.data, 'close_dates', res.data.close_dates || [])
|
||||
this.$set(res.data, 'reserve_deadline', res.data.reserve_deadline || '')
|
||||
return res.data
|
||||
}).always(() => {
|
||||
this.formLoading = false
|
||||
})
|
||||
},
|
||||
openView(row) {
|
||||
this.$refs.guava.view()
|
||||
},
|
||||
async openEdit(id) {
|
||||
this.formData = await this.findOneSite(id)
|
||||
this.$refs.guava.edit()
|
||||
openEdit(id) {
|
||||
this.findOneSite(id).then((data) => {
|
||||
this.formData = data
|
||||
this.$refs.guava.edit()
|
||||
}, () => {
|
||||
this.$message.error('场地信息加载失败,请稍后重试')
|
||||
})
|
||||
},
|
||||
openAdd() {
|
||||
this.formData = {
|
||||
state: true,
|
||||
multiple: true,
|
||||
open_hours: [{}],
|
||||
reserve_deadline: '',
|
||||
close_dates: [],
|
||||
create_username: '${@shiro.getPrincipalProperty("username")}',
|
||||
create_time: moment().format('YYYY-MM-DD'),
|
||||
|
||||
@@ -29,6 +29,11 @@ layout("/layouts/platform.html"){
|
||||
color: #1989FA;
|
||||
}
|
||||
|
||||
.reserve-date-disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.div-Calendar {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
@@ -325,7 +330,9 @@ layout("/layouts/platform.html"){
|
||||
</el-row>
|
||||
<el-calendar v-model="reserve_time">
|
||||
<template slot="dateCell" slot-scope="{date, data}">
|
||||
<div class="div-Calendar" @click="pushDay(data)">
|
||||
<div class="div-Calendar" @click="pushDay(data)"
|
||||
:class="{'reserve-date-disabled': !!getReserveDateMessage(data.day, infoViewData)}"
|
||||
:aria-disabled="!!getReserveDateMessage(data.day, infoViewData)">
|
||||
<p :style="dayArray.includes(data.day) ? 'color: #246fb4' : ''">
|
||||
{{ data.day.split('-').slice(1).join('-') }} {{ dayArray.includes(data.day) ? '√' : ''}}
|
||||
</p>
|
||||
@@ -465,7 +472,7 @@ layout("/layouts/platform.html"){
|
||||
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="closeAdd">取 消</el-button>
|
||||
<el-button type="primary" :disabled="subDis" @click="doAdd">确 定</el-button>
|
||||
<el-button type="primary" :disabled="formLoading" @click="doAdd">确 定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
@@ -535,7 +542,7 @@ layout("/layouts/platform.html"){
|
||||
reserveViewData: {},
|
||||
tableLoading: false,
|
||||
addDialogVisible: false,
|
||||
subDis: false,
|
||||
formLoading: false,
|
||||
loading: false,
|
||||
tableData: [],
|
||||
formData: {
|
||||
@@ -568,8 +575,11 @@ layout("/layouts/platform.html"){
|
||||
/*reserve_cause: [{required: true, message: '请选择预约事由', trigger: ['blur', 'change']}],*/
|
||||
},
|
||||
dayArray: [],
|
||||
holidays: ['2022-06-03','2022-06-04','2022-06-05','2022-09-10','2022-09-11','2022-09-12',
|
||||
'2022-10-01','2022-10-02','2022-10-03','2022-10-04','2022-10-05','2022-10-06','2022-10-07',],
|
||||
holidays: [],
|
||||
holidayYears: [],
|
||||
holidayReady: false,
|
||||
holidayLoading: false,
|
||||
reserveDataLoading: false,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
@@ -577,6 +587,56 @@ layout("/layouts/platform.html"){
|
||||
'reserve-info': httpVueLoader('/components/activity/site/ReserveInfo.vue'),
|
||||
},
|
||||
methods: {
|
||||
// 后端返回days日期数组及years已同步年份,加载失败时禁止按空日历放行。
|
||||
loadHolidayCalendar() {
|
||||
this.holidayReady = false
|
||||
this.holidayLoading = true
|
||||
return $.post('/platform/activity/site/reserve/holidayCalendar', {})
|
||||
.then((res) => {
|
||||
if (res.code !== 0 || !res.data || !Array.isArray(res.data.days) || !Array.isArray(res.data.years)) {
|
||||
return $.Deferred().reject('节假日日历加载失败,请稍后重试').promise()
|
||||
}
|
||||
this.holidays = res.data.days
|
||||
this.holidayYears = res.data.years
|
||||
this.holidayReady = true
|
||||
})
|
||||
.always(() => {
|
||||
this.holidayLoading = false
|
||||
})
|
||||
},
|
||||
// 截止日期独立于工作日开关:截止当天允许,超过截止日禁止;原有假期规则继续生效。
|
||||
getReserveDateMessage(day, siteData) {
|
||||
if (!siteData) {
|
||||
return ''
|
||||
}
|
||||
const date = moment(day, 'YYYY-MM-DD', true)
|
||||
if (!date.isValid()) {
|
||||
return '预约日期格式不正确'
|
||||
}
|
||||
if (siteData.reserve_deadline) {
|
||||
const deadline = moment(siteData.reserve_deadline, 'YYYY-MM-DD', true)
|
||||
if (!deadline.isValid()) {
|
||||
return '场地预约截止日期配置异常,请联系管理员'
|
||||
}
|
||||
if (date.isAfter(deadline, 'day')) {
|
||||
return '该场地最晚可预约至' + siteData.reserve_deadline + ',请选择截止日期当天或之前的日期'
|
||||
}
|
||||
}
|
||||
if (siteData.workday !== true) {
|
||||
return ''
|
||||
}
|
||||
if (date.day() === 0 || date.day() === 6) {
|
||||
return day + '为周末,该场地仅限工作日预约'
|
||||
}
|
||||
if (!this.holidayReady) {
|
||||
return '节假日日历尚未加载,请稍后重试'
|
||||
}
|
||||
if (!this.holidayYears.includes(date.year())) {
|
||||
return date.year() + '年节假日尚未同步,暂不能预约,请联系管理员'
|
||||
}
|
||||
return this.holidays.includes(day) ? day + '为节假日,该场地仅限工作日预约' : ''
|
||||
},
|
||||
|
||||
diaOpen() {
|
||||
if(this.infoViewData.multiple === true ||
|
||||
(this.infoViewData.open_hours.length === 1 && this.infoViewData.open_hours[0].disabled !== true)) {
|
||||
@@ -776,7 +836,6 @@ layout("/layouts/platform.html"){
|
||||
return day >= start && day <= end
|
||||
},
|
||||
pushDay (data) {
|
||||
const d = new Date(data.day).getDay()
|
||||
if (this.isInCloseDates(data.day, this.textData)) {
|
||||
this.$message.warning(this.getCloseReserveMessage(this.textData))
|
||||
return
|
||||
@@ -791,8 +850,9 @@ layout("/layouts/platform.html"){
|
||||
return
|
||||
}
|
||||
}
|
||||
if(this.textData.workday === true && (d === 0 || d === 6 || this.holidays.includes(data.day))) {
|
||||
this.$message.warning('抱歉,该场地只能预约工作日')
|
||||
const workdayMessage = this.getReserveDateMessage(data.day, this.textData)
|
||||
if (workdayMessage) {
|
||||
this.$message.warning(workdayMessage)
|
||||
return
|
||||
}
|
||||
if (data.day < moment(new Date()).format('YYYY-MM-DD')) {
|
||||
@@ -895,11 +955,16 @@ layout("/layouts/platform.html"){
|
||||
transNum(hm) {
|
||||
return (moment("1970-01-01 " + hm).valueOf() + HOUR8) / HOUR1
|
||||
},
|
||||
async doAdd() {
|
||||
doAdd() {
|
||||
if (!this.formData.time) {
|
||||
this.$notify.warning({title: '警告', message: '未选择预约时间!'});
|
||||
return
|
||||
}
|
||||
const invalidDay = this.dayArray.find(day => this.getReserveDateMessage(day, this.infoViewData))
|
||||
if (invalidDay) {
|
||||
this.$notify.warning({title: '警告', message: this.getReserveDateMessage(invalidDay, this.infoViewData)});
|
||||
return
|
||||
}
|
||||
const closeDay = this.dayArray.find(day => this.isInCloseDates(day, this.infoViewData))
|
||||
if (closeDay) {
|
||||
this.$notify.warning({title: '警告', message: this.getCloseReserveMessage(this.infoViewData)});
|
||||
@@ -918,9 +983,9 @@ layout("/layouts/platform.html"){
|
||||
this.$notify.warning({title: '警告', message: '您预约的时间段已过!'});
|
||||
return
|
||||
}
|
||||
this.$refs["addForm"].validate(async (valid) => {
|
||||
this.$refs["addForm"].validate((valid) => {
|
||||
if (valid) {
|
||||
this.subDis = true
|
||||
this.formLoading = true
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
text: '正在提交...',
|
||||
@@ -928,20 +993,24 @@ layout("/layouts/platform.html"){
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
});
|
||||
|
||||
const {code, msg} = await $.post(location.href + "/doAdd", {
|
||||
$.post(location.href + "/doAdd", {
|
||||
data: JSON.stringify(this.formData),
|
||||
days: JSON.stringify(this.dayArray)
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.addDialogVisible = false
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
this.$refs.guava.index()
|
||||
} else {
|
||||
this.$notify.warning({title: '警告', message: res.msg})
|
||||
}
|
||||
}, () => {
|
||||
this.$message.warning('预约提交失败,请稍后重试')
|
||||
}).always(() => {
|
||||
loading.close()
|
||||
this.formLoading = false
|
||||
})
|
||||
if (code === 0) {
|
||||
this.addDialogVisible = false
|
||||
this.$message.success(msg);
|
||||
this.pageData()
|
||||
this.$refs.guava.index()
|
||||
} else {
|
||||
this.$notify.warning({title: '警告', message: msg});
|
||||
}
|
||||
loading.close()
|
||||
this.subDis = false
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -953,16 +1022,23 @@ layout("/layouts/platform.html"){
|
||||
this.$refs.reserve.backOptionShow = (role || backOptionShow)
|
||||
this.$refs.reserve.openView(row.id)
|
||||
},
|
||||
async openReserve(data) {
|
||||
openReserve(data) {
|
||||
this.drawer = false
|
||||
data.open_hours = site.reverseRankingDate(data.open_hours)
|
||||
this.reserveViewData = await this.findReserveInfo(data.id)
|
||||
this.reserveViewData.forEach(item => {
|
||||
item.index = 0
|
||||
})
|
||||
this.formData.site_id = data.id
|
||||
this.infoViewData = data
|
||||
this.$refs.guava.edit()
|
||||
this.$set(data, 'open_hours', site.reverseRankingDate(data.open_hours))
|
||||
// 进入预约日历前先读取服务端假期,再显示可选日期。
|
||||
const calendarRequest = data.workday === true ? this.loadHolidayCalendar() : $.Deferred().resolve().promise()
|
||||
calendarRequest.then(() => this.findReserveInfo(data.id))
|
||||
.then((records) => {
|
||||
this.reserveViewData = records
|
||||
this.reserveViewData.forEach(item => {
|
||||
this.$set(item, 'index', 0)
|
||||
})
|
||||
this.$set(this.formData, 'site_id', data.id)
|
||||
this.infoViewData = data
|
||||
this.$refs.guava.edit()
|
||||
}, () => {
|
||||
this.$message.warning('预约数据加载失败,请稍后重试')
|
||||
})
|
||||
},
|
||||
async doSearch() {
|
||||
this.pageForm.pageNumber = 1
|
||||
@@ -997,9 +1073,15 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
}, "json");
|
||||
},
|
||||
async findReserveInfo(siteId) {
|
||||
const {data} = await $.get("/platform/activity/site/reserve/findReserveInfo", {siteId})
|
||||
return data;
|
||||
findReserveInfo(siteId) {
|
||||
// siteId为场地ID,返回该场地预约记录数组,供预约日历显示。
|
||||
this.reserveDataLoading = true
|
||||
return $.post('/platform/activity/site/reserve/findReserveInfo', {siteId: siteId})
|
||||
.then((res) => {
|
||||
return res.code === 0 && Array.isArray(res.data) ? res.data : $.Deferred().reject().promise()
|
||||
}).always(() => {
|
||||
this.reserveDataLoading = false
|
||||
})
|
||||
},
|
||||
async findReserveInfoByClickDay(siteId, day) {
|
||||
const {data} = await $.get("/platform/activity/site/reserve/findReserveInfo", {siteId: siteId, day: day})
|
||||
|
||||
Reference in New Issue
Block a user