feat: 对接登录和命名空间规范接口

This commit is contained in:
xlsea 2024-03-21 11:53:27 +08:00
parent 31bf70fa73
commit 6b1cf17428
19 changed files with 104 additions and 51 deletions

View File

@ -1,5 +1,5 @@
# backend service base url, test environment
VITE_SERVICE_BASE_URL=https://mock.apifox.com/m1/3109515-0-default
VITE_SERVICE_BASE_URL=http://preview.easyretry.com/easy-retry
# other backend service base url, test environment
VITE_OTHER_SERVICE_BASE_URL= `{

View File

@ -29,6 +29,7 @@ function createProxyItem(item: App.Service.ServiceConfigItem) {
proxy[item.proxyPattern] = {
target: item.baseURL,
changeOrigin: true,
ws: false,
rewrite: path => path.replace(new RegExp(`^${item.proxyPattern}`), '')
};

View File

@ -58,6 +58,7 @@
"naive-ui": "2.38.1",
"nprogress": "0.2.0",
"pinia": "2.1.7",
"ts-md5": "^1.3.1",
"vue": "3.4.21",
"vue-draggable-plus": "0.3.5",
"vue-i18n": "9.10.2",

View File

@ -53,6 +53,9 @@ importers:
pinia:
specifier: 2.1.7
version: 2.1.7(typescript@5.4.2)(vue@3.4.21)
ts-md5:
specifier: ^1.3.1
version: 1.3.1
vue:
specifier: 3.4.21
version: 3.4.21(typescript@5.4.2)
@ -7330,6 +7333,11 @@ packages:
typescript: 5.4.2
dev: true
/ts-md5@1.3.1:
resolution: {integrity: sha512-DiwiXfwvcTeZ5wCE0z+2A9EseZsztaiZtGrtSaY5JOD7ekPnR/GoIVD5gXZAlK9Na9Kvpo9Waz5rW64WKAWApg==}
engines: {node: '>=12'}
dev: false
/tslib@2.3.0:
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
dev: false

View File

@ -82,7 +82,7 @@ function handleDropdown(key: DropdownKey) {
<div>
<ButtonIcon>
<SvgIcon icon="ph:user-circle" class="text-icon-large" />
<span class="text-16px font-medium">{{ authStore.userInfo.userName }}</span>
<span class="text-16px font-medium">{{ authStore.userInfo.username }}</span>
</ButtonIcon>
</div>
</NDropdown>

View File

@ -8,6 +8,7 @@ import HorizontalMenu from '../global-menu/base-menu.vue';
import GlobalLogo from '../global-logo/index.vue';
import GlobalBreadcrumb from '../global-breadcrumb/index.vue';
import GlobalSearch from '../global-search/index.vue';
import NamespaceSelect from '../namespace-select/index.vue';
import { useMixMenuContext } from '../../hooks/use-mix-menu';
import ThemeButton from './components/theme-button.vue';
import UserAvatar from './components/user-avatar.vue';
@ -55,6 +56,7 @@ const headerMenus = computed(() => {
<GlobalBreadcrumb v-if="!appStore.isMobile" class="ml-12px" />
</div>
<div class="h-full flex-y-center justify-end">
<NamespaceSelect />
<GlobalSearch />
<FullScreen v-if="!appStore.isMobile" :full="isFullscreen" @click="toggle" />
<LangSwitch :lang="appStore.locale" :lang-options="appStore.localeOptions" @change-lang="appStore.changeLocale" />

View File

@ -0,0 +1,37 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { localStg } from '@/utils/storage';
const router = useRouter();
const namespaceId = ref<string>(localStg.get('namespaceId')!);
const userInfo = localStg.get('userInfo');
const options = ref(
userInfo?.namespaceIds.map(item => {
return { label: item.name, value: item.uniqueId };
})
);
const onChange = (value: string) => {
localStg.set('namespaceId', value);
setTimeout(() => {
router.go(0);
}, 500);
};
</script>
<template>
<NSelect v-model:value="namespaceId" class="namespace-select" :options="options" @update:value="onChange" />
</template>
<style lang="scss" scoped>
.namespace-select {
width: 150px;
:deep(.n-base-selection) {
border-radius: 32px !important;
}
}
</style>

View File

@ -30,10 +30,7 @@ export function createPermissionGuard(router: Router) {
// 2. if the user is super admin, 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));
const hasPermission = !routeRoles.length || authStore.userInfo.role === SUPER_ADMIN;
const strategicPatterns: CommonType.StrategicPattern[] = [
// 1. if it is login route when logged in, change to the root page

View File

@ -3,15 +3,15 @@ import { request } from '../request';
/**
* Login
*
* @param userName User name
* @param username User name
* @param password Password
*/
export function fetchLogin(userName: string, password: string) {
export function fetchLogin(username: string, password: string) {
return request<Api.Auth.LoginToken>({
url: '/auth/login',
method: 'post',
data: {
userName,
username,
password
}
});
@ -19,7 +19,7 @@ export function fetchLogin(userName: string, password: string) {
/** Get user info */
export function fetchGetUserInfo() {
return request<Api.Auth.UserInfo>({ url: '/auth/getUserInfo' });
return request<Api.Auth.UserInfo>({ url: '/user/info' });
}
/**

View File

@ -8,9 +8,7 @@ const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy
export const request = createFlatRequest<App.Service.Response>(
{
baseURL,
headers: {
apifoxToken: 'XL299LiMEDZ0H5h3A29PxwQXdMJqWyY2'
}
timeout: 6000
},
{
async onRequest(config) {
@ -18,15 +16,18 @@ export const request = createFlatRequest<App.Service.Response>(
// set token
const token = localStg.get('token');
const Authorization = token ? `Bearer ${token}` : null;
Object.assign(headers, { Authorization });
const namespaceId = localStg.get('namespaceId');
// const Authorization = token ? `Bearer ${token}` : null;
headers['EASY-RETRY-AUTH'] = token;
headers['EASY-RETRY-NAMESPACE-ID'] = namespaceId;
Object.assign(headers, { 'EASY-RETRY-AUTH': token, 'EASY-RETRY-NAMESPACE-ID': namespaceId });
return config;
},
isBackendSuccess(response) {
// when the backend response code is "0000", it means the request is success
// you can change this logic by yourself
return response.data.code === '0000';
return response.data.status === 1;
},
async onBackendFail(_response) {
// when the backend response code is not "0000", it means the request is fail
@ -42,7 +43,7 @@ export const request = createFlatRequest<App.Service.Response>(
// show backend error message
if (error.code === BACKEND_ERROR_CODE) {
message = error.response?.data?.msg || message;
message = error.response?.data?.message || message;
}
window.$message?.error(message);

View File

@ -58,7 +58,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
if (routeStore.isInitAuthRoute) {
window.$notification?.success({
title: $t('page.login.common.loginSuccess'),
content: $t('page.login.common.welcomeBack', { userName: userInfo.userName }),
content: $t('page.login.common.welcomeBack', { userName: userInfo.username }),
duration: 4500
});
}
@ -73,7 +73,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
async function loginByToken(loginToken: Api.Auth.LoginToken) {
// 1. stored in the localStorage, the later requests need it in headers
localStg.set('token', loginToken.token);
localStg.set('refreshToken', loginToken.refreshToken);
localStg.set('namespaceId', loginToken.namespaceIds[0].uniqueId);
const { data: info, error } = await fetchGetUserInfo();

View File

@ -8,9 +8,11 @@ export function getToken() {
/** Get user info */
export function getUserInfo() {
const emptyInfo: Api.Auth.UserInfo = {
userId: '',
userName: '',
roles: []
id: '',
mode: '',
username: '',
role: '',
namespaceIds: []
};
const userInfo = localStg.get('userInfo') || emptyInfo;
@ -20,6 +22,6 @@ export function getUserInfo() {
/** Clear auth storage */
export function clearAuthStorage() {
localStg.remove('token');
localStg.remove('refreshToken');
localStg.remove('namespaceId');
localStg.remove('userInfo');
}

View File

@ -9,7 +9,7 @@ import { ROOT_ROUTE, createRoutes, getAuthVueRoutes } from '@/router/routes';
import { getRouteName, getRoutePath } from '@/router/elegant/transform';
import { fetchGetUserRoutes, fetchIsRouteExist } from '@/service/api';
import { useAppStore } from '../app';
import { useAuthStore } from '../auth';
// import { useAuthStore } from '../auth';
import { useTabStore } from '../tab';
import {
filterAuthRoutesByRoles,
@ -25,7 +25,7 @@ import {
export const useRouteStore = defineStore(SetupStoreId.Route, () => {
const appStore = useAppStore();
const authStore = useAuthStore();
// const authStore = useAuthStore();
const tabStore = useTabStore();
const { bool: isInitAuthRoute, setBool: setIsInitAuthRoute } = useBoolean();
const removeRouteFns: (() => void)[] = [];
@ -160,7 +160,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
async function initStaticAuthRoute() {
const { authRoutes } = createRoutes();
const filteredAuthRoutes = filterAuthRoutesByRoles(authRoutes, authStore.userInfo.roles);
const filteredAuthRoutes = filterAuthRoutesByRoles(authRoutes, []);
handleAuthRoutes(filteredAuthRoutes);

20
src/typings/api.d.ts vendored
View File

@ -52,14 +52,28 @@ declare namespace Api {
*/
namespace Auth {
interface LoginToken {
id: string;
mode: string;
role: String;
token: string;
refreshToken: string;
createDt: string;
updateDt: string;
namespaceIds: NamespaceId[];
}
interface UserInfo {
userId: string;
userName: string;
roles: string[];
id: string;
mode: string;
username: string;
role: string;
namespaceIds: NamespaceId[];
}
interface NamespaceId {
id: string;
name: string;
uniqueId: string;
}
}

View File

@ -631,9 +631,9 @@ declare namespace App {
/** The backend service response data */
type Response<T = unknown> = {
/** The backend service response code */
code: string;
status: number;
/** The backend service response message */
msg: string;
message: string;
/** The backend service response data */
data: T;
};

View File

@ -16,21 +16,14 @@ declare module 'vue' {
FullScreen: typeof import('./../components/common/full-screen.vue')['default']
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default']
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
IconIcRoundDelete: typeof import('~icons/ic/round-delete')['default']
IconIcRoundPlus: typeof import('~icons/ic/round-plus')['default']
IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default']
IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
IconLocalBanner: typeof import('~icons/local/banner')['default']
IconLocalLogo: typeof import('~icons/local/logo')['default']
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
IconMdiDrag: typeof import('~icons/mdi/drag')['default']
IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default']
IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
IconUilSearch: typeof import('~icons/uil/search')['default']
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
LookForward: typeof import('./../components/custom/look-forward.vue')['default']
@ -41,9 +34,6 @@ declare module 'vue' {
NCard: typeof import('naive-ui')['NCard']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NColorPicker: typeof import('naive-ui')['NColorPicker']
NDataTable: typeof import('naive-ui')['NDataTable']
NDescriptions: typeof import('naive-ui')['NDescriptions']
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NDivider: typeof import('naive-ui')['NDivider']
NDrawer: typeof import('naive-ui')['NDrawer']
@ -52,7 +42,6 @@ declare module 'vue' {
NEmpty: typeof import('naive-ui')['NEmpty']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid']
NInput: typeof import('naive-ui')['NInput']
@ -65,10 +54,6 @@ declare module 'vue' {
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
NPopover: typeof import('naive-ui')['NPopover']
NRadio: typeof import('naive-ui')['NRadio']
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect']
NSpace: typeof import('naive-ui')['NSpace']
@ -76,7 +61,6 @@ declare module 'vue' {
NSwitch: typeof import('naive-ui')['NSwitch']
NTab: typeof import('naive-ui')['NTab']
NTabs: typeof import('naive-ui')['NTabs']
NTag: typeof import('naive-ui')['NTag']
NThing: typeof import('naive-ui')['NThing']
NTooltip: typeof import('naive-ui')['NTooltip']
PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']

View File

@ -14,6 +14,8 @@ declare namespace StorageType {
lang: App.I18n.LangType;
/** The token */
token: string;
/** The namespace id */
namespaceId: string;
/** The refresh token */
refreshToken: string;
/** The user info */

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, reactive } from 'vue';
import { Md5 } from 'ts-md5';
import { $t } from '@/locales';
import { loginModuleRecord } from '@/constants/app';
import { useRouterPush } from '@/hooks/common/router';
@ -20,8 +21,8 @@ interface FormModel {
}
const model: FormModel = reactive({
userName: 'Soybean',
password: '123456'
userName: 'admin',
password: '654321'
});
const rules = computed<Record<keyof FormModel, App.Global.FormRule[]>>(() => {
@ -35,7 +36,10 @@ const rules = computed<Record<keyof FormModel, App.Global.FormRule[]>>(() => {
async function handleSubmit() {
await validate();
await authStore.login(model.userName, model.password);
const md5 = new Md5();
md5.appendAsciiStr(model.password);
const password: string = md5.end() as string;
await authStore.login(model.userName, password);
}
</script>

View File

@ -48,7 +48,7 @@ const statisticData = computed<StatisticData[]>(() => [
</div>
<div class="pl-12px">
<h3 class="text-18px font-semibold">
{{ $t('page.home.greeting', { userName: authStore.userInfo.userName }) }}
{{ $t('page.home.greeting', { userName: authStore.userInfo.username }) }}
</h3>
<p class="text-#999 leading-30px">{{ $t('page.home.weatherDesc') }}</p>
</div>