vue实现 APP扫码登录 客户端

30 阅读6分钟

vue3客户端后台

Snipaste_2024-02-03_14-49-59.png

微信图片_20240203161549.png

 <a-spin :indicator="indicator" wrapperClassName="qrcode-img" :spinning="state.loading" class="qrcode-spin">
                            <div>
                                <!-- 显示刷新按钮,点击时调用 getScanQrCode 方法重新获取二维码 -->
                                <div v-if="state.status === 3" @click="getScanQrCode" class="refresh-wrap">
                                    <ReloadOutlined style="font-size: 32px; color: #999" />
                                    <div class="refresh-text">刷新</div>
                                </div>
                                <!-- 当扫码成功时,显示扫码成功提示 -->
                                <div v-if="state.status === 1" class="refresh-wrap">
                                    <CheckOutlined style="font-size: 28px; color: green; margin-bottom: 10px" />
                                    <div class="scan-success-text">扫码成功</div>
                                    <div class="scan-success-text">请在手机确认</div>
                                </div>
                                <!-- 显示二维码,使用 qrcode-vue 组件生成二维码 -->
                                <qrcode-vue v-if="state.qrcode" :value="state.qrcode" :size="200" />
                            </div>
                        </a-spin>
// 用于获取扫描二维码相关数据
async function getScanQrCode() {
    try {
        // 设置加载状态为 true,表示开始处理获取二维码的过程
        state.loading = true;
        // 调用 getScanQrCodeApi 异步API获取二维码信息
        const res = await getScanQrCodeApi();
        // 判断返回的结果码,0通常表示成功
        if (res.code == 0) {
            // 将获取到的二维码和过期时间保存到状态中
            state.qrcode = res.data.code;
            state.expiryTime = res.data.expiryTime;
            // 调用 scanResultInterval 函数,可能用于设置一个周期性检查扫描结果的定时器
            scanResultInterval();
            // 调用 updateQrcode 函数,可能用于更新页面上显示的二维码
            updateQrcode();
            // 设置状态为0,表示二维码获取并准备就绪
            state.status = 0;
        } else {
            // 如果返回结果码不是0,表示获取二维码失败,执行清理操作
            clearTimeoutHandle(); // 清除相关的超时处理
            clearScanResultInterval(); // 清除周期性检查扫描结果的定时器
            state.status = 3; // 设置状态为3,表示获取二维码失败
        }
    } catch (e) {
        // 如果在过程中出现异常,也执行清理操作
        clearTimeoutHandle();
        clearScanResultInterval(); // 清除周期性检查扫描结果的定时器
        state.status = 3; // 设置状态为3,表示出现异常导致失败
    } finally {
        // 最后,无论成功或失败,都将加载状态设置为false,表示处理结束
        state.loading = false;
    }
}

// 更新二维码  // 定义 updateQrcode 函数,用于根据二维码的过期时间来决定是否需要重新获取二维码
function updateQrcode() {
    // 首先检查二维码的剩余有效时间,如果小于3秒,则不进行更新操作
    // 这里的3可以视为一个阈值,用于确保有足够的时间让用户扫描二维码
    if (state.expiryTime < 3) return;
    // 如果存在之前设置的定时器(timer),则首先清除这个定时器
    // 这是为了避免重复设置定时器导致的逻辑错误或资源浪费
    clearTimeout(state.timer);
    // 将 timer 属性重置为 null,表示当前没有活跃的定时器
    state.timer = null;
    // 设置一个新的定时器,延迟时间为二维码的过期时间(单位:秒)
    // 这里通过将过期时间乘以1000转换为毫秒,因为 setTimeout 需要的延迟时间单位是毫秒
    state.timer = setTimeout(() => {
        getScanQrCode();
    }, state.expiryTime * 1000);
}

// 获取二维码扫码结果
async function gerQrCodeLoginResult() {
    try {
        // 准备请求参数,其中包含了之前生成的二维码信息
        const p = { code: state.qrcode };
        // 准备请求参数,其中包含了之前生成的二维码信息
        const res = await gerQrCodeLoginResultApi(p);
        if (res.code == 0) {
            if (res.data) {
                // 如果返回的登录状态是1,表示扫码成功但等待用户确认
                if (res.data.status === 1) {
                    clearTimeoutHandle(); // 清除相关定时器
                    state.status = 1; // 更新状态为1,表示等待用户确认
                    state.waitConfirmCount += 1; // 增加等待确认的计数器
                    // 如果等待确认的次数超过30次,则认为登录失败,清理并更新状态
                    if (state.waitConfirmCount > 30) {
                        clearScanResultInterval();
                        state.status = 3; // 更新状态为3,表示登录失败
                        state.waitConfirmCount = 0; // 重置等待确认的计数器
                    }
                } else if (res.data.status === 2) {
                    // 如果返回的登录状态是2,表示用户已确认登录
                    clearScanResultInterval();
                    clearTimeoutHandle();
                    // 调用 Vuex 的 action 进行登录处理,并在登录成功后获取用户信息
                    store
                        .dispatch("ScanLogin", res.data)
                        .then(() => {
                            store
                                .dispatch("Userinfo")
                                .then(() => {
                                    if (store.getters.Routers.length !== 0) {
                                        notification.info({
                                            message: "登陆成功",
                                            description: "欢迎回来",
                                            icon: () => h(CheckCircleOutlined, { style: "color: #07C15F" }),
                                        });
                                    }
                                    router.replace("/");
                                })
                                .finish(() => {
                                    loading.value = false;
                                });
                        })
                        .catch(() => {
                            loading.value = false;
                        });
                }
            }
        }
    } catch (e) {
        clearScanResultInterval();
        clearTimeoutHandle();
        state.status = 3; // 更新状态为3,表示登录失败
    }
}
// 定义 scanResultInterval 函数,用于周期性地检查二维码扫描登录的结果
function scanResultInterval() {
    // 首先清除之前可能存在的 timerResult 定时器,避免重复设置
    clearInterval(state.timerResult);
    state.timerResult = null;
    // 创建一个新的定时器,每隔1000毫秒(1秒)执行一次检查二维码扫描登录的结果
    state.timerResult = setInterval(async () => {
        // 调用 gerQrCodeLoginResult 函数获取扫码登录结果
        await gerQrCodeLoginResult();
    }, 1000);
}
// 定义 clearTimeoutHandle 函数,用于清除与二维码更新相关的定时器
function clearTimeoutHandle() {
    // 如果存在 timer 定时器,则清除该定时器
    if (state.timer) {
        clearTimeout(state.timer);
        // 清除后将 timerResult 重置为 null
        state.timer = null;
    }
}
// 定义 clearScanResultInterval 函数,用于清除检查二维码扫描结果的定时器
function clearScanResultInterval() {
    // 如果存在 timerResult 定时器,则清除该定时器
    if (state.timerResult) {
        // 清除后将 timerResult 重置为 null
        clearInterval(state.timerResult);

        state.timerResult = null;
    }
}
// 使用 Vue 的 onBeforeUnmount 生命周期钩子
// 当组件即将卸载前,执行清理操作
onBeforeUnmount(() => {
    // 清除与二维码更新相关的定时器
    clearTimeoutHandle();
    // 清除检查二维码扫描结果的定时器
    clearScanResultInterval();
});

APP是vue2

<view @click="scanHandle" class="scan-wrap">
					<u-icon name="scan" :size="28" color="#fff"></u-icon>
				</view>
	// 定义一个异步函数 scanHandle 来处理二维码扫描  扫码登录
			async scanHandle() {
				// 使用预处理指令来排除H5平台,即这段代码只在非H5平台(如小程序或App)中执行
				// #ifndef H5
				try {
					// 调用 uni.scanCode API 异步获取扫描结果,此API返回一个Promise
					const [err, res] = await uni.scanCode({
						onlyFromCamera: true, // 仅允许从相机扫码
						scanType: ['qrCode'] // 指定扫描类型为二维码
					});
					if (res) {
						// 检查扫描结果是否有效且为二维码
						if (res.result && res.scanType === 'QR_CODE') {
							// 显示加载提示
							uni.showLoading({
								mask: true
							});
							// 调用 getScanCode 方法处理扫描到的二维码内容
							await this.getScanCode(res.result);
						} else {
							// 如果不是有效的二维码,则隐藏加载提示并显示无效二维码的提示信息
							uni.hideLoading();
							uni.showToast({
								title: '无效二维码',
								icon: 'none'
							});
						}
					}
					// 如果扫描过程中出现错误
					if (err) {
						// 隐藏加载提示并显示无效二维码的提示信息
						uni.hideLoading();
						uni.showToast({
							title: '无效二维码',
							icon: 'none'
						});
					}
				} catch (e) {
					// 如果在扫描或处理过程中抛出异常,则捕获异常并隐藏加载提示
					uni.hideLoading();
				} finally {
					// 最后,无论成功或失败,都确保隐藏加载提示
					uni.hideLoading();
				}
				// #endif
			},
			// 定义一个异步函数 getScanCode 来处理从二维码中获取到的信息async getScanCode(code) {
				// 调用API接口,传递扫描到的二维码内容(code),等待响应
				const res = await this.$api.getScanCodeApi({
					code // 将扫描到的二维码内容作为参数发送给后端接口
				});
				// 判断响应结果的状态码,0通常表示操作成功
				if (res.code == 0) {
					// 如果操作成功,则使用uni.navigateTo方法跳转到指定页面
					// 并将扫描到的二维码内容作为参数附加在URL后面
					uni.navigateTo({
						url: '/pages/my/scan-login/scan-login?code=' + code
					});
				} else {
					uni.showToast({
						title: res.msg, // 接口返回的错误消息
						icon: 'none' // 提示图标,这里使用'none'表示不使用图标
					});
				}
			},

			

scan-login 页面确定登录页面

微信图片_20240203161401.png

<template>
  <view class="container">
  	<view class="img-wrap">
  		<!-- 显示桌面图标 -->
  		<image class="desktop" src="/static/img/desktop.png" mode="aspectFill"></image>
  		<!-- 显示二维码图标 -->
  		<image class="qrcode" src="/static/img/qrcode.png" mode="aspectFill"></image>
  	</view>
  	<view class="title">正在尝试网页端扫码登录</view>
  	<view class="title">请确认是否为本人操作</view>
  	<button @click="getScanCodeConfirm" class="btn-confirm">确认登录</button>
  	<button @click="cancelHandle" plain class="btn-cancel">取消登录</button>
  </view>
</template>

<script>
  export default {
  	data() {
  		return {
  			code: ''  // 存储从URL参数获取的code
  		};
  	},
  	onLoad(opt) {
  		console.log(opt);
  		if (opt.code) {
  			this.code = opt.code;   // 如果URL中有code参数,则保存到data中
  		}
  	},
  	methods: {
  		async getScanCodeConfirm() {
  			try {
  				uni.showLoading({ mask: true }); // 显示加载提示 
  				const accessToken = uni.getStorageSync('token');  // 从本地存储获取accessToken
  				const p = { code: this.code, accessToken };  // 准备请求参数
  				const res = await this.$api.getScanCodeConfirmApi(p); // 调用API进行扫码确认
  				if (res.code == 0) {
  					uni.navigateBack();  // 如果确认成功,返回上一页
  				} else {
  					uni.showToast({
  						title: res.msg,
  						icon: 'none',
  						complete: () => {
  							setTimeout(() => {
  								uni.navigateBack();  // 显示错误信息后,延时返回上一页
  							}, 1000);
  						}
  					});
  				}
  			} finally {
  				uni.hideLoading(); //隐藏加载提示
  			}
  		},

  		cancelHandle() {
  			uni.navigateBack();   // 取消登录,返回上一页
  		}
  	}
  };
</script>

<style scoped lang="scss">
  .container {
  	display: flex;
  	flex-direction: column;
  	align-items: center;
  	padding-top: 200rpx;

  	.img-wrap {
  		width: 200rpx;
  		height: 200rpx;
  		margin-bottom: 32rpx;
  		position: relative;
  	}

  	.desktop {
  		width: 200rpx;
  		height: 200rpx;
  	}

  	.qrcode {
  		width: 60rpx;
  		height: 60rpx;
  		position: absolute;
  		left: 50%;
  		transform: translateX(-50%);
  		top: 50rpx;
  	}

  	.title {
  		font-size: 32rpx;
  		color: #666;
  	}

  	.btn-confirm {
  		margin: 32rpx 64rpx;
  		width: 80%;
  		background: #00b053;
  		color: #fff;
  		font-size: 32rpx;
  	}

  	.btn-cancel {
  		border: none;
  		font-size: 30rpx;
  	}
  }
</style>

                      ```