Skip to content

FlowForm

流程表单

一些钩子

ts
import { useInjectState } from '@yusui/flow-pages'

const { onAfterGetDetail } = useInjectState()
onAfterGetDetail((data) => {
  console.log(data)
})
名称说明参数
onAfterGetDetail获取流程详情后(data: FlowDetail) => MaybePromise<any>
onBeforeClick获取流程详情后(btn: FlowButton) => MaybePromise<any>
onBeforeSubmit流程提交前(btn: FlowButton) => MaybePromise<any>
onAfterSubmit流程提交成功后(btn: FlowButton) => MaybePromise<any>

扩展tabs

ts
import FlowPages, { CONFIG_DEFAULT } from '@yusui/flow-pages'

import CustomTab from '@/components/CustomTab.vue'

app.use(FlowPages, {
  tabs: [
    ...CONFIG_DEFAULT.tabs,
    // { label: '审批表单', prop: 'formTab', component: InternalForm },
    // { label: '附件资料', prop: 'fileTab', component: UploadTable },
    // { label: '流程跟踪', prop: 'trackTab', component: FlowTrack },
    { label: '自定义Tab', prop: 'customTab', component: CustomTab },
  ],
})
vue
<script setup lang="ts">
import { useInjectState } from '@yusui/flow-pages'
import { ElMessage } from 'element-plus'

const { flowDetail, formData } = useInjectState()

function validate() {
  if (!formData.value.custom) {
    ElMessage.warning('请输入自定义值')
    return Promise.reject()
  }
}

// 验证不通过可以阻止流程的发送
defineExpose({ validate })
</script>

<template>
  <div>这是一个自定义Tab</div>
</template>

扩展按钮

页面 -> 流程按钮 -> 新增 -> { name: "自定义按钮", buttonKey: "flow_custom" }

ts
import FlowPages from '@yusui/flow-pages'

import customButtonHandler from './customButtonHandler.ts'

app.use(FlowPages, {
  buttonHandler: customButtonHandler
})
ts
import type { ButtonHandler, FlowFormState } from '@yusui/flow-pages'

export function useButtonHandler(state: FlowFormState): ButtonHandler {
  return {
    // 自定义
    flow_custom() {
      // ...做一些自定义操作
      // 返回Promise并成功则提交流程
      return request.post('xxx/xxx', {})
    },
  }
}

自定义流程表单

ts
const importedForms = import.meta.glob('../custom-form/**/*.vue')
const customForm = Object.fromEntries(Object.entries(importedForms).map(([key, value]) => [key.replace('../custom-form/', ''), value]))
// {
//   "test/index.vue": () => Promise<Component>
//   "xxx.vue": () => Promise<Component>
// }

app.use(FlowPages, {
  customForm,
})
vue
<script setup lang="ts">
import type { AvueFormInstance, AvueFormOption } from '@smallwei/avue'

import { ref } from 'vue'
import { asyncValidate, useFormDefaults, useInjectState } from '@yusui/flow-pages'

// 获取流程表单上下文数据
const { flowDetail, formData, onAfterGetDetail, onBeforeClick, onBeforeSubmit } = useInjectState()

const formRef = ref<AvueFormInstance>()
const formOption: AvueFormOption = {
  menuBtn: false,
  span: 24,
  column: [
    { prop: 'title', type: 'title', value: '这是一个自定义表单' },
    { label: '输入框', prop: 'input', placeholder: '请输入值', rules: [{ required: true, message: '请输入值' }] },
  ],
}

// 可以使用FlowForm内置的表单控制,或自行定义
const formDefaults = useFormDefaults(flowDetail)

// 获取流程详情后的钩子
onAfterGetDetail((data) => {
  console.log('流程详情', data)
})
// 按钮点击的钩子,返回Promise.reject()可以阻止点击
onBeforeClick((activeBtn) => {
  if (activeBtn.buttonKey === 'flow_pass')
    console.log('正在选择审批人')
})
// 提交前的钩子,返回Promise.reject()可以阻止提交
onBeforeSubmit((activeBtn) => {
  if (activeBtn.buttonKey === 'flow_pass') {
    console.log('正在提交')
    formData.value = {
      ...formData.value,
    }
  }
})

// 暴露异步validate方法作为提交前的校验
const validate = () => asyncValidate(formRef)
defineExpose({ validate })
</script>

<script lang="ts">
export default { name: 'TestForm' }
</script>

<template>
  <avue-form ref="formRef" v-model="formData" v-model:defaults="formDefaults" :option="formOption" />
</template>

直接重写FlowForm(不建议)

ts
import FlowPages from '@yusui/flow-pages'

import CustomFlowForm from './CustomFlowForm.vue'

app.use(FlowPages, {
  FlowForm: CustomFlowForm
})
vue
<script setup lang="ts">
import { flowFormEmits, flowFormProps, isMobile, useConfigProvider, useProvideState } from '@yusui/flow-pages'

import ButtonList from './components/ButtonList.vue'
import InternalApprovalForm from './components/ApprovalForm.vue'

const props = defineProps(flowFormProps)
const emit = defineEmits(flowFormEmits)
const state = useProvideState(props, emit)
const { ApprovalForm: rewriteApprovalForm, tabsProps } = useConfigProvider()

const ApprovalForm = rewriteApprovalForm ?? InternalApprovalForm

const {
  title,
  detail,
  flowDetail,
  tabRefs,
  tabList,
  activeTab,
  formLoading,
  tabsRef,
  onButtonClick,
  onSubmit,
} = state
</script>

<template>
  <el-main v-if="formLoading">
    <el-skeleton />
  </el-main>
  <el-container v-else class="flow-form">
    <el-header class="flow-form__header" height="auto">
      <div class="flow-form__title">
        {{ title ?? flowDetail.flowInstance?.title }}
      </div>
      <ButtonList v-if="!detail && !isMobile()" @click="onButtonClick" />
    </el-header>
    <el-main class="flow-form__main">
      <el-tabs ref="tabsRef" v-model="activeTab" v-bind="tabsProps">
        <el-tab-pane
          v-for="tab in tabList" :key="tab.prop" :label="tab.label" :name="tab.prop" :lazy="tab.lazy"
          :disabled="tab.disabled" :closable="tab.closable"
        >
          <component :is="tab.component" :ref="(el: any) => tabRefs[tab.prop!] = el" />
        </el-tab-pane>
      </el-tabs>
    </el-main>
    <el-footer v-if="!detail && isMobile()" class="flow-form__footer" height="auto">
      <ButtonList @click="onButtonClick" />
    </el-footer>

    <ApprovalForm @submit="onSubmit" />
  </el-container>
</template>

<style lang="scss">
.flow-form {
  height: 100%;

  .flow-form__title {
    padding: 8px 0;
    font-size: 16px;
  }

  .flow-form__footer {
    border-top: 2px solid var(--el-border-color-light);
    padding: 10px;
    text-align: center;
  }

  .flow-form__main {
    padding-top: 0;

    .avue-form {
      padding: 0;
    }

    .el-tabs {
      height: 100%;

      .el-tabs__content {
        height: calc(100% - var(--el-tabs-header-height) - 15px);

        .el-tab-pane {
          height: 100%;
          overflow-y: auto;
        }
      }
    }
  }
}

.flow-form-overlay {
  .el-drawer__header,
  .el-dialog__header {
    margin: 0;
    padding: 0;

    .el-dialog__headerbtn {
      top: 0;
      z-index: 1;
    }

    .el-drawer__close-btn {
      position: absolute;
      top: 10px;
      right: 10px;
      z-index: 1;
    }
  }

  .el-drawer__body,
  .el-dialog__body {
    margin: 0;
    padding: 0;
  }
}
</style>