Skip to content

Commit

Permalink
feat(projects): add auth example
Browse files Browse the repository at this point in the history
  • Loading branch information
honghuangdc committed Mar 24, 2024
1 parent 41e8bc4 commit c11d56d
Show file tree
Hide file tree
Showing 20 changed files with 269 additions and 22 deletions.
5 changes: 4 additions & 1 deletion .env
Expand Up @@ -12,7 +12,7 @@ VITE_ICON_PREFIX=icon
VITE_ICON_LOCAL_PREFIX=icon-local

# auth route mode: static | dynamic
VITE_AUTH_ROUTE_MODE=static
VITE_AUTH_ROUTE_MODE=dynamic

# static auth route home
VITE_ROUTE_HOME=home
Expand All @@ -37,3 +37,6 @@ VITE_SERVICE_MODAL_LOGOUT_CODES=7777,7778

# token expired codes of backend service, when the code is received, it will refresh the token and resend the request
VITE_SERVICE_EXPIRED_TOKEN_CODES=9999,9998

# when the route mode is static, the defined super role
VITE_STATIC_SUPER_ROLE=R_SUPER
7 changes: 4 additions & 3 deletions src/components/common/exception-base.vue
@@ -1,6 +1,7 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { $t } from '@/locales';
import { useRouterPush } from '@/hooks/common/router';
defineOptions({ name: 'ExceptionBase' });
Expand All @@ -19,6 +20,8 @@ interface Props {
const props = defineProps<Props>();
const { routerPushByKey } = useRouterPush();
const iconMap: Record<ExceptionType, string> = {
'403': 'no-permission',
'404': 'not-found',
Expand All @@ -33,9 +36,7 @@ const icon = computed(() => iconMap[props.type]);
<div class="flex text-400px text-primary">
<SvgIcon :local-icon="icon" />
</div>
<RouterLink to="/">
<NButton type="primary">{{ $t('common.backToHome') }}</NButton>
</RouterLink>
<NButton type="primary" @click="routerPushByKey('root')">{{ $t('common.backToHome') }}</NButton>
</div>
</template>

Expand Down
21 changes: 21 additions & 0 deletions src/hooks/business/auth.ts
@@ -0,0 +1,21 @@
import { useAuthStore } from '@/store/modules/auth';

export function useAuth() {
const authStore = useAuthStore();

function hasAuth(codes: string | string[]) {
if (!authStore.isLogin) {
return false;
}

if (typeof codes === 'string') {
return authStore.userInfo.buttons.includes(codes);
}

return codes.some(code => authStore.userInfo.buttons.includes(code));
}

return {
hasAuth
};
}
15 changes: 12 additions & 3 deletions src/locales/langs/en-us.ts
Expand Up @@ -148,6 +148,8 @@ const local: App.I18n.Schema = {
'function_hide-child_two': 'Two',
'function_hide-child_three': 'Three',
function_request: 'Request',
'function_toggle-auth': 'Toggle Auth',
'function_super-page': 'Super Admin Visible',
manage: 'System Manage',
manage_user: 'User Manage',
'manage_user-detail': 'User Detail',
Expand Down Expand Up @@ -187,9 +189,9 @@ const local: App.I18n.Schema = {
register: 'Register',
otherAccountLogin: 'Other Account Login',
otherLoginMode: 'Other Login Mode',
superAdmin: 'Super Administrator',
admin: 'Administrator',
user: 'Ordinary User'
superAdmin: 'Super Admin',
admin: 'Admin',
user: 'User'
},
codeLogin: {
title: 'Verification Code Login',
Expand Down Expand Up @@ -275,6 +277,13 @@ const local: App.I18n.Schema = {
multiTab: {
routeParam: 'Route Param',
backTab: 'Back function_tab'
},
toggleAuth: {
toggleAccount: 'Toggle Account',
authHook: 'Auth Hook Function `hasAuth`',
superAdminVisible: 'Super Admin Visible',
adminVisible: 'Admin Visible',
adminOrUserVisible: 'Admin and User Visible'
}
},
manage: {
Expand Down
9 changes: 9 additions & 0 deletions src/locales/langs/zh-cn.ts
Expand Up @@ -148,6 +148,8 @@ const local: App.I18n.Schema = {
'function_hide-child_two': '菜单二',
'function_hide-child_three': '菜单三',
function_request: '请求',
'function_toggle-auth': '切换权限',
'function_super-page': '超级管理员可见',
manage: '系统管理',
manage_user: '用户管理',
'manage_user-detail': '用户详情',
Expand Down Expand Up @@ -275,6 +277,13 @@ const local: App.I18n.Schema = {
multiTab: {
routeParam: '路由参数',
backTab: '返回 function_tab'
},
toggleAuth: {
toggleAccount: '切换账号',
authHook: '权限钩子函数 `hasAuth`',
superAdminVisible: '超级管理员可见',
adminVisible: '管理员可见',
adminOrUserVisible: '管理员和用户可见'
}
},
manage: {
Expand Down
2 changes: 2 additions & 0 deletions src/router/elegant/imports.ts
Expand Up @@ -25,7 +25,9 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
"function_hide-child_two": () => import("@/views/function/hide-child/two/index.vue"),
"function_multi-tab": () => import("@/views/function/multi-tab/index.vue"),
function_request: () => import("@/views/function/request/index.vue"),
"function_super-page": () => import("@/views/function/super-page/index.vue"),
function_tab: () => import("@/views/function/tab/index.vue"),
"function_toggle-auth": () => import("@/views/function/toggle-auth/index.vue"),
home: () => import("@/views/home/index.vue"),
manage_menu: () => import("@/views/manage/menu/index.vue"),
manage_role: () => import("@/views/manage/role/index.vue"),
Expand Down
32 changes: 29 additions & 3 deletions src/router/elegant/routes.ts
Expand Up @@ -64,7 +64,8 @@ export const generatedRoutes: GeneratedRoute[] = [
meta: {
title: 'function_hide-child',
i18nKey: 'route.function_hide-child',
icon: 'material-symbols:filter-list-off'
icon: 'material-symbols:filter-list-off',
order: 2
},
redirect: '/function/hide-child/one',
children: [
Expand Down Expand Up @@ -124,7 +125,19 @@ export const generatedRoutes: GeneratedRoute[] = [
meta: {
title: 'function_request',
i18nKey: 'route.function_request',
icon: 'carbon:network-overlay'
icon: 'carbon:network-overlay',
order: 3
}
},
{
name: 'function_super-page',
path: '/function/super-page',
component: 'view.function_super-page',
meta: {
title: 'function_super-page',
i18nKey: 'route.function_super-page',
icon: 'ic:round-supervisor-account',
order: 5
}
},
{
Expand All @@ -134,7 +147,20 @@ export const generatedRoutes: GeneratedRoute[] = [
meta: {
title: 'function_tab',
i18nKey: 'route.function_tab',
icon: 'ic:round-tab'
icon: 'ic:round-tab',
order: 1
}
},
{
name: 'function_toggle-auth',
path: '/function/toggle-auth',
component: 'view.function_toggle-auth',
meta: {
title: 'function_toggle-auth',
i18nKey: 'route.function_toggle-auth',
icon: 'ic:round-construction',
order: 4,
roles: ['R_SUPER']
}
}
]
Expand Down
2 changes: 2 additions & 0 deletions src/router/elegant/transform.ts
Expand Up @@ -158,7 +158,9 @@ const routeMap: RouteMap = {
"function_hide-child_two": "/function/hide-child/two",
"function_multi-tab": "/function/multi-tab",
"function_request": "/function/request",
"function_super-page": "/function/super-page",
"function_tab": "/function/tab",
"function_toggle-auth": "/function/toggle-auth",
"home": "/home",
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
"manage": "/manage",
Expand Down
7 changes: 2 additions & 5 deletions src/router/guard/permission.ts
Expand Up @@ -27,13 +27,10 @@ export function createPermissionGuard(router: Router) {

// check whether the user has permission to access the route
// 1. if the route's "roles" is empty, then it is allowed to access
// 2. if the user is super admin, then it is allowed to access
// 2. if the user is super admin in static route, then it is allowed to access
// 3. if the user's role is included in the route's "roles", then it is allowed to access
const SUPER_ADMIN = 'R_SUPER';
const hasPermission =
!routeRoles.length ||
authStore.userInfo.roles.includes(SUPER_ADMIN) ||
authStore.userInfo.roles.some(role => routeRoles.includes(role));
!routeRoles.length || authStore.isStaticSuper || authStore.userInfo.roles.some(role => routeRoles.includes(role));

const strategicPatterns: CommonType.StrategicPattern[] = [
// 1. if it is login route when logged in, change to the root page
Expand Down
15 changes: 13 additions & 2 deletions src/store/modules/auth/index.ts
Expand Up @@ -18,6 +18,13 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {

const userInfo: Api.Auth.UserInfo = reactive(getUserInfo());

/** is super role in static route */
const isStaticSuper = computed(() => {
const { VITE_AUTH_ROUTE_MODE, VITE_STATIC_SUPER_ROLE } = import.meta.env;

return VITE_AUTH_ROUTE_MODE === 'static' && userInfo.roles.includes(VITE_STATIC_SUPER_ROLE);
});

/** Is login */
const isLogin = computed(() => Boolean(token.value));

Expand All @@ -41,8 +48,9 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
*
* @param userName User name
* @param password Password
* @param [redirect=true] Whether to redirect after login. Default is `true`
*/
async function login(userName: string, password: string) {
async function login(userName: string, password: string, redirect = true) {
startLoading();

const { data: loginToken, error } = await fetchLogin(userName, password);
Expand All @@ -53,7 +61,9 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
if (pass) {
await routeStore.initAuthRoute();

await redirectFromLogin();
if (redirect) {
await redirectFromLogin();
}

if (routeStore.isInitAuthRoute) {
window.$notification?.success({
Expand Down Expand Up @@ -94,6 +104,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
return {
token,
userInfo,
isStaticSuper,
isLogin,
loginLoading,
resetStore,
Expand Down
8 changes: 7 additions & 1 deletion src/store/modules/auth/shared.ts
Expand Up @@ -10,10 +10,16 @@ export function getUserInfo() {
const emptyInfo: Api.Auth.UserInfo = {
userId: '',
userName: '',
roles: []
roles: [],
buttons: []
};
const userInfo = localStg.get('userInfo') || emptyInfo;

// fix new property: buttons, this will be removed in the next version `1.1.0`
if (!userInfo.buttons) {
userInfo.buttons = [];
}

return userInfo;
}

Expand Down
2 changes: 2 additions & 0 deletions src/store/modules/route/index.ts
Expand Up @@ -194,6 +194,8 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {

const vueRoutes = getAuthVueRoutes(sortRoutes);

resetVueRoutes();

addRoutesToVueRouter(vueRoutes);

getGlobalMenus(sortRoutes);
Expand Down
7 changes: 3 additions & 4 deletions src/store/modules/route/shared.ts
Expand Up @@ -10,6 +10,7 @@ import { useSvgIcon } from '@/hooks/common/icon';
* @param roles Roles
*/
export function filterAuthRoutesByRoles(routes: ElegantConstRoute[], roles: string[]) {
// in static mode of auth route, the super admin role is defined in front-end
const SUPER_ROLE = 'R_SUPER';

// if the user is super admin, then it is allowed to access all routes
Expand All @@ -30,9 +31,7 @@ function filterAuthRouteByRoles(route: ElegantConstRoute, roles: string[]) {
const routeRoles = (route.meta && route.meta.roles) || [];

// if the route's "roles" is empty, then it is allowed to access
if (!routeRoles.length) {
return [route];
}
const isEmptyRoles = !routeRoles.length;

// if the user's role is included in the route's "roles", then it is allowed to access
const hasPermission = routeRoles.some(role => roles.includes(role));
Expand All @@ -43,7 +42,7 @@ function filterAuthRouteByRoles(route: ElegantConstRoute, roles: string[]) {
filterRoute.children = filterRoute.children.flatMap(item => filterAuthRouteByRoles(item, roles));
}

return hasPermission ? [filterRoute] : [];
return hasPermission || isEmptyRoles ? [filterRoute] : [];
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/typings/api.d.ts
Expand Up @@ -60,6 +60,7 @@ declare namespace Api {
userId: string;
userName: string;
roles: string[];
buttons: string[];
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/typings/app.d.ts
Expand Up @@ -458,6 +458,13 @@ declare namespace App {
routeParam: string;
backTab: string;
};
toggleAuth: {
toggleAccount: string;
authHook: string;
superAdminVisible: string;
adminVisible: string;
adminOrUserVisible: string;
};
};
manage: {
common: {
Expand Down
4 changes: 4 additions & 0 deletions src/typings/elegant-router.d.ts
Expand Up @@ -32,7 +32,9 @@ declare module "@elegant-router/types" {
"function_hide-child_two": "/function/hide-child/two";
"function_multi-tab": "/function/multi-tab";
"function_request": "/function/request";
"function_super-page": "/function/super-page";
"function_tab": "/function/tab";
"function_toggle-auth": "/function/toggle-auth";
"home": "/home";
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
"manage": "/manage";
Expand Down Expand Up @@ -119,7 +121,9 @@ declare module "@elegant-router/types" {
| "function_hide-child_two"
| "function_multi-tab"
| "function_request"
| "function_super-page"
| "function_tab"
| "function_toggle-auth"
| "home"
| "manage_menu"
| "manage_role"
Expand Down
2 changes: 2 additions & 0 deletions src/typings/env.d.ts
Expand Up @@ -57,6 +57,8 @@ declare namespace Env {
* use "," to separate multiple codes
*/
readonly VITE_SERVICE_EXPIRED_TOKEN_CODES: string;
/** when the route mode is static, the defined super role */
readonly VITE_STATIC_SUPER_ROLE: string;
/**
* other backend service base url
*
Expand Down

0 comments on commit c11d56d

Please sign in to comment.