使用canvas实现签名功能
使用canvas画签名并上传到阿里云
import React, { useEffect, useRef, useState } from "react"; import CanvasDraw from "react-canvas-draw"; import MessageBox from "../../../../components/message-box"; //弹框 import request from "../../../../network/request"; import axios from "axios"; import styles from "./index.module.scss"; import getExploreName from "./util"; import { useHistory, useLocation } from "react-router-dom"; import Loading from "../../../../components/loading"; export default function SignatureTemplate(props) { const saveBlen: any = useRef(null); let baseUrl = useRef(""); const [width, setWidth] = useState(0); const [height, setHeight] = useState(0); const history = useHistory(); const location = useLocation(); useEffect(() => { // eslint-disable-next-line no-restricted-globals // console.log(location,history, 'props'); history.listen((pre) => { console.log(location.pathname, pre.pathname, "路由变化"); if (location.pathname !== pre.pathname) { Loading.hide(); MessageBox.removeAll(); } }); }, [location]); function windowChangeEvent() { let visualWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; // 获取当前窗口的宽度(包含滚动条) || 可视区域宽 let visualHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; // 获取当前窗口的高度(包含滚动条) || 可视区域高 let bowser = getExploreName(); //获取浏览器类型和版本 console.log(visualWidth, visualHeight, bowser, "bowser"); setWidth(visualWidth); setHeight(visualHeight); // 把对应的canvas都修改 for (let key in saveBlen.current) { if (saveBlen.current.canvas) { saveBlen.current.canvas[key].width = visualWidth; saveBlen.current.canvas[key].height = visualHeight; } } // 兼容 IOS 手机上非原生浏览器 if (window.orientation === 90 || window.orientation === -90) { //判断屏幕旋转角度 // 横屏 if (visualWidth <= visualHeight || bowser === "Unkonwn") { visualWidth = window.screen.height; // 屏幕分辨率高 visualHeight = window.screen.width; // 屏幕分辨率宽 } } else { // 竖屏 if (visualHeight <= visualWidth || bowser === "Unkonwn") { visualWidth = window.screen.width; // 屏幕分辨率宽 visualHeight = window.screen.height; // 屏幕分辨率高 } } } let preventDefault = (e) => { e.preventDefault(); }; useEffect(() => { console.log(saveBlen, "saveBlen"); // 缓存空白 canvas 的 base64 // eslint-disable-next-line react-hooks/exhaustive-deps //返回base64的png格式图片,保留最初的空画板(签名后base64会发生变化) baseUrl = saveBlen.current.getDataURL("image/png", "", "#fff"); //getDataURL是canvas提供的方法 // 进入页面禁止滚动 document.addEventListener("touchmove", preventDefault, false); // 组件销毁时恢复页面滚动 return () => { window.addEventListener( "popstate", (e) => { // 恢复页面滚动 document.removeEventListener("touchmove", preventDefault, false); }, false ); }; }, []); // 监听屏幕大小变化 useEffect(() => { window.addEventListener("resize", windowChangeEvent, { passive: false, }); console.log(saveBlen, "saveBlen"); saveBlen.current.canvas.grid.style.background = "#eee"; return () => { window.removeEventListener("resize", windowChangeEvent); }; }, []); // 清空签名画板 const Eliminate = () => { saveBlen.current.clear(); }; // 确认签名 const confirm = async (): Promise<any> => { Loading.show("签名中"); let pic = saveBlen.current.getDataURL("image/png", "", "#fff"); //返回base64的png格式图片 // 比较是否是空白 canvas if (baseUrl === pic) { MessageBox.confirm("请先签名,在确认提交!", { showCancelButton: false, }).then(() => { Loading.hide(); }); return; } // base64转出file对象 const data = pic.split(","); const mime = data[0].match(/:(.*?);/)![1]; const binary = atob(data[1].trim()); // 字符转换为二进制格式 let n = binary.length; const u8arr = new Uint8Array(n); while (n--) { u8arr[n] = binary.charCodeAt(n); } // 转换成file对象 const file = new File([u8arr], `${new Date().getTime()}.png`, { type: mime, }); console.log(file, "file"); var reader = new FileReader(); reader.readAsArrayBuffer(file); // 这个读法是异步的 reader.onloadend = function () { upload("/getUploadUrl", reader.result, file); }; }; const upload = async (url: string, binary: any, fileObj: any) => { // 配置上传到阿里云的配置(headers) let config = { headers: { "Content-Type": "application/octet-stream", //Content-Type,告知浏览器这是一个字节流 }, }; try { // 请求后端接口,得到阿里云上传地址 const data: any = await request(url, { fileName: fileObj.name }, "POST"); console.log(data, "data"); let formatFile = fileObj.name + "|" + data.downloadUrl; console.log(data.downloadUrl, "data.downloadUrl"); console.log(formatFile, "formatFile"); let uploadUrl = data.uploadUrl; //阿里云上传地址 axios .put(uploadUrl, binary, config) .then(() => { // 此时阿里云已经上传成功 let params; let saveUrl = "xxxxx"; params = { id: "id", signPic: formatFile, }; // 调用后端接口,记录上传成功的文件 request(saveUrl, params, "POST").then((res) => { MessageBox.toast("上传成功"); history.goBack(); }); Loading.hide(); }) .catch((err) => { console.log(err); Loading.hide(); MessageBox.confirm("系统异常,请重新签名!", { showCancelButton: false, }).then(() => { Eliminate(); }); }); } catch (err) { Loading.hide(); MessageBox.confirm("系统异常,请重新签名!", { showCancelButton: false, }).then(() => { Eliminate(); }); } }; return ( <div> {/* hideGrid 隐藏表格 */} <div className={styles.draw}> <CanvasDraw ref={saveBlen} hideGrid hideInterface canvasWidth={width !== 0 ? width : document.body.clientWidth} canvasHeight={ height !== 0 ? height - 60 : document.body.clientHeight - 60 } brushRadius={2} lazyRadius={2} /> </div> <div className={styles.singeBottom}> <div className={styles.singeDescribe}>请在上方空白处签下您的姓名</div> <div className={styles.singeBtn}> <button type="button" className={styles.btnClear} onClick={Eliminate}> 重置 </button> <button type="button" className={styles.btnSure} onClick={confirm}> 确认 </button> </div> </div> </div> ); }
获取浏览器信息
export default function getExploreName() { var Sys: any = {}; var ua = navigator.userAgent.toLowerCase(); var s; // eslint-disable-next-line @typescript-eslint/no-unused-expressions (s = ua.match(/rv:([\d.]+)\) like gecko/)) ? (Sys.ie = s[1]) : (s = ua.match(/msie ([\d\.]+)/)) ? (Sys.ie = s[1]) : (s = ua.match(/edge\/([\d\.]+)/)) ? (Sys.edge = s[1]) : (s = ua.match(/firefox\/([\d\.]+)/)) ? (Sys.firefox = s[1]) : (s = ua.match(/(?:opera|opr).([\d\.]+)/)) ? (Sys.opera = s[1]) : (s = ua.match(/chrome\/([\d\.]+)/)) ? (Sys.chrome = s[1]) : (s = ua.match(/version\/([\d\.]+).*safari/)) ? (Sys.safari = s[1]) : 0; // 根据关系进行判断 if (Sys.ie) return "IE: " + Sys.ie; if (Sys.edge) return "EDGE: " + Sys.edge; if (Sys.firefox) return "Firefox: " + Sys.firefox; if (Sys.chrome) return "Chrome: " + Sys.chrome; if (Sys.opera) return "Opera: " + Sys.opera; if (Sys.safari) return "Safari: " + Sys.safari; return "Unkonwn"; }