index_new.vue 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. <template>
  2. <ContentWrap :bodyStyle="{ padding: '10px 20px' }" class="position-relative">
  3. <img
  4. class="position-absolute right-20px"
  5. width="150"
  6. :src="auditIcons[processInstance.status]"
  7. alt=""
  8. />
  9. <div class="text-#878c93">编号:{{ id }}</div>
  10. <el-divider class="!my-8px" />
  11. <div class="flex items-center gap-5 mb-10px">
  12. <div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
  13. <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="processInstance.status" />
  14. </div>
  15. <div class="flex items-center gap-5 mb-10px text-13px">
  16. <div class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600">
  17. <img class="rounded-full h-28px" src="@/assets/imgs/avatar.jpg" alt="" />
  18. {{ processInstance?.startUser?.nickname }}
  19. </div>
  20. <div class="text-#878c93"> {{ formatDate(processInstance.startTime) }} 提交 </div>
  21. </div>
  22. <el-tabs>
  23. <!-- 表单信息 -->
  24. <el-tab-pane label="表单信息">
  25. <el-row :gutter="10">
  26. <el-col :span="18" class="!flex !flex-col formCol">
  27. <!-- 表单信息 -->
  28. <div v-loading="processInstanceLoading" class="form-box flex flex-col mb-30px flex-1">
  29. <!-- 情况一:流程表单 -->
  30. <el-col
  31. v-if="processInstance?.processDefinition?.formType === 10"
  32. :offset="6"
  33. :span="16"
  34. >
  35. <form-create
  36. v-model="detailForm.value"
  37. v-model:api="fApi"
  38. :option="detailForm.option"
  39. :rule="detailForm.rule"
  40. />
  41. </el-col>
  42. <!-- 情况二:业务表单 -->
  43. <div v-if="processInstance?.processDefinition?.formType === 20">
  44. <BusinessFormComponent :id="processInstance.businessKey" />
  45. </div>
  46. </div>
  47. <!-- 操作栏按钮 -->
  48. <!-- TODO @GoldenZqqq:ProcessInstanceOperationButton,操作按钮。不叫 Container 会好点点,和后端也更统一 -->
  49. <ProcessInstanceBtnConatiner
  50. ref="processInstanceBtnRef"
  51. :processInstance="processInstance"
  52. :userOptions="userOptions"
  53. @success="getDetail"
  54. />
  55. </el-col>
  56. <el-col :span="6">
  57. <!-- TODO @GoldenZqqq:后续这个,也拆个小组件出来 -->
  58. <el-timeline class="pt-20px">
  59. <el-timeline-item type="primary" size="large">
  60. <div class="flex flex-col items-start gap-2">
  61. <div class="font-bold"> 发起人:{{ processInstance?.startUser?.nickname }}</div>
  62. <el-tag type="success">发起</el-tag>
  63. <div class="text-#a5a5a5 text-12px">
  64. 发起时间:{{ formatDate(processInstance.startTime) }}
  65. </div>
  66. </div>
  67. </el-timeline-item>
  68. <el-timeline-item
  69. v-for="(activity, index) in tasks"
  70. :key="index"
  71. type="primary"
  72. size="large"
  73. >
  74. <div class="flex flex-col items-start gap-2">
  75. <div class="font-bold"> 审批人:{{ activity.assigneeUser?.nickname }}</div>
  76. <dict-tag
  77. :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS"
  78. :value="activity.status"
  79. />
  80. <!-- TODO:暂无该字段 -->
  81. <div v-if="activity.receiveTime" class="text-#a5a5a5 text-12px">
  82. 接收时间:{{ formatDate(activity.receiveTime) }}
  83. </div>
  84. <div v-if="activity.createTime" class="text-#a5a5a5 text-12px">
  85. 审批时间:{{ formatDate(activity.createTime) }}
  86. </div>
  87. <div v-if="activity.opinion" class="text-#a5a5a5 text-12px w-100%">
  88. <div class="mb-5px">审批意见:</div>
  89. <div
  90. class="w-100% border-1px border-#a5a5a5 border-dashed rounded py-5px px-15px text-#2d2d2d"
  91. >
  92. {{ activity.opinion }}
  93. </div>
  94. </div>
  95. </div>
  96. <!-- 该节点用户的头像 -->
  97. <!-- <template #dot>
  98. <img :src="activity?.avatar" alt="" />
  99. </template> -->
  100. </el-timeline-item>
  101. </el-timeline>
  102. </el-col>
  103. </el-row>
  104. </el-tab-pane>
  105. <!-- 流程图 -->
  106. <el-tab-pane label="流程图">
  107. <ProcessInstanceBpmnViewer
  108. :id="`${id}`"
  109. :bpmn-xml="bpmnXml"
  110. :loading="processInstanceLoading"
  111. :process-instance="processInstance"
  112. :tasks="tasks"
  113. />
  114. </el-tab-pane>
  115. <!-- 流转记录 -->
  116. <el-tab-pane label="流转记录">
  117. <ProcessInstanceTaskList
  118. :loading="tasksLoad"
  119. :process-instance="processInstance"
  120. :tasks="tasks"
  121. @refresh="getTaskList"
  122. />
  123. </el-tab-pane>
  124. <!-- 流转评论 -->
  125. <el-tab-pane label="流转评论"> 流转评论 </el-tab-pane>
  126. </el-tabs>
  127. </ContentWrap>
  128. </template>
  129. <script lang="ts" setup>
  130. import { formatDate } from '@/utils/formatTime'
  131. import { DICT_TYPE } from '@/utils/dict'
  132. import { setConfAndFields2 } from '@/utils/formCreate'
  133. import type { ApiAttrs } from '@form-create/element-ui/types/config'
  134. import * as DefinitionApi from '@/api/bpm/definition'
  135. import * as ProcessInstanceApi from '@/api/bpm/processInstance'
  136. import * as TaskApi from '@/api/bpm/task'
  137. import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
  138. import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
  139. import { registerComponent } from '@/utils/routerHelper'
  140. import * as UserApi from '@/api/system/user'
  141. import audit1 from '@/assets/svgs/bpm/audit1.svg'
  142. import audit2 from '@/assets/svgs/bpm/audit2.svg'
  143. import audit3 from '@/assets/svgs/bpm/audit3.svg'
  144. defineOptions({ name: 'BpmProcessInstanceDetail' })
  145. const { query } = useRoute() // 查询参数
  146. const message = useMessage() // 消息弹窗
  147. const id = query.id as unknown as string // 流程实例的编号
  148. const processInstanceLoading = ref(false) // 流程实例的加载中
  149. const processInstance = ref<any>({}) // 流程实例
  150. const processInstanceBtnRef = ref()
  151. const bpmnXml = ref('') // BPMN XML
  152. const tasksLoad = ref(true) // 任务的加载中
  153. const tasks = ref<any[]>([]) // 任务列表
  154. const auditIcons = {
  155. 1: audit1,
  156. 2: audit2,
  157. 3: audit3
  158. }
  159. // ========== 申请信息 ==========
  160. const fApi = ref<ApiAttrs>() //
  161. const detailForm = ref({
  162. rule: [],
  163. option: {},
  164. value: {}
  165. }) // 流程实例的表单详情
  166. /** 获得详情 */
  167. const getDetail = () => {
  168. // 1. 获得流程实例相关
  169. getProcessInstance()
  170. // 2. 获得流程任务列表(审批记录)
  171. getTaskList()
  172. }
  173. /** 加载流程实例 */
  174. const BusinessFormComponent = ref<any>(null) // 异步组件
  175. const getProcessInstance = async () => {
  176. try {
  177. processInstanceLoading.value = true
  178. const data = await ProcessInstanceApi.getProcessInstance(id)
  179. if (!data) {
  180. message.error('查询不到流程信息!')
  181. return
  182. }
  183. processInstance.value = data
  184. // 设置表单信息
  185. const processDefinition = data.processDefinition
  186. if (processDefinition.formType === 10) {
  187. setConfAndFields2(
  188. detailForm,
  189. processDefinition.formConf,
  190. processDefinition.formFields,
  191. data.formVariables
  192. )
  193. nextTick().then(() => {
  194. fApi.value?.btn.show(false)
  195. fApi.value?.resetBtn.show(false)
  196. fApi.value?.disabled(true)
  197. })
  198. } else {
  199. // 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
  200. BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath)
  201. }
  202. // 加载流程图
  203. bpmnXml.value = (await DefinitionApi.getProcessDefinition(processDefinition.id))?.bpmnXml
  204. } finally {
  205. processInstanceLoading.value = false
  206. }
  207. }
  208. /** 加载任务列表 */
  209. const getTaskList = async () => {
  210. try {
  211. // 获得未取消的任务
  212. tasksLoad.value = true
  213. const data = await TaskApi.getTaskListByProcessInstanceId(id)
  214. tasks.value = []
  215. // 1.1 移除已取消的审批
  216. data.forEach((task) => {
  217. if (task.status !== 4) {
  218. tasks.value.push(task)
  219. }
  220. })
  221. // 1.2 排序,将未完成的排在前面,已完成的排在后面;
  222. tasks.value.sort((a, b) => {
  223. // 有已完成的情况,按照完成时间倒序
  224. if (a.endTime && b.endTime) {
  225. return b.endTime - a.endTime
  226. } else if (a.endTime) {
  227. return 1
  228. } else if (b.endTime) {
  229. return -1
  230. // 都是未完成,按照创建时间倒序
  231. } else {
  232. return b.createTime - a.createTime
  233. }
  234. })
  235. // 获得需要自己审批的任务
  236. processInstanceBtnRef.value.loadRunningTask(tasks.value)
  237. } finally {
  238. tasksLoad.value = false
  239. }
  240. }
  241. /** 初始化 */
  242. const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
  243. onMounted(async () => {
  244. getDetail()
  245. // 获得用户列表
  246. userOptions.value = await UserApi.getSimpleUserList()
  247. })
  248. </script>
  249. <style lang="scss" scoped>
  250. .form-box {
  251. :deep(.el-card) {
  252. border: none;
  253. }
  254. }
  255. </style>