mirror of https://github.com/halo-dev/halo
refactor: logic of user login (halo-dev/console#636)
#### What type of PR is this? /kind improvement /milestone 2.0 #### What this PR does / why we need it: 优化用户登录的逻辑。 适配:https://github.com/halo-dev/halo/pull/2528 #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/2506 #### Special notes for your reviewer: #### Does this PR introduce a user-facing change? ```release-note 优化用户登录的逻辑 ```pull/3445/head
parent
db39c3a1d7
commit
64aa137bab
|
@ -6,27 +6,49 @@ import {
|
|||
VRoutesMenu,
|
||||
VTag,
|
||||
VAvatar,
|
||||
VSpace,
|
||||
VButton,
|
||||
useDialog,
|
||||
} from "@halo-dev/components";
|
||||
import type { MenuGroupType, MenuItemType } from "../types/menus";
|
||||
import type { User } from "@halo-dev/api-client";
|
||||
import logo from "@/assets/logo.svg";
|
||||
import { RouterView, useRoute, useRouter } from "vue-router";
|
||||
import { computed, inject, ref, type Ref } from "vue";
|
||||
import axios from "axios";
|
||||
|
||||
const menus = inject<MenuGroupType[]>("menus");
|
||||
const minimenus = inject<MenuItemType[]>("minimenus");
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const dialog = useDialog();
|
||||
|
||||
const moreMenuVisible = ref(false);
|
||||
const moreMenuRootVisible = ref(false);
|
||||
|
||||
const currentUser = inject<User>("currentUser");
|
||||
const apiUrl = inject<string>("apiUrl");
|
||||
|
||||
const handleRouteToProfile = () => {
|
||||
router.push({ path: `/users/${currentUser?.metadata.name}/detail` });
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
dialog.warning({
|
||||
title: "是否确认退出登录?",
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await axios.post(`${apiUrl}/logout`, undefined, {
|
||||
withCredentials: true,
|
||||
});
|
||||
router.replace({ name: "Login" });
|
||||
} catch (error) {
|
||||
console.error("Failed to logout", error);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const currentRole = computed(() => {
|
||||
return JSON.parse(
|
||||
currentUser?.metadata.annotations?.[
|
||||
|
@ -86,12 +108,33 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
|||
</VTag>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
<FloatingDropdown
|
||||
class="profile-control cursor-pointer rounded p-1 transition-all hover:bg-gray-100"
|
||||
@click="handleRouteToProfile"
|
||||
>
|
||||
<IconMore />
|
||||
</div>
|
||||
<template #popper>
|
||||
<div class="w-48 p-2">
|
||||
<VSpace class="w-full" direction="column">
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="handleRouteToProfile"
|
||||
>
|
||||
个人资料
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="default"
|
||||
@click="handleLogout"
|
||||
>
|
||||
退出登录
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</div>
|
||||
</template>
|
||||
</FloatingDropdown>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="content w-full overflow-y-auto pb-12 mb-safe md:pb-0">
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
IconPages,
|
||||
IconUserSettings,
|
||||
} from "@halo-dev/components";
|
||||
import { computed, markRaw, onMounted, ref, watch, type Component } from "vue";
|
||||
import { computed, markRaw, ref, watch, type Component } from "vue";
|
||||
import Fuse from "fuse.js";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import { usePermission } from "@/utils/permission";
|
||||
|
@ -356,6 +356,8 @@ watch(
|
|||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
handleBuildSearchIndex();
|
||||
|
||||
setTimeout(() => {
|
||||
globalSearchInput.value?.focus();
|
||||
}, 100);
|
||||
|
@ -369,10 +371,6 @@ watch(
|
|||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
handleBuildSearchIndex();
|
||||
});
|
||||
|
||||
const onVisibleChange = (visible: boolean) => {
|
||||
emit("update:visible", visible);
|
||||
};
|
||||
|
|
|
@ -214,6 +214,7 @@ async function initApp() {
|
|||
} finally {
|
||||
app.provide<MenuGroupType[]>("menus", menus);
|
||||
app.provide<MenuItemType[]>("minimenus", minimenus);
|
||||
app.provide<string>("apiUrl", import.meta.env.VITE_API_URL);
|
||||
|
||||
app.use(router);
|
||||
app.mount("#app");
|
||||
|
|
|
@ -3,9 +3,12 @@ import { IconShieldUser, IconUserLine, VButton } from "@halo-dev/components";
|
|||
import { v4 as uuid } from "uuid";
|
||||
import qs from "qs";
|
||||
import logo from "@/assets/logo.svg";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { inject, onBeforeMount, onMounted, ref } from "vue";
|
||||
import { submitForm } from "@formkit/vue";
|
||||
import router from "@/router";
|
||||
import axios from "axios";
|
||||
import type { User } from "@halo-dev/api-client";
|
||||
import { setFocus } from "@/formkit/utils/focus";
|
||||
|
||||
interface LoginForm {
|
||||
_csrf: string;
|
||||
|
@ -18,6 +21,7 @@ const loginForm = ref<LoginForm>({
|
|||
username: "",
|
||||
password: "",
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const handleGenerateToken = async () => {
|
||||
|
@ -29,24 +33,35 @@ const handleGenerateToken = async () => {
|
|||
const handleLogin = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
await fetch(`${import.meta.env.VITE_API_URL}/login`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
credentials: "include",
|
||||
redirect: "manual",
|
||||
body: qs.stringify(loginForm.value),
|
||||
});
|
||||
await router.push({ name: "Dashboard" });
|
||||
await router.go(0);
|
||||
await axios.post(
|
||||
`${import.meta.env.VITE_API_URL}/login`,
|
||||
qs.stringify(loginForm.value),
|
||||
{
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
}
|
||||
);
|
||||
localStorage.setItem("logged_in", "true");
|
||||
router.go(0);
|
||||
} catch (e) {
|
||||
console.error("Failed to login", e);
|
||||
alert("登录失败,用户名或密码错误");
|
||||
loginForm.value.password = "";
|
||||
setFocus("passwordInput");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
const currentUser = inject<User>("currentUser");
|
||||
if (currentUser) {
|
||||
router.push({ name: "Dashboard" });
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
handleGenerateToken();
|
||||
});
|
||||
|
@ -61,7 +76,7 @@ onMounted(() => {
|
|||
name="login-form"
|
||||
:actions="false"
|
||||
type="form"
|
||||
:config="{ animation: 'none' }"
|
||||
:config="{ validationVisibility: 'submit' }"
|
||||
@submit="handleLogin"
|
||||
@keyup.enter="submitForm('login-form')"
|
||||
>
|
||||
|
@ -79,6 +94,7 @@ onMounted(() => {
|
|||
</template>
|
||||
</FormKit>
|
||||
<FormKit
|
||||
id="passwordInput"
|
||||
:validation-messages="{
|
||||
required: '请输入密码',
|
||||
}"
|
||||
|
@ -92,7 +108,12 @@ onMounted(() => {
|
|||
</template>
|
||||
</FormKit>
|
||||
</FormKit>
|
||||
<VButton block type="secondary" @click="submitForm('login-form')">
|
||||
<VButton
|
||||
block
|
||||
:loading="loading"
|
||||
type="secondary"
|
||||
@click="submitForm('login-form')"
|
||||
>
|
||||
登录
|
||||
</VButton>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import type { Router } from "vue-router";
|
||||
|
||||
export function setupAuthCheckGuard(router: Router) {
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.name === "Setup" || to.name === "Login") {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if (localStorage.getItem("logged_in") !== "true") {
|
||||
next({ name: "Login" });
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
|
@ -5,6 +5,7 @@ export function setupCheckStatesGuard(router: Router) {
|
|||
router.beforeEach(async (to, from, next) => {
|
||||
if (to.name === "Setup" || to.name === "Login") {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
const systemStateStore = useSystemStatesStore();
|
||||
|
|
|
@ -2,6 +2,7 @@ import { createRouter, createWebHashHistory } from "vue-router";
|
|||
import routesConfig from "@/router/routes.config";
|
||||
import { setupPermissionGuard } from "./guards/permission";
|
||||
import { setupCheckStatesGuard } from "./guards/check-states";
|
||||
import { setupAuthCheckGuard } from "./guards/auth-check";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||
|
@ -9,6 +10,7 @@ const router = createRouter({
|
|||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||
});
|
||||
|
||||
setupAuthCheckGuard(router);
|
||||
setupPermissionGuard(router);
|
||||
setupCheckStatesGuard(router);
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ axiosInstance.interceptors.response.use(
|
|||
},
|
||||
async (error) => {
|
||||
if (error.response.status === 401) {
|
||||
localStorage.removeItem("logged_in");
|
||||
router.push({
|
||||
name: "Login",
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue