uni-app canvas 签名

在这里插入图片描述
调用方法

import Signature from "@/components/signature.vue"
const base64Img = ref()
//监听getSignImg
uni.$on('getSignImg', ({ base64, path }) => {base64Img.value = base64//console.log('签名base64, path ====>', base64, path) //拿到的图片数据// 之后取消监听,防止重复监听uni.$off('getSignImg')})
<Signature :showMark="false"  @cancle="cancle"></Signature>

signature.vue

<template><view class="sign-page" v-cloak><view class="dis-flex justify-end"><uv-icon name="close" color="#333" size="48rpx" @click="cancle"></uv-icon></view><view class="sign-body"><canvas id="signCanvas" canvas-id="signCanvas" class="sign-canvas" disable-scroll@touchstart.stop="signCanvasStart" @touchmove.stop="signCanvasMove"@touchend.stop="signCanvasEnd"></canvas><!-- #ifndef APP --><!--用于临时储存横屏图片的canvas容器,H5和小程序需要--><canvas v-if="horizontal" id="hsignCanvas" canvas-id="hsignCanvas"style="position: absolute; top: -1000px; z-index: -1":style="{ width: canvasHeight + 'px', height: canvasWidth + 'px' }"></canvas><!-- #endif --></view><view class="sign-footer" :class="[horizontal ? 'horizontal-btns' : 'vertical-btns']"><uv-buttoncustomStyle="margin-top: 20rpx;width:300rpx;height:100rpx;border-radius:20rpx;border:1px solid #3894FF"@click="reset"><uv-icon name="shuaxin" color="#3894FF" size="48rpx" custom-prefix="custom-icon"></uv-icon><text class="txt">重新签字</text></uv-button><uv-button type="primary" text="确定提交" customTextStyle="font-size:36rpx"customStyle="margin-top: 20rpx;width:300rpx;height:100rpx;border-radius:20rpx"@click="confirm"></uv-button></view></view>
</template><script>import {pathToBase64,base64ToPath} from '@/utils/signature.js'export default {name: 'sign',props: {// 背景水印图,优先级大于 bgColorbgImg: {type: String,default: ''},// 背景纯色底色,为空则透明bgColor: {type: String,default: ''},// 是否显示水印showMark: {type: Boolean,default: true},// 水印内容,可多行markText: {type: Array,default: () => {return [] // ['水印1', '水印2']}},// 水印样式markStyle: {type: Object,default: () => {return {fontSize: 12, // 水印字体大小fontFamily: 'microsoft yahei', // 水印字体color: '#cccccc', // 水印字体颜色rotate: 60, // 水印旋转角度step: 2.2 // 步长,部分场景下可通过调节该参数来调整水印间距,建议为1.4-2.6左右}}},// 是否横屏horizontal: {type: Boolean,default: false},// 画笔样式penStyle: {type: Object,default: () => {return {lineWidth: 3, // 画笔线宽 建议1~5color: '#000000' // 画笔颜色}}},// 导出图片配置expFile: {type: Object,default: () => {return {fileType: 'png', // png/jpg (png不可压缩质量,支持透明;jpg可压缩质量,不支持透明)quality: 1 // 范围 0 - 1 (仅jpg支持)}}}},data() {return {canvasCtx: null, // canvascanvasWidth: 0, // canvas宽度canvasWidth: 0, // canvas宽度canvasHeight: 0, // canvas高度x0: 0, // 初始横坐标或上一段touchmove事件中触摸点的横坐标y0: 0, // 初始纵坐标或上一段touchmove事件中触摸点的纵坐标signFlag: false // 签名旗帜}},mounted() {this.$nextTick(() => {this.createCanvas()})},methods: {// 创建canvas实例createCanvas() {this.canvasCtx = uni.createCanvasContext('signCanvas', this)this.canvasCtx.setLineCap('round') // 向线条的每个末端添加圆形线帽// 获取canvas宽高const query = uni.createSelectorQuery().in(this)query.select('.sign-body').boundingClientRect((data) => {this.canvasWidth = data.widththis.canvasHeight = data.height}).exec(async () => {await this.drawBg()this.drawMark(this.markText)})},async drawBg() {if (this.bgImg) {const img = await uni.getImageInfo({src: this.bgImg})this.canvasCtx.drawImage(img.path, 0, 0, this.canvasWidth, this.canvasHeight)} else if (this.bgColor) {// 绘制底色填充,否则为透明this.canvasCtx.setFillStyle(this.bgColor)this.canvasCtx.fillRect(0, 0, this.canvasWidth, this.canvasHeight)}},// 绘制动态水印drawMark(textArray) {if (!this.showMark) {this.canvasCtx.draw()return}// 绘制背景this.drawBg()// 水印参数const markStyle = Object.assign({fontSize: 12, // 水印字体大小fontFamily: 'microsoft yahei', // 水印字体color: '#cccccc', // 水印字体颜色rotate: 60, // 水印旋转角度step: 2 // 步长,部分场景下可通过调节该参数来调整水印间距,建议为1.4-2.6左右},this.markStyle)this.canvasCtx.font = `${markStyle.fontSize}px ${markStyle.fontFamily}`this.canvasCtx.fillStyle = markStyle.color// 文字坐标const maxPx = Math.max(this.canvasWidth / 2, this.canvasHeight / 2)const stepPx = Math.floor(maxPx / markStyle.step)let arrayX = [0] // 初始水印位置 canvas坐标 0 0 点while (arrayX[arrayX.length - 1] < maxPx / 2) {arrayX.push(arrayX[arrayX.length - 1] + stepPx)}arrayX.push(...arrayX.slice(1, arrayX.length).map((item) => {return -item}))for (let i = 0; i < arrayX.length; i++) {for (let j = 0; j < arrayX.length; j++) {this.canvasCtx.save()this.canvasCtx.translate(this.canvasWidth / 2, this.canvasHeight / 2) // 画布旋转原点 移到 图片中心this.canvasCtx.rotate(Math.PI * (markStyle.rotate / 180))textArray.forEach((item, index) => {let offsetY = markStyle.fontSize * indexthis.canvasCtx.fillText(item, arrayX[i], arrayX[j] + offsetY)})this.canvasCtx.restore()}}this.canvasCtx.draw()},cancle() {//取消按钮事件this.$emit('cancle')this.reset()//uni.navigateBack()},async reset() {this.$emit('reset')this.signFlag = falsethis.canvasCtx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)await this.drawBg()this.drawMark(this.markText)},async confirm() {this.$emit('confirm')// 确认按钮事件if (!this.signFlag) {uni.showToast({title: '请签名后再点击确定',icon: 'none',duration: 2000})return}uni.showModal({title: '确认',content: '确认签名无误吗',showCancel: true,success: async ({confirm}) => {if (confirm) {let tempFileif (this.horizontal) {tempFile = await this.saveHorizontalCanvas()} else {tempFile = await this.saveCanvas()}const base64 = await pathToBase64(tempFile)const path = await base64ToPath(base64)uni.$emit('getSignImg', {base64,path})//uni.navigateBack()}}})},signCanvasEnd(e) {// 签名抬起事件// console.log(e, 'signCanvasEnd')this.x0 = 0this.y0 = 0},signCanvasMove(e) {// 签名滑动事件// console.log(e, 'signCanvasMove')// #ifdef MP-WEIXINlet dx = e.touches[0].clientX - this.x0let dy = e.touches[0].clientY - this.y0// #endif// #ifndef MP-WEIXINlet dx = e.touches[0].x - this.x0let dy = e.touches[0].y - this.y0// #endifthis.canvasCtx.moveTo(this.x0, this.y0)this.canvasCtx.lineTo(this.x0 + dx, this.y0 + dy)this.canvasCtx.setLineWidth(this.penStyle?.lineWidth || 4)this.canvasCtx.strokeStyle = this.penStyle?.color || '#000000'this.canvasCtx.stroke()this.canvasCtx.draw(true)// #ifdef MP-WEIXINthis.x0 = e.touches[0].clientXthis.y0 = e.touches[0].clientY// #endif// #ifndef MP-WEIXINthis.x0 = e.touches[0].xthis.y0 = e.touches[0].y// #endif},signCanvasStart(e) {// 签名按下事件 app获取的e不一样区分小程序app// console.log('signCanvasStart', e)if (!this.signFlag) {// 导出第一次开始触碰事件this.$emit('firstTouchStart')}this.signFlag = true// #ifdef MP-WEIXINthis.x0 = e.touches[0].clientXthis.y0 = e.touches[0].clientY// #endif// #ifndef MP-WEIXINthis.x0 = e.touches[0].xthis.y0 = e.touches[0].y// #endif},// 保存竖屏图片async saveCanvas() {return await new Promise((resolve, reject) => {uni.canvasToTempFilePath({canvasId: 'signCanvas',fileType: this.expFile.fileType, // 只支持png和jpgquality: this.expFile.quality, // 范围 0 - 1success: (res) => {if (!res.tempFilePath) {uni.showModal({title: '提示',content: '保存签名失败',showCancel: false})return}resolve(res.tempFilePath)},fail: (r) => {console.log('图片生成失败:' + r)resolve(false)}},this)})},// 保存横屏图片async saveHorizontalCanvas() {return await new Promise((resolve, reject) => {uni.canvasToTempFilePath({canvasId: 'signCanvas',fileType: this.expFile.fileType, // 只支持png和jpgsuccess: (res) => {if (!res.tempFilePath) {uni.showModal({title: '提示',content: '保存签名失败',showCancel: false})return}// #ifdef APPuni.compressImage({src: res.tempFilePath,quality: this.expFile.quality * 100, // 范围 0 - 100rotate: 270,success: (r) => {console.log('==== compressImage :', r)resolve(r.tempFilePath)}})// #endif// #ifndef APPuni.getImageInfo({src: res.tempFilePath,success: (r) => {// console.log('==== getImageInfo :', r)// 将signCanvas的内容复制到hsignCanvas中const hcanvasCtx = uni.createCanvasContext('hsignCanvas', this)// 横屏宽高互换hcanvasCtx.translate(this.canvasHeight / 2, this.canvasWidth / 2)hcanvasCtx.rotate(Math.PI * (-90 / 180))hcanvasCtx.drawImage(r.path,-this.canvasWidth / 2,-this.canvasHeight / 2,this.canvasWidth,this.canvasHeight)hcanvasCtx.draw(false, async () => {const hpathRes = await uni.canvasToTempFilePath({canvasId: 'hsignCanvas',fileType: this.expFile.fileType, // 只支持png和jpgquality: this.expFile.quality // 范围 0 - 1},this)let tempFile = ''if (Array.isArray(hpathRes)) {hpathRes.some((item) => {if (item) {tempFile = item.tempFilePathreturn}})} else {tempFile = hpathRes.tempFilePath}resolve(tempFile)})}})// #endif},fail: (err) => {console.log('图片生成失败:' + err)resolve(false)}},this)})}}}
</script><style scoped lang="scss">[v-cloak] {display: none !important;}.sign-page {height: 600rpx;width: 710rpx;padding: 20rpx;display: flex;flex-direction: column;.sign-body {margin-top: 50rpx;width: 100%;flex-grow: 1;background: #E5E5E5;.sign-canvas {width: 100%;height: 100%;}}.sign-footer {width: 100%;height: 80rpx;margin: 15rpx 0;display: flex;justify-content: space-evenly;align-items: center;.txt{color:#3894FF;padding-left:10rpx;font-size: 36rpx;}}.vertical-btns {.btn {width: 120rpx;height: 66rpx;}}.horizontal-btns {.btn {width: 66rpx;height: 120rpx;writing-mode: vertical-lr;transform: rotate(90deg);}}}:deep(.uvicon-close) {font-size: 48rpx}
</style>

signature.js

function getLocalFilePath(path) {if (path.indexOf('_www') === 0 || path.indexOf('_doc') === 0 || path.indexOf('_documents') === 0 || path.indexOf('_downloads') === 0) {return path}if (path.indexOf('file://') === 0) {return path}if (path.indexOf('/storage/emulated/0/') === 0) {return path}if (path.indexOf('/') === 0) {var localFilePath = plus.io.convertAbsoluteFileSystem(path)if (localFilePath !== path) {return localFilePath} else {path = path.substr(1)}}return '_www/' + path
}function dataUrlToBase64(str) {var array = str.split(',')return array[array.length - 1]
}var index = 0
function getNewFileId() {return Date.now() + String(index++)
}function biggerThan(v1, v2) {var v1Array = v1.split('.')var v2Array = v2.split('.')var update = falsefor (var index = 0; index < v2Array.length; index++) {var diff = v1Array[index] - v2Array[index]if (diff !== 0) {update = diff > 0break}}return update
}export function pathToBase64(path) {return new Promise(function(resolve, reject) {if (typeof window === 'object' && 'document' in window) {if (typeof FileReader === 'function') {var xhr = new XMLHttpRequest()xhr.open('GET', path, true)xhr.responseType = 'blob'xhr.onload = function() {if (this.status === 200) {let fileReader = new FileReader()fileReader.onload = function(e) {resolve(e.target.result)}fileReader.onerror = rejectfileReader.readAsDataURL(this.response)}}xhr.onerror = rejectxhr.send()return}var canvas = document.createElement('canvas')var c2x = canvas.getContext('2d')var img = new Imageimg.onload = function() {canvas.width = img.widthcanvas.height = img.heightc2x.drawImage(img, 0, 0)resolve(canvas.toDataURL())canvas.height = canvas.width = 0}img.onerror = rejectimg.src = pathreturn}if (typeof plus === 'object') {plus.io.resolveLocalFileSystemURL(getLocalFilePath(path), function(entry) {entry.file(function(file) {var fileReader = new plus.io.FileReader()fileReader.onload = function(data) {resolve(data.target.result)}fileReader.onerror = function(error) {reject(error)}fileReader.readAsDataURL(file)}, function(error) {reject(error)})}, function(error) {reject(error)})return}if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {wx.getFileSystemManager().readFile({filePath: path,encoding: 'base64',success: function(res) {resolve('data:image/png;base64,' + res.data)},fail: function(error) {reject(error)}})return}reject(new Error('not support'))})
}export function base64ToPath(base64) {return new Promise(function(resolve, reject) {if (typeof window === 'object' && 'document' in window) {base64 = base64.split(',')var type = base64[0].match(/:(.*?);/)[1]var str = atob(base64[1])var n = str.lengthvar array = new Uint8Array(n)while (n--) {array[n] = str.charCodeAt(n)}return resolve((window.URL || window.webkitURL).createObjectURL(new Blob([array], { type: type })))}var extName = base64.split(',')[0].match(/data\:\S+\/(\S+);/)if (extName) {extName = extName[1]} else {reject(new Error('base64 error'))}var fileName = getNewFileId() + '.' + extNameif (typeof plus === 'object') {var basePath = '_doc'var dirPath = 'uniapp_temp'var filePath = basePath + '/' + dirPath + '/' + fileNameif (!biggerThan(plus.os.name === 'Android' ? '1.9.9.80627' : '1.9.9.80472', plus.runtime.innerVersion)) {plus.io.resolveLocalFileSystemURL(basePath, function(entry) {entry.getDirectory(dirPath, {create: true,exclusive: false,}, function(entry) {entry.getFile(fileName, {create: true,exclusive: false,}, function(entry) {entry.createWriter(function(writer) {writer.onwrite = function() {resolve(filePath)}writer.onerror = rejectwriter.seek(0)writer.writeAsBinary(dataUrlToBase64(base64))}, reject)}, reject)}, reject)}, reject)return}var bitmap = new plus.nativeObj.Bitmap(fileName)bitmap.loadBase64Data(base64, function() {bitmap.save(filePath, {}, function() {bitmap.clear()resolve(filePath)}, function(error) {bitmap.clear()reject(error)})}, function(error) {bitmap.clear()reject(error)})return}if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {var filePath = wx.env.USER_DATA_PATH + '/' + fileNamewx.getFileSystemManager().writeFile({filePath: filePath,data: dataUrlToBase64(base64),encoding: 'base64',success: function() {resolve(filePath)},fail: function(error) {reject(error)}})return}reject(new Error('not support'))})
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/316500.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux的学习之路:21、线程(1)

摘要&#xff1a; 本章说一下线程 目录 摘要&#xff1a; 一、回忆一下 二、如何理解线程 三、命令行看线程 四、利用函数进行使用 五、本章总结 1、线程的优点 2、线程的缺点 3、线程的异常 4、线程的用途 一、回忆一下 1、exe就是一个文件 2、我们的可执行程序…

LT6911UXE HDMI 2.0 至双端口 MIPI DSI/CSI,带音频 龙迅方案

1. 描述LT6911UXE 是一款高性能 HDMI2.0 至 MIPI DSI/CSI 转换器&#xff0c;适用于 VR、智能手机和显示应用。HDMI2.0 输入支持高达 6Gbps 的数据速率&#xff0c;可为4k60Hz视频提供足够的带宽。此外&#xff0c;数据解密还支持 HDCP2.3。对于 MIPI DSI / CSI 输出&#xff0…

记录一次大数据量接口优化过程

问题描述 记录一次大数据量接口优化过程。最近在优化一个大数据量的接口&#xff0c;是提供给安卓端APP调用的&#xff0c;因为安卓端没做分批次获取&#xff0c;接口的数据量也比较大&#xff0c;因为加载速度超过一两分钟&#xff0c;所以导致接口超时的异常&#xff0c;要让…

编译Qt6.5.3LTS版本(Mac/Windows)的mysql驱动(附带编译后的全部文件)

文章目录 0 背景1 编译过程2 福利参考 0 背景 因为项目要用到对MYSQL数据库操作&#xff0c;所以需要连接到MYSQL数据库。但是连接需要MYSQL驱动&#xff0c;但是Qt本身不自带MYSQL驱动&#xff0c;需要自行编译。网上有很多qt之前版本的mysql驱动&#xff0c;但是没有找到qt6…

【服务器部署篇】Linux下快速安装Jenkins

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0c;产…

在PR中使用 obs 和 vokoscreen 录制的视频遇到的问题

1. obs 录制的视频 在 Adobe Premiere Pro CS6 中只有音频没有视频 2. vokoscreen 录制的视频&#xff0c;没有声音 这是是和视频录制的编码有关系&#xff0c;也和显卡驱动关系 首先 obs 点击 文件 ---> 设置 录制的视频都是可以正常播放的&#xff0c;在PR不行。更…

根据txt文件绘制词云 -- python

根据一段文字绘制词云&#xff0c;我们有两种方法 &#xff0c;一种是登录专业的绘图网站http://yciyun.com/ 不过&#xff0c;貌似这个网站需要会员才可以体验&#xff0c;他只是给出了一些形状图案的词云&#xff0c;虽然看起来很精美&#xff0c;但是他不能让我们自己随意更…

杰发科技AC7840——SPI通信简介(1)_跑通Demo

0. 简介 一些配置项&#xff1a; CPHA&#xff1a;相序 CPLO&#xff1a;极性 看着demo需要按键&#xff0c;于是去掉按键&#xff0c;去掉打印&#xff0c;直接输出波形看逻辑分析仪的信号。 其实现在做这些demo测试应该都有逻辑分析仪&#xff0c;直接看波形更直观一点。…

基于随机森林和Xgboost对肥胖风险的多类别预测

基于随机森林和Xgboost对肥胖风险的多类别预测 作者&#xff1a;i阿极 作者简介&#xff1a;数据分析领域优质创作者、多项比赛获奖者&#xff1a;博主个人首页 &#x1f60a;&#x1f60a;&#x1f60a;如果觉得文章不错或能帮助到你学习&#xff0c;可以点赞&#x1f44d;收藏…

短视频交友系统搭建重点,会用到哪些三方服务?

在搭建短视频交友系统时&#xff0c;为了确保系统的稳定性、安全性和用户体验&#xff0c;通常需要用到多种第三方服务。以下是搭建短视频交友系统时可能用到的关键第三方服务&#xff1a; 云服务提供商&#xff1a;如阿里云、腾讯云等&#xff0c;提供稳定、可扩展的服务器资源…

网络爬虫软件学习

1 什么是爬虫软件 爬虫软件&#xff0c;也称为网络爬虫或网络蜘蛛&#xff0c;是一种自动抓取万维网信息的程序或脚本。它基于一定的规则&#xff0c;自动地访问网页并抓取需要的信息。爬虫软件可以应用于大规模数据采集和分析&#xff0c;广泛应用于舆情监测、品牌竞争分析、…

MySQL随便聊----之MySQL的调控按钮-启动选项和系统变量

-------MySQL是怎么运行的 基本介绍 如果你用过手机&#xff0c;你的手机上一定有一个设置的功能&#xff0c;你可以选择设置手机的来电铃声、设置音量大小、设置解锁密码等等。假如没有这些设置功能&#xff0c;我们的生活将置于尴尬的境地&#xff0c;比如在图书馆里无法把手…

微服务之分布式理论zookeeper概述

一、分布式技术相关的理论 CAP理论 CAP定理(CAP theorem)&#xff0c;⼜被称作布鲁尔定理(Eric Brewer)&#xff0c;1998年第⼀次提出. 最初提出是指分布式数据存储不可能同时提供以下三种保证中的两种以上: (1) ⼀致性(Consistency): 每次读取收到的信息都是最新的; (2) …

Andorid复习

组件 TextView 阴影 android:shadowColor"color/red" 阴影颜色android:shadowRadius"3.0" 阴影模糊度&#xff08;大小&#xff09;android:shadowDx"10.0" 横向偏移android:shadowDy"10.0" 跑马灯 这里用自定义控件 public cla…

第G9周:ACGAN理论与实战

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制&#x1f680; 文章来源&#xff1a;K同学的学习圈子 上一周已经给出代码&#xff0c;需要可以跳转上一周的任务 第G8周&#xff1a;ACGAN任…

源码编译framework.jar 并成功导入android studio 开发

一、不同安卓版本对应路径 Android N/O: 7 和 8 out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar Android P/Q: 9 和 10 out/soong/.intermediates/frameworks/base/framework/android_common/combined/framework.jar Android R: 11以上 out/so…

Microsoft Threat Modeling Tool 使用(二)

主界面 翻译 详细描述 选择了 “SDL TM Knowledge Base (Core)” 模板并打开了一个新的威胁模型。这个界面主要用于绘制数据流图&#xff08;Data Flow Diagram, DFD&#xff09;&#xff0c;它帮助您可视化系统的组成部分和它们之间的交互。以下是界面中各个部分的功能介绍&a…

Nacos、OpenFeign、网关 笔记

一、远程调用 1.1配置RestTemplate配置类 package com.hmall.cart.config;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate;Configuration public c…

python监听html click教程

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 Python实现监听HTML点击事件 在Web开发中&#xff0c;经常需要在用户与页面交互时执行一些…

MySQL中SELECT语句的执行过程

2.1.1. 一条SELECT语句的执行过程 MySQL 的架构共分为两层&#xff1a;Server 层和存储引擎层 Server层负责建立连接、分析和执行SQL存储引擎层负责数据的存储和提取&#xff0c;支持 InnoDB、MyISAM、Memory 等多个存储引擎&#xff0c;MySQL5.5以后默认使用InnoDB&#xff0…