Vue-Uniapp-Node 实现扫码登录功能
前端
Vue
1. 使用`qrcode`第三库实现二维码的生成。`npm install qrcode` 2. 从后端获取当前二维码唯一id以及设置过期时间等。 3. 二维码生成后开启轮询监听用户是否扫码以及二维码是否过期。 4. 用户扫码登录成功后返回登录信息以及token等。
Vue-Uniapp-Node 实现扫码登录功能
PC端
pc端采用vue组件化形式实现二维码组件。
实现思路:
- 使用
qrcode第三库实现二维码的生成。npm install qrcode - 从后端获取当前二维码唯一id以及设置过期时间等。
- 二维码生成后开启轮询监听用户是否扫码以及二维码是否过期。
- 用户扫码登录成功后返回登录信息以及token等。
qrcode.vue
html<template> <div class="qr-code" :style="{ width: size + 'px', height: size + 'px' }"> <canvas @click="createQRCode" ref="canvas" width="300" height="300"></canvas> <div class="mask" v-if="qrcodeStatus !== 1"> <span class="hint-message" :class="{ loading: qrcodeStatus === 0, error: qrcodeStatus === 2 || qrcodeStatus === 3, success: qrcodeStatus === 4 }">{{ qrcodeStatusName }}</span> <span v-if="[2, 3].includes(qrcodeStatus)" class="update-btn" @click="createQRCode">重新加载</span> </div> </div> </template>
js<script> // 扫码登录功能 import QRCode from "qrcode" // 生成二维码图 import { nanoid } from "nanoid" // 生成唯一id import { Message } from 'element-ui' export default { name: 'QRCode', props: { size: { type: Number, default: 150 } }, data() { return { // 0: 生成状态/ 1: 生成成功/ 2:生成失败/ // 3:二维码过期/ 4:扫码成功/ qrcodeStatus: 0, qrcode: { url: window.location.href, id: nanoid(), // 唯一id }, // 二维码登录信息 timer: null, // 轮询定时器 } }, computed: { // 状态文字 qrcodeStatusName() { const arr = ['加载中...', '', '加载失败', '二维码过期', '扫码成功√'] return arr[this.qrcodeStatus] } }, mounted() { this.createQRCode() // 生成二维码 }, beforeDestroy() { if (this.timer) { clearInterval(this.timer) } }, methods: { // 生成二维码 (canvas对象) async createQRCode() { this.qrcodeStatus = 0 // 获取唯一id this.qrcode.id = (await this.$api.login.getQrCode()).data // 生成canvas二维码 QRCode.toCanvas(this.$refs.canvas, JSON.stringify(this.qrcode), { width: this.size, }, (error) => { // 生成错误时 if (error) { this.qrcodeStatus = 2 } else { this.qrcodeStatus = 1 // 开启轮询 this.sendPoll() } }) }, // 开启轮询是否扫码 sendPoll() { if (this.timer) { clearInterval(this.timer) this.timer = null } this.timer = setInterval(() => { this.$api.login.sendPoll({ id: this.qrcode.id }).then(res => { Message.closeAll() // 登录成功 if (res.code === 200) { clearInterval(this.timer) this.timer = null this.qrcodeStatus === 4 setTimeout(() => { this.$emit('success', res.data) }, 300) } else if (res.code === 202) { // 未登录 } else { // 查询失败或登录过期 clearInterval(this.timer) this.timer = null this.qrcodeStatus = 3 } }) }, 1500) } } } </script>
css<style lang="less" scoped> .qr-code { position: relative; overflow: hidden; margin-top: 10px; .mask { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(255, 255, 255, .95); display: flex; flex-direction: column; justify-content: center; align-items: center; .hint-message { font-size: 1rem; font-weight: bold; font-family: Arial, Helvetica, sans-serif; &.loading { display: flex; flex-direction: column; align-items: center; font-weight: 500; font-size: 0.9rem; color: #4c9cff; &::before { width: 30px; height: 30px; content: ''; display: block; border: 2px solid #4c9cff; border-radius: 50%; border-right: none; border-top: none; border-left-width: 3px; border-bottom-width: 2px; margin-bottom: 20px; animation: ratote linear infinite 1s; @keyframes ratote { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } } } &.error { color: #F56C6C; } &.success { color: #67C23A; } } .update-btn { color: #4c9cff; font-size: 0.8rem; margin-top: 10%; cursor: pointer; } } } </style>
APP端
app端采用uni-app实现,用户在app上登录并扫描pc端二维码。
实现思路:
- 登录app并存储携带token。
- 扫码二维码发送登录确认请求。(携带当前账号token信息)
vue<template> <view class="qr-code" @tap="scanCode"> <uni-icons type="scan" size="24" color="#fff"></uni-icons> </view> </template> <script setup> import { qrCodeLogin } from '@/Api/login.js' import { useUserInfoStore } from '@/store/pinia.js' import { toRef } from 'vue' const store = useUserInfoStore() const isLogin = toRef(store, 'isLogin') const scanCode = (e) => { // #ifdef APP-PLUS // 判断是否登录 if (!isLogin.value) { uni.navigateTo({ url: '/pages/login/login' }) return } // 使用相机扫码 uni.scanCode({ onlyFromCamera: true, scanType: "qrCode", success({ result }) { // 输出扫码结果 try { const { id, url } = JSON.parse(result) uni.showModal({ title: '确认登录', content: `正在扫码登录${url},请确认是否本人操作!!!`, success(res) { // 确认登录 if (res.confirm) { // 发送登录请求 qrCodeLogin({ id }).then(res => { if (res.code === 200) { uni.showToast({ title: res.msg || '登录成功', icon: 'success' }) } else { uni.showToast({ title: res.msg || '登录异常', icon: 'error' }) } }) } } }) } catch (e) { console.log(e); uni.showToast({ title: '无法识别二维码', icon: 'exception' }) //TODO handle the exception } } }) // #endif } </script> <style lang="less"> </style>
服务端
服务器采用Express框架搭建接口,共实现三个接口请求。
- 获取二维码唯一id并设置过期时间。
- 轮询获取二维码当前状态。
- app端确认登录请求。
js// 扫码数据存储 const { v4: uuid } = require('uuid') const _QrCode = new Map() // 获取唯一id和设置过期时间 router.post('/getQrCode', async (req, res) => { const date = Date.now() // 当前时间戳 const data = { startTime: date, endTime: date + 1000 * 60 * 3, // 三分钟有效时间 id: uuid(), // 唯一id status: false, // 是否扫码 data: null, // 数据信息 } _QrCode.set(data.id, data) // 清理过期数据 _QrCode.forEach((v, k, m) => { if (v.endTime < date) m.delete(k) }) res.$data(200, 'uuid', data.id) }) // 扫码轮询接口 router.post('/sendPoll', async (req, res) => { const { id } = req.body if (id) { if (_QrCode.has(id) && _QrCode.get(id).endTime > Date.now()) { // 判断二维码是否过期 const qrcode = _QrCode.get(id) if (qrcode.status && qrcode.data) { // 判断是否登录 _QrCode.delete(id) res.$data(200, '登录成功', qrcode.data) } else { res.$data(202, '未登录', null) } } else { res.$data(201, '登录过期', null) } } else { res.$data(400, '查询失败', 200) } }) // 扫码确认接口 router.post('/qrCodeLogin', [isToken], async (req, res) => { const { id } = req.body const { username } = req.$token // 获取用户名 if (id && username) { try { // 判断是否过期 const qrcode = _QrCode.get(id) if (_QrCode.has(id) && qrcode.endTime > Date.now()) { const result = (await loginModel.select({ username }))[0] // 获取用户信息存储 if (result && !qrcode.status) { // 判断用户是否存在并且防止登录冲突 // 生成Token const token = JWT.generate({ _id: result._id, username: result.username, root: result.root }, "1d") result.token = token qrcode.data = result qrcode.status = true // 变更状态 _QrCode.set(id, qrcode) res.$data(200, '登录成功', null) } else { res.$data(400, '无权限登录', null) } } else { res.$data(400, '验证码过期', null) } } catch (error) { console.log(error) res.$data(400, '登录失败', null) } } else { res.$data(400, '登录失败', null) } })