一个完整的流程表单流转

1.写在前面

一个完整的流程表单审批(起表单-->各环节审批-->回退-->重新审批-->完成),前端由Vue2+js+Element UI升级为Vue3+ts+Element Plus,后端流程框架使用Flowable,项目参考了ruoyi-vue-pro(https://gitee.com/zhijiantianya/ruoyi-vue-pro)项目。

2.视频演示

OA系统自定义表单审批流转流程演示

3.表单

3.1表单的设计

依据业务需求,完全自定义表单,可以依据流程节点设置表单中每个属性的读写,实现原理是读取审批节点的编码,依据编码控制每个属性的读写。

<el-tree-select:disabled="!required"v-model="form.deptCode":data="deptList":props="defaultPropsForData"check-strictlynode-key="id"placeholder="请选择集团成员单位"/>/*** 根据不同参数控制表单属性的可读可写* @param pattern*/
const changeRequiredByPattern = (pattern: string) => {if (pattern === 'create') {//说明增加required.value = true} else if (pattern === 'update' || pattern === 'starter') {//修改required.value = truegetBid()} else {//只读required.value = falsegetBid()}
}

3.2.表单与流程关联

通过设计流程时定义的流程编码,在创建表单的后端服务调用中,实现表单与流程的绑定。

/*** 投标对应的流程定义 KEY*/public static final String PROCESS_KEY = "bidApproval";//发起BPM流程Map<String, Object> processInstanceVariables = new HashMap<>();processInstanceVariables.put("deptId", bidDO.getDeptId());processInstanceVariables.put("bidMoney", bidDO.getBidMoney());String processInstanceId = processInstanceApi.createProcessInstance(getLoginUserId(),new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(PROCESS_KEY).setVariables(processInstanceVariables).setBusinessKey(bidDO.getProjectName()));

3.3.表单与前端流程实例的关联

在通过待办打开需要审批的表单时,不同的流程实例如何对应不同的表单,并在页面展示具体的表单数据了?答案是通过定义流程时填写的表单组件名称,利用Vue的<component>元组件来实现。

<componentref="formDetailRef"v-if="processInstance.id !== undefined":is="processInstance.processDefinition.formComponentName":processInstanceId="processInstance.id":pattern="runningTasks.length > 0 ? runningTasks[0].definitionKey : 'readOnly'"@success="getDetail"/>

3.4.表单的保存

表单的保存分为提交时的保存与不提交的保存。不提交的保存用于修改数据但流程不需要提交到下一节点审批的情况,方便保存数据进入待办里面进行后续的修改。提交的时,流程会自动调用保存接口,先进行业务数据的保存,然后再进行流程的提交。

<el-button color="#626aef" @click="handleSave"><Icon icon="ep:coin" />保存</el-button><el-button type="success" @click="handleApproval(item)"><Icon icon="ep:select" />提交</el-button>/** 处理保存表单的操作 只更新表单数据 不提交流程任务 */
const formDetailRef = ref()
const handleSave = () => {formDetailRef.value.submitForm()
}/** 处理审批通过的操作 */
const approvalRef = ref()
const handleApproval = async (item) => {approvalRef.value.open(item)
}
@Override@Transactional(rollbackFor = Exception.class)public Long updateBid(BidUpdateReqVO updateReqVO) {BidDO bidDO = BidConvert.INSTANCE.convert3(updateReqVO);//checkDeptIsMateTendererDept(deptRespDTO, tendererDeptRespDTO);bidDO.setDeptId(findDeptByCode(updateReqVO.getDeptCode()).getId());bidDO.setTendererId(findDeptByName(updateReqVO.getTendererName()).getId());fileApi.deleteFile(bidMapper.selectById(bidDO.getId()).getFiles(), bidDO.getFiles());bidMapper.updateById(bidDO);return bidDO.getId();}@Override@Transactional(rollbackFor = Exception.class)public void approveProcessTask(BidUpdateReqVO bidUpdateReqVO, BpmProcessTaskApprovalDTO bpmProcessTaskApprovalDTO) {//需要根据流程中不同的节点 更改对应的表单信息 比如 需要在流程最后一个节点点击提交时 更改流程状态为完成BidDO bidDO = BidConvert.INSTANCE.convert3(bidUpdateReqVO);bidDO.setDeptId(findDeptByCode(bidUpdateReqVO.getDeptCode()).getId());bidDO.setTendererId(findDeptByName(bidUpdateReqVO.getTendererName()).getId());updateFlowInfoByProcessInstanceState(bidDO);fileApi.deleteFile(bidMapper.selectById(bidDO.getId()).getFiles(), bidDO.getFiles());bidMapper.updateById(bidDO);bpmProcessTaskApi.approvalTask(bpmProcessTaskApprovalDTO);}

4.流程的审批

流程的审批按照设计流程的审批节点依次进行流转,不支持夸环节提交,支持流程的自由回退。提交当前审批任务时,进行下一节点的人员选择,在流程设计时,每个节点审批的人员的选择逻辑已经确定,也支持自由选择组织中的所有人员。

4.1.流程审批人员的设置

流程审批节点的人员设置主要思路为给定一个角色,让审批人员提交任务时,从角色中选择一个人员,这样可以缩小选择的范围。如果表单有对应的部门属性,可以设置审批人员是某个角色中且部门与表单部门属性相同的人员。

// 选择角色中的人员
private Set<Long> calculateTaskCandidateUsersByRole(BpmTaskAssignRuleDO rule) {return permissionApi.getUserRoleIdListByRoleIds(rule.getOptions());}//依据角色选择表单部门中的所属人员
private Set<Long> calculateTaskCandidateUsersByRolePerson(Map<String, Object> variables, BpmTaskAssignRuleDO rule) {Long deptId = (Long) variables.get("deptId");//获取流程实例变量的部门//从角色中获取属于该部门的人员Set<Long> userIdsByRoleId = permissionApi.getUserRoleIdListByRoleIds(rule.getOptions());Set<Long> userIdsByDeptId = userApi.getUsersByDeptId(deptId);return new HashSet<>(CollUtil.intersection(userIdsByDeptId, userIdsByRoleId));}

4.2.流程的流转

流程的流转其实没有什么好说的,就是按照流程设计的审批节点依次往下走,遇到网关时,根据前期设计好的条件读取对应的属性跳转到不同审批支线。本例中,会根据投标金额是否大于500万做判断,走不同的分支,而500万的属性,在创建流程时就已经传入了流程实例的变量中。

流程的回退,流程的回退依据流程的节点图,不管流程流转了多少圈,回退只允许回退当前审批节点的前面节点。

public Set<BpmDoneUserTaskNodeRespVO> getDoneUserTaskNodes(String taskId) {Set<BpmDoneUserTaskNodeRespVO> resultList = new HashSet<>();//获取流程实例idTask task = getTask(taskId);// 校验流程实例存在ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId());//获取历史任务实例 条件为 流程实例 未完成 按照任务开始时间降序排列List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery().processInstanceId(instance.getProcessInstanceId()).finished().orderByHistoricTaskInstanceEndTime().desc().list();//需要做一个筛选,只能选择当前任务节点之前的节点进行回退// 1. 获取流程模型实例 BpmnModelBpmnModel bpmnModel = bpmProcessDefinitionService.getBpmnModel(task.getProcessDefinitionId());// 2. 通过任务节点id,来获取当前节点信息FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());// 3.获取下一个节点(或者多个节点的)信息,需要去重,因为并行的节点之前的节点会找多遍Set<FlowElement> flowElements = new HashSet<>();// 4.获取流程实例的变量Map<String, Object> variables = taskService.getVariables(taskId);getBeforeNodes(flowElement, flowElements, variables);if (flowElements.isEmpty()){//说明处于第一个节点,此时不能回退throw exception(TASK_ROLLBACK_FORBIDDEN);}//找交集for(FlowElement f: flowElements) {HistoricTaskInstance h = historicTaskInstances.stream().filter( hi -> hi.getTaskDefinitionKey().equals(f.getId())).findFirst().orElse(null);if (h == null){//没有获取到最新的的节点审批信息,是不正常的情况throw exception(TASK_ROLLBACK_APPROVED_INFO_NULL);}//获取 审批人员的编号Long assignee = Long.valueOf(h.getAssignee());//获取人员信息UserRespDTO userRespDTO = adminUserApi.getUser(assignee);//获取部门信息DeptRespDTO deptRespDTO = deptApi.getDept(userRespDTO.getDeptId());resultList.add(BpmTaskConvert.INSTANCE.convertBpmDoneUserTaskNodeRespVO(h, userRespDTO, deptRespDTO));}return resultList;}

5.流程审批详情

流程审批详情包括审批记录与流程图的展示。

5.1.审批记录详情

审批记录根据审批的先后顺序展示数据,状态根据提交回退的不同使用不同颜色的标签显示。

<el-table v-loading="loading" :data="tasks" border><el-table-column align="center" prop="name" label="审批环节" width="200" /><el-table-column align="center" prop="assigneeUser.nickname" label="审批人" width="180" /><el-table-columnlabel="任务开始时间"align="center"prop="createTime"width="180":formatter="dateFormatter"/><el-table-columnlabel="任务结束时间"align="center"prop="endTime"width="180":formatter="dateFormatter"/><el-table-column label="任务耗时" align="center" prop="durationInMillis" width="150"><template #default="scope"><span>{{ formatPast2(scope.row.durationInMillis) }}</span></template></el-table-column><el-table-column label="审批结果" align="center" prop="result" width="150"><template #default="scope"><el-tag :type="getTimelineItemType(scope.row)">{{ getDictLabel(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT, scope.row.result) }}</el-tag></template></el-table-column><el-table-column label="审批意见" align="center" prop="reason" width="250" /></el-table>
public List<BpmTaskRespVO> getTaskListByProcessInstanceId(String processInstanceId) {// 获得任务列表List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).orderByHistoricTaskInstanceStartTime().desc() // 创建时间倒序.list();if (CollUtil.isEmpty(tasks)) {return Collections.emptyList();}// 获得 TaskExtDO MapList<BpmTaskExtDO> bpmTaskExtDOs = taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId));Map<String, BpmTaskExtDO> bpmTaskExtDOMap = convertMap(bpmTaskExtDOs, BpmTaskExtDO::getTaskId);// 获得 ProcessInstance MapHistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(processInstanceId);// 获得 User MapSet<Long> userIds = convertSet(tasks, task -> NumberUtils.parseLong(task.getAssignee()));userIds.add(NumberUtils.parseLong(processInstance.getStartUserId()));Map<Long, UserRespDTO> userMap = adminUserApi.getUserMap(userIds);// 获得 Dept MapMap<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), UserRespDTO::getDeptId));// 拼接数据return BpmTaskConvert.INSTANCE.convertList3(tasks, bpmTaskExtDOMap, processInstance, userMap, deptMap);}

5.2.流程图显示

流程图显示比较复杂,可以查看对应的代码,主要就是使用了bpmn-js库,根据后端的数据,进行不同的展示,核心代码个人理解是这一块。

const highlightDiagram = async () => {const activityList = activityLists.valueif (activityList.length === 0) {return}// 参考自 https://gitee.com/tony2y/RuoYi-flowable/blob/master/ruoyi-ui/src/components/Process/index.vue#L222 实现// 再次基础上,增加不同审批结果的颜色等等let canvas = bpmnModeler.get('canvas')let todoActivity: any = activityList.find((m: any) => !m.endTime) // 找到待办的任务let endActivity: any = activityList[activityList.length - 1] // 获得最后一个任务// debuggerbpmnModeler.getDefinitions().rootElements[0].flowElements?.forEach((n: any) => {let activity: any = activityList.find((m: any) => m.key === n.id) // 找到对应的活动if (!activity) {return}if (n.$type === 'bpmn:UserTask') {// 用户任务// 处理用户任务的高亮const task: any = taskList.value.find((m: any) => m.id === activity.taskId) // 找到活动对应的 taskIdif (!task) {return}// 高亮任务canvas.addMarker(n.id, getResultCss(task.result))// 如果非通过,就不走后面的线条了if (task.result !== 2) {return}// 处理 outgoing 出线const outgoing = getActivityOutgoing(activity)outgoing?.forEach((nn: any) => {// debuggerlet targetActivity: any = activityList.find((m: any) => m.key === nn.targetRef.id)// 如果目标活动存在,则根据该活动是否结束,进行【bpmn:SequenceFlow】连线的高亮设置if (targetActivity) {canvas.addMarker(nn.id, targetActivity.endTime ? 'highlight' : 'highlight-todo')} else if (nn.targetRef.$type === 'bpmn:ExclusiveGateway') {// TODO 芋艿:这个流程,暂时没走到过canvas.addMarker(nn.id, activity.endTime ? 'highlight' : 'highlight-todo')canvas.addMarker(nn.targetRef.id, activity.endTime ? 'highlight' : 'highlight-todo')} else if (nn.targetRef.$type === 'bpmn:EndEvent') {// TODO 芋艿:这个流程,暂时没走到过if (!todoActivity && endActivity.key === n.id) {canvas.addMarker(nn.id, 'highlight')canvas.addMarker(nn.targetRef.id, 'highlight')}if (!activity.endTime) {canvas.addMarker(nn.id, 'highlight-todo')canvas.addMarker(nn.targetRef.id, 'highlight-todo')}}})} else if (n.$type === 'bpmn:ExclusiveGateway') {// 排它网关// 设置【bpmn:ExclusiveGateway】排它网关的高亮canvas.addMarker(n.id, getActivityHighlightCss(activity))// 查找需要高亮的连线let matchNN: any = undefinedlet matchActivity: any = undefinedconst outgoing = getActivityOutgoing(activity)outgoing.forEach((nn: any) => {let targetActivity = activityList.find((m: any) => m.key === nn.id)if (!targetActivity) {return}// 特殊判断 endEvent 类型的原因,ExclusiveGateway 可能后续连有 2 个路径://  1. 一个是 UserTask => EndEvent//  2. 一个是 EndEvent// 在选择路径 1 时,其实 EndEvent 可能也存在,导致 1 和 2 都高亮,显然是不正确的。// 所以,在 matchActivity 为 EndEvent 时,需要进行覆盖~~if (!matchActivity || matchActivity.type === 'endEvent') {matchNN = nnmatchActivity = targetActivity}})if (matchNN && matchActivity) {canvas.addMarker(matchNN.id, getActivityHighlightCss(matchActivity))}} else if (n.$type === 'bpmn:ParallelGateway') {// 并行网关// 设置【bpmn:ParallelGateway】并行网关的高亮canvas.addMarker(n.id, getActivityHighlightCss(activity))const outgoing = getActivityOutgoing(activity)outgoing.forEach((nn: any) => {// 获得连线是否有指向目标。如果有,则进行高亮const targetActivity = activityList.find((m: any) => m.key === nn.targetRef.id)if (targetActivity) {canvas.addMarker(nn.id, getActivityHighlightCss(targetActivity)) // 高亮【bpmn:SequenceFlow】连线// 高亮【...】目标。其中 ... 可以是 bpm:UserTask、也可以是其它的。当然,如果是 bpm:UserTask 的话,其实不做高亮也没问题,因为上面有逻辑做了这块。canvas.addMarker(nn.targetRef.id, getActivityHighlightCss(targetActivity))}})} else if (n.$type === 'bpmn:StartEvent') {// 开始节点 流程只要发起 开始节点就是完成状态let targetActivity = activityList.find((m) => m.key === n.id)if (targetActivity) {canvas.addMarker(n.id, 'highlight') // 高亮【bpmn:StartEvent】开始节点(自己)}// 开始节点const outgoing = getActivityOutgoing(activity)outgoing.forEach((nn) => {// outgoing 例如说【bpmn:SequenceFlow】连线// 获得连线是否有指向目标。如果有,则进行高亮let targetActivity = activityList.find((m: any) => m.key === nn.targetRef.id)if (targetActivity) {canvas.addMarker(nn.id, 'highlight') // 高亮【bpmn:SequenceFlow】连线canvas.addMarker(n.id, 'highlight') // 高亮【bpmn:StartEvent】开始节点(自己)}})} else if (n.$type === 'bpmn:EndEvent') {// 结束节点if (!processInstance.value || processInstance.value.result === 1) {return}canvas.addMarker(n.id, getResultCss(processInstance.value.result))} else if (n.$type === 'bpmn:ServiceTask') {//服务任务if (activity.startTime > 0 && activity.endTime === 0) {//进入执行,标识进行色canvas.addMarker(n.id, getResultCss(1))}if (activity.endTime > 0) {// 执行完成,节点标识完成色, 所有outgoing标识完成色。canvas.addMarker(n.id, getResultCss(2))const outgoing = getActivityOutgoing(activity)outgoing?.forEach((out) => {canvas.addMarker(out.id, getResultCss(2))})}}})
}

6.写在最后

本文简单的介绍了一个OA办公系统表单审批的全过程,行文比较粗糙,代码只展示了很少的一部分,如果有兴趣一起研究讨论的,欢迎留言批评指教。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/624300.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java学习(十八)--网络编程

介绍 需求 如何准确地定位网络上一台或多台主机&#xff1b; 定位主机上的特定的应用 找到主机后如何可靠高效地进行数据传输 目的 直接或间接地通过网络协议与其它计算机实现数据交换&#xff0c;进行通讯&#xff1b; 网络通信 网络&#xff1a;两台或多台设备通过一…

怎样利用MATLAB制作图中图(局部放大图片)

先做一个声明&#xff1a;文章是由我的个人公众号中的推送直接复制粘贴而来&#xff0c;因此对智能优化算法感兴趣的朋友&#xff0c;可关注我的个人公众号&#xff1a;启发式算法讨论。我会不定期在公众号里分享不同的智能优化算法&#xff0c;经典的&#xff0c;或者是近几年…

Quick taxi route assignment via real-time intersection state prediction

Quick taxi route assignment via real-time intersection state prediction with a spatial-temporal graph neural network(通过时空图神经网络实时交叉口状态预测快速分配出租车路线) PAPER LINK 简单说一下: 本文采用了一种新的方法,通过使用空间-时间图神经网络(ST…

基于位的权限系统

基于位的权限系统是一种利用二进制位运算进行权限管理的技术。在这种系统中&#xff0c;不同的权限被编码为2的幂次方 (例如1、2、4、8等)&#xff0c;每个权限对应一个独立的二进制位&#xff08;可想而知运算速度是非常快的&#xff09;。通过将这些权限值组合在一起形成一个…

力扣198. 打家劫舍(java 动态规划)

Problem: 198. 打家劫舍 文章目录 题目描述思路解题方法复杂度Code 题目描述 思路 1.构建多阶段决策模型&#xff1a;n个房屋对应n个阶段&#xff0c;每一个阶段决定一个房间是偷还是不偷&#xff0c;两种决策&#xff1a;偷、不偷 2.定义状态&#xff1a;不能记录每个阶段决策…

笔记系统的部署架构

前天给笔记系统打了0.0.3的tag&#xff0c;一个简单的全栈功能闭环基本完成。既然是开源&#xff0c;因此&#xff0c;这里有必要分享一下部署结构&#xff0c;希望能够获得小伙伴们的反馈。 目前整个系统采用docker容器来部署。应用介绍 auth_app: 登录/注册的前端应用 web_ap…

西贝柳斯音乐记谱软件Avid Sibelius Ultimate 2023中文激活版

Avid Sibelius(西贝柳斯终极解锁版) 是一款记谱软件&#xff0c;从有抱负的作曲家和词曲作者到教师和学生&#xff0c;任何人都可以快速轻松地开始创作和分享音乐。对于那些还不熟悉使用符号软件的人来说&#xff0c;直观的界面将引导您完成整个过程。磁性布局可防止对象相互碰…

跨国企业如何高效又安全的传输视频大文件?

在视频传输需求日益增长的今天&#xff0c;如何高效、安全地传输视频大文件成为跨国企业面临的重要问题。传统的文件传输方式存在诸多弊端&#xff0c;无法满足跨国企业对于传输效率、文件安全以及合规性的需求。那么跨国企业如何在市场是找到一种文件传输工具能在安全性、稳定…

构建基于RHEL9系列(CentOS9,AlmaLinux9,RockyLinux9等)的MySQL8.0.32的RPM包

本文适用&#xff1a;rhel9系列&#xff0c;或同类系统(CentOS9,AlmaLinux9,RockyLinux9等) 文档形成时期&#xff1a;2023年 因系统版本不同&#xff0c;构建部署应略有差异&#xff0c;但本文未做细分&#xff0c;对稍有经验者应不存在明显障碍。 因软件世界之复杂和个人能力…

Elastic Search的RestFul API入门:DSL查询

在我们之前的基础篇中,我们已经初步了解了DSL的架构与基础结构。现在,我们将进一步学习DSL的查询语句,这些查询语句对于我们的工作和学习而言至关重要。 DSL(Domain Specific Language)是一种专门用于特定领域的编程语言。在Elasticsearch(ES)中,DSL被广泛用于构建灵活…

debug之pycharm调试:出现Collecting data......

pycharm调试时&#xff0c;出现Collecting data… 一直在这个界面很久&#xff0c;这是新版本的Pycharm的bug&#xff0c;通常在多线程的情况下发生。 解决方法&#xff1a; File->Setting->Build,Execution,Deployment->Python Debugger。把Gevent compatible勾选…

Postgresql 12.2 + PostGIS 3.0.1 安装部署

参考文档&#xff1a; 按照该文档安装即可&#xff0c;如果遇到报错&#xff0c;可以参考下文&#xff1a; https://blog.csdn.net/weixin_41166785/article/details/127674169 所需的安装包 在资源里面&#xff08;我看下怎么可以不用积分下载&#xff09; 1、no acceptable…

代码随想录算法训练营第六天 |242.有效的字母异位词,349.两个数组的交集,202.快乐数,1.两数只和

哈希表理论基础 1、哈希表定义&#xff1a; 哈希表是根据关键码的值而直接进行访问的数据结构。 这么这官方的解释可能有点懵&#xff0c;其实直白来讲其实数组就是一张哈希表。 哈希表中关键码就是数组的索引下标&#xff0c;然后通过下标直接访问数组中的元素&#xff0c…

花几分钟整点jmeter花活,轻松超越90%软件测试

jmeter 可以做性能测试&#xff0c;这个很多人都知道&#xff0c;那你知道&#xff0c;jmeter 可以在启动运行时&#xff0c;指定线程数和运行时间&#xff0c;自定义性能场景吗&#xff1f; jmeter 性能测试&#xff0c;动态设定性能场景 平时&#xff0c;我们使用 jmeter 进…

天津大数据分析培训班 常见的大数据培训课程

大数据现在属于热门职业技能之一&#xff0c;不管是大学毕业生&#xff0c;计算机和数据相关专业青年&#xff0c;已经工作一阵的开发人员&#xff0c;运营小白&#xff0c;还是其他想进入这个行当的&#xff0c;可能还没有编程基础的转行人&#xff0c;都想尝试大数据行业&…

LLM论文:ALCE (Enabling Large Language Models to Generate Text with Citations)

这是一篇RAG领域的文章&#xff0c;原文在这&#xff1a;https://aclanthology.org/2023.emnlp-main.398.pdf 时间[Submitted on 24 May 2023 (v1), last revised 31 Oct 2023 (this version, v2)]背景LLM在信息搜索、生成带引用的文本时存在幻觉问题&#xff0c;即事实准确性…

【Axure高保真原型】树控制内联框架

今天和大家分享树控制内联框架的原型模板&#xff0c;点击树的箭头可以打开或者收起子节点&#xff0c;点击最后一级人物节点&#xff0c;可以切换右侧内联框到对应的页面&#xff0c;左侧的树是通过中继器制作的&#xff0c;使用简单&#xff0c;只需要按要求填写中继器表格即…

艾瑞报告:HR数字化需关注体系化能力,红海云等标杆厂商引领一体化趋势

新全球化时代背景下&#xff0c;企业经营所面临的国内外环境的不确定性增强&#xff0c;如何从不确定性中找到确定性成了大多数企业的关注要点。近日&#xff0c;艾瑞咨询发布《2023中国人力资源数字化研究报告》&#xff0c;从数字化转型的角度切入&#xff0c;探讨数字化如何…

基于传统机器学习模型算法的项目开发详细过程

1 场景分析 1.1 项目背景 描述开发项目模型的一系列情境和因素&#xff0c;包括问题、需求、机会、市场环境、竞争情况等 1.2. 解决问题 传统机器学习在解决实际问题中主要分为两类&#xff1a; 有监督学习&#xff1a;已知输入、输出之间的关系而进行的学习&#xff0c;从而…

二叉树DFS

基础知识 二叉树遍历 二叉搜索树BST 二叉树三种深度遍历 LeetCode 94. 二叉树的中序遍历 class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> ans new ArrayList<>();inorder(root, ans);return ans;}public void in…