场地预约问题

This commit is contained in:
2026-09-16 17:10:17 +08:00
parent 8748fa0c7d
commit 4b3c41921a
9 changed files with 517 additions and 159 deletions
@@ -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})