Compare commits
17
Commits
194aa4ff0a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
216329a345 | ||
|
|
b6a4a50e10 | ||
|
|
1906371506 | ||
|
|
ce773afea6 | ||
|
|
68a838221b | ||
|
|
cf48e2211d | ||
|
|
8089a1a2d8 | ||
|
|
05275e65ec | ||
|
|
3e9fdf0584 | ||
|
|
626b3a0713 | ||
|
|
1706f2d60d | ||
|
|
660864a30d | ||
|
|
3c119d9e34 | ||
|
|
91e820dfbb | ||
|
|
d8a4cfa250 | ||
|
|
49b44937d1 | ||
|
|
b2dd2681a2 |
@@ -33,14 +33,14 @@ public class SysCompletedController {
|
||||
@Inject
|
||||
private MainPageNeedItemsService mainPageNeedItemsService;
|
||||
|
||||
@Inject
|
||||
private io.v.nutz.sys.services.SysCompletedService completedService;
|
||||
|
||||
/** 返回当前登录人的手机已办,已在 service 中校验目标页面权限。 */
|
||||
@At
|
||||
@ViReturn
|
||||
public Object getCompletes(){
|
||||
CndPlus cnd = CndPlus.create();
|
||||
cnd.and("auditBy","=", ShiroUtil.getPrincipalProperty("id"));
|
||||
cnd.and("YEAR(auditTime)","=",DateUtil.thisYear());
|
||||
cnd.desc("auditTime");
|
||||
return baseService.dao().query(Sys_completed.class,cnd);
|
||||
return completedService.getCompletes();
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ public class SysCompletedController {
|
||||
//审核完成前的待办
|
||||
List<NeedItems> itemsList = Arrays.asList(items);
|
||||
//审核完成后的待办
|
||||
List<NeedItems> needItemsList = mainPageNeedItemsService.getNeedItems();
|
||||
List<NeedItems> needItemsList = mainPageNeedItemsService.getNeedItems(true);
|
||||
//过滤没有移动端地址和数量为0的待办
|
||||
List<NeedItems> needItems = needItemsList.stream().filter(v-> v.getCount()>0 && Strings.isNotBlank(v.getMobileHref())).collect(Collectors.toList());
|
||||
//审核前与审核后待办取差集
|
||||
|
||||
@@ -52,6 +52,24 @@ public class SysHomeActivityController {
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@RequiresPermissions("sys.homeActivity")
|
||||
public Object doHandle(Sys_home_activity activity) {
|
||||
try {
|
||||
sysHomeActivityService.saveHomeActivity(activity);
|
||||
return Result.success();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@At
|
||||
@RequiresPermissions("sys.homeActivity")
|
||||
public Object doDelete(@Valid String id) {
|
||||
sysHomeActivityService.delete(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@RequiresPermissions("sys.homeActivity")
|
||||
public Object enable(@Valid String id) {
|
||||
|
||||
+5
-67
@@ -1,22 +1,13 @@
|
||||
package io.v.nutz.sys.controllers.platform.sys;
|
||||
|
||||
import cn.wizzer.framework.base.Result;
|
||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.Daos;
|
||||
import org.nutz.dao.util.cri.Static;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 任务待办
|
||||
@@ -27,67 +18,14 @@ import java.util.List;
|
||||
public class SysLocalProcessController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
private io.v.nutz.sys.services.SysLocalProcessService sysLocalProcessService;
|
||||
|
||||
/**
|
||||
* 查询待办
|
||||
* @param mode 1待处理 2已处理 3我发起的
|
||||
* @return
|
||||
*/
|
||||
/** mode=1 待办、2 已办、3 本人发起;mobile=true 手机端,默认 PC;返回 code/data,data 为包含双端处理地址的任务数组。 */
|
||||
@At
|
||||
@RequiresAuthentication
|
||||
@Ok("json:full")
|
||||
public Result todoList(Integer mode) {
|
||||
if (mode == 1 || mode == 2) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
t.taskNodeName,
|
||||
t.createdByUserName,
|
||||
t.formUrl,
|
||||
t.formUrlView,
|
||||
t.createdOn,
|
||||
t2.processName,
|
||||
t2.nodeName
|
||||
FROM
|
||||
sys_local_process_instance_task t
|
||||
LEFT JOIN sys_local_process_instance t2 ON t2.processUniqueId = t.processUniqueId
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and(new Static("JSON_CONTAINS( t.assignments, '\"" + ShiroUtil.getPrincipalProperty("loginname") + "\"')"));
|
||||
cnd.and("t.status", "=", mode);
|
||||
cnd.and("t.taskDeleteFlag", "=", 0);
|
||||
cnd.desc("t2.processInitiationTime");
|
||||
sql.setCondition(cnd);
|
||||
List<NutMap> list = (List<NutMap>) Daos.query(dao, sql.toString(), Sqls.callback.maps());
|
||||
return Result.success(list);
|
||||
} else if (mode == 3) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
t.id,
|
||||
t.processUniqueId,
|
||||
t.processName,
|
||||
t.nodeName,
|
||||
t.pcUrl,
|
||||
t.mobileUrl,
|
||||
t.pcUrl as formUrl,
|
||||
t.processInstanceStatus,
|
||||
t.processInitiationTime as createdOn,
|
||||
u.username as createdByUserName
|
||||
FROM
|
||||
sys_local_process_instance t
|
||||
LEFT JOIN sys_user u ON u.id = t.processInitiatorId
|
||||
WHERE
|
||||
t.processInitiatorId = @id
|
||||
AND t.processDeleteFlag = 0
|
||||
AND t.delFlag = 0
|
||||
ORDER BY t.processInitiationTime DESC
|
||||
""");
|
||||
sql.setParam("id", ShiroUtil.getUserId());
|
||||
List<NutMap> list = (List<NutMap>) Daos.query(dao, sql.toString(), Sqls.callback.maps());
|
||||
return Result.success(list);
|
||||
}
|
||||
return null;
|
||||
public Result todoList(Integer mode, Boolean mobile) {
|
||||
if (mode == null || mode < 1 || mode > 3) return Result.error("请选择有效的待办查询类型");
|
||||
return Result.success(sysLocalProcessService.todoList(mode, Boolean.TRUE.equals(mobile)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import io.v.nutz.sys.services.SysTaskService;
|
||||
import io.v.nutz.task.services.TaskPlatformService;
|
||||
import io.v.nutz.web.commons.slog.annotation.SLog;
|
||||
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 org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.dao.Cnd;
|
||||
|
||||
+2
-1
@@ -59,7 +59,8 @@ public class ClubExamineRegisterAgentController {
|
||||
|
||||
Sys_user user = (Sys_user) ShiroUtil.getPrincipal();
|
||||
List<Sys_menu> menus = Optional.ofNullable(user).orElse(new Sys_user()).getMenus();
|
||||
return result.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").contains(x.getHref()))).distinct().collect(Collectors.toList());
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return result.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").contains(x.getHref()))).filter(v -> io.v.nutz.sys.services.TodoAccessService.canAccess(v.getString("url"))).distinct().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
|
||||
+8
-4
@@ -60,7 +60,8 @@ public class ClubNeedItemsController {
|
||||
|
||||
Sys_user user = (Sys_user) ShiroUtil.getPrincipal();
|
||||
List<Sys_menu> menus = Optional.ofNullable(user).orElse(new Sys_user()).getMenus();
|
||||
return result.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").contains(x.getHref()))).distinct().collect(Collectors.toList());
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return result.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").contains(x.getHref()))).filter(v -> io.v.nutz.sys.services.TodoAccessService.canAccess(v.getString("url"))).distinct().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private int getClubRegistCount(Boolean flag, String roleId, Integer state) {
|
||||
@@ -87,7 +88,8 @@ public class ClubNeedItemsController {
|
||||
|
||||
Sys_user user = (Sys_user) ShiroUtil.getPrincipal();
|
||||
List<Sys_menu> menus = Optional.ofNullable(user).orElse(new Sys_user()).getMenus();
|
||||
return result.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").contains(x.getHref()))).distinct().collect(Collectors.toList());
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return result.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").contains(x.getHref()))).filter(v -> io.v.nutz.sys.services.TodoAccessService.canAccess(v.getString("url"))).distinct().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private int getClubApplyCount(String roleId, Integer state) {
|
||||
@@ -116,7 +118,8 @@ public class ClubNeedItemsController {
|
||||
|
||||
Sys_user user = (Sys_user) ShiroUtil.getPrincipal();
|
||||
List<Sys_menu> menus = Optional.ofNullable(user).orElse(new Sys_user()).getMenus();
|
||||
return result.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").contains(x.getHref()))).distinct().collect(Collectors.toList());
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return result.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").contains(x.getHref()))).filter(v -> io.v.nutz.sys.services.TodoAccessService.canAccess(v.getString("url"))).distinct().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private int getClubManageCount() {
|
||||
@@ -139,7 +142,8 @@ public class ClubNeedItemsController {
|
||||
|
||||
Sys_user user = (Sys_user) ShiroUtil.getPrincipal();
|
||||
List<Sys_menu> menus = Optional.ofNullable(user).orElse(new Sys_user()).getMenus();
|
||||
return result.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").contains(x.getHref()))).distinct().collect(Collectors.toList());
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return result.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").contains(x.getHref()))).filter(v -> io.v.nutz.sys.services.TodoAccessService.canAccess(v.getString("url"))).distinct().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private int getClubEvaluateCount(Boolean flag, String roleId, Integer state) {
|
||||
|
||||
@@ -366,6 +366,22 @@ public class Sys_user extends BaseModel implements Serializable {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 10)
|
||||
private String userState;
|
||||
|
||||
/**
|
||||
* 数据中心 ZZZTM 原始代码;源数据全量保存,人员更新仅处理代码为 100 的记录。
|
||||
*/
|
||||
@Column
|
||||
@Comment("源人员状态代码(ZZZTM)")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String zzztm;
|
||||
|
||||
/**
|
||||
* 数据中心 ZZZTMC 原始名称,与 zzztm 配对保存,不替代现有 userState 字典值。
|
||||
*/
|
||||
@Column
|
||||
@Comment("源人员状态名称(ZZZTMC)")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String zzztmc;
|
||||
|
||||
@Column
|
||||
@Comment("人员类型")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.v.nutz.sys.services;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import io.v.nutz.base.dao.CndPlus;
|
||||
import io.v.nutz.sys.models.Sys_completed;
|
||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** 旧版手机已办查询,权限过滤保留原有本人和年度范围。 */
|
||||
@IocBean
|
||||
public class SysCompletedService {
|
||||
@Inject private Dao dao;
|
||||
|
||||
/** 无外部身份参数;返回当前登录人本年度、手机查看入口仍有权限的已办记录。 */
|
||||
public List<Sys_completed> getCompletes() {
|
||||
CndPlus cnd = CndPlus.create();
|
||||
cnd.and("auditBy", "=", ShiroUtil.getUserId());
|
||||
cnd.and("YEAR(auditTime)", "=", DateUtil.thisYear());
|
||||
cnd.desc("auditTime");
|
||||
return dao.query(Sys_completed.class, cnd).stream()
|
||||
.filter(row -> TodoAccessService.canAccess(row.getMobileUrl())).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -5,4 +5,12 @@ import cn.wizzer.framework.base.service.BaseService;
|
||||
import io.v.nutz.sys.models.Sys_home_activity;
|
||||
|
||||
public interface SysHomeActivityService extends BaseService<Sys_home_activity> {
|
||||
|
||||
/**
|
||||
* 保存首页活动配置,新增时补齐主键和默认状态,避免 controller 直接处理公共保存规则。
|
||||
*
|
||||
* @param activity 首页活动配置
|
||||
* @return 保存后的首页活动
|
||||
*/
|
||||
Sys_home_activity saveHomeActivity(Sys_home_activity activity);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
public interface SysLocalProcessService {
|
||||
/** mode=1 待办、2 已办、3 本人发起;返回含原 PC 字段及手机处理/查看地址的任务列表,身份取当前登录人。 */
|
||||
List<org.nutz.lang.util.NutMap> todoList(Integer mode);
|
||||
/** mobile=true 按手机跳转地址鉴权,false 按 PC 地址鉴权;返回已过滤的任务数组。 */
|
||||
List<org.nutz.lang.util.NutMap> todoList(Integer mode, boolean mobile);
|
||||
|
||||
/**
|
||||
* 开始一个实例流程
|
||||
|
||||
@@ -7,6 +7,12 @@ import io.v.nutz.sys.models.Sys_unit;
|
||||
* Created by wizzer on 2016/12/22.
|
||||
*/
|
||||
public interface SysUnitService extends ViService<Sys_unit> {
|
||||
/**
|
||||
* 无需参数,从数据中心同步全部单位,供单位维护及人员更新共同使用。
|
||||
* 返回 void;同步失败抛出异常,由调用接口返回失败提示。
|
||||
*/
|
||||
void syncSourceUnits();
|
||||
|
||||
/**
|
||||
* 保存单位
|
||||
*
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package io.v.nutz.sys.services;
|
||||
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authz.annotation.*;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.nutz.mvc.*;
|
||||
import org.nutz.mvc.impl.MappingNode;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** 待办入口访问判断:读取 MVC 实际注册的路由及权限,不执行目标方法、不修改业务数据。 */
|
||||
public class TodoAccessService {
|
||||
private static final String KEY = TodoAccessService.class.getName();
|
||||
private final Map<String, ActionInfo> exact = new HashMap<>();
|
||||
private final MappingNode<ActionInfo> patterns = new MappingNode<>();
|
||||
|
||||
/** MVC 启动时登记入口;注册表跟随本次应用配置,重启后重新建立,不缓存用户权限。 */
|
||||
public static synchronized void register(NutConfig config, ActionInfo info) {
|
||||
// 待办点击使用 GET;同一路径的提交接口不能覆盖页面自身权限。
|
||||
if (info.getHttpMethods() != null && !info.getHttpMethods().isEmpty()
|
||||
&& info.getHttpMethods().stream().noneMatch(method -> "GET".equalsIgnoreCase(method))) return;
|
||||
TodoAccessService service = (TodoAccessService) config.getAttribute(KEY);
|
||||
if (service == null) { service = new TodoAccessService(); config.setAttribute(KEY, service); }
|
||||
for (String path : info.getPaths()) {
|
||||
if (!path.startsWith("/")) path = "/" + path;
|
||||
service.exact.put(path, info);
|
||||
service.patterns.add(path, info);
|
||||
}
|
||||
}
|
||||
|
||||
/** url 为待办真实跳转地址;返回当前登录账号能否访问。不存在的入口不展示。 */
|
||||
public static boolean canAccess(String url) {
|
||||
NutConfig config = Mvcs.getNutConfig();
|
||||
TodoAccessService service = config == null ? null : (TodoAccessService) config.getAttribute(KEY);
|
||||
return service != null && service.allowed(url, SecurityUtils.getSubject());
|
||||
}
|
||||
|
||||
/** 去掉查询参数、锚点和应用上下文后匹配路由;权限保留原来的 AND/OR 组合语义。 */
|
||||
public boolean allowed(String url, Subject subject) {
|
||||
if (url == null || url.trim().isEmpty() || subject == null || !subject.isAuthenticated()) return false;
|
||||
try {
|
||||
URI uri = URI.create(url.trim());
|
||||
// 允许配置为本系统绝对地址的旧任务,外部域名不能按本系统权限推断。
|
||||
if (uri.isAbsolute() || uri.getRawAuthority() != null) {
|
||||
String domain = io.v.nutz.web.commons.base.Globals.AppDomain;
|
||||
if (domain == null || domain.isBlank()) return false;
|
||||
URI app = URI.create(domain);
|
||||
if (!Objects.equals(app.getScheme(), uri.getScheme()) || !Objects.equals(app.getRawAuthority(), uri.getRawAuthority())) return false;
|
||||
}
|
||||
String path = uri.getPath();
|
||||
if (path == null || !path.startsWith("/")) return false;
|
||||
if (Mvcs.getReq() != null) {
|
||||
String context = Mvcs.getReq().getContextPath();
|
||||
if (context != null && !context.isEmpty() && path.startsWith(context + "/")) path = path.substring(context.length());
|
||||
}
|
||||
ActionInfo info = exact.get(path);
|
||||
if (info == null && path.endsWith("/")) info = exact.get(path.substring(0, path.length()-1));
|
||||
if (info == null) info = patterns.get(new ActionContext().setPath(path), path);
|
||||
return info != null && check(info.getModuleType(), subject) && check(info.getMethod(), subject);
|
||||
} catch (IllegalArgumentException e) { return false; }
|
||||
}
|
||||
|
||||
private boolean check(AnnotatedElement element, Subject subject) {
|
||||
if (element == null) return true;
|
||||
if (element.isAnnotationPresent(RequiresGuest.class)) return subject.getPrincipal() == null;
|
||||
RequiresPermissions permission = element.getAnnotation(RequiresPermissions.class);
|
||||
if (permission != null) {
|
||||
boolean passed = permission.logical() == Logical.OR
|
||||
? Arrays.stream(permission.value()).anyMatch(subject::isPermitted)
|
||||
: Arrays.stream(permission.value()).allMatch(subject::isPermitted);
|
||||
if (!passed) return false;
|
||||
}
|
||||
RequiresRoles roles = element.getAnnotation(RequiresRoles.class);
|
||||
return roles == null || (roles.logical() == Logical.OR
|
||||
? Arrays.stream(roles.value()).anyMatch(subject::hasRole)
|
||||
: Arrays.stream(roles.value()).allMatch(subject::hasRole));
|
||||
}
|
||||
|
||||
/** rows 为已有数据范围内的待办;urlField 是本端点击使用的地址字段,返回过滤后的列表。 */
|
||||
public static List<NutMap> filter(List<NutMap> rows, String urlField) {
|
||||
return rows.stream().filter(row -> canAccess(row.getString(urlField))).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
package io.v.nutz.sys.services.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.wizzer.framework.base.service.BaseServiceImpl;
|
||||
import io.v.nutz.sys.models.Sys_home_activity;
|
||||
import io.v.nutz.sys.services.SysHomeActivityService;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.random.R;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class SysHomeActivityServiceImpl extends BaseServiceImpl<Sys_home_activity> implements SysHomeActivityService {
|
||||
@@ -12,4 +14,31 @@ public class SysHomeActivityServiceImpl extends BaseServiceImpl<Sys_home_activit
|
||||
public SysHomeActivityServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sys_home_activity saveHomeActivity(Sys_home_activity activity) {
|
||||
if (StrUtil.isBlank(activity.getName())) {
|
||||
throw new IllegalArgumentException("活动名称不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(activity.getH5Url())) {
|
||||
throw new IllegalArgumentException("H5端链接不能为空");
|
||||
}
|
||||
if (activity.getStartDate() == null || activity.getEndDate() == null) {
|
||||
throw new IllegalArgumentException("活动开始时间和结束时间不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(activity.getId())) {
|
||||
activity.setId(R.UU32());
|
||||
}
|
||||
// 手工维护的首页活动没有业务模块同步默认值,这里统一兜底,保证新增后能被首页列表正常读取。
|
||||
if (activity.getEnable() == null) {
|
||||
activity.setEnable(false);
|
||||
}
|
||||
if (activity.getTop() == null) {
|
||||
activity.setTop(false);
|
||||
}
|
||||
if (activity.getSortNo() == null) {
|
||||
activity.setSortNo(0);
|
||||
}
|
||||
return insertOrUpdate(activity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,80 @@ public class SysLocalProcessServiceImpl implements SysLocalProcessService {
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
/**
|
||||
* mode 为 1 待办、2 已办、3 本人发起;返回字段保留 PC 兼容,并增加 id、formMobileUrl、formMobileUrlView、endOn。
|
||||
* 待办/已办按接收人工号查询,发起记录按当前用户 ID 查询;删除标记的任务和流程不再展示。
|
||||
*/
|
||||
@Override
|
||||
public List<org.nutz.lang.util.NutMap> todoList(Integer mode) {
|
||||
return todoList(mode, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<org.nutz.lang.util.NutMap> todoList(Integer mode, boolean mobile) {
|
||||
if (mode == null || mode < 1 || mode > 3) throw new IllegalArgumentException("请选择有效的待办查询类型");
|
||||
Sql sql;
|
||||
if (mode == 1 || mode == 2) {
|
||||
sql = Sqls.create("""
|
||||
select t.id,t.processUniqueId,t.taskNodeName,t.createdByUserName,t.formUrl,t.formUrlView,
|
||||
t.formMobileUrl,t.formMobileUrlView,t.createdOn,t.endOn,t2.processName,t2.nodeName
|
||||
from sys_local_process_instance_task t
|
||||
join sys_local_process_instance t2 on t2.processUniqueId=t.processUniqueId
|
||||
where JSON_CONTAINS(t.assignments,JSON_QUOTE(@loginname)) and t.status=@mode
|
||||
and coalesce(t.taskDeleteFlag,0)=0 and coalesce(t.delFlag,0)=0
|
||||
and coalesce(t2.processDeleteFlag,0)=0 and coalesce(t2.delFlag,0)=0
|
||||
order by t2.processInitiationTime desc,t.id desc
|
||||
""").setParam("loginname",io.v.nutz.web.commons.utils.ShiroUtil.getPrincipalProperty("loginname"))
|
||||
.setParam("mode",mode);
|
||||
} else {
|
||||
sql = Sqls.create("""
|
||||
select t.id,t.processUniqueId,t.processName,t.nodeName,t.pcUrl,t.mobileUrl,t.pcUrl as formUrl,
|
||||
t.mobileUrl as formMobileUrl,t.mobileUrl as formMobileUrlView,t.processInstanceStatus,
|
||||
t.processInitiationTime as createdOn,u.username as createdByUserName
|
||||
from sys_local_process_instance t left join sys_user u on u.id=t.processInitiatorId
|
||||
where t.processInitiatorId=@id and coalesce(t.processDeleteFlag,0)=0 and coalesce(t.delFlag,0)=0
|
||||
order by t.processInitiationTime desc
|
||||
""").setParam("id",io.v.nutz.web.commons.utils.ShiroUtil.getUserId());
|
||||
}
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
dao.execute(sql);
|
||||
List<org.nutz.lang.util.NutMap> rows = sql.getList(org.nutz.lang.util.NutMap.class);
|
||||
// 兼容历史会员申请任务:只修正返回的手机地址,不回写数据库,也不改变 PC 地址和查询权限。
|
||||
for (org.nutz.lang.util.NutMap row : rows) {
|
||||
if (!row.getString("processUniqueId", "").startsWith("MEMBER_APPLY@")) continue;
|
||||
for (String field : List.of("formMobileUrl", "formMobileUrlView", "mobileUrl")) {
|
||||
if (row.containsKey(field)) row.put(field, memberApplyMobileUrl(row.getString(field)));
|
||||
}
|
||||
}
|
||||
// 权限仅进一步收紧已有候选人/本人范围;已办按查看入口判断,不能因历史授权继续展示。
|
||||
return rows.stream().filter(row -> {
|
||||
String url = mobile ? row.getString("formMobileUrl") : row.getString("formUrl");
|
||||
if (mode == 2) {
|
||||
String view = row.getString(mobile ? "formMobileUrlView" : "formUrlView");
|
||||
if (view != null && !view.isBlank()) url = view;
|
||||
}
|
||||
return io.v.nutz.sys.services.TodoAccessService.canAccess(url);
|
||||
}).collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入历史手机 URL,返回对应 H5 地址;仅匹配已存在 H5 页面的三个会员申请入口。
|
||||
* 保留查询参数与锚点,空值、正确 H5 地址及其他业务地址原样返回,避免重复添加 /h5。
|
||||
*/
|
||||
private String memberApplyMobileUrl(String url) {
|
||||
if (url == null || url.isEmpty()) return url;
|
||||
int end = url.length();
|
||||
if (url.indexOf('?') >= 0) end = Math.min(end, url.indexOf('?'));
|
||||
if (url.indexOf('#') >= 0) end = Math.min(end, url.indexOf('#'));
|
||||
String path = url.substring(0, end);
|
||||
if (path.endsWith("/")) path = path.substring(0, path.length() - 1);
|
||||
if (List.of("/platform/member/apply/mine", "/platform/member/apply/branchUnion/audit",
|
||||
"/platform/member/apply/schoolUnion/audit").contains(path)) {
|
||||
return path + "/h5" + url.substring(end);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startProcess(String processName, String processUniqueId, String nodeName, String processInitiatorId, String pcUrl, String mobileUrl) {
|
||||
int count = dao.count(Sys_local_process_instance.class, Cnd.where("processUniqueId", "=", processUniqueId));
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
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.service.impl.ViServiceImpl;
|
||||
import io.v.nutz.sys.models.Sys_unit;
|
||||
@@ -25,6 +31,47 @@ public class SysUnitServiceImpl extends ViServiceImpl<Sys_unit> implements SysUn
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增单位
|
||||
*
|
||||
|
||||
@@ -64,7 +64,7 @@ public class SysUserServiceImpl extends BaseServiceImpl<Sys_user> implements Sys
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
@CacheResult(cacheKey = "${args[0].id}_getRoleCodeList")
|
||||
// @CacheResult(cacheKey = "${args[0].id}_getRoleCodeList")
|
||||
public List<String> getRoleCodeList(Sys_user user) {
|
||||
dao().fetchLinks(user, "roles");
|
||||
List<String> roleNameList = new ArrayList<String>();
|
||||
|
||||
@@ -56,6 +56,7 @@ public class NutShiroProcessor extends AbstractProcessor {
|
||||
throw new IllegalStateException("this Processor have bean inited!!");
|
||||
}
|
||||
super.init(config, ai);
|
||||
io.v.nutz.sys.services.TodoAccessService.register(config, ai);
|
||||
match = NutShiro.match(ai.getMethod());
|
||||
init = true;
|
||||
}
|
||||
|
||||
+34
-148
@@ -1,164 +1,50 @@
|
||||
package io.v.nutz.zhgh.activity.controller.site;
|
||||
|
||||
import cn.wizzer.framework.base.Result;
|
||||
import io.v.nutz.zhgh.activity.models.ActivityType;
|
||||
import io.v.nutz.zhgh.activity.services.ActivityCommonService;
|
||||
import io.v.nutz.zhgh.activity.services.ActivityTypeService;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.base.model.AuditState;
|
||||
import io.v.nutz.base.model.AuditStateUser;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.apache.shiro.authz.annotation.*;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.Daos;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.ioc.loader.annotation.*;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
import org.nutz.mvc.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author JyuHsin
|
||||
* @Date 2022/5/11
|
||||
* @Description
|
||||
*/
|
||||
@IocBean
|
||||
@At("/platform/activity/site/type")
|
||||
@Ok("json:full")
|
||||
public class ActivityTypeController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
@Inject
|
||||
private ActivityTypeService activityTypeService;
|
||||
|
||||
@Inject
|
||||
private ActivityCommonService activityCommonService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/activity/site/SiteType.html")
|
||||
@RequiresPermissions("activity.site.type")
|
||||
public void index(HttpServletRequest request) {
|
||||
|
||||
@Inject private ActivityTypeService activityTypeService;
|
||||
@At("") @Ok("beetl:/platform/activity/site/SiteType.html") @RequiresPermissions("activity.site.type")
|
||||
public void index() { }
|
||||
/** enabledOnly=true 仅返回可预约类型;默认返回全部供历史筛选,结果为类型数组。 */
|
||||
@At @ViReturn @RequiresAuthentication
|
||||
public Object findAll(Boolean enabledOnly) {
|
||||
Cnd c=Cnd.NEW(); c.asc("sortNum");
|
||||
if(Boolean.TRUE.equals(enabledOnly)) c.and("enabled","=",true);
|
||||
return activityTypeService.query(c);
|
||||
}
|
||||
|
||||
@At("/findAll")
|
||||
@Ok("json:full")
|
||||
@ViReturn
|
||||
public Object findAll() {
|
||||
List<ActivityType> meetingTypeList = dao.query(ActivityType.class, Cnd.NEW().asc("sortNum"));
|
||||
dao.fetchLinks(meetingTypeList, "^auditStateList$");
|
||||
for (ActivityType ActivityType : meetingTypeList) {
|
||||
List<AuditState> auditStateList = ActivityType.getAuditStateList();
|
||||
dao.fetchLinks(auditStateList, "^auditStateUserList$");
|
||||
}
|
||||
return meetingTypeList;
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("json:full")
|
||||
@ViReturn
|
||||
@RequiresPermissions("activity.site.type")
|
||||
public Object findOne(String module) {
|
||||
|
||||
List<AuditState> auditStateList = dao.query(AuditState.class, Cnd.where("module", "=", module));
|
||||
auditStateList.forEach(v -> {
|
||||
v.setAuditStateUserList(dao.query(AuditStateUser.class, Cnd.where("stateId", "=", v.getStateId())));
|
||||
});
|
||||
return auditStateList;
|
||||
}
|
||||
|
||||
@At("/doHandle")
|
||||
@Ok("json:full")
|
||||
@ViReturn
|
||||
@RequiresPermissions("activity.site.type")
|
||||
public Object doHandle(@Param("data") String data) {
|
||||
ActivityType activityType = Json.fromJson(ActivityType.class, data);
|
||||
|
||||
if (null == activityType.getId()) {
|
||||
int count = dao.count(AuditState.class, Cnd.where("module", "=", activityType.getModuleName()));
|
||||
if (count > 0) {
|
||||
return Result.error("模块名称已存在,换一个试试");
|
||||
}
|
||||
activityTypeService.add(activityType);
|
||||
} else {
|
||||
activityTypeService.edit(activityType);
|
||||
}
|
||||
/** pageNumber/pageSize 为分页参数,返回 list/totalCount;复用页面表格 mixin。 */
|
||||
@At @ViReturn @RequiresPermissions("activity.site.type")
|
||||
public Object pageData(int pageNumber,int pageSize) { return activityTypeService.listPage(Math.max(1,pageNumber),Math.min(100,Math.max(1,pageSize)),Cnd.NEW().asc("sortNum")); }
|
||||
/** data 为类型 JSON(id、code、meetingTypeName、enabled);返回 Object,由 @ViReturn 包装 code(0 成功)和 msg(结果说明)。 */
|
||||
@At @ViReturn @RequiresPermissions("activity.site.type") @Aop(TransAop.READ_COMMITTED)
|
||||
public Object doHandle(String data) {
|
||||
ActivityType t=Json.fromJson(ActivityType.class,data);
|
||||
if(t==null) throw new IllegalArgumentException("类型参数不能为空");
|
||||
if(t.getId()==null) activityTypeService.add(t); else activityTypeService.edit(t);
|
||||
// Object 返回类型使 @ViReturn 包装的 code/msg 能传递给 JSON 视图。
|
||||
return null;
|
||||
}
|
||||
|
||||
@At("/delete/?")
|
||||
@Ok("json:full")
|
||||
@ViReturn
|
||||
@RequiresPermissions("activity.site.type")
|
||||
public Object delete(Integer id) {
|
||||
if (id == 1) {
|
||||
return Result.error("此记录不可删除");
|
||||
}
|
||||
ActivityType activityType = dao.fetch(ActivityType.class, id);
|
||||
dao.clear(ActivityType.class, Cnd.where("id", "=", id));
|
||||
dao.count(AuditState.class, Cnd.where("module", "=", activityType.getModuleName()));
|
||||
/** id 为类型主键,enabled 为是否启用;返回 Object,由 @ViReturn 包装 code(0 成功)和 msg(结果说明)。 */
|
||||
@At @ViReturn @RequiresPermissions("activity.site.type") @Aop(TransAop.READ_COMMITTED)
|
||||
public Object setEnabled(Integer id,Boolean enabled) {
|
||||
ActivityType t=activityTypeService.fetch(id);
|
||||
if(t==null) throw new IllegalArgumentException("类型不存在");
|
||||
t.setEnabled(enabled); activityTypeService.edit(t);
|
||||
return null;
|
||||
}
|
||||
|
||||
@At("/findUserList")
|
||||
@Ok("json:full")
|
||||
@ViReturn
|
||||
@RequiresPermissions("activity.site.type")
|
||||
public Object findUserList(@Param("keyWords") String[] keyWords) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
loginname,
|
||||
mobile,
|
||||
sex,
|
||||
unitname,
|
||||
unionname,
|
||||
unionid,
|
||||
unitid
|
||||
FROM
|
||||
`user`
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
for (String keyWord : keyWords) {
|
||||
seg.andLike("loginname", keyWord);
|
||||
seg.orLike("username", keyWord);
|
||||
}
|
||||
cnd.and(seg);
|
||||
sql.setCondition(cnd);
|
||||
return Daos.query(dao, sql.toString(), Sqls.callback.maps());
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("json:full")
|
||||
@ViReturn
|
||||
public Object findMaxStateId() {
|
||||
return activityTypeService.findMaxStateId();
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("json:full")
|
||||
@ViReturn
|
||||
@RequiresPermissions("activity.site.type")
|
||||
public Object checkStateId(@Param("stateId") Integer stateId, @Param("stateIndex") Integer stateIndex) {
|
||||
if (stateId == null) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
int count = dao.count(AuditState.class, Cnd.where("stateId", "=", stateId));
|
||||
if (count == 0) {
|
||||
return Result.success().addData(stateId);
|
||||
} else {
|
||||
return Result.success(activityTypeService.findMaxStateId() + 10 * stateIndex);
|
||||
}
|
||||
}
|
||||
/** id 为待删除类型主键,服务层检查场地引用;返回 Object,由 @ViReturn 包装 code(0 成功)和 msg(结果说明)。 */
|
||||
@At("/delete/?") @ViReturn @RequiresPermissions("activity.site.type") @Aop(TransAop.READ_COMMITTED)
|
||||
public Object delete(Integer id) { activityTypeService.delete(activityTypeService.fetch(id)); return null; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.v.nutz.zhgh.activity.controller.site;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.zhgh.activity.services.impl.SiteBookingService;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.ioc.loader.annotation.*;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.mvc.annotation.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/** 协会审核:PC/H5 共享业务服务,入口固定节点,不能由请求绕过节点限制。 */
|
||||
@IocBean @Ok("json:full") @At("/platform/activity/site/review/club")
|
||||
@RequiresPermissions("activity.site.review.club")
|
||||
public class SiteClubReviewController {
|
||||
@Inject private SiteBookingService siteBookingService;
|
||||
@At("") @Ok("beetl:/platform/activity/site/SiteReview.html")
|
||||
public void index(HttpServletRequest request) {
|
||||
request.setAttribute("reviewTitle","协会审核");
|
||||
request.setAttribute("reviewApi","/platform/activity/site/review/club");
|
||||
}
|
||||
/** isAudit 为已审核/未审核;searchName 为场地名称或预约人字段,month 为 yyyy-MM,siteType 为类型 ID,返回 list/totalCount。 */
|
||||
@At @ViReturn
|
||||
public Object pageData(Boolean isAudit,String searchName,String searchKeyword,String month,String siteType,int pageNumber,int pageSize) {
|
||||
return siteBookingService.reviewPage("club",Boolean.TRUE.equals(isAudit),searchName,searchKeyword,month,siteType,pageNumber,pageSize);
|
||||
}
|
||||
/** id 是预约记录主键,返回申请、时段及审核历史,服务层校验查看范围。 */
|
||||
@At @ViReturn
|
||||
public Object detail(String id) { return siteBookingService.detail(id); }
|
||||
/** ids 为预约记录 ID 数组,isPass 为通过/拒绝,auditOpinion 为必填意见;返回 Object,由 @ViReturn 包装 code(0 成功)和 msg(结果说明)。 */
|
||||
@At @ViReturn @Aop(TransAop.READ_COMMITTED)
|
||||
public Object doReview(String[] ids,Boolean isPass,String auditOpinion) {
|
||||
siteBookingService.review("club",ids,isPass,auditOpinion);
|
||||
// Object 返回类型保留 @ViReturn 的统一结果,避免前端收到空响应。
|
||||
return null;
|
||||
}
|
||||
/** id 为预约记录主键,撤回本人最近一次审核;返回统一 code/msg,成功后恢复当前节点待审。 */
|
||||
@At @ViReturn @Aop(TransAop.READ_COMMITTED)
|
||||
public Object doRevoke(String id) {
|
||||
siteBookingService.revokeReview("club",id);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.base.utils.PageUtil;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.Strings;
|
||||
@@ -70,22 +72,27 @@ public class SiteManageController {
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("activity.site.manage")
|
||||
/** data 为新增场地表单,返回统一 code/msg;类型关联和校区校验由 service 完成。 */
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object doAdd(@Param("data") ActivitySiteInfo activitySiteInfo) {
|
||||
siteInfoService.insert(activitySiteInfo);
|
||||
siteInfoService.saveManagedSite(activitySiteInfo, false);
|
||||
return null;
|
||||
}
|
||||
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("activity.site.manage")
|
||||
/** data 为包含 id 的场地表单,返回统一 code/msg,成功后可重新查询校区回显。 */
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object doEdit(@Param("data") ActivitySiteInfo activitySiteInfo) {
|
||||
siteInfoService.updateIgnoreNull(activitySiteInfo);
|
||||
siteInfoService.saveManagedSite(activitySiteInfo, true);
|
||||
return null;
|
||||
}
|
||||
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("activity.site.manage")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object doDelete(String id) {
|
||||
siteInfoService.delete(id);
|
||||
return null;
|
||||
|
||||
@@ -28,6 +28,8 @@ import org.nutz.mvc.annotation.Param;
|
||||
@At("/platform/activity/site/record")
|
||||
public class SiteRecordController {
|
||||
|
||||
@Inject private io.v.nutz.zhgh.activity.services.impl.SiteBookingService siteBookingService;
|
||||
|
||||
@Inject
|
||||
private SiteReserveService siteReserveService;
|
||||
|
||||
@@ -57,14 +59,13 @@ public class SiteRecordController {
|
||||
ass.`stateName` state_name,
|
||||
ass.`stateColor` state_color,
|
||||
ass.stateAuditType,
|
||||
count(sqid) days,
|
||||
GROUP_CONCAT(reserve_day order by reserve_day asc) concat_day
|
||||
count(distinct reserve_day) days
|
||||
FROM
|
||||
activity_site_reserve asr
|
||||
LEFT JOIN
|
||||
activity_site_info asi ON asi.id = asr.site_id
|
||||
LEFT JOIN
|
||||
audit_state ass ON ass.stateId = asr.reserve_state
|
||||
(select state_id stateId,state_name stateName,state_color stateColor,'activity_site' module,case when state_id=4030 then 3 when state_id in (4040,4050) then 1 else 0 end stateAuditType from state where belong='activity_site') ass ON ass.stateId = asr.reserve_state
|
||||
LEFT JOIN
|
||||
sys_user u on u.id = asr.reserve_person_id
|
||||
$condition
|
||||
@@ -96,7 +97,7 @@ public class SiteRecordController {
|
||||
cnd.groupBy("sqid");
|
||||
|
||||
sql.setCondition(cnd);
|
||||
return siteReserveService.listPage(pageNumber, pageSize, sql);
|
||||
return siteBookingService.reservationPage(pageNumber, pageSize, sql, month);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@ import org.nutz.lang.Strings;
|
||||
import org.nutz.lang.random.R;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import io.v.nutz.zhgh.activity.services.impl.SiteBookingService;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
@@ -50,6 +53,8 @@ import java.util.stream.Collectors;
|
||||
@At("/platform/activity/site/reserve")
|
||||
public class SiteReserveController {
|
||||
|
||||
@Inject private SiteBookingService siteBookingService;
|
||||
|
||||
@Inject
|
||||
private SiteInfoService siteInfoService;
|
||||
|
||||
@@ -94,14 +99,13 @@ public class SiteReserveController {
|
||||
ass.`stateName` state_name,
|
||||
ass.`stateColor` state_color,
|
||||
ass.stateAuditType,
|
||||
count(sqid) days,
|
||||
GROUP_CONCAT(reserve_day order by reserve_day asc) concat_day
|
||||
count(distinct reserve_day) days
|
||||
FROM
|
||||
activity_site_reserve asr
|
||||
LEFT JOIN
|
||||
activity_site_info asi ON asi.id = asr.site_id
|
||||
LEFT JOIN
|
||||
audit_state ass ON ass.stateId = asr.reserve_state
|
||||
(select state_id stateId,state_name stateName,state_color stateColor,'activity_site' module,case when state_id=4030 then 3 when state_id in (4040,4050) then 1 else 0 end stateAuditType from state where belong='activity_site') ass ON ass.stateId = asr.reserve_state
|
||||
$condition
|
||||
""");
|
||||
|
||||
@@ -115,6 +119,7 @@ public class SiteReserveController {
|
||||
|
||||
if (Strings.isNotBlank(siteId)) {
|
||||
cnd.and("site_id", "=", siteId);
|
||||
cnd.and("reserve_state", "not in", java.util.Arrays.asList(4040,4050));
|
||||
}
|
||||
|
||||
if (Strings.isNotBlank(searchName) && Strings.isNotBlank(searchKeyword)) {
|
||||
@@ -134,130 +139,46 @@ public class SiteReserveController {
|
||||
cnd.groupBy("sqid");
|
||||
|
||||
sql.setCondition(cnd);
|
||||
return siteReserveService.listPage(pageNumber, pageSize, sql);
|
||||
return siteBookingService.reservationPage(pageNumber, pageSize, sql, time);
|
||||
}
|
||||
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("activity.site.reserve")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
/** id 为预约记录主键,op=1 删除已撤销申请、op=2 撤销申请;成功返回空 data,由 ViReturn 包装结果。 */
|
||||
public Object doDelete(String id, Integer op) {
|
||||
ActivitySiteReserve fetch = siteReserveService.fetch(id);
|
||||
if (op == 1) {
|
||||
siteReserveService.clear(Cnd.NEW().and("sqid", "=", fetch.getSqid()));
|
||||
} else if (op == 2) {
|
||||
//siteReserveService.update(Chain.make("reserve_state", 50), Cnd.where("id", "=", id));
|
||||
siteReserveService.clear(Cnd.NEW().and("sqid", "=", fetch.getSqid()));
|
||||
}
|
||||
if (org.nutz.lang.Strings.isBlank(id)) throw new IllegalArgumentException("请选择预约记录");
|
||||
if (Integer.valueOf(1).equals(op)) siteBookingService.deleteCancelled(id);
|
||||
else if (Integer.valueOf(2).equals(op)) siteBookingService.cancel(id);
|
||||
else throw new IllegalArgumentException("不支持的预约操作");
|
||||
return null;
|
||||
}
|
||||
|
||||
@At
|
||||
@RequiresPermissions("activity.site.reserve")
|
||||
public Object isCanRollBack(String id) {
|
||||
//根据活动id查询第一个审核节点
|
||||
ActivitySiteReserve fetch = siteReserveService.fetch(id);
|
||||
String moduleName = activityCommonService.findModuleNameByActivityId(fetch.getSite_id());
|
||||
Integer stateCode = activityCommonService.findStartStateCodeByModuleName(moduleName);
|
||||
|
||||
return fetch.getReserve_state() > stateCode;
|
||||
ActivitySiteReserve b=siteReserveService.fetch(id);
|
||||
if(b==null) return true;
|
||||
return b.getReserve_state()!=siteBookingService.firstState(b.getReserve_type()) || b.getAuditList()!=null && !b.getAuditList().isEmpty();
|
||||
}
|
||||
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("activity.site.reserve")
|
||||
public Object doAdd(@Param("data") ActivitySiteReserve activitySiteReserve, @Param("days") String[] days) {
|
||||
|
||||
if(activitySiteReserve.getReserve_type() == 1 && days.length > 1) {
|
||||
return Result.error("个人预约只支持预约明天的时间");
|
||||
/** data 为场地、类型、协会及事由;days 为日期数组,起止时间取 data;返回新申请 sqid。 */
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object doAdd(@Param("data") ActivitySiteReserve activitySiteReserve, @Param("days") String[] days, @Param("times") String[] times) {
|
||||
List<NutMap> slots = new ArrayList<>();
|
||||
if (activitySiteReserve == null || days == null) throw new IllegalArgumentException("请选择预约日期");
|
||||
// times为所选HH:mm-HH:mm场次数组;保留旧客户端单场次参数的兼容。
|
||||
if(times==null || times.length==0)times=new String[]{activitySiteReserve.getStart_time()+"-"+activitySiteReserve.getEnd_time()};
|
||||
for(String day:days)for(String time:times){
|
||||
String[] parts=time.split("-",-1);
|
||||
if(parts.length!=2)throw new IllegalArgumentException("请选择有效预约场次");
|
||||
slots.add(NutMap.NEW().setv("day",day).setv("start_time",parts[0]).setv("end_time",parts[1]));
|
||||
}
|
||||
|
||||
//主要来查询个人预约时的人数上限
|
||||
ActivitySiteInfo siteInfo = siteInfoService.dao().fetch(ActivitySiteInfo.class, activitySiteReserve.getSite_id());
|
||||
String moduleName = activityCommonService.findModuleNameByActivityId(activitySiteReserve.getSite_id());
|
||||
Integer limitNum = siteInfo.getLimitNum();
|
||||
|
||||
for (String day : days) {
|
||||
String time = day + " " + activitySiteReserve.getStart_time();
|
||||
int compare = DateUtil.compare(new Date(), DateUtil.parse(time), "yyyy-MM-dd HH:mm");
|
||||
if(compare > 0) {
|
||||
return Result.error("您预约的【%s】时间已过".formatted(time));
|
||||
}
|
||||
if(activitySiteReserve.getReserve_type() == 1 && !day.equals(DateUtil.format(DateUtil.offsetDay(new Date(), 1), "yyyy-MM-dd"))) {
|
||||
return Result.error("个人预约只支持预约明天的时间");
|
||||
}
|
||||
|
||||
List<ActivitySiteReserve> list = siteReserveService.query(Cnd.NEW()
|
||||
.and("reserve_day", "=", day)
|
||||
.and("start_time", "=", activitySiteReserve.getStart_time())
|
||||
.and("end_time", "=", activitySiteReserve.getEnd_time())
|
||||
.and("site_id", "=", activitySiteReserve.getSite_id())
|
||||
.and(new Static(" reserve_state in (select stateId from audit_state where stateAuditType != 1 and module = '%s')".formatted(moduleName))));
|
||||
ActivitySiteReserve reserve = list.stream().filter(o -> o.getReserve_person_id().equals(ShiroUtil.getPrincipalProperty("id"))).findAny().orElse(null);
|
||||
if (reserve != null) {
|
||||
return Result.error("您已预约该时间段!");
|
||||
}
|
||||
if(activitySiteReserve.getReserve_type() == 1) {
|
||||
if((list.size() + 1) > limitNum) {
|
||||
return Result.error("【%s】时间段预约人数已满!".formatted(time));
|
||||
}
|
||||
} else {
|
||||
if(list.size() > 0) {
|
||||
return Result.error("【%s】时间段已有预约!".formatted(time));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String r = R.UU32();
|
||||
activitySiteReserve.setSqid(r);
|
||||
|
||||
Integer stateCode = activityCommonService.findStartStateCodeByModuleName(moduleName);
|
||||
|
||||
if (stateCode == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (String d : days) {
|
||||
activitySiteReserve.setReserve_day(d);
|
||||
activitySiteReserve.setReserve_state(stateCode);
|
||||
activitySiteReserve.setReserve_person_id(ShiroUtil.getPrincipalProperty("id").toString());
|
||||
siteReserveService.insert(activitySiteReserve);
|
||||
}
|
||||
|
||||
//发送微信提醒审核人
|
||||
//查询状态码对应的审核人
|
||||
Sql sql = Sqls.create("""
|
||||
select userId,u.loginname,u.username,u.unitname from audit_state_user asu
|
||||
left join `user` u on u.id = asu.userId left join audit_state s on s.stateId=asu.stateId $condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("asu.stateId", "=", stateCode);
|
||||
cnd.and("s.stateAuditType", "=", "0");
|
||||
if ("infantRoom".equals(moduleName)) {
|
||||
//获取当前登录人的单位id
|
||||
String unitId = ShiroUtil.getPrincipalProperty("unitid").toString();
|
||||
cnd.and("u.unitid", "=", unitId);
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
List<Record> list = siteReserveService.list(sql);
|
||||
|
||||
list.forEach(item -> {
|
||||
String content = "%s老师您好!%s老师预约%s日使用%s,请您通过门户网站或点击详情登录智慧工会平台进行审核。"
|
||||
.formatted(item.getString("username"),
|
||||
activitySiteReserve.getReserve_person(),
|
||||
StringUtils.join(days, ","),
|
||||
siteInfoService.fetch(activitySiteReserve.getSite_id()).getName());
|
||||
|
||||
Sys_user sys_user = sysUserService.fetch(item.getString("userId"));
|
||||
NutMap map = new NutMap();
|
||||
map.setv("keyword1", NutMap.NEW().setv("value", "场地预约"));
|
||||
map.setv("keyword2", NutMap.NEW().setv("value", item.getString("username")
|
||||
+ "(" + item.getString("loginname") + ")"));
|
||||
map.setv("keyword3", NutMap.NEW().setv("value", item.getString("unitname")));
|
||||
map.setv("keyword4", NutMap.NEW().setv("value", content));
|
||||
map.setv("keyword5", NutMap.NEW().setv("value", DateUtil.now()));
|
||||
//msgApi.sendMsg(sys_user.getWeAppOpenid(), msgApi.DSH_TEMPLATE_ID, Globals.AppDomain + "/mobile/activity/site/audit", map);
|
||||
});
|
||||
return null;
|
||||
return siteBookingService.submit(activitySiteReserve,slots);
|
||||
}
|
||||
|
||||
@At
|
||||
@@ -267,7 +188,7 @@ public class SiteReserveController {
|
||||
@Param(value = "siteType",required = false) String siteType) {
|
||||
String sb = "SELECT " +
|
||||
"asi.*," +
|
||||
"( SELECT count( 1 ) FROM activity_site_reserve WHERE site_id = asi.id AND reserve_state = (select stateId from audit_state where module = (select moduleName from activity_type where id = asi.typeId) and stateAuditType = 3) ";
|
||||
"( SELECT count( 1 ) FROM activity_site_reserve WHERE site_id = asi.id AND reserve_state = 4030 ";
|
||||
if (StrUtil.isNotBlank(time)) {
|
||||
sb += " AND left(reserve_day, 7) = '" + time + "'";
|
||||
}
|
||||
@@ -276,6 +197,7 @@ public class SiteReserveController {
|
||||
Sql sql = Sqls.create(sb);
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("state", "=", true);
|
||||
cnd.and("typeId", "in", Sqls.create("select id from activity_type where enabled=1"));
|
||||
if (StrUtil.isNotBlank(siteType)) {
|
||||
cnd.and("typeId", "=", siteType);
|
||||
}
|
||||
@@ -302,28 +224,14 @@ public class SiteReserveController {
|
||||
@RequiresPermissions("activity.site.reserve")
|
||||
public Object findReserveInfo(@Param(value = "siteId",required = false) String siteId,
|
||||
@Param(value = "day",required = false) String day) {
|
||||
Sql sql = Sqls.create("SELECT ar.*,`as`.stateAuditType FROM activity_site_reserve ar left join audit_state `as` on ar.reserve_state=`as`.stateId $condition");
|
||||
|
||||
String moduleName = activityCommonService.findModuleNameByActivityId(siteId);
|
||||
//根据moduleName找审核失败的数据
|
||||
Sql s = Sqls.create("""
|
||||
select
|
||||
stateId
|
||||
from
|
||||
audit_state
|
||||
where
|
||||
stateAuditType = 1 and
|
||||
module = @module
|
||||
""").setParam("module", moduleName);
|
||||
|
||||
List<Record> list = siteReserveService.list(s);
|
||||
List<String> stateList = list.stream().map(o -> o.getString("stateId")).collect(Collectors.toList());
|
||||
Sql sql = Sqls.create("SELECT ar.*,`as`.stateAuditType FROM activity_site_reserve ar left join (select state_id stateId,state_name stateName,state_color stateColor,'activity_site' module,case when state_id=4030 then 3 when state_id in (4040,4050) then 1 else 0 end stateAuditType from state where belong='activity_site') `as` on ar.reserve_state=`as`.stateId $condition");
|
||||
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("site_id", "=", siteId);
|
||||
cnd.and("reserve_state", "not in", java.util.Arrays.asList(4040,4050));
|
||||
if (StrUtil.isNotBlank(day)) {
|
||||
cnd.and("reserve_day", "=", day);
|
||||
cnd.and("reserve_state", "not in ", stateList);
|
||||
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
return siteReserveService.list(sql);
|
||||
@@ -335,7 +243,7 @@ public class SiteReserveController {
|
||||
public Object checkReserve(String reserve_day, String start_time, String end_time, String site_id) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("site_id", "=", site_id)
|
||||
.and("reserve_state", "!=", 40)
|
||||
.and("reserve_state", "not in", java.util.Arrays.asList(4040,4050))
|
||||
.and("reserve_day", "=", reserve_day);
|
||||
SqlExpressionGroup group = new SqlExpressionGroup();
|
||||
group.or("start_time", "<=", start_time).and("end_time", ">=", start_time);
|
||||
@@ -348,84 +256,14 @@ public class SiteReserveController {
|
||||
@ViReturn
|
||||
@RequiresPermissions("activity.site.reserve")
|
||||
public Object findOne(String id) {
|
||||
ActivitySiteReserve fetch = siteReserveService.fetch(id);
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
asr.*,
|
||||
asi.`name` site_name,
|
||||
ass.`stateName` state_name,
|
||||
ass.stateAuditType,
|
||||
su.username,
|
||||
sm.username smusername,
|
||||
su.sex,
|
||||
su.loginname,
|
||||
count(sqid) days,
|
||||
GROUP_CONCAT(reserve_day order by reserve_day asc) concat_day
|
||||
FROM
|
||||
activity_site_reserve asr
|
||||
LEFT JOIN activity_site_info asi ON asi.id = asr.site_id
|
||||
LEFT JOIN audit_state ass ON ass.stateId = asr.reserve_state
|
||||
LEFT JOIN sys_user su ON su.id = asr.reserve_person_id
|
||||
LEFT JOIN sys_user sm ON sm.id = asr.site_manager_id
|
||||
WHERE
|
||||
asr.sqid = @id
|
||||
group by sqid
|
||||
""").setParam("id", fetch.getSqid());
|
||||
NutMap record = (NutMap) Daos.query(siteReserveService.dao(), sql.toString(), Sqls.callback.map());
|
||||
String auditList = record.getString("auditList");
|
||||
if (auditList != null) {
|
||||
List<NutMap> nutMaps = Json.fromJsonAsList(NutMap.class, auditList);
|
||||
nutMaps.forEach(item -> {
|
||||
item.put("auditListName", item.getBoolean("auditState") ? "审核通过" : "审核拒绝");
|
||||
Sys_user auditUSer = sysUserService.fetch(item.getString("auditUser"));
|
||||
item.put("auditUserName", auditUSer.getUsername());
|
||||
item.put("auditUserUnionName", sysUnitService.fetch(auditUSer.getUnitid()).getName());
|
||||
Sql sqlStr = Sqls.create("select stateName from audit_state where stateId = '" + item.getString("stateCode") + "'");
|
||||
Record re = sysUserService.list(sqlStr).get(0);
|
||||
item.put("auditStateName", re.getString("stateName"));
|
||||
});
|
||||
record.put("auditListTable", nutMaps);
|
||||
} else {
|
||||
record.put("auditListTable", new ArrayList<>());
|
||||
}
|
||||
|
||||
Cnd cnd = Cnd.NEW();
|
||||
String site_id = record.getString("site_id");
|
||||
String reserve_day = record.getString("reserve_day");
|
||||
|
||||
String moduleName = activityCommonService.findModuleNameByActivityId(record.getString("site_id"));
|
||||
//根据moduleName找审核失败的数据
|
||||
Sql s = Sqls.create("""
|
||||
select
|
||||
stateId
|
||||
from
|
||||
audit_state
|
||||
where
|
||||
stateAuditType = 1 and
|
||||
module = @module
|
||||
""").setParam("module", moduleName);
|
||||
|
||||
List<NutMap> list = siteReserveService.listMap(s);
|
||||
List<String> stateList = list.stream().map(o -> o.getString("stateId")).collect(Collectors.toList());
|
||||
|
||||
Sql i = Sqls.create("""
|
||||
SELECT
|
||||
ar.*,
|
||||
`as`.stateAuditType
|
||||
FROM
|
||||
activity_site_reserve ar
|
||||
left join audit_state `as` on `as`.stateId = ar.reserve_state
|
||||
$condition
|
||||
""");
|
||||
cnd.and("site_id", "=", site_id)
|
||||
//.and("reserve_day", "=", reserve_day)
|
||||
.and("reserve_state", "not in", stateList);
|
||||
|
||||
i.setCondition(cnd);
|
||||
List<NutMap> list1 = siteReserveService.listMap(i);
|
||||
record.put("site_info", list1);
|
||||
|
||||
return record;
|
||||
return siteBookingService.detail(id);
|
||||
}
|
||||
|
||||
/** 返回当前用户有效协会数组(id/name),供个人所属协会展示和协会预约选择。 */
|
||||
@At @ViReturn @org.apache.shiro.authz.annotation.RequiresAuthentication
|
||||
public Object myClubs() { return siteBookingService.myClubs(); }
|
||||
|
||||
/** 无入参;返回统一code/data,data.id/name为当前申请人所属分工会,仅供表单展示。 */
|
||||
@At @ViReturn @RequiresPermissions("activity.site.reserve")
|
||||
public Object myUnion() { return siteBookingService.myBookingUnion(); }
|
||||
}
|
||||
|
||||
@@ -1,258 +1,10 @@
|
||||
package io.v.nutz.zhgh.activity.controller.site;
|
||||
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import io.v.nutz.zhgh.activity.models.ActivitySiteInfo;
|
||||
import io.v.nutz.zhgh.activity.models.ActivitySiteReserve;
|
||||
import io.v.nutz.zhgh.activity.services.ActivityCommonService;
|
||||
import io.v.nutz.zhgh.activity.services.SiteInfoService;
|
||||
import io.v.nutz.zhgh.activity.services.SiteReserveService;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.base.utils.MsgApi;
|
||||
import io.v.nutz.base.utils.PageUtil;
|
||||
|
||||
import io.v.nutz.base.model.Audit;
|
||||
import io.v.nutz.base.model.AuditState;
|
||||
import io.v.nutz.zhgh.msgNotify.service.MsgNotifyService;
|
||||
import io.v.nutz.sys.models.Sys_user;
|
||||
import io.v.nutz.sys.services.SysUserService;
|
||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.entity.Record;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.dao.util.cri.Static;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author: Aaron
|
||||
* @create: 2020-12-15 13:52
|
||||
* @description: 预约审核
|
||||
**/
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/platform/activity/site/review")
|
||||
import org.nutz.mvc.annotation.*;
|
||||
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
||||
/** 旧审核入口仅展示三个新入口,不再提供旧审核提交接口。 */
|
||||
@IocBean @At("/platform/activity/site/review") @RequiresAuthentication
|
||||
public class SiteReviewController {
|
||||
|
||||
@Inject
|
||||
private SiteInfoService siteInfoService;
|
||||
|
||||
@Inject
|
||||
private SiteReserveService siteReserveService;
|
||||
|
||||
@Inject
|
||||
private ActivityCommonService activityCommonService;
|
||||
|
||||
|
||||
|
||||
@Inject
|
||||
private MsgApi msgApi;
|
||||
|
||||
@Inject
|
||||
private MsgNotifyService msgNotifyService;
|
||||
|
||||
@Inject
|
||||
private SysUserService sysUserService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:platform/activity/site/SiteReview.html")
|
||||
@RequiresPermissions("activity.site.review")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("activity.site.review")
|
||||
public Object pageData(@Param(value = "month",required = false) String month,
|
||||
@Param(value = "siteType",required = false) String siteType,
|
||||
@Param(value = "searchName",required = false) String searchName,
|
||||
@Param(value = "searchKeyword",required = false) String searchKeyword,
|
||||
@Param("pageNumber") int pageNumber,@Param("pageSize") int pageSize,
|
||||
@Param(value = "pageOrderName",required = false) String pageOrderName,
|
||||
@Param(value = "pageOrderBy",required = false) String pageOrderBy,
|
||||
@Param(value = "isAudit",required = false) Boolean isAudit) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
asr.*,
|
||||
asi.`name` site_name,
|
||||
ass.`stateName` state_name,
|
||||
ass.`stateColor` state_color,
|
||||
ass.stateAuditType,
|
||||
count(sqid) days,
|
||||
GROUP_CONCAT(reserve_day order by reserve_day asc) concat_day
|
||||
FROM
|
||||
activity_site_reserve asr
|
||||
LEFT JOIN
|
||||
activity_site_info asi ON asi.id = asr.site_id
|
||||
LEFT JOIN
|
||||
audit_state ass ON ass.stateId = asr.reserve_state
|
||||
LEFT JOIN
|
||||
`user` u on u.id = asr.reserve_person_id
|
||||
$condition
|
||||
""");
|
||||
|
||||
/*if (ShiroUtil.hasAnyRoles(new String[]{"gh10"})) {
|
||||
Sys_user user = (Sys_user) ShiroUtil.getPrincipal();
|
||||
cnd.and("u.unitid", "=", user.getUnitid());
|
||||
}*/
|
||||
|
||||
/*if (!ShiroUtil.hasAnyRoles(new String[]{"sysadmin", "A06"})) {
|
||||
cnd.and(new Static("if(asi.typeId=1, u.unionId = '" + Vi.getUnionId() + "', 1=1)"));
|
||||
}*/
|
||||
|
||||
//查询未审核,获取当前用户可以审核的节点
|
||||
Sql s = Sqls.create("""
|
||||
select stateId from audit_state_user where userId=@userId
|
||||
""").setParam("userId", ShiroUtil.getPrincipalProperty("id"));
|
||||
List<Record> sList = siteReserveService.list(s);
|
||||
List<String> stateList = sList.stream().map(o -> o.getString("stateId")).collect(Collectors.toList());
|
||||
|
||||
List<AuditState> audit = siteInfoService.dao().query(AuditState.class, Cnd.where("stateId", "in", stateList));
|
||||
SqlExpressionGroup sqlExpressionGroup = new SqlExpressionGroup();
|
||||
audit.forEach(v->{
|
||||
if (Strings.isNotBlank(v.getAuditAfterType())){
|
||||
// CustomAuditTypeHandle instance = Enum.instance(CustomAuditTypeHandle.class, v.getAuditAfterType());
|
||||
// if(instance != null) {
|
||||
// instance.next(sqlExpressionGroup);
|
||||
// }
|
||||
}
|
||||
});
|
||||
if (!sqlExpressionGroup.isEmpty()) {
|
||||
cnd.and(sqlExpressionGroup);
|
||||
}
|
||||
|
||||
if (isAudit == null) {
|
||||
SqlExpressionGroup group = new SqlExpressionGroup();
|
||||
group.and(new Static("JSON_CONTAINS(auditList, JSON_OBJECT('auditUser', '" + ShiroUtil.getPrincipalProperty("id") + "'))"))
|
||||
.or("reserve_state", "in", stateList);
|
||||
cnd.and(group);
|
||||
} else if (isAudit) {
|
||||
//查询已审核
|
||||
cnd.and(new Static("JSON_CONTAINS(auditList, JSON_OBJECT('auditUser', '" + ShiroUtil.getPrincipalProperty("id") + "'))"));
|
||||
} else {
|
||||
cnd.and("reserve_state", "in", stateList);
|
||||
}
|
||||
|
||||
if (Strings.isNotBlank(siteType)) {
|
||||
cnd.and("asi.typeId", "=", siteType);
|
||||
}
|
||||
|
||||
if (Strings.isNotBlank(month)) {
|
||||
cnd.and("left(reserve_day, 7)", "=", month);
|
||||
}
|
||||
|
||||
if (Strings.isNotBlank(searchName) && Strings.isNotBlank(searchKeyword)) {
|
||||
cnd.where().andLike(searchName, searchKeyword);
|
||||
}
|
||||
|
||||
if (Strings.isNotBlank(pageOrderName) && Strings.isNotBlank(pageOrderName)) {
|
||||
cnd.orderBy(pageOrderName, PageUtil.getOrder(pageOrderBy));
|
||||
} else {
|
||||
cnd.asc("asr.reserve_state").desc("u.unitid");
|
||||
}
|
||||
|
||||
cnd.groupBy("asi.id");
|
||||
|
||||
sql.setCondition(cnd);
|
||||
return siteReserveService.listPage(pageNumber, pageSize, sql);
|
||||
}
|
||||
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("activity.site.review")
|
||||
public Object doReview(String[] id, Audit audit, Boolean isPass) {
|
||||
siteReserveService.insert(audit);
|
||||
|
||||
for (String s : id) {
|
||||
ActivitySiteReserve fetch = siteReserveService.fetch(s);
|
||||
|
||||
Integer stateCode = fetch.getReserve_state();
|
||||
Integer afterStateCode = activityCommonService.findAfterStateCode(stateCode, isPass);
|
||||
|
||||
List<ActivitySiteReserve> reserves = siteReserveService.query(Cnd.NEW().and("sqid", "=", fetch.getSqid()));
|
||||
String days = "";
|
||||
for (ActivitySiteReserve siteReserve : reserves) {
|
||||
|
||||
siteReserve.setReserve_state(afterStateCode);
|
||||
|
||||
List<NutMap> auditList = siteReserve.getAuditList();
|
||||
if (Lang.isEmpty(auditList)) {
|
||||
auditList = new ArrayList<>();
|
||||
}
|
||||
|
||||
auditList.add(NutMap.NEW().addv("stateCode", stateCode).addv("auditId", audit.getId())
|
||||
.addv("auditUser", ShiroUtil.getPrincipalProperty("id"))
|
||||
.addv("auditState", isPass)
|
||||
.addv("auditOption", audit.getAuditOpinion())
|
||||
);
|
||||
siteReserve.setAuditList(auditList);
|
||||
|
||||
days += siteReserve.getReserve_day() + ",";
|
||||
siteReserveService.update(siteReserve);
|
||||
}
|
||||
days = days.substring(0, days.length() - 1);
|
||||
|
||||
Integer successCode = activityCommonService.findSuccessStateCode(fetch.getSite_id());
|
||||
ActivitySiteInfo siteInfo = siteInfoService.fetch(fetch.getSite_id());
|
||||
|
||||
if (afterStateCode.equals(successCode) && siteInfo.getTypeId() == 1) {
|
||||
String content = "%s老师您好!您提交的%s使用申请已通过审批,如有疑问,欢迎咨询校工会。"
|
||||
.formatted(fetch.getReserve_person(), siteInfo.getName());
|
||||
Sys_user user = sysUserService.dao().fetch(Sys_user.class, fetch.getReserve_person_id());
|
||||
// msgApi.sendMsg(content, List.of(user.getLoginname()));
|
||||
} else if (!afterStateCode.equals(successCode)) {
|
||||
//发送微信提醒审核人
|
||||
//查询状态码对应的审核人
|
||||
Sql sql = Sqls.create("""
|
||||
select userId,u.loginname,u.username,u.unitname from audit_state_user asu
|
||||
left join `user` u on u.id = asu.userId left join audit_state s on s.stateId=asu.stateId $condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("asu.stateId", "=", afterStateCode);
|
||||
cnd.and("s.stateAuditType", "=", "0");
|
||||
String moduleName = activityCommonService.findModuleNameByActivityId(fetch.getSite_id());
|
||||
if ("infantRoom".equals(moduleName)) {
|
||||
//获取当前登录人的单位id
|
||||
String unitId = ShiroUtil.getPrincipalProperty("unitid").toString();
|
||||
cnd.and("u.unitid", "=", unitId);
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
List<Record> list = siteReserveService.list(sql);
|
||||
|
||||
for (Record item : list) {
|
||||
String content = "%s老师您好!%s老师预约%s日使用%s,请您通过门户网站或点击详情登录智慧工会平台进行审核。"
|
||||
.formatted(item.getString("username"),
|
||||
fetch.getReserve_person(),
|
||||
days,
|
||||
siteInfo.getName());
|
||||
Sys_user sys_user = sysUserService.fetch(item.getString("userId"));
|
||||
NutMap map = new NutMap();
|
||||
map.setv("keyword1", NutMap.NEW().setv("value", "场地预约"));
|
||||
map.setv("keyword2", NutMap.NEW().setv("value", item.getString("username")
|
||||
+ "(" + item.getString("loginname") + ")"));
|
||||
map.setv("keyword3", NutMap.NEW().setv("value", item.getString("unitname")));
|
||||
map.setv("keyword4", NutMap.NEW().setv("value", content));
|
||||
map.setv("keyword5", NutMap.NEW().setv("value", DateUtil.now()));
|
||||
// msgApi.sendMsg(sys_user.getWeAppOpenid(), msgApi.DSH_TEMPLATE_ID, Globals.AppDomain + "/mobile/activity/site/audit", map);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@At("") @Ok("beetl:/platform/activity/site/ReviewEntries.html")
|
||||
public void index() { }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.v.nutz.zhgh.activity.controller.site;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.zhgh.activity.services.impl.SiteBookingService;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.ioc.loader.annotation.*;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.mvc.annotation.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/** 校工会审核:PC/H5 共享业务服务,入口固定节点,不能由请求绕过节点限制。 */
|
||||
@IocBean @Ok("json:full") @At("/platform/activity/site/review/school")
|
||||
@RequiresPermissions("activity.site.review.school")
|
||||
public class SiteSchoolReviewController {
|
||||
@Inject private SiteBookingService siteBookingService;
|
||||
@At("") @Ok("beetl:/platform/activity/site/SiteReview.html")
|
||||
public void index(HttpServletRequest request) {
|
||||
request.setAttribute("reviewTitle","校工会审核");
|
||||
request.setAttribute("reviewApi","/platform/activity/site/review/school");
|
||||
}
|
||||
/** isAudit 为已审核/未审核;searchName 为场地名称或预约人字段,month 为 yyyy-MM,siteType 为类型 ID,返回 list/totalCount。 */
|
||||
@At @ViReturn
|
||||
public Object pageData(Boolean isAudit,String searchName,String searchKeyword,String month,String siteType,int pageNumber,int pageSize) {
|
||||
return siteBookingService.reviewPage("school",Boolean.TRUE.equals(isAudit),searchName,searchKeyword,month,siteType,pageNumber,pageSize);
|
||||
}
|
||||
/** id 是预约记录主键,返回申请、时段及审核历史,服务层校验查看范围。 */
|
||||
@At @ViReturn
|
||||
public Object detail(String id) { return siteBookingService.detail(id); }
|
||||
/** ids 为预约记录 ID 数组,isPass 为通过/拒绝,auditOpinion 为必填意见;返回 Object,由 @ViReturn 包装 code(0 成功)和 msg(结果说明)。 */
|
||||
@At @ViReturn @Aop(TransAop.READ_COMMITTED)
|
||||
public Object doReview(String[] ids,Boolean isPass,String auditOpinion) {
|
||||
siteBookingService.review("school",ids,isPass,auditOpinion);
|
||||
// Object 返回类型保留 @ViReturn 的统一结果,避免前端收到空响应。
|
||||
return null;
|
||||
}
|
||||
/** id 为预约记录主键,撤回本人最近一次审核;返回统一 code/msg,成功后恢复当前节点待审。 */
|
||||
@At @ViReturn @Aop(TransAop.READ_COMMITTED)
|
||||
public Object doRevoke(String id) {
|
||||
siteBookingService.revokeReview("school",id);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.v.nutz.zhgh.activity.controller.site;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.zhgh.activity.services.impl.SiteBookingService;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.ioc.loader.annotation.*;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.mvc.annotation.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/** 分工会审核:PC/H5 共享业务服务,入口固定节点,不能由请求绕过节点限制。 */
|
||||
@IocBean @Ok("json:full") @At("/platform/activity/site/review/union")
|
||||
@RequiresPermissions("activity.site.review.union")
|
||||
public class SiteUnionReviewController {
|
||||
@Inject private SiteBookingService siteBookingService;
|
||||
@At("") @Ok("beetl:/platform/activity/site/SiteReview.html")
|
||||
public void index(HttpServletRequest request) {
|
||||
request.setAttribute("reviewTitle","分工会审核");
|
||||
request.setAttribute("reviewApi","/platform/activity/site/review/union");
|
||||
}
|
||||
/** isAudit 为已审核/未审核;searchName 为场地名称或预约人字段,month 为 yyyy-MM,siteType 为类型 ID,返回 list/totalCount。 */
|
||||
@At @ViReturn
|
||||
public Object pageData(Boolean isAudit,String searchName,String searchKeyword,String month,String siteType,int pageNumber,int pageSize) {
|
||||
return siteBookingService.reviewPage("union",Boolean.TRUE.equals(isAudit),searchName,searchKeyword,month,siteType,pageNumber,pageSize);
|
||||
}
|
||||
/** id 是预约记录主键,返回申请、时段及审核历史,服务层校验查看范围。 */
|
||||
@At @ViReturn
|
||||
public Object detail(String id) { return siteBookingService.detail(id); }
|
||||
/** ids 为预约记录 ID 数组,isPass 为通过/拒绝,auditOpinion 为必填意见;返回 Object,由 @ViReturn 包装 code(0 成功)和 msg(结果说明)。 */
|
||||
@At @ViReturn @Aop(TransAop.READ_COMMITTED)
|
||||
public Object doReview(String[] ids,Boolean isPass,String auditOpinion) {
|
||||
siteBookingService.review("union",ids,isPass,auditOpinion);
|
||||
// Object 返回类型保留 @ViReturn 的统一结果,避免前端收到空响应。
|
||||
return null;
|
||||
}
|
||||
/** id 为预约记录主键,撤回本人最近一次审核;返回统一 code/msg,成功后恢复当前节点待审。 */
|
||||
@At @ViReturn @Aop(TransAop.READ_COMMITTED)
|
||||
public Object doRevoke(String id) {
|
||||
siteBookingService.revokeReview("union",id);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -28,13 +28,38 @@ public class ActivitySiteInfo {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String name;
|
||||
|
||||
@Column
|
||||
@Comment("校区名称,取自系统校区选项")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String campus;
|
||||
|
||||
@Column
|
||||
@Comment("预约时间段类型:1分段预约,2全天候预约;旧场地按分段兼容")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer reserveTimeType;
|
||||
|
||||
@Column
|
||||
@Comment("禁用时段:date、startTime、endTime")
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
private List<org.nutz.lang.util.NutMap> notApplyTimeList;
|
||||
|
||||
@Column
|
||||
@Comment("分段场次原始配置,保留切换前的时间及timeUnit分钟单位")
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
private List<org.nutz.lang.util.NutMap> segmentedOpenHours;
|
||||
|
||||
@Column
|
||||
@Comment("全天候起止时间及timeUnit分钟单位")
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
private org.nutz.lang.util.NutMap fullDayOpenHour;
|
||||
|
||||
@Column
|
||||
@Comment("场地地址")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String address;
|
||||
|
||||
@Column
|
||||
@Comment("联系人")
|
||||
@Comment("场地管理员")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String contact_person;
|
||||
|
||||
@@ -84,7 +109,7 @@ public class ActivitySiteInfo {
|
||||
private Integer sexLimit;
|
||||
|
||||
@Column
|
||||
@Comment("个人预约限定人数")
|
||||
@Comment("限定人数")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer limitNum;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,16 @@ import java.util.List;
|
||||
@Comment("活动场地预约信息")
|
||||
public class ActivitySiteReserve extends BaseModel {
|
||||
|
||||
@Column
|
||||
@Comment("协会预约所属协会,提交时校验申请人的有效成员关系")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String clubId;
|
||||
|
||||
@Column
|
||||
@Comment("提交时所属分工会快照,用于分工会主席审核范围")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String reserve_person_unionid;
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@@ -127,7 +137,7 @@ public class ActivitySiteReserve extends BaseModel {
|
||||
private String joinUser;
|
||||
|
||||
@Column
|
||||
@Comment("预约类型(1.个人预约,2.单位预约)")
|
||||
@Comment("预约类型(1.个人预约,2.分工会预约,3.协会预约)")
|
||||
@ColDefine(type = ColType.INT, width = 10)
|
||||
private Integer reserve_type;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,16 @@ import java.util.List;
|
||||
@Comment("活动场地类型")
|
||||
public class ActivityType {
|
||||
|
||||
@Column
|
||||
@Comment("类型编码,字符串保留前导零")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String code;
|
||||
|
||||
@Column
|
||||
@Comment("是否启用;停用后不可新增预约")
|
||||
@Default("1")
|
||||
private Boolean enabled;
|
||||
|
||||
@Id
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.INT)
|
||||
|
||||
@@ -5,4 +5,12 @@ import cn.wizzer.framework.base.service.BaseService;
|
||||
import io.v.nutz.zhgh.activity.models.ActivitySiteInfo;
|
||||
|
||||
public interface SiteInfoService extends BaseService<ActivitySiteInfo> {
|
||||
/** form 为场地表单,editing 区分新增和编辑;保存校区并由后端关联职工之家类型,返回 void。 */
|
||||
void saveManagedSite(ActivitySiteInfo form, boolean editing);
|
||||
/** 校验 day(yyyy-MM-dd)、start/end(HH:mm,结束可24:00)属于有效场次且不与禁用时间交叉;不通过抛出参数异常。 */
|
||||
void validateBookingSlot(ActivitySiteInfo site, String day, String start, String end);
|
||||
/** 查询某天可预约场次,reserveType=1/2/3;返回 start_time/end_time、code(1可约)、msg 和人数。 */
|
||||
java.util.List<org.nutz.lang.util.NutMap> availableSlots(String siteId, String day, Integer reserveType);
|
||||
/** 返回start/end范围内实际同时占用的峰值人数,避免配置扩大后把相邻历史场次累加成超额。 */
|
||||
int occupiedCount(java.util.List<io.v.nutz.zhgh.activity.models.ActivitySiteReserve> rows, String start, String end);
|
||||
}
|
||||
|
||||
@@ -1,103 +1,47 @@
|
||||
package io.v.nutz.zhgh.activity.services.impl;
|
||||
|
||||
import io.v.nutz.zhgh.activity.models.ActivityType;
|
||||
import io.v.nutz.zhgh.activity.services.ActivityCommonService;
|
||||
import io.v.nutz.zhgh.activity.models.ActivitySiteInfo;
|
||||
import io.v.nutz.zhgh.activity.services.ActivityTypeService;
|
||||
import io.v.nutz.base.model.AuditState;
|
||||
import io.v.nutz.base.model.AuditStateUser;
|
||||
import io.v.nutz.base.service.impl.ViServiceImpl;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.Daos;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.dao.*;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.Lang;
|
||||
|
||||
import java.util.List;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.lang.Strings;
|
||||
|
||||
/** 类型仅管理分类资料;保留旧模块字段,不再增删审核节点。 */
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class ActivityTypeServiceImpl extends ViServiceImpl<ActivityType> implements ActivityTypeService {
|
||||
|
||||
@Inject
|
||||
private ActivityCommonService activityCommonService;
|
||||
|
||||
public ActivityTypeServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
public ActivityTypeServiceImpl(Dao dao) { super(dao); }
|
||||
/** 旧接口兼容,不用于新预约流程。 */
|
||||
public int findMaxStateId() { return dao().func("audit_state", "max", "stateId"); }
|
||||
/** 校验编码/名称/启用状态,编码须唯一且保留前导零。 */
|
||||
private void validate(ActivityType t) {
|
||||
if(t == null || Strings.isBlank(t.getCode()) || Strings.isBlank(t.getMeetingTypeName()) || t.getEnabled()==null)
|
||||
throw new IllegalArgumentException("请填写类型编码、类型名称和是否启用");
|
||||
t.setCode(t.getCode().trim()); t.setMeetingTypeName(t.getMeetingTypeName().trim());
|
||||
if(t.getCode().length()>50 || t.getMeetingTypeName().length()>50) throw new IllegalArgumentException("编码和名称不能超过50字");
|
||||
Cnd c=Cnd.where("code","=",t.getCode());
|
||||
if(t.getId()!=null) c.and("id","!=",t.getId());
|
||||
if(count(c)>0) throw new IllegalArgumentException("类型编码已存在");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int findMaxStateId() {
|
||||
return dao().func(AuditState.class, "max", "stateId");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模块名称查找stateId
|
||||
*
|
||||
* @param moduleName
|
||||
* @return
|
||||
*/
|
||||
private String[] findStateIdArray(String moduleName) {
|
||||
//查询该类型原来关联的stateId
|
||||
Sql stateSql = Sqls.create("select stateId from audit_state where module = @moduleName").setParam("moduleName", moduleName);
|
||||
return (String[]) Daos.query(dao(), stateSql.toString(), Sqls.callback.strs());
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加流程信息
|
||||
*
|
||||
* @param activityType
|
||||
*/
|
||||
private void insertAuditInfo(ActivityType activityType) {
|
||||
List<AuditState> auditStateList = activityType.getAuditStateList();
|
||||
if (Lang.isNotEmpty(auditStateList)) {
|
||||
for (int i = 0; i < auditStateList.size(); i++) {
|
||||
AuditState state = auditStateList.get(i);
|
||||
state.setModule(activityType.getModuleName());
|
||||
state.setMeetingTypeId(activityType.getId());
|
||||
dao().insertWith(state, "^auditStateUserList$");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
/** 新增类型,不生成审核节点。 */
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void add(ActivityType activityType) {
|
||||
dao().insert(activityType);
|
||||
insertAuditInfo(activityType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(ActivityType t) { validate(t); t.setSortNum(count()+1); t.setModuleName(t.getCode()); dao().insert(t); }
|
||||
/** 更新分类资料,保留历史 moduleName 和关联节点。 */
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void edit(ActivityType activityType) {
|
||||
dao().update(activityType);
|
||||
|
||||
//查询该类型原来关联的stateId
|
||||
String[] stateIdStr = findStateIdArray(activityType.getModuleName());
|
||||
|
||||
//删除原来审核状态下的审核人员
|
||||
dao().clear(AuditStateUser.class, Cnd.where("stateId", "in", stateIdStr));
|
||||
//删除原来的审核状态
|
||||
dao().clear(AuditState.class, Cnd.where("stateId", "in", stateIdStr));
|
||||
|
||||
//添加新的审核流程
|
||||
insertAuditInfo(activityType);
|
||||
public void edit(ActivityType t) {
|
||||
validate(t);
|
||||
ActivityType old=fetch(t.getId());
|
||||
if(old==null) throw new IllegalArgumentException("类型不存在");
|
||||
old.setCode(t.getCode()).setMeetingTypeName(t.getMeetingTypeName()).setEnabled(t.getEnabled()); dao().update(old);
|
||||
}
|
||||
|
||||
@Override
|
||||
/** 被场地使用的类型不可删除,防止预约历史失去分类。 */
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void delete(ActivityType activityType) {
|
||||
//查询该类型原来关联的stateId
|
||||
String[] stateIdStr = findStateIdArray(activityType.getModuleName());
|
||||
|
||||
//删除原来审核状态下的审核人员
|
||||
dao().clear(AuditStateUser.class, Cnd.where("stateId", "in", stateIdStr));
|
||||
//删除原来的审核状态
|
||||
dao().clear(AuditState.class, Cnd.where("stateId", "in", stateIdStr));
|
||||
|
||||
dao().delete(activityType);
|
||||
public void delete(ActivityType t) {
|
||||
if(t==null) throw new IllegalArgumentException("类型不存在");
|
||||
if(dao().count(ActivitySiteInfo.class,Cnd.where("typeId","=",t.getId()))>0) throw new IllegalArgumentException("该类型已被场地使用,请停用而非删除");
|
||||
dao().delete(t);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,557 @@
|
||||
package io.v.nutz.zhgh.activity.services.impl;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import io.v.nutz.base.model.Audit;
|
||||
import io.v.nutz.base.service.impl.BaseServiceImpl;
|
||||
import io.v.nutz.sys.models.Sys_user;
|
||||
import io.v.nutz.sys.models.Sys_local_process_instance;
|
||||
import io.v.nutz.sys.models.Sys_local_process_instance_task;
|
||||
import io.v.nutz.base.page.Pagination;
|
||||
import io.v.nutz.sys.services.SysLocalProcessService;
|
||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||
import io.v.nutz.zhgh.activity.models.*;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.*;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.entity.Record;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.*;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import java.time.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 场地预约固定状态流程。一次 sqid 对应多条时段和一个待办实例;
|
||||
* 状态名称取自 state,路由由预约类型决定,与场地类型配置无关。
|
||||
*/
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class SiteBookingService extends BaseServiceImpl<ActivitySiteReserve> {
|
||||
public static final int UNION = 4000, CLUB = 4010, SCHOOL = 4020, PASS = 4030, REJECT = 4040, CANCEL = 4050;
|
||||
/** 校工会节点统一按专属场地管理员角色分配待办和校验审核资格。 */
|
||||
private static final String SCHOOL_REVIEW_ROLE = "SchoolUnionActivityVenueAdmin";
|
||||
/** 兼容现有日历组件的状态字段;名称统一读取 state,不再读取 audit_state。 */
|
||||
public static final String STATES = "(select state_id stateId,state_name stateName,state_color stateColor,"
|
||||
+ "case when state_id=4030 then 3 when state_id in (4040,4050) then 1 else 0 end stateAuditType "
|
||||
+ "from state where belong='activity_site')";
|
||||
@Inject private SysLocalProcessService sysLocalProcessService;
|
||||
@Inject private io.v.nutz.zhgh.activity.services.SiteInfoService siteInfoService;
|
||||
@Inject private io.v.nutz.base.utils.MsgApi msgApi;
|
||||
|
||||
public SiteBookingService(Dao dao) { super(dao); }
|
||||
|
||||
/** 返回当前登录人 ID,不接受前端冒用申请人或审核人。 */
|
||||
private String uid() { return String.valueOf(ShiroUtil.getPrincipalProperty("id")); }
|
||||
|
||||
/** 返回已通过入会且正常在会的协会,元素为 id、name;不按会长角色限制申请资格。 */
|
||||
public List<NutMap> myClubs() {
|
||||
return listMap(Sqls.create("select distinct c.id,c.name from sys_club_user m join sys_club c on c.id=m.clubid "
|
||||
+ "where m.userid=@uid and m.status=5 and m.isNormal=1 and coalesce(m.delFlag,0)=0 "
|
||||
+ "order by c.name").setParam("uid", uid()));
|
||||
}
|
||||
|
||||
/** 无入参,返回当前申请人的id/name所属分工会;采用与提交相同的user视图组织来源,未配置时返回空字符串。 */
|
||||
public NutMap myBookingUnion() {
|
||||
List<NutMap> rows=listMap(Sqls.create("select u.unionid id,un.unionname name from `user` u left join sys_union un on un.id=u.unionid where u.id=@id")
|
||||
.setParam("id",uid()));
|
||||
return rows.isEmpty() ? NutMap.NEW().setv("id","").setv("name","") : rows.get(0);
|
||||
}
|
||||
|
||||
/** 预约类型 1 历史个人、2 分工会、3 协会,返回应进入的首个状态 ID。 */
|
||||
public int firstState(Integer type) {
|
||||
if (Integer.valueOf(1).equals(type)) return UNION;
|
||||
if (Integer.valueOf(2).equals(type)) return UNION;
|
||||
if (Integer.valueOf(3).equals(type)) return CLUB;
|
||||
throw new IllegalArgumentException("请选择有效的预约类型");
|
||||
}
|
||||
|
||||
/** 按节点角色及组织范围查实际审核人;不允许空组织退化为全校范围。 */
|
||||
private List<NutMap> reviewers(int node, ActivitySiteReserve booking) {
|
||||
String role = node == UNION ? "gh01" : node == CLUB ? "club01" : SCHOOL_REVIEW_ROLE;
|
||||
String filter = "";
|
||||
if (node == UNION) {
|
||||
if (Strings.isBlank(booking.getReserve_person_unionid())) throw new IllegalArgumentException("申请人未关联分工会,请先完善所属分工会");
|
||||
filter = " and ur.unionid=@org";
|
||||
} else if (node == CLUB) {
|
||||
if (Strings.isBlank(booking.getClubId())) throw new IllegalArgumentException("请选择所属协会");
|
||||
filter = " and ur.stid=@org";
|
||||
} else if (node != SCHOOL) throw new IllegalArgumentException("当前节点不允许审核");
|
||||
return listMap(Sqls.create("select distinct u.id,u.loginname from sys_user_role ur join sys_role r on r.id=ur.roleId "
|
||||
+ "join sys_user u on u.id=ur.userId where r.code=@role and coalesce(u.disabled,0)=0 "
|
||||
+ "and coalesce(u.delFlag,0)=0" + filter).setParam("role", role)
|
||||
.setParam("org", node == UNION ? booking.getReserve_person_unionid() : booking.getClubId()));
|
||||
}
|
||||
|
||||
/** 返回非空审核人工号名单,缺少节点负责人时中止事务并提示具体节点。 */
|
||||
private List<String> assignments(int node, ActivitySiteReserve booking) {
|
||||
List<String> names = reviewers(node, booking).stream().map(v -> v.getString("loginname"))
|
||||
.filter(Strings::isNotBlank).distinct().collect(Collectors.toList());
|
||||
if (names.isEmpty()) throw new IllegalArgumentException(node == UNION ? "所属分工会未配置可用的分工会主席"
|
||||
: node == CLUB ? "所选协会未配置可用的协会会长" : "未配置可用的校工会场地管理员");
|
||||
return names;
|
||||
}
|
||||
|
||||
/** stages 是后端固定入口 union/club/school,返回节点 ID,禁止任意传入状态码。 */
|
||||
public int node(String stage) {
|
||||
if ("union".equals(stage)) return UNION;
|
||||
if ("club".equals(stage)) return CLUB;
|
||||
if ("school".equals(stage)) return SCHOOL;
|
||||
throw new IllegalArgumentException("无效的审核入口");
|
||||
}
|
||||
|
||||
private String stage(int node) { return node == UNION ? "union" : node == CLUB ? "club" : "school"; }
|
||||
private String title(int node) { return node == UNION ? "分工会审核" : node == CLUB ? "协会审核" : "校工会审核"; }
|
||||
private String process(ActivitySiteReserve b) { return "activity_site@" + b.getSqid(); }
|
||||
|
||||
/**
|
||||
* 保存一次预约。form 提供场地、事由、类型和协会;slots 提供 day/start_time/end_time。
|
||||
* 返回新申请 sqid;人员身份、组织、状态在后端生成。锁定场地避免并发超额预约。
|
||||
*/
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public String submit(ActivitySiteReserve form, List<NutMap> slots) {
|
||||
if (form == null || Strings.isBlank(form.getSite_id())) throw new IllegalArgumentException("请选择场地");
|
||||
int first = firstState(form.getReserve_type());
|
||||
// 个人日期及共享容量规则按业务类型判断,不能随首审节点变更套用到分工会预约。
|
||||
boolean personal = Integer.valueOf(1).equals(form.getReserve_type());
|
||||
if (slots == null || slots.isEmpty()) throw new IllegalArgumentException("请选择预约时段");
|
||||
listMap(Sqls.create("select id from activity_site_info where id=@id for update").setParam("id", form.getSite_id()));
|
||||
ActivitySiteInfo site = dao().fetch(ActivitySiteInfo.class, form.getSite_id());
|
||||
if (site == null || !Boolean.TRUE.equals(site.getState())) throw new IllegalArgumentException("场地不存在或已停用");
|
||||
ActivityType type = dao().fetch(ActivityType.class, site.getTypeId());
|
||||
if (type == null || !Boolean.TRUE.equals(type.getEnabled())) throw new IllegalArgumentException("场地类型已停用");
|
||||
if (Strings.isBlank(form.getReserve_cause())) throw new IllegalArgumentException("请填写预约事由");
|
||||
Sys_user user = dao().fetch(Sys_user.class, uid());
|
||||
form.setReserve_person_id(uid());
|
||||
form.setReserve_person(user.getUsername());
|
||||
form.setReserve_person_phone(user.getMobile());
|
||||
// user 视图按特殊人员工会关系、所属单位计算有效分工会;原表 unionid 可能为空或为历史值。
|
||||
// 提交时保存有效组织快照,后续待办和审核范围均使用该快照匹配主席角色。
|
||||
List<NutMap> profile=listMap(Sqls.create("select unionid,unitname from `user` where id=@id").setParam("id",uid()));
|
||||
form.setReserve_person_unionid(profile.isEmpty() ? null : profile.get(0).getString("unionid"));
|
||||
form.setReserve_person_unit(profile.isEmpty() ? "" : profile.get(0).getString("unitname"));
|
||||
if (first == CLUB && myClubs().stream().noneMatch(v -> v.getString("id").equals(form.getClubId())))
|
||||
throw new IllegalArgumentException("您不是所选协会的有效成员,请重新选择所属协会");
|
||||
if (first != CLUB) form.setClubId(null);
|
||||
// 提交前同时检查首节点和最终节点,避免申请进入流程后无审核人可处理。
|
||||
assignments(first, form);
|
||||
assignments(SCHOOL, form);
|
||||
List<NutMap> open = Json.fromJsonAsList(NutMap.class, Json.toJson(site.getOpen_hours()));
|
||||
// 全天候按每个日期连续选择时间单位,避免跨禁用间隔或拆成多段。
|
||||
if (Integer.valueOf(2).equals(site.getReserveTimeType())) {
|
||||
Map<String,List<NutMap>> byDay=slots.stream().collect(Collectors.groupingBy(v->v.getString("day")));
|
||||
for(List<NutMap> selected:byDay.values()) {
|
||||
selected.sort(Comparator.comparing(v->v.getString("start_time")));
|
||||
for(int i=1;i<selected.size();i++) if(!selected.get(i-1).getString("end_time").equals(selected.get(i).getString("start_time")))
|
||||
throw new IllegalArgumentException("全天候预约请选择连续时段");
|
||||
}
|
||||
}
|
||||
Set<String> distinct = new HashSet<>();
|
||||
for (NutMap slot : slots) {
|
||||
String day = slot.getString("day"), start = slot.getString("start_time"), end = slot.getString("end_time");
|
||||
if (Strings.isBlank(day) || Strings.isBlank(start) || Strings.isBlank(end)) throw new IllegalArgumentException("预约日期和起止时间不能为空");
|
||||
siteInfoService.validateBookingSlot(site,day,start,end);
|
||||
LocalDate date = LocalDate.parse(day);
|
||||
LocalTime startTime = LocalTime.parse(start), endTime = "24:00".equals(end) ? LocalTime.MIDNIGHT : LocalTime.parse(end);
|
||||
if (!"24:00".equals(end) && !endTime.isAfter(startTime)) throw new IllegalArgumentException("结束时间必须晚于开始时间");
|
||||
if (!LocalDateTime.of(date, startTime).isAfter(LocalDateTime.now())) throw new IllegalArgumentException("预约时段已过期");
|
||||
if (personal && !date.equals(LocalDate.now().plusDays(1))) throw new IllegalArgumentException("个人预约只支持预约明天的时间");
|
||||
if (Boolean.TRUE.equals(site.getWorkday()) && date.getDayOfWeek().getValue() >= 6) throw new IllegalArgumentException("该场地仅支持工作日预约");
|
||||
if (open == null || open.stream().noneMatch(v -> start.equals(v.getString("start_time")) && end.equals(v.getString("end_time"))))
|
||||
throw new IllegalArgumentException("所选时段不在场地开放场次中,请刷新重选");
|
||||
if (!distinct.add(day + " " + start + " " + end)) throw new IllegalArgumentException("同一申请不能重复选择时段");
|
||||
List<ActivitySiteReserve> existing = query(Cnd.where("site_id", "=", site.getId()).and("reserve_day", "=", day)
|
||||
.and("start_time", "<", end).and("end_time", ">", start).and("reserve_state", "not in", Arrays.asList(REJECT, CANCEL)));
|
||||
if (existing.stream().anyMatch(v -> uid().equals(v.getReserve_person_id()))) throw new IllegalArgumentException("您已预约该时间段");
|
||||
if (!personal && !existing.isEmpty()) throw new IllegalArgumentException("该时段已有预约,协会或分工会预约需要空闲时段");
|
||||
if (personal && (existing.stream().anyMatch(v -> !Integer.valueOf(1).equals(v.getReserve_type()))
|
||||
|| site.getLimitNum() == null || siteInfoService.occupiedCount(existing,start,end) >= site.getLimitNum())) throw new IllegalArgumentException("该时段预约人数已满或已被分工会/协会预约");
|
||||
}
|
||||
String sqid = UUID.randomUUID().toString().replace("-", "");
|
||||
for (NutMap slot : slots) {
|
||||
ActivitySiteReserve row = new ActivitySiteReserve();
|
||||
row.setSqid(sqid); row.setSite_id(site.getId()); row.setReserve_type(form.getReserve_type());
|
||||
row.setClubId(form.getClubId()); row.setReserve_person_unionid(form.getReserve_person_unionid());
|
||||
row.setReserve_person_id(uid()); row.setReserve_person(user.getUsername());
|
||||
row.setReserve_person_phone(user.getMobile()); row.setReserve_person_unit(form.getReserve_person_unit());
|
||||
row.setReserve_cause(form.getReserve_cause()); row.setReserve_state(first);
|
||||
row.setReserve_day(slot.getString("day")); row.setStart_time(slot.getString("start_time")); row.setEnd_time(slot.getString("end_time"));
|
||||
dao().insert(row);
|
||||
}
|
||||
form.setSqid(sqid);
|
||||
sysLocalProcessService.startProcess("场地预约:" + site.getName(), process(form), title(first), uid(),
|
||||
"/platform/activity/site/reserve", "/mobile/activity/site/info/my");
|
||||
createTask(first, form);
|
||||
notifyBooking(form,first,null);
|
||||
return sqid;
|
||||
}
|
||||
|
||||
/** 创建当前节点待办,所有候选人共用一个任务,一人处理即完成该节点。 */
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void createTask(int node, ActivitySiteReserve booking) {
|
||||
String url = "/platform/activity/site/review/" + stage(node);
|
||||
String mobile = "/mobile/activity/site/audit/" + stage(node);
|
||||
sysLocalProcessService.createTask(process(booking), stage(node), title(node), uid(), assignments(node, booking),
|
||||
url, url, mobile, mobile);
|
||||
sysLocalProcessService.updateProcessNodeName(process(booking), title(node));
|
||||
}
|
||||
|
||||
/**
|
||||
* booking 为整条申请,state 为新待审节点或最终 PASS/REJECT,opinion 为最终审核意见(待审时为空)。
|
||||
* 返回 void;每次按 sqid 组织一条钉钉消息、接收人工号去重,复用消息开关与发送日志。
|
||||
* 只由首次提交和审核流转调用,撤回审核、新建恢复待办、撤销和删除不发送。
|
||||
*/
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
private void notifyBooking(ActivitySiteReserve booking,int state,String opinion) {
|
||||
try {
|
||||
boolean result = state == PASS || state == REJECT;
|
||||
List<String> receivers;
|
||||
if (result) {
|
||||
Sys_user applicant = dao().fetch(Sys_user.class,booking.getReserve_person_id());
|
||||
receivers = applicant == null || Strings.isBlank(applicant.getLoginname())
|
||||
? Collections.emptyList() : Collections.singletonList(applicant.getLoginname());
|
||||
} else receivers = assignments(state,booking);
|
||||
if (receivers.isEmpty()) throw new IllegalArgumentException("消息接收人工号为空");
|
||||
ActivitySiteInfo site = dao().fetch(ActivitySiteInfo.class,booking.getSite_id());
|
||||
List<NutMap> slots = listMap(Sqls.create("select reserve_day,start_time,end_time from activity_site_reserve where sqid=@sqid")
|
||||
.setParam("sqid",booking.getSqid()));
|
||||
List<NutMap> groups = groupReservationTimes(slots);
|
||||
// 长申请只列前五个日期,完整时段在业务页查看,避免超出消息长度限制。
|
||||
List<String> days = new ArrayList<>();
|
||||
for (NutMap group : groups.stream().limit(5).collect(Collectors.toList())) {
|
||||
List<NutMap> ranges = (List<NutMap>) group.get("ranges");
|
||||
String times = ranges.stream().limit(4).map(range -> range.getString("start") + "–" + range.getString("end"))
|
||||
.collect(Collectors.joining("、"));
|
||||
days.add(group.getString("date") + " " + times + (ranges.size()>4 ? "等时段" : ""));
|
||||
}
|
||||
String status = state == PASS ? "审核通过" : state == REJECT ? "审核拒绝" : "待" + title(state);
|
||||
String content = "申请人:" + booking.getReserve_person() + ";场地:" + (site == null ? "" : site.getName())
|
||||
+ ";预约时段:" + String.join(";",days) + (groups.size()>5 ? "等,共" + groups.size() + "天" : "")
|
||||
+ ";状态:" + status + (result && Strings.isNotBlank(opinion) ? ";审核意见:" + opinion : "")
|
||||
+ (result ? "。请点击查看我的预约。" : "。请点击进入审核页面处理。");
|
||||
String path = result ? "/mobile/activity/site/info/my" : "/mobile/activity/site/audit/" + stage(state);
|
||||
String domain = Strings.sNull(io.v.nutz.web.commons.base.Globals.AppDomain).replaceAll("/+$","");
|
||||
msgApi.sendMsgInsertLog(Collections.singletonList("DingTalk"),receivers.stream().distinct().collect(Collectors.toList()),
|
||||
2,result ? "场地预约" + status : "场地预约审核通知",content,"",domain + path,"场地预约");
|
||||
} catch (Exception e) {
|
||||
// 外部发送失败不回滚已完成的预约操作;记录申请编号以便排查,避免用户重试造成重复申请。
|
||||
org.nutz.log.Logs.get().error("场地预约钉钉通知失败,sqid=" + booking.getSqid() + ",state=" + state,e);
|
||||
}
|
||||
}
|
||||
|
||||
/** 返回指定审核入口的数据范围,分工会/协会使用角色关系上的组织字段。 */
|
||||
private String scope(int node) {
|
||||
if (node == SCHOOL) return "exists(select 1 from sys_user_role ur join sys_role r on r.id=ur.roleId where ur.userId=@uid and r.code='" + SCHOOL_REVIEW_ROLE + "')";
|
||||
return "exists(select 1 from sys_user_role ur join sys_role r on r.id=ur.roleId where ur.userId=@uid and r.code='"
|
||||
+ (node == UNION ? "gh01" : "club01") + "' and "
|
||||
+ (node == UNION ? "ur.unionid=b.reserve_person_unionid" : "ur.stid=b.clubId") + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询节点待审/已审申请。isAudit=null 为两者合并;返回标准分页 list/totalCount。
|
||||
* 以每个 sqid 的代表行展示,避免按场地分组混合不同申请。
|
||||
*/
|
||||
public Object reviewPage(String stage, Boolean isAudit, String keyword, String month, String typeId, int page, int size) {
|
||||
return reviewPage(stage, isAudit, "", keyword, month, typeId, page, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* PC 指定 searchName=asi.name(场地名称)或 asr.reserve_person(预约人);空值兼容手机端组合搜索。
|
||||
* keyword 为关键词,month 为 yyyy-MM,typeId 为场地类型主键;返回 list/totalCount 标准分页结果。
|
||||
* 查询字段只映射固定白名单,不把请求提供的字段名拼入 SQL。
|
||||
*/
|
||||
public Object reviewPage(String stage, Boolean isAudit, String searchName, String keyword, String month, String typeId, int page, int size) {
|
||||
String keywordFilter;
|
||||
if (Strings.isBlank(searchName)) keywordFilter = "s.name like @like or b.reserve_person like @like";
|
||||
else if ("asi.name".equals(searchName)) keywordFilter = "s.name like @like";
|
||||
else if ("asr.reserve_person".equals(searchName)) keywordFilter = "b.reserve_person like @like";
|
||||
else throw new IllegalArgumentException("请选择有效的查询类型");
|
||||
int node = node(stage);
|
||||
String pending = "(b.reserve_state=" + node + " and " + scope(node) + ")";
|
||||
String done = "JSON_CONTAINS(coalesce(b.auditList,'[]'),JSON_OBJECT('auditUser',@uid,'stateCode'," + node + "))";
|
||||
String selected = isAudit == null ? "(" + pending + " or " + done + ")" : isAudit ? done : pending;
|
||||
Sql sql = Sqls.create(baseSelect() + " where b.id=(select min(x.id) from activity_site_reserve x where x.sqid=b.sqid) and "
|
||||
+ selected + " and (@keyword='' or " + keywordFilter + ") "
|
||||
+ "and (@month='' or left(b.reserve_day,7)=@month) and (@typeId='' or s.typeId=@typeId) order by b.opAt desc,b.id")
|
||||
.setParam("uid", uid()).setParam("keyword", Strings.sNull(keyword)).setParam("like", "%" + Strings.sNull(keyword) + "%")
|
||||
.setParam("month", Strings.sNull(month)).setParam("typeId", Strings.sNull(typeId));
|
||||
Pagination result = listPageMap(Math.max(1,page), Math.min(100,Math.max(1,size)), sql);
|
||||
// 按当前页的申请一次读取原始时段,不依赖可能被数据库截断的group_concat。
|
||||
List<String> sqids=result.<Map<String,Object>>getList().stream().map(row->String.valueOf(row.get("sqid"))).collect(Collectors.toList());
|
||||
Map<String,List<NutMap>> slotsByBooking=new HashMap<>();
|
||||
if(!sqids.isEmpty()) {
|
||||
List<NutMap> slots=listMap(Sqls.create("select sqid,reserve_day,start_time,end_time from activity_site_reserve where sqid in (@sqids)").setParam("sqids",sqids));
|
||||
slotsByBooking=slots.stream().collect(Collectors.groupingBy(row->row.getString("sqid")));
|
||||
}
|
||||
// 两端共用后端资格判断;真正撤回仍在加锁后复查,不能信任列表中的旧状态。
|
||||
for (Map<String,Object> row : result.<Map<String,Object>>getList()) {
|
||||
ActivitySiteReserve booking = fetch(String.valueOf(row.get("id")));
|
||||
String reason = revokeReason(node, booking);
|
||||
row.put("canRevoke", reason == null);
|
||||
row.put("revokeReason", reason);
|
||||
row.put("reservationTimeGroups",groupReservationTimes(slotsByBooking.getOrDefault(String.valueOf(row.get("sqid")),Collections.emptyList())));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* pageNumber/pageSize 为分页参数,sql 为调用页已包含权限及筛选的分组查询,month 为可空的 YYYY-MM。
|
||||
* 返回原分页结构,list 每行补充 reservationTimeGroups(日期及连续时段)和去重后的 days;不扩大月份范围。
|
||||
*/
|
||||
public Pagination reservationPage(int pageNumber, int pageSize, Sql sql, String month) {
|
||||
Pagination page = listPage(pageNumber,pageSize,sql);
|
||||
List<Record> rows = page.getList();
|
||||
if (rows.isEmpty()) return page;
|
||||
List<String> sqids = rows.stream().map(row -> row.getString("sqid")).collect(Collectors.toList());
|
||||
List<NutMap> slots = listMap(Sqls.create("select sqid,reserve_day,start_time,end_time from activity_site_reserve "
|
||||
+ "where sqid in (@sqids) and (@month='' or left(reserve_day,7)=@month)")
|
||||
.setParam("sqids",sqids).setParam("month",Strings.sNull(month)));
|
||||
Map<String,List<NutMap>> grouped = slots.stream().collect(Collectors.groupingBy(row -> row.getString("sqid")));
|
||||
// 仅批量读取当前页申请,避免逐条查询及 group_concat 长度限制导致的时段丢失。
|
||||
List<Map<String,Object>> output = new ArrayList<>();
|
||||
for (Record row : rows) {
|
||||
List<NutMap> groups = groupReservationTimes(grouped.getOrDefault(row.getString("sqid"),Collections.emptyList()));
|
||||
// Record 会将新增键转成小写;转换后保留既有字段名及 Vue 所需的驼峰分组字段。
|
||||
Map<String,Object> item = new LinkedHashMap<>(row);
|
||||
item.put("reservationTimeGroups",groups);
|
||||
item.put("days",groups.size());
|
||||
output.add(item);
|
||||
}
|
||||
page.setList(output);
|
||||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
* slots为原始reserve_day/start_time/end_time记录,返回按日期排序的[{date,ranges:[{start,end}]}]。
|
||||
* 仅合并同日首尾相接的时段,跨日和有间隔的场次独立保留;不改变原始预约记录。
|
||||
*/
|
||||
public List<NutMap> groupReservationTimes(List<NutMap> slots) {
|
||||
List<NutMap> ordered=new ArrayList<>(slots);
|
||||
ordered.sort(Comparator.comparing((NutMap row)->row.getString("reserve_day"))
|
||||
.thenComparing(row->row.getString("start_time")).thenComparing(row->row.getString("end_time")));
|
||||
List<NutMap> groups=new ArrayList<>();
|
||||
String day=null;
|
||||
List<NutMap> ranges=null;
|
||||
for(NutMap slot:ordered) {
|
||||
String date=slot.getString("reserve_day"),start=slot.getString("start_time"),end=slot.getString("end_time");
|
||||
if(!Objects.equals(day,date)) {
|
||||
day=date;ranges=new ArrayList<>();
|
||||
groups.add(NutMap.NEW().setv("date",date).setv("ranges",ranges));
|
||||
}
|
||||
NutMap previous=ranges.isEmpty() ? null : ranges.get(ranges.size()-1);
|
||||
if(previous!=null && Objects.equals(previous.getString("end"),start))previous.setv("end",end);
|
||||
else ranges.add(NutMap.NEW().setv("start",start).setv("end",end));
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
/** 公共展示字段,保留既有组件所需别名,并显示预约类型和所属协会。 */
|
||||
private String baseSelect() {
|
||||
return "select b.*,u.loginname,u.sex,s.name site_name,c.name club_name,un.unionname union_name,st.stateName state_name,st.stateColor state_color,st.stateAuditType,"
|
||||
+ "(select count(*) from activity_site_reserve x where x.sqid=b.sqid) days,"
|
||||
+ "(select group_concat(concat(x.reserve_day,' ',x.start_time,'-',x.end_time) order by x.reserve_day,x.start_time separator ';') from activity_site_reserve x where x.sqid=b.sqid) concat_day "
|
||||
+ "from activity_site_reserve b left join sys_user u on u.id=b.reserve_person_id left join activity_site_info s on s.id=b.site_id left join sys_club c on c.id=b.clubId "
|
||||
+ "left join sys_union un on un.id=b.reserve_person_unionid "
|
||||
+ "left join " + STATES + " st on st.stateId=b.reserve_state";
|
||||
}
|
||||
|
||||
/** 详情仅允许申请人或对应组织审核人查看,已处理者可以继续查看自己的历史。 */
|
||||
public NutMap detail(String id) {
|
||||
ActivitySiteReserve b = fetch(id);
|
||||
if (b == null) throw new IllegalArgumentException("预约不存在");
|
||||
boolean allowed = uid().equals(b.getReserve_person_id());
|
||||
for (int node : new java.util.LinkedHashSet<>(Arrays.asList(firstState(b.getReserve_type()), SCHOOL))) {
|
||||
if (node == UNION && Strings.isBlank(b.getReserve_person_unionid()) || node == CLUB && Strings.isBlank(b.getClubId())) continue;
|
||||
allowed |= reviewers(node,b).stream().anyMatch(v -> uid().equals(v.getString("id")));
|
||||
}
|
||||
List<NutMap> history = b.getAuditList() == null ? new ArrayList<>() : b.getAuditList();
|
||||
allowed |= history.stream().anyMatch(v -> uid().equals(v.getString("auditUser")));
|
||||
if (!allowed) throw new IllegalArgumentException("无权查看该预约");
|
||||
NutMap result = listMap(Sqls.create(baseSelect() + " where b.id=@id").setParam("id",id)).get(0);
|
||||
result.put("auditListTable",history);
|
||||
// 日历仅携带本次申请的时段,避免通过详情获取其他申请人的资料。
|
||||
List<NutMap> slots=listMap(Sqls.create("select b.*,st.stateAuditType from activity_site_reserve b left join "
|
||||
+ STATES + " st on st.stateId=b.reserve_state where b.sqid=@sqid").setParam("sqid",b.getSqid()));
|
||||
result.put("site_info",slots);
|
||||
result.put("reservationTimeGroups",groupReservationTimes(slots));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核 ids 中的申请;pass 为明确的通过/拒绝,opinion 为必填意见。
|
||||
* 返回 void;事务内锁定申请并检查节点、角色及组织,重复/越权审核不产生历史或待办。
|
||||
*/
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void review(String stage, String[] ids, Boolean pass, String opinion) {
|
||||
int node = node(stage);
|
||||
if (ids == null || ids.length == 0 || pass == null) throw new IllegalArgumentException("请选择申请及审核结果");
|
||||
if (Strings.isBlank(opinion) || opinion.length() > 500) throw new IllegalArgumentException("请填写不超过500字的审核意见");
|
||||
Set<String> handled = new HashSet<>();
|
||||
List<ActivitySiteReserve> notifications = new ArrayList<>();
|
||||
// 固定加锁顺序降低批量审核之间的死锁风险。
|
||||
List<String> ordered = Arrays.stream(ids).sorted().collect(Collectors.toList());
|
||||
for (String id : ordered) {
|
||||
ActivitySiteReserve b = fetch(id);
|
||||
if (b == null) throw new IllegalArgumentException("预约不存在,请刷新列表");
|
||||
if (!handled.add(b.getSqid())) continue;
|
||||
listMap(Sqls.create("select id from activity_site_reserve where sqid=@sqid order by id for update").setParam("sqid",b.getSqid()));
|
||||
b = fetch(id);
|
||||
if (!Integer.valueOf(node).equals(b.getReserve_state())) throw new IllegalArgumentException("申请已处理或不属于当前审核节点,请刷新列表");
|
||||
if (reviewers(node,b).stream().noneMatch(v -> uid().equals(v.getString("id")))) throw new IllegalArgumentException("无权审核该分工会或协会的申请");
|
||||
int next = !pass ? REJECT : node == SCHOOL ? PASS : SCHOOL;
|
||||
if (next == SCHOOL) assignments(SCHOOL,b);
|
||||
Audit audit = new Audit();
|
||||
audit.setAuditor(uid()); audit.setUsername(String.valueOf(ShiroUtil.getPrincipalProperty("username"))); audit.setLoginname(String.valueOf(ShiroUtil.getPrincipalProperty("loginname")));
|
||||
audit.setAuditTime(new Date()); audit.setAuditPass(pass); audit.setAuditType(pass ? 1 : 2); audit.setAuditOpinion(opinion);
|
||||
dao().insert(audit);
|
||||
List<NutMap> history = b.getAuditList() == null ? new ArrayList<>() : b.getAuditList();
|
||||
history.add(NutMap.NEW().setv("stateCode",node).setv("auditId",audit.getId()).setv("auditUser",uid())
|
||||
.setv("auditState",pass).setv("auditOption",opinion).setv("auditUserName",audit.getUsername())
|
||||
.setv("auditStateName",title(node)).setv("auditListName",pass ? "审核通过" : "审核拒绝")
|
||||
.setv("auditTime",DateUtil.now()).setv("auditUserUnionName",""));
|
||||
for (ActivitySiteReserve row : query(Cnd.where("sqid","=",b.getSqid()))) {
|
||||
row.setReserve_state(next); row.setAuditList(history); dao().update(row);
|
||||
}
|
||||
sysLocalProcessService.completeTask(stage,process(b),uid(),opinion);
|
||||
if (next == SCHOOL) createTask(SCHOOL,b);
|
||||
else if (next == REJECT) sysLocalProcessService.refuseProcess(process(b),"审核拒绝,流程结束");
|
||||
else sysLocalProcessService.completeProcess(process(b));
|
||||
b.setReserve_state(next);
|
||||
notifications.add(b);
|
||||
}
|
||||
// 整批审核业务处理完成后再通知,避免后续申请校验失败时提前发送前几条消息。
|
||||
for (ActivitySiteReserve booking : notifications) notifyBooking(booking,booking.getReserve_state(),opinion);
|
||||
}
|
||||
|
||||
/** 返回不可撤回原因;仅最近一次有效审核的本人且仍拥有该组织审核资格可以撤回。 */
|
||||
private String revokeReason(int node, ActivitySiteReserve booking) {
|
||||
if (booking == null) return "预约不存在";
|
||||
List<NutMap> history = booking.getAuditList();
|
||||
if (history == null || history.isEmpty()) return "申请尚未审核或已撤回";
|
||||
NutMap last = history.get(history.size() - 1);
|
||||
if (last.getInt("stateCode") != node) return "下一级已审核,不能撤回当前节点";
|
||||
if (!uid().equals(last.getString("auditUser"))) return "只能撤回本人最近一次审核";
|
||||
int expected = last.getBoolean("auditState") ? node == SCHOOL ? PASS : SCHOOL : REJECT;
|
||||
if (!Integer.valueOf(expected).equals(booking.getReserve_state())) return "申请状态已变化,请刷新列表";
|
||||
if (reviewers(node,booking).stream().noneMatch(user -> uid().equals(user.getString("id"))))
|
||||
return "已无该组织的审核权限,不能撤回";
|
||||
for (ActivitySiteReserve slot : query(Cnd.where("sqid","=",booking.getSqid()))) {
|
||||
if (!LocalDateTime.of(LocalDate.parse(slot.getReserve_day()),LocalTime.parse(slot.getStart_time())).isAfter(LocalDateTime.now()))
|
||||
return "预约时段已经开始,不能撤回";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回审核:stage 为固定入口,id 为预约记录主键;恢复同一 sqid 全部时段及当前节点待办。
|
||||
* 仅撤回最近一次本人的有效审核,拒绝后恢复须重新校验占用;返回 void,由 Controller 包装结果。
|
||||
*/
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void revokeReview(String stage, String id) {
|
||||
int node = node(stage);
|
||||
if (Strings.isBlank(id)) throw new IllegalArgumentException("请选择需要撤回的申请");
|
||||
ActivitySiteReserve booking = fetch(id);
|
||||
if (booking == null) throw new IllegalArgumentException("预约不存在,请刷新列表");
|
||||
// 与提交保持先锁场地再锁申请的顺序,避免拒绝后恢复与新预约抢占同一时段。
|
||||
listMap(Sqls.create("select id from activity_site_info where id=@id for update").setParam("id",booking.getSite_id()));
|
||||
listMap(Sqls.create("select id from activity_site_reserve where sqid=@sqid order by id for update").setParam("sqid",booking.getSqid()));
|
||||
booking = fetch(id);
|
||||
String reason = revokeReason(node,booking);
|
||||
if (reason != null) throw new IllegalArgumentException(reason);
|
||||
ActivitySiteInfo site = dao().fetch(ActivitySiteInfo.class,booking.getSite_id());
|
||||
ActivityType type = site == null ? null : dao().fetch(ActivityType.class,site.getTypeId());
|
||||
if (site == null || !Boolean.TRUE.equals(site.getState()) || type == null || !Boolean.TRUE.equals(type.getEnabled()))
|
||||
throw new IllegalArgumentException("场地或场地类型已停用,不能恢复待审核预约");
|
||||
assignments(node,booking);
|
||||
List<ActivitySiteReserve> slots = query(Cnd.where("sqid","=",booking.getSqid()));
|
||||
// 恢复预约同样受最新开放配置与禁用时间约束,不复活已禁用的时段。
|
||||
for (ActivitySiteReserve slot : slots) siteInfoService.validateBookingSlot(site,slot.getReserve_day(),slot.getStart_time(),slot.getEnd_time());
|
||||
if (Integer.valueOf(REJECT).equals(booking.getReserve_state())) {
|
||||
for (ActivitySiteReserve slot : slots) {
|
||||
List<ActivitySiteReserve> occupied = query(Cnd.where("site_id","=",booking.getSite_id())
|
||||
.and("sqid","!=",booking.getSqid()).and("reserve_day","=",slot.getReserve_day())
|
||||
.and("start_time","<",slot.getEnd_time()).and("end_time",">",slot.getStart_time())
|
||||
.and("reserve_state","not in",Arrays.asList(REJECT,CANCEL)));
|
||||
boolean conflict = Integer.valueOf(1).equals(booking.getReserve_type())
|
||||
? occupied.stream().anyMatch(row -> !Integer.valueOf(1).equals(row.getReserve_type())
|
||||
|| row.getReserve_person_id().equals(slot.getReserve_person_id()))
|
||||
|| site.getLimitNum() == null || siteInfoService.occupiedCount(occupied,slot.getStart_time(),slot.getEnd_time()) >= site.getLimitNum()
|
||||
: !occupied.isEmpty();
|
||||
if (conflict) throw new IllegalArgumentException("原预约时段已被占用或人数已满,不能撤回拒绝结果");
|
||||
}
|
||||
}
|
||||
String processId = process(booking);
|
||||
Sys_local_process_instance instance = dao().fetch(Sys_local_process_instance.class,
|
||||
Cnd.where("processUniqueId","=",processId).and("processDeleteFlag","=",false).and("delFlag","=",false));
|
||||
Sys_local_process_instance_task task = dao().fetch(Sys_local_process_instance_task.class,
|
||||
Cnd.where("processUniqueId","=",processId).and("taskUniqueId","=",stage)
|
||||
.and("status","=",2).and("taskDeleteFlag","=",false).and("delFlag","=",false).desc("id"));
|
||||
if (instance == null || task == null || !uid().equals(task.getActualOwnerId()))
|
||||
throw new IllegalArgumentException("审核待办记录不完整,不能撤回,请联系管理员");
|
||||
List<NutMap> history = new ArrayList<>(booking.getAuditList());
|
||||
NutMap last = history.remove(history.size()-1);
|
||||
Audit audit = dao().fetch(Audit.class,last.getString("auditId"));
|
||||
if (audit == null) throw new IllegalArgumentException("审核记录不存在,不能撤回");
|
||||
// 原审核保留在 audit,扩展信息记录撤回人、时间及申请关联,不物理删除审核痕迹。
|
||||
cn.hutool.json.JSONObject ext = audit.getExt() == null ? new cn.hutool.json.JSONObject() : audit.getExt();
|
||||
ext.set("siteRevoke",NutMap.NEW().setv("sqid",booking.getSqid()).setv("node",node)
|
||||
.setv("userId",uid()).setv("time",DateUtil.now()));
|
||||
audit.setExt(ext);
|
||||
dao().update(audit,"ext");
|
||||
for (ActivitySiteReserve slot : slots) {
|
||||
slot.setReserve_state(node);
|
||||
slot.setAuditList(history);
|
||||
dao().update(slot);
|
||||
}
|
||||
// 失效本次已办及其后续未办记录,保留前级已办;新建本节点待办,兼容首节点拒绝和终审。
|
||||
dao().update(Sys_local_process_instance_task.class,Chain.make("taskDeleteFlag",true).add("delFlag",true),
|
||||
Cnd.where("processUniqueId","=",processId).and("id",">=",task.getId()).and("taskDeleteFlag","=",false));
|
||||
instance.setProcessInstanceStatus(1);
|
||||
instance.setNodeName(title(node));
|
||||
dao().update(instance);
|
||||
createTask(node,booking);
|
||||
}
|
||||
|
||||
/**
|
||||
* id 为申请中任一时段的记录主键,返回 void;仅系统管理员可删除全部已撤销的申请。
|
||||
* 按 sqid 锁定并删除所有时段,关联流程和待办沿用系统删除标记,保留流程痕迹。
|
||||
*/
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void deleteCancelled(String id) {
|
||||
if (!ShiroUtil.hasRole("sysadmin")) throw new IllegalArgumentException("仅系统管理员可删除已撤销预约");
|
||||
ActivitySiteReserve booking = fetch(id);
|
||||
if (booking == null) throw new IllegalArgumentException("预约不存在或已删除");
|
||||
if (Strings.isBlank(booking.getSqid())) throw new IllegalArgumentException("预约申请编号缺失,不能删除");
|
||||
List<NutMap> slots = listMap(Sqls.create("select id,reserve_state from activity_site_reserve where sqid=@sqid order by id for update")
|
||||
.setParam("sqid",booking.getSqid()));
|
||||
// 锁后再次检查整组状态,避免重复删除或误删同一申请中尚未撤销的时段。
|
||||
if (slots.isEmpty()) throw new IllegalArgumentException("预约不存在或已删除");
|
||||
if (slots.stream().anyMatch(slot -> slot.getInt("reserve_state") != CANCEL))
|
||||
throw new IllegalArgumentException("只能删除已撤销的预约");
|
||||
sysLocalProcessService.deleteProcessInstance(process(booking));
|
||||
dao().clear(ActivitySiteReserve.class,Cnd.where("sqid","=",booking.getSqid()));
|
||||
}
|
||||
|
||||
/** 仅申请人可撤销尚未处理的首节点申请;保留取消状态及流程记录。 */
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void cancel(String id) {
|
||||
ActivitySiteReserve b = fetch(id);
|
||||
if (b == null || !uid().equals(b.getReserve_person_id())) throw new IllegalArgumentException("只能撤销本人的申请");
|
||||
listMap(Sqls.create("select id from activity_site_reserve where sqid=@sqid order by id for update").setParam("sqid",b.getSqid()));
|
||||
b=fetch(id);
|
||||
if (b.getReserve_state() != firstState(b.getReserve_type()) || b.getAuditList()!=null && !b.getAuditList().isEmpty())
|
||||
throw new IllegalArgumentException("申请已处理,不能撤销");
|
||||
dao().update(ActivitySiteReserve.class,Chain.make("reserve_state",CANCEL),Cnd.where("sqid","=",b.getSqid()));
|
||||
sysLocalProcessService.refuseProcess(process(b),"申请人已撤销");
|
||||
}
|
||||
|
||||
/** 反馈只更新本人申请,option 为不超过200字的使用反馈。 */
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void feedback(String id,String option) {
|
||||
ActivitySiteReserve b=fetch(id);
|
||||
if(b==null || !uid().equals(b.getReserve_person_id())) throw new IllegalArgumentException("只能反馈本人的预约");
|
||||
if(option!=null && option.length()>200) throw new IllegalArgumentException("反馈不能超过200字");
|
||||
dao().update(ActivitySiteReserve.class,Chain.make("back_option",option),Cnd.where("sqid","=",b.getSqid()));
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,17 @@ import io.v.nutz.zhgh.activity.models.ActivitySiteInfo;
|
||||
import io.v.nutz.zhgh.activity.services.SiteInfoService;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import io.v.nutz.zhgh.activity.models.ActivityType;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import java.util.*;
|
||||
import java.time.*;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import io.v.nutz.zhgh.activity.models.ActivitySiteReserve;
|
||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||
|
||||
/**
|
||||
* @author: Aaron
|
||||
@@ -18,4 +29,151 @@ public class SiteInfoServiceImpl extends BaseServiceImpl<ActivitySiteInfo> imple
|
||||
public SiteInfoServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
|
||||
/**
|
||||
* form 包含名称、校区、地址、管理员、电话、人数、开放时段和开关;editing=true 时必须携带有效 id。
|
||||
* 返回 void;校区按 sys_dq 校验,typeId 不信任前端传值,避免隐藏类型选择后产生不可预约的场地。
|
||||
*/
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void saveManagedSite(ActivitySiteInfo form, boolean editing) {
|
||||
// 名称支持选择或自定义输入,拒绝空白及超过实体字段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
|
||||
|| dao().count("sys_dq", Cnd.where("dq_name", "=", form.getCampus())) == 0)
|
||||
throw new IllegalArgumentException("请选择有效校区");
|
||||
if (Strings.isBlank(form.getAddress()) || form.getAddress().length() > 100)
|
||||
throw new IllegalArgumentException("请填写不超过100字的场地地址");
|
||||
if (Strings.isBlank(form.getContact_person()) || form.getContact_person().length() > 32)
|
||||
throw new IllegalArgumentException("请填写不超过32字的场地管理员");
|
||||
if (Strings.isBlank(form.getContact_phone()) || form.getContact_phone().length() > 30)
|
||||
throw new IllegalArgumentException("请填写不超过30字的联系方式");
|
||||
if (form.getLimitNum() == null || form.getLimitNum() < 1 || form.getLimitNum() > 100)
|
||||
throw new IllegalArgumentException("限定人数应为1至100");
|
||||
if (form.getState() == null) throw new IllegalArgumentException("请选择开启状态");
|
||||
ActivitySiteInfo old = editing && Strings.isNotBlank(form.getId()) ? fetch(form.getId()) : null;
|
||||
if (editing && old == null) throw new IllegalArgumentException("场地不存在,请刷新列表");
|
||||
List<ActivityType> types = dao().query(ActivityType.class,
|
||||
Cnd.where("meetingTypeName", "=", "职工之家").and("enabled", "=", true));
|
||||
if (types.size() != 1) throw new IllegalArgumentException("请配置唯一且启用的职工之家场地类型");
|
||||
// 保存和提交使用同一场地行锁,避免配置变更与预约提交并发穿透。
|
||||
if (editing) {
|
||||
org.nutz.dao.sql.Sql lock = org.nutz.dao.Sqls.create("select id from activity_site_info where id=@id for update").setParam("id", form.getId());
|
||||
dao().execute(lock);
|
||||
}
|
||||
normalizeSchedule(form, editing);
|
||||
form.setTypeId(types.get(0).getId());
|
||||
// 控件已隐藏:新增不限制性别,编辑保留原值,避免未展示的字段被请求篡改。
|
||||
form.setSexLimit(editing && old.getSexLimit()!=null ? old.getSexLimit() : 0);
|
||||
if (editing) {
|
||||
form.setCreate_username(old.getCreate_username());
|
||||
form.setCreate_time(old.getCreate_time());
|
||||
updateIgnoreNull(form);
|
||||
} else {
|
||||
form.setId(null);
|
||||
insert(form);
|
||||
}
|
||||
}
|
||||
|
||||
/** 严格解析分钟,24:00仅用于结束边界,不接受跨日或秒级配置。 */
|
||||
private int minute(String value, boolean end) {
|
||||
if (end && "24:00".equals(value)) return 1440;
|
||||
if (value == null || !value.matches("[0-2][0-9]:[0-5][0-9]")) throw new IllegalArgumentException("请填写有效的HH:mm时间");
|
||||
try { LocalTime t = LocalTime.parse(value); return t.getHour()*60+t.getMinute(); }
|
||||
catch (RuntimeException e) { throw new IllegalArgumentException("时间必须在00:00至24:00之间"); }
|
||||
}
|
||||
|
||||
private String clockText(int value) {
|
||||
return String.format("%02d:%02d",value/60,value%60);
|
||||
}
|
||||
|
||||
/** 同一分钟先合并结束和开始事件,首尾相接的预约不会重复占用。 */
|
||||
public int occupiedCount(List<ActivitySiteReserve> rows,String start,String end) {
|
||||
SortedMap<Integer,Integer> events=new TreeMap<>();
|
||||
int from=minute(start,false),to=minute(end,true),current=0,peak=0;
|
||||
for(ActivitySiteReserve row:rows){
|
||||
int a=Math.max(from,minute(row.getStart_time(),false)),b=Math.min(to,minute(row.getEnd_time(),true));
|
||||
if(a<b){events.merge(a,1,Integer::sum);events.merge(b,-1,Integer::sum);}
|
||||
}
|
||||
for(int change:events.values()){current+=change;peak=Math.max(peak,current);}
|
||||
return peak;
|
||||
}
|
||||
|
||||
/** 两套原始配置独立保存,仅将当前模式展开为既有open_hours,兼容原有列表和日历。 */
|
||||
private void normalizeSchedule(ActivitySiteInfo form, boolean editing) {
|
||||
int mode = form.getReserveTimeType()==null ? (editing ? 1 : 2) : form.getReserveTimeType();
|
||||
if (mode!=1 && mode!=2) throw new IllegalArgumentException("请选择有效预约时间段类型");
|
||||
form.setReserveTimeType(mode);
|
||||
List<NutMap> ranges = mode==2 ? Collections.singletonList(form.getFullDayOpenHour()) : form.getSegmentedOpenHours();
|
||||
// 老场地第一次编辑没有独立配置时沿用原始场次,不擅自缩短预约时长。
|
||||
if (mode==1 && (ranges==null || ranges.isEmpty())) ranges=Json.fromJsonAsList(NutMap.class,Json.toJson(form.getOpen_hours()));
|
||||
if (ranges==null || ranges.isEmpty()) throw new IllegalArgumentException("请配置至少一个开放时间段");
|
||||
List<NutMap> normalized=new ArrayList<>(), slots=new ArrayList<>();
|
||||
for (NutMap row:ranges) {
|
||||
if(row==null) throw new IllegalArgumentException("请配置开放起止时间");
|
||||
int start=minute(row.getString("start_time"),false), end=minute(row.getString("end_time"),true);
|
||||
if(end<=start) throw new IllegalArgumentException("开放结束时间必须晚于开始时间");
|
||||
int unit=end-start;
|
||||
if(row.get("timeUnit")!=null) {
|
||||
try { unit=Integer.parseInt(String.valueOf(row.get("timeUnit"))); }
|
||||
catch(RuntimeException e){throw new IllegalArgumentException("预约时间单位必须为正整数分钟");}
|
||||
} else if(mode==2) throw new IllegalArgumentException("请配置全天候预约时间单位");
|
||||
if(unit<1 || unit>end-start || (end-start)%unit!=0) throw new IllegalArgumentException("预约时间单位必须为正整数,且能整除开放时长");
|
||||
for(NutMap previous:normalized) if(start<minute(previous.getString("end_time"),true) && end>minute(previous.getString("start_time"),false))
|
||||
throw new IllegalArgumentException("分段场次不能互相重叠");
|
||||
normalized.add(NutMap.NEW().setv("start_time",clockText(start)).setv("end_time",clockText(end)).setv("timeUnit",unit));
|
||||
for(int cursor=start;cursor<end;cursor+=unit) slots.add(NutMap.NEW().setv("start_time",clockText(cursor)).setv("end_time",clockText(cursor+unit)));
|
||||
}
|
||||
slots.sort(Comparator.comparing(row->row.getString("start_time")));
|
||||
if(mode==1)form.setSegmentedOpenHours(normalized);else form.setFullDayOpenHour(normalized.get(0));
|
||||
form.setOpen_hours(slots);
|
||||
List<NutMap> disabled=form.getNotApplyTimeList()==null ? new ArrayList<>() : form.getNotApplyTimeList();
|
||||
for(NutMap row:disabled){
|
||||
if(row==null)throw new IllegalArgumentException("请填写完整的禁用时间");
|
||||
try{LocalDate.parse(row.getString("date"));}catch(RuntimeException e){throw new IllegalArgumentException("请选择有效的禁用日期");}
|
||||
if(minute(row.getString("endTime"),true)<=minute(row.getString("startTime"),false))throw new IllegalArgumentException("禁用结束时间必须晚于开始时间");
|
||||
}
|
||||
form.setNotApplyTimeList(disabled);
|
||||
}
|
||||
|
||||
/** 半开区间判定:结束恰好等于禁用开始允许预约,有实际交叉才拦截。 */
|
||||
public void validateBookingSlot(ActivitySiteInfo site,String day,String start,String end) {
|
||||
LocalDate date;
|
||||
try{date=LocalDate.parse(day);}catch(RuntimeException e){throw new IllegalArgumentException("请选择有效预约日期");}
|
||||
int from=minute(start,false),to=minute(end,true);
|
||||
if(to<=from)throw new IllegalArgumentException("预约结束时间必须晚于开始时间");
|
||||
if(Boolean.TRUE.equals(site.getWorkday()) && date.getDayOfWeek().getValue()>=6)throw new IllegalArgumentException("该场地仅支持工作日预约");
|
||||
List<NutMap> open=Json.fromJsonAsList(NutMap.class,Json.toJson(site.getOpen_hours()));
|
||||
if(open==null || open.stream().noneMatch(row->start.equals(row.getString("start_time")) && end.equals(row.getString("end_time"))))
|
||||
throw new IllegalArgumentException("所选时段不在当前开放场次中,请刷新重选");
|
||||
if(site.getNotApplyTimeList()!=null)for(NutMap range:site.getNotApplyTimeList()){
|
||||
if(day.equals(range.getString("date")) && from<minute(range.getString("endTime"),true) && to>minute(range.getString("startTime"),false))
|
||||
throw new IllegalArgumentException("所选时段与禁用时间冲突");
|
||||
}
|
||||
}
|
||||
|
||||
/** PC/H5共享可约判定,按重叠时段查占用,兼容历史整段预约与新拆分时段。 */
|
||||
public List<NutMap> availableSlots(String siteId,String day,Integer reserveType){
|
||||
ActivitySiteInfo site=fetch(siteId);
|
||||
if(site==null || !Boolean.TRUE.equals(site.getState()))throw new IllegalArgumentException("场地不存在或已停用");
|
||||
LocalDate date=LocalDate.parse(day);
|
||||
String uid=String.valueOf(ShiroUtil.getPrincipalProperty("id"));
|
||||
List<NutMap> slots=Json.fromJsonAsList(NutMap.class,Json.toJson(site.getOpen_hours()));
|
||||
if(slots==null)return new ArrayList<>();
|
||||
for(NutMap slot:slots){
|
||||
String start=slot.getString("start_time"),end=slot.getString("end_time"),reason=null;
|
||||
List<ActivitySiteReserve> occupied=dao().query(ActivitySiteReserve.class,Cnd.where("site_id","=",siteId).and("reserve_day","=",day)
|
||||
.and("start_time","<",end).and("end_time",">",start).and("reserve_state","not in",Arrays.asList(4040,4050)));
|
||||
try{validateBookingSlot(site,day,start,end);}catch(IllegalArgumentException e){reason=e.getMessage();}
|
||||
if(reason==null && !LocalDateTime.of(date,LocalTime.parse(start)).isAfter(LocalDateTime.now()))reason="预约时段已过期";
|
||||
if(reason==null && occupied.stream().anyMatch(row->uid.equals(row.getReserve_person_id())))reason="已预约";
|
||||
if(reason==null && (occupied.stream().anyMatch(row->!Integer.valueOf(1).equals(row.getReserve_type()))
|
||||
|| site.getLimitNum()==null || occupiedCount(occupied,start,end)>=site.getLimitNum()
|
||||
|| reserveType!=null && reserveType!=1 && !occupied.isEmpty()))reason="已约满";
|
||||
slot.setv("code",reason==null ? 1 : -2).setv("msg",reason==null ? "可预约" : reason)
|
||||
.setv("limitNum",site.getLimitNum()).setv("reserveNum",occupiedCount(occupied,start,end)).setv("disabled",reason!=null)
|
||||
.setv("backColor",reason==null ? "#e8ffef" : "").setv("color",reason==null ? "#52986a" : "");
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ public interface SourceData {
|
||||
put("SFZJH", new String[]{"idcard"}); // 身份证件号
|
||||
put("ZZMMM", new String[]{"political"}); // 政治面貌码
|
||||
put("DQZTM", new String[]{"userState", "personalStatus"}); // 在职状态码
|
||||
put("ZZZTM", new String[]{"zzztm"}); // 保留源代码,供人员更新阶段筛选
|
||||
put("ZZZTMC", new String[]{"zzztmc"}); // 保留源名称,不做字典转换
|
||||
put("RYFLMC", new String[]{"personType"}); // 人员类型码
|
||||
put("ZGXLM", new String[]{"education"}); // 最高学历码
|
||||
put("ZGXWM", new String[]{"academicDegree"}); // 最高学位码
|
||||
@@ -104,6 +106,7 @@ public interface SourceData {
|
||||
// checkSuccess(map);
|
||||
List<NutMap> data = map.getAsList("data", NutMap.class);
|
||||
for (NutMap row : data) {
|
||||
// 源人员全量保存,包括非 100 和状态缺失的记录;筛选统一放在人员更新阶段。
|
||||
Map entity = new HashMap(500);
|
||||
|
||||
row.forEach((k, v) -> {
|
||||
@@ -146,33 +149,51 @@ public interface SourceData {
|
||||
}};
|
||||
|
||||
/**
|
||||
* 获取单位
|
||||
* 分页获取全部源单位,无入参;响应异常或空页时抛出异常,阻止不完整同步。
|
||||
*
|
||||
* @return units
|
||||
* @return 单位列表,DWH 映射为 id/unitcode,DWMC 为名称,SJDWH 为父级编号
|
||||
*/
|
||||
static List<Sys_unit> units() {
|
||||
List<Sys_unit> units = new ArrayList<>();
|
||||
int page = 1;
|
||||
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();
|
||||
NutMap map = Json.fromJson(NutMap.class, body);
|
||||
// checkSuccess(map);
|
||||
List<NutMap> data = map.getAsList("data", NutMap.class);
|
||||
while (true) {
|
||||
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();
|
||||
NutMap map = Json.fromJson(NutMap.class, body);
|
||||
// 缺少数据或明确返回失败时不能当作同步成功,避免用不完整单位继续更新人员。
|
||||
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);
|
||||
int totalCount = map.getInt("totalCount");
|
||||
if (data == null || data.isEmpty() || totalCount <= 0) {
|
||||
throw new IllegalStateException("单位同步失败:数据中心返回空页或总数异常,第 " + page + " 页。");
|
||||
}
|
||||
|
||||
for (NutMap row : data) {
|
||||
Map entity = new HashMap(5);
|
||||
row.forEach((k, v) -> {
|
||||
if (UNIT_FIELD_RELATION.containsKey(k)) {
|
||||
Object value = v;
|
||||
/* if (UNIT_FIELD_PLUGIN.containsKey(k)) {
|
||||
value = UNIT_FIELD_PLUGIN.get(k).run(v);
|
||||
}*/
|
||||
for (String key : UNIT_FIELD_RELATION.get(k)) {
|
||||
entity.put(key, value);
|
||||
}
|
||||
for (NutMap row : data) {
|
||||
if (row == null) {
|
||||
throw new IllegalStateException("单位同步失败:源单位记录为空。");
|
||||
}
|
||||
});
|
||||
units.add(BeanUtil.mapToBean(entity, Sys_unit.class, true));
|
||||
Map entity = new HashMap(5);
|
||||
row.forEach((k, v) -> {
|
||||
if (UNIT_FIELD_RELATION.containsKey(k)) {
|
||||
Object value = v;
|
||||
/* if (UNIT_FIELD_PLUGIN.containsKey(k)) {
|
||||
value = UNIT_FIELD_PLUGIN.get(k).run(v);
|
||||
}*/
|
||||
for (String key : UNIT_FIELD_RELATION.get(k)) {
|
||||
entity.put(key, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
units.add(BeanUtil.mapToBean(entity, Sys_unit.class, true));
|
||||
}
|
||||
// 根据源接口总数逐页拉取,避免原先只读取前 100 个单位。
|
||||
if (units.size() >= totalCount) {
|
||||
break;
|
||||
}
|
||||
page++;
|
||||
}
|
||||
return units;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package io.v.nutz.zhgh.data.controller;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.wizzer.framework.base.Result;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.base.dao.CndPlus;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
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.services.SysDqService;
|
||||
import io.v.nutz.sys.services.SysGxService;
|
||||
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.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
@@ -25,8 +25,6 @@ import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@At("/platform/data/unit")
|
||||
@Ok("json:full")
|
||||
@@ -35,8 +33,8 @@ public class UpdateUnitController {
|
||||
|
||||
private static final Log log = Logs.get();
|
||||
|
||||
@Inject("Sys_unit")
|
||||
private ViService<Sys_unit> sysUnitService;
|
||||
@Inject
|
||||
private SysUnitService sysUnitService;
|
||||
|
||||
@Inject
|
||||
private SysUnitClassService sysUnitClassService;
|
||||
@@ -61,45 +59,16 @@ public class UpdateUnitController {
|
||||
/**
|
||||
* 单位数据更新
|
||||
*
|
||||
* @return {@link Object}
|
||||
* 无入参,调用 service 同步单位;返回 null,由 ViReturn 转为 code/msg 响应。
|
||||
* 同步异常由 ViReturn 返回失败提示。
|
||||
* @return 同步成功返回 null
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("sys.data.unit")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object dataUpdate() {
|
||||
List<Sys_unit> sys_units = sysUnitService.query();
|
||||
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);
|
||||
}
|
||||
});
|
||||
sysUnitService.syncSourceUnits();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import cn.hutool.http.HtmlUtil;
|
||||
import io.v.nutz.base.utils.ManyAddOrRenewUtil;
|
||||
import io.v.nutz.base.utils.Roles;
|
||||
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.SysUserRoleService;
|
||||
import io.v.nutz.zhgh.activity.models.ActivityUserScope;
|
||||
@@ -83,6 +85,8 @@ public class SourceUserServiceImpl extends ViServiceImpl<UserSource> implements
|
||||
@Inject
|
||||
private SysUserService sysUserService;
|
||||
@Inject
|
||||
private SysUnitService sysUnitService;
|
||||
@Inject
|
||||
private HistoryUserService historyUserService;
|
||||
@Inject
|
||||
private UserPartUpService userPatUpService;
|
||||
@@ -109,25 +113,39 @@ public class SourceUserServiceImpl extends ViServiceImpl<UserSource> implements
|
||||
dao().insert(users);
|
||||
}
|
||||
|
||||
/**
|
||||
* 先同步单位并校验人员引用,再按现有配置生成记录和更新人员。
|
||||
* @param pullTime 已拉取人员数据的批次时间
|
||||
* @param sourceType all 全部更新、add 新增人员、part 按组别及过滤字段更新,三种方式均只处理 zzztm=100
|
||||
* @param columnNames 部分更新时忽略的字段名,可为空
|
||||
* @param isUpSet 保留现有接口参数,当前方法不使用
|
||||
* @param partGroupId 更新组别 ID,为空时使用整个批次
|
||||
* @param isInvert 是否选择组别之外的人员
|
||||
* @return 无返回值;同步或校验失败抛出异常,由接口返回 code/msg 提示
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void updateSysUser(String pullTime, String sourceType, String[] columnNames, String isUpSet, String partGroupId, boolean isInvert) {
|
||||
try {
|
||||
// 单位与后续校验使用当前事务,单位同步失败时不进入人员更新。
|
||||
sysUnitService.syncSourceUnits();
|
||||
List<UserSource> sources = new ArrayList<>();
|
||||
Map<String, String> userPartMap = new HashMap<>();
|
||||
// 三种更新方式及定时更新共用筛选;先于分组、单位校验和会员变更,其他源记录仍保留。
|
||||
Cnd sourceCnd = Cnd.where("pullTime", "=", pullTime).and("zzztm", "=", "100");
|
||||
if (Strings.isNotBlank(partGroupId)) {
|
||||
//如果为true,更新组别之外人员
|
||||
Sql sql = Sqls.create("select u.loginname from user_part up left join `user` u on u.id=up.userId where up.groupId = @groupId").setParam("groupId", partGroupId);
|
||||
List<String> loginNameList = userPatUpService.listMap(sql).stream().map(o -> o.getString("loginname")).collect(Collectors.toList());
|
||||
if (isInvert) {
|
||||
sources = query(Cnd.where("pullTime", "=", pullTime).and("loginname", "not in", loginNameList).groupBy("loginname"));
|
||||
sources = query(sourceCnd.and("loginname", "not in", loginNameList).groupBy("loginname"));
|
||||
userPartMap = sources.stream().collect(Collectors.toMap(Sys_user::getLoginname, Sys_user::getLoginname));
|
||||
} else {
|
||||
userPartMap = userPatUpService.query(Cnd.where("groupId", "=", partGroupId)).stream().collect(Collectors.toMap(UserPartUp::getLoginname, UserPartUp::getLoginname));
|
||||
sources = query(Cnd.where("pullTime", "=", pullTime).and("loginname", "in", loginNameList).groupBy("loginname"));
|
||||
sources = query(sourceCnd.and("loginname", "in", loginNameList).groupBy("loginname"));
|
||||
}
|
||||
} else {
|
||||
sources = query(Cnd.where("pullTime", "=", pullTime).groupBy("loginname"));
|
||||
sources = query(sourceCnd.groupBy("loginname"));
|
||||
}
|
||||
|
||||
// 查询所有用户,判断是否需要更新
|
||||
@@ -172,104 +190,89 @@ public class SourceUserServiceImpl extends ViServiceImpl<UserSource> implements
|
||||
Set<String> userIds = list.stream().map(SpecialStaff::getUserId).collect(Collectors.toSet());
|
||||
|
||||
|
||||
// 创建一个固定大小的线程池 可以根据服务器性能调整线程池大小
|
||||
int numberOfThreads = Runtime.getRuntime().availableProcessors() * 2;
|
||||
List<CompletableFuture<Void>> futures = new CopyOnWriteArrayList<>();
|
||||
// 每批处理的数据量,可以根据实际情况调整
|
||||
int batchSize = 200;
|
||||
// 写入人员、角色、历史之前统一校验;记录计算留在当前线程,读取本事务新同步的单位。
|
||||
validateSourceUnits(sources, userMap, userIds, allowChangeFieldNames);
|
||||
for (UserSource source : sources) {
|
||||
//不更新会员和福利会员字段
|
||||
source.setMember(null);
|
||||
source.setWelfareMember(null);
|
||||
Sys_user user = userMap.get(source.getLoginname());
|
||||
|
||||
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;
|
||||
if (user != null && userIds.contains(user.getId())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
||||
for (UserSource source : batch) {
|
||||
//不更新会员和福利会员字段
|
||||
source.setMember(null);
|
||||
source.setWelfareMember(null);
|
||||
Sys_user user = userMap.get(source.getLoginname());
|
||||
if (StrUtil.isBlank(source.getPersonType())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (user != null && userIds.contains(user.getId())) {
|
||||
continue;
|
||||
}
|
||||
// 这里是杭医的不进系统人员的判断,其他学校用不到的话删掉
|
||||
boolean isNotEnterUser = NOT_ENTERING_PERSON_TYPE.contains(source.getPersonType())
|
||||
|| NOT_ENTERING_USER_LOGIN_NAME.contains(source.getLoginname())
|
||||
|| NOT_ENTERING_USER_STATE.contains(source.getUserState());
|
||||
|
||||
if (StrUtil.isBlank(source.getPersonType())) {
|
||||
continue;
|
||||
}
|
||||
if (isAuto) {
|
||||
checkMemberOrWelfareMember(changeConfig, source, user, addMemberUserIds, deleteMemberUserIds,
|
||||
addWelfareMemberUserIds, deleteWelfareMemberUserIds);
|
||||
} else {
|
||||
if (user != null) {
|
||||
source.setMember(user.getMember());
|
||||
source.setWelfareMember(user.getWelfareMember());
|
||||
}
|
||||
}
|
||||
|
||||
// 这里是杭医的不进系统人员的判断,其他学校用不到的话删掉
|
||||
boolean isNotEnterUser = NOT_ENTERING_PERSON_TYPE.contains(source.getPersonType())
|
||||
|| NOT_ENTERING_USER_LOGIN_NAME.contains(source.getLoginname())
|
||||
|| NOT_ENTERING_USER_STATE.contains(source.getUserState());
|
||||
|
||||
if (isAuto) {
|
||||
checkMemberOrWelfareMember(changeConfig, source, user, addMemberUserIds, deleteMemberUserIds,
|
||||
addWelfareMemberUserIds, deleteWelfareMemberUserIds);
|
||||
} else {
|
||||
if (user != null) {
|
||||
source.setMember(user.getMember());
|
||||
source.setWelfareMember(user.getWelfareMember());
|
||||
}
|
||||
}
|
||||
|
||||
Sys_user u = new Sys_user();
|
||||
BeanUtils.copyProperties(source, u);
|
||||
switch (sourceType) {
|
||||
case "all" -> {
|
||||
// 复制属性到一个新的user对象
|
||||
u.setId(user == null ? source.getId() : user.getId());
|
||||
if (user != null) {
|
||||
needDoUpdateList.add(u);
|
||||
}
|
||||
}
|
||||
case "add" -> {
|
||||
u.setId(source.getId());
|
||||
}
|
||||
case "part" -> {
|
||||
u.setId(user == null ? source.getId() : user.getId());
|
||||
if (columnNames != null && columnNames.length > 0) {
|
||||
String lockedColumn = String.join("|", columnNames);
|
||||
FieldFilter fieldFilter = FieldFilter.create(Sys_user.class, null, "^" + lockedColumn + "$", true);
|
||||
filterColumnDao.set(Daos.ext(dao(), fieldFilter));
|
||||
}
|
||||
if (Strings.isNotBlank(finalUserPartMap.get(u.getLoginname())) && user != null) {
|
||||
needDoUpdateList.add(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isAuto) {
|
||||
UserHistory history = createHistory(source, user, dictMap, allowChangeFieldNames);
|
||||
if (Lang.isEmpty(history)) {
|
||||
continue;
|
||||
}
|
||||
if (user != null) {
|
||||
histories.add(history);
|
||||
}
|
||||
if (!isNotEnterUser && Lang.isNotEmpty(history.getChangeTypes()) && history.getChangeTypes().contains(MemberChangeType.NEW.getType())) {
|
||||
needInitUserList.add(u);
|
||||
histories.add(history);
|
||||
}
|
||||
} else {
|
||||
SourceChangeMiddleTable table = createSourceChangeMiddleTable(source, user, dictMap, allowChangeFieldNames);
|
||||
if (Lang.isEmpty(table)) {
|
||||
continue;
|
||||
}
|
||||
if (user != null) {
|
||||
middleTables.add(table);
|
||||
}
|
||||
if (!isNotEnterUser && Lang.isNotEmpty(table.getChangeTypes()) && table.getChangeTypes().contains(MemberChangeType.NEW.getType())) {
|
||||
needInitUserList.add(u);
|
||||
middleTables.add(table);
|
||||
}
|
||||
Sys_user u = new Sys_user();
|
||||
BeanUtils.copyProperties(source, u);
|
||||
switch (sourceType) {
|
||||
case "all" -> {
|
||||
// 复制属性到一个新的user对象
|
||||
u.setId(user == null ? source.getId() : user.getId());
|
||||
if (user != null) {
|
||||
needDoUpdateList.add(u);
|
||||
}
|
||||
}
|
||||
}, Executors.newFixedThreadPool(numberOfThreads));
|
||||
futures.add(future);
|
||||
}
|
||||
case "add" -> {
|
||||
u.setId(source.getId());
|
||||
}
|
||||
case "part" -> {
|
||||
u.setId(user == null ? source.getId() : user.getId());
|
||||
if (columnNames != null && columnNames.length > 0) {
|
||||
String lockedColumn = String.join("|", columnNames);
|
||||
FieldFilter fieldFilter = FieldFilter.create(Sys_user.class, null, "^" + lockedColumn + "$", true);
|
||||
filterColumnDao.set(Daos.ext(dao(), fieldFilter));
|
||||
}
|
||||
if (Strings.isNotBlank(userPartMap.get(u.getLoginname())) && user != null) {
|
||||
needDoUpdateList.add(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||
if (isAuto) {
|
||||
UserHistory history = createHistory(source, user, dictMap, allowChangeFieldNames);
|
||||
if (Lang.isEmpty(history)) {
|
||||
continue;
|
||||
}
|
||||
if (user != null) {
|
||||
histories.add(history);
|
||||
}
|
||||
if (!isNotEnterUser && Lang.isNotEmpty(history.getChangeTypes()) && history.getChangeTypes().contains(MemberChangeType.NEW.getType())) {
|
||||
needInitUserList.add(u);
|
||||
histories.add(history);
|
||||
}
|
||||
} else {
|
||||
SourceChangeMiddleTable table = createSourceChangeMiddleTable(source, user, dictMap, allowChangeFieldNames);
|
||||
if (Lang.isEmpty(table)) {
|
||||
continue;
|
||||
}
|
||||
if (user != null) {
|
||||
middleTables.add(table);
|
||||
}
|
||||
if (!isNotEnterUser && Lang.isNotEmpty(table.getChangeTypes()) && table.getChangeTypes().contains(MemberChangeType.NEW.getType())) {
|
||||
needInitUserList.add(u);
|
||||
middleTables.add(table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//如果有新用户,增加到用户表同时增加角色
|
||||
if (Lang.isNotEmpty(needInitUserList)) {
|
||||
@@ -322,11 +325,69 @@ public class SourceUserServiceImpl extends ViServiceImpl<UserSource> implements
|
||||
|
||||
} catch (Exception e) {
|
||||
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 人员更新的配置
|
||||
@@ -553,8 +614,9 @@ public class SourceUserServiceImpl extends ViServiceImpl<UserSource> implements
|
||||
if (Lang.isEmpty(changeList)) {
|
||||
return null;
|
||||
}
|
||||
// 单位缺失由前置校验阻断,其他字段的空值按原业务展示为无数据。
|
||||
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(";"));
|
||||
|
||||
// 比较变更数据
|
||||
@@ -609,8 +671,9 @@ public class SourceUserServiceImpl extends ViServiceImpl<UserSource> implements
|
||||
if (Lang.isEmpty(changeList)) {
|
||||
return null;
|
||||
}
|
||||
// 单位缺失由前置校验阻断,其他字段的空值按原业务展示为无数据。
|
||||
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(";"));
|
||||
// 比较变更数据
|
||||
commonCompareChange(source, user, changeTypes);
|
||||
|
||||
@@ -134,7 +134,8 @@ public class GhkhLssjcxController {
|
||||
if (ShiroUtil.hasAnyRoles("sysadmin,SchoolUnionAdmin")) {
|
||||
result.add(new NutMap().setv("title", title).setv("url", "/platform/ghkh/xghsh").setv("iconClass", vi.getIconByPath("/platform/ghkh/xghsh")).setv("label", "校工会审核").setv("number", getXghCount()));
|
||||
}
|
||||
return result;
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return io.v.nutz.sys.services.TodoAccessService.filter(result, "url");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@ public class dbYxAgentController {
|
||||
if (ShiroUtil.hasAnyRoles("sysadmin,SchoolUnionAdmin")) {
|
||||
result.add(new NutMap().setv("title", title).setv("url", UNION).setv("iconClass", vi.getIconByPath(UNION)).setv("label", "待校工会审核").setv("number", getUnionCount()));
|
||||
}
|
||||
return result;
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return io.v.nutz.sys.services.TodoAccessService.filter(result, "url");
|
||||
}
|
||||
|
||||
|
||||
|
||||
+2
-1
@@ -80,7 +80,8 @@ public class ActivitybxAgentController {
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return io.v.nutz.sys.services.TodoAccessService.filter(result, "url");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,8 @@ public class LxyAgentController {
|
||||
result.add(new NutMap().setv("title", title).setv("url", FGHSH).setv("iconClass", vi.getIconByPath(FGHSH)).setv("label", "待院级工会审核").setv("number", getFghshCount()));
|
||||
result.add(new NutMap().setv("title", title).setv("url", XGHSH).setv("iconClass", vi.getIconByPath(XGHSH)).setv("label", "待校工会审核").setv("number", getXghshCount().size()));
|
||||
}
|
||||
return result;
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return io.v.nutz.sys.services.TodoAccessService.filter(result, "url");
|
||||
}
|
||||
|
||||
private List<Record> getXghshCount() {
|
||||
|
||||
@@ -171,8 +171,8 @@ public class MainPageNeedItemsController {
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresAuthentication
|
||||
public Object getNeedItems() {
|
||||
return needItemsService.getNeedItems();
|
||||
public Object getNeedItems(Boolean mobile) {
|
||||
return needItemsService.getNeedItems(Boolean.TRUE.equals(mobile));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,4 +8,6 @@ import java.util.List;
|
||||
public interface MainPageNeedItemsService extends ViService<NeedItems> {
|
||||
|
||||
List<NeedItems> getNeedItems();
|
||||
/** mobile 指明跳转端,返回当前权限可访问且数量大于零的待办。 */
|
||||
List<NeedItems> getNeedItems(boolean mobile);
|
||||
}
|
||||
|
||||
+7
-2
@@ -27,7 +27,10 @@ public class MainPageNeedItemsServiceImpl extends ViServiceImpl<NeedItems> imple
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NeedItems> getNeedItems() {
|
||||
public List<NeedItems> getNeedItems() { return getNeedItems(false); }
|
||||
|
||||
@Override
|
||||
public List<NeedItems> getNeedItems(boolean mobile) {
|
||||
//查询数据库中的待办事项
|
||||
List<NeedItems> needItemsList = dao().queryByJoin(NeedItems.class, "^needItemsSource$", Cnd.NEW());
|
||||
|
||||
@@ -91,6 +94,8 @@ public class MainPageNeedItemsServiceImpl extends ViServiceImpl<NeedItems> imple
|
||||
|
||||
List<NeedItems> result = needItemsList.stream().filter(v -> Strings.isNotBlank(v.getHref())
|
||||
&& menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && x.getHref().equals(v.getHref()))).distinct().collect(Collectors.toList());
|
||||
return result.stream().filter(v -> v.getCount() > 0).collect(Collectors.toList());
|
||||
return result.stream().filter(v -> v.getCount() > 0)
|
||||
.filter(v -> io.v.nutz.sys.services.TodoAccessService.canAccess(mobile ? v.getMobileHref() : v.getHref()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,8 @@ public class ManuscriptAgentController {
|
||||
|
||||
Sys_user user = (Sys_user) ShiroUtil.getPrincipal();
|
||||
List<Sys_menu> menus = Optional.ofNullable(user).orElse(new Sys_user()).getMenus();
|
||||
return result.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").contains(x.getHref()))).distinct().collect(Collectors.toList());
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return result.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").contains(x.getHref()))).filter(v -> io.v.nutz.sys.services.TodoAccessService.canAccess(v.getString("url"))).distinct().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,310 +1,10 @@
|
||||
package io.v.nutz.zhgh.mobile.activity.site;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.wizzer.framework.page.Pagination;
|
||||
import io.v.nutz.zhgh.activity.models.ActivitySiteInfo;
|
||||
import io.v.nutz.zhgh.activity.models.ActivitySiteReserve;
|
||||
import io.v.nutz.zhgh.activity.services.ActivityCommonService;
|
||||
import io.v.nutz.zhgh.activity.services.SiteInfoService;
|
||||
import io.v.nutz.zhgh.activity.services.SiteReserveService;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.base.utils.MsgApi;
|
||||
|
||||
import io.v.nutz.base.utils.Vi;
|
||||
import io.v.nutz.base.enums.AuditTypeEnum;
|
||||
import io.v.nutz.base.dao.CndPlus;
|
||||
import io.v.nutz.base.model.Audit;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.sys.models.Sys_user;
|
||||
import io.v.nutz.sys.services.SysUserService;
|
||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.entity.Record;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.dao.util.cri.Static;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author JyuHsin
|
||||
* @Date 2022/5/13
|
||||
* @Description
|
||||
*/
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/mobile/activity/site/audit")
|
||||
import org.nutz.mvc.annotation.*;
|
||||
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
||||
/** 旧审核入口仅展示三个新入口,不再提供旧审核提交接口。 */
|
||||
@IocBean @At("/mobile/activity/site/audit") @RequiresAuthentication
|
||||
public class SiteAuditMobileController {
|
||||
|
||||
@Inject
|
||||
private SiteInfoService siteInfoService;
|
||||
|
||||
@Inject
|
||||
private SiteReserveService siteReserveService;
|
||||
|
||||
@Inject
|
||||
private SysUserService sysUserService;
|
||||
|
||||
@Inject
|
||||
private ActivityCommonService activityCommonService;
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
@Inject
|
||||
private MsgApi msgApi;
|
||||
|
||||
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:mobile/activity/site/audit.html")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/pageData")
|
||||
@Ok("json:full")
|
||||
@ViReturn
|
||||
public Object pageData(PageForm pageForm, @Param(value = "typeId", required = false) String typeId) {
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
info.*,
|
||||
type.meetingTypeName,
|
||||
type.moduleName,
|
||||
(select unitname from `user` where username = info.contact_person) as unitname,
|
||||
(select count(DISTINCT sqid) from activity_site_reserve where site_id=info.id and JSON_CONTAINS(auditList, JSON_OBJECT('auditUser', @userid))) as audit
|
||||
FROM
|
||||
activity_site_info info
|
||||
LEFT JOIN activity_type type ON type.id = info.typeId
|
||||
LEFT JOIN audit_state state ON state.module = type.moduleName
|
||||
$condition
|
||||
""").setParam("userid", ShiroUtil.getPrincipalProperty("id"));
|
||||
CndPlus cnd = CndPlus.create();
|
||||
|
||||
cnd.andEX("state.stateId", "in", activityCommonService.findStateIdForMeCanAudit());
|
||||
cnd.and("info.state", "=", true);
|
||||
if (StringUtils.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.orLike("info.name", pageForm.getSearchKeyword());
|
||||
seg.orLike("info.address", pageForm.getSearchKeyword());
|
||||
cnd.and(seg);
|
||||
}
|
||||
if (StrUtil.isNotBlank(typeId)) {
|
||||
cnd.andEX("info.typeId", "=", typeId);
|
||||
}
|
||||
cnd.groupBy("info.id");
|
||||
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = siteInfoService.listPage(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
|
||||
List<Object> list = pagination.getList();
|
||||
list.forEach(item -> {
|
||||
Record map = (Record) item;
|
||||
List<NutMap> canAuditUserByLoginUser = this.getCanAuditUserByLoginUser(map.getString("id"), map.getString("moduleName"));
|
||||
map.put("no_audit", canAuditUserByLoginUser.size());
|
||||
});
|
||||
return pagination;
|
||||
}
|
||||
|
||||
@At("/getLeaveUser")
|
||||
@Ok("json:full")
|
||||
@ViReturn
|
||||
public Object getLeaveUser(@Param(value = "siteId", required = false) String siteId,
|
||||
@Param(value = "moduleName", required = false) String moduleName,
|
||||
@Param(value = "auditType", required = false)String auditType) {
|
||||
|
||||
List<NutMap> list = "canAudit".equals(auditType) ? this.getCanAuditUserByLoginUser(siteId, moduleName) : this.getHasAuditUserByLoginUser(siteId);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/*根据当前登录用户获取可以审核的用户*/
|
||||
public List<NutMap> getCanAuditUserByLoginUser(String siteId, String moduleName) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
ar.*,
|
||||
u.username,
|
||||
u.loginname,
|
||||
u.sex,
|
||||
u.unitname,
|
||||
state.stateName,
|
||||
count(sqid) days,
|
||||
GROUP_CONCAT(reserve_day order by reserve_day asc) concat_day
|
||||
FROM
|
||||
activity_site_reserve ar
|
||||
LEFT JOIN activity_site_info asi ON asi.id = ar.site_id
|
||||
left join `user` u on ar.reserve_person_id=u.id
|
||||
LEFT JOIN audit_state state on state.stateId = ar.reserve_state
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX("ar.site_id", "=", siteId);
|
||||
cnd.and("u.username", "is not", null);
|
||||
cnd.and("u.username", "!=", "");
|
||||
cnd.and("ar.reserve_state", "in", Sqls.createf("SELECT stateId FROM audit_state_user WHERE userid = '%s'", ShiroUtil.getPrincipalProperty("id")));
|
||||
cnd.and("state.stateAuditType", "in", Lang.list(AuditTypeEnum.AUDIT.getValue()));
|
||||
|
||||
/*if(!ShiroUtil.hasAnyRoles(new String[]{"xghng","A06","sysadmin"})) {
|
||||
if (ShiroUtil.hasAnyRoles(new String[]{"gh10"})) {
|
||||
Sys_user user = (Sys_user) ShiroUtil.getPrincipal();
|
||||
cnd.and("u.unitid", "=", user.getUnitid());
|
||||
}
|
||||
}*/
|
||||
|
||||
if (!io.v.nutz.web.commons.utils.ShiroUtil.hasAnyRoles(new String[]{"sysadmin"})) {
|
||||
cnd.and(new Static("if(asi.typeId=1, u.unitid = '" + Vi.getUnit().getId() + "', 1=1)"));
|
||||
}
|
||||
|
||||
cnd.groupBy("sqid");
|
||||
|
||||
sql.setCondition(cnd);
|
||||
|
||||
return listMap(sql);
|
||||
}
|
||||
|
||||
/*根据当前登录用户获取已经审核的用户*/
|
||||
public List<NutMap> getHasAuditUserByLoginUser(String siteId) {
|
||||
Sql sql = Sqls.create("""
|
||||
select
|
||||
ar.*,
|
||||
u.username,
|
||||
u.loginname,
|
||||
u.unitname,
|
||||
u.sex,
|
||||
state.stateName,
|
||||
count(sqid) days,
|
||||
GROUP_CONCAT(reserve_day order by reserve_day asc) concat_day
|
||||
from
|
||||
activity_site_reserve ar
|
||||
left join
|
||||
`user` u on ar.reserve_person_id=u.id
|
||||
LEFT JOIN
|
||||
audit_state state on state.stateId = ar.reserve_state
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("site_id", "=", siteId);
|
||||
cnd.and(new Static("JSON_CONTAINS(auditList, JSON_OBJECT('auditUser', '" + ShiroUtil.getPrincipalProperty("id") + "'))"));
|
||||
|
||||
cnd.groupBy("sqid");
|
||||
|
||||
sql.setCondition(cnd);
|
||||
List<NutMap> list = listMap(sql);
|
||||
list.forEach(item -> {
|
||||
List<NutMap> listMap = Json.fromJsonAsList(NutMap.class, item.getString("auditList"));
|
||||
Boolean auditState = false;
|
||||
for (NutMap map : listMap) {
|
||||
if (map.getString("auditUser").equals(ShiroUtil.getPrincipalProperty("id"))) {
|
||||
auditState = map.get("auditState") == null ? null : map.getBoolean("auditState");
|
||||
}
|
||||
}
|
||||
item.put("auditState", auditState);
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
@At("/audit")
|
||||
@Ok("json:full")
|
||||
@ViReturn
|
||||
public Object audit(@Param(value = "ids", required = false) String[] ids, Audit audit,
|
||||
@Param(value = "isPass", required = false) boolean isPass) {
|
||||
dao.insert(audit);
|
||||
for (String id : ids) {
|
||||
|
||||
ActivitySiteReserve fetch = siteReserveService.fetch(id);
|
||||
|
||||
Integer stateCode = fetch.getReserve_state();
|
||||
Integer afterStateCode = activityCommonService.findAfterStateCode(stateCode, isPass);
|
||||
|
||||
List<ActivitySiteReserve> reserves = siteReserveService.query(Cnd.NEW().and("sqid", "=", fetch.getSqid()));
|
||||
String days = "";
|
||||
|
||||
for (ActivitySiteReserve siteReserve : reserves) {
|
||||
siteReserve.setReserve_state(afterStateCode);
|
||||
|
||||
List<NutMap> auditIdList = siteReserve.getAuditList();
|
||||
if (Lang.isEmpty(auditIdList)) {
|
||||
auditIdList = new ArrayList<>();
|
||||
}
|
||||
auditIdList.add(NutMap.NEW().addv("stateCode", stateCode)
|
||||
.addv("auditId", audit.getId())
|
||||
.addv("auditUser", ShiroUtil.getPrincipalProperty("id"))
|
||||
.addv("auditState", isPass)
|
||||
.addv("auditOption", audit.getAuditOpinion()));
|
||||
siteReserve.setAuditList(auditIdList);
|
||||
|
||||
days += siteReserve.getReserve_day() + ",";
|
||||
dao.update(siteReserve);
|
||||
}
|
||||
days = days.substring(0, days.length() - 1);
|
||||
|
||||
Integer successCode = activityCommonService.findSuccessStateCode(fetch.getSite_id());
|
||||
ActivitySiteInfo siteInfo = siteInfoService.fetch(fetch.getSite_id());
|
||||
if (afterStateCode.equals(successCode) && siteInfo.getTypeId() == 1) {
|
||||
String content = "%s老师您好!您提交的%s使用申请已通过审批,在预约使用期间可刷校园卡进入母婴室,如有疑问,欢迎咨询校工会。"
|
||||
.formatted(fetch.getReserve_person(), siteInfo.getName());
|
||||
Sys_user user = sysUserService.fetch(fetch.getReserve_person_id());
|
||||
|
||||
ArrayList<Map> list2 = new ArrayList<>();
|
||||
list2.add(Map.of("type", "User", "userId", user.getLoginname(), "name", user.getUsername()));
|
||||
// msgApi.sendMsg(content, list2, "场地预约", "WeChat", MsgApi.sendMode.normal.name());
|
||||
} else if (!afterStateCode.equals(successCode)) {
|
||||
//发送微信提醒审核人
|
||||
//查询状态码对应的审核人
|
||||
Sql sql = Sqls.create("""
|
||||
select userId,u.loginname,u.username,u.unitname from audit_state_user asu
|
||||
left join `user` u on u.id = asu.userId left join audit_state s on s.stateId=asu.stateId $condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("asu.stateId", "=", afterStateCode);
|
||||
cnd.and("s.stateAuditType", "=", "0");
|
||||
String moduleName = activityCommonService.findModuleNameByActivityId(fetch.getSite_id());
|
||||
if ("infantRoom".equals(moduleName)) {
|
||||
//获取当前登录人的单位id
|
||||
String unitId = io.v.nutz.web.commons.utils.ShiroUtil.getPrincipalProperty("unitid").toString();
|
||||
cnd.and("u.unitid", "=", unitId);
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
List<Record> list = siteReserveService.list(sql);
|
||||
|
||||
for (Record item : list) {
|
||||
String content = "%s老师您好!%s老师预约%s日使用%s,请您通过门户网站或点击详情登录智慧工会平台进行审核。"
|
||||
.formatted(item.getString("username"),
|
||||
fetch.getReserve_person(),
|
||||
days,
|
||||
siteInfo.getName());
|
||||
Sys_user sys_user = sysUserService.fetch(item.getString("userId"));
|
||||
NutMap map = new NutMap();
|
||||
map.setv("keyword1", NutMap.NEW().setv("value", "场地预约"));
|
||||
map.setv("keyword2", NutMap.NEW().setv("value", item.getString("username")
|
||||
+ "(" + item.getString("loginname") + ")"));
|
||||
map.setv("keyword3", NutMap.NEW().setv("value", item.getString("unitname")));
|
||||
map.setv("keyword4", NutMap.NEW().setv("value", content));
|
||||
map.setv("keyword5", NutMap.NEW().setv("value", DateUtil.now()));
|
||||
// msgApi.sendMsg(sys_user.getWeAppOpenid(), msgApi.DSH_TEMPLATE_ID, Globals.AppDomain + "/mobile/activity/site/audit", map);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<NutMap> listMap(Sql sql) {
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
dao.execute(sql);
|
||||
return sql.getList(NutMap.class);
|
||||
}
|
||||
@At("") @Ok("beetl:/platform/activity/site/ReviewEntries.html")
|
||||
public void index() { }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.v.nutz.zhgh.mobile.activity.site;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.zhgh.activity.services.impl.SiteBookingService;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.ioc.loader.annotation.*;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.mvc.annotation.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/** 协会审核:PC/H5 共享业务服务,入口固定节点,不能由请求绕过节点限制。 */
|
||||
@IocBean @Ok("json:full") @At("/mobile/activity/site/audit/club")
|
||||
@RequiresPermissions("activity.site.review.club")
|
||||
public class SiteClubAuditMobileController {
|
||||
@Inject private SiteBookingService siteBookingService;
|
||||
@At("") @Ok("beetl:/mobile/activity/site/audit.html")
|
||||
public void index(HttpServletRequest request) {
|
||||
request.setAttribute("reviewTitle","协会审核");
|
||||
request.setAttribute("reviewApi","/mobile/activity/site/audit/club");
|
||||
}
|
||||
/** isAudit 为未审/已审/null全部;keyword/month/typeId 为筛选条件,返回分页 list/totalCount。 */
|
||||
@At @ViReturn
|
||||
public Object pageData(Boolean isAudit,String searchKeyword,String month,String typeId,int pageNumber,int pageSize) {
|
||||
return siteBookingService.reviewPage("club",isAudit,searchKeyword,month,typeId,pageNumber,pageSize);
|
||||
}
|
||||
/** id 是预约记录主键,返回申请、时段及审核历史,服务层校验查看范围。 */
|
||||
@At @ViReturn
|
||||
public Object detail(String id) { return siteBookingService.detail(id); }
|
||||
/** ids 为预约记录 ID 数组,isPass 为通过/拒绝,auditOpinion 为必填意见;返回 Object,由 @ViReturn 包装 code(0 成功)、msg(结果说明)。 */
|
||||
@At @ViReturn @Aop(TransAop.READ_COMMITTED)
|
||||
public Object doReview(String[] ids,Boolean isPass,String auditOpinion) {
|
||||
siteBookingService.review("club",ids,isPass,auditOpinion);
|
||||
// 非 void 签名保留拦截器生成的统一结果,避免审核成功却返回空响应。
|
||||
return null;
|
||||
}
|
||||
/** id 为预约记录主键,撤回本人最近一次审核;返回统一 code/msg,成功后恢复当前节点待审。 */
|
||||
@At @ViReturn @Aop(TransAop.READ_COMMITTED)
|
||||
public Object doRevoke(String id) {
|
||||
siteBookingService.revokeReview("club",id);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,9 @@ import org.nutz.json.Json;
|
||||
import org.nutz.lang.random.R;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import io.v.nutz.zhgh.activity.services.impl.SiteBookingService;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
@@ -45,9 +48,12 @@ import java.util.List;
|
||||
*/
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@org.apache.shiro.authz.annotation.RequiresPermissions("activity.site.reserve")
|
||||
@At("/mobile/activity/site/info")
|
||||
public class SiteInfoMobileController {
|
||||
|
||||
@Inject private SiteBookingService siteBookingService;
|
||||
|
||||
@Inject
|
||||
private SiteInfoService siteInfoService;
|
||||
|
||||
@@ -88,20 +94,20 @@ public class SiteInfoMobileController {
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
info.*,
|
||||
type.meetingTypeName,
|
||||
type.moduleName,
|
||||
(select unitname from `user` where username = info.contact_person) as unitname,
|
||||
( SELECT count( 1 ) FROM activity_site_reserve WHERE site_id = info.id AND reserve_state = (select stateId from audit_state where module = (select moduleName from activity_type where id = info.typeId) and stateAuditType = 3)) count
|
||||
info.*,
|
||||
type.meetingTypeName,
|
||||
type.moduleName,
|
||||
(select unitname from `user` where username = info.contact_person) as unitname,
|
||||
( SELECT count( 1 ) FROM activity_site_reserve WHERE site_id = info.id AND reserve_state = 4030) count
|
||||
FROM
|
||||
activity_site_info info
|
||||
LEFT JOIN activity_type type ON type.id = info.typeId
|
||||
LEFT JOIN audit_state state ON state.module = type.moduleName
|
||||
activity_site_info info
|
||||
LEFT JOIN activity_type type ON type.id = info.typeId
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
|
||||
cnd.and("info.state", "=", true);
|
||||
cnd.and("type.enabled","=",true);
|
||||
if (StringUtils.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.orLike("info.name", pageForm.getSearchKeyword());
|
||||
@@ -142,12 +148,11 @@ public class SiteInfoMobileController {
|
||||
info.name,
|
||||
info.address,
|
||||
`as`.stateAuditType,
|
||||
count(sqid) days,
|
||||
GROUP_CONCAT(reserve_day order by reserve_day asc) concat_day
|
||||
count(distinct reserve_day) days
|
||||
from
|
||||
activity_site_reserve ar
|
||||
left join activity_site_info info on info.id = ar.site_id
|
||||
left join audit_state `as` on ar.reserve_state=`as`.stateId
|
||||
left join (select state_id stateId,state_name stateName,state_color stateColor,'activity_site' module,case when state_id=4030 then 3 when state_id in (4040,4050) then 1 else 0 end stateAuditType from state where belong='activity_site') `as` on ar.reserve_state=`as`.stateId
|
||||
$condition
|
||||
""");
|
||||
|
||||
@@ -161,7 +166,7 @@ public class SiteInfoMobileController {
|
||||
if (StrUtil.isNotBlank(typeId)) {
|
||||
cnd.andEX("info.typeId", "=", typeId);
|
||||
}
|
||||
if (!timeSwitch && StrUtil.isNotBlank(time)) {
|
||||
if (!Boolean.TRUE.equals(timeSwitch) && StrUtil.isNotBlank(time)) {
|
||||
cnd.and("left(ar.reserve_day, 7)", "=", time);
|
||||
}
|
||||
cnd.and("reserve_person_id", "=", ShiroUtil.getPrincipalProperty("id"));
|
||||
@@ -170,7 +175,8 @@ public class SiteInfoMobileController {
|
||||
|
||||
sql.setCondition(cnd);
|
||||
|
||||
return siteInfoService.listPage(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return siteBookingService.reservationPage(pageForm.getPageNumber(), pageForm.getPageSize(), sql,
|
||||
Boolean.TRUE.equals(timeSwitch) ? null : time);
|
||||
}
|
||||
|
||||
@At("/getDetail")
|
||||
@@ -183,202 +189,37 @@ public class SiteInfoMobileController {
|
||||
@At("/rollback")
|
||||
@Ok("json:full")
|
||||
@ViReturn
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object rollback(String id) {
|
||||
//根据活动id查询第一个审核节点
|
||||
ActivitySiteReserve fetch = siteReserveService.fetch(id);
|
||||
String moduleName = activityCommonService.findModuleNameByActivityId(fetch.getSite_id());
|
||||
Integer stateCode = activityCommonService.findStartStateCodeByModuleName(moduleName);
|
||||
|
||||
if (fetch.getReserve_state() > stateCode) {
|
||||
return Result.error("当前状态不能进行撤销操作");
|
||||
}
|
||||
ActivitySiteReserve siteReserve = siteReserveService.fetch(id);
|
||||
siteReserveService.clear(Cnd.NEW().and("sqid", "=", siteReserve.getSqid()));
|
||||
siteBookingService.cancel(id);
|
||||
return null;
|
||||
}
|
||||
|
||||
@At("/backOption")
|
||||
@Ok("json:full")
|
||||
@ViReturn
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object backOption(String id, String option) {
|
||||
ActivitySiteReserve fetch = siteReserveService.fetch(id);
|
||||
siteReserveService.update(Chain.make("back_option", option), Cnd.NEW().and("sqid", "=", fetch.getSqid()));
|
||||
siteBookingService.feedback(id,option);
|
||||
return null;
|
||||
}
|
||||
|
||||
@At("/getReserve")
|
||||
@Ok("json:full")
|
||||
@ViReturn
|
||||
public Object getReserve(String siteId, String day) {
|
||||
|
||||
//获取场地的开放时间
|
||||
ActivitySiteInfo fetch = siteInfoService.fetch(siteId);
|
||||
String moduleName = activityCommonService.findModuleNameByActivityId(siteId);
|
||||
List<NutMap> hours = fetch.getOpen_hours();
|
||||
Integer limitNum = fetch.getLimitNum();
|
||||
|
||||
hours.forEach(item -> {
|
||||
String startTime = item.getString("start_time");
|
||||
String endTime = item.getString("end_time");
|
||||
item.put("code", 1);
|
||||
item.put("msg", "可预约");
|
||||
item.put("backColor", "#e8ffef");
|
||||
item.put("color", "#52986a");
|
||||
item.put("limitNum", limitNum);
|
||||
//如果这个时间段过了,显示已过期
|
||||
String time = day + " " + startTime;
|
||||
int compare = DateUtil.compare(new Date(), DateUtil.parse(time), "yyyy-MM-dd HH:mm");
|
||||
if(compare > 0) {
|
||||
item.put("code", -1);
|
||||
item.put("msg", "已过期");
|
||||
item.put("backColor", "");
|
||||
item.put("color", "");
|
||||
}
|
||||
|
||||
List<ActivitySiteReserve> list = siteReserveService.query(Cnd.NEW()
|
||||
.and("reserve_day", "=", day)
|
||||
.and("start_time", "=", startTime)
|
||||
.and("end_time", "=", endTime)
|
||||
.and("site_id", "=", siteId)
|
||||
.and(new Static(" reserve_state in (select stateId from audit_state where stateAuditType != 1 and module = '%s')".formatted(moduleName))));
|
||||
|
||||
//如果这个时间段被单位预约了,直接显示约满
|
||||
ActivitySiteReserve reserve1 = list.stream().filter(o -> o.getReserve_type() == 2).findAny().orElse(null);
|
||||
if(reserve1 != null) {
|
||||
item.put("code", -2);
|
||||
item.put("msg", "已约满");
|
||||
item.put("backColor", "");
|
||||
item.put("color", "");
|
||||
}
|
||||
//如果这个时间段预约的人数满了,显示约满
|
||||
if(list.size() >= limitNum) {
|
||||
item.put("code", -1);
|
||||
item.put("msg", "已约满");
|
||||
item.put("backColor", "");
|
||||
item.put("color", "");
|
||||
}
|
||||
//如果预约过了,显示已预约
|
||||
ActivitySiteReserve reserve = list.stream().filter(o -> o.getReserve_person_id().equals(ShiroUtil.getPrincipalProperty("id"))).findAny().orElse(null);
|
||||
if(reserve != null && reserve.getReserve_type() != 2) {
|
||||
item.put("code", -1);
|
||||
item.put("msg", "已预约");
|
||||
item.put("backColor", "#0e78c5");
|
||||
item.put("color", "#fff3f3");
|
||||
}
|
||||
item.put("reserveNum", list.size());
|
||||
});
|
||||
|
||||
return hours;
|
||||
/** siteId为场地,day为日期,reserveType为1个人/2单位/3协会;返回带code/msg的可预约场次数组。 */
|
||||
public Object getReserve(String siteId, String day, Integer reserveType) {
|
||||
return siteInfoService.availableSlots(siteId,day,reserveType);
|
||||
}
|
||||
|
||||
@At("/reserveDo")
|
||||
@Ok("json:full")
|
||||
@ViReturn
|
||||
public Object reserveDo(String siteId, String times, String message, Integer reserve_type) {
|
||||
String moduleName = activityCommonService.findModuleNameByActivityId(siteId);
|
||||
Integer stateCode = activityCommonService.findStartStateCodeByModuleName(moduleName);
|
||||
|
||||
if (stateCode == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Sys_user user = (Sys_user) ShiroUtil.getPrincipal();
|
||||
List<ActivitySiteReserve> li = new ArrayList<>();
|
||||
|
||||
List<NutMap> nutMaps = Json.fromJsonAsList(NutMap.class, times);
|
||||
|
||||
//主要来查询个人预约时的人数上限
|
||||
ActivitySiteInfo siteInfo = siteInfoService.dao().fetch(ActivitySiteInfo.class, siteId);
|
||||
Integer limitNum = siteInfo.getLimitNum();
|
||||
|
||||
for (NutMap item : nutMaps) {
|
||||
String time = item.getString("day") + " " + item.getString("start_time");
|
||||
int compare = DateUtil.compare(new Date(), DateUtil.parse(time), "yyyy-MM-dd HH:mm");
|
||||
if(compare > 0) {
|
||||
return Result.error("您预约的【%s】时间已过".formatted(time));
|
||||
}
|
||||
if(reserve_type == 1 && !item.getString("day").equals(DateUtil.format(DateUtil.offsetDay(new Date(), 1), "yyyy-MM-dd"))) {
|
||||
return Result.error("个人预约只支持预约明天的时间");
|
||||
}
|
||||
|
||||
List<ActivitySiteReserve> list = siteReserveService.query(Cnd.NEW()
|
||||
.and("reserve_day", "=", item.getString("day"))
|
||||
.and("start_time", "=", item.getString("start_time"))
|
||||
.and("end_time", "=", item.getString("end_time"))
|
||||
.and("site_id", "=", siteId)
|
||||
.and(new Static(" reserve_state in (select stateId from audit_state where stateAuditType != 1 and module = '%s')".formatted(moduleName))));
|
||||
ActivitySiteReserve reserve = list.stream().filter(o -> o.getReserve_person_id().equals(ShiroUtil.getPrincipalProperty("id"))).findAny().orElse(null);
|
||||
if (reserve != null) {
|
||||
return Result.error("您已预约该时间段!");
|
||||
}
|
||||
if(reserve_type == 1) {
|
||||
if((list.size() + 1) > limitNum) {
|
||||
return Result.error("【%s】时间段预约人数已满!".formatted(time));
|
||||
}
|
||||
} else {
|
||||
if(list.size() > 0) {
|
||||
return Result.error("【%s】时间段已有预约!".formatted(time));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String str = "";
|
||||
String r = R.UU32();
|
||||
for (NutMap item : nutMaps) {
|
||||
ActivitySiteReserve as = new ActivitySiteReserve();
|
||||
as.setSqid(r);
|
||||
as.setSite_id(siteId);
|
||||
as.setReserve_person(user.getUsername());
|
||||
as.setReserve_person_id(user.getId());
|
||||
as.setReserve_person_unit(user.getUnit() == null ? null : user.getUnit().getName());
|
||||
as.setReserve_person_phone(user.getMobile());
|
||||
as.setReserve_cause(message);
|
||||
as.setReserve_state(stateCode);
|
||||
as.setReserve_day(item.getString("day"));
|
||||
as.setStart_time(item.getString("start_time"));
|
||||
as.setEnd_time(item.getString("end_time"));
|
||||
as.setReserve_type(reserve_type);
|
||||
str += item.getString("day") + ",";
|
||||
li.add(as);
|
||||
}
|
||||
str = str.substring(0, str.length() - 1);
|
||||
|
||||
siteReserveService.insert(li);
|
||||
|
||||
//发送微信提醒审核人
|
||||
//查询状态码对应的审核人
|
||||
Sql sql = Sqls.create("""
|
||||
select userId,u.loginname,u.username,u.unitname from audit_state_user asu
|
||||
left join `user` u on u.id = asu.userId left join audit_state s on s.stateId=asu.stateId $condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("asu.stateId", "=", stateCode);
|
||||
cnd.and("s.stateAuditType", "=", "0");
|
||||
if ("infantRoom".equals(moduleName)) {
|
||||
//获取当前登录人的单位id
|
||||
String unitId = ShiroUtil.getPrincipalProperty("unitid").toString();
|
||||
cnd.and("u.unitid", "=", unitId);
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
List<Record> list = siteReserveService.list(sql);
|
||||
|
||||
for (Record item : list) {
|
||||
String content = "%s老师您好!%s老师预约%s日使用%s,请您通过门户网站或点击详情登录智慧工会平台进行审核。"
|
||||
.formatted(item.getString("username"),
|
||||
user.getUsername(),
|
||||
str,
|
||||
siteInfoService.fetch(siteId).getName());
|
||||
|
||||
Sys_user sys_user = sysUserService.fetch(item.getString("userId"));
|
||||
NutMap map = new NutMap();
|
||||
map.setv("keyword1", NutMap.NEW().setv("value", "场地预约"));
|
||||
map.setv("keyword2", NutMap.NEW().setv("value", item.getString("username")
|
||||
+ "(" + item.getString("loginname") + ")"));
|
||||
map.setv("keyword3", NutMap.NEW().setv("value", item.getString("unitname")));
|
||||
map.setv("keyword4", NutMap.NEW().setv("value", content));
|
||||
map.setv("keyword5", NutMap.NEW().setv("value", DateUtil.now()));
|
||||
// msgApi.sendMsg(sys_user.getWeAppOpenid(), msgApi.DSH_TEMPLATE_ID, Globals.AppDomain + "/mobile/activity/site/audit", map);
|
||||
}
|
||||
return null;
|
||||
/** siteId 为场地;times 为 day/start_time/end_time 数组 JSON;message 为事由;reserve_type 为1/2/3,clubId 为协会预约所属协会;返回 sqid。 */
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object reserveDo(String siteId, String times, String message, Integer reserve_type, String clubId) {
|
||||
ActivitySiteReserve form=new ActivitySiteReserve();
|
||||
form.setSite_id(siteId);form.setReserve_cause(message);form.setReserve_type(reserve_type);form.setClubId(clubId);
|
||||
return siteBookingService.submit(form,Json.fromJsonAsList(NutMap.class,times));
|
||||
}
|
||||
}
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package io.v.nutz.zhgh.mobile.activity.site;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.zhgh.activity.services.impl.SiteBookingService;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.ioc.loader.annotation.*;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.mvc.annotation.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/** 校工会审核:PC/H5 共享业务服务,入口固定节点,不能由请求绕过节点限制。 */
|
||||
@IocBean @Ok("json:full") @At("/mobile/activity/site/audit/school")
|
||||
@RequiresPermissions("activity.site.review.school")
|
||||
public class SiteSchoolAuditMobileController {
|
||||
@Inject private SiteBookingService siteBookingService;
|
||||
@At("") @Ok("beetl:/mobile/activity/site/audit.html")
|
||||
public void index(HttpServletRequest request) {
|
||||
request.setAttribute("reviewTitle","校工会审核");
|
||||
request.setAttribute("reviewApi","/mobile/activity/site/audit/school");
|
||||
}
|
||||
/** isAudit 为未审/已审/null全部;keyword/month/typeId 为筛选条件,返回分页 list/totalCount。 */
|
||||
@At @ViReturn
|
||||
public Object pageData(Boolean isAudit,String searchKeyword,String month,String typeId,int pageNumber,int pageSize) {
|
||||
return siteBookingService.reviewPage("school",isAudit,searchKeyword,month,typeId,pageNumber,pageSize);
|
||||
}
|
||||
/** id 是预约记录主键,返回申请、时段及审核历史,服务层校验查看范围。 */
|
||||
@At @ViReturn
|
||||
public Object detail(String id) { return siteBookingService.detail(id); }
|
||||
/** ids 为预约记录 ID 数组,isPass 为通过/拒绝,auditOpinion 为必填意见;返回 Object,由 @ViReturn 包装 code(0 成功)、msg(结果说明)。 */
|
||||
@At @ViReturn @Aop(TransAop.READ_COMMITTED)
|
||||
public Object doReview(String[] ids,Boolean isPass,String auditOpinion) {
|
||||
siteBookingService.review("school",ids,isPass,auditOpinion);
|
||||
// 非 void 签名保留拦截器生成的统一结果,避免审核成功却返回空响应。
|
||||
return null;
|
||||
}
|
||||
/** id 为预约记录主键,撤回本人最近一次审核;返回统一 code/msg,成功后恢复当前节点待审。 */
|
||||
@At @ViReturn @Aop(TransAop.READ_COMMITTED)
|
||||
public Object doRevoke(String id) {
|
||||
siteBookingService.revokeReview("school",id);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.v.nutz.zhgh.mobile.activity.site;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.zhgh.activity.services.impl.SiteBookingService;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.ioc.loader.annotation.*;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.mvc.annotation.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/** 分工会审核:PC/H5 共享业务服务,入口固定节点,不能由请求绕过节点限制。 */
|
||||
@IocBean @Ok("json:full") @At("/mobile/activity/site/audit/union")
|
||||
@RequiresPermissions("activity.site.review.union")
|
||||
public class SiteUnionAuditMobileController {
|
||||
@Inject private SiteBookingService siteBookingService;
|
||||
@At("") @Ok("beetl:/mobile/activity/site/audit.html")
|
||||
public void index(HttpServletRequest request) {
|
||||
request.setAttribute("reviewTitle","分工会审核");
|
||||
request.setAttribute("reviewApi","/mobile/activity/site/audit/union");
|
||||
}
|
||||
/** isAudit 为未审/已审/null全部;keyword/month/typeId 为筛选条件,返回分页 list/totalCount。 */
|
||||
@At @ViReturn
|
||||
public Object pageData(Boolean isAudit,String searchKeyword,String month,String typeId,int pageNumber,int pageSize) {
|
||||
return siteBookingService.reviewPage("union",isAudit,searchKeyword,month,typeId,pageNumber,pageSize);
|
||||
}
|
||||
/** id 是预约记录主键,返回申请、时段及审核历史,服务层校验查看范围。 */
|
||||
@At @ViReturn
|
||||
public Object detail(String id) { return siteBookingService.detail(id); }
|
||||
/** ids 为预约记录 ID 数组,isPass 为通过/拒绝,auditOpinion 为必填意见;返回 Object,由 @ViReturn 包装 code(0 成功)、msg(结果说明)。 */
|
||||
@At @ViReturn @Aop(TransAop.READ_COMMITTED)
|
||||
public Object doReview(String[] ids,Boolean isPass,String auditOpinion) {
|
||||
siteBookingService.review("union",ids,isPass,auditOpinion);
|
||||
// 非 void 签名保留拦截器生成的统一结果,避免审核成功却返回空响应。
|
||||
return null;
|
||||
}
|
||||
/** id 为预约记录主键,撤回本人最近一次审核;返回统一 code/msg,成功后恢复当前节点待审。 */
|
||||
@At @ViReturn @Aop(TransAop.READ_COMMITTED)
|
||||
public Object doRevoke(String id) {
|
||||
siteBookingService.revokeReview("union",id);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
package io.v.nutz.zhgh.mobile.proposal;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.sys.models.Sys_signature;
|
||||
import io.v.nutz.sys.services.SysSignatureService;
|
||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||
import io.v.nutz.zhgh.proposal.constants.ProposalState;
|
||||
import io.v.nutz.zhgh.proposal.constants.ProposalToDoHandler;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalAudit;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalBranchLeaderSuffixAudit;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalInfo;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalReply;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalSearch;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalUndertake;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalBranchLeaderSuffixAuditService;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalInfoService;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.POST;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
/**
|
||||
* 手机端分管校领导审批承办答复。
|
||||
*/
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/mobile/proposal/branchLeaderSuffixAudit")
|
||||
public class MProposalBranchLeaderSuffixAuditController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
@Inject
|
||||
private ProposalInfoService proposalInfoService;
|
||||
|
||||
@Inject
|
||||
private ProposalBranchLeaderSuffixAuditService proposalBranchLeaderSuffixAuditService;
|
||||
|
||||
@Inject
|
||||
private SysSignatureService sysSignatureService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/mobile/proposal/branchLeaderSuffixAudit/list.html")
|
||||
@RequiresPermissions("proposal.transact.branchLeaderSuffixAudit")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/view")
|
||||
@Ok("beetl:/mobile/proposal/branchLeaderSuffixAudit/audit.html")
|
||||
@RequiresPermissions("proposal.transact.branchLeaderSuffixAudit")
|
||||
public void view() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端分管校领导审批列表。
|
||||
* 参数说明:
|
||||
* page:分页参数,包含 pageNumber、pageSize、排序字段等;
|
||||
* search:筛选参数,meetingId 为届次、proposalTypeId 为提案类型、proposalResultCode 为立案结果、isAudit 表示是否已完成分管校领导审批;
|
||||
* 返回值:分页结果,list 中包含提案基础信息、承办单位、审批状态、pblsaId 等字段。
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.branchLeaderSuffixAudit")
|
||||
public Object pageData(PageForm page, ProposalSearch search) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
proposalInfoService.sortAndSearch(cnd, page, search);
|
||||
return proposalBranchLeaderSuffixAuditService.pageData(page, cnd, search);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询手机端分管校领导审批办理信息。
|
||||
* 参数说明:
|
||||
* pblsaId:proposal_branch_leader_suffix_audit 表主键;
|
||||
* 返回值:NutMap,包含 proposalId、underTakeId、underTakeNames、stateCode、isAudit,用于详情页判断是否显示审核表单。
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.branchLeaderSuffixAudit")
|
||||
public Object getAuditInfo(@Param("pblsaId") String pblsaId) {
|
||||
ProposalBranchLeaderSuffixAudit suffixAudit = dao.fetch(ProposalBranchLeaderSuffixAudit.class, pblsaId);
|
||||
if (suffixAudit == null) {
|
||||
return NutMap.NEW();
|
||||
}
|
||||
|
||||
ProposalInfo proposalInfo = dao.fetch(ProposalInfo.class, suffixAudit.getProposalId());
|
||||
ProposalUndertake undertake = dao.fetch(ProposalUndertake.class, suffixAudit.getUnderTakeId());
|
||||
ProposalReply reply = dao.fetch(ProposalReply.class,
|
||||
Cnd.where("proposalId", "=", suffixAudit.getProposalId())
|
||||
.and("replyUnitId", "=", suffixAudit.getUnderTakeId()));
|
||||
|
||||
return NutMap.NEW()
|
||||
.addv("pblsaId", suffixAudit.getId())
|
||||
.addv("proposalId", suffixAudit.getProposalId())
|
||||
.addv("underTakeId", suffixAudit.getUnderTakeId())
|
||||
.addv("underTakeNames", undertake == null ? "" : undertake.getUnitName())
|
||||
.addv("undertakeType", reply == null ? "" : reply.getUndertakeType())
|
||||
.addv("stateCode", proposalInfo == null ? null : proposalInfo.getStateCode())
|
||||
.addv("isAudit", suffixAudit.getIsAudit());
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端提交分管校领导审批。
|
||||
* 参数说明:
|
||||
* sign:签名图片数据,可为空,传入后保存为签名记录并写入审核记录 signId;
|
||||
* audit:ProposalAudit 审核对象,需包含 proposalId、username、loginName、auditTime、opinion 等字段;
|
||||
* pblsaId:proposal_branch_leader_suffix_audit 表主键,用于定位本次分管校领导审批记录;
|
||||
* 返回值:无业务数据,成功时由 ViReturn 包装为成功响应。
|
||||
*/
|
||||
@At
|
||||
@POST
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.branchLeaderSuffixAudit")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object doAudit(@Param(value = "sign", required = false) String sign,
|
||||
@Param("audit") ProposalAudit audit,
|
||||
@Param("pblsaId") String pblsaId) {
|
||||
String signId = null;
|
||||
if (StrUtil.isNotBlank(sign)) {
|
||||
signId = sysSignatureService.insert(new Sys_signature(sign)).getId();
|
||||
}
|
||||
audit.setUserId(ShiroUtil.getUserId());
|
||||
audit.setSignId(signId);
|
||||
dao.insert(audit);
|
||||
|
||||
dao.update(ProposalBranchLeaderSuffixAudit.class,
|
||||
Chain.make("isAudit", true).add("auditId", audit.getId()),
|
||||
Cnd.where("id", "=", pblsaId));
|
||||
|
||||
dao.update(ProposalInfo.class, Chain.make("stateCode", ProposalState.FEEDBACKSCORE), Cnd.where("id", "=", audit.getProposalId()));
|
||||
ProposalToDoHandler.COMPLETE_BRANCH_LEADER_SUFFIX_AUDIT_TASK.exec(audit.getProposalId(), NutMap.NEW().addv("pblsaId", pblsaId).addv("opinion", audit.getOpinion()));
|
||||
ProposalToDoHandler.CREATE_FEEDBACK_TASK.exec(audit.getProposalId(), null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package io.v.nutz.zhgh.mobile.proposal;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.sys.models.Sys_signature;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalAudit;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalSearch;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalCaseService;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.POST;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 手机端提案工作组预立案审核。
|
||||
*/
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/mobile/proposal/case")
|
||||
public class MProposalCaseController {
|
||||
|
||||
@Inject
|
||||
private ProposalCaseService proposalCaseService;
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/mobile/proposal/case/list.html")
|
||||
@RequiresPermissions("proposal.transact.case")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/view")
|
||||
@Ok("beetl:/mobile/proposal/case/audit.html")
|
||||
@RequiresPermissions("proposal.transact.case")
|
||||
public void view() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端预立案审核列表。
|
||||
* 参数说明:
|
||||
* page:分页参数,包含 pageNumber、pageSize、排序字段等;
|
||||
* search:筛选参数,meetingId 为届次、proposalTypeId 为提案类型、proposalResultCode 为立案结果、isAudit 表示是否已完成预立案审核;
|
||||
* 返回值:分页结果,list 中包含提案基础信息、状态、类型、届次、代表团、立案结果等字段。
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.case")
|
||||
public Object pageData(PageForm page, ProposalSearch search) {
|
||||
return proposalCaseService.pageData(page, search);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端承办单位下拉数据。
|
||||
* 参数说明:无;
|
||||
* 返回值:承办单位列表,每项包含 id、unitName、unitCode、leader 等字段,leader 为该承办单位对应领导姓名。
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.case")
|
||||
public Object getUnderTakeAndLeader() {
|
||||
return proposalCaseService.getUnderTakeAndLeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端提交预立案审核。
|
||||
* 参数说明:
|
||||
* audit:ProposalAudit 审核对象,包含 username、loginName、auditTime、opinion 等审核字段;
|
||||
* resultCode:立案结果,取 proposal_result 字典 code,如 determine、opinion、notGive;
|
||||
* determineTypeCode:立案类型,可为空;
|
||||
* typeId:提案类型 id;
|
||||
* hostUnit:主办单位 id;
|
||||
* helpUnit:协办单位 id 数组,可为空;
|
||||
* sign:签名图片数据,可为空;
|
||||
* proposalIds:审核提案 id 数组,手机端单条审核时传当前提案 id;
|
||||
* auditType:审核类型,手机端单条审核传 one;
|
||||
* flag:审核动作,1 为提交通过,2 为退回修改;
|
||||
* 返回值:无业务数据,成功时由 ViReturn 包装为成功响应。
|
||||
*/
|
||||
@At("/audit")
|
||||
@POST
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.case")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object audit(@Param("audit") ProposalAudit audit,
|
||||
@Param("resultCode") String resultCode,
|
||||
@Param(value = "determineTypeCode", required = false) String determineTypeCode,
|
||||
@Param("typeId") String typeId,
|
||||
@Param("hostUnit") String hostUnit,
|
||||
@Param(value = "helpUnit", required = false) String[] helpUnit,
|
||||
@Param(value = "sign", required = false) String sign,
|
||||
@Param("proposalIds") String[] proposalIds,
|
||||
@Param("auditType") String auditType,
|
||||
@Param("flag") int flag) {
|
||||
if (StrUtil.isNotBlank(sign)) {
|
||||
String signId = dao.insert(new Sys_signature(sign)).getId();
|
||||
audit.setSignId(signId);
|
||||
}
|
||||
audit.setFlag(flag == 1);
|
||||
List<String> helpUnitList = helpUnit == null ? Collections.emptyList() : Arrays.asList(helpUnit);
|
||||
proposalCaseService.audit(Arrays.asList(proposalIds), resultCode, determineTypeCode, hostUnit, helpUnitList, typeId, audit, auditType, flag);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package io.v.nutz.zhgh.mobile.proposal;
|
||||
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalSearch;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalCaseReadService;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
/**
|
||||
* 手机端提案工作组成员意见审核。
|
||||
*/
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/mobile/proposal/caseRead")
|
||||
public class MProposalCaseReadController {
|
||||
|
||||
@Inject
|
||||
private ProposalCaseReadService proposalCaseReadService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/mobile/proposal/caseRead/list.html")
|
||||
@RequiresPermissions("proposal.transact.caseRead")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/view")
|
||||
@Ok("beetl:/mobile/proposal/caseRead/audit.html")
|
||||
@RequiresPermissions("proposal.transact.caseRead")
|
||||
public void view() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端审核列表。
|
||||
* 参数说明:
|
||||
* page:分页参数,包含 pageNumber、pageSize、排序字段等;
|
||||
* search:筛选参数,meetingId 为届次、proposalTypeId 为提案类型、proposalResultCode 为立案结果、isAudit 表示是否已提交成员意见;
|
||||
* 返回值:分页结果,list 中包含提案基础信息、状态、类型、届次、代表团、立案结果以及当前用户是否已发表意见 isOpinion。
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.caseRead")
|
||||
public Object pageData(PageForm page, ProposalSearch search) {
|
||||
return proposalCaseReadService.pageData(page, search);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交手机端成员意见。
|
||||
* 参数说明:
|
||||
* sign:签名图片数据,保存后写入审核记录 signId;
|
||||
* audit:ProposalAudit 的 JSON 字符串,需包含 proposalId、username、loginName、auditTime、opinion、other、determineTypeCode 等审核字段;
|
||||
* proposalId:被审核的提案 id;
|
||||
* 返回值:无业务数据,成功时由 ViReturn 包装为成功响应。
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.caseRead")
|
||||
public Object putSuggestion(@Param(value = "sign", required = false) String sign,
|
||||
@Param("audit") String audit,
|
||||
@Param("proposalId") String proposalId) {
|
||||
proposalCaseReadService.putSuggestion(sign, audit, proposalId);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package io.v.nutz.zhgh.mobile.proposal;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.base.utils.Roles;
|
||||
import io.v.nutz.sys.models.Sys_signature;
|
||||
import io.v.nutz.sys.models.Sys_user_role;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalAudit;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalReply;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalSearch;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalCaseConfirmService;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalCaseService;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalInfoService;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.FieldFilter;
|
||||
import org.nutz.dao.util.Daos;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.POST;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 手机端提案工作组正式立案审核。
|
||||
*/
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/mobile/proposal/caseUnit")
|
||||
public class MProposalCaseUnitController {
|
||||
|
||||
@Inject
|
||||
private ProposalCaseConfirmService proposalCaseConfirmService;
|
||||
|
||||
@Inject
|
||||
private ProposalCaseService proposalCaseService;
|
||||
|
||||
@Inject
|
||||
private ProposalInfoService proposalInfoService;
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/mobile/proposal/caseUnit/list.html")
|
||||
@RequiresPermissions("proposal.transact.caseUnit")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/view")
|
||||
@Ok("beetl:/mobile/proposal/caseUnit/audit.html")
|
||||
@RequiresPermissions("proposal.transact.caseUnit")
|
||||
public void view() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端正式立案审核列表。
|
||||
* 参数说明:
|
||||
* page:分页参数,包含 pageNumber、pageSize、排序字段等;
|
||||
* search:筛选参数,meetingId 为届次、proposalTypeId 为提案类型、proposalResultCode 为立案结果、isAudit 表示是否已完成正式立案审核;
|
||||
* 返回值:分页结果,list 中包含提案基础信息、状态、类型、届次、代表团、立案结果、审核状态等字段。
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.caseUnit")
|
||||
public Object pageData(PageForm page, ProposalSearch search) {
|
||||
return proposalCaseConfirmService.pageData(page, search);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端承办单位下拉数据。
|
||||
* 参数说明:无;
|
||||
* 返回值:承办单位列表,每项包含 id、unitName、unitCode、leader 等字段,leader 为该承办单位对应领导姓名。
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.caseUnit")
|
||||
public Object getUnderTakeAndLeader() {
|
||||
return proposalCaseService.getUnderTakeAndLeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询该提案当前已分配的承办单位。
|
||||
* 参数说明:
|
||||
* proposalId:提案 id;
|
||||
* 返回值:承办单位列表,每项包含 replyUnitId 与 undertakeType,undertakeType 为 1 表示主办、2 表示协办。
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.caseUnit")
|
||||
public Object queryUnderTakeByProposalId(String proposalId) {
|
||||
FieldFilter ff = FieldFilter.create(ProposalReply.class, "^replyUnitId|undertakeType$");
|
||||
return Daos.ext(proposalInfoService.dao(), ff).query(ProposalReply.class, Cnd.where("proposalId", "=", proposalId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查主办单位是否已配置答复人和分管领导。
|
||||
* 参数说明:
|
||||
* hostUnit:主办单位 id;
|
||||
* 返回值:boolean,true 表示该单位至少配置了承办人和分管领导角色,可提交正式立案。
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.caseUnit")
|
||||
public Object checkUnderTakeUser(String hostUnit) {
|
||||
String[] roleIds = Lang.array(Roles.CONTRACTOR_PERSON, Roles.IN_CHARGE_LEADER);
|
||||
int count = dao.count(Sys_user_role.class, Cnd.where("cbdwid", "=", hostUnit).and("roleId", "in", roleIds));
|
||||
return count >= 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端提交正式立案审核。
|
||||
* 参数说明:
|
||||
* audit:ProposalAudit 审核对象,包含 username、loginName、auditTime、opinion 等审核字段;
|
||||
* resultCode:立案结果,取 proposal_result 字典 code,如 determine、opinion、notGive;
|
||||
* determineTypeCode:立案类型,可为空;
|
||||
* typeId:提案类型 id;
|
||||
* hostUnit:主办单位 id;
|
||||
* helpUnit:协办单位 id 数组,可为空;
|
||||
* sign:签名图片数据,可为空;
|
||||
* proposalIds:审核提案 id 数组,手机端单条审核时传当前提案 id,若存在并案则传并案提案 id 集合;
|
||||
* 返回值:无业务数据,成功时由 ViReturn 包装为成功响应。
|
||||
*/
|
||||
@At("/audit")
|
||||
@POST
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.caseUnit")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object audit(@Param("audit") ProposalAudit audit,
|
||||
@Param("resultCode") String resultCode,
|
||||
@Param(value = "determineTypeCode", required = false) String determineTypeCode,
|
||||
@Param(value = "typeId", required = false) String typeId,
|
||||
@Param("hostUnit") String hostUnit,
|
||||
@Param(value = "helpUnit", required = false) String[] helpUnit,
|
||||
@Param(value = "sign", required = false) String sign,
|
||||
@Param("proposalIds") String[] proposalIds) {
|
||||
if (StrUtil.isNotBlank(sign)) {
|
||||
String signId = dao.insert(new Sys_signature(sign)).getId();
|
||||
audit.setSignId(signId);
|
||||
}
|
||||
List<String> helpUnitList = helpUnit == null ? Collections.emptyList() : Arrays.asList(helpUnit);
|
||||
proposalCaseConfirmService.audit(Arrays.asList(proposalIds), resultCode, determineTypeCode, hostUnit, helpUnitList, typeId, audit);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package io.v.nutz.zhgh.mobile.proposal;
|
||||
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.base.utils.Roles;
|
||||
import io.v.nutz.base.utils.Vi;
|
||||
import io.v.nutz.base.dao.CndPlus;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.zhgh.proposal.constants.ProposalState;
|
||||
@@ -9,6 +10,7 @@ import io.v.nutz.zhgh.proposal.models.ProposalSearch;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalInfoService;
|
||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
@@ -53,8 +55,17 @@ public class MProposalDelegationController {
|
||||
if (!ShiroUtil.hasAnyRoles("tamange,sysadmin")) {
|
||||
Sql sql = Sqls.create("SELECT dbtid FROM sys_user_role sur LEFT JOIN proposal_info info ON info.teacherMeetingId = sur.jdhid WHERE sur.roleId = @roleId AND sur.userId = @userId").setParam("roleId", Roles.DBT_TZ).setParam("userId", userId);
|
||||
cnd.and("info.delegationId", "IN", sql);
|
||||
cnd.and(Cnd.exps("info.delegationUnionId", "=", Vi.getUnionId()).or("info.delegationUnionId", "is", null));
|
||||
}
|
||||
if (Strings.isNotBlank(search.getIsAudit())) {
|
||||
if (search.getIsAudit().equals("true")) {
|
||||
cnd.and(Cnd.exps("info.stateCode", ">", ProposalState.DELEGATION));
|
||||
} else {
|
||||
cnd.and("info.stateCode", "=", ProposalState.DELEGATION);
|
||||
}
|
||||
} else {
|
||||
cnd.and(Cnd.exps("info.stateCode", ">=", ProposalState.DELEGATION));
|
||||
}
|
||||
cnd.and("info.stateCode", search.getIsAudit().equals("true") ? ">" : "=", ProposalState.DELEGATION);
|
||||
if (Strings.isNotBlank(search.getSearchKeyWord())) {
|
||||
SqlExpressionGroup sqlExpressionGroup = new SqlExpressionGroup();
|
||||
sqlExpressionGroup.andLike("info.proposalName", search.getSearchKeyWord());
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package io.v.nutz.zhgh.mobile.proposal;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalFeedback;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalSearch;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalFeedBackService;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.POST;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
/**
|
||||
* 手机端提案反馈评价。
|
||||
*/
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/mobile/proposal/feedback")
|
||||
public class MProposalFeedbackScoreController {
|
||||
|
||||
@Inject
|
||||
private ProposalFeedBackService proposalFeedBackService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/mobile/proposal/feedback/list.html")
|
||||
@RequiresPermissions("proposal.transact.feedback")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/view")
|
||||
@Ok("beetl:/mobile/proposal/feedback/audit.html")
|
||||
@RequiresPermissions("proposal.transact.feedback")
|
||||
public void view() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端反馈评价列表。
|
||||
* 参数说明:
|
||||
* page:分页参数,包含 pageNumber、pageSize、searchKeyWord;
|
||||
* search:筛选参数,meetingId 为届次、proposalTypeId 为提案类型、proposalResultCode 为立案结果、isAudit 表示是否已反馈;
|
||||
* 返回值:分页结果,list 中为 NutMap,用于手机端列表展示和待反馈状态判断。
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.feedback")
|
||||
public Object pageData(PageForm page, ProposalSearch search) {
|
||||
return proposalFeedBackService.pageData(page, search);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询手机端反馈办理信息。
|
||||
* 参数说明:
|
||||
* proposalId:proposal_info 表主键;
|
||||
* 返回值:NutMap,包含 proposalId、stateCode、handle,用于详情页判断是否显示反馈表单。
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.feedback")
|
||||
public Object getFeedbackInfo(@Param("proposalId") String proposalId) {
|
||||
return proposalFeedBackService.getFeedbackInfo(proposalId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端提交反馈评价。
|
||||
* 参数说明:
|
||||
* feedBack:ProposalFeedback 反馈对象,需包含 proposalId、feedbackCode、feedbackOpinion;
|
||||
* scoreSign:反馈人签名图片数据,可为空,传入后保存到 scoreSignId;
|
||||
* 返回值:无业务数据,成功时由 ViReturn 包装为成功响应。
|
||||
*/
|
||||
@At
|
||||
@POST
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.feedback")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object doFeedback(@Param("feedBack") ProposalFeedback proposalFeedback,
|
||||
@Param(value = "scoreSign", required = false) String scoreSign) {
|
||||
if (proposalFeedback == null || StrUtil.isBlank(proposalFeedback.getProposalId())) {
|
||||
throw new RuntimeException("提案信息不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(proposalFeedback.getFeedbackCode())) {
|
||||
throw new RuntimeException("请选择办理结果评价");
|
||||
}
|
||||
proposalFeedBackService.doFeedback(proposalFeedback, scoreSign);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -26,11 +26,13 @@ import io.v.nutz.zhgh.msgNotify.service.MsgNotifyService;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.integration.jedis.RedisService;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
@@ -155,6 +157,7 @@ public class MsgNotifyController {
|
||||
|
||||
@At
|
||||
@ViReturn
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@RequiresPermissions("msgNotify.send")
|
||||
public Object doAdd(@Param(value = "flag", required = false) boolean flag,
|
||||
@Param(value = "existsLoginNameRedisKey", required = false) String existsLoginNameRedisKey,
|
||||
@@ -168,12 +171,6 @@ public class MsgNotifyController {
|
||||
|
||||
|
||||
List<MsgNotifyUser> msgNotifyUsers = new ArrayList<>();
|
||||
MsgNotifyUser msgNotifyUser = new MsgNotifyUser();
|
||||
msgNotifyUser.setSendUser(ShiroUtil.getUserId());
|
||||
msgNotifyUser.setTitle(msgNotify.getTitle());
|
||||
msgNotifyUser.setContent(msgNotify.getContent());
|
||||
msgNotifyUser.setLink(msgNotify.getLink());
|
||||
msgNotifyUser.setApiModule("消息管理系统");
|
||||
|
||||
if (msgNotify.getSendMode().equals("one")) {
|
||||
List<NutMap> list = msgNotifyService.getUserByRoleIds(msgNotify.getModule(), msgNotify.getTeacherMeetingId(), msgNotify.getRoleIds());
|
||||
@@ -182,6 +179,13 @@ public class MsgNotifyController {
|
||||
msgNotifyService.sendMsg2(msgNotify.getSendTypes(), loginNames, msgNotify.getContent(),msgNotify.getTitle(),msgNotify.getLink());
|
||||
}
|
||||
list.forEach(v -> {
|
||||
// 每位收件人创建独立明细,避免循环复用对象导致所有记录指向最后一人。
|
||||
MsgNotifyUser msgNotifyUser = new MsgNotifyUser();
|
||||
msgNotifyUser.setSendUser(ShiroUtil.getUserId());
|
||||
msgNotifyUser.setTitle(msgNotify.getTitle());
|
||||
msgNotifyUser.setContent(msgNotify.getContent());
|
||||
msgNotifyUser.setLink(msgNotify.getLink());
|
||||
msgNotifyUser.setApiModule("消息管理系统");
|
||||
if (flag) {
|
||||
msgNotify.setSendTime(DateUtil.getDateTime());
|
||||
msgNotifyUser.setSendTime(DateUtil.getDateTime());
|
||||
@@ -199,6 +203,13 @@ public class MsgNotifyController {
|
||||
msgNotifyService.sendMsg2(msgNotify.getSendTypes(), loginNames, msgNotify.getContent(),msgNotify.getTitle(),msgNotify.getLink());
|
||||
}
|
||||
userScopes.forEach(v -> {
|
||||
// 每位收件人创建独立明细,避免循环复用对象导致所有记录指向最后一人。
|
||||
MsgNotifyUser msgNotifyUser = new MsgNotifyUser();
|
||||
msgNotifyUser.setSendUser(ShiroUtil.getUserId());
|
||||
msgNotifyUser.setTitle(msgNotify.getTitle());
|
||||
msgNotifyUser.setContent(msgNotify.getContent());
|
||||
msgNotifyUser.setLink(msgNotify.getLink());
|
||||
msgNotifyUser.setApiModule("消息管理系统");
|
||||
if (flag) {
|
||||
msgNotify.setSendTime(DateUtil.getDateTime());
|
||||
msgNotifyUser.setSendTime(DateUtil.getDateTime());
|
||||
@@ -215,6 +226,13 @@ public class MsgNotifyController {
|
||||
msgNotifyService.sendMsg2(msgNotify.getSendTypes(), loginNames, msgNotify.getContent(),msgNotify.getTitle(),msgNotify.getLink());
|
||||
}
|
||||
for (String u : users) {
|
||||
// 每位收件人创建独立明细,避免循环复用对象导致所有记录指向最后一人。
|
||||
MsgNotifyUser msgNotifyUser = new MsgNotifyUser();
|
||||
msgNotifyUser.setSendUser(ShiroUtil.getUserId());
|
||||
msgNotifyUser.setTitle(msgNotify.getTitle());
|
||||
msgNotifyUser.setContent(msgNotify.getContent());
|
||||
msgNotifyUser.setLink(msgNotify.getLink());
|
||||
msgNotifyUser.setApiModule("消息管理系统");
|
||||
if (flag) {
|
||||
msgNotify.setSendTime(DateUtil.getDateTime());
|
||||
msgNotifyUser.setSendTime(DateUtil.getDateTime());
|
||||
@@ -233,6 +251,13 @@ public class MsgNotifyController {
|
||||
|
||||
msgNotify.setSendTime(DateUtil.getDateTime());
|
||||
for (User u : userList) {
|
||||
// 每位收件人创建独立明细,确保导入人员与收件箱记录一一对应。
|
||||
MsgNotifyUser msgNotifyUser = new MsgNotifyUser();
|
||||
msgNotifyUser.setSendUser(ShiroUtil.getUserId());
|
||||
msgNotifyUser.setTitle(msgNotify.getTitle());
|
||||
msgNotifyUser.setContent(msgNotify.getContent());
|
||||
msgNotifyUser.setLink(msgNotify.getLink());
|
||||
msgNotifyUser.setApiModule("消息管理系统");
|
||||
msgNotifyUser.setUserId(u.getId());
|
||||
msgNotifyUser.setTitleId(msgNotify.getId());
|
||||
msgNotifyUser.setSendTime(DateUtil.getDateTime());
|
||||
@@ -249,6 +274,7 @@ public class MsgNotifyController {
|
||||
|
||||
@At
|
||||
@ViReturn
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@RequiresPermissions("msgNotify.send")
|
||||
public Object sendMsgByGroupId(@Param("msgNotify") String msgNotifyData) {
|
||||
|
||||
@@ -257,12 +283,12 @@ public class MsgNotifyController {
|
||||
msgNotify.setCreateTime(DateUtil.getDateTime());
|
||||
dao.insert(msgNotify);
|
||||
|
||||
MsgNotifyUser msgNotifyUser = new MsgNotifyUser();
|
||||
msgNotifyUser.setSendUser(ShiroUtil.getUserId());
|
||||
|
||||
List<NutMap> userScopes = msgNotifyService.getUserScopes(msgNotify.getActivityGroupId());
|
||||
msgNotifyService.sendMsg(msgNotify.getSendTypes(), userScopes, msgNotify.getContent());
|
||||
userScopes.forEach(v -> {
|
||||
// 按组发送时每位收件人使用独立实体,避免主键和人员信息被重复复用。
|
||||
MsgNotifyUser msgNotifyUser = new MsgNotifyUser();
|
||||
msgNotifyUser.setSendUser(ShiroUtil.getUserId());
|
||||
msgNotify.setSendTime(DateUtil.getDateTime());
|
||||
msgNotifyUser.setUserId(v.getString("id"));
|
||||
msgNotifyUser.setTitleId(msgNotify.getId());
|
||||
@@ -274,6 +300,7 @@ public class MsgNotifyController {
|
||||
|
||||
@At
|
||||
@ViReturn
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@RequiresPermissions("msgNotify.send")
|
||||
public Object doEdit(boolean flag, @Param("msgNotify") String msgNotifyData, @Param(value = "users", required = false) String[] users) {
|
||||
MsgNotify msgNotify = Json.fromJson(MsgNotify.class, msgNotifyData);
|
||||
@@ -285,8 +312,6 @@ public class MsgNotifyController {
|
||||
list = msgNotifyService.getUserByRoleIds(msgNotify.getModule(), msgNotify.getTeacherMeetingId(), msgNotify.getRoleIds());
|
||||
}
|
||||
|
||||
MsgNotifyUser msgNotifyUser = new MsgNotifyUser();
|
||||
msgNotifyUser.setSendUser(ShiroUtil.getUserId());
|
||||
if (msgNotify.getType() != null && msgNotify.getType().equals("system")) {
|
||||
msgNotify.setHold(flag ? true : false);
|
||||
|
||||
@@ -295,6 +320,9 @@ public class MsgNotifyController {
|
||||
|
||||
}
|
||||
list.forEach(v -> {
|
||||
// 编辑后发送时为每位系统角色人员创建独立明细。
|
||||
MsgNotifyUser msgNotifyUser = new MsgNotifyUser();
|
||||
msgNotifyUser.setSendUser(ShiroUtil.getUserId());
|
||||
if (flag) {
|
||||
msgNotify.setSendTime(DateUtil.getDateTime());
|
||||
}
|
||||
@@ -314,6 +342,9 @@ public class MsgNotifyController {
|
||||
}
|
||||
msgNotify.setHold(flag ? true : false);
|
||||
for (String u : users) {
|
||||
// 编辑后发送时为每位自定义人员创建独立明细。
|
||||
MsgNotifyUser msgNotifyUser = new MsgNotifyUser();
|
||||
msgNotifyUser.setSendUser(ShiroUtil.getUserId());
|
||||
if (flag) {
|
||||
msgNotify.setSendTime(DateUtil.getDateTime());
|
||||
}
|
||||
@@ -328,6 +359,9 @@ public class MsgNotifyController {
|
||||
msgNotifyService.sendMsg(msgNotify.getSendTypes(), userScopes, msgNotify.getContent());
|
||||
}
|
||||
userScopes.forEach(v -> {
|
||||
// 编辑后发送时为活动组中的每位人员创建独立明细。
|
||||
MsgNotifyUser msgNotifyUser = new MsgNotifyUser();
|
||||
msgNotifyUser.setSendUser(ShiroUtil.getUserId());
|
||||
if (flag) {
|
||||
msgNotify.setSendTime(DateUtil.getDateTime());
|
||||
}
|
||||
@@ -342,7 +376,6 @@ public class MsgNotifyController {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresAuthentication
|
||||
|
||||
@@ -69,8 +69,8 @@ public enum ProposalToDoHandler {
|
||||
List.of(loginName),
|
||||
"/platform/proposal/transact/seconded",
|
||||
"/platform/proposal/transact/seconded",
|
||||
"/platform/proposal/transact/seconded",
|
||||
"/platform/proposal/transact/seconded");
|
||||
"/mobile/proposal/seconded",
|
||||
"/mobile/proposal/seconded");
|
||||
}
|
||||
localProcessService.updateProcessNodeName("proposal_info@" + proposalId, "邀请附议人");
|
||||
}
|
||||
|
||||
+2
-24
@@ -4,18 +4,16 @@ import cn.hutool.core.util.StrUtil;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.sys.services.SysLocalProcessService;
|
||||
import io.v.nutz.sys.models.Sys_signature;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalAudit;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalSearch;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalAuditService;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalCaseService;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalInfoService;
|
||||
import io.v.nutz.sys.models.Sys_signature;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
@@ -73,27 +71,7 @@ public class ProposalCaseController {
|
||||
@ViReturn
|
||||
@RequiresPermissions(value = {"proposal.transact.case", "offerAdvice.schoolUnionAudit"}, logical = Logical.OR)
|
||||
public Object getUnderTakeAndLeader() {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
pu.*,
|
||||
(
|
||||
SELECT
|
||||
GROUP_CONCAT( su.username )
|
||||
FROM
|
||||
sys_user_role sur
|
||||
LEFT JOIN sys_user su ON sur.userId = su.id
|
||||
WHERE
|
||||
sur.cbdwid = pu.id
|
||||
AND sur.roleId = '153cba3fb88b4c88b7c1002f0eb63749'
|
||||
) leader
|
||||
FROM
|
||||
proposal_undertake pu
|
||||
WHERE
|
||||
pu.isEnable = 1
|
||||
ORDER BY
|
||||
pu.unitCode ASC
|
||||
""");
|
||||
return proposalInfoService.listMap(sql);
|
||||
return proposalCaseService.getUnderTakeAndLeader();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-109
@@ -1,38 +1,23 @@
|
||||
package io.v.nutz.zhgh.proposal.controller.transact;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.wizzer.framework.page.Pagination;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.base.utils.MsgApi;
|
||||
import io.v.nutz.base.utils.Roles;
|
||||
import io.v.nutz.base.dao.CndPlus;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.zhgh.proposal.constants.ProposalConstant;
|
||||
import io.v.nutz.zhgh.proposal.constants.ProposalState;
|
||||
import io.v.nutz.zhgh.proposal.constants.ProposalStateDirectionEnum;
|
||||
import io.v.nutz.zhgh.proposal.constants.ProposalToDoHandler;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalAudit;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalInfo;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalSearch;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalCaseReadService;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalInfoService;
|
||||
import io.v.nutz.sys.models.Sys_signature;
|
||||
import io.v.nutz.sys.models.Sys_user_role;
|
||||
import io.v.nutz.sys.models.User;
|
||||
import io.v.nutz.sys.services.SysSignatureService;
|
||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalProcessService;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.Static;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
@@ -51,6 +36,8 @@ public class ProposalCaseReadController {
|
||||
private ProposalInfoService proposalInfoService;
|
||||
@Inject
|
||||
private ProposalProcessService proposalProcessService;
|
||||
@Inject
|
||||
private ProposalCaseReadService proposalCaseReadService;
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@@ -65,73 +52,7 @@ public class ProposalCaseReadController {
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.caseRead")
|
||||
public Object pageData(PageForm page, ProposalSearch search) {
|
||||
CndPlus cnd = CndPlus.create();
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
info.*,
|
||||
su.username,
|
||||
su.loginname,
|
||||
state.stateName,
|
||||
state.stateColor,
|
||||
type.typeCode,
|
||||
type.typeName,
|
||||
jdh.jdhallname meetingName,
|
||||
dbt.dbtname delegationName,
|
||||
dbt.`code` delegationCode,
|
||||
manner.`name` mannerName,
|
||||
result.`name` resultName,
|
||||
$temp_sql
|
||||
FROM
|
||||
proposal_info info
|
||||
LEFT JOIN `user` su ON su.id = info.createUser
|
||||
LEFT JOIN proposal_state state ON state.stateCode = info.stateCode
|
||||
LEFT JOIN proposal_type type ON type.id = info.typeId
|
||||
LEFT JOIN jdh_jdhxx jdh ON jdh.id = info.teacherMeetingId
|
||||
LEFT JOIN jdh_dbt dbt ON dbt.id = info.delegationId
|
||||
LEFT JOIN sys_dict manner ON manner.`code` = info.mannerCode
|
||||
LEFT JOIN sys_dict result ON result.`code` = info.resultCode
|
||||
$condition
|
||||
""").setParam("userId", ShiroUtil.getPrincipalProperty("id"));
|
||||
|
||||
|
||||
/* StringBuffer tempStr = new StringBuffer("""
|
||||
(JSON_CONTAINS(JSON_EXTRACT( membersOpinions, '$[*].userId' ), '\\"%s\\"') = 1) AS isOpinion
|
||||
""".formatted(ShiroUtil.getUserId()));
|
||||
sql.setVar("temp_sql", tempStr);*/
|
||||
|
||||
StringBuffer tempStr = new StringBuffer("""
|
||||
FIND_IN_SET(( SELECT id FROM proposal_audit WHERE proposalId = info.id AND userId = '%s' AND other IS NOT NULL limit 1), info.membersOpinions )is not null isOpinion
|
||||
""".formatted(ShiroUtil.getUserId()));
|
||||
sql.setVar("temp_sql", tempStr);
|
||||
|
||||
if (Strings.isNotBlank(search.getIsAudit())) {
|
||||
if (search.getIsAudit().equals("true")) {
|
||||
cnd.and(new Static("FIND_IN_SET(( SELECT id FROM proposal_audit WHERE proposalId = info.id AND userId = '%s' AND other IS NOT NULL limit 1), info.membersOpinions )is not null".formatted(ShiroUtil.getUserId())));
|
||||
// cnd.and(new Static("JSON_CONTAINS(JSON_EXTRACT( info.membersOpinions, '$[*].userId' ), '\"%s\"') = 1".formatted(ShiroUtil.getUserId())));
|
||||
} else {
|
||||
cnd.and(new Static("FIND_IN_SET(( SELECT id FROM proposal_audit WHERE proposalId = info.id AND userId = '%s' AND other IS NOT NULL limit 1), info.membersOpinions ) is null".formatted(ShiroUtil.getUserId())));
|
||||
|
||||
// cnd.and(new Static("(JSON_CONTAINS(JSON_EXTRACT( info.membersOpinions, '$[*].userId' ), '\"%s\"') = 0 or JSON_CONTAINS(JSON_EXTRACT( info.membersOpinions, '$[*].userId' ), '\"%s\"') is null)".formatted(ShiroUtil.getUserId(), ShiroUtil.getUserId())));
|
||||
}
|
||||
}
|
||||
cnd.and("info.stateCode", ">", ProposalState.DELEGATION);
|
||||
cnd.asc("info.stateCode");
|
||||
// proposalInfoService.sortAndSearch(cnd,page,search);
|
||||
Pagination pagination = (Pagination) proposalInfoService.initSql(sql, page, search, cnd);
|
||||
|
||||
// List<NutMap> list = pagination.getList();
|
||||
// for (NutMap map : list) {
|
||||
// if (StrUtil.isNotBlank(search.getIsAudit()) && "true".equals(search.getIsAudit())) {
|
||||
// String membersOpinions = map.getString("membersOpinions");
|
||||
// List<String> mlist = Json.fromJsonAsList(String.class, membersOpinions);
|
||||
// map.putAll(getCommitteeOpinion(mlist));
|
||||
// } else {
|
||||
// map.putAll(NutMap.NEW().addv("caseNum", "暂无")
|
||||
// .addv("opinionNum", "暂无").addv("notGiveNum", "暂无"));
|
||||
// }
|
||||
// }
|
||||
// pagination.setList(list);
|
||||
return pagination;
|
||||
return proposalCaseReadService.pageData(page, search);
|
||||
}
|
||||
|
||||
|
||||
@@ -153,9 +74,6 @@ public class ProposalCaseReadController {
|
||||
}
|
||||
|
||||
|
||||
@Inject
|
||||
private SysSignatureService sysSignatureService;
|
||||
|
||||
/**
|
||||
* 委员会成员意见
|
||||
*
|
||||
@@ -167,29 +85,7 @@ public class ProposalCaseReadController {
|
||||
@ViReturn
|
||||
@RequiresPermissions("proposal.transact.caseRead")
|
||||
public Object putSuggestion(String sign, @Param("audit") String audit, @Param("proposalId") String proposalId) {
|
||||
ProposalInfo proposalInfo = proposalInfoService.fetch(proposalId);
|
||||
String signId = sysSignatureService.insert(new Sys_signature(sign)).getId();
|
||||
ProposalAudit proposalAudit = Json.fromJson(ProposalAudit.class, audit);
|
||||
proposalAudit.setSignId(signId);
|
||||
proposalAudit.setUserId(ShiroUtil.getUserId());
|
||||
if (!proposalAudit.getOther().equals(ProposalConstant.DETERMINE)) {
|
||||
proposalAudit.setDetermineTypeCode(null);
|
||||
}
|
||||
|
||||
dao.insert(proposalAudit);
|
||||
|
||||
/*List<NutMap> membersOpinions = proposalInfo.getMembersOpinions() == null ? new ArrayList<>() : proposalInfo.getMembersOpinions();
|
||||
membersOpinions.add(NutMap.NEW().addv("userId", ShiroUtil.getUserId()).addv("auditId", insertAudit.getId()));
|
||||
proposalInfo.setMembersOpinions(membersOpinions);*/
|
||||
List<String> membersOpinions = proposalInfo.getMembersOpinions() == null ? new ArrayList<>() : proposalInfo.getMembersOpinions();
|
||||
membersOpinions.add(proposalAudit.getId());
|
||||
proposalInfo.setMembersOpinions(membersOpinions);
|
||||
proposalInfoService.updateIgnoreNull(proposalInfo);
|
||||
// // 提案小组组长审核后,修改提案状态之后,流程开始往下走
|
||||
// proposalInfo.setStateCode(proposalProcessService.getStateCode(proposalId, ProposalStateDirectionEnum.NEXT, false));
|
||||
// ProposalToDoHandler.COMPLETE_PROPOSAL_TEAM_LEADER_REVIEW_TASK.exec(proposalInfo.getId(), NutMap.NEW().setv("opinion", "提案工作组组长审阅完成"));
|
||||
// // 创建校党委审议待办
|
||||
// ProposalToDoHandler.CREATE_CASE_TASK.exec(proposalInfo.getId(), NutMap.NEW().setv("opinion", "提案工作组组长审阅完成"));
|
||||
proposalCaseReadService.putSuggestion(sign, audit, proposalId);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.v.nutz.zhgh.proposal.services;
|
||||
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalSearch;
|
||||
|
||||
/**
|
||||
* 提案工作组成员意见审核公共服务。
|
||||
*/
|
||||
public interface ProposalCaseReadService {
|
||||
|
||||
/**
|
||||
* 查询提案工作组成员意见列表。
|
||||
*
|
||||
* @param page 分页参数,包含 pageNumber、pageSize、排序字段等
|
||||
* @param search 查询参数,meetingId 为届次、proposalTypeId 为提案类型、proposalResultCode 为立案结果、isAudit 表示是否已提交成员意见
|
||||
* @return 分页结果,list 中包含提案基础信息、状态、类型、届次、代表团、立案结果以及当前用户是否已发表意见 isOpinion
|
||||
*/
|
||||
Object pageData(PageForm page, ProposalSearch search);
|
||||
|
||||
/**
|
||||
* 保存提案工作组成员意见。
|
||||
*
|
||||
* @param sign 签名图片数据,保存后关联到审核记录
|
||||
* @param audit ProposalAudit 的 JSON 字符串,包含审核人、审核时间、审核意见、立案意见等字段
|
||||
* @param proposalId 提案 id
|
||||
*/
|
||||
void putSuggestion(String sign, String audit, String proposalId);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalAudit;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalSearch;
|
||||
import io.v.nutz.base.service.ViService;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -23,6 +24,13 @@ public interface ProposalCaseService extends ViService {
|
||||
*/
|
||||
Pagination pageData(PageForm pageForm, ProposalSearch search);
|
||||
|
||||
/**
|
||||
* 获取开启的承办单位及单位领导信息。
|
||||
*
|
||||
* @return 承办单位列表,包含承办单位基础字段以及 leader 领导姓名串
|
||||
*/
|
||||
List<NutMap> getUnderTakeAndLeader();
|
||||
|
||||
|
||||
/**
|
||||
* 立案
|
||||
|
||||
@@ -1,13 +1,42 @@
|
||||
package io.v.nutz.zhgh.proposal.services;
|
||||
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalFeedback;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalSearch;
|
||||
import io.v.nutz.base.service.ViService;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import cn.wizzer.framework.page.Pagination;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ProposalFeedBackService extends ViService<ProposalFeedback> {
|
||||
|
||||
/**
|
||||
* 手机端反馈评价列表。
|
||||
* 参数说明:
|
||||
* page:分页参数,包含 pageNumber、pageSize、searchKeyWord 等;
|
||||
* search:筛选参数,meetingId 为届次、proposalTypeId 为提案类型、proposalResultCode 为立案结果、isAudit 表示是否已反馈;
|
||||
* 返回值:分页结果,list 中为 NutMap,包含提案基础字段和 isFeedback。
|
||||
*/
|
||||
Pagination pageData(PageForm page, ProposalSearch search);
|
||||
|
||||
/**
|
||||
* 查询手机端反馈办理状态。
|
||||
* 参数说明:
|
||||
* proposalId:proposal_info 表主键;
|
||||
* 返回值:NutMap,包含 stateCode 和 handle,handle 为 true 时手机端展示反馈表单。
|
||||
*/
|
||||
NutMap getFeedbackInfo(String proposalId);
|
||||
|
||||
/**
|
||||
* 提交提案反馈评价。
|
||||
* 参数说明:
|
||||
* proposalFeedback:反馈实体,必须包含 proposalId、feedbackCode、feedbackOpinion 等反馈内容;
|
||||
* scoreSign:反馈人签名图片数据,可为空;
|
||||
* 返回值:无业务数据,成功后更新提案流程状态并处理待办。
|
||||
*/
|
||||
void doFeedback(ProposalFeedback proposalFeedback, String scoreSign);
|
||||
|
||||
/**
|
||||
* @param proposalId 提案id
|
||||
* @return
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package io.v.nutz.zhgh.proposal.services.impl;
|
||||
|
||||
import cn.wizzer.framework.page.Pagination;
|
||||
import io.v.nutz.base.dao.CndPlus;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.zhgh.proposal.constants.ProposalConstant;
|
||||
import io.v.nutz.zhgh.proposal.constants.ProposalState;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalAudit;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalInfo;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalSearch;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalCaseReadService;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalInfoService;
|
||||
import io.v.nutz.sys.models.Sys_signature;
|
||||
import io.v.nutz.sys.services.SysSignatureService;
|
||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.Static;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.Strings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 提案工作组成员意见审核公共服务实现。
|
||||
*/
|
||||
@IocBean
|
||||
public class ProposalCaseReadServiceImpl implements ProposalCaseReadService {
|
||||
|
||||
@Inject
|
||||
private ProposalInfoService proposalInfoService;
|
||||
|
||||
@Inject
|
||||
private SysSignatureService sysSignatureService;
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
@Override
|
||||
public Object pageData(PageForm page, ProposalSearch search) {
|
||||
CndPlus cnd = CndPlus.create();
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
info.*,
|
||||
su.username,
|
||||
su.loginname,
|
||||
state.stateName,
|
||||
state.stateColor,
|
||||
type.typeCode,
|
||||
type.typeName,
|
||||
jdh.jdhallname meetingName,
|
||||
dbt.dbtname delegationName,
|
||||
dbt.`code` delegationCode,
|
||||
manner.`name` mannerName,
|
||||
result.`name` resultName,
|
||||
$temp_sql
|
||||
FROM
|
||||
proposal_info info
|
||||
LEFT JOIN `user` su ON su.id = info.createUser
|
||||
LEFT JOIN proposal_state state ON state.stateCode = info.stateCode
|
||||
LEFT JOIN proposal_type type ON type.id = info.typeId
|
||||
LEFT JOIN jdh_jdhxx jdh ON jdh.id = info.teacherMeetingId
|
||||
LEFT JOIN jdh_dbt dbt ON dbt.id = info.delegationId
|
||||
LEFT JOIN sys_dict manner ON manner.`code` = info.mannerCode
|
||||
LEFT JOIN sys_dict result ON result.`code` = info.resultCode
|
||||
$condition
|
||||
""").setParam("userId", ShiroUtil.getPrincipalProperty("id"));
|
||||
|
||||
// 与 PC 端原查询保持一致:当前用户的 proposal_audit 记录必须存在于 membersOpinions 中才算已提交成员意见。
|
||||
String userId = ShiroUtil.getUserId();
|
||||
sql.setVar("temp_sql", """
|
||||
FIND_IN_SET(( SELECT id FROM proposal_audit WHERE proposalId = info.id AND userId = '%s' AND other IS NOT NULL limit 1), info.membersOpinions )is not null isOpinion
|
||||
""".formatted(userId));
|
||||
|
||||
if (Strings.isNotBlank(search.getIsAudit())) {
|
||||
if ("true".equals(search.getIsAudit())) {
|
||||
cnd.and(new Static("FIND_IN_SET(( SELECT id FROM proposal_audit WHERE proposalId = info.id AND userId = '%s' AND other IS NOT NULL limit 1), info.membersOpinions )is not null".formatted(userId)));
|
||||
} else {
|
||||
cnd.and(new Static("FIND_IN_SET(( SELECT id FROM proposal_audit WHERE proposalId = info.id AND userId = '%s' AND other IS NOT NULL limit 1), info.membersOpinions ) is null".formatted(userId)));
|
||||
}
|
||||
}
|
||||
cnd.and("info.stateCode", ">", ProposalState.DELEGATION);
|
||||
cnd.asc("info.stateCode");
|
||||
Pagination pagination = (Pagination) proposalInfoService.initSql(sql, page, search, cnd);
|
||||
return pagination;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存提案工作组成员意见,并把审核记录 id 追加到提案 membersOpinions。
|
||||
* sign 为签名图片数据,audit 为 ProposalAudit JSON,proposalId 为提案 id;
|
||||
* audit.other 表示立案意见,非确定立案时会清空 determineTypeCode,避免残留立案类型。
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void putSuggestion(String sign, String audit, String proposalId) {
|
||||
ProposalInfo proposalInfo = proposalInfoService.fetch(proposalId);
|
||||
String signId = sysSignatureService.insert(new Sys_signature(sign)).getId();
|
||||
ProposalAudit proposalAudit = Json.fromJson(ProposalAudit.class, audit);
|
||||
proposalAudit.setSignId(signId);
|
||||
proposalAudit.setUserId(ShiroUtil.getUserId());
|
||||
if (!proposalAudit.getOther().equals(ProposalConstant.DETERMINE)) {
|
||||
proposalAudit.setDetermineTypeCode(null);
|
||||
}
|
||||
|
||||
dao.insert(proposalAudit);
|
||||
|
||||
// membersOpinions 保存审核记录 id,保持 PC 端和手机端列表判断、详情展示一致。
|
||||
List<String> membersOpinions = proposalInfo.getMembersOpinions() == null ? new ArrayList<>() : proposalInfo.getMembersOpinions();
|
||||
membersOpinions.add(proposalAudit.getId());
|
||||
proposalInfo.setMembersOpinions(membersOpinions);
|
||||
proposalInfoService.updateIgnoreNull(proposalInfo);
|
||||
}
|
||||
}
|
||||
@@ -117,6 +117,31 @@ public class ProposalCaseServiceImpl extends ViServiceImpl implements ProposalCa
|
||||
return pagination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NutMap> getUnderTakeAndLeader() {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
pu.*,
|
||||
(
|
||||
SELECT
|
||||
GROUP_CONCAT( su.username )
|
||||
FROM
|
||||
sys_user_role sur
|
||||
LEFT JOIN sys_user su ON sur.userId = su.id
|
||||
WHERE
|
||||
sur.cbdwid = pu.id
|
||||
AND sur.roleId = '153cba3fb88b4c88b7c1002f0eb63749'
|
||||
) leader
|
||||
FROM
|
||||
proposal_undertake pu
|
||||
WHERE
|
||||
pu.isEnable = 1
|
||||
ORDER BY
|
||||
pu.unitCode ASC
|
||||
""");
|
||||
return listMap(sql);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void audit(List<String> proposalIds, String resultCode, String determineTypeCode, String hostUnit, List<String> helpUnits, String typeId, ProposalAudit audit, String auditType, int flag) {
|
||||
|
||||
@@ -1,13 +1,31 @@
|
||||
package io.v.nutz.zhgh.proposal.services.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.wizzer.framework.page.Pagination;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalFeedback;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalInfo;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalReply;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalSearch;
|
||||
import io.v.nutz.zhgh.proposal.models.ProposalUndertake;
|
||||
import io.v.nutz.zhgh.proposal.constants.ProposalConstant;
|
||||
import io.v.nutz.zhgh.proposal.constants.ProposalState;
|
||||
import io.v.nutz.zhgh.proposal.constants.ProposalToDoHandler;
|
||||
import io.v.nutz.zhgh.proposal.services.ProposalFeedBackService;
|
||||
import io.v.nutz.base.service.impl.ViServiceImpl;
|
||||
import io.v.nutz.sys.models.Sys_signature;
|
||||
import io.v.nutz.sys.services.SysSignatureService;
|
||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||
import io.v.nutz.base.utils.DateUtil;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.Lang;
|
||||
@@ -23,6 +41,136 @@ public class ProposalFeedBackServiceImpl extends ViServiceImpl<ProposalFeedback>
|
||||
super(dao);
|
||||
}
|
||||
|
||||
@Inject
|
||||
private SysSignatureService sysSignatureService;
|
||||
|
||||
@Override
|
||||
public Pagination pageData(PageForm page, ProposalSearch search) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
info.*,
|
||||
su.username,
|
||||
su.loginname,
|
||||
state.stateName,
|
||||
state.stateColor,
|
||||
type.typeName,
|
||||
jdh.jdhallname meetingName,
|
||||
dbt.dbtname delegationName,
|
||||
manner.`name` mannerName,
|
||||
result.`name` resultName,
|
||||
(
|
||||
SELECT count(1) > 0
|
||||
FROM proposal_conjoin
|
||||
WHERE JSON_CONTAINS(proposalIds, JSON_QUOTE(info.id))
|
||||
) AS isConjoin,
|
||||
(
|
||||
SELECT count(1)
|
||||
FROM proposal_feedback pf
|
||||
WHERE pf.proposalId = info.id
|
||||
) AS feedbackCount
|
||||
FROM
|
||||
proposal_info info
|
||||
LEFT JOIN `user` su ON su.id = info.createUser
|
||||
LEFT JOIN proposal_state state ON state.stateCode = info.stateCode
|
||||
LEFT JOIN proposal_type type ON type.id = info.typeId
|
||||
LEFT JOIN jdh_jdhxx jdh ON jdh.id = info.teacherMeetingId
|
||||
LEFT JOIN jdh_dbt dbt ON dbt.id = info.delegationId
|
||||
LEFT JOIN sys_dict manner ON manner.`code` = info.mannerCode
|
||||
LEFT JOIN sys_dict result ON result.`code` = info.resultCode
|
||||
$condition
|
||||
""");
|
||||
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if (!ShiroUtil.hasAnyRoles("sysadmin, tamange,A06")) {
|
||||
cnd.and("info.createUser", "=", ShiroUtil.getUserId());
|
||||
}
|
||||
if (StrUtil.isNotBlank(search.getSearchKeyWord())) {
|
||||
SqlExpressionGroup keywordGroup = new SqlExpressionGroup();
|
||||
keywordGroup.orLike("info.proposalName", search.getSearchKeyWord());
|
||||
keywordGroup.orLike("info.proposalCode", search.getSearchKeyWord());
|
||||
cnd.and(keywordGroup);
|
||||
}
|
||||
cnd.andEX("info.teacherMeetingId", "=", search.getMeetingId());
|
||||
cnd.andEX("info.typeId", "=", search.getProposalTypeId());
|
||||
cnd.andEX("info.resultCode", "=", search.getProposalResultCode());
|
||||
|
||||
if (StrUtil.isBlank(search.getIsAudit())) {
|
||||
SqlExpressionGroup allGroup = new SqlExpressionGroup();
|
||||
allGroup.or("info.stateCode", "=", ProposalState.FEEDBACKSCORE);
|
||||
allGroup.or("info.stateCode", ">", ProposalState.FEEDBACKSCORE);
|
||||
allGroup.orGT("(SELECT count(1) FROM proposal_feedback pf WHERE pf.proposalId = info.id)", 0);
|
||||
cnd.and(allGroup);
|
||||
} else if ("true".equals(search.getIsAudit())) {
|
||||
SqlExpressionGroup feedbackGroup = new SqlExpressionGroup();
|
||||
feedbackGroup.or("info.stateCode", ">", ProposalState.FEEDBACKSCORE);
|
||||
feedbackGroup.orGT("(SELECT count(1) FROM proposal_feedback pf WHERE pf.proposalId = info.id)", 0);
|
||||
cnd.and(feedbackGroup);
|
||||
} else if ("false".equals(search.getIsAudit())) {
|
||||
cnd.and("info.stateCode", "=", ProposalState.FEEDBACKSCORE);
|
||||
}
|
||||
cnd.asc("info.proposalCode").desc("info.createTime");
|
||||
sql.setCondition(cnd);
|
||||
|
||||
Pagination pagination = listPageMap(page.getPageNumber(), page.getPageSize(), sql);
|
||||
List<NutMap> list = pagination.getList(NutMap.class);
|
||||
list.forEach(v -> {
|
||||
int feedbackCount = v.getInt("feedbackCount", 0);
|
||||
Integer stateCode = v.getInt("stateCode");
|
||||
v.setv("isFeedback", feedbackCount > 0 || (stateCode != null && stateCode > ProposalState.FEEDBACKSCORE));
|
||||
});
|
||||
pagination.setList(list);
|
||||
return pagination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NutMap getFeedbackInfo(String proposalId) {
|
||||
ProposalInfo proposalInfo = dao().fetch(ProposalInfo.class, Cnd.where("id", "=", proposalId));
|
||||
return NutMap.NEW()
|
||||
.addv("proposalId", proposalId)
|
||||
.addv("stateCode", proposalInfo == null ? null : proposalInfo.getStateCode())
|
||||
.addv("handle", proposalInfo != null && proposalInfo.getStateCode() != null && proposalInfo.getStateCode().equals(ProposalState.FEEDBACKSCORE));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void doFeedback(ProposalFeedback proposalFeedback, String scoreSign) {
|
||||
if (StrUtil.isNotBlank(scoreSign)) {
|
||||
Sys_signature signature = sysSignatureService.insert(new Sys_signature(scoreSign));
|
||||
proposalFeedback.setScoreSignId(signature.getId());
|
||||
}
|
||||
|
||||
int feedbackCount = count(Cnd.where("proposalId", "=", proposalFeedback.getProposalId()));
|
||||
proposalFeedback.setFeedbackPerson(ShiroUtil.getUserId());
|
||||
proposalFeedback.setFeedbackNumber(feedbackCount + 1);
|
||||
proposalFeedback.setFeedbackTime(DateUtil.getDateTime());
|
||||
insert(proposalFeedback);
|
||||
|
||||
ProposalToDoHandler.COMPLETE_FEEDBACK_TASK.exec(proposalFeedback.getProposalId(), NutMap.NEW().addv("opinion", proposalFeedback.getFeedbackOpinion()));
|
||||
|
||||
ProposalReply lastReply = dao().fetch(ProposalReply.class, Cnd.where("proposalId", "=", proposalFeedback.getProposalId()).and("undertakeType", "=", 1));
|
||||
if (lastReply == null) {
|
||||
throw new RuntimeException("未找到主办单位办理记录,无法提交反馈评价");
|
||||
}
|
||||
// 不满意时需要主办单位二次答复,其他评价结果直接结束当前提案流程。
|
||||
if (ProposalConstant.NOT_SATISFIED.equals(proposalFeedback.getFeedbackCode())) {
|
||||
dao().update(ProposalInfo.class, Chain.make("stateCode", ProposalState.UNITREPLY), Cnd.where("id", "=", proposalFeedback.getProposalId()));
|
||||
|
||||
ProposalReply newReply = new ProposalReply();
|
||||
newReply.setProposalId(lastReply.getProposalId());
|
||||
newReply.setUndertakeType(lastReply.getUndertakeType());
|
||||
newReply.setReplyUnitId(lastReply.getReplyUnitId());
|
||||
newReply.setReplyNumber(lastReply.getReplyNumber() + 1);
|
||||
newReply.setIsReply(false);
|
||||
newReply.setIsLeaderAudit(false);
|
||||
dao().insert(newReply);
|
||||
|
||||
ProposalToDoHandler.CREATE_UNDERTAKE_TASK.exec(proposalFeedback.getProposalId(), null);
|
||||
} else {
|
||||
dao().update(ProposalInfo.class, Chain.make("stateCode", ProposalState.FINISH), Cnd.where("id", "=", lastReply.getProposalId()));
|
||||
ProposalToDoHandler.COMPLETE_PROCESS.exec(lastReply.getProposalId(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NutMap> getFeedbackList(String proposalId) {
|
||||
Sql sql = Sqls.create("""
|
||||
|
||||
+2
-1
@@ -109,6 +109,7 @@ public class MemberAgendaController {
|
||||
|
||||
Sys_user user = (Sys_user) ShiroUtil.getPrincipal();
|
||||
List<Sys_menu> menus = Optional.ofNullable(user).orElse(new Sys_user()).getMenus();
|
||||
return list.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").contains(x.getHref()))).distinct().collect(Collectors.toList());
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return list.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").contains(x.getHref()))).filter(v -> io.v.nutz.sys.services.TodoAccessService.canAccess(v.getString("url"))).distinct().collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -56,7 +56,8 @@ public class MemberSelfAgentController {
|
||||
.addv("mobileHref", "/mobile/member/edit?taskId=" + v.getId()));
|
||||
});
|
||||
}
|
||||
return list;
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return io.v.nutz.sys.services.TodoAccessService.filter(list, "mobileHref");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import io.v.nutz.zhgh.staffmanage.member.model.MemberApplyRecord;
|
||||
import io.v.nutz.zhgh.staffmanage.member.service.MemberCommonService;
|
||||
import io.v.nutz.zhgh.staffmanage.member.service.impl.MemberCommonServiceImpl;
|
||||
import lombok.Getter;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
@@ -24,6 +26,7 @@ import java.util.stream.Collectors;
|
||||
* @name:MemberApplyController
|
||||
* @Date 2025/1/20 19:01
|
||||
* @注释 会员申请流程
|
||||
* 手机处理和查看地址必须指向 /h5;PC 页面依赖不能在手机 PJAX 容器内加载。
|
||||
*/
|
||||
|
||||
@Getter
|
||||
@@ -34,6 +37,7 @@ public enum MemberApplyToDoHandler {
|
||||
*/
|
||||
START_PROCESS() {
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void exec(MemberApplyRecord record, NutMap extra) {
|
||||
User user = dao.fetch(User.class, Cnd.where("id", "=", record.getUserId()));
|
||||
localProcessService.startProcess(
|
||||
@@ -42,7 +46,7 @@ public enum MemberApplyToDoHandler {
|
||||
"",
|
||||
record.getUserId(),
|
||||
"/platform/member/apply/mine",
|
||||
"/platform/member/apply/mine"
|
||||
"/platform/member/apply/mine/h5"
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -53,6 +57,7 @@ public enum MemberApplyToDoHandler {
|
||||
*/
|
||||
CREATE_APPLY_RE_MODIFY_TASK() {
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void exec(MemberApplyRecord record, NutMap extra) {
|
||||
Sql sql = Sqls.fetchString(Sqls.create("select loginname from sys_user where id = @id").setParam("id", record.getUserId()).toString());
|
||||
dao.execute(sql);
|
||||
@@ -66,8 +71,8 @@ public enum MemberApplyToDoHandler {
|
||||
List.of(loginname),
|
||||
"/platform/member/apply/mine",
|
||||
"/platform/member/apply/mine",
|
||||
"/platform/member/apply/mine",
|
||||
"/platform/member/apply/mine"
|
||||
"/platform/member/apply/mine/h5",
|
||||
"/platform/member/apply/mine/h5"
|
||||
);
|
||||
|
||||
localProcessService.updateProcessNodeName("MEMBER_APPLY@" + record.getId(), "退回重新修改");
|
||||
@@ -98,6 +103,7 @@ public enum MemberApplyToDoHandler {
|
||||
*/
|
||||
CREATE_SCHOOL_TASK() {
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void exec(MemberApplyRecord record, NutMap extra) {
|
||||
// 获取校工会会员管理员
|
||||
List<String> schoolLeaderLoginNames = memberService.getSchoolOrBranchUnionMemberAdminLoginNames("school", record.getUserId());
|
||||
@@ -109,8 +115,8 @@ public enum MemberApplyToDoHandler {
|
||||
schoolLeaderLoginNames,
|
||||
"/platform/member/apply/schoolUnion/audit",
|
||||
"/platform/member/apply/schoolUnion/audit",
|
||||
"/platform/member/apply/schoolUnion/audit",
|
||||
"/platform/member/apply/schoolUnion/audit"
|
||||
"/platform/member/apply/schoolUnion/audit/h5",
|
||||
"/platform/member/apply/schoolUnion/audit/h5"
|
||||
);
|
||||
|
||||
localProcessService.updateProcessNodeName("MEMBER_APPLY@" + record.getId(), "校工会审核");
|
||||
@@ -141,6 +147,7 @@ public enum MemberApplyToDoHandler {
|
||||
*/
|
||||
CREATE_UNION_TASK() {
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void exec(MemberApplyRecord record, NutMap extra) {
|
||||
List<String> unionLeaderLoginNames = extra.getAsList("unionLeaderLoginNames", String.class);
|
||||
localProcessService.createTask(
|
||||
@@ -151,8 +158,8 @@ public enum MemberApplyToDoHandler {
|
||||
unionLeaderLoginNames,
|
||||
"/platform/member/apply/branchUnion/audit",
|
||||
"/platform/member/apply/branchUnion/audit",
|
||||
"/platform/member/apply/branchUnion/audit",
|
||||
"/platform/member/apply/branchUnion/audit"
|
||||
"/platform/member/apply/branchUnion/audit/h5",
|
||||
"/platform/member/apply/branchUnion/audit/h5"
|
||||
);
|
||||
localProcessService.updateProcessNodeName("MEMBER_APPLY@" + record.getId(), "分工会审核");
|
||||
}
|
||||
|
||||
+8
-1
@@ -248,12 +248,19 @@ public class TheRapyRecuperationEnrollController {
|
||||
@RequiresAuthentication
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object doSignUpForBaseManagement(@Param("enroll") TheRapyRecuperationEnroll enrollInfo) {
|
||||
// 行锁会持续到当前事务结束,保证同一目的地的人数校验与保存不会并发执行。
|
||||
enrollService.lockBaseManagementForSignUp(enrollInfo.getTakePartInBaseManagementId());
|
||||
Sys_config config = dao.fetch(Sys_config.class, Cnd.where("configKey", "=", "recuperationVersion"));
|
||||
Map<Boolean, String> resultMap = new HashMap<>();
|
||||
if("zjxu".equals(config.getConfigValue())) {
|
||||
resultMap = enrollService.validSignUpInfoForZJXU((String) ShiroUtil.getPrincipalProperty("loginname"), enrollInfo);
|
||||
} else {
|
||||
} else if("zjiet".equals(config.getConfigValue())) {
|
||||
resultMap = enrollService.validSignUpInfo((String) ShiroUtil.getPrincipalProperty("loginname"), enrollInfo);
|
||||
} else if("zjnu".equals(config.getConfigValue())) {
|
||||
resultMap = enrollService.validSignUpInfoForZJNU((String) ShiroUtil.getPrincipalProperty("loginname"), enrollInfo);
|
||||
} else if("hmc".equals(config.getConfigValue())) {
|
||||
// HMC酒店提交必须在行锁范围内重新校验名额,避免并发报名超过酒店最大人数。
|
||||
resultMap = enrollService.validSignUpInfoForHMC((String) ShiroUtil.getPrincipalProperty("loginname"), enrollInfo);
|
||||
}
|
||||
if (resultMap.containsKey(false)) {
|
||||
return Result.error(resultMap.get(false));
|
||||
|
||||
+5
@@ -117,6 +117,11 @@ public class TheRapyRecuperationBaseManagement {
|
||||
@Excel(name = "预计费用")
|
||||
private String estimatedCost;
|
||||
|
||||
@Column
|
||||
@ColDefine(type = ColType.INT)
|
||||
@Comment("最大报名人数")
|
||||
private Integer maxApplyNum;
|
||||
|
||||
@Column
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
@Comment("缩略图")
|
||||
|
||||
+7
@@ -64,6 +64,13 @@ public interface TheRapyRecuperationEnrollService extends ViService<TheRapyRecup
|
||||
*/
|
||||
void doSignUpForHotel(TheRapyRecuperationEnroll enrollInfo);
|
||||
|
||||
/**
|
||||
* 在当前报名事务中锁定目的地记录,确保同一目的地的人数校验和保存串行执行。
|
||||
*
|
||||
* @param baseManagementId 目的地ID
|
||||
*/
|
||||
void lockBaseManagementForSignUp(String baseManagementId);
|
||||
|
||||
void updateSignUpHotel(TheRapyRecuperationEnroll enrollInfo);
|
||||
|
||||
/**
|
||||
|
||||
+52
-1
@@ -434,6 +434,19 @@ public class TheRapyRecuperationEnrollServiceImpl extends ViServiceImpl<TheRapyR
|
||||
insertWith(enrollInfo, "companionList");
|
||||
}
|
||||
|
||||
/**
|
||||
* 在当前报名事务中锁定目的地记录。
|
||||
*
|
||||
* @param baseManagementId 目的地ID,用于锁定本次报名对应的目的地
|
||||
*/
|
||||
@Override
|
||||
public void lockBaseManagementForSignUp(String baseManagementId) {
|
||||
Sql sql = Sqls.create("SELECT id FROM the_rapy_recuperation_base_management WHERE id = @baseManagementId FOR UPDATE")
|
||||
.setParam("baseManagementId", baseManagementId);
|
||||
sql.setCallback(Sqls.callback.str());
|
||||
dao().execute(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 变更报名
|
||||
*
|
||||
@@ -467,6 +480,35 @@ public class TheRapyRecuperationEnrollServiceImpl extends ViServiceImpl<TheRapyR
|
||||
updateIgnoreNull(enrollInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验定点疗休养目的地是否还有报名名额。
|
||||
*
|
||||
* @param enrollInfo 当前提交的报名信息,包含目的地ID、报名记录ID和改报记录ID
|
||||
* @param baseManagement 目的地配置,用于读取最大报名人数
|
||||
* @return 达到人数上限时返回失败信息;未配置人数上限或仍有名额时返回null
|
||||
*/
|
||||
private Map<Boolean, String> validBaseMaxApplyNum(TheRapyRecuperationEnroll enrollInfo,
|
||||
TheRapyRecuperationBaseManagement baseManagement) {
|
||||
Integer maxApplyNum = baseManagement.getMaxApplyNum();
|
||||
// 兼容尚未维护最大报名人数的历史目的地,避免旧数据无法继续报名。
|
||||
if (maxApplyNum == null || maxApplyNum <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Cnd cnd = Cnd.where("takePartInBaseManagementId", "=", baseManagement.getId())
|
||||
.and("isNormal", "=", true)
|
||||
.and("stateId", "not in", Lang.array(TheRapyRecuperationState.UNITFAIL,
|
||||
TheRapyRecuperationState.LINEUNITFAIL, TheRapyRecuperationState.SCHOOLFAIL));
|
||||
// 编辑或改报时排除原报名记录,防止当前报名人被重复计入名额。
|
||||
cnd.andEX("id", "!=", enrollInfo.getId());
|
||||
cnd.andEX("id", "!=", enrollInfo.getEditOther());
|
||||
int appliedNum = dao().count(TheRapyRecuperationEnroll.class, cnd);
|
||||
if (appliedNum >= maxApplyNum) {
|
||||
return Map.of(false, "该目的地报名人数已满,最大报名人数为" + maxApplyNum + "人");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证报名登记信息
|
||||
*
|
||||
@@ -583,6 +625,10 @@ public class TheRapyRecuperationEnrollServiceImpl extends ViServiceImpl<TheRapyR
|
||||
if (DateUtil.compare(new Date(), signUpEndTime) > 0) {
|
||||
return Map.of(false, "报名时间已过,抱歉不能报名");
|
||||
}
|
||||
Map<Boolean, String> maxApplyNumResult = validBaseMaxApplyNum(enrollInfo, baseManagement);
|
||||
if (maxApplyNumResult != null) {
|
||||
return maxApplyNumResult;
|
||||
}
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(enrollInfo.getId())) {
|
||||
@@ -1029,7 +1075,8 @@ public class TheRapyRecuperationEnrollServiceImpl extends ViServiceImpl<TheRapyR
|
||||
}
|
||||
|
||||
Map<Boolean, String> oneYear = this.validCountOneYear(loginName, travelFrequency, editOther);
|
||||
if (oneYear != null) {
|
||||
// 酒店年度报名校验仅在失败时提前返回;校验成功后继续判断酒店最大报名人数。
|
||||
if (oneYear != null && oneYear.containsKey(false)) {
|
||||
return oneYear;
|
||||
}
|
||||
}
|
||||
@@ -1221,6 +1268,10 @@ public class TheRapyRecuperationEnrollServiceImpl extends ViServiceImpl<TheRapyR
|
||||
if (DateUtil.compare(new Date(), signUpEndTime) > 0) {
|
||||
return Map.of(false, "报名时间已过,抱歉不能报名");
|
||||
}
|
||||
Map<Boolean, String> maxApplyNumResult = validBaseMaxApplyNum(enrollInfo, baseManagement);
|
||||
if (maxApplyNumResult != null) {
|
||||
return maxApplyNumResult;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ public class WelfareAgendaController {
|
||||
public Object getAgenda() {
|
||||
List<NutMap> list = new ArrayList<>();
|
||||
if (!ShiroUtil.hasAnyRoles("flwyh01,sysadmin")) {
|
||||
return list;
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return io.v.nutz.sys.services.TodoAccessService.filter(list, "url");
|
||||
}
|
||||
|
||||
int welfareMemberAuditNumber = service.count(Sqls.create("SELECT count(1) from welfare_member_apply_record WHERE stateId = @state").setParam("state", WelfareMemberApplyState.SCHOOL_UNION));
|
||||
@@ -48,6 +49,7 @@ public class WelfareAgendaController {
|
||||
// int welfareMemberChangeAuditNumber = service.count(Sqls.create("SELECT count(1) from member_change_record WHERE recordMode = @mode and stateId = @state").setParam("mode", MemberChangeRecordMode.WELFARE_MEMBER.getCode()).setParam("state", MemberChangeApplyState.SCHOOL_UNION));
|
||||
// list.add(new NutMap().addv("iconClass", vi.getIconByPath("/platform/welfare/member/change/audit")).addv("label", "福利会员变更审核").addv("number", welfareMemberChangeAuditNumber).addv("url", "/platform/welfare/member/change/audit"));
|
||||
|
||||
return list;
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return io.v.nutz.sys.services.TodoAccessService.filter(list, "url");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ public class WelfareSelfAgentController {
|
||||
.addv("mobileHref", "/mobile/member/edit?taskId=" + v.getId()));
|
||||
});
|
||||
}
|
||||
return list;
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return io.v.nutz.sys.services.TodoAccessService.filter(list, "mobileHref");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.v.nutz.zhgh.welfare.controller;
|
||||
|
||||
import cn.wizzer.framework.base.Result;
|
||||
import cn.wizzer.framework.page.Pagination;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.base.service.SimpleService;
|
||||
@@ -15,13 +14,10 @@ import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
@@ -57,43 +53,25 @@ public class WelfareUserChooseController {
|
||||
public void index() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前用户福利项目分页查询。
|
||||
*
|
||||
* @param pageForm 分页参数,pageNumber 为页码,pageSize 为每页条数
|
||||
* @param year 选择开始时间所属年度,为空时不限制年度
|
||||
* @param isChoose 选择状态:1 为已选择,2 为未选择;未传时默认未选择
|
||||
* @return Pagination 分页对象;list 为项目列表,totalCount 为总条数,
|
||||
* 列表中 isChoose 为 1 表示已选择、0 表示未选择,gist_list 为所选福利说明
|
||||
*/
|
||||
@At
|
||||
@ViReturn
|
||||
@RequiresPermissions("welfare.user.choose")
|
||||
public Object pageData(PageForm pageForm, Integer year) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
wp.*,
|
||||
CASE
|
||||
WHEN wus.selectUserId IS NOT NULL THEN 1
|
||||
ELSE 0
|
||||
END AS isChoose,
|
||||
wus.selectTime,
|
||||
GROUP_CONCAT( DISTINCT wuso.optionName, IF(wus.selectSpecs is not null, (CONCAT('—规格:', wus.selectSpecs)), ''), '(', wus.selectNum, '份)' ) AS gist_list,
|
||||
wus.receiveAddress
|
||||
FROM
|
||||
welfare_project wp
|
||||
LEFT JOIN welfare_project_user_selection wus ON wp.id = wus.welfareId AND wus.selectUserId = @selectUserId
|
||||
LEFT JOIN welfare_list wl ON wp.id = wl.projectId AND wl.userId = @selectUserId
|
||||
LEFT JOIN welfare_project_subject_option wuso ON wus.selectOptionId = wuso.id
|
||||
$condition
|
||||
""");
|
||||
sql.setParam("selectUserId", ShiroUtil.getUserId());
|
||||
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("wp.provideMode", "in", "2,3");
|
||||
cnd.and("wl.userId", "=", ShiroUtil.getUserId());
|
||||
cnd.andEX("YEAR(wp.choiceTimeStart)", "in", year);
|
||||
cnd.and("wp.isDisabled", "=", 0);
|
||||
cnd.groupBy("wp.id");
|
||||
cnd.desc("YEAR(wp.choiceTimeStart)");
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = simpleService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return pagination;
|
||||
|
||||
public Object pageData(PageForm pageForm, Integer year, Integer isChoose) {
|
||||
if (isChoose != null && !Integer.valueOf(1).equals(isChoose) && !Integer.valueOf(2).equals(isChoose)) {
|
||||
return Result.error("选择状态只能为已选择或未选择");
|
||||
}
|
||||
return welfareProjectService.userChoosePage(pageForm, year, isChoose, ShiroUtil.getUserId());
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@ViReturn
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.v.nutz.zhgh.welfare.service;
|
||||
|
||||
import cn.wizzer.framework.page.Pagination;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.base.service.ViService;
|
||||
import io.v.nutz.zhgh.welfare.model.WelfareProject;
|
||||
|
||||
@@ -22,6 +24,18 @@ public interface WelfareProjectService extends ViService<WelfareProject> {
|
||||
*/
|
||||
WelfareProject projectInfo(String projectId);
|
||||
|
||||
/**
|
||||
* 按选择状态查询用户有资格选择的福利项目。
|
||||
*
|
||||
* @param pageForm 分页参数,pageNumber 为页码,pageSize 为每页条数
|
||||
* @param year 选择开始时间所属年度,为空时不限制年度
|
||||
* @param isChoose 1 为已选择,2 或 null 为未选择;调用方须校验其他值
|
||||
* @param userId 当前登录用户 ID,由服务端获取
|
||||
* @return Pagination:list 为项目列表,totalCount 为总条数;
|
||||
* list 中 isChoose 为 1/0,分别表示已选择/未选择,gist_list 为所选福利说明
|
||||
*/
|
||||
Pagination userChoosePage(PageForm pageForm, Integer year, Integer isChoose, String userId);
|
||||
|
||||
List<WelfareProject> getIngWelfareProject();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package io.v.nutz.zhgh.welfare.service.impl;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.wizzer.framework.page.Pagination;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.base.service.BaseService;
|
||||
import io.v.nutz.zhgh.data.model.MatchConditionStructure;
|
||||
import io.v.nutz.base.service.impl.ViServiceImpl;
|
||||
@@ -110,6 +112,48 @@ public class WelfareProjectServiceImpl extends ViServiceImpl<WelfareProject> imp
|
||||
return project;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Pagination userChoosePage(PageForm pageForm, Integer year, Integer isChoose, String userId) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
wp.*,
|
||||
CASE
|
||||
WHEN wus.selectUserId IS NOT NULL THEN 1
|
||||
ELSE 0
|
||||
END AS isChoose,
|
||||
wus.selectTime,
|
||||
GROUP_CONCAT( DISTINCT wuso.optionName, IF(wus.selectSpecs is not null, (CONCAT('—规格:', wus.selectSpecs)), ''), '(', wus.selectNum, '份)' ) AS gist_list,
|
||||
wus.receiveAddress
|
||||
FROM
|
||||
welfare_project wp
|
||||
LEFT JOIN welfare_project_user_selection wus ON wp.id = wus.welfareId AND wus.selectUserId = @selectUserId
|
||||
LEFT JOIN welfare_list wl ON wp.id = wl.projectId AND wl.userId = @selectUserId
|
||||
LEFT JOIN welfare_project_subject_option wuso ON wus.selectOptionId = wuso.id
|
||||
$condition
|
||||
""");
|
||||
sql.setParam("selectUserId", userId);
|
||||
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("wp.provideMode", "in", "2,3");
|
||||
cnd.and("wl.userId", "=", userId);
|
||||
cnd.andEX("YEAR(wp.choiceTimeStart)", "in", year);
|
||||
cnd.and("wp.isDisabled", "=", 0);
|
||||
// 与列表状态一致:存在当前用户的选择记录为已选择,否则为未选择。
|
||||
if (Integer.valueOf(1).equals(isChoose)) {
|
||||
cnd.and("wus.selectUserId", "is not", null);
|
||||
} else {
|
||||
cnd.and("wus.selectUserId", "is", null);
|
||||
}
|
||||
cnd.groupBy("wp.id");
|
||||
cnd.desc("YEAR(wp.choiceTimeStart)");
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return pagination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WelfareProject> getIngWelfareProject() {
|
||||
return dao().query(WelfareProject.class, Cnd.where("choiceTimeStart", "<=", DateUtil.date())
|
||||
|
||||
@@ -150,7 +150,8 @@ public class XhkhLssjcxController {
|
||||
if (ShiroUtil.hasAnyRoles("sysadmin,SchoolUnionAdmin")) {
|
||||
result.add(new NutMap().setv("title", title).setv("url", "/platform/xhkh/xghsh").setv("iconClass", vi.getIconByPath("/platform/xhkh/xghsh")).setv("label", "校工会审核").setv("number", getXghCount()));
|
||||
}
|
||||
return result;
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return io.v.nutz.sys.services.TodoAccessService.filter(result, "url");
|
||||
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -59,7 +59,8 @@ public class CondolenceAgentController {
|
||||
result.add(new NutMap().setv("title", title).setv("url", ECR).setv("iconClass", vi.getIconByPath(ECR)).setv("label", "待工会常务副主席审核").setv("number", getEcrCount()));
|
||||
Sys_user user = (Sys_user) ShiroUtil.getPrincipal();
|
||||
List<Sys_menu> menus = Optional.ofNullable(user).orElse(new Sys_user()).getMenus();
|
||||
return result.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").equals(x.getHref()))).distinct().collect(Collectors.toList());
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return result.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").equals(x.getHref()))).filter(v -> io.v.nutz.sys.services.TodoAccessService.canAccess(v.getString("url"))).distinct().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import io.v.nutz.base.model.Review;
|
||||
import io.v.nutz.base.utils.ViTool;
|
||||
import io.v.nutz.web.commons.base.Globals;
|
||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||
import io.v.nutz.zgfw.model.condolence.CondolenceType;
|
||||
import io.v.nutz.zhgh.zgfw.model.condolence.CondolenceType;
|
||||
import io.v.nutz.sys.models.Sys_signature;
|
||||
import io.v.nutz.zhgh.zgfw.model.condolence.Condolence;
|
||||
import io.v.nutz.zhgh.zgfw.service.CondolenceService;
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ package io.v.nutz.zhgh.zgfw.controller.condolence;
|
||||
import cn.wizzer.framework.base.Result;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.base.service.ViService;
|
||||
import io.v.nutz.zgfw.model.condolence.CondolenceType;
|
||||
import io.v.nutz.zhgh.zgfw.model.condolence.CondolenceType;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
|
||||
@@ -10,7 +10,7 @@ import io.v.nutz.sys.models.Sys_user;
|
||||
import io.v.nutz.sys.services.SysMsgService;
|
||||
import io.v.nutz.sys.services.SysMsgUserService;
|
||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||
import io.v.nutz.zgfw.model.condolence.CondolenceType;
|
||||
import io.v.nutz.zhgh.zgfw.model.condolence.CondolenceType;
|
||||
import io.v.nutz.zhgh.zgfw.handler.condolence.CondolenceToDoHandler;
|
||||
import io.v.nutz.zhgh.zgfw.model.condolence.Condolence;
|
||||
import io.v.nutz.zhgh.zgfw.service.CondolenceService;
|
||||
|
||||
@@ -4,7 +4,7 @@ import cn.hutool.core.util.StrUtil;
|
||||
import io.v.nutz.base.annontation.ViReturn;
|
||||
import io.v.nutz.base.utils.Vi;
|
||||
import io.v.nutz.base.service.ViService;
|
||||
import io.v.nutz.zgfw.model.condolence.CondolenceType;
|
||||
import io.v.nutz.zhgh.zgfw.model.condolence.CondolenceType;
|
||||
import io.v.nutz.zhgh.zgfw.service.CondolenceService;
|
||||
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
|
||||
@@ -64,7 +64,8 @@ public class rxdjAgentController {
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return io.v.nutz.sys.services.TodoAccessService.filter(result, "url");
|
||||
}
|
||||
|
||||
public Integer getUnitCount() {
|
||||
|
||||
+2
-1
@@ -54,7 +54,8 @@ public class singleChildAgentController {
|
||||
result.add(new NutMap().setv("url", SCHOOL).setv("iconClass", vi.getIconByPath(SCHOOL)).setv("label", "独生子女待工会主席审核").setv("number", getSchoolCount()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
// 保留原有任务数据范围,再按当前账号的目标页面权限过滤入口。
|
||||
return io.v.nutz.sys.services.TodoAccessService.filter(result, "url");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.v.nutz.zgfw.model.condolence;
|
||||
package io.v.nutz.zhgh.zgfw.model.condolence;
|
||||
|
||||
import lombok.Data;
|
||||
import org.nutz.dao.DB;
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
.list-card {
|
||||
--card-accent: #2563eb;
|
||||
--card-bg: #ffffff;
|
||||
--card-title: #111827;
|
||||
--card-label: #8e969f;
|
||||
--card-value: #374151;
|
||||
--card-line: #f3f4f6;
|
||||
--card-shadow: 0 14px 34px rgba(15, 23, 42, 0.07);
|
||||
--badge-bg: #eef2ff;
|
||||
--badge-color: #3730a3;
|
||||
margin: 12px 14px;
|
||||
padding: 15px 16px;
|
||||
background: var(--card-bg);
|
||||
border-radius: 14px;
|
||||
box-shadow: var(--card-shadow);
|
||||
line-height: 1.5;
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-card:active {
|
||||
background: #fbfcfe;
|
||||
transform: scale(0.998);
|
||||
}
|
||||
|
||||
.list-card .card-header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 1px 0 13px 12px;
|
||||
}
|
||||
|
||||
.list-card .card-header::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 3px;
|
||||
width: 3px;
|
||||
height: 18px;
|
||||
border-radius: 999px;
|
||||
background: var(--card-accent);
|
||||
}
|
||||
|
||||
.list-card .card-header::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 1px;
|
||||
background: var(--card-line);
|
||||
}
|
||||
|
||||
.list-card .card-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
color: var(--card-title);
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 22px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.list-card .card-body {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
column-gap: 18px;
|
||||
row-gap: 10px;
|
||||
padding: 13px 0;
|
||||
}
|
||||
|
||||
.list-card .card-item {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.list-card .card-label {
|
||||
flex: none;
|
||||
color: var(--card-label);
|
||||
}
|
||||
|
||||
.list-card .card-value {
|
||||
min-width: 0;
|
||||
color: var(--card-value);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.list-card .card-footer {
|
||||
position: relative;
|
||||
padding-top: 12px;
|
||||
color: var(--card-value);
|
||||
}
|
||||
|
||||
.list-card .card-footer::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 1px;
|
||||
background: var(--card-line);
|
||||
}
|
||||
|
||||
.list-card .badge {
|
||||
flex: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 50px;
|
||||
padding: 3px 9px;
|
||||
border-radius: 999px;
|
||||
color: var(--badge-color);
|
||||
background: var(--badge-bg);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.list-card .badge-draft {
|
||||
--badge-bg: #F1F5F9;
|
||||
--badge-color: #64748B;
|
||||
}
|
||||
|
||||
.list-card .badge-green {
|
||||
--badge-bg: #e7f7ee;
|
||||
--badge-color: #047857;
|
||||
}
|
||||
|
||||
.list-card .badge-blue {
|
||||
--badge-bg: #eaf3ff;
|
||||
--badge-color: #1d4ed8;
|
||||
}
|
||||
|
||||
.list-card .badge-gray {
|
||||
--badge-bg: #f2f4f7;
|
||||
--badge-color: #667085;
|
||||
}
|
||||
|
||||
.list-card .badge-red {
|
||||
--badge-bg: #fff0ef;
|
||||
--badge-color: #b42318;
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.list-card {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.list-card .card-body {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,8 @@ body {
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
/* Vue 挂载前隐藏原始模板及弹框内容,避免被 #app 的 display 规则覆盖;挂载后 Vue 自动移除此属性。 */
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
a, img {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
.van-pagination__item--active {
|
||||
background-color: #1867b0;
|
||||
background-color: #004e64;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
.van-pagination__item:active {
|
||||
background-color: #1867b0 !important;
|
||||
background-color: #004e64 !important;
|
||||
}
|
||||
|
||||
.van-pagination__item {
|
||||
color: #1867b0;
|
||||
color: #004e64;
|
||||
}
|
||||
|
||||
.van-dropdown-menu__bar {
|
||||
@@ -24,7 +24,7 @@
|
||||
/*}*/
|
||||
|
||||
.van-dropdown-menu__title--active, .van-dropdown-item__option--active, .van-dropdown-item__option--active .van-dropdown-item__icon {
|
||||
color: #1867b0;
|
||||
color: #004e64 !important;
|
||||
}
|
||||
|
||||
.van-tabs {
|
||||
@@ -32,15 +32,19 @@
|
||||
}
|
||||
|
||||
.van-tabs__line {
|
||||
background-color: #1867b0;
|
||||
background-color: #004e64 !important;
|
||||
}
|
||||
|
||||
.van-tab--active {
|
||||
color: #004e64 !important;
|
||||
}
|
||||
|
||||
.font-theme-color {
|
||||
color: #1867b0;
|
||||
color: #004e64;
|
||||
}
|
||||
|
||||
.van-nav-bar {
|
||||
/*background-color: #1867b0;*/
|
||||
/*background-color: #004e64;*/
|
||||
background-color: #FFFFFF;
|
||||
z-index: 11;
|
||||
}
|
||||
@@ -158,7 +162,7 @@
|
||||
}
|
||||
|
||||
.van-sidebar-item--select::before {
|
||||
background-color: #1867b0;
|
||||
background-color: #004e64;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,448 @@
|
||||
.info-page-container {
|
||||
min-height: 100%;
|
||||
padding: 1px 0 12px;
|
||||
background: #F7F8FA;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
margin: 12px;
|
||||
padding: 16px;
|
||||
background: #ffffff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.info-section-title {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 22px;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 12px;
|
||||
color: #1D2129;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.info-section-title::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 4px;
|
||||
width: 4px;
|
||||
height: 14px;
|
||||
border-radius: 999px;
|
||||
background: #1890ff;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.info-row + .info-row {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
flex: 0 0 90px;
|
||||
color: #86909C;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
color: #1D2129;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.info-value.is-long {
|
||||
text-align: left;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.info-long-block {
|
||||
margin-top: 14px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid #F2F3F5;
|
||||
}
|
||||
|
||||
.info-long-title {
|
||||
margin-bottom: 8px;
|
||||
color: #86909C;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-long-content {
|
||||
color: #1D2129;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 1.7;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.info-input-area {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.info-input-area .van-cell-group,
|
||||
.info-input-area .van-cell {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.info-input-area .van-cell::after,
|
||||
.info-input-area .van-cell-group::after {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.info-input-area .van-field {
|
||||
margin-top: 10px;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background: #F2F3F5;
|
||||
}
|
||||
|
||||
.info-input-area .van-field__label {
|
||||
color: #4E5969;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.info-input-area .van-field__control {
|
||||
color: #1D2129;
|
||||
font-size: 14px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.info-input-title {
|
||||
margin-bottom: 10px;
|
||||
color: #1D2129;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.info-cell-form {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.info-cell-form .info-row,
|
||||
.info-cell-form .info-picker-card .van-field,
|
||||
.info-cell-form > .van-field {
|
||||
margin: 0;
|
||||
padding: 13px 0;
|
||||
border-bottom: 1px solid #F2F3F5;
|
||||
border-radius: 0;
|
||||
background: #ffffff;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.info-cell-form .info-row + .info-row {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.info-cell-form .info-label,
|
||||
.info-cell-form .van-field__label,
|
||||
.info-cell-form .info-picker-label {
|
||||
flex: 0 0 90px;
|
||||
width: 90px;
|
||||
margin-right: 12px;
|
||||
color: #86909c;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.info-cell-form .info-value,
|
||||
.info-cell-form .van-field__control {
|
||||
color: #1d2129;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.info-cell-form .info-value.is-long {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.info-cell-form .van-field__control::placeholder {
|
||||
color: #c9cdd4;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.info-cell-form .van-field__right-icon {
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
.info-cell-form .van-cell--required::before {
|
||||
top: 50%;
|
||||
left: -8px;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.info-cell-form .info-multi-value-field .van-field__body {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.info-cell-form .info-multi-value-field .van-field__right-icon {
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
.info-multi-value {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
color: #1d2129;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
text-align: right;
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.info-multi-placeholder {
|
||||
color: #c9cdd4;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.info-cell-form .van-radio-group {
|
||||
width: 100%;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.info-cell-form .van-radio {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.info-textarea-field {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.info-textarea-label {
|
||||
margin-bottom: 6px;
|
||||
color: #86909c;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.info-required-label::before {
|
||||
content: "*";
|
||||
margin-right: 2px;
|
||||
color: #ee0a24;
|
||||
}
|
||||
|
||||
.info-cell-form .info-textarea-field .van-field {
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: #f2f3f5;
|
||||
}
|
||||
|
||||
.info-cell-form .info-textarea-field .van-field__control {
|
||||
min-height: 96px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.info-cell-form .info-textarea-field .van-field__word-limit {
|
||||
color: #c9cdd4;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.info-footer-actions {
|
||||
padding: 20px 0 4px;
|
||||
}
|
||||
|
||||
.info-cell-form .info-footer-actions .van-button {
|
||||
height: 44px;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-cell-form .info-ghost-button {
|
||||
border: 1px solid #4e5969;
|
||||
background: #ffffff;
|
||||
color: #4e5969;
|
||||
}
|
||||
|
||||
.info-cell-form .info-primary-button {
|
||||
border: 0;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.unit-select-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.unit-select-title {
|
||||
color: #1D2129;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.unit-select-cancel,
|
||||
.unit-select-confirm {
|
||||
height: 100%;
|
||||
padding: 0 16px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.unit-select-cancel {
|
||||
color: #969799;
|
||||
}
|
||||
|
||||
.unit-select-confirm {
|
||||
color: #576b95;
|
||||
}
|
||||
|
||||
.unit-select-list {
|
||||
max-height: 320px;
|
||||
overflow-y: auto;
|
||||
padding: 4px 16px 16px;
|
||||
}
|
||||
|
||||
.unit-select-list .van-checkbox {
|
||||
align-items: flex-start;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.unit-select-list .van-checkbox__label {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.info-sign-image {
|
||||
overflow: hidden;
|
||||
margin-top: 12px;
|
||||
padding: 10px;
|
||||
border-radius: 12px;
|
||||
background: #F7F8FA;
|
||||
}
|
||||
|
||||
.info-record + .info-record {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #F2F3F5;
|
||||
}
|
||||
|
||||
.info-record-title {
|
||||
margin-bottom: 12px;
|
||||
color: #4E5969;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.info-collapse {
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
background: #F7F8FA;
|
||||
}
|
||||
|
||||
.info-inner-collapse {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.info-collapse .van-cell {
|
||||
padding: 12px 14px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.info-collapse .van-cell::after,
|
||||
.info-collapse .van-collapse-item__wrapper::after {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.info-collapse .van-cell__title {
|
||||
min-width: 0;
|
||||
color: #1D2129;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.info-collapse-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.info-collapse-title-text {
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.info-collapse .van-collapse-item__content {
|
||||
padding: 12px 14px 14px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.info-collapse-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.info-collapse-row + .info-collapse-row,
|
||||
.info-collapse-group + .info-collapse-group {
|
||||
border-top: 1px solid #F2F3F5;
|
||||
}
|
||||
|
||||
.info-collapse-group {
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.info-collapse-group:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.info-collapse-group:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.info-status {
|
||||
flex: 0 0 auto;
|
||||
padding: 3px 9px;
|
||||
border-radius: 999px;
|
||||
color: #64748B;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
background: #F1F5F9;
|
||||
}
|
||||
|
||||
.info-status.is-success {
|
||||
color: #16A34A;
|
||||
background: #ECFDF3;
|
||||
}
|
||||
|
||||
.info-status.is-danger {
|
||||
color: #EF4444;
|
||||
background: #FEF2F2;
|
||||
}
|
||||
|
||||
.info-status.is-muted {
|
||||
color: #64748B;
|
||||
background: #F1F5F9;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// 所有接入页面共用一个弹框历史栈;只注册一次 popstate,系统返回按后进先出关闭。
|
||||
window.popupHistory = window.popupHistory || (() => {
|
||||
let stack = [];
|
||||
let pending = null;
|
||||
let navigating = false;
|
||||
const queue = [];
|
||||
const owners = new Map();
|
||||
const finish = () => {
|
||||
const callback = pending;
|
||||
pending = null;
|
||||
if (callback) callback();
|
||||
};
|
||||
window.addEventListener('popstate', (event) => {
|
||||
const marker = event.state && event.state.popupHistoryId;
|
||||
const index = stack.findIndex((entry) => entry.id === marker);
|
||||
const removed = stack.splice(index + 1);
|
||||
removed.reverse().forEach((entry) => {
|
||||
const callback = owners.get(entry.owner);
|
||||
if (callback) callback(entry.key);
|
||||
});
|
||||
navigating = false;
|
||||
finish();
|
||||
// 无需回退的排队操作也继续消费;发生下一次历史回退时等待对应 popstate。
|
||||
while (!navigating && queue.length) queue.shift()();
|
||||
});
|
||||
return {
|
||||
register(owner, callback) { owners.set(owner, callback) },
|
||||
push(owner, key) {
|
||||
if (navigating) { queue.push(() => this.push(owner, key)); return; }
|
||||
if (stack.some((entry) => entry.owner === owner && entry.key === key)) return;
|
||||
const id = Date.now().toString() + Math.random().toString(16).slice(2);
|
||||
stack.push({owner, key, id});
|
||||
window.history.pushState(Object.assign({}, window.history.state, {popupHistoryId:id}), '');
|
||||
},
|
||||
close(owner, key) {
|
||||
if (navigating) { queue.push(() => this.close(owner, key)); return; }
|
||||
const top = stack[stack.length - 1];
|
||||
if (top && top.owner === owner && top.key === key) { navigating = true; window.history.back(); }
|
||||
},
|
||||
clear(owner, callback) {
|
||||
if (navigating) { queue.push(() => this.clear(owner, callback)); return; }
|
||||
const count = stack.filter((entry) => entry.owner === owner).length;
|
||||
if (!count) { if (callback) callback(); return; }
|
||||
pending = callback || null;
|
||||
navigating = true;
|
||||
window.history.go(-count);
|
||||
},
|
||||
unregister(owner) {
|
||||
owners.delete(owner);
|
||||
this.clear(owner);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
// 页面声明 historyPopupKeys 即可同步 Popup/Dialog/ActionSheet;无需页面重复监听浏览器事件。
|
||||
window.popupHistoryMixin = {
|
||||
mounted() {
|
||||
this._popupOwner = 'page-' + this._uid;
|
||||
this._popupFromHistory = new Set();
|
||||
this._popupUnwatch = [];
|
||||
window.popupHistory.register(this._popupOwner, (key) => {
|
||||
this._popupFromHistory.add(key);
|
||||
this.$set(this, key, false);
|
||||
this.$nextTick(() => this._popupFromHistory.delete(key));
|
||||
});
|
||||
(this.historyPopupKeys || []).forEach((key) => {
|
||||
this._popupUnwatch.push(this.$watch(key, (value) => {
|
||||
if (this._popupFromHistory.has(key)) return;
|
||||
if (value) window.popupHistory.push(this._popupOwner, key);
|
||||
else window.popupHistory.close(this._popupOwner, key);
|
||||
}));
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
(this._popupUnwatch || []).forEach((unwatch) => unwatch());
|
||||
window.popupHistory.unregister(this._popupOwner);
|
||||
},
|
||||
methods: {
|
||||
// 成功提交或页面跳转前清空本页弹框历史,等待回退完成再执行回调。
|
||||
popupBack() { window.history.back() },
|
||||
clearPopupHistory(callback) { window.popupHistory.clear(this._popupOwner, callback) }
|
||||
}
|
||||
};
|
||||
@@ -95,16 +95,18 @@ const initTableMixins = {
|
||||
const address = url ? url : loc() + "/pageData"
|
||||
sublime.showLoadingbar();
|
||||
this.tableLoading = true
|
||||
$.post(address, data ? data : this.pageForm, (data) => {
|
||||
sublime.closeLoadingbar();
|
||||
this.tableLoading = false
|
||||
if (data.code === 0) {
|
||||
this.tableData = data.data.list;
|
||||
this.pageForm.totalCount = data.data.totalCount;
|
||||
// 公共查询统一在 always 收尾;分页及查询入口继续由各页面复用。
|
||||
return $.post(address, data ? data : this.pageForm).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = res.data.list;
|
||||
this.pageForm.totalCount = res.data.totalCount;
|
||||
} else {
|
||||
this.$message.error(data.msg);
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
}, "json");
|
||||
}).always(() => {
|
||||
sublime.closeLoadingbar();
|
||||
this.tableLoading = false;
|
||||
});
|
||||
},
|
||||
notifySuccess(msg) {
|
||||
this.$notify({
|
||||
|
||||
@@ -33,14 +33,27 @@
|
||||
|
||||
<el-tab-pane label="预约基本信息" name="2">
|
||||
<el-descriptions class="margin-top" :column="3" border>
|
||||
<el-descriptions-item label="预约类型">{{ {1:'个人预约',2:'分工会预约',3:'协会预约'}[viewData.reserve_type] }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属协会">{{viewData.club_name || '—'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="预约人">{{ viewData.reserve_person }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="2" label="场地名称"> {{ viewData.site_name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ viewData.sex }}</el-descriptions-item>
|
||||
<el-descriptions-item label="工号">{{ viewData.loginname }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属单位">{{ viewData.reserve_person_unit }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ viewData.reserve_person_phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="预约时间"> ({{ viewData.concat_day }}) {{ viewData.start_time }} - {{ viewData.end_time }}</el-descriptions-item>
|
||||
<el-descriptions-item label="预约状态">
|
||||
<!-- 复用详情接口按日期合并的时段;独占一行,避免与状态、事由挤在同一行。 -->
|
||||
<el-descriptions-item label="预约时间" :span="3">
|
||||
<div class="reservation-times">
|
||||
<div v-for="group in viewData.reservationTimeGroups" :key="group.date" class="reservation-time-day">
|
||||
<span class="reservation-time-date">{{group.date}}</span>
|
||||
<div class="reservation-time-ranges">
|
||||
<span v-for="(range,index) in group.ranges" :key="index" class="reservation-time-range">{{range.start}}–{{range.end}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span v-if="!viewData.reservationTimeGroups || !viewData.reservationTimeGroups.length">—</span>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="预约状态" :span="3">
|
||||
<span v-if="[0].includes(viewData.stateAuditType)" style="color: #e6a23c">{{viewData.state_name}}</span>
|
||||
<span v-if="viewData.stateAuditType==3" style="color: #67c23a">{{viewData.state_name}}</span>
|
||||
<span v-if="viewData.stateAuditType==1" style="color: #f56c6c">{{viewData.state_name}}</span>
|
||||
@@ -123,22 +136,18 @@ module.exports = {
|
||||
}
|
||||
return this.panes.includes(name)
|
||||
},
|
||||
async getSiteReserveInfo(id) {
|
||||
const {data} = await $.get(base + "/platform/activity/site/reserve/findOne", {id})
|
||||
return data
|
||||
},
|
||||
async openView(id) {
|
||||
this.loading = true
|
||||
this.$forceUpdate()
|
||||
this.viewData = await this.getSiteReserveInfo(id)
|
||||
if (this.viewData) {
|
||||
this.activeName = this.role === true ? "1" : "2"
|
||||
} else {
|
||||
this.viewData = {}
|
||||
this.$message.error("获取场地预约信息失败");
|
||||
}
|
||||
this.$forceUpdate()
|
||||
this.loading = false
|
||||
// id 为预约记录主键;返回包含申请、时段和审核历史的 data。
|
||||
openView(id) {
|
||||
this.loading = true;
|
||||
$.post(base + '/platform/activity/site/reserve/findOne', {id}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.viewData = res.data;
|
||||
this.activeName = this.role === true ? '1' : '2';
|
||||
} else {
|
||||
this.viewData = {};
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
}).always(() => { this.loading = false });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,4 +157,33 @@ module.exports = {
|
||||
.el-descriptions-item__cell {
|
||||
text-align: center !important;
|
||||
}
|
||||
/* 日期与完整时段整体居中,长时段列表仅在各时间段之间换行。 */
|
||||
.reservation-times {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
text-align: left;
|
||||
font-variant-numeric: tabular-nums;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.reservation-time-day {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 12px;
|
||||
padding: 3px 0;
|
||||
line-height: 22px;
|
||||
}
|
||||
.reservation-time-date {
|
||||
flex: 0 0 88px;
|
||||
white-space: nowrap;
|
||||
font-weight: 500;
|
||||
}
|
||||
.reservation-time-ranges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px 12px;
|
||||
}
|
||||
.reservation-time-range {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,245 +1,477 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="info-page-container">
|
||||
|
||||
<template name="提案基本信息">
|
||||
<div class="van-cell-group__title" style="padding: 5px 0 0 0 !important;">
|
||||
<div class="van-sidebar-item van-sidebar-item--select">
|
||||
提案基本信息
|
||||
<section class="info-card">
|
||||
<div class="info-section-title">提案基本信息</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">提案名称</span>
|
||||
<span class="info-value is-long">{{ viewData.proposalName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<van-cell-group>
|
||||
<van-collapse v-model="secondedActiveNames" :border="false" accordion>
|
||||
<van-collapse-item name="1">
|
||||
<div class="info-row">
|
||||
<span class="info-label">提案编号</span>
|
||||
<span class="info-value">{{ viewData.proposalCode }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">提案时间</span>
|
||||
<span class="info-value">{{ viewData.createTime }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">教代会届次</span>
|
||||
<span class="info-value">{{ viewData.meetingName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">代表工会</span>
|
||||
<span class="info-value">{{ viewData.unionName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">{{ viewData.mannerCode !== 'W03' ? '代表团名称' : '委员会名称' }}</span>
|
||||
<span class="info-value">
|
||||
{{ viewData.mannerCode !== 'W03' ? viewData.delegationName : viewData.committeeName }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">提案类型</span>
|
||||
<span class="info-value">{{ viewData.typeName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">提案方式</span>
|
||||
<span class="info-value">{{ viewData.mannerName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">立案结果</span>
|
||||
<span class="info-value">{{ viewData.resultName ? viewData.resultName : '暂无' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">建议落实部门</span>
|
||||
<span class="info-value">{{ viewData.implementUnitName || '暂无' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">提案状态</span>
|
||||
<span class="info-value" :style="'color:'+viewData.stateColor">{{ viewData.stateName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">提案人</span>
|
||||
<span class="info-value">{{ viewData.createUserName }}({{ viewData.loginname }})</span>
|
||||
</div>
|
||||
|
||||
<div class="info-long-block" v-if="viewData.undertake&&viewData.undertake.length>0">
|
||||
<div class="info-long-title">承办单位</div>
|
||||
<div class="info-row" v-for="(item,index) in viewData.undertake">
|
||||
<span class="info-label">{{ item.undertakeType === 1 ? '主办' : '协办' }}</span>
|
||||
<span class="info-value is-long">{{ item.unitName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-long-block">
|
||||
<div class="info-long-title">案由</div>
|
||||
<div class="info-long-content" v-html="viewData.brief"></div>
|
||||
</div>
|
||||
|
||||
<div class="info-long-block">
|
||||
<div class="info-long-title">建议措施</div>
|
||||
<div class="info-long-content" v-html="viewData.measures"></div>
|
||||
</div>
|
||||
|
||||
<div class="info-long-block" v-if="viewData.createUserSign">
|
||||
<div class="info-long-title">签字</div>
|
||||
<div class="info-sign-image">
|
||||
<van-image width="200" height="100" :src="viewData.createUserSign"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-long-block">
|
||||
<div class="info-long-title">附件</div>
|
||||
<upload v-if="viewData.files&&viewData.files.length" :del="false" :files.sync="viewData.files" view></upload>
|
||||
<div class="info-long-content" v-else>无</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-if="viewData.isConjoin !== 0 && viewData.conJoinList && viewData.conJoinList.length>0" name="提案并案信息">
|
||||
<section class="info-card">
|
||||
<div class="info-section-title">提案并案信息</div>
|
||||
<van-collapse class="info-collapse" v-model="undertakeActiveNames" :border="false">
|
||||
<van-collapse-item v-for="(item,index) in viewData.conJoinList" :name="'conJoin-' + index">
|
||||
<template #title>
|
||||
<div>提案名称  {{ viewData.proposalName }}
|
||||
<van-icon name="question-o"/>
|
||||
</div>
|
||||
{{ item.proposalName }}
|
||||
</template>
|
||||
<van-cell :value="viewData.proposalCode" title="提案编号"></van-cell>
|
||||
<van-cell title="提案时间">{{ viewData.createTime }}</van-cell>
|
||||
<van-cell title="教代会届次">{{ viewData.meetingName }}</van-cell>
|
||||
<van-cell title="代表工会">{{ viewData.unionName }}</van-cell>
|
||||
<van-cell title="代表团名称">{{ viewData.delegationName }}</van-cell>
|
||||
<van-cell title="提案类型">{{ viewData.typeName }}</van-cell>
|
||||
<van-cell title="提案方式">{{ viewData.mannerName }}</van-cell>
|
||||
<van-cell title="立案结果">{{ viewData.resultName ? viewData.resultName : '暂无' }}</van-cell>
|
||||
<van-cell title="建议落实部门">{{ viewData.implementUnitName || '暂无' }}</van-cell>
|
||||
<van-cell title="提案状态"><span :style="'color:'+viewData.stateColor">{{ viewData.stateName }}</span>
|
||||
</van-cell>
|
||||
</van-collapse-item>
|
||||
<van-cell title="提  案  人">
|
||||
{{ viewData.createUserName }}({{ viewData.loginname }})
|
||||
</van-cell>
|
||||
|
||||
|
||||
<van-collapse-item v-if="viewData.undertake&&viewData.undertake.length>0" name="3" title="承办单位">
|
||||
<span v-if="!viewData.undertake">暂未分配</span>
|
||||
<van-cell v-for="(item,index) in viewData.undertake" v-else
|
||||
:title="item.undertakeType === 1 ? '主办' : '协办'">
|
||||
{{ item.unitName }}
|
||||
</van-cell>
|
||||
</van-collapse-item>
|
||||
|
||||
<van-collapse-item name="4" title="案  由">
|
||||
<span v-html="viewData.brief"></span>
|
||||
</van-collapse-item>
|
||||
|
||||
<van-collapse-item name="5" title="建议措施">
|
||||
<span v-html="viewData.measures"></span>
|
||||
</van-collapse-item>
|
||||
|
||||
</van-collapse>
|
||||
|
||||
|
||||
<van-collapse v-model="createUserSignNames" :border="false" v-if="viewData.createUserSign">
|
||||
<van-collapse-item name="1" title="签  字">
|
||||
<van-image width="200" height="100" :src="viewData.createUserSign" v-if="viewData.createUserSign"/>
|
||||
<span v-else>暂无</span>
|
||||
<div class="info-row">
|
||||
<span class="info-label">提案编号</span>
|
||||
<span class="info-value">{{ item.proposalCode }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">提案人</span>
|
||||
<span class="info-value">{{ item.username }}</span>
|
||||
</div>
|
||||
</van-collapse-item>
|
||||
</van-collapse>
|
||||
|
||||
|
||||
<!-- <van-cell title="附件">
|
||||
<div v-for="(obj,index) in viewData.files" class="text-nowrap color-puple"
|
||||
@click="previewFile(obj.filepath,obj.filename,viewData.files,index)">
|
||||
{{ obj.filename }}
|
||||
</div>
|
||||
</van-cell>-->
|
||||
</van-cell-group>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-if="viewData.seconded" name="附议信息">
|
||||
<div class="van-cell-group__title" style="padding: 5px 0 0 0 !important;">
|
||||
<div class="van-sidebar-item van-sidebar-item--select">
|
||||
附议信息
|
||||
</div>
|
||||
</div>
|
||||
<van-collapse v-model="secondedNames" :border="false">
|
||||
<van-collapse-item name="1" title="附  议  人">
|
||||
<van-cell v-for="(item,index) in viewData.seconded"
|
||||
:value="item.isAgree === true ? '同意' : item.isAgree === false ? '拒绝' : '未附议'">
|
||||
<section class="info-card">
|
||||
<div class="info-section-title">附议信息</div>
|
||||
<van-collapse class="info-collapse" v-model="secondedNames" :border="false">
|
||||
<van-collapse-item v-for="(item,index) in viewData.seconded" :name="'seconded-' + index">
|
||||
<template #title>
|
||||
<span>{{ item.username }}({{ item.unitname }})</span>
|
||||
<div class="info-collapse-title">
|
||||
<span class="info-collapse-title-text">{{ item.username }}({{ item.unitname }})</span>
|
||||
<span class="info-status"
|
||||
:class="item.isAgree === true ? 'is-success' : item.isAgree === false ? 'is-danger' : 'is-muted'">
|
||||
{{ item.isAgree === true ? '同意' : item.isAgree === false ? '拒绝' : '未附议' }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-collapse-item>
|
||||
</van-collapse>
|
||||
<div class="info-row">
|
||||
<span class="info-label">邀请时间</span>
|
||||
<span class="info-value">{{ item.inviteTime || '暂无' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">附议时间</span>
|
||||
<span class="info-value">{{ item.secondedTime || '暂无' }}</span>
|
||||
</div>
|
||||
</van-collapse-item>
|
||||
</van-collapse>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
||||
<template v-if="viewData.delegationAudit&&viewData.delegationAudit.length>0" name="团长审核信息">
|
||||
<div class="van-cell-group__title" style="padding: 5px 0 0 0 !important;">
|
||||
<div class="van-sidebar-item van-sidebar-item--select">
|
||||
团长审核信息
|
||||
<section class="info-card">
|
||||
<div class="info-section-title">团长审核信息</div>
|
||||
<div class="info-record" v-for="(item,index) in viewData.delegationAudit">
|
||||
<div class="info-record-title" v-if="viewData.delegationAudit.length>1">审核记录{{ index + 1 }}</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">审核人</span>
|
||||
<span class="info-value">{{ item.username }}-{{ item.loginName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">审核时间</span>
|
||||
<span class="info-value">{{ item.auditTime }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">审核结果</span>
|
||||
<span class="info-value">{{ item.flag ? '通过' : '退回' }}</span>
|
||||
</div>
|
||||
<div class="info-long-block">
|
||||
<div class="info-long-title">审核意见</div>
|
||||
<div class="info-long-content">{{ item.opinion }}</div>
|
||||
</div>
|
||||
<div class="info-long-block" v-if="item.auditSign">
|
||||
<div class="info-long-title">签字</div>
|
||||
<div class="info-sign-image">
|
||||
<van-image width="200" height="100" :src="item.auditSign"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<van-cell-group v-for="(item,index) in viewData.delegationAudit" class="info">
|
||||
<van-cell title="审核时间">{{ item.auditTime }}</van-cell>
|
||||
<van-cell title="审核结果">{{ item.flag ? '通过' : '退回' }}</van-cell>
|
||||
<van-cell title="审核意见">{{ item.opinion }}</van-cell>
|
||||
<van-cell title="签  字" v-if="item.auditSign">
|
||||
<van-image width="200" height="100" :src="item.auditSign" v-if="item.auditSign"/>
|
||||
<span v-else>暂无</span>
|
||||
</van-cell>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
</van-cell-group>
|
||||
<template v-if="viewData.membersOpinions&&viewData.membersOpinions.length>0" name="提案工作组意见">
|
||||
<section class="info-card">
|
||||
<div class="info-section-title">提案工作组意见</div>
|
||||
<div class="info-record" v-for="(item,index) in viewData.membersOpinions">
|
||||
<div class="info-record-title" v-if="viewData.membersOpinions.length>1">审核记录{{ index + 1 }}</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">审核人</span>
|
||||
<span class="info-value">{{ item.username }}-{{ item.loginName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">立案意见</span>
|
||||
<span class="info-value">{{ item.dictName || '暂无' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">审核时间</span>
|
||||
<span class="info-value">{{ item.auditTime }}</span>
|
||||
</div>
|
||||
<div class="info-long-block">
|
||||
<div class="info-long-title">审核意见</div>
|
||||
<div class="info-long-content">{{ item.opinion }}</div>
|
||||
</div>
|
||||
<div class="info-long-block" v-if="item.auditSign">
|
||||
<div class="info-long-title">签字</div>
|
||||
<div class="info-sign-image">
|
||||
<van-image width="200" height="100" :src="item.auditSign"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-if="viewData.caseAuditId && viewData.caseAudit" name="提案工作组预立案">
|
||||
<section class="info-card">
|
||||
<div class="info-section-title">提案工作组预立案</div>
|
||||
<div class="info-record" v-for="(item,index) in toArray(viewData.caseAudit)">
|
||||
<div class="info-record-title" v-if="toArray(viewData.caseAudit).length>1">审核记录{{ index + 1 }}</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">审核人</span>
|
||||
<span class="info-value">{{ item.username }}-{{ item.loginName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">审核结果</span>
|
||||
<span class="info-value">{{ item.flag ? '通过' : '退回修改' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">审核时间</span>
|
||||
<span class="info-value">{{ item.auditTime }}</span>
|
||||
</div>
|
||||
<div class="info-long-block" v-if="viewData.resultCode!=='notGive'">
|
||||
<div class="info-long-title">承办单位</div>
|
||||
<div class="info-long-content" v-if="hostUnit">
|
||||
{{ hostUnit }}(主办)<template v-if="helpUnit&&helpUnit.length>0">,{{ helpUnit }}(协办)</template>
|
||||
</div>
|
||||
<div class="info-long-content" v-else>暂未分配</div>
|
||||
</div>
|
||||
<div class="info-long-block">
|
||||
<div class="info-long-title">审核意见</div>
|
||||
<div class="info-long-content">{{ item.opinion }}</div>
|
||||
</div>
|
||||
<div class="info-long-block" v-if="item.auditSign">
|
||||
<div class="info-long-title">签字</div>
|
||||
<div class="info-sign-image">
|
||||
<van-image width="200" height="100" :src="item.auditSign"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-if="viewData.secretaryAudit&&viewData.secretaryAudit.length>0" name="提案委员会立案信息">
|
||||
<div class="van-cell-group__title" style="padding: 5px 0 0 0 !important;">
|
||||
<div class="van-sidebar-item van-sidebar-item--select">
|
||||
提案委员会立案信息
|
||||
<section class="info-card">
|
||||
<div class="info-section-title">提案委员会立案信息</div>
|
||||
<div class="info-record" v-for="(item,index) in viewData.secretaryAudit">
|
||||
<div class="info-record-title" v-if="viewData.secretaryAudit.length>1">立案审核{{ index + 1 }}</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">审核时间</span>
|
||||
<span class="info-value">{{ item.auditTime }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">立案编号</span>
|
||||
<span class="info-value">{{ viewData.caseCode }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">审核结果</span>
|
||||
<span class="info-value">{{ viewData.resultName }}</span>
|
||||
</div>
|
||||
<div class="info-long-block">
|
||||
<div class="info-long-title">承办单位</div>
|
||||
<div class="info-long-content" v-if="!viewData.undertake">暂未分配</div>
|
||||
<div class="info-row" v-for="(undertake,index) in viewData.undertake" v-else>
|
||||
<span class="info-label">{{ undertake.undertakeType === 1 ? '主办' : '协办' }}</span>
|
||||
<span class="info-value is-long">{{ undertake.unitName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-long-block">
|
||||
<div class="info-long-title">审核意见</div>
|
||||
<div class="info-long-content">{{ item.opinion }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<van-cell-group v-for="item in viewData.secretaryAudit" class="info">
|
||||
<van-cell title="审核时间">{{ item.auditTime }}</van-cell>
|
||||
<van-cell title="立案编号">{{ viewData.caseCode }}</van-cell>
|
||||
<van-cell title="审核结果">{{ viewData.resultName }}</van-cell>
|
||||
<van-cell v-if="!viewData.undertake" title="承办单位">暂未分配</van-cell>
|
||||
<van-collapse v-else v-model="undertakeActiveNames">
|
||||
<van-collapse-item name="1" title="承办单位">
|
||||
<van-cell v-for="(item,index) in viewData.undertake"
|
||||
:title="item.undertakeType === 1 ? '主办' : '协办'">
|
||||
{{ item.unitName }}
|
||||
</van-cell>
|
||||
</van-collapse-item>
|
||||
</van-collapse>
|
||||
<van-cell title="审核意见">{{ item.opinion }}</van-cell>
|
||||
|
||||
</van-cell-group>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-if="viewData.caseAudit && viewData.caseAudit.length > 0" name="确认承办单位">
|
||||
<div class="van-cell-group__title" style="padding: 5px 0 0 0 !important;">
|
||||
<div class="van-sidebar-item van-sidebar-item--select">
|
||||
确认承办单位
|
||||
<template v-if="viewData.underTakeFirstOpinion&&viewData.underTakeFirstOpinion.length>0" name="承办单位意见">
|
||||
<section class="info-card">
|
||||
<div class="info-section-title">承办单位意见</div>
|
||||
<div class="info-record" v-for="(item,index) in viewData.underTakeFirstOpinion">
|
||||
<div class="info-record-title" v-if="viewData.underTakeFirstOpinion.length>1">意见记录{{ index + 1 }}</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">承办单位</span>
|
||||
<span class="info-value">{{ item.underTakeName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">时间</span>
|
||||
<span class="info-value">{{ item.opionTime }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">审核结果</span>
|
||||
<span class="info-value">{{ item.canTake ? '同意承办' : '无法承办' }}</span>
|
||||
</div>
|
||||
<div class="info-long-block">
|
||||
<div class="info-long-title">意见</div>
|
||||
<div class="info-long-content">{{ item.opinion }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<van-cell-group class="info">
|
||||
<van-cell title="审核时间">{{ viewData.caseAudit.auditTime }}</van-cell>
|
||||
<van-cell title="立案结果">{{ viewData.resultName }}</van-cell>
|
||||
<van-collapse v-model="undertakeActiveNames">
|
||||
<van-collapse-item name="2" title="承办单位">
|
||||
<span v-if="!viewData.undertake">暂未分配</span>
|
||||
<van-cell v-for="(item,index) in viewData.undertake" v-else
|
||||
:title="item.undertakeType === 1 ? '主办' : '协办'">
|
||||
{{ item.unitName }}
|
||||
</van-cell>
|
||||
</van-collapse-item>
|
||||
</van-collapse>
|
||||
<van-cell title="审核意见">{{ viewData.caseAudit.opinion }}</van-cell>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
</van-cell-group>
|
||||
<template v-if="viewData.caseUnitAuditId && viewData.caseUnitAudit" name="提案工作组正式立案">
|
||||
<section class="info-card">
|
||||
<div class="info-section-title">提案工作组正式立案</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">审核人</span>
|
||||
<span class="info-value">{{ viewData.caseUnitAudit.username }}-{{ viewData.caseUnitAudit.loginName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">审核结果</span>
|
||||
<span class="info-value">{{ viewData.resultName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">审核时间</span>
|
||||
<span class="info-value">{{ viewData.caseUnitAudit.auditTime }}</span>
|
||||
</div>
|
||||
<div class="info-long-block" v-if="viewData.resultCode!=='notGive'">
|
||||
<div class="info-long-title">承办单位</div>
|
||||
<div class="info-long-content" v-if="!viewData.undertake">暂未分配</div>
|
||||
<div class="info-row" v-for="(item,index) in viewData.undertake" v-else>
|
||||
<span class="info-label">{{ item.undertakeType === 1 ? '主办' : '协办' }}</span>
|
||||
<span class="info-value is-long">{{ item.unitName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-long-block">
|
||||
<div class="info-long-title">审核意见</div>
|
||||
<div class="info-long-content">{{ viewData.caseUnitAudit.opinion }}</div>
|
||||
</div>
|
||||
<div class="info-long-block" v-if="viewData.caseUnitAudit.auditSign">
|
||||
<div class="info-long-title">签字</div>
|
||||
<div class="info-sign-image">
|
||||
<van-image width="200" height="100" :src="viewData.caseUnitAudit.auditSign"/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-if="viewData.makeLeader" name="分管校领导批示信息">
|
||||
<div class="van-cell-group__title" style="padding: 5px 0 0 0 !important;">
|
||||
<div class="van-sidebar-item van-sidebar-item--select">
|
||||
分管校领导批示信息
|
||||
</div>
|
||||
</div>
|
||||
<van-cell-group class="info">
|
||||
<van-collapse v-model="undertakeActiveNames">
|
||||
<van-collapse-item v-for="(v,k) in viewData.makeLeader" name="3">
|
||||
<!--({{ v[0].undertakeType === 1 ? '主办' : '协办' }}) -->
|
||||
<section class="info-card">
|
||||
<div class="info-section-title">分管校领导批示信息</div>
|
||||
<van-collapse class="info-collapse" v-model="undertakeActiveNames" :border="false">
|
||||
<van-collapse-item v-for="(v,k) in viewData.makeLeader" :name="'makeLeader-' + k">
|
||||
<template #title>
|
||||
{{ v[0].unitName }}
|
||||
</template>
|
||||
<template v-for="item in v">
|
||||
<van-cell title="批示时间"> {{ item.auditTime }}</van-cell>
|
||||
<van-cell title="批示意见"> {{ item.opinion }}</van-cell>
|
||||
</template>
|
||||
<div class="info-collapse-group" v-for="item in v">
|
||||
<div class="info-row">
|
||||
<span class="info-label">批示时间</span>
|
||||
<span class="info-value">{{ item.auditTime }}</span>
|
||||
</div>
|
||||
<div class="info-long-block">
|
||||
<div class="info-long-title">批示意见</div>
|
||||
<div class="info-long-content">{{ item.opinion }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-collapse-item>
|
||||
</van-collapse>
|
||||
</van-cell-group>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-if="viewData.replyInfo && viewData.replyInfo.length>0 && viewData.replyInfo.some(v=>v.isReply)"
|
||||
<template v-if="viewData.replyInfo && viewData.replyInfo.length>0 && viewData.replyInfo.some(v=>v.isReply || v.leaderCheckResult!=null)"
|
||||
name="承办单位答复">
|
||||
<div class="van-cell-group__title" style="padding: 5px 0 0 0 !important;">
|
||||
<div class="van-sidebar-item van-sidebar-item--select">
|
||||
承办单位答复信息
|
||||
</div>
|
||||
</div>
|
||||
<van-cell-group class="info">
|
||||
<van-collapse v-model="undertakeActiveNames">
|
||||
<van-collapse-item v-for="item in viewData.replyInfo" :name="item.unitName">
|
||||
<section class="info-card">
|
||||
<div class="info-section-title">承办单位办理信息</div>
|
||||
<van-collapse class="info-collapse" v-model="undertakeActiveNames" :border="false">
|
||||
<van-collapse-item v-for="item in viewData.replyInfo.filter(v=>v.isReply || v.leaderCheckResult!=null)"
|
||||
:name="'reply-' + item.unitName">
|
||||
<template #title>
|
||||
{{ item.unitName }}
|
||||
{{ item.unitName }}({{ item.undertakeType === 1 ? '主办' : '协办' }})
|
||||
</template>
|
||||
<van-cell title="答复时间"> {{ item.replyTime }}</van-cell>
|
||||
<van-cell title="落实情况"> {{ item.implementState }}</van-cell>
|
||||
<van-cell title="答复信息">
|
||||
<span v-html="item.replyContent"></span>
|
||||
</van-cell>
|
||||
<van-panel>
|
||||
<template #header>
|
||||
<div class="van-cell van-panel__header">
|
||||
<div class="van-cell__title">
|
||||
附件
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div style="padding: 5px 16px">
|
||||
<upload v-if="item.replyFiles&&item.replyFiles.length>0" :del="false" :files.sync="item.files"
|
||||
view></upload>
|
||||
<span v-else>暂无</span>
|
||||
<div class="info-row">
|
||||
<span class="info-label">办理人</span>
|
||||
<span class="info-value">{{ item.dfrUserName || '暂无' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">办理时间</span>
|
||||
<span class="info-value">{{ item.replyTime }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">办理次数</span>
|
||||
<span class="info-value">{{ item.replyNumber }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">落实情况</span>
|
||||
<span class="info-value">{{ item.implementState }}</span>
|
||||
</div>
|
||||
<div class="info-long-block">
|
||||
<div class="info-long-title">办理内容</div>
|
||||
<div class="info-long-content" v-html="item.replyContent"></div>
|
||||
</div>
|
||||
<div class="info-long-block" v-if="item.replySignData">
|
||||
<div class="info-long-title">承办单位签字</div>
|
||||
<div class="info-sign-image">
|
||||
<van-image width="200" height="100" :src="item.replySignData"/>
|
||||
</div>
|
||||
</van-panel>
|
||||
</div>
|
||||
<div class="info-long-block">
|
||||
<div class="info-long-title">附件</div>
|
||||
<upload v-if="item.files&&item.files.length>0" :del="false" :files.sync="item.files" view></upload>
|
||||
<div class="info-long-content" v-else>暂无</div>
|
||||
</div>
|
||||
</van-collapse-item>
|
||||
</van-collapse>
|
||||
</van-cell-group>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-if="viewData.branchLeaderSuffixAuditOpinion&&viewData.branchLeaderSuffixAuditOpinion.length>0"
|
||||
name="分管校领导审批">
|
||||
<section class="info-card">
|
||||
<div class="info-section-title">分管校领导审批</div>
|
||||
<div class="info-record" v-for="(item,index) in viewData.branchLeaderSuffixAuditOpinion">
|
||||
<div class="info-record-title" v-if="viewData.branchLeaderSuffixAuditOpinion.length>1">
|
||||
审批记录{{ index + 1 }}
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">审批人</span>
|
||||
<span class="info-value">{{ item.username }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">审批时间</span>
|
||||
<span class="info-value">{{ item.auditTime }}</span>
|
||||
</div>
|
||||
<div class="info-long-block">
|
||||
<div class="info-long-title">审批意见</div>
|
||||
<div class="info-long-content">{{ item.opinion }}</div>
|
||||
</div>
|
||||
<div class="info-long-block" v-if="item.auditSign">
|
||||
<div class="info-long-title">分管校领导签字</div>
|
||||
<div class="info-sign-image">
|
||||
<van-image width="200" height="100" :src="item.auditSign"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-if="viewData.feedback&&viewData.feedback.length>0" name="反馈评分信息">
|
||||
<div class="van-cell-group__title" style="padding: 5px 0 0 0 !important;">
|
||||
<div class="van-sidebar-item van-sidebar-item--select">
|
||||
反馈评分信息
|
||||
</div>
|
||||
</div>
|
||||
<van-cell-group class="info">
|
||||
<template v-for="item in viewData.feedback">
|
||||
<van-cell title="反馈时间">{{ item.feedbackTime }}</van-cell>
|
||||
<van-cell title="反馈结果">{{ item.feedbackResult }}</van-cell>
|
||||
<van-cell title="反馈意见"><span v-html="item.feedbackOpinion"></span></van-cell>
|
||||
<van-panel>
|
||||
<template #header>
|
||||
<div class="van-cell van-panel__header">
|
||||
<div class="van-cell__title">
|
||||
附件
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div style="padding: 5px 16px">
|
||||
<upload :del="false" :files.sync="item.files" view></upload>
|
||||
<section class="info-card">
|
||||
<div class="info-section-title">反馈评分信息</div>
|
||||
<div class="info-record" v-for="(item,index) in viewData.feedback">
|
||||
<div class="info-record-title" v-if="viewData.feedback.length>1">反馈记录{{ index + 1 }}</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">反馈人</span>
|
||||
<span class="info-value">{{ item.username }}({{ item.loginname }})</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">反馈时间</span>
|
||||
<span class="info-value">{{ item.feedbackTime }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">反馈次数</span>
|
||||
<span class="info-value">{{ item.feedbackNumber }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">办理评价</span>
|
||||
<span class="info-value">{{ item.feedbackResult }}</span>
|
||||
</div>
|
||||
<div class="info-long-block">
|
||||
<div class="info-long-title">反馈意见</div>
|
||||
<div class="info-long-content" v-html="item.feedbackOpinion"></div>
|
||||
</div>
|
||||
<div class="info-long-block" v-if="item.scoreSign">
|
||||
<div class="info-long-title">反馈人签字</div>
|
||||
<div class="info-sign-image">
|
||||
<van-image width="200" height="100" :src="item.scoreSign"/>
|
||||
</div>
|
||||
</van-panel>
|
||||
</template>
|
||||
</van-cell-group>
|
||||
</div>
|
||||
<div class="info-long-block">
|
||||
<div class="info-long-title">附件</div>
|
||||
<upload v-if="item.files&&item.files.length" :del="false" :files.sync="item.files" view></upload>
|
||||
<div class="info-long-content" v-else>无</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-if="handle">
|
||||
<div id="wechat_proposal_handle">
|
||||
<div id="wechat_proposal_handle" class="info-card info-input-area">
|
||||
<slot name="handle"></slot>
|
||||
</div>
|
||||
</template>
|
||||
@@ -268,6 +500,9 @@ module.exports = {
|
||||
createUserSignNames: [],
|
||||
undertakeActiveNames: [],
|
||||
briefActiveNames: [],
|
||||
hostUnit: '',
|
||||
helpUnit: [],
|
||||
unitOptions: [],
|
||||
viewData: {},
|
||||
}
|
||||
},
|
||||
@@ -291,27 +526,69 @@ module.exports = {
|
||||
'upload': httpVueLoader('/components/plugins/VantFileUpload.vue'),
|
||||
},
|
||||
methods: {
|
||||
async openView(id) {
|
||||
this.viewData = await getProposalInfo(id)
|
||||
if (this.viewData.files) {
|
||||
this.viewData.files = JSON.parse(this.viewData.files)
|
||||
// 将 PC 端可能返回的对象或数组统一转换为数组,方便手机端模板用同一套 v-for 渲染审核记录。
|
||||
toArray(data) {
|
||||
if (!data) {
|
||||
return []
|
||||
}
|
||||
if (this.viewData.replyInfo) {
|
||||
const keys = Object.keys(this.viewData.replyInfo)
|
||||
|
||||
keys.map(k => {
|
||||
if (this.viewData.replyInfo[k].replyFiles != null) {
|
||||
this.viewData.replyInfo[k].replyFiles = JSON.parse(this.viewData.replyInfo[k].replyFiles)
|
||||
return Array.isArray(data) ? data : [data]
|
||||
},
|
||||
// 解析后端返回的附件 JSON 字符串;如果已经是数组则原样返回,返回值统一交给 upload 组件展示。
|
||||
parseList(data) {
|
||||
if (!data) {
|
||||
return []
|
||||
}
|
||||
return typeof data === 'string' ? JSON.parse(data) : data
|
||||
},
|
||||
// 根据预立案审核记录里的 other.hostUnit/helpUnit 转换主办、协办单位名称,用于对齐 PC 端承办单位展示。
|
||||
setCaseAuditUnits() {
|
||||
const caseAudit = this.toArray(this.viewData.caseAudit)[0]
|
||||
this.hostUnit = ''
|
||||
this.helpUnit = []
|
||||
if (!caseAudit || this.viewData.resultCode === 'notGive' || !caseAudit.other) {
|
||||
return
|
||||
}
|
||||
const other = typeof caseAudit.other === 'string' ? JSON.parse(caseAudit.other) : caseAudit.other
|
||||
const host = this.unitOptions.find(v => v.id === other.hostUnit)
|
||||
if (host) {
|
||||
this.hostUnit = host.unitName
|
||||
}
|
||||
if (other.helpUnit && other.helpUnit.length > 0) {
|
||||
const helpUnit = []
|
||||
other.helpUnit.forEach((v, i) => {
|
||||
const unit = this.unitOptions.find(z => other.helpUnit[i] === z.id)
|
||||
if (unit) {
|
||||
helpUnit.push(unit.unitName)
|
||||
}
|
||||
})
|
||||
if (this.viewData.feedback && this.viewData.feedback.length > 0) {
|
||||
this.viewData.feedback.map(v => {
|
||||
v.files = JSON.parse(v.files)
|
||||
this.helpUnit = helpUnit.join('、')
|
||||
}
|
||||
},
|
||||
// 查询提案详情并处理手机端展示所需的数据结构:附件转数组、答复附件转 files、反馈附件转数组。
|
||||
openView(id) {
|
||||
return getProposalInfo(id).then((data) => {
|
||||
this.viewData = data
|
||||
if (this.viewData.files) {
|
||||
this.$set(this.viewData, 'files', this.parseList(this.viewData.files))
|
||||
}
|
||||
if (this.viewData.replyInfo) {
|
||||
const keys = Object.keys(this.viewData.replyInfo)
|
||||
|
||||
keys.map(k => {
|
||||
if (this.viewData.replyInfo[k].replyFiles != null) {
|
||||
this.$set(this.viewData.replyInfo[k], 'files', this.parseList(this.viewData.replyInfo[k].replyFiles))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
if (this.viewData.feedback && this.viewData.feedback.length > 0) {
|
||||
this.viewData.feedback.map(v => {
|
||||
this.$set(v, 'files', this.parseList(v.files))
|
||||
})
|
||||
}
|
||||
this.setCaseAuditUnits()
|
||||
|
||||
this.activeName = this.handle ? "999" : "1"
|
||||
this.activeName = this.handle ? "999" : "1"
|
||||
})
|
||||
},
|
||||
previewFile(filepath, filename, fileList, index) {
|
||||
const doctype = ['doc', 'docx', 'xls', 'xlsx'];
|
||||
@@ -334,7 +611,7 @@ module.exports = {
|
||||
}
|
||||
})
|
||||
const viewer = new Viewer(ele, {
|
||||
title: function (img, obj) {
|
||||
title: (img, obj) => {
|
||||
return img.getAttribute("alt");
|
||||
}
|
||||
});
|
||||
@@ -354,11 +631,15 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.openView(this.proposal_id)
|
||||
if (this.handle) {
|
||||
window.scrollTo(0, document.getElementById('wechat_proposal_handle').offsetTop)
|
||||
}
|
||||
created() {
|
||||
getProposalUndertake().then((data) => {
|
||||
this.unitOptions = data
|
||||
return this.openView(this.proposal_id)
|
||||
}).then(() => {
|
||||
if (this.handle) {
|
||||
window.scrollTo(0, document.getElementById('wechat_proposal_handle').offsetTop)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -368,17 +649,4 @@ module.exports = {
|
||||
color: #323233 !important;
|
||||
}
|
||||
|
||||
.van-cell-group__title {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
.van-sidebar-item {
|
||||
font-size: 16px !important;
|
||||
text-indent: 4px !important;
|
||||
}
|
||||
|
||||
.van-sidebar-item--select::before {
|
||||
left: 5px !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,551 +1,275 @@
|
||||
<!--#
|
||||
layout("/mobile/platform.html"){
|
||||
#-->
|
||||
|
||||
<!--# layout("/mobile/platform.html"){ #-->
|
||||
<style>
|
||||
/* 日期和每个时间段保持完整,同日连续场次由后端统一合并展示。 */
|
||||
.site-audit .reservation-times { text-align:left; font-variant-numeric:tabular-nums; }
|
||||
.site-audit .reservation-time-day { display:flex; align-items:baseline; flex-wrap:wrap; gap:4px 12px; padding:3px 0; line-height:22px; }
|
||||
.site-audit .reservation-time-date { flex:0 0 88px; white-space:nowrap; font-weight:500; }
|
||||
.site-audit .reservation-time-ranges { display:flex; flex-wrap:wrap; gap:2px 12px; }
|
||||
.site-audit .reservation-time-range { white-space:nowrap; }
|
||||
.site-audit .reservation-times { padding:4px 0 6px; font-size:14px; overflow-wrap:normal; }
|
||||
.site-audit .reservation-time-day { display:block; padding:4px 0; }
|
||||
.site-audit .reservation-time-date { display:block; color:#606266; }
|
||||
|
||||
.van-hairline--top-bottom::after, .van-hairline-unset--top-bottom::after {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.van-index-bar__sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.van-doc-card {
|
||||
margin: 14px;
|
||||
padding: 12px 12px 12px 12px;
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 8px 12px #ebedf0;
|
||||
line-height: 20px;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.in-sheet-card {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.van-divider {
|
||||
margin: 6px 0 6px 0px;
|
||||
border-color: lightgray;
|
||||
}
|
||||
|
||||
.van-button--small {
|
||||
border-radius: revert;
|
||||
width: 40%;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.van-col {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.title_span {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
left: -1px;
|
||||
top: 11px;
|
||||
color: #1867b0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cus_overflow {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.showMore {
|
||||
color: #1867b0;
|
||||
}
|
||||
|
||||
.cus_popup {
|
||||
width: 70%;
|
||||
height: 50%;
|
||||
/*border-radius: 10px;*/
|
||||
padding: 10px 14px;
|
||||
font-size: 15px;
|
||||
/*background-color: #E5E7E9;
|
||||
background-image: url("https://www.transparenttextures.com/patterns/green-cup.png");*/
|
||||
}
|
||||
|
||||
.cus_icon {
|
||||
z-index: 10000;
|
||||
color: white;
|
||||
font-size: 35px;
|
||||
position: fixed;
|
||||
top: 81%;
|
||||
left: 44%;
|
||||
}
|
||||
|
||||
.userPopup {
|
||||
max-height: 96%;
|
||||
height: 96%;
|
||||
background-color: #f6f7f9;
|
||||
font-size: 14px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.van-row div {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.firstName {
|
||||
width: 45px;
|
||||
background-color: lightgrey;
|
||||
border-radius: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.content {
|
||||
height: 86%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.no_content {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.van-action-sheet__content {
|
||||
height: 88%;
|
||||
}
|
||||
|
||||
.van-dialog__header {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.van-dialog__confirm {
|
||||
color: #1867b0;
|
||||
}
|
||||
|
||||
.van-field__label {
|
||||
width: 6em;
|
||||
}
|
||||
|
||||
.cus_cell {
|
||||
padding: 4px 0px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.van-checkbox-group {
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.unit {
|
||||
display: inline-block;
|
||||
max-width: 160px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.cus_button {
|
||||
border-top-right-radius: 16px;
|
||||
border-bottom-left-radius: 16px;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.van-dropdown-menu__item {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.audit_icon {
|
||||
font-size: 24px;
|
||||
/*position: absolute;*/
|
||||
font-weight: bolder;
|
||||
right: 0;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
.van-cell__value--alone {
|
||||
text-align: center;
|
||||
}
|
||||
.site-audit .van-doc-card { margin:14px; padding:12px; background:#fff; border-radius:10px; box-shadow:0 8px 12px #ebedf0; line-height:20px; font-size:15px; position:relative; }
|
||||
.site-audit .card-title { font-size:18px; font-weight:bold; color:#1867b0; padding-left:4px; }
|
||||
.site-audit .title-mark { position:absolute; left:0; top:12px; color:#1867b0; font-weight:bold; }
|
||||
.site-audit .card-fields { margin-top:4px; line-height:30px; overflow-wrap:anywhere; }
|
||||
.site-audit .field-label { color:grey; }
|
||||
.site-audit .van-divider { margin:6px 0; }
|
||||
.site-audit .card-footer { display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:8px; }
|
||||
.site-audit .card-footer .van-button { border-radius:8px; height:26px; font-size:16px; margin-left:4px; }
|
||||
.site-audit .process-title { color:#1867b0; font-size:15px; font-weight:600; margin:10px 7px; padding:10px; }
|
||||
.site-audit .audit-actions { display:flex; gap:10px; padding:10px; }
|
||||
.site-audit .site-audit-section { color:#1867b0; font-weight:bold; font-size:16px; }
|
||||
.site-audit .site-audit-loading { padding:24px; text-align:center; }
|
||||
.site-audit .site-audit-text .van-cell__label { white-space:pre-wrap; overflow-wrap:anywhere; }
|
||||
.site-audit .detail-popup { background:#f7f8fa; }
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
|
||||
<m-page-loading v-if="mLoading"></m-page-loading>
|
||||
|
||||
<!--top栏-->
|
||||
<van-nav-bar
|
||||
title="场地预约审核"
|
||||
left-arrow
|
||||
placeholder
|
||||
fixed
|
||||
@click-left="pjaxReplace('/mobile/index')"
|
||||
safe-area-inset-top
|
||||
></van-nav-bar>
|
||||
|
||||
<!--筛选框-->
|
||||
<div class="search-fixed">
|
||||
<van-search
|
||||
v-model="pageForm.searchKeyword"
|
||||
shape="round"
|
||||
maxlength="10"
|
||||
@search="doSearch"
|
||||
placeholder="请输入场地名称、场地地点进行查询"
|
||||
>
|
||||
</van-search>
|
||||
<van-dropdown-menu>
|
||||
<!--<van-dropdown-item v-model="pageForm.meetingTime" :options="meetingTimeList"
|
||||
@change="doSearch"></van-dropdown-item>-->
|
||||
<van-dropdown-item v-model="pageForm.typeId" :options="typeList"
|
||||
@change="doSearch"></van-dropdown-item>
|
||||
<div id="app" class="site-audit" v-cloak>
|
||||
<van-sticky>
|
||||
<van-nav-bar title="${reviewTitle}" left-text="返回" left-arrow @click-left="goBack"></van-nav-bar>
|
||||
<van-search v-model="keyword" placeholder="请输入场地名称或申请人" @search="search" @clear="search"></van-search>
|
||||
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
|
||||
<van-dropdown-item ref="auditFilter" v-model="isAudit" :options="auditOptions" @change="search"
|
||||
@open="filterOpened" @close="filterClosed"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</van-sticky>
|
||||
<van-list v-model="loading" :finished="finished" :error.sync="listError" error-text="加载失败,点击重试"
|
||||
:immediate-check="false" :finished-text="rows.length ? '没有更多了' : ''" @load="loadMore">
|
||||
<div v-for="row in rows" :key="row.id" class="van-doc-card">
|
||||
<div class="card-title van-ellipsis"><span class="title-mark">|</span>{{row.reserve_person}}({{row.loginname}})</div>
|
||||
<div class="card-fields">
|
||||
<div><span class="field-label">场地名称:</span>{{row.site_name}}</div>
|
||||
<div><span class="field-label">所属单位:</span>{{row.reserve_person_unit || '—'}}</div>
|
||||
<div><span class="field-label">预约类型:</span>{{typeName(row.reserve_type)}}</div>
|
||||
<div v-if="row.club_name"><span class="field-label">所属协会:</span>{{row.club_name}}</div>
|
||||
<div><span class="field-label">预约时段:</span><div class="reservation-times">
|
||||
<div v-for="group in row.reservationTimeGroups" :key="group.date" class="reservation-time-day">
|
||||
<span class="reservation-time-date">{{group.date}}</span>
|
||||
<div class="reservation-time-ranges"><span v-for="(range,index) in group.ranges" :key="index" class="reservation-time-range">{{range.start}}–{{range.end}}</span></div>
|
||||
</div>
|
||||
|
||||
<!--列表-->
|
||||
<div style="margin-top: 116px">
|
||||
<van-list
|
||||
v-model="loading"
|
||||
:finished="finished" :immediate-check="false"
|
||||
:finished-text="tableData.length>0?'没有更多了':''"
|
||||
@load="onLoad">
|
||||
|
||||
<div class="van-doc-card" v-for="o in tableData">
|
||||
<div @click="openView(o.id, o.modulename)">
|
||||
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<div style="width: 74%">
|
||||
<div class="van-ellipsis title">
|
||||
<span class="title_span">|</span>
|
||||
<span>{{o.name}}</span>
|
||||
</div>
|
||||
<div style="color: grey">{{o.address}}</div>
|
||||
</div>
|
||||
<div style="color: #1867b0;">
|
||||
{{o.meetingtypename}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<van-divider></van-divider>
|
||||
|
||||
<div style="margin-top: 10px">
|
||||
<van-row>
|
||||
<van-col span="12"><span style="color: grey"> 联系人:</span>{{o.unitname ? (o.unitname +
|
||||
'') : '' + '' + o.contact_person}}
|
||||
</van-col>
|
||||
</van-row>
|
||||
<van-row>
|
||||
<van-col span="12"><span style="color: grey">联系方式:</span>{{o.contact_phone}}</van-col>
|
||||
</van-row>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<!--<div class="cus_overflow" style="line-height: 20px; width: 83%">
|
||||
<span style="color: grey">开放时段:</span>
|
||||
{{o.meetingdescription}}
|
||||
</div>-->
|
||||
<!--<span class="showMore" @click.stop="desc = o.meetingdescription; popupShow = true">查看更多</span>-->
|
||||
</div>
|
||||
|
||||
<div style="display: flex; justify-content: flex-end; margin-top: 4px">
|
||||
<div>
|
||||
<van-button @click.stop="getAuditList(o)" class="cus_button"
|
||||
type="primary" size="small" color="#1867b0"
|
||||
style="margin-right: 8px; width: auto;">已审{{o.audit}}
|
||||
</van-button>
|
||||
<van-button @click.stop="openView(o.id, o.modulename)" class="cus_button"
|
||||
type="primary" size="small" color="#1867b0"
|
||||
style="margin-right: 8px; width: auto;">未审{{o.no_audit}}
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
<span v-if="!row.reservationTimeGroups || !row.reservationTimeGroups.length">—</span>
|
||||
</div></div>
|
||||
</div>
|
||||
<van-divider></van-divider>
|
||||
<div class="card-footer">
|
||||
<!-- 仅状态内容绑定配置颜色,保留字段标签原有灰色。 -->
|
||||
<div><span class="field-label">当前状态:</span><span :style="{color: row.state_color || null}">{{row.state_name}}</span></div>
|
||||
<div>
|
||||
<van-button size="small" type="info" @click="openDetail(row,false)">查看</van-button>
|
||||
<van-button v-if="canReview(row)" size="small" type="primary" @click="openDetail(row,true)">审核</van-button>
|
||||
<van-button v-if="isAudit===true" size="small" type="danger" :disabled="!row.canRevoke || formLoading"
|
||||
:loading="formLoading && revokeId===row.id" @click="openRevoke(row)">撤回</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
<van-empty
|
||||
v-if="mLoading==false&&tableData.length==0"
|
||||
class="custom-image"
|
||||
image="/none.svg"
|
||||
description="暂无数据"
|
||||
></van-empty>
|
||||
</div>
|
||||
|
||||
<!--弹出框-->
|
||||
<van-popup v-model:show="popupShow" class="cus_popup">
|
||||
<pre style="margin: 0; white-space: break-spaces">{{desc}}</pre>
|
||||
</div>
|
||||
</van-list>
|
||||
<van-empty v-if="!loading && !listError && !rows.length" image="/none.svg" description="暂无数据"></van-empty>
|
||||
<van-popup v-model="detailShow" position="right" :style="{height:'100%',width:'100%'}" class="detail-popup" safe-area-inset-bottom>
|
||||
<van-sticky>
|
||||
<van-nav-bar title="申请详情" left-text="返回" left-arrow @click-left="goBack"></van-nav-bar>
|
||||
</van-sticky>
|
||||
<site-audit-info ref="infoRef" :review-api="reviewApi" @loaded="onDetailLoaded"></site-audit-info>
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">${reviewTitle}</div>
|
||||
<van-form @submit="openConfirm(true)">
|
||||
<van-field label="审核人员" readonly v-model="formData.username"></van-field>
|
||||
<van-field label="当前时间" readonly v-model="formData.auditTime"></van-field>
|
||||
<van-field label="审核意见" type="textarea" maxlength="500" show-word-limit required
|
||||
v-model="formData.auditOpinion" placeholder="请输入审核意见"></van-field>
|
||||
</van-form>
|
||||
<div class="audit-actions">
|
||||
<van-button block type="default" :disabled="formLoading" @click="goBack">取消</van-button>
|
||||
<van-button block type="danger" :disabled="!auditReady || formLoading" :loading="formLoading && !pendingPass" @click="openConfirm(false)">拒绝</van-button>
|
||||
<van-button block type="primary" :disabled="!auditReady || formLoading" :loading="formLoading && pendingPass" @click="openConfirm(true)">通过</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
|
||||
<van-icon @click="popupShow = false" class="cus_icon" v-if="popupShow" name="close"></van-icon>
|
||||
|
||||
<!--审核人员-->
|
||||
<van-action-sheet v-model:show="userShow" :title="queryType === 'notAudit' ? '预约列表' : '预约列表'"
|
||||
class="userPopup" @close="popClose">
|
||||
<div :class="queryType == 'notAudit' ? 'content' : 'no_content'">
|
||||
<div v-if="queryType === 'notAudit'" style="text-align: right; width: 96%">
|
||||
<van-tag color="#1867b0" style="margin-right: 10px" @click="cancelAll" size="large" type="primary">
|
||||
取消选中
|
||||
</van-tag>
|
||||
<van-tag color="#1867b0" @click="allIn" size="large" type="primary">全选</van-tag>
|
||||
</div>
|
||||
<div class="van-doc-card in-sheet-card" v-for="(item, i) in userList">
|
||||
<!--<div style="display: flex; justify-content: space-between">
|
||||
<div style="width: 80%">
|
||||
<span>{{moment(item.startTime).format('YYYY-MM-DD HH:mm') + ' ~ ' + moment(item.endTime).format('YYYY-MM-DD HH:mm')}}</span>
|
||||
</div>
|
||||
<div v-if="queryType === 'notAudit'">
|
||||
<span @click="checkAll(i)">全选</span>
|
||||
<span @click="toggleAll(i)" style="margin-left: 4px">反选</span>
|
||||
</div>
|
||||
</div>-->
|
||||
<div style="width: 90%">
|
||||
<div>
|
||||
<span style="display: inline-block; min-width: 40px; max-width: 76px">{{item.username}}</span>
|
||||
<span style="display: inline-block; width: 20px">{{item.sex}}</span>
|
||||
<span style="display: inline-block; width: 76px">{{item.loginname}}</span>
|
||||
<span class="unit">{{item.unitname}}</span>
|
||||
</div>
|
||||
<div style="width: 94%; display: flex">
|
||||
<div style="color: grey;">预约时间:</div>
|
||||
<div>
|
||||
<div v-for="(d,index) in item.concat_day.split(',')">{{getTime(item, d)}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 94%;">
|
||||
<span style="color: grey">参加人员:</span><span>{{item.joinUser}}</span>
|
||||
</div>
|
||||
<div style="width: 94%;">
|
||||
<span style="color: grey">预约事由:</span><span>{{item.reserve_cause}}</span>
|
||||
</div>
|
||||
<div style="width: 94%;">
|
||||
<span style="color: grey">审核状态:</span><span>{{item.stateName}}</span>
|
||||
</div>
|
||||
<div v-if="${@shiro.hasRole('sysadmin')||@shiro.hasRole('xghng')||@shiro.hasRole('A06')}"
|
||||
style="width: 94%;">
|
||||
<span style="color: grey">反馈意见:</span><span>{{item.back_option}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<van-checkbox-group v-model="result" ref="checkboxGroup">
|
||||
<van-cell-group>
|
||||
<van-cell
|
||||
clickable class="cus_cell"
|
||||
:key="item.id"
|
||||
@click="toggle(i)">
|
||||
|
||||
<van-icon class="audit_icon" color="green"
|
||||
v-if="queryType === 'hasAudit' && item.auditState == true"
|
||||
name="passed"></van-icon>
|
||||
<van-icon class="audit_icon" color="grey"
|
||||
v-if="queryType === 'hasAudit' && item.auditState == false"
|
||||
name="close"></van-icon>
|
||||
<template #right-icon v-if="queryType === 'notAudit'">
|
||||
<van-checkbox :name="item.id" ref="checkboxes"></van-checkbox>
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</van-checkbox-group>
|
||||
</div>
|
||||
<!--<div v-if="queryType === 'notAudit'" style="display: flex; justify-content: space-evenly; position: fixed; bottom: 50px; width: 95%">
|
||||
<van-button @click="audit('all', 'reject')" color="#ff976a" size="small" style="height: 38px">一键全部驳回</van-button>
|
||||
<van-button @click="audit('all', 'pass')" color="#1867b0" size="small" style="height: 38px">一键全部通过</van-button>
|
||||
</div>-->
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 20px; width: 100%;z-index: 9999">
|
||||
<div v-if="queryType === 'notAudit'" style="display: flex; justify-content: center">
|
||||
<van-button @click="audit('many', 'reject')" color="#ff976a" size="small"
|
||||
style="height: 38px;border-radius: 10px;margin-right: 20px">驳回
|
||||
</van-button>
|
||||
<van-button @click="audit('many', 'pass')" color="#1867b0" size="small"
|
||||
style="height: 38px;border-radius: 10px">通过
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</van-action-sheet>
|
||||
|
||||
<!--审核弹框-->
|
||||
<van-dialog v-model:show="auditShow" @confirm="auditDo" title="温馨提示" show-cancel-button>
|
||||
<div style="padding-top: 8px; text-align: center; font-size: 14px; color: #646566">{{str}}</div>
|
||||
<van-divider style="margin: 12px 0 1px 0px;"></van-divider>
|
||||
<van-field
|
||||
v-model="reason"
|
||||
rows="2"
|
||||
label="审核意见:"
|
||||
autosize
|
||||
type="textarea"
|
||||
maxlength="50"
|
||||
placeholder="请输入审核意见"
|
||||
show-word-limit
|
||||
></van-field>
|
||||
<van-dialog v-model="confirmShow" title="提示" show-cancel-button :before-close="beforeConfirmClose"
|
||||
:message="pendingPass ? '确定通过该申请吗?' : '确定拒绝该申请吗?拒绝后流程结束。'">
|
||||
</van-dialog>
|
||||
<van-dialog v-model="revokeShow" title="撤回审核" show-cancel-button :before-close="beforeRevokeClose"
|
||||
message="确定撤回本次审核吗?撤回后申请恢复到当前节点待审核。"></van-dialog>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<!--# include("/platform/activity/includeJs/activityUtil.js"){} #-->
|
||||
const siteTypeUtil = new typeUtil()
|
||||
const vue = new Vue({
|
||||
(() => {
|
||||
// PJAX 先执行内联脚本;捕获本次页面,依赖加载完成后才允许创建 Vue。
|
||||
const pageRoot = document.getElementById('app')
|
||||
const startPage = () => {
|
||||
if (document.getElementById('app') !== pageRoot || !pageRoot.isConnected) return
|
||||
<!--# include('./common/info.js'){} #-->
|
||||
new Vue({
|
||||
el: '#app',
|
||||
mixins: [mobileMixins],
|
||||
mixins: [window.popupHistoryMixin],
|
||||
components: {'site-audit-info': SITE_AUDIT_INFO},
|
||||
data() {
|
||||
return {
|
||||
desc: '',
|
||||
list: ['a', 'b'],
|
||||
result: [],
|
||||
reason: '',
|
||||
auditShow: false,
|
||||
popupShow: false,
|
||||
userShow: false,
|
||||
userList: [],
|
||||
userClickList: [],
|
||||
str: '',
|
||||
type: '',
|
||||
auditType: '',
|
||||
queryType: '',
|
||||
typeList: [],
|
||||
meetingTimeList: [{text: '全部时间', value: null}, {text: '即将开始', value: 0}, {text: '已结束', value: 1}],
|
||||
historyPopupKeys: ['filterShow','detailShow','confirmShow','revokeShow'],
|
||||
revokeShow:false, revokeId:'',
|
||||
reviewApi: '${reviewApi}',
|
||||
auditOptions: [{text:'已审核',value:true},{text:'未审核',value:false}],
|
||||
isAudit: false, keyword: '', rows: [], page: 1,
|
||||
loading: false, finished: false, listError: false, requestVersion: 0, requesting: false,
|
||||
filterShow: false, detailShow: false, confirmShow: false,
|
||||
showApprovalForm: false, auditReady: false, pendingPass: false, formLoading: false,
|
||||
formData: {id:'',username:'',auditTime:'',auditOpinion:''}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// DropdownItem 没有弹层 v-model,通过全局历史状态同步关闭内部下拉层。
|
||||
filterShow(value) { if (!value && this.$refs.auditFilter) this.$refs.auditFilter.toggle(false) }
|
||||
},
|
||||
methods: {
|
||||
getTime(o, day) {
|
||||
const week = new Date(day).getDay()
|
||||
const arr = ['日', '一', '二', '三', '四', '五', '六']
|
||||
return moment(day).format('MM月DD日') + ' 周' + arr[week] + ' ' + o.start_time + '-' + o.end_time
|
||||
openRevoke(row) {
|
||||
if (this.formLoading || !row.canRevoke) return
|
||||
this.$set(this, 'revokeId', row.id)
|
||||
this.$set(this, 'revokeShow', true)
|
||||
},
|
||||
async getAuditList(o) {
|
||||
this.queryType = 'hasAudit'
|
||||
const resp = await $.post('/mobile/activity/site/audit/getLeaveUser', {
|
||||
siteId: o.id,
|
||||
auditType: 'hasAudit',
|
||||
// 确认框也进入历史栈;失败保留弹框,请求结束复位 Vant 内部 loading。
|
||||
beforeRevokeClose(action, done) {
|
||||
if (this.formLoading) { done(false); return }
|
||||
if (action!=='confirm') { done(false); this.goBack(); return }
|
||||
this.$set(this, 'formLoading', true)
|
||||
$.post(this.reviewApi + '/doRevoke', {id:this.revokeId}).then((res) => {
|
||||
if (res && res.code===0) {
|
||||
this.clearPopupHistory(() => { this.$toast.success('已撤回审核'); this.search() })
|
||||
} else { this.$toast.fail(res && res.msg || '响应异常,请刷新核对申请状态') }
|
||||
}, () => { this.$toast.fail('请求失败,请刷新核对申请状态') }).always(() => {
|
||||
this.$set(this, 'formLoading', false)
|
||||
done(false)
|
||||
})
|
||||
if (resp.code === 0) {
|
||||
this.userList = resp.data
|
||||
if (this.userList.length === 0) {
|
||||
vant.Toast('暂无数据')
|
||||
},
|
||||
// 菜单采用 PJAX replace 进入,列表返回应回手机首页,不能依赖上一条浏览器历史。
|
||||
// 弹框存在时通过统一栈关闭最上层,重复点击也不会连续退过业务页面。
|
||||
goBack() {
|
||||
if (this.formLoading) return
|
||||
const key = this.revokeShow ? 'revokeShow' : this.confirmShow ? 'confirmShow' : this.detailShow ? 'detailShow' : this.filterShow ? 'filterShow' : null
|
||||
if (key) {
|
||||
window.popupHistory.close(this._popupOwner, key)
|
||||
} else {
|
||||
this.clearPopupHistory(() => pjaxReplace('/mobile/index'))
|
||||
}
|
||||
},
|
||||
typeName(type) { return {1:'个人预约',2:'分工会预约',3:'协会预约'}[type] || '—' },
|
||||
filterOpened() { this.$set(this, 'filterShow', true) },
|
||||
filterClosed() { this.$set(this, 'filterShow', false) },
|
||||
canReview(row) {
|
||||
const stage = this.reviewApi.substring(this.reviewApi.lastIndexOf('/') + 1)
|
||||
return Number(row.reserve_state) === {union:4000,club:4010,school:4020}[stage]
|
||||
},
|
||||
search() {
|
||||
// 筛选变更使旧响应失效,避免未审核和已审核数据混入同一列表。
|
||||
this.$set(this, 'requestVersion', this.requestVersion + 1)
|
||||
this.$set(this, 'requesting', false)
|
||||
this.$set(this, 'rows', [])
|
||||
this.$set(this, 'page', 1)
|
||||
this.$set(this, 'finished', false)
|
||||
this.$set(this, 'listError', false)
|
||||
this.loadMore()
|
||||
},
|
||||
// isAudit 为审核状态,pageNumber/pageSize 为分页;响应 data 含 list 和 totalCount。
|
||||
loadMore() {
|
||||
if (this.requesting || this.finished) return
|
||||
const version = this.requestVersion
|
||||
this.$set(this, 'requesting', true)
|
||||
this.$set(this, 'loading', true)
|
||||
$.post(this.reviewApi + '/pageData', {
|
||||
isAudit: this.isAudit, searchKeyword: this.keyword, pageNumber: this.page, pageSize: 10
|
||||
}).then((res) => {
|
||||
if (version !== this.requestVersion) return
|
||||
if (res.code === 0) {
|
||||
this.$set(this, 'rows', this.rows.concat(res.data.list))
|
||||
this.$set(this, 'page', this.page + 1)
|
||||
this.$set(this, 'finished', this.rows.length >= res.data.totalCount)
|
||||
} else {
|
||||
this.$set(this, 'listError', true)
|
||||
this.$toast.fail(res.msg)
|
||||
}
|
||||
}).fail(() => {
|
||||
if (version === this.requestVersion) this.$set(this, 'listError', true)
|
||||
}).always(() => {
|
||||
if (version === this.requestVersion) {
|
||||
this.$set(this, 'loading', false)
|
||||
this.$set(this, 'requesting', false)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 进入审核时默认同意;仅查看时不填意见,切换申请不保留上一条输入。
|
||||
openDetail(row, approval) {
|
||||
this.$set(this, 'auditReady', false)
|
||||
this.$set(this, 'showApprovalForm', approval)
|
||||
this.$set(this, 'formData', {
|
||||
id: row.id, username: "${@shiro.getPrincipalProperty('username')}",
|
||||
auditTime: moment().format('YYYY-MM-DD HH:mm:ss'), auditOpinion: approval ? '同意' : ''
|
||||
})
|
||||
this.$set(this, 'detailShow', true)
|
||||
this.$nextTick(() => this.$refs.infoRef.onOpen(row.id))
|
||||
},
|
||||
onDetailLoaded(detail) {
|
||||
this.$set(this, 'auditReady', detail.id === this.formData.id && this.canReview(detail))
|
||||
},
|
||||
openConfirm(isPass) {
|
||||
if (this.formLoading || !this.auditReady) return
|
||||
if (!this.formData.auditOpinion.trim()) { this.$toast.fail('请填写审核意见'); return }
|
||||
this.$set(this, 'pendingPass', isPass)
|
||||
this.$set(this, 'confirmShow', true)
|
||||
},
|
||||
// 确认时保留 Dialog,请求收尾通过 done(false) 复位其内部 loading;取消只退一层历史。
|
||||
beforeConfirmClose(action, done) {
|
||||
if (this.formLoading) { done(false); return }
|
||||
if (action === 'confirm') this.review(done)
|
||||
else { done(false); this.goBack() }
|
||||
},
|
||||
// ids 为预约 ID 数组,isPass 为通过/拒绝;响应应为统一 code/msg,空响应不能视为成功。
|
||||
review(dialogDone = () => {}) {
|
||||
if (this.formLoading || !this.auditReady) { dialogDone(false); return }
|
||||
const opinion = (this.formData.auditOpinion || '').trim()
|
||||
if (!opinion) { dialogDone(false); this.$toast.fail('请填写审核意见'); return }
|
||||
this.$set(this, 'formLoading', true)
|
||||
$.post(this.reviewApi + '/doReview', {
|
||||
ids: JSON.stringify([this.formData.id]), isPass: this.pendingPass,
|
||||
auditOpinion: opinion
|
||||
}).then((res) => {
|
||||
// jQuery 1.x 的 then 回调异常可能中断后续链,先检查空或非标准响应再读取字段。
|
||||
if (!res || typeof res !== 'object' || typeof res.code !== 'number') {
|
||||
this.$toast.fail('审核响应异常,请刷新核对申请状态')
|
||||
return
|
||||
}
|
||||
this.userList.sort((a, b) => {
|
||||
return b.auditState - a.auditState
|
||||
})
|
||||
this.userShow = true
|
||||
}
|
||||
},
|
||||
cancelAll() {
|
||||
this.$refs.checkboxes.forEach(item => item.toggle(false))
|
||||
},
|
||||
allIn() {
|
||||
this.$refs.checkboxes.forEach(item => item.toggle(true))
|
||||
},
|
||||
checkAll(i) {
|
||||
this.$refs.checkboxGroup[i].children.forEach(item => item.toggle(true))
|
||||
},
|
||||
toggleAll(i) {
|
||||
this.$refs.checkboxGroup[i].children.forEach(item => item.toggle())
|
||||
},
|
||||
toggle(i) {
|
||||
this.$refs.checkboxes[i].toggle();
|
||||
},
|
||||
async auditDo() {
|
||||
let idList = this.result
|
||||
if (this.type === 'all') {
|
||||
idList = this.userList.map(o => o.id)
|
||||
}
|
||||
const resp = await $.post('/mobile/activity/site/audit/audit', {
|
||||
ids: JSON.stringify(idList),
|
||||
auditOpinion: this.reason,
|
||||
isPass: this.auditType === 'pass'
|
||||
})
|
||||
if (resp.code === 0) {
|
||||
vant.Toast(resp.msg)
|
||||
this.doSearch()
|
||||
} else {
|
||||
vant.Toast('操作失败')
|
||||
}
|
||||
this.userShow = false
|
||||
},
|
||||
audit(type, auditType) {
|
||||
this.type = type
|
||||
this.auditType = auditType
|
||||
if (type === 'many' && this.result.length === 0) {
|
||||
vant.Toast('请先选择人员')
|
||||
return
|
||||
}
|
||||
this.str = type === 'many' ? '您选择了' + this.result.length + '个人,请确认您的选择' : '您确定要一键全部审核吗?'
|
||||
this.reason = auditType === 'pass' ? '同意' : '拒绝'
|
||||
this.auditShow = true
|
||||
},
|
||||
async openView(id, modulename) {
|
||||
this.queryType = 'notAudit'
|
||||
this.userList = []
|
||||
const resp = await $.post('/mobile/activity/site/audit/getLeaveUser', {
|
||||
siteId: id,
|
||||
moduleName: modulename,
|
||||
auditType: 'canAudit',
|
||||
})
|
||||
if (resp.code === 0) {
|
||||
this.userList = resp.data
|
||||
this.userClickList = []
|
||||
if (this.userList.length === 0) {
|
||||
vant.Toast('暂无预约数据')
|
||||
} else {
|
||||
this.userShow = true
|
||||
}
|
||||
return
|
||||
}
|
||||
vant.Toast('系统错误,请联系管理员')
|
||||
},
|
||||
popClose() {
|
||||
this.$nextTick(function () {
|
||||
if (this.$refs.checkboxes !== undefined) {
|
||||
this.$refs.checkboxes.forEach(item => item.toggle(false));
|
||||
}
|
||||
})
|
||||
},
|
||||
doSearch() {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.tableData = []
|
||||
this.finished = false
|
||||
this.onLoad()
|
||||
},
|
||||
async onLoad() {
|
||||
this.loading = true
|
||||
$.post('/mobile/activity/site/audit/pageData', this.pageForm).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = this.tableData.concat(res.data.list)
|
||||
if (this.tableData.length === res.data.totalCount) {
|
||||
this.finished = true
|
||||
} else {
|
||||
this.pageForm.pageNumber++
|
||||
}
|
||||
}
|
||||
this.loading = false
|
||||
this.$set(this, 'auditReady', false)
|
||||
this.clearPopupHistory(() => { this.$toast.success('审核成功'); this.search() })
|
||||
} else { this.$toast.fail(res.msg || '审核失败,请刷新核对申请状态') }
|
||||
}).fail(() => { this.$toast.fail('提交失败,请刷新核对申请状态') }).always(() => {
|
||||
// 同时复位表单按钮及 Vant Dialog 内部按钮,失败时保留意见供核对。
|
||||
this.$set(this, 'formLoading', false)
|
||||
dialogDone(false)
|
||||
})
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
this.typeList = await siteTypeUtil.getAllType()
|
||||
this.typeList.unshift({text: '全部场地类型'})
|
||||
if (this.typeList && this.typeList.length > 0) {
|
||||
this.$set(this.pageForm, "typeId", this.typeList[0].value)
|
||||
}
|
||||
this.$set(this.pageForm, "meetingTime", this.meetingTimeList[1].value)
|
||||
this.onLoad()
|
||||
},
|
||||
created() { this.search() },
|
||||
mounted() {
|
||||
// PJAX 替换 DOM 不会自动销毁 Vue,离开时注销弹框历史同步及本页事件。
|
||||
this._siteAuditDispose = () => this.$destroy()
|
||||
$(document).one('pjax:beforeReplace.siteAudit', this._siteAuditDispose)
|
||||
},
|
||||
beforeDestroy() {
|
||||
$(document).off('pjax:beforeReplace.siteAudit', this._siteAuditDispose)
|
||||
this.$set(this, 'requestVersion', this.requestVersion + 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
if (window.popupHistoryMixin) {
|
||||
startPage()
|
||||
} else {
|
||||
// 共用一次加载请求;失败后允许重新进入重试,不能在缺少 mixin 时继续挂载。
|
||||
if (!window.siteAuditHistoryLoading) {
|
||||
window.siteAuditHistoryLoading = $.getScript('/assets/mobile/js/popupHistory.js')
|
||||
.fail(() => { window.siteAuditHistoryLoading = null })
|
||||
}
|
||||
window.siteAuditHistoryLoading.then(() => startPage()).fail(() => {
|
||||
if (document.getElementById('app') === pageRoot) vant.Toast.fail('页面组件加载失败,请重新进入')
|
||||
})
|
||||
}
|
||||
})()
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
<!--# } #-->
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
const SITE_AUDIT_INFO = {
|
||||
template: /*language=HTML*/ `
|
||||
<div>
|
||||
<div v-if="detailLoading" class="site-audit-loading"><van-loading size="24px">加载中...</van-loading></div>
|
||||
<van-tabs v-else v-model="activeName">
|
||||
<van-tab title="申请信息" name="basic">
|
||||
<van-cell-group>
|
||||
<van-cell title="申请信息" class="site-audit-section" :value="expanded ? '点击收起' : '点击展开'"
|
||||
:icon="expanded ? 'arrow-up' : 'arrow-down'" @click="toggleExpanded"></van-cell>
|
||||
<template v-if="expanded">
|
||||
<van-cell title="申请人" :value="viewData.reserve_person"></van-cell>
|
||||
<van-cell title="工号" :value="viewData.loginname"></van-cell>
|
||||
<van-cell title="所属单位" :value="viewData.reserve_person_unit"></van-cell>
|
||||
<van-cell title="联系电话" :value="viewData.reserve_person_phone"></van-cell>
|
||||
<van-cell title="场地名称" :value="viewData.site_name"></van-cell>
|
||||
<van-cell title="预约类型" :value="typeName(viewData.reserve_type)"></van-cell>
|
||||
<van-cell title="所属协会" :value="viewData.club_name || '—'"></van-cell>
|
||||
<!-- value 插槽只给状态内容着色,与审核列表保持一致。 -->
|
||||
<van-cell title="当前状态">
|
||||
<template #default><span :style="{color: viewData.state_color || null}">{{viewData.state_name}}</span></template>
|
||||
</van-cell>
|
||||
<van-cell title="预约时段"><template #label><div class="reservation-times">
|
||||
<div v-for="group in viewData.reservationTimeGroups" :key="group.date" class="reservation-time-day">
|
||||
<span class="reservation-time-date">{{group.date}}</span>
|
||||
<div class="reservation-time-ranges"><span v-for="(range,index) in group.ranges" :key="index" class="reservation-time-range">{{range.start}}–{{range.end}}</span></div>
|
||||
</div>
|
||||
<span v-if="!viewData.reservationTimeGroups || !viewData.reservationTimeGroups.length">—</span>
|
||||
</div></template></van-cell>
|
||||
<van-cell title="预约事由" :label="viewData.reserve_cause" class="site-audit-text"></van-cell>
|
||||
</template>
|
||||
</van-cell-group>
|
||||
</van-tab>
|
||||
<van-tab v-for="(item,index) in viewData.auditListTable" :key="item.auditId || index"
|
||||
:title="item.auditStateName + '信息'" :name="'history-' + index">
|
||||
<van-cell-group>
|
||||
<van-cell title="审核人员" :value="item.auditUserName"></van-cell>
|
||||
<van-cell title="审核时间" :value="item.auditTime"></van-cell>
|
||||
<van-cell title="审核结果" :value="item.auditListName"></van-cell>
|
||||
<van-cell title="审核意见" :label="item.auditOption" class="site-audit-text"></van-cell>
|
||||
</van-cell-group>
|
||||
</van-tab>
|
||||
<slot></slot>
|
||||
</van-tabs>
|
||||
</div>
|
||||
`,
|
||||
props: {reviewApi: {type: String, required: true}},
|
||||
data() { return {viewData: {}, activeName: 'basic', expanded: true, detailLoading: false, requestId: 0} },
|
||||
methods: {
|
||||
typeName(type) { return {1:'个人预约',2:'分工会预约',3:'协会预约'}[type] || '—' },
|
||||
toggleExpanded() { this.$set(this, 'expanded', !this.expanded) },
|
||||
// id 为预约记录主键;返回预约字段及 auditListTable 历史,loaded 通知父页核验可审核状态。
|
||||
onOpen(id) {
|
||||
const requestId = this.requestId + 1
|
||||
this.$set(this, 'requestId', requestId)
|
||||
this.$set(this, 'viewData', {})
|
||||
this.$set(this, 'activeName', 'basic')
|
||||
this.$set(this, 'expanded', true)
|
||||
this.$set(this, 'detailLoading', true)
|
||||
return $.post(this.reviewApi + '/detail', {id}).then((res) => {
|
||||
// 忽略快速切换申请留下的旧响应,避免审核表单与详情不一致。
|
||||
if (requestId !== this.requestId) return
|
||||
if (res.code === 0) {
|
||||
this.$set(this, 'viewData', res.data)
|
||||
this.$emit('loaded', res.data)
|
||||
} else { this.$toast.fail(res.msg) }
|
||||
}).fail(() => {
|
||||
if (requestId === this.requestId) this.$toast.fail('详情加载失败,请返回重试')
|
||||
}).always(() => {
|
||||
if (requestId === this.requestId) this.$set(this, 'detailLoading', false)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,19 +158,8 @@ layout("/mobile/platform.html"){
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.pop {
|
||||
max-height: 70%;
|
||||
width: 90%;
|
||||
padding: 0px 10px 10px 10px;
|
||||
}
|
||||
|
||||
.pop h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pop h4 {
|
||||
line-height: 26px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
@@ -179,7 +168,7 @@ layout("/mobile/platform.html"){
|
||||
|
||||
<!--top栏-->
|
||||
<van-nav-bar
|
||||
@click-left="pjaxReplace('/mobile/index')"
|
||||
@click-left="goBack"
|
||||
fixed
|
||||
left-arrow
|
||||
placeholder
|
||||
@@ -198,7 +187,8 @@ layout("/mobile/platform.html"){
|
||||
>
|
||||
</van-search>
|
||||
<van-dropdown-menu>
|
||||
<van-dropdown-item :options="typeList" @change="doSearch"
|
||||
<van-dropdown-item :options="typeList" @change="doSearch" ref="typeFilter"
|
||||
@open="setPopupState('typeFilterShow',true)" @close="setPopupState('typeFilterShow',false)"
|
||||
v-model="pageForm.typeId"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</div>
|
||||
@@ -208,10 +198,11 @@ layout("/mobile/platform.html"){
|
||||
<van-list
|
||||
:finished="finished"
|
||||
:finished-text="tableData.length>0?'没有更多了':''" :immediate-check="false"
|
||||
:error.sync="listError" error-text="加载失败,点击重试"
|
||||
@load="onLoad"
|
||||
v-model="loading">
|
||||
|
||||
<div class="van-doc-card" v-for="o in tableData">
|
||||
<div class="van-doc-card" v-for="o in tableData" :key="o.id">
|
||||
<div @click="openView(o)">
|
||||
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
@@ -231,7 +222,7 @@ layout("/mobile/platform.html"){
|
||||
|
||||
<div style="margin-top: 10px">
|
||||
<van-row>
|
||||
<van-col span="24"><span style="color: grey"> 联系人:</span>{{(o.unitname
|
||||
<van-col span="24"><span style="color: grey">场地管理员:</span>{{(o.unitname
|
||||
? o.unitname :
|
||||
'') + ' ' + o.contact_person}}
|
||||
</van-col>
|
||||
@@ -261,45 +252,13 @@ layout("/mobile/platform.html"){
|
||||
></van-empty>
|
||||
</div>
|
||||
|
||||
<van-popup class="pop" round v-model:show="show">
|
||||
<div style="text-align: center;padding: 10px 0; font-size: 20px">爱心母婴室管理规定</div>
|
||||
<div>一、基本原则</div>
|
||||
<div style="padding-left: 30px;">
|
||||
<p>1.使用对象:有哺乳需求的本校在职女教职工。</p>
|
||||
<p>2.开放时间:工作日8:00--17:30。</p>
|
||||
<p>3.使用制度:实行预约登记制。凡有哺乳需求的女教职工提前向校工会提出使用申请,经审核通过并开通权限后即可使用。</p>
|
||||
<p>4.日常管理:由校工会负责。</p>
|
||||
</div>
|
||||
|
||||
<div>二、管理人员</div>
|
||||
<div style="padding-left: 30px;">
|
||||
<p>1.负责爱心母婴室使用登记管理。</p>
|
||||
<p>2.定期保养设备,确保正常使用。</p>
|
||||
<p>3.保持室内整洁、卫生安全,做好保洁消毒记录。</p>
|
||||
<p>4.定期收集意见和建议,不断改进服务。</p>
|
||||
</div>
|
||||
<div>三、使用人员</div>
|
||||
<div style="padding-left: 30px;">
|
||||
<p>1.遵守学校安全管理制度和爱心母婴室管理规定。</p>
|
||||
<p>2.严禁吸烟,安全使用电器,爱护公物,损坏赔偿。</p>
|
||||
<p>3.保持室内卫生清洁,勿大声喧哗,不影响楼内工作秩序。</p>
|
||||
<p>4.妥善保管自带物品,冰存的母乳须贴上姓名标签并及时取走。</p>
|
||||
<p>5.不得擅自携他人入内,不做和哺乳无关事宜,使用完毕及时离开。</p>
|
||||
<p>6.欢迎在《爱心母婴室使用意见簿》上留下您宝贵的意见和建议。</p>
|
||||
</div>
|
||||
<br/>
|
||||
<p>联系人:曾钰媛梦 84894774、15850692181</p>
|
||||
|
||||
<van-button @click="toReserve" color="#246fb4" style="width: 100%" type="primary">已知晓并遵守
|
||||
</van-button>
|
||||
</van-popup>
|
||||
|
||||
<div>
|
||||
<van-tabbar v-model="tarBarActive">
|
||||
<van-tabbar-item @click="pjaxReplace('/mobile/activity/site/info')" icon="home-o"
|
||||
<van-tabbar-item @click="navigateTo('/mobile/activity/site/info')" icon="home-o"
|
||||
replace>场地预约
|
||||
</van-tabbar-item>
|
||||
<van-tabbar-item @click="pjaxReplace('/mobile/activity/site/info/my')" icon="manager-o"
|
||||
<van-tabbar-item @click="navigateTo('/mobile/activity/site/info/my')" icon="manager-o"
|
||||
replace>我的预约
|
||||
</van-tabbar-item>
|
||||
</van-tabbar>
|
||||
@@ -308,68 +267,161 @@ layout("/mobile/platform.html"){
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
// PJAX 先执行内联脚本,等待历史组件加载;离开后的旧加载回调不能挂载到新页面。
|
||||
const pageRoot = document.getElementById('app')
|
||||
let pageStarted = false
|
||||
const startPage = () => {
|
||||
if (pageStarted || document.getElementById('app') !== pageRoot || !pageRoot.isConnected) return
|
||||
pageStarted = true
|
||||
<!--# include("/platform/activity/includeJs/activityUtil.js"){} #-->
|
||||
const siteTypeUtil = new typeUtil()
|
||||
const vue = new Vue({
|
||||
new Vue({
|
||||
el: '#app',
|
||||
mixins: [mobileMixins],
|
||||
mixins: [mobileMixins, window.popupHistoryMixin],
|
||||
data() {
|
||||
return {
|
||||
historyPopupKeys: ['typeFilterShow'],
|
||||
typeFilterShow:false, formLoading:false,
|
||||
tarBarActive: 0,
|
||||
list: ['a', 'b'],
|
||||
typeList: [],
|
||||
show: false,
|
||||
siteId: '',
|
||||
// 初始化完成前禁止列表触发请求;requesting 独立于 Vant 的 loading 双向绑定。
|
||||
listReady: false, requesting: false, requestVersion: 0, listError: false,
|
||||
loading: true, mLoading: true,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 筛选下拉层没有 v-model 开关,通过状态同步交给全局历史栈管理。
|
||||
typeFilterShow(value) { if (!value && this.$refs.typeFilter) this.$refs.typeFilter.toggle(false) },
|
||||
},
|
||||
mounted() {
|
||||
this._siteListDispose = () => this.$destroy()
|
||||
$(document).one('pjax:beforeReplace.siteList', this._siteListDispose)
|
||||
},
|
||||
beforeDestroy() {
|
||||
// PJAX 替换 DOM 不会自动销毁实例,显式清理本页监听并触发历史 mixin 注销。
|
||||
$(document).off('pjax:beforeReplace.siteList', this._siteListDispose)
|
||||
// 离开页面后,类型请求和列表请求的旧回调均不能再发请求或追加数据。
|
||||
this.$set(this, 'listReady', false)
|
||||
this.$set(this, 'requestVersion', this.requestVersion + 1)
|
||||
},
|
||||
methods: {
|
||||
setPopupState(key, value) { this.$set(this, key, value) },
|
||||
// 列表由 PJAX replace 进入,导航返回明确回首页;有弹层时只关最上层。
|
||||
goBack() {
|
||||
if (this.formLoading) return
|
||||
const key = this.typeFilterShow ? 'typeFilterShow' : null
|
||||
if (key) window.popupHistory.close(this._popupOwner, key)
|
||||
else this.navigateTo('/mobile/index')
|
||||
},
|
||||
// 切换栏目先清理弹框历史,避免返回时重新出现上一个页面的弹层。
|
||||
navigateTo(url) {
|
||||
if (this.formLoading) return
|
||||
this.clearPopupHistory(() => {
|
||||
if (window.location.pathname !== url) pjaxReplace(url)
|
||||
})
|
||||
},
|
||||
|
||||
openView(o) {
|
||||
this.siteId = o.id
|
||||
if (o.sexlimit === 1 || o.sexlimit === 2) {
|
||||
const sex = o.sexlimit === 1 ? '男' : '女'
|
||||
if ("${@shiro.getPrincipalProperty('sex')}" === sex) {
|
||||
location.href = '/mobile/activity/site/info/reserve?id=' + o.id
|
||||
this.toReserve()
|
||||
} else {
|
||||
vant.Toast('抱歉,该场地仅限' + sex + '性会员预约')
|
||||
}
|
||||
} else {
|
||||
location.href = '/mobile/activity/site/info/reserve?id=' + o.id
|
||||
this.toReserve()
|
||||
}
|
||||
},
|
||||
toReserve() {
|
||||
location.href = '/mobile/activity/site/info/reserve?id=' + this.siteId
|
||||
this.show = false
|
||||
this.clearPopupHistory(()=>{location.href = '/mobile/activity/site/info/reserve?id=' + this.siteId})
|
||||
},
|
||||
doSearch() {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.tableData = []
|
||||
this.finished = false
|
||||
if (!this.listReady || this._isDestroyed || this._isBeingDestroyed) return
|
||||
// 新筛选拥有独立请求版本,旧响应及其 always 不能污染新列表或关闭新请求的 loading。
|
||||
this.$set(this, 'requestVersion', this.requestVersion + 1)
|
||||
this.$set(this, 'requesting', false)
|
||||
this.$set(this.pageForm, 'pageNumber', 1)
|
||||
this.$set(this.pageForm, 'totalCount', 0)
|
||||
this.$set(this, 'tableData', [])
|
||||
this.$set(this, 'finished', false)
|
||||
this.$set(this, 'listError', false)
|
||||
this.onLoad()
|
||||
},
|
||||
async onLoad() {
|
||||
this.loading = true
|
||||
$.post('/mobile/activity/site/info/pageData', this.pageForm).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = this.tableData.concat(res.data.list)
|
||||
if (this.tableData.length === res.data.totalCount) {
|
||||
this.finished = true
|
||||
} else {
|
||||
this.pageForm.pageNumber++
|
||||
}
|
||||
onLoad() {
|
||||
if (!this.listReady || this.requesting || this.finished || this._isDestroyed || this._isBeingDestroyed) return
|
||||
const version = this.requestVersion
|
||||
// 请求携带筛选和页码快照;成功后才推进页码,失败重试仍请求原页。
|
||||
const params = Object.assign({}, this.pageForm)
|
||||
this.$set(this, 'requesting', true)
|
||||
this.$set(this, 'loading', true)
|
||||
this.$set(this, 'listError', false)
|
||||
return $.post('/mobile/activity/site/info/pageData', params).then((res) => {
|
||||
if (version !== this.requestVersion) return
|
||||
if (!res || res.code !== 0 || !res.data || !Array.isArray(res.data.list)) {
|
||||
this.$set(this, 'listError', true)
|
||||
vant.Toast(res && res.msg || '场地列表加载失败,请重试')
|
||||
return
|
||||
}
|
||||
this.loading = false
|
||||
const rows = this.tableData.concat(res.data.list)
|
||||
this.$set(this, 'tableData', rows)
|
||||
this.$set(this.pageForm, 'totalCount', res.data.totalCount)
|
||||
this.$set(this, 'finished', res.data.list.length === 0 || rows.length >= res.data.totalCount)
|
||||
this.$set(this.pageForm, 'pageNumber', params.pageNumber + 1)
|
||||
}, () => {
|
||||
if (version !== this.requestVersion) return
|
||||
this.$set(this, 'listError', true)
|
||||
vant.Toast('场地列表加载失败,请重试')
|
||||
}).always(() => {
|
||||
if (version !== this.requestVersion) return
|
||||
this.$set(this, 'requesting', false)
|
||||
this.$set(this, 'loading', false)
|
||||
this.$set(this, 'mLoading', false)
|
||||
})
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
this.typeList = await siteTypeUtil.getAllType()
|
||||
this.typeList.unshift({text: '全部场地类型'})
|
||||
if (this.typeList && this.typeList.length > 0) {
|
||||
this.$set(this.pageForm, "typeId", this.typeList[0].value)
|
||||
}
|
||||
this.onLoad()
|
||||
created() {
|
||||
// 首屏取得职工之家实际类型 ID 后才查询;缺失或失败时不降级为全部类型。
|
||||
siteTypeUtil.getAllType().then((rows) => {
|
||||
if (this._isDestroyed || this._isBeingDestroyed) return
|
||||
this.$set(this, 'typeList', rows)
|
||||
if (!rows.length) {
|
||||
this.$set(this, 'listError', true)
|
||||
vant.Toast('未配置职工之家场地类型')
|
||||
return
|
||||
}
|
||||
this.$set(this.pageForm, 'typeId', rows[0].value)
|
||||
this.$set(this, 'listReady', true)
|
||||
this.doSearch()
|
||||
}, () => {
|
||||
if (this._isDestroyed || this._isBeingDestroyed) return
|
||||
this.$set(this, 'listError', true)
|
||||
vant.Toast('场地类型加载失败,请重新进入')
|
||||
}).always(() => {
|
||||
if (this._isDestroyed || this._isBeingDestroyed) return
|
||||
if (!this.listReady) {
|
||||
this.$set(this, 'loading', false)
|
||||
this.$set(this, 'mLoading', false)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
if (window.popupHistoryMixin) startPage()
|
||||
else {
|
||||
if (!window.siteListHistoryLoading) {
|
||||
window.siteListHistoryLoading = $.getScript('/assets/mobile/js/popupHistory.js')
|
||||
.fail(() => { window.siteListHistoryLoading = null })
|
||||
}
|
||||
window.siteListHistoryLoading.then(() => startPage()).fail(() => {
|
||||
if (document.getElementById('app')===pageRoot) vant.Toast.fail('页面组件加载失败,请重新进入')
|
||||
})
|
||||
}
|
||||
})()
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
|
||||
@@ -163,13 +163,21 @@ layout("/mobile/platform.html"){
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 我的预约按日期展示完整时段,不再重复使用申请中某一条记录的起止时间。 */
|
||||
.site-my-times { font-variant-numeric:tabular-nums; line-height:22px; }
|
||||
.site-my-day { padding:4px 0; }
|
||||
.site-my-date { font-weight:500; }
|
||||
.site-my-ranges { display:flex; flex-wrap:wrap; gap:2px 12px; }
|
||||
.site-my-ranges span { white-space:nowrap; }
|
||||
</style>
|
||||
<div id="app" v-cloak>
|
||||
|
||||
<m-page-loading v-if="mLoading"></m-page-loading>
|
||||
|
||||
<!--top栏-->
|
||||
<van-nav-bar
|
||||
@click-left="pjaxReplace('/mobile/index')"
|
||||
@click-left="goBack"
|
||||
fixed
|
||||
left-arrow
|
||||
placeholder
|
||||
@@ -188,7 +196,8 @@ layout("/mobile/platform.html"){
|
||||
>
|
||||
</van-search>
|
||||
<van-dropdown-menu>
|
||||
<van-dropdown-item :title="pageForm.time" @open="" ref="item">
|
||||
<van-dropdown-item :title="pageForm.time" ref="item"
|
||||
@open="setPopupState('monthFilterShow',true)" @close="setPopupState('monthFilterShow',false)">
|
||||
<van-cell center title="查询全部">
|
||||
<template #right-icon>
|
||||
<van-switch active-color="#246fb4" size="24"
|
||||
@@ -208,7 +217,8 @@ layout("/mobile/platform.html"){
|
||||
</van-button>
|
||||
</div>
|
||||
</van-dropdown-item>
|
||||
<van-dropdown-item :options="typeList" @change="doSearch"
|
||||
<van-dropdown-item :options="typeList" @change="doSearch" ref="typeFilter"
|
||||
@open="setPopupState('typeFilterShow',true)" @close="setPopupState('typeFilterShow',false)"
|
||||
v-model="pageForm.typeId"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</div>
|
||||
@@ -248,10 +258,14 @@ layout("/mobile/platform.html"){
|
||||
</van-row>
|
||||
<van-row>
|
||||
<van-col span="24">
|
||||
<span style="color: grey; float: left">预约时间:</span>
|
||||
<span v-for="(item,index) in o.concat_day.split(',')">
|
||||
<div :style="index !== 0 ? 'text-indent: 5em' : ''">{{getTime(o, item)}}</div>
|
||||
</span>
|
||||
<span style="color: grey">预约时间:</span>
|
||||
<div class="site-my-times">
|
||||
<div v-for="group in o.reservationTimeGroups" :key="group.date" class="site-my-day">
|
||||
<div class="site-my-date">{{group.date}}</div>
|
||||
<div class="site-my-ranges"><span v-for="(range,index) in group.ranges" :key="index">{{range.start}}–{{range.end}}</span></div>
|
||||
</div>
|
||||
<span v-if="!o.reservationTimeGroups || !o.reservationTimeGroups.length">—</span>
|
||||
</div>
|
||||
</van-col>
|
||||
</van-row>
|
||||
<van-row>
|
||||
@@ -263,10 +277,10 @@ layout("/mobile/platform.html"){
|
||||
<span style="color: #f56c6c" v-if="o.stateaudittype==1">{{o.statename}}</span>
|
||||
</van-col>
|
||||
</van-row>
|
||||
<van-row v-if="o.stateaudittype == '1'">
|
||||
<van-row v-if="Number(o.reserve_state) === 4040">
|
||||
<van-col span="24">
|
||||
<span style="color: grey">审核意见:</span>
|
||||
<span>{{JSON.parse(o.auditlist).find(x => x.auditState === false).auditOption}}</span>
|
||||
<span>{{rejectionOpinion(o)}}</span>
|
||||
</van-col>
|
||||
</van-row>
|
||||
</div>
|
||||
@@ -295,7 +309,8 @@ layout("/mobile/platform.html"){
|
||||
</div>
|
||||
|
||||
|
||||
<van-dialog @confirm="backDo" show-cancel-button title="反馈意见" v-model="show">
|
||||
<van-dialog v-model="cancelShow" title="撤销预约" show-cancel-button :before-close="beforeCancelClose">确定撤销此预约?已审核的申请不能撤销。</van-dialog>
|
||||
<van-dialog :before-close="beforeFeedbackClose" show-cancel-button title="反馈意见" v-model="show">
|
||||
<van-field
|
||||
autosize
|
||||
label="反馈意见:"
|
||||
@@ -310,10 +325,10 @@ layout("/mobile/platform.html"){
|
||||
|
||||
<div>
|
||||
<van-tabbar v-model="tarBarActive">
|
||||
<van-tabbar-item @click="pjaxReplace('/mobile/activity/site/info')" icon="home-o"
|
||||
<van-tabbar-item @click="navigateTo('/mobile/activity/site/info')" icon="home-o"
|
||||
replace>场地预约
|
||||
</van-tabbar-item>
|
||||
<van-tabbar-item @click="pjaxReplace('/mobile/activity/site/info/my')" icon="manager-o"
|
||||
<van-tabbar-item @click="navigateTo('/mobile/activity/site/info/my')" icon="manager-o"
|
||||
replace>我的预约
|
||||
</van-tabbar-item>
|
||||
</van-tabbar>
|
||||
@@ -322,13 +337,21 @@ layout("/mobile/platform.html"){
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
// PJAX 先执行内联脚本,等待历史组件加载;离开后的旧加载回调不能挂载到新页面。
|
||||
const pageRoot = document.getElementById('app')
|
||||
const startPage = () => {
|
||||
if (document.getElementById('app') !== pageRoot || !pageRoot.isConnected) return
|
||||
<!--# include("/platform/activity/includeJs/activityUtil.js"){} #-->
|
||||
const siteTypeUtil = new typeUtil()
|
||||
const vue = new Vue({
|
||||
new Vue({
|
||||
el: '#app',
|
||||
mixins: [mobileMixins],
|
||||
mixins: [mobileMixins, window.popupHistoryMixin],
|
||||
data() {
|
||||
return {
|
||||
historyPopupKeys: ['show','cancelShow','typeFilterShow','monthFilterShow'],
|
||||
typeFilterShow:false, monthFilterShow:false,
|
||||
cancelShow:false,cancelId:'',formLoading:false,
|
||||
tarBarActive: 1,
|
||||
time: new Date(),
|
||||
option: '',
|
||||
@@ -339,7 +362,60 @@ layout("/mobile/platform.html"){
|
||||
info: {},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 筛选下拉层没有 v-model 开关,通过状态同步交给全局历史栈管理。
|
||||
typeFilterShow(value) { if (!value && this.$refs.typeFilter) this.$refs.typeFilter.toggle(false) },
|
||||
monthFilterShow(value) { if (!value && this.$refs.item) this.$refs.item.toggle(false) },
|
||||
},
|
||||
mounted() {
|
||||
this._siteListDispose = () => this.$destroy()
|
||||
$(document).one('pjax:beforeReplace.siteList', this._siteListDispose)
|
||||
},
|
||||
beforeDestroy() {
|
||||
// PJAX 替换 DOM 不会自动销毁实例,显式清理本页监听并触发历史 mixin 注销。
|
||||
$(document).off('pjax:beforeReplace.siteList', this._siteListDispose)
|
||||
},
|
||||
methods: {
|
||||
// 历史审核记录可能是 JSON 字符串、数组或空值;仅取最近一条拒绝意见,缺失时不阻断整页渲染。
|
||||
rejectionOpinion(row) {
|
||||
let history = row.auditlist || row.auditList || []
|
||||
if (typeof history === 'string') {
|
||||
try { history = JSON.parse(history) } catch (error) { return '—' }
|
||||
}
|
||||
if (!Array.isArray(history)) return '—'
|
||||
const rejection = history.slice().reverse().find(item => item && item.auditState === false)
|
||||
return rejection && rejection.auditOption || '—'
|
||||
},
|
||||
setPopupState(key, value) { this.$set(this, key, value) },
|
||||
// 列表由 PJAX replace 进入,导航返回明确回首页;有弹层时只关最上层。
|
||||
goBack() {
|
||||
if (this.formLoading) return
|
||||
const key = this.cancelShow ? 'cancelShow' : this.show ? 'show' : this.monthFilterShow ? 'monthFilterShow' : this.typeFilterShow ? 'typeFilterShow' : null
|
||||
if (key) window.popupHistory.close(this._popupOwner, key)
|
||||
else this.navigateTo('/mobile/index')
|
||||
},
|
||||
// 切换栏目先清理弹框历史,避免返回时重新出现上一个页面的弹层。
|
||||
navigateTo(url) {
|
||||
if (this.formLoading) return
|
||||
this.clearPopupHistory(() => {
|
||||
if (window.location.pathname !== url) pjaxReplace(url)
|
||||
})
|
||||
},
|
||||
|
||||
// Dialog 保持打开直至成功,取消仅回退对应弹框历史;校验失败保留已输入内容。
|
||||
beforeCancelClose(action, done) {
|
||||
done(false)
|
||||
if (this.formLoading) return
|
||||
if (action==='confirm') this.cancelBooking()
|
||||
else window.popupHistory.close(this._popupOwner, 'cancelShow')
|
||||
},
|
||||
beforeFeedbackClose(action, done) {
|
||||
done(false)
|
||||
if (this.formLoading) return
|
||||
if (action==='confirm') this.backDo()
|
||||
else window.popupHistory.close(this._popupOwner, 'show')
|
||||
},
|
||||
cancelBooking(){if(this.formLoading)return;this.formLoading=true;$.post('/mobile/activity/site/info/rollback',{id:this.cancelId}).then((res)=>{vant.Toast(res.msg);if(res.code===0)this.clearPopupHistory(()=>this.doSearch())}).always(()=>{this.formLoading=false})},
|
||||
onConfirm() {
|
||||
if (this.pageForm.timeSwitch === true) {
|
||||
this.timeList = [{text: '全部', value: '全部'}]
|
||||
@@ -350,64 +426,28 @@ layout("/mobile/platform.html"){
|
||||
}]
|
||||
}
|
||||
this.$set(this.pageForm, "time", this.timeList[0].value)
|
||||
this.$refs.item.toggle()
|
||||
window.popupHistory.close(this._popupOwner, 'monthFilterShow')
|
||||
this.doSearch()
|
||||
},
|
||||
timeFormatter(type, val) {
|
||||
if (type === 'year') {
|
||||
return val + `年`;
|
||||
return val + '年';
|
||||
}
|
||||
if (type === 'month') {
|
||||
return val + `月`;
|
||||
return val + '月';
|
||||
}
|
||||
return val;
|
||||
},
|
||||
async rollback(o) {
|
||||
const self = this
|
||||
const res = await $.post("/platform/activity/site/reserve/isCanRollBack", {id: o.id});
|
||||
if (res === true) {
|
||||
vant.Toast('此预约状态下不能进行撤回操作!')
|
||||
return
|
||||
}
|
||||
vant.Dialog.confirm({
|
||||
title: '温馨提示',
|
||||
message: '您是否要撤销此预约?',
|
||||
}).then(async () => {
|
||||
const resp = await $.post('/mobile/activity/site/info/rollback', {
|
||||
id: o.id
|
||||
})
|
||||
vant.Toast(resp.msg)
|
||||
if (resp.code === 0) {
|
||||
self.doSearch()
|
||||
}
|
||||
}).catch(() => {
|
||||
});
|
||||
rollback(o) {
|
||||
this.cancelId=o.id;this.cancelShow=true;
|
||||
},
|
||||
backOption(o) {
|
||||
this.info = o
|
||||
this.option = ''
|
||||
this.show = true
|
||||
},
|
||||
async backDo() {
|
||||
if (this.option === '') {
|
||||
vant.Toast('请填写反馈意见')
|
||||
return
|
||||
}
|
||||
const res = await $.post('/mobile/activity/site/info/backOption', {
|
||||
id: this.info.id,
|
||||
option: this.option
|
||||
})
|
||||
if (res.code === 0) {
|
||||
this.doSearch()
|
||||
vant.Toast(res.msg)
|
||||
} else {
|
||||
vant.Toast('操作失败')
|
||||
}
|
||||
},
|
||||
getTime(o, day) {
|
||||
const week = new Date(day).getDay()
|
||||
const arr = ['日', '一', '二', '三', '四', '五', '六']
|
||||
return moment(day).format('MM月DD日') + ' 周' + arr[week] + ' ' + o.start_time + '-' + o.end_time
|
||||
backDo() {
|
||||
if(this.formLoading)return;if(!this.option.trim()){vant.Toast('请填写反馈意见');return}this.formLoading=true;$.post('/mobile/activity/site/info/backOption',{id:this.info.id,option:this.option}).then((res)=>{vant.Toast(res.msg);if(res.code===0)this.clearPopupHistory(()=>this.doSearch())}).always(()=>{this.formLoading=false});
|
||||
},
|
||||
doSearch() {
|
||||
this.pageForm.pageNumber = 1
|
||||
@@ -415,34 +455,42 @@ layout("/mobile/platform.html"){
|
||||
this.finished = false
|
||||
this.onLoad()
|
||||
},
|
||||
async onLoad() {
|
||||
this.loading = true
|
||||
$.post('/mobile/activity/site/info/myReserve', this.pageForm).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = this.tableData.concat(res.data.list)
|
||||
if (this.tableData.length === res.data.totalCount) {
|
||||
this.finished = true
|
||||
} else {
|
||||
this.pageForm.pageNumber++
|
||||
}
|
||||
}
|
||||
this.loading = false
|
||||
})
|
||||
onLoad() {
|
||||
// 类型未加载成功时停止请求,避免筛选操作意外查询其他场地类型。
|
||||
if (this.pageForm.typeId == null) { this.$set(this,'loading',false); return }
|
||||
this.loading=true;return $.post('/mobile/activity/site/info/myReserve',this.pageForm).then((res)=>{if(res.code===0){this.tableData=this.tableData.concat(res.data.list);this.finished=this.tableData.length>=res.data.totalCount;if(!this.finished)this.pageForm.pageNumber++}else{vant.Toast(res.msg)}}).always(()=>{this.loading=false});
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
this.typeList = await siteTypeUtil.getAllType()
|
||||
this.typeList.unshift({text: '全部场地类型'})
|
||||
if (this.typeList && this.typeList.length > 0) {
|
||||
this.$set(this.pageForm, "typeId", this.typeList[0].value)
|
||||
}
|
||||
//this.timeList.unshift({text: moment().format('YYYY-MM'), value: moment().format('YYYY-MM')})
|
||||
this.timeList = [{text: '全部', value: '全部'}]
|
||||
this.$set(this.pageForm, "time", this.timeList[0].value)
|
||||
this.$set(this.pageForm, "timeSwitch", true)
|
||||
this.onLoad()
|
||||
created() {
|
||||
// 本页只查询职工之家,使用接口返回的实际类型 ID;类型缺失时不降级成全部类型查询。
|
||||
siteTypeUtil.getAllType().then((rows) => {
|
||||
const types = rows
|
||||
this.$set(this, 'typeList', types)
|
||||
if (!types.length) {
|
||||
this.$set(this, 'finished', true)
|
||||
vant.Toast('未配置职工之家场地类型')
|
||||
return
|
||||
}
|
||||
this.$set(this.pageForm, 'typeId', types[0].value)
|
||||
this.$set(this, 'timeList', [{text:'全部',value:'全部'}])
|
||||
this.$set(this.pageForm, 'time', '全部')
|
||||
this.$set(this.pageForm, 'timeSwitch', true)
|
||||
this.onLoad()
|
||||
}, () => { vant.Toast('场地类型加载失败,请重新进入') });
|
||||
}
|
||||
})
|
||||
}
|
||||
if (window.popupHistoryMixin) startPage()
|
||||
else {
|
||||
if (!window.siteListHistoryLoading) {
|
||||
window.siteListHistoryLoading = $.getScript('/assets/mobile/js/popupHistory.js')
|
||||
.fail(() => { window.siteListHistoryLoading = null })
|
||||
}
|
||||
window.siteListHistoryLoading.then(() => startPage()).fail(() => {
|
||||
if (document.getElementById('app')===pageRoot) vant.Toast.fail('页面组件加载失败,请重新进入')
|
||||
})
|
||||
}
|
||||
})()
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
|
||||
@@ -23,27 +23,28 @@ layout("/mobile/platform.html"){
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.time {
|
||||
width: 45%;
|
||||
.site-reserve-slots .time {
|
||||
display: block;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.num {
|
||||
width: 30%;
|
||||
/* 长禁用原因由内容撑开行高,避免固定高度导致文字覆盖下一场次。 */
|
||||
.site-reserve-slots .state {
|
||||
width: 100%;
|
||||
display: block;
|
||||
line-height: 18px;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
text-align: center;
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.state {
|
||||
width: 80px;
|
||||
height: 36px;
|
||||
text-align: center;
|
||||
background-color: #f2f2f2;
|
||||
border-radius: 2px;
|
||||
color: #929292;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 10px 0px;
|
||||
.site-reserve-slots {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 18px 16px;
|
||||
padding: 8px 0 66px;
|
||||
background-color: white;
|
||||
width: 95%;
|
||||
margin: 0 auto;
|
||||
@@ -51,10 +52,53 @@ layout("/mobile/platform.html"){
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
tr {
|
||||
line-height: 36px;
|
||||
/* 单个按钮容纳时间和状态,选中仅改变外观,继续复用原有时段选择逻辑。 */
|
||||
.site-reserve-slots .slot-button {
|
||||
min-width: 0;
|
||||
min-height: 54px;
|
||||
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 .slot-button.is-selected {
|
||||
background: #ecf6ff;
|
||||
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 {
|
||||
padding: 10px;
|
||||
font-size: 15px;
|
||||
@@ -141,6 +185,11 @@ layout("/mobile/platform.html"){
|
||||
.van-action-sheet {
|
||||
max-height: 90%;
|
||||
}
|
||||
.reserve-choice.van-cell, .site-reserve-options .van-cell { display:flex; }
|
||||
.reserve-choice .van-cell__value { flex:1; overflow-wrap:anywhere; }
|
||||
.site-reserve-options { max-height:70vh; overflow-y:auto; }
|
||||
.site-reserve-options .van-cell__title { flex:1; white-space:normal; }
|
||||
.site-reserve-options .selected-option { color:#246fb4; }
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
@@ -151,7 +200,7 @@ layout("/mobile/platform.html"){
|
||||
left-arrow
|
||||
placeholder
|
||||
fixed
|
||||
@click-left="pjaxReplace('/mobile/index')"
|
||||
@click-left="goBack"
|
||||
safe-area-inset-top
|
||||
></van-nav-bar>
|
||||
|
||||
@@ -177,34 +226,28 @@ layout("/mobile/platform.html"){
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<table>
|
||||
<tr v-for="item in timeData">
|
||||
<td class="time">{{item.start_time + '-' + item.end_time}}</td>
|
||||
<td class="num">
|
||||
<span v-if="item.code !== -2">
|
||||
{{(item.limitNum - item.reserveNum) + '/' + item.limitNum}}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{0 + '/' + item.limitNum}}
|
||||
</span>
|
||||
</td>
|
||||
<td v-if="item.code !== 1" @click.stop="">
|
||||
<div class="state" :style="'background-color: ' + item.backColor + ';color:' +item.color">
|
||||
{{item.msg}}
|
||||
</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>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div v-if="isFullDay" class="site-reserve-slots full-day-slots">
|
||||
<div v-for="point in fullDayPoints" :key="point.time" class="point-cell"
|
||||
:class="{'in-range':point.inRange,'range-start':point.mark==='起' && currentRange.end,'range-end':point.mark==='终'}">
|
||||
<button type="button" class="slot-button" :class="{'range-boundary':!!point.mark}"
|
||||
:disabled="slotsLoading || point.disabled" :aria-pressed="point.selected" :title="point.reason"
|
||||
@click.stop="chooseFullDayPoint(point)">
|
||||
<span class="time">{{point.time}}</span>
|
||||
<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>
|
||||
<div v-else class="site-reserve-slots">
|
||||
<button v-for="item in timeData" :key="item.start_time + '-' + item.end_time"
|
||||
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 style="position: fixed; bottom: 0; width: 100%">
|
||||
@@ -219,19 +262,20 @@ layout("/mobile/platform.html"){
|
||||
<van-cell title="所属单位">{{unit}}</van-cell>
|
||||
<van-cell title="联系电话">{{phone}}</van-cell>
|
||||
<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)">
|
||||
{{time.fullDay}}
|
||||
</van-tag>
|
||||
</div>
|
||||
</van-cell>
|
||||
<van-cell title="预约地点">{{site.name}}</van-cell>
|
||||
<van-cell title="预约类型" class="reserve_type">
|
||||
<van-radio-group v-model="reserve_type" direction="horizontal">
|
||||
<van-radio :name="1">个人预约</van-radio>
|
||||
<van-radio :name="2">单位预约</van-radio>
|
||||
</van-radio-group>
|
||||
</van-cell>
|
||||
<van-cell title="预约类型" required :value="reserveTypeName" is-link class="reserve-choice" @click="openOptions('type')"></van-cell>
|
||||
<!-- 分工会使用后端查询的当前组织,只读显示;协会仅在协会预约时选择。 -->
|
||||
<van-cell title="所属分工会" v-if="reserve_type===2" :value="unionLoading ? '加载中...' : bookingUnionName || '未配置所属分工会'"></van-cell>
|
||||
<van-cell title="所属协会" v-if="reserve_type===3" class="reserve-choice"
|
||||
:value="reserve_type===3 ? selectedClubName : myClubs.map(club=>club.name).join('、')"
|
||||
:is-link="reserve_type===3" @click="openOptions('club')"></van-cell>
|
||||
<div v-if="reserve_type===3 && !myClubs.length" style="padding:12px;color:#ee0a24">您暂无已通过入会审核的有效协会</div>
|
||||
|
||||
<!--<van-field
|
||||
v-model="joinUser"
|
||||
@@ -248,6 +292,7 @@ layout("/mobile/platform.html"){
|
||||
rows="4"
|
||||
autosize
|
||||
label="预约事由"
|
||||
required
|
||||
type="textarea"
|
||||
maxlength="50"
|
||||
placeholder="请输入预约事由"
|
||||
@@ -255,18 +300,33 @@ layout("/mobile/platform.html"){
|
||||
></van-field>
|
||||
|
||||
<div style="margin: 20px 0px; display: flex; justify-content: space-between;padding: 0px 22px;">
|
||||
<van-button @click="message = '';show = false" color="#246fb4" style="width: 47%" size="small" plain
|
||||
<van-button @click="closeConfirmation" :disabled="formLoading" color="#246fb4" style="width: 47%" size="small" plain
|
||||
type="info">关闭
|
||||
</van-button>
|
||||
<van-button @click="reserveDo" color="#246fb4" style="width: 47%" size="small" type="info">确认</van-button>
|
||||
<van-button @click="reserveDo" :loading="formLoading" color="#246fb4" style="width: 47%" size="small" type="info">确认</van-button>
|
||||
</div>
|
||||
</van-action-sheet>
|
||||
<!-- 预约类型和所属协会共用底部选项层,适配底部安全区域;选择或返回只关闭本层。 -->
|
||||
<van-popup v-model="optionsShow" position="bottom" class="site-reserve-options" safe-area-inset-bottom>
|
||||
<van-nav-bar :title="optionKind==='type' ? '选择预约类型' : '选择所属协会'"
|
||||
left-text="返回" left-arrow @click-left="closeOptions"></van-nav-bar>
|
||||
<van-cell v-for="option in selectionOptions" :key="option.value" :title="option.text" clickable
|
||||
:class="{'selected-option':option.value===selectedOption}" @click="selectOption(option)">
|
||||
<template #right-icon><van-icon v-if="option.value===selectedOption" name="success" color="#246fb4"></van-icon></template>
|
||||
</van-cell>
|
||||
<van-empty v-if="!selectionOptions.length" description="暂无可选协会"></van-empty>
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
// PJAX 内联脚本先执行,必须等待弹框历史依赖加载后再创建页面。
|
||||
const pageRoot = document.getElementById('app')
|
||||
const startPage = () => {
|
||||
if (document.getElementById('app') !== pageRoot || !pageRoot.isConnected) return
|
||||
moment.locale('zh_cn');
|
||||
|
||||
function getQueryString(name) {
|
||||
const getQueryString = (name) => {
|
||||
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r != null) return decodeURI(r[2]);
|
||||
@@ -275,12 +335,19 @@ layout("/mobile/platform.html"){
|
||||
|
||||
<!--# include("/platform/activity/includeJs/activityUtil.js"){} #-->
|
||||
const siteTypeUtil = new typeUtil()
|
||||
const vue = new Vue({
|
||||
new Vue({
|
||||
el: '#app',
|
||||
mixins: [mobileMixins],
|
||||
mixins: [mobileMixins, window.popupHistoryMixin],
|
||||
data() {
|
||||
return {
|
||||
reserve_type: 1,
|
||||
historyPopupKeys: ['show','calendarVisible','optionsShow'],
|
||||
optionsShow: false, optionKind: 'type',
|
||||
// 新申请仅开放分工会和协会,个人类型保留用于历史数据。
|
||||
reserveTypeOptions: [{text:'分工会预约',value:2},{text:'协会预约',value:3}],
|
||||
bookingUnionName:'', unionLoading:false,
|
||||
myClubs: [], clubId: '', formLoading: false,
|
||||
// 新预约须主动选择类型,初始不选中任何选项。
|
||||
reserve_type: null,
|
||||
person: "${@shiro.getPrincipalProperty('username')}",
|
||||
unit: "${@shiro.getPrincipalProperty('unit').getName()}",
|
||||
phone: "${@shiro.getPrincipalProperty('mobile')}",
|
||||
@@ -289,14 +356,55 @@ layout("/mobile/platform.html"){
|
||||
site_id: '',
|
||||
site: {},
|
||||
timeData: [],
|
||||
slotRequestVersion:0, slotsLoading:false,
|
||||
times: [],
|
||||
// 每个日期独立保存起止边界;times仍保存原接口要求的逐场次记录。
|
||||
fullDayRanges: {}, slotsFailed:false,
|
||||
weekList: [],
|
||||
joinUser: '',
|
||||
calendarVisible: false,
|
||||
selectDate: null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 分工会与协会预约的时段占用规则一致,切换时保留跨日期选择,仅刷新场次状态;提交时仍由后端校验冲突。
|
||||
reserve_type() { this.getReserve() }
|
||||
},
|
||||
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() {
|
||||
// 未选择时显示提示,避免读取不存在的选项名称。
|
||||
const option = this.reserveTypeOptions.find(item => item.value===this.reserve_type)
|
||||
return option ? option.text : '请选择预约类型'
|
||||
},
|
||||
selectedClubName() {
|
||||
const club = this.myClubs.find(item => item.id===this.clubId)
|
||||
return club ? club.name : '请选择所属协会'
|
||||
},
|
||||
selectionOptions() {
|
||||
return this.optionKind==='type' ? this.reserveTypeOptions : this.myClubs.map(club => ({text:club.name,value:club.id}))
|
||||
},
|
||||
selectedOption() { return this.optionKind==='type' ? this.reserve_type : this.clubId },
|
||||
getWeekTextByDate() {
|
||||
return (date) => {
|
||||
const weekNum = moment(date).day()
|
||||
@@ -331,8 +439,97 @@ layout("/mobile/platform.html"){
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// PJAX 替换页面时显式销毁,触发统一历史 mixin 的监听注销。
|
||||
this._reserveDispose = () => this.$destroy()
|
||||
$(document).one('pjax:beforeReplace.siteReserve', this._reserveDispose)
|
||||
this.loadBookingUnion()
|
||||
$.post('/platform/activity/site/reserve/myClubs').then((res)=>{
|
||||
if(res.code===0){this.$set(this,'myClubs',res.data);this.$set(this,'clubId',res.data.length===1 ? res.data[0].id : '')}
|
||||
else{vant.Toast(res.msg)}
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
$(document).off('pjax:beforeReplace.siteReserve', this._reserveDispose)
|
||||
this.$set(this,'slotRequestVersion',this.slotRequestVersion+1)
|
||||
},
|
||||
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共用所属分工会接口,页面销毁后的响应不再更新界面。
|
||||
loadBookingUnion() {
|
||||
this.$set(this,'unionLoading',true)
|
||||
$.post('/platform/activity/site/reserve/myUnion').then(res=>{
|
||||
if(this._isDestroyed || this._isBeingDestroyed)return
|
||||
if(res && res.code===0)this.$set(this,'bookingUnionName',res.data && res.data.name || '')
|
||||
else vant.Toast(res && res.msg || '所属分工会加载失败')
|
||||
},()=>{if(!this._isDestroyed && !this._isBeingDestroyed)vant.Toast('所属分工会加载失败,请重新进入')}).always(()=>{
|
||||
if(!this._isDestroyed && !this._isBeingDestroyed)this.$set(this,'unionLoading',false)
|
||||
})
|
||||
},
|
||||
// type 显示预约类型,club 仅在协会预约时可选;不修改所属协会的成员校验规则。
|
||||
openOptions(kind) {
|
||||
if (this.formLoading || this.optionsShow || kind==='club' && this.reserve_type!==3) return
|
||||
this.$set(this, 'optionKind', kind)
|
||||
this.$set(this, 'optionsShow', true)
|
||||
},
|
||||
// value 保留原接口要求的类型数字/协会 ID,选择后回填并回退选项层历史。
|
||||
selectOption(option) {
|
||||
if (!this.selectionOptions.some(item => item.value===option.value)) return
|
||||
if (this.optionKind==='type') this.$set(this, 'reserve_type', option.value)
|
||||
else this.$set(this, 'clubId', option.value)
|
||||
this.closeOptions()
|
||||
},
|
||||
closeOptions() { window.popupHistory.close(this._popupOwner, 'optionsShow') },
|
||||
closeConfirmation() {
|
||||
if (this.formLoading) return
|
||||
this.$set(this, 'message', '')
|
||||
window.popupHistory.close(this._popupOwner, 'show')
|
||||
},
|
||||
// 有弹层先关闭最上层;直接链接进入也能明确返回场地列表,不依赖上一条历史。
|
||||
goBack() {
|
||||
if (this.formLoading) return
|
||||
const key = this.optionsShow ? 'optionsShow' : this.show ? 'show' : this.calendarVisible ? 'calendarVisible' : null
|
||||
if (key) window.popupHistory.close(this._popupOwner, key)
|
||||
else this.clearPopupHistory(() => pjaxReplace('/mobile/activity/site/info'))
|
||||
},
|
||||
close(f) {
|
||||
if (this.isFullDay) {
|
||||
if (this.confirmationTimes.length===1) { vant.Toast('必须保留一个时间段'); return }
|
||||
this.clearFullDay(f.split(' ')[0])
|
||||
return
|
||||
}
|
||||
if (this.times.length === 1) {
|
||||
vant.Toast('必须保留一个时间段')
|
||||
return
|
||||
@@ -343,6 +540,7 @@ layout("/mobile/platform.html"){
|
||||
onConfirm(date) {
|
||||
this.selectDate = moment(date)
|
||||
this.setWeekList(this.selectDate)
|
||||
this.getReserve()
|
||||
this.calendarVisible = false
|
||||
},
|
||||
//这里要排除掉休息日
|
||||
@@ -365,34 +563,37 @@ layout("/mobile/platform.html"){
|
||||
this.getReserve()
|
||||
},
|
||||
reserve() {
|
||||
if (this.slotsLoading || this.slotsFailed || !this.validateFullDay()) return
|
||||
if (this.times.length === 0) {
|
||||
vant.Toast('请选择要预约的时段')
|
||||
return
|
||||
}
|
||||
this.show = true
|
||||
},
|
||||
async reserveDo() {
|
||||
const resp = await $.post('/mobile/activity/site/info/reserveDo', {
|
||||
siteId: this.site_id,
|
||||
times: JSON.stringify(this.times),
|
||||
message: this.message,
|
||||
joinUser: this.joinUser,
|
||||
reserve_type: this.reserve_type
|
||||
})
|
||||
if (resp.code === 0) {
|
||||
vant.Toast('预约成功')
|
||||
if (resp.code === 0) {
|
||||
this.show = false
|
||||
this.times = []
|
||||
this.message = null
|
||||
await this.getReserve()
|
||||
await this.onLoad()
|
||||
}
|
||||
} else {
|
||||
vant.Toast(resp.msg)
|
||||
reserveDo() {
|
||||
if(this.slotsLoading || this.formLoading)return
|
||||
if(this.slotsFailed || !this.validateFullDay())return
|
||||
if(!this.times.length){vant.Toast('请选择预约时段');return}
|
||||
if(this.isFullDay){
|
||||
const days=Array.from(new Set(this.times.map(row=>row.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))
|
||||
return selected.some((row,i)=>i>0 && selected[i-1].end_time!==row.start_time)
|
||||
})
|
||||
if(gaps){vant.Toast('全天候预约请选择连续时段');return}
|
||||
}
|
||||
// 类型必须为当前可选项,事由不能为空或仅含空白;校验通过后才发送预约请求。
|
||||
if(!this.reserveTypeOptions.some(option => option.value===this.reserve_type)){vant.Toast('请选择预约类型');return}
|
||||
if(!this.message || !this.message.trim()){vant.Toast('请填写预约事由');return}
|
||||
if(this.reserve_type===3 && !this.clubId){vant.Toast('请选择所属协会');return}
|
||||
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)=>{
|
||||
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)}
|
||||
}).always(()=>{this.formLoading=false})
|
||||
},
|
||||
chooseTime(item) {
|
||||
if(this.slotsLoading || item.code!==1)return
|
||||
const cloneItem = clone(item)
|
||||
const map = this.times.map(o => o.fullDay)
|
||||
const index = map.indexOf(this.selectDate.format('YYYY-MM-DD') + ' ' + cloneItem.start_time + '-' + cloneItem.end_time)
|
||||
@@ -405,22 +606,34 @@ layout("/mobile/platform.html"){
|
||||
}
|
||||
},
|
||||
|
||||
async 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
|
||||
}
|
||||
this.loading = false
|
||||
onLoad() {
|
||||
this.loading=true;return $.post('/mobile/activity/site/info/getDetail',{id:this.site_id}).then((res)=>{if(res.code===0){this.site=res.data}else{vant.Toast(res.msg)}}).always(()=>{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'),
|
||||
getReserve() {
|
||||
if(!this.site_id || !this.selectDate)return
|
||||
const version=this.slotRequestVersion+1
|
||||
this.$set(this,'slotRequestVersion',version)
|
||||
this.$set(this,'slotsLoading',true)
|
||||
this.$set(this,'slotsFailed',false)
|
||||
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=>{
|
||||
if(version!==this.slotRequestVersion)return
|
||||
if(res && res.code===0 && Array.isArray(res.data)) {
|
||||
this.$set(this,'timeData',res.data)
|
||||
// 切换日期或类型后重新核验该日范围,保留其他日期;占用变化时整段清空。
|
||||
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 (resp.code === 0) {
|
||||
this.timeData = resp.data
|
||||
}
|
||||
},
|
||||
//获取最新的一个工作日设为当前时间
|
||||
getLatestWorkDay() {
|
||||
@@ -439,14 +652,22 @@ layout("/mobile/platform.html"){
|
||||
return selectDate
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.site_id = getQueryString('id')
|
||||
this.selectDate = this.getLatestWorkDay()
|
||||
await this.onLoad()
|
||||
this.setWeekList(this.selectDate)
|
||||
await this.getReserve()
|
||||
created() {
|
||||
this.site_id=getQueryString('id');this.selectDate=this.getLatestWorkDay();this.onLoad().then(()=>{this.setWeekList(this.selectDate);return this.getReserve()});
|
||||
}
|
||||
})
|
||||
}
|
||||
if (window.popupHistoryMixin) startPage()
|
||||
else {
|
||||
if (!window.siteReserveHistoryLoading) {
|
||||
window.siteReserveHistoryLoading = $.getScript('/assets/mobile/js/popupHistory.js')
|
||||
.fail(() => { window.siteReserveHistoryLoading = null })
|
||||
}
|
||||
window.siteReserveHistoryLoading.then(() => startPage()).fail(() => {
|
||||
if (document.getElementById('app') === pageRoot) vant.Toast.fail('页面组件加载失败,请重新进入')
|
||||
})
|
||||
}
|
||||
})()
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
|
||||
@@ -240,9 +240,10 @@ layout("/mobile/platform.html"){
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div style="margin-top: 6px; color: grey; font-size: 11px">
|
||||
<div>开始时间:{{ moment(item.startDate).format('MM/DD HH:mm') }}
|
||||
<!-- 场地入口仅显示时分秒,其他活动保留原日期格式。 -->
|
||||
<div>开始时间:{{ moment(item.startDate).format(item.id === 'home_site_reserve_entry' ? 'HH:mm:ss' : 'MM/DD HH:mm') }}
|
||||
</div>
|
||||
<div>结束时间:{{ moment(item.endDate).format('MM/DD HH:mm') }}
|
||||
<div>结束时间:{{ moment(item.endDate).format(item.id === 'home_site_reserve_entry' ? 'HH:mm:ss' : 'MM/DD HH:mm') }}
|
||||
</div>
|
||||
</div>
|
||||
</van-col>
|
||||
@@ -332,13 +333,22 @@ layout("/mobile/platform.html"){
|
||||
<div style="background-color: #f6f7f9; min-height: 100vh" v-if="active === 1">
|
||||
<van-notice-bar
|
||||
background="#ecf9ff" color="#1989fa" left-icon="volume-o"
|
||||
text="温馨提示:只显示当前年份的待办和已办事项"
|
||||
text="温馨提示:点击待办进行处理,点击已办查看办理情况"
|
||||
></van-notice-bar>
|
||||
|
||||
<van-loading v-if="legacyTaskLoading" style="text-align: center; padding: 8px">正在加载其他办理事项</van-loading>
|
||||
<div v-if="legacyTaskError" style="text-align: center; padding: 8px" @click="initData">其他办理事项加载失败,点击重试</div>
|
||||
<van-tabs @click="initData" v-model="activeName">
|
||||
<van-tab name="0" title="待办">
|
||||
<div style="min-height: 80vh">
|
||||
<div v-for="o in needItems">
|
||||
<van-loading v-if="taskLoading" style="padding: 16px; text-align: center">正在加载办理事项</van-loading>
|
||||
<div v-if="taskError" style="padding: 16px; text-align: center" @click="loadLocalTasks">流程待办加载失败,点击重试</div>
|
||||
<div v-for="o in localNeedItems" :key="'pending-' + o.id" class="van-doc-card" @click="openLocalTask(o, false)">
|
||||
<div class="title">{{ o.processName }}</div>
|
||||
<div>{{ o.taskNodeName }}</div>
|
||||
<div style="color: grey; margin-top: 6px">{{ o.createdByUserName }} · {{ o.createdOn }}</div>
|
||||
</div>
|
||||
<div v-for="o in visibleNeedItems">
|
||||
<div @click="toUrl(o)" class="van-doc-card" v-if="o.count > 0">
|
||||
<div class="title">{{ o.title }}</div>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||||
@@ -356,14 +366,21 @@ layout("/mobile/platform.html"){
|
||||
description="暂无待办"
|
||||
image="https://img01.yzcdn.cn/vant/custom-empty-image.png"
|
||||
style="margin-top: 44px"
|
||||
v-if="needItems.length === 0"
|
||||
v-if="!taskLoading && !taskError && !legacyTaskLoading && !legacyTaskError && visibleNeedItems.length === 0 && localNeedItems.length === 0"
|
||||
></van-empty>
|
||||
</div>
|
||||
</van-tab>
|
||||
|
||||
<van-tab name="1" title="已办">
|
||||
<div style="min-height: 80vh">
|
||||
<div @click="toHref(o)" class="van-doc-card" v-for="o in completedItems">
|
||||
<van-loading v-if="taskLoading" style="padding: 16px; text-align: center">正在加载办理事项</van-loading>
|
||||
<div v-if="taskError" style="padding: 16px; text-align: center" @click="loadLocalTasks">流程已办加载失败,点击重试</div>
|
||||
<div v-for="o in localCompletedItems" :key="'completed-' + o.id" class="van-doc-card" @click="openLocalTask(o, true)">
|
||||
<div class="title">{{ o.processName }}</div>
|
||||
<div>{{ o.taskNodeName }} · 已办理</div>
|
||||
<div style="color: grey; margin-top: 6px">{{ o.createdByUserName }} · {{ o.endOn || o.createdOn }}</div>
|
||||
</div>
|
||||
<div @click="toHref(o)" class="van-doc-card" v-for="o in visibleCompletedItems">
|
||||
<div class="title">{{ o.moduleName }}</div>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||||
<div>
|
||||
@@ -379,7 +396,7 @@ layout("/mobile/platform.html"){
|
||||
description="暂无已办"
|
||||
image="https://img01.yzcdn.cn/vant/custom-empty-image.png"
|
||||
style="margin-top: 44px"
|
||||
v-if="completedItems.length === 0"
|
||||
v-if="!taskLoading && !taskError && !legacyTaskLoading && !legacyTaskError && visibleCompletedItems.length === 0 && localCompletedItems.length === 0"
|
||||
></van-empty>
|
||||
</div>
|
||||
</van-tab>
|
||||
@@ -387,8 +404,8 @@ layout("/mobile/platform.html"){
|
||||
|
||||
</div>
|
||||
|
||||
<!--工作台-->
|
||||
<div style="margin-bottom: 60px" v-if="active === 2">
|
||||
<!--工作台:底部预留导航栏及手机安全区空间,确保最后一排菜单可完整滚动展示。-->
|
||||
<div style="padding-bottom: 80px; padding-bottom: calc(80px + constant(safe-area-inset-bottom)); padding-bottom: calc(80px + env(safe-area-inset-bottom))" v-if="active === 2">
|
||||
<van-row class="module" v-for="m in moduleMenus">
|
||||
<div class="van-sidebar-item van-sidebar-item--select">
|
||||
{{m.moduleName}}
|
||||
@@ -517,7 +534,13 @@ layout("/mobile/platform.html"){
|
||||
moduleMenus: [],
|
||||
roles: [],
|
||||
active: 0,
|
||||
needCount: 0,
|
||||
localNeedItems: [],
|
||||
localCompletedItems: [],
|
||||
taskLoading: false,
|
||||
taskError: false,
|
||||
taskRequestVersion: 0,
|
||||
legacyTaskLoading: false,
|
||||
legacyTaskError: false,
|
||||
needItems: [],
|
||||
activeName: '0',
|
||||
completedItems: [],
|
||||
@@ -545,6 +568,20 @@ layout("/mobile/platform.html"){
|
||||
activityQrCodeShow: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 同一手机入口已接入流程任务时,不再重复显示旧接口的汇总卡片。
|
||||
visibleNeedItems() {
|
||||
const urls = this.localNeedItems.map(o => this.taskPath(o.formMobileUrl));
|
||||
return this.needItems.filter(o => !urls.includes(this.taskPath(o.mobileHref)));
|
||||
},
|
||||
visibleCompletedItems() {
|
||||
const urls = this.localCompletedItems.map(o => this.taskPath(o.formMobileUrlView || o.formMobileUrl));
|
||||
return this.completedItems.filter(o => !urls.includes(this.taskPath(o.mobileUrl)));
|
||||
},
|
||||
needCount() {
|
||||
return this.visibleNeedItems.length + this.localNeedItems.length > 0;
|
||||
}
|
||||
},
|
||||
components: {},
|
||||
methods: {
|
||||
async getMenus() {
|
||||
@@ -571,7 +608,6 @@ layout("/mobile/platform.html"){
|
||||
tabbrChange(o) {
|
||||
console.log(o)
|
||||
sessionStorage.setItem("zhgh-mobile-home-active",o)
|
||||
this.needItems = []
|
||||
this.initData()
|
||||
},
|
||||
toUrl(o) {
|
||||
@@ -580,14 +616,11 @@ layout("/mobile/platform.html"){
|
||||
toHref(o) {
|
||||
pjaxReplace(o.mobileUrl + '?audit=1');
|
||||
},
|
||||
async getNeedItems() {
|
||||
const {data, code} = await $.get('/platform/needItems/getNeedItems')
|
||||
if (code === 0) {
|
||||
const obj = data.filter(v => {
|
||||
return v.mobileHref != null && v.count > 0
|
||||
})
|
||||
this.needItems = obj
|
||||
}
|
||||
getNeedItems() {
|
||||
// mobile=true 由后端按实际手机入口过滤,返回数组用于数量及红点计算。
|
||||
return $.get('/platform/needItems/getNeedItems', {mobile: true}).then(res => {
|
||||
if (res.code === 0) this.$set(this, 'needItems', res.data.filter(v => v.mobileHref && v.count > 0));
|
||||
});
|
||||
},
|
||||
async getCompletes() {
|
||||
const {data, code} = await $.get('/platform/sys/completed/getCompletes')
|
||||
@@ -615,23 +648,72 @@ layout("/mobile/platform.html"){
|
||||
this.needItems = this.needItems.concat(data.filter(v => v.count > 0))
|
||||
}
|
||||
},
|
||||
async initData() {
|
||||
await this.getNeedItems();
|
||||
await this.getMemberCheckSelfAgenda()
|
||||
await this.getWelfareCheckSelfAgenda()
|
||||
await this.getCompletes();
|
||||
await this.getMsg()
|
||||
await this.listActivity();
|
||||
this.needCount = this.needItems.length
|
||||
// 双端共用流程任务来源;只使用手机地址,避免手机进入 PC 表单。
|
||||
taskPath(url) {
|
||||
return (url || '').split('?')[0].replace(/\/$/, '');
|
||||
},
|
||||
async setNeedItems() {
|
||||
const {data, code} = await $.get('/platform/needItems/getNeedItems')
|
||||
if (code === 0) {
|
||||
const obj = data.filter(v => {
|
||||
return v.mobileHref != null && v.count > 0
|
||||
})
|
||||
window.localStorage.setItem('needItems', escape(JSON.stringify(obj)))
|
||||
openLocalTask(row, completed) {
|
||||
const url = completed ? (row.formMobileUrlView || row.formMobileUrl) : row.formMobileUrl;
|
||||
if (url) pjaxReplace(url);
|
||||
},
|
||||
loadLocalTasks() {
|
||||
const version = this.taskRequestVersion + 1;
|
||||
this.$set(this, 'taskRequestVersion', version);
|
||||
this.$set(this, 'taskLoading', true);
|
||||
this.$set(this, 'taskError', false);
|
||||
let remaining = 2;
|
||||
// mode=1 待办、2 已办;data 是任务数组。忽略旧请求,防止快速切换覆盖新结果。
|
||||
[1, 2].forEach(mode => {
|
||||
$.post('/platform/sys/localProcess/todoList', {mode: mode, mobile: true})
|
||||
.then(res => {
|
||||
if (version !== this.taskRequestVersion || this._isDestroyed) return;
|
||||
if (res.code !== 0 || !Array.isArray(res.data)) {
|
||||
this.$set(this, 'taskError', true);
|
||||
return;
|
||||
}
|
||||
const ids = new Set();
|
||||
const rows = res.data.filter(row => {
|
||||
const url = mode === 1 ? row.formMobileUrl : (row.formMobileUrlView || row.formMobileUrl);
|
||||
if (!url || ids.has(row.id)) return false;
|
||||
ids.add(row.id);
|
||||
return true;
|
||||
});
|
||||
this.$set(this, mode === 1 ? 'localNeedItems' : 'localCompletedItems', rows);
|
||||
}, () => {
|
||||
if (version === this.taskRequestVersion && !this._isDestroyed) this.$set(this, 'taskError', true);
|
||||
}).always(() => {
|
||||
remaining--;
|
||||
if (remaining === 0 && version === this.taskRequestVersion && !this._isDestroyed) this.$set(this, 'taskLoading', false);
|
||||
});
|
||||
});
|
||||
},
|
||||
initData() {
|
||||
// 活动独立启动,返回其完成状态供首页选择默认栏目,不等待待办加载。
|
||||
const activityRequest = this.listActivity().catch(() => {});
|
||||
this.loadLocalTasks();
|
||||
// 保留原有业务待办;重复点击不并发追加旧接口的汇总记录。
|
||||
if (!this.legacyTaskLoading) {
|
||||
this.$set(this, 'legacyTaskLoading', true);
|
||||
this.$set(this, 'legacyTaskError', false);
|
||||
// jQuery 1.11 的请求链不支持catch/finally;先由原生Promise接管,并等待各待办请求依次完成。
|
||||
Promise.resolve().then(() => this.getNeedItems())
|
||||
.then(() => this.getMemberCheckSelfAgenda())
|
||||
.then(() => this.getWelfareCheckSelfAgenda())
|
||||
.then(() => this.getCompletes())
|
||||
.catch(() => this.$set(this, 'legacyTaskError', true))
|
||||
.finally(() => this.$set(this, 'legacyTaskLoading', false));
|
||||
}
|
||||
// 消息独立加载,不阻塞活动和办理事项展示。
|
||||
this.getMsg().catch(() => {});
|
||||
return activityRequest;
|
||||
},
|
||||
setNeedItems() {
|
||||
return $.get('/platform/needItems/getNeedItems', {mobile: true}).then(res => {
|
||||
if (res.code === 0) {
|
||||
const items = res.data.filter(v => v.mobileHref && v.count > 0);
|
||||
window.localStorage.setItem('needItems', escape(JSON.stringify(items)));
|
||||
}
|
||||
});
|
||||
},
|
||||
//获取站内消息
|
||||
async getMsg() {
|
||||
@@ -719,18 +801,18 @@ layout("/mobile/platform.html"){
|
||||
beforeMount() {
|
||||
this.setNeedItems();
|
||||
},
|
||||
async created() {
|
||||
await this.getMenus();
|
||||
created() {
|
||||
this.getAllMonth();
|
||||
this.createYearList();
|
||||
await this.initData();
|
||||
const active = sessionStorage.getItem("zhgh-mobile-home-active")
|
||||
this.active = active ? parseInt(active) : 0
|
||||
|
||||
if (!this.activityList || !this.activityList.length > 0) {
|
||||
this.clickIndex = 2
|
||||
this.clickMenu = this.moduleMenus.find(o => o.moduleName === '职工权益')
|
||||
}
|
||||
const active = sessionStorage.getItem("zhgh-mobile-home-active");
|
||||
this.$set(this, 'active', active ? parseInt(active) : 0);
|
||||
// 菜单加载失败不应阻止待办请求;活动和菜单加载完成后再选择默认栏目。
|
||||
Promise.all([this.getMenus().catch(() => {}), this.initData()]).then(() => {
|
||||
if (!this.activityList || this.activityList.length === 0) {
|
||||
this.$set(this, 'clickIndex', 2);
|
||||
this.$set(this, 'clickMenu', this.moduleMenus.find(o => o.moduleName === '职工权益') || {});
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
|
||||
<link rel="stylesheet" href="${base!}/assets/mobile/css/theme.css">
|
||||
<link rel="stylesheet" href="${base!}/assets/mobile/css/main.css">
|
||||
<link rel="stylesheet" href="${base!}/assets/mobile/css/universal-info.css">
|
||||
<link rel="stylesheet" href="${base!}/assets/mobile/css/list-card.css">
|
||||
|
||||
<link rel="stylesheet" href="${base!}/assets/mobile/css/pdfh5.css">
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user