离线二维码项目已完成tp6+uniapp

实现思路:参考微信支付和支付宝支付

用户出示二维码商家进行扫码扣款,可以是用户余额钱包也可以是绑定的会员卡

 前端用的uniapp,后端thinkphp6.0,同时兼容微信小程序和app

<template>
	<view class="page">
		<view class="cu-card">
			<view class="cu-item padding shadow" style="padding-bottom: 100upx;">
				<view class="cu-bar bg-white solid-bottom">
					<view class="action">
						<text class="cuIcon-check text-green"></text>
						商家进行扫码扣点 {
   
   { str2 }}
					</view>

					<view class="action"><text class="cuIcon-moreandroid"></text></view>
				</view>
				<view class="image qrbar margin-top">
					<canvas canvas-id="canvasbar" id="canvasbar" style="width:590rpx; height:180rpx;"></canvas>
					<view class="gui-margin-top gui-text-center">
						<text class="text-lg">{
   
   { str1 }}</text>
					</view>
				</view>
				<view class="image qrcode"><canvas canvas-id="canvasqrcode" id="canvasqrcode" style="width:360rpx; height:360rpx;"></canvas></view>
			</view>

			<view class="cu-list menu sm-border card-menu ">
				<view class="cu-item shadow arrow" @click="showModal">
					<view class="content flex-sub padding">
						<view class="text-bold">{
   
   { currCard.type_name }}({
   
   { currCard.type_text }})</view>
						<view>{
   
   { currCard.card_number }}</view>
						<view class="text-gray text-sm flex justify-between"><view>优先使用此卡券进行扣点</view></view>
					</view>
				</view>
			</view>
		</view>

		<view class="cu-modal bottom-modal block" :class="modalShow ? 'show' : ''">
			<view class="cu-dialog">
				<view class="cu-bar bg-white justify-end">
					<view class="content">选择扣点的卡券</view>
					<view class="action" @tap="hideModal"><text class="cuIcon-close text-red"></text></view>
				</view>
				<view class="padding">
					<radio-group class="block" @change="RadioChange">
						<view class="cu-list menu sm-border text-left">
							<view class="cu-item" v-for="(item, index) in cardpaylist" :key="index">
								<label class="flex justify-between align-center flex-sub">
									<view class="flex-sub">{
   
   { item.type_name }}({
   
   { item.type_text }}){
   
   { item.card_number }}</view>
									<radio class="" :checked="item.is_dev == 1 ? true : false" :value="item"></radio>
								</label>
							</view>
						</view>
					</radio-group>
				</view>
				<view class="cu-bar bg-white justify-center">
					<view class="action"><button class="cu-btn bg-green margin-left lg" @tap="qudingselectcard">确定选择</button></view>
				</view>
			</view>
		</view>
	</view>
</template>

<script>
var QRCode = require('../../utils/qrcode.js');
var barcode = require('../../utils/barcode.js');
const util = require('../../utils/otp/util.js');
const otpapi = require('../../utils/otp/otpapi.js');
const otp = require('../../utils/otp/otp.js');
export default {
	data() {
		return {
			// 二维码绘制对象
			qrcode: null,
			// 二维码尺寸,单位 rpx
			qrcodeSize: 360,
			// 二维码数据
			qrcodeContent: '',
			// 二维码背景颜色
			qrcodeBgColor: '#FFFFFF',
			// 二维码颜色
			qrcodeColor: '#000000',
			// 画布 id
			qrcodeId: 'canvasqrcode',
			// 条形码绘制对象
			barcode: null,
			// 条形码尺寸,单位 rpx [ 宽度, 高度 ]
			barcodeSize: [590, 180],
			// 二维码数据
			barcodeContent: '',
			// 画布 id
			canvasId: 'canvasbar',
			selected_secret_name: null, // 选中的口令名
			selected_secret: null, // 选中的口令
			selected_secret_result: null, // 选中的口令的计算结果
			totp_secret_list: [], // 口令列表
			intervalId: null, // 动态绘制更新口令结果的定时器
			timestamp_adj: null, // 用来矫正时间,使得从12点方向开始绘制进度
			str2: '',
			str1: '',
			windowWidth: null,
			windowHeight: null,
			secret_list_height: '400rpx',
			modalShow: false,
			cardpaylist: [],
			currCard: '',
			daicurrCard: ''
		};
	},
	onLoad() {
		this.initpage();
	},
	onHide() {
		this.stopUpdateSelectedSecret();
	},
	onUnload() {
		this.stopUpdateSelectedSecret();
	},
	onReady() {
		//this.makeqrcode();
		var that = this;
		setTimeout(function() {
			that.totp_gen_token();
		}, 200);
	},
	methods: {
		initpage() {
			var that = this;
			this.$api.post('user/userpaylist', {}, function(res) {
				var data = res.data;
				data.forEach((item, index) => {
					if (item.is_dev == 1) {
						that.currCard = item;
					}
				});
				that.cardpaylist = data;
			});
		},
		makeqrcode(qrcodeContent) {
			barcode.code128(uni.createCanvasContext(this.canvasId), qrcodeContent, uni.upx2px(this.barcodeSize[0]), uni.upx2px(this.barcodeSize[1]));
			this.qrcode = new QRCode(this.qrcodeId, {
				text: qrcodeContent,
				width: uni.upx2px(this.qrcodeSize),
				height: uni.upx2px(this.qrcodeSize),
				colorDark: this.qrcodeColor,
				colorLight: this.qrcodeBgColor,
				correctLevel: QRCode.CorrectLevel.H
			});
		},

		totp_gen_token() {
			//获取默认的卡券
			var secret = this.currCard.secret;
			this.selected_secret = secret;
			this.selected_secret_result = null;
			this.startUpdateSelectedSecret();
		},
		// 停止显示口令
		stopUpdateSelectedSecret() {
			if (this.intervalId != null) {
				clearInterval(this.intervalId);
				this.intervalId = null;
				this.selected_secret_result = null;
			}
			this.updateTotpToken(null);
		},
		// 启动显示口令
		restartUpdateSelectedSecret() {
			this.stopUpdateSelectedSecret();
			this.startUpdateSelectedSecret();
		},
		startUpdateSelectedSecret() {
			// 已启动
			if (this.intervalId) return;
			// 验证

			if (!this.selected_secret) {
				this.updateTotpToken(null);
				return;
			}
			// 启动定时计算和绘制口令结果
			//console.log('setInterval...');
			this.computeTimestampAdj();
			var intervalId = setInterval(this.updateSelectedSecret, 100);
			this.intervalId = intervalId;
		},

		// 计算adj,作为补偿,使得从12点钟方向开始显示口令
		computeTimestampAdj: function() {
			var timestamp = new Date().getTime() / 1000;
			var timestamp_adj = timestamp % 30;
			if (timestamp_adj > 15) {
				timestamp_adj = 30 - timestamp_adj; // 大于15,补到30,用下一轮的
			} else {
				timestamp_adj = -timestamp_adj; // 不够15,退回到0,用之前的
			}
			timestamp_adj = timestamp_adj * 1000;
			//console.log(timestamp_adj);
			this.timestamp_adj=timestamp_adj;
		},
		
		// 定时刷新口令并显示
		updateSelectedSecret() {
			var that = this;
			// 验证需要绘制
			if (!that.selected_secret) {
				that.stopUpdateSelectedSecret();
				return;
			}
			// 使用旧结果绘制,如果旧结果有效的话。
			var result = that.selected_secret_result;
			if (result) {
				// 通过对比当前时间和刷新时间和刷新周期来判断
				var timestamp = new Date().getTime() + this.timestamp_adj;
				if (timestamp - result.refresh_time <= result.refresh_interval) {
					that.updateTotpToken(result.token, timestamp, result.refresh_time, result.refresh_interval);
					return;
				}
			} else {
				// 旧结果没效,那应该是重选了另一个口令,或口令删除,需要重计算adj
				this.computeTimestampAdj();
			}

			// 使用新结果绘制
			var timestamp = new Date().getTime() + this.timestamp_adj;
			otpapi.totp_gen2(that.selected_secret, timestamp).then(function(res) {
				//console.log("获取到令牌,设置令牌", res);
				var result = res.data;

				that.selected_secret_result = result;

				that.updateTotpToken(result.token, timestamp, result.refresh_time, result.refresh_interval);
			});
		},
		// ==============================================
		// 口令绘制
		updateTotpToken: function(token, timestamp, refresh_time, refresh_interval) {
			var str1 = '0000000000000000';
			//if (token) str1 =String(token);//String(parseInt(this.currCard.card_number)*3+);//String();

			if (token) str1 = String(parseInt(this.currCard.id) + parseInt(token) * 10000002527); //String();
			//付款码=TOTP * 质数 +  用户ID
			//质数取值需要大于最大用户ID,如:当前系统设计最大承受用户有9900人,则质数可以设定必须大于9900的数值(999999991).
			var str2 = '- - - - - -';
			if (timestamp && refresh_time && refresh_interval) {
				var sec = parseInt((refresh_interval + refresh_time - timestamp) / 1000) + 1;
				str2 = sec + '秒后刷新';
			}
			this.str1 = str1;
			this.str2 = str2;
			//二维码应该能确定用户的身份,能确认卡号,用完一次就失效

			this.makeqrcode(str1);
		},
		qudingselectcard() {
			this.hideModal();
			if (this.daicurrCard != '') {
				this.currCard = this.daicurrCard;
				//this.selected_secret_name = this.daicurrCard.card_number;
				this.selected_secret = this.daicurrCard.secret;
				this.totp_gen_token();
			}
		},
		showModal() {
			this.modalShow = true;
		},
		RadioChange(e) {
			this.daicurrCard = e.target.value;
		},
		hideModal() {
			this.modalShow = false;
		}
	}
};
</script>

<style>
.page {
	background-color: #f23030;
	height: 100vh;
}
.section {
}
.qrcode {
	display: flex;
	align-items: center;
	flex-direction: column;
	padding: 30upx;
}
.qrbar {
	display: flex;
	align-items: center;
	flex-direction: column;
	padding: 30upx;
}
</style>

猜你喜欢

转载自blog.csdn.net/u013373006/article/details/120005037