Răsfoiți Sursa

feat: 完善UserTask审批签名

Lesan 7 luni în urmă
părinte
comite
04f4f630ad

+ 1 - 0
src/api/bpm/processInstance/index.ts

@@ -36,6 +36,7 @@ export type ApprovalTaskInfo = {
   assigneeUser: User
   status: number
   reason: string
+  sign: string
 }
 
 // 审批节点信息

+ 23 - 1
src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue

@@ -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" />&nbsp; 再次提交
     </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>
 

+ 9 - 0
src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue

@@ -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)未来的审批任务等 -->

+ 84 - 0
src/views/bpm/processInstance/detail/SignDialog.vue

@@ -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>