feat(auth): 登录多租户选择弹窗 + auth store 登录签名调整
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b03e9ef62b
commit
42b0a6a479
@ -1,6 +1,24 @@
|
|||||||
import { http } from '@/api/http'
|
import { http } from '@/api/http'
|
||||||
import type { ResourceNode } from '@/types/resource'
|
import type { ResourceNode } from '@/types/resource'
|
||||||
|
|
||||||
|
export interface LoginSuccess {
|
||||||
|
accessToken: string
|
||||||
|
refreshToken: string
|
||||||
|
expiresIn: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginNeedTenant {
|
||||||
|
needSelectTenant: true
|
||||||
|
tenants: { tenantCode: string; tenantName: string }[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LoginResult = LoginSuccess | LoginNeedTenant
|
||||||
|
|
||||||
|
/** 登录:缺省 tenantCode 时按用户名自动探测;多租户返回 needSelectTenant。 */
|
||||||
|
export function login(username: string, password: string, tenantCode?: string): Promise<LoginResult> {
|
||||||
|
return http.post<LoginResult>('/auth/login', { username, password, ...(tenantCode ? { tenantCode } : {}) })
|
||||||
|
}
|
||||||
|
|
||||||
/** 当前用户菜单树。 */
|
/** 当前用户菜单树。 */
|
||||||
export function fetchMenus(): Promise<ResourceNode[]> {
|
export function fetchMenus(): Promise<ResourceNode[]> {
|
||||||
return http.get<ResourceNode[]>('/auth/menus')
|
return http.get<ResourceNode[]>('/auth/menus')
|
||||||
|
|||||||
@ -101,6 +101,20 @@ meta:
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<el-dialog v-model="tenantDialogVisible" title="选择登录租户" width="360px" :close-on-click-modal="false">
|
||||||
|
<div class="login__tenants">
|
||||||
|
<div
|
||||||
|
v-for="t in tenantOptions"
|
||||||
|
:key="t.tenantCode"
|
||||||
|
class="login__tenant-item"
|
||||||
|
@click="selectTenant(t.tenantCode)"
|
||||||
|
>
|
||||||
|
<span>{{ t.tenantName }}</span>
|
||||||
|
<span class="login__tenant-code">{{ t.tenantCode }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -136,6 +150,8 @@ const captcha = ref('')
|
|||||||
const captchaCode = ref('')
|
const captchaCode = ref('')
|
||||||
const remember = ref(true)
|
const remember = ref(true)
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
|
const tenantOptions = ref<{ tenantCode: string; tenantName: string }[]>([])
|
||||||
|
const tenantDialogVisible = ref(false)
|
||||||
|
|
||||||
const rootStyle = computed(() => (config.background ? { background: config.background } : {}))
|
const rootStyle = computed(() => (config.background ? { background: config.background } : {}))
|
||||||
|
|
||||||
@ -173,11 +189,16 @@ function refreshCaptcha(): void {
|
|||||||
refreshCaptcha()
|
refreshCaptcha()
|
||||||
|
|
||||||
const { loading, run: doLogin } = useRequest(
|
const { loading, run: doLogin } = useRequest(
|
||||||
() => auth.login(config.tenantCode, model.username, model.password),
|
() => auth.login(model.username, model.password),
|
||||||
{
|
{
|
||||||
manual: true,
|
manual: true,
|
||||||
onSuccess: () => {
|
onSuccess: (data) => {
|
||||||
|
if (data && 'needSelectTenant' in data && data.needSelectTenant) {
|
||||||
|
tenantOptions.value = data.tenants
|
||||||
|
tenantDialogVisible.value = true
|
||||||
|
} else {
|
||||||
router.push(config.homePath)
|
router.push(config.homePath)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onError: (e) => {
|
onError: (e) => {
|
||||||
error.value = (e as Error).message
|
error.value = (e as Error).message
|
||||||
@ -185,6 +206,16 @@ const { loading, run: doLogin } = useRequest(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async function selectTenant(tenantCode: string): Promise<void> {
|
||||||
|
tenantDialogVisible.value = false
|
||||||
|
try {
|
||||||
|
await auth.login(model.username, model.password, tenantCode)
|
||||||
|
router.push(config.homePath)
|
||||||
|
} catch (e) {
|
||||||
|
error.value = (e as Error).message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function onSubmit(): Promise<void> {
|
async function onSubmit(): Promise<void> {
|
||||||
if (loading.value) return
|
if (loading.value) return
|
||||||
error.value = ''
|
error.value = ''
|
||||||
@ -355,6 +386,25 @@ function onRegister(): void {
|
|||||||
color: var(--el-color-danger);
|
color: var(--el-color-danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- 租户选择 ---------- */
|
||||||
|
.login__tenants {
|
||||||
|
@apply flex flex-col gap-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login__tenant-item {
|
||||||
|
@apply flex cursor-pointer items-center justify-between rounded-base border border-solid px-4 py-3 transition-colors;
|
||||||
|
border-color: var(--wm-border-color-light);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@apply border-primary;
|
||||||
|
background: var(--el-color-primary-light-9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login__tenant-code {
|
||||||
|
@apply text-xs text-text-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- 卡片布局(card) ---------- */
|
/* ---------- 卡片布局(card) ---------- */
|
||||||
.login__panel-head {
|
.login__panel-head {
|
||||||
@apply mb-lg text-center;
|
@apply mb-lg text-center;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { http } from '@/api/http';
|
import { http } from '@/api/http';
|
||||||
|
import { login as loginApi, type LoginResult } from '@/api/auth';
|
||||||
import { clearTokens, getAccessToken, getRefreshToken, setTokens } from '@/utils/token';
|
import { clearTokens, getAccessToken, getRefreshToken, setTokens } from '@/utils/token';
|
||||||
|
|
||||||
export const useAuthStore = defineStore('authStore', () => {
|
export const useAuthStore = defineStore('authStore', () => {
|
||||||
@ -9,16 +10,15 @@ export const useAuthStore = defineStore('authStore', () => {
|
|||||||
|
|
||||||
const isAuthenticated = computed(() => !!accessToken.value);
|
const isAuthenticated = computed(() => !!accessToken.value);
|
||||||
|
|
||||||
async function login(tenantCode: string, username: string, password: string): Promise<void> {
|
async function login(username: string, password: string, tenantCode?: string): Promise<LoginResult> {
|
||||||
const data = await http.post<{ accessToken: string; refreshToken: string }>('/auth/login', {
|
const data = await loginApi(username, password, tenantCode);
|
||||||
tenantCode,
|
if ('accessToken' in data) {
|
||||||
username,
|
|
||||||
password,
|
|
||||||
});
|
|
||||||
setTokens(data.accessToken, data.refreshToken);
|
setTokens(data.accessToken, data.refreshToken);
|
||||||
accessToken.value = data.accessToken;
|
accessToken.value = data.accessToken;
|
||||||
refreshToken.value = data.refreshToken;
|
refreshToken.value = data.refreshToken;
|
||||||
}
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
async function logout(): Promise<void> {
|
async function logout(): Promise<void> {
|
||||||
const rt = refreshToken.value;
|
const rt = refreshToken.value;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user