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
pull/48/MERGE
Li Kui 2024-07-11 11:05:01 +08:00 committed by GitHub
parent 0f246f7e9e
commit 8e6c1abf19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 174 additions and 73 deletions

View File

@ -1,12 +1,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { NotificationItem } from '@vben/layouts'; import { computed, ref, toRefs } from 'vue';
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { LOGIN_PATH } from '@vben/constants'; import { LOGIN_PATH } from '@vben/constants';
import { IcRoundCreditScore, MdiDriveDocument, MdiGithub } from '@vben/icons'; 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 { openWindow } from '@vben/utils';
import { preferences } from '@vben-core/preferences'; import { preferences } from '@vben-core/preferences';
@ -80,7 +84,8 @@ const menus = computed(() => [
]); ]);
const appStore = useAppStore(); const appStore = useAppStore();
const { userInfo } = useAccessStore(); const accessStore = useAccessStore();
const { showLoginDialog, userInfo } = toRefs(accessStore);
const router = useRouter(); const router = useRouter();
async function handleLogout() { async function handleLogout() {
@ -118,5 +123,13 @@ function handleMakeAll() {
@make-all="handleMakeAll" @make-all="handleMakeAll"
/> />
</template> </template>
<template #dialog>
<LoginDialog
:open="showLoginDialog"
password-placeholder="123456"
username-placeholder="vben"
@login="accessStore.authLogin"
/>
</template>
</BasicLayout> </BasicLayout>
</template> </template>

View File

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

View File

@ -17,6 +17,11 @@ export const useAccessStore = defineStore('access', () => {
const router = useRouter(); const router = useRouter();
const loading = ref(false); const loading = ref(false);
const showLoginDialog = ref(false);
function setShowLoginDialog(value: boolean) {
showLoginDialog.value = value;
}
const accessToken = computed(() => coreStoreAccess.accessToken); const accessToken = computed(() => coreStoreAccess.accessToken);
const userRoles = computed(() => coreStoreAccess.userRoles); const userRoles = computed(() => coreStoreAccess.userRoles);
const userInfo = computed(() => coreStoreAccess.userInfo); const userInfo = computed(() => coreStoreAccess.userInfo);
@ -65,6 +70,7 @@ export const useAccessStore = defineStore('access', () => {
coreStoreAccess.setUserInfo(userInfo); coreStoreAccess.setUserInfo(userInfo);
coreStoreAccess.setAccessCodes(accessCodes); coreStoreAccess.setAccessCodes(accessCodes);
showLoginDialog.value = false;
onSuccess onSuccess
? await onSuccess?.() ? await onSuccess?.()
: await router.push(userInfo.homePath || DEFAULT_HOME_PATH); : await router.push(userInfo.homePath || DEFAULT_HOME_PATH);
@ -99,6 +105,8 @@ export const useAccessStore = defineStore('access', () => {
reset, reset,
setAccessMenus, setAccessMenus,
setAccessRoutes, setAccessRoutes,
setShowLoginDialog,
showLoginDialog,
userInfo, userInfo,
userRoles, userRoles,
}; };

View File

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

View File

@ -48,6 +48,7 @@
"@vben-core/stores": "workspace:*", "@vben-core/stores": "workspace:*",
"@vben-core/tabs-ui": "workspace:*", "@vben-core/tabs-ui": "workspace:*",
"@vben-core/toolkit": "workspace:*", "@vben-core/toolkit": "workspace:*",
"@vben/universal-ui": "workspace:*",
"@vueuse/core": "^10.11.0", "@vueuse/core": "^10.11.0",
"vue": "^3.4.31", "vue": "^3.4.31",
"vue-router": "^4.4.0" "vue-router": "^4.4.0"

View File

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

View File

@ -4,6 +4,7 @@ export { default as CozeAssistant } from './coze-assistant.vue';
export * from './global-search'; export * from './global-search';
export { default as LanguageToggle } from './language-toggle.vue'; export { default as LanguageToggle } from './language-toggle.vue';
export { default as AuthenticationLayoutToggle } from './layout-toggle.vue'; export { default as AuthenticationLayoutToggle } from './layout-toggle.vue';
export * from './login-dialog';
export * from './notification'; export * from './notification';
export * from './preferences'; export * from './preferences';
export * from './theme-toggle'; export * from './theme-toggle';

View File

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

View File

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

View File

@ -3,4 +3,8 @@ export { default as AuthenticationForgetPassword } from './forget-password.vue';
export { default as AuthenticationLogin } from './login.vue'; export { default as AuthenticationLogin } from './login.vue';
export { default as AuthenticationQrCodeLogin } from './qrcode-login.vue'; export { default as AuthenticationQrCodeLogin } from './qrcode-login.vue';
export { default as AuthenticationRegister } from './register.vue'; export { default as AuthenticationRegister } from './register.vue';
export type { LoginAndRegisterParams, LoginCodeParams } from './typings'; export type {
AuthenticationProps,
LoginAndRegisterParams,
LoginCodeParams,
} from './typings';

View File

@ -1,6 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import type { LoginEmits } from './typings';
import { computed, reactive } from 'vue'; import { computed, reactive } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -14,68 +12,9 @@ import {
import Title from './auth-title.vue'; import Title from './auth-title.vue';
import ThirdPartyLogin from './third-party-login.vue'; import ThirdPartyLogin from './third-party-login.vue';
import { AuthenticationProps, LoginEmits } from './typings';
interface Props { interface Props extends 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;
}
defineOptions({ defineOptions({
name: 'AuthenticationLogin', name: 'AuthenticationLogin',

View File

@ -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 { interface LoginAndRegisterParams {
password: string; password: string;
username: string; username: string;
@ -21,6 +83,7 @@ interface RegisterEmits {
} }
export type { export type {
AuthenticationProps,
LoginAndRegisterParams, LoginAndRegisterParams,
LoginCodeEmits, LoginCodeEmits,
LoginCodeParams, LoginCodeParams,

View File

@ -878,6 +878,9 @@ importers:
'@vben-core/toolkit': '@vben-core/toolkit':
specifier: workspace:* specifier: workspace:*
version: link:../../@core/shared/toolkit version: link:../../@core/shared/toolkit
'@vben/universal-ui':
specifier: workspace:*
version: link:../universal-ui
'@vueuse/core': '@vueuse/core':
specifier: ^10.11.0 specifier: ^10.11.0
version: 10.11.0(vue@3.4.31(typescript@5.5.3)) version: 10.11.0(vue@3.4.31(typescript@5.5.3))