feat(projects): 增加登录鉴权

This commit is contained in:
‘chen.home’ 2022-08-13 00:46:42 +08:00
parent 07864134ee
commit 5e9e3d740e
19 changed files with 224 additions and 87 deletions

View File

@ -35,5 +35,6 @@ module.exports = {
'no-debugger': 'off', // 关闭debugger警告
'vue/multi-word-component-names': 0, // 关闭文件名多单词
// 'import/no-unresolved': ['error', { ignore: ['~icons/*'] }],
"@typescript-eslint/no-explicit-any": ["off"]
},
};

View File

@ -1,23 +1,17 @@
import Mock from 'mockjs';
function resultSuccess(data: any, { msg = 'success' } = {}) {
return Mock.mock({
code: 200,
data,
msg,
});
}
import { resultSuccess } from '../utils';
const Random = Mock.Random;
const token = Random.string('upper', 32, 32);
const adminInfo = {
const userInfo = {
userId: '1',
username: 'admin',
realName: 'Admin',
avatar: Random.image(),
desc: 'manager',
password: Random.string('upper', 4, 16),
userName: 'admin',
realName: '管理员大人',
avatar: 'https://z3.ax1x.com/2021/10/29/5jnWgf.jpg',
role: 'admin',
password: '123456',
token,
permissions: [
{
@ -28,18 +22,6 @@ const adminInfo = {
label: '监控页',
value: 'dashboard_monitor',
},
{
label: '工作台',
value: 'dashboard_workplace',
},
{
label: '基础列表',
value: 'basic_list',
},
{
label: '基础列表删除',
value: 'basic_list_delete',
},
],
};
@ -49,17 +31,7 @@ export default [
timeout: 1000,
method: 'post',
response: () => {
return resultSuccess({ token });
},
},
{
url: '/mock/admin_info',
timeout: 1000,
method: 'get',
response: () => {
// const token = getRequestToken(request);
// if (!token) return resultError('Invalid token');
return resultSuccess(adminInfo);
return resultSuccess(userInfo);
},
},
];

16
mock/utils.ts Normal file
View File

@ -0,0 +1,16 @@
import Mock from 'mockjs';
export function resultSuccess(data: any, { msg = 'success' } = {}) {
return Mock.mock({
code: 200,
data,
msg,
});
}
export function resultFailed(data: any, { msg = 'failed' } = {}) {
return Mock.mock({
code: 400,
data,
msg,
});
}

5
src/enum/common.ts Normal file
View File

@ -0,0 +1,5 @@
/* 缓存的Key值 */
export enum EnumStorageKey {
userInfo = '__USER_INFO__',
token = '__TOKEN__',
}

1
src/enum/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './common';

View File

@ -4,7 +4,7 @@
<Logo />
<Menu />
</n-layout-sider>
<n-layout class="h-full bg-hex-f3f4f6" :native-scrollbar="false">
<n-layout class="h-full" :native-scrollbar="false" embedded>
<n-layout-header bordered class="h-60px flex-y-center justify-between">
<div class="flex-y-center h-full">
<CollapaseButton />
@ -30,7 +30,7 @@
</router-view>
</n-layout-content>
</div>
<n-layout-footer position="absolute" bordered class="flex-center bg-white h-40px">
<n-layout-footer position="absolute" bordered class="flex-center h-40px">
{{ appStore.footerText }}
</n-layout-footer>
</n-layout>

View File

@ -1,5 +1,5 @@
<template>
<n-dropdown trigger="hover" :options="options" @select="handleSelect">
<n-dropdown trigger="click" :options="options" @select="handleSelect">
<HeaderButton>
<n-avatar round size="large" src="https://z3.ax1x.com/2021/10/29/5jnWgf.jpg" />
{{ authStore.name }}

View File

@ -1,7 +1,22 @@
import type { Router } from 'vue-router';
import { getToken } from '@/utils/auth';
// const authStore = useAuthStore();
export function setupRouterGuard(router: Router) {
router.beforeEach((_to, _from, next) => {
const isLogin = Boolean(getToken());
router.beforeEach((to, _from, next) => {
// 登录鉴权
if (!isLogin) {
if (to.name === 'login') {
next();
} else {
const redirect = to.fullPath;
next({ path: '/login', query: { redirect } });
}
return false;
}
next();
});
// router.afterEach((_to) => {});

View File

@ -41,7 +41,7 @@ const routes: RouteRecordRaw[] = [
},
{
path: '/login',
name: 'Login',
name: 'login',
component: () => import('@/views/login/index.vue'), // 注意这里要带上 文件后缀.vue
},
{

View File

@ -1 +0,0 @@
export * from './login';

View File

@ -1,30 +1,9 @@
import { request } from '../http';
import { mockRequest } from '../http';
interface Itest {
data: string;
interface Ilogin {
userName: string;
password: string;
}
/* get方法测试 */
export function fetachGet() {
return request.get('/getAPI');
}
/* post方法测试 */
export function fetachPost(params: Itest) {
return request.post('/postAPI', params);
}
/* delete方法测试 */
export function fetachDelete() {
return request.Delete('/deleteAPI');
}
/* put方法测试 */
export function fetachPut(params: Itest) {
return request.put('/putAPI', params);
}
/* patch方法测试 */
export function fetachPatch(params: Itest) {
return request.patch('/patchAPI', params);
}
/* mock方法测试 */
export function fetchMock() {
return mockRequest.post('/login');
export function fetchLogin(params: Ilogin) {
return mockRequest.post('/login', params);
}

30
src/service/api/test.ts Normal file
View File

@ -0,0 +1,30 @@
import { request } from '../http';
import { mockRequest } from '../http';
interface Itest {
data: string;
}
/* get方法测试 */
export function fetachGet() {
return request.get('/getAPI');
}
/* post方法测试 */
export function fetachPost(params: Itest) {
return request.post('/postAPI', params);
}
/* delete方法测试 */
export function fetachDelete() {
return request.Delete('/deleteAPI');
}
/* put方法测试 */
export function fetachPut(params: Itest) {
return request.put('/putAPI', params);
}
/* patch方法测试 */
export function fetachPatch(params: Itest) {
return request.patch('/patchAPI', params);
}
/* mock方法测试 */
export function fetchMock() {
return mockRequest.post('/login');
}

2
src/service/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './api/test';
export * from './api/login';

View File

@ -1,9 +1,55 @@
import { defineStore } from 'pinia';
import { fetchLogin } from '@/service';
import { setUserInfo, getUserInfo, getToken, setToken } from '@/utils/auth';
import { router } from '@/router';
export const useAuthStore = defineStore('auth-store', {
state: () => {
return {
name: '张三',
userInfo: getUserInfo(),
token: getToken(),
loginLoading: false,
};
},
getters: {
/** 是否登录 */
isLogin(state) {
return Boolean(state.token);
},
},
actions: {
/* 用户登录 */
async login(userName: string, password: string) {
this.loginLoading = true;
const data = await fetchLogin({ userName, password });
// 处理登录信息
this.handleAfterLogin(data as any);
this.loginLoading = false;
},
handleAfterLogin(data: Auth.UserInfo) {
// 存储用户信息
setUserInfo(data);
setToken(data.token);
this.userInfo = data;
this.token = data.token;
// 触发用户提示
window.$notification?.success({
title: '登录成功!',
content: `欢迎回来,${this.userInfo.realName}!`,
duration: 3000,
});
// 进行跳转
const route = router.currentRoute;
const { query } = route.value;
if (query?.redirect) {
router.push(query.redirect as string);
} else {
router.push('/');
}
},
},
});

7
src/types/api.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
/* 接口类型数据 */
/** 后端返回的用户相关类型 */
declare namespace ApiAuth {
/** 返回的用户信息 */
type UserInfo = Auth.UserInfo;
}

31
src/types/business.d.ts vendored Normal file
View File

@ -0,0 +1,31 @@
/** 用户相关模块 */
declare namespace Auth {
/**
* ()
* - super: ()
* - admin: 管理员
* - user: 用户
* - custom: 自定义角色
*/
// type RoleType = keyof typeof import('@/enum').EnumUserRole;
/** 用户信息 */
interface UserInfo {
/** 用户id */
userId: string;
/** 用户名 */
userName: string;
/* 用户称呼 */
realName: string;
/* 用户头像 */
avatar: string;
/** 用户角色类型 */
role: RoleType;
/* 密码 */
password: string;
/* token */
token: string;
/* 权限路由 */
permissions: [];
}
}

34
src/utils/auth.ts Normal file
View File

@ -0,0 +1,34 @@
import { setLocal, getLocal, removeLocal } from './storage';
import { EnumStorageKey } from '@/enum';
/* 获取当前token */
export function getToken() {
return getLocal(EnumStorageKey.token);
}
/* 设置token */
export function setToken(data: string) {
setLocal(EnumStorageKey.token, data);
}
/* 移除token */
export function removeToken() {
removeLocal(EnumStorageKey.token);
}
/* 获取用户详情 */
export function getUserInfo() {
return getLocal(EnumStorageKey.userInfo);
}
/* 设置用户详情 */
export function setUserInfo(data: any) {
setLocal(EnumStorageKey.userInfo, data);
}
/* 移除用户详情 */
export function removeUserInfo() {
removeLocal(EnumStorageKey.userInfo);
}
/** 去除用户相关缓存 */
export function clearAuthStorage() {
removeToken();
removeUserInfo();
}

View File

@ -1,12 +1,12 @@
<template>
<div flex-center wh-full bg-hex-F3F4F6>
<n-carousel autoplay trigger="hover" dot-type="line" effect="fade" :interval="3000" w-lg sm:hidden xl:block>
<img v-for="(item, index) in swiperList" :key="index" h-screen object-cover :src="item" />
<n-carousel autoplay trigger="hover" dot-type="line" effect="fade" class="w-3/4">
<img v-for="(item, index) in swiperList" :key="index" class="h-screen object-cover" :src="item" />
</n-carousel>
<div flex-1 flex-center>
<div b-rd-2 bg-white w-md h-xl shadow-lg p-5xl>
<div w-full h-xl px-6xl>
<n-h1 c-blue>
<Icon icon="icon-park-outline:plastic-surgery" inline-block />
<e-icon icon="icon-park-outline:plastic-surgery" :size="28" />
Ench Admin
</n-h1>
<n-p depth="3">高效简约可能对你有点帮助</n-p>
@ -52,7 +52,7 @@
<n-checkbox>记住我</n-checkbox>
<n-button :text="true">忘记密码</n-button>
</div>
<n-button w-full type="primary" round size="large" @click="validateAll">登录</n-button>
<n-button w-full type="primary" round size="large" @click="handleLogin">登录</n-button>
</n-space>
</n-form>
<n-divider><span op-50>其他登录</span></n-divider>
@ -63,16 +63,15 @@
<script setup lang="ts">
import { FormInst } from 'naive-ui';
import { useRouter } from 'vue-router';
import { useAuthStore } from '@/store';
import { ref } from 'vue';
import { Icon } from '@iconify/vue';
const router = useRouter();
const authStore = useAuthStore();
const swiperList = ref([
'https://images.unsplash.com/photo-1659991689791-db84493f8544?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=686&q=80',
'https://images.unsplash.com/photo-1599420186946-7b6fb4e297f0?ixlib=rb-1.2.1&ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80',
'https://images.unsplash.com/photo-1657299156568-c94580e20fb6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80',
'https://images.unsplash.com/photo-1659983391845-47ea9d99e7bd?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80',
'https://images.unsplash.com/photo-1546414809-22c82b5e2ad4?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80',
'https://images.unsplash.com/photo-1659533982925-09cb4f3f7876?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1473&q=80',
'https://images.unsplash.com/photo-1630771077377-674b39a13f58?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80',
'https://images.unsplash.com/photo-1543782248-03e2c5a93e18?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1471&q=80',
]);
const formValue = ref({
@ -100,12 +99,12 @@ const rules = {
};
const formRef = ref<FormInst | null>(null);
const validateAll = () => {
const handleLogin = () => {
formRef.value?.validate((errors) => {
if (errors) {
return console.error(errors);
}
router.push('/');
if (errors) return console.error(errors);
const { account, pwd } = formValue.value;
authStore.login(account, pwd);
});
};
</script>

View File

@ -15,7 +15,7 @@
</template>
<script setup lang="ts">
import { fetachGet, fetachPost, fetachDelete, fetachPut, fetachPatch, fetchMock } from '@/service/api';
import { fetachGet, fetachPost, fetachDelete, fetachPut, fetachPatch, fetchMock } from '@/service';
import { ref } from 'vue';
const msg = ref();
const pinter = () => {