uni-flow-design
v1.0.1
Published
A zero-API-dependency flow designer component library for Vue, React and plain web apps.
Maintainers
Readme
uni-flow-design
一个零接口依赖的流程设计器组件库。宿主只需要提供 DOM 容器,即可通过 new UniFlowDesign(container, options) 挂载流程画布;节点设置抽屉、流程属性、保存接口由宿主应用自行实现。
README 会作为 npm 包详情页的主要展示内容。发布到 npm 前建议保持这份文档和 dist/module.d.ts 一致。
特性
- 基于 Vue 3 和 ant-design-vue,产物以 library mode 输出。
- 可在 Vue、React 或普通 Web 页面中使用。
- 支持开始、审批、抄送、处理、条件分支节点。
- 支持缩放、全屏、节点增删、条件分支增删。
- 点击节点只抛出
node-click,宿主自行渲染设置抽屉。 - 支持
flowOptions控制可新增节点类型、最小节点数量、节点展示文案。 - 导出完整 TypeScript 类型和工具方法。
安装
pnpm add uni-flow-design宿主还需要安装 peer dependencies:
pnpm add vue@^3.2.25 ant-design-vue@^4 @ant-design/icons-vue@^7.0.1 dayjs@^1.11.13 lodash@^4.17.21宿主入口需要引入样式(antdv 4 基础样式 reset.css + 本组件样式):
import 'ant-design-vue/dist/reset.css';
import 'uni-flow-design/dist/style.css';antdv 4 采用 CSS-in-JS,已移除 antdv 3 的
antd.variable.min.css。uiOptions.primaryColor会写入挂载根的--ant-primary-color,画布自身样式直接消费它(与 antdv 版本无关);组件内嵌的 antdv 控件主色若需跟随,宿主可用<a-config-provider :theme="{ token: { colorPrimary } }">统一配置。
快速开始
import { UniFlowDesign } from 'uni-flow-design';
import 'ant-design-vue/dist/reset.css';
import 'uni-flow-design/dist/style.css';
const designer = new UniFlowDesign('#flow-root', {
uiOptions: {
primaryColor: '#1677ff'
}
});
designer.on('node-click', (payload) => {
// 宿主在这里打开自己的设置抽屉
// 抽屉保存后调用 designer.updateNode(payload.uid, editedNode)
});
function onSave() {
if (!designer.validate()) {
designer.markInvalid();
return;
}
const flow = designer.getValue();
// 调用宿主自己的保存接口
}
// 页面销毁时调用
designer.unmount();页面容器需要有明确高度:
<div id="flow-root" style="height: 100vh"></div>Vue 示例
<template>
<div style="display:flex;flex-direction:column;height:100vh">
<div style="padding:12px;text-align:right">
<button @click="onSave">保存</button>
</div>
<div ref="root" style="flex:1;min-height:0"></div>
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue';
import { UniFlowDesign, NodeType, type NodeClickPayload, type FlowNode } from 'uni-flow-design';
import 'ant-design-vue/dist/reset.css';
import 'uni-flow-design/dist/style.css';
const root = ref<HTMLElement | null>(null);
let designer: UniFlowDesign | undefined;
onMounted(() => {
if (!root.value) return;
designer = new UniFlowDesign(root.value, {
flowOptions: {
addableNodeTypes: [NodeType.Approval, NodeType.Cc, NodeType.Transactor, NodeType.Condition],
display: {
startLabel: '发起人'
}
}
});
designer.on('node-click', (payload: NodeClickPayload) => {
openNodeDrawer(payload, (editedNode: FlowNode) => {
designer?.updateNode(payload.uid, editedNode);
});
});
});
function onSave() {
if (!designer?.validate()) {
designer?.markInvalid();
return;
}
const flow = designer.getValue();
}
onBeforeUnmount(() => designer?.unmount());
</script>React 示例
import { useEffect, useRef } from 'react';
import { UniFlowDesign } from 'uni-flow-design';
import 'ant-design-vue/dist/reset.css';
import 'uni-flow-design/dist/style.css';
export default function FlowPage() {
const rootRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (!rootRef.current) return;
const designer = new UniFlowDesign(rootRef.current, {});
designer.on('node-click', (payload) => {
// 打开宿主自己的节点设置面板
});
return () => designer.unmount();
}, []);
return <div ref={rootRef} style={{ height: '100vh' }} />;
}参数
export interface UniFlowDesignProps {
defaultValue?: FlowNode | string;
isPreviewStatus?: boolean;
flowOptions?: FlowOptions;
uiOptions?: FlowUiOptions;
}
export interface FlowUiOptions {
primaryColor?: string;
theme?: 'light' | 'dark';
}| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| defaultValue | FlowNode \| string | 默认开始节点 | 初始流程 JSON,可传对象或 JSON 字符串 |
| isPreviewStatus | boolean | false | 预览态;为 true 时隐藏编辑入口并禁用节点点击 |
| flowOptions | FlowOptions | 见下方 | 画布能力与展示配置 |
| uiOptions | FlowUiOptions | 见下方 | UI 配置,例如主题色、明暗主题 |
uiOptions
| 参数 | 默认值 | 说明 |
| --- | --- | --- |
| primaryColor | #40a9ff | 主题主色,写入挂载根 --ant-primary-color 驱动画布主色(antdv 4 控件主色建议宿主用 ConfigProvider theme 配置) |
| theme | 'light' | 画布明暗主题初始值,运行时可用 setTheme 切换 |
flowOptions
节点类型统一用 NodeType 枚举表示:
export enum NodeType {
Launch = 'LAUNCH', // 发起 / 开始
Approval = 'APPROVAL', // 审批
Cc = 'CC', // 抄送
Transactor = 'TRANSACTOR', // 处理
Condition = 'CONDITION', // 条件分支
ConditionTerm = 'CONDITION_TERM' // 条件项(条件分支内的一条条件)
}
export interface FlowOptions {
addableNodeTypes?: AddableNodeKind[];
minNodeCounts?: Partial<Record<AddableNodeKind, number>>;
display?: FlowDisplayOptions;
}
// 可新增的节点类型(不含发起节点)
export type AddableNodeKind =
| NodeType.Approval
| NodeType.Cc
| NodeType.Transactor
| NodeType.Condition;
export interface FlowDisplayOptions {
startLabel?: string;
}| 参数 | 说明 |
| --- | --- |
| addableNodeTypes | 允许新增的节点类型。可选 NodeType.Approval 审批、NodeType.Cc 抄送、NodeType.Transactor 处理、NodeType.Condition 条件分支。默认全部允许 |
| minNodeCounts | 节点最小保留数量,例如 { [NodeType.Approval]: 1 } 表示至少保留 1 个审批节点 |
| display.startLabel | 开始节点显示文案,默认 发起人 |
节点标题的取值规则:
- 开始节点(
NodeType.Launch)始终显示display.startLabel。 - 其余节点统一显示节点自身的
name。新建节点的默认name为内置文案(审批 / 抄送 / 处理 / 条件判断),改名通过setNodeName(uid, name)写入node.name即可反映到画布。
示例:只允许新增审批节点,并至少保留一个审批节点:
new UniFlowDesign('#flow-root', {
flowOptions: {
addableNodeTypes: [NodeType.Approval],
minNodeCounts: { [NodeType.Approval]: 1 },
display: {
startLabel: '发起'
}
}
});事件
designer.on('node-click', (payload) => {});export interface NodeClickPayload {
node: FlowNode;
nodeConfigAll: FlowNode;
type: FlowNodeKind;
uid: FlowId;
conditionTermIndex?: number;
}| 字段 | 说明 |
| --- | --- |
| node | 被点击节点的副本 |
| nodeConfigAll | 当前整棵流程树 |
| type | 节点类型(NodeType):Launch 开始,Approval 审批,Cc 抄送,Transactor 处理,Condition 条件分支 |
| uid | 节点唯一标识,写回节点时使用 |
| conditionTermIndex | 仅条件分支有效,表示点击的是第几个条件项 |
在抽屉里设置节点名称
监听 node-click 打开业务抽屉,确定时调用 setNodeName(uid, name) 写回,画布标题即显示该 name(开始节点除外,始终显示 startLabel):
const designer = new UniFlowDesign('#flow-root');
designer.on('node-click', (payload) => {
// 打开宿主自己的抽屉,初始值取 payload.node.name
openDrawer(payload.node.name, (newName: string) => {
designer.setNodeName(payload.uid, newName); // 确定保存,画布即时更新
});
});实例方法
| 方法 | 返回值 | 说明 |
| --- | --- | --- |
| on('node-click', fn) | void | 监听节点点击 |
| getValue() | FlowNode | 获取当前画布流程树 |
| setValue(value) | unknown | 整树覆盖当前画布 |
| updateNode(uid, node) | unknown | 按 uid 写回宿主抽屉编辑后的节点 |
| setNodeName(uid, name) | boolean | 按 uid 更新节点名称,命中返回 true。画布标题即显示该 name(开始节点除外) |
| setTheme(mode) | unknown | 运行时切换明暗主题,mode 取 'light' 或 'dark',画布即时变色 |
| validate() | boolean | 校验流程是否配置完整,true 表示通过 |
| markInvalid(show?) | unknown | 高亮未配置完整的节点 |
| unmount() | void | 卸载组件实例 |
明暗主题
构造时用 theme 设初始值,页面里的明暗开关调用 setTheme(mode) 即时切换,无需重建实例:
const designer = new UniFlowDesign('#flow-root', { theme: 'light' });
// 页面开关
themeSwitch.onChange((checked) => {
designer.setTheme(checked ? 'dark' : 'light');
});画布所有可变颜色都走 --uf-* CSS 变量(定义在挂载根元素上),需要微调配色时,可在宿主样式里覆盖:
/* 覆盖暗色画布背景与节点卡片色 */
.uni-flow-design[data-theme="dark"] {
--uf-canvas-bg: #18181b;
--uf-node-bg: #27272a;
}常用变量:--uf-canvas-bg(画布背景)、--uf-node-bg(节点卡片)、--uf-text / --uf-text-secondary(文字)、--uf-line(连线)、--uf-border(分隔线)、--uf-approve-bg / --uf-handle-bg / --uf-send-bg(审批/处理/抄送节点底色)。
antd 的抽屉、下拉、tooltip 等组件由宿主提供,其暗色样式需宿主自行配置(如 antd 的
theme.darkAlgorithm);本组件的theme只负责画布自身。
工具方法
import {
NodeType,
createDefaultFlow,
flowCheckIsSetting,
flowTransformData,
flowTransToXML
} from 'uni-flow-design';| 导出 | 说明 |
| --- | --- |
| NodeType | 节点类型枚举(字符串值),用于 addableNodeTypes、payload.type 比较等 |
| createDefaultFlow() | 生成默认开始节点流程 |
| flowCheckIsSetting(flow) | 检查流程是否存在未配置项 |
| flowTransformData(flow) | 保存前转换流程数据,补齐流转关系 |
| flowTransToXML(flow) | 将流程 JSON 转为 Flowable XML |
数据类型
包内导出常用类型:
import type {
FlowId,
FlowNode,
FlowNodeKind,
FlowOptions,
FlowUiOptions,
FlowUser,
AddableNodeKind,
FlowDisplayOptions
} from 'uni-flow-design';UMD 使用
UMD 产物不会打包 peer dependencies。使用前需要先准备这些全局变量:
VueantdIconsVuedayjs_
<link rel="stylesheet" href="https://unpkg.com/ant-design-vue@4/dist/reset.css" />
<link rel="stylesheet" href="./dist/style.css" />
<div id="flow-root" style="height: 100vh"></div>
<script src="https://unpkg.com/[email protected]/dist/vue.global.prod.js"></script>
<script src="https://unpkg.com/dayjs@1/dayjs.min.js"></script>
<script src="https://unpkg.com/lodash@4/lodash.min.js"></script>
<script src="https://unpkg.com/ant-design-vue@4/dist/antd.min.js"></script>
<script src="https://unpkg.com/@ant-design/icons-vue@7/dist/index.umd.min.js"></script>
<script src="./dist/index.umd.js"></script>
<script>
const designer = new window.UniFlowDesign.UniFlowDesign('#flow-root', {});
</script>本地开发
pnpm install
pnpm dev构建:
pnpm build类型检查:
npx tsc --noEmit发布到 npm
npm 包详情页会展示 README.md,所以需要有 npm 展示文档。发布前建议执行:
pnpm build
npx tsc --noEmit
npm pack --dry-run确认包内容至少包含:
package.jsonREADME.mddist/index.es.jsdist/index.umd.jsdist/style.cssdist/module.d.tsdist/services.d.tsdist/flowDesign/type.d.ts
首次发布:
npm login
npm publish --access public后续发布前需要更新版本号:
npm version patch
npm publish设计边界
组件只负责流程画布本身:
- 组件负责:画布渲染、节点增删、条件分支增删、缩放、全屏、节点点击事件。
- 宿主负责:节点设置抽屉、保存按钮、流程属性、接口请求、业务校验。
这个边界能保证组件不绑定具体业务模块,也不会因为新增业务场景再改组件内部。
