This commit is contained in:
那些花儿
2025-07-30 15:19:03 +08:00
parent 65b7960783
commit 73974dbdd3
32 changed files with 1403 additions and 388 deletions
@@ -0,0 +1,32 @@
package com.budwk.app.flow.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.entity.ProcessCategory;
import io.swagger.annotations.Api;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
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 java.util.List;
@IocBean
@At("/flow/category")
@Ok("json:full")
@Api("流程分类")
public class FlowCategoryController {
@Inject
private Dao dao;
@At
@SaCheckLogin
public Result list() {
List<ProcessCategory> list = dao.query(ProcessCategory.class, Cnd.NEW());
return Result.success(list);
}
}
@@ -4,16 +4,20 @@ import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Dict; import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.result.Result; import com.budwk.app.base.result.Result;
import com.budwk.app.base.vo.LabelValueVO; import com.budwk.app.base.vo.LabelValueVO;
import com.budwk.app.flow.constant.FlowConst; import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.FlowEngine; import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.flow.engine.event.ProcessEvent;
import com.budwk.app.flow.engine.event.ProcessPublisher;
import com.budwk.app.flow.engine.model.ProcessModel; import com.budwk.app.flow.engine.model.ProcessModel;
import com.budwk.app.flow.engine.model.TaskModel; import com.budwk.app.flow.engine.model.TaskModel;
import com.budwk.app.flow.entity.Candidate; import com.budwk.app.flow.entity.Candidate;
import com.budwk.app.flow.entity.ProcessDefine; import com.budwk.app.flow.entity.ProcessDefine;
import com.budwk.app.flow.entity.ProcessInstance; import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask; import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum; import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum; import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.vo.ProcessInstanceVO; import com.budwk.app.flow.vo.ProcessInstanceVO;
@@ -66,17 +70,9 @@ public class FlowCommonController {
todoTaskVos.add(taskVO); todoTaskVos.add(taskVO);
} }
// List<ProcessTaskVO> doneTaskVos = new ArrayList<>();
// List<ProcessTask> doneTaskList = flowEngine.processTaskService().getDoneTaskList(instanceId, null);
// for (ProcessTask doneTask : doneTaskList) {
// ProcessTaskVO taskVO = flowEngine.processTaskService().findById(doneTask.getId());
// doneTaskVos.add(taskVO);
// }
HashMap<String, Object> result = new HashMap<>(); HashMap<String, Object> result = new HashMap<>();
result.put("processInstance", instanceVO); result.put("processInstance", instanceVO);
result.put("todoTasks", todoTaskVos); result.put("todoTasks", todoTaskVos);
// result.put("doneTasks", doneTaskVos);
return Result.success(result); return Result.success(result);
} }
@@ -105,12 +101,20 @@ public class FlowCommonController {
@At("/defineInfo") @At("/defineInfo")
@SaCheckLogin @SaCheckLogin
@ApiOperation("流程定义详情") @ApiOperation("流程定义详情")
public Result defineInfo(String defineKey) { public Result defineInfo(String defineKey, Long instanceId) {
// 通过defineKey获取最新的流程定义 ProcessDefine define = null;
ProcessDefine define = flowEngine.processDefineService().getLastByName(defineKey); if (StrUtil.isNotBlank(defineKey)) {
if (define == null) { define = flowEngine.processDefineService().getLastByName(defineKey);
return Result.error("未找到指定的流程定义");
} }
if (instanceId != null) {
ProcessInstance instance = dao.fetch(ProcessInstance.class, instanceId);
define = flowEngine.processDefineService().getById(instanceId);
}
if (define == null) {
return Result.error("没有流程定义");
}
return Result.success(define); return Result.success(define);
} }
@@ -170,9 +174,34 @@ public class FlowCommonController {
@At @At
@SaCheckLogin @SaCheckLogin
@ApiOperation("撤回流程实例") @Aop(TransAop.READ_COMMITTED)
public Result withdrawInstance(@Param("instanceId") Long instanceId) { @ApiOperation("重新发起流程实例")
flowEngine.processInstanceService().withdraw(instanceId, SecurityUtil.getUserId()); public Result reissueInstance(@Param("instanceId") Long instanceId, @Param("bizData") String bizData) {
// // 把已撤销流程修改为进行中
// dao.update(ProcessInstance.class, Chain.make("state", ProcessInstanceStateEnum.DOING.getCode()), Cnd.where(ProcessInstance::getId, "=", instanceId));
// Dict args = Dict.create();
// args.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.RE_APPLY.getCode());
// args.set(FlowConst.FORM_DATA, bizData);
//
// // 查找第一个提交任务节点
// ProcessInstance instance = dao.fetch(ProcessInstance.class, instanceId);
// ProcessModel processModel = flowEngine.processDefineService().getProcessModel(instance.getProcessDefineId());
//
// args.set(FlowConst.PROCESS_INSTANCE_ID_KEY, instanceId);
// args.set(FlowConst.INITIATOR,instance.getCreatedBy());
//
// // 构建执行参数对象
// Execution execution = new Execution();
// execution.setProcessModel(processModel);
// execution.setProcessInstance(instance);
// execution.setProcessInstanceId(instance.getId());
// execution.setEngine(flowEngine);
// execution.setArgs(args);
// execution.setOperator(SecurityUtil.getUserId());
// // 拿到开始节点模型,调用其execute方法
// processModel.getStart().execute(execution);
//
// return Result.success();
return Result.success(); return Result.success();
} }
@@ -215,6 +244,10 @@ public class FlowCommonController {
// 会签不同意,追加不同意标识 // 会签不同意,追加不同意标识
args.put(FlowConst.COUNTERSIGN_DISAGREE_FLAG, 1); args.put(FlowConst.COUNTERSIGN_DISAGREE_FLAG, 1);
flowEngine.executeProcessTask(processTaskId, operator, args); flowEngine.executeProcessTask(processTaskId, operator, args);
} else if (ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.RE_APPLY.getCode())) {
// 重新提交
// args.put(FlowConst.FORM_DATA, args.getStr(FlowConst.FORM_DATA));
flowEngine.executeProcessTask(processTaskId, operator, args);
} else { } else {
// 默认执行 // 默认执行
flowEngine.executeProcessTask(processTaskId, operator, args); flowEngine.executeProcessTask(processTaskId, operator, args);
@@ -222,6 +255,32 @@ public class FlowCommonController {
return Result.success(); return Result.success();
} }
@At
@SaCheckLogin
@Aop(TransAop.READ_COMMITTED)
@ApiOperation("撤销任务")
public Result revokeTask(@Param("taskId") Long taskId) {
// 1.撤销任务
List<ProcessTask> taskList = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getTaskParentId, "=", taskId));
for (ProcessTask processTask : taskList) {
processTask.setTaskState(ProcessTaskStateEnum.WITHDRAW.getCode());
dao.update(processTask);
}
// 2.再次激活自己任务
ProcessTask processTask = dao.fetch(ProcessTask.class, taskId);
processTask.setTaskState(ProcessTaskStateEnum.DOING.getCode());
dao.update(processTask);
// 3.发送任务撤回事件 确保上面执行成功
for (ProcessTask task : taskList) {
ProcessPublisher.notify(ProcessEvent.builder().eventType(ProcessEventTypeEnum.PROCESS_TASK_REVOKE).sourceId(task.getId()).build());
}
return Result.success();
}
@At("/candidate") @At("/candidate")
@SaCheckLogin @SaCheckLogin
@ApiOperation("获取候选用户") @ApiOperation("获取候选用户")
@@ -252,5 +311,13 @@ public class FlowCommonController {
return Result.success(); return Result.success();
} }
@At("/addCandidate")
@SaCheckLogin
@ApiOperation("代理")
public Result addCandidate(@Param("taskId") Long taskId, @Param("actorIds") String[] actorIds) {
flowEngine.processTaskService().addCandidateActor(taskId, Arrays.asList(actorIds));
return Result.success();
}
} }
@@ -122,8 +122,9 @@ public class FlowDesignController {
jsonObject.set("category", processDesign.getCategory()); jsonObject.set("category", processDesign.getCategory());
jsonObject.set("instanceUrl", processDesign.getInstanceUrl()); jsonObject.set("instanceUrl", processDesign.getInstanceUrl());
jsonObject.set("h5InstanceUrl", processDesign.getH5InstanceUrl()); jsonObject.set("h5InstanceUrl", processDesign.getH5InstanceUrl());
jsonObject.set("instanceViewUrl",processDesign.getInstanceViewUrl()); jsonObject.set("instanceViewUrl", processDesign.getInstanceViewUrl());
jsonObject.set("h5InstanceViewUrl",processDesign.getH5InstanceViewUrl()); jsonObject.set("h5InstanceViewUrl", processDesign.getH5InstanceViewUrl());
jsonObject.set("icon", processDesign.getIcon());
processDesign.setContent(jsonObject); processDesign.setContent(jsonObject);
dao.insert(processDesign); dao.insert(processDesign);
return Result.success(); return Result.success();
@@ -139,8 +140,9 @@ public class FlowDesignController {
jsonObject.set("category", processDesign.getCategory()); jsonObject.set("category", processDesign.getCategory());
jsonObject.set("instanceUrl", processDesign.getInstanceUrl()); jsonObject.set("instanceUrl", processDesign.getInstanceUrl());
jsonObject.set("h5InstanceUrl", processDesign.getH5InstanceUrl()); jsonObject.set("h5InstanceUrl", processDesign.getH5InstanceUrl());
jsonObject.set("instanceViewUrl",processDesign.getInstanceViewUrl()); jsonObject.set("instanceViewUrl", processDesign.getInstanceViewUrl());
jsonObject.set("h5InstanceViewUrl",processDesign.getH5InstanceViewUrl()); jsonObject.set("h5InstanceViewUrl", processDesign.getH5InstanceViewUrl());
jsonObject.set("icon", processDesign.getIcon());
processDesign.setContent(jsonObject); processDesign.setContent(jsonObject);
processDesignService.update(processDesign); processDesignService.update(processDesign);
return Result.success(); return Result.success();
@@ -156,8 +158,9 @@ public class FlowDesignController {
jsonObject.set("category", processDesign.getCategory()); jsonObject.set("category", processDesign.getCategory());
jsonObject.set("instanceUrl", processDesign.getInstanceUrl()); jsonObject.set("instanceUrl", processDesign.getInstanceUrl());
jsonObject.set("h5InstanceUrl", processDesign.getH5InstanceUrl()); jsonObject.set("h5InstanceUrl", processDesign.getH5InstanceUrl());
jsonObject.set("instanceViewUrl",processDesign.getInstanceViewUrl()); jsonObject.set("instanceViewUrl", processDesign.getInstanceViewUrl());
jsonObject.set("h5InstanceViewUrl",processDesign.getH5InstanceViewUrl()); jsonObject.set("h5InstanceViewUrl", processDesign.getH5InstanceViewUrl());
jsonObject.set("icon", processDesign.getIcon());
processDesign.setContent(jsonObject); processDesign.setContent(jsonObject);
processDesignService.update(processDesign); processDesignService.update(processDesign);
return Result.success(); return Result.success();
@@ -43,14 +43,16 @@ public class FlowTodoCenter {
public Result todo(Integer pageNumber, Integer pageSize, String keyword, String categoty) { public Result todo(Integer pageNumber, Integer pageSize, String keyword, String categoty) {
Sql sql = Sqls.create(""" Sql sql = Sqls.create("""
SELECT SELECT
t.id, t.id AS taskId,
t.displayName AS taskName, t.displayName AS taskName,
t.taskState, t.taskState,
t.formKey, t.formKey,
t.createdAt, t.createdAt,
t.finishTime, t.finishTime,
t.variable, t.variable,
ins.state AS instanceState ins.state AS instanceState,
ins.id AS instanceId,
ins.businessNo
FROM FROM
wf_process_task t wf_process_task t
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
@@ -76,12 +78,16 @@ public class FlowTodoCenter {
public Result done(Integer pageNumber, Integer pageSize, String keyword, String categoty) { public Result done(Integer pageNumber, Integer pageSize, String keyword, String categoty) {
Sql sql = Sqls.create(""" Sql sql = Sqls.create("""
SELECT SELECT
t.id, t.id AS taskId,
t.displayName AS taskName, t.displayName AS taskName,
t.taskState, t.taskState,
t.formKey, t.formKey,
t.createdAt,
t.finishTime, t.finishTime,
ins.state AS instanceState t.variable,
ins.state AS instanceState,
ins.id AS instanceId,
ins.businessNo
FROM FROM
wf_process_task t wf_process_task t
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
@@ -107,14 +113,14 @@ public class FlowTodoCenter {
public Result started(Integer pageNumber, Integer pageSize, String keyword, String categoty) { public Result started(Integer pageNumber, Integer pageSize, String keyword, String categoty) {
Sql sql = Sqls.create(""" Sql sql = Sqls.create("""
SELECT SELECT
ins.id, ins.id AS instanceId,
ins.processDefineId, ins.processDefineId,
ins.state, ins.state,
ins.businessNo, ins.businessNo,
ins.operator, ins.operator,
ins.variable, ins.variable,
ins.createdAt, ins.createdAt,
GROUP_CONCAT(t.displayName) as taskName GROUP_CONCAT(DISTINCT t.displayName) as taskName
FROM FROM
wf_process_instance ins wf_process_instance ins
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id and t.taskState = 10 LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id and t.taskState = 10
@@ -19,6 +19,7 @@ import com.budwk.app.flow.service.ProcessDefineService;
import com.budwk.app.flow.service.ProcessInstanceService; import com.budwk.app.flow.service.ProcessInstanceService;
import com.budwk.app.flow.service.ProcessTaskService; import com.budwk.app.flow.service.ProcessTaskService;
import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.ioc.aop.Aop; import org.nutz.ioc.aop.Aop;
import java.util.Collections; import java.util.Collections;
@@ -22,6 +22,7 @@ import java.util.stream.Collectors;
@Data @Data
public class ProcessModel extends BaseModel { public class ProcessModel extends BaseModel {
private String type; // 流程定义分类 private String type; // 流程定义分类
private String icon; // 流程定义图标
private String category; // 流程定义分类 private String category; // 流程定义分类
private String instanceUrl; // 启动实例要填写的表单key private String instanceUrl; // 启动实例要填写的表单key
private String h5InstanceUrl; // 启动实例要填写的手机端表单key private String h5InstanceUrl; // 启动实例要填写的手机端表单key
@@ -4,15 +4,17 @@ import com.budwk.app.flow.engine.model.BaseModel;
import lombok.Data; import lombok.Data;
import java.util.List; import java.util.List;
/** /**
*
* logicFlow模型 * logicFlow模型
*
* @author mldong * @author mldong
* @date 2023/4/26 * @date 2023/4/26
*/ */
@Data @Data
public class LfModel extends BaseModel { public class LfModel extends BaseModel {
private String type; // 流程定义分类 private String type; // 流程定义分类
private String category;
private String expireTime;// 过期时间(常量或变量) private String expireTime;// 过期时间(常量或变量)
private String instanceUrl; // 启动实例的url,前后端分离后,定义为路由名或或路由地址 private String instanceUrl; // 启动实例的url,前后端分离后,定义为路由名或或路由地址
private String h5InstanceUrl; private String h5InstanceUrl;
@@ -78,6 +78,7 @@ public class ModelParser {
processModel.setName(lfModel.getName()); processModel.setName(lfModel.getName());
processModel.setDisplayName(lfModel.getDisplayName()); processModel.setDisplayName(lfModel.getDisplayName());
processModel.setType(lfModel.getType()); processModel.setType(lfModel.getType());
processModel.setCategory(lfModel.getCategory());
processModel.setInstanceUrl(lfModel.getInstanceUrl()); processModel.setInstanceUrl(lfModel.getInstanceUrl());
processModel.setH5InstanceUrl(lfModel.getH5InstanceUrl()); processModel.setH5InstanceUrl(lfModel.getH5InstanceUrl());
processModel.setInstanceViewUrl(lfModel.getInstanceViewUrl()); processModel.setInstanceViewUrl(lfModel.getInstanceViewUrl());
@@ -0,0 +1,28 @@
package com.budwk.app.flow.entity;
import com.budwk.app.base.model.BaseModel;
import lombok.Getter;
import lombok.Setter;
import org.nutz.dao.entity.annotation.*;
@Getter
@Setter
@Table("wf_process_category")
@Comment("流程分类")
public class ProcessCategory extends BaseModel {
@Id
@Comment("ID")
private Long id;
@Comment("名称")
@Column
@ColDefine(type = ColType.VARCHAR, width = 10)
private String name;
@Comment("图标")
@Column
@ColDefine(type = ColType.VARCHAR, width = 255)
private String icon;
}
@@ -1,5 +1,6 @@
package com.budwk.app.flow.entity; package com.budwk.app.flow.entity;
import cn.hutool.extra.pinyin.PinyinUtil;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import com.budwk.app.base.model.BaseModel; import com.budwk.app.base.model.BaseModel;
import lombok.Getter; import lombok.Getter;
@@ -72,4 +73,9 @@ public class ProcessDefine extends BaseModel {
@Column @Column
private Integer version; private Integer version;
@Comment
@Column
@ColDefine(type = ColType.CHAR)
private Character pinyinName;
} }
@@ -9,6 +9,9 @@ public enum ProcessEventTypeEnum{
PROCESS_INSTANCE_END(2, "流程实例结束事件"), PROCESS_INSTANCE_END(2, "流程实例结束事件"),
PROCESS_TASK_START(3, "流程任务开始事件"), PROCESS_TASK_START(3, "流程任务开始事件"),
PROCESS_TASK_END(4, "流程任务结束事件"), PROCESS_TASK_END(4, "流程任务结束事件"),
PROCESS_TASK_REVOKE(5, "流程任务撤销事件"),
; ;
private final Integer code; private final Integer code;
@@ -0,0 +1,47 @@
package com.budwk.app.flow.listenter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.event.ProcessEvent;
import com.budwk.app.flow.engine.event.ProcessEventListener;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
/**
* 流程任务撤销事件监听器
*/
@IocBean
public class ProcessTaskRevokeEventListener implements ProcessEventListener {
@Inject
private Dao dao;
@Override
public void onEvent(ProcessEvent event) {
if(event.getEventType()== ProcessEventTypeEnum.PROCESS_TASK_REVOKE){
Long taskId = event.getSourceId();
ProcessTask task = dao.fetch(ProcessTask.class, taskId);
String taskDisplayName = task.getDisplayName();
ProcessInstance instance = dao.fetch(ProcessInstance.class, task.getProcessInstanceId());
JSONObject variable = JSONUtil.parseObj(instance.getVariable());
String initiatorName = variable.getStr(FlowConst.INITIATOR_NAME);
String instanceName = variable.getStr(FlowConst.PROCESS_INSTANCE_NAME);
// 消息API提醒
String msgTemplate = "{},您的{}已被撤销,无需审核。";
String msg = StrUtil.format(msgTemplate, instanceName, taskDisplayName);
// 发送 go go go
}
}
}
@@ -233,4 +233,17 @@ public interface ProcessInstanceService extends BaseService<ProcessInstance> {
* @return * @return
*/ */
List<LabelValueVO> getAssigneeTextData(Long processInstanceId); List<LabelValueVO> getAssigneeTextData(Long processInstanceId);
/**
* 删除流程实例
* @param processInstanceId
*/
void deleteProcessInstanceById(Long processInstanceId);
/**
* 删除流程实例
* @param businessKey
*/
void deleteProcessInstanceByBusinessKey(String businessKey);
} }
@@ -3,17 +3,14 @@ package com.budwk.app.flow.service.impl;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Dict; import cn.hutool.extra.pinyin.PinyinUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.budwk.app.base.enums.UpAndDownParam; import com.budwk.app.base.enums.UpAndDownParam;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.service.impl.BaseServiceImpl; import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.flow.engine.model.ProcessModel; import com.budwk.app.flow.engine.model.ProcessModel;
import com.budwk.app.flow.engine.parser.ModelParser; import com.budwk.app.flow.engine.parser.ModelParser;
import com.budwk.app.flow.entity.ProcessDefine; import com.budwk.app.flow.entity.ProcessDefine;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.service.ProcessDefineService; import com.budwk.app.flow.service.ProcessDefineService;
import com.budwk.app.flow.vo.ProcessDefineVO; import com.budwk.app.flow.vo.ProcessDefineVO;
import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.aop.interceptor.ioc.TransAop;
@@ -23,9 +20,7 @@ import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Date; import java.util.Date;
import java.util.List;
@IocBean(args = {"refer:dao"}) @IocBean(args = {"refer:dao"})
public class ProcessDefineServiceImpl extends BaseServiceImpl<ProcessDefine> implements ProcessDefineService { public class ProcessDefineServiceImpl extends BaseServiceImpl<ProcessDefine> implements ProcessDefineService {
@@ -100,8 +95,6 @@ public class ProcessDefineServiceImpl extends BaseServiceImpl<ProcessDefine> imp
// 1. json定义文件转成流程模型 // 1. json定义文件转成流程模型
ProcessModel processModel = ModelParser.parse(defineJsonStr); ProcessModel processModel = ModelParser.parse(defineJsonStr);
// 2. 根据名称查询,取最新版本的流程定义记录 // 2. 根据名称查询,取最新版本的流程定义记录
List<ProcessDefine> processDefineList = query(Cnd.where(ProcessDefine::getName, "=", processModel.getName()).desc(ProcessDefine::getId));
ProcessDefine latestDefine = dao().fetch(ProcessDefine.class, Cnd.where(ProcessDefine::getName, "=", processModel.getName()).desc(ProcessDefine::getId)); ProcessDefine latestDefine = dao().fetch(ProcessDefine.class, Cnd.where(ProcessDefine::getName, "=", processModel.getName()).desc(ProcessDefine::getId));
ProcessDefine define = null; ProcessDefine define = null;
@@ -122,9 +115,8 @@ public class ProcessDefineServiceImpl extends BaseServiceImpl<ProcessDefine> imp
define.setH5InstanceUrl(processModel.getH5InstanceUrl()); define.setH5InstanceUrl(processModel.getH5InstanceUrl());
define.setInstanceViewUrl(processModel.getInstanceViewUrl()); define.setInstanceViewUrl(processModel.getInstanceViewUrl());
define.setH5InstanceViewUrl(processModel.getH5InstanceViewUrl()); define.setH5InstanceViewUrl(processModel.getH5InstanceViewUrl());
define.setIcon(processModel.getIcon());
// define.setInstanceViewUrl(latestDefine.getInstanceViewUrl()); define.setPinyinName(PinyinUtil.getFirstLetter(define.getDisplayName().charAt(0)));
// define.setH5InstanceViewUrl(latestDefine.getH5InstanceViewUrl());
define.setState(1); define.setState(1);
define.setContent(JSONUtil.parseObj(defineJsonStr)); define.setContent(JSONUtil.parseObj(defineJsonStr));
@@ -9,11 +9,9 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.expression.ExpressionUtil; import cn.hutool.extra.expression.ExpressionUtil;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.budwk.app.base.enums.YesNoEnum;
import com.budwk.app.base.service.impl.BaseServiceImpl; import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.base.vo.LabelValueVO; import com.budwk.app.base.vo.LabelValueVO;
import com.budwk.app.flow.constant.FlowConst; import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.Context;
import com.budwk.app.flow.engine.DecisionHandler; import com.budwk.app.flow.engine.DecisionHandler;
import com.budwk.app.flow.engine.FlowEngine; import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.flow.engine.core.Execution; import com.budwk.app.flow.engine.core.Execution;
@@ -93,7 +91,13 @@ public class ProcessInstanceServiceImpl extends BaseServiceImpl<ProcessInstance>
ProcessInstanceVO instanceVO = new ProcessInstanceVO(); ProcessInstanceVO instanceVO = new ProcessInstanceVO();
BeanUtil.fillBeanWithMapIgnoreCase(nutMap, instanceVO, true); BeanUtil.fillBeanWithMapIgnoreCase(nutMap, instanceVO, true);
instanceVO.setJsonObject(processDefineService.getDefineJsonObject(instanceVO.getProcessDefineId()));
ProcessDefine define = processDefineService.fetch(instanceVO.getProcessDefineId());
// instanceVO.setJsonObject(processDefineService.getDefineJsonObject(instanceVO.getProcessDefineId()));
instanceVO.setJsonObject(JSONUtil.parseObj(define));
return instanceVO; return instanceVO;
} }
@@ -319,24 +323,38 @@ public class ProcessInstanceServiceImpl extends BaseServiceImpl<ProcessInstance>
@Override @Override
public void withdraw(Long processInstanceId, String operator) { public void withdraw(Long processInstanceId, String operator) {
Date now = new Date(); // Date now = new Date();
// 1. 将该流程实例状态修改为撤回 // 1. 将该流程实例状态修改为撤回
ProcessInstance processInstance = new ProcessInstance(); ProcessInstance processInstance = new ProcessInstance();
processInstance.setId(processInstanceId);
processInstance.setState(ProcessInstanceStateEnum.WITHDRAW.getCode()); processInstance.setState(ProcessInstanceStateEnum.WITHDRAW.getCode());
processInstance.setUpdatedAt(System.currentTimeMillis()); processInstance.setUpdatedAt(System.currentTimeMillis());
processInstance.setUpdatedBy(operator); processInstance.setUpdatedBy(operator);
int update = dao().update(ProcessInstance.class, Chain.from(processInstance), Cnd.where(ProcessInstance::getId, "=", processInstanceId) int updated = dao().updateIgnoreNull(processInstance);
.and(ProcessInstance::getState, "=", ProcessInstanceStateEnum.DOING.getCode()));
if (update >= 1) { // 2. 将该流程实例产生的任务状态修改为撤回
// 2. 将该流程实例产生的任务状态修改为撤回 if (updated > 0) {
ProcessTask processTask = new ProcessTask(); List<ProcessTask> taskList = dao().query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId).and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.DOING.getCode()));
processTask.setTaskState(ProcessTaskStateEnum.WITHDRAW.getCode()); for (ProcessTask processTask : taskList) {
processTask.setUpdatedAt(System.currentTimeMillis()); processTask.setTaskState(ProcessTaskStateEnum.WITHDRAW.getCode());
processTask.setUpdatedBy(operator); processTask.setUpdatedAt(System.currentTimeMillis());
dao().update(ProcessTask.class, Chain.from(processTask), Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId) processTask.setUpdatedBy(operator);
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.DOING.getCode())); }
dao().update(taskList, "taskState|updateAt|updateBy");
} }
// int update = dao().update(ProcessInstance.class, Chain.from(processInstance), Cnd.where(ProcessInstance::getId, "=", processInstanceId)
// .and(ProcessInstance::getState, "=", ProcessInstanceStateEnum.DOING.getCode()));
//
// if (update >= 1) {
// // 2. 将该流程实例产生的任务状态修改为撤回
// ProcessTask processTask = new ProcessTask();
// processTask.setTaskState(ProcessTaskStateEnum.WITHDRAW.getCode());
// processTask.setUpdatedAt(System.currentTimeMillis());
// processTask.setUpdatedBy(operator);
// dao().update(ProcessTask.class, Chain.from(processTask), Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)
// .and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.DOING.getCode()));
// }
} }
/** /**
@@ -531,4 +549,22 @@ public class ProcessInstanceServiceImpl extends BaseServiceImpl<ProcessInstance>
.collect(Collectors.toList()); .collect(Collectors.toList());
return result; return result;
} }
@Override
@Aop(TransAop.READ_COMMITTED)
public void deleteProcessInstanceById(Long processInstanceId) {
List<ProcessTask> taskList = dao().query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId));
List<Long> taskIds = taskList.stream().map(ProcessTask::getId).toList();
dao().clear(ProcessTask.class, Cnd.where(ProcessTask::getId, "in", taskIds));
dao().clear(ProcessInstance.class, Cnd.where(ProcessInstance::getId, "=", processInstanceId));
}
@Override
public void deleteProcessInstanceByBusinessKey(String businessKey) {
ProcessInstance processInstance = fetch(Cnd.where(ProcessInstance::getBusinessNo, "=", businessKey));
if (processInstance != null) {
deleteProcessInstanceById(processInstance.getId());
}
}
} }
@@ -108,7 +108,7 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl<ProcessTask> impleme
@Override @Override
public List<ProcessTask> getDoneTaskList(Long processInstanceId, String[] taskNames) { public List<ProcessTask> getDoneTaskList(Long processInstanceId, String[] taskNames) {
Cnd cnd = Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId).and(ProcessTask::getTaskState, "!=", ProcessTaskStateEnum.DOING.getCode()); Cnd cnd = Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId).and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.FINISHED.getCode());
cnd.andEX(ProcessTask::getTaskName, "", taskNames); cnd.andEX(ProcessTask::getTaskName, "", taskNames);
List<ProcessTask> processTaskList = query(cnd); List<ProcessTask> processTaskList = query(cnd);
return processTaskList; return processTaskList;
@@ -47,6 +47,16 @@ public class SysHomeV4Controller {
} }
/**
* 服务中心
*/
@At("/serv")
@Ok("beetl:/layouts/v4/serv.html")
@SaCheckLogin
public void serv(){
}
/** /**
* 子系统 * 子系统
* @param appId 应用ID * @param appId 应用ID
@@ -0,0 +1,69 @@
package com.budwk.app.sys.controller.v4;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.entity.ProcessCategory;
import com.budwk.app.sys.services.SysMenuService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@IocBean
@At("/platform/v4/serv")
@Api(value = "服务中心接口", tags = "服务中心接口")
public class SysV4ServController {
@Inject
private Dao dao;
@Inject
private SysMenuService sysMenuService;
@At("/categories")
@Ok("json")
@SaCheckLogin
@ApiOperation("获取分类")
public Result categories() {
List<ProcessCategory> list = dao.query(ProcessCategory.class, Cnd.NEW());
return Result.success(list);
}
@At("/list")
@Ok("json:full")
@SaCheckLogin
@ApiOperation("获取应用列表")
public Result list(@Param("categoryId") String categoryId, @Param("letter") String letter, @Param("keyword") String keyword, HttpServletRequest req) {
Sql sql = Sqls.create("""
SELECT t.*
FROM wf_process_define t
INNER JOIN (
SELECT name, MAX(id) AS max_id
FROM wf_process_define
GROUP BY name
) sub ON t.name = sub.name AND t.id = sub.max_id
$condition
""");
Cnd cnd = Cnd.NEW();
if (StrUtil.isNotBlank(keyword)) {
cnd.where().andLike("t.disPlayName", keyword);
}
cnd.andEX("t.category", "=", categoryId);
sql.setCondition(cnd);
List<NutMap> list = sysMenuService.listMap(sql);
return Result.success(list);
}
}
@@ -3,17 +3,22 @@ package com.budwk.app.zhgh.democratic.suggestionBox.controller;
import cn.dev33.satoken.annotation.SaCheckLogin; import cn.dev33.satoken.annotation.SaCheckLogin;
import com.budwk.app.base.page.Pagination; import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result; import com.budwk.app.base.result.Result;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.web.commons.auth.utils.SecurityUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.democratic.suggestionBox.service.SuggestionBoxService; import com.budwk.app.zhgh.democratic.suggestionBox.service.SuggestionBoxService;
import io.swagger.annotations.Api; 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.Cnd;
import org.nutz.dao.Sqls; import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql; 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.Inject;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap; import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
@IocBean @IocBean
@At("/platform/suggestionBox2/apply") @At("/platform/suggestionBox2/apply")
@@ -23,11 +28,13 @@ public class SuggestionApplyController {
@Inject @Inject
private SuggestionBoxService suggestionBoxService; private SuggestionBoxService suggestionBoxService;
@Inject
private FlowEngine flowEngine;
@At("/index") @At("/index")
@Ok("beetl:/platform/zhgh/dayofficework/suggestionBox/apply/index.html") @Ok("beetl:/platform/zhgh/dayofficework/suggestionBox/apply/index.html")
@SaCheckLogin @SaCheckLogin
public void index(){ public void index() {
} }
@@ -62,7 +69,9 @@ public class SuggestionApplyController {
t.taskState, t.taskState,
t.finishTime, t.finishTime,
t.taskParentId, t.taskParentId,
t.variable taskVariale t.variable taskVariale,
IF((SELECT taskName FROM wf_process_task tt WHERE tt.id = t.taskParentId) = 'startTask', 1, 0) canRevoke,
(select MAX(id) from wf_process_task where processInstanceId = ins.id and taskName = 'startTask') AS startTaskId
FROM FROM
suggestion_box info suggestion_box info
LEFT JOIN `wf_process_instance` ins ON ins.businessNo = info.id LEFT JOIN `wf_process_instance` ins ON ins.businessNo = info.id
@@ -78,5 +87,14 @@ public class SuggestionApplyController {
return Result.success(pagination); return Result.success(pagination);
} }
@At
@SaCheckLogin
@Aop(TransAop.READ_COMMITTED)
@ApiOperation("删除")
public Result delete(@Param("id") String id) {
suggestionBoxService.delete(id);
flowEngine.processInstanceService().deleteProcessInstanceByBusinessKey(id);
return Result.success();
}
} }
@@ -68,12 +68,16 @@ public class SuggestionXghController {
t.taskState, t.taskState,
t.finishTime, t.finishTime,
t.taskParentId, t.taskParentId,
t.variable taskVariale t.variable taskVariable,
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName),'结束') curTaskName,
IF(rt.id IS NOT NULL, 1, 0) AS canRevoke
FROM FROM
wf_process_task t wf_process_task t
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
LEFT JOIN suggestion_box info ON info.id = ins.businessNo LEFT JOIN suggestion_box info ON info.id = ins.businessNo
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id 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
$condition $condition
"""); """);
Cnd cnd = Cnd.NEW(); Cnd cnd = Cnd.NEW();
@@ -86,8 +90,6 @@ public class SuggestionXghController {
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode()); cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
} }
cnd.desc("t.createdAt");
cnd.andEX("YEAR(info.submitTime)", "=", year); cnd.andEX("YEAR(info.submitTime)", "=", year);
cnd.and(Cnd.likeEX("info.title", title)); cnd.and(Cnd.likeEX("info.title", title));
@@ -96,6 +98,8 @@ public class SuggestionXghController {
} else { } else {
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy())); cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
} }
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
sql.setCondition(cnd); sql.setCondition(cnd);
Pagination<NutMap> pageVO = suggestionBoxService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); Pagination<NutMap> pageVO = suggestionBoxService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pageVO); return Result.success(pageVO);
@@ -664,7 +664,6 @@ td.no-b {
} }
.flow-task-form .el-descriptions-item__label.is-bordered-label { .flow-task-form .el-descriptions-item__label.is-bordered-label {
color: rgb(102, 102, 102) !important;
background: rgb(248, 249, 250) !important; background: rgb(248, 249, 250) !important;
padding: 8px 12px !important; padding: 8px 12px !important;
border: 1px solid rgb(221, 221, 221) !important; border: 1px solid rgb(221, 221, 221) !important;
@@ -36,7 +36,7 @@
<div class="panel-content"> <div class="panel-content">
<div class="info-table"> <div class="info-table">
<!-- 新申请场景 --> <!-- 新申请场景 -->
<template v-if="isNewApplication"> <template v-if="isNewApplication || (processInstance && processInstance.state === 30)">
<div class="info-row"> <div class="info-row">
<div class="info-label">流程名称</div> <div class="info-label">流程名称</div>
<div class="info-value">{{ processDefinition.displayName || processDefinition.name }}</div> <div class="info-value">{{ processDefinition.displayName || processDefinition.name }}</div>
@@ -82,13 +82,20 @@
</div> </div>
<!-- 表单处理区域 --> <!-- 表单处理区域 -->
<div class="form-panel" v-if="isNewApplication || todoTasks.map((v) => v.id).includes(taskId)"> <div
class="form-panel"
v-if="isNewApplication || (processInstance && processInstance.state === 30) || todoTasks.map((v) => v.id).includes(taskId)"
>
<div class="panel-header"> <div class="panel-header">
<h3 class="panel-title">{{ formPanelTitle }}</h3> <h3 class="panel-title">{{ formPanelTitle }}</h3>
</div> </div>
<div class="panel-content"> <div class="panel-content">
<!-- 新申请表单 --> <!-- 新申请表单 -->
<div v-if="isNewApplication" id="application-form-container" class="form-content"> <div
v-if="isNewApplication || (processInstance && processInstance.state === 30)"
id="application-form-container"
class="form-content"
>
<div v-if="pjaxLoading.apply" class="loading-state"> <div v-if="pjaxLoading.apply" class="loading-state">
<i class="el-icon-loading"></i> <i class="el-icon-loading"></i>
<span>正在加载申请表单...</span> <span>正在加载申请表单...</span>
@@ -121,7 +128,72 @@
</div> </div>
</template> </template>
<script> <script type="text/javascript">
class PjaxSync {
constructor() {
this.isLoading = false
this.queue = []
}
async request(url, options = {}) {
return new Promise((resolve, reject) => {
const requestData = { url, options, resolve, reject }
if (this.isLoading) {
this.queue.push(requestData)
return
}
this._executeRequest(requestData)
})
}
_executeRequest({ url, options, resolve, reject }) {
this.isLoading = true
const defaultOptions = {
timeout: 10000,
push: false,
replace: false,
...options
}
const successHandler = (event, data, status, xhr) => {
this._cleanup()
resolve({ data, status, xhr })
this._processQueue()
}
const errorHandler = (event, xhr, textStatus, errorThrown) => {
this._cleanup()
reject(new Error(textStatus || "PJAX request failed"))
this._processQueue()
}
$(document).one("pjax:success", successHandler)
$(document).one("pjax:error", errorHandler)
$.pjax({
url: url,
...defaultOptions
})
}
_cleanup() {
this.isLoading = false
$(document).off("pjax:success pjax:error")
}
_processQueue() {
if (this.queue.length > 0) {
const nextRequest = this.queue.shift()
this._executeRequest(nextRequest)
}
}
}
// 创建全局实例
const pjaxSync = new PjaxSync()
module.exports = { module.exports = {
name: "SnakerFlow", name: "SnakerFlow",
data() { data() {
@@ -221,7 +293,7 @@ module.exports = {
} else if (this.shouldShowTaskForm) { } else if (this.shouldShowTaskForm) {
return this.currentTask.displayName || "任务表单" return this.currentTask.displayName || "任务表单"
} else if (this.shouldShowHistoryProcess) { } else if (this.shouldShowHistoryProcess) {
return "办理过程" return "申请表单"
} else { } else {
return "表单信息" return "表单信息"
} }
@@ -236,7 +308,6 @@ module.exports = {
// 是否显示任务表单 // 是否显示任务表单
shouldShowTaskForm() { shouldShowTaskForm() {
debugger
return this.taskId != null return this.taskId != null
}, },
@@ -277,20 +348,20 @@ module.exports = {
}, },
created() { created() {
this.initComponent() this.initComponent()
// 监听 // 监听消息
window.addEventListener("message", (event) => { new BroadcastChannel("zhgh-global-channel").addEventListener("message", (event) => {
if (event.data.type === "task-complete") { if (event.data.type === "task-complete") {
this.initComponent() this.initComponent()
} }
}) })
}, },
beforeDestroy() {
console.log("beforeDestroy")
},
methods: { methods: {
// 初始化组件 // 初始化组件
async initComponent() { async initComponent() {
this.loading = true this.loading = true
debugger
if (this.isNewApplication) { if (this.isNewApplication) {
// 新申请:加载流程定义信息 // 新申请:加载流程定义信息
await this.loadProcessDefinition() await this.loadProcessDefinition()
@@ -299,6 +370,12 @@ module.exports = {
} else if (this.isExistingProcess) { } else if (this.isExistingProcess) {
// 已有流程:加载流程实例信息 // 已有流程:加载流程实例信息
await this.loadProcessInstance() await this.loadProcessInstance()
if (this.processInstance && this.processInstance.state === 30) {
// 流程被撤回了 此时加载申请表单
// 加载流程申请表单
await this.loadApplicationForm()
}
// 加载任务信息 // 加载任务信息
await this.loadCurrentTask() await this.loadCurrentTask()
// 如果有businessId且不是第一个任务节点,加载历史办理过程 // 如果有businessId且不是第一个任务节点,加载历史办理过程
@@ -317,9 +394,11 @@ module.exports = {
// 加载流程定义信息 // 加载流程定义信息
async loadProcessDefinition() { async loadProcessDefinition() {
if (!this.defineKey) return if (!this.defineKey) return
try { try {
const response = await $.get("/flow/common/defineInfo", { defineKey: this.defineKey }) const response = await $.get("/flow/common/defineInfo", {
defineKey: this.defineKey,
instanceId: this.instanceId
})
if (response.code === 0) { if (response.code === 0) {
this.processDefinition = response.data this.processDefinition = response.data
} }
@@ -334,20 +413,11 @@ module.exports = {
this.pjaxLoading.apply = true this.pjaxLoading.apply = true
$.pjax({ await pjaxSync.request(this.processDefinition.instanceUrl, {
url: this.processDefinition.instanceUrl, container: "#application-form-container"
container: "#application-form-container",
push: false,
replace: false,
timeout: 10000
}) })
.done(() => {
this.pjaxLoading.apply = false this.pjaxLoading.apply = false
})
.fail(() => {
this.pjaxLoading.apply = false
console.log(this.processDefinition.instanceUrl, "申请表单加载失败")
})
}, },
// 加载流程实例信息 // 加载流程实例信息
@@ -356,6 +426,7 @@ module.exports = {
const { code, data, msg } = await $.get("/flow/common/instanceInfo", { instanceId: this.instanceId }) const { code, data, msg } = await $.get("/flow/common/instanceInfo", { instanceId: this.instanceId })
if (code === 0) { if (code === 0) {
this.processInstance = data.processInstance this.processInstance = data.processInstance
this.processDefinition = data.processInstance?.jsonObject
this.todoTasks = data.todoTasks this.todoTasks = data.todoTasks
} }
}, },
@@ -381,43 +452,23 @@ module.exports = {
this.pjaxLoading.taskForm = true this.pjaxLoading.taskForm = true
$.pjax({ await pjaxSync.request(this.currentTask.taskModel.form, {
url: this.currentTask.taskModel.form, container: "#task-form-container"
container: "#task-form-container",
push: false,
replace: false,
timeout: 10000
}) })
.done(() => {
this.pjaxLoading.taskForm = false
})
.fail(() => {
this.pjaxLoading.taskForm = false
console.log(this.currentTask.taskModel.form, "任务表单加载失败")
})
}, },
// 加载历史办理过程 // 加载历史办理过程
async loadHistoryProcess() { async loadHistoryProcess() {
if (!this.businessId) return if (!this.businessId) return
const { instanceViewUrl } = this.processInstance.jsonObject const { instanceViewUrl } = this.processInstance.jsonObject
this.pjaxLoading.historyProcess = true
if (instanceViewUrl) { if (instanceViewUrl) {
$.pjax({ this.pjaxLoading.historyProcess = true
url: `${instanceViewUrl}?businessId=${this.businessId}&instanceId=${this.instanceId}`,
container: "#history-process-container", await pjaxSync.request(`${instanceViewUrl}?businessId=${this.businessId}&instanceId=${this.instanceId}`, {
push: false, container: "#history-process-container"
replace: false,
timeout: 10000
}) })
.done(() => {
this.pjaxLoading.historyProcess = false this.pjaxLoading.historyProcess = false
})
.fail(() => {
this.pjaxLoading.historyProcess = false
console.log("历史办理过程加载失败")
})
} }
}, },
@@ -149,8 +149,7 @@ module.exports = {
// 处理提交操作 // 处理提交操作
handleSubmit() { handleSubmit() {
// 根据是否有流程实例来确定操作类型 // 根据是否有流程实例来确定操作类型
// const submitAction = this.instanceId ? this.actionEnum.RE_APPLY : this.actionEnum.APPLY const submitAction = this.instanceId ? this.actionEnum.RE_APPLY : this.actionEnum.APPLY
const submitAction = this.actionEnum.APPLY
// 触发父组件事件,传递提交操作 // 触发父组件事件,传递提交操作
this.$emit("task-action", { this.$emit("task-action", {
@@ -160,7 +159,6 @@ module.exports = {
defineId: this.defineId, defineId: this.defineId,
defineKey: this.defineKey, defineKey: this.defineKey,
businessId: this.businessId businessId: this.businessId
// currentTask: this.taskInfo
}) })
}, },
@@ -201,7 +199,11 @@ module.exports = {
// 取消操作 // 取消操作
handleCancel() { handleCancel() {
this.$emit("cancel") const func = () => window.close()
this.$emit("cancel", func)
if (!this.$listeners.cancel) {
func()
}
}, },
// 刷新任务信息 // 刷新任务信息
@@ -71,7 +71,7 @@
<script src="${base!}/assets/platform/plugins/form-create/form-create.min.js"></script> <script src="${base!}/assets/platform/plugins/form-create/form-create.min.js"></script>
<script src="${base!}/assets/platform/plugins/form-create/index.umd.js"></script> <script src="${base!}/assets/platform/plugins/form-create/index.umd.js"></script>
<!-- <script src="${base!}/assets/platform/plugins/snaker/SnakerflowDesigner.umd.min.js"></script>--> <!-- <script src="${base!}/assets/platform/plugins/snaker/SnakerflowDesigner.umd.min.js"></script>-->
<script src="${base!}/components/plugins/sysDict/DictData.js"></script> <script src="${base!}/components/plugins/sysDict/DictData.js"></script>
<script src="${base!}/assets/platform/js/util/commonUtil.js"></script> <script src="${base!}/assets/platform/js/util/commonUtil.js"></script>
@@ -101,6 +101,11 @@
Vue.use(FcDesigner.formCreate) Vue.use(FcDesigner.formCreate)
</script> </script>
<!--广播频道-->
<script type="text/javascript">
window.GlobalBroadcastChannel = new BroadcastChannel("zhgh-global-channel")
</script>
<!--ws--> <!--ws-->
<script type="text/javascript"> <script type="text/javascript">
class WebSocketPubSub { class WebSocketPubSub {
@@ -334,12 +339,17 @@
Vue.component("excel-import", httpVueLoader("/components/plugins/sysImport/excelImport.vue?v=" + new Date().getTime())) Vue.component("excel-import", httpVueLoader("/components/plugins/sysImport/excelImport.vue?v=" + new Date().getTime()))
Vue.component("flow-form-button", httpVueLoader("/components/plugins/flowable/formButton.vue?v=" + new Date().getTime())) Vue.component("flow-form-button", httpVueLoader("/components/plugins/flowable/formButton.vue?v=" + new Date().getTime()))
Vue.component("snaker-flow", httpVueLoader("/components/plugins/snaker/snakerFlow.vue?v=" + new Date().getTime())) Vue.component("snaker-flow", httpVueLoader("/components/plugins/snaker/snakerFlow.vue?v=" + new Date().getTime()))
Vue.component("snaker-flow-task-form-action", httpVueLoader("/components/plugins/snaker/snakerFlowTaskFormAction.vue?v=" + new Date().getTime())) Vue.component(
Vue.component("snaker-flow-history-approval", httpVueLoader("/components/plugins/snaker/snakerFlowHisApproval.vue?v=" + new Date().getTime())) "snaker-flow-task-form-action",
httpVueLoader("/components/plugins/snaker/snakerFlowTaskFormAction.vue?v=" + new Date().getTime())
)
Vue.component(
"snaker-flow-history-approval",
httpVueLoader("/components/plugins/snaker/snakerFlowHisApproval.vue?v=" + new Date().getTime())
)
</script> </script>
<style> <style>
.v4-header { .v4-header {
background-color: rgb(0, 109, 185); background-color: rgb(0, 109, 185);
box-shadow: 0 2px 10px rgba(0, 109, 185, 0.3); box-shadow: 0 2px 10px rgba(0, 109, 185, 0.3);
@@ -555,6 +565,10 @@
<i class="fa fa-th-large"></i> <i class="fa fa-th-large"></i>
应用中心 应用中心
</a> </a>
<a href="/platform/v4/serv" data-pjax class="v4-nav-item">
<i class="fa fa-th-large"></i>
服务中心
</a>
<a href="/flow/todoCenter" data-pjax class="v4-nav-item"> <a href="/flow/todoCenter" data-pjax class="v4-nav-item">
<i class="fa fa-th-large"></i> <i class="fa fa-th-large"></i>
待办中心 待办中心
@@ -0,0 +1,597 @@
<!--#
layout("/layouts/v4/baseLayout.html"){
#-->
<style>
.apps-hero {
position: relative;
overflow: hidden;
display: flex;
align-items: center;
/*border-radius: 8px 8px 0 0;*/
margin-bottom: 0;
}
.apps-hero img {
width: 100%;
height: 100%;
}
.apps-container {
display: flex;
/*height: calc(100vh - 64px - 48px - 180px);*/
background-color: #fff;
/*border-radius: 0 0 8px 8px;*/
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
overflow: hidden;
flex: 1;
}
.apps-sidebar {
width: 220px;
background-color: #f7f7f7;
border-right: 1px solid #e8e8e8;
overflow-y: auto;
}
.apps-sidebar-item {
padding: 16px 20px;
cursor: pointer;
transition: all 0.3s;
font-size: 16px;
display: flex;
align-items: center;
gap: 10px;
}
.apps-sidebar-item.active {
background-color: #e6f7ff;
color: var(--color-primary);
border-right: 2px solid #1890ff;
}
.apps-sidebar-item:hover:not(.active) {
background-color: #f0f0f0;
}
.apps-content {
flex: 1;
padding: 20px;
overflow: auto;
display: flex;
flex-direction: column;
}
.apps-header {
padding: 0 0 20px 0;
border-bottom: 1px solid #eee;
margin-bottom: 20px;
}
.apps-search-container {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.apps-search {
position: relative;
width: 400px;
}
.apps-search input {
width: 100%;
height: 36px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 15px;
font-size: 14px;
box-sizing: border-box;
}
.apps-search .search-icon {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
color: #999;
cursor: pointer;
}
.apps-filter {
margin-top: 20px;
}
.apps-filter-title {
color: #666;
font-size: 14px;
margin-bottom: 5px;
}
.apps-filter-tags {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.filter-tag {
padding: 5px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.filter-tag.active {
background-color: var(--color-primary);
color: #fff;
}
.filter-tag:not(.active) {
background-color: #f0f0f0;
color: #333;
}
.filter-tag:not(.active):hover {
background-color: #e0e0e0;
}
.apps-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
flex: 1;
overflow-y: auto;
grid-auto-rows: min-content;
padding-top: 5px;
}
.app-card {
border: 1px solid #e8e8e8;
border-radius: 8px;
overflow: hidden;
transition: all 0.3s;
display: flex;
align-items: center;
padding: 15px;
position: relative;
}
.app-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
cursor: pointer;
}
.app-icon {
width: 48px;
height: 48px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
color: #fff;
font-size: 24px;
}
.app-icon img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 8px;
}
.app-icon i {
color: var(--color-primary);
font-size: 38px;
}
.favorite-icon {
position: absolute;
bottom: 10px;
right: 10px;
cursor: pointer;
transition: all 0.3s;
font-size: 18px;
color: #c0c4cc;
}
.favorite-icon.active {
color: #ff9800;
}
.favorite-icon:hover {
transform: scale(1.2);
}
.app-info {
flex: 1;
}
.app-title {
font-weight: 500;
margin-bottom: 5px;
font-size: 14px;
}
.app-desc {
color: #999;
font-size: 12px;
}
.alphabet-filter {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.alphabet-item {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
font-size: 14px;
}
.alphabet-item.active {
background-color: var(--color-primary);
color: #fff;
}
.alphabet-item:not(.active) {
background-color: #f0f0f0;
}
.alphabet-item:hover:not(.active) {
background-color: #e0e0e0;
}
.app-status {
position: absolute;
top: 0;
right: 0;
background-color: #ff9800;
color: #fff;
padding: 2px 10px;
font-size: 12px;
border-bottom-left-radius: 8px;
}
.apps-wrapper {
/*margin: 0 auto;*/
/*margin: 24px;*/
/*padding: 24px;*/
display: flex;
flex-direction: column;
height: calc(100vh - 64px);
}
.no-results {
display: flex;
justify-content: center;
align-items: center;
min-height: 300px;
padding: 40px 20px;
}
.no-results-content {
text-align: center;
max-width: 300px;
}
.no-results i {
color: #c0c4cc;
margin-bottom: 16px;
}
.no-results .title {
font-size: 16px;
color: #606266;
font-weight: 500;
margin-bottom: 8px;
}
.no-results .hint {
font-size: 14px;
color: #909399;
line-height: 1.5;
}
.page-item.disabled {
color: #c0c4cc;
cursor: not-allowed;
}
.page-item.disabled:hover {
border-color: #d9d9d9;
color: #c0c4cc;
}
.loading-container {
text-align: center;
padding: 40px 0;
color: #909399;
font-size: 14px;
}
</style>
<div class="apps-wrapper" id="app">
<div class="apps-hero">
<img src="https://i.cug.edu.cn/data/sys-attach/download/1i8hmpaqdwmvwajjvw2c8isi61jj0gkmosw0" alt="应用中心" />
</div>
<div class="apps-container">
<!-- Left Sidebar -->
<div class="apps-sidebar">
<div
v-for="category in allCategories"
:key="category.id"
:class="['apps-sidebar-item', activeCategory === category.id ? 'active' : '']"
@click="changeCategory(category.id)"
>
<i :class="category.icon"></i>
{{ category.name }}
</div>
</div>
<!-- Main Content -->
<div class="apps-content">
<div class="apps-header">
<div class="apps-search-container">
<div class="apps-search">
<input type="text" v-model="searchKeyword" @keyup.enter="search" placeholder="请输入内容" />
<i class="el-icon-search search-icon" @click="search"></i>
</div>
</div>
<!-- 字母过滤 -->
<div class="apps-filter">
<div class="apps-filter-title">首字母:</div>
<div class="apps-filter-tags alphabet-filter">
<div :class="['filter-tag', activeAlphabet === '' ? 'active' : '']" @click="changeAlphabet('')">全部</div>
<div
v-for="letter in alphabets"
:key="letter"
:class="['alphabet-item', activeAlphabet === letter ? 'active' : '']"
@click="changeAlphabet(letter)"
>
{{ letter }}
</div>
</div>
</div>
</div>
<!-- 应用网格 -->
<div class="apps-grid" v-if="applications.length > 0">
<div v-for="app in applications" :key="app.id" class="app-card" @click.stop="openApp(app)">
<div :class="['app-icon', app.iconType]">
<i v-if="app.icon" :class="app.icon"></i>
<i v-else class="fa fa-skype"></i>
</div>
<div class="app-info">
<div class="app-title">{{ app.displayName }}</div>
</div>
</div>
</div>
<!-- 加载状态 -->
<div class="loading-container" v-if="loading">
<i class="el-icon-loading"></i>
<p>加载中...</p>
</div>
<!-- 无搜索结果 -->
<div class="no-results" v-else-if="applications.length === 0">
<div class="no-results-content">
<i class="el-icon-document" style="font-size: 48px"></i>
<p class="title">没有找到匹配的服务</p>
<p class="hint">请尝试不同的搜索词或筛选条件</p>
</div>
</div>
</div>
</div>
</div>
<script>
new Vue({
el: "#app",
data: {
// 应用列表
applications: [],
// 分类列表
categories: [{ id: "all", name: "全部服务", icon: "fa fa-th-large" }],
// 动态加载的分类
dynamicCategories: [],
// 当前选中的分类
activeCategory: "all",
// 字母表筛选
alphabets: [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z"
],
activeAlphabet: "",
// 搜索关键词
searchKeyword: "",
// 分页相关
currentPage: 1,
pageSize: 15,
totalPage: 1,
total: 0,
// 加载状态
loading: true
},
computed: {
// 所有分类(固定分类 + 动态分类)
allCategories() {
return [...this.categories, ...this.dynamicCategories]
}
},
mounted() {
// 页面加载时获取分类和应用数据
this.loadCategories()
this.loadApps()
},
methods: {
// 加载分类数据
loadCategories() {
$.get("/platform/v4/serv/categories")
.then((result) => {
if (result.code === 0) {
this.dynamicCategories = result.data || []
} else {
console.error("获取应用分类失败:", result.msg)
}
})
.fail((error) => {
console.error("获取应用分类异常:", error)
})
},
// 加载应用数据
loadApps() {
this.loading = true
// 构建请求参数
const params = {
categoryId:
this.activeCategory === "all"
? ""
: this.activeCategory === "favorites" || this.activeCategory === "recommended"
? this.activeCategory
: this.activeCategory,
letter: this.activeAlphabet,
keyword: this.searchKeyword
}
// 将参数转换为URL查询参数
const queryString = Object.keys(params)
.filter((key) => params[key] !== null && params[key] !== undefined && params[key] !== "")
.map((key) => encodeURIComponent(key) + "=" + encodeURIComponent(params[key]))
.join("&")
// 如果是"我的收藏"分类
if (this.activeCategory === "favorites") {
this.loadFavorites()
return
}
// 发送请求获取普通应用列表
$.get("/platform/v4/serv/list?" + queryString)
.then((result) => {
if (result.code === 0) {
this.applications = result.data || []
} else {
console.error("获取应用列表失败:", result.msg)
}
this.loading = false
})
.fail((error) => {
console.error("获取应用列表异常:", error)
this.loading = false
})
},
// 加载收藏的应用
loadFavorites() {
$.get("/platform/v4/serv/favorite")
.then((result) => {
if (result.code === 0) {
this.applications = result.data || []
} else {
console.error("获取收藏应用失败:", result.msg)
}
})
.fail((error) => {
console.error("获取收藏应用异常:", error)
})
},
// 切换应用收藏状态
toggleFavorite(appId) {
const app = this.applications.find((a) => a.id === appId)
if (!app) return
const url = app.isFavorite ? "/platform/v4/serv/removeFavorite" : "/platform/v4/serv/addFavorite"
const params = { appId: appId }
$.post(url, params)
.then((result) => {
if (result.code === 0) {
app.isFavorite = !app.isFavorite
this.$message.success(app.isFavorite ? "收藏成功" : "取消收藏成功")
} else {
console.error(app.isFavorite ? "取消收藏失败:" : "收藏失败:", result.msg)
}
})
.fail((error) => {
console.error(app.isFavorite ? "取消收藏异常:" : "收藏异常:", error)
})
},
// 切换应用分类
changeCategory(categoryId) {
this.activeCategory = categoryId
this.currentPage = 1
// 如果是"我的收藏"分类
if (categoryId === "favorites") {
this.loadFavorites()
} else {
this.loadApps()
}
},
// 切换应用字母
changeAlphabet(letter) {
this.activeAlphabet = letter
this.currentPage = 1
this.loadApps()
},
// 搜索
search() {
this.loadApps()
},
// 打开应用
openApp(app) {
console.log(app)
// 新标签页打开
window.open("/flow/common/approval/form?defineKey=" + app.name, "_blank")
}
}
})
</script>
<!--#
}
#-->
@@ -84,7 +84,8 @@ layout("/layouts/platform.html"){
<el-input v-model="formData.h5InstanceViewUrl" placeholder="请输入手机端发起地址"></el-input> <el-input v-model="formData.h5InstanceViewUrl" placeholder="请输入手机端发起地址"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="图标" prop="icon"> <el-form-item label="图标" prop="icon">
<el-input v-model="formData.icon" placeholder="请输入图标"></el-input> <el-input maxlength="100" placeholder="图标" v-model="formData.icon"></el-input>
<i :class="formData.icon" v-if="formData.icon"></i>
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
@@ -201,11 +202,11 @@ layout("/layouts/platform.html"){
}, },
listCategory() { listCategory() {
// this.$axios.get("/platform/warmFlow/category/list").then((res) => { this.$axios.post("/flow/category/list").then((res) => {
// if (res.code === 0) { if (res.code === 0) {
// this.categoryOptions = res.data this.categoryOptions = res.data
// } }
// }) })
} }
}, },
created() { created() {
@@ -2,172 +2,6 @@
layout("/layouts/v4/baseLayout.html"){ layout("/layouts/v4/baseLayout.html"){
#--> #-->
<div id="app" v-cloak>
<div class="task-todo-center">
<!-- 统计数据区域 -->
<el-row :gutter="20" class="statistics-section">
<el-col :span="8">
<el-card shadow="hover" class="stat-card">
<div class="stat-content">
<div class="stat-icon todo-icon">
<i class="el-icon-s-claim"></i>
</div>
<div class="stat-info">
<div class="stat-value">{{todoCount}}</div>
<div class="stat-label">待办任务</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover" class="stat-card">
<div class="stat-content">
<div class="stat-icon done-icon">
<i class="el-icon-finished"></i>
</div>
<div class="stat-info">
<div class="stat-value">{{doneCount}}</div>
<div class="stat-label">已办任务</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover" class="stat-card">
<div class="stat-content">
<div class="stat-icon started-icon">
<i class="el-icon-s-promotion"></i>
</div>
<div class="stat-info">
<div class="stat-value">{{startedCount}}</div>
<div class="stat-label">我发起的</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 搜索筛选区域 -->
<el-card shadow="never" class="filter-section">
<el-form :inline="true" :model="searchForm" class="search-form">
<el-form-item>
<el-input v-model="searchForm.keyword" placeholder="请输入关键词搜索" prefix-icon="el-icon-search" clearable></el-input>
</el-form-item>
<el-form-item>
<el-select v-model="searchForm.category" placeholder="所有流程类型" clearable>
<el-option v-for="type in processTypes" :key="type.value" :label="type.label" :value="type.value"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="search">搜索</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 任务列表区域 -->
<el-card shadow="never" class="task-section">
<div slot="header" class="task-header">
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane label="待办任务" name="todo"></el-tab-pane>
<el-tab-pane label="已办任务" name="done"></el-tab-pane>
<el-tab-pane label="我发起的" name="started"></el-tab-pane>
</el-tabs>
</div>
<!-- 表格展示 -->
<el-table
v-loading="loading"
:data="tasks"
style="width: 100%"
:key="activeTab"
:header-cell-style="{backgroundColor: '#f5f7fa'}"
:row-class-name="tableRowClassName"
>
<el-table-column prop="processInstanceName" label="流程名称" min-width="180" show-overflow-tooltip>
<template slot-scope="scope">
<span class="task-title">{{scope.row.processInstanceName || scope.row.title}}</span>
</template>
</el-table-column>
<el-table-column prop="taskName" label="任务节点" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="processDefinitionName" label="流程类型" min-width="150" show-overflow-tooltip>
<template slot-scope="scope">{{scope.row.processDefinitionName || scope.row.processType}}</template>
</el-table-column>
<el-table-column prop="initiatorName" label="申请人" min-width="120">
<template slot-scope="{row}">
{{row.variable.initiatorName}}
</template>
</el-table-column>
<el-table-column label="时间" min-width="180">
<template slot-scope="scope">
<div v-if="activeTab === 'todo'">
<i class="el-icon-time"></i>
{{scope.row.createdAt}}
</div>
<div v-else-if="activeTab === 'done'">
<i class="el-icon-check"></i>
{{scope.row.finishTime}}
</div>
<div v-else-if="activeTab === 'started'">
<i class="el-icon-s-promotion"></i>
{{scope.row.createdAt}}
</div>
</template>
</el-table-column>
<el-table-column v-if="activeTab === 'started'" label="状态" width="100">
<template slot-scope="{row}">
{{processStatusMap[row.state]?.text}}
</template>
</el-table-column>
<el-table-column label="操作" width="250" fixed="right">
<template slot-scope="scope">
<el-button v-if="activeTab === 'todo'" type="primary" size="mini" @click="handleTask(scope.row)">处理</el-button>
<el-button type="info" size="mini" @click="viewTaskDetail(scope.row)">查看</el-button>
</template>
</el-table-column>
</el-table>
<!-- 空状态展示 -->
<el-empty v-if="tasks.length === 0" :description="getEmptyText()"></el-empty>
<!-- 分页 -->
<div class="pagination-container" v-if="tasks.length > 0">
<el-pagination
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-size="pageSize"
layout="prev, pager, next, jumper"
:total="total"
></el-pagination>
</div>
</el-card>
<!-- 任务详情对话框 -->
<el-dialog title="任务详情" :visible.sync="dialogVisible" width="600px" :close-on-click-modal="false">
<div v-if="currentTask" class="task-detail">
<el-descriptions :column="1" border>
<el-descriptions-item label="流程名称">{{currentTask.processInstanceName || currentTask.title}}</el-descriptions-item>
<el-descriptions-item label="任务名称">{{currentTask.taskName || '-'}}</el-descriptions-item>
<el-descriptions-item label="流程类型">{{currentTask.processDefinitionName || currentTask.processType}}</el-descriptions-item>
<el-descriptions-item label="申请人">{{currentTask.applyUserName || '-'}}</el-descriptions-item>
<el-descriptions-item label="申请部门">{{currentTask.applyUserUnitId || '-'}}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{currentTask.createTime}}</el-descriptions-item>
<el-descriptions-item v-if="currentTask.description" label="任务描述">
<div class="description-content">{{currentTask.description}}</div>
</el-descriptions-item>
<el-descriptions-item v-if="currentTask.taskMobileFormUrl" label="表单链接">
<el-link type="primary" :href="currentTask.taskMobileFormUrl" target="_blank">点击查看详细表单</el-link>
</el-descriptions-item>
</el-descriptions>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">关闭</el-button>
<el-button v-if="activeTab === 'todo'" type="primary" @click="handleTask(currentTask)">处理任务</el-button>
</span>
</el-dialog>
</div>
</div>
<style> <style>
.task-todo-center { .task-todo-center {
padding: 20px; padding: 20px;
@@ -369,20 +203,138 @@ layout("/layouts/v4/baseLayout.html"){
height: 28px; height: 28px;
border-radius: 4px; border-radius: 4px;
} }
/* 任务详情样式 */
.task-detail .el-descriptions {
margin-bottom: 20px;
}
.description-content {
background-color: #f5f7fa;
padding: 10px;
border-radius: 4px;
white-space: pre-wrap;
}
</style> </style>
<div id="app" v-cloak>
<div class="task-todo-center">
<!-- 统计数据区域 -->
<el-row :gutter="20" class="statistics-section">
<el-col :span="8">
<el-card shadow="hover" class="stat-card">
<div class="stat-content">
<div class="stat-icon todo-icon">
<i class="el-icon-s-claim"></i>
</div>
<div class="stat-info">
<div class="stat-value">{{todoCount}}</div>
<div class="stat-label">待办任务</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover" class="stat-card">
<div class="stat-content">
<div class="stat-icon done-icon">
<i class="el-icon-finished"></i>
</div>
<div class="stat-info">
<div class="stat-value">{{doneCount}}</div>
<div class="stat-label">已办任务</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover" class="stat-card">
<div class="stat-content">
<div class="stat-icon started-icon">
<i class="el-icon-s-promotion"></i>
</div>
<div class="stat-info">
<div class="stat-value">{{startedCount}}</div>
<div class="stat-label">我发起的</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 搜索筛选区域 -->
<el-card shadow="never" class="filter-section">
<el-form :inline="true" :model="searchForm" class="search-form">
<el-form-item>
<el-input v-model="searchForm.keyword" placeholder="请输入关键词搜索" prefix-icon="el-icon-search" clearable></el-input>
</el-form-item>
<el-form-item>
<el-select v-model="searchForm.category" placeholder="所有流程分类" clearable>
<el-option v-for="type in categoryOptions" :key="type.id" :label="type.name" :value="type.id"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="search">搜索</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 任务列表区域 -->
<el-card shadow="never" class="task-section">
<div slot="header" class="task-header">
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane label="待办任务" name="todo"></el-tab-pane>
<el-tab-pane label="已办任务" name="done"></el-tab-pane>
<el-tab-pane label="我发起的" name="started"></el-tab-pane>
</el-tabs>
</div>
<!-- 表格展示 -->
<el-table v-loading="loading" :data="tasks" style="width: 100%" :key="activeTab" :header-cell-style="{backgroundColor: '#f5f7fa'}">
<el-table-column prop="processInstanceName" label="流程名称" min-width="180" show-overflow-tooltip>
<template slot-scope="scope">
<span class="task-title">{{scope.row.variable?.instanceName}}</span>
</template>
</el-table-column>
<el-table-column prop="taskName" label="任务节点" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="categoryName" label="流程分类" min-width="150" show-overflow-tooltip>
<template slot-scope="{row}">{{categoryOptions.find(item => item.id === row.category)?.name}}</template>
</el-table-column>
<el-table-column prop="initiatorName" label="申请人" min-width="120">
<template slot-scope="{row}">{{row.variable.initiatorName}}</template>
</el-table-column>
<!-- <el-table-column label="时间" min-width="180">-->
<!-- <template slot-scope="scope">-->
<!-- <div v-if="activeTab === 'todo'">-->
<!-- <i class="el-icon-time"></i>-->
<!-- {{scope.row.createdAt}}-->
<!-- </div>-->
<!-- <div v-else-if="activeTab === 'done'">-->
<!-- <i class="el-icon-check"></i>-->
<!-- {{scope.row.finishTime}}-->
<!-- </div>-->
<!-- <div v-else-if="activeTab === 'started'">-->
<!-- <i class="el-icon-s-promotion"></i>-->
<!-- {{scope.row.createdAt}}-->
<!-- </div>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column v-if="activeTab === 'started'" label="状态" width="100">
<template slot-scope="{row}">{{processStatusMap[row.state]?.text}}</template>
</el-table-column>
<el-table-column label="操作" width="100px" fixed="right">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="openView(scope.row)">查看</el-button>
</template>
</el-table-column>
</el-table>
<!-- 空状态展示 -->
<el-empty v-if="tasks.length === 0" :description="getEmptyText()"></el-empty>
<!-- 分页 -->
<div class="pagination-container" v-if="tasks.length > 0">
<el-pagination
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-size="pageSize"
layout="prev, pager, next, jumper"
:total="total"
></el-pagination>
</div>
</el-card>
</div>
</div>
<script> <script>
new Vue({ new Vue({
el: "#app", el: "#app",
@@ -400,7 +352,7 @@ layout("/layouts/v4/baseLayout.html"){
}, },
// 流程类型选项 // 流程类型选项
processTypes: [], categoryOptions: [],
// 任务列表 // 任务列表
tasks: [], tasks: [],
@@ -436,9 +388,7 @@ layout("/layouts/v4/baseLayout.html"){
INTERRUPT: 40, INTERRUPT: 40,
PENDING: 50, PENDING: 50,
ABANDON: 99 ABANDON: 99
}, }
} }
}, },
@@ -450,12 +400,18 @@ layout("/layouts/v4/baseLayout.html"){
created() { created() {
this.initData() this.initData()
// 监听
window.GlobalBroadcastChannel.addEventListener("message", (event) => {
if (event.data.type === "task-complete") {
this.initData()
}
})
}, },
methods: { methods: {
// 初始化数据 // 初始化数据
async initData() { async initData() {
await Promise.all([this.getStatistics(), this.getProcessTypes(), this.getTasks()]) await Promise.all([this.getStatistics(), this.listCategory(), this.getTasks()])
}, },
// 获取统计数据 // 获取统计数据
@@ -475,16 +431,12 @@ layout("/layouts/v4/baseLayout.html"){
}, },
// 获取流程类型 // 获取流程类型
async getProcessTypes() { async listCategory() {
try { this.$axios.post("/flow/category/list").then((res) => {
const res = await $.post("/platform/warmFlow/todoCenter/category")
if (res.code === 0) { if (res.code === 0) {
this.processTypes = res.data this.categoryOptions = res.data
} }
} catch (error) { })
this.$message.error("获取流程类型失败")
console.error("获取流程类型失败:", error)
}
}, },
// 获取任务列表 // 获取任务列表
@@ -549,31 +501,12 @@ layout("/layouts/v4/baseLayout.html"){
}, },
// 处理任务 // 处理任务
async handleTask(task) { async openView(task) {
if (!task) return const { taskId, taskState, instanceId, businessNo } = task
if (taskState === this.taskStateEnum.DOING) {
// 如果有表单链接,直接跳转 window.open("/flow/common/approval/form?instanceId=" + instanceId + "&taskId=" + taskId + "&businessId=" + businessNo)
if (task.taskMobileFormUrl) { } else {
window.open(task.taskMobileFormUrl, "_blank") window.open("/flow/common/approval/form?instanceId=" + instanceId + "&businessId=" + businessNo)
return
}
},
// 获取状态对应的类型
getStatusType(task) {
if (!task.status) return "info"
switch (task.status) {
case "审批中":
return "primary"
case "已完成":
return "success"
case "已通过":
return "success"
case "已拒绝":
return "danger"
default:
return "info"
} }
}, },
@@ -589,11 +522,6 @@ layout("/layouts/v4/baseLayout.html"){
default: default:
return "暂无数据" return "暂无数据"
} }
},
// 表格行类名
tableRowClassName({ row, rowIndex }) {
return ""
} }
} }
}) })
@@ -17,7 +17,7 @@
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-form> </el-form>
<snaker-flow-task-form-action @task-action="handleTaskAction" @save-draft="handleSaveDraft" @cancel="handleCancel"></snaker-flow-task-form-action> <snaker-flow-task-form-action @task-action="handleTaskAction" @save-draft="handleSaveDraft"></snaker-flow-task-form-action>
</div> </div>
<script> <script>
@@ -27,6 +27,7 @@
data() { data() {
return { return {
businessId: GetQueryString("businessId"), businessId: GetQueryString("businessId"),
instanceId: GetQueryString("instanceId"),
formData: {}, formData: {},
formRules: { formRules: {
title: [{ required: true, message: "必填", trigger: ["change", "blur"] }], title: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
@@ -39,20 +40,54 @@
console.log(val) console.log(val)
this.$refs.formRef.validate((valid) => { this.$refs.formRef.validate((valid) => {
if (valid) { if (valid) {
this.$axios if (!this.instanceId) {
.post("/flow/common/startInstanceAndExecute", { this.handleApply(val)
...val, } else {
bizData: JSON.stringify(this.formData) this.handleReApply(val)
}) }
.then((res) => {
if (res.code === 0) {
this.$message.success("操作成功")
window.parent.postMessage({ type: "task-complete" }, "*")
}
})
} }
}) })
}, },
// 提交申请
handleApply(val) {
this.$axios
.post("/flow/common/startInstanceAndExecute", {
...val,
bizData: JSON.stringify(this.formData)
})
.then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
window.GlobalBroadcastChannel.postMessage({
type: "task-complete"
})
}
})
},
// 重新提交申请
handleReApply(val) {
this.$axios
.post("/flow/common/executeTask", {
data: JSON.stringify({
processTaskId: val.taskId,
processInstanceId: val.instanceId,
submitType: val.submitType,
f_data: JSON.stringify(this.formData)
})
})
.then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
window.GlobalBroadcastChannel.postMessage({
type: "task-complete"
})
}
})
},
// 保存申请
handleSaveDraft(val) { handleSaveDraft(val) {
this.$refs.formRef.validateField(["title"], (errMsg) => { this.$refs.formRef.validateField(["title"], (errMsg) => {
if (errMsg) { if (errMsg) {
@@ -78,12 +113,15 @@
}) })
}) })
}, },
handleCancel() {
// 关闭浏览器窗口 // 初始化
window.close()
},
init() { init() {
if (this.businessId) { if (this.businessId) {
this.$axios.post("/platform/suggestionBox/view/info", { id: this.businessId }).then((res) => {
if (res.code === 0) {
this.formData = res.data
}
})
} else { } else {
const { username, loginname, id, unit, union, mobile } = this.$store.state.user const { username, loginname, id, unit, union, mobile } = this.$store.state.user
this.formData = { this.formData = {
@@ -33,12 +33,12 @@ layout("/layouts/platform.html"){
</el-table-column> </el-table-column>
<el-table-column label="操作" fixed="right" width="300px"> <el-table-column label="操作" fixed="right" width="300px">
<template slot-scope="{row}"> <template slot-scope="{row}">
<el-button @click="onOpen(row.id)" size="mini" type="primary">查看</el-button> <el-button @click="openView(row)" size="mini" type="primary">查看</el-button>
<el-button v-if="row.taskKey === 'apply' || !row.instanceId" @click="onEdit(row)" size="mini" type="primary">编辑</el-button> <el-button v-if="row.taskKey === 'startTask'" @click="onEdit(row)" size="mini" type="primary">编辑</el-button>
<el-button v-if="['waiting'].includes(row.flow_status)" size="mini" type="danger" @click="onRevoke(row.id)"></el-button> <el-button v-if="row.canRevoke" @click="onRevoke(row)" size="mini" type="danger"></el-button>
<el-button v-if="row.taskKey === 'apply' || !row.instanceId" @click="onDelete(row.id)" size="mini" type="danger"> <!-- <el-button v-if="row.instanceState === 10" size="mini" type="danger" @click="onWithDraw(row)">撤销</el-button>-->
删除 <!-- v-if="row.instanceState === 30"-->
</el-button> <el-button @click="onDelete(row.id)" size="mini" type="danger">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -58,7 +58,9 @@ layout("/layouts/platform.html"){
} }
}, },
methods: { methods: {
onOpen(id) {}, openView(row) {
window.open("/flow/common/approval/form?" + "instanceId=" + (row.instanceId || "") + "&businessId=" + row.id + "&defineKey=XWTG")
},
onEdit(row) { onEdit(row) {
window.open( window.open(
"/flow/common/approval/form?taskId=" + "/flow/common/approval/form?taskId=" +
@@ -70,8 +72,50 @@ layout("/layouts/platform.html"){
"&defineKey=XWTG" "&defineKey=XWTG"
) )
}, },
onRevoke(id) {},
onDelete(id) {} 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()
}
})
})
},
onWithDraw({ instanceId }) {
this.$confirm("您确定要撤销申请吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/flow/common/withdrawInstance", { instanceId }).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
}
})
})
},
onDelete(id) {
this.$confirm("您确定要删除吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/platform/suggestionBox2/apply/delete", { id }).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
}
})
})
}
}, },
created() { created() {
this.pageData() this.pageData()
@@ -5,7 +5,7 @@
<el-descriptions-item label="工号">{{viewData.loginName}}</el-descriptions-item> <el-descriptions-item label="工号">{{viewData.loginName}}</el-descriptions-item>
<el-descriptions-item label="单位">{{viewData.unitName}}</el-descriptions-item> <el-descriptions-item label="单位">{{viewData.unitName}}</el-descriptions-item>
<el-descriptions-item label="工会">{{viewData.unionName}}</el-descriptions-item> <el-descriptions-item label="工会">{{viewData.unionName}}</el-descriptions-item>
<el-descriptions-item label="标题" :span="2">{{viewData.viewData}}</el-descriptions-item> <el-descriptions-item label="标题" :span="2">{{viewData.title}}</el-descriptions-item>
<el-descriptions-item label="填写意见建议内容" :span="2">{{viewData.content}}</el-descriptions-item> <el-descriptions-item label="填写意见建议内容" :span="2">{{viewData.content}}</el-descriptions-item>
</el-descriptions> </el-descriptions>
@@ -21,6 +21,7 @@
methods: { methods: {
handleTaskAction(val) { handleTaskAction(val) {
console.log(val) console.log(val)
this.$axios this.$axios
.post("/flow/common/executeTask", { .post("/flow/common/executeTask", {
data: JSON.stringify({ data: JSON.stringify({
@@ -32,12 +33,10 @@
if (res.code === 0) { if (res.code === 0) {
this.$message.success("操作成功") this.$message.success("操作成功")
// 发送完成消息 // 发送完成消息
window.parent.postMessage( window.GlobalBroadcastChannel.postMessage({
{ type: "task-complete",
type: "task-complete" payload: val
}, })
"*"
)
} }
}) })
}, },
@@ -30,16 +30,17 @@ layout("/layouts/platform.html"){
<el-table-column prop="unitName" label="投稿人单位"></el-table-column> <el-table-column prop="unitName" label="投稿人单位"></el-table-column>
<el-table-column prop="unionName" label="投稿人工会"></el-table-column> <el-table-column prop="unionName" label="投稿人工会"></el-table-column>
<el-table-column prop="submitTime" label="投稿时间"></el-table-column> <el-table-column prop="submitTime" label="投稿时间"></el-table-column>
<el-table-column prop="taskName" label="当前节点"></el-table-column> <el-table-column prop="curTaskName" label="当前节点"></el-table-column>
<el-table-column prop="instanceState" label="流程状态"> <el-table-column prop="instanceState" label="流程状态">
<template slot-scope="{row}"> <template slot-scope="{row}">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag> <enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" fixed="right" width="300px"> <el-table-column label="操作" fixed="right" width="200px">
<template slot-scope="{row}"> <template slot-scope="{row}">
<el-button @click="onOpen(row.id)" size="mini" type="primary">查看</el-button> <el-button @click="openView(row)" size="mini" type="primary">查看</el-button>
<el-button v-if="row.taskState === 10" @click="openApproval(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.taskState === 10" @click="openView(row)" size="mini" type="primary">审核</el-button>-->
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -62,9 +63,11 @@ layout("/layouts/platform.html"){
} }
}, },
methods: { methods: {
onOpen() {}, openView(row) {
openApproval(row) {
window.open("/flow/common/approval/form?taskId=" + row.taskId + "&instanceId=" + row.instanceId + "&businessId=" + row.businessNo) window.open("/flow/common/approval/form?taskId=" + row.taskId + "&instanceId=" + row.instanceId + "&businessId=" + row.businessNo)
},
onRevoke(row) {
console.log(row)
} }
}, },
created() { created() {