vue3客户端后台
<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 页面确定登录页面
<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>
```