feat(derective): 增加权限指令

This commit is contained in:
chen.home 2023-03-26 13:17:41 +08:00
parent ac0666b825
commit dd408611de
16 changed files with 198 additions and 66 deletions

View File

@ -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);

View File

@ -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
View File

@ -0,0 +1,6 @@
import type { App } from 'vue';
import { setupPermission } from './permission'
export function setupDirectives(app: App) {
setupPermission(app);
}

View 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)
}

View File

@ -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';

View File

@ -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')

View File

@ -4,7 +4,8 @@ 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() {
// 引入静态资源 // 引入静态资源
setupAssets(); setupAssets();
@ -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);
// 挂载 // 挂载

View File

@ -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);

View File

@ -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');
},
}, },
}); });

View File

@ -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 {
/* 返回的性别类型 */ /* 返回的性别类型 */

View File

@ -8,7 +8,7 @@ declare namespace Auth {
/** 用户名 */ /** 用户名 */
userName: string; userName: string;
/* 用户称呼 */ /* 用户称呼 */
realName: string; nickName: string;
/* 用户头像 */ /* 用户头像 */
avatar: string; avatar: string;
/** 用户角色类型 */ /** 用户角色类型 */

View File

@ -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;
}
} }

View File

@ -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

View File

@ -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);

View File

@ -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>

View File

@ -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 }}