简介这份资源面向Web应用开发与规则引擎方向的前端及全栈开发者提供一套基于LiteFlow规则引擎、借助LogicFlow流程图编辑库搭建的可视化流程配置页面。用户可在界面上拖拽节点、连线并定义规则配置结果以JSON格式输出交由后端解析并生成LiteFlow所需的EL表达式从而把复杂业务逻辑转化为可执行流程降低非技术人员理解与维护的门槛。压缩包共22个文件约180KB以9个TypeScript源码、2个Vue组件、4个JSON配置为主另含少量图片与HTML入口文件整体结构轻量便于快速集成到现有Vue工程中。目前已有1497人学习下载。通过该示例读者可掌握前端可视化配置与后端规则引擎协同的完整思路理解JSON到EL的转换链路并参考其目录组织与组件拆分方式为自建低代码流程编排或规则配置平台提供可复用的实践模板。1. 规则引擎前端配置页把 LiteFlow 的 EL 表达式从手写里解放出来如果你维护过 LiteFlow 的规则链大概率经历过这种场景业务方要加一个「满减叠加优惠券」的分支你打开 XML 或 EL 文件对着THEN(A, B, WHEN(C, D))这种嵌套结构改了半天改完还得让后端重启验证。一个逗号位置错了整条链路跑不起来排查半小时。这份资源要解决的正是这个痛点——它是一套基于 LiteFlow 规则引擎、用 LogicFlow 前端架构搭起来的可视化配置页面你在页面上拖拽节点、连线、配参数页面直接吐出 JSON后端拿到 JSON 再生成 EL 表达式。适合谁正在做规则引擎落地、又不想让业务人员碰代码的后端和前端工程师。核心价值就一句话把 EL 的编写门槛从「懂语法」降到「会画图」。2. 拆开这套配置页LogicFlow 画布与 LiteFlow 语义怎么对齐2.1 为什么选 LogicFlow 而不是手搓 CanvasLogicFlow 是滴滴开源的流程图编辑框架它把节点、边、锚点、对齐线这些交互细节都封装好了你只需要定义业务节点和 JSON 序列化规则。常见做法是直接用它提供的register方法注册自定义节点再用getGraphData()拿到画布数据。相比自己用 Canvas 或 SVG 从零画省掉的是拖拽吸附、撤销重做、缩放平移这些「不做难受、做了费时」的交互层。更关键的是LogicFlow 的数据结构天然适合转成 LiteFlow 需要的树形或链式结构——节点有 id、type、properties边有 sourceNodeId、targetNodeId这些字段直接对应 EL 里的组件标识和顺序关系。我一般会先定义一套节点类型和 LiteFlow 的组件类型一一映射LogicFlow 节点 typeLiteFlow 语义对应 EL 片段common普通执行组件Aif条件判断IF(x, a, b)switch选择器SWITCH(s).to(a, b)when并行执行WHEN(a, b)then串行执行THEN(a, b)for循环FOR(n).DO(a)这张表是前后端约定的「字典」页面配置时每个节点携带的type和properties必须能唯一映射到 EL 的某个语法结构。比如if节点至少要有condition、trueBranch、falseBranch三个属性否则后端生成 EL 时不知道往哪填。2.2 从画布到 JSON节点数据怎么组织LogicFlow 默认输出的图数据是扁平的节点数组和边数组但 LiteFlow 的 EL 是嵌套的树形结构。所以中间需要一层转换把扁平图转成以「执行顺序」为线索的树。常见做法是先从起始节点出发做深度优先遍历遇到分支节点就递归处理子分支最后拼成嵌套对象。下面是一个转换函数的骨架我把它放在前端utils/graphToJson.js里// 将 LogicFlow 的图数据转为 LiteFlow 可解析的嵌套 JSON function graphToLiteFlowJson(graphData) { const { nodes, edges } graphData; // 建立节点 id 到节点对象的映射方便 O(1) 查找 const nodeMap new Map(nodes.map(n [n.id, n])); // 建立邻接表sourceNodeId - [targetNodeId, ...] const adjacency new Map(); edges.forEach(e { if (!adjacency.has(e.sourceNodeId)) adjacency.set(e.sourceNodeId, []); adjacency.get(e.sourceNodeId).push(e.targetNodeId); }); // 从起始节点开始递归构建 function buildNode(nodeId) { const node nodeMap.get(nodeId); if (!node) return null; const children adjacency.get(nodeId) || []; const base { id: node.id, type: node.type, props: node.properties || {} }; // 根据节点类型决定子节点如何挂载 switch (node.type) { case if: // 条件节点第一个子节点为 true 分支第二个为 false 分支 base.trueBranch children[0] ? buildNode(children[0]) : null; base.falseBranch children[1] ? buildNode(children[1]) : null; break; case switch: // 选择器所有子节点按顺序作为分支 base.branches children.map(cid buildNode(cid)).filter(Boolean); break; case when: case then: // 并行/串行子节点按顺序排列 base.children children.map(cid buildNode(cid)).filter(Boolean); break; default: // 普通组件只保留自身子节点继续往下串 base.next children[0] ? buildNode(children[0]) : null; } return base; } // 找到没有入边的节点作为起始节点 const targetIds new Set(edges.map(e e.targetNodeId)); const startNode nodes.find(n !targetIds.has(n.id)); return startNode ? buildNode(startNode.id) : null; }这段代码的逻辑说明先建映射和邻接表把图结构转成可快速查询的形式然后从起始节点递归根据节点类型决定子节点挂到哪个字段。参数方面graphData就是 LogicFlow 的getGraphData()返回值nodes里每个节点必须带type和propertiesedges里每条边必须有sourceNodeId和targetNodeId。容易翻车的地方是起始节点判定——如果图里有环或者多个无入边节点这里会选错所以我在页面里加了「设为起点」的右键菜单让用户手动指定。2.3 后端拿到 JSON 后怎么生成 EL前端吐出的 JSON 是中间态后端需要把它翻译成 LiteFlow 的 EL 字符串。常见做法是写一个递归的 EL 生成器根据节点 type 拼接不同的语法片段。比如then节点生成THEN(child1, child2, ...)if节点生成IF(condition, trueBranch, falseBranch)。这里有个细节LiteFlow 的 EL 里组件标识必须是已注册的组件名所以 JSON 里的props.componentId要提前和后端组件注册表对齐否则生成出来的 EL 跑起来会报「组件未找到」。我一般会在后端加一层校验生成 EL 之前先遍历 JSON检查每个componentId是否在注册表里不在就直接返回错误避免把问题留到运行期。这个校验接口前端在保存配置时就会调用相当于一道「后悔药」。3. 把配置页跑起来环境、依赖与最小可运行示例3.1 前端工程初始化与 LogicFlow 接入这套配置页的前端部分基于 LogicFlow 官方包常见做法是用 Vite 或 Webpack 起一个 Vue/React 工程然后安装logicflow/core和logicflow/extension。下面以 Vue 3 Vite 为例给出最小接入步骤# 创建工程如果已有工程可跳过 npm create vitelatest liteflow-designer -- --template vue cd liteflow-designer # 安装 LogicFlow 核心包和扩展包 npm install logicflow/core logicflow/extension # 安装一个轻量状态管理用于保存画布数据 npm install pinia安装完成后在组件里初始化画布import LogicFlow from logicflow/core; import logicflow/core/dist/style/index.css; import { Menu, SelectionSelect } from logicflow/extension; // 注册扩展插件右键菜单和框选 LogicFlow.use(Menu); LogicFlow.use(SelectionSelect); export function initCanvas(container) { const lf new LogicFlow({ container, grid: true, // 显示网格方便对齐 snapline: true, // 开启对齐线 adjustEdge: true, // 允许调整连线 edgeType: polyline, // 折线更贴近 EL 的串行语义 style: { nodeText: { overflowMode: autoWrap } } }); // 注册自定义节点这里以「普通组件」为例 lf.register(common, ({ RectNode, RectNodeModel }) { class CommonNodeModel extends RectNodeModel { setAttributes() { this.width 120; this.height 48; this.radius 6; } } class CommonNode extends RectNode { getShape() { const { x, y, width, height, radius } this.props.model; return { x, y, width, height, radius, // 实际渲染时用 h 函数或模板这里只示意结构 }; } } return { view: CommonNode, model: CommonNodeModel }; }); lf.render(); // 空画布启动 return lf; }逻辑说明LogicFlow.use注册的是全局扩展Menu提供右键菜单SelectionSelect提供框选。new LogicFlow的配置项里grid和snapline是提升配置体验的关键edgeType选polyline是因为 EL 的串行关系用折线表达更直观。lf.register注册的节点类型名common必须和前面表格里的映射一致。参数方面width和height控制节点尺寸radius是圆角这些按你的 UI 规范调即可。3.2 节点面板与属性配置区画布只是容器真正让业务人员能用起来的是左侧节点面板和右侧属性面板。节点面板常见做法是用一个数组驱动每个节点项包含type、label、icon拖拽时调用lf.dnd.startDrag把节点类型传过去。属性面板则监听画布的node:click事件拿到当前选中节点的properties渲染成表单。下面是一个属性面板的简化实现// 监听节点点击把节点数据同步到属性面板 lf.on(node:click, ({ data }) { currentSelectedNode.value data; // 根据节点类型决定显示哪些表单项 const schemaMap { common: [componentId, tag], if: [condition, componentId], switch: [selectorId, branches], when: [componentIds], then: [componentIds] }; formSchema.value schemaMap[data.type] || []; }); // 属性变更时回写节点数据 function updateNodeProperty(key, value) { const node lf.getNodeModelById(currentSelectedNode.value.id); node.setProperties({ [key]: value }); // 触发画布数据变更便于自动保存 lf.emit(graph:updated); }逻辑说明node:click事件里拿到的data是节点完整数据包括properties。schemaMap定义了每种节点类型需要哪些配置项这样属性面板不用写一堆v-if。setProperties是 LogicFlow 提供的 API回写后画布上的节点文本会同步更新。参数方面componentId对应 LiteFlow 组件注册名condition是条件表达式字符串branches是分支标识数组。注意setProperties是浅合并如果某个属性是对象要整体替换而不是只传子字段。3.3 导出 JSON 与后端联调配置完成后页面需要提供一个「导出」按钮把画布数据转成前面说的嵌套 JSON然后发给后端。常见做法是导出时先做一轮前端校验检查是否有孤立节点、是否有未配置componentId的节点、是否有环。校验通过后再调后端接口。async function exportAndValidate() { const graphData lf.getGraphData(); const liteFlowJson graphToLiteFlowJson(graphData); // 前端基础校验 const errors []; if (!liteFlowJson) errors.push(未找到起始节点); // 递归检查每个节点是否配置了 componentId function checkNode(node) { if (!node) return; if (node.type common !node.props.componentId) { errors.push(节点 ${node.id} 未配置组件标识); } // 递归子节点... } checkNode(liteFlowJson); if (errors.length) { return { ok: false, errors }; } // 调后端接口后端返回生成的 EL 和校验结果 const res await fetch(/api/liteflow/generate-el, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(liteFlowJson) }); return res.json(); }逻辑说明getGraphData()拿到的是 LogicFlow 原始数据graphToLiteFlowJson做结构转换然后前端先做一轮「必填项」校验减少无效请求。后端接口/api/liteflow/generate-el接收 JSON返回{ el: ..., errors: [] }。参数方面请求体就是嵌套 JSON后端需要根据type字段递归生成 EL。联调时最容易出问题的是字段命名不一致——前端用componentId后端可能习惯用componentName这个要在接口文档里锁死。4. 避坑与排查配置页落地时最容易翻车的五个点4.1 节点连线顺序与 EL 执行顺序不一致现象页面上画的顺序是 A → B → C生成的 EL 却是THEN(C, A, B)执行结果和预期不符。原因LogicFlow 的edges数组顺序不保证和视觉顺序一致如果转换函数直接按edges遍历顺序取子节点就会乱序。另外如果用户先画了 C 再拖 A 和 B 连线节点在nodes数组里的顺序也会影响遍历结果。解决转换时不要依赖数组顺序而是从起始节点出发按sourceNodeId找targetNodeId再以目标节点为起点继续找形成严格的链式遍历。对于分支节点用锚点位置或连线时的properties.order来排序。我一般会在连线上加一个order属性用户拖拽时自动按从左到右、从上到下赋值。4.2 条件表达式里的变量名和后端对不上现象页面里条件配的是order.amount 100后端生成 EL 后运行报「变量 order 不存在」。原因前端配置时用的是业务语义的变量名后端 LiteFlow 上下文里实际存的可能是orderDTO或orderInfo。两边没有统一的变量字典。解决在配置页里加一个「变量映射」配置项或者直接让后端提供一个变量列表接口前端属性面板里用下拉选择而不是手输。如果必须手输就在保存时调后端校验接口让后端解析表达式并检查变量是否存在。4.3 循环节点嵌套层级过深导致 EL 生成栈溢出现象配置了一个三层嵌套的FOR循环点击导出时页面卡死或后端返回 500。原因递归生成 EL 时没有限制深度或者 JSON 结构里出现了循环引用比如 A 的 next 指向 BB 的 next 又指回 A。解决转换函数里加一个visited集合检测到重复节点就抛错同时限制最大嵌套深度比如 5 层超过就提示用户拆分规则链。后端生成 EL 时也要做同样的深度校验不能只依赖前端。4.4 画布数据自动保存覆盖了未完成的配置现象用户拖了节点还没连线刷新页面后发现节点没了或者连了一半的线消失了。原因自动保存策略太激进每次graph:updated都全量覆盖后端草稿而草稿里可能包含不完整的图数据。解决自动保存分两级——本地localStorage存实时草稿后端只存用户手动点「保存」的版本。恢复时优先读本地草稿并提示「检测到未保存的编辑是否恢复」。这样即使后端草稿是旧的用户也不会丢工作。4.5 组件标识大小写敏感导致 EL 解析失败现象页面里配的componentId是orderCheck后端注册的组件名是OrderCheck生成的 EL 跑起来报「组件未找到」。原因LiteFlow 的组件标识默认大小写敏感而前端输入框没有做统一大小写处理。解决在属性面板的componentId输入框上加toLowerCase或toUpperCase的格式化或者直接改成下拉选择选项来自后端注册表接口。如果必须手输就在失焦时调一次后端校验实时提示「组件不存在」。5. 进阶技巧用配置页做规则链的版本对比与回滚配置页跑通之后我习惯再加一个「版本快照」功能。每次用户点保存后端除了存当前 JSON还会存一份带时间戳的快照。页面上提供一个版本列表选中两个版本可以并排对比 JSON 差异或者直接回滚到某个历史版本。这个功能的实现不复杂后端加一张rule_snapshot表字段包括rule_id、version、json_content、created_at前端加一个抽屉组件用diff库渲染差异。更实用的是「模拟执行」——在配置页上直接调后端的试跑接口传入一组模拟参数后端用 LiteFlow 的execute方法跑一遍当前 JSON 生成的 EL把每个节点的执行结果和耗时返回给前端在画布上用颜色标注成功/失败/跳过。这样业务人员不用等发版就能验证规则对不对。我踩过最深的一个坑是早期版本没有做「组件注册表同步」前端配置页里手输的组件名和后端实际注册的组件名对不上导致生成的 EL 在测试环境跑得好好的上生产就报错。从那以后我每次改配置页的组件选择逻辑都强制走一遍「后端注册表接口拉取 → 前端下拉渲染 → 保存时二次校验」的流程再也不敢让用户手输组件名了。希望这套思路帮到你少走点我走过的弯路。本文还有配套的精品资源点击获取