一、先说结果:61 行装配代码,换来九页能用的流程中心
我在本机开了一个空白 Vite + Vue3 工程,只装两个包,写了两个文件,跑出来是这样:
- 九页:工作台、发起申请、待办审批、已办、我的申请、抄送我的、流程定义、流程设计、委托代理
- 三个抽屉:发起、办理、实例详情(详情里带流程图和审批记录两个 tab)
- 真数据 :工作台那八枚数字卡、近 7 天趋势、流程 Top 5、状态分布,全是后端
stats三个接口回来的,不是占位骨架 - 全程没写一行 CSS------包里带
style.css,宿主 import 一次就完事
装配代码一共 61 行(main.js 29 行 + App.vue 32 行),加上两张业务表单是 112 行。这篇讲三件事:账是多少 、表单这条腿为什么包不能替你写完 、一个 baseUrl 换八套后端会发生什么。

二、把账摆出来:装包、体积、导出面
装包。 干净工程里:
bash
npm i vue # 23 个包
npm i @mldong/jeeflow-ui # 再加 2 个包
那 2 个包是组件包本体,和它的一个 peer(钉钉风格设计器 mldong-flow-designer-dingtalk,流程设计页要用)。包自己的 dependencies 是空的 ------npm ls 里它对 vue 和设计器都显示 deduped,也就是全部骑在宿主已有的那份上,没有第二份 vue。
体积。 发布物解包 584,523 字节(约 571 KB),tarball 133,409 字节。真正进浏览器的是两个文件:
| 文件 | 字节 |
|---|---|
dist/jeeflow-ui.js(ESM) |
272,094 |
dist/style.css |
54,993 |
导出面。 从发布物的 dist/index.d.ts 数出来的组件导出是 21 个,分四类:
- 整页 9 个:工作台、发起申请、待办审批、已办、我的申请、抄送我的、流程定义、流程设计、委托代理
- 抽屉 3 个:发起、办理、实例详情
- 布局 1 个 :
JfLayout(顶部 + 左侧菜单 + 内容区) - 基础件 8 个:抽屉壳、徽标、流程图查看器、选人器、图标、tab、审批记录、发起附加区
它替你吃掉了多少接口。 包里 api.ts 一共调用统一门面的 42 个 action ------processDefine/* 8 个、processInstance/* 14 个(含 stats 三个)、processTask/* 10 个、processDesign/* 8 个、processSurrogate/* 3 个(有个 action 跨两组)。这就是"九页能直接用"的全部来源:接口形状、分页参数、错误码判断都在包里,宿主的 REST 层一行不用写。

三、装配就三块:注入配置、给菜单、注册表单
main.js------把引擎地址和"我是谁"注入进去:
js
import { createApp } from 'vue'
import App from './App.vue'
import '@mldong/jeeflow-ui/dist/style.css'
import { createJeeflowUi, JeeflowUiKey }
from '@mldong/jeeflow-ui'
import ApplyForm from './forms/apply-form.vue'
import LeaveForm from './forms/leave-form.vue'
const jeeflowUi = createJeeflowUi({
// 传函数:每次请求现取,切后端不 reload 也不重建实例
baseUrl: () => localStorage.getItem('jf_base') || '/java-api',
getOperator: () => localStorage.getItem('op') || 'user1',
getToken: () => null, // 有登录态则发 Bearer
hasPermission: () => true, // 按 wf:{action} 权限码控显隐
adapters: { // 选人/角色/字典/上传,缺则降级
listUsers: async (kw) =>
USERS.filter((u) => !kw || u.realName.includes(kw)),
getUsersByIds: async (ids) =>
USERS.filter((u) => ids.includes(u.userId)),
},
})
jeeflowUi.registerForm('apply-form', ApplyForm)
jeeflowUi.registerForm('leave-form', LeaveForm,
{ scenes: ['approve'] })
createApp(App).provide(JeeflowUiKey, jeeflowUi).mount('#app')
App.vue------菜单项里给 key/title/icon,内容区渲染对应页面:
vue
<template>
<JfLayout :menus="menus" title="我的系统 · 流程中心"
:default-key="key" @select="(k) => (key = k)">
<component :is="pages[key]" :key="key" />
</JfLayout>
</template>
<script setup>
import { ref } from 'vue'
import { JfLayout, JfWorkbenchPage, JfApplyListPage,
/* 余七页同名规则略 */ } from '@mldong/jeeflow-ui'
const pages = {
workbench: JfWorkbenchPage, apply: JfApplyListPage,
}
const menus = [
{ key: 'workbench', title: '工作台', icon: 'home' },
{ key: 'apply', title: '发起申请', icon: 'apply' },
/* 余七项略 */
]
const key = ref('workbench')
</script>
两个容易漏的点,都是我这次真撞上的:
- 样式要手工 import 。
import '@mldong/jeeflow-ui/dist/style.css'这一行不能省------ESM 产物里没有内联样式的副作用导入,漏了就是九页裸奔(没背景、没间距、表格糊在一起)。 baseUrl建议传函数而不是字符串。传字符串时地址被固定在创建那一刻;传函数则每次请求现取,切后端、切租户只改 localStorage,页面不 reload。
四、表单这条腿,包真的不替你写
审批界面里唯一没法通用的就是业务表单------请假单和报销单长得就不一样。包的边界是:表单组件由宿主注册,formKey 是从流程定义里读出来的。
我故意先什么都不注册,看看会怎样。办理抽屉打开,"申请信息"下面是这么一句话:
css
未注册办理表单「leave-form」,宿主 registerForm 后即可填写
发起抽屉则是另一档:
未配置表单字段,可直接发起
没有整段静默消失。这很重要------表单没回显时,"数据没传回来"和"宿主没注册组件"是两种完全不同的排查方向,可读提示直接把后者点名了。
补上注册之后(第三节那两行 registerForm),办理抽屉变成这样:上面是只读的"申请信息"(请假事由、请假天数),中间是我自己写的"核准天数"输入框,下面是审批意见和六个动作按钮(同意/拒绝/退回上一步/退回发起人/跳转/转办)。
表单组件的契约就三条:
- props 收
modelValue(一个普通对象)+view(true 为只读明细) - 字段名带前缀:申请级
f_、办理级tf_------引擎按这个前缀把数据往返到实例变量和任务表单数据 - 建议
defineExpose({ validate }),返回错误文案或null,包会在提交前调用
一个 5 字段、发起/详情两态的表单,我写了 33 行;办理侧只填一个字段,18 行。这部分成本谁也省不掉,任何一套审批前端都一样,只是包不假装能替你写。

五、闭环真跑了一遍:同一张单从发起到办结
拿同一张请假单走完整条链,每一步都留了读数:
- 发起 :张三(
user1)在"发起申请"点卡片、填事由和天数、点发起 ------ 提示「发起成功」,新实例 id94012933769322,state=10 - 定位 :切到李四(
leader),在"待办审批"里找到这张单,点办理 ------ 抽屉里"申请信息"回显的事由与发起时逐字一致 - 办理:填核准天数 2.5、写审批意见,点同意 ------ 提示「办理成功」
- 复核 :回查同一个实例 ------
state=20(已办结),审批记录 2 条,两个节点时间都是 12:51 - 看图:详情抽屉切"流程图"tab ------ 四节点全绿(已完成),处理人显示姓名不是 id
第 5 步那张图值得单看:流程图是包里的 JfFlowViewer 画的,它吃的是 processInstance/highLight 的返回------节点上那些绿框、勾、姓名,都是接口回来现画的,宿主一行绘图代码没写。

六、一个 baseUrl 换八套后端:六通、两台被 CORS 拦
因为数据层只依赖统一门面(POST {baseUrl}/wf/{action}),我把 baseUrl 换成演示站八种语言的后端,前端代码一个字没改:
| 后端 | 跨域直连 | 九页渲染 | 待办行数 |
|---|---|---|---|
| Java | 通 | 正常 | 6 |
| Go | 通 | 正常 | 6 |
| Node.js | 通 | 正常 | 6 |
| Python | 通 | 正常 | 6 |
| PHP | 通 | 正常 | 5 |
| C# | 通 | 正常 | 6 |
| Rust | 不通 | 空态 | 0 |
| MoonBit | 不通 | 空态 | 0 |
rust 和 moon 那两格红得很具体:预检请求 OPTIONS 一个回 405、一个回 204 但没带 Access-Control-Allow-Origin,浏览器直接把后续 fetch 拦掉。这是演示站网关配置的差异,不是引擎或组件的问题------自己部署时前端和后端同域(vite proxy 或 nginx 反代),这两台一样能用。我把它记下来是因为它会误导第一次试的人:页面全空、控制台一片红,看着像包坏了。
六套通的后端之间也有出口形状的差异,页面都容忍了:Go 和 Rust 的信封没有 msg 字段,Go 的 createTime 是 0001-01-01... 这种零值形状,页面照样把列表排出来。
七、哪些页开箱即用、哪些还要后端配合
九页不是同一档,说清楚:
- 八页开箱即用:工作台、发起申请、待办、已办、我的申请、抄送我的、流程定义、委托代理------只要后端实现了门面这些 action。
- 流程设计页要扩展仓储 。它读的是"设计稿"(
processDesign/*,落在wf_process_design表),演示站的 Python 后端只预置了已发布定义、没注册扩展仓储,所以这页是空态「暂无流程设计」,包里的注释也写了这一档:后端未注册扩展仓储时processDesign/*不可用。 - 发起申请页有兜底 。它先问
processDesign/listByType,演示站回的是空对象{},于是回落到"已发布定义"列表------所以我截图里那 16 张流程卡来自定义表而不是设计稿表。 - 委托页那一格显示的是 id 。我的委托列表里"被委托人"是
manager这个字符串,这一页没做 id→姓名 的解析(其他页做了)。
八、什么时候用它,什么时候别用
- 该用它:Vue3 工程 + 任意语言后端,要快速有整套审批界面 ------ 九页加三抽屉直接拿
- 该用它:后端只实现了门面那 40 多个 action ------ 这是它唯一的接口依赖
- 谨慎:已有自己的设计语言(antd / element 深度定制)------ 它自带一套朴素样式、不依赖任何 UI 库,但改视觉得覆盖 CSS 变量
- 只取零件:只要流程图或审批记录那几块 ------ 可以只引查看器和记录组件,不必上整套九页
- 换路子 :要把流程中心嵌进非 Vue 系统(若依、老 JSP)------ 走 iframe 壳那条路(仓库里的
apps/embed),别在老页面里引 Vue - 不适用:需要移动端 ------ 移动端是另一件东西(uni-app x 的审批端),和这个包共用同一套门面
包和仓库都是 Apache-2.0,npm 装得到,GitHub 也开着。
参考资料(发布版保留纯文本链接)
- npm 包:www.npmjs.com/package/@ml...
- 组件仓库:github.com/mldong/jeef...
- 引擎统一门面规范(42 个 action 的契约):jeeflow-doc.mldong.com/spec/06-fac...
- 开源演示站(八语言在线体验):jeeflow-demo.mldong.com
- 集成演示站(十三套框架后端):jeeflow-pro.mldong.com
- 钉钉风格流程设计器 npm 包:mldong-flow-designer-dingtalk