index_new.vue 9.6 KB

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