封装axios
src/apis/request.ts
import axios, { type InternalAxiosRequestConfig, type AxiosResponse } from 'axios'
export interface IResponseData<T = any> {
code: number
data: T
message: string
}
const request = axios.create({
timeout: 10000,
baseURL: ''
})
request.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
return config
},
(error: any) => {
return Promise.reject(error)
}
)
// 响应拦截器
request.interceptors.response.use(
(response: AxiosResponse) => {
const { code, message = 'Error' } = response.data
if (code === 200) {
return response.data
}
// 返回包含错误信息的新的 Promise 对象
return Promise.reject(new Error(message))
},
(error: any) => {
const { code, message = '系统出错' } = error.response.data
if (code !== 200) {
ElMessage.error(message)
return Promise.reject(error.message)
}
}
)
// 导出 axios 实例
export default request
安装pinia
pinia-plugin-persistedstate为pinia数据持久化
pnpm i pinia pinia-plugin-persistedstate
src/stores/modules/user.ts
showAvatar控制头像下拉框,userInfoCpd显示用户名称
import { defineStore } from 'pinia'
import { computed } from 'vue'
export const useUserStore = defineStore(
'user',
() => {
const userInfo = ref<string | null>(null)
const token = ref<string | null>(null)
const showAvatar = computed(() => {
return token.value !== null && userInfo.value !== null
})
const userInfoCpd = computed(() => {
if (userInfo.value !== null) {
return JSON.parse(userInfo.value)
}
return ''
})
return {
userInfo,
token,
showAvatar,
userInfoCpd
}
},
// 使用数据持久化
{
persist: true
}
)
注册页面
添加路由
import { useLocalStorage } from '@vueuse/core'
import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
// 定义 404 页面路由
{
path: '/:pathMatch(.*)',
name: 'NotFound',
redirect: '/404'
},
{
path: '/404',
name: '404',
component: () => import('@/views/notFound/index.vue')
},
{
path: '/',
redirect: '/home',
component: () => import('@/layouts/MainLayout.vue'),
children: [
{
path: '/home',
name: 'home',
component: () => import('@/views/home/index.vue')
},
{
path: '/register',
name: 'register',
component: () => import('@/views/register/index.vue')
}
]
}
]
const router = createRouter({
routes,
history: createWebHashHistory()
})
export default router
src/views/register/index.vue
<template>
<div class="flex justify-center items-center h-full flex-col">
<div class="flex justify-center items-center">
<el-icon><UserFilled /></el-icon>
<el-text size="large">用户登录</el-text>
</div>
<el-form :model="registerForm" ref="registerFormRef" label-position="top">
<el-form-item
label="用户名"
prop="username"
:rules="{ required: true, message: '请输入用户名', trigger: 'blur' }"
>
<el-input v-model="registerForm.username" clearable></el-input>
</el-form-item>
<el-form-item
label="密码"
prop="password"
:rules="{ required: true, message: '请输入密码', trigger: 'blur' }"
>
<el-input
v-model="registerForm.password"
clearable
type="password"
show-password
></el-input>
</el-form-item>
<el-form-item
label="确认密码"
prop="confirm"
:rules="{ validator: validateConfirmPass, trigger: 'blur' }"
>
<el-input v-model="registerForm.confirm" clearable type="password" show-password></el-input>
</el-form-item>
<el-form-item label="昵称" prop="nickname">
<el-input v-model="registerForm.nickname" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">注册</el-button>
<el-link :underline="false" type="primary" class="ml-3" @click="router.push('/login')"
>已有账户,登录</el-link
>
</el-form-item>
</el-form>
</div>
</template>
校验密码、用户名规则
<script setup lang="ts">
import { userRegister } from '@/apis/user'
import { type FormInstance } from 'element-plus'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const registerForm = ref({
username: '',
password: '',
confirm: '',
nickname: ''
})
const registerFormRef = ref<FormInstance>()
const loading = ref(false)
const onSubmit = async () => {
await registerFormRef.value?.validate((valid) => {
if (valid) {
handleuserRegister()
}
})
}
const handleuserRegister = async () => {
loading.value = true
try {
await userRegister({ ...registerForm.value })
} catch (error) {
console.log(error)
} finally {
loading.value = false
}
}
const validateConfirmPass = (_rule: any, value: any, callback: any) => {
if (value === '') {
callback(new Error('请再次输入密码'))
} else if (value !== registerForm.value.password) {
callback(new Error('两次输入密码不一致!'))
} else {
callback()
}
}
const validateName = (_rule: any, value: any, callback: any) => {
const re = /^[a-zA-Z0-9#$%_-]{6,30}$/
if (!re.test(value)) {
callback(new Error('用户名只能是字母、数字或者 #、$、%、_、- 这些字符,且长度为6-30'))
} else {
callback()
}
}
const validatePass = (_rule: any, value: any, callback: any) => {
const re = /^.{6,30}$/
if (!re.test(value)) {
callback(new Error('密码只能6-30字符'))
} else {
callback()
}
}
</script>
src/apis/user/types.ts
注册接口类型
import { type IResponseData } from '../request'
export interface IUserRegisterParams {
username: string
password: string
nickname: string
}
export type TUserRegister = (pramas: IUserRegisterParams) => Promise<IResponseData<string>>
src/apis/user/index.ts
注册接口
import request from '../request'
import { type IUserRegisterParams, type TUserRegister } from './types'
// 用户注册
export const userRegister: TUserRegister = (pramas: IUserRegisterParams) => {
return request.post('api/user/register', pramas)
}
登录页面
添加路由
import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/home',
component: () => import('@/layouts/MainLayout.vue'),
children: [
{
path: '/home',
name: 'home',
component: () => import('@/views/home/index.vue')
},
+ {
+ path: '/login',
+ name: 'login',
+ component: () => import('@/views/login/index.vue')
+ },
{
path: '/register',
name: 'register',
component: () => import('@/views/register/index.vue')
}
]
}
]
export default router
<template>
<div class="flex justify-center items-center h-full flex-col">
<div class="flex justify-center items-center">
<el-icon><UserFilled /></el-icon>
<el-text size="large">用户登录</el-text>
</div>
<el-form :model="loginForm" ref="loginFormRef" label-position="top">
<el-form-item
label="用户名"
prop="username"
:rules="{ required: true, message: '请输入用户名', trigger: 'blur' }"
>
<el-input v-model="loginForm.username" clearable></el-input>
</el-form-item>
<el-form-item
label="密码"
prop="password"
:rules="{ required: true, message: '请输入密码', trigger: 'blur' }"
>
<el-input v-model="loginForm.password" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">登录</el-button>
<el-link :underline="false" type="primary" class="ml-3" @click="router.push('/register')"
>注册新用户</el-link
>
</el-form-item>
</el-form>
</div>
</template>
在登录时把后端返回的token保存到userStore中
import { userLogin } from '@/apis/user'
import { useUserStore } from '@/stores'
import { type FormInstance } from 'element-plus'
import { storeToRefs } from 'pinia'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const loginForm = ref({
username: '',
password: ''
})
const loginFormRef = ref<FormInstance>()
const loading = ref(false)
const userStore = useUserStore()
const { token, userInfo } = storeToRefs(userStore)
const onSubmit = async () => {
await loginFormRef.value?.validate((valid) => {
if (valid) {
handleUserLogin()
}
})
}
const handleUserLogin = async () => {
try {
loading.value = true
const res = await userLogin(loginForm.value)
if (res.data.token !== '') {
token.value = res.data.token
userInfo.value = JSON.stringify(loginForm.value)
ElMessage.success('登录成功')
router.push('/')
}
} catch (error) {
console.log(error)
} finally {
loading.value = false
}
}
src/router/index.ts
使用beforeEach来判断本地的token是否存在,访问页面不在白名单中并且token不存在就会跳转到登录页
router.beforeEach((to, from) => {
const userStore = useUserStore()
const token = userStore.token
const whitePath = ['/login', '/register']
// 判断是否登录
if (!whitePath.includes(to.path) && token === null) {
return {
path: '/login',
query: {
redirect: to.path
}
}
}
})