create.vue 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. <template>
  2. <el-row :gutter="20">
  3. <el-col :span="16">
  4. <ContentWrap title="申请信息">
  5. <el-form
  6. ref="formRef"
  7. v-loading="formLoading"
  8. :model="formData"
  9. :rules="formRules"
  10. label-width="80px"
  11. >
  12. <el-form-item label="请假类型" prop="type">
  13. <el-select v-model="formData.type" clearable placeholder="请选择请假类型">
  14. <el-option
  15. v-for="dict in getIntDictOptions(DICT_TYPE.BPM_OA_LEAVE_TYPE)"
  16. :key="dict.value"
  17. :label="dict.label"
  18. :value="dict.value"
  19. />
  20. </el-select>
  21. </el-form-item>
  22. <el-form-item label="开始时间" prop="startTime">
  23. <el-date-picker
  24. v-model="formData.startTime"
  25. clearable
  26. placeholder="请选择开始时间"
  27. type="datetime"
  28. value-format="x"
  29. />
  30. </el-form-item>
  31. <el-form-item label="结束时间" prop="endTime">
  32. <el-date-picker
  33. v-model="formData.endTime"
  34. clearable
  35. placeholder="请选择结束时间"
  36. type="datetime"
  37. value-format="x"
  38. />
  39. </el-form-item>
  40. <el-form-item label="原因" prop="reason">
  41. <el-input v-model="formData.reason" placeholder="请输入请假原因" type="textarea" />
  42. </el-form-item>
  43. <el-form-item>
  44. <el-button :disabled="formLoading" type="primary" @click="submitForm">
  45. 确 定
  46. </el-button>
  47. </el-form-item>
  48. </el-form>
  49. </ContentWrap>
  50. </el-col>
  51. <!-- 审批相关:流程信息 -->
  52. <el-col :span="8">
  53. <ContentWrap title="审批流程" :bodyStyle="{ padding: '0 20px 0' }">
  54. <ProcessInstanceTimeline
  55. ref="timelineRef"
  56. :activity-nodes="activityNodes"
  57. :show-status-icon="false"
  58. @select-user-confirm="selectUserConfirm"
  59. />
  60. </ContentWrap>
  61. </el-col>
  62. </el-row>
  63. </template>
  64. <script lang="ts" setup>
  65. import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
  66. import * as LeaveApi from '@/api/bpm/leave'
  67. import { useTagsViewStore } from '@/store/modules/tagsView'
  68. // 审批相关:import
  69. import * as DefinitionApi from '@/api/bpm/definition'
  70. import ProcessInstanceTimeline from '@/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue'
  71. import * as ProcessInstanceApi from '@/api/bpm/processInstance'
  72. import { CandidateStrategy, NodeId } from '@/components/SimpleProcessDesignerV2/src/consts'
  73. import { ApprovalNodeInfo } from '@/api/bpm/processInstance'
  74. defineOptions({ name: 'BpmOALeaveCreate' })
  75. const message = useMessage() // 消息弹窗
  76. const { delView } = useTagsViewStore() // 视图操作
  77. const { push, currentRoute } = useRouter() // 路由
  78. const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
  79. const formData = ref({
  80. type: undefined,
  81. reason: undefined,
  82. startTime: undefined,
  83. endTime: undefined
  84. })
  85. const formRules = reactive({
  86. type: [{ required: true, message: '请假类型不能为空', trigger: 'blur' }],
  87. reason: [{ required: true, message: '请假原因不能为空', trigger: 'change' }],
  88. startTime: [{ required: true, message: '请假开始时间不能为空', trigger: 'change' }],
  89. endTime: [{ required: true, message: '请假结束时间不能为空', trigger: 'change' }]
  90. })
  91. const formRef = ref() // 表单 Ref
  92. // 审批相关:变量
  93. const processDefineKey = 'oa_leave' // 流程定义 Key
  94. const startUserSelectTasks = ref([]) // 发起人需要选择审批人的用户任务列表
  95. const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
  96. const tempStartUserSelectAssignees = ref({}) // 历史发起人选择审批人的数据,用于每次表单变更时,临时保存
  97. const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) // 审批节点信息
  98. const processDefinitionId = ref('')
  99. /** 提交表单 */
  100. const submitForm = async () => {
  101. // 1.1 校验表单
  102. if (!formRef) return
  103. const valid = await formRef.value.validate()
  104. if (!valid) return
  105. // 1.2 审批相关:校验指定审批人
  106. if (startUserSelectTasks.value?.length > 0) {
  107. for (const userTask of startUserSelectTasks.value) {
  108. if (
  109. Array.isArray(startUserSelectAssignees.value[userTask.id]) &&
  110. startUserSelectAssignees.value[userTask.id].length === 0
  111. ) {
  112. return message.warning(`请选择${userTask.name}的审批人`)
  113. }
  114. }
  115. }
  116. // 2. 提交请求
  117. formLoading.value = true
  118. try {
  119. const data = { ...formData.value } as unknown as LeaveApi.LeaveVO
  120. // 审批相关:设置指定审批人
  121. if (startUserSelectTasks.value?.length > 0) {
  122. data.startUserSelectAssignees = startUserSelectAssignees.value
  123. }
  124. await LeaveApi.createLeave(data)
  125. message.success('发起成功')
  126. // 关闭当前 Tab
  127. delView(unref(currentRoute))
  128. await push({ name: 'BpmOALeave' })
  129. } finally {
  130. formLoading.value = false
  131. }
  132. }
  133. /** 审批相关:获取审批详情 */
  134. const getApprovalDetail = async () => {
  135. try {
  136. const data = await ProcessInstanceApi.getApprovalDetail({
  137. processDefinitionId: processDefinitionId.value,
  138. // TODO 小北:可以支持 processDefinitionKey 查询
  139. activityId: NodeId.START_USER_NODE_ID,
  140. processVariablesStr: JSON.stringify({ day: daysDifference() }) // 解决 GET 无法传递对象的问题,后端 String 再转 JSON
  141. })
  142. if (!data) {
  143. message.error('查询不到审批详情信息!')
  144. return
  145. }
  146. // 获取审批节点,显示 Timeline 的数据
  147. activityNodes.value = data.activityNodes
  148. // 获取发起人自选的任务
  149. startUserSelectTasks.value = data.activityNodes?.filter(
  150. (node: ApprovalNodeInfo) => CandidateStrategy.START_USER_SELECT === node.candidateStrategy
  151. )
  152. // 恢复之前的选择审批人
  153. if (startUserSelectTasks.value?.length > 0) {
  154. for (const node of startUserSelectTasks.value) {
  155. if (
  156. tempStartUserSelectAssignees.value[node.id] &&
  157. tempStartUserSelectAssignees.value[node.id].length > 0
  158. ) {
  159. startUserSelectAssignees.value[node.id] = tempStartUserSelectAssignees.value[node.id]
  160. } else {
  161. startUserSelectAssignees.value[node.id] = []
  162. }
  163. }
  164. }
  165. } finally {
  166. }
  167. }
  168. /** 审批相关:选择发起人 */
  169. const selectUserConfirm = (id: string, userList: any[]) => {
  170. startUserSelectAssignees.value[id] = userList?.map((item: any) => item.id)
  171. }
  172. // 计算天数差
  173. // TODO @小北:可以搞到 formatTime 里面去,然后看看 dayjs 里面有没有现成的方法,或者辅助计算的方法。
  174. const daysDifference = () => {
  175. const oneDay = 24 * 60 * 60 * 1000 // 一天的毫秒数
  176. const diffTime = Math.abs(Number(formData.value.endTime) - Number(formData.value.startTime))
  177. return Math.floor(diffTime / oneDay)
  178. }
  179. /** 初始化 */
  180. onMounted(async () => {
  181. // TODO @小北:这里可以简化,统一通过 getApprovalDetail 处理么?
  182. const processDefinitionDetail = await DefinitionApi.getProcessDefinition(
  183. undefined,
  184. processDefineKey
  185. )
  186. if (!processDefinitionDetail) {
  187. message.error('OA 请假的流程模型未配置,请检查!')
  188. return
  189. }
  190. processDefinitionId.value = processDefinitionDetail.id
  191. startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
  192. // 审批相关:加载最新的审批详情,主要用于节点预测
  193. await getApprovalDetail()
  194. })
  195. /** 审批相关:预测流程节点会因为输入的参数值而产生新的预测结果值,所以需重新预测一次, formData.value可改成实际业务中的特定字段 */
  196. watch(
  197. formData.value,
  198. (newValue, oldValue) => {
  199. if (!oldValue) {
  200. return
  201. }
  202. if (newValue && Object.keys(newValue).length > 0) {
  203. // 记录之前的节点审批人
  204. tempStartUserSelectAssignees.value = startUserSelectAssignees.value
  205. startUserSelectAssignees.value = {}
  206. // 加载最新的审批详情,主要用于节点预测
  207. getApprovalDetail()
  208. }
  209. },
  210. {
  211. immediate: true
  212. }
  213. )
  214. </script>