diff --git a/src/main/java/io/v/nutz/sys/controllers/platform/sys/SysLocalProcessController.java b/src/main/java/io/v/nutz/sys/controllers/platform/sys/SysLocalProcessController.java index 42fb742..a0650d3 100644 --- a/src/main/java/io/v/nutz/sys/controllers/platform/sys/SysLocalProcessController.java +++ b/src/main/java/io/v/nutz/sys/controllers/platform/sys/SysLocalProcessController.java @@ -9,7 +9,7 @@ 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 io.v.nutz.sys.services.SysLocalProcessService; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.util.NutMap; @@ -29,6 +29,9 @@ public class SysLocalProcessController { @Inject private Dao dao; + @Inject + private SysLocalProcessService localProcessService; + /** * 查询待办 * @param mode 1待处理 2已处理 3我发起的 @@ -39,28 +42,7 @@ public class SysLocalProcessController { @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 list = (List) Daos.query(dao, sql.toString(), Sqls.callback.maps()); - return Result.success(list); + return Result.success(localProcessService.getTaskList(mode)); } else if (mode == 3) { Sql sql = Sqls.create(""" SELECT diff --git a/src/main/java/io/v/nutz/sys/controllers/platform/sys/club/applyIn/ClubApplyAuditController.java b/src/main/java/io/v/nutz/sys/controllers/platform/sys/club/applyIn/ClubApplyAuditController.java index 91f44b6..597a382 100644 --- a/src/main/java/io/v/nutz/sys/controllers/platform/sys/club/applyIn/ClubApplyAuditController.java +++ b/src/main/java/io/v/nutz/sys/controllers/platform/sys/club/applyIn/ClubApplyAuditController.java @@ -2,25 +2,19 @@ package io.v.nutz.sys.controllers.platform.sys.club.applyIn; import cn.hutool.core.util.StrUtil; import io.v.nutz.base.annontation.ViReturn; -import io.v.nutz.base.model.Audit; import io.v.nutz.base.service.BaseService; import io.v.nutz.base.utils.MsgApi; import io.v.nutz.base.utils.PageUtil; -import io.v.nutz.base.utils.Roles; import io.v.nutz.sys.constant.club.ClubRegistAuditState; -import io.v.nutz.sys.controllers.platform.sys.handler.ClubUserApplyToDoHandler; -import io.v.nutz.sys.models.Sys_club; -import io.v.nutz.sys.models.Sys_club_user; import io.v.nutz.sys.services.SysClubService; import io.v.nutz.sys.services.SysClubUserService; import io.v.nutz.web.commons.utils.ShiroUtil; import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.RequiresAuthentication; import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.apache.shiro.authz.annotation.RequiresRoles; 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.entity.Record; import org.nutz.dao.sql.Sql; @@ -35,7 +29,6 @@ import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; -import java.util.Date; import java.util.List; /** @@ -85,9 +78,31 @@ public class ClubApplyAuditController { @Inject private SysClubUserService sysClubUserService; + /** + * 无请求参数,查询当前会话用户的协会个人待办;无审核权限时返回空列表。 + * ViReturn 返回 code、msg、data,data 为包含 title、name、count、mobileHref 的卡片列表。 + */ @At @ViReturn - @RequiresPermissions(value = {"sys.club.sh", "sys.club.schoolsh"}, logical = Logical.OR) + @RequiresAuthentication + public Object mobileTodo() { + return sysClubUserService.getMobileReviewTodo(); + } + + /** + * 无请求参数;data 返回本人实际办理的协会已办卡片,applyId 用于打开详情,其余字段见服务接口。 + * 未授权或无已办记录时返回空列表;不接收前端指定审核人。 + */ + @At + @ViReturn + @RequiresAuthentication + public Object mobileCompleted() { + return sysClubUserService.getMobileReviewCompleted(); + } + + @At + @ViReturn + @RequiresPermissions("sys.club.sh") public Object pageData(@Param(value = "clubid", required = false) String clubid, @Param(value = "searchName", required = false) String searchName, @Param(value = "searchKeyword", required = false) String searchKeyword, @@ -100,6 +115,7 @@ public class ClubApplyAuditController { @Param(value = "userState", required = false) String userState, @Param(value = "sex", required = false) String sex, @Param(value = "unitid", required = false) String unitid) { + // 所属单位取人员资料;独立别名避免与申请表 unitName 重名,并用全小写匹配 Record 返回键。 Sql sql = Sqls.create(""" SELECT scu.*, @@ -108,6 +124,7 @@ public class ClubApplyAuditController { u.sex, u.userState, u.unionname, + u.unitname AS applicantunitname, club.name clubname, club.code, u.personType rylx @@ -139,12 +156,12 @@ public class ClubApplyAuditController { cnd.andEX("u.sex", "=", sex); cnd.andEX("u.unitid", "=", unitid); - if (!ShiroUtil.hasAnyRoles(new String[]{"sysadmin", "A06"})) { - if (ShiroUtil.hasAnyRoles("club01, club02, club03, club04")) { - cnd.and(new Static(""" - scu.clubid IN ( SELECT stid FROM sys_user_role WHERE roleid in ('%s','%s','%s','%s') AND userid = '%s' AND stid IS NOT NULL ) - """.formatted(Roles.club01, Roles.club02, Roles.club03, Roles.club04, ShiroUtil.getPrincipalProperty("id")))); - } + // 超管取得全部协会;普通审核人无所属协会时返回空列表,不能省略范围条件。 + List reviewClubIds = sysClubUserService.getReviewClubIds(); + if (reviewClubIds.isEmpty()) { + cnd.and(new Static("1 = 0")); + } else { + cnd.and("scu.clubid", "in", reviewClubIds); } if (Strings.isNotBlank(searchName) && Strings.isNotBlank(searchKeyword)) { @@ -160,45 +177,28 @@ public class ClubApplyAuditController { } + /** + * id 为入会申请主键数组,flag 为是否通过,auditOpinion 为审核意见,auditSign 为签字内容, + * isjf 为可选缴费标记;返回 null,由 ViReturn 包装为包含 code、msg、data 的 JSON 结果。 + */ @At @ViReturn - @RequiresAuthentication + @RequiresPermissions("sys.club.sh") + @RequiresRoles(value = {"sysadmin", "club01", "club03"}, logical = Logical.OR) + @Aop(TransAop.READ_COMMITTED) public Object check(String[] id, Boolean flag, String auditOpinion, String auditSign, @Param(value = "isjf", required = false) Boolean isjf) { - Audit audit = new Audit(); - audit.setAuditPass(flag); - audit.setAuditor(ShiroUtil.getPrincipalProperty("id").toString()); - audit.setUsername(ShiroUtil.getPrincipalProperty("username").toString()); - audit.setLoginname(ShiroUtil.getPrincipalProperty("loginname").toString()); - audit.setAuditOpinion(auditOpinion); - audit.setAuditTime(new Date()); - audit.setAuditSign(auditSign); - Audit insert = sysClubService.dao().insert(audit); - - Cnd cnd = Cnd.NEW(); - cnd.where().andInStrArray("id", id); - sysClubUserService.update(Chain.make("status", flag ? 5 : 2) - .add("isjf", isjf).add("clubAuditId", insert.getId()), cnd); - - List clubUsers = sysClubUserService.dao().query(Sys_club_user.class, Cnd.where("id", "in", id)); - for (Sys_club_user clubUser : clubUsers) { - Sys_club club = sysClubService.fetch(clubUser.getClubid()); - Sql sql = Sqls.fetchRecord("select username,loginname from sys_user where id = @id").setParam("id", clubUser.getUserid()); - sysClubUserService.dao().execute(sql); - Record userRecord = (Record) sql.getResult(); - if (flag) { - String content = "您申请加入的" + club.getName() + "已经审批通过。"; - System.out.println(userRecord.getString("username")); - System.out.println(userRecord.getString("loginname")); - System.out.println(content); - } else { - String content = "您申请加入的" + club.getName() + "被审批拒绝了,拒绝理由:" + auditOpinion; - System.out.println(userRecord.getString("username")); - System.out.println(content); + if (id == null || id.length == 0 || flag == null) { + throw new IllegalArgumentException("请选择申请并提供审核结果"); + } + for (String applyId : id) { + if (Strings.isBlank(applyId)) { + throw new IllegalArgumentException("申请编号不能为空"); } } + sysClubUserService.reviewApplications(id, flag, auditOpinion, auditSign, isjf); return null; } @@ -206,7 +206,7 @@ public class ClubApplyAuditController { //获取当前登录用户负责的社团 @At @ViReturn - @RequiresAuthentication + @RequiresPermissions("sys.club.sh") public Object getMyClub(@Param(value = "searchKeyword", required = false) String searchKeyword, @Param(value = "pageNumber", required = false) int pageNumber, @Param(value = "pageSize", required = false) int pageSize, @@ -235,12 +235,12 @@ public class ClubApplyAuditController { cnd.and("state", "=", ClubRegistAuditState.SCHOOL_PASS); cnd.and("isjs", "=", false); - if (!ShiroUtil.hasAnyRoles(new String[]{"sysadmin", "A06"})) { - if (ShiroUtil.hasAnyRoles("club01, club02, club03, club04")) { - cnd.and(new Static(""" - club.id IN ( SELECT stid FROM sys_user_role WHERE roleid in ('%s','%s','%s','%s') AND userid = '%s' AND stid IS NOT NULL ) - """.formatted(Roles.club01, Roles.club02, Roles.club03, Roles.club04, ShiroUtil.getPrincipalProperty("id")))); - } + // 超管取得全部协会;普通审核人无所属协会时返回空列表,不能省略范围条件。 + List reviewClubIds = sysClubUserService.getReviewClubIds(); + if (reviewClubIds.isEmpty()) { + cnd.and(new Static("1 = 0")); + } else { + cnd.and("club.id", "in", reviewClubIds); } if (StrUtil.isNotBlank(clubType)) { cnd.and("club.type", "=", clubType); @@ -257,7 +257,7 @@ public class ClubApplyAuditController { //获取社团已审或者未审人员 @At @ViReturn - @RequiresAuthentication + @RequiresPermissions("sys.club.sh") public Object getClubUser(@Param(value = "id", required = false) String id, @Param(value = "auditType", required = false) String auditType, @Param(value = "type", required = false) int type) { @@ -310,12 +310,12 @@ public class ClubApplyAuditController { } cnd.andEX("scu.clubid", "=", id); - if (!ShiroUtil.hasAnyRoles(new String[]{"sysadmin", "A06"})) { - if (ShiroUtil.hasAnyRoles("club01, club02, club03, club04")) { - cnd.and(new Static(""" - club.id IN ( SELECT stid FROM sys_user_role WHERE roleid in ('%s','%s','%s','%s') AND userid = '%s' AND stid IS NOT NULL ) - """.formatted(Roles.club01, Roles.club02, Roles.club03, Roles.club04, ShiroUtil.getPrincipalProperty("id")))); - } + // 超管取得全部协会;普通审核人无所属协会时返回空列表,不能省略范围条件。 + List reviewClubIds = sysClubUserService.getReviewClubIds(); + if (reviewClubIds.isEmpty()) { + cnd.and(new Static("1 = 0")); + } else { + cnd.and("club.id", "in", reviewClubIds); } sql.setCondition(cnd); List list = sysClubService.list(sql); diff --git a/src/main/java/io/v/nutz/sys/controllers/platform/sys/club/applyIn/ClubApplyInController.java b/src/main/java/io/v/nutz/sys/controllers/platform/sys/club/applyIn/ClubApplyInController.java index 5949b4a..9f49d28 100644 --- a/src/main/java/io/v/nutz/sys/controllers/platform/sys/club/applyIn/ClubApplyInController.java +++ b/src/main/java/io/v/nutz/sys/controllers/platform/sys/club/applyIn/ClubApplyInController.java @@ -171,56 +171,7 @@ public class ClubApplyInController { || (sys_club_user.getOtherThing() != null && sys_club_user.getOtherThing().length() > 100)) { throw new IllegalArgumentException("联系电话最多20字,紧急联系人最多50字,特别说明最多100字"); } - sysClubUserService.clear(Cnd.where("clubid", "=", sys_club_user.getClubid()).and("userid", "=", ShiroUtil.getPrincipalProperty("id"))); - - String dateTime = DateUtil.getDateTime(); - - sys_club_user.setUserid(ShiroUtil.getPrincipalProperty("id").toString()); - sys_club_user.setJoin_time(dateTime); - sys_club_user.setSf(2); - sys_club_user.setStatus(1); - sys_club_user.setIsjf(false); - sys_club_user.setIsNormal(true); - sys_club_user.setGiveMoney(true); - Sys_club_user clubUser = sysClubUserService.insert(sys_club_user); - - //插入后,发起待办流程 - ClubUserApplyToDoHandler.START_PROCESS.exec(clubUser); - //发任务给协会会长 - ClubUserApplyToDoHandler.CREATE_CLUB_TASK.exec(clubUser); - //完成申请人的任务(申请拒绝的情况) - ClubUserApplyToDoHandler.COMPLETE_APPLY_TASK.exec(clubUser); - - //根据社团id查询社团负责人,发短信 - Sql sql = Sqls.create(""" - SELECT - u.loginname as loginName, - u.username as userName, - u.unitid as unitId, - u.unitname as unitName, - u.mobile, - c.`name` - FROM - sys_user_role r - LEFT JOIN - `user` u ON r.userId = u.id - LEFT JOIN - sys_club c ON c.id = r.stid - $condition - """); - Cnd cnd = Cnd.NEW(); - cnd.and("stid", "=", sys_club_user.getClubid()); - cnd.and("roleId", "in", Lang.array(Roles.club01, Roles.club02, Roles.club03, Roles.club04)); - sql.setCondition(cnd); - List list = baseService.listMap(sql); - - String platformUsername = ShiroUtil.getPlatformUsername(); - - list.forEach(item -> { - String content = "%s老师您好,%s老师于%s申请加入%s,请登陆智慧工会进行审核!".formatted(item.getString("userName") - , platformUsername, dateTime, item.getString("name")); - //msgApi.sendWxMsg(person, content); - }); + sysClubUserService.submitApplication(sys_club_user); return null; } diff --git a/src/main/java/io/v/nutz/sys/controllers/platform/sys/handler/ClubUserApplyToDoHandler.java b/src/main/java/io/v/nutz/sys/controllers/platform/sys/handler/ClubUserApplyToDoHandler.java index 6819ed2..84f5853 100644 --- a/src/main/java/io/v/nutz/sys/controllers/platform/sys/handler/ClubUserApplyToDoHandler.java +++ b/src/main/java/io/v/nutz/sys/controllers/platform/sys/handler/ClubUserApplyToDoHandler.java @@ -84,12 +84,12 @@ public enum ClubUserApplyToDoHandler { }, /** - * 创建协会会长任务 + * 为申请所属协会的会长、秘书长创建审核任务 */ CREATE_CLUB_TASK() { @Override public void exec(Sys_club_user clubUser) { - //查询协会审核人 + // 角色关联的 stid 为所属协会主键,按本次申请 clubid 筛选,避免跨协会派发。 Sql sqlStr = Sqls.create(""" SELECT u.loginname @@ -100,7 +100,9 @@ public enum ClubUserApplyToDoHandler { GROUP BY sur.userId """); - sqlStr.setCondition(Cnd.NEW().and("sur.roleId", "in", Lang.array(Roles.club01, Roles.club02, Roles.club03, Roles.club04))); + sqlStr.setCondition(Cnd.NEW() + .and("sur.stid", "=", clubUser.getClubid()) + .and("sur.roleId", "in", Lang.array(Roles.club01, Roles.club03))); Sql sql = Sqls.queryString(sqlStr.toString()); dao.execute(sql); List loginNames = sql.getList(String.class); diff --git a/src/main/java/io/v/nutz/sys/services/SysClubUserService.java b/src/main/java/io/v/nutz/sys/services/SysClubUserService.java index 5b4947d..c17a8f3 100644 --- a/src/main/java/io/v/nutz/sys/services/SysClubUserService.java +++ b/src/main/java/io/v/nutz/sys/services/SysClubUserService.java @@ -3,6 +3,47 @@ package io.v.nutz.sys.services; import cn.wizzer.framework.base.service.BaseService; import io.v.nutz.sys.models.Sys_club_user; +import java.util.List; +import org.nutz.lang.util.NutMap; + public interface SysClubUserService extends BaseService { + + /** + * PC、H5 共用的入会提交入口:保存申请、创建审核待办并通知所属协会的会长和秘书长。 + * 无返回值;保存或待办失败时抛出异常回滚,消息发送异常仅记录日志。 + * @param application 已通过接口必填及长度校验的入会资料,clubid 为申请协会主键; + * 申请人、申请时间和待审核状态由服务端确定,不采信客户端传值 + */ + void submitApplication(Sys_club_user application); + + /** + * 按当前登录用户确定可查看及审核的协会,不接收客户端用户 ID 或代审标志。 + * @return 协会主键列表:超管返回全部协会,其他人仅返回本人担任会长或秘书长的协会;无资格时为空 + */ + List getReviewClubIds(); + + /** + * 查询当前登录人的本年度协会审核待办,不接收客户端用户或协会参数。 + * @return 首页卡片列表;无待办返回空列表,卡片包含 title、name、count 和 mobileHref + * 仅统计已分配给本人且仍具有所属协会会长或秘书长资格的有效待办,超管代审不额外派单 + */ + List getMobileReviewTodo(); + + /** + * 查询当前登录人本年度实际完成的协会审核,不接收前端审核人参数。 + * @return 已办列表,包含 businessType、applyId、moduleName、auditState、auditTime、mobileUrl;无记录为空列表 + */ + List getMobileReviewCompleted(); + + /** + * 保存协会审核结果,并完成每条申请的审核待办及流程。 + * 无返回值;保存或关闭流程失败时抛出异常并回滚事务。 + * @param ids 入会申请主键数组,支持单条及批量审核 + * @param pass true 通过(状态 5),false 拒绝(状态 2) + * @param auditOpinion 审核意见 + * @param auditSign 审核签字内容 + * @param isjf 可选缴费标记,沿用现有保存规则 + */ + void reviewApplications(String[] ids, Boolean pass, String auditOpinion, String auditSign, Boolean isjf); } diff --git a/src/main/java/io/v/nutz/sys/services/SysLocalProcessService.java b/src/main/java/io/v/nutz/sys/services/SysLocalProcessService.java index c93892b..e4d4507 100644 --- a/src/main/java/io/v/nutz/sys/services/SysLocalProcessService.java +++ b/src/main/java/io/v/nutz/sys/services/SysLocalProcessService.java @@ -2,9 +2,19 @@ package io.v.nutz.sys.services; import javax.validation.constraints.NotNull; import java.util.List; +import org.nutz.lang.util.NutMap; public interface SysLocalProcessService { + /** + * 查询当前登录人的任务列表,用户身份从会话获取,不由客户端指定。 + * @param mode 1 待处理,2 已处理;其他值返回空列表 + * @return taskNodeName、createdByUserName、formUrl、formUrlView、createdOn、processName、nodeName 组成的列表 + * 协会待办须对应有效待审申请;协会已办仅归实际处理人,其他业务沿用原接收人规则 + */ + List getTaskList(Integer mode); + + /** * 开始一个实例流程 * diff --git a/src/main/java/io/v/nutz/sys/services/impl/SysClubUserServiceImpl.java b/src/main/java/io/v/nutz/sys/services/impl/SysClubUserServiceImpl.java index fc66da8..4fe191f 100644 --- a/src/main/java/io/v/nutz/sys/services/impl/SysClubUserServiceImpl.java +++ b/src/main/java/io/v/nutz/sys/services/impl/SysClubUserServiceImpl.java @@ -3,14 +3,268 @@ package io.v.nutz.sys.services.impl; import cn.wizzer.framework.base.service.BaseServiceImpl; import io.v.nutz.sys.models.Sys_club_user; import io.v.nutz.sys.services.SysClubUserService; +import io.v.nutz.base.model.Audit; +import io.v.nutz.base.utils.DateUtil; +import io.v.nutz.base.utils.MsgApi; +import io.v.nutz.base.utils.Roles; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authz.UnauthorizedException; +import org.nutz.lang.Strings; +import org.nutz.lang.util.NutMap; +import java.util.Arrays; +import java.util.HashSet; +import io.v.nutz.sys.controllers.platform.sys.handler.ClubUserApplyToDoHandler; +import io.v.nutz.sys.models.Sys_club; +import io.v.nutz.web.commons.utils.ShiroUtil; +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.entity.Record; +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 java.util.Date; +import java.util.List; + @IocBean(args = {"refer:dao"}) +@Slf4j public class SysClubUserServiceImpl extends BaseServiceImpl implements SysClubUserService { + @Inject + private MsgApi msgApi; + public SysClubUserServiceImpl(Dao dao) { super(dao); } + + /** + * 沿用双端原有的申请替换和待办顺序;申请及待办保存完成后发送审核提醒。 + * 参数、返回值和异常约定见接口,当前申请人以登录会话为准。 + */ + @Override + @Aop(TransAop.READ_COMMITTED) + public void submitApplication(Sys_club_user application) { + String userId = ShiroUtil.getPrincipalProperty("id").toString(); + clear(Cnd.where("clubid", "=", application.getClubid()).and("userid", "=", userId)); + + application.setUserid(userId); + application.setJoin_time(DateUtil.getDateTime()); + application.setSf(2); + application.setStatus(1); + application.setIsjf(false); + application.setIsNormal(true); + application.setGiveMoney(true); + Sys_club_user clubUser = insert(application); + + // 新建本次流程及会长、秘书长待办,再完成申请人被退回时的提交任务。 + ClubUserApplyToDoHandler.START_PROCESS.exec(clubUser); + ClubUserApplyToDoHandler.CREATE_CLUB_TASK.exec(clubUser); + ClubUserApplyToDoHandler.COMPLETE_APPLY_TASK.exec(clubUser); + notifyApplicationReviewers(clubUser); + } + + /** + * 按已保存申请的 clubid 查询审核角色,复用日常慰问的企业微信消息卡片发送方式。 + * 无返回值;缺少接收人工号、协会资料或通知异常时记录日志,不中断申请提交。 + */ + private void notifyApplicationReviewers(Sys_club_user application) { + try { + // stid 限定申请所属协会;按工号去重,兼任会长、秘书长也只接收一条消息。 + Sql sql = Sqls.queryString(""" + SELECT DISTINCT TRIM(u.loginname) + FROM sys_user_role sur + INNER JOIN `user` u ON u.id = sur.userid + WHERE sur.stid = @clubId + AND sur.roleId IN (@chairman, @secretary) + AND u.loginname IS NOT NULL AND TRIM(u.loginname) <> '' + """); + sql.setParam("clubId", application.getClubid()); + sql.setParam("chairman", Roles.club01); + sql.setParam("secretary", Roles.club03); + dao().execute(sql); + List loginNames = sql.getList(String.class); + if (loginNames.isEmpty()) { + log.warn("入会申请审核提醒未发送:未配置会长、秘书长或有效工号,申请ID={},协会ID={}", + application.getId(), application.getClubid()); + return; + } + Sys_club club = dao().fetch(Sys_club.class, application.getClubid()); + if (club == null) { + log.warn("入会申请审核提醒未发送:协会不存在,申请ID={},协会ID={}", + application.getId(), application.getClubid()); + return; + } + String content = "您好,%s老师于%s申请加入【%s】,请您前往门户网站或点击此消息链接登录智慧工会平台进行审核。" + .formatted(ShiroUtil.getPlatformUsername(), application.getJoin_time(), club.getName()); + // 统一使用 H5 审核入口,消息开关、开发环境禁发及频率限制由 MsgApi 控制。 + msgApi.sendWxMsg("智慧工会【协会入会】", "https://zhghydd.cpu.edu.cn/platform/sys/club/sh/mobile", + "textcard", content, loginNames, ""); + } catch (Exception e) { + // 通知渠道故障或频率限制不应使申请失败,保留申请 ID 便于排查未送达原因。 + log.error("入会申请审核提醒发送异常,申请ID=" + application.getId(), e); + } + } + + /** + * 首页个人待办必须同时满足接收人、当前任职、申请及流程状态,不复用超管全协会代审范围。 + * 返回首页卡片结构见接口;按申请去重,防止重复任务或兼任角色导致数量翻倍。 + */ + @Override + public List getMobileReviewTodo() { + if (!ShiroUtil.hasPermission("sys.club.sh") + || !ShiroUtil.hasAnyRoles(new String[]{"club01", "club03"})) { + return List.of(); + } + Sql sql = Sqls.fetchInt(""" + SELECT COUNT(DISTINCT cu.id) + FROM sys_club_user cu + INNER JOIN sys_local_process_instance_task t + ON t.processUniqueId = CONCAT('sys_club_user@', cu.id) + INNER JOIN sys_local_process_instance p ON p.processUniqueId = t.processUniqueId + WHERE cu.status = 1 AND cu.sf = 2 AND cu.isNormal = 1 + AND t.taskUniqueId = 'clubAuditId' AND t.status = 1 + AND t.taskDeleteFlag = 0 AND t.delFlag = 0 + AND p.processInstanceStatus = 1 AND p.processDeleteFlag = 0 AND p.delFlag = 0 + AND t.createdOn >= MAKEDATE(YEAR(CURRENT_DATE), 1) + AND t.createdOn < MAKEDATE(YEAR(CURRENT_DATE) + 1, 1) + AND JSON_CONTAINS(t.assignments, JSON_QUOTE(@loginName)) + AND EXISTS ( + SELECT 1 FROM sys_user_role sur + WHERE sur.userId = @userId AND sur.stid = cu.clubid + AND sur.roleId IN (@chairman, @secretary) + ) + """); + // 登录用户及工号均取自会话;参数绑定避免工号中的特殊字符影响 JSON 或 SQL。 + sql.setParam("loginName", ShiroUtil.getPrincipalProperty("loginname")); + sql.setParam("userId", ShiroUtil.getPlatformUid()); + sql.setParam("chairman", Roles.club01); + sql.setParam("secretary", Roles.club03); + dao().execute(sql); + int count = sql.getInt(); + if (count == 0) return List.of(); + return List.of(NutMap.NEW().setv("title", "协会入会申请") + .setv("name", "协会入会审核").setv("count", count) + .setv("mobileHref", "/platform/sys/club/sh/mobile")); + } + + /** + * 已办以审核记录中的实际审核人为准,超管代审归超管本人,不归任务的其他接收人。 + * 按审核完成年份筛选,因此往年发起、今年办理的申请仍会显示;使用 maps 保留页面字段大小写。 + */ + @Override + public List getMobileReviewCompleted() { + if (!ShiroUtil.hasPermission("sys.club.sh")) return List.of(); + Sql sql = Sqls.create(""" + SELECT 'clubApply' AS businessType, cu.id AS applyId, + CONCAT('协会入会申请 · ', COALESCE(c.name, ''), ' · ', COALESCE(u.username, '')) AS moduleName, + CASE WHEN a.auditPass = 1 THEN '审核通过' ELSE '审核拒绝' END AS auditState, + DATE_FORMAT(a.auditTime, '%Y-%m-%d %H:%i:%s') AS auditTime, + '/platform/sys/club/sh/mobile' AS mobileUrl + FROM sys_club_user cu + INNER JOIN audit a ON a.id = cu.clubAuditId + LEFT JOIN sys_club c ON c.id = cu.clubid + LEFT JOIN sys_user u ON u.id = cu.userid + WHERE cu.sf = 2 AND cu.isNormal = 1 AND cu.status IN (2, 5) + AND a.auditor = @userId + AND a.auditTime >= MAKEDATE(YEAR(CURRENT_DATE), 1) + AND a.auditTime < MAKEDATE(YEAR(CURRENT_DATE) + 1, 1) + ORDER BY a.auditTime DESC, cu.id + """); + sql.setParam("userId", ShiroUtil.getPlatformUid()); + sql.setCallback(Sqls.callback.maps()); + dao().execute(sql); + return sql.getList(NutMap.class); + } + + /** 查询当前用户可查看和审核的协会;超管允许全协会代审,其他人按实际任职限定。 */ + @Override + public List getReviewClubIds() { + // 超管使用服务端角色判定,不接收前端传入的代审标志;返回实际协会 ID 供查询与审核共用。 + if (ShiroUtil.hasAnyRoles(new String[]{"sysadmin"})) { + Sql allClubs = Sqls.queryString("SELECT id FROM sys_club"); + dao().execute(allClubs); + return allClubs.getList(String.class); + } + Sql sql = Sqls.queryString("SELECT DISTINCT stid FROM sys_user_role " + + "WHERE userId = @userId AND roleId IN (@chairman, @secretary) " + + "AND stid IS NOT NULL AND stid <> ''"); + sql.setParam("userId", ShiroUtil.getPlatformUid()); + sql.setParam("chairman", Roles.club01); + sql.setParam("secretary", Roles.club03); + dao().execute(sql); + return sql.getList(String.class); + } + + /** + * 审核记录、申请状态、待办及流程在同一事务内保存,防止出现已审核但待办未关闭。 + * 参数及返回约定见接口;实际审核人从当前登录会话取得。 + */ + @Override + @Aop(TransAop.READ_COMMITTED) + public void reviewApplications(String[] id, Boolean flag, String auditOpinion, String auditSign, Boolean isjf) { + // 所有权限及所属协会检查必须在任何写入前完成,批量混入越权申请时整批拒绝。 + SecurityUtils.getSubject().checkPermission("sys.club.sh"); + if (!ShiroUtil.hasAnyRoles(new String[]{"sysadmin", "club01", "club03"})) { + throw new UnauthorizedException("仅超管或申请所属协会的会长、秘书长可以审核"); + } + if (id == null || id.length == 0 || flag == null + || Arrays.stream(id).anyMatch(Strings::isBlank)) { + throw new IllegalArgumentException("请选择申请并提供审核结果"); + } + // auditSign 只接受上传接口返回的 PNG 文件路径,禁止将 Base64 或任意地址写入审核记录。 + if (auditSign == null || !auditSign.matches("^/signature/[0-9]{4}/[0-9]{2}/[0-9]{2}/[A-Za-z0-9_-]+[.]png$")) { + throw new IllegalArgumentException("请完成签字图片上传后再审核"); + } + List applications = dao().query(Sys_club_user.class, Cnd.where("id", "in", id)); + if (applications.size() != new HashSet<>(Arrays.asList(id)).size()) { + throw new IllegalArgumentException("申请记录不存在,请刷新后重试"); + } + List reviewClubIds = getReviewClubIds(); + for (Sys_club_user application : applications) { + if (!reviewClubIds.contains(application.getClubid())) { + throw new UnauthorizedException("仅超管或申请所属协会的会长、秘书长可以审核"); + } + } + Audit audit = new Audit(); + audit.setAuditPass(flag); + audit.setAuditor(ShiroUtil.getPrincipalProperty("id").toString()); + audit.setUsername(ShiroUtil.getPrincipalProperty("username").toString()); + audit.setLoginname(ShiroUtil.getPrincipalProperty("loginname").toString()); + audit.setAuditOpinion(auditOpinion); + audit.setAuditTime(new Date()); + audit.setAuditSign(auditSign); + Audit insert = dao().insert(audit); + + Cnd cnd = Cnd.NEW(); + cnd.where().andInStrArray("id", id); + update(Chain.make("status", flag ? 5 : 2) + .add("isjf", isjf).add("clubAuditId", insert.getId()), cnd); + + List clubUsers = dao().query(Sys_club_user.class, Cnd.where("id", "in", id)); + for (Sys_club_user clubUser : clubUsers) { + // 先记录实际审核人和意见,再结束本次流程;通过、拒绝均关闭本次待办。 + ClubUserApplyToDoHandler.COMPLETE_CLUB_TASK.exec(clubUser); + ClubUserApplyToDoHandler.COMPLETE_PROCESS.exec(clubUser); + Sys_club club = dao().fetch(Sys_club.class, clubUser.getClubid()); + Sql sql = Sqls.fetchRecord("select username,loginname from sys_user where id = @id").setParam("id", clubUser.getUserid()); + dao().execute(sql); + Record userRecord = (Record) sql.getResult(); + if (flag) { + String content = "您申请加入的" + club.getName() + "已经审批通过。"; + System.out.println(userRecord.getString("username")); + System.out.println(userRecord.getString("loginname")); + System.out.println(content); + } else { + String content = "您申请加入的" + club.getName() + "被审批拒绝了,拒绝理由:" + auditOpinion; + System.out.println(userRecord.getString("username")); + System.out.println(content); + } + } + } } diff --git a/src/main/java/io/v/nutz/sys/services/impl/SysLocalProcessServiceImpl.java b/src/main/java/io/v/nutz/sys/services/impl/SysLocalProcessServiceImpl.java index a1111ec..3871689 100644 --- a/src/main/java/io/v/nutz/sys/services/impl/SysLocalProcessServiceImpl.java +++ b/src/main/java/io/v/nutz/sys/services/impl/SysLocalProcessServiceImpl.java @@ -5,6 +5,9 @@ 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.sys.services.SysLocalProcessService; import io.v.nutz.web.commons.base.Globals; +import io.v.nutz.web.commons.utils.ShiroUtil; +import io.v.nutz.base.utils.Roles; +import org.nutz.lang.util.NutMap; import io.v.nutz.zhgh.needhandlebycpu.service.HandleCpuCommonService; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; @@ -34,6 +37,57 @@ public class SysLocalProcessServiceImpl implements SysLocalProcessService { @Inject private HandleCpuCommonService handleCpuCommonService; + /** + * 只对协会入会任务补充业务有效性过滤,其他模块保持原来的接收人及任务状态规则。 + * 协会已办按 actualOwnerId 归属,避免把其他接收人或超管代审算成本人办理。 + */ + @Override + public List getTaskList(Integer mode) { + if (!Integer.valueOf(1).equals(mode) && !Integer.valueOf(2).equals(mode)) return List.of(); + 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 + WHERE t.status = @mode AND t.taskDeleteFlag = 0 + AND ( + (NOT (t.taskUniqueId = 'clubAuditId' AND LEFT(t.processUniqueId, 14) = 'sys_club_user@') + AND JSON_CONTAINS(t.assignments, JSON_QUOTE(@loginName))) + OR + (t.taskUniqueId = 'clubAuditId' AND LEFT(t.processUniqueId, 14) = 'sys_club_user@' + AND @clubPermission = 1 AND t.delFlag = 0 + AND t2.processDeleteFlag = 0 AND t2.delFlag = 0 + AND ( + (@mode = 2 AND t.actualOwnerId = @userId) + OR + (@mode = 1 AND t2.processInstanceStatus = 1 + AND JSON_CONTAINS(t.assignments, JSON_QUOTE(@loginName)) + AND EXISTS ( + SELECT 1 FROM sys_club_user cu + WHERE t.processUniqueId = CONCAT('sys_club_user@', cu.id) + AND cu.status = 1 AND cu.sf = 2 AND cu.isNormal = 1 + AND EXISTS ( + SELECT 1 FROM sys_user_role sur + WHERE sur.userId = @userId AND sur.stid = cu.clubid + AND sur.roleId IN (@chairman, @secretary) + ) + )) + )) + ) + ORDER BY t2.processInitiationTime DESC + """); + // 工号通过 JSON_QUOTE 和参数绑定处理,避免特殊字符破坏接收人匹配。 + sql.setParam("mode", mode); + sql.setParam("loginName", ShiroUtil.getPrincipalProperty("loginname")); + sql.setParam("userId", ShiroUtil.getPlatformUid()); + sql.setParam("clubPermission", ShiroUtil.hasPermission("sys.club.sh") ? 1 : 0); + sql.setParam("chairman", Roles.club01); + sql.setParam("secretary", Roles.club03); + sql.setCallback(Sqls.callback.maps()); + dao.execute(sql); + return sql.getList(NutMap.class); + } + @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)); diff --git a/src/main/resources/static/components/club/clubUserApplyInfo.vue b/src/main/resources/static/components/club/clubUserApplyInfo.vue index e90e575..dca022f 100644 --- a/src/main/resources/static/components/club/clubUserApplyInfo.vue +++ b/src/main/resources/static/components/club/clubUserApplyInfo.vue @@ -33,7 +33,7 @@ 暂无 diff --git a/src/main/resources/static/components/mobile/club/mobileClubUserApplyInfo.vue b/src/main/resources/static/components/mobile/club/mobileClubUserApplyInfo.vue new file mode 100644 index 0000000..1ce127c --- /dev/null +++ b/src/main/resources/static/components/mobile/club/mobileClubUserApplyInfo.vue @@ -0,0 +1,84 @@ + + + diff --git a/src/main/resources/views/mobile/club/clubAudit.html b/src/main/resources/views/mobile/club/clubAudit.html index 0582b87..4ddf5f3 100644 --- a/src/main/resources/views/mobile/club/clubAudit.html +++ b/src/main/resources/views/mobile/club/clubAudit.html @@ -101,7 +101,7 @@ layout("/mobile/platform.html"){ left-arrow placeholder fixed - @click-left="pjaxReplace('/mobile/index')" + @click-left="leavePage" safe-area-inset-top > @@ -135,7 +135,7 @@ layout("/mobile/platform.html"){ @load="onLoad">
-
+
@@ -167,7 +167,7 @@ layout("/mobile/platform.html"){ 所属单位: - {{o.unitname}} + {{o.applicantunitname}} @@ -185,7 +185,7 @@ layout("/mobile/platform.html"){ style="margin-right: 8px; width: auto;"> 查看 - 审核 @@ -203,16 +203,16 @@ layout("/mobile/platform.html"){ >
- + - + - - - 拒 绝 - - - - 通 过 - - - + +
+ 拒绝 + 通过 +
@@ -272,6 +269,8 @@ layout("/mobile/platform.html"){ data() { return { handle: false, + applyId: '', + formLoading: false, clubOptions: [], userTypeOptions: [], auditOptions: [ @@ -283,52 +282,71 @@ layout("/mobile/platform.html"){ radioType: 3 }, infoShow: false, + signatureShow: false, formData: {}, } }, components: { - 'club-user-apply-info': httpVueLoader('/components/mobile/club/mobileClubUserApplyInfo.vue?v=1.0.1'), - 'mobile-sign': httpVueLoader('/components/sign/mobileSign.vue'), + 'club-user-apply-info': httpVueLoader('/components/mobile/club/mobileClubUserApplyInfo.vue?v=1.0.4'), + 'mobile-sign': httpVueLoader('/components/sign/mobileSign.vue?v=1.0.2'), }, methods: { + // 使用远端统一历史会话;签字层和详情层由同一回调驱动,避免重复压栈。 + syncPopup(layers) { + this.$set(this, 'infoShow', layers.includes('info')) + this.$set(this, 'signatureShow', layers.includes('signature')) + }, + closePopup() { + return this.$options.popupSession.close() + }, + openSignature() { + this.$options.popupSession.open('signature') + }, + leavePage() { + if (this.infoShow || this.signatureShow) return this.closePopup() + this.disposePopup() + pjaxReplace('/mobile/index') + }, + disposePopup() { + if (this.$options.popupSession) this.$options.popupSession.dispose() + $(document).off('pjax:beforeSend.clubAudit') + $(document).off('pjax:beforeReplace.clubAudit') + }, openAudit(row) { - this.formData = { + this.$set(this, 'formData', { id: row.id, auditTime: moment().format('YYYY-MM-DD'), username: "${@shiro.getPrincipalProperty('username')}", auditOpinion: null, - auditSign: "" - } - this.infoShow = true - this.handle = true - this.$nextTick(() => { - this.$refs.applyInfo.getInfo(row.id) + auditSign: '' }) + this.$set(this, 'applyId', row.id) + this.$set(this, 'handle', true) + this.$options.popupSession.open('info') }, openView(row) { - this.infoShow = true - this.handle = false - this.$nextTick(() => { - this.$refs.applyInfo.getInfo(row.id) - }) + this.$set(this, 'applyId', row.id) + this.$set(this, 'handle', false) + this.$options.popupSession.open('info') }, - async doReview(flag) { - if (!this.formData.auditOpinion) { - vant.Toast('请填写审核意见'); + // id 为申请记录,flag 为是否通过;响应 code 为 0 时清理弹框历史并刷新列表。 + doReview(flag) { + if (this.formLoading) return + if (!this.formData.auditOpinion || !this.formData.auditOpinion.trim()) { + vant.Toast('请填写审核意见') return } if (!this.formData.auditSign) { vant.Toast('请签字') return } - // this.$refs.signature.submitSign() - this.formData.flag = flag - const resp = await $.post("/platform/sys/club/sh/check", this.formData) - if (resp.code === 0) { - this.doSearch() - this.infoShow = false - } - vant.Toast(resp.msg) + this.$set(this, 'formLoading', true) + const params = Object.assign({}, this.formData, {flag}) + $.post('/platform/sys/club/sh/check', params).then(resp => { + if (resp.code === 0) this.$options.popupSession.clear().then(() => this.doSearch()) + vant.Toast(resp.msg) + }).fail(() => vant.Toast('审核提交失败,请重试')) + .always(() => { this.$set(this, 'formLoading', false) }) }, doSearch() { this.pageForm.pageNumber = 1 @@ -336,7 +354,7 @@ layout("/mobile/platform.html"){ this.finished = false this.onLoad() }, - async onLoad() { + onLoad() { this.loading = true $.post('/platform/sys/club/sh/pageData', this.pageForm).then(res => { if (res.code === 0) { @@ -347,26 +365,51 @@ layout("/mobile/platform.html"){ this.pageForm.pageNumber++ } } - this.loading = false - }) + }).always(() => { this.$set(this, 'loading', false) }) }, - async getMyMangeClub() { - const {data} = await $.get("/platform/sys/club/users/getMyMangeClub") - return data + getMyMangeClub() { + return $.get('/platform/sys/club/users/getMyMangeClub').then(res => res.data || []) }, }, - async created() { - const data = await this.getMyMangeClub() - data.forEach(item => { - this.clubOptions.push({text: item.name, value: item.id}) + created() { + this.$options.popupSession = window.mobilePopupHistory.register('clubAudit', this.syncPopup) + // PJAX 离开前清理本页历史,再执行原跳转,防止返回到已关闭弹框。 + $(document).on('pjax:beforeSend.clubAudit', (event, xhr, options) => { + if (this.infoShow || this.signatureShow) { + event.preventDefault() + this.$options.popupSession.clear().then(() => { + this.disposePopup() + pjaxReplace(options.url) + }) + return false + } + this.disposePopup() }) - this.clubOptions.unshift({text: '全部社团'}) - const userType = await getDictOptions('UserType') - userType.forEach(item => { - this.userTypeOptions.push({text: item.name, value: item.code}) + // PJAX 不会自动销毁旧页面实例,替换容器前释放本页会话及事件。 + $(document).one('pjax:beforeReplace.clubAudit', () => this.$destroy()) + this.getMyMangeClub().then(data => { + this.$set(this, 'clubOptions', [{text: '全部社团'}].concat( + data.map(item => ({text: item.name, value: item.id})))) }) - this.userTypeOptions.unshift({text: '人员类型'}) + getDictOptions('UserType').then(data => { + this.$set(this, 'userTypeOptions', [{text: '人员类型'}].concat( + data.map(item => ({text: item.name, value: item.code})))) + }) + // 首页已办传 audit=1、applyId;复用现有详情弹框和全局历史栈,只读展示审核结果。 + if (GetQueryString('audit') === '1') { + this.$set(this.pageForm, 'radioType', 2) + const applyId = GetQueryString('applyId') + if (applyId) this.openView({id: applyId}) + } this.onLoad() + }, + beforeDestroy() { + // 空栈同步注销,防止下一页注册时旧会话尚未释放。 + if (this.infoShow || this.signatureShow) { + this.$options.popupSession.clear().then(() => this.disposePopup()) + } else { + this.disposePopup() + } } }) diff --git a/src/main/resources/views/mobile/home.html b/src/main/resources/views/mobile/home.html index c0cf7bc..146d5ef 100644 --- a/src/main/resources/views/mobile/home.html +++ b/src/main/resources/views/mobile/home.html @@ -353,7 +353,7 @@ 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="needItems.length === 0 && !clubTodoLoading" >
@@ -376,7 +376,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="completedItems.length === 0 && !clubCompletedLoading" >
@@ -510,6 +510,10 @@ layout("/mobile/platform.html"){ active: 0, needCount: 0, needItems: [], + clubTodoLoading: false, + clubTodoVersion: 0, + clubCompletedLoading: false, + clubCompletedVersion: 0, activeName: '0', completedItems: [], bigPictureList: [], @@ -555,14 +559,18 @@ layout("/mobile/platform.html"){ tabbrChange(o) { console.log(o) sessionStorage.setItem("zhgh-mobile-home-active",o) - this.needItems = [] this.initData() }, toUrl(o) { pjaxReplace(o.mobileHref) }, toHref(o) { - pjaxReplace(o.mobileUrl + '?audit=1'); + // 协会已办携带申请 ID,直接以查看模式打开该申请,不进入待审核列表。 + if (o.businessType === 'clubApply') { + pjaxReplace(o.mobileUrl + '?audit=1&applyId=' + encodeURIComponent(o.applyId)) + return + } + pjaxReplace(o.mobileUrl + '?audit=1'); }, async getNeedItems() { const {data, code} = await $.get('/platform/needItems/getNeedItems') @@ -599,14 +607,55 @@ layout("/mobile/platform.html"){ this.needItems = this.needItems.concat(data.filter(v => v.count > 0)) } }, - async initData() { + // 无参数;data 返回协会个人待办卡片。每次替换协会卡片,保留其他业务待办。 + getClubTodo() { + const version = this.clubTodoVersion + 1 + this.$set(this, 'clubTodoVersion', version) + this.$set(this, 'clubTodoLoading', true) + return $.post('/platform/sys/club/sh/mobileTodo', {}).then(res => { + if (version !== this.clubTodoVersion) return + if (res.code === 0) { + const otherItems = this.needItems.filter(item => item.mobileHref !== '/platform/sys/club/sh/mobile') + this.$set(this, 'needItems', otherItems.concat(res.data || [])) + this.$set(this, 'needCount', this.needItems.reduce((total, item) => total + (Number(item.count) || 0), 0)) + } else { + vant.Toast(res.msg || '协会待办加载失败,请重试') + } + }).fail(() => { + if (version === this.clubTodoVersion) vant.Toast('协会待办加载失败,请重试') + }).always(() => { + if (version === this.clubTodoVersion) this.$set(this, 'clubTodoLoading', false) + }) + }, + // 已办按实际审核人查询,刷新替换而非追加,避免页签切换产生重复卡片。 + getClubCompleted() { + const version = this.clubCompletedVersion + 1 + this.$set(this, 'clubCompletedVersion', version) + this.$set(this, 'clubCompletedLoading', true) + return $.post('/platform/sys/club/sh/mobileCompleted', {}).then(res => { + if (version !== this.clubCompletedVersion) return + if (res.code === 0) { + const otherItems = this.completedItems.filter(item => item.businessType !== 'clubApply') + this.$set(this, 'completedItems', otherItems.concat(res.data || [])) + } else { + vant.Toast(res.msg || '协会已办加载失败,请重试') + } + }).fail(() => { + if (version === this.clubCompletedVersion) vant.Toast('协会已办加载失败,请重试') + }).always(() => { + if (version === this.clubCompletedVersion) this.$set(this, 'clubCompletedLoading', false) + }) + }, + initData() { // await this.getNeedItems(); // await this.getMemberCheckSelfAgenda() // await this.getWelfareCheckSelfAgenda() // await this.getCompletes(); // await this.getMsg() this.listActivity(); - this.needCount = this.needItems.length + // 首页首次进入、办理页签切换及 pageshow 返回均走此入口,刷新已处理数量。 + this.getClubTodo() + this.getClubCompleted() }, async setNeedItems() { const {data, code} = await $.get('/platform/needItems/getNeedItems') diff --git a/src/main/resources/views/platform/sys/club/applyIn/shrt.html b/src/main/resources/views/platform/sys/club/applyIn/shrt.html index 7f68456..24930f9 100644 --- a/src/main/resources/views/platform/sys/club/applyIn/shrt.html +++ b/src/main/resources/views/platform/sys/club/applyIn/shrt.html @@ -114,7 +114,7 @@ layout("/layouts/platform.html"){ prop="rylx"> --> - 查看 审核 - 导出申请表 + @@ -190,14 +190,14 @@ layout("/layouts/platform.html"){ - +
- 拒绝 - 通过 + 拒绝 + 通过
@@ -219,6 +219,7 @@ layout("/layouts/platform.html"){ tabKey: '', clubOptions: [], tabloading: false, + formLoading: false, formData: { sign: '' }, @@ -242,7 +243,7 @@ layout("/layouts/platform.html"){ } }, components: { - 'club-user-apply-info': httpVueLoader('/components/club/clubUserApplyInfo.vue?v=1.0.1'), + 'club-user-apply-info': httpVueLoader('/components/club/clubUserApplyInfo.vue?v=1.0.2'), 'dict-select': httpVueLoader('/components/plugins/DictSelect.vue?v=1.0.0'), }, methods: { @@ -273,18 +274,39 @@ layout("/layouts/platform.html"){ } this.$refs.guava.edit() }, - async doReview(flag) { - const valid = await this.$refs.form.validate() - if (!valid) return - this.formData.flag = flag - const resp = await $.post(base + "/platform/sys/club/sh/check", this.formData) - if(resp.code === 0) { - this.doSearch() - this.$refs.guava.index() - this.$message({message: resp.msg, type: 'success'}) - } else { - this.$message({message: resp.msg, type: 'warning'}) - } + doReview(flag) { + if (this.formLoading) return + this.$refs.form.validate((valid) => { + if (!valid || this.formLoading) return + this.$set(this, 'formLoading', true) + this.$set(this.formData, 'flag', flag) + // 公共签字组件可能读取当前用户的个人 Base64 签名,先上传为路径再提交审核。 + // uploadSignData 的 data 参数为 PNG Base64,成功响应的 data 为文件路径。 + const sign = this.formData.auditSign + const signRequest = sign.indexOf('data:image/png;base64,') === 0 + ? $.post('/signature/uploadSignData', {data: sign}).then((res) => { + return res.code === 0 && res.data + ? $.Deferred().resolve(res.data).promise() + : $.Deferred().reject(res.msg || '签字上传失败').promise() + }) + : $.Deferred().resolve(sign).promise() + signRequest.then((path) => { + this.$set(this.formData, 'auditSign', path) + return $.post(base + '/platform/sys/club/sh/check', this.formData) + }).then((resp) => { + if (resp.code === 0) { + this.doSearch() + this.$refs.guava.index() + this.$message({message: resp.msg, type: 'success'}) + } else { + this.$message({message: resp.msg, type: 'warning'}) + } + }).fail((error) => { + this.$message.error(typeof error === 'string' ? error : '审核提交失败,请重试') + }).always(() => { + this.$set(this, 'formLoading', false) + }) + }) }, doSearch() { this.pageForm.pageNumber = 1