feat: login dialog (#37)
* chore: login-dialog demo * Merge branch 'main' into login-dialog * chore: update dialog * Merge branch 'main' into login-dialog * chore: accept login params * chore: redirect to login or show login dialogpull/48/MERGE
							parent
							
								
									0f246f7e9e
								
							
						
					
					
						commit
						8e6c1abf19
					
				|  | @ -1,12 +1,16 @@ | |||
| <script lang="ts" setup> | ||||
| import type { NotificationItem } from '@vben/layouts'; | ||||
| 
 | ||||
| import { computed, ref } from 'vue'; | ||||
| import { computed, ref, toRefs } from 'vue'; | ||||
| import { useRouter } from 'vue-router'; | ||||
| 
 | ||||
| import { LOGIN_PATH } from '@vben/constants'; | ||||
| import { IcRoundCreditScore, MdiDriveDocument, MdiGithub } from '@vben/icons'; | ||||
| import { BasicLayout, Notification, UserDropdown } from '@vben/layouts'; | ||||
| import { | ||||
|   BasicLayout, | ||||
|   LoginDialog, | ||||
|   Notification, | ||||
|   NotificationItem, | ||||
|   UserDropdown, | ||||
| } from '@vben/layouts'; | ||||
| import { openWindow } from '@vben/utils'; | ||||
| import { preferences } from '@vben-core/preferences'; | ||||
| 
 | ||||
|  | @ -80,7 +84,8 @@ const menus = computed(() => [ | |||
| ]); | ||||
| 
 | ||||
| const appStore = useAppStore(); | ||||
| const { userInfo } = useAccessStore(); | ||||
| const accessStore = useAccessStore(); | ||||
| const { showLoginDialog, userInfo } = toRefs(accessStore); | ||||
| const router = useRouter(); | ||||
| 
 | ||||
| async function handleLogout() { | ||||
|  | @ -118,5 +123,13 @@ function handleMakeAll() { | |||
|         @make-all="handleMakeAll" | ||||
|       /> | ||||
|     </template> | ||||
|     <template #dialog> | ||||
|       <LoginDialog | ||||
|         :open="showLoginDialog" | ||||
|         password-placeholder="123456" | ||||
|         username-placeholder="vben" | ||||
|         @login="accessStore.authLogin" | ||||
|       /> | ||||
|     </template> | ||||
|   </BasicLayout> | ||||
| </template> | ||||
|  |  | |||
|  | @ -92,10 +92,25 @@ function setupAccessGuard(router: Router) { | |||
| 
 | ||||
|     // 生成路由表
 | ||||
|     // 当前登录用户拥有的角色标识列表
 | ||||
|     let userRoles: string[] = []; | ||||
|     try { | ||||
|       const userInfo = | ||||
|         accessStore.userInfo || (await accessStore.fetchUserInfo()); | ||||
| 
 | ||||
|     const userRoles = userInfo.roles ?? []; | ||||
|       userRoles = userInfo.roles ?? []; | ||||
|     } catch (error: any) { | ||||
|       if (error.status === 409) { | ||||
|         accessStore.setShowLoginDialog(true); | ||||
|       } else if (error.status === 401) { | ||||
|         accessStore.reset(); | ||||
|         return { | ||||
|           path: LOGIN_PATH, | ||||
|           // 如不需要,直接删除 query
 | ||||
|           query: { redirect: encodeURIComponent(to.fullPath) }, | ||||
|           // 携带当前跳转的页面,登录后重新跳转该页面
 | ||||
|           replace: true, | ||||
|         }; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // 生成菜单和路由
 | ||||
|     const { accessibleMenus, accessibleRoutes } = await generateAccess({ | ||||
|  |  | |||
|  | @ -17,6 +17,11 @@ export const useAccessStore = defineStore('access', () => { | |||
|   const router = useRouter(); | ||||
|   const loading = ref(false); | ||||
| 
 | ||||
|   const showLoginDialog = ref(false); | ||||
|   function setShowLoginDialog(value: boolean) { | ||||
|     showLoginDialog.value = value; | ||||
|   } | ||||
| 
 | ||||
|   const accessToken = computed(() => coreStoreAccess.accessToken); | ||||
|   const userRoles = computed(() => coreStoreAccess.userRoles); | ||||
|   const userInfo = computed(() => coreStoreAccess.userInfo); | ||||
|  | @ -65,6 +70,7 @@ export const useAccessStore = defineStore('access', () => { | |||
|         coreStoreAccess.setUserInfo(userInfo); | ||||
|         coreStoreAccess.setAccessCodes(accessCodes); | ||||
| 
 | ||||
|         showLoginDialog.value = false; | ||||
|         onSuccess | ||||
|           ? await onSuccess?.() | ||||
|           : await router.push(userInfo.homePath || DEFAULT_HOME_PATH); | ||||
|  | @ -99,6 +105,8 @@ export const useAccessStore = defineStore('access', () => { | |||
|     reset, | ||||
|     setAccessMenus, | ||||
|     setAccessRoutes, | ||||
|     setShowLoginDialog, | ||||
|     showLoginDialog, | ||||
|     userInfo, | ||||
|     userRoles, | ||||
|   }; | ||||
|  |  | |||
|  | @ -565,6 +565,7 @@ function handleOpenMenu() { | |||
|         <slot name="footer"></slot> | ||||
|       </LayoutFooter> | ||||
|     </div> | ||||
|     <slot name="extra"></slot> | ||||
|     <div | ||||
|       v-if="maskVisible" | ||||
|       :style="maskStyle" | ||||
|  |  | |||
|  | @ -48,6 +48,7 @@ | |||
|     "@vben-core/stores": "workspace:*", | ||||
|     "@vben-core/tabs-ui": "workspace:*", | ||||
|     "@vben-core/toolkit": "workspace:*", | ||||
|     "@vben/universal-ui": "workspace:*", | ||||
|     "@vueuse/core": "^10.11.0", | ||||
|     "vue": "^3.4.31", | ||||
|     "vue-router": "^4.4.0" | ||||
|  |  | |||
|  | @ -277,5 +277,9 @@ function clearPreferencesAndLogout() { | |||
|         /> | ||||
|       </LayoutFooter> | ||||
|     </template> | ||||
| 
 | ||||
|     <template #extra> | ||||
|       <slot name="dialog"></slot> | ||||
|     </template> | ||||
|   </VbenAdminLayout> | ||||
| </template> | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ export { default as CozeAssistant } from './coze-assistant.vue'; | |||
| export * from './global-search'; | ||||
| export { default as LanguageToggle } from './language-toggle.vue'; | ||||
| export { default as AuthenticationLayoutToggle } from './layout-toggle.vue'; | ||||
| export * from './login-dialog'; | ||||
| export * from './notification'; | ||||
| export * from './preferences'; | ||||
| export * from './theme-toggle'; | ||||
|  |  | |||
|  | @ -0,0 +1 @@ | |||
| export { default as LoginDialog } from './login-dialog.vue'; | ||||
|  | @ -0,0 +1,48 @@ | |||
| <script setup lang="ts"> | ||||
| import { computed } from 'vue'; | ||||
| 
 | ||||
| import { | ||||
|   AuthenticationLogin, | ||||
|   AuthenticationProps, | ||||
|   LoginAndRegisterParams, | ||||
| } from '@vben/universal-ui'; | ||||
| import { Dialog, DialogContent } from '@vben-core/shadcn-ui'; | ||||
| 
 | ||||
| interface Props extends AuthenticationProps { | ||||
|   open: boolean; | ||||
| } | ||||
| 
 | ||||
| defineOptions({ | ||||
|   name: 'LoginDialog', | ||||
| }); | ||||
| 
 | ||||
| const props = withDefaults(defineProps<Props>(), { | ||||
|   open: false, | ||||
| }); | ||||
| 
 | ||||
| const emit = defineEmits<{ | ||||
|   login: [LoginAndRegisterParams]; | ||||
| }>(); | ||||
| 
 | ||||
| const loginProps = computed(() => { | ||||
|   const { open: _, ...rest } = props; | ||||
|   return rest; | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div> | ||||
|     <Dialog :open="open" class="flex items-center justify-center"> | ||||
|       <DialogContent | ||||
|         class="top-[50%] w-full translate-y-[-50%] border-none p-0 shadow-xl sm:w-[600px] sm:rounded-2xl" | ||||
|       > | ||||
|         <div class="p-4"> | ||||
|           <AuthenticationLogin | ||||
|             v-bind="loginProps" | ||||
|             @submit="(e) => emit('login', e)" | ||||
|           /> | ||||
|         </div> | ||||
|       </DialogContent> | ||||
|     </Dialog> | ||||
|   </div> | ||||
| </template> | ||||
|  | @ -3,4 +3,8 @@ export { default as AuthenticationForgetPassword } from './forget-password.vue'; | |||
| export { default as AuthenticationLogin } from './login.vue'; | ||||
| export { default as AuthenticationQrCodeLogin } from './qrcode-login.vue'; | ||||
| export { default as AuthenticationRegister } from './register.vue'; | ||||
| export type { LoginAndRegisterParams, LoginCodeParams } from './typings'; | ||||
| export type { | ||||
|   AuthenticationProps, | ||||
|   LoginAndRegisterParams, | ||||
|   LoginCodeParams, | ||||
| } from './typings'; | ||||
|  |  | |||
|  | @ -1,6 +1,4 @@ | |||
| <script setup lang="ts"> | ||||
| import type { LoginEmits } from './typings'; | ||||
| 
 | ||||
| import { computed, reactive } from 'vue'; | ||||
| import { useRouter } from 'vue-router'; | ||||
| 
 | ||||
|  | @ -14,68 +12,9 @@ import { | |||
| 
 | ||||
| import Title from './auth-title.vue'; | ||||
| import ThirdPartyLogin from './third-party-login.vue'; | ||||
| import { AuthenticationProps, LoginEmits } from './typings'; | ||||
| 
 | ||||
| interface Props { | ||||
|   /** | ||||
|    * @zh_CN 验证码登录路径 | ||||
|    */ | ||||
|   codeLoginPath?: string; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 忘记密码路径 | ||||
|    */ | ||||
|   forgetPasswordPath?: string; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 是否处于加载处理状态 | ||||
|    */ | ||||
|   loading?: boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 密码占位符 | ||||
|    */ | ||||
|   passwordPlaceholder?: string; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 二维码登录路径 | ||||
|    */ | ||||
|   qrCodeLoginPath?: string; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 注册路径 | ||||
|    */ | ||||
|   registerPath?: string; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 是否显示验证码登录 | ||||
|    */ | ||||
|   showCodeLogin?: boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 是否显示忘记密码 | ||||
|    */ | ||||
|   showForgetPassword?: boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 是否显示二维码登录 | ||||
|    */ | ||||
|   showQrcodeLogin?: boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 是否显示注册按钮 | ||||
|    */ | ||||
|   showRegister?: boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 是否显示第三方登录 | ||||
|    */ | ||||
|   showThirdPartyLogin?: boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 用户名占位符 | ||||
|    */ | ||||
|   usernamePlaceholder?: string; | ||||
| } | ||||
| interface Props extends AuthenticationProps {} | ||||
| 
 | ||||
| defineOptions({ | ||||
|   name: 'AuthenticationLogin', | ||||
|  |  | |||
|  | @ -1,3 +1,65 @@ | |||
| interface AuthenticationProps { | ||||
|   /** | ||||
|    * @zh_CN 验证码登录路径 | ||||
|    */ | ||||
|   codeLoginPath?: string; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 忘记密码路径 | ||||
|    */ | ||||
|   forgetPasswordPath?: string; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 是否处于加载处理状态 | ||||
|    */ | ||||
|   loading?: boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 密码占位符 | ||||
|    */ | ||||
|   passwordPlaceholder?: string; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 二维码登录路径 | ||||
|    */ | ||||
|   qrCodeLoginPath?: string; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 注册路径 | ||||
|    */ | ||||
|   registerPath?: string; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 是否显示验证码登录 | ||||
|    */ | ||||
|   showCodeLogin?: boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 是否显示忘记密码 | ||||
|    */ | ||||
|   showForgetPassword?: boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 是否显示二维码登录 | ||||
|    */ | ||||
|   showQrcodeLogin?: boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 是否显示注册按钮 | ||||
|    */ | ||||
|   showRegister?: boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 是否显示第三方登录 | ||||
|    */ | ||||
|   showThirdPartyLogin?: boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * @zh_CN 用户名占位符 | ||||
|    */ | ||||
|   usernamePlaceholder?: string; | ||||
| } | ||||
| 
 | ||||
| interface LoginAndRegisterParams { | ||||
|   password: string; | ||||
|   username: string; | ||||
|  | @ -21,6 +83,7 @@ interface RegisterEmits { | |||
| } | ||||
| 
 | ||||
| export type { | ||||
|   AuthenticationProps, | ||||
|   LoginAndRegisterParams, | ||||
|   LoginCodeEmits, | ||||
|   LoginCodeParams, | ||||
|  |  | |||
|  | @ -878,6 +878,9 @@ importers: | |||
|       '@vben-core/toolkit': | ||||
|         specifier: workspace:* | ||||
|         version: link:../../@core/shared/toolkit | ||||
|       '@vben/universal-ui': | ||||
|         specifier: workspace:* | ||||
|         version: link:../universal-ui | ||||
|       '@vueuse/core': | ||||
|         specifier: ^10.11.0 | ||||
|         version: 10.11.0(vue@3.4.31(typescript@5.5.3)) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Li Kui
						Li Kui