提交 | 用户 | 时间
|
820397
|
1 |
<template> |
H |
2 |
<ContentWrap> |
|
3 |
<!-- 审批信息 --> |
|
4 |
<el-card |
|
5 |
v-for="(item, index) in runningTasks" |
|
6 |
:key="index" |
|
7 |
v-loading="processInstanceLoading" |
|
8 |
class="box-card" |
|
9 |
> |
|
10 |
<template #header> |
|
11 |
<span class="el-icon-picture-outline">审批任务【{{ item.name }}】</span> |
|
12 |
</template> |
|
13 |
<el-col :offset="6" :span="16"> |
|
14 |
<el-form |
|
15 |
:ref="'form' + index" |
|
16 |
:model="auditForms[index]" |
|
17 |
:rules="auditRule" |
|
18 |
label-width="100px" |
|
19 |
> |
|
20 |
<el-form-item v-if="processInstance && processInstance.name" label="流程名"> |
|
21 |
{{ processInstance.name }} |
|
22 |
</el-form-item> |
|
23 |
<el-form-item v-if="processInstance && processInstance.startUser" label="流程发起人"> |
|
24 |
{{ processInstance?.startUser.nickname }} |
|
25 |
<el-tag size="small" type="info">{{ processInstance?.startUser.deptName }}</el-tag> |
|
26 |
</el-form-item> |
|
27 |
<el-card v-if="runningTasks[index].formId > 0" class="mb-15px !-mt-10px"> |
|
28 |
<template #header> |
|
29 |
<span class="el-icon-picture-outline"> |
|
30 |
填写表单【{{ runningTasks[index]?.formName }}】 |
|
31 |
</span> |
|
32 |
</template> |
|
33 |
<form-create |
|
34 |
v-model="approveForms[index].value" |
|
35 |
v-model:api="approveFormFApis[index]" |
|
36 |
:option="approveForms[index].option" |
|
37 |
:rule="approveForms[index].rule" |
|
38 |
/> |
|
39 |
</el-card> |
|
40 |
<el-form-item label="审批建议" prop="reason"> |
|
41 |
<el-input |
|
42 |
v-model="auditForms[index].reason" |
|
43 |
placeholder="请输入审批建议" |
|
44 |
type="textarea" |
|
45 |
/> |
|
46 |
</el-form-item> |
|
47 |
<el-form-item label="抄送人" prop="copyUserIds"> |
|
48 |
<el-select v-model="auditForms[index].copyUserIds" multiple placeholder="请选择抄送人"> |
|
49 |
<el-option |
|
50 |
v-for="item in userOptions" |
|
51 |
:key="item.id" |
|
52 |
:label="item.nickname" |
|
53 |
:value="item.id" |
|
54 |
/> |
|
55 |
</el-select> |
|
56 |
</el-form-item> |
|
57 |
</el-form> |
|
58 |
<div style="margin-bottom: 20px; margin-left: 10%; font-size: 14px"> |
|
59 |
<el-button type="success" @click="handleAudit(item, true)"> |
|
60 |
<Icon icon="ep:select" /> |
|
61 |
通过 |
|
62 |
</el-button> |
|
63 |
<el-button type="danger" @click="handleAudit(item, false)"> |
|
64 |
<Icon icon="ep:close" /> |
|
65 |
不通过 |
|
66 |
</el-button> |
|
67 |
<el-button type="primary" @click="openTaskUpdateAssigneeForm(item.id)"> |
|
68 |
<Icon icon="ep:edit" /> |
|
69 |
转办 |
|
70 |
</el-button> |
|
71 |
<el-button type="primary" @click="handleDelegate(item)"> |
|
72 |
<Icon icon="ep:position" /> |
|
73 |
委派 |
|
74 |
</el-button> |
|
75 |
<el-button type="primary" @click="handleSign(item)"> |
|
76 |
<Icon icon="ep:plus" /> |
|
77 |
加签 |
|
78 |
</el-button> |
|
79 |
<el-button type="warning" @click="handleBack(item)"> |
|
80 |
<Icon icon="ep:back" /> |
|
81 |
回退 |
|
82 |
</el-button> |
|
83 |
</div> |
|
84 |
</el-col> |
|
85 |
</el-card> |
|
86 |
|
|
87 |
<!-- 申请信息 --> |
|
88 |
<el-card v-loading="processInstanceLoading" class="box-card"> |
|
89 |
<template #header> |
|
90 |
<span class="el-icon-document">申请信息【{{ processInstance.name }}】</span> |
|
91 |
</template> |
|
92 |
<!-- 情况一:流程表单 --> |
|
93 |
<el-col v-if="processInstance?.processDefinition?.formType === 10" :offset="6" :span="16"> |
|
94 |
<form-create |
|
95 |
v-model="detailForm.value" |
|
96 |
v-model:api="fApi" |
|
97 |
:option="detailForm.option" |
|
98 |
:rule="detailForm.rule" |
|
99 |
/> |
|
100 |
</el-col> |
|
101 |
<!-- 情况二:业务表单 --> |
|
102 |
<div v-if="processInstance?.processDefinition?.formType === 20"> |
|
103 |
<BusinessFormComponent :id="processInstance.businessKey" /> |
|
104 |
</div> |
|
105 |
</el-card> |
|
106 |
|
|
107 |
<!-- 审批记录 --> |
|
108 |
<ProcessInstanceTaskList |
|
109 |
:loading="tasksLoad" |
|
110 |
:process-instance="processInstance" |
|
111 |
:tasks="tasks" |
|
112 |
@refresh="getTaskList" |
|
113 |
/> |
|
114 |
|
|
115 |
<!-- 高亮流程图 --> |
|
116 |
<ProcessInstanceBpmnViewer |
|
117 |
:id="`${id}`" |
|
118 |
:bpmn-xml="bpmnXml" |
|
119 |
:loading="processInstanceLoading" |
|
120 |
:process-instance="processInstance" |
|
121 |
:tasks="tasks" |
|
122 |
/> |
|
123 |
|
|
124 |
<!-- 弹窗:转派审批人 --> |
|
125 |
<TaskTransferForm ref="taskTransferFormRef" @success="getDetail" /> |
|
126 |
<!-- 弹窗:回退节点 --> |
|
127 |
<TaskReturnForm ref="taskReturnFormRef" @success="getDetail" /> |
|
128 |
<!-- 弹窗:委派,将任务委派给别人处理,处理完成后,会重新回到原审批人手中--> |
|
129 |
<TaskDelegateForm ref="taskDelegateForm" @success="getDetail" /> |
|
130 |
<!-- 弹窗:加签,当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 --> |
|
131 |
<TaskSignCreateForm ref="taskSignCreateFormRef" @success="getDetail" /> |
|
132 |
</ContentWrap> |
|
133 |
</template> |
|
134 |
<script lang="ts" setup> |
|
135 |
import { useUserStore } from '@/store/modules/user' |
|
136 |
import { setConfAndFields2 } from '@/utils/formCreate' |
|
137 |
import type { ApiAttrs } from '@form-create/element-ui/types/config' |
|
138 |
import * as DefinitionApi from '@/api/bpm/definition' |
|
139 |
import * as ProcessInstanceApi from '@/api/bpm/processInstance' |
|
140 |
import * as TaskApi from '@/api/bpm/task' |
|
141 |
import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue' |
|
142 |
import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue' |
|
143 |
import TaskReturnForm from './dialog/TaskReturnForm.vue' |
|
144 |
import TaskDelegateForm from './dialog/TaskDelegateForm.vue' |
|
145 |
import TaskTransferForm from './dialog/TaskTransferForm.vue' |
|
146 |
import TaskSignCreateForm from './dialog/TaskSignCreateForm.vue' |
|
147 |
import { registerComponent } from '@/utils/routerHelper' |
|
148 |
import { isEmpty } from '@/utils/is' |
|
149 |
import * as UserApi from '@/api/system/user' |
|
150 |
|
|
151 |
defineOptions({ name: 'BpmProcessInstanceDetail' }) |
|
152 |
|
|
153 |
const { query } = useRoute() // 查询参数 |
|
154 |
const message = useMessage() // 消息弹窗 |
|
155 |
const { proxy } = getCurrentInstance() as any |
|
156 |
|
|
157 |
const userId = useUserStore().getUser.id // 当前登录的编号 |
|
158 |
const id = query.id as unknown as string // 流程实例的编号 |
|
159 |
const processInstanceLoading = ref(false) // 流程实例的加载中 |
|
160 |
const processInstance = ref<any>({}) // 流程实例 |
|
161 |
const bpmnXml = ref('') // BPMN XML |
|
162 |
const tasksLoad = ref(true) // 任务的加载中 |
|
163 |
const tasks = ref<any[]>([]) // 任务列表 |
|
164 |
// ========== 审批信息 ========== |
|
165 |
const runningTasks = ref<any[]>([]) // 运行中的任务 |
|
166 |
const auditForms = ref<any[]>([]) // 审批任务的表单 |
|
167 |
const auditRule = reactive({ |
|
168 |
reason: [{ required: true, message: '审批建议不能为空', trigger: 'blur' }] |
|
169 |
}) |
|
170 |
const approveForms = ref<any[]>([]) // 审批通过时,额外的补充信息 |
|
171 |
const approveFormFApis = ref<ApiAttrs[]>([]) // approveForms 的 fAPi |
|
172 |
|
|
173 |
// ========== 申请信息 ========== |
|
174 |
const fApi = ref<ApiAttrs>() // |
|
175 |
const detailForm = ref({ |
|
176 |
rule: [], |
|
177 |
option: {}, |
|
178 |
value: {} |
|
179 |
}) // 流程实例的表单详情 |
|
180 |
|
|
181 |
/** 监听 approveFormFApis,实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */ |
|
182 |
watch( |
|
183 |
() => approveFormFApis.value, |
|
184 |
(value) => { |
|
185 |
value?.forEach((api) => { |
|
186 |
api.btn.show(false) |
|
187 |
api.resetBtn.show(false) |
|
188 |
}) |
|
189 |
}, |
|
190 |
{ |
|
191 |
deep: true |
|
192 |
} |
|
193 |
) |
|
194 |
|
|
195 |
/** 处理审批通过和不通过的操作 */ |
|
196 |
const handleAudit = async (task, pass) => { |
|
197 |
// 1.1 获得对应表单 |
|
198 |
const index = runningTasks.value.indexOf(task) |
|
199 |
const auditFormRef = proxy.$refs['form' + index][0] |
|
200 |
// 1.2 校验表单 |
|
201 |
const elForm = unref(auditFormRef) |
|
202 |
if (!elForm) return |
|
203 |
const valid = await elForm.validate() |
|
204 |
if (!valid) return |
|
205 |
|
|
206 |
// 2.1 提交审批 |
|
207 |
const data = { |
|
208 |
id: task.id, |
|
209 |
reason: auditForms.value[index].reason, |
|
210 |
copyUserIds: auditForms.value[index].copyUserIds |
|
211 |
} |
|
212 |
if (pass) { |
|
213 |
// 审批通过,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交 |
|
214 |
const formCreateApi = approveFormFApis.value[index] |
|
215 |
if (formCreateApi) { |
|
216 |
await formCreateApi.validate() |
|
217 |
data.variables = approveForms.value[index].value |
|
218 |
} |
|
219 |
await TaskApi.approveTask(data) |
|
220 |
message.success('审批通过成功') |
|
221 |
} else { |
|
222 |
await TaskApi.rejectTask(data) |
|
223 |
message.success('审批不通过成功') |
|
224 |
} |
|
225 |
// 2.2 加载最新数据 |
|
226 |
getDetail() |
|
227 |
} |
|
228 |
|
|
229 |
/** 转派审批人 */ |
|
230 |
const taskTransferFormRef = ref() |
|
231 |
const openTaskUpdateAssigneeForm = (id: string) => { |
|
232 |
taskTransferFormRef.value.open(id) |
|
233 |
} |
|
234 |
|
|
235 |
/** 处理审批退回的操作 */ |
|
236 |
const taskDelegateForm = ref() |
|
237 |
const handleDelegate = async (task) => { |
|
238 |
taskDelegateForm.value.open(task.id) |
|
239 |
} |
|
240 |
|
|
241 |
/** 处理审批退回的操作 */ |
|
242 |
const taskReturnFormRef = ref() |
|
243 |
const handleBack = async (task: any) => { |
|
244 |
taskReturnFormRef.value.open(task.id) |
|
245 |
} |
|
246 |
|
|
247 |
/** 处理审批加签的操作 */ |
|
248 |
const taskSignCreateFormRef = ref() |
|
249 |
const handleSign = async (task: any) => { |
|
250 |
taskSignCreateFormRef.value.open(task.id) |
|
251 |
} |
|
252 |
|
|
253 |
/** 获得详情 */ |
|
254 |
const getDetail = () => { |
|
255 |
// 1. 获得流程实例相关 |
|
256 |
getProcessInstance() |
|
257 |
// 2. 获得流程任务列表(审批记录) |
|
258 |
getTaskList() |
|
259 |
} |
|
260 |
|
|
261 |
/** 加载流程实例 */ |
|
262 |
const BusinessFormComponent = ref(null) // 异步组件 |
|
263 |
const getProcessInstance = async () => { |
|
264 |
try { |
|
265 |
processInstanceLoading.value = true |
|
266 |
const data = await ProcessInstanceApi.getProcessInstance(id) |
|
267 |
if (!data) { |
|
268 |
message.error('查询不到流程信息!') |
|
269 |
return |
|
270 |
} |
|
271 |
processInstance.value = data |
|
272 |
|
|
273 |
// 设置表单信息 |
|
274 |
const processDefinition = data.processDefinition |
|
275 |
if (processDefinition.formType === 10) { |
|
276 |
setConfAndFields2( |
|
277 |
detailForm, |
|
278 |
processDefinition.formConf, |
|
279 |
processDefinition.formFields, |
|
280 |
data.formVariables |
|
281 |
) |
|
282 |
nextTick().then(() => { |
|
283 |
fApi.value?.btn.show(false) |
|
284 |
fApi.value?.resetBtn.show(false) |
|
285 |
fApi.value?.disabled(true) |
|
286 |
}) |
|
287 |
} else { |
|
288 |
// 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue |
|
289 |
BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath) |
|
290 |
} |
|
291 |
|
|
292 |
// 加载流程图 |
|
293 |
bpmnXml.value = ( |
|
294 |
await DefinitionApi.getProcessDefinition(processDefinition.id as number) |
|
295 |
)?.bpmnXml |
|
296 |
} finally { |
|
297 |
processInstanceLoading.value = false |
|
298 |
} |
|
299 |
} |
|
300 |
|
|
301 |
/** 加载任务列表 */ |
|
302 |
const getTaskList = async () => { |
|
303 |
runningTasks.value = [] |
|
304 |
auditForms.value = [] |
|
305 |
approveForms.value = [] |
|
306 |
approveFormFApis.value = [] |
|
307 |
try { |
|
308 |
// 获得未取消的任务 |
|
309 |
tasksLoad.value = true |
|
310 |
const data = await TaskApi.getTaskListByProcessInstanceId(id) |
|
311 |
tasks.value = [] |
|
312 |
// 1.1 移除已取消的审批 |
|
313 |
data.forEach((task) => { |
|
314 |
if (task.status !== 4) { |
|
315 |
tasks.value.push(task) |
|
316 |
} |
|
317 |
}) |
|
318 |
// 1.2 排序,将未完成的排在前面,已完成的排在后面; |
|
319 |
tasks.value.sort((a, b) => { |
|
320 |
// 有已完成的情况,按照完成时间倒序 |
|
321 |
if (a.endTime && b.endTime) { |
|
322 |
return b.endTime - a.endTime |
|
323 |
} else if (a.endTime) { |
|
324 |
return 1 |
|
325 |
} else if (b.endTime) { |
|
326 |
return -1 |
|
327 |
// 都是未完成,按照创建时间倒序 |
|
328 |
} else { |
|
329 |
return b.createTime - a.createTime |
|
330 |
} |
|
331 |
}) |
|
332 |
|
|
333 |
// 获得需要自己审批的任务 |
|
334 |
loadRunningTask(tasks.value) |
|
335 |
} finally { |
|
336 |
tasksLoad.value = false |
|
337 |
} |
|
338 |
} |
|
339 |
|
|
340 |
/** |
|
341 |
* 设置 runningTasks 中的任务 |
|
342 |
*/ |
|
343 |
const loadRunningTask = (tasks) => { |
|
344 |
tasks.forEach((task) => { |
|
345 |
if (!isEmpty(task.children)) { |
|
346 |
loadRunningTask(task.children) |
|
347 |
} |
|
348 |
// 2.1 只有待处理才需要 |
|
349 |
if (task.status !== 1 && task.status !== 6) { |
|
350 |
return |
|
351 |
} |
|
352 |
// 2.2 自己不是处理人 |
|
353 |
if (!task.assigneeUser || task.assigneeUser.id !== userId) { |
|
354 |
return |
|
355 |
} |
|
356 |
// 2.3 添加到处理任务 |
|
357 |
runningTasks.value.push({ ...task }) |
|
358 |
auditForms.value.push({ |
|
359 |
reason: '', |
|
360 |
copyUserIds: [] |
|
361 |
}) |
|
362 |
|
|
363 |
// 2.4 处理 approve 表单 |
|
364 |
if (task.formId && task.formConf) { |
|
365 |
const approveForm = {} |
|
366 |
setConfAndFields2(approveForm, task.formConf, task.formFields, task.formVariables) |
|
367 |
approveForms.value.push(approveForm) |
|
368 |
} else { |
|
369 |
approveForms.value.push({}) // 占位,避免为空 |
|
370 |
} |
|
371 |
}) |
|
372 |
} |
|
373 |
|
|
374 |
/** 初始化 */ |
|
375 |
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表 |
|
376 |
onMounted(async () => { |
|
377 |
getDetail() |
|
378 |
// 获得用户列表 |
|
379 |
userOptions.value = await UserApi.getSimpleUserList() |
|
380 |
}) |
|
381 |
</script> |