场地预约整改:预约时间优化
拉取数据bug整改
This commit is contained in:
@@ -5,7 +5,7 @@ import io.v.nutz.sys.services.SysTaskService;
|
|||||||
import io.v.nutz.task.services.TaskPlatformService;
|
import io.v.nutz.task.services.TaskPlatformService;
|
||||||
import io.v.nutz.web.commons.slog.annotation.SLog;
|
import io.v.nutz.web.commons.slog.annotation.SLog;
|
||||||
import io.v.nutz.base.utils.PageUtil;
|
import io.v.nutz.base.utils.PageUtil;
|
||||||
import io.v.nutz.base.result.Result;;
|
import io.v.nutz.base.result.Result;
|
||||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||||
import org.nutz.dao.Cnd;
|
import org.nutz.dao.Cnd;
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ import io.v.nutz.sys.models.Sys_unit;
|
|||||||
* Created by wizzer on 2016/12/22.
|
* Created by wizzer on 2016/12/22.
|
||||||
*/
|
*/
|
||||||
public interface SysUnitService extends ViService<Sys_unit> {
|
public interface SysUnitService extends ViService<Sys_unit> {
|
||||||
|
/**
|
||||||
|
* 无需参数,从数据中心同步全部单位,供单位维护及人员更新共同使用。
|
||||||
|
* 返回 void;同步失败抛出异常,由调用接口返回失败提示。
|
||||||
|
*/
|
||||||
|
void syncSourceUnits();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存单位
|
* 保存单位
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
package io.v.nutz.sys.services.impl;
|
package io.v.nutz.sys.services.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import io.v.nutz.zhgh.data.constant.SourceData;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import io.v.nutz.base.constant.RedisConstant;
|
import io.v.nutz.base.constant.RedisConstant;
|
||||||
import io.v.nutz.base.service.impl.ViServiceImpl;
|
import io.v.nutz.base.service.impl.ViServiceImpl;
|
||||||
import io.v.nutz.sys.models.Sys_unit;
|
import io.v.nutz.sys.models.Sys_unit;
|
||||||
@@ -25,6 +31,47 @@ public class SysUnitServiceImpl extends ViServiceImpl<Sys_unit> implements SysUn
|
|||||||
super(dao);
|
super(dao);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步数据中心单位,无入参、无返回值;异常交由调用方处理。
|
||||||
|
* 保留现有层级规则及根单位映射,重复同步按映射后的主键更新。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public void syncSourceUnits() {
|
||||||
|
List<Sys_unit> units = SourceData.units();
|
||||||
|
Set<String> ids = query().stream().map(Sys_unit::getId).collect(Collectors.toSet());
|
||||||
|
for (Sys_unit unit : units) {
|
||||||
|
if (Strings.isBlank(unit.getId()) || Strings.isBlank(unit.getUnitcode())) {
|
||||||
|
throw new IllegalStateException("单位同步失败:源单位编号为空,请核对单位源数据。");
|
||||||
|
}
|
||||||
|
// 源根单位 100 在本系统保存为 1,必须先映射再判断是否已存在。
|
||||||
|
if ("100".equals(unit.getId())) {
|
||||||
|
unit.setId("1");
|
||||||
|
unit.setUnitcode("1");
|
||||||
|
unit.setUnitlevel(1);
|
||||||
|
}
|
||||||
|
if (ids.contains(unit.getId())) {
|
||||||
|
updateIgnoreNull(unit);
|
||||||
|
} else {
|
||||||
|
Map<String, Object> beanMap = BeanUtil.beanToMap(unit);
|
||||||
|
if (unit.getUnitcode().length() == 6) {
|
||||||
|
beanMap.put("unitlevel", 2);
|
||||||
|
beanMap.put("parentId", 1);
|
||||||
|
}
|
||||||
|
beanMap.remove("child");
|
||||||
|
insert("sys_unit", Chain.from(beanMap));
|
||||||
|
ids.add(unit.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 沿用原入口对子单位标志的维护规则。
|
||||||
|
for (Sys_unit unit : query()) {
|
||||||
|
if (count(Cnd.where("parentId", "=", unit.getId())) > 0) {
|
||||||
|
unit.setHasChildren(true);
|
||||||
|
update(unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增单位
|
* 新增单位
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ public class SiteInfoServiceImpl extends BaseServiceImpl<ActivitySiteInfo> imple
|
|||||||
*/
|
*/
|
||||||
@Aop(TransAop.READ_COMMITTED)
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public void saveManagedSite(ActivitySiteInfo form, boolean editing) {
|
public void saveManagedSite(ActivitySiteInfo form, boolean editing) {
|
||||||
if (form == null || !"职工之家".equals(form.getName())) throw new IllegalArgumentException("场地名称请选择职工之家");
|
// 名称支持选择或自定义输入,拒绝空白及超过实体字段100字符的值,类型关联仍沿用现有规则。
|
||||||
|
if (form == null || Strings.isBlank(form.getName())) throw new IllegalArgumentException("请选择或输入场地名称");
|
||||||
|
if (form.getName().length() > 100) throw new IllegalArgumentException("场地名称不能超过100字符");
|
||||||
if (Arrays.asList("青山湖科创中心", "不固定校区").contains(form.getCampus()) || Strings.isBlank(form.getCampus()) || form.getCampus().length() > 100
|
if (Arrays.asList("青山湖科创中心", "不固定校区").contains(form.getCampus()) || Strings.isBlank(form.getCampus()) || form.getCampus().length() > 100
|
||||||
|| dao().count("sys_dq", Cnd.where("dq_name", "=", form.getCampus())) == 0)
|
|| dao().count("sys_dq", Cnd.where("dq_name", "=", form.getCampus())) == 0)
|
||||||
throw new IllegalArgumentException("请选择有效校区");
|
throw new IllegalArgumentException("请选择有效校区");
|
||||||
|
|||||||
@@ -146,20 +146,32 @@ public interface SourceData {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取单位
|
* 分页获取全部源单位,无入参;响应异常或空页时抛出异常,阻止不完整同步。
|
||||||
*
|
*
|
||||||
* @return units
|
* @return 单位列表,DWH 映射为 id/unitcode,DWMC 为名称,SJDWH 为父级编号
|
||||||
*/
|
*/
|
||||||
static List<Sys_unit> units() {
|
static List<Sys_unit> units() {
|
||||||
List<Sys_unit> units = new ArrayList<>();
|
List<Sys_unit> units = new ArrayList<>();
|
||||||
int page = 1;
|
int page = 1;
|
||||||
|
while (true) {
|
||||||
String body = HttpUtil.createPost(DATA_URL).body(JSON.toJSONString(new NutMap().addv("address", "rsxt_dwjbsj").addv("params", new NutMap())
|
String body = HttpUtil.createPost(DATA_URL).body(JSON.toJSONString(new NutMap().addv("address", "rsxt_dwjbsj").addv("params", new NutMap())
|
||||||
.addv("token", "3318a5a9-c2f2-4c8b-a8ea-e99dd68c165c").addv("pageIndex", page).addv("pageSize", 100))).execute().body();
|
.addv("token", "3318a5a9-c2f2-4c8b-a8ea-e99dd68c165c").addv("pageIndex", page).addv("pageSize", 100))).execute().body();
|
||||||
NutMap map = Json.fromJson(NutMap.class, body);
|
NutMap map = Json.fromJson(NutMap.class, body);
|
||||||
// checkSuccess(map);
|
// 缺少数据或明确返回失败时不能当作同步成功,避免用不完整单位继续更新人员。
|
||||||
|
if (map == null || (map.containsKey("success") && !map.getBoolean("success"))
|
||||||
|
|| !map.containsKey("data") || !map.containsKey("totalCount")) {
|
||||||
|
throw new IllegalStateException("单位同步失败:数据中心响应异常,请核对单位源接口。");
|
||||||
|
}
|
||||||
List<NutMap> data = map.getAsList("data", NutMap.class);
|
List<NutMap> data = map.getAsList("data", NutMap.class);
|
||||||
|
int totalCount = map.getInt("totalCount");
|
||||||
|
if (data == null || data.isEmpty() || totalCount <= 0) {
|
||||||
|
throw new IllegalStateException("单位同步失败:数据中心返回空页或总数异常,第 " + page + " 页。");
|
||||||
|
}
|
||||||
|
|
||||||
for (NutMap row : data) {
|
for (NutMap row : data) {
|
||||||
|
if (row == null) {
|
||||||
|
throw new IllegalStateException("单位同步失败:源单位记录为空。");
|
||||||
|
}
|
||||||
Map entity = new HashMap(5);
|
Map entity = new HashMap(5);
|
||||||
row.forEach((k, v) -> {
|
row.forEach((k, v) -> {
|
||||||
if (UNIT_FIELD_RELATION.containsKey(k)) {
|
if (UNIT_FIELD_RELATION.containsKey(k)) {
|
||||||
@@ -174,6 +186,12 @@ public interface SourceData {
|
|||||||
});
|
});
|
||||||
units.add(BeanUtil.mapToBean(entity, Sys_unit.class, true));
|
units.add(BeanUtil.mapToBean(entity, Sys_unit.class, true));
|
||||||
}
|
}
|
||||||
|
// 根据源接口总数逐页拉取,避免原先只读取前 100 个单位。
|
||||||
|
if (units.size() >= totalCount) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
page++;
|
||||||
|
}
|
||||||
return units;
|
return units;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package io.v.nutz.zhgh.data.controller;
|
package io.v.nutz.zhgh.data.controller;
|
||||||
|
|
||||||
import cn.hutool.core.bean.BeanUtil;
|
|
||||||
import cn.wizzer.framework.base.Result;
|
import cn.wizzer.framework.base.Result;
|
||||||
import io.v.nutz.base.annontation.ViReturn;
|
import io.v.nutz.base.annontation.ViReturn;
|
||||||
import io.v.nutz.base.dao.CndPlus;
|
import io.v.nutz.base.dao.CndPlus;
|
||||||
import io.v.nutz.base.query.PageForm;
|
import io.v.nutz.base.query.PageForm;
|
||||||
import io.v.nutz.base.service.AsyncService;
|
import io.v.nutz.base.service.AsyncService;
|
||||||
import io.v.nutz.base.service.ViService;
|
import io.v.nutz.sys.services.SysUnitService;
|
||||||
import io.v.nutz.sys.models.Sys_unit;
|
import io.v.nutz.sys.models.Sys_unit;
|
||||||
import io.v.nutz.sys.services.SysDqService;
|
import io.v.nutz.sys.services.SysDqService;
|
||||||
import io.v.nutz.sys.services.SysGxService;
|
import io.v.nutz.sys.services.SysGxService;
|
||||||
import io.v.nutz.sys.services.SysUnitClassService;
|
import io.v.nutz.sys.services.SysUnitClassService;
|
||||||
import io.v.nutz.zhgh.data.constant.SourceData;
|
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||||
|
import org.nutz.ioc.aop.Aop;
|
||||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||||
import org.nutz.dao.Chain;
|
import org.nutz.dao.Chain;
|
||||||
import org.nutz.dao.Cnd;
|
import org.nutz.dao.Cnd;
|
||||||
@@ -25,8 +25,6 @@ import org.nutz.mvc.annotation.Ok;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@At("/platform/data/unit")
|
@At("/platform/data/unit")
|
||||||
@Ok("json:full")
|
@Ok("json:full")
|
||||||
@@ -35,8 +33,8 @@ public class UpdateUnitController {
|
|||||||
|
|
||||||
private static final Log log = Logs.get();
|
private static final Log log = Logs.get();
|
||||||
|
|
||||||
@Inject("Sys_unit")
|
@Inject
|
||||||
private ViService<Sys_unit> sysUnitService;
|
private SysUnitService sysUnitService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private SysUnitClassService sysUnitClassService;
|
private SysUnitClassService sysUnitClassService;
|
||||||
@@ -61,45 +59,16 @@ public class UpdateUnitController {
|
|||||||
/**
|
/**
|
||||||
* 单位数据更新
|
* 单位数据更新
|
||||||
*
|
*
|
||||||
* @return {@link Object}
|
* 无入参,调用 service 同步单位;返回 null,由 ViReturn 转为 code/msg 响应。
|
||||||
|
* 同步异常由 ViReturn 返回失败提示。
|
||||||
|
* @return 同步成功返回 null
|
||||||
*/
|
*/
|
||||||
@At
|
@At
|
||||||
@ViReturn
|
@ViReturn
|
||||||
@RequiresPermissions("sys.data.unit")
|
@RequiresPermissions("sys.data.unit")
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public Object dataUpdate() {
|
public Object dataUpdate() {
|
||||||
List<Sys_unit> sys_units = sysUnitService.query();
|
sysUnitService.syncSourceUnits();
|
||||||
List<String> list = sys_units.stream().map(Sys_unit::getId).collect(Collectors.toList());
|
|
||||||
|
|
||||||
// 从源数据中心拉取所有的单位
|
|
||||||
List<Sys_unit> units = SourceData.units();
|
|
||||||
|
|
||||||
for (Sys_unit unit : units) {
|
|
||||||
if (list.contains(unit.getId())) {
|
|
||||||
sysUnitService.updateIgnoreNull(unit);
|
|
||||||
} else {
|
|
||||||
Map<String, Object> beanMap = BeanUtil.beanToMap(unit);
|
|
||||||
String unitcode = beanMap.get("unitcode").toString();
|
|
||||||
if (unitcode.length() == 6) {
|
|
||||||
beanMap.put("unitlevel", 2);
|
|
||||||
beanMap.put("parentId", 1);
|
|
||||||
}
|
|
||||||
if (beanMap.get("id").equals("100")) {
|
|
||||||
beanMap.put("unitlevel", 1);
|
|
||||||
beanMap.put("id", 1);
|
|
||||||
beanMap.put("unitcode", 1);
|
|
||||||
}
|
|
||||||
beanMap.remove("child");
|
|
||||||
sysUnitService.insert("sys_unit", Chain.from(beanMap));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sys_units = sysUnitService.query();
|
|
||||||
sys_units.forEach(v -> {
|
|
||||||
if (sysUnitService.count(Cnd.where("parentId", "=", v.getId())) > 0) { //查询此单位是否有父级单位
|
|
||||||
v.setHasChildren(true); //设置子级菜单
|
|
||||||
sysUnitService.update(v);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import cn.hutool.http.HtmlUtil;
|
|||||||
import io.v.nutz.base.utils.ManyAddOrRenewUtil;
|
import io.v.nutz.base.utils.ManyAddOrRenewUtil;
|
||||||
import io.v.nutz.base.utils.Roles;
|
import io.v.nutz.base.utils.Roles;
|
||||||
import io.v.nutz.sys.models.Sys_user_role;
|
import io.v.nutz.sys.models.Sys_user_role;
|
||||||
|
import io.v.nutz.sys.models.Sys_unit;
|
||||||
|
import io.v.nutz.sys.services.SysUnitService;
|
||||||
import io.v.nutz.sys.services.SysDictService;
|
import io.v.nutz.sys.services.SysDictService;
|
||||||
import io.v.nutz.sys.services.SysUserRoleService;
|
import io.v.nutz.sys.services.SysUserRoleService;
|
||||||
import io.v.nutz.zhgh.activity.models.ActivityUserScope;
|
import io.v.nutz.zhgh.activity.models.ActivityUserScope;
|
||||||
@@ -83,6 +85,8 @@ public class SourceUserServiceImpl extends ViServiceImpl<UserSource> implements
|
|||||||
@Inject
|
@Inject
|
||||||
private SysUserService sysUserService;
|
private SysUserService sysUserService;
|
||||||
@Inject
|
@Inject
|
||||||
|
private SysUnitService sysUnitService;
|
||||||
|
@Inject
|
||||||
private HistoryUserService historyUserService;
|
private HistoryUserService historyUserService;
|
||||||
@Inject
|
@Inject
|
||||||
private UserPartUpService userPatUpService;
|
private UserPartUpService userPatUpService;
|
||||||
@@ -109,10 +113,22 @@ public class SourceUserServiceImpl extends ViServiceImpl<UserSource> implements
|
|||||||
dao().insert(users);
|
dao().insert(users);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 先同步单位并校验人员引用,再按现有配置生成记录和更新人员。
|
||||||
|
* @param pullTime 已拉取人员数据的批次时间
|
||||||
|
* @param sourceType all 全部更新、add 新增人员、part 按组别及过滤字段更新
|
||||||
|
* @param columnNames 部分更新时忽略的字段名,可为空
|
||||||
|
* @param isUpSet 保留现有接口参数,当前方法不使用
|
||||||
|
* @param partGroupId 更新组别 ID,为空时使用整个批次
|
||||||
|
* @param isInvert 是否选择组别之外的人员
|
||||||
|
* @return 无返回值;同步或校验失败抛出异常,由接口返回 code/msg 提示
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Aop(TransAop.READ_COMMITTED)
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public void updateSysUser(String pullTime, String sourceType, String[] columnNames, String isUpSet, String partGroupId, boolean isInvert) {
|
public void updateSysUser(String pullTime, String sourceType, String[] columnNames, String isUpSet, String partGroupId, boolean isInvert) {
|
||||||
try {
|
try {
|
||||||
|
// 单位与后续校验使用当前事务,单位同步失败时不进入人员更新。
|
||||||
|
sysUnitService.syncSourceUnits();
|
||||||
List<UserSource> sources = new ArrayList<>();
|
List<UserSource> sources = new ArrayList<>();
|
||||||
Map<String, String> userPartMap = new HashMap<>();
|
Map<String, String> userPartMap = new HashMap<>();
|
||||||
if (Strings.isNotBlank(partGroupId)) {
|
if (Strings.isNotBlank(partGroupId)) {
|
||||||
@@ -172,19 +188,9 @@ public class SourceUserServiceImpl extends ViServiceImpl<UserSource> implements
|
|||||||
Set<String> userIds = list.stream().map(SpecialStaff::getUserId).collect(Collectors.toSet());
|
Set<String> userIds = list.stream().map(SpecialStaff::getUserId).collect(Collectors.toSet());
|
||||||
|
|
||||||
|
|
||||||
// 创建一个固定大小的线程池 可以根据服务器性能调整线程池大小
|
// 写入人员、角色、历史之前统一校验;记录计算留在当前线程,读取本事务新同步的单位。
|
||||||
int numberOfThreads = Runtime.getRuntime().availableProcessors() * 2;
|
validateSourceUnits(sources, userMap, userIds, allowChangeFieldNames);
|
||||||
List<CompletableFuture<Void>> futures = new CopyOnWriteArrayList<>();
|
for (UserSource source : sources) {
|
||||||
// 每批处理的数据量,可以根据实际情况调整
|
|
||||||
int batchSize = 200;
|
|
||||||
|
|
||||||
for (int i = 0; i < sources.size(); i += batchSize) {
|
|
||||||
final int end = Math.min(i + batchSize, sources.size());
|
|
||||||
List<UserSource> batch = sources.subList(i, end);
|
|
||||||
Map<String, String> finalUserPartMap = userPartMap;
|
|
||||||
|
|
||||||
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
|
||||||
for (UserSource source : batch) {
|
|
||||||
//不更新会员和福利会员字段
|
//不更新会员和福利会员字段
|
||||||
source.setMember(null);
|
source.setMember(null);
|
||||||
source.setWelfareMember(null);
|
source.setWelfareMember(null);
|
||||||
@@ -233,7 +239,7 @@ public class SourceUserServiceImpl extends ViServiceImpl<UserSource> implements
|
|||||||
FieldFilter fieldFilter = FieldFilter.create(Sys_user.class, null, "^" + lockedColumn + "$", true);
|
FieldFilter fieldFilter = FieldFilter.create(Sys_user.class, null, "^" + lockedColumn + "$", true);
|
||||||
filterColumnDao.set(Daos.ext(dao(), fieldFilter));
|
filterColumnDao.set(Daos.ext(dao(), fieldFilter));
|
||||||
}
|
}
|
||||||
if (Strings.isNotBlank(finalUserPartMap.get(u.getLoginname())) && user != null) {
|
if (Strings.isNotBlank(userPartMap.get(u.getLoginname())) && user != null) {
|
||||||
needDoUpdateList.add(u);
|
needDoUpdateList.add(u);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -265,11 +271,6 @@ public class SourceUserServiceImpl extends ViServiceImpl<UserSource> implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, Executors.newFixedThreadPool(numberOfThreads));
|
|
||||||
futures.add(future);
|
|
||||||
}
|
|
||||||
|
|
||||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
|
||||||
|
|
||||||
//如果有新用户,增加到用户表同时增加角色
|
//如果有新用户,增加到用户表同时增加角色
|
||||||
if (Lang.isNotEmpty(needInitUserList)) {
|
if (Lang.isNotEmpty(needInitUserList)) {
|
||||||
@@ -322,11 +323,69 @@ public class SourceUserServiceImpl extends ViServiceImpl<UserSource> implements
|
|||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new RuntimeException(e.getMessage());
|
throw new RuntimeException(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验本次范围内参与新增、更新或变更记录生成的单位,失败时阻断全部人员写入。
|
||||||
|
* @param sources 已按数据源时间、组别及反选条件筛选的人员
|
||||||
|
* @param userMap 以工号为键的现有人员,用于检查变更前单位
|
||||||
|
* @param excludedUserIds 按现有特殊人员规则不做变动的人员 ID
|
||||||
|
* @param allowChangeFieldNames 允许记录变更的字段,含 unitId 时核对变更前单位
|
||||||
|
* @return 无返回值;缺失时抛出包含单位编号、姓名和工号的异常,供接口 msg 展示
|
||||||
|
*/
|
||||||
|
private void validateSourceUnits(List<UserSource> sources, Map<String, Sys_user> userMap,
|
||||||
|
Set<String> excludedUserIds, Set<String> allowChangeFieldNames) {
|
||||||
|
Map<String, Sys_unit> units = sysUnitService.query().stream()
|
||||||
|
.collect(Collectors.toMap(Sys_unit::getId, unit -> unit));
|
||||||
|
List<String> missing = new ArrayList<>();
|
||||||
|
for (UserSource source : sources) {
|
||||||
|
Sys_user user = userMap.get(source.getLoginname());
|
||||||
|
if (StrUtil.isBlank(source.getPersonType()) || (user != null && excludedUserIds.contains(user.getId()))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 不进系统规则只排除新增人员;现有人员仍按原逻辑生成变更记录。
|
||||||
|
if (user == null && (NOT_ENTERING_PERSON_TYPE.contains(source.getPersonType())
|
||||||
|
|| NOT_ENTERING_USER_LOGIN_NAME.contains(source.getLoginname())
|
||||||
|
|| (source.getUserState() != null && NOT_ENTERING_USER_STATE.contains(source.getUserState())))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
collectMissingUnit(units, source.getUnitid(), "新单位", source, missing);
|
||||||
|
if (user != null && allowChangeFieldNames.contains("unitId")
|
||||||
|
&& !Objects.equals(source.getUnitid(), user.getUnitid())) {
|
||||||
|
collectMissingUnit(units, user.getUnitid(), "原单位", source, missing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!missing.isEmpty()) {
|
||||||
|
// 页面提示限制长度,同时告知总数;完整明细写日志便于核对。
|
||||||
|
log.warn("人员更新单位校验失败:{}", String.join(";", missing));
|
||||||
|
throw new IllegalStateException("单位同步校验后仍存在缺失单位或单位名称为空,本次人员更新已停止。"
|
||||||
|
+ String.join(";", missing.subList(0, Math.min(10, missing.size())))
|
||||||
|
+ (missing.size() > 10 ? ";共 " + missing.size() + " 项,完整明细请查看服务日志" : "")
|
||||||
|
+ "。请核对单位源数据。");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将不存在、编号为空或名称为空的单位加入提示明细;仅收集问题,不修改数据。
|
||||||
|
* @param units 当前事务同步后的单位,键为单位 ID
|
||||||
|
* @param unitId 人员引用的单位 ID,空值也需提示
|
||||||
|
* @param label 新单位或原单位,标明问题来源
|
||||||
|
* @param source 相关人员,提供姓名及工号
|
||||||
|
* @param missing 校验失败明细,返回结果追加到此集合;方法返回 void
|
||||||
|
*/
|
||||||
|
private void collectMissingUnit(Map<String, Sys_unit> units, String unitId, String label,
|
||||||
|
UserSource source, List<String> missing) {
|
||||||
|
Sys_unit unit = units.get(unitId);
|
||||||
|
if (StrUtil.isBlank(unitId) || unit == null || StrUtil.isBlank(unit.getName())) {
|
||||||
|
missing.add(label + "编号:" + Strings.sBlank(unitId, "未提供")
|
||||||
|
+ ",相关人员:" + Strings.sBlank(source.getUsername(), "未提供姓名")
|
||||||
|
+ "(工号:" + Strings.sBlank(source.getLoginname(), "未提供") + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断自动更新的会员和福利会员
|
* 判断自动更新的会员和福利会员
|
||||||
* @param config 人员更新的配置
|
* @param config 人员更新的配置
|
||||||
@@ -553,8 +612,9 @@ public class SourceUserServiceImpl extends ViServiceImpl<UserSource> implements
|
|||||||
if (Lang.isEmpty(changeList)) {
|
if (Lang.isEmpty(changeList)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
// 单位缺失由前置校验阻断,其他字段的空值按原业务展示为无数据。
|
||||||
String changeInfos = changeList.stream().map(v -> {
|
String changeInfos = changeList.stream().map(v -> {
|
||||||
return v.getString("fieldName") + ":" + HtmlUtil.cleanHtmlTag(v.getString("sourceValue")) + "—>" + HtmlUtil.cleanHtmlTag(v.getString("newValue"));
|
return v.getString("fieldName") + ":" + HtmlUtil.cleanHtmlTag(Strings.sBlank(v.getString("sourceValue"), "无数据")) + "—>" + HtmlUtil.cleanHtmlTag(Strings.sBlank(v.getString("newValue"), "无数据"));
|
||||||
}).collect(Collectors.joining(";"));
|
}).collect(Collectors.joining(";"));
|
||||||
|
|
||||||
// 比较变更数据
|
// 比较变更数据
|
||||||
@@ -609,8 +669,9 @@ public class SourceUserServiceImpl extends ViServiceImpl<UserSource> implements
|
|||||||
if (Lang.isEmpty(changeList)) {
|
if (Lang.isEmpty(changeList)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
// 单位缺失由前置校验阻断,其他字段的空值按原业务展示为无数据。
|
||||||
String changeInfos = changeList.stream().map(v -> {
|
String changeInfos = changeList.stream().map(v -> {
|
||||||
return v.getString("fieldName") + ":" + HtmlUtil.cleanHtmlTag(v.getString("sourceValue")) + "—>" + HtmlUtil.cleanHtmlTag(v.getString("newValue"));
|
return v.getString("fieldName") + ":" + HtmlUtil.cleanHtmlTag(Strings.sBlank(v.getString("sourceValue"), "无数据")) + "—>" + HtmlUtil.cleanHtmlTag(Strings.sBlank(v.getString("newValue"), "无数据"));
|
||||||
}).collect(Collectors.joining(";"));
|
}).collect(Collectors.joining(";"));
|
||||||
// 比较变更数据
|
// 比较变更数据
|
||||||
commonCompareChange(source, user, changeTypes);
|
commonCompareChange(source, user, changeTypes);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ layout("/mobile/platform.html"){
|
|||||||
}
|
}
|
||||||
|
|
||||||
.site-reserve-slots .time {
|
.site-reserve-slots .time {
|
||||||
width: 50%;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@@ -32,23 +32,19 @@ layout("/mobile/platform.html"){
|
|||||||
/* 长禁用原因由内容撑开行高,避免固定高度导致文字覆盖下一场次。 */
|
/* 长禁用原因由内容撑开行高,避免固定高度导致文字覆盖下一场次。 */
|
||||||
.site-reserve-slots .state {
|
.site-reserve-slots .state {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 36px;
|
display: block;
|
||||||
box-sizing: border-box;
|
line-height: 18px;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 6px 8px;
|
|
||||||
line-height: 20px;
|
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: #f2f2f2;
|
color: #1989fa;
|
||||||
border-radius: 2px;
|
|
||||||
color: #929292;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-reserve-slots {
|
.site-reserve-slots {
|
||||||
padding: 10px 0px;
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 18px 16px;
|
||||||
|
padding: 8px 0 66px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
width: 95%;
|
width: 95%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@@ -56,18 +52,53 @@ layout("/mobile/platform.html"){
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-reserve-slots table {
|
/* 单个按钮容纳时间和状态,选中仅改变外观,继续复用原有时段选择逻辑。 */
|
||||||
width: 100%;
|
.site-reserve-slots .slot-button {
|
||||||
table-layout: fixed;
|
min-width: 0;
|
||||||
border-collapse: separate;
|
min-height: 54px;
|
||||||
border-spacing: 0 4px;
|
box-sizing: border-box;
|
||||||
|
padding: 8px 6px;
|
||||||
|
border: 1px solid #e6e8ed;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
|
||||||
|
color: #323233;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-reserve-slots td {
|
.site-reserve-slots .slot-button.is-selected {
|
||||||
padding: 0 6px;
|
background: #ecf6ff;
|
||||||
vertical-align: middle;
|
border-color: #a4d2ff;
|
||||||
|
color: #1989fa;
|
||||||
|
box-shadow: 0 4px 12px rgba(25, 137, 250, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.site-reserve-slots .slot-button:disabled,
|
||||||
|
.site-reserve-slots .slot-button:disabled .state {
|
||||||
|
color: #969799;
|
||||||
|
background: #f7f8fa;
|
||||||
|
box-shadow: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
/* 全天候四列时间点:网格单元承载连续底色,按钮端点保留圆角及起终角标。 */
|
||||||
|
.site-reserve-slots.full-day-slots { grid-template-columns:repeat(4,minmax(0,1fr)); gap:18px 0; }
|
||||||
|
.full-day-slots .point-cell { min-width:0; padding:0 8px; display:flex; }
|
||||||
|
.full-day-slots .point-cell.in-range { background:#e2f1ff; padding:0; }
|
||||||
|
.full-day-slots .point-cell.range-start { padding-left:8px; background:linear-gradient(to right,#fff 8px,#e2f1ff 8px); }
|
||||||
|
.full-day-slots .point-cell.range-end { padding-right:8px; background:linear-gradient(to left,#fff 8px,#e2f1ff 8px); }
|
||||||
|
.full-day-slots .slot-button { width:100%; position:relative; padding:8px 2px; }
|
||||||
|
.full-day-slots .in-range .slot-button { border-radius:0; background:#e2f1ff; border-color:transparent; box-shadow:none; }
|
||||||
|
.full-day-slots .range-start .slot-button { border-radius:12px 0 0 12px; }
|
||||||
|
.full-day-slots .range-end .slot-button { border-radius:0 12px 12px 0; }
|
||||||
|
.full-day-slots .point-cell .range-boundary { border-color:#a4d2ff; color:#1989fa; background:#ecf6ff; }
|
||||||
|
.full-day-slots .range-mark { position:absolute; color:#fff; background:#1989fa; font-size:11px; line-height:18px; padding:0 4px; }
|
||||||
|
.full-day-slots .range-mark.start { top:0; left:0; border-radius:10px 0 5px 0; }
|
||||||
|
.full-day-slots .range-mark.end { bottom:0; right:0; border-radius:5px 0 10px 0; }
|
||||||
|
|
||||||
.van-action-sheet__content {
|
.van-action-sheet__content {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
@@ -195,27 +226,28 @@ layout("/mobile/platform.html"){
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="site-reserve-slots">
|
<div v-if="isFullDay" class="site-reserve-slots full-day-slots">
|
||||||
<table>
|
<div v-for="point in fullDayPoints" :key="point.time" class="point-cell"
|
||||||
<tr v-for="item in timeData">
|
:class="{'in-range':point.inRange,'range-start':point.mark==='起' && currentRange.end,'range-end':point.mark==='终'}">
|
||||||
<td class="time">{{item.start_time + '-' + item.end_time}}</td>
|
<button type="button" class="slot-button" :class="{'range-boundary':!!point.mark}"
|
||||||
<td v-if="item.code !== 1" @click.stop="">
|
:disabled="slotsLoading || point.disabled" :aria-pressed="point.selected" :title="point.reason"
|
||||||
<div class="state" :style="'background-color: ' + item.backColor + ';color:' +item.color">
|
@click.stop="chooseFullDayPoint(point)">
|
||||||
<!-- 仅缩短过期展示文案,保留接口原始状态和其他禁用原因。 -->
|
<span class="time">{{point.time}}</span>
|
||||||
{{item.msg === '预约时段已过期' ? '已过期' : item.msg}}
|
<span class="state">{{point.reason}}</span>
|
||||||
|
<span v-if="point.mark" class="range-mark" :class="point.mark==='起' ? 'start' : 'end'">{{point.mark}}</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
|
||||||
<td v-else @click.stop="chooseTime(item)">
|
|
||||||
<div class="state"
|
|
||||||
:style="'background-color: ' + item.backColor + ';color:' +item.color">
|
|
||||||
<span v-if="!times.map(o => o.fullDay).includes(selectDate.format('YYYY-MM-DD') + ' ' + item.start_time + '-' + item.end_time)">{{item.msg}}</span>
|
|
||||||
<span v-else>
|
|
||||||
<van-icon name="passed" size="26" style="line-height: 36px;position: unset"></van-icon>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
<div v-else class="site-reserve-slots">
|
||||||
</tr>
|
<button v-for="item in timeData" :key="item.start_time + '-' + item.end_time"
|
||||||
</table>
|
type="button" class="slot-button" :disabled="item.code !== 1"
|
||||||
|
:class="{'is-selected':item.code === 1 && times.map(o => o.fullDay).includes(selectDate.format('YYYY-MM-DD') + ' ' + item.start_time + '-' + item.end_time)}"
|
||||||
|
:aria-pressed="item.code === 1 && times.map(o => o.fullDay).includes(selectDate.format('YYYY-MM-DD') + ' ' + item.start_time + '-' + item.end_time)"
|
||||||
|
@click.stop="chooseTime(item)">
|
||||||
|
<span class="time">{{item.start_time + ' - ' + item.end_time}}</span>
|
||||||
|
<!-- 保留原状态文案;选中通过按钮高亮表示,不替换时间和状态内容。 -->
|
||||||
|
<span class="state">{{item.msg === '预约时段已过期' ? '已过期' : item.msg}}</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="position: fixed; bottom: 0; width: 100%">
|
<div style="position: fixed; bottom: 0; width: 100%">
|
||||||
@@ -230,7 +262,7 @@ layout("/mobile/platform.html"){
|
|||||||
<van-cell title="所属单位">{{unit}}</van-cell>
|
<van-cell title="所属单位">{{unit}}</van-cell>
|
||||||
<van-cell title="联系电话">{{phone}}</van-cell>
|
<van-cell title="联系电话">{{phone}}</van-cell>
|
||||||
<van-cell title="预约时间">
|
<van-cell title="预约时间">
|
||||||
<div v-for="time in times">
|
<div v-for="time in confirmationTimes" :key="time.fullDay">
|
||||||
<van-tag closeable size="medium" type="primary" @close="close(time.fullDay)">
|
<van-tag closeable size="medium" type="primary" @close="close(time.fullDay)">
|
||||||
{{time.fullDay}}
|
{{time.fullDay}}
|
||||||
</van-tag>
|
</van-tag>
|
||||||
@@ -326,6 +358,8 @@ layout("/mobile/platform.html"){
|
|||||||
timeData: [],
|
timeData: [],
|
||||||
slotRequestVersion:0, slotsLoading:false,
|
slotRequestVersion:0, slotsLoading:false,
|
||||||
times: [],
|
times: [],
|
||||||
|
// 每个日期独立保存起止边界;times仍保存原接口要求的逐场次记录。
|
||||||
|
fullDayRanges: {}, slotsFailed:false,
|
||||||
weekList: [],
|
weekList: [],
|
||||||
joinUser: '',
|
joinUser: '',
|
||||||
calendarVisible: false,
|
calendarVisible: false,
|
||||||
@@ -337,6 +371,27 @@ layout("/mobile/platform.html"){
|
|||||||
reserve_type() { this.getReserve() }
|
reserve_type() { this.getReserve() }
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
isFullDay() { return Number(this.site.reserveTimeType || this.site.reservetimetype || 1) === 2 },
|
||||||
|
currentRange() { return this.fullDayRanges[this.selectDate.format('YYYY-MM-DD')] || {start:'',end:''} },
|
||||||
|
// 结束点包括最后一个场次的结束边界,24:00保留原字符串,不转换为次日00:00。
|
||||||
|
fullDayPoints() {
|
||||||
|
const range=this.currentRange, choosingEnd=range.start && !range.end
|
||||||
|
return Array.from(new Set(this.timeData.flatMap(row=>[row.start_time,row.end_time]))).sort().map(time=>{
|
||||||
|
const row=this.timeData.find(slot=>slot.start_time===time)
|
||||||
|
const selected=time===range.start || !!range.end && time>=range.start && time<=range.end
|
||||||
|
const validEnd=choosingEnd && this.fullDaySlots(range.start,time)
|
||||||
|
const disabled=!selected && (choosingEnd ? !validEnd : !row || row.code!==1)
|
||||||
|
const reason=disabled ? choosingEnd ? '不可选' : row ? row.msg==='预约时段已过期' ? '已过期' : row.msg : '仅结束时间' : '可预约'
|
||||||
|
return {time,selected,disabled,reason,inRange:!!range.end && selected,mark:time===range.start ? '起' : time===range.end ? '终' : ''}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
confirmationTimes() {
|
||||||
|
if (!this.isFullDay) return this.times
|
||||||
|
return Object.keys(this.fullDayRanges).sort().filter(day=>this.fullDayRanges[day].end).map(day=>{
|
||||||
|
const range=this.fullDayRanges[day]
|
||||||
|
return {day,fullDay:day+' '+range.start+'-'+range.end}
|
||||||
|
})
|
||||||
|
},
|
||||||
reserveTypeName() {
|
reserveTypeName() {
|
||||||
// 未选择时显示提示,避免读取不存在的选项名称。
|
// 未选择时显示提示,避免读取不存在的选项名称。
|
||||||
const option = this.reserveTypeOptions.find(item => item.value===this.reserve_type)
|
const option = this.reserveTypeOptions.find(item => item.value===this.reserve_type)
|
||||||
@@ -399,6 +454,39 @@ layout("/mobile/platform.html"){
|
|||||||
this.$set(this,'slotRequestVersion',this.slotRequestVersion+1)
|
this.$set(this,'slotRequestVersion',this.slotRequestVersion+1)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// start/end为HH:mm边界,返回全部连续可约场次;空范围、禁用或断档返回null。
|
||||||
|
fullDaySlots(start,end) {
|
||||||
|
if (!start || !end || end<=start) return null
|
||||||
|
const rows=this.timeData.filter(row=>row.start_time>=start && row.end_time<=end).slice().sort((a,b)=>a.start_time.localeCompare(b.start_time))
|
||||||
|
if (!rows.length || rows[0].start_time!==start || rows[rows.length-1].end_time!==end || rows.some((row,i)=>row.code!==1 || i>0 && rows[i-1].end_time!==row.start_time)) return null
|
||||||
|
return rows
|
||||||
|
},
|
||||||
|
// 仅清除指定日期的完整范围及提交记录,其他日期的选择保持不变。
|
||||||
|
clearFullDay(day) {
|
||||||
|
this.$set(this.fullDayRanges,day,{start:'',end:''})
|
||||||
|
this.$set(this,'times',this.times.filter(row=>row.day!==day))
|
||||||
|
},
|
||||||
|
chooseFullDayPoint(point) {
|
||||||
|
if (this.slotsLoading || this.formLoading || point.disabled) return
|
||||||
|
const day=this.selectDate.format('YYYY-MM-DD'),range=this.currentRange
|
||||||
|
if (point.selected) { this.clearFullDay(day); return }
|
||||||
|
if (!range.start || range.end) {
|
||||||
|
this.clearFullDay(day)
|
||||||
|
this.$set(this.fullDayRanges,day,{start:point.time,end:''})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const rows=this.fullDaySlots(range.start,point.time)
|
||||||
|
if (!rows) return
|
||||||
|
this.$set(this.fullDayRanges,day,{start:range.start,end:point.time})
|
||||||
|
this.$set(this,'times',this.times.filter(row=>row.day!==day).concat(rows.map(row=>Object.assign({},row,{day,fullDay:day+' '+row.start_time+'-'+row.end_time}))))
|
||||||
|
},
|
||||||
|
// 任一日期仅选起点都不能确认,避免跨日切换后漏提交未完成的范围。
|
||||||
|
validateFullDay() {
|
||||||
|
if (this.isFullDay && Object.values(this.fullDayRanges).some(range=>range.start && !range.end)) {
|
||||||
|
vant.Toast('请选择结束时间或取消未完成的选择'); return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
// 与PC共用所属分工会接口,页面销毁后的响应不再更新界面。
|
// 与PC共用所属分工会接口,页面销毁后的响应不再更新界面。
|
||||||
loadBookingUnion() {
|
loadBookingUnion() {
|
||||||
this.$set(this,'unionLoading',true)
|
this.$set(this,'unionLoading',true)
|
||||||
@@ -437,6 +525,11 @@ layout("/mobile/platform.html"){
|
|||||||
else this.clearPopupHistory(() => pjaxReplace('/mobile/activity/site/info'))
|
else this.clearPopupHistory(() => pjaxReplace('/mobile/activity/site/info'))
|
||||||
},
|
},
|
||||||
close(f) {
|
close(f) {
|
||||||
|
if (this.isFullDay) {
|
||||||
|
if (this.confirmationTimes.length===1) { vant.Toast('必须保留一个时间段'); return }
|
||||||
|
this.clearFullDay(f.split(' ')[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
if (this.times.length === 1) {
|
if (this.times.length === 1) {
|
||||||
vant.Toast('必须保留一个时间段')
|
vant.Toast('必须保留一个时间段')
|
||||||
return
|
return
|
||||||
@@ -447,6 +540,7 @@ layout("/mobile/platform.html"){
|
|||||||
onConfirm(date) {
|
onConfirm(date) {
|
||||||
this.selectDate = moment(date)
|
this.selectDate = moment(date)
|
||||||
this.setWeekList(this.selectDate)
|
this.setWeekList(this.selectDate)
|
||||||
|
this.getReserve()
|
||||||
this.calendarVisible = false
|
this.calendarVisible = false
|
||||||
},
|
},
|
||||||
//这里要排除掉休息日
|
//这里要排除掉休息日
|
||||||
@@ -469,6 +563,7 @@ layout("/mobile/platform.html"){
|
|||||||
this.getReserve()
|
this.getReserve()
|
||||||
},
|
},
|
||||||
reserve() {
|
reserve() {
|
||||||
|
if (this.slotsLoading || this.slotsFailed || !this.validateFullDay()) return
|
||||||
if (this.times.length === 0) {
|
if (this.times.length === 0) {
|
||||||
vant.Toast('请选择要预约的时段')
|
vant.Toast('请选择要预约的时段')
|
||||||
return
|
return
|
||||||
@@ -477,8 +572,9 @@ layout("/mobile/platform.html"){
|
|||||||
},
|
},
|
||||||
reserveDo() {
|
reserveDo() {
|
||||||
if(this.slotsLoading || this.formLoading)return
|
if(this.slotsLoading || this.formLoading)return
|
||||||
|
if(this.slotsFailed || !this.validateFullDay())return
|
||||||
if(!this.times.length){vant.Toast('请选择预约时段');return}
|
if(!this.times.length){vant.Toast('请选择预约时段');return}
|
||||||
if(Number(this.site.reserveTimeType || 1)===2){
|
if(this.isFullDay){
|
||||||
const days=Array.from(new Set(this.times.map(row=>row.day)))
|
const days=Array.from(new Set(this.times.map(row=>row.day)))
|
||||||
const gaps=days.some(day=>{
|
const gaps=days.some(day=>{
|
||||||
const selected=this.times.filter(row=>row.day===day).slice().sort((a,b)=>a.start_time.localeCompare(b.start_time))
|
const selected=this.times.filter(row=>row.day===day).slice().sort((a,b)=>a.start_time.localeCompare(b.start_time))
|
||||||
@@ -492,7 +588,7 @@ layout("/mobile/platform.html"){
|
|||||||
if(this.reserve_type===3 && !this.clubId){vant.Toast('请选择所属协会');return}
|
if(this.reserve_type===3 && !this.clubId){vant.Toast('请选择所属协会');return}
|
||||||
this.formLoading=true;
|
this.formLoading=true;
|
||||||
$.post('/mobile/activity/site/info/reserveDo',{siteId:this.site_id,times:JSON.stringify(this.times),message:this.message,reserve_type:this.reserve_type,clubId:this.clubId}).then((res)=>{
|
$.post('/mobile/activity/site/info/reserveDo',{siteId:this.site_id,times:JSON.stringify(this.times),message:this.message,reserve_type:this.reserve_type,clubId:this.clubId}).then((res)=>{
|
||||||
if(res.code===0){this.clearPopupHistory(()=>{vant.Toast('预约成功');this.times=[];this.message='';this.getReserve();this.onLoad()})}
|
if(res.code===0){this.clearPopupHistory(()=>{vant.Toast('预约成功');this.times=[];this.$set(this,'fullDayRanges',{});this.message='';this.getReserve();this.onLoad()})}
|
||||||
else{vant.Toast(res.msg)}
|
else{vant.Toast(res.msg)}
|
||||||
}).always(()=>{this.formLoading=false})
|
}).always(()=>{this.formLoading=false})
|
||||||
},
|
},
|
||||||
@@ -519,13 +615,23 @@ layout("/mobile/platform.html"){
|
|||||||
const version=this.slotRequestVersion+1
|
const version=this.slotRequestVersion+1
|
||||||
this.$set(this,'slotRequestVersion',version)
|
this.$set(this,'slotRequestVersion',version)
|
||||||
this.$set(this,'slotsLoading',true)
|
this.$set(this,'slotsLoading',true)
|
||||||
|
this.$set(this,'slotsFailed',false)
|
||||||
this.$set(this,'timeData',[])
|
this.$set(this,'timeData',[])
|
||||||
// 未选类型时仍按团体预约规则查询占用,仅用于场次查询,不回填表单类型。
|
// 未选类型时仍按团体预约规则查询占用,仅用于场次查询,不回填表单类型。
|
||||||
return $.post('/mobile/activity/site/info/getReserve',{siteId:this.site_id,day:moment(this.selectDate).format('YYYY-MM-DD'),reserveType:this.reserve_type===null ? 2 : this.reserve_type}).then(res=>{
|
return $.post('/mobile/activity/site/info/getReserve',{siteId:this.site_id,day:moment(this.selectDate).format('YYYY-MM-DD'),reserveType:this.reserve_type===null ? 2 : this.reserve_type}).then(res=>{
|
||||||
if(version!==this.slotRequestVersion)return
|
if(version!==this.slotRequestVersion)return
|
||||||
if(res && res.code===0)this.$set(this,'timeData',res.data)
|
if(res && res.code===0 && Array.isArray(res.data)) {
|
||||||
else vant.Toast(res && res.msg || '场次加载失败')
|
this.$set(this,'timeData',res.data)
|
||||||
},()=>{if(version===this.slotRequestVersion)vant.Toast('场次加载失败,请重试')}).always(()=>{
|
// 切换日期或类型后重新核验该日范围,保留其他日期;占用变化时整段清空。
|
||||||
|
if (this.isFullDay) {
|
||||||
|
const range=this.currentRange
|
||||||
|
if (range.start && (range.end ? !this.fullDaySlots(range.start,range.end) : !res.data.some(row=>row.start_time===range.start && row.code===1))) {
|
||||||
|
this.clearFullDay(this.selectDate.format('YYYY-MM-DD'))
|
||||||
|
vant.Toast('所选时间已失效,请重新选择')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { this.$set(this,'slotsFailed',true); vant.Toast(res && res.msg || '场次加载失败') }
|
||||||
|
},()=>{if(version===this.slotRequestVersion){this.$set(this,'slotsFailed',true);vant.Toast('场次加载失败,请重试')}}).always(()=>{
|
||||||
if(version===this.slotRequestVersion)this.$set(this,'slotsLoading',false)
|
if(version===this.slotRequestVersion)this.$set(this,'slotsLoading',false)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -137,7 +137,9 @@ layout("/layouts/platform.html"){
|
|||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item prop="name" label="场地名称">
|
<el-form-item prop="name" label="场地名称">
|
||||||
<el-select v-model="formData.name" placeholder="请选择场地名称" style="width:100%">
|
<!-- 保留常用名称选项,其他名称可输入后选择创建项或按回车确认。 -->
|
||||||
|
<el-select v-model="formData.name" filterable allow-create default-first-option
|
||||||
|
placeholder="请选择或输入场地名称" style="width:100%">
|
||||||
<el-option label="职工之家" value="职工之家"></el-option>
|
<el-option label="职工之家" value="职工之家"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -319,7 +321,11 @@ layout("/layouts/platform.html"){
|
|||||||
year: ""
|
year: ""
|
||||||
},
|
},
|
||||||
formRules: {
|
formRules: {
|
||||||
name: [{required: true, message: '请选择场地名称', trigger: ['blur', 'change']}],
|
// 自定义名称与后端保持一致:必填、拒绝全空格且不超过实体字段的100字符。
|
||||||
|
name: [
|
||||||
|
{required: true, whitespace: true, message: '请选择或输入场地名称', trigger: ['blur', 'change']},
|
||||||
|
{max: 100, message: '场地名称不能超过100字符', trigger: ['blur', 'change']}
|
||||||
|
],
|
||||||
address: [{required: true, message: '请填写场地地址', trigger: ['blur', 'change']}],
|
address: [{required: true, message: '请填写场地地址', trigger: ['blur', 'change']}],
|
||||||
contact_person: [{required: true, message: '请填写场地管理员', trigger: ['blur', 'change']}],
|
contact_person: [{required: true, message: '请填写场地管理员', trigger: ['blur', 'change']}],
|
||||||
contact_phone: [{required: true, message: '请填写联系电话', trigger: ['blur', 'change']}],
|
contact_phone: [{required: true, message: '请填写联系电话', trigger: ['blur', 'change']}],
|
||||||
@@ -422,9 +428,9 @@ layout("/layouts/platform.html"){
|
|||||||
this.findOneSite(id).then((row)=>{if(row){this.initSchedule(row);this.$set(this, 'formData', row);this.$refs.guava.edit()}});
|
this.findOneSite(id).then((row)=>{if(row){this.initSchedule(row);this.$set(this, 'formData', row);this.$refs.guava.edit()}});
|
||||||
},
|
},
|
||||||
openAdd() {
|
openAdd() {
|
||||||
// 新建只提供职工之家;校区由用户明确选择,不沿用上次编辑值。
|
// 新建时名称留空,由用户选择或输入;校区由用户明确选择,不沿用上次编辑值。
|
||||||
this.$set(this, 'formData', {
|
this.$set(this, 'formData', {
|
||||||
name: '职工之家',
|
name: '',
|
||||||
campus: '',
|
campus: '',
|
||||||
state: true,
|
state: true,
|
||||||
multiple: true,
|
multiple: true,
|
||||||
|
|||||||
@@ -109,6 +109,17 @@ layout("/layouts/platform.html"){
|
|||||||
.site-booking-date { flex:0 0 88px; white-space:nowrap; font-weight:500; }
|
.site-booking-date { flex:0 0 88px; white-space:nowrap; font-weight:500; }
|
||||||
.site-booking-ranges { display:flex; flex-wrap:wrap; gap:2px 12px; }
|
.site-booking-ranges { display:flex; flex-wrap:wrap; gap:2px 12px; }
|
||||||
.site-booking-range { white-space:nowrap; }
|
.site-booking-range { white-space:nowrap; }
|
||||||
|
/* 时间选择区平铺全部选项,日期和按钮自动换行;禁用原因仅通过悬停提示展示。 */
|
||||||
|
.site-time-dates { display:flex; flex-wrap:wrap; gap:8px; margin:12px 0 20px; }
|
||||||
|
.site-time-group { display:flex; gap:16px; margin:20px 0; align-items:flex-start; }
|
||||||
|
.site-time-heading { flex:0 0 140px; line-height:36px; color:#606266; white-space:nowrap; }
|
||||||
|
.site-time-options { display:flex; flex-wrap:wrap; gap:8px; flex:1; }
|
||||||
|
.site-time-options .el-button { margin:0; position:relative; min-width:82px; min-height:36px; }
|
||||||
|
.site-time-options .is-range { background:#ecf5ff; border-color:#409eff; color:#1672bd; }
|
||||||
|
.site-time-mark { position:absolute; top:2px; right:3px; font-size:10px; line-height:12px; font-weight:bold; }
|
||||||
|
.site-time-summary { margin:16px 0; color:#303133; line-height:24px; }
|
||||||
|
.site-time-help { color:#909399; line-height:24px; }
|
||||||
|
@media (max-width:800px) { .site-time-group { flex-direction:column; gap:4px; } .site-time-heading { flex-basis:auto; } }
|
||||||
</style>
|
</style>
|
||||||
<div id="app" v-cloak>
|
<div id="app" v-cloak>
|
||||||
<guava ref="guava">
|
<guava ref="guava">
|
||||||
@@ -320,7 +331,7 @@ layout("/layouts/platform.html"){
|
|||||||
您正在预约【<span style="color: #409EFF">{{infoViewData.name}}</span>】活动场地
|
您正在预约【<span style="color: #409EFF">{{infoViewData.name}}</span>】活动场地
|
||||||
</div>
|
</div>
|
||||||
<div style="color:#909399;">
|
<div style="color:#909399;">
|
||||||
开放时间段为:<span v-for="(item,index) in infoViewData.open_hours" style="color: #303133">
|
开放时间段为:<span v-for="(item,index) in displayedOpenHours" style="color: #303133">
|
||||||
<span v-if="infoViewData.workday === true && index === 0">工作日</span>
|
<span v-if="infoViewData.workday === true && index === 0">工作日</span>
|
||||||
【{{item.start_time}} - {{item.end_time}}】
|
【{{item.start_time}} - {{item.end_time}}】
|
||||||
</span>
|
</span>
|
||||||
@@ -368,7 +379,32 @@ layout("/layouts/platform.html"){
|
|||||||
<el-dialog title="新增场地预约" class="dia" :close-on-click-modal="false" @close="diaClose"
|
<el-dialog title="新增场地预约" class="dia" :close-on-click-modal="false" @close="diaClose"
|
||||||
:visible.sync="addDialogVisible" custom-class="ViewDialogClass" width="70%" top="2%">
|
:visible.sync="addDialogVisible" custom-class="ViewDialogClass" width="70%" top="2%">
|
||||||
|
|
||||||
<vi-title :title="'您已选择的预约日期为:'+ dayArray.toString()" style="margin-left: 48px"></vi-title>
|
<el-tabs :value="bookingTab" :before-leave="beforeBookingTabLeave" @input="changeBookingTab">
|
||||||
|
<el-tab-pane label="选择时间" name="time">
|
||||||
|
<div>已选择的预约日期</div>
|
||||||
|
<div class="site-time-dates"><el-tag v-for="day in dayArray" :key="day">{{day}}</el-tag></div>
|
||||||
|
<div class="site-time-help">所有选中日期使用同一组时间,只有全部日期均可预约的时间才能选择。</div>
|
||||||
|
<div class="site-time-summary">{{isFullDayBooking ? '先点击开始时间,再点击结束时间;再次点击已选时间可取消本次选择。' : '点击时间段选择,再次点击取消,可选择多个时间段。'}}</div>
|
||||||
|
<div v-loading="slotsLoading" style="min-height:160px">
|
||||||
|
<div v-if="slotsLoadFailed"><el-alert title="时间加载失败,请重试" type="error" :closable="false"></el-alert><el-button @click="loadBookingSlots">重新加载</el-button></div>
|
||||||
|
<div v-else-if="!slotsLoading && !bookingSlots.length" class="site-time-help">暂无开放时段</div>
|
||||||
|
<div v-for="group in bookingTimeGroups" :key="group.label" class="site-time-group">
|
||||||
|
<div class="site-time-heading">{{group.label}}</div>
|
||||||
|
<div class="site-time-options">
|
||||||
|
<el-button v-for="item in group.items" :key="item.key" size="small"
|
||||||
|
:disabled="slotsLoading || item.disabled" :title="item.reason || item.label"
|
||||||
|
:type="item.mark ? 'primary' : 'default'" :class="{'is-range':item.selected && !item.mark}"
|
||||||
|
@click="chooseBookingTime(item)">
|
||||||
|
{{item.label}}<span v-if="item.mark" class="site-time-mark">{{item.mark}}</span>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="site-time-summary">已选时间:{{bookingTimeSummary}}</div>
|
||||||
|
<el-button size="small" :disabled="slotsLoading" @click="clearBookingTime">清空选择</el-button>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="填写信息" name="info">
|
||||||
|
<div class="site-time-dates"><el-tag v-for="day in dayArray" :key="day">{{day}}</el-tag></div>
|
||||||
|
|
||||||
<el-form :model="formData" ref="addForm" :rules="formRules" size="small" label-position="right"
|
<el-form :model="formData" ref="addForm" :rules="formRules" size="small" label-position="right"
|
||||||
label-width="100px">
|
label-width="100px">
|
||||||
@@ -427,13 +463,8 @@ layout("/layouts/platform.html"){
|
|||||||
</el-form-item>-->
|
</el-form-item>-->
|
||||||
|
|
||||||
<el-form-item label="预约时间" prop="time">
|
<el-form-item label="预约时间" prop="time">
|
||||||
<el-select v-model="formData.time" style="width: 100%" multiple :disabled="slotsLoading" filterable @change="timeChange"
|
<span>{{bookingTimeSummary}}</span>
|
||||||
placeholder="请选择预约时间">
|
<el-button type="text" :disabled="formLoading" @click="changeBookingTab('time')">修改时间</el-button>
|
||||||
<el-option v-for="item in bookingSlots" :key="item.start_time + item.end_time"
|
|
||||||
:label="item.disabled ? item.start_time + ' - ' + item.end_time + '(' + item.msg + ')' : item.start_time + ' - ' + item.end_time"
|
|
||||||
:disabled="item.disabled"
|
|
||||||
:value="item.start_time + '-' + item.end_time"></el-option>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<!--<el-form-item label="预约时间" prop="start_time">
|
<!--<el-form-item label="预约时间" prop="start_time">
|
||||||
@@ -486,10 +517,14 @@ layout("/layouts/platform.html"){
|
|||||||
type="textarea" :autosize="{ minRows: 4, maxRows: 6}"></el-input>
|
type="textarea" :autosize="{ minRows: 4, maxRows: 6}"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
|
||||||
<span slot="footer" class="dialog-footer">
|
<span slot="footer" class="dialog-footer">
|
||||||
<el-button @click="closeAdd">取 消</el-button>
|
<el-button :disabled="formLoading" @click="closeAdd">取 消</el-button>
|
||||||
<el-button type="primary" :disabled="subDis" @click="doAdd">确 定</el-button>
|
<el-button v-if="bookingTab==='info'" :disabled="formLoading" @click="changeBookingTab('time')">上一步</el-button>
|
||||||
|
<el-button v-if="bookingTab==='time'" type="primary" :disabled="slotsLoading" @click="nextBookingTab">下一步</el-button>
|
||||||
|
<el-button v-else type="primary" :loading="formLoading" :disabled="subDis || slotsLoading" @click="doAdd">确 定</el-button>
|
||||||
</span>
|
</span>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
@@ -513,6 +548,7 @@ layout("/layouts/platform.html"){
|
|||||||
myClubs: [],
|
myClubs: [],
|
||||||
bookingUnionName:'', unionLoading:false, unionRequestVersion:0,
|
bookingUnionName:'', unionLoading:false, unionRequestVersion:0,
|
||||||
bookingSlots:[], slotsLoading:false, slotsVersion:0,
|
bookingSlots:[], slotsLoading:false, slotsVersion:0,
|
||||||
|
bookingTab:'time', bookingRangeStart:'', bookingRangeEnd:'', slotsLoadFailed:false,
|
||||||
formLoading: false,
|
formLoading: false,
|
||||||
slider_width: 0,
|
slider_width: 0,
|
||||||
single_width: 0,
|
single_width: 0,
|
||||||
@@ -569,11 +605,114 @@ layout("/layouts/platform.html"){
|
|||||||
'2022-10-01', '2022-10-02', '2022-10-03', '2022-10-04', '2022-10-05', '2022-10-06', '2022-10-07',],
|
'2022-10-01', '2022-10-02', '2022-10-03', '2022-10-04', '2022-10-05', '2022-10-06', '2022-10-07',],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
isFullDayBooking() {
|
||||||
|
return Number(this.infoViewData.reserveTimeType || this.infoViewData.reservetimetype || 1) === 2
|
||||||
|
},
|
||||||
|
bookingTimeSummary() {
|
||||||
|
if (this.isFullDayBooking) return this.bookingRangeEnd ? this.bookingRangeStart + ' - ' + this.bookingRangeEnd : this.bookingRangeStart ? this.bookingRangeStart + ' 起(请选择结束时间)' : '尚未选择'
|
||||||
|
return (this.formData.time || []).join('、') || '尚未选择'
|
||||||
|
},
|
||||||
|
// 全天候展示时间边界(含最后结束点),分段展示完整场次;12:00及之后统一归入下午。
|
||||||
|
bookingTimeGroups() {
|
||||||
|
let items
|
||||||
|
if (this.isFullDayBooking) {
|
||||||
|
const points = Array.from(new Set(this.bookingSlots.flatMap(row => [row.start_time, row.end_time]))).sort()
|
||||||
|
const choosingEnd = this.bookingRangeStart && !this.bookingRangeEnd
|
||||||
|
items = points.map(point => {
|
||||||
|
const startSlot = this.bookingSlots.find(row => row.start_time === point)
|
||||||
|
const validEnd = choosingEnd && this.rangeBookingSlots(this.bookingRangeStart, point)
|
||||||
|
// 已选端点和范围内时间保持可点击,包括仅可作为结束边界的最后时间点,方便取消选择。
|
||||||
|
const selected = point === this.bookingRangeStart || !!this.bookingRangeEnd && point >= this.bookingRangeStart && point <= this.bookingRangeEnd
|
||||||
|
const disabled = !selected && (choosingEnd ? !validEnd : !startSlot || startSlot.disabled)
|
||||||
|
return {key:point, point, label:point, disabled,
|
||||||
|
reason:disabled ? choosingEnd ? '不可作为结束' : startSlot ? startSlot.msg : '仅结束时间' : '',
|
||||||
|
selected,
|
||||||
|
mark:point === this.bookingRangeStart ? '起' : point === this.bookingRangeEnd ? '终' : ''}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
items = this.bookingSlots.map(row => ({key:row.start_time + '-' + row.end_time, point:row.start_time,
|
||||||
|
label:row.start_time + ' - ' + row.end_time, disabled:row.disabled, reason:row.msg,
|
||||||
|
selected:(this.formData.time || []).includes(row.start_time + '-' + row.end_time), mark:''}))
|
||||||
|
}
|
||||||
|
return [{label:'上午(00:00 - 12:00)',items:items.filter(row => row.point < '12:00')},
|
||||||
|
{label:'下午(12:00 - 24:00)',items:items.filter(row => row.point >= '12:00')}].filter(group => group.items.length)
|
||||||
|
},
|
||||||
|
// 全天候场次仅在标题中合并为完整范围,保留原始时段供选择和提交;旧数据按分段展示。
|
||||||
|
displayedOpenHours() {
|
||||||
|
const hours = this.infoViewData.open_hours || []
|
||||||
|
if (Number(this.infoViewData.reserveTimeType || this.infoViewData.reservetimetype || 1) !== 2 || !hours.length) return hours
|
||||||
|
// 独立计算最早开始和最晚结束时间,不依赖接口顺序,也不修改原始数组。
|
||||||
|
return [{
|
||||||
|
start_time: hours.reduce((start, row) => row.start_time < start ? row.start_time : start, hours[0].start_time),
|
||||||
|
end_time: hours.reduce((end, row) => row.end_time > end ? row.end_time : end, hours[0].end_time)
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
'guava': httpVueLoader('/components/plugins/Guava.vue'),
|
'guava': httpVueLoader('/components/plugins/Guava.vue'),
|
||||||
'reserve-info': httpVueLoader('/components/activity/site/ReserveInfo.vue?v=20260910-2'),
|
'reserve-info': httpVueLoader('/components/activity/site/ReserveInfo.vue?v=20260910-2'),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// start/end为HH:mm边界,返回可提交场次数组;不连续、跨禁用段或边界不匹配时返回null。
|
||||||
|
rangeBookingSlots(start, end) {
|
||||||
|
if (!start || !end || end <= start) return null
|
||||||
|
const rows = this.bookingSlots.filter(row => row.start_time >= start && row.end_time <= end).slice().sort((a,b) => a.start_time.localeCompare(b.start_time))
|
||||||
|
if (!rows.length || rows[0].start_time !== start || rows[rows.length-1].end_time !== end || rows.some((row,index) => row.disabled || index > 0 && rows[index-1].end_time !== row.start_time)) return null
|
||||||
|
return rows.map(row => row.start_time + '-' + row.end_time)
|
||||||
|
},
|
||||||
|
clearBookingTime() {
|
||||||
|
this.$set(this,'bookingRangeStart','')
|
||||||
|
this.$set(this,'bookingRangeEnd','')
|
||||||
|
this.$set(this.formData,'time',[])
|
||||||
|
this.timeChange([])
|
||||||
|
},
|
||||||
|
// 全天候两次点击确定起止范围;分段切换单个场次,统一同步原接口使用的time数组。
|
||||||
|
chooseBookingTime(item) {
|
||||||
|
if (this.slotsLoading || this.formLoading || item.disabled) return
|
||||||
|
if (this.isFullDayBooking) {
|
||||||
|
// 全天候取消的是完整连续范围,不能留下中间缺口;只选起点时也允许再次点击清空。
|
||||||
|
if (item.point === this.bookingRangeStart || this.bookingRangeEnd && item.point >= this.bookingRangeStart && item.point <= this.bookingRangeEnd) {
|
||||||
|
this.clearBookingTime()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.bookingRangeStart || this.bookingRangeEnd) {
|
||||||
|
this.clearBookingTime()
|
||||||
|
this.$set(this,'bookingRangeStart',item.point)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const times = this.rangeBookingSlots(this.bookingRangeStart,item.point)
|
||||||
|
if (!times) return
|
||||||
|
this.$set(this,'bookingRangeEnd',item.point)
|
||||||
|
this.$set(this.formData,'time',times)
|
||||||
|
} else {
|
||||||
|
const times = (this.formData.time || []).slice()
|
||||||
|
const index = times.indexOf(item.key)
|
||||||
|
if (index >= 0) times.splice(index,1)
|
||||||
|
else times.push(item.key)
|
||||||
|
this.$set(this.formData,'time',times.sort())
|
||||||
|
}
|
||||||
|
this.timeChange(this.formData.time)
|
||||||
|
},
|
||||||
|
// 切换到表单及提交前共用校验,防止绕过第一步或提交加载失败、已失效的时段。
|
||||||
|
validateBookingTime(showMessage = true) {
|
||||||
|
const times = this.formData.time || []
|
||||||
|
const range = this.isFullDayBooking ? this.rangeBookingSlots(this.bookingRangeStart,this.bookingRangeEnd) : null
|
||||||
|
const valid = !this.slotsLoading && !this.slotsLoadFailed && times.length > 0
|
||||||
|
&& times.every(time => this.bookingSlots.some(row => !row.disabled && row.start_time + '-' + row.end_time === time))
|
||||||
|
&& (!this.isFullDayBooking || range && range.length === times.length && range.every(time => times.includes(time)))
|
||||||
|
if (!valid && showMessage) this.$message.warning('请选择完整且可预约的时间')
|
||||||
|
return !!valid
|
||||||
|
},
|
||||||
|
beforeBookingTabLeave(name) {
|
||||||
|
return !this.formLoading && (name !== 'info' || this.validateBookingTime())
|
||||||
|
},
|
||||||
|
changeBookingTab(name) {
|
||||||
|
if (!this.formLoading) this.$set(this,'bookingTab',name)
|
||||||
|
},
|
||||||
|
nextBookingTab() {
|
||||||
|
if (this.validateBookingTime()) this.changeBookingTab('info')
|
||||||
|
},
|
||||||
// 每次打开表单刷新组织信息;结果只用于展示,提交仍由服务端取得真实组织。
|
// 每次打开表单刷新组织信息;结果只用于展示,提交仍由服务端取得真实组织。
|
||||||
loadBookingUnion() {
|
loadBookingUnion() {
|
||||||
const version=this.unionRequestVersion+1
|
const version=this.unionRequestVersion+1
|
||||||
@@ -589,9 +728,11 @@ layout("/layouts/platform.html"){
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
diaClose() {
|
diaClose() {
|
||||||
this.$set(this.formData, 'time', [])
|
// 关闭后令在途时段请求失效,避免旧弹框响应污染下一次选择。
|
||||||
this.formData.start_time = ''
|
this.$set(this,'slotsVersion',this.slotsVersion+1)
|
||||||
this.formData.end_time = ''
|
this.$set(this,'slotsLoading',false)
|
||||||
|
this.$set(this,'bookingTab','time')
|
||||||
|
this.clearBookingTime()
|
||||||
},
|
},
|
||||||
timeChange(values) {
|
timeChange(values) {
|
||||||
const times=values.slice().sort()
|
const times=values.slice().sort()
|
||||||
@@ -603,6 +744,7 @@ layout("/layouts/platform.html"){
|
|||||||
const version=this.slotsVersion+1
|
const version=this.slotsVersion+1
|
||||||
this.$set(this,'slotsVersion',version)
|
this.$set(this,'slotsVersion',version)
|
||||||
this.$set(this,'slotsLoading',true)
|
this.$set(this,'slotsLoading',true)
|
||||||
|
this.$set(this,'slotsLoadFailed',false)
|
||||||
this.$set(this,'bookingSlots',[])
|
this.$set(this,'bookingSlots',[])
|
||||||
const days=this.dayArray.slice(), results=[]
|
const days=this.dayArray.slice(), results=[]
|
||||||
let pending=days.length, failed=false
|
let pending=days.length, failed=false
|
||||||
@@ -610,7 +752,7 @@ layout("/layouts/platform.html"){
|
|||||||
days.forEach(day=>{
|
days.forEach(day=>{
|
||||||
$.post('/mobile/activity/site/info/getReserve',{siteId:this.formData.site_id,day,reserveType:this.formData.reserve_type}).then(res=>{
|
$.post('/mobile/activity/site/info/getReserve',{siteId:this.formData.site_id,day,reserveType:this.formData.reserve_type}).then(res=>{
|
||||||
if(version!==this.slotsVersion)return
|
if(version!==this.slotsVersion)return
|
||||||
if(res && res.code===0)results.push(res.data)
|
if(res && res.code===0 && Array.isArray(res.data))results.push(res.data.map(row => Object.assign({},row,{sourceDay:day})))
|
||||||
else{failed=true;this.$message.error(res && res.msg || '场次加载失败')}
|
else{failed=true;this.$message.error(res && res.msg || '场次加载失败')}
|
||||||
},()=>{if(version===this.slotsVersion){failed=true;this.$message.error('场次加载失败')}}).always(()=>{
|
},()=>{if(version===this.slotsVersion){failed=true;this.$message.error('场次加载失败')}}).always(()=>{
|
||||||
if(version!==this.slotsVersion)return
|
if(version!==this.slotsVersion)return
|
||||||
@@ -619,9 +761,16 @@ layout("/layouts/platform.html"){
|
|||||||
if(!failed && results.length)this.$set(this,'bookingSlots',results[0].map(row=>{
|
if(!failed && results.length)this.$set(this,'bookingSlots',results[0].map(row=>{
|
||||||
const blocked=results.map(rows=>rows.find(item=>item.start_time===row.start_time && item.end_time===row.end_time)).find(item=>!item || item.code!==1)
|
const blocked=results.map(rows=>rows.find(item=>item.start_time===row.start_time && item.end_time===row.end_time)).find(item=>!item || item.code!==1)
|
||||||
const missing=results.some(rows=>!rows.some(item=>item.start_time===row.start_time && item.end_time===row.end_time))
|
const missing=results.some(rows=>!rows.some(item=>item.start_time===row.start_time && item.end_time===row.end_time))
|
||||||
return Object.assign({},row,{disabled:missing || !!blocked,msg:missing ? '场次已变化' : blocked ? blocked.msg : row.msg})
|
return Object.assign({},row,{disabled:missing || !!blocked,msg:missing ? '场次已变化' : blocked ? blocked.sourceDay + ' ' + blocked.msg : row.msg})
|
||||||
}))
|
}).sort((a,b)=>a.start_time.localeCompare(b.start_time)))
|
||||||
|
this.$set(this,'slotsLoadFailed',failed)
|
||||||
this.$set(this,'slotsLoading',false)
|
this.$set(this,'slotsLoading',false)
|
||||||
|
// 类型切换后仅保留仍有效的完整选择;失效则返回时间页重新选择。
|
||||||
|
if ((this.formData.time.length || this.bookingRangeStart) && !this.validateBookingTime(false)) {
|
||||||
|
this.clearBookingTime()
|
||||||
|
this.$set(this,'bookingTab','time')
|
||||||
|
this.$message.warning('所选时间已失效,请重新选择')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -772,19 +921,13 @@ layout("/layouts/platform.html"){
|
|||||||
|
|
||||||
this.$set(this.formData, 'reserve_type', 2)
|
this.$set(this.formData, 'reserve_type', 2)
|
||||||
// 新开申请时清空上次时段;类型切换刷新场次时不重置选择。
|
// 新开申请时清空上次时段;类型切换刷新场次时不重置选择。
|
||||||
this.$set(this.formData, 'time', [])
|
this.clearBookingTime()
|
||||||
this.timeChange([])
|
this.$set(this,'bookingTab','time')
|
||||||
this.loadBookingUnion()
|
this.loadBookingUnion()
|
||||||
this.addDialogVisible = true
|
this.addDialogVisible = true
|
||||||
this.loadBookingSlots()
|
this.loadBookingSlots()
|
||||||
|
|
||||||
this.$nextTick(() => {
|
// 原滑块已停用,打开弹框不再读取不存在的滑块节点或注册resize事件。
|
||||||
|
|
||||||
this.flushSliderWidth()
|
|
||||||
$(window).resize(() => {
|
|
||||||
vue.flushSliderWidth()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
},
|
},
|
||||||
doDelete(id, op) {
|
doDelete(id, op) {
|
||||||
@@ -799,7 +942,8 @@ layout("/layouts/platform.html"){
|
|||||||
return (moment("1970-01-01 " + hm).valueOf() + HOUR8) / HOUR1
|
return (moment("1970-01-01 " + hm).valueOf() + HOUR8) / HOUR1
|
||||||
},
|
},
|
||||||
doAdd() {
|
doAdd() {
|
||||||
if (this.slotsLoading || !this.formData.time || !this.formData.time.length) { this.$message.warning('请选择预约时间'); return; }
|
if (this.formLoading) return
|
||||||
|
if (!this.validateBookingTime()) { this.$set(this,'bookingTab','time'); return; }
|
||||||
if (this.formData.reserve_type===3 && !this.formData.clubId) { this.$message.warning('请选择所属协会'); return; }
|
if (this.formData.reserve_type===3 && !this.formData.clubId) { this.$message.warning('请选择所属协会'); return; }
|
||||||
const selected=this.formData.time.slice().sort()
|
const selected=this.formData.time.slice().sort()
|
||||||
if(Number(this.infoViewData.reserveTimeType || this.infoViewData.reservetimetype || 1)===2 && selected.some((time,i)=>i>0 && selected[i-1].split('-')[1]!==time.split('-')[0])){
|
if(Number(this.infoViewData.reserveTimeType || this.infoViewData.reservetimetype || 1)===2 && selected.some((time,i)=>i>0 && selected[i-1].split('-')[1]!==time.split('-')[0])){
|
||||||
|
|||||||
Reference in New Issue
Block a user