feat: 完善UserTask审批签名
							parent
							
								
									dd72d35fe4
								
							
						
					
					
						commit
						04f4f630ad
					
				|  | @ -36,6 +36,7 @@ export type ApprovalTaskInfo = { | |||
|   assigneeUser: User | ||||
|   status: number | ||||
|   reason: string | ||||
|   sign: string | ||||
| } | ||||
| 
 | ||||
| // 审批节点信息
 | ||||
|  |  | |||
|  | @ -44,6 +44,12 @@ | |||
|               :rows="4" | ||||
|             /> | ||||
|           </el-form-item> | ||||
|           <el-form-item v-if="runningTask.signEnable" label="签名" prop="sign" ref="approveSignFormRef"> | ||||
|             <el-button @click="signRef.open()">点击签名</el-button> | ||||
|             <el-image class="w-90px h-40px ml-5px" v-if="approveReasonForm.sign" | ||||
|                       :src="approveReasonForm.sign" | ||||
|                       :preview-src-list="[approveReasonForm.sign]"/> | ||||
|           </el-form-item> | ||||
|           <el-form-item> | ||||
|             <el-button :disabled="formLoading" type="success" @click="handleAudit(true, approveFormRef)"> | ||||
|               {{ getButtonDisplayName(OperationButtonType.APPROVE) }} | ||||
|  | @ -471,6 +477,8 @@ | |||
|       <Icon :size="14" icon="ep:refresh" />  再次提交 | ||||
|     </div> | ||||
|   </div> | ||||
| 
 | ||||
|   <SignDialog ref="signRef" @success="handleSignFinish"/> | ||||
| </template> | ||||
| <script lang="ts" setup> | ||||
| import { useUserStoreWithOut } from '@/store/modules/user' | ||||
|  | @ -484,6 +492,7 @@ import { | |||
| } from '@/components/SimpleProcessDesignerV2/src/consts' | ||||
| import { BpmProcessInstanceStatus, BpmModelFormType } from '@/utils/constants' | ||||
| import type { FormInstance, FormRules } from 'element-plus' | ||||
| import SignDialog from "./SignDialog.vue"; | ||||
| defineOptions({ name: 'ProcessInstanceBtnContainer' }) | ||||
| 
 | ||||
| const router = useRouter() // 路由 | ||||
|  | @ -522,11 +531,15 @@ const approveFormFApi = ref<any>({}) // approveForms 的 fAPi | |||
| 
 | ||||
| // 审批通过意见表单 | ||||
| const approveFormRef = ref<FormInstance>() | ||||
| const signRef = ref() | ||||
| const approveSignFormRef = ref() | ||||
| const approveReasonForm = reactive({ | ||||
|   reason: '' | ||||
|   reason: '', | ||||
|   sign: '' | ||||
| }) | ||||
| const approveReasonRule = reactive<FormRules<typeof approveReasonForm>>({ | ||||
|   reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }], | ||||
|   sign: [{ required: true, message: '签名不能为空', trigger: 'change' }] | ||||
| }) | ||||
| // 拒绝表单 | ||||
| const rejectFormRef = ref<FormInstance>() | ||||
|  | @ -672,6 +685,10 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) => | |||
|         reason: approveReasonForm.reason, | ||||
|         variables // 审批通过, 把修改的字段值赋于流程实例变量 | ||||
|       } | ||||
|       // 签名 | ||||
|       if (runningTask.value.signEnable) { | ||||
|         data.sign = approveReasonForm.sign | ||||
|       } | ||||
|       // 多表单处理,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交 | ||||
|       // TODO 芋艿 任务有多表单这里要如何处理,会和可编辑的字段冲突 | ||||
|       const formCreateApi = approveFormFApi.value | ||||
|  | @ -966,6 +983,11 @@ const getUpdatedProcessInstanceVaiables = ()=> { | |||
|   return variables | ||||
| } | ||||
| 
 | ||||
| const handleSignFinish = (url) => { | ||||
|   approveReasonForm.sign = url | ||||
|   approveSignFormRef.value.validate('change') | ||||
| } | ||||
| 
 | ||||
| defineExpose({ loadTodoTask }) | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -123,6 +123,15 @@ | |||
|               > | ||||
|                 审批意见:{{ task.reason }} | ||||
|               </div> | ||||
|               <div | ||||
|                 v-if="task.sign && activity.nodeType === NodeType.USER_TASK_NODE" | ||||
|                 class="text-#a5a5a5 text-13px mt-1 w-full bg-#f8f8fa p2 rounded-md" | ||||
|               > | ||||
|                 签名: | ||||
|                 <el-image class="w-90px h-40px ml-5px" | ||||
|                           :src="task.sign" | ||||
|                           :preview-src-list="[task.sign]"/> | ||||
|               </div> | ||||
|             </teleport> | ||||
|           </div> | ||||
|           <!-- 情况二:遍历每个审批节点下的【候选的】task 任务。例如说,1)依次审批,2)未来的审批任务等 --> | ||||
|  |  | |||
|  | @ -0,0 +1,84 @@ | |||
| <template> | ||||
|   <el-dialog | ||||
|     v-model="signDialogVisible" | ||||
|     title="签名" | ||||
|     width="935" | ||||
|   > | ||||
|     <div class="position-relative"> | ||||
|       <Vue3Signature class="b b-solid b-gray" ref="signature" w="900px" h="400px"/> | ||||
|       <el-button | ||||
|         style="position: absolute; bottom: 20px; right: 10px" | ||||
|         type="primary" | ||||
|         text | ||||
|         size="small" | ||||
|         @click="signature.clear()" | ||||
|       > | ||||
|         <Icon icon="ep:delete" class="mr-5px"/> | ||||
|         清除 | ||||
|       </el-button> | ||||
|     </div> | ||||
|     <template #footer> | ||||
|       <div class="dialog-footer"> | ||||
|         <el-button @click="signDialogVisible = false">取消</el-button> | ||||
|         <el-button type="primary" @click="submit"> | ||||
|           提交 | ||||
|         </el-button> | ||||
|       </div> | ||||
|     </template> | ||||
|   </el-dialog> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
| import Vue3Signature from "vue3-signature" | ||||
| import * as FileApi from '@/api/infra/file' | ||||
| 
 | ||||
| const message = useMessage() // 消息弹窗 | ||||
| const signDialogVisible = ref(false) | ||||
| const signature = ref() | ||||
| 
 | ||||
| const open = async () => { | ||||
|   signDialogVisible.value = true | ||||
| } | ||||
| defineExpose({open}) | ||||
| 
 | ||||
| const emits = defineEmits(['success']) | ||||
| const submit = async () => { | ||||
|   message.success('签名上传中请稍等。。。') | ||||
|   const res = await FileApi.updateFile({file: base64ToFile(signature.value.save('image/png'), '签名')}) | ||||
|   emits('success', res.data) | ||||
|   signDialogVisible.value = false | ||||
| } | ||||
| 
 | ||||
| const base64ToFile = (base64, fileName) => { | ||||
|   // 将base64按照 , 进行分割 将前缀  与后续内容分隔开 | ||||
|   let data = base64.split(','); | ||||
|   // 利用正则表达式 从前缀中获取图片的类型信息(image/png、image/jpeg、image/webp等) | ||||
|   let type = data[0].match(/:(.*?);/)[1]; | ||||
|   // 从图片的类型信息中 获取具体的文件格式后缀(png、jpeg、webp) | ||||
|   let suffix = type.split('/')[1]; | ||||
|   // 使用atob()对base64数据进行解码  结果是一个文件数据流 以字符串的格式输出 | ||||
|   const bstr = window.atob(data[1]); | ||||
|   // 获取解码结果字符串的长度 | ||||
|   let n = bstr.length | ||||
|   // 根据解码结果字符串的长度创建一个等长的整形数字数组 | ||||
|   // 但在创建时 所有元素初始值都为 0 | ||||
|   const u8arr = new Uint8Array(n) | ||||
|   // 将整形数组的每个元素填充为解码结果字符串对应位置字符的UTF-16 编码单元 | ||||
|   while (n--) { | ||||
|     // charCodeAt():获取给定索引处字符对应的 UTF-16 代码单元 | ||||
|     u8arr[n] = bstr.charCodeAt(n) | ||||
|   } | ||||
|   // 利用构造函数创建File文件对象 | ||||
|   // new File(bits, name, options) | ||||
|   const file = new File([u8arr], `${fileName}.${suffix}`, { | ||||
|     type: type | ||||
|   }) | ||||
|   // 将File文件对象返回给方法的调用者 | ||||
|   return file; | ||||
| } | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| <style scoped> | ||||
| 
 | ||||
| </style> | ||||
		Loading…
	
		Reference in New Issue
	
	 Lesan
						Lesan