feat: request && login && router【e6939e22】(login.vue 和 request.ts 增加租户的选择)
							parent
							
								
									3c3886e345
								
							
						
					
					
						commit
						c2358e2132
					
				|  | @ -11,8 +11,6 @@ VITE_DEVTOOLS=false | ||||||
| # 是否注入全局loading | # 是否注入全局loading | ||||||
| VITE_INJECT_APP_LOADING=true | VITE_INJECT_APP_LOADING=true | ||||||
| 
 | 
 | ||||||
| # 默认租户名称 |  | ||||||
| VITE_APP_DEFAULT_TENANT_NAME=芋道源码 |  | ||||||
| # 默认登录用户名 | # 默认登录用户名 | ||||||
| VITE_APP_DEFAULT_USERNAME=admin | VITE_APP_DEFAULT_USERNAME=admin | ||||||
| # 默认登录密码 | # 默认登录密码 | ||||||
|  |  | ||||||
|  | @ -21,6 +21,13 @@ export namespace AuthApi { | ||||||
|     data: string; |     data: string; | ||||||
|     status: number; |     status: number; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   /** 租户信息返回值 */ | ||||||
|  |   export interface TenantResult { | ||||||
|  |     id: number; | ||||||
|  |     name: string; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -34,7 +41,8 @@ export async function loginApi(data: AuthApi.LoginParams) { | ||||||
|  * 刷新 accessToken |  * 刷新 accessToken | ||||||
|  */ |  */ | ||||||
| export async function refreshTokenApi() { | export async function refreshTokenApi() { | ||||||
|   return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', { |   // TODO @芋艿:refreshToken 传递
 | ||||||
|  |   return baseRequestClient.post<AuthApi.RefreshTokenResult>('/system/auth/refresh', { | ||||||
|     withCredentials: true, |     withCredentials: true, | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  | @ -43,7 +51,7 @@ export async function refreshTokenApi() { | ||||||
|  * 退出登录 |  * 退出登录 | ||||||
|  */ |  */ | ||||||
| export async function logoutApi() { | export async function logoutApi() { | ||||||
|   return baseRequestClient.post('/auth/logout', { |   return baseRequestClient.post('/system/auth/logout', { | ||||||
|     withCredentials: true, |     withCredentials: true, | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  | @ -63,3 +71,31 @@ export function getAuthPermissionInfoApi() { | ||||||
|     '/system/auth/get-permission-info', |     '/system/auth/get-permission-info', | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 获取租户列表 | ||||||
|  |  */ | ||||||
|  | export function getTenantSimpleList() { | ||||||
|  |   return requestClient.get<AuthApi.TenantResult[]>( | ||||||
|  |     `/system/tenant/simple-list`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 使用租户域名,获得租户信息 | ||||||
|  |  */ | ||||||
|  | export function getTenantByWebsite(website: string) { | ||||||
|  |   // TODO @芋艿:改成 params 传递?
 | ||||||
|  |   return requestClient.get<AuthApi.TenantResult>(`/system/tenant/get-by-website?website=${website}`); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TODO 芋艿:后续怎么放好。
 | ||||||
|  | // // 获取验证图片 以及token
 | ||||||
|  | // export async function getCaptcha(data: any) {
 | ||||||
|  | //   return baseRequestClient.post('/system/captcha/get', data);
 | ||||||
|  | // }
 | ||||||
|  | //
 | ||||||
|  | // // 滑动或者点选验证
 | ||||||
|  | // export async function checkCaptcha(data: any) {
 | ||||||
|  | //   return baseRequestClient.post('/system/captcha/check', data);
 | ||||||
|  | // }
 | ||||||
|  | @ -19,7 +19,7 @@ import { useAuthStore } from '#/store'; | ||||||
| 
 | 
 | ||||||
| import { refreshTokenApi } from './core'; | import { refreshTokenApi } from './core'; | ||||||
| 
 | 
 | ||||||
| const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); | const { apiURL, tenantEnable } = useAppConfig(import.meta.env, import.meta.env.PROD); | ||||||
| 
 | 
 | ||||||
| function createRequestClient(baseURL: string, options?: RequestClientOptions) { | function createRequestClient(baseURL: string, options?: RequestClientOptions) { | ||||||
|   const client = new RequestClient({ |   const client = new RequestClient({ | ||||||
|  | @ -67,10 +67,7 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) { | ||||||
| 
 | 
 | ||||||
|       config.headers.Authorization = formatToken(accessStore.accessToken); |       config.headers.Authorization = formatToken(accessStore.accessToken); | ||||||
|       config.headers['Accept-Language'] = preferences.app.locale; |       config.headers['Accept-Language'] = preferences.app.locale; | ||||||
|       config.headers['tenant-id'] = 1 |       config.headers['tenant-id'] = tenantEnable ? accessStore.tenantId : undefined; | ||||||
|       // TODO @芋艿:优化一下
 |  | ||||||
|       // config.headers['tenant-id'] =
 |  | ||||||
|       //   tenantEnable && tenantId ? tenantId : undefined;
 |  | ||||||
|       return config; |       return config; | ||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  | @ -1,48 +1,85 @@ | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import type { VbenFormSchema } from '@vben/common-ui'; | import type { VbenFormSchema } from '@vben/common-ui'; | ||||||
| import type { BasicOption } from '@vben/types'; | import type { BasicOption } from '@vben/types'; | ||||||
|  | import type { AuthApi } from '#/api/core/auth'; | ||||||
| 
 | 
 | ||||||
| import { computed, markRaw } from 'vue'; | import { computed, markRaw, onMounted, ref } from 'vue'; | ||||||
| 
 | 
 | ||||||
| import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui'; | import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui'; | ||||||
| import { $t } from '@vben/locales'; | import { $t } from '@vben/locales'; | ||||||
|  | import { useAppConfig } from '@vben/hooks'; | ||||||
| 
 | 
 | ||||||
| import { useAuthStore } from '#/store'; | import { useAuthStore } from '#/store'; | ||||||
|  | import { useAccessStore } from '@vben/stores'; | ||||||
|  | 
 | ||||||
|  | import { getTenantSimpleList, getTenantByWebsite } from '#/api/core/auth'; | ||||||
| 
 | 
 | ||||||
| defineOptions({ name: 'Login' }); | defineOptions({ name: 'Login' }); | ||||||
| 
 | 
 | ||||||
| const authStore = useAuthStore(); | const authStore = useAuthStore(); | ||||||
|  | const accessStore = useAccessStore(); | ||||||
| 
 | 
 | ||||||
| const MOCK_USER_OPTIONS: BasicOption[] = [ | // 租户列表 | ||||||
|   { | const tenantList = ref<AuthApi.TenantResult[]>([]); | ||||||
|     label: 'Super', | // 当前选中的租户编号 | ||||||
|     value: 'vben', | const currentTenantId = ref<null | number>(); | ||||||
|   }, | 
 | ||||||
|   { | // 获取租户列表,并默认选中 | ||||||
|     label: 'Admin', | const fetchTenantList = async () => { | ||||||
|     value: 'admin', |   try { | ||||||
|   }, |     // 获取租户列表、域名对应租户 | ||||||
|   { |     const websiteTenantPromise = getTenantByWebsite(window.location.hostname); | ||||||
|     label: 'User', |     tenantList.value = await getTenantSimpleList(); | ||||||
|     value: 'jack', | 
 | ||||||
|   }, |     // 选中租户:域名 > store 中的租户 > 首个租户 | ||||||
| ]; |     let tenantId: number | null = null; | ||||||
|  |     const websiteTenant = await websiteTenantPromise; | ||||||
|  |     if (websiteTenant?.id) { | ||||||
|  |       tenantId = websiteTenant.id; | ||||||
|  |     } | ||||||
|  |     // 如果没有从域名获取到租户,尝试从 store 中获取 | ||||||
|  |     debugger; | ||||||
|  |     if (!tenantId && accessStore.tenantId) { | ||||||
|  |       tenantId = accessStore.tenantId; | ||||||
|  |     } | ||||||
|  |     // 如果还是没有租户,使用列表中的第一个 | ||||||
|  |     if (!tenantId && tenantList.value?.[0]?.id) { | ||||||
|  |       tenantId = tenantList.value[0].id; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 设置选中的租户编号 | ||||||
|  |     currentTenantId.value = tenantId; | ||||||
|  |     accessStore.setTenantId(tenantId); | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('获取租户列表失败:', error); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // 组件挂载时获取租户信息 | ||||||
|  | onMounted(() => { | ||||||
|  |   fetchTenantList(); | ||||||
|  | }); | ||||||
| 
 | 
 | ||||||
| const formSchema = computed((): VbenFormSchema[] => { | const formSchema = computed((): VbenFormSchema[] => { | ||||||
|   return [ |   return [ | ||||||
|     { |     { | ||||||
|       component: 'VbenSelect', |       component: 'VbenSelect', | ||||||
|       componentProps: { |       componentProps: { | ||||||
|         options: MOCK_USER_OPTIONS, |         options: tenantList.value.map((item) => ({ | ||||||
|         placeholder: $t('authentication.selectAccount'), |           label: item.name, | ||||||
|  |           value: item.id, | ||||||
|  |         })), | ||||||
|  |         placeholder: $t('authentication.selectTenant'), | ||||||
|  |         // value: currentTenantId.value ?? null, // TODO @芋艿:change 的设置 | ||||||
|  |         onChange: (value: number) => { | ||||||
|  |           // currentTenantId.value = value ?? null; | ||||||
|  |           accessStore.setTenantId(value); | ||||||
|         }, |         }, | ||||||
|       fieldName: 'selectAccount', |       }, | ||||||
|       label: $t('authentication.selectAccount'), |       fieldName: 'tenantId', | ||||||
|       rules: z |       label: $t('authentication.selectTenant'), | ||||||
|         .string() |       // TODO @芋艿:开关租户的逻辑 | ||||||
|         .min(1, { message: $t('authentication.selectAccount') }) |       rules: z.number().default(currentTenantId.value), // TODO @芋艿:默认值的设置 | ||||||
|         .optional() |  | ||||||
|         .default('vben'), |  | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       component: 'VbenInput', |       component: 'VbenInput', | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
|   "loginSuccessDesc": "Welcome Back", |   "loginSuccessDesc": "Welcome Back", | ||||||
|   "loginSubtitle": "Enter your account details to manage your projects", |   "loginSubtitle": "Enter your account details to manage your projects", | ||||||
|   "selectAccount": "Quick Select Account", |   "selectAccount": "Quick Select Account", | ||||||
|  |   "selectTenant": "Please Select Tenant", | ||||||
|   "username": "Username", |   "username": "Username", | ||||||
|   "password": "Password", |   "password": "Password", | ||||||
|   "usernameTip": "Please enter username", |   "usernameTip": "Please enter username", | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
|   "loginSuccessDesc": "欢迎回来", |   "loginSuccessDesc": "欢迎回来", | ||||||
|   "loginSubtitle": "请输入您的帐户信息以开始管理您的项目", |   "loginSubtitle": "请输入您的帐户信息以开始管理您的项目", | ||||||
|   "selectAccount": "快速选择账号", |   "selectAccount": "快速选择账号", | ||||||
|  |   "selectTenant": "请选择租户", | ||||||
|   "username": "账号", |   "username": "账号", | ||||||
|   "password": "密码", |   "password": "密码", | ||||||
|   "usernameTip": "请输入用户名", |   "usernameTip": "请输入用户名", | ||||||
|  |  | ||||||
|  | @ -35,6 +35,10 @@ interface AccessState { | ||||||
|    * 登录 accessToken |    * 登录 accessToken | ||||||
|    */ |    */ | ||||||
|   refreshToken: AccessToken; |   refreshToken: AccessToken; | ||||||
|  |   /** | ||||||
|  |    * 登录租户编号 | ||||||
|  |    */ | ||||||
|  |   tenantId: null | number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -82,10 +86,13 @@ export const useAccessStore = defineStore('core-access', { | ||||||
|     setRefreshToken(token: AccessToken) { |     setRefreshToken(token: AccessToken) { | ||||||
|       this.refreshToken = token; |       this.refreshToken = token; | ||||||
|     }, |     }, | ||||||
|  |     setTenantId(tenantId: null | number) { | ||||||
|  |       this.tenantId = tenantId; | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
|   persist: { |   persist: { | ||||||
|     // 持久化
 |     // 持久化
 | ||||||
|     pick: ['accessToken', 'refreshToken', 'accessCodes'], // TODO @芋艿:accessCodes 不持久化
 |     pick: ['accessToken', 'refreshToken', 'tenantId'], | ||||||
|   }, |   }, | ||||||
|   state: (): AccessState => ({ |   state: (): AccessState => ({ | ||||||
|     accessCodes: [], |     accessCodes: [], | ||||||
|  | @ -95,6 +102,7 @@ export const useAccessStore = defineStore('core-access', { | ||||||
|     isAccessChecked: false, |     isAccessChecked: false, | ||||||
|     loginExpired: false, |     loginExpired: false, | ||||||
|     refreshToken: null, |     refreshToken: null, | ||||||
|  |     tenantId: null | ||||||
|   }), |   }), | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 YunaiV
						YunaiV