登录
文件结构
创建loginPanel
login-panel
<template>
<div>
loginPanel
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>
创建login account和login register
login-acount
<template>
<div>
loginAccount
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>
register同上
配置login.vue文件,引入loginPanel
login
<template>
<div class="login">
<loginPanel />
</div>
</template>
<script setup lang="ts">
import loginPanel from './compontents/login-panel.vue'
</script>
<style scoped lang="less">
.login {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background-color: rgba(230, 230, 230, 1);
}
</style>
login-panel
loginPanel引入element-Plus panel组件
loginPanel主要内容有选择卡片 以及三个按钮
<template>
<div class="login_panel">
<h1 class="title"></h1>
<el-tabs type="border-card" stretch class="tabs">
<el-tab-pane>
<template #label>
<span class="tabs-label">
<el-icon><Coordinate /></el-icon>
<span>登录</span>
</span>
</template>
<login-account />
</el-tab-pane>
<el-tab-pane>
<template #label>
<span class="tabs-label">
<el-icon><EditPen /></el-icon>
<span>注册</span>
</span>
</template>
<LoginRegister />
</el-tab-pane>
</el-tabs>
<div class="account-control">
<el-checkbox>记住密码</el-checkbox>
<el-link type="primary">忘记密码</el-link>
</div>
<el-button type="primary" class="login-btn">
立即登录
</el-button>
</div>
</template>
<script setup lang="ts">
import { Coordinate, EditPen } from '@element-plus/icons-vue'
import LoginAccount from './login-account.vue'
import LoginRegister from './login-register.vue'
</script>
<style scoped>
.login_panel {
margin-bottom: 150px;
width: 320px;
}
.title {
text-align: center;
}
.account-control {
margin-top: 10px;
display: flex;
justify-content: space-between;
}
.login-btn {
width: 100%;
margin-top: 10px;
}
</style>
创建登录界面
配置rules
- 创建 input 验证的规则
account-config'
// 编写好规则
export const rules = {
name: [
{
required: true,
message: '用户名是必传内容~',
trigger: 'blur'
},
{
pattern: /^[a-z0-9]{5,10}$/,
message: '用户名必须是5~10个字母或者数字~',
trigger: 'blur'
}
],
password: [
{
required: true,
message: '密码是必传内容~',
trigger: 'blur'
},
{
pattern: /^[a-z0-9]{3,}$/,
message: '用户名必须是3位以上的字母或者数字~',
trigger: 'blur'
}
]
}
引入rules动态绑定在el-form处
引入vue中的ref对象,获取到组件实例,在实例中取到输入框的值是否符合规范
login-acount
<template>
<div class="login-account">
<el-form label-width="60px" :rules="rules" :model="account" ref="formRef">
<el-form-item label="账号" prop="name">
<el-input v-model="account.name" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="account.password" show-password />
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { defineExpose, reactive, ref } from 'vue'
import { ElForm } from 'element-plus'
import { rules } from '../config/account-config'
// 初始化数据
const account = reactive({
name: '',
password: ''
})
</script>
<style scoped></style>
响应panel点击事件
在account组件中传出一个函数,可以被panel获取到,当登录按钮按下的时候,触发事件
login-acount
<template>
<div class="login-account">
<el-form label-width="60px" :rules="rules" :model="account" ref="formRef">
<el-form-item label="账号" prop="name">
<el-input v-model="account.name" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="account.password" show-password />
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { defineExpose, reactive, ref } from 'vue'
import { ElForm } from 'element-plus'
// 工具类
import localCache from '@/utils/cache'
// 初始化数据
const account = reactive({
name: localCache.getCache('name') ?? '',
password: crypt.decryptByDES(localCache.getCache('password')) ?? ''
})
// 获取输入框对象
const formRef = ref<InstanceType<typeof ElForm>>()
const loginAction = () => {
formRef.value?.validate((valid) => {
// 判断输入框内数据是否符合规范
if (valid) {
console.log("我被调用啦")
console.log(account)
}
})
}
// setup语法糖需要对导出的函数进行声明
defineExpose({
// 导出函数
loginAction
})
</script>
<style scoped></style>
定义ref对象 , 并在代码中绑定获取子组件
响应点击事件
login-panel
<template>
<div class="login_panel">
<h1 class="title"></h1>
<el-tabs type="border-card" stretch class="tabs">
<el-tab-pane>
<template #label>
<span class="tabs-label">
<el-icon><Coordinate /></el-icon>
<span>登录</span>
</span>
</template>
<login-account ref="accountRef" />
</el-tab-pane>
<el-tab-pane>
<template #label>
<span class="tabs-label">
<el-icon><EditPen /></el-icon>
<span>注册</span>
</span>
</template>
<LoginRegister />
</el-tab-pane>
</el-tabs>
<div class="account-control">
<el-checkbox v-model="isKeepPassword">记住密码</el-checkbox>
<el-link type="primary">忘记密码</el-link>
</div>
<el-button type="primary" class="login-btn" @click="handleLoginClick">
立即登录
</el-button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Coordinate, EditPen } from '@element-plus/icons-vue'
import LoginAccount from './login-account.vue'
import LoginRegister from './login-register.vue'
const accountRef = ref<InstanceType<typeof LoginAccount>>()
// 调用子函数响应事件
const handleLoginClick = () => {
accountRef.value?.loginAction()
}
</script>
<style scoped>
略...
</style>
将账号密码存储在本地
定义isKeepPassword类型,获取选项内容
传入到 loginAction 函数中
login-panel
<template>
<div class="login_panel">
<h1 class="title"></h1>
<el-tabs type="border-card" stretch class="tabs">
<el-tab-pane>
<template #label>
<span class="tabs-label">
<el-icon><Coordinate /></el-icon>
<span>登录</span>
</span>
</template>
<login-account ref="accountRef" />
</el-tab-pane>
<el-tab-pane>
<template #label>
<span class="tabs-label">
<el-icon><EditPen /></el-icon>
<span>注册</span>
</span>
</template>
<LoginRegister />
</el-tab-pane>
</el-tabs>
<div class="account-control">
<el-checkbox v-model="isKeepPassword">记住密码</el-checkbox>
<el-link type="primary">忘记密码</el-link>
</div>
<el-button type="primary" class="login-btn" @click="handleLoginClick">
立即登录
</el-button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Coordinate, EditPen } from '@element-plus/icons-vue'
import LoginAccount from './login-account.vue'
import LoginRegister from './login-register.vue'
const isKeepPassword = ref(true)
const accountRef = ref<InstanceType<typeof LoginAccount>>()
// 调用子函数响应事件
const handleLoginClick = () => {
accountRef.value?.loginAction(isKeepPassword.value)
}
</script>
<style scoped>
略...
</style>
判断是否需要存储账号密码
将密码存入到localstorage中(封装了cache工具)
login-acount
<template>
<div class="login-account">
<el-form label-width="60px" :rules="rules" :model="account" ref="formRef">
<el-form-item label="账号" prop="name">
<el-input v-model="account.name" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="account.password" show-password />
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { defineExpose, reactive, ref } from 'vue'
import { ElForm } from 'element-plus'
// 工具类
import localCache from '@/utils/cache'
import { rules } from '../config/account-config'
// 初始化数据
const account = reactive({
name: localCache.getCache('name') ?? '',
password: localCache.getCache('password') ?? ''
})
// 获取输入框对象
const formRef = ref<InstanceType<typeof ElForm>>()
const loginAction = (isKeepPassword: boolean) => {
formRef.value?.validate((valid) => {
// 判断输入框内数据是否符合规范
if (valid) {
// 1.判断是否需要记住密码
if (isKeepPassword) {
// TODO 将密码缓存在本地缓存中, 不安全需要升级
localCache.setCache('name', account.name)
localCache.setCache('password', account.password)
} else {
localCache.deleteCache('name')
localCache.deleteCache('password')
}
}
})
}
// setup语法糖需要对导出的函数进行声明
defineExpose({
loginAction
})
</script>
<style scoped></style>
utils路径
cache
// 封装的本地存储方法
class LocalCache {
setCache(key: string, value: any) {
window.localStorage.setItem(key, JSON.stringify(value))
}
getCache(key: string) {
// obj => string => obj
const value = window.localStorage.getItem(key)
if (value) {
return JSON.parse(value)
}
}
deleteCache(key: string) {
window.localStorage.removeItem(key)
}
clearCache() {
window.localStorage.clear()
}
}
export default new LocalCache()
由于密码在locastorage中是以明文的形式存储,我们应该对密码进行加密处理
安装crypot
npm install crypto-js --save-dev
创建crypyt工具类
crypyt
//DES 加密
import CryptoJS from 'crypto-js'
const normolKey = '88888888'
class crypto {
// 加密
encryptByDES(message: string, key = normolKey) {
const keyHex = CryptoJS.enc.Utf8.parse(key)
const encrypted = CryptoJS.DES.encrypt(message, keyHex, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
})
return encrypted.ciphertext.toString()
}
// 解密
decryptByDES(ciphertext: string, key = normolKey) {
const keyHex = CryptoJS.enc.Utf8.parse(key)
const decrypted = CryptoJS.DES.decrypt(
{
ciphertext: CryptoJS.enc.Hex.parse(ciphertext)
},
keyHex,
{
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}
)
const result_value = decrypted.toString(CryptoJS.enc.Utf8)
return result_value
}
}
export default new crypto()
login-acount
在存储的时候加密在获取的时候解密
<template>
<div class="login-account">
<el-form label-width="60px" :rules="rules" :model="account" ref="formRef">
<el-form-item label="账号" prop="name">
<el-input v-model="account.name" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="account.password" show-password />
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { defineExpose, reactive, ref } from 'vue'
import { ElForm } from 'element-plus'
// 工具类
import localCache from '@/utils/cache'
import crypt from '@/utils/crypt'
import { rules } from '../config/account-config'
// 初始化数据
const account = reactive({
name: localCache.getCache('name') ?? '',
password: crypt.decryptByDES(localCache.getCache('password')) ?? ''
})
// 获取输入框对象
const formRef = ref<InstanceType<typeof ElForm>>()
const loginAction = (isKeepPassword: boolean) => {
formRef.value?.validate((valid) => {
// 判断输入框内数据是否符合规范
if (valid) {
// 1.判断是否需要记住密码
if (isKeepPassword) {
// TODO 将密码缓存在本地缓存中, 不安全需要升级
localCache.setCache('name', account.name)
localCache.setCache('password', crypt.encryptByDES(account.password))
} else {
localCache.deleteCache('name')
localCache.deleteCache('password')
}
}
})
}
// setup语法糖需要对导出的函数进行声明
defineExpose({
loginAction
})
</script>
<style scoped></style>
通过vuex进行身份验证
store目录结构
store/index
// store.ts
import { createStore } from 'vuex'
import { rootState } from './types'
import login from './login/login'
export const store = createStore<rootState>({
state: {
name: 'codermjjh',
token: ''
},
getters: {},
mutations: {},
actions: {},
modules: {
login
}
})
store/type
// 为 store state 声明类型
export interface rootState {
name: string
token: string
}
store/login/login
创建login中的module对象,声明action函数
import { Module } from 'vuex'
import { ILoginState } from './types'
import { rootState } from '../types'
const loginModule: Module<ILoginState, rootState> = {
namespaced: true,
state: {
token: '',
userInfo: {}
},
getters: {},
mutations: {},
actions: {
// 登录
accountLoginAction({ commit }, payload: any) {
console.log('accountLoginAction', payload)
},
// 注册
registerAction({ commit }, payload: any) {
console.log('registerAction', payload)
}
}
}
export default loginModule
store/login/type
export interface ILoginState {
token: string
userInfo: any
}
login-panel
需要在login-panel中对登录或者是注册的请求进行筛选
<template>
<div class="login_panel">
<h1 class="title"></h1>
<el-tabs type="border-card" stretch class="tabs" v-model="currentTab">
<el-tab-pane name="account">
<template #label>
<span class="tabs-label">
<el-icon><Coordinate /></el-icon>
<span>登录</span>
</span>
</template>
<login-account ref="accountRef" />
</el-tab-pane>
<el-tab-pane name="register">
<template #label>
<span class="tabs-label">
<el-icon><EditPen /></el-icon>
<span>注册</span>
</span>
</template>
<login-register ref="registerRef" />
</el-tab-pane>
</el-tabs>
<div class="account-control">
<el-checkbox v-model="isKeepPassword">记住密码</el-checkbox>
<el-link type="primary">忘记密码</el-link>
</div>
<el-button type="primary" class="login-btn" @click="handleLoginClick">
立即登录
</el-button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Coordinate, EditPen } from '@element-plus/icons-vue'
import LoginAccount from './login-account.vue'
import LoginRegister from './login-register.vue'
const isKeepPassword = ref(true)
const accountRef = ref<InstanceType<typeof LoginAccount>>()
const registerRef = ref<InstanceType<typeof LoginRegister>>()
const currentTab = ref('account')
// 调用子函数响应事件
const handleLoginClick = () => {
if (currentTab.value === 'account') {
accountRef.value?.loginAction(isKeepPassword.value)
} else {
// 调用注册逻辑
console.log('注册', currentTab.value)
registerRef.value?.registerAction()
}
}
</script>
<style scoped>
.login_panel {
margin-bottom: 150px;
width: 320px;
}
.title {
text-align: center;
}
.account-control {
margin-top: 10px;
display: flex;
justify-content: space-between;
}
.login-btn {
width: 100%;
margin-top: 10px;
}
</style>
导入store,并且调用对应函数
login-account
<template>
<div class="login-account">
<el-form label-width="60px" :rules="rules" :model="account" ref="formRef">
<el-form-item label="账号" prop="name">
<el-input v-model="account.name" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="account.password" show-password />
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { defineExpose, reactive, ref } from 'vue'
import { useStore } from 'vuex'
import { ElForm } from 'element-plus'
// 工具类
import localCache from '@/utils/cache'
import crypt from '@/utils/crypt'
import { rules } from '../config/account-config'
const store = useStore()
// 初始化数据
const account = reactive({
name: localCache.getCache('name') ?? '',
password: crypt.decryptByDES(localCache.getCache('password')) ?? ''
})
// 获取输入框对象
const formRef = ref<InstanceType<typeof ElForm>>()
const loginAction = (isKeepPassword: boolean) => {
formRef.value?.validate((valid) => {
// 判断输入框内数据是否符合规范
if (valid) {
// 1.判断是否需要记住密码
if (isKeepPassword) {
// TODO 将密码缓存在本地缓存中, 不安全需要升级
localCache.setCache('name', account.name)
localCache.setCache('password', crypt.encryptByDES(account.password))
} else {
localCache.deleteCache('name')
localCache.deleteCache('password')
}
// 2.开始进行登录验证
// 调用stroe中的方法,将account中的数据结构后传入
store.dispatch('login/accountLoginAction', { ...account })
}
})
}
// setup语法糖需要对导出的函数进行声明
defineExpose({
loginAction
})
</script>
<style scoped></style>
login-register
<template>
<div class="login-register">
<el-form label-width="80px" :rules="rules" :model="account" ref="formRef">
<el-form-item label="账号" prop="name">
<el-input v-model="account.name" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="account.password" show-password />
</el-form-item>
<el-form-item label="确认密码" prop="password">
<el-input v-model="account.passwordagain" show-password />
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { defineExpose, ref, reactive } from 'vue'
import { useStore } from 'vuex'
import { ElForm } from 'element-plus'
import { rules } from '../config/account-config'
const store = useStore()
const account = reactive({
name: '',
password: '',
passwordagain: ''
})
const formRef = ref<InstanceType<typeof ElForm>>()
const registerAction = () => {
formRef.value?.validate((valid) => {
if (valid && account.password == account.passwordagain) {
console.log('我被注册啦', account)
store.dispatch('login/registerAction', { ...account })
} else {
console.log('两次密码需要一致')
}
})
}
defineExpose({
registerAction
})
</script>
<style scoped></style>
配置默认路径以及解决跨域问题
vue.config.js
添加devServer属性
devServer: {
proxy: {
'^/api': {
target: 'http://152.136.185.210:5000',
pathRewrite: {
'^/api': ''
},
changeOrigin: true
}
}
},
创建service/login工具类
service/logintype
export interface IAccount {
name: string
password: string
}
export interface ILoginResult {
id: number
name: string
token: string
}
export interface IDataType<T = any> {
code: number
data: T
}
service/login/login
import mjRequest from '../index'
import { IAccount, IDataType, ILoginResult } from './type'
enum LoginAPI {
AccountLogin = '/login',
LoginUserInfo = '/users/', // 用法: /users/1
UserMenus = '/role/' // 用法: role/1/menu
}
export function accountLoginRequest(account: IAccount) {
return mjRequest.post<IDataType<ILoginResult>>({
url: LoginAPI.AccountLogin,
data: account
})
}
export function requestUserInfoById(id: number) {
return mjRequest.get<IDataType>({
url: LoginAPI.LoginUserInfo + id,
showLoading: false
})
}
export function requestUserMenusByRoleId(id: number) {
return mjRequest.get<IDataType>({
url: LoginAPI.UserMenus + id + '/menu',
showLoading: false
})
}
调用工具类获取数据
store/login/login
import { Module } from 'vuex'
import { ILoginState } from './types'
import { rootState } from '../types'
import {
accountLoginRequest,
requestUserInfoById,
requestUserMenusByRoleId
} from '@/service/login/login'
import localCache from '@/utils/cache'
import router from '@/router'
import { IAccount } from '@/service/login/type'
const loginModule: Module<ILoginState, rootState> = {
namespaced: true,
state: {
token: '',
userInfo: {},
userMenus: []
},
getters: {},
mutations: {
changeToken(state, token: string) {
state.token = token
},
changeUserInfo(state, userInfo: any) {
state.userInfo = userInfo
},
changeUserMenus(state, userMenus: any) {
state.userMenus = userMenus
}
},
actions: {
// 登录
async accountLoginAction({ commit }, payload: IAccount) {
// 1.实现登录逻辑
const loginResult = await accountLoginRequest(payload)
const { id, token } = loginResult.data
commit('changeToken', token)
localCache.setCache('token', token)
// 2.请求用户信息
const userInfoResult = await requestUserInfoById(id)
const userInfo = userInfoResult.data
commit('changeUserInfo', userInfo)
localCache.setCache('userInfo', userInfo)
// 3.请求用户菜单
const userMenusResult = await requestUserMenusByRoleId(userInfo.role.id)
const userMenus = userMenusResult.data
commit('changeUserMenus', userMenus)
localCache.setCache('userMenus', userMenus)
// 4.跳到首页
router.push('/main')
},
// 注册
registerAction({ commit }, payload: any) {
console.log('registerAction', payload)
}
}
}
export default loginModule
store/login/login
export interface ILoginState {
token: string
userInfo: any
userMenus: any
}
配置Token
service/index.ts
requestInterceptor: (config) => {
// console.log('请求成功的拦截')
const token = localCache.getCache('token')
if (token) {
config.headers ? (config.headers.Authorization = `Bearer ${token}`) : ''
}
return config
},