效果
无token情况下 访问dashboard
刷新跳转到登录页,url上redirect携带的是之前访问过的路径 之前query也未丢失
登录成功后
登录成功后 重定向回 dashboard url参数依然存在 (url参数都是 useRouteQuery hook功劳 在下面有实现)
4-1 安装nProgress进度条
npm install --save nprogress
# ts
npm install -D @types/nprogress
4-2 添加路由验证
创建permission.ts
添加路由导航钩子,进行登录验证
src/permission.ts
import router from '@/router'
import nProgress from 'nprogress'
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from './utils/auth'
nProgress.configure({ showSpinner: false })
const whiteList = ['/login'] // 白名单
router.beforeEach((to) => {
nProgress.start()
const hasToken = getToken()
if (hasToken) { // 有token代表已登录
if (to.path === '/login') {
nProgress.done()
return {
path: '/',
replace: true
}
}
nProgress.done()
return true
} else {
if (whiteList.includes(to.path)) {
nProgress.done()
return true
}
nProgress.done()
return {
path: '/login',
query: {
redirect: to.path,
...to.query
}
}
}
})
router.afterEach(() => {
nProgress.done()
})
4-3 登录后跳转逻辑修改
src/views/login/index.vue
<template>
<div class="login-container">
<el-form
class="login-form"
:model="loginForm"
:rules="loginRules"
ref="loginFormRef"
>
<div class="admin-logo">
<img class="logo" src="../../assets/logo.png" alt="logo">
<h1 class="name">Vue3 Admin</h1>
</div>
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon-class="user"></svg-icon>
</span>
<el-input
ref="usernameRef"
placeholder="请输入用户名"
v-model="loginForm.username"
autocomplete="off"
tabindex="1"
/>
</el-form-item>
<el-form-item prop="password">
<span class="svg-container">
<svg-icon icon-class="password"></svg-icon>
</span>
<el-input
ref="passwordRef"
:class="{
'no-autofill-pwd': passwordType === 'password'
}"
placeholder="请输入密码"
v-model="loginForm.password"
type="text"
autocomplete="off"
tabindex="2"
/>
<span class="show-pwd" @click="showPwd">
<svg-icon
:icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
</span>
</el-form-item>
<!-- 登录按钮 -->
<el-button
type="primary"
style=" width: 100%; margin-bottom: 30px"
:loading="loading"
@click="handleLogin"
>Login</el-button>
</el-form>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, toRefs, onMounted } from 'vue'
import { ElForm } from 'element-plus'
import { useRouter } from 'vue-router'
import { useStore } from '@/store'
import useRouteQuery from './hooks/useRouteQuery'
type IElFormInstance = InstanceType<typeof ElForm>
export default defineComponent({
name: 'Login',
setup() {
const store = useStore()
const router = useRouter()
const loading = ref(false) // 登录加载状态
// form ref
const loginFormRef = ref<IElFormInstance | null>(null)
// form username ref
const usernameRef = ref<HTMLInputElement | null>(null)
// form password ref
const passwordRef = ref<HTMLInputElement | null>(null)
const loginState = reactive({
loginForm: {
username: '',
password: ''
},
loginRules: {
username: [
{
required: true,
trigger: 'blur',
message: '请输入用户名!'
}
],
password: [
{
required: true,
trigger: 'blur',
message: '请输入密码!'
}
]
},
passwordType: 'password'
})
// 显示密码
const showPwd = () => {
loginState.passwordType = loginState.passwordType === 'password' ? 'text' : 'password'
}
// 重定向router query处理
const { redirect, otherQuery } = useRouteQuery()
// 登录
const handleLogin = () => {
(loginFormRef.value as IElFormInstance).validate((valid) => {
if (valid) {
loading.value = true
store.dispatch('user/login', loginState.loginForm).then(() => {
// 登录成功后跳转之前被访问页或首页
router.push({
path: redirect.value || '/',
query: otherQuery.value
})
}).finally(() => {
loading.value = false
})
} else {
console.log('error submit!!')
}
})
}
// 自动获取焦点
onMounted(() => {
if (loginState.loginForm.username === '') {
(usernameRef.value as HTMLInputElement).focus()
} else if (loginState.loginForm.password === '') {
(passwordRef.value as HTMLInputElement).focus()
}
})
return {
loading,
loginFormRef,
handleLogin,
showPwd,
usernameRef,
passwordRef,
...toRefs(loginState)
}
}
})
</script>
<style lang="scss">
$bg:#283443;
$light_gray:#fff;
$cursor: #fff;
.login-container {
.el-form-item {
border: 1px solid #dcdee2;
border-radius: 5px;
.el-input {
display: inline-block;
height: 40px;
width: 85%;
input {
background: transparent;
border: 0;
-webkit-appearance: none;
border-radius: 0px;
padding: 12px 5px 12px 15px;
height: 40px;
}
}
}
.no-autofill-pwd { // 解决自动填充问题
.el-input__inner { // 模仿密码框原点
-webkit-text-security: disc !important;
}
}
}
</style>
<style lang="scss" scoped>
$bg:#2d3a4b;
$dark_gray:#889aa4;
$light_gray:#eee;
.login-container {
min-height: 100%;
width: 100%;
overflow: hidden;
background-image: url('../../assets/body.svg');
background-repeat: no-repeat;
background-position: 50%;
background-size: 100%;
.login-form {
position: relative;
width: 500px;
max-width: 100%;
margin: 0 auto;
padding: 140px 35px 0;
overflow: hidden;
box-sizing: border-box;
.svg-container {
padding: 0 10px;
}
.show-pwd {
font-size: 16px;
cursor: pointer;
margin-left: 7px;
}
.admin-logo {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
.logo {
width: 60px;
height: 60px;
}
.name {
font-weight: normal;
margin-left: 10px;
}
}
}
}
</style>
创建useRouteQuery hook
获取url query 并得到redirect参数 redirec参数或作为登录成功后跳转路径
src/views/login/hooks/useRouteQuery.ts
import { ref, Ref, watchEffect } from 'vue'
import { useRoute, LocationQueryRaw } from 'vue-router'
interface RouteQuery {
redirect: Ref<string>;
otherQuery: Ref<LocationQueryRaw | undefined>;
}
const useRouteQuery = (): RouteQuery => {
const route = useRoute()
const query = route.query
const redirect = ref('')
const otherQuery = ref<LocationQueryRaw | undefined>(undefined)
const getOtherQuery = (query: LocationQueryRaw) => {
return Object.keys(query || {}).filter(q => q !== 'redirect').reduce((obj, key) => {
obj[key] = query[key]
return obj
}, {} as LocationQueryRaw)
}
otherQuery.value = getOtherQuery(query)
// 不使用watch(route) 原因说明:
// 尤大回应 https://www.gitmemory.com/issue/vuejs/vue-next/2027/685247838
// https://blog.csdn.net/weixin_47339511/article/details/117221559
// 修复使用watch 监听route 性能开销问题
watchEffect(() => {
const query = route.query
if (query) {
redirect.value = query.redirect as string
otherQuery.value = getOtherQuery(query as LocationQueryRaw)
}
})
return {
redirect,
otherQuery
}
}
export default useRouteQuery
本节参考源码
https://gitee.com/brolly/vue3-element-admin/commit/a4471f25ee5d88125dc7ee3569b2122beb384a07