Skip to content

@gauss/flowable

工作流扩展包,内置基于 BPMN 2.0 封装的流程引擎,支持流程定义、流程实例、任务等的管理。

包含 7 个页面,分别是

管理员角色: 角色管理,分类管理,流程模板,流程监控

普通角色: 我的流程,待办任务,已办任务

安装

shell
$ yarn add @gauss/flowable -S
$ yarn add @gauss/flowable -S

使用

导入路由

src/routes/index.js

js
import { flowableRoutes } from '@gauss/flowable'
import { createRoutes } from '@gauss/prime'

export const routes = createRoutes([
    ...flowableRoutes,
    // ...其他路由
])
import { flowableRoutes } from '@gauss/flowable'
import { createRoutes } from '@gauss/prime'

export const routes = createRoutes([
    ...flowableRoutes,
    // ...其他路由
])

注册表单组件

Frame

src/main.js

js
import { locales, provide } from '@gauss/flowable'
import { merge } from 'lodash-es'
import TaskForm from './components/task-form/index.vue'
import en from './locales/en'
import zh from './locales/zh'

provide({ TaskForm })

const prime = createPrime({
    i18nOptions: {
        // ...其它配置
        // 国际化
        messages: {
            zh: merge(locales.zh, zh),
            en: merge(locales.en, en),
        },
    },
})
import { locales, provide } from '@gauss/flowable'
import { merge } from 'lodash-es'
import TaskForm from './components/task-form/index.vue'
import en from './locales/en'
import zh from './locales/zh'

provide({ TaskForm })

const prime = createPrime({
    i18nOptions: {
        // ...其它配置
        // 国际化
        messages: {
            zh: merge(locales.zh, zh),
            en: merge(locales.en, en),
        },
    },
})

表单组件

表单组件需暴露 getFormData,getFormData 是 promise

src/components/task-form/index.vue

vue
<template>
    <component
        :is="getComponent(template.key)"
        :type="type"
        :template="template"
        :taskNodes="taskNodes"
        :getApplyInfo="getApplyInfo"
        ref="taskFormRef"
    ></component>
</template>

<script setup>
import { taskFormProps } from '@gauss/flowable'
import { defineProps, ref } from 'vue'
import { getComponent } from './form'

defineProps(taskFormProps)

const taskFormRef = ref()

defineExpose({
    getFormData() {
        return taskFormRef.value.getFormData()
    },
})
</script>
<template>
    <component
        :is="getComponent(template.key)"
        :type="type"
        :template="template"
        :taskNodes="taskNodes"
        :getApplyInfo="getApplyInfo"
        ref="taskFormRef"
    ></component>
</template>

<script setup>
import { taskFormProps } from '@gauss/flowable'
import { defineProps, ref } from 'vue'
import { getComponent } from './form'

defineProps(taskFormProps)

const taskFormRef = ref()

defineExpose({
    getFormData() {
        return taskFormRef.value.getFormData()
    },
})
</script>

src/components/task-form/form/index.js

js
import AdjustmentForm from './adjustment-form.vue'

// 不同的流程注册不同的表单
const forms = {
    process_IBUedushenqing_diaozhengdan: AdjustmentForm,
}

export const getComponent = key => {
    return forms[key] || AdjustmentForm
}
import AdjustmentForm from './adjustment-form.vue'

// 不同的流程注册不同的表单
const forms = {
    process_IBUedushenqing_diaozhengdan: AdjustmentForm,
}

export const getComponent = key => {
    return forms[key] || AdjustmentForm
}

src/components/task-form/form/adjustment-form.vue

vue
<template>
    <h-form-render :config="formConfig" ref="formRef"></h-form-render>
</template>

<script setup>
import { FLOW_TASK_TYPE_CREATE, FLOW_TASK_TYPE_VIEW, taskFormProps } from '@gauss/flowable'
import { useUserInfo } from '@gauss/prime'
import { defineFRConfig } from '@hamlet/render'
import { defineExpose, defineProps, nextTick, ref, watch } from 'vue'

const props = defineProps(taskFormProps)
const formRef = ref()
const userInfo = useUserInfo()

watch(
    () => props.type,
    value => {
        // 如果不是创建状态,获取流程初始提交信息
        if (value !== FLOW_TASK_TYPE_CREATE) {
            props.getApplyInfo().then(res => {
                const { name, department, companyCode, applyCreditLimit, applyPaymentDays } =
                    res?.applyInfo || {}
                formRef.value.setFieldsValue({
                    name,
                    department,
                    companyCode,
                    applyCreditLimit,
                    applyPaymentDays,
                })
            })
        } else {
            // 否则获取当前用户信息并赋值
            nextTick(() => {
                formRef.value.setFieldsValue({
                    name: userInfo.userName,
                    department: userInfo.departmentName,
                })
            })
        }
    },
    {
        immediate: true,
    },
)

const formConfig = defineFRConfig({
    type: 'form',
    labelPosition: 'top',
    rowGutter: 16,
    labelWidth: 0,
    column: 2,
    size: 'middle',
    body: [
        {
            prop: 'name',
            label: '申请人',
            type: 'input',
            disabled: true,
        },
        {
            prop: 'department',
            label: '申请人部门',
            type: 'input',
            disabled: true,
        },
        {
            prop: 'companyCode',
            label: '所属公司',
            type: 'select',
            required: true,
            map: {
                D802: '总部-D802',
                F800: '总部-F800',
                O801: '总部-O801',
                O802: '子公司-O802',
                O803: '子公司-O803',
                O804: '子公司-O804',
                O805: '子公司-O805',
            },
            disabled() {
                return props.type !== FLOW_TASK_TYPE_CREATE
            },
        },
        {
            prop: 'applyCreditLimit',
            label: '信贷额度',
            type: 'input',
            disabled() {
                return props.type !== FLOW_TASK_TYPE_CREATE
            },
        },
        {
            prop: 'applyPaymentDays',
            label: '付款日',
            type: 'input',
            disabled() {
                return props.type !== FLOW_TASK_TYPE_CREATE
            },
        },
        {
            prop: 'comment',
            label: '签字意见',
            type: 'textarea',
            rows: 2,
            required: true,
            colSpan: 2,
            visible() {
                return props.type !== FLOW_TASK_TYPE_VIEW
            },
        },
    ],
})

function getFormData() {
    return new Promise((resolve, reject) => {
        formRef.value
            .validate()
            .then(formData => {
                resolve(formData)
            })
            .catch(() => {
                reject()
            })
    })
}

defineExpose({ getFormData })
</script>
<template>
    <h-form-render :config="formConfig" ref="formRef"></h-form-render>
</template>

<script setup>
import { FLOW_TASK_TYPE_CREATE, FLOW_TASK_TYPE_VIEW, taskFormProps } from '@gauss/flowable'
import { useUserInfo } from '@gauss/prime'
import { defineFRConfig } from '@hamlet/render'
import { defineExpose, defineProps, nextTick, ref, watch } from 'vue'

const props = defineProps(taskFormProps)
const formRef = ref()
const userInfo = useUserInfo()

watch(
    () => props.type,
    value => {
        // 如果不是创建状态,获取流程初始提交信息
        if (value !== FLOW_TASK_TYPE_CREATE) {
            props.getApplyInfo().then(res => {
                const { name, department, companyCode, applyCreditLimit, applyPaymentDays } =
                    res?.applyInfo || {}
                formRef.value.setFieldsValue({
                    name,
                    department,
                    companyCode,
                    applyCreditLimit,
                    applyPaymentDays,
                })
            })
        } else {
            // 否则获取当前用户信息并赋值
            nextTick(() => {
                formRef.value.setFieldsValue({
                    name: userInfo.userName,
                    department: userInfo.departmentName,
                })
            })
        }
    },
    {
        immediate: true,
    },
)

const formConfig = defineFRConfig({
    type: 'form',
    labelPosition: 'top',
    rowGutter: 16,
    labelWidth: 0,
    column: 2,
    size: 'middle',
    body: [
        {
            prop: 'name',
            label: '申请人',
            type: 'input',
            disabled: true,
        },
        {
            prop: 'department',
            label: '申请人部门',
            type: 'input',
            disabled: true,
        },
        {
            prop: 'companyCode',
            label: '所属公司',
            type: 'select',
            required: true,
            map: {
                D802: '总部-D802',
                F800: '总部-F800',
                O801: '总部-O801',
                O802: '子公司-O802',
                O803: '子公司-O803',
                O804: '子公司-O804',
                O805: '子公司-O805',
            },
            disabled() {
                return props.type !== FLOW_TASK_TYPE_CREATE
            },
        },
        {
            prop: 'applyCreditLimit',
            label: '信贷额度',
            type: 'input',
            disabled() {
                return props.type !== FLOW_TASK_TYPE_CREATE
            },
        },
        {
            prop: 'applyPaymentDays',
            label: '付款日',
            type: 'input',
            disabled() {
                return props.type !== FLOW_TASK_TYPE_CREATE
            },
        },
        {
            prop: 'comment',
            label: '签字意见',
            type: 'textarea',
            rows: 2,
            required: true,
            colSpan: 2,
            visible() {
                return props.type !== FLOW_TASK_TYPE_VIEW
            },
        },
    ],
})

function getFormData() {
    return new Promise((resolve, reject) => {
        formRef.value
            .validate()
            .then(formData => {
                resolve(formData)
            })
            .catch(() => {
                reject()
            })
    })
}

defineExpose({ getFormData })
</script>

@gauss/flowable Export

flowableRoutes

路由配置

locales

国际化配置

locales
    └── zh
    └── en
locales
    └── zh
    └── en

provide

注册表单组件,建议在 main.js 内注册

js
provide({ TaskForm })
provide({ TaskForm })

taskFormProps

表单组件 props

js
export const taskFormProps = {
    type: {
        type: String,
    },
    template: {
        type: Object,
    },
    taskNodes: {
        type: Object,
    },
    getApplyInfo: {
        type: Function,
        default: async () => {},
    },
}
export const taskFormProps = {
    type: {
        type: String,
    },
    template: {
        type: Object,
    },
    taskNodes: {
        type: Object,
    },
    getApplyInfo: {
        type: Function,
        default: async () => {},
    },
}

任务状态

FLOW_TASK_TYPE_CREATE 创建状态

FLOW_TASK_TYPE_HANDLE 处理中

FLOW_TASK_TYPE_VIEW 只读

定制任务处理表单

TaskForm

接入方需要实现一个表单组件,用来替换任务处理页面默认的表单,并 expose一个getFormData方法让父组件获取需要提交的表单数据。

html
<template>
    <el-form ref="formRef" :model="formData">
        <template v-if="type === 'create'">
            <!-- 创建任务 -->
        </template>
        <template v-else-if="type === 'handle'">
            <!-- 处理任务 -->
        </template>
        <template v-else-if="type === 'view'">
            <!-- 任务详情 -->
        </template>
        <el-form-item>
            <el-input v-model="comment" />
        </el-form-item>
    </el-form>
</template>

<script setup lang="ts">
    import { defineProps, defineExpose, ref, reactive } from 'vue'
    import { FormInstance } from 'element-plus'
    const props = defineProps({
        type: {
            type: String as 'create' | 'handle' | 'view',
        },
        taskNodes: {
            type: Object as ITaskNodeInfo,
        },
        template: {
            type: Object as ITemplate,
        },
        getApplyInfo: {
            type: Function as () => Promise<IApplyInfo>,
        },
    })

    const formRef = ref<FormInstance>(null)
    const comment = ref('')
    const formData = reactive({})

    async function getFormData() {
        if (formRef.value) {
            await formRef.value.validate()
            return {
                applyForm: formData,
                comment: comment.value,
            }
        }
        return Promise.reject(new Error('xxx'))
    }
</script>
<template>
    <el-form ref="formRef" :model="formData">
        <template v-if="type === 'create'">
            <!-- 创建任务 -->
        </template>
        <template v-else-if="type === 'handle'">
            <!-- 处理任务 -->
        </template>
        <template v-else-if="type === 'view'">
            <!-- 任务详情 -->
        </template>
        <el-form-item>
            <el-input v-model="comment" />
        </el-form-item>
    </el-form>
</template>

<script setup lang="ts">
    import { defineProps, defineExpose, ref, reactive } from 'vue'
    import { FormInstance } from 'element-plus'
    const props = defineProps({
        type: {
            type: String as 'create' | 'handle' | 'view',
        },
        taskNodes: {
            type: Object as ITaskNodeInfo,
        },
        template: {
            type: Object as ITemplate,
        },
        getApplyInfo: {
            type: Function as () => Promise<IApplyInfo>,
        },
    })

    const formRef = ref<FormInstance>(null)
    const comment = ref('')
    const formData = reactive({})

    async function getFormData() {
        if (formRef.value) {
            await formRef.value.validate()
            return {
                applyForm: formData,
                comment: comment.value,
            }
        }
        return Promise.reject(new Error('xxx'))
    }
</script>

Props

属性名说明类型
type任务类型'create' / 'handle' / 'view'
template查询流程模板基本信息ITemplate
taskNodes获取流程节点的配置信息,已办任务无ITaskNodeInfo
getApplyInfo获取创建流程时提交的申请信息() => Promise<IApplyInfo>

Expose

属性名说明类型
getFormData执行表单校验,并返回需要提交的表单信息() => Promise<ISubmitPayload>

Interface

ts
interface IApplyInfo {
    applyInfo: Record<string, unknown> // 申请信息
    isEdit: boolean // 表单是否可编辑,驳回到申请节点时返回true
}

interface ISubmitPayload {
    comment: string // 审批意见
    applyInfo: Record<string, unknown> // 申请信息
}

interface ITaskNodeInfo {
    firstKey: string // 第一个节点key
    currentNodeKey?: string // 当前节点key
    node: {
        // 节点配置数组
        key: string // 节点key
        extensionProp: string // 节点配置值
    }[]
}

interface ITemplate {
    category: string // 流程分类
    categoryName: string // 流程分类
    id: string // 模板id
    name: string // 模板名称
    key: string // 模板标识
    version: string // 模板版本
}
interface IApplyInfo {
    applyInfo: Record<string, unknown> // 申请信息
    isEdit: boolean // 表单是否可编辑,驳回到申请节点时返回true
}

interface ISubmitPayload {
    comment: string // 审批意见
    applyInfo: Record<string, unknown> // 申请信息
}

interface ITaskNodeInfo {
    firstKey: string // 第一个节点key
    currentNodeKey?: string // 当前节点key
    node: {
        // 节点配置数组
        key: string // 节点key
        extensionProp: string // 节点配置值
    }[]
}

interface ITemplate {
    category: string // 流程分类
    categoryName: string // 流程分类
    id: string // 模板id
    name: string // 模板名称
    key: string // 模板标识
    version: string // 模板版本
}

TaskGateway

当业务接入方需要对任务创建、处理、查看、批注页面进行定制时可以使用 TaskGateway 组件,此组件集成了任务生命周期中的所有功能,并支持业务接入方根据自身需求对界面进行一定程序的定制和扩展。

使用示例

html
<template>
  <task-gateway type="create" ref="taskGatewayRef" :externalTabPanes="externalTabPanes">
    <template #taskForm>
      <task-form ref="taskFormRef" />
    </template>
    <template #taskFormActions>
      <el-button @click="createTask">提交</el-button>
      <el-button @click="saveDraft">保存草稿</el-button>
    </template>
  </task-gateway>
</template>
<script lang="ts" setup>
  import { ref } from 'vue'
  import { TaskGateway } from '@guass/flowable'

  const taskGatewayRef = ref<ITaskGatewayInstance>()
  const taskFormRef = ref<{ getFormData({ businessType: 'create' | 'agree' | 'reject' }): Promise<ISubmitPayload> }>()
  const externalTabPanes = [{ label: 'label', name: 'name', content: 'content' }]
  async function createTask() {
    const formData = await taskFormRef.value.getFormData({ businessType: 'create' })
    taskGatewayRef.value.createTask(formData)
  }

  async function saveDraft() {
    const formData = await taskFormRef.value.getFormData()
    // 业务接入方实现保存草稿接口
  }
</script>
<template>
  <task-gateway type="create" ref="taskGatewayRef" :externalTabPanes="externalTabPanes">
    <template #taskForm>
      <task-form ref="taskFormRef" />
    </template>
    <template #taskFormActions>
      <el-button @click="createTask">提交</el-button>
      <el-button @click="saveDraft">保存草稿</el-button>
    </template>
  </task-gateway>
</template>
<script lang="ts" setup>
  import { ref } from 'vue'
  import { TaskGateway } from '@guass/flowable'

  const taskGatewayRef = ref<ITaskGatewayInstance>()
  const taskFormRef = ref<{ getFormData({ businessType: 'create' | 'agree' | 'reject' }): Promise<ISubmitPayload> }>()
  const externalTabPanes = [{ label: 'label', name: 'name', content: 'content' }]
  async function createTask() {
    const formData = await taskFormRef.value.getFormData({ businessType: 'create' })
    taskGatewayRef.value.createTask(formData)
  }

  async function saveDraft() {
    const formData = await taskFormRef.value.getFormData()
    // 业务接入方实现保存草稿接口
  }
</script>

属性 Props

属性名描述类型默认值
type类型-
procInsId流程实例idstring-
procDefId流程定义idstring-
deployId流程模板部署idstring-
taskId任务idstring-
allowCreate允许创建booleantrue
allowAgree允许同意booleantrue
allowReject允许驳回booleantrue
allowAssign允许转办booleantrue
allowCced允许抄送booleantrue
allowDelegate允许助审booleantrue
allowRemark允许批注booleantrue
rejectResubmitTarget驳回后再次提交目标IRejectResubmitTarget{ visible: true, defaultValue: 'defaultNode' }
showProcessRecords显示流转记录booleantrue
showProcessGraph显示流程图booleantrue
showFormHeader显示表单headerbooleantrue
showBack显示返回按钮booleantrue
goBackUrl操作完成后返回哪个页面string-
externalTabPanes扩展选项卡IExternalTabPane[]-
buttonPlacement表单操作按钮的位置'bottom' | 'top-right'bottom
buttonWrapClass为按钮组的容器添加类名,仅当buttonPlacementtop-right时有效string-
rejectSetting驳回时是否弹出驳回设置弹窗booleantrue

事件 Events

事件名描述回调参数
onSubmit任务提交回调-
onAgree任务审批回调-
onReject任务驳回回调-

方法 Expose

方法名描述参数返回值
getTaskFormRef返回表单组件的引用-Record<string, unknown>
getApplyInfo返回当前任务的创建信息-IApplyInfo
getTaskNodeInfo返回当前流程的节点配置信息-ITaskNodeInfo
getTemplateInfo返回流程模板信息-ITemplate
createTask创建任务(发起流程)ISubmitPayload-
agreeTask同意任务Pick<ISubmitPayload, 'applyInfo' | 'comment'>-
rejectTask驳回任务Pick<ISubmitPayload,'comment'>-

插槽 Slots

插槽名描述
taskForm表单组件
taskFormActions任务操作按钮

类型 Types

ts
interface ISubmitPayload {
  applyInfo: Record<string, unknown> // 申请信息
  comment: string // 审批意见
  businessId?: string // 业务标识
  skipUrl?: string // 指定在流程列表点击“业务标识”字段时的跳转地址
}

interface IApplyInfo {
  applyInfo: Record<string, unknwon> // 申请信息
  isEdit: boolean // 是否在申请节点(驳回到申请节点)
}

interface ITaskNodeInfo {
  firstKey: string // 第一个节点key
  currentNodeKey?: string // 当前节点key
  node: {
    key: string // 节点key
    extensionProp: string // 节点配置值
  }[] // 节点配置数组
}

interface ITemplate {
  category: string // 流程分类
  categoryName: string // 流程分类
  id: string // 模板id
  name: string // 模板名称
  key: string // 模板标识
  version: string // 模板版本
}

interface IExternalTabPane {
  label: string
  name: string
  content: any
}

interface IRejectResubmitTarget {
  visible?: boolean
  defaultValue?: 'defaultNode' | 'rejectNode'
}
interface ISubmitPayload {
  applyInfo: Record<string, unknown> // 申请信息
  comment: string // 审批意见
  businessId?: string // 业务标识
  skipUrl?: string // 指定在流程列表点击“业务标识”字段时的跳转地址
}

interface IApplyInfo {
  applyInfo: Record<string, unknwon> // 申请信息
  isEdit: boolean // 是否在申请节点(驳回到申请节点)
}

interface ITaskNodeInfo {
  firstKey: string // 第一个节点key
  currentNodeKey?: string // 当前节点key
  node: {
    key: string // 节点key
    extensionProp: string // 节点配置值
  }[] // 节点配置数组
}

interface ITemplate {
  category: string // 流程分类
  categoryName: string // 流程分类
  id: string // 模板id
  name: string // 模板名称
  key: string // 模板标识
  version: string // 模板版本
}

interface IExternalTabPane {
  label: string
  name: string
  content: any
}

interface IRejectResubmitTarget {
  visible?: boolean
  defaultValue?: 'defaultNode' | 'rejectNode'
}