From d10439fb9e19df8a15d1456c0dbb243bc2240ded Mon Sep 17 00:00:00 2001 From: gaoluyang <2820782392@qq.com> Date: 星期五, 08 八月 2025 14:56:06 +0800 Subject: [PATCH] 1.协同审批-提交时添加签名 --- src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue | 381 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 files changed, 321 insertions(+), 60 deletions(-) diff --git a/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue b/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue index 5c1a34f..9b92361 100644 --- a/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue +++ b/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue @@ -1,97 +1,331 @@ <template> <div> <el-dialog - v-model="dialogFormVisible" - :title="operationType === 'add' ? '鏂板瀹℃壒娴佺▼' : '缂栬緫瀹℃壒娴佺▼'" - width="700px" - @close="closeDia" + v-model="dialogFormVisible" + :title="operationType === 'add' ? '鏂板瀹℃壒娴佺▼' : '缂栬緫瀹℃壒娴佺▼'" + width="700px" + @close="closeDia" > + <el-form :model="form" label-width="140px" label-position="top" ref="formRef"> + <el-row> + <el-col :span="24"> + <el-form-item label="娴佺▼缂栧彿锛�" prop="approveId"> + <el-input v-model="form.approveId" placeholder="鑷姩缂栧彿" clearable disabled/> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="24"> + <el-form-item label="鐢宠閮ㄩ棬锛�" prop="approveDeptId"> + <el-select + disabled + v-model="form.approveDeptId" + placeholder="閫夋嫨閮ㄩ棬" + > + <el-option + v-for="user in productOptions" + :key="user.deptId" + :label="user.deptName" + :value="user.deptId" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="24"> + <el-form-item label="瀹℃壒浜嬬敱锛�" prop="approveReason"> + <el-input v-model="form.approveReason" placeholder="璇疯緭鍏�" clearable type="textarea" disabled/> + </el-form-item> + </el-col> + </el-row> + <!-- 瀹℃壒浜洪�夋嫨锛堝姩鎬佽妭鐐癸級 --> + <el-row :gutter="30"> + <el-col :span="12"> + <el-form-item label="鐢宠浜猴細" prop="approveUser"> + <el-select + v-model="form.approveUser" + placeholder="閫夋嫨浜哄憳" + disabled + > + <el-option + v-for="user in userList" + :key="user.userId" + :label="user.nickName" + :value="user.userId" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="鐢宠鏃ユ湡锛�" prop="approveTime"> + <el-date-picker + v-model="form.approveTime" + type="date" + placeholder="璇烽�夋嫨鏃ユ湡" + value-format="YYYY-MM-DD" + format="YYYY-MM-DD" + clearable + style="width: 100%" + disabled + /> + </el-form-item> + </el-col> + </el-row> + </el-form> <el-form :model="{ activities }" ref="formRef" label-position="top"> - <el-timeline style="max-width: 600px"> - <el-timeline-item - v-for="(activity, index) in activities" - :key="index" - :type="activity.current ? 'primary' : ''" - :hollow="activity.current" - :timestamp="activity.timestamp" + <el-steps :active="getActiveStep()" finish-status="success" process-status="process" align-center direction="vertical"> + <el-step + v-for="(activity, index) in activities" + :key="index" + finish-status="success" + :title="getNodeTitle(index, activities.length)" + :description="activity.approveNodeUser" + :icon="getNodeIcon(activity, index)" > - <el-card> - <span style="font-size: 18px;font-weight: 700">{{activity.content}}</span> - <div style="margin: 10px 0"> - <span style="font-size: 16px;font-weight: 600">瀹℃壒浜猴細{{activity.people}}</span> + <template #icon> + <el-icon v-if="activity.approveNodeStatus === 2" color="red" :size="22"><WarningFilled /></el-icon> + <el-icon v-else-if="activity.isShen" color="#1890ff" :size="22"><Edit /></el-icon> + <el-icon v-else-if="activity.approveNodeStatus === 1" color="#67C23A" :size="26"><Check /></el-icon> + <el-icon v-else color="#C0C4CC" :size="22"><MoreFilled /></el-icon> + </template> + <template #title> + <span style="color: #000000">{{ getNodeTitle(index, activities.length) }}</span> + </template> + <template #description> + <div class="node-user"> + <div class="avatar-wrapper"> + <img :src="userStore.avatar" class="user-avatar" alt=""/> + </div> + <span style="color: #000000">{{ activity.approveNodeUser }}-{{activity.isApproval}}</span> </div> - <div> - <span style="margin-bottom: 8px;display: inline-block;font-size: 16px;font-weight: 600">瀹℃壒鎰忚锛�</span> + <div v-if="!activity.isShen" class="node-reason"> + <span>瀹℃壒鎰忚锛�</span>{{ activity.approveNodeReason }} + </div> + <div v-if="!activity.isShen" class="node-reason"> + <span>绛惧悕锛�</span> + <img :src="activity.urlTem" class="signImg" alt="" v-if="activity.urlTem"/> + </div> + <div v-else-if="activity.isShen"> <el-form-item - v-if="activity.current" - :prop="'activities.' + index + '.value'" + :prop="'activities.' + index + '.approveNodeReason'" :rules="[{ required: true, message: '瀹℃壒鎰忚涓嶈兘涓虹┖', trigger: 'blur' }]" > - <el-input v-model="activity.value" clearable type="textarea" :disabled="operationType === 'view'"></el-input> - </el-form-item> - <el-form-item v-else> - <el-input v-model="activity.value" clearable type="textarea" disabled></el-input> + <el-input v-model="activity.approveNodeReason" clearable type="textarea" :disabled="operationType === 'view'"></el-input> </el-form-item> </div> - </el-card> - </el-timeline-item> - </el-timeline> + </template> + </el-step> + </el-steps> </el-form> <template #footer v-if="operationType === 'approval'"> <div class="dialog-footer"> - <el-button type="primary" @click="submitForm">纭</el-button> + <el-button type="primary" @click="submitForm(2)">涓嶉�氳繃</el-button> + <el-button type="primary" @click="openSignatureDialog(1)">閫氳繃</el-button> <el-button @click="closeDia">鍙栨秷</el-button> </div> </template> + </el-dialog> + <!-- 鐢靛瓙绛惧悕寮圭獥锛坴ue3-signature-pad锛� --> + <el-dialog v-model="signatureDialogVisible" title="鐢靛瓙绛惧悕" width="600px" append-to-body> + <vueEsign + ref="esign" + class="mySign" + :width="800" + :height="300" + :isCrop="isCrop" + :lineWidth="lineWidth" + :lineColor="lineColor" + /> + <div style="margin-top:10px;"> + <el-button @click="clearSignature">娓呴櫎</el-button> + <el-button type="primary" @click="confirmSignature">纭畾</el-button> + </div> </el-dialog> </div> </template> <script setup> -import {getCurrentInstance, ref} from "vue"; -import {approveProcessDetails} from "../../../../api/collaborativeApproval/approvalProcess.js"; +import { getCurrentInstance, reactive, ref, toRefs } from "vue"; +import vueEsign from "vue-esign"; +import { + approveProcessDetails, + getDept, + updateApproveNode +} from "@/api/collaborativeApproval/approvalProcess.js"; +import useUserStore from "@/store/modules/user.js"; +import {userListNoPageByTenantId} from "@/api/system/user.js"; +import { WarningFilled, Edit, Check, MoreFilled } from '@element-plus/icons-vue' +import { getToken } from "@/utils/auth"; const emit = defineEmits(['close']) const { proxy } = getCurrentInstance() const dialogFormVisible = ref(false); const operationType = ref('') -const activities = ref([ - { - content: '鑺傜偣1', - timestamp: '', - type: 'primary', - hollow: true, - people: 'admin', - value: '' - }, - { - content: '鑺傜偣2', - timestamp: '', - type: '', - hollow: false, - current: true, - people: 'admin', - value: '' - }, -]) +const activities = ref([]) const formRef = ref(null); +const userStore = useUserStore() +const productOptions = ref([]); +const userList = ref([]) +const data = reactive({ + form: { + approveTime: "", + approveId: "", + approveUser: "", + approveDeptId: "", + approveReason: "", + checkResult: "", + }, +}); +const { form } = toRefs(data); +const signatureDialogVisible = ref(false); +const signatureImg = ref(''); +let submitStatus = null; // 涓存椂瀛樺偍閫氳繃/涓嶉�氳繃鐘舵�� +const isCrop = ref(""); +const esign = ref(null); +const lineWidth = ref(0); +const lineColor = ref("#000000"); + +// 涓婁紶閰嶇疆 +const upload = reactive({ + // 涓婁紶鐨勫湴鍧� + url: import.meta.env.VITE_APP_BASE_API + "/file/upload", + // 璁剧疆涓婁紶鐨勮姹傚ご閮� + headers: { Authorization: "Bearer " + getToken() }, +}); + +// 鑺傜偣鏍囬 +const getNodeTitle = (index, len) => { + if (index === len - 1) return '缁撴潫'; + return '瀹℃壒'; +}; + +// 鑾峰彇褰撳墠婵�娲绘楠� +const getActiveStep = () => { + // 濡傛灉鎵�鏈� isShen 閮戒负 false锛岃繑鍥炴渶鍚庝竴涓楠わ紙鍏ㄩ儴瀹屾垚锛� + const hasActive = activities.value.some(a => a.isShen === true); + if (!hasActive) return activities.value.length; + // 褰撳墠鑺傜偣绱㈠紩 + return activities.value.findIndex(a => a.isShen == true); +}; +// 姝ラicon +const getNodeIcon = (activity, index) => { + if (activity.approveNodeStatus === 2) return 'el-icon-warning'; // 涓嶉�氳繃 + if (activity.isShen) return 'Edit'; + return ''; +}; // 鎵撳紑寮规 const openDialog = (type, row) => { operationType.value = type; dialogFormVisible.value = true; - approveProcessDetails({id: row.approveId}).then((res) => { - console.log(res) - }) -} -// 鎻愪氦瀹℃壒 -const submitForm = () => { - formRef.value.validate(valid => { - if (valid) { - // 鏍¢獙閫氳繃鍚庣殑閫昏緫 - } + userListNoPageByTenantId().then((res) => { + userList.value = res.data; + }); + form.value = {...row} + getProductOptions() + approveProcessDetails(row.approveId).then((res) => { + activities.value = res.data + // 澧炲姞isApproval瀛楁 + activities.value.forEach(item => { + if (item.url.includes('word')) { + item.urlTem = item.url.replaceAll('word', 'img') + } else { + item.urlTem = item.url + } + if (item.approveNodeStatus === 2) { + item.isApproval = '宸查┏鍥�'; + } else if (item.approveNodeStatus === 1) { + item.isApproval = '宸插悓鎰�'; + } else { + item.isApproval = '鏈鎵�'; + } + }) }) } +const getProductOptions = () => { + getDept().then((res) => { + productOptions.value = res.data; + }); +}; +// 鎵撳紑绛惧悕寮圭獥 +const openSignatureDialog = (status) => { + submitStatus = status; + signatureDialogVisible.value = true; +}; +// 娓呴櫎绛惧悕 +const clearSignature = () => { + esign.value.reset(); +}; +// 纭绛惧悕 +const confirmSignature = () => { + esign.value.generate().then((res) => { + console.log(res); + // 灏哹ase64杞崲涓轰簩杩涘埗 + const base64Data = res.split(',')[1]; // 绉婚櫎data:image/png;base64,鍓嶇紑 + const binaryString = atob(base64Data); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + signatureImg.value = bytes; + + // 鍒涘缓鏂囦欢瀵硅薄鐢ㄤ簬涓婁紶 + const blob = new Blob([bytes], { type: 'image/png' }); + const file = new File([blob], 'signature.png', { type: 'image/png' }); + + // 鍒涘缓FormData + const formData = new FormData(); + formData.append('file', file); + + // 涓婁紶绛惧悕鍥剧墖 + fetch(upload.url, { + method: 'POST', + headers: upload.headers, + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.code === 200) { + console.log('data---', data) + let tempFileIds = []; + tempFileIds.push(data.data.tempId); + signatureDialogVisible.value = false; + clearSignature(); + // 鍙湁閫氳繃鏃舵墠浼犻�掔鍚嶆枃浠禝D + if (submitStatus === 1) { + submitForm(submitStatus, tempFileIds); + } else { + submitForm(submitStatus); + } + } else { + proxy.$modal.msgError("绛惧悕鍥剧墖涓婁紶澶辫触锛�" + data.msg); + } + }) + .catch(error => { + console.error('涓婁紶澶辫触:', error); + proxy.$modal.msgError("绛惧悕鍥剧墖涓婁紶澶辫触"); + }); + }).catch((err) => { + console.log(err); + proxy.$modal.msgWarning("璇峰厛绛惧悕锛�"); + }) +}; +// 鎻愪氦瀹℃壒 +const submitForm = (status, tempFileIds) => { + const filteredActivities = activities.value.filter(activity => activity.isShen); + filteredActivities[0].approveNodeStatus = status; + // 鍙湁閫氳繃鏃舵墠闇�瑕佺鍚� + if (status === 1 && tempFileIds) { + filteredActivities[0].tempFileIds = tempFileIds; + } + // 鍒ゆ柇鏄惁涓烘渶鍚庝竴姝� + const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length-1; + updateApproveNode({ ...filteredActivities[0], isLast }).then(() => { + proxy.$modal.msgSuccess("鎻愪氦鎴愬姛"); + closeDia(); + }); +}; // 鍏抽棴寮规 const closeDia = () => { proxy.resetForm("formRef"); @@ -104,7 +338,34 @@ </script> <style scoped> -.el-timeline { - padding-left: 10px; + +.node-user { + margin: 10px 0; + font-size: 16px; + font-weight: 600; + display: flex; + align-items: center; + gap: 8px; +} +.node-status { + color: #1890ff; + margin-left: 8px; + font-size: 14px; +} +.node-reason { + font-size: 15px; + color: #333; + margin: 10px 0; +} +.user-avatar { + cursor: pointer; + width: 30px; + height: 30px; + border-radius: 50px; +} +.signImg { + cursor: pointer; + width: 200px; + height: 60px; } </style> \ No newline at end of file -- Gitblit v1.9.3