index.vue 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. <template>
  2. <doc-alert title="流程发起、取消、重新发起" url="https://doc.iocoder.cn/bpm/process-instance/" />
  3. <!-- 第一步,通过流程定义的列表,选择对应的流程 -->
  4. <ContentWrap v-if="!selectProcessDefinition" v-loading="loading">
  5. <el-tabs tab-position="left" v-model="categoryActive">
  6. <el-tab-pane
  7. :label="category.name"
  8. :name="category.code"
  9. :key="category.code"
  10. v-for="category in categoryList"
  11. >
  12. <el-row :gutter="20">
  13. <el-col
  14. :lg="6"
  15. :sm="12"
  16. :xs="24"
  17. v-for="definition in categoryProcessDefinitionList"
  18. :key="definition.id"
  19. >
  20. <el-card
  21. shadow="hover"
  22. class="mb-20px cursor-pointer"
  23. @click="handleSelect(definition)"
  24. >
  25. <template #default>
  26. <div class="flex">
  27. <el-image :src="definition.icon" class="w-32px h-32px" />
  28. <el-text class="!ml-10px" size="large">{{ definition.name }}</el-text>
  29. </div>
  30. </template>
  31. </el-card>
  32. </el-col>
  33. </el-row>
  34. </el-tab-pane>
  35. </el-tabs>
  36. </ContentWrap>
  37. <!-- 第二步,填写表单,进行流程的提交 -->
  38. <ContentWrap v-else>
  39. <el-card class="box-card">
  40. <div class="clearfix">
  41. <span class="el-icon-document">申请信息【{{ selectProcessDefinition.name }}】</span>
  42. <el-button style="float: right" type="primary" @click="selectProcessDefinition = undefined">
  43. <Icon icon="ep:delete" /> 选择其它流程
  44. </el-button>
  45. </div>
  46. <el-col :span="16" :offset="6" style="margin-top: 20px">
  47. <form-create
  48. :rule="detailForm.rule"
  49. v-model:api="fApi"
  50. v-model="detailForm.value"
  51. :option="detailForm.option"
  52. @submit="submitForm"
  53. >
  54. <template #type-startUserSelect>
  55. <el-col :span="24">
  56. <el-card class="mb-10px">
  57. <template #header>指定审批人</template>
  58. <el-form
  59. :model="startUserSelectAssignees"
  60. :rules="startUserSelectAssigneesFormRules"
  61. ref="startUserSelectAssigneesFormRef"
  62. >
  63. <el-form-item
  64. v-for="userTask in startUserSelectTasks"
  65. :key="userTask.id"
  66. :label="`任务【${userTask.name}】`"
  67. :prop="userTask.id"
  68. >
  69. <el-select
  70. v-model="startUserSelectAssignees[userTask.id]"
  71. multiple
  72. placeholder="请选择审批人"
  73. >
  74. <el-option
  75. v-for="user in userList"
  76. :key="user.id"
  77. :label="user.nickname"
  78. :value="user.id"
  79. />
  80. </el-select>
  81. </el-form-item>
  82. </el-form>
  83. </el-card>
  84. </el-col>
  85. </template>
  86. </form-create>
  87. </el-col>
  88. </el-card>
  89. <!-- 流程图预览 -->
  90. <ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML as any" />
  91. </ContentWrap>
  92. </template>
  93. <script lang="ts" setup>
  94. import * as DefinitionApi from '@/api/bpm/definition'
  95. import * as ProcessInstanceApi from '@/api/bpm/processInstance'
  96. import { setConfAndFields2 } from '@/utils/formCreate'
  97. import type { ApiAttrs } from '@form-create/element-ui/types/config'
  98. import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
  99. import { CategoryApi } from '@/api/bpm/category'
  100. import { useTagsViewStore } from '@/store/modules/tagsView'
  101. import * as UserApi from '@/api/system/user'
  102. defineOptions({ name: 'BpmProcessInstanceCreate' })
  103. const route = useRoute() // 路由
  104. const { push, currentRoute } = useRouter() // 路由
  105. const message = useMessage() // 消息
  106. const { delView } = useTagsViewStore() // 视图操作
  107. const processInstanceId = route.query.processInstanceId
  108. const loading = ref(true) // 加载中
  109. const categoryList = ref([]) // 分类的列表
  110. const categoryActive = ref('') // 选中的分类
  111. const processDefinitionList = ref([]) // 流程定义的列表
  112. /** 查询列表 */
  113. const getList = async () => {
  114. loading.value = true
  115. try {
  116. // 流程分类
  117. categoryList.value = await CategoryApi.getCategorySimpleList()
  118. if (categoryList.value.length > 0) {
  119. categoryActive.value = categoryList.value[0].code
  120. }
  121. // 流程定义
  122. processDefinitionList.value = await DefinitionApi.getProcessDefinitionList({
  123. suspensionState: 1
  124. })
  125. // 如果 processInstanceId 非空,说明是重新发起
  126. if (processInstanceId?.length > 0) {
  127. const processInstance = await ProcessInstanceApi.getProcessInstance(processInstanceId)
  128. if (!processInstance) {
  129. message.error('重新发起流程失败,原因:流程实例不存在')
  130. return
  131. }
  132. const processDefinition = processDefinitionList.value.find(
  133. (item) => item.key == processInstance.processDefinition?.key
  134. )
  135. if (!processDefinition) {
  136. message.error('重新发起流程失败,原因:流程定义不存在')
  137. return
  138. }
  139. await handleSelect(processDefinition, processInstance.formVariables)
  140. }
  141. } finally {
  142. loading.value = false
  143. }
  144. }
  145. /** 选中分类对应的流程定义列表 */
  146. const categoryProcessDefinitionList = computed(() => {
  147. return processDefinitionList.value.filter((item) => item.category == categoryActive.value)
  148. })
  149. // ========== 表单相关 ==========
  150. const fApi = ref<ApiAttrs>()
  151. const detailForm = ref({
  152. rule: [],
  153. option: {},
  154. value: {}
  155. }) // 流程表单详情
  156. const selectProcessDefinition = ref() // 选择的流程定义
  157. // 指定审批人
  158. const bpmnXML = ref(null) // BPMN 数据
  159. const startUserSelectTasks = ref([]) // 发起人需要选择审批人的用户任务列表
  160. const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
  161. const startUserSelectAssigneesFormRef = ref() // 发起人选择审批人的表单 Ref
  162. const startUserSelectAssigneesFormRules = ref({}) // 发起人选择审批人的表单 Rules
  163. const userList = ref<any[]>([]) // 用户列表
  164. /** 处理选择流程的按钮操作 **/
  165. const handleSelect = async (row, formVariables) => {
  166. // 设置选择的流程
  167. selectProcessDefinition.value = row
  168. // 重置指定审批人
  169. startUserSelectTasks.value = []
  170. startUserSelectAssignees.value = {}
  171. startUserSelectAssigneesFormRules.value = {}
  172. // 情况一:流程表单
  173. if (row.formType == 10) {
  174. // 设置表单
  175. setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables)
  176. // 加载流程图
  177. const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id)
  178. if (processDefinitionDetail) {
  179. bpmnXML.value = processDefinitionDetail.bpmnXml
  180. startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
  181. // 设置指定审批人
  182. if (startUserSelectTasks.value?.length > 0) {
  183. detailForm.value.rule.push({
  184. type: 'startUserSelect',
  185. props: {
  186. title: '指定审批人'
  187. }
  188. })
  189. // 设置校验规则
  190. for (const userTask of startUserSelectTasks.value) {
  191. startUserSelectAssignees.value[userTask.id] = []
  192. startUserSelectAssigneesFormRules.value[userTask.id] = [
  193. { required: true, message: '请选择审批人', trigger: 'blur' }
  194. ]
  195. }
  196. // 加载用户列表
  197. userList.value = await UserApi.getSimpleUserList()
  198. }
  199. }
  200. // 情况二:业务表单
  201. } else if (row.formCustomCreatePath) {
  202. await push({
  203. path: row.formCustomCreatePath
  204. })
  205. // 这里暂时无需加载流程图,因为跳出到另外个 Tab;
  206. }
  207. }
  208. /** 提交按钮 */
  209. const submitForm = async (formData) => {
  210. if (!fApi.value || !selectProcessDefinition.value) {
  211. return
  212. }
  213. // 如果有指定审批人,需要校验
  214. if (startUserSelectTasks.value?.length > 0) {
  215. await startUserSelectAssigneesFormRef.value.validate()
  216. }
  217. // 提交请求
  218. fApi.value.btn.loading(true)
  219. try {
  220. await ProcessInstanceApi.createProcessInstance({
  221. processDefinitionId: selectProcessDefinition.value.id,
  222. variables: formData,
  223. startUserSelectAssignees: startUserSelectAssignees.value
  224. })
  225. // 提示
  226. message.success('发起流程成功')
  227. // 跳转回去
  228. delView(unref(currentRoute))
  229. await push({
  230. name: 'BpmProcessInstanceMy'
  231. })
  232. } finally {
  233. fApi.value.btn.loading(false)
  234. }
  235. }
  236. /** 初始化 */
  237. onMounted(() => {
  238. getList()
  239. })
  240. </script>