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>
|
<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>
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -277,5 +277,9 @@ function clearPreferencesAndLogout() {
|
||||||
/>
|
/>
|
||||||
</LayoutFooter>
|
</LayoutFooter>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #extra>
|
||||||
|
<slot name="dialog"></slot>
|
||||||
|
</template>
|
||||||
</VbenAdminLayout>
|
</VbenAdminLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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 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';
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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))
|
||||||
|
|
Loading…
Reference in New Issue