mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-04-06 03:57:54 +08:00
feat(projects): 增加登录鉴权
This commit is contained in:
parent
07864134ee
commit
5e9e3d740e
@ -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"]
|
||||
},
|
||||
};
|
||||
|
@ -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
16
mock/utils.ts
Normal 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
5
src/enum/common.ts
Normal file
@ -0,0 +1,5 @@
|
||||
/* 缓存的Key值 */
|
||||
export enum EnumStorageKey {
|
||||
userInfo = '__USER_INFO__',
|
||||
token = '__TOKEN__',
|
||||
}
|
1
src/enum/index.ts
Normal file
1
src/enum/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './common';
|
@ -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>
|
||||
|
@ -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 }}
|
||||
|
@ -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) => {});
|
||||
|
@ -41,7 +41,7 @@ const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
name: 'login',
|
||||
component: () => import('@/views/login/index.vue'), // 注意这里要带上 文件后缀.vue
|
||||
},
|
||||
{
|
||||
|
@ -1 +0,0 @@
|
||||
export * from './login';
|
@ -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
30
src/service/api/test.ts
Normal 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
2
src/service/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './api/test';
|
||||
export * from './api/login';
|
@ -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
7
src/types/api.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/* 接口类型数据 */
|
||||
|
||||
/** 后端返回的用户相关类型 */
|
||||
declare namespace ApiAuth {
|
||||
/** 返回的用户信息 */
|
||||
type UserInfo = Auth.UserInfo;
|
||||
}
|
31
src/types/business.d.ts
vendored
Normal file
31
src/types/business.d.ts
vendored
Normal 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
34
src/utils/auth.ts
Normal 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();
|
||||
}
|
@ -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>
|
||||
|
@ -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 = () => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user