uniapp 实现电子签名功能
以下代码仅用于参考,具体功能需要根据实际情况做出改变
<template>
<view
class="main-box"
:class="{
'main-box-vertical': props.direction == 'vertical',
'main-box-horizontal': props.direction == 'horizontal',
}"
:style="{ height: props.signHeight }"
>
<view
class="operation-box"
:class="{
'operation-box-vertical': props.direction == 'vertical',
'operation-box-horizontal': props.direction == 'horizontal',
}"
>
<view
class="c-btn cancel"
@click="cancel"
>取消</view
>
<view
class="c-btn rewrite"
@click="rewrite"
>重写</view
>
<view
class="c-btn confirm"
@click="confirm"
>确定</view
>
</view>
<canvas
:class="{
'canvas-vertical': props.direction == 'vertical',
'canvas-horizontal': props.direction == 'horizontal',
}"
id="mycanvas"
canvas-id="mycanvas"
@touchstart="touchstart"
@touchmove="touchmove"
@touchend="touchend"
></canvas>
</view>
</template>
<script setup>
import { ref, onMounted, computed, watchEffect, nextTick } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
const { proxy } = getCurrentInstance();
const props = defineProps({
signHeight: {
type: String,
default: '100%',
},
pictureBoardBackgroundColor: {
type: String,
default: '',
},
direction: {
type: String,
default: 'vertical', // vertical 竖向(默认),horizontal 横向
},
});
const emit = defineEmits(['cancel', 'complete']);
onMounted(async () => {
try {
await new Promise((resolve) => setTimeout(resolve, 500));
ctx.value = uni.createCanvasContext('mycanvas'); // 创建绘图对象
await getBoardInfo();
initCanvas();
} catch (error) {
console.log(error);
}
});
const boardWidth = ref(0); // 画板宽度
const boardHeight = ref(0); // 画板高度
async function getBoardInfo() {
await nextTick();
return new Promise((resolve, reject) => {
uni
.createSelectorQuery()
.in(proxy)
.select('#mycanvas')
.boundingClientRect((data) => {
if (!data) {
reject('获取画板信息失败');
return;
}
boardWidth.value = data.width;
boardHeight.value = data.height;
resolve();
})
.exec();
});
}
function initCanvas() {
// 设置画笔样式
ctx.value.lineWidth = 4;
ctx.value.lineCap = 'round';
ctx.value.lineJoin = 'round';
ctx.value.setStrokeStyle('#000');
// 设置画板背景色
if (props.pictureBoardBackgroundColor) {
ctx.value.setFillStyle(props.pictureBoardBackgroundColor);
ctx.value.fillRect(0, 0, boardWidth.value, boardHeight.value);
ctx.value.draw();
}
}
// 取消
function cancel() {
emit('cancel');
}
// 签名开始
let ctx = ref(''); //绘图图像
let points = ref([]); //路径点集合
//触摸开始,获取到起点
function touchstart(e) {
let startX = e.changedTouches[0].x;
let startY = e.changedTouches[0].y;
let startPoint = { X: startX, Y: startY };
points.value.push(startPoint);
//每次触摸开始,开启新的路径
ctx.value.beginPath();
}
//触摸移动,获取到路径点
function touchmove(e) {
let moveX = e.changedTouches[0].x;
let moveY = e.changedTouches[0].y;
let movePoint = { X: moveX, Y: moveY };
points.value.push(movePoint); //存点
let len = points.value.length;
if (len >= 2) {
draw(); //绘制路径
}
}
// 触摸结束,将未绘制的点清空防止对后续路径产生干扰
function touchend() {
points.value = [];
}
let isDraw = ref(false); // 是否有绘画
/* ***********************************************
# 绘制笔迹
# 1.为保证笔迹实时显示,必须在移动的同时绘制笔迹
# 2.为保证笔迹连续,每次从路径集合中区两个点作为起点(moveTo)和终点(lineTo)
# 3.将上一次的终点作为下一次绘制的起点(即清除第一个点)
************************************************ */
function draw() {
isDraw.value = true;
let point1 = points.value[0];
let point2 = points.value[1];
points.value.shift();
ctx.value.moveTo(point1.X, point1.Y);
ctx.value.lineTo(point2.X, point2.Y);
ctx.value.stroke();
ctx.value.draw(true);
}
//清空画布
function rewrite() {
isDraw.value = false;
ctx.value.clearRect(0, 0, boardWidth.value, boardHeight.value);
ctx.value.draw();
initCanvas();
}
//完成绘画并保存到本地
function confirm() {
if (isDraw.value == false) {
uni.showToast({
title: '请先签名',
icon: 'none',
});
return;
}
uni.canvasToTempFilePath({
canvasId: 'mycanvas',
success: async (res) => {
let path = res.tempFilePath;
// console.log(path);
emit('complete', path);
},
});
}
</script>
<style lang="scss" scoped>
.main-box {
background: #f5f5f5;
padding: 15rpx;
}
.main-box-vertical {
display: flex;
flex-direction: column;
}
.main-box-horizontal {
display: flex;
}
.operation-box {
.c-btn {
white-space: nowrap;
background: #ffffff;
border-radius: 28rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 26rpx;
font-weight: 500;
color: #666666;
}
.cancel {
background: #ffffff;
color: #666666;
}
.rewrite {
background: #959595;
color: #ffffff;
}
.confirm {
background: #2d99a1;
color: #ffffff;
}
}
.operation-box-vertical {
display: flex;
justify-content: center;
align-items: center;
gap: 20rpx;
margin-bottom: 20rpx;
.c-btn {
padding: 10rpx 50rpx;
}
}
.operation-box-horizontal {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 20rpx;
margin-right: 20rpx;
.c-btn {
/* 保持竖向排列模式 */
writing-mode: vertical-rl;
/* sideways 会让中文像英文一样顺时针旋转90度显示 */
text-orientation: sideways;
/* 边距和竖向模式的边距反过来 */
padding: 50rpx 10rpx;
/* 微调字间距,让旋转后的文字看起来不那么挤 */
letter-spacing: 4rpx;
}
}
.canvas-vertical {
flex: 1;
width: 100%;
}
.canvas-horizontal {
flex: 1;
height: 100%;
}
</style>