使用Vue3封裝實(shí)現(xiàn)支持Base64導(dǎo)出的電子簽名組件
默認(rèn)支持簽字回顯,base64壓縮,內(nèi)存釋放
傳參支持禁用簽字也就是查看,組件大小內(nèi)置'small', 'default', 'large'三個大小
效果圖
準(zhǔn)備工作
組件內(nèi)用到elementPlus,vue-esign
組件,使用前提前安裝好。
組件代碼
<template> <!-- 簽名容器 --> <div class="sign-container" > <div class="sign-preview" :class="[sizeClass, { 'has-sign': base64Img }]" @click="openDialog"> <img v-if="base64Img" :src="base64Img" class="preview-image" /> <div v-else class="placeholder"> <el-icon><EditPen /></el-icon> <span>點(diǎn)擊簽名</span> </div> </div> <!-- 簽字彈窗 --> <el-dialog v-model="dialogVisible" title="電子簽名" width="800px"> <vue-esign ref="esignRef" :width="800" :height="300" :lineWidth="4" :lineColor="'#000000'" :bgColor="'#ffffff'" :id="uuid" /> <template #footer> <el-button @click="dialogVisible = false">取消</el-button> <el-button @click="handleReset">清空</el-button> <el-button type="primary" @click="handleConfirm">確認(rèn)</el-button> </template> </el-dialog> </div> </template> <script setup> import { ref } from 'vue'; import { useMessage, useMessageBox } from '/@/hooks/message'; import { generateUUID } from '/@/utils/other'; import vueEsign from 'vue-esign'; // 生成組件唯一id const uuid = ref('id-' + generateUUID()); // 組件尺寸 const sizeClass = computed(() => `sign-size--${props.size}`); const emit = defineEmits(['update:modelValue']); const props = defineProps({ modelValue: String, // v-model綁定 disabled: { type: Boolean, default: false, }, size: { type: String, default: 'default', validator: (v) => ['small', 'default', 'large'].includes(v), }, }); const dialogVisible = ref(false); const esignRef = ref(null); const base64Img = ref(props.modelValue); // 打開彈窗時重置畫布 const openDialog = () => { if (props.disabled) return; dialogVisible.value = true; // handleReset(); }; // 清空畫布(保留二次確認(rèn)) const handleReset = async () => { try { await useMessageBox().confirm('此操作將清空簽名,確定嗎?'); esignRef.value?.reset(); } catch {} }; // 生成簽名后壓縮 const compressBase64 = (base64) => { return new Promise((resolve, reject) => { const img = new Image(); img.src = base64; img.onload = () => { // 創(chuàng)建canvas并設(shè)置縮放尺寸 const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // 計(jì)算壓縮后尺寸(取原圖50%) const targetWidth = img.width * 0.5; const targetHeight = img.height * 0.5; // 設(shè)置畫布尺寸 canvas.width = targetWidth; canvas.height = targetHeight; // 繪制壓縮圖像 ctx.drawImage(img, 0, 0, targetWidth, targetHeight); // 生成新base64(自動處理格式) const mimeType = base64.match(/data:(.*?);/)[1]; canvas.toBlob( (blob) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.readAsDataURL(blob); }, mimeType, 0.6 ); // 質(zhì)量參數(shù)生效于JPEG/WebP格式 }; img.onerror = (e) => reject('圖片加載失敗'); }); }; // 確認(rèn)簽名 const handleConfirm = async () => { esignRef.value .generate() .then(async (res) => { base64Img.value = await compressBase64(res); // 驗(yàn)證壓縮效果 const originalSize = Math.round((res.length * 3) / 4 / 1024); const compressedSize = Math.round((base64Img.value.length * 3) / 4 / 1024); console.log(` 尺寸變化:${originalSize}KB → ${compressedSize}KB`); emit('update:modelValue', base64Img.value); dialogVisible.value = false; }) .catch(() => { base64Img.value = ''; emit('update:modelValue', ''); dialogVisible.value = false; }); }; // watch同步 watch( () => props.modelValue, async (val) => { if (!val) { base64Img.value = ''; esignRef.value?.reset(); } console.log(val); base64Img.value = await compressBase64(val); } ); onBeforeUnmount(() => { // 釋放canvas內(nèi)存 const canvas = esignRef.value?.$el.querySelector('canvas'); canvas.width = 0; canvas.height = 0; URL.revokeObjectURL(base64Img.value); // 釋放Blob URL }); </script> <style scoped lang="scss"> .sign-container { display: inline-block; cursor: pointer; } .sign-preview { border: 1px solid #dcdfe6; background: #fff; border-radius: 4px; &.sign-size--small { width: 120px; height: 60px; } &.sign-size--default { width: 180px; height: 90px; } &.sign-size--large { width: 240px; height: 120px; } &.has-sign { border-color: var(--el-color-primary); } .preview-image { width: 100%; height: 100%; object-fit: contain; } .placeholder { height: 100%; display: flex; align-items: center; justify-content: center; color: #909399; .el-icon { margin-right: 8px; font-size: 18px; } } } </style>
使用組件
<el-form ref="dataFormRef" :model="form" inline :rules="dataRules"> <el-form-item label="經(jīng)辦人簽字" prop="signatureHandler" label-width="8em"> <!-- 簽名組件 --> <signature-component v-model="form.signatureHandler" /> </el-form-item> </el-form>
注意事項(xiàng)
使用時將組件內(nèi)的提示框替換為elementPlus官方的
generateUUID方法自行修改為生成UUID的方法,也可以去掉。
到此這篇關(guān)于使用Vue3封裝實(shí)現(xiàn)支持Base64導(dǎo)出的電子簽名組件的文章就介紹到這了,更多相關(guān)Vue3電子簽名內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue+高德地圖實(shí)現(xiàn)地圖搜索及點(diǎn)擊定位操作
這篇文章主要介紹了vue+高德地圖實(shí)現(xiàn)地圖搜索及點(diǎn)擊定位操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09解決VUE項(xiàng)目使用Element-ui 下拉組件的驗(yàn)證失效問題
這篇文章主要介紹了解決VUE項(xiàng)目使用Element-ui 下拉組件的驗(yàn)證失效問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11vue3 element plus中el-radio選中之后再次點(diǎn)擊取消選中問題
這篇文章主要介紹了vue3 element plus中el-radio選中之后再次點(diǎn)擊取消選中問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08詳解mpvue實(shí)現(xiàn)對蘋果X安全區(qū)域的適配
這篇文章主要介紹了詳解mpvue實(shí)現(xiàn)對蘋果X安全區(qū)域的適配,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07