1. 切换账号功能
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
This commit is contained in:
@@ -6,6 +6,7 @@ export namespace AuthApi {
|
||||
account: string;
|
||||
password?: string;
|
||||
code?: string;
|
||||
admin_id?: number;
|
||||
/** @deprecated 兼容旧客户端 */
|
||||
login_method?: 'phone' | 'login_account' | 'job_number';
|
||||
username?: string;
|
||||
@@ -15,8 +16,23 @@ export namespace AuthApi {
|
||||
}
|
||||
|
||||
/** 登录接口返回值 */
|
||||
export interface AccountOption {
|
||||
account_type?: string;
|
||||
id: number;
|
||||
nick_name: string;
|
||||
role_name: string;
|
||||
store_name: string;
|
||||
login_account?: string;
|
||||
is_current?: boolean;
|
||||
password_matched?: boolean;
|
||||
}
|
||||
|
||||
export interface LoginResult {
|
||||
token: string;
|
||||
token?: string;
|
||||
need_select?: boolean;
|
||||
accounts?: AccountOption[];
|
||||
default_account_id?: number;
|
||||
default_account_type?: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenResult {
|
||||
@@ -28,6 +44,9 @@ export namespace AuthApi {
|
||||
export interface SendCodeResult {
|
||||
code?: string;
|
||||
need_select?: boolean;
|
||||
accounts?: AccountOption[];
|
||||
default_account_id?: number;
|
||||
default_account_type?: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +59,7 @@ export async function loginApi(data: AuthApi.LoginParams) {
|
||||
account: data.account,
|
||||
password: data.password,
|
||||
code: data.code,
|
||||
admin_id: data.admin_id,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -66,6 +86,7 @@ export async function loginApi(data: AuthApi.LoginParams) {
|
||||
export async function sendVerificationCode(params: {
|
||||
account?: string;
|
||||
password?: string;
|
||||
admin_id?: number;
|
||||
login_method?: 'phone' | 'login_account' | 'job_number';
|
||||
username?: string;
|
||||
phone?: string;
|
||||
@@ -75,6 +96,7 @@ export async function sendVerificationCode(params: {
|
||||
if (params.account) {
|
||||
return requestClient.post<AuthApi.SendCodeResult>('send-verification-code', {
|
||||
account: params.account,
|
||||
admin_id: params.admin_id,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -120,3 +142,19 @@ export async function logoutApi() {
|
||||
export async function getAccessCodesApi() {
|
||||
return requestClient.get<string[]>('auth/codes');
|
||||
}
|
||||
|
||||
/**
|
||||
* 同手机号可切换的管理员账号
|
||||
*/
|
||||
export async function getSiblingAccountsApi() {
|
||||
return requestClient.get<AuthApi.AccountOption[]>('auth/sibling-accounts');
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换管理员账号
|
||||
*/
|
||||
export async function switchAccountApi(data: {
|
||||
admin_id: number;
|
||||
}) {
|
||||
return requestClient.post<AuthApi.LoginResult>('auth/switch-account', data);
|
||||
}
|
||||
|
||||
@@ -23,17 +23,20 @@ import {
|
||||
Bell,
|
||||
CalendarClock,
|
||||
Megaphone,
|
||||
Users,
|
||||
} from 'lucide-vue-next';
|
||||
|
||||
// import { $t } from '#/locales';
|
||||
import { useAuthStore } from '#/store';
|
||||
import LoginForm from '#/views/_core/authentication/login.vue';
|
||||
import SwitchAccountModal from '#/layouts/components/SwitchAccountModal.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import {getNoticeListApi, readAllApi} from "#/views/notice/api";
|
||||
import {formatTimeToRelative} from "#/util/tool";
|
||||
import {notification} from "ant-design-vue";
|
||||
|
||||
const router = useRouter();
|
||||
const switchAccountModalRef = ref<InstanceType<typeof SwitchAccountModal>>();
|
||||
|
||||
// 样式相关
|
||||
const typeIcons = {
|
||||
@@ -177,41 +180,13 @@ const showDot = computed(() =>
|
||||
);
|
||||
|
||||
const menus = computed(() => [
|
||||
// TODO 后续放个人中心
|
||||
// {
|
||||
// handler: () => {
|
||||
// UpdatePasswordModalApi.open();
|
||||
// },
|
||||
// icon: 'carbon:password',
|
||||
// text: '修改密码',
|
||||
// },
|
||||
// {
|
||||
// handler: () => {
|
||||
// openWindow(VBEN_DOC_URL, {
|
||||
// target: '_blank',
|
||||
// });
|
||||
// },
|
||||
// icon: BookOpenText,
|
||||
// text: $t('ui.widgets.document'),
|
||||
// },
|
||||
// {
|
||||
// handler: () => {
|
||||
// openWindow(VBEN_GITHUB_URL, {
|
||||
// target: '_blank',
|
||||
// });
|
||||
// },
|
||||
// icon: MdiGithub,
|
||||
// text: 'GitHub',
|
||||
// },
|
||||
// {
|
||||
// handler: () => {
|
||||
// openWindow(`${VBEN_GITHUB_URL}/issues`, {
|
||||
// target: '_blank',
|
||||
// });
|
||||
// },
|
||||
// icon: CircleHelp,
|
||||
// text: $t('ui.widgets.qa'),
|
||||
// },
|
||||
{
|
||||
handler: () => {
|
||||
switchAccountModalRef.value?.openModal();
|
||||
},
|
||||
icon: Users,
|
||||
text: '切换账号',
|
||||
},
|
||||
]);
|
||||
|
||||
const avatar = computed(() => {
|
||||
@@ -285,6 +260,7 @@ watch(
|
||||
>
|
||||
<LoginForm />
|
||||
</AuthenticationLoginExpiredModal>
|
||||
<SwitchAccountModal ref="switchAccountModalRef" />
|
||||
</template>
|
||||
<template #lock-screen>
|
||||
<LockScreen :avatar @to-login="handleLogout" />
|
||||
|
||||
164
apps/web-antd/src/layouts/components/SwitchAccountModal.vue
Normal file
164
apps/web-antd/src/layouts/components/SwitchAccountModal.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<script lang="ts" setup>
|
||||
import type { AuthApi } from '#/api/core/auth';
|
||||
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
|
||||
import { DEFAULT_HOME_PATH } from '@vben/constants';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import {
|
||||
getSiblingAccountsApi,
|
||||
switchAccountApi,
|
||||
} from '#/api/core/auth';
|
||||
import { useAuthStore } from '#/store';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
|
||||
import AccountOptionList from '#/views/_core/authentication/AccountOptionList.vue';
|
||||
|
||||
defineOptions({ name: 'SwitchAccountModal' });
|
||||
|
||||
const visible = defineModel<boolean>('open', { default: false });
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const accounts = ref<AuthApi.AccountOption[]>([]);
|
||||
const selectedKey = ref<string | null>(null);
|
||||
const loading = ref(false);
|
||||
|
||||
const successVisible = ref(false);
|
||||
const countdown = ref(5);
|
||||
let countdownTimer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
function accountKey(item: AuthApi.AccountOption): string {
|
||||
return `${item.account_type || 'admin'}-${item.id}-${item.store_name || ''}`;
|
||||
}
|
||||
|
||||
function resolveInitialKey(list: AuthApi.AccountOption[]): string | null {
|
||||
if (!list.length) {
|
||||
return null;
|
||||
}
|
||||
const current = list.find((item) => item.is_current);
|
||||
if (current) {
|
||||
return accountKey(current);
|
||||
}
|
||||
return accountKey(list[0]);
|
||||
}
|
||||
|
||||
function clearCountdownTimer() {
|
||||
if (countdownTimer) {
|
||||
clearInterval(countdownTimer);
|
||||
countdownTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function reloadPage() {
|
||||
clearCountdownTimer();
|
||||
const userStore = useUserStore();
|
||||
const home = userStore.userInfo?.home_path || DEFAULT_HOME_PATH;
|
||||
window.location.href = home;
|
||||
}
|
||||
|
||||
function openSuccessModal() {
|
||||
successVisible.value = true;
|
||||
countdown.value = 5;
|
||||
clearCountdownTimer();
|
||||
countdownTimer = setInterval(() => {
|
||||
countdown.value -= 1;
|
||||
if (countdown.value <= 0) {
|
||||
reloadPage();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
async function openModal() {
|
||||
visible.value = true;
|
||||
loading.value = true;
|
||||
try {
|
||||
accounts.value = await getSiblingAccountsApi();
|
||||
selectedKey.value = resolveInitialKey(accounts.value);
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '获取账号列表失败');
|
||||
visible.value = false;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSwitch() {
|
||||
if (!selectedKey.value) {
|
||||
message.error('请选择要切换的账号');
|
||||
return;
|
||||
}
|
||||
|
||||
const selected = accounts.value.find(
|
||||
(item) => accountKey(item) === selectedKey.value,
|
||||
);
|
||||
if (!selected) {
|
||||
message.error('请选择要切换的账号');
|
||||
return;
|
||||
}
|
||||
|
||||
if (selected.is_current) {
|
||||
message.info('已是当前账号');
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const result = await switchAccountApi({
|
||||
admin_id: selected.id,
|
||||
});
|
||||
if (!result?.token) {
|
||||
message.error('切换失败');
|
||||
return;
|
||||
}
|
||||
await authStore.applySwitchSession(result.token);
|
||||
visible.value = false;
|
||||
openSuccessModal();
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '切换失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearCountdownTimer();
|
||||
});
|
||||
|
||||
defineExpose({ openModal });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
v-model:open="visible"
|
||||
title="切换账号"
|
||||
ok-text="确认切换"
|
||||
cancel-text="取消"
|
||||
:mask-closable="false"
|
||||
:keyboard="false"
|
||||
:confirm-loading="loading"
|
||||
@ok="handleSwitch"
|
||||
>
|
||||
<AccountOptionList
|
||||
:accounts="accounts"
|
||||
:selected-key="selectedKey"
|
||||
@select="selectedKey = $event"
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
v-model:open="successVisible"
|
||||
title="切换成功"
|
||||
:closable="false"
|
||||
:mask-closable="false"
|
||||
:keyboard="false"
|
||||
:cancel-button-props="{ style: { display: 'none' } }"
|
||||
:ok-text="countdown > 0 ? `确认 (${countdown}s)` : '确认'"
|
||||
@ok="reloadPage"
|
||||
>
|
||||
<div class="text-muted-foreground text-sm">
|
||||
账号已切换成功,页面将在 {{ countdown > 0 ? countdown : 0 }} 秒后自动刷新。
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -32,17 +32,25 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
params: Recordable<any>,
|
||||
onSuccess?: () => Promise<void> | void,
|
||||
) {
|
||||
// 异步处理用户登录操作并获取 token
|
||||
let userInfo: null | UserInfo = null;
|
||||
try {
|
||||
loginLoading.value = true;
|
||||
const { token } = await loginApi(params);
|
||||
const result = await loginApi(params);
|
||||
|
||||
// 如果成功获取到 token
|
||||
if (result?.need_select && result.accounts?.length) {
|
||||
return {
|
||||
need_select: true,
|
||||
accounts: result.accounts,
|
||||
default_account_id: result.default_account_id,
|
||||
default_account_type: result.default_account_type,
|
||||
userInfo: null,
|
||||
};
|
||||
}
|
||||
|
||||
const token = result?.token;
|
||||
if (token) {
|
||||
accessStore.setAccessToken(token);
|
||||
|
||||
// 获取用户信息并存储到 accessStore 中
|
||||
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
||||
fetchUserInfo(),
|
||||
getAccessCodesApi(),
|
||||
@@ -53,7 +61,6 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
userStore.setUserInfo(userInfo);
|
||||
accessStore.setAccessCodes(accessCodes);
|
||||
|
||||
console.log('userInfo', accessStore.loginExpired);
|
||||
if (accessStore.loginExpired) {
|
||||
accessStore.setLoginExpired(false);
|
||||
} else {
|
||||
@@ -61,30 +68,55 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
? await onSuccess?.()
|
||||
: await router.push(userInfo.home_path || DEFAULT_HOME_PATH);
|
||||
}
|
||||
|
||||
// if (userInfo?.nick_name) {
|
||||
// // 这里修改水印内容
|
||||
// await updateWatermark({
|
||||
// contentType: 'multi-line-text',
|
||||
// // 水印内容
|
||||
// content: `${userInfo?.nick_name}\r\n${userInfo?.phone}`,
|
||||
// });
|
||||
// notification.success({
|
||||
// description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.nick_name}`,
|
||||
// duration: 3,
|
||||
// message: $t('authentication.loginSuccess'),
|
||||
// });
|
||||
// }
|
||||
}
|
||||
} finally {
|
||||
loginLoading.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
need_select: false,
|
||||
userInfo,
|
||||
};
|
||||
}
|
||||
|
||||
async function applySwitchSession(token: string) {
|
||||
accessStore.setAccessToken(token);
|
||||
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
||||
fetchUserInfo(),
|
||||
getAccessCodesApi(),
|
||||
]);
|
||||
userStore.setUserInfo(fetchUserInfoResult);
|
||||
accessStore.setAccessCodes(accessCodes);
|
||||
|
||||
if (accessStore.loginExpired) {
|
||||
accessStore.setLoginExpired(false);
|
||||
}
|
||||
|
||||
return fetchUserInfoResult;
|
||||
}
|
||||
|
||||
async function applyLoginSession(token: string, onSuccess?: () => Promise<void> | void) {
|
||||
let userInfo: null | UserInfo = null;
|
||||
accessStore.setAccessToken(token);
|
||||
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
||||
fetchUserInfo(),
|
||||
getAccessCodesApi(),
|
||||
]);
|
||||
userInfo = fetchUserInfoResult;
|
||||
userStore.setUserInfo(userInfo);
|
||||
accessStore.setAccessCodes(accessCodes);
|
||||
|
||||
if (accessStore.loginExpired) {
|
||||
accessStore.setLoginExpired(false);
|
||||
} else {
|
||||
onSuccess
|
||||
? await onSuccess?.()
|
||||
: await router.push(userInfo.home_path || DEFAULT_HOME_PATH);
|
||||
}
|
||||
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
async function logout(redirect: boolean = true) {
|
||||
try {
|
||||
await logoutApi();
|
||||
@@ -118,6 +150,8 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
|
||||
return {
|
||||
$reset,
|
||||
applyLoginSession,
|
||||
applySwitchSession,
|
||||
authLogin,
|
||||
fetchUserInfo,
|
||||
loginLoading,
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<script lang="ts" setup>
|
||||
import type { AuthApi } from '#/api/core/auth';
|
||||
|
||||
defineOptions({ name: 'AccountOptionList' });
|
||||
|
||||
const props = defineProps<{
|
||||
accounts: AuthApi.AccountOption[];
|
||||
selectedKey?: string | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
select: [key: string];
|
||||
}>();
|
||||
|
||||
function accountKey(item: AuthApi.AccountOption): string {
|
||||
return `${item.account_type || 'admin'}-${item.id}-${item.store_name || ''}`;
|
||||
}
|
||||
|
||||
function handleSelect(item: AuthApi.AccountOption) {
|
||||
emit('select', accountKey(item));
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-3">
|
||||
<div
|
||||
v-for="item in accounts"
|
||||
:key="accountKey(item)"
|
||||
class="border-border cursor-pointer rounded-lg border px-3.5 py-3 transition-all"
|
||||
:class="{
|
||||
'border-primary bg-primary/10': selectedKey === accountKey(item),
|
||||
}"
|
||||
@click="handleSelect(item)"
|
||||
>
|
||||
<div class="text-foreground text-[15px] font-semibold">
|
||||
{{ item.nick_name }}
|
||||
<span v-if="item.is_current" class="text-primary ml-2 text-xs font-normal">当前</span>
|
||||
</div>
|
||||
<div class="text-muted-foreground mt-1 text-[13px]">
|
||||
<span>{{ item.role_name }}</span>
|
||||
<span v-if="item.store_name"> · {{ item.store_name }}</span>
|
||||
<span v-if="item.login_account"> · {{ item.login_account }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,117 @@
|
||||
<script lang="ts" setup>
|
||||
import type { AuthApi } from '#/api/core/auth';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
import AccountOptionList from './AccountOptionList.vue';
|
||||
|
||||
defineOptions({ name: 'AccountSelectModal' });
|
||||
|
||||
const props = defineProps<{
|
||||
accounts: AuthApi.AccountOption[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
confirm: [account: AuthApi.AccountOption];
|
||||
cancel: [];
|
||||
}>();
|
||||
|
||||
const visible = defineModel<boolean>('open', { default: false });
|
||||
|
||||
const selectedKey = ref<string | null>(null);
|
||||
|
||||
function accountKey(item: AuthApi.AccountOption): string {
|
||||
return `${item.account_type || 'admin'}-${item.id}-${item.store_name || ''}`;
|
||||
}
|
||||
|
||||
function resolveInitialKey(
|
||||
accounts: AuthApi.AccountOption[],
|
||||
options: {
|
||||
initialAdminId?: number | null;
|
||||
defaultAccountId?: number | null;
|
||||
defaultAccountType?: string | null;
|
||||
} = {},
|
||||
): string | null {
|
||||
if (!accounts.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { initialAdminId, defaultAccountId, defaultAccountType } = options;
|
||||
|
||||
if (initialAdminId != null) {
|
||||
const byId = accounts.find((item) => item.id === initialAdminId);
|
||||
if (byId) {
|
||||
return accountKey(byId);
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultAccountId != null && defaultAccountType) {
|
||||
const byDefault = accounts.find(
|
||||
(item) =>
|
||||
item.account_type === defaultAccountType &&
|
||||
item.id === defaultAccountId,
|
||||
);
|
||||
if (byDefault) {
|
||||
return accountKey(byDefault);
|
||||
}
|
||||
}
|
||||
|
||||
const matched = accounts.find((item) => item.password_matched);
|
||||
if (matched) {
|
||||
return accountKey(matched);
|
||||
}
|
||||
|
||||
return accountKey(accounts[0]);
|
||||
}
|
||||
|
||||
function handleOk() {
|
||||
const account = props.accounts.find(
|
||||
(item) => accountKey(item) === selectedKey.value,
|
||||
);
|
||||
if (!account) {
|
||||
return;
|
||||
}
|
||||
emit('confirm', account);
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
emit('cancel');
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function openWithAccounts(
|
||||
accounts: AuthApi.AccountOption[],
|
||||
options: {
|
||||
initialAdminId?: number | null;
|
||||
defaultAccountId?: number | null;
|
||||
defaultAccountType?: string | null;
|
||||
} = {},
|
||||
) {
|
||||
selectedKey.value = resolveInitialKey(accounts, options);
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
defineExpose({ openWithAccounts });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
v-model:open="visible"
|
||||
title="选择登录账号"
|
||||
ok-text="确认"
|
||||
cancel-text="取消"
|
||||
:mask-closable="false"
|
||||
:keyboard="false"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<AccountOptionList
|
||||
:accounts="accounts"
|
||||
:selected-key="selectedKey"
|
||||
@select="selectedKey = $event"
|
||||
/>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Recordable } from '@vben/types';
|
||||
import type { AuthApi } from '#/api/core/auth';
|
||||
|
||||
import { computed, ref, useTemplateRef } from 'vue';
|
||||
|
||||
@@ -11,14 +12,69 @@ import { sendVerificationCode } from '#/api';
|
||||
import { useAuthStore } from '#/store';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import AccountSelectModal from './AccountSelectModal.vue';
|
||||
|
||||
defineOptions({ name: 'Login' });
|
||||
|
||||
const loginRef =
|
||||
useTemplateRef<InstanceType<typeof AuthenticationCodeLogin>>('loginRef');
|
||||
const accountSelectRef = useTemplateRef<InstanceType<typeof AccountSelectModal>>(
|
||||
'accountSelectRef',
|
||||
);
|
||||
const authStore = useAuthStore();
|
||||
const CODE_LENGTH = 6;
|
||||
|
||||
const loading = ref(false);
|
||||
const pendingAccounts = ref<AuthApi.AccountOption[]>([]);
|
||||
const selectedAdminId = ref<number | null>(null);
|
||||
|
||||
async function resolveAccountSelection(
|
||||
accounts: AuthApi.AccountOption[],
|
||||
options: {
|
||||
defaultAccountId?: number | null;
|
||||
defaultAccountType?: string | null;
|
||||
} = {},
|
||||
): Promise<number | null> {
|
||||
return new Promise((resolve) => {
|
||||
pendingAccounts.value = accounts;
|
||||
const defaultId =
|
||||
options.defaultAccountId ??
|
||||
accounts.find((item) => item.password_matched)?.id ??
|
||||
accounts[0]?.id ??
|
||||
null;
|
||||
selectedAdminId.value = defaultId;
|
||||
accountSelectRef.value?.openWithAccounts(accounts, {
|
||||
defaultAccountId: options.defaultAccountId,
|
||||
defaultAccountType: options.defaultAccountType,
|
||||
initialAdminId: defaultId,
|
||||
});
|
||||
|
||||
const onConfirm = (account: AuthApi.AccountOption) => {
|
||||
resolve(account.id);
|
||||
};
|
||||
const onCancel = () => resolve(null);
|
||||
|
||||
pendingConfirmHandler.value = onConfirm;
|
||||
pendingCancelHandler.value = onCancel;
|
||||
});
|
||||
}
|
||||
|
||||
const pendingConfirmHandler = ref<((account: AuthApi.AccountOption) => void) | null>(
|
||||
null,
|
||||
);
|
||||
const pendingCancelHandler = ref<(() => void) | null>(null);
|
||||
|
||||
function handleAccountConfirm(account: AuthApi.AccountOption) {
|
||||
pendingConfirmHandler.value?.(account);
|
||||
pendingConfirmHandler.value = null;
|
||||
pendingCancelHandler.value = null;
|
||||
}
|
||||
|
||||
function handleAccountCancel() {
|
||||
pendingCancelHandler.value?.();
|
||||
pendingConfirmHandler.value = null;
|
||||
pendingCancelHandler.value = null;
|
||||
}
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => [
|
||||
{
|
||||
@@ -72,11 +128,30 @@ const formSchema = computed((): VbenFormSchema[] => [
|
||||
}
|
||||
|
||||
const values = await formApi.getValues();
|
||||
const result = await sendVerificationCode({
|
||||
let adminId = selectedAdminId.value ?? undefined;
|
||||
let result = await sendVerificationCode({
|
||||
account: values.account,
|
||||
password: values.password,
|
||||
admin_id: adminId,
|
||||
});
|
||||
|
||||
if (result?.need_select && result.accounts?.length) {
|
||||
const pickedId = await resolveAccountSelection(result.accounts, {
|
||||
defaultAccountId: result.default_account_id,
|
||||
defaultAccountType: result.default_account_type,
|
||||
});
|
||||
if (!pickedId) {
|
||||
throw new Error('请选择要登录的账号');
|
||||
}
|
||||
selectedAdminId.value = pickedId;
|
||||
adminId = pickedId;
|
||||
result = await sendVerificationCode({
|
||||
account: values.account,
|
||||
password: values.password,
|
||||
admin_id: adminId,
|
||||
});
|
||||
}
|
||||
|
||||
if (result) {
|
||||
message.success('发送成功,请注意查收');
|
||||
if (result.code) {
|
||||
@@ -106,11 +181,31 @@ async function handleLogin(values: Recordable<any>) {
|
||||
return;
|
||||
}
|
||||
|
||||
await authStore.authLogin({
|
||||
let adminId = selectedAdminId.value ?? undefined;
|
||||
let result = await authStore.authLogin({
|
||||
account,
|
||||
password: values.password,
|
||||
code: values.code,
|
||||
admin_id: adminId,
|
||||
});
|
||||
|
||||
if (result?.need_select && result.accounts?.length) {
|
||||
const pickedId = await resolveAccountSelection(result.accounts, {
|
||||
defaultAccountId: result.default_account_id,
|
||||
defaultAccountType: result.default_account_type,
|
||||
});
|
||||
if (!pickedId) {
|
||||
message.warning('请选择要登录的账号');
|
||||
return;
|
||||
}
|
||||
selectedAdminId.value = pickedId;
|
||||
await authStore.authLogin({
|
||||
account,
|
||||
password: values.password,
|
||||
code: values.code,
|
||||
admin_id: pickedId,
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -127,4 +222,10 @@ async function handleLogin(values: Recordable<any>) {
|
||||
:show-third-party-login="false"
|
||||
@submit="handleLogin"
|
||||
/>
|
||||
<AccountSelectModal
|
||||
ref="accountSelectRef"
|
||||
:accounts="pendingAccounts"
|
||||
@confirm="handleAccountConfirm"
|
||||
@cancel="handleAccountCancel"
|
||||
/>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user