仿写网易云-项目初始化-扫描二维码登录
1. 项目初始化
2. 安装使用 Element UI
这里使用的是全部引入,比较方便
-
安装 element ui :
npm i element-ui -S
全部引入
在 main.js 中添加下面的代码-
// element ui import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' Vue.use(ElementUI)
按需引入
-
-
安装
babel-plugin-component
:npm install babel-plugin-component -D
-
修改
babel.config.js
文件:module.exports = { presets: [ '@vue/cli-plugin-babel/preset', ['@babel/preset-env', { modules: false }] ], plugins: [ [ 'component', { libraryName: 'element-ui', styleLibraryName: 'theme-chalk' } ] ] }
-
按需引入使用案例,在
main.js
中// element ui import { Button, Select } from 'element-ui' Vue.component(Button) Vue.component(Select)
3. 设置一下页面的布局
- Header
- Footer
- Side
- Main
4. 开始写 Header
头部导航
- 左边的
HeaderLogo
- 中间的搜索框
HeaderSearch
- 右边的头像昵称(登录状态)或显示未登录
HeaderRight
5. 点击未登录弹出登录窗口
5.0 添加代理解决跨域问题
在写扫码登录的时候发现及时二维码状态返回扫码登录成功并且收到cookie的情况下,再次访问接口查询当前的状态的时候还是一个未登录的状态,原因是因为本地node运行在3000的端口,而vue项目占用8080的端口,端口号不一致导致跨域问题,8080端口的cookie再次访问3000的端口时没有被携带,所以这里使用代理的方式来解决跨域问题。
在 vue.config.js 中加入 devServer 节点,并加入下面的配置:
- target:接口域名
- changeOrigin:表示是否跨域
devServer: {
proxy: {
'/': {
target: 'http://192.168.3.16:3000',
ws: false,
changeOrigin: true
}
}
}
5.1 原理分析
主要用到 Element 组件的 Dialog对话框
参考了这篇博客: vue之登录弹框Dialog对话框实现
但是这里与此篇博客不同在于,这个弹窗有三个页面
(所以需要用到路由Router来进行切换):
- 扫码登录
- 电话号码登录
- 注册
-
Dialog 的使用
- 官方使用文档:
el-dialog
设置一个居中和宽度- 在弹出框
el-dialog
中加入一个占位符router-view
根据不同路径渲染不同组件 - 未登录字眼链接默认跳转
/logincode
,也就是扫描二维码登录
<el-button class="noLogin" type="text" @click="loginVisible = true"> <router-link class="noLoginA" to="/logincode">未登录 </router-link> <i class="el-icon-caret-bottom"></i> </el-button> <el-dialog :visible.sync="loginVisible" center :append-to-body='true' :lock-scroll="false" width="350px"> <router-view></router-view> </el-dialog>
data () { return { loginVisible: false } }
-
路由使用步骤
-
占位符
<router-view></router-view>
-
router/index.js 引入组件,配置跳转路径对应的组件
import LoginByScanCode from '@/components/Header/User/LoginByScanCode.vue' import LoginByPhoneNumber from '@/components/Header/User/LoginByPhoneNumber.vue' import RegisterByPhoneNumber from '@/components/Header/User/RegisterByPhoneNumber.vue' const routes = [ {path: '/logincode',component: LoginByScanCode}, {path: '/loginphone',component: LoginByPhoneNumber}, {path: '/register',component: RegisterByPhoneNumber} ]
-
页面中根据需要使用
<router-link to="/xxx"></router-link>
-
-
/logincode
通过扫描二维码登录LoginByScanCode
-
/loginphone
通过验证手机和手机密码登录LoginByPhoneNumber
-
/register
注册新账号RegisterByPhoneNumber
5.2 扫码登录 LoginByScanCode 组件
5.2.1 组件UI 样式
HTML 结构
<template>
<div id="login-code-container">
<p class="title">扫码登录</p>
<div class="main-outer">
<div class="left-img-outer">
<img id="left-img" src="@/assets/images/left-img.png">
</div>
<div class="right-code-outer">
<div id="right-code">
<img :src="QRBase64">
<div class="code-invalid" v-if="codeIsValid === false">
<p>二维码已失效</p>
<h5><el-button type="primary" size="mini" @click="afreshGetQR()">点击刷新</el-button></h5>
</div>
<div class="code-loading" v-if="codeIsLoading === true">
<p><i class="el-icon-loading"></i></p>
<p>登录中...... </p>
</div>
</div>
<p>使用<a href="https://music.163.com/#/download">PT音乐</a>APP版本扫码登录</p>
</div>
</div>
<router-link to="/loginphone" class="other-way">选择其他登录模式 ></router-link>
</div>
</template>
CSS 样式
<style lang="less" scoped>
// 扫码登录 标题
#login-code-container .title{
margin-top: 30px;
font-size: 28px;
text-align: center;
color: #3c3c3c;
}
// 选择其他登录模式 样式
#login-code-container .other-way{
display: inline-block;
margin: 30px 0 25px 0;
width: 100%;
text-align: center;
padding-right: 0;
font-size: 12px;
line-height: 28px;
color: rgba(0,0,0,0.80);
text-decoration: none;
}
// 选择其他登录模式 hover 样式
#login-code-container .other-way:hover{
text-decoration-line:underline;
}
// 中间主体扫码样式
#login-code-container .main-outer{
height: 250px;
margin-top: 40px;
// background-color: rgb(209, 169, 169);
}
// 左边图片外框 右边二维码外框
.left-img-outer,.right-code-outer{
display: inline-block;
width: 45%;
}
.left-img-outer{
// background-color: rgb(174, 218, 138);
}
.right-code-outer{
float: right;
padding: 20px 0;
// background-color: rgb(218, 210, 147);
}
.right-code-outer>p{
text-align: center;
line-height: 20px;
}
.right-code-outer a{
text-decoration: none;
color: #0c73c2;
}
#left-img{
width: 100%;
}
#right-code{
width: 100%;
height: 135px;
// background-color: rgb(238, 234, 175);
margin: 10px 0;
position: relative;
}
#right-code img{
width: 100%;
}
#right-code .code-invalid{
width: 100%;
height: 100%;
position: absolute;
top: 0;
background-color: rgba(5, 5, 5, 0.7);
}
.code-invalid p{
text-align: center;
color: #fff;
font-size: 14px;
margin: 30% 0 10%;
}
.code-invalid h5{
text-align: center;
}
#right-code .code-loading{
width: 100%;
height: 100%;
position: absolute;
top: 0;
background-color: rgba(253, 253, 253, 0.9);
}
.code-loading i{
margin-top: 35%;
font-size: 20px;
}
.code-loading p{
line-height: 20px;
text-align: center;
color: #3c3c3c;
}
</style>
5.2.2 函数的封装
因为需要使用 axios ,在
src
目录下utils/request.js
引入axios
,并且设置请求根路径。
import axios from 'axios'
// 调用 axios.create() 函数,创建一个 axios 的实例对象,用 request 来接收
const request = axios.create({
// 指定请求的根路径
baseURL: 'http://127.0.0.1:3000'
})
export default request
在在
src
目录下创建api
,用来封装函数
api/
LoginAndReguster/loginByCode.js 放扫码登录相关的请求api/
user/user.js 放与用户账户相关的请求
// 注册相关的 API 接口
import request from '@/utils/request.js'
// 每次请求都带上时间戳 timestamp 参数 防止缓存
// withCredentials 请求为跨域类型时是否在请求中协带cookie
// 获得 QR 的 key
export const getLoginQRKey = function () {
return request.get('/login/qr/key', {
params: {
timestamp: new Date().getTime(),
withCredentials: true
}
})
}
// 传入 key 生成二维码图片的 base64 和二维码信息
export const getLoginQR = function (key) {
return request.get('/login/qr/create', {
params: {
key: key,
qrimg: true,
timestamp: new Date().getTime(),
withCredentials: true
}
})
}
// 获取登录信息
export const getLoginStatus = function () {
return request.get('/login/status', {
params: {
timestamp: new Date().getTime(),
withCredentials: true
}
})
}
// 带上key 检查二维码是否过期
export const checkStatus = function (key) {
return request.get('/login/qr/check', {
params: {
key: key,
timestamp: new Date().getTime(),
withCredentials: true
}
})
}
import request from '@/utils/request.js'
// 获取用户信息
export const getUserAccount = function (cookier) {
return request.get('/user/account', {
params: {
cookie: cookier,
timestamp: new Date().getTime(),
withCredentials: true
}
})
}
5.2.3 页面功能逻辑
写在前面:感觉整个页面逻辑确实没有什么问题,但是在扫描成功确认登录后再次去访问登录状态时还是没有登录,根据登录成功返回的 cookie 带着再去访问账号信息时还是得不到账号信息,对比官方文档给出的案例感觉并无差别,但是官方的案例登陆之后给浏览器设置了cookie,而我的登录成功之后浏览器并没有被设置cookie,导致检查状态一直是未登录。不知道该如何解决。如果有哪位大佬知道原因,还麻烦指点我一下。
getLoginQRImg
函数
-
getLoginQRKey
获取二维码的 key -
getLoginQR
获取二维码的信息和 base64 格式的编码,可以直接作为 img 的src渲染到页面上 -
使用
setInterval
创建定时器timer
,间隔 3000ms 便访问一次二维码的状态,根据不同的状态渲染页面样式code === 800
二维码已经失效,渲染失效样式的DOM,并且清理 timercode === 802
正在等待确认中,渲染登录中的样式code === 803
登录成功,返回 cookie,,并且清理 timer
-
afreshGetQR
函数 当二维码失效的时候点击点击刷新
的时候重新调用getLoginQRImg
函数 -
将
getLoginQRImg
函数放在created
组件周期时 -
但是这里有一个问题,点击 Dialog 的关闭按钮的时候,timer 没有被清除,所以需要监听 Dialog 的状态,父组件中有一个
loginVisible
,当Dialog 关闭时为false
,打开时为true
。需要将此变量传递给子组件:-
父组件中的占位符bind绑定变量给子组件:
<router-view :loginVisible="loginVisible"></router-view>
-
子组件使用 props 接收,设置一个默认值 false:
props: { loginVisible: { type: Boolean, default: false } }
-
-
在进入定时器 timer 的时候判断该值,如果位false则清除定时器,达到关闭Dialog 同时清除timer的作用。
// 导入获取 key 和 QR 的 API
import { getLoginQRKey, getLoginQR, getLoginStatus, checkStatus } from '@/api/LoginAndRegister/loginByQR.js'
// 导入 user.js
import { getUserAccount } from '@/api/user/user.js'
export default {
props: {
loginVisible: {
type: Boolean,
default: false
}
},
data () {
return {
codeIsValid: true,
codeIsLoading: false,
QRkey: '',
QRBase64: '',
cookier: ''
}
},
methods: {
// 获取登录二维码
async getLoginQRImg () {
// 获取 key
const { data: dataKeyObj } = await getLoginQRKey()
const keyObj = dataKeyObj.data
this.QRkey = keyObj.unikey
console.log('二维码的key:', keyObj.unikey)
// 获取 二维码
const { data: dataQRObj } = await getLoginQR(this.QRkey)
const QRObj = dataQRObj.data
this.QRBase64 = QRObj.qrimg
console.log('二维码base64格式:', QRObj.qrimg)
// 循环判断二维码是否过期 是否已经登录
const timer = setInterval(async () => {
// loginVisible 为 true 时定时器才有效
// 当 dialog 被删除时 定时器被消除
if (this.loginVisible === false) {
clearInterval(timer)
}
const { data: keyStatus } = await checkStatus(this.QRkey)
console.log(keyStatus)
if (keyStatus.code === 800) {
// 二维码过期
console.log('二维码已失效')
this.codeIsLoading = false
clearInterval(timer)
this.codeIsValid = false
} else if (keyStatus.code === 802) {
// 授权登录中
console.log('授权登录中...')
this.codeIsLoading = true
} else if (keyStatus.code === 803) {
this.codeIsLoading = false
// 登录成功 会返回 cookie
console.log('登录成功')
clearInterval(timer)
const { data: loginStatus } = await getLoginStatus()
console.log(loginStatus)
this.cookier = keyStatus.cookie
const { data: userAccount } = await getUserAccount(this.cookier)
console.log(userAccount)
}
}, 3000)
},
// 重新获取二维码
afreshGetQR () {
console.log('重新获取二维码')
this.getLoginQRImg()
this.codeIsValid = true
}
},
created () {
this.getLoginQRImg()
}
}