Browse Source

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 dialog
Li Kui 8 months ago
parent
commit
8e6c1abf19

+ 18 - 5
apps/web-antd/src/layouts/basic.vue

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

+ 19 - 4
apps/web-antd/src/router/guard.ts

@@ -92,10 +92,25 @@ function setupAccessGuard(router: Router) {
 
     // 生成路由表
     // 当前登录用户拥有的角色标识列表
-    const userInfo =
-      accessStore.userInfo || (await accessStore.fetchUserInfo());
-
-    const userRoles = userInfo.roles ?? [];
+    let userRoles: string[] = [];
+    try {
+      const userInfo =
+        accessStore.userInfo || (await accessStore.fetchUserInfo());
+      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({

+ 8 - 0
apps/web-antd/src/store/modules/access.ts

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

+ 1 - 0
packages/@core/ui-kit/layout-ui/src/vben-layout.vue

@@ -565,6 +565,7 @@ function handleOpenMenu() {
         <slot name="footer"></slot>
       </LayoutFooter>
     </div>
+    <slot name="extra"></slot>
     <div
       v-if="maskVisible"
       :style="maskStyle"

+ 1 - 0
packages/business/layouts/package.json

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

+ 4 - 0
packages/business/layouts/src/basic/layout.vue

@@ -277,5 +277,9 @@ function clearPreferencesAndLogout() {
         />
       </LayoutFooter>
     </template>
+
+    <template #extra>
+      <slot name="dialog"></slot>
+    </template>
   </VbenAdminLayout>
 </template>

+ 1 - 0
packages/business/layouts/src/widgets/index.ts

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

+ 1 - 0
packages/business/layouts/src/widgets/login-dialog/index.ts

@@ -0,0 +1 @@
+export { default as LoginDialog } from './login-dialog.vue';

+ 48 - 0
packages/business/layouts/src/widgets/login-dialog/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>

+ 5 - 1
packages/business/universal-ui/src/authentication/index.ts

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

+ 2 - 63
packages/business/universal-ui/src/authentication/login.vue

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

+ 63 - 0
packages/business/universal-ui/src/authentication/typings.ts

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

+ 3 - 0
pnpm-lock.yaml

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