提交 | 用户 | 时间
3e359e 1 <template>
H 2   <ContentWrap :bodyStyle="{ padding: '10px 20px 0' }">
3     <div class="processInstance-wrap-main">
4       <el-scrollbar>
5         <div class="text-#878c93 h-15px">流程:{{ selectProcessDefinition.name }}</div>
6         <el-divider class="!my-8px" />
7
8         <!-- 中间主要内容 tab 栏 -->
9         <el-tabs v-model="activeTab">
10           <!-- 表单信息 -->
c9a6f7 11           <el-tab-pane label="表单填写" name="form">
9259c2 12             <div class="form-scroll-area" v-loading="processInstanceStartLoading">
3e359e 13               <el-scrollbar>
H 14                 <el-row>
15                   <el-col :span="17">
16                     <form-create
17                       :rule="detailForm.rule"
18                       v-model:api="fApi"
19                       v-model="detailForm.value"
20                       :option="detailForm.option"
21                       @submit="submitForm"
22                     />
23                   </el-col>
24
25                   <el-col :span="6" :offset="1">
26                     <!-- 流程时间线 -->
27                     <ProcessInstanceTimeline
28                       ref="timelineRef"
29                       :activity-nodes="activityNodes"
30                       :show-status-icon="false"
31                       @select-user-confirm="selectUserConfirm"
32                     />
33                   </el-col>
34                 </el-row>
35               </el-scrollbar>
36             </div>
37           </el-tab-pane>
38           <!-- 流程图 -->
39           <el-tab-pane label="流程图" name="diagram">
40             <div class="form-scroll-area">
41               <!-- BPMN 流程图预览 -->
42               <ProcessInstanceBpmnViewer
43                 :bpmn-xml="bpmnXML"
44                 v-if="BpmModelType.BPMN === selectProcessDefinition.modelType"
45               />
46
47               <!-- Simple 流程图预览 -->
48               <ProcessInstanceSimpleViewer
49                 :simple-json="simpleJson"
50                 v-if="BpmModelType.SIMPLE === selectProcessDefinition.modelType"
51               />
52             </div>
53           </el-tab-pane>
54         </el-tabs>
55
56         <!-- 底部操作栏 -->
57         <div class="b-t-solid border-t-1px border-[var(--el-border-color)]">
58           <!-- 操作栏按钮 -->
59           <div
60             v-if="activeTab === 'form'"
61             class="h-50px bottom-10 text-14px flex items-center color-#32373c dark:color-#fff font-bold btn-container"
62           >
63             <el-button plain type="success" @click="submitForm">
64               <Icon icon="ep:select" />&nbsp; 发起
65             </el-button>
66             <el-button plain type="danger" @click="handleCancel">
67               <Icon icon="ep:close" />&nbsp; 取消
68             </el-button>
69           </div>
70         </div>
71       </el-scrollbar>
72     </div>
73   </ContentWrap>
74 </template>
75 <script lang="ts" setup>
76 import { decodeFields, setConfAndFields2 } from '@/utils/formCreate'
77 import { BpmModelType } from '@/utils/constants'
c9a6f7 78 import {
H 79   CandidateStrategy,
80   NodeId,
81   FieldPermissionType
82 } from '@/components/SimpleProcessDesignerV2/src/consts'
3e359e 83 import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
H 84 import ProcessInstanceSimpleViewer from '../detail/ProcessInstanceSimpleViewer.vue'
85 import ProcessInstanceTimeline from '../detail/ProcessInstanceTimeline.vue'
86 import type { ApiAttrs } from '@form-create/element-ui/types/config'
87 import { useTagsViewStore } from '@/store/modules/tagsView'
88 import * as ProcessInstanceApi from '@/api/bpm/processInstance'
89 import * as DefinitionApi from '@/api/bpm/definition'
90 import { ApprovalNodeInfo } from '@/api/bpm/processInstance'
91
92 defineOptions({ name: 'ProcessDefinitionDetail' })
93 const props = defineProps<{
94   selectProcessDefinition: any
95 }>()
96 const emit = defineEmits(['cancel'])
9259c2 97 const processInstanceStartLoading = ref(false) // 流程实例发起中
3e359e 98 const { push, currentRoute } = useRouter() // 路由
H 99 const message = useMessage() // 消息弹窗
100 const { delView } = useTagsViewStore() // 视图操作
101
102 const detailForm: any = ref({
103   rule: [],
104   option: {},
105   value: {}
106 }) // 流程表单详情
107 const fApi = ref<ApiAttrs>()
108 // 指定审批人
109 const startUserSelectTasks: any = ref([]) // 发起人需要选择审批人或抄送人的任务列表
110 const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
111 const bpmnXML: any = ref(null) // BPMN 数据
112 const simpleJson = ref<string | undefined>() // Simple 设计器数据 json 格式
113
114 const activeTab = ref('form') // 当前的 Tab
115 const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) // 审批节点信息
116
117 /** 设置表单信息、获取流程图数据 **/
118 const initProcessInfo = async (row: any, formVariables?: any) => {
119   // 重置指定审批人
120   startUserSelectTasks.value = []
121   startUserSelectAssignees.value = {}
122
123   // 情况一:流程表单
124   if (row.formType == 10) {
125     // 设置表单
126     // 注意:需要从 formVariables 中,移除不在 row.formFields 的值。
127     // 原因是:后端返回的 formVariables 里面,会有一些非表单的信息。例如说,某个流程节点的审批人。
128     //        这样,就可能导致一个流程被审批不通过后,重新发起时,会直接后端报错!!!
129     const allowedFields = decodeFields(row.formFields).map((fieldObj: any) => fieldObj.field)
130     for (const key in formVariables) {
131       if (!allowedFields.includes(key)) {
132         delete formVariables[key]
133       }
134     }
135     setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables)
c9a6f7 136
3e359e 137     await nextTick()
H 138     fApi.value?.btn.show(false) // 隐藏提交按钮
c9a6f7 139
3e359e 140     // 获取流程审批信息
H 141     await getApprovalDetail(row)
142
143     // 加载流程图
144     const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id)
145     if (processDefinitionDetail) {
146       bpmnXML.value = processDefinitionDetail.bpmnXml
147       simpleJson.value = processDefinitionDetail.simpleModel
148     }
149     // 情况二:业务表单
150   } else if (row.formCustomCreatePath) {
151     await push({
152       path: row.formCustomCreatePath
153     })
154     // 这里暂时无需加载流程图,因为跳出到另外个 Tab;
155   }
156 }
157
158 /** 获取审批详情 */
159 const getApprovalDetail = async (row: any) => {
160   try {
c9a6f7 161     // TODO 获取审批详情,设置 activityId 为发起人节点(为了获取字段权限。暂时只对 Simple 设计器有效)
H 162     const data = await ProcessInstanceApi.getApprovalDetail({
163       processDefinitionId: row.id,
164       activityId: NodeId.START_USER_NODE_ID
165     })
166
3e359e 167     if (!data) {
H 168       message.error('查询不到审批详情信息!')
169       return
170     }
171
172     // 获取发起人自选的任务
173     startUserSelectTasks.value = data.activityNodes?.filter(
174       (node: ApprovalNodeInfo) => CandidateStrategy.START_USER_SELECT === node.candidateStrategy
175     )
176     if (startUserSelectTasks.value?.length > 0) {
177       for (const node of startUserSelectTasks.value) {
178         startUserSelectAssignees.value[node.id] = []
179       }
180     }
181
182     // 获取审批节点,显示 Timeline 的数据
183     activityNodes.value = data.activityNodes
c9a6f7 184     // 获取表单字段权限
H 185     const formFieldsPermission = data.formFieldsPermission
186     // 设置表单字段权限
187     if (formFieldsPermission) {
188       Object.keys(formFieldsPermission).forEach((item) => {
189         setFieldPermission(item, formFieldsPermission[item])
190       })
191     }
3e359e 192   } finally {
c9a6f7 193   }
H 194 }
195
196 /**
197  * 设置表单权限
198  */
199 const setFieldPermission = (field: string, permission: string) => {
200   if (permission === FieldPermissionType.READ) {
201     //@ts-ignore
202     fApi.value?.disabled(true, field)
203   }
204   if (permission === FieldPermissionType.WRITE) {
205     //@ts-ignore
206     fApi.value?.disabled(false, field)
207   }
208   if (permission === FieldPermissionType.NONE) {
209     //@ts-ignore
210     fApi.value?.hidden(true, field)
3e359e 211   }
H 212 }
213
214 /** 提交按钮 */
215 const submitForm = async () => {
216   if (!fApi.value || !props.selectProcessDefinition) {
217     return
218   }
9259c2 219   // 流程表单校验
H 220   await fApi.value.validate()
3e359e 221   // 如果有指定审批人,需要校验
H 222   if (startUserSelectTasks.value?.length > 0) {
223     for (const userTask of startUserSelectTasks.value) {
224       if (
225         Array.isArray(startUserSelectAssignees.value[userTask.id]) &&
226         startUserSelectAssignees.value[userTask.id].length === 0
227       )
228         return message.warning(`请选择${userTask.name}的候选人`)
229     }
230   }
231
232   // 提交请求
9259c2 233   processInstanceStartLoading.value = true
3e359e 234   try {
H 235     await ProcessInstanceApi.createProcessInstance({
236       processDefinitionId: props.selectProcessDefinition.id,
237       variables: detailForm.value.value,
238       startUserSelectAssignees: startUserSelectAssignees.value
239     })
240     // 提示
241     message.success('发起流程成功')
242     // 跳转回去
243     delView(unref(currentRoute))
244     await push({
245       name: 'BpmProcessInstanceMy'
246     })
247   } finally {
9259c2 248     processInstanceStartLoading.value = false
3e359e 249   }
H 250 }
251
252 /** 取消发起审批 */
253 const handleCancel = () => {
254   emit('cancel')
255 }
256
257 /** 选择发起人 */
258 const selectUserConfirm = (id: string, userList: any[]) => {
259   startUserSelectAssignees.value[id] = userList?.map((item: any) => item.id)
260 }
261
262 defineExpose({ initProcessInfo })
263 </script>
264
265 <style lang="scss" scoped>
266 $wrap-padding-height: 20px;
267 $wrap-margin-height: 15px;
268 $button-height: 51px;
269 $process-header-height: 105px;
270
271 .processInstance-wrap-main {
272   height: calc(
273     100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
274   );
275   max-height: calc(
276     100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
277   );
278   overflow: auto;
279
280   .form-scroll-area {
281     height: calc(
282       100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
c9a6f7 283       $process-header-height - 40px
3e359e 284     );
H 285     max-height: calc(
286       100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
c9a6f7 287       $process-header-height - 40px
3e359e 288     );
H 289     overflow: auto;
290   }
291 }
292
293 .form-box {
294   :deep(.el-card) {
295     border: none;
296   }
297 }
298 </style>