This commit is contained in:
2026-05-14 10:26:44 +08:00
parent 6a2407e84c
commit 0a2e7b3502
15 changed files with 1610 additions and 26 deletions
@@ -63,6 +63,7 @@ public class SysLoginController {
@Inject
protected PropertiesProxy conf;
/*
@At("")
@Ok("re")
@ApiOperation("用户本地登录页面")
@@ -70,6 +71,7 @@ public class SysLoginController {
public String login(HttpServletRequest req, HttpSession session) {
return "beetl:/platform/sys/login.html";
}
*/
@At
@Ok("re")
@@ -103,12 +103,8 @@ public class SysMsgServiceImpl extends BaseServiceImpl<Sys_msg> implements SysMs
});
//发送学校平台消息
// for (String loginName : loginNames) {
// smsService.send(loginName, sysMsg.getTitle(), sysMsg.getNote());
// }
if(sysMsg.getWechatEnterprise()) {
ThreadUtil.execute(() -> {
//smsService.massSend(loginNames, sysMsg.getTitle(), HtmlUtil.cleanHtmlTag(StrUtil.blankToDefault(sysMsg.getNote(),"")), sysMsg.getUrl());
String plainText = SysMsgServiceImpl.htmlToPlainText(StrUtil.blankToDefault(sysMsg.getNote(), ""));
smsService.massSendMessage(loginNames, HtmlUtil.cleanHtmlTag(plainText));
});
@@ -10,20 +10,20 @@ package com.budwk.app.todo.platform;
public class TodoPlatformConfig {
// token 获取
public static final String TOKEN_URL = "https://gateway.jshvc.edu.cn/token/gateway/accessToken";
public static final String TOKEN_URL = "/accessToken";
// 待办平台基础地址
public static final String TODO_PLATFORM_BASE_URL = "https://gateway.jshvc.edu.cn";
// 待办平台新增或者修改
public static final String TODO_PLATFORM_SAVE_OR_UPDATE_URL = "https://gateway.jshvc.edu.cn/casp-tdc/task/saveOrUpdate";
public static final String TODO_PLATFORM_SAVE_OR_UPDATE_URL = "/saveOrUpdate";
// 待办平台修改地址
public static final String TODO_PLATFORM_MODIFY_URL = "https://gateway.jshvc.edu.cn/casp-tdc/task/modifyInfo";
public static final String TODO_PLATFORM_MODIFY_URL = "/modifyInfo";
// 待办平台删除任务地址
public static final String TODO_PLATFORM_DELETE_TASK_URL = "https://gateway.jshvc.edu.cn/casp-tdc/task/delete";
public static final String TODO_PLATFORM_DELETE_TASK_URL = "/delete";
// 待办平台删除实例地址
public static final String TODO_PLATFORM_DELETE_INSTANCE_URL = "https://gateway.jshvc.edu.cn/casp-tdc/processInstance/realDelete";
public static final String TODO_PLATFORM_DELETE_INSTANCE_URL = "/realDelete";
public static final String TODO_APP_ID = "1430576717768122368";
public static final String TODO_APP_SECRET = "19A0ACB03FBQHL4R4XQD";
public static final String TODO_APP_ID = "XXXXXXXXXXXXXXXXXXXXXX";
public static final String TODO_APP_SECRET = "XXXXXXXXXXXXXXXXXXXXXX";
public static final String REDIS_KEY_TODO_ACCESS_TOKEN = "todo:token:";
public static final Long TODO_REQUEST_TIMEOUT = 5000L;
@@ -0,0 +1,59 @@
package com.budwk.app.zhgh.dayofficework.workflowmessage.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.dayofficework.workflowmessage.service.WorkflowMessageAuditService;
import com.budwk.app.zhgh.dayofficework.workflowmessage.service.WorkflowMessageService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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;
import javax.validation.Valid;
@IocBean
@At("/platform/dayofficework/workflowmessage/audit")
@Ok("json:full")
@Api(tags = "工作流消息审核")
public class WorkflowMessageAuditController {
@Inject
private WorkflowMessageAuditService workflowMessageAuditService;
@Inject
private WorkflowMessageService workflowMessageService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/workflowmessage/audit/index.html")
@SaCheckPermission("dayofficework.message.workflow.audit")
@ApiOperation("工作流消息审核页面")
public void index() {
}
@At
@SaCheckPermission("dayofficework.message.workflow.audit")
@ApiOperation("消息审核列表")
public Result pageData(@Valid PageForm pageForm) {
return Result.success(workflowMessageAuditService.pageData(pageForm));
}
@At
@SaCheckPermission("dayofficework.message.workflow.audit")
@ApiOperation("消息申请详情")
public Result info(@Param("id") String id) {
return Result.success(workflowMessageService.info(id));
}
@At
@SaCheckPermission("dayofficework.message.workflow.audit")
@ApiOperation("审核消息发送申请")
@SLog(tag = "工作流消息审核", msg = "审核消息发送申请")
public Result audit(@Param("taskId") Long taskId, @Param("opinion") String opinion, @Param("action") Integer action) {
workflowMessageAuditService.audit(taskId, opinion, action);
return Result.success();
}
}
@@ -0,0 +1,103 @@
package com.budwk.app.zhgh.dayofficework.workflowmessage.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.annotation.RepeatSubmit;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.sys.models.Sys_role;
import com.budwk.app.zhgh.dayofficework.workflowmessage.models.WorkflowMessage;
import com.budwk.app.zhgh.dayofficework.workflowmessage.service.WorkflowMessageService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.nutz.aop.interceptor.ioc.TransAop;
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.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.validation.Valid;
@IocBean
@At("/platform/dayofficework/workflowmessage")
@Ok("json:full")
@Api(tags = "工作流消息申请")
public class WorkflowMessageController {
@Inject
private WorkflowMessageService workflowMessageService;
@Inject
private Dao dao;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/workflowmessage/apply/index.html")
@SaCheckPermission("dayofficework.message.workflow")
@ApiOperation("工作流消息申请页面")
public void index() {
}
@At
@SaCheckPermission("dayofficework.message.workflow")
@ApiOperation("消息申请列表")
public Result pageData(@Valid PageForm pageForm) {
return Result.success(workflowMessageService.pageData(pageForm));
}
@At
@SaCheckPermission("dayofficework.message.workflow")
@ApiOperation("提交消息发送申请")
@SLog(tag = "工作流消息管理", msg = "提交消息发送申请")
@Aop(TransAop.READ_COMMITTED)
public Result submit(@Param("message") @Valid WorkflowMessage message, @Param("users") String[] users) {
return Result.success(workflowMessageService.submit(message, users));
}
@At
@SaCheckPermission("dayofficework.message.workflow")
@ApiOperation("更新并重新提交消息发送申请")
@SLog(tag = "工作流消息管理", msg = "更新并重新提交消息发送申请")
public Result updateSubmit(@Param("message") @Valid WorkflowMessage message, @Param("users") String[] users, @Param("taskId") Long taskId) {
return Result.success(workflowMessageService.updateSubmit(message, users, taskId));
}
@At
@SaCheckPermission("dayofficework.message.workflow")
@ApiOperation("消息申请详情")
public Result info(@Param("id") String id) {
return Result.success(workflowMessageService.info(id));
}
@At
@SaCheckPermission("dayofficework.message.workflow")
@ApiOperation("消息接收人列表")
public Result receiverList(@Param("messageId") String messageId) {
return Result.success(workflowMessageService.receiverList(messageId));
}
@At
@SaCheckPermission("dayofficework.message.workflow")
@ApiOperation("删除消息申请")
@SLog(tag = "工作流消息管理", msg = "删除消息申请")
public Result delete(@Param("id") String id) {
workflowMessageService.deleteApply(id);
return Result.success();
}
@At
@SaCheckPermission("dayofficework.message.workflow")
@ApiOperation("可选接收人列表")
public Result userData(@Valid PageForm pageForm, String unionId, String unitId, String roleId, String sex, String clubId) {
return Result.success(workflowMessageService.userPageData(pageForm, unionId, unitId, roleId, sex, clubId));
}
@At
@SaCheckPermission("dayofficework.message.workflow")
@ApiOperation("角色列表")
public Result roleList() {
return Result.success(dao.query(Sys_role.class, Cnd.NEW()));
}
}
@@ -0,0 +1,121 @@
package com.budwk.app.zhgh.dayofficework.workflowmessage.models;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import javax.validation.constraints.NotBlank;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("workflow_message")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@TableIndexes({
@Index(name = "INDEX_WORKFLOW_MESSAGE_APPLY_STATUS", fields = "applyStatus", unique = false),
@Index(name = "INDEX_WORKFLOW_MESSAGE_PROCESS_INSTANCE", fields = "processInstanceId", unique = false)
})
@Comment("工作流消息申请")
public class WorkflowMessage extends BaseModel {
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("消息标题")
@ColDefine(type = ColType.VARCHAR, width = 255)
@NotBlank(message = "消息标题不能为空")
private String title;
@Column
@Comment("消息内容")
@ColDefine(type = ColType.TEXT)
@NotBlank(message = "消息内容不能为空")
private String note;
@Column
@Comment("消息URL")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String url;
@Column
@Comment("是否同步企业微信")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean wechatEnterprise;
@Column
@Comment("是否需要反馈")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean needBack;
@Column
@Comment("消息附件")
@ColDefine(type = ColType.MYSQL_JSON)
private List<JSONObject> files;
@Column
@Comment("流程实例ID")
private Long processInstanceId;
@Column
@Comment("申请状态 10待审核 20审核通过 30审核拒绝 40退回发起人")
@ColDefine(type = ColType.INT)
private Integer applyStatus;
@Column
@Comment("发送状态 0未发送 1已发送 2发送失败")
@ColDefine(type = ColType.INT)
private Integer sendStatus;
@Column
@Comment("发送时间")
private Long sendAt;
@Column
@Comment("发送结果")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String sendResult;
@Column
@Comment("申请人ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String applyUserId;
@Column
@Comment("申请人姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String applyUserName;
@Column
@Comment("申请人工号")
@ColDefine(type = ColType.VARCHAR, width = 120)
private String applyLoginName;
@Column
@Comment("申请人单位ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String applyUnitId;
@Column
@Comment("申请人单位")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String applyUnitName;
@Column
@Comment("申请人分工会ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String applyUnionId;
@Column
@Comment("申请人分工会")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String applyUnionName;
}
@@ -0,0 +1,88 @@
package com.budwk.app.zhgh.dayofficework.workflowmessage.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("workflow_message_receiver")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@TableIndexes({
@Index(name = "INDEX_WORKFLOW_MESSAGE_RECEIVER_MSG", fields = "messageId", unique = false),
@Index(name = "INDEX_WORKFLOW_MESSAGE_RECEIVER_LOGIN", fields = "loginName", unique = false)
})
@Comment("工作流消息接收人")
public class WorkflowMessageReceiver extends BaseModel {
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("工作流消息ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String messageId;
@Column
@Comment("接收人ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String userId;
@Column
@Comment("接收人工号")
@ColDefine(type = ColType.VARCHAR, width = 120)
private String loginName;
@Column
@Comment("接收人姓名")
@ColDefine(type = ColType.VARCHAR, width = 120)
private String userName;
@Column
@Comment("接收人性别")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String sex;
@Column
@Comment("接收人手机")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String mobile;
@Column
@Comment("在职状态")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String userState;
@Column
@Comment("教职工类别")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String personType;
@Column
@Comment("单位ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unitId;
@Column
@Comment("单位名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String unitName;
@Column
@Comment("分工会ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unionId;
@Column
@Comment("分工会名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String unionName;
@One(target = WorkflowMessage.class, field = "messageId")
private WorkflowMessage message;
}
@@ -0,0 +1,25 @@
package com.budwk.app.zhgh.dayofficework.workflowmessage.service;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import org.nutz.lang.util.NutMap;
public interface WorkflowMessageAuditService {
/**
* 查询消息审核任务列表。
*
* @param pageForm 分页参数,audit=true 查询已审核,audit=false 查询待审核
* @return 分页后的审核任务数据
*/
Pagination<NutMap> pageData(PageForm pageForm);
/**
* 执行消息审核任务,流程审核通过并结束后正式发送消息。
*
* @param taskId 流程任务ID
* @param opinion 审核意见
* @param action 提交类型,1同意、2拒绝、6退回发起人等
*/
void audit(Long taskId, String opinion, Integer action);
}
@@ -0,0 +1,75 @@
package com.budwk.app.zhgh.dayofficework.workflowmessage.service;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.workflowmessage.models.WorkflowMessage;
import org.nutz.lang.util.NutMap;
import java.util.List;
public interface WorkflowMessageService extends BaseService<WorkflowMessage> {
/**
* 保存消息申请并启动流程。
*
* @param message 待审核消息,包含标题、内容、链接、附件和发送选项
* @param users 接收人工号数组,审核通过后会按这些工号正式发送消息
* @return 已保存并绑定流程实例的消息申请
*/
WorkflowMessage submit(WorkflowMessage message, String[] users);
/**
* 更新撤回后的消息申请并重新提交发起节点。
*
* @param message 待更新消息,必须包含 id、标题、内容、附件和发送选项
* @param users 接收人工号数组,会覆盖原接收人快照
* @param startTaskId 撤回后发起节点任务ID,用于重新提交流程
* @return 更新后的消息申请
*/
WorkflowMessage updateSubmit(WorkflowMessage message, String[] users, Long startTaskId);
/**
* 查询消息申请列表。
*
* @param pageForm 分页与关键字参数,searchKeyword 用于匹配标题
* @return 分页后的申请数据
*/
Pagination<NutMap> pageData(PageForm pageForm);
/**
* 获取消息申请详情。
*
* @param id 消息申请ID
* @return 详情数据,包含消息主体和接收人列表
*/
NutMap info(String id);
/**
* 删除消息申请、接收人快照及关联流程实例。
*
* @param id 消息申请ID
*/
void deleteApply(String id);
/**
* 获取可选接收人列表。
*
* @param pageForm 查询字段、关键字、分页参数
* @param unionId 分工会ID
* @param unitId 单位ID
* @param roleId 角色ID
* @param sex 性别
* @param clubId 协会ID
* @return 分页后的人员数据
*/
Pagination<NutMap> userPageData(PageForm pageForm, String unionId, String unitId, String roleId, String sex, String clubId);
/**
* 根据消息ID查询接收人快照。
*
* @param messageId 消息申请ID
* @return 接收人列表
*/
List<NutMap> receiverList(String messageId);
}
@@ -0,0 +1,205 @@
package com.budwk.app.zhgh.dayofficework.workflowmessage.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.service.FlowCommonService;
import com.budwk.app.sys.models.Sys_msg;
import com.budwk.app.sys.services.SysMsgService;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.workflowmessage.models.WorkflowMessage;
import com.budwk.app.zhgh.dayofficework.workflowmessage.models.WorkflowMessageReceiver;
import com.budwk.app.zhgh.dayofficework.workflowmessage.service.WorkflowMessageAuditService;
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.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.lang.util.NutMap;
import java.util.List;
@IocBean(args = {"refer:dao"})
public class WorkflowMessageAuditServiceImpl extends BaseServiceImpl<WorkflowMessage> implements WorkflowMessageAuditService {
private static final String PROCESS_DEFINE_KEY = "XXZX";
private static final int APPLY_STATUS_AUDITING = 10;
private static final int APPLY_STATUS_PASS = 20;
private static final int APPLY_STATUS_REJECT = 30;
private static final int APPLY_STATUS_ROLLBACK = 40;
private static final int SEND_STATUS_SUCCESS = 1;
private static final int SEND_STATUS_FAIL = 2;
@Inject
private FlowEngine flowEngine;
@Inject
private FlowCommonService flowCommonService;
@Inject
private SysMsgService sysMsgService;
public WorkflowMessageAuditServiceImpl(Dao dao) {
super(dao);
}
@Override
public Pagination<NutMap> pageData(PageForm pageForm) {
Sql sql = Sqls.create("""
SELECT
msg.*,
ins.id AS instanceId,
ins.businessNo,
ins.state instanceState,
ins.variable instanceVariable,
ins.processDefineId instanceProcessDefineId,
t.id taskId,
t.taskName AS taskKey,
t.displayName taskName,
t.taskType,
t.performType taskPerformType,
t.taskState,
t.finishTime,
t.taskParentId,
t.variable taskVariable,
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName), '结束') curTaskName,
IF(rt.id IS NOT NULL, 1, 0) AS canRevoke,
COUNT(DISTINCT receiver.id) receiverCount
FROM wf_process_task t
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
LEFT JOIN workflow_message msg ON msg.id = ins.businessNo
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
LEFT JOIN workflow_message_receiver receiver ON receiver.messageId = msg.id
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("def.name", "=", PROCESS_DEFINE_KEY);
cnd.and("msg.id", "is not", null);
if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) {
cnd.and("ta.actorId", "=", SecurityUtil.getUserId());
}
if (Boolean.TRUE.equals(pageForm.getAudit())) {
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode(), ProcessTaskStateEnum.WITHDRAW.getCode()));
} else {
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
}
cnd.and(Cnd.likeEX("msg.title", pageForm.getSearchKeyword()));
if (StrUtil.isAllNotBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
cnd.orderBy("msg." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
} else {
cnd.desc("t.createdAt");
}
cnd.groupBy("t.id");
sql.setCondition(cnd);
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void audit(Long taskId, String opinion, Integer action) {
ProcessTask task = flowEngine.processTaskService().fetch(taskId);
if (task == null) {
throw new BaseException("流程任务不存在");
}
ProcessInstance instance = flowEngine.processInstanceService().fetch(task.getProcessInstanceId());
if (instance == null) {
throw new BaseException("流程实例不存在");
}
// 参数含义:processTaskId 为当前流程任务,submitType 为审核动作,tf_opinion 为审核意见;流程组件会记录办理人和办理结果。
Dict args = Dict.create();
args.set(FlowConst.PROCESS_TASK_ID_KEY, taskId);
args.set(FlowConst.SUBMIT_TYPE, action);
args.set("tf_opinion", opinion);
flowCommonService.executeTask(args);
ProcessInstance currentInstance = flowEngine.processInstanceService().fetch(instance.getId());
WorkflowMessage message = dao().fetch(WorkflowMessage.class, instance.getBusinessNo());
if (message == null) {
throw new BaseException("消息申请不存在");
}
updateApplyStatusByTaskAction(message, action, currentInstance);
if (ProcessSubmitTypeEnum.AGREE.getCode().equals(action)
&& ProcessInstanceStateEnum.FINISHED.getCode().equals(currentInstance.getState())) {
publishMessage(message.getId());
}
}
private void updateApplyStatusByTaskAction(WorkflowMessage message, Integer action, ProcessInstance instance) {
WorkflowMessage update = new WorkflowMessage();
update.setId(message.getId());
if (ProcessSubmitTypeEnum.REJECT.getCode().equals(action)
|| ProcessInstanceStateEnum.REJECT.getCode().equals(instance.getState())) {
update.setApplyStatus(APPLY_STATUS_REJECT);
} else if (ProcessSubmitTypeEnum.ROLLBACK_TO_OPERATOR.getCode().equals(action)) {
update.setApplyStatus(APPLY_STATUS_ROLLBACK);
} else if (ProcessInstanceStateEnum.FINISHED.getCode().equals(instance.getState())) {
update.setApplyStatus(APPLY_STATUS_PASS);
} else {
update.setApplyStatus(APPLY_STATUS_AUDITING);
}
dao().updateIgnoreNull(update);
}
private void publishMessage(String id) {
WorkflowMessage message = dao().fetch(WorkflowMessage.class, id);
if (message == null) {
throw new BaseException("消息申请不存在");
}
if (Integer.valueOf(SEND_STATUS_SUCCESS).equals(message.getSendStatus())) {
return;
}
List<WorkflowMessageReceiver> receivers = dao().query(WorkflowMessageReceiver.class, Cnd.where(WorkflowMessageReceiver::getMessageId, "=", id));
if (Lang.isEmpty(receivers)) {
throw new BaseException("消息接收人为空,无法发送");
}
try {
Sys_msg sysMsg = new Sys_msg();
sysMsg.setType("user");
sysMsg.setTitle(message.getTitle());
sysMsg.setNote(message.getNote());
sysMsg.setUrl(message.getUrl());
sysMsg.setNeedBack(Boolean.TRUE.equals(message.getNeedBack()));
sysMsg.setWechatEnterprise(Boolean.TRUE.equals(message.getWechatEnterprise()));
sysMsg.setFiles(message.getFiles());
sysMsg.setSendType("show");
sysMsg.setSendAt(DateUtil.current());
sysMsg.setCreatedBy(message.getApplyUserId());
sysMsgService.saveMsg(sysMsg, receivers.stream().map(WorkflowMessageReceiver::getLoginName).toArray(String[]::new), true);
WorkflowMessage update = new WorkflowMessage();
update.setId(id);
update.setApplyStatus(APPLY_STATUS_PASS);
update.setSendStatus(SEND_STATUS_SUCCESS);
update.setSendAt(DateUtil.current());
update.setSendResult("发送成功");
dao().updateIgnoreNull(update);
} catch (Exception e) {
WorkflowMessage update = new WorkflowMessage();
update.setId(id);
update.setSendStatus(SEND_STATUS_FAIL);
update.setSendResult(StrUtil.sub(e.getMessage(), 0, 500));
dao().updateIgnoreNull(update);
throw e;
}
}
}
@@ -0,0 +1,389 @@
package com.budwk.app.zhgh.dayofficework.workflowmessage.service.impl;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.club.model.ClubUser;
import com.budwk.app.zhgh.club.model.SysClub;
import com.budwk.app.zhgh.club.service.SysClubService;
import com.budwk.app.zhgh.dayofficework.workflowmessage.models.WorkflowMessage;
import com.budwk.app.zhgh.dayofficework.workflowmessage.models.WorkflowMessageReceiver;
import com.budwk.app.zhgh.dayofficework.workflowmessage.service.WorkflowMessageService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.*;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
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.lang.Lang;
import org.nutz.lang.util.NutMap;
import java.util.List;
@IocBean(args = {"refer:dao"})
public class WorkflowMessageServiceImpl extends BaseServiceImpl<WorkflowMessage> implements WorkflowMessageService {
private static final String PROCESS_DEFINE_KEY = "XXZX";
private static final int APPLY_STATUS_AUDITING = 10;
private static final int SEND_STATUS_WAIT = 0;
private static final String MSG_SCOPE_SCHOOL = "dayofficework.message.workflow.school";
private static final String MSG_SCOPE_UNION = "dayofficework.message.workflow.union";
private static final String MSG_SCOPE_CLUB = "dayofficework.message.workflow.club";
@Inject
private FlowEngine flowEngine;
@Inject
private SysClubService sysClubService;
public WorkflowMessageServiceImpl(Dao dao) {
super(dao);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public WorkflowMessage submit(WorkflowMessage message, String[] users) {
if (ObjectUtil.isEmpty(users)) {
throw new BaseException("请选择用户");
}
if (users.length > 100) {
throw new BaseException("单条消息最多发送100人,请分批次发送。");
}
fillApplyUser(message);
message.setApplyStatus(APPLY_STATUS_AUDITING);
message.setSendStatus(SEND_STATUS_WAIT);
message.setSendAt(null);
message.setSendResult(null);
insert(message);
saveReceivers(message.getId(), users);
startWorkflow(message);
return message;
}
@Override
public Pagination<NutMap> pageData(PageForm pageForm) {
Sql sql = Sqls.create("""
SELECT
msg.*,
ins.id AS instanceId,
ins.businessNo,
ins.state instanceState,
ins.variable instanceVariable,
ins.processDefineId instanceProcessDefineId,
t.id taskId,
t.taskName AS taskKey,
t.displayName taskName,
t.taskType,
t.performType taskPerformType,
t.taskState,
t.finishTime,
t.taskParentId,
t.variable taskVariable,
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName), '结束') curTaskName,
IF((SELECT taskName FROM wf_process_task tt WHERE tt.id = t.taskParentId) = 'startTask', 1, 0) canRevoke,
(SELECT MAX(st.id) FROM wf_process_task st WHERE st.processInstanceId = ins.id AND st.taskName = 'startTask') startTaskId,
COUNT(DISTINCT receiver.id) receiverCount
FROM workflow_message msg
LEFT JOIN wf_process_instance ins ON ins.id = msg.processInstanceId
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
LEFT JOIN workflow_message_receiver receiver ON receiver.messageId = msg.id
$condition
""");
Cnd cnd = Cnd.NEW();
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
cnd.and("msg.applyUserId", "=", SecurityUtil.getUserId());
}
cnd.and(Cnd.likeEX("msg.title", pageForm.getSearchKeyword()));
if (StrUtil.isAllNotBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
cnd.orderBy("msg." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
} else {
cnd.desc("msg.createdAt");
}
cnd.groupBy("msg.id");
sql.setCondition(cnd);
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public WorkflowMessage updateSubmit(WorkflowMessage message, String[] users, Long startTaskId) {
if (StrUtil.isBlank(message.getId())) {
throw new BaseException("消息申请ID不能为空");
}
if (ObjectUtil.isEmpty(users)) {
throw new BaseException("请选择用户");
}
if (users.length > 100) {
throw new BaseException("单条消息最多发送100人,请分批次发送。");
}
WorkflowMessage oldMessage = fetch(message.getId());
if (oldMessage == null) {
throw new BaseException("消息申请不存在");
}
checkApplyOwner(oldMessage);
checkEditable(oldMessage);
WorkflowMessage update = new WorkflowMessage();
update.setId(message.getId());
update.setTitle(message.getTitle());
update.setNote(message.getNote());
update.setUrl(message.getUrl());
update.setWechatEnterprise(message.getWechatEnterprise());
update.setNeedBack(message.getNeedBack());
update.setFiles(message.getFiles());
update.setApplyStatus(APPLY_STATUS_AUDITING);
update.setSendStatus(SEND_STATUS_WAIT);
updateIgnoreNull(update);
dao().clear(WorkflowMessageReceiver.class, Cnd.where(WorkflowMessageReceiver::getMessageId, "=", message.getId()));
saveReceivers(message.getId(), users);
submitStartTask(message.getId(), startTaskId);
return fetch(message.getId());
}
@Override
public NutMap info(String id) {
WorkflowMessage message = fetch(id);
if (message == null) {
throw new BaseException("消息申请不存在");
}
return NutMap.NEW()
.addv("message", message)
.addv("receivers", receiverList(id));
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void deleteApply(String id) {
WorkflowMessage message = fetch(id);
if (message == null) {
throw new BaseException("消息申请不存在");
}
checkApplyOwner(message);
checkEditable(message);
dao().clear(WorkflowMessageReceiver.class, Cnd.where(WorkflowMessageReceiver::getMessageId, "=", id));
delete(id);
flowEngine.processInstanceService().deleteProcessInstanceByBusinessKey(id);
}
@Override
public Pagination<NutMap> userPageData(PageForm pageForm, String unionId, String unitId, String roleId, String sex, String clubId) {
Sql sql = Sqls.create("""
SELECT
u.id,
u.username,
u.loginname,
u.sex,
u.mobile,
u.unitId unitid,
u.unitName unitname,
u.unionId unionid,
u.unionName unionname,
u.userState,
u.personType,
u.preparedBy
FROM vw_user u
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("u.unionId", "=", unionId);
cnd.andEX("u.unitId", "=", unitId);
cnd.andEX("u.sex", "=", sex);
if (StrUtil.isNotBlank(roleId)) {
cnd.and(new Static("u.id in (select userId from sys_user_role where roleId = '%s')".formatted(roleId)));
}
if (StrUtil.isAllNotBlank(pageForm.getSearchName(), pageForm.getSearchKeyword())) {
cnd.and(pageForm.getSearchName(), "like", "%" + pageForm.getSearchKeyword() + "%");
}
appendSendUserScope(cnd, "u");
appendClubScope(cnd, clubId, "u");
if (StrUtil.isAllNotBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
} else {
cnd.desc("u.loginname");
}
sql.setCondition(cnd);
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
@Override
public List<NutMap> receiverList(String messageId) {
Sql sql = Sqls.create("""
SELECT
id,
messageId,
userId,
loginName,
userName,
sex,
mobile,
userState,
personType,
unitName,
unionName
FROM workflow_message_receiver
WHERE messageId = @messageId
ORDER BY loginName DESC
""");
sql.setParam("messageId", messageId);
return listMap(sql);
}
private void fillApplyUser(WorkflowMessage message) {
View_user user = dao().fetch(View_user.class, Cnd.where(View_user::getId, "=", SecurityUtil.getUserId()));
message.setApplyUserId(SecurityUtil.getUserId());
message.setApplyUserName(SecurityUtil.getUserUsername());
message.setApplyLoginName(SecurityUtil.getUserLoginname());
if (user != null) {
message.setApplyUnitId(user.getUnitId());
message.setApplyUnitName(user.getUnitName());
message.setApplyUnionId(user.getUnionId());
message.setApplyUnionName(user.getUnionName());
} else {
message.setApplyUnitId(SecurityUtil.getUnitId());
message.setApplyUnionId(SecurityUtil.getUnionId());
}
}
private void saveReceivers(String messageId, String[] users) {
List<View_user> viewUsers = dao().query(View_user.class, Cnd.where(View_user::getLoginname, "in", users).groupBy("loginname"));
if (Lang.isEmpty(viewUsers)) {
throw new BaseException("未找到有效接收人");
}
List<WorkflowMessageReceiver> receivers = viewUsers.stream().map(user -> {
WorkflowMessageReceiver receiver = new WorkflowMessageReceiver();
receiver.setMessageId(messageId);
receiver.setUserId(user.getId());
receiver.setLoginName(user.getLoginname());
receiver.setUserName(user.getUsername());
receiver.setSex(user.getSex());
receiver.setMobile(user.getMobile());
receiver.setUserState(user.getUserState());
receiver.setPersonType(user.getPersonType());
receiver.setUnitId(user.getUnitId());
receiver.setUnitName(user.getUnitName());
receiver.setUnionId(user.getUnionId());
receiver.setUnionName(user.getUnionName());
return receiver;
}).toList();
dao().insert(receivers);
}
private void startWorkflow(WorkflowMessage message) {
Dict args = Dict.create();
args.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.APPLY.getCode());
args.set(FlowConst.FORM_DATA, message);
args.set("title", message.getTitle());
ProcessInstance instance = flowEngine.startProcessInstanceByKey(PROCESS_DEFINE_KEY, message.getId(), SecurityUtil.getUserId(), args);
List<ProcessTask> doingTaskList = flowEngine.processTaskService().getDoingTaskList(instance.getId(), null);
for (ProcessTask task : doingTaskList) {
flowEngine.executeProcessTask(task.getId(), SecurityUtil.getUserId(), args);
}
message.setProcessInstanceId(instance.getId());
updateIgnoreNull(message);
}
private void submitStartTask(String messageId, Long startTaskId) {
if (startTaskId == null) {
throw new BaseException("流程发起节点任务不存在,无法重新提交");
}
ProcessTask task = flowEngine.processTaskService().fetch(startTaskId);
if (task == null) {
throw new BaseException("流程发起节点任务不存在,无法重新提交");
}
if (!StrUtil.equals(task.getTaskName(), "startTask")) {
throw new BaseException("当前流程不在发起节点,无法重新提交");
}
WorkflowMessage currentMessage = fetch(messageId);
if (!ObjectUtil.equals(currentMessage.getProcessInstanceId(), task.getProcessInstanceId())) {
throw new BaseException("流程任务与消息申请不匹配");
}
Dict args = Dict.create();
args.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.APPLY.getCode());
args.set(FlowConst.FORM_DATA, currentMessage);
args.set("title", currentMessage.getTitle());
flowEngine.executeProcessTask(startTaskId, SecurityUtil.getUserId(), args);
}
private void checkApplyOwner(WorkflowMessage message) {
if (AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
return;
}
if (!StrUtil.equals(message.getApplyUserId(), SecurityUtil.getUserId())) {
throw new BaseException("只能操作本人提交的消息申请");
}
}
private void checkEditable(WorkflowMessage message) {
if (message.getProcessInstanceId() == null) {
return;
}
List<ProcessTask> doingTaskList = flowEngine.processTaskService().getDoingTaskList(message.getProcessInstanceId(), null);
if (Lang.isEmpty(doingTaskList) || doingTaskList.stream().noneMatch(task -> StrUtil.equals(task.getTaskName(), "startTask"))) {
throw new BaseException("当前流程状态不允许编辑或删除");
}
}
private void appendClubScope(Cnd cnd, String clubId, String userAlias) {
if (StrUtil.isBlank(clubId)) {
return;
}
List<ClubUser> clubUsers = sysClubService.dao().query(ClubUser.class, Cnd.where(ClubUser::getClubId, "=", clubId));
if (Lang.isEmpty(clubUsers)) {
cnd.and(new Static("1 = 0"));
return;
}
cnd.and(userAlias + ".id", "in", clubUsers.stream().map(ClubUser::getUserId).distinct().toList());
}
private void appendSendUserScope(Cnd cnd, String userAlias) {
// 校级消息权限可选择全校人员。
if (AuthUtil.hasPermission(MSG_SCOPE_SCHOOL)) {
return;
}
SqlExpressionGroup scopeGroup = new SqlExpressionGroup();
// 分工会消息权限只能选择当前登录人所在分工会人员。
if (AuthUtil.hasPermission(MSG_SCOPE_UNION) && StrUtil.isNotBlank(SecurityUtil.getUnionId())) {
scopeGroup.or(userAlias + ".unionId", "=", SecurityUtil.getUnionId());
}
// 协会消息权限只能选择当前登录人管理协会下的会员。
if (AuthUtil.hasPermission(MSG_SCOPE_CLUB)) {
List<SysClub> myManageClub = sysClubService.getMyManageClub();
if (Lang.isNotEmpty(myManageClub)) {
List<String> clubIds = myManageClub.stream().map(SysClub::getId).toList();
List<ClubUser> clubUsers = sysClubService.dao().query(ClubUser.class, Cnd.where(ClubUser::getClubId, "in", clubIds));
if (Lang.isNotEmpty(clubUsers)) {
scopeGroup.or(userAlias + ".id", "in", clubUsers.stream().map(ClubUser::getUserId).distinct().toList());
}
}
}
if (scopeGroup.isEmpty()) {
cnd.and(new Static("1 = 0"));
} else {
cnd.and(scopeGroup);
}
}
}
@@ -237,17 +237,17 @@ layout("/layouts/platform.html"){
<el-dialog append-to-body :title="menuDialogTitle" :visible.sync="menuDialogVisible" :close-on-click-modal="false"
width="70%">
<!-- 搜索框 -->
<!-- <el-row style="margin-bottom: 10px">-->
<!-- <el-input-->
<!-- v-model="searchText"-->
<!-- placeholder="搜索11"-->
<!-- size="small"-->
<!-- clearable-->
<!-- style="width: 300px"-->
<!-- >-->
<!-- <i slot="prefix" class="el-input__icon el-icon-search"></i>-->
<!-- </el-input>-->
<!-- </el-row>-->
<el-row style="margin-bottom: 10px">
<el-input
v-model="searchText"
placeholder="搜索"
size="small"
clearable
style="width: 300px"
>
<i slot="prefix" class="el-input__icon el-icon-search"></i>
</el-input>
</el-row>
<el-row style="margin-bottom: 3px">
<el-button size="small" @click="menuRoleSelAll">全选</el-button>
<el-button size="small" @click="menuRoleSelClear">清空</el-button>
@@ -664,10 +664,10 @@ layout("/layouts/platform.html"){
})
},
doMenu() {
// if (this.searchText) {
// this.$message.warning('请清空搜索条件后再提交')
// return;
// }
if (this.searchText) {
this.$message.warning('请清空搜索条件后再提交')
return;
}
const ids = this.$refs["doMenuTree"].getCheckedKeys()
if (!ids || ids.length === 0) {
@@ -0,0 +1,223 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
.workflow-message-note {
min-height: 180px;
max-height: 360px;
overflow-y: auto;
padding: 10px;
border: 1px solid #ebeef5;
border-radius: 4px;
}
</style>
<div id="app" v-cloak>
<guava ref="guava">
<template>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="消息标题">
<el-input v-model="pageForm.searchKeyword" placeholder="请输入消息标题" clearable></el-input>
</search-item>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool label="消息申请列表">
<el-button size="small" type="primary" icon="el-icon-plus" @click="openAdd">提交消息申请</el-button>
</table-tool>
<el-table :data="tableData" v-loading="tableLoading" @sort-change="pageOrder" style="width: 100%">
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
<el-table-column :show-overflow-tooltip="true" label="标题" prop="title"></el-table-column>
<el-table-column label="接收人数" prop="receiverCount" width="100"></el-table-column>
<el-table-column label="同步企业微信" prop="wechatEnterprise" width="120">
<template slot-scope="{row}">
<el-tag size="mini" type="success" v-if="row.wechatEnterprise"></el-tag>
<el-tag size="mini" v-else></el-tag>
</template>
</el-table-column>
<el-table-column label="申请状态" prop="applyStatus" width="120">
<template slot-scope="{row}">
<el-tag size="mini" :type="applyStatusType(row.applyStatus)">{{ applyStatusName(row.applyStatus) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="发送状态" prop="sendStatus" width="120">
<template slot-scope="{row}">
<el-tag size="mini" :type="sendStatusType(row.sendStatus)">{{ sendStatusName(row.sendStatus) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="当前节点" prop="curTaskName" width="140"></el-table-column>
<el-table-column label="申请时间" prop="createdAt" width="170">
<template slot-scope="{row}">{{$moment(row.createdAt).format("YYYY-MM-DD HH:mm:ss")}}</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="220">
<template slot-scope="{row}">
<el-button @click="openView(row)" size="mini" type="primary">查看</el-button>
<el-button v-if="row.taskKey === 'startTask' || !row.instanceId" @click="openEdit(row)" size="mini" type="primary">编辑</el-button>
<el-button v-if="row.canRevoke" @click="onRevoke(row)" size="mini" type="danger">撤回</el-button>
<el-button v-if="row.taskKey === 'startTask' || !row.instanceId" @click="onDelete(row)" size="mini" type="danger">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-row class="el-pagination-container">
<el-pagination
@size-change="pageSizeChange"
@current-change="pageNumberChange"
:current-page="pageForm.pageNumber"
:page-sizes="[10, 20, 30, 50]"
:page-size="pageForm.pageSize"
layout="total, sizes, prev, pager, next"
:total="pageForm.totalCount">
</el-pagination>
</el-row>
</el-card>
</template>
<template #edit>
<workflow-message-form
v-if="editMode === 'form'"
ref="workflowMessageFormRef"
@back="back"
@success="pageData">
</workflow-message-form>
<workflow-message-info v-else ref="workflowMessageInfoRef"></workflow-message-info>
</template>
</guava>
</div>
<script nonce="${cspNonce!}">
<!--#include("../form.js"){}#-->
<!--#include("../info.js"){}#-->
new Vue({
el: "#app",
store,
mixins: [initTableMixins],
components: {
"workflow-message-form": WORKFLOW_MESSAGE_FORM,
"workflow-message-info": WORKFLOW_MESSAGE_INFO
},
data() {
return {
editMode: "form",
tableLoading: false,
tableData: [],
pageForm: {
searchKeyword: "",
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "",
pageOrderBy: ""
}
}
},
methods: {
back() {
this.$refs.guava.index()
},
doSearch() {
this.pageForm.pageNumber = 1
this.pageData()
},
pageNumberChange(val) {
this.pageForm.pageNumber = val
this.pageData()
},
pageSizeChange(val) {
this.pageForm.pageSize = val
this.pageData()
},
pageOrder(column) {
this.pageForm.pageOrderName = column.prop
this.pageForm.pageOrderBy = column.order
this.pageData()
},
pageData() {
this.tableLoading = true
this.$axios.post("/platform/dayofficework/workflowmessage/pageData", this.pageForm).then((res) => {
if (res.code === 0) {
this.tableData = res.data.list
this.pageForm.totalCount = res.data.totalCount
} else {
this.$message.warning(res.msg)
}
}).finally(() => {
this.tableLoading = false
})
},
openAdd() {
this.editMode = "form"
this.$refs.guava.edit(() => {
this.$refs.workflowMessageFormRef.initAdd()
})
},
openEdit(row) {
this.editMode = "form"
this.$refs.guava.edit(() => {
this.$refs.workflowMessageFormRef.initEdit(row)
})
},
openView(row) {
this.editMode = "view"
this.$refs.guava.edit(() => {
this.$refs.workflowMessageInfoRef.onOpen(row)
})
},
onRevoke(row) {
this.$confirm("您确定要撤回吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/flow/common/revokeTask", { taskId: row.startTaskId }).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
} else {
this.$message.warning(res.msg)
}
})
})
},
onDelete(row) {
this.$confirm("确定要删除此申请吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/platform/dayofficework/workflowmessage/delete", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
} else {
this.$message.warning(res.msg)
}
})
})
},
applyStatusName(status) {
const map = { 10: "待审核", 20: "审核通过", 30: "审核拒绝", 40: "退回发起人" }
return map[status] || "未知"
},
applyStatusType(status) {
const map = { 10: "warning", 20: "success", 30: "danger", 40: "info" }
return map[status] || "info"
},
sendStatusName(status) {
const map = { 0: "未发送", 1: "已发送", 2: "发送失败" }
return map[status] || "未知"
},
sendStatusType(status) {
const map = { 0: "info", 1: "success", 2: "danger" }
return map[status] || "info"
}
},
created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,203 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
.workflow-message-note {
min-height: 180px;
max-height: 360px;
overflow-y: auto;
padding: 10px;
border: 1px solid #ebeef5;
border-radius: 4px;
}
</style>
<div id="app" v-cloak>
<guava ref="guava">
<template>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="消息标题">
<el-input v-model="pageForm.searchKeyword" placeholder="请输入消息标题" clearable></el-input>
</search-item>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool label="审核列表">
<el-radio-group v-model="pageForm.audit" size="small" @change="doSearch">
<el-radio-button :label="true">已审核</el-radio-button>
<el-radio-button :label="false">未审核</el-radio-button>
</el-radio-group>
</table-tool>
<el-table :data="tableData" v-loading="tableLoading" @sort-change="pageOrder" style="width: 100%">
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
<el-table-column :show-overflow-tooltip="true" label="标题" prop="title"></el-table-column>
<el-table-column label="申请人" prop="applyUserName" width="120"></el-table-column>
<el-table-column label="申请人工号" prop="applyLoginName" width="120"></el-table-column>
<el-table-column label="申请人单位" prop="applyUnitName" show-overflow-tooltip></el-table-column>
<el-table-column label="接收人数" prop="receiverCount" width="100"></el-table-column>
<el-table-column label="当前节点" prop="curTaskName" width="140"></el-table-column>
<el-table-column label="流程状态" prop="instanceState" width="120">
<template slot-scope="{row}">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="150">
<template slot-scope="{row}">
<el-button @click="openView(row)" size="mini" type="primary">查看</el-button>
<el-button v-if="row.taskState === 10" @click="openAudit(row)" size="mini" type="primary">审核</el-button>
</template>
</el-table-column>
</el-table>
<el-row class="el-pagination-container">
<el-pagination
@size-change="pageSizeChange"
@current-change="pageNumberChange"
:current-page="pageForm.pageNumber"
:page-sizes="[10, 20, 30, 50]"
:page-size="pageForm.pageSize"
layout="total, sizes, prev, pager, next"
:total="pageForm.totalCount">
</el-pagination>
</el-row>
</el-card>
</template>
<template #edit>
<workflow-message-info ref="workflowMessageInfoRef">
<div v-if="showApprovalForm">
<div class="process-title">{{ formData.taskName }}</div>
<el-form :model="formData" ref="formRef" label-width="0" label-suffix="" class="flow-task-form">
<el-form-item label="审批意见" prop="opinion" :rules="[{required:true,message:'必填',trigger:['change','blur']}]">
<user-opinion-textarea v-model="formData.opinion"></user-opinion-textarea>
</el-form-item>
</el-form>
<el-row type="flex" justify="end">
<el-button @click="back" size="small">取消</el-button>
<el-button @click="handleTaskAction(6)" :loading="formLoading" size="small" type="info">退回到发起人</el-button>
<el-button @click="handleTaskAction(2)" :loading="formLoading" size="small" type="danger">拒绝申请</el-button>
<el-button @click="handleTaskAction(1)" :loading="formLoading" size="small" type="primary">同意申请</el-button>
</el-row>
</div>
</workflow-message-info>
</template>
</guava>
</div>
<script nonce="${cspNonce!}">
<!--#include("../info.js"){}#-->
new Vue({
el: "#app",
store,
mixins: [initTableMixins],
components: {
"workflow-message-info": WORKFLOW_MESSAGE_INFO
},
data() {
return {
showApprovalForm: false,
tableLoading: false,
formLoading: false,
tableData: [],
pageForm: {
audit: false,
searchKeyword: "",
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "",
pageOrderBy: ""
},
formData: {}
}
},
methods: {
back() {
this.$refs.guava.index()
},
doSearch() {
this.pageForm.pageNumber = 1
this.pageData()
},
pageNumberChange(val) {
this.pageForm.pageNumber = val
this.pageData()
},
pageSizeChange(val) {
this.pageForm.pageSize = val
this.pageData()
},
pageOrder(column) {
this.pageForm.pageOrderName = column.prop
this.pageForm.pageOrderBy = column.order
this.pageData()
},
pageData() {
this.tableLoading = true
this.$axios.post("/platform/dayofficework/workflowmessage/audit/pageData", this.pageForm).then((res) => {
if (res.code === 0) {
this.tableData = res.data.list
this.pageForm.totalCount = res.data.totalCount
} else {
this.$message.warning(res.msg)
}
}).finally(() => {
this.tableLoading = false
})
},
openView(row) {
this.showApprovalForm = false
this.$refs.guava.edit(() => {
this.$refs.workflowMessageInfoRef.onOpen(row)
})
},
openAudit(row) {
this.showApprovalForm = true
this.formData = {
taskId: row.taskId,
taskName: row.curTaskName,
opinion: ""
}
this.$refs.guava.edit(() => {
this.$refs.workflowMessageInfoRef.onOpen(row)
})
},
handleTaskAction(action) {
this.$refs.formRef.validate((valid) => {
if (!valid) {
return
}
this.$confirm("您确定要提交审核结果吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.formLoading = true
this.$axios.post("/platform/dayofficework/workflowmessage/audit/audit", {
taskId: this.formData.taskId,
opinion: this.formData.opinion,
action: action
}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.back()
this.pageData()
} else {
this.$message.error(res.msg)
}
}).finally(() => {
this.formLoading = false
})
})
})
}
},
created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,95 @@
const WORKFLOW_MESSAGE_INFO = {
template: /*language=HTML*/ `
<div>
<div class="process-title">
消息申请信息
<el-link type="primary" @click="openChart" v-if="row && (row.instanceId || row.processInstanceId)">点击查看流程图</el-link>
</div>
<el-descriptions :column="2" border class="flow-task-form">
<el-descriptions-item label="标题">{{ message.title }}</el-descriptions-item>
<!-- <el-descriptions-item label="链接地址">{{ message.url || "无" }}</el-descriptions-item>-->
<el-descriptions-item label="同步企业微信">{{ message.wechatEnterprise ? "是" : "否" }}</el-descriptions-item>
<!-- <el-descriptions-item label="是否需要反馈">{{ message.needBack ? "是" : "否" }}</el-descriptions-item>-->
<el-descriptions-item label="申请人">{{ message.applyUserName }}({{ message.applyLoginName }})</el-descriptions-item>
<el-descriptions-item label="申请人单位">{{ message.applyUnitName }}</el-descriptions-item>
<el-descriptions-item label="消息内容" :span="2">
<div class="workflow-message-note" v-html="message.note"></div>
</el-descriptions-item>
<el-descriptions-item label="消息附件" :span="2">
<file-preview complete_result :files="message.files"></file-preview>
</el-descriptions-item>
</el-descriptions>
<div class="process-title mt10">接收人</div>
<el-table :data="receivers" size="small" max-height="360">
<el-table-column prop="loginName" label="工号" width="140"></el-table-column>
<el-table-column prop="userName" label="姓名" width="120"></el-table-column>
<el-table-column prop="sex" label="性别" width="70"></el-table-column>
<el-table-column prop="mobile" label="手机" width="130"></el-table-column>
<el-table-column prop="unitName" label="所属单位" show-overflow-tooltip></el-table-column>
<el-table-column prop="unionName" label="所属工会" show-overflow-tooltip></el-table-column>
</el-table>
<template v-for="task in doneTasks">
<div class="mt10">
<div class="process-title">{{ task.displayName }}</div>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-if="task.ext.isFirstTaskNode">
<el-descriptions-item label="申请用户">{{ task.ext.initiatorName }}({{ task.ext.initiatorAccount }})</el-descriptions-item>
<el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
</el-descriptions>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-else>
<el-descriptions-item label="办理用户">{{ task.taskFormData.userName }}({{ task.taskFormData.loginName }})</el-descriptions-item>
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
<el-descriptions-item label="办理意见">{{ task.taskFormData.opinion }}</el-descriptions-item>
</el-descriptions>
</div>
</template>
<slot></slot>
<snaker-chart ref="snakerChartRef"></snaker-chart>
</div>
`,
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data() {
return {
row: null,
message: {},
receivers: [],
doneTasks: []
}
},
methods: {
// 打开消息详情:row 为列表当前行,必须包含 id;组件内部统一拉取消息详情和流程办理记录。
onOpen(row) {
this.row = row
this.getInfo()
this.getDoneTasks()
},
// 查询消息详情:参数 id 为工作流消息申请ID;返回 message 详情对象和 receivers 接收人数组。
getInfo() {
this.$axios.post("/platform/dayofficework/workflowmessage/info", { id: this.row.id }).then((res) => {
if (res.code === 0) {
this.message = res.data.message
this.receivers = res.data.receivers
} else {
this.$message.warning(res.msg)
}
})
},
// 查询流程办理记录:参数 bizId 为工作流消息申请ID;返回已办任务数组,用于查看申请和审核流转意见。
getDoneTasks() {
this.$axios.post("/flow/common/doneTasks", { bizId: this.row.id }).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data
}
})
},
// 打开流程图:优先使用列表行中的流程定义ID和流程实例ID,兼容申请列表的 processInstanceId 字段。
openChart() {
this.$refs.snakerChartRef.onOpenFull(this.row.instanceProcessDefineId, this.row.instanceId || this.row.processInstanceId)
}
}
}