生态产品多个设计器现已发布候选版本

查看生态总览
跳转到内容

XiHan 工作流引擎预设 ​

XiHan.Framework 的工作流引擎按流程定义 JSON 运行流程。@xihan-flow/core/presets/xihan-workflow 把引擎的每种活动做成一个节点类型,属性键与引擎读的一致,规则与引擎发布时的检查对齐,并能与引擎定义互转:设计器里画好的流程换成引擎定义交给后端保存、发布,引擎里的定义也能读回设计器。全部节点类型、属性与问题码见预设参考。

装上预设 ​

createXihanWorkflow 按文案生成一个 core 层扩展,交给查看器、设计器与 validateDocument 都可以。不传文案是英文,也可以导入现成的英文扩展 xihanWorkflow。文案与流程的语言包一样按 XiHan.UI 的语言一一对应:xihanWorkflowZhCN、xihanWorkflowZhTW、xihanWorkflowJaJP、xihanWorkflowKoKR、xihanWorkflowDeDE、xihanWorkflowFrFR、xihanWorkflowEsES、xihanWorkflowPtBR、xihanWorkflowRuRU 与 xihanWorkflowEnUS,换哪种语言就把哪一份传给 texts;只改几处时传其中几个键,其余照英文。17 种节点类型各配了一枚 @xihan-ui/icons 的图标,画布上的节点与物料条目都画在文字前面,对应关系见参考页。

ts
/** 节点类型、属性与规则:XiHan 工作流引擎的节点预设,文案用中文。 */
export const workflow = createXihanWorkflow({ texts: xihanWorkflowZhCN })

export const extensions = [workflow]
  • 节点类型与属性的文字写在扩展里。切换语言就换一个扩展对象,id 相同,设计器按卸下再装上处理,流程定义不变。
  • 物料的类别与属性的分组只给出键,文字在文案的 categories 与 groups 里,交给设计器的 paletteGroupLabels 与 propertyGroupLabels,写法见设计器。
  • 节点的类型名就是引擎的活动类型,如 Start、UserTask、Http。

节点与属性 ​

节点的属性按引擎的字段存放:活动属性写在 data.properties 下,键名与引擎活动读的一样,如人工任务的 Assignees、HTTP 请求的 Url;超时、失败续行与重试策略是节点级字段,写在 data.timeoutSeconds、data.continueOnError 与 data.retryPolicy。节点名称就是节点的标签。

属性面板按基本、参与人、表单与执行分组。必填的属性没填、取值超出范围或不在选项里时,校验照常报问题。

连线的属性随源节点类型:从条件分支连出的连线有条件、优先级与默认分支,并行分支沿全部出线流转,连出的连线没有条件。默认分支画成虚线:外观由 isDefault 推出,勾选、取消时跟着变,不另存一份样式。流程编码、名称、分类、描述、是否启用补偿与启动变量是流程定义级的属性,写在流程定义的 data 里,什么都没选中时属性面板编辑它们。

受理人规则 ​

人工任务的受理人规则、兜底受理人规则与抄送规则用预设登记的编辑方式 XIHAN_WORKFLOW_ASSIGNEE_RULES,取值是 { type, values } 的数组,与引擎的受理人规则一致:引擎内置 User、Starter、Expression 三种,角色、部门等由应用注册。预设检查取值的形状,并要求 User 至少有一个受理人、Expression 恰好一条非空的表达式;应用注册的类型只检查形状。

选人、选角色要查宿主自己的数据,所以这种编辑方式的编辑器由宿主在设计器层的扩展里按种类提供;没有提供时,属性面板里这一行只读。下面是一个最简单的写法,把规则写成 JSON:

ts
const assigneeRulesEditor: FlowPropertyEditor = (initial) => {
  const input = document.createElement('textarea')
  input.rows = 3
  input.setAttribute('aria-label', initial.descriptor.label ?? '')
  let context = initial
  input.addEventListener('change', () => {
    try {
      context.setValue(input.value.trim() === '' ? undefined : JSON.parse(input.value))
      input.removeAttribute('aria-invalid')
      context.commit()
    }
    catch {
      input.setAttribute('aria-invalid', 'true')
    }
  })
  const update = (next: typeof initial): void => {
    context = next
    if (document.activeElement !== input)
      input.value = next.value === undefined ? '' : JSON.stringify(next.value)
    input.readOnly = next.readonly
  }
  update(initial)
  return { element: input, update }
}

export const assigneeRules: FlowDesignerExtension = {
  id: '@example/assignee-rules',
  editors: { byControl: { [XIHAN_WORKFLOW_ASSIGNEE_RULES]: assigneeRulesEditor } },
}

人工任务还保留着旧的受理人写法:直接列受理人(Assignees)、受理人表达式(AssigneesExpression)与抄送人(CcUserIds)。只用受理人规则的应用写 createXihanWorkflow({ legacyAssignees: false }),属性面板里就不再显示这三项;定义里已有的值照旧读入、写回,往返不丢,校验也照旧认它们。缺省是 true,三项都显示。

把 assigneeRules 与预设一起放进设计器的 extensions 即可。编辑器的写法见扩展。

启动变量 ​

流程定义的启动变量用预设登记的编辑方式 XIHAN_WORKFLOW_START_VARIABLES,取值是变量声明的数组,与引擎读的一致:name 是变量名;required 为 true 时,启动流程缺了这个变量引擎就拒绝启动;defaultValue 在启动时没有提供、又不是必填时生效;description 是说明;type 只是给人看的类型提示,引擎不按它检查。预设检查取值的形状:变量名不能为空、不能重名,required 是布尔,type 与 description 是字符串。

这种编辑方式的编辑器是设计器内建的:属性面板里排成一张表,一个变量一行,列是变量名、必填、缺省值与说明,可以增删。三个文字列有最小宽度,面板窄时表格区横向滚动,行号与把手留在两侧。缺省值按 JSON 读写,字符串要带引号,如 "lisi"、1000、true;写不成 JSON 时这一格标为无效、不写入,清空即不设缺省值。变量上表里没有的字段(如 type)原样保留。要换一种编辑器时,与受理人规则一样在设计器层的扩展里按种类登记,它压过内建的那个。

规则 ​

预设带着几条规则,检查引擎发布定义时会拒绝、而通用校验覆盖不到的情况:

  • 流程里没有结束或终止节点,报警告。
  • 条件分支有不止一条默认分支,报错误;有带条件的出线、却既没有默认分支也没有无条件的出线,报警告。
  • 人工任务没有配置受理人规则、受理人或受理人表达式之一,报错误。

问题码以 xihan-workflow. 开头,说明在预设的文案里,严重程度可以经设计器的 validateOptions 改写。

与引擎定义互转 ​

toWorkflowDefinition 把流程定义换成引擎的定义 JSON,fromWorkflowDefinition 反过来。下面把费用审批换成引擎定义,再读回查看器;去掉坐标读回时,节点按连线自左向右重新排开:

ts
const definition: XihanWorkflowDefinition = toWorkflowDefinition(schema)
const json = JSON.stringify(definition, null, 2)
ts
const readBack = button('去掉坐标后读回', () => {
  const saved = JSON.parse(json) as XihanWorkflowDefinition
  const { designerLayout: _, ...extraProperties } = saved.extraProperties ?? {}
  viewer.schema = fromWorkflowDefinition({ ...saved, extraProperties })
})
const original = button('按保存的坐标读回', () => {
  viewer.schema = fromWorkflowDefinition(JSON.parse(json) as XihanWorkflowDefinition)
})

字段这样对应:

流程定义引擎定义
节点的 type节点的 activityType
节点的 label节点的 name
节点 data 里的字段(properties、retryPolicy、timeoutSeconds、continueOnError 等)节点上的同名字段
连线的 source.node、target.node连线的 sourceNodeId、targetNodeId
连线的 label连线的 name
连线 data 里的字段(condition、priority、isDefault 等)连线上的同名字段
流程定义 data 里的字段(code、name、category 等)定义顶层的同名字段
节点坐标extraProperties.designerLayout
连线的拐点与连线自己写的外观(由 isDefault 推出的虚线不写)extraProperties.designerEdges
节点、连线与流程定义上的扩展数据extraProperties.designerExtensions
  • extraProperties 里这三项是 JSON 字符串,引擎不读,原样存着。节点的宽高不存,读回时按活动类型取。
  • 读回时节点没有坐标,就按连线自左向右分层排出坐标,所以引擎里用代码写的定义也能直接打开。连线没有 id 时补成「源->目标#序号」,值为 null 的字段去掉;结构不对时抛 TypeError。
  • 引擎定义里没列出的字段原样保留,往返之后不丢。示例里的费用审批换成引擎定义再读回与原来一致,文档站的检查会核对这一点。

保存时把 toWorkflowDefinition(designer.schema) 交给后端,引擎在发布时再按它自己的规则把关;打开时把后端给的定义交给 fromWorkflowDefinition,结果赋给 schema。

Released under The MIT License