|
@@ -0,0 +1,151 @@
|
|
|
+<template>
|
|
|
+ <van-popup v-model:show="isShowSignature" :style="{ height: '100%', width: '100%', maxWidth: '100vw' }" @close="handleClose">
|
|
|
+ <!-- 横屏提示,可根据需求决定是否保留 -->
|
|
|
+ <div class="signature-tip" v-if="isLandscape"> 请横屏进行签名操作 </div>
|
|
|
+ <div class="signature-container">
|
|
|
+ <vue-signature-pad ref="signaturePad" :options="signaturePadOptions" class="signature-pad" />
|
|
|
+ <div class="signature-btns">
|
|
|
+ <van-button type="primary" @click="handleClear">重新签名</van-button>
|
|
|
+ <van-button type="info" @click="handleConfirm">确认签名</van-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </van-popup>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+ import { ref, onMounted, watch } from 'vue';
|
|
|
+ // import { VanPopup, VanButton } from 'vant';
|
|
|
+ import { VueSignaturePad } from 'vue-signature-pad';
|
|
|
+ import { useWindowSize } from '@vueuse/core'; // 用于监听窗口尺寸变化
|
|
|
+ const emit = defineEmits(['submit']);
|
|
|
+ const isShowSignature = ref(false);
|
|
|
+ const signaturePad = ref(null);
|
|
|
+ const signaturePadOptions = ref({
|
|
|
+ penColor: 'rgb(0, 0, 0)', // 画笔颜色
|
|
|
+ backgroundColor: 'rgb(255, 255, 255)', // 画布背景色
|
|
|
+ });
|
|
|
+ const { width, height } = useWindowSize(); // 获取窗口宽高
|
|
|
+ const isLandscape = ref(false); // 是否横屏标识
|
|
|
+
|
|
|
+ // 监听窗口尺寸变化,判断是否横屏
|
|
|
+ watch([width, height], () => {
|
|
|
+ isLandscape.value = width.value > height.value;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 打开签名弹窗方法,供外部调用
|
|
|
+ const openSignature = () => {
|
|
|
+ isShowSignature.value = true;
|
|
|
+ // 这里可尝试触发横屏,部分设备或浏览器可能不支持自动强制横屏,可提示用户手动横屏
|
|
|
+ // 比如:screen.orientation.lock('landscape').catch(err => console.warn(err));
|
|
|
+ };
|
|
|
+ defineExpose({
|
|
|
+ openSignature,
|
|
|
+ });
|
|
|
+ //将base64转换为file
|
|
|
+ const dataURLtoFile = (dataurl, filename) => {
|
|
|
+ var arr = dataurl.split(','),
|
|
|
+ mime = arr[0].match(/:(.*?);/)[1],
|
|
|
+ bstr = atob(arr[1]),
|
|
|
+ n = bstr.length,
|
|
|
+ u8arr = new Uint8Array(n);
|
|
|
+ while (n--) {
|
|
|
+ u8arr[n] = bstr.charCodeAt(n);
|
|
|
+ }
|
|
|
+ return new File([u8arr], filename, { type: mime });
|
|
|
+ };
|
|
|
+ function blobToFile(blob, fileName, options = {}) {
|
|
|
+ // 如果已经是 File 对象,直接返回
|
|
|
+ if (blob instanceof File) {
|
|
|
+ return blob;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取或设置 MIME 类型
|
|
|
+ const type = options.type || blob.type || 'application/octet-stream';
|
|
|
+
|
|
|
+ // 创建 File 对象
|
|
|
+ const file = new File([blob], fileName, {
|
|
|
+ type,
|
|
|
+ lastModified: options.lastModified || new Date().getTime(),
|
|
|
+ });
|
|
|
+
|
|
|
+ return file;
|
|
|
+ }
|
|
|
+ // 清空签名
|
|
|
+ const handleClear = () => {
|
|
|
+ if (signaturePad.value) {
|
|
|
+ signaturePad.value.clearSignature();
|
|
|
+ }
|
|
|
+ };
|
|
|
+ // 关闭弹窗
|
|
|
+ const getImg = () => {
|
|
|
+ const response = signaturePad.value.saveSignature();
|
|
|
+ if (response.isEmpty) {
|
|
|
+ return response;
|
|
|
+ } else {
|
|
|
+ // 转成二进制形式
|
|
|
+ const binaryData = convertBase64ToBinary(response.data);
|
|
|
+ const blob = new Blob([binaryData], { type: 'image/png' });
|
|
|
+ // console.log('+子组件43+', blob)
|
|
|
+ return blob;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 确认签名(可根据需求将签名数据传递给父组件等)
|
|
|
+ const handleConfirm = () => {
|
|
|
+ let img = getImg();
|
|
|
+ emit('submit', blobToFile(img, '签名图片.png'));
|
|
|
+ isShowSignature.value = false;
|
|
|
+ };
|
|
|
+
|
|
|
+ function convertBase64ToBinary(base64Str) {
|
|
|
+ // 去除data:image/png;base64,这部分,只保留Base64编码的字符串
|
|
|
+ const base64Data = base64Str.split(',')[1];
|
|
|
+ // 使用atob函数解码Base64字符串
|
|
|
+ const binaryStr = atob(base64Data);
|
|
|
+ // 创建一个Uint8Array来保存二进制数据
|
|
|
+ const len = binaryStr.length;
|
|
|
+ const bytes = new Uint8Array(len);
|
|
|
+ for (let i = 0; i < len; i++) {
|
|
|
+ bytes[i] = binaryStr.charCodeAt(i);
|
|
|
+ }
|
|
|
+ // 返回Uint8Array对象,或者根据需要进一步处理
|
|
|
+ return bytes;
|
|
|
+ }
|
|
|
+ // 关闭弹窗
|
|
|
+ const handleClose = () => {
|
|
|
+ isShowSignature.value = false;
|
|
|
+ handleClear(); // 关闭时清空签名,可根据需求调整
|
|
|
+ };
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+ .van-popup {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+ .signature-tip {
|
|
|
+ margin-bottom: 10px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+ .signature-container {
|
|
|
+ width: 90%;
|
|
|
+ height: 60%;
|
|
|
+ border: 1px solid #eee;
|
|
|
+ border-radius: 8px;
|
|
|
+ overflow: hidden;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+ .signature-pad {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+ .signature-btns {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-around;
|
|
|
+ padding: 10px;
|
|
|
+ background-color: #fff;
|
|
|
+ }
|
|
|
+</style>
|