当前位置: 首页 > news >正文

Flowable31动态表单-----------------------终章

首先我们需要明白的是动态表单主要分为两类:内置、外置。在这分别介绍一下内置外置表单。

一、内置表单中的动态表单 (Flowable Forms)

Flowable 自带的表单引擎(Flowable Forms)支持一定程度的动态能力。因为是内置方案,它的实现相对简单,但不那么灵活。
实现机制
内置表单的动态性主要通过在表单的 JSON 定义中使用 表达式(Expressions) 来实现。你可以在表单设计器中为组件的 visibility (可见性) 或 disabled (禁用) 等属性设置条件表达式。
语法: 表达式通常使用 {{…}} 包裹。
原理: Flowable Form 的前端渲染引擎会实时监听表单中各个字段值的变化。当某个值改变时,它会重新计算所有依赖该值的表达式,并根据计算结果(true 或 false)来更新对应组件的显示状态。
举例:请假类型联动
场景:创建一个请假表单,当“请假类型”选择“其他”时,才显示“具体事由”这个文本框。
创建表单字段:
一个下拉框,ID 为 type,选项包含“年假”、“病假”、“其他”。
一个文本输入框,ID 为 otherReason。
但是一般很少用内置的表单!!!!所以在这不详细讲了。

二、外置表单中的动态表单 (External Forms)

二、外置表单中的动态表单 (External Forms)
这是实现动态表单最主流、最强大、最灵活的方式。
实现机制
在外置表单模式下,Flowable 引擎完全不关心表单长什么样、有什么动态效果。所有的动态逻辑都由你的前端应用(Vue/React/Angular 等)全权负责。
你拥有前端框架提供的所有“武器”来实现任何你能想到的动态效果:
条件渲染: Vue 的 v-if/v-show,React 的 {condition && }。
数据绑定: Vue 的 v-model,React 的 state + onChange。
侦听器 (Watchers): 监听某个数据的变化,然后执行复杂的逻辑,比如去后端请求新的数据来填充另一个下拉框。
计算属性 (Computed Properties): 根据一个或多个数据派生出新的状态。
事件处理: 响应用户的各种点击、输入事件。
在这我们同个一个举例来详细了解一下外置表单。

场景设定

流程:员工提交请假申请 -> 直属领导审批 -> 流程结束。
角色:员工(employee),领导(manager)。
技术栈:
后端:Spring Boot
流程引擎:Flowable
前端:Vue + Vue Router + Axios

第 1 步:设计 BPMN 流程并定义 formKey
这是所有工作的基础。我们创建一个名为 leave-process.bpmn20.xml 的文件。
核心节点与 formKey 设置:
申请请假 (User Task):
ID: applyTask
负责人: 由流程发起人自己处理,所以用 ${initiator}
formKey: leave-apply-form (这是我们给前端Vue组件起的名字)
领导审批 (User Task):
ID: approvalTask
负责人: manager (为简化,我们写死;实际项目中会是动态的)
formKey: leave-approval-form (这是审批页面的Vue组件名)
BPMN 核心 XML 代码:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/2.0/MODEL"xmlns:flowable="http://flowable.org/bpmn"targetNamespace="http://flowable.org/examples"><process id="leaveProcess" name="请假流程"><startEvent id="startEvent" flowable:initiator="applyUser"/><sequenceFlow id="flow1" sourceRef="startEvent" targetRef="applyTask"/><!-- 申请节点:员工填写请假单 --><userTask id="applyTask" name="填写请假单" flowable:assignee="${applyUser}"><extensionElements><!-- 关键点1: 定义外置表单的Key --><flowable:formKey>leave-apply-form</flowable:formKey></extensionElements></userTask><sequenceFlow id="flow2" sourceRef="applyTask" targetRef="approvalTask"/><!-- 审批节点:领导审批 --><userTask id="approvalTask" name="领导审批" flowable:assignee="manager"><extensionElements><!-- 关键点2: 定义审批表单的Key --><flowable:formKey>leave-approval-form</flowable:formKey></extensionElements></userTask><sequenceFlow id="flow3" sourceRef="approvalTask" targetRef="decision"/><!-- 排他网关:根据审批结果决定走向 --><exclusiveGateway id="decision"/><!-- 如果同意,走向结束 --><sequenceFlow id="flowApproved" sourceRef="decision" targetRef="endEvent"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${approved == true}]]></conditionExpression></sequenceFlow><!-- 如果拒绝,回到申请节点(重新提交) --><sequenceFlow id="flowRejected" sourceRef="decision" targetRef="applyTask"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${approved == false}]]></conditionExpression></sequenceFlow><endEvent id="endEvent"/></process>
</definitions>

注意:
我们将这个 .bpmn20.xml 文件放到 Spring Boot 项目的 src/main/resources/processes/ 目录下。
我们这里为了演示,把“发起”和“填写表单”分开了。在实际中,常常是发起流程时就直接提交了表单数据,从而直接完成第一个任务。我们这里的演示流程是:发起 -> 任务列表出现“填写请假单” -> 填写并提交。

第 2 步:后端 Spring Boot 开发
创建 REST API 接口 (LeaveController.java)
这是连接前后端的桥梁。

@RestController
@RequestMapping("/api/leave")
@RequiredArgsConstructor // 使用Lombok注入
@CrossOrigin // 允许跨域,方便本地Vue开发调试
public class LeaveController {private final RuntimeService runtimeService;private final TaskService taskService;/*** 1. 启动请假流程* @param userId 申请人ID*/@PostMapping("/start/{userId}")public String startProcess(@PathVariable String userId) {// 流程变量,设置申请人Map<String, Object> variables = new HashMap<>();variables.put("applyUser", userId);ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leaveProcess", variables);return "流程已启动,实例ID: " + processInstance.getId();}/*** 2. 获取用户的待办任务列表* @param userId 用户ID* @return 任务列表*/@GetMapping("/tasks/{userId}")public List<Map<String, Object>> getTasks(@PathVariable String userId) {List<Task> tasks = taskService.createTaskQuery().taskAssignee(userId).orderByTaskCreateTime().desc().list();// 返回给前端一个简化的DTO列表return tasks.stream().map(task -> {Map<String, Object> map = new HashMap<>();map.put("taskId", task.getId());map.put("taskName", task.getName());map.put("createTime", task.getCreateTime());map.put("formKey", task.getFormKey()); // **核心:把formKey传给前端**map.put("processInstanceId", task.getProcessInstanceId());return map;}).collect(Collectors.toList());}/*** 3. 获取任务关联的流程变量 (用于表单数据回显)* @param taskId 任务ID* @return 流程变量*/@GetMapping("/tasks/{taskId}/variables")public Map<String, Object> getTaskVariables(@PathVariable String taskId) {return taskService.getVariables(taskId);}/*** 4. 完成任务 (提交表单)* @param taskId 任务ID* @param variables 表单提交的数据,以JSON格式的Map传入*/@PostMapping("/tasks/{taskId}/complete")public void completeTask(@PathVariable String taskId, @RequestBody Map<String, Object> variables) {taskService.complete(taskId, variables);}
}

第 3 步:前端 Vue 开发

假设你已经创建了一个 Vue 项目,并安装了 vue-router 和 axios。

  1. API 服务 (src/api/leave.js)
    将后端接口封装成函数,便于组件调用。
import axios from 'axios';const apiClient = axios.create({baseURL: 'http://localhost:8080/api/leave', // 你的Spring Boot地址headers: {'Content-Type': 'application/json',},
});export default {startProcess(userId) {return apiClient.post(`/start/${userId}`);},getTasks(userId) {return apiClient.get(`/tasks/${userId}`);},getTaskVariables(taskId) {return apiClient.get(`/tasks/${taskId}/variables`);},completeTask(taskId, variables) {return apiClient.post(`/tasks/${taskId}/complete`, variables);},
};
  1. Vue Router 配置 (src/router/index.js)
    这是实现 formKey 动态路由的核心。
import { createRouter, createWebHistory } from 'vue-router';
import TaskList from '../views/TaskList.vue';
import FormWrapper from '../views/FormWrapper.vue'; // 一个动态加载表单的包装器组件const routes = [{path: '/',redirect: '/tasks/employee', // 默认重定向到员工的任务列表},{path: '/tasks/:userId', // 任务列表页,:userId是动态的name: 'TaskList',component: TaskList,props: true, // 将路由参数userId作为props传递给组件},{// **核心路由:根据formKey动态展示表单**path: '/form/:formKey',name: 'Form',component: FormWrapper,props: true,},
];const router = createRouter({history: createWebHistory(),routes,
});export default router;
  1. 视图组件
    a. TaskList.vue (任务列表)
<template><div><h2>{{ userId }} 的待办任务</h2><button @click="startLeaveProcess" v-if="userId === 'employee'">发起请假流程</button><ul><li v-for="task in tasks" :key="task.taskId">{{ task.taskName }} - 创建时间: {{ new Date(task.createTime).toLocaleString() }}<button @click="handleTask(task)">去处理</button></li></ul></div>
</template><script>
import api from '../api/leave';export default {props: ['userId'],data() {return {tasks: [],};},methods: {async fetchTasks() {const response = await api.getTasks(this.userId);this.tasks = response.data;},async startLeaveProcess() {try {await api.startProcess(this.userId);alert('流程已启动!');this.fetchTasks(); // 刷新任务列表} catch (error) {console.error("启动流程失败:", error);}},handleTask(task) {// **关键跳转:使用formKey和taskId导航到表单页**this.$router.push({name: 'Form',params: { formKey: task.formKey },query: { taskId: task.taskId },});},},created() {this.fetchTasks();},watch: {// 监听userId变化,例如从 /tasks/employee 切换到 /tasks/manageruserId() {this.fetchTasks();}}
};
</script>

b. FormWrapper.vue (表单包装器/分发器)
这个组件非常巧妙,它根据路由参数 formKey 来决定到底渲染哪个具体的表单组件。

<template><div><!-- Vue的动态组件,:is 属性绑定到要渲染的组件 --><component :is="formComponent" :task-id="taskId" /></div>
</template><script>
import { defineAsyncComponent } from 'vue';// 定义formKey到组件的映射
const formMap = {'leave-apply-form': defineAsyncComponent(() => import('../components/LeaveApplyForm.vue')),'leave-approval-form': defineAsyncComponent(() => import('../components/LeaveApprovalForm.vue')),
};export default {props: ['formKey'],computed: {formComponent() {return formMap[this.formKey] || null; // 根据formKey返回对应的组件},taskId() {return this.$route.query.taskId; // 从路由的query中获取taskId},},
};
</script>

c. LeaveApplyForm.vue (请假申请表单)

<template><div class="form-container"><h3>填写请假单</h3><p>任务ID: {{ taskId }}</p><div><label>请假天数:</label><input type="number" v-model.number="formData.days" /></div><div><label>请假事由:</label><textarea v-model="formData.reason"></textarea></div><button @click="submitForm">提交申请</button></div>
</template><script>
import api from '../api/leave';
export default {props: ['taskId'],data() {return {formData: {days: 0,reason: '',},};},methods: {async submitForm() {try {await api.completeTask(this.taskId, this.formData);alert('申请已提交!');this.$router.push('/tasks/employee'); // 提交后返回任务列表} catch (error) {console.error('提交失败:', error);}},},
};
</script>

d. LeaveApprovalForm.vue (领导审批表单)
这个表单需要先加载数据显示,然后再提交。

<template><div class="form-container"><h3>审批请假单</h3><div v-if="loading">正在加载数据...</div><div v-else><p><strong>申请人:</strong> {{ variables.applyUser }}</p><p><strong>请假天数:</strong> {{ variables.days }}</p><p><strong>请假事由:</strong> {{ variables.reason }}</p><hr /><div><label>审批意见:</label><textarea v-model="approval.comment"></textarea></div><button @click="handleApproval(true)">同意</button><button @click="handleApproval(false)" style="background-color: red;">拒绝</button></div></div>
</template><script>
import api from '../api/leave';
export default {props: ['taskId'],data() {return {loading: true,variables: {}, // 存放从后端加载的流程变量approval: {comment: '',approved: null, // 将在点击按钮时设置},};},methods: {async fetchVariables() {try {const response = await api.getTaskVariables(this.taskId);this.variables = response.data;} catch (error) {console.error('加载表单数据失败:', error);} finally {this.loading = false;}},async handleApproval(isApproved) {this.approval.approved = isApproved;try {await api.completeTask(this.taskId, this.approval);alert('审批完成!');this.$router.push('/tasks/manager'); // 返回领导的任务列表} catch (error) {console.error('审批失败:', error);}},},created() {this.fetchVariables(); // 组件创建时加载数据},
};
</script>

第 4 步:演示整个流程
启动 Spring Boot 后端。
启动 Vue 前端。
员工操作:
浏览器访问 http://localhost:8081/tasks/employee (假设Vue运行在8081端口)。
点击 “发起请假流程”,后端启动流程实例,任务列表刷新,出现 “填写请假单” 任务。
点击 “去处理”,Vue Router 根据 formKey (leave-apply-form) 导航到表单页,FormWrapper 加载 LeaveApplyForm.vue。
填写天数和事由,点击 “提交申请”。数据作为流程变量提交,第一个任务完成。
领导操作:
浏览器访问 http://localhost:8081/tasks/manager。
任务列表显示 “领导审批” 任务。
点击 “去处理”,Vue Router 根据 formKey (leave-approval-form) 导航,FormWrapper 加载 LeaveApprovalForm.vue。
LeaveApprovalForm 组件会调用后端API,获取并显示员工提交的请假天数和事由(数据回显)。
领导填写审批意见,点击 “同意”。approved: true 和 comment 作为流程变量提交,任务完成。
流程结束:
流程引擎根据 approved == true 的条件,流向结束节点。
如果领导点击 “拒绝”,流程会回到 “填写请假单” 节点,员工会在自己的任务列表里再次看到这个任务。

前端架构的核心思想

前端的核心任务是:根据后端传来的 formKey,动态地展示正确的表单组件,并处理好数据的加载和提交。
为了实现这个目标,我们设计了以下几个关键部分:
API 服务层 (api/leave.js): 专职与后端打交道,让组件代码更干净。
路由 (router/index.js): 充当交通警察的角色。它负责解析 URL,并将用户引导到正确的“页面”或“视图”。
任务列表视图 (views/TaskList.vue): 用户的工作台。展示所有待办事项,是所有操作的入口。
表单分发器/包装器 (views/FormWrapper.vue): 这是整个前端设计的**“枢纽”或“魔法盒”。它本身不展示任何具体表单,而是根据指令(formKey)决定调用哪个真正的表单组件。
具体表单组件 (components/Leave*.vue): 真正负责 UI 展示和用户交互的
“工人”**。

用户在 TaskList.vue 点击 "去处理"│└─> 1. handleTask(task) 被调用│└─> 2. this.$router.push({ name: 'Form', params: { formKey: '...' }, query: { taskId: '...' }})│└─> 3. Vue Router 匹配到 /form/:formKey 路由,浏览器 URL 变化│└─> 4. FormWrapper.vue 组件被渲染│├─> 5. FormWrapper 从路由接收 props: { formKey: '...' }│└─> 6. FormWrapper 从路由 query 中获取 taskId: '...'│└─> 7. formComponent 计算属性根据 formKey 从 formMap 中找到对应的组件(如 LeaveApprovalForm)│└─> 8. <component :is="LeaveApprovalForm" :task-id="taskId"> 被渲染│└─> 9. LeaveApprovalForm.vue 组件被创建│├─> 10. 它接收到 taskId 这个 prop│└─> 11. 在 created() 钩子中,使用 this.taskId 调用 API (getTaskVariables) 加载数据│└─> 12. 用户填写表单,点击提交│└─> 13. 调用 API (completeTask) 并携带 taskId 和表单数据│└─> 14. 提交成功后,路由跳转回任务列表
http://www.dtcms.com/a/287192.html

相关文章:

  • AI编程工具对比:Cursor、GitHub Copilot与Claude Code
  • bws-rs:Rust 编写的 S3 协议网关框架,支持灵活后端接入
  • 【Linux】AKHQ实现kafka可视化
  • 电力载波通信技术全景解析:从历史演进到智能电网创新应用
  • 【Linux服务器】-MySQL数据库参数调优
  • 打造高效订单处理!ZKmall开源商城的统一履约中心架构解析
  • 本地部署开源的 AI 驱动的搜索引擎 Perplexica 并实现外部访问
  • 【黑马SpringCloud微服务开发与实战】(三)微服务01
  • 快速上手AI整合包!GPT-SoVITS-v2打包教程,解锁AIStarter应用市场潜力
  • freertos任务调度关键函数理解
  • 笔试强训——第一周
  • 标准文件和系统文件I/O
  • 鸿蒙与web混合开发双向通信
  • 云服务器磁盘IO性能优化的测试与配置方法
  • docker|Linux|以centos基础镜像为基础制作nmap专用镜像(镜像瘦身计划)
  • SQL基础操作指南:约束、表设计与复杂查询
  • 【RK3576】【Android14】USB开发调试
  • install_arm_docker.sh
  • 【Qt开发】Qt的背景介绍(三)-> 认识Qt Creator
  • python网络爬虫之selenium库(二)
  • Android回调机制入门
  • 工程图矢量化 笔记 | potrace ezdxf svgpathtools | png转svg保存dxf用matplotlib画出来
  • 基于springboot的考研互助小程序
  • Redis的持久化-RDB
  • 【橘子分布式】gRPC(编程篇-中)
  • 基于开放API接口采集的定制开发开源AI智能名片S2B2C商城小程序数据整合与增长策略研究
  • 通过phpStudy,用nginx部署vue项目,支持部署多套vue项目(详细教程)
  • LLM 的Top-P参数 是在LLM中的每一层发挥作用,还是最后一层?
  • CSS-in-JSVue的解决方案
  • 将HTML+JS+CSS数独游戏包装为安卓App