当前位置:

首页 > 编程开发 > 怎么使用SpringBoot+Vue+Flowable模拟实现请假审批流程

怎么使用SpringBoot+Vue+Flowable模拟实现请假审批流程

1.效果展示在正式开搞之前,我先来给小伙伴们看下我们今天要完成的效果。简单起见,我这里并没有引入用户、角色等概念,涉及到用户的地方都是手动输入,在后续的文章中我会继续结合SpringSecurity来和大家展示引入用户之后的情况。我们先来看看请假页面:员工可以在这个页面输入姓名,请假天数以及请假理由等,然后点击按钮提交一个请假申请。当员工提交请假申请之后,这个请假申请默认是由经理来处理的,此时经理登录之后,就可以看到员工提交上来的请求:经理此时可以选择批准或者拒绝。无论是批准还是拒绝,都可以通过短信或者邮

本文目录

    1. 效果展示

    在正式开搞之前,我先来给小伙伴们看下我们今天要完成的效果。

    简单起见,我这里并没有引入用户、角色等概念,涉及到用户的地方都是手动输入,在后续的文章中我会继续结合 Spring Security 来和大家展示引入用户之后的情况。

    我们先来看看请假页面:

    怎么使用SpringBoot+Vue+Flowable模拟实现请假审批流程

    员工可以在这个页面输入姓名,请假天数以及请假理由等,然后点击按钮提交一个请假申请。

    当员工提交请假申请之后,这个请假申请默认是由经理来处理的,此时经理登录之后,就可以看到员工提交上来的请求:

    怎么使用SpringBoot+Vue+Flowable模拟实现请假审批流程

    经理此时可以选择批准或者拒绝。无论是批准还是拒绝,都可以通过短信或者邮件等告知员工。

    对于员工来说,也可以在一个页面查询自己请假流程的最终情况:

    怎么使用SpringBoot+Vue+Flowable模拟实现请假审批流程

    2. 工程创建

    我就直接来和小伙伴们展示 Spring Boot 中 flowable 的用法了。

    首先我们创建一个 Spring Boot 项目,创建的时候引入 Web 和 MySQL 驱动依赖即可,项目创建成功之后,再引入 flowable 依赖,最终的依赖文件如下:

    
        org.springframework.boot
        spring-boot-starter-web
    
    
        org.flowable
        flowable-spring-boot-starter
        6.7.2
    
    
        mysql
        mysql-connector-java
        runtime
    

    项目创建成功之后,首先需要我们在 application.properties 中配置一下数据库连接信息,如下:

    spring.datasource.username=root
    spring.datasource.password=123
    spring.datasource.url=jdbc:mysql:///flowable02?serverTimezone=Asia/Shanghai&useSSL=false&nullCatalogMeansCurrent=true

    配置完成之后,当 Spring Boot 项目第一次启动的时候,会自动创建出来对应的表和需要的数据。

    同时,Spring Boot 项目也会自动创建并暴露 Flowable 中的 ProcessEngine、CmmnEngine、DmnEngine、FormEngine、ContentEngine 及 IdmEngine 等 Bean。

    并且所有的 Flowable 服务都暴露为 Spring Bean。例如 RuntimeService、TaskService、HistoryService 等等服务,我们都可以在需要使用的时候,直接注入就可以使用了。

    同时:

    • resources/processes 目录下的任何 BPMN 2.0 流程定义都会被自动部署,所以在 Spring Boot 项目中,我们只需要将自己的流程文件放对位置即可,剩下的事情就会自动完成。

    • cases 目录下的任何 CMMN 1.1 事例都会被自动部署。

    • forms 目录下的任何 Form 定义都会被自动部署。

    3. 流程图分析

    今天这个例子比较简单,就是一个请假流程,我暂时先不跟小伙伴们去扯画流程图的事,咱们直接用一个官网现成的请假流程图:

    怎么使用SpringBoot+Vue+Flowable模拟实现请假审批流程

    我们先来简单分析一下这张图:

    • 最左侧的圆圈叫做启动事件(start event),这表示一个流程实例的起点。

    • 一个流程启动之后,首先到达第一个有用户图标的矩形中,这个矩形称为一个 User Task,在这个 User Task 中,经理可以选择批准亦或者拒绝。

    • UserTask 的下一步是一个菱形,这个称作排他网关(Exclusive Gateway),这个会将请求路由到不同的地方。

    • 先说批准,如果在第一个矩形中,经理选择了批准,那么就会进入到一个带有齿轮图标的矩形中,在这个矩形中我们我们可以额外做一些事情,然后又会调用到一个 UserTask,最终完成整个流程。

    • 如果经理选择了拒绝,则会进入到下面的发邮件的矩形中,在这个中我们可以给员工发送一个通知,告知他请假没有通过。

    • 当系统走到最右边的圆圈之后,就表示这个流程执行结束了。

    这个流程图对应的 XML 文件位于 src/main/resources/processes/holiday-request.bpmn20.xml 位置,其内容如下:

    
    
        
    
            
            
    
            
            
    
            
            
                
                    
                
            
            
                
                    
                
            
    
            
            
    
            
            
    
            
            
    
            
    
            
    
        
    

    很多想学习流程引擎的小伙伴都会被这个 XML 文件劝退,但是!!!

    如果你愿意静下心来认真阅读这个 XML 文件,你会发现流程引擎原来如此简单!

    我们来挨个看下这里的每一个节点:

    • process:这表示一个流程,例如本文和大家分享的请假就是一个流程。

    • startEvent:这表示流程的开始,这就是一个开始事件。

    • userTask:这就是一个具体的流程节点了,flowable:candidateGroups 属性表示这个节点该由哪个用户组中的用户来处理。

    • sequenceFlow:这就是连接各个流程节点之间的线条,这个里边一般有两个属性,sourceRef 和 targetRef,前者表示线条的起点,后者表示线条的终点。

    • exclusiveGateway:表示一个排他性网关,也就是那个菱形选择框。

    • 从排他性网关出来的线条有两个,大家注意看上面的代码,这两个线条中都涉及到一个变量 approved,如果这个变量为 true,则 targeRef 就是 externalSystemCall;如果这个变量为 false,则 targetRef 就是 rejectLeave。

    • serviceTask:这就是我们定义的一个具体的外部服务,如果在整个流程执行的过程中,你有一些需要自己完成的事情,那么可以通过 serviceTask 来实现,这个节点会有一个 flowable:class 属性,这个属性的值就是一个自定义类。

    • 另外,上文中部分节点中还涉及到变量 ${},这个变量是在流程执行的过程中传入进来的。

    总而言之,只要小伙伴们静下心来认真阅读一下上面的 XML,你会发现 So Easy!

    4. 请假申请

    好了,接下来我们就来看一个具体的请假申请。由于请假流程只要放对位置,就会自动加载,所以我们并不需要手动加载请假流程,直接开始一个请假申请流程即可。

    4.1 服务端接口

    首先我们需要一个实体类来接受前端传来的请假参数:用户名、请假天数以及请假理由:

    public class AskForLeaveVO {
        private String name;
        private Integer days;
        private String reason;
        // 省略 getter/setter
    }

    再拿出祖传的 RespBean,以便响应数据方便一些:

    public class RespBean {
        private Integer status;
        private String msg;
        private Object data;
    
        public static RespBean ok(String msg, Object data) {
            return new RespBean(200, msg, data);
        }
    
    
        public static RespBean ok(String msg) {
            return new RespBean(200, msg, null);
        }
    
    
        public static RespBean error(String msg, Object data) {
            return new RespBean(500, msg, data);
        }
    
    
        public static RespBean error(String msg) {
            return new RespBean(500, msg, null);
        }
    
        private RespBean() {
        }
    
        private RespBean(Integer status, String msg, Object data) {
            this.status = status;
            this.msg = msg;
            this.data = data;
        }
        // 省略 getter/setter
    }

    接下来我们提供一个处理请假申请的接口:

    @RestController
    public class AskForLeaveController {
    
        @Autowired
        AskForLeaveService askForLeaveService;
    
        @PostMapping("/ask_for_leave")
        public RespBean askForLeave(@RequestBody AskForLeaveVO askForLeaveVO) {
            return askForLeaveService.askForLeave(askForLeaveVO);
        }
    }

    核心逻辑在 AskForLeaveService 中,来继续看:

    @Service
    public class AskForLeaveService {
    
        @Autowired
        RuntimeService runtimeService;
    
        @Transactional
        public RespBean askForLeave(AskForLeaveVO askForLeaveVO) {
            Map variables = new HashMap<>();
            variables.put("name", askForLeaveVO.getName());
            variables.put("days", askForLeaveVO.getDays());
            variables.put("reason", askForLeaveVO.getReason());
            try {
                runtimeService.startProcessInstanceByKey("holidayRequest", askForLeaveVO.getName(), variables);
                return RespBean.ok("已提交请假申请");
            } catch (Exception e) {
                e.printStackTrace();
            }
            return RespBean.error("提交申请失败");
        }
    }

    小伙伴们看一下,在提交请假申请的时候,分别传入了 name、days 以及 reason 三个参数,我们将这三个参数放入到一个 Map 中,然后通过 RuntimeService#startProcessInstanceByKey 方法来开启一个流程,开启流程的时候一共传入了三个参数:

    • 第一个参数表示流程引擎的名字,这就是我们刚才在流程的 XML 文件中定义的名字。

    • 第二个参数表示当前这个流程的 key,我用了申请人的名字,将来我们可以通过申请人的名字查询这个人曾经提交的所有申请流程。

    • 第三个参数就是我们的变量了。

    好了,这服务端就写好了。

    4.2 前端页面

    接下来我们来开发前端页面。

    前端我使用 Vue+ElementUI+Axios,咱们这个案例比较简单,就没有必要搭建单页面了,直接用普通的 HTML 就行了。另外,Vue 我是用了 Vue3:

    
    
    
        
        Title
        
        
        
        
        
        
    
    
    
        

    开始一个请假流程

                                                                                                                                                 
    请输入姓名:                              
    请输入请假天数:                              
    请输入请假理由:                              
        提交请假申请

    这个页面有几个需要注意的点:

    • 通过 Vue.createApp 来创建一个 Vue 实例,这跟以前 Vue2 中直接 new 一个 Vue 实例不一样。

    • 在最下面,通过 use 来配置 ElementPlus 插件,这个跟 Vue2 也不一样。在 Vue2 中,如果我们单纯的在 HTML 页面中引用 ElementUI 并不需要这个步骤。

    • 剩下的东西就比较简单了,上面先引入 Vue3、Axios 以及 ElementPlus,然后三个输入框,点击按钮提交请求,参数就是三个输入框中的数据,提交成功或者失败,分别弹个框出来提示一下就行了。

    好啦,这就写好了。

    然而,提交完成后,没有一个直观的展示,虽然前端提示说提交成功了,但是究竟成功没,还得眼见为实。

    5. 任务展示

    好了,接下来我们要做的事情就是把用户提交的流程展示出来。

    按理说,比如经理登录成功之后,系统页面就自动展示出来经理需要审批的流程,但是我们当前这个例子为了简单,就没有登录这个操作了,需要需要用户将来在网页上选一下自己的身份,接下来就会展示出这个身份所对应的需要操作的流程。

    我们来看任务接口:

    @GetMapping("/list")
    public RespBean leaveList(String identity) {
        return askForLeaveService.leaveList(identity);
    }

    这个请求参数 identity 就表示当前用户的身份(本来应该是登录后自动获取,但是因为我们目前没有登录,所以这个参数是由前端传递过来)。来继续看 askForLeaveService 中的方法:

    @Service
    public class AskForLeaveService {
    
        @Autowired
        TaskService taskService;
    
        public RespBean leaveList(String identity) {
            List tasks = taskService.createTaskQuery().taskCandidateGroup(identity).list();
            List> list = new ArrayList<>();
            for (int i = 0; i < tasks.size(); i++) {
                Task task = tasks.get(i);
                Map variables = taskService.getVariables(task.getId());
                variables.put("id", task.getId());
                list.add(variables);
            }
            return RespBean.ok("加载成功", list);
        }
    }

    Task 就是流程中要做的每一件事情,我们首先通过 TaskService,查询出来这个用户需要处理的任务,例如前端前传来的是 managers,那么这里就是查询所有需要由 managers 用户组处理的任务。

    这段代码要结合流程图一起来理解,小伙伴们回顾下我们流程图中有如下一句:

    这意思就是说这个 userTask 是由 managers 这个组中的用户来处理,所以上面 Java 代码中的查询就是查询 managers 这个组中的用户需要审批的任务。

    我们将所有需要审批的任务查询出来后,通过 taskId 可以进一步查询到这个任务中当时传入的各种变量,我们将这些数据封装成一个对象,并最终返回到前端。

    最后,我们再来看下前端页面:

    
    
    
        
        Title
        
        
        
        
        
        
    
    
    
        
            
    请选择你的身份:
            
                                                           刷新一下         
        
                                                                          批准                 拒绝                           

    大家看到,首先有一个下拉框,我们在这个下拉框中来选择用户的身份。选择完成后,触发 initTasks 方法,然后在这个方法中,发起网络请求,最终将请求结果渲染出来。

    最终效果如下:

    怎么使用SpringBoot+Vue+Flowable模拟实现请假审批流程

    当然用户也可以点击刷新按钮,刷新列表。

    这样,当第五小节中,员工提交了一个请假审批之后,我们在这个列表中就可以查看到员工提交的请假审批了(在流程图中,我们直接设置了用户的请假审批固定提交给 managers,在后续的文章中,松哥会教大家如何把这个提交的目标用户变成一个动态的)。

    6. 请假审批

    接下来经理就可以选择批准或者是拒绝这请假了。

    首先我们封装一个实体类用来接受前端传来的请求:

    public class ApproveRejectVO {
        private String taskId;
        private Boolean approve;
        private String name;
        // 省略 getter/setter
    }

    参数都好理解,approve 为 true 表示申请通过,false 表示申请被拒绝。

    接下来我们来看接口:

    @PostMapping("/handler")
    public RespBean askForLeaveHandler(@RequestBody ApproveRejectVO approveRejectVO) {
        return askForLeaveService.askForLeaveHandler(approveRejectVO);
    }

    看具体的 askForLeaveHandler 方法:

    @Service
    public class AskForLeaveService {
    
        @Autowired
        TaskService taskService;
    
        public RespBean askForLeaveHandler(ApproveRejectVO approveRejectVO) {
            try {
                boolean approved = approveRejectVO.getApprove();
                Map variables = new HashMap();
                variables.put("approved", approved);
                variables.put("employee", approveRejectVO.getName());
                Task task = taskService.createTaskQuery().taskId(approveRejectVO.getTaskId()).singleResult();
                taskService.complete(task.getId(), variables);
                if (approved) {
                    //如果是同意,还需要继续走一步
                    Task t = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
                    taskService.complete(t.getId());
                }
                return RespBean.ok("操作成功");
            } catch (Exception e) {
                e.printStackTrace();
            }
            return RespBean.error("操作失败");
        }
    }

    大家注意这个审批流程:

    • 审批时需要两个参数,approved 和 employee,approved 为 true,就会自动进入到审批通过的流程中,approved 为 false 则会自动进入到拒绝流程中。

    • 通过 taskService,结合 taskId,从流程中查询出对应的 task,然后调用 taskService.complete 方法传入 taskId 和 变量,以使流程向下走。

    • 小伙伴们再回顾一下我们前面的流程图,如果请求被批准备了,那么在执行完自定义的 Approve 逻辑后,就会进入到 Holiday approved 这个 userTask 中,注意此时并不会继续向下走了(还差一步到结束事件);如果是请求拒绝,则在执行完自定义的 Reject 逻辑后,就进入到结束事件了,这个流程就结束了。

    • 针对第三条,所以代码中我们还需要额外再加一步,如果是 approved 为 true,那么就再从当前流程中查询出来需要执行的 task,再调用 complete 继续走一步,此时就到了结束事件了,这个流程就结束了。注意这次的查询是根据当前流程的 ID 查询的,一个流程就是一条线,这条线上有很多 Task,我们可以从 Task 中获取到流程的 ID。

    好啦,接口就写好了。

    当然,这里还涉及到两个自定义的逻辑,就是批准或者拒绝之后的自定义逻辑,这个其实很好写,如下:

    public class Approve implements JavaDelegate {
        @Override
        public void execute(DelegateExecution execution) {
            System.out.println("申请通过:"+execution.getVariables());
        }
    }

    我们自定义类实现 JavaDelegate 接口即可,然后我们在 execute 方法中做自己想要做的事情即可,execution 中有这个流程中的所有变量。我们可以在这里发邮件、发短信等等。Reject 的定义方式也是类似的。这些自定义类写好之后,将来配置到流程图中即可(可查看上文的流程图)。

    最后再来看看前端提交方法就简单了(页面源码上文已经列出):

    approveOrReject(taskId, approve,name) {
        let _this = this;
        axios.post('/handler', {taskId: taskId, approve: approve,name:name})
            .then(function (response) {
                _this.initTasks();
            })
            .catch(function (error) {
                console.log(error);
            });
    }

    这就一个普通的 Ajax 请求,批准的话第二个参数就为 true,拒绝的话第二个参数就为 false。

    7. 结果查询

    最后,每个用户都可以查看自己曾经的申请记录。本来这个登录之后就可以展示了,但是因为我们没有登录,所以这里也是需要手动输入查询的用户,然后根据用户名查询这个用户的历史记录,我们先来看查询接口:

    @GetMapping("/search")
    public RespBean searchResult(String name) {
        return askForLeaveService.searchResult(name);
    }

    参数就是要查询的用户名。具体的查询流程如下:

    public RespBean searchResult(String name) {
        List historyInfos = new ArrayList<>();
        List historicProcessInstances = historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(name).finished().orderByProcessInstanceEndTime().desc().list();
        for (HistoricProcessInstance historicProcessInstance : historicProcessInstances) {
            HistoryInfo historyInfo = new HistoryInfo();
            Date startTime = historicProcessInstance.getStartTime();
            Date endTime = historicProcessInstance.getEndTime();
            List historicVariableInstances = historyService.createHistoricVariableInstanceQuery()
                    .processInstanceId(historicProcessInstance.getId())
                    .list();
            for (HistoricVariableInstance historicVariableInstance : historicVariableInstances) {
                String variableName = historicVariableInstance.getVariableName();
                Object value = historicVariableInstance.getValue();
                if ("reason".equals(variableName)) {
                    historyInfo.setReason((String) value);
                } else if ("days".equals(variableName)) {
                    historyInfo.setDays(Integer.parseInt(value.toString()));
                } else if ("approved".equals(variableName)) {
                    historyInfo.setStatus((Boolean) value);
                } else if ("name".equals(variableName)) {
                    historyInfo.setName((String) value);
                }
            }
            historyInfo.setStartTime(startTime);
            historyInfo.setEndTime(endTime);
            historyInfos.add(historyInfo);
        }
        return RespBean.ok("ok", historyInfos);
    }
    • 我们当时在开启流程的时候,传入了一个参数 key,这里就是再次通过这个 key,也就是用户名去查询历史流程,查询的时候还加上了 finished 方法,这个表示要查询的流程必须是执行完毕的流程,对于没有执行完毕的流程,这里不查询,查完之后,按照流程最后的处理时间进行排序。

    • 遍历第一步的查询结果,从 HistoricProcessInstance 中提取出每一个流程的详细信息,并存入到集合中,并最终返回。

    • 这里涉及到两个历史数据查询,createHistoricProcessInstanceQuery 用来查询历史流程,而 createHistoricVariableInstanceQuery 则主要是用来查询流程变量的。

    最后,前端通过表格展示这个数据即可:

    
    
    
        
        Title
        
        
        
        
        
        
    
    
    
        
            
            查询
        
        
                                                                                                                                已通过                     已拒绝                                            

    这个都是一些常规操作,我就不多说了,最终展示效果如下:

    怎么使用SpringBoot+Vue+Flowable模拟实现请假审批流程

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 VUE
    上一篇: 怎么使用SpringBoot @GroupSequenceProvider注解实现bean多属性联合校验
    下一篇: mysql外键怎么使用
    相关文章 更多
    vue上传文件格式怎么设置及要求说明
    vue上传文件格式怎么设置及要求说明

    学习如何在Vue项目中设置文件上传格式限制。本文介绍使用HTML accept属性进行基础过滤,以及结合JavaScript进行严格后缀名校验的方法,确保只允许指定类型的文件被上传。

    vue创建项目很慢怎么解决及常见原因排查教程
    vue创建项目很慢怎么解决及常见原因排查教程

    分析Vue项目创建缓慢的常见原因,包括网络延迟和依赖下载问题,提供切换镜像源、更换包管理器及使用Vite等具体优化方案,提升开发环境搭建效率。

    vue下载文件怎么做到点上就有反应在浏览器显示进度条
    vue下载文件怎么做到点上就有反应在浏览器显示进度条

    详解如何在Vue项目中利用Axios的onDownloadProgress回调捕获下载进度,并结合UI组件实时渲染进度条,同时分析Blob对象处理大文件时的内存限制与替代方案。

    uniapp网络请求拦截怎么设置方法详细教程
    uniapp网络请求拦截怎么设置方法详细教程

    学习如何在UniApp项目中设置网络请求拦截器,实现统一的Token管理、请求参数处理和响应错误捕获,优化前端开发体验。

    Vue页面空白的排查方法及解决办法
    Vue页面空白的排查方法及解决办法

    遇到Vue项目页面空白不要慌,本文通过路由匹配、组件生命周期、异步数据加载及权限控制四个真实场景,详解排查步骤与解决办法,助你快速恢复页面显示。

    Sublime实现一键生成Vue模板_自定义Snippet实战
    Sublime实现一键生成Vue模板_自定义Snippet实战

    在Sublime中为Vue文件自定义Snippet时,scope参数错误是最常见的Tab无响应原因。需通过Developer:ShowScopeName确认正确语法作用域。占位符需依次递增,$0可选但建议添加。相同数字占位符会同步编辑,可通过不同编号解耦。Snippet无法在新建文件时自动展开,需手动输入tabTrigger后按Tab。务必确保文件已正确识别

    VSCode如何解决Prettier和Vue单引号双引号的冲突问题
    VSCode如何解决Prettier和Vue单引号双引号的冲突问题

    在Vue项目中,为解决Prettier默认双引号与Vue单引号规则的冲突,必须在项目根目录创建.prettierrc并设置singleQuote:true。同时VSCode全局设置失效,需依赖项目级配置。此外,需配置Vetur使JS格式化使用Prettier;Vue3项目禁用Vetur启用Volar。最后在ESLint的extends末尾添加eslint-c

    VSCode自动补全代码必备扩展_Vue项目开发必装
    VSCode自动补全代码必备扩展_Vue项目开发必装

    Vue3项目需安装Volar并彻底卸载Vetur,否则代码补全失效。ESLint需配置import/resolver以支持别名路径补全。避免同时启用Copilot与TabNine,防止卡顿及补全冲突,推荐二选一使用。

    VSCode离线环境插件管理_VSCode自动格式化Vue代码【指南】
    VSCode离线环境插件管理_VSCode自动格式化Vue代码【指南】

    离线环境VSCode自动格式化Vue文件需确保插件版本兼容、路径正确、签名有效。Vue2使用vetur,Vue3使用volar,不可混用。项目级配置写入.vscode/settings.json,保存失败多因ESLint冲突或缺失prettier依赖。建议预先离线安装prettier并验证签名。

    VSCode离线插件安装技巧_VSCode自动格式化Vue文件【配置】
    VSCode离线插件安装技巧_VSCode自动格式化Vue文件【配置】

    离线安装VSCode插件需通过命令面板或输出日志确认加载成功,避免静默失败。Vue文件格式化需确保语言模式正确且每块有对应格式化器,离线环境下Prettier需完整复制插件目录及依赖。关键配置如editor.formatOnSave和语言专属格式化器必须正确设置,防止相互覆盖。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。