鲁班带斩杀又如何 發表於 2025-3-28 13:33:00

Vue3封装支持Base64导出的电子签名组件

<p><span style="font-size: 18px; color: rgba(53, 152, 219, 1)"><strong>默认支持签字回显,base64压缩,内存释放</strong></span></p>
<p><span style="font-size: 18px; color: rgba(53, 152, 219, 1)"><strong>传参支持禁用签字也就是查看,组件大小内置</strong><strong>'small', 'default', 'large'三个大小</strong></span></p>
<h2>&nbsp;</h2>
<h2>效果图</h2>
<p><img src="https://img2024.cnblogs.com/blog/2546855/202503/2546855-20250328131530124-747912050.png"><img src="https://img2024.cnblogs.com/blog/2546855/202503/2546855-20250328131640943-1908175733.png" height="106" width="293"></p>
<h2><img src="https://img2024.cnblogs.com/blog/2546855/202503/2546855-20250328131617581-710241893.png" height="198" width="332" style="font-size: 14px"></h2>
<h2>准备工作</h2>
<p>组件内用到<code>elementPlus,vue-esign</code>组件,使用前提前安装好。</p>
<p>&nbsp;</p>
<h2>组件代码</h2>
<pre class="language-javascript highlighter-hljs" data-dark-theme="true"><code>&lt;template&gt;
        &lt;!-- 签名容器 --&gt;
        &lt;div class="sign-container" &gt;
                &lt;div class="sign-preview" :class="" @click="openDialog"&gt;
                        &lt;img v-if="base64Img" :src="base64Img" class="preview-image" /&gt;
                        &lt;div v-else class="placeholder"&gt;
                                &lt;el-icon&gt;&lt;EditPen /&gt;&lt;/el-icon&gt;
                                &lt;span&gt;点击签名&lt;/span&gt;
                        &lt;/div&gt;
                &lt;/div&gt;

                &lt;!-- 签字弹窗 --&gt;
                &lt;el-dialog v-model="dialogVisible" title="电子签名" width="800px"&gt;
                        &lt;vue-esign ref="esignRef" :width="800" :height="300" :lineWidth="4" :lineColor="'#000000'" :bgColor="'#ffffff'":id="uuid" /&gt;

                        &lt;template #footer&gt;
                                &lt;el-button @click="dialogVisible = false"&gt;取消&lt;/el-button&gt;
                                &lt;el-button @click="handleReset"&gt;清空&lt;/el-button&gt;
                                &lt;el-button type="primary" @click="handleConfirm"&gt;确认&lt;/el-button&gt;
                        &lt;/template&gt;
                &lt;/el-dialog&gt;
        &lt;/div&gt;
&lt;/template&gt;

&lt;script setup&gt;
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(() =&gt; `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) =&gt; ['small', 'default', 'large'].includes(v),
        },
});

const dialogVisible = ref(false);
const esignRef = ref(null);
const base64Img = ref(props.modelValue);

// 打开弹窗时重置画布
const openDialog = () =&gt; {
    if (props.disabled) return;
        dialogVisible.value = true;
        // handleReset();
};

// 清空画布(保留二次确认)
const handleReset = async () =&gt; {
        try {
                await useMessageBox().confirm('此操作将清空签名,确定吗?');
                esignRef.value?.reset();
        } catch {}
};
// 生成签名后压缩
const compressBase64 = (base64) =&gt; {
        return new Promise((resolve, reject) =&gt; {
                const img = new Image();
                img.src = base64;

                img.onload = () =&gt; {
                        // 创建canvas并设置缩放尺寸
                        const canvas = document.createElement('canvas');
                        const ctx = canvas.getContext('2d');

                        // 计算压缩后尺寸(取原图50%)
                        const targetWidth = img.width * 0.5;
                        const targetHeight = img.height * 0.5;

                        // 设置画布尺寸
                        canvas.width = targetWidth;
                        canvas.height = targetHeight;

                        // 绘制压缩图像
                        ctx.drawImage(img, 0, 0, targetWidth, targetHeight);

                        // 生成新base64(自动处理格式)
                        const mimeType = base64.match(/data:(.*?);/);
                        canvas.toBlob(
                                (blob) =&gt; {
                                        const reader = new FileReader();
                                        reader.onloadend = () =&gt; resolve(reader.result);
                                        reader.readAsDataURL(blob);
                                },
                                mimeType,
                                0.6
                        ); // 质量参数生效于JPEG/WebP格式
                };

                img.onerror = (e) =&gt; reject('图片加载失败');
        });
};
// 确认签名
const handleConfirm = async () =&gt; {
        esignRef.value
                .generate()
                .then(async (res) =&gt; {
                        base64Img.value = await compressBase64(res);
                        // 验证压缩效果
                        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(() =&gt; {
                        base64Img.value = '';
                        emit('update:modelValue', '');
                        dialogVisible.value = false;
                });
};


// watch同步
watch(
        () =&gt; props.modelValue,
        async (val) =&gt; {
                if (!val) {
                        base64Img.value = '';
                        esignRef.value?.reset();
                }
                console.log(val);
      base64Img.value = await compressBase64(val);

        }
);



onBeforeUnmount(() =&gt; {
        // 释放canvas内存
        const canvas = esignRef.value?.$el.querySelector('canvas');
        canvas.width = 0;
        canvas.height = 0;
        URL.revokeObjectURL(base64Img.value); // 释放Blob URL
});
&lt;/script&gt;

&lt;style scoped lang="scss"&gt;
.sign-container {
        display: inline-block;
        cursor: pointer;
}
.sign-preview {
        border: 1px solid #dcdfe6;
        background: #fff;
        border-radius: 4px;

        &amp;.sign-size--small {
                width: 120px;
                height: 60px;
        }
        &amp;.sign-size--default {
                width: 180px;
                height: 90px;
        }
        &amp;.sign-size--large {
                width: 240px;
                height: 120px;
        }

        &amp;.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;
                }
        }
}
&lt;/style&gt;</code></pre>
<p>&nbsp;</p>
<h2>使用组件</h2>
<pre class="language-javascript highlighter-hljs" data-dark-theme="true"><code>&lt;el-form ref="dataFormRef" :model="form" inline :rules="dataRules"&gt;
    &lt;el-form-item label="经办人签字" prop="signatureHandler" label-width="8em"&gt;
                &lt;!-- 签名组件 --&gt;
                &lt;signature-component v-model="form.signatureHandler" /&gt;
    &lt;/el-form-item&gt;
&lt;/el-form&gt;</code></pre>
<p>&nbsp;</p>
<h2>注意事项</h2>
<p><span style="color: rgba(224, 62, 45, 1)">使用时将组件内的提示框替换为elementPlus官方的</span></p>
<div>
<p><span style="color: rgba(224, 62, 45, 1)">generateUUID方法自行修改为生成UUID的方法,也可以去掉。</span></p>
<h1>&nbsp;</h1>
</div>

</div>
<div id="MySignature" role="contentinfo">
    <p>本文来自博客园,作者:脆,转载请注明原文链接:https://www.cnblogs.com/Wei-notes/p/18797838</p><br><br>
来源:https://www.cnblogs.com/Wei-notes/p/18797838
頁: [1]
查看完整版本: Vue3封装支持Base64导出的电子签名组件