使用uniapp的语法写一个vue-signature-pad vue2版本的电子签名
1.创建签名组件 (SignaturePad.vue)
点击查看代码
<template>
<view class="signature-container">
<canvas
class="signature-canvas"
:style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
canvas-id="signatureCanvas"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
></canvas>
<view class="button-group">
<button @click="clearSignature" class="btn-clear">清除签名</button>
<button @click="saveSignature" class="btn-save">保存签名</button>
</view>
</view>
</template>
<script>
export default {
props: {
width: {
type: Number,
default: 300
},
height: {
type: Number,
default: 200
},
lineWidth: {
type: Number,
default: 2
},
lineColor: {
type: String,
default: '#000000'
}
},
data() {
return {
canvasWidth: this.width,
canvasHeight: this.height,
ctx: null,
isDrawing: false,
lastX: 0,
lastY: 0
}
},
mounted() {
this.initCanvas();
},
methods: {
initCanvas() {
this.ctx = uni.createCanvasContext('signatureCanvas', this);
this.ctx.setStrokeStyle(this.lineColor);
this.ctx.setLineWidth(this.lineWidth);
this.ctx.setLineCap('round');
this.ctx.setLineJoin('round');
},
handleTouchStart(e) {
const touch = e.touches[0];
this.isDrawing = true;
this.lastX = touch.x;
this.lastY = touch.y;
},
handleTouchMove(e) {
if (!this.isDrawing) return;
const touch = e.touches[0];
this.ctx.beginPath();
this.ctx.moveTo(this.lastX, this.lastY);
this.ctx.lineTo(touch.x, touch.y);
this.ctx.stroke();
this.ctx.draw(true);
this.lastX = touch.x;
this.lastY = touch.y;
},
handleTouchEnd() {
this.isDrawing = false;
},
clearSignature() {
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.ctx.draw(true);
this.$emit('cleared');
},
saveSignature() {
uni.canvasToTempFilePath({
canvasId: 'signatureCanvas',
success: (res) => {
this.$emit('saved', res.tempFilePath);
uni.showToast({
title: '签名保存成功',
icon: 'success'
});
},
fail: (err) => {
console.error('保存签名失败:', err);
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
}, this);
}
}
}
</script>
<style scoped>
.signature-container {
display: flex;
flex-direction: column;
align-items: center;
margin: 20rpx;
}
.signature-canvas {
border: 1px solid #ddd;
background-color: #fff;
margin-bottom: 20rpx;
}
.button-group {
display: flex;
justify-content: space-between;
width: 100%;
}
.btn-clear {
background-color: #f8f8f8;
color: #333;
margin-right: 10rpx;
}
.btn-save {
background-color: #07c160;
color: #fff;
}
</style>
点击查看代码
<template>
<view class="container">
<view class="title">请在下方签名</view>
<signature-pad
ref="signaturePad"
:width="300"
:height="200"
@saved="handleSignatureSaved"
@cleared="handleSignatureCleared"
/>
<view class="signature-preview" v-if="signatureImage">
<image :src="signatureImage" mode="aspectFit" class="preview-image" />
<text class="preview-text">签名预览</text>
</view>
</view>
</template>
<script>
import SignaturePad from '@/components/SignaturePad.vue';
export default {
components: {
SignaturePad
},
data() {
return {
signatureImage: ''
}
},
methods: {
handleSignatureSaved(tempFilePath) {
this.signatureImage = tempFilePath;
// 这里可以添加将签名图片上传服务器的逻辑
// this.uploadSignature(tempFilePath);
},
handleSignatureCleared() {
this.signatureImage = '';
uni.showToast({
title: '签名已清除',
icon: 'none'
});
},
uploadSignature(filePath) {
uni.uploadFile({
url: 'https://your-api-endpoint.com/upload',
filePath: filePath,
name: 'signature',
success: (res) => {
console.log('上传成功', res);
},
fail: (err) => {
console.error('上传失败', err);
}
});
}
}
}
</script>
<style scoped>
.container {
padding: 30rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.title {
font-size: 36rpx;
margin-bottom: 30rpx;
color: #333;
}
.signature-preview {
margin-top: 40rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.preview-image {
width: 300rpx;
height: 200rpx;
border: 1rpx solid #eee;
}
.preview-text {
margin-top: 10rpx;
font-size: 28rpx;
color: #666;
}
</style>
点击查看代码
<template>
<view class="signature-container">
<view class="canvas-wrapper">
<canvas
class="signature-canvas"
:style="{
width: canvasWidth + 'px',
height: canvasHeight + 'px',
border: `${borderWidth}px solid ${borderColor}`,
backgroundColor: backgroundColor
}"
canvas-id="signatureCanvas"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
></canvas>
</view>
<view class="controls">
<view class="button-group">
<button @click="clearSignature" class="btn-clear">清除</button>
<button @click="undoLastStroke" class="btn-undo" :disabled="history.length === 0">撤销</button>
<button @click="saveSignature" class="btn-save">保存</button>
</view>
<view class="config-group" v-if="showConfig">
<slider :value="lineWidth" min="1" max="10" @change="setLineWidth" />
<picker mode="selector" :range="colorOptions" @change="setLineColor">
<view class="color-picker">选择颜色</view>
</picker>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
width: { type: Number, default: 300 },
height: { type: Number, default: 200 },
initialLineWidth: { type: Number, default: 2 },
initialLineColor: { type: String, default: '#000000' },
backgroundColor: { type: String, default: '#FFFFFF' },
borderColor: { type: String, default: '#DDDDDD' },
borderWidth: { type: Number, default: 1 },
showConfig: { type: Boolean, default: true }
},
data() {
return {
canvasWidth: this.width,
canvasHeight: this.height,
lineWidth: this.initialLineWidth,
lineColor: this.initialLineColor,
ctx: null,
isDrawing: false,
lastX: 0,
lastY: 0,
history: [],
colorOptions: [
'#000000', '#FF0000', '#00FF00',
'#0000FF', '#FFFF00', '#FF00FF'
]
}
},
mounted() {
this.initCanvas();
},
methods: {
initCanvas() {
this.ctx = uni.createCanvasContext('signatureCanvas', this);
this.setLineStyle();
},
setLineStyle() {
this.ctx.setStrokeStyle(this.lineColor);
this.ctx.setLineWidth(this.lineWidth);
this.ctx.setLineCap('round');
this.ctx.setLineJoin('round');
},
handleTouchStart(e) {
const touch = e.touches[0];
this.isDrawing = true;
[this.lastX, this.lastY] = [touch.x, touch.y];
},
handleTouchMove(e) {
if (!this.isDrawing) return;
const touch = e.touches[0];
this.drawLine(this.lastX, this.lastY, touch.x, touch.y);
[this.lastX, this.lastY] = [touch.x, touch.y];
},
drawLine(x1, y1, x2, y2) {
this.ctx.beginPath();
this.ctx.moveTo(x1, y1);
this.ctx.lineTo(x2, y2);
this.ctx.stroke();
this.ctx.draw(true);
this.history.push({
startX: x1, startY: y1,
endX: x2, endY: y2,
color: this.lineColor,
width: this.lineWidth
});
},
handleTouchEnd() {
this.isDrawing = false;
},
clearSignature() {
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.ctx.draw(true);
this.history = [];
this.$emit('cleared');
},
undoLastStroke() {
if (this.history.length === 0) return;
this.history.pop();
this.redrawCanvas();
},
redrawCanvas() {
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.history.forEach(stroke => {
this.ctx.setStrokeStyle(stroke.color);
this.ctx.setLineWidth(stroke.width);
this.ctx.beginPath();
this.ctx.moveTo(stroke.startX, stroke.startY);
this.ctx.lineTo(stroke.endX, stroke.endY);
this.ctx.stroke();
});
this.setLineStyle();
this.ctx.draw(true);
},
saveSignature() {
uni.canvasToTempFilePath({
canvasId: 'signatureCanvas',
fileType: 'png',
quality: 1,
success: (res) => {
this.$emit('saved', {
tempFilePath: res.tempFilePath,
asBase64: () => this.getBase64(res.tempFilePath)
});
},
fail: (err) => {
console.error('保存失败:', err);
this.$emit('error', err);
}
}, this);
},
getBase64(tempFilePath) {
return new Promise((resolve, reject) => {
uni.getFileSystemManager().readFile({
filePath: tempFilePath,
encoding: 'base64',
success: (res) => {
resolve(`data:image/png;base64,${res.data}`);
},
fail: reject
});
});
},
setLineWidth(e) {
this.lineWidth = e.detail.value;
this.setLineStyle();
},
setLineColor(e) {
this.lineColor = this.colorOptions[e.detail.value];
this.setLineStyle();
}
}
}
</script>
<style scoped>
.signature-container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.canvas-wrapper {
margin: 20rpx 0;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.1);
}
.signature-canvas {
background-color: #fff;
}
.controls {
width: 100%;
padding: 0 20rpx;
}
.button-group {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
}
.btn-clear, .btn-undo, .btn-save {
flex: 1;
margin: 0 10rpx;
height: 70rpx;
line-height: 70rpx;
border-radius: 8rpx;
font-size: 28rpx;
}
.btn-clear {
background-color: #f8f8f8;
color: #333;
}
.btn-undo {
background-color: #ff976a;
color: #fff;
}
.btn-undo[disabled] {
opacity: 0.6;
}
.btn-save {
background-color: #07c160;
color: #fff;
}
.config-group {
background-color: #f7f7f7;
padding: 20rpx;
border-radius: 8rpx;
}
.color-picker {
padding: 10rpx;
background-color: #fff;
border: 1rpx solid #ddd;
border-radius: 4rpx;
text-align: center;
margin-top: 20rpx;
}
</style>

浙公网安备 33010602011771号