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



准备工作
组件内用到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>点击签名</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">确认</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();
};
// 清空画布(保留二次确认)
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 = () => {
// 创建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:(.*?);/)[1];
canvas.toBlob(
(blob) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
},
mimeType,
0.6
); // 质量参数生效于JPEG/WebP格式
};
img.onerror = (e) => reject('图片加载失败');
});
};
// 确认签名
const handleConfirm = async () => {
esignRef.value
.generate()
.then(async (res) => {
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(() => {
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内存
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="经办人签字" prop="signatureHandler" label-width="8em">
<!-- 签名组件 -->
<signature-component v-model="form.signatureHandler" />
</el-form-item>
</el-form>
注意事项
使用时将组件内的提示框替换为elementPlus官方的
generateUUID方法自行修改为生成UUID的方法,也可以去掉。
有想法和建议欢迎留言讨论
Vue3封装支持Base64导出的电子签名组件的更多相关文章
- Vue3 封装 Element Plus Menu 无限级菜单组件
本文分别使用 SFC(模板方式)和 tsx 方式对 Element Plus el-menu 组件进行二次封装,实现配置化的菜单,有了配置化的菜单,后续便可以根据路由动态渲染菜单. 1 数据结构定义 ...
- NPOI读写Excel组件封装Excel导入导出组件
后台管理系统多数情况会与Excel打交道,常见的就是Excel的导入导出,对于Excel的操作往往是繁琐且容易出错的,对于后台系统的导入导出交互过程往往是固定的,对于这部分操作,我们可以抽离出公共组件 ...
- React Native封装Toast与加载Loading组件
React Native开发封装Toast与加载Loading组件 在App开发中,我们避免不了使用的两个组件,一个Toast,一个网络加载Loading,在RN开发中,也是一样,React Nati ...
- 并发编程概述 委托(delegate) 事件(event) .net core 2.0 event bus 一个简单的基于内存事件总线实现 .net core 基于NPOI 的excel导出类,支持自定义导出哪些字段 基于Ace Admin 的菜单栏实现 第五节:SignalR大杂烩(与MVC融合、全局的几个配置、跨域的应用、C/S程序充当Client和Server)
并发编程概述 前言 说实话,在我软件开发的头两年几乎不考虑并发编程,请求与响应把业务逻辑尽快完成一个星期的任务能两天完成绝不拖三天(剩下时间各种浪),根本不会考虑性能问题(能接受范围内).但随着工 ...
- 一个 OpenTiny,Vue2 Vue3 都支持!
大家好,我是 Kagol,OpenTiny 开源社区运营,TinyVue 跨端.跨框架组件库核心贡献者,专注于前端组件库建设和开源社区运营. 今天给大家介绍如何同时在 Vue2 和 Vue3 项目中使 ...
- 基于DotNetCoreNPOI封装特性通用导出excel
基于DotNetCoreNPOI封装特性通用导出excel 目前根据项目中的要求,支持列名定义,列索引排序,行合并单元格,EXCEL单元格的格式也是随着数据的类型做对应的调整. 效果图: 调用方式 可 ...
- 封装Vue Element的table表格组件
上周分享了几篇关于React组件封装方面的博文,这周就来分享几篇关于Vue组件封装方面的博文,也好让大家能更好地了解React和Vue在组件封装方面的区别. 在封装Vue组件时,我依旧会交叉使用函数式 ...
- 封装React AntD的dialog弹窗组件
前一段时间分享了基于vue和element所封装的弹窗组件(封装Vue Element的dialog弹窗组件),今天就来分享一个基于react和antD所封装的弹窗组件,反正所使用的技术还是那个技术, ...
- 整理目前支持 Vue 3 的 UI 组件库 (2021 年)
最近,让前端圈子振奋的消息莫过于 Vue 3.0 的发布,一个无论是性能还是 API 设计都有了重大升级的新版本.距离 Vue 3.0 正式版发布已经有一段时间了,相信相关生态周边库也正在适配新版本中 ...
- vue3 封装简单的 tabs 切换组件
背景:公司项目要求全部换成 vue3 ,而且也没有应用像 element-ui 一类的UI组件,用到的公共组件都是根据项目需求封装的,下面是使用vue3实现简单的tabs组件,我只是把代码分享出来,实 ...
随机推荐
- DVWA靶场Weak Session IDs(弱会话) 漏洞所有级别通关教程及源码审计
Weak Session IDs(弱会话) Weak Session IDs(弱会话ID)漏洞是一种安全漏洞,涉及在 web 应用程序中使用不安全或容易被猜测的会话标识符(Session IDs).会 ...
- 将 EasySQLite 从 .NET 8 升级到 .NET 9
前言 EasySQLite是一个.NET 8操作SQLite入门到实战的详细教程,主要是对学校班级,学生信息进行管理维护.今天咱们的主要内容是将EasySQLite从.NET 8升级到.NET 9. ...
- AutoGen入门-让两个AI自行聊天完成任务
AutoGen介绍 AutoGen 是一个开源编程框架,用于构建 AI 代理并促进多个代理之间的合作以解决问题.AutoGen 旨在提供一个易于使用和灵活的框架,以加速代理型 AI 的开发和研究,就像 ...
- Spring Cloud的5大核心组件详解
Spring Cloud Spring Cloud 是一套完整的微服务解决方案,基于 Spring Boot 框架,准确的说,它不是一个框架,而是一个大的容器,它将市面上较好的微服务框架集成进来,从而 ...
- Nibbles PG walkthrough Intermediate
nmap nmap -p- -A 192.168.239.47 Starting Nmap 7.95 ( https://nmap.org ) at 2025-01-15 02:26 UTC Nmap ...
- playwright相关
Playwright 介绍 Playwright 是一个用于自动化浏览器操作的开源工具,由 Microsoft 开发和维护.它支持多种浏览器(包括 Chromium.Firefox 和 WebKit) ...
- Netty实战入门教程
概述 Netty 是一个异步的.基于事件驱动的网络应用框架,用于快速开发可维护.高性能的网络服务器和客户端 Netty 在 Java 网络应用框架中的地位就好比:Spring 框架在 JavaEE 开 ...
- 蝉联第一,天翼云电脑持续领跑中国DaaS市场!
近日,IDC发布的<中国虚拟桌面软件及云服务市场半年跟踪报告>数据显示,中国电信天翼云在2023年上半年中国桌面即服务(Desktop as a Service,以下简称"Daa ...
- 库卡机器人KR500维修保养
随着现代工业自动化,KUKA库卡机器人以其卓越的性能.灵活的操作和高效的产出而备受赞誉.然而,为了确保机器人的持续稳定运行和延长使用寿命,应联系子锐机器人维修对库卡机器人保养至关重要. 一.库卡机器人 ...
- yarn的安装与配置(秒懂yarn用法)
安装和配置Yarn可以通过以下步骤完成: 安装Node.js:首先,确保已经安装了Node.js.可以通过在终端中运行node -v来检查是否已安装.如果没有安装,可以从Node.js官方网站(htt ...