mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-05-21 08:19:16 +08:00
feat(derective): 增加权限指令
This commit is contained in:
parent
ac0666b825
commit
dd408611de
@ -1,17 +1,36 @@
|
||||
import Mock from 'mockjs';
|
||||
import { resultSuccess } from '../utils';
|
||||
import { resultSuccess, resultFailed } from '../utils';
|
||||
|
||||
const Random = Mock.Random;
|
||||
|
||||
const token = () => Random.string('upper', 32, 32);
|
||||
|
||||
const userInfo = {
|
||||
const userData = [
|
||||
{
|
||||
userId: 1,
|
||||
userName: 'iamsee',
|
||||
realName: '管理员大人',
|
||||
userName: 'super',
|
||||
password: '123456',
|
||||
nickName: '超级管理员大人',
|
||||
avatar: 'https://z3.ax1x.com/2021/10/29/5jnWgf.jpg',
|
||||
role: "super",
|
||||
};
|
||||
role: 'super',
|
||||
},
|
||||
{
|
||||
userId: 2,
|
||||
userName: 'admin',
|
||||
password: '123456',
|
||||
nickName: '管理员大人',
|
||||
avatar: 'https://z3.ax1x.com/2021/10/29/5jnWgf.jpg',
|
||||
role: 'admin',
|
||||
},
|
||||
{
|
||||
userId: 3,
|
||||
userName: 'user',
|
||||
password: '123456',
|
||||
nickName: '用户大人',
|
||||
avatar: 'https://z3.ax1x.com/2021/10/29/5jnWgf.jpg',
|
||||
role: 'user',
|
||||
},
|
||||
];
|
||||
const userRoutes = [
|
||||
{
|
||||
name: 'dashboard',
|
||||
@ -312,7 +331,7 @@ const userRoutes = [
|
||||
icon: 'icon-park-outline:wrong-user',
|
||||
},
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'error',
|
||||
@ -322,7 +341,6 @@ const userRoutes = [
|
||||
title: '异常页',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-park-outline:error-computer',
|
||||
|
||||
},
|
||||
children: [
|
||||
{
|
||||
@ -425,15 +443,32 @@ const userRoutes = [
|
||||
export default [
|
||||
{
|
||||
url: '/mock/login',
|
||||
timeout: 1000,
|
||||
method: 'post',
|
||||
response: () => {
|
||||
return resultSuccess({ token: token(), refreshToken: token() });
|
||||
response: (options: any) => {
|
||||
const { userName = undefined, password = undefined } = options.body;
|
||||
|
||||
if (!userName || !password) {
|
||||
return resultFailed(null, '账号密码不全');
|
||||
}
|
||||
|
||||
const userInfo = userData.find((item) => item.userName === userName && item.password === password);
|
||||
|
||||
if (userInfo) {
|
||||
return {
|
||||
code: 200,
|
||||
message: 'ok',
|
||||
data: {
|
||||
userId: userInfo.userId,
|
||||
token: token(),
|
||||
refreshToken: token(),
|
||||
},
|
||||
};
|
||||
}
|
||||
return resultFailed(null, '账号密码错误');
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/mock/updateToken',
|
||||
timeout: 1000,
|
||||
method: 'post',
|
||||
response: () => {
|
||||
return resultSuccess({ token: token(), refreshToken: token() });
|
||||
@ -441,15 +476,21 @@ export default [
|
||||
},
|
||||
{
|
||||
url: '/mock/getUserInfo',
|
||||
timeout: 1000,
|
||||
method: 'get',
|
||||
response: () => {
|
||||
response: (options: any) => {
|
||||
const { userId = undefined } = options.query;
|
||||
if (!userId) {
|
||||
return resultFailed(null, '未传入用户id!');
|
||||
}
|
||||
const userInfo = userData.find((item) => item.userId == userId);
|
||||
if (userInfo) {
|
||||
return resultSuccess(userInfo);
|
||||
}
|
||||
return resultFailed(null, '未找到用户信息,请检查提交参数');
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/mock/getUserRoutes',
|
||||
timeout: 1000,
|
||||
method: 'post',
|
||||
response: () => {
|
||||
return resultSuccess(userRoutes);
|
||||
|
@ -1,16 +1,16 @@
|
||||
import Mock from 'mockjs';
|
||||
|
||||
export function resultSuccess(data: any, { msg = 'success' } = {}) {
|
||||
export function resultSuccess(data: any, msg?:string ) {
|
||||
return Mock.mock({
|
||||
code: 200,
|
||||
data,
|
||||
msg,
|
||||
msg: msg || 'success',
|
||||
});
|
||||
}
|
||||
export function resultFailed(data: any, { msg = 'failed' } = {}) {
|
||||
export function resultFailed(data: any, msg?: string ) {
|
||||
return Mock.mock({
|
||||
code: 400,
|
||||
code: 500,
|
||||
data,
|
||||
msg,
|
||||
msg: msg || 'failed',
|
||||
});
|
||||
}
|
||||
|
6
src/directive/index.ts
Normal file
6
src/directive/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import type { App } from 'vue';
|
||||
import { setupPermission } from './permission'
|
||||
|
||||
export function setupDirectives(app: App) {
|
||||
setupPermission(app);
|
||||
}
|
26
src/directive/permission.ts
Normal file
26
src/directive/permission.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import type { App, Directive } from 'vue';
|
||||
import { usePermission } from '@/hooks';
|
||||
|
||||
|
||||
export function setupPermission(app: App) {
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
function updatapermission(el: HTMLElement, permission: Auth.RoleType | Auth.RoleType[]) {
|
||||
if (!permission) {
|
||||
throw new Error(`v-permissson Directive with no explicit role attached`);
|
||||
}
|
||||
if (!hasPermission(permission)) {
|
||||
el.parentElement?.removeChild(el);
|
||||
}
|
||||
}
|
||||
|
||||
const permissionDirective: Directive<HTMLElement, Auth.RoleType | Auth.RoleType[]> = {
|
||||
mounted(el, binding) {
|
||||
updatapermission(el, binding.value)
|
||||
},
|
||||
updated(el, binding) {
|
||||
updatapermission(el, binding.value)
|
||||
}
|
||||
}
|
||||
app.directive('permission', permissionDirective)
|
||||
}
|
@ -34,6 +34,7 @@ export function usePermission() {
|
||||
|
||||
if (!permission) return true
|
||||
|
||||
if (!authStore.userInfo) return false
|
||||
const { role } = authStore.userInfo
|
||||
|
||||
let has = role === 'super';
|
||||
|
@ -10,7 +10,7 @@
|
||||
size="large"
|
||||
:src="userInfo?.avatar"
|
||||
/>
|
||||
{{ userInfo?.realName }}
|
||||
{{ userInfo?.nickName }}
|
||||
</HeaderButton>
|
||||
</n-dropdown>
|
||||
</template>
|
||||
@ -42,7 +42,15 @@ const { userInfo, resetAuthStore } = useAuthStore();
|
||||
];
|
||||
const handleSelect = (key: string | number) => {
|
||||
if (key === 'loginOut') {
|
||||
window.$dialog.info({
|
||||
title: '退出登录',
|
||||
content: '确认退出当前账号?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
resetAuthStore();
|
||||
},
|
||||
})
|
||||
}
|
||||
if (key === 'userCenter') {
|
||||
router.push('/userCenter')
|
||||
|
@ -4,6 +4,7 @@ import AppLoading from './components/common/appLoading.vue';
|
||||
import { setupRouter } from './router';
|
||||
import { setupAssets } from './plugins';
|
||||
import { setupStore } from './store';
|
||||
import { setupDirectives } from './directive'
|
||||
|
||||
async function setupApp() {
|
||||
// 引入静态资源
|
||||
@ -16,6 +17,8 @@ async function setupApp() {
|
||||
const app = createApp(App);
|
||||
// 安装pinia全局状态库
|
||||
setupStore(app);
|
||||
// 安装自定义指令
|
||||
setupDirectives(app)
|
||||
// 安装router
|
||||
await setupRouter(app);
|
||||
// 挂载
|
||||
|
@ -11,8 +11,8 @@ export function fetchLogin(params: Ilogin) {
|
||||
export function fetchUpdateToken(params: any) {
|
||||
return mockRequest.post<ApiAuth.loginToken>('/updateToken', params);
|
||||
}
|
||||
export function fetchUserInfo() {
|
||||
return mockRequest.get<Auth.UserInfo>('/getUserInfo');
|
||||
export function fetchUserInfo(params:any) {
|
||||
return mockRequest.get<Auth.UserInfo>('/getUserInfo',{params});
|
||||
}
|
||||
export function fetchUserRoutes(params: { userId: number }) {
|
||||
return mockRequest.post<any>('/getUserRoutes', params);
|
||||
|
@ -6,11 +6,19 @@ import { unref } from 'vue';
|
||||
import { useRouteStore } from './route';
|
||||
import { local } from '@/utils';
|
||||
|
||||
const emptyInfo: Auth.UserInfo = {
|
||||
userId: 0,
|
||||
userName: '',
|
||||
nickName: '',
|
||||
avatar: '',
|
||||
role: 'user',
|
||||
};
|
||||
export const useAuthStore = defineStore('auth-store', {
|
||||
state: () => {
|
||||
return {
|
||||
userInfo: local.get('userInfo'),
|
||||
token: local.get('token'),
|
||||
userInfo: local.get('userInfo') || emptyInfo,
|
||||
token: local.get('token') || '',
|
||||
refreshToken: local.get('refreshToken') || '',
|
||||
loginLoading: false,
|
||||
};
|
||||
},
|
||||
@ -44,7 +52,11 @@ export const useAuthStore = defineStore('auth-store', {
|
||||
/* 用户登录 */
|
||||
async login(userName: string, password: string) {
|
||||
this.loginLoading = true;
|
||||
const { data } = await fetchLogin({ userName, password });
|
||||
const { error, data } = await fetchLogin({ userName, password });
|
||||
if (error) {
|
||||
this.loginLoading = false;
|
||||
return;
|
||||
}
|
||||
// 处理登录信息
|
||||
await this.handleAfterLogin(data);
|
||||
|
||||
@ -69,41 +81,37 @@ export const useAuthStore = defineStore('auth-store', {
|
||||
// 触发用户提示
|
||||
window.$notification?.success({
|
||||
title: '登录成功!',
|
||||
content: `欢迎回来😊,${this.userInfo?.realName}!`,
|
||||
content: `欢迎回来😊,${this.userInfo.nickName}!`,
|
||||
duration: 3000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 如果不成功则重置存储
|
||||
this.resetAuthStore();
|
||||
// 登录失败提示
|
||||
window.$notification?.error({
|
||||
title: '登录失败!',
|
||||
content: `验证失败,请检查账号密码`,
|
||||
duration: 3000,
|
||||
});
|
||||
},
|
||||
|
||||
/* 缓存用户信息 */
|
||||
async catchUserInfo(userToken: ApiAuth.loginToken) {
|
||||
let catchSuccess = false;
|
||||
// 先存储token
|
||||
const { token, refreshToken } = userToken;
|
||||
local.set('token', token);
|
||||
local.set('refreshToken', refreshToken,)
|
||||
|
||||
// 请求/存储用户信息
|
||||
const { data } = await fetchUserInfo();
|
||||
if (data) {
|
||||
local.set('userInfo', data);
|
||||
const { token, refreshToken, userId } = userToken;
|
||||
const { error, data } = await fetchUserInfo({ userId });
|
||||
if (error) {
|
||||
return catchSuccess;
|
||||
}
|
||||
// 再将token和userInfo初始化
|
||||
this.userInfo = data;
|
||||
// 先存储token
|
||||
local.set('token', token);
|
||||
local.set('refreshToken', refreshToken);
|
||||
this.token = token;
|
||||
|
||||
this.refreshToken = refreshToken;
|
||||
// 请求/存储用户信息
|
||||
local.set('userInfo', data);
|
||||
this.userInfo = data;
|
||||
catchSuccess = true;
|
||||
|
||||
return catchSuccess;
|
||||
},
|
||||
toggleUserRole(role: Auth.RoleType) {
|
||||
this.login(role, '123456');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
1
src/typings/api.d.ts
vendored
1
src/typings/api.d.ts
vendored
@ -8,6 +8,7 @@ declare namespace ApiAuth {
|
||||
interface loginToken {
|
||||
token: string;
|
||||
refreshToken: string;
|
||||
userId: number;
|
||||
}
|
||||
}
|
||||
declare namespace CommonList {
|
||||
|
2
src/typings/business.d.ts
vendored
2
src/typings/business.d.ts
vendored
@ -8,7 +8,7 @@ declare namespace Auth {
|
||||
/** 用户名 */
|
||||
userName: string;
|
||||
/* 用户称呼 */
|
||||
realName: string;
|
||||
nickName: string;
|
||||
/* 用户头像 */
|
||||
avatar: string;
|
||||
/** 用户角色类型 */
|
||||
|
9
src/typings/storage.d.ts
vendored
9
src/typings/storage.d.ts
vendored
@ -5,9 +5,10 @@ declare namespace Storage {
|
||||
}
|
||||
|
||||
interface Local {
|
||||
userInfo: Auth.UserInfo
|
||||
token: string
|
||||
refreshToken: string
|
||||
tabsRoutes: string
|
||||
userInfo: Auth.UserInfo;
|
||||
token: string;
|
||||
refreshToken: string;
|
||||
tabsRoutes: string;
|
||||
login_account:any;
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
/>
|
||||
<div class="pl-12px">
|
||||
<h3 class="text-18px font-semibold">
|
||||
您好,{{ userInfo?.realName }},今天又是充满活力的一天!
|
||||
您好,{{ userInfo?.nickName }},今天又是充满活力的一天!
|
||||
</h3>
|
||||
<p class="leading-30px text-[#999]">
|
||||
今日多云转晴,20℃ - 25℃!
|
||||
|
@ -134,8 +134,8 @@ const rules = {
|
||||
},
|
||||
};
|
||||
const formValue = ref({
|
||||
account: 'admin',
|
||||
pwd: '000000',
|
||||
account: 'super',
|
||||
pwd: '123456',
|
||||
code: '1234',
|
||||
});
|
||||
const isRemember = ref(false);
|
||||
|
@ -2,13 +2,50 @@
|
||||
<div>
|
||||
权限示例:
|
||||
<n-h1> 当前权限:{{ role }}</n-h1>
|
||||
<n-button-group>
|
||||
<n-button
|
||||
v-for="item in roleList"
|
||||
:key="item"
|
||||
type="default"
|
||||
@click="authStore.toggleUserRole(item)"
|
||||
>
|
||||
{{ item }}
|
||||
</n-button>
|
||||
</n-button-group>
|
||||
<n-h2>v-permission 指令用法</n-h2>
|
||||
<n-space>
|
||||
<n-button v-permission="'super'">
|
||||
仅super可见
|
||||
</n-button>
|
||||
<n-button v-permission="['admin']">
|
||||
admin可见
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
<n-h2>usePermission 函数用法</n-h2>
|
||||
<n-space>
|
||||
<n-button v-if="hasPermission('super')">
|
||||
super可见
|
||||
</n-button>
|
||||
<n-button v-if="hasPermission('admin')">
|
||||
admin可见
|
||||
</n-button>
|
||||
<n-button v-if="hasPermission(['admin', 'user'])">
|
||||
admin和user可见
|
||||
</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from '@/store';
|
||||
const authStore = useAuthStore();
|
||||
const { role } = authStore.userInfo;
|
||||
import { useAuthStore } from '@/store';
|
||||
import { usePermission } from '@/hooks';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const { hasPermission } = usePermission();
|
||||
const { role } = authStore.userInfo;
|
||||
|
||||
const roleList: Auth.RoleType[] = ['super', 'admin', 'user'];
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
@ -24,7 +24,7 @@
|
||||
{{ userInfo?.userName }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="真实名称">
|
||||
{{ userInfo?.realName }}
|
||||
{{ userInfo?.nickName }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="角色">
|
||||
{{ userInfo?.role }}
|
||||
|
Loading…
x
Reference in New Issue
Block a user