uniapp,小程序,实现签名功能
1.需求介绍
有的时候开发uniapp或者小程序需要实现用户签名的一个功能,但是网上又不想购买插件,自己也不想写,这个时候就可以CV开发了
2.目标明确
我们需要把用户签名后转成base64码调取后端给的接口,我这里是以字符串(String)的形式,具体格式和后端的沟通
**3.注意事项 ** 项目一定要安装scss不然指定报错 不会的去百度这里安装太简单了就省了
- .
页面签名展示的效果: - 第一步.新建components 新建->bobo-message -> 新建 -> bobo-message.vue 如图所示
- bobo-message.vue 代码
<template>
<view :style="{zIndex: zIndex}" class="container">
<block v-for="message in messageQueue" :key="message.id">
<view :class="[message.animation, backgroundClass(message)]" class="message">
<text v-if="message.type === 'info'" class="bm-icon info"></text>
<text v-if="message.type === 'success'" class="bm-icon success"></text>
<text v-if="message.type === 'warn'" class="bm-icon warn"></text>
<text v-if="message.type === 'error'" class="bm-icon error"></text>
<text>{{message.content}}</text>
</view>
</block>
</view>
</template>
<script>
// 需要支持的单独配置项:显示时长、是否启用背景、类型、内容
export default {
name: 'bobo-message-cpt',
props: {
zIndex: {
type: Number,
default: 10000
},
duration: {
type: Number,
default: 2000
},
background: {
type: Boolean,
default: false
}
},
data() {
return {
messageQueue: [],
lastId: 0
}
},
computed: {
backgroundClass() {
return msg => {
return this.background || msg.background ? `background-${msg.type}` : ''
}
}
},
methods: {
/**
* 展示普通提示信息
* @param {Object} content
*/
info(arg) {
const message = {
type: 'info'
}
if (typeof arg === 'object' && arg) {
message.content = arg.content
message.duration = arg.duration
message.background = arg.background
} else if(typeof arg === 'string') {
message.content = arg
}
this.fadeIn(message)
},
/**
* 展示成功提示
* @param {Object} content
*/
success(arg) {
const message = {
type: 'success'
}
if (typeof arg === 'object' && arg) {
message.content = arg.content
// 显示时长会用在 settimeout的参数中,必须保证类型正确
if (arg.duration && typeof arg.duration === 'number' && arg.duration >= 0) {
message.duration = arg.duration
}
message.background = arg.background
} else if(typeof arg === 'string') {
message.content = arg
}
this.fadeIn(message)
},
/**
* 展示警告提示
*/
warn(arg) {
const message = {
type: 'warn'
}
if (typeof arg === 'object' && arg) {
message.content = arg.content
if (arg.duration && typeof arg.duration === 'number' && arg.duration >= 0) {
message.duration = arg.duration
}
message.background = arg.background
} else if(typeof arg === 'string') {
message.content = arg
}
this.fadeIn(message)
},
/**
* 展示错误提示
* @param {Object} message
*/
error(arg) {
const message = {
type: 'error'
}
if (typeof arg === 'object' && arg) {
message.content = arg.content
if (arg.duration && typeof arg.duration === 'number' && arg.duration >= 0) {
message.duration = arg.duration
}
message.background = arg.background
} else if(typeof arg === 'string') {
message.content = arg
}
this.fadeIn(message)
},
fadeIn(message) {
message.id = this.generateId()
message.animation = 'fadeIn'
this.messageQueue.push(message)
// 动画执行完毕后取消动画效果,防止列表刷新时重新执行动画
setTimeout(() => {
message.animation = ''
}, 410)
// 显示一段时间后隐藏
setTimeout(() => {
this.fadeOut(message)
}, message.duration || this.duration)
},
fadeOut(message) {
message.animation = 'fadeOut'
setTimeout(() => {
let idx = 0
this.messageQueue.some((msg, index) => {
if (msg.id === message.id) {
idx = index
return true
}
})
this.messageQueue.splice(idx, 1)
}, 500)
},
generateId() {
return (new Date()).getTime() * 1000 + (this.lastId++) % 1000
}
}
}
</script>
<style lang="scss" scoped>
@font-face {
font-family: 'bobo-message-iconfont'; /* project id 1477381 */
src: url('https://at.alicdn.com/t/font_1477381_i3ji49ios6.eot');
src: url('https://at.alicdn.com/t/font_1477381_i3ji49ios6.eot?#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_1477381_i3ji49ios6.woff2') format('woff2'),
url('https://at.alicdn.com/t/font_1477381_i3ji49ios6.woff') format('woff'),
url('https://at.alicdn.com/t/font_1477381_i3ji49ios6.ttf') format('truetype'),
url('https://at.alicdn.com/t/font_1477381_i3ji49ios6.svg#iconfont') format('svg');
}
.container {
left: 0;
right: 0;
top: calc(var(--status-bar-height) + 24px);
min-width: 10rpx;
max-width: 550rpx;
position: fixed;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
// 透明层允许点击穿透
pointer-events: none;
.message {
display: flex;
// 内容区域禁止点击穿透
pointer-events: all;
align-items: center;
padding: 8px 16px;
border-radius: 4px;
margin-top: 5px;
box-shadow: 0 1px 6px rgba(0, 0, 0, .2);
background: #fff;
min-height: 37px;
transition: all .5s;
box-sizing: border-box;
.bm-icon {
font-family: bobo-message-iconfont;
margin-right: 10rpx;
}
.info {
color: #288ced;
}
.success {
color: #09be70;
}
.warn {
color: #ff991f;
}
.error {
color: #ef4017;
}
}
.background-info {
border: 2px solid #d4eefe;
background: #f0faff;
color: #288ced;
box-shadow: none !important;
}
.background-success {
border: 2px solid #baf2d1;
background: #edfff4;
color: #09be70;
box-shadow: none !important;
}
.background-warn {
border: 2px solid #ffe7a7;
background: #fff9e7;
color: #ff991f;
box-shadow: none !important;
}
.background-error {
border: 2px solid #ffcfb9;
background: #ffefe6;
color: #ef4017;
box-shadow: none !important;
}
.fadeIn {
animation: fadeIn 0.4s both;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translate(0, -30px);
}
to {
opacity: 1;
transform: translate(0, 0);
}
}
.fadeOut {
animation: fadeOut 0.4s forwards;
}
@keyframes fadeOut {
from {
opacity: 1;
transform: translate(0, 0);
}
to {
opacity: 0;
margin-top: -37px;
}
}
}
</style>
然后就可以选择你要在那个页面进行签名由于我这里是autograph.vue 名字你们随便起
autograph.vue 代码如下
<template>
<view class="signa">
<view class="btn">
<view class="cancel-btn" @click="toBack">返回</view>
<view class="hand-title">手写签名</view>
<view @click="clear" class="rewrite-btn">重写</view>
<view @click="save" class="save-btn">使用</view>
</view>
<view class="canvas-wrap">
<canvas
class="canvas"
disable-scroll="true"
canvas-id="designature"
@touchstart="starts"
@touchmove="moves"
@touchend="end"
></canvas>
</view>
<Message ref="Message"></Message>
</view>
</template>
<script>
/*
* 已兼容h5和小程序端
*/
import boboMessage from "@/components/bobo-message/bobo-message.vue";
export default {
data() {
return {
dom: null,
line: [],
radius: 0,
isMove: false,
};
},
components: {
Message: boboMessage,
},
created() {
this.dom = uni.createCanvasContext("designature", this);
},
onLoad() {
},
methods: {
toBack() {
uni.navigateBack();
},
end(e) {},
distance(a, b) {
let x = b.x - a.x;
let y = b.y - a.y;
return Math.sqrt(x * x + y * y);
},
starts(e) {
this.line.push({
points: [
{
time: new Date().getTime(),
x: e.touches[0].x,
y: e.touches[0].y,
dis: 0,
},
],
});
let currentPoint = {
x: e.touches[0].x,
y: e.touches[0].y,
};
this.currentPoint = currentPoint;
this.drawer(this.line[this.line.length - 1]);
},
moves(e) {
this.isMove = true;
let point = {
x: e.touches[0].x,
y: e.touches[0].y,
};
(this.lastPoint = this.currentPoint), (this.currentPoint = point);
this.line[this.line.length - 1].points.push({
time: new Date().getTime(),
x: e.touches[0].x,
y: e.touches[0].y,
dis: this.distance(this.currentPoint, this.lastPoint),
});
this.drawer(this.line[this.line.length - 1]);
},
drawer(item) {
let x1,
x2,
y1,
y2,
len,
radius,
r,
cx,
cy,
t = 0.5,
x,
y;
var time = 0;
if (item.points.length > 2) {
let lines = item.points[item.points.length - 3];
let line = item.points[item.points.length - 2];
let end = item.points[item.points.length - 1];
x = line.x;
y = line.y;
x1 = lines.x;
y1 = lines.y;
x2 = end.x;
y2 = end.y;
var dis = 0;
time = line.time - lines.time + (end.time - line.time);
dis = line.dis + lines.dis + end.dis;
var dom = this.dom;
var or = Math.min(
(time / dis) * this.linePressure + this.lineMin,
this.lineMax
);
cx =
(x - Math.pow(1 - t, 2) * x1 - Math.pow(t, 2) * x2) /
(2 * t * (1 - t));
cy =
(y - Math.pow(1 - t, 2) * y1 - Math.pow(t, 2) * y2) /
(2 * t * (1 - t));
dom.setLineCap("round");
dom.beginPath();
dom.setStrokeStyle("black");
dom.setLineWidth(5);
dom.moveTo(x1, y1);
dom.quadraticCurveTo(cx, cy, x2, y2);
dom.stroke();
dom.draw(true);
}
},
clear() {
this.dom.clearRect(0, 0, 1000, 1000);
this.dom.draw();
this.isMove = false;
},
save() {
// uni.showLoading({
// title: "加载中",
// mask:true
// });
if (!this.isMove) return this.$refs.Message.error("尚未进行签名!");
const { type } = this.$route.query;
uni.canvasToTempFilePath({
canvasId: "designature",
fileType: "png",
quality: 1, //图片质量
success: async (res) => {
try {
//开始
console.log("开始");
console.log(res);
} catch (error) {
setTimeout(()=>{
uni.hideLoading();
},2000)
}
}
});
}, //save方法结束
},
};
</script>
<style scoped lang="scss">
.signa {
position: relative;
overflow: hidden;
// background-color: #fbfbfb;
height: 100vh;
width: 100vw;
z-index: 1;
.canvas-wrap {
display: flex;
justify-content: center;
align-items: center;
width: calc(100vw - 180rpx);
height: 100vh;
}
.canvas {
width: 100%;
height: 90vh;
background-color: #f4f8fb;
position: absolute;
z-index: 9999;
// left: 45px;
// border: 1px solid #d6d6d6;
}
.btn {
height: 100vh;
width: 160rpx;
right: 0;
position: fixed;
// background-color: #007AFF;
font-size: 40rpx;
.cancel-btn {
position: fixed;
top: 30rpx;
right: 0;
color: $uni-text-color-blue;
transform: rotate(90deg);
}
.hand-title {
position: fixed;
top: 45%;
right: -40rpx;
color: $uni-text-color-blue;
transform: rotate(90deg);
}
.rewrite-btn {
position: fixed;
top: 82%;
right: 0;
color: $uni-text-color-blue;
transform: rotate(90deg);
}
.save-btn {
position: fixed;
bottom: 30rpx;
right: -10rpx;
padding: 0 10rpx;
color: #fff;
background: $uni-color-primary;
transform: rotate(90deg);
}
}
}
</style>
需要注意的是这里一定要在uni.scss文件里面定义不然指定报错
uni.scss
$uni-text-color-blue:#10468c;
$uni-color-primary: #255796;
不定义的话就会报这个错误 (到时候网上去搜索又是说版本问题)就难搞!
在pages.json找到对应的页面加上这个去掉页面导航
"app-plus": {
"titleNView":false
}
运行项目打开可以看到是这个样子说明已经没啥问题了
手动签名转成base64就已经完成了!打开控制台就能看到! res已经把图片转成了base64码了
找到代码里面的这个打印语句 然后在try里面就可以写调用后端的接口来就行上传了!
上面有一个开启动画的,可以调用接口前开启动画,接口响应200的时候关闭动画 防止用户上传的时候还能手动签名!
完结!!!