最近、小さなプログラム プロジェクトを作成しました.この小さなプログラムは APP 側で使用されます.一般的な需要は次のとおりです.ユーザーは、WeChat をスキャンするか、小さなプログラムのホームページをスキャンして、APP の QR コードをスキャンできます.コードをスキャンして識別します. . 識別が成功したら、レンダリング用のページにジャンプし、ユーザーが署名、編集、送信できるようにします。この小さなプログラムのフロントエンドにおける主な技術的問題には、バックエンドから返されたインターフェイス データに基づく動的フォームの生成、関数への署名、ユーザーの WeChat アバターのニックネームの取得、承認が繰り返されているかどうかの検出、 WeChat APP の QR コードを使用して、対応する小さなプログラムにジャンプすると、結果ページが表示されます。
小さなプログラムのホームページ:
ここで注意すべきことは、WeChat ユーザーのアバター、ニックネーム、および携帯電話番号を取得する必要があることです。アバターとニックネームは、1 つのステップ、最初の承認、および承認番号として使用されます。 2 番目のステップ、2 番目の認証、そしてインターフェースを呼び出して認証が認証されているかどうかを知るには、次のようにします。
//index.wxml
<!--index.wxml-->
<view class="Box" >
<!-- 扫描logo -->
<image class="logo" src="/images/Scancode.png"/>
<button type="primary" class="scanButton" bindtap="getUserProfile">扫一扫</button>
</view>
<!-- 授权获取手机号弹窗 -->
<view class="mask" wx:if="{
{isShowModal}}">
<view class="content">
<text class="title">绑定手机号</text>
<text class="explain">请先绑定手机号再进行此操作</text>
<view class="close" catchtap="close">x</view>
<button type="primary" class="clickButton" open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">微信用户一键绑定</button>
</view>
</view>
// index.js
var util = require('../../utils/util.js')
import api from '../../utils/api.js';
const app = getApp()
Page({
/**
* 页面的初始数据
*/
data: {
isbutton: false, //是否授权了获取了头像昵称手机号,只有全部授权了才为true,默认没授权为false
code: '',
isShowModal: false
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
// console.log("onLoad")
let that = this
wx.login({
success: (res) => {
that.data.code = res.code;
},
})
//当签完字后跳回到首页的时候 这个时候会跳到携带的有hasLogin参数为true 此时肯定是已经授权了的 isbutton为true
if (options.hasLogin) {
//已经授权过
that.setData({
isbutton: true
})
}
},
onShow: function() {
let that = this;
wx.login({
success: async(res) => {
//获取openid
await util.getOpenId(res.code)
// console.log("1231",wx.getStorageSync('openid'))
//检测是否授权过
api.getSetting({
"openId": wx.getStorageSync('openid')
}).then(res => {
// console.log("检测是否授权过", res)
if(res.data.code == 0){
if(res.data.result){
//授权过
that.setData({
isbutton: true
})
}else{
//没授权过
that.setData({
isbutton: false
})
}
}
})
},
})
//显示"转发给朋友"
wx.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage', ],
success: function(res) {
},
fail: function(res) {
}
})
},
onLaunch: function() {
// console.log("onLaunch")
},
getUserProfile() {
//检测判断是否已经授权了
if (!this.data.isbutton) {
//没授权
wx.getUserProfile({
desc: '用于获取头像昵称',
success: (res) => {
// console.log("获取用户信息", res)
//将用户信息存储到全局变量里
app.globalData.userInfo = res.userInfo
app.globalData.userInfo.avatarUrl = res.userInfo.avatarUrl
this.setData({
isShowModal: true
})
//将用户的头像昵称保存到数据库
// let params = {
// openId:wx.getStorageSync('openid'),
// phone:'',
// nickName:res.userInfo.nickName,
// photo:res.userInfo.avatarUrl,
// sex:res.userInfo.gender
// }
// api.SaveWechatInfo(params).then(res=>{
// // console.log("保存用户头像昵称",res)
// })
}
})
} else {
//已经授权了 直接开始扫一扫
this.scanCode()
}
},
//用户授权后获取其手机号
async getPhoneNumber(e) {
// console.log('e.detail', e.detail)
let that = this
//判断用户是选择拒绝还是允许
if (e.detail.errMsg != 'getPhoneNumber:ok') {
//拒绝
that.setData({
isbutton: false,
})
return false
} else {
//允许
that.setData({
isbutton: true,
})
await util.getPhoneNumberToLogin(e, that.data.isbutton, that.data.code);
//授权绑定成功 自动关闭弹窗
that.setData({
isShowModal: false
})
wx.showToast({
title: '绑定成功',
icon: 'success',
duration: 3000
})
that.setData({
isbutton: true,
})
}
},
//用户手动点击右上角关闭弹窗 相当于没有同意授权获取手机号
close() {
this.setData({
isShowModal: false,
isbutton: false
})
},
//授权了头像昵称手机号后开始扫描
scanCode() {
wx.scanCode({
success(res) {
console.log("扫描", res)
let result = res.result;
wx.navigateTo({
url: '/pages/temporary/temporary?result='+encodeURIComponent(result)
})
}
})
},
})
ユーザーがコードを正常にスキャンすると、一時ページ temporary.wxml にジャンプします. このページはトランジット ページとして使用されます, 主な目的は、ユーザーが WeChat からスキャンインしたときに、このトランジット ページからWeChat から結果ページに移動するのと同じ結果ページ. スキャンインするときは、認証などのためにアプレットのスキャンに行く必要はありません. ここでは、js の onLoad で区別し、decodeURIComponent() を介して QR コードの元のコンテンツを取得します。コードは次のとおりです。
//temporary.wxml
<view class="temporary">
</view>
//temporary.js
// pages/temporary/temporary.js
import api from '../../utils/api.js';
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(query) {
console.log("接收", query)
wx.showLoading({
title: '加载中',
})
let string;
if (query.hasOwnProperty('result')) {
//从小程序内的扫一扫跳转过来的
string = decodeURIComponent(query.result)
console.log("sadas", decodeURIComponent(query.result))
} else if (query.hasOwnProperty('q')) {
//从微信扫一扫跳转过来的
string = decodeURIComponent(query.q) //获取到二维码原始链接内容
console.log("外面微信扫码获得的", string)
// const scancode_time = parseInt(query.scancode_time) // 获取用户扫码时间 UNIX 时间戳
}
// console.log("route", this.getQueryVariable(string, "route"))
// console.log("tkId", this.getQueryVariable(string, "tkId"))
// console.log("batchNo", this.getQueryVariable(string, "batchNo"))
// console.log("randomCode", this.getQueryVariable(string, "randomCode"))
// console.log("formId", this.getQueryVariable(string, "formId"))
try {
var val = wx.getStorageSync('openid')
let route = this.getQueryVariable(string, "route")
let params = {
route: route,
tkId: this.getQueryVariable(string, "tkId"),
batchNo: this.getQueryVariable(string, "batchNo"),
randomCode: this.getQueryVariable(string, "randomCode"),
formId: this.getQueryVariable(string, "formId") ? this.getQueryVariable(string, "formId") :
''
}
if (val) {
// 本地有openid
params.openId = val
let that = this
//检测用户之前是否授权过
api.getSetting({
"openId": val
}).then(res => {
console.log("检测是否授权过", res.data)
wx.hideLoading()
if (res.data.code == 0) {
if (res.data.result) {
//授权过
api.getCodeContent(params).then(res => {
console.log("二维码业务内容成功获取", res)
if (res.data.code == 1) {
//二维码失效
wx.showModal({
content: `${
res.data.message}`,
showCancel: false,
confirmText: '我知道了',
success(res) {
if (res.confirm) {
// console.log('用户点击确定')
that.goHome()
} else if (res.cancel) {
// console.log('用户点击取消授权')
}
}
})
} else if (res.data.code == 0) {
if (route == 'activeTask') {
//激活任务
let myParams = {
...params,
}
if (myParams.hasOwnProperty('formId')) {
delete myParams.formId
}
api.saveScanResult(myParams).then(response => {
console.log("激活", response)
if(response.data.code == 0){
wx.showModal({
title: '提示',
content: `${
response.data.result}`,
showCancel: false,
confirmText: '我知道了'
}).then(res => {
that.goHome()
}).catch(err => {
})
}else{
wx.showModal({
title: '提示',
content: `${
response.data.message}`,
showCancel: false,
confirmText: '我知道了'
}).then(res => {
that.goHome()
}).catch(err => {
})
}
}).catch(error => {
wx.showModal({
title: '服务器内部出错',
content: '请稍后重试',
showCancel: false,
confirmText: '我知道了'
})
})
} else if (route == 'submitForm') {
//表单提交
// formType为1新表单 通用表单
if (res.data.result.formType == 1) {
wx.navigateTo({
url: '/pages/check/check_detail'
})
wx.setStorageSync('FormDetailTitle',
res.data.result.title)
//将场所登记表缓存在本地
wx.setStorageSync('placeFormJson', res.data
.result
.formJson)
wx.setStorageSync('taskItemResultId', JSON
.stringify(res.data.result
.taskItemResultId)
)
//将从业人员列表缓存到本地
wx.setStorageSync('personFormJson', JSON
.stringify(
res.data.result.personFormJson))
} else if (res.data.result.formType == 3) {
//formType为3旧表单 自定义表单
wx.navigateTo({
url: '/pages/check/check_detail_old'
})
wx.setStorageSync('oldFormDetailTitle',
res.data.result.title)
wx.setStorageSync('oldFormJson',
res.data.result.formJson)
wx.setStorageSync('taskItemResultId', JSON
.stringify(res.data.result
.taskItemResultId)
)
wx.setStorageSync('oldResultId',JSON
.stringify(res.data.result
.taskItemResultId))
}
wx.setStorageSync('params', JSON.stringify(params))
wx.setStorageSync('taskItemId', res.data.result
.taskItemId)
//通过isFill判断表单是否填写过 isFill为0是未填写
wx.setStorageSync('isFill', JSON.stringify(res.data
.result.isFill))
}
} else if (res.data.code == 401) {
wx.showModal({
title: '服务器内部出错',
showCancel: false,
content: '请稍后重试',
confirmText: '我知道了'
})
}
}).catch(err => {
console.log("二维码业务内容获取失败", err)
})
} else {
wx.hideLoading()
//没授权过就提醒其授权
that.isAuthorize()
}
}
})
} else {
//本地拿不到openId 提醒需要授权
that.isAuthorize()
}
} catch (e) {
}
},
//提醒用户授权
isAuthorize() {
let that = this;
wx.showModal({
content: '您还没有授权',
showCancel: false,
confirmText: '我知道了',
success(res) {
if (res.confirm) {
// console.log('用户点击确定')
that.goHome()
} else if (res.cancel) {
// console.log('用户点击取消授权')
}
}
})
},
//跳回首页
goHome() {
wx.hideLoading()
wx.showLoading({
title: '正在跳回首页...'
})
//跳回首页
setTimeout(() => {
wx.redirectTo({
url: '/pages/index/index',
success: function() {
},
fail: function() {
},
complete: function() {
wx.hideLoading()
}
})
}, 2000)
},
//获取url参数的方法
getQueryVariable(url, variable)
{
var query = url.substr(url.indexOf('?') + 1);
// console.log("query",query)
var vars = query.split("&");
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split("=");
if (pair[0] == variable) {
return pair[1];
}
}
return (false);
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
wx.hideLoading()
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})
これらのロジックを書き終えるだけでは十分ではなく、WeChat APP でスキャンしてアプレットの特定のページにジャンプすることを実現するには、WeChat パブリック プラットフォームのバックグラウンドで構成し、バックグラウンド開発を見つける必要があります。管理 - 開発設定 - 通常のリンク QR コードをスキャンしてアプレットを開きます:
オンライン環境はポート 8443 で、次のように構成します:
app.json の最初のページ ページはまだ pages/index/index です。ユーザーがアプレットを通常どおりに検索すると、それがホームページ index.wxml になり、スキャンしてWeChat のアプレット、一時的なページになります.wxml
アプレットの署名ページは主にキャンバスを使用し、ページには再署名と終了の 2 つのボタンがあります。
//sign.wxml
<canvas canvas-id="firstCanvas" id='firstCanvas' bindtouchstart="bindtouchstart" bindtouchmove="bindtouchmove"></canvas>
<view class="bottomBox">
<view class="clear" bindtap='clear'>重新签名</view>
<view class="save {
{sureBtn?'sureBet':''}}" bindtap='export'>完成</view>
</view>
//sign.js
//index.js
//获取应用实例
const app = getApp()
Page({
data: {
context: null,
index: 0,
height: 0,
width: 0,
writeTips: '请清晰书写您的签名',
writeTipsTrue: true,
src: '',
src1: '',
sureBtn: false,
saveContext: null,
pixelRatio:2 //设备像素比默认为2
},
/**记录开始点 */
bindtouchstart: function(e) {
let {
writeTipsTrue
} = this.data
if (writeTipsTrue) {
this.data.context.clearRect(0, 0, this.data.width, this.data.height)
this.setData({
writeTipsTrue: false,
sureBtn: true
})
}
this.data.context.moveTo(e.changedTouches[0].x, e.changedTouches[0].y)
},
/**记录移动点,刷新绘制 */
bindtouchmove: function(e) {
this.data.context.setLineWidth(2) // 设置线条宽度
this.data.context.lineTo(e.changedTouches[0].x, e.changedTouches[0].y)
this.data.context.stroke()
this.data.context.draw(true)
this.data.context.moveTo(e.changedTouches[0].x, e.changedTouches[0].y)
},
/**清空画布 */
clear() {
let context = this.data.context
this.setData({
writeTipsTrue: true,
sureBtn: false
})
context.clearRect(0, 0, this.data.width, this.data.height)
// this.data.saveContext.clearRect(0, 0, this.data.height, this.data.width);
context.save()
// context.setTransform(1, 0, 0, 1, Math.ceil(this.data.width / 2), 155) // 旋转画布 默认文字区域
context.setTransform(1, 0, 0, 1, 50, 50)
let str = this.data.writeTips
context.setFontSize(24)
context.setFillStyle('#ADADB2')
context.fillText(str, 0, 0)
context.restore()
context.draw()
},
/**导出图片 */
export () {
const that = this
if (!that.data.sureBtn) {
wx.showModal({
title: '您没有签名',
content: '请签完名后再提交',
showCancel: false,
confirmText: '我知道了'
})
return false
}
let signImg
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: that.data.width,
height: that.data.height,
destWidth: that.data.width * that.data.pixelRatio,
destHeight: that.data.height * that.data.pixelRatio,
canvasId: 'firstCanvas',
success(res) {
signImg = res.tempFilePath
that.setData({
src1: signImg
})
//下载图片
wx.getImageInfo({
src: signImg, // 签字画布生成的暂存地址
success(res) {
let rototalImg = res.path
that.setData({
src: rototalImg
})
if (rototalImg) {
// 单独处理图片旋转
that.saveImg(rototalImg)
}
},
fail(err) {
}
})
},
})
},
// drew img
saveImg(signImg) {
// 旋转图
let that = this
let context = wx.createCanvasContext('firstCanvas');
that.setData({
saveContext: context
})
context.setTransform(1, 0, 0, 1, 50, 50)
//绘制图片 生成图片函数写在draw()的回调中,不然会出现还没有画图就生成图片的问题
context.draw(true, function() {
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: that.data.width,
height: that.data.height,
destWidth: that.data.width * that.data.pixelRatio,
destHeight: that.data.height * that.data.pixelRatio,
canvasId: 'firstCanvas',
fileType: 'png',
success: function(res) {
var tempFilePath = res.tempFilePath
//将图片转成base64格式
wx.getFileSystemManager().readFile({
filePath:tempFilePath,
encoding: 'base64',
success: res => {
var userImageBase64 =
'data:image/jpg;base64,' + res.data;
// console.log("签字base64",
// userImageBase64);
// 生成图片 并返回上一页 赋值
var curPages = getCurrentPages()
var currPage = curPages[curPages.length - 1]; //当前页面
var prevPage = curPages[curPages
.length - 2] //上一页面
prevPage.setData({
sign: userImageBase64
})
wx.navigateBack({
delta: 1,
})
}
})
},
fail: function(res) {
// console.log(res)
},
})
})
},
onShow: function() {
// 进入页面渲染canvas
let query = wx.createSelectorQuery()
const that = this
let context
query.select('#firstCanvas').boundingClientRect()
query.exec(function(rect) {
let width = rect[0].width
let height = rect[0].height
that.setData({
width,
height,
})
const context = wx.createCanvasContext('firstCanvas')
that.setData({
context: context,
})
context.save()
// context.translate(Math.ceil(width / 2) - 20,0)
context.setTransform(1, 0, 0, 1, 50, 50)
let str = that.data.writeTips
context.setFontSize(24)
context.setFillStyle('#ADADB2')
context.fillText(str, 0, 0)
context.restore()
context.draw()
})
},
onLoad(){
//获取设备像素比
wx.getSystemInfo({
success: (res) => {
// console.log("获取设备像素比",res)
this.setData({
pixelRatio : res.pixelRatio
})
}
})
},
// 弹窗
onToast() {
app.toast(this, {
type: 'text',
text: '生成成功',
})
},
onShareAppMessage: (res) => {
}
})
//sign.wxss
/* pages/sign/sign.wxss */
#firstCanvas{
width:95vw;
height:78vh;
margin:0 auto;
border:1rpx solid #ACACAC;
}
#saveImg{
width:95vw;
border:1px solid blue;
}
.bottomBox{
display: flex;
margin-top:10rpx;
justify-content: center;
}
.clear{
width: 157rpx;
text-align: center;
height: 44rpx;
line-height: 44rpx;
border-radius: 22rpx ;
opacity: 1;
border: 1rpx solid #ACACAC;
font-size: 15rpx;
font-family: PingFang SC-Bold, PingFang SC;
font-weight: bold;
color: #EC3535;
margin-right:20rpx;
}
.save{
width: 157rpx;
height: 44rpx;
line-height: 44rpx;
border-radius: 22rpx ;
text-align: center;
font-size: 15rpx;
font-family: PingFang SC-Bold, PingFang SC;
font-weight: bold;
color: #FFFFFF;
background: linear-gradient(270deg, #3670FF 0%, #65A7FF 100%);
opacity: 1;
}
.sureBet{
}
//sign.json
{
"usingComponents": {
},
"navigationBarTitleText":"签名",
"pageOrientation":"landscape",
"disableScroll":true
}