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