以下是我为您提供的几个签名组件解决方案,您可以根据需要选择使用:
微信小程序原生签名组件 (pages/agreement/sign-integration.vue)
针对微信小程序和H5环境进行了特别优化 使用条件编译区分不同平台,适应性更强
不需要额外依赖,直接基于Canvas API实现
最适合对跨平台兼容性要求高的场景
<template>
<view class="signature-page">
<view class="signature-header">
<text class="signature-title">请在下方区域签名</text>
</view>
<!-- #ifdef MP-WEIXIN -->
<view class="signature-container">
<canvas
type="2d"
id="signature-canvas"
class="signature-canvas"
disable-scroll="true"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
></canvas>
</view>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="signature-container">
<canvas
id="signature-canvas"
canvas-id="signature-canvas"
class="signature-canvas"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
></canvas>
</view>
<!-- #endif -->
<view class="button-group">
<button class="btn-clear" @tap="handleClear">清除</button>
<button class="btn-save" @tap="handleSave" :disabled="isEmpty">确认</button>
</view>
<view class="tip-text">
请确保签名清晰可辨,签名后点击"确认"按钮完成
</view>
</view>
</template>
<script>
export default {
data() {
return {
ctx: null,
canvas: null,
isEmpty: true,
lastX: 0,
lastY: 0,
isDrawing: false,
options: {
lineWidth: 4,
lineColor: '#000000',
canvasWidth: 0,
canvasHeight: 0
}
};
},
mounted() {
// 延迟初始化,确保组件已渲染
setTimeout(() => {
this.initCanvas();
}, 100);
},
methods: {
// 初始化画布
async initCanvas() {
try {
// 获取设备信息
const sysInfo = uni.getSystemInfoSync();
this.options.canvasWidth = sysInfo.windowWidth - 40; // 留出边距
this.options.canvasHeight = 200;
// #ifdef MP-WEIXIN
// 微信小程序使用新的2D接口
const query = uni.createSelectorQuery().in(this);
query.select('#signature-canvas')
.fields({
node: true, size: true })
.exec((res) => {
if (res[0] && res[0].node) {
this.canvas = res[0].node;
this.ctx = this.canvas.getContext('2d');
// 设置画布大小
this.canvas.width = this.options.canvasWidth;
this.canvas.height = this.options.canvasHeight;
// 初始化样式
this.ctx.lineWidth = this.options.lineWidth;
this.ctx.strokeStyle = this.options.lineColor;
this.ctx.lineCap = 'round';
this.ctx.lineJoin = 'round';
// 填充白色背景
this.ctx.fillStyle = '#ffffff';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
});
// #endif
// #ifndef MP-WEIXIN
// 其他平台使用传统Canvas接口
this.ctx = uni.createCanvasContext('signature-canvas', this);
// 设置样式
this.ctx.lineWidth = this.options.lineWidth;
this.ctx.strokeStyle = this.options.lineColor;
this.ctx.lineCap = 'round';
this.ctx.lineJoin = 'round';
// 填充白色背景
this.ctx.fillStyle = '#ffffff';
this.ctx.fillRect(0, 0, this.options.canvasWidth, this.options.canvasHeight);
this.ctx.draw(true);
// #endif
} catch (error) {
console.error('初始化画布失败:', error);
uni.showToast({
title: '初始化画布失败',
icon: 'none'
});
}
},
// 触摸开始
handleTouchStart(e) {
this.isDrawing = true;
this.isEmpty = false;
const touch = e.touches[0];
// #ifdef MP-WEIXIN
// 微信小程序使用新的2D接口
if (this.ctx && touch) {
this.lastX = touch.x;
this.lastY = touch.y;
}
// #endif
// #ifndef MP-WEIXIN
// 其他平台使用传统Canvas接口
if (this.ctx && touch) {
this.lastX = touch.x;
this.lastY = touch.y;
}
// #endif
},
// 触摸移动
handleTouchMove(e) {
if (!this.isDrawing) return;
const touch = e.touches[0];
if (!touch) return;
// #ifdef MP-WEIXIN
// 微信小程序使用新的2D接口
if (this.ctx) {
const currentX = touch.x;
const currentY = touch.y;
// 绘制线条
this.ctx.beginPath();
this.ctx.moveTo(this.lastX, this.lastY);
this.ctx.lineTo(currentX, currentY);
this.ctx.stroke();
// 更新最后位置
this.lastX = currentX;
this.lastY = currentY;
}
// #endif
// #ifndef MP-WEIXIN
// 其他平台使用传统Canvas接口
if (this.ctx) {
const currentX = touch.x;
const currentY = touch.y;
// 绘制线条
this.ctx.beginPath();
this.ctx.moveTo(this.lastX, this.lastY);
this.ctx.lineTo(currentX, currentY);
this.ctx.stroke();
this.ctx.draw(true);
// 更新最后位置
this.lastX = currentX;
this.lastY = currentY;
}
// #endif
},
// 触摸结束
handleTouchEnd() {
this.isDrawing = false;
},
// 清除画布
handleClear() {
// #ifdef MP-WEIXIN
// 微信小程序使用新的2D接口
if (this.ctx && this.canvas) {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// 重新填充白色背景
this.ctx.fillStyle = '#ffffff';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
// #endif
// #ifndef MP-WEIXIN
// 其他平台使用传统Canvas接口
if (this.ctx) {
this.ctx.clearRect(0, 0, this.options.canvasWidth, this.options.canvasHeight);
// 重新填充白色背景
this.ctx.fillStyle = '#ffffff';
this.ctx.fillRect(0, 0, this.options.canvasWidth, this.options.canvasHeight);
this.ctx.draw(true);
}
// #endif
this.isEmpty = true;
},
// 保存签名
handleSave() {
if (this.isEmpty) {
uni.showToast({
title: '请先完成签名',
icon: 'none'
});
return;
}
// #ifdef MP-WEIXIN
// 微信小程序使用新的2D接口
const query = uni.createSelectorQuery().in(this);
query.select('#signature-canvas')
.fields({
node: true, size: true })
.exec((res) => {
const canvas = res[0].node;
// 将画布内容导出为图片
wx.canvasToTempFilePath({
canvas: canvas,
success: (res) => {
uni.$emit('signature-complete', res.tempFilePath);
uni.navigateBack();
},
fail: (err) => {
console.error('获取签名图片失败:', err);
uni.showToast({
title: '获取签名失败',
icon: 'none'
});
}
});
});
// #endif
// #ifndef MP-WEIXIN
// 其他平台使用传统Canvas接口
uni.canvasToTempFilePath({
canvasId: 'signature-canvas',
success: (res) => {
uni.$emit('signature-complete', res.tempFilePath);
uni.navigateBack();
},
fail: (err) => {
console.error('获取签名图片失败:', err);
uni.showToast({
title: '获取签名失败',
icon: 'none'
});
}
}, this);
// #endif
}
}
};
</script>
<style lang="scss">
.signature-page {
padding: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.signature-header {
width: 100%;
margin-bottom: 30rpx;
}
.signature-title {
font-size: 32rpx;
font-weight: 500;
color: #333;
}
.signature-container {
width: 100%;
height: 400rpx;
border: 1px solid #e0e0e0;
border-radius: 8rpx;
margin-bottom: 40rpx;
overflow: hidden;
background-color: #ffffff;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.signature-canvas {
width: 100%;
height: 100%;
background-color: #ffffff;
}
.button-group {
width: 100%;
display: flex;
justify-content: space-between;
margin-bottom: 30rpx;
}
.btn-clear, .btn-save {
width: 45%;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 40rpx;
font-size: 28rpx;
}
.btn-clear {
background-color: #f2f2f2;
color: #666;
}
.btn-save {
background-color: #3c9cff;
color: #ffffff;
}
.btn-save[disabled] {
background-color: #a0cfff;
}
.tip-text {
font-size: 24rpx;
color: #999;
text-align: center;
line-height: 1.5;
}
</style>
基于Vue3的签名组件 (pages/agreement/signature-vue3.vue)
使用Vue3的组合式API开发
可与uView UI框架结合使用
代码结构清晰,易于维护和扩展
适合Vue3项目使用
<template>
<view class="signature-page">
<view class="signature-container">
<canvas
id="signature-canvas"
canvas-id="signature-canvas"
class="signature-canvas"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
@mousedown="mouseDown"
@mousemove="mouseMove"
@mouseup="mouseUp"
@mouseleave="mouseUp"
></canvas>
</view>
<view class="tips">请在上方区域书写您的签名</view>
<view class="button-group">
<u-button type="default" text="清除" @click="clear"></u-button>
<u-button type="primary" text="确认" @click="save" :disabled="isEmpty"></u-button>
</view>
</view>
</template>
<script setup>
import {
onMounted, onUnmounted, ref } from 'vue';
// 状态变量
const isEmpty = ref(true);
const canvas = ref(null);
const ctx = ref(null);
const isDrawing = ref(false);
const lastPoint = ref({
x: 0, y: 0 });
// 配置项
const canvasOptions = {
lineWidth: 3,
lineColor: '#000000',
background: '#ffffff'
};
// 初始化画布
onMounted(() => {
// 等待界面渲染完成后初始化
setTimeout(initCanvas, 100);
});
// 清理资源
onUnmounted(() => {
canvas.value = null;
ctx.value = null;
});
// 初始化画布方法
const initCanvas = () => {
try {
// 判断当前环境
const isMiniProgram = typeof wx !== 'undefined' && wx.canIUse('getSystemInfoSync');
if (isMiniProgram) {
// 小程序环境
ctx.value = uni.createCanvasContext('signature-canvas');
setBackground();
} else {
// H5环境
const query = uni.createSelectorQuery();
query.select('#signature-canvas')
.fields({
node: true, size: true })
.exec((res) => {
if (res[0]) {
canvas.value = res[0].node;
ctx.value = canvas.value.getContext('2d');
// 设置画布尺寸
const dpr = uni.getSystemInfoSync().pixelRatio;
const rect = res[0].width ? res[0] : document.getElementById('signature-canvas').getBoundingClientRect();
canvas.value.width = rect.width;
canvas.value.height = rect.height;
// 设置画布样式
ctx.value.lineWidth = canvasOptions.lineWidth;
ctx.value.strokeStyle = canvasOptions.lineColor;
ctx.value.lineCap = 'round';
ctx.value.lineJoin = 'round';
// 填充背景色
ctx.value.fillStyle = canvasOptions.background;
ctx.value.fillRect(0, 0, canvas.value.width, canvas.value.height);
}
});
}
} catch (error) {
console.error('初始化画布失败:', error);
uni.showToast({
title: '画布初始化失败,请重试',
icon: 'none'
});
}
};
// 设置背景色 (小程序环境)
const setBackground = () => {
if (!ctx.value) return;
// 获取系统信息用于设置画布大小
const sysInfo = uni.getSystemInfoSync();
const canvasW = sysInfo.windowWidth;
const canvasH = 200; // 设置画布高度
ctx.value.fillStyle = canvasOptions.background;
ctx.value.fillRect(0, 0, canvasW, canvasH);
ctx.value.lineWidth = canvasOptions.lineWidth;
ctx.value.strokeStyle = canvasOptions.lineColor;
ctx.value.lineCap = 'round';
ctx.value.lineJoin = 'round';
ctx.value.draw(true);
};
// 触摸开始事件
const touchStart = (e) => {
isEmpty.value = false;
isDrawing.value = true;
const touch = e.touches[0];
const rect = getCanvasRect();
// 记录起始点
if (touch.x !== undefined) {
// 小程序环境
lastPoint.value = {
x: touch.x, y: touch.y };
} else {
// H5环境
lastPoint.value = {
x: touch.clientX - rect.left,
y: touch.clientY - rect.top
};
}
};
// 触摸移动事件
const touchMove = (e) => {
if (!isDrawing.value) return;
const touch = e.touches[0];
const rect = getCanvasRect();
let currentPoint;
// 获取当前触摸点
if (touch.x !== undefined) {
// 小程序环境
currentPoint = {
x: touch.x, y: touch.y };
drawLine(lastPoint.value, currentPoint);
} else {
// H5环境
currentPoint = {
x: touch.clientX - rect.left,
y: touch.clientY - rect.top
};
drawLineH5(lastPoint.value, currentPoint);
}
// 更新最后一个点
lastPoint.value = currentPoint;
};
// 触摸结束事件
const touchEnd = () => {
isDrawing.value = false;
};
// 鼠标按下事件 (仅H5)
const mouseDown = (e) => {
isEmpty.value = false;
isDrawing.value = true;
const rect = getCanvasRect();
lastPoint.value = {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
};
// 鼠标移动事件 (仅H5)
const mouseMove = (e) => {
if (!isDrawing.value) return;
const rect = getCanvasRect();
const currentPoint = {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
drawLineH5(lastPoint.value, currentPoint);
lastPoint.value = currentPoint;
};
// 鼠标释放事件 (仅H5)
const mouseUp = () => {
isDrawing.value = false;
};
// 获取画布位置信息
const getCanvasRect = () => {
try {
const canvasEl = document.getElementById('signature-canvas');
return canvasEl ? canvasEl.getBoundingClientRect() : {
left: 0, top: 0 };
} catch (e) {
return {
left: 0, top: 0 };
}
};
// 绘制线条 (小程序)
const drawLine = (start, end) => {
if (!ctx.value) return;
ctx.value.beginPath();
ctx.value.moveTo(start.x, start.y);
ctx.value.lineTo(end.x, end.y);
ctx.value.stroke();
ctx.value.draw(true);
};
// 绘制线条 (H5)
const drawLineH5 = (start, end) => {
if (!ctx.value) return;
ctx.value.beginPath();
ctx.value.moveTo(start.x, start.y);
ctx.value.lineTo(end.x, end.y);
ctx.value.stroke();
};
// 清除画布
const clear = () => {
if (!ctx.value) return;
const isMiniProgram = typeof wx !== 'undefined';
if (isMiniProgram) {
// 小程序环境
const sysInfo = uni.getSystemInfoSync();
ctx.value.clearRect(0, 0, sysInfo.windowWidth, 200);
setBackground();
} else {
// H5环境
ctx.value.clearRect(0, 0, canvas.value.width, canvas.value.height);
ctx.value.fillStyle = canvasOptions.background;
ctx.value.fillRect(0, 0, canvas.value.width, canvas.value.height);
}
isEmpty.value = true;
};
// 保存签名
const save = () => {
if (isEmpty.value) {
uni.showToast({
title: '请先完成签名',
icon: 'none'
});
return;
}
try {
const isMiniProgram = typeof wx !== 'undefined';
if (isMiniProgram) {
// 小程序环境
uni.canvasToTempFilePath({
canvasId: 'signature-canvas',
success: (res) => {
uni.$emit('signature-complete', res.tempFilePath);
uni.navigateBack();
},
fail: (err) => {
console.error('保存签名失败:', err);
uni.showToast({
title: '保存签名失败',
icon: 'none'
});
}
});
} else {
// H5环境
const dataURL = canvas.value.toDataURL('image/png');
uni.$emit('signature-complete', dataURL);
uni.navigateBack();
}
} catch (error) {
console.error('保存签名失败:', error);
uni.showToast({
title: '保存签名失败',
icon: 'none'
});
}
};
</script>
<style lang="scss">
.signature-page {
padding: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.signature-container {
width: 100%;
height: 400rpx;
border: 1px solid #e0e0e0;
border-radius: 8rpx;
margin-bottom: 20rpx;
overflow: hidden;
}
.signature-canvas {
width: 100%;
height: 100%;
background-color: #ffffff;
}
.tips {
font-size: 28rpx;
color: #999;
margin-bottom: 30rpx;
}
.button-group {
display: flex;
width: 100%;
justify-content: space-between;
}
.button-group :deep(.u-button) {
width: 45%;
margin: 0;
}
</style>
vue3-esign开源组件 (pages/agreement/signature-vue3-esign.vue)
需要安装依赖:npm install vue3-esign
提供丰富的配置选项和事件 功能完善,使用简单
适合快速实现签名功能
<template>
<view class="signature-page">
<view class="signature-title">请在下方区域签名</view>
<view class="signature-container">
<esign
ref="esignRef"
:width="signatureWidth"
:height="signatureHeight"
:lineWidth="lineWidth"
:lineColor="lineColor"
:bgColor="bgColor"
:isCrop="isCrop"
:resultType="resultType"
@start="handleStart"
@end="handleEnd"
></esign>
</view>
<view class="button-group">
<button class="btn-clear" @tap="handleClear">清除</button>
<button class="btn-save" @tap="handleSave" :disabled="isEmpty">确认</button>
</view>
<view class="tip-text">
请确保签名清晰可辨,签名后点击"确认"按钮完成
</view>
</view>
</template>
<script setup>
import {
onMounted, ref } from 'vue';
import Esign from 'vue3-esign';
// 状态变量
const esignRef = ref(null);
const isEmpty = ref(true);
const screenWidth = ref(320);
const screenHeight = ref(500);
// 签名配置
const signatureWidth = ref(320);
const signatureHeight = ref(200);
const lineWidth = ref(4);
const lineColor = ref('#000000');
const bgColor = ref('#ffffff');
const isCrop = ref(true);
const resultType = ref('png');
// 生命周期
onMounted(() => {
// 获取设备信息
try {
const sysInfo = uni.getSystemInfoSync();
screenWidth.value = sysInfo.windowWidth;
screenHeight.value = sysInfo.windowHeight;
signatureWidth.value = screenWidth.value * 0.9; // 宽度为屏幕宽度的90%
} catch (e) {
console.error('获取设备信息失败', e);
}
});
// 开始签名事件
const handleStart = (event) => {
isEmpty.value = false;
console.log('开始签名', event);
};
// 结束签名事件
const handleEnd = (event) => {
console.log('结束签名', event);
};
// 清除签名
const handleClear = () => {
if (esignRef.value) {
esignRef.value.clear();
isEmpty.value = true;
}
};
// 保存签名
const handleSave = async () => {
if (isEmpty.value) {
uni.showToast({
title: '请先完成签名',
icon: 'none'
});
return;
}
try {
if (esignRef.value) {
const base64 = await esignRef.value.generate();
if (base64) {
// 发送签名数据给上一页面
uni.$emit('signature-complete', base64);
// 返回上一页
uni.navigateBack({
success: () => {
console.log('签名完成,返回上一页');
}
});
}
}
} catch (error) {
console.error('获取签名图片失败', error);
uni.showToast({
title: '获取签名失败,请重试',
icon: 'none'
});
}
};
</script>
<style lang="scss" scoped>
.signature-page {
padding: 30rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.signature-title {
font-size: 32rpx;
font-weight: 500;
margin-bottom: 30rpx;
color: #333;
}
.signature-container {
width: 100%;
border: 1px solid #ebedf0;
border-radius: 12rpx;
margin-bottom: 40rpx;
background-color: #ffffff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.button-group {
width: 100%;
display: flex;
justify-content: space-between;
margin-bottom: 30rpx;
}
.btn-clear, .btn-save {
width: 45%;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 40rpx;
font-size: 28rpx;
}
.btn-clear {
background-color: #f2f2f2;
color: #666;
}
.btn-save {
background-color: #3c9cff;
color: #ffffff;
}
.btn-save[disabled] {
background-color: #a0cfff;
}
.tip-text {
font-size: 24rpx;
color: #999;
margin-top: 20rpx;
text-align: center;
line-height: 1.5;
}
</style>
建议您从这三个方案中先尝试”微信小程序原生签名组件”,因为它不依赖外部库且针对多平台做了优化。如果遇到问题,再考虑使用第三方组件。
要使用这些组件,需要在相应页面中注册并引入,然后通过uni.$on(‘signature-complete’)监听签名完成事件。
© 版权声明
文章版权归作者所有,未经允许请勿转载。如内容涉嫌侵权,请在本页底部进入<联系我们>进行举报投诉!
THE END
暂无评论内容