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,
|
VRoutesMenu,
|
||||||
VTag,
|
VTag,
|
||||||
VAvatar,
|
VAvatar,
|
||||||
|
VSpace,
|
||||||
|
VButton,
|
||||||
|
useDialog,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import type { MenuGroupType, MenuItemType } from "../types/menus";
|
import type { MenuGroupType, MenuItemType } from "../types/menus";
|
||||||
import type { User } from "@halo-dev/api-client";
|
import type { User } from "@halo-dev/api-client";
|
||||||
import logo from "@/assets/logo.svg";
|
import logo from "@/assets/logo.svg";
|
||||||
import { RouterView, useRoute, useRouter } from "vue-router";
|
import { RouterView, useRoute, useRouter } from "vue-router";
|
||||||
import { computed, inject, ref, type Ref } from "vue";
|
import { computed, inject, ref, type Ref } from "vue";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
const menus = inject<MenuGroupType[]>("menus");
|
const menus = inject<MenuGroupType[]>("menus");
|
||||||
const minimenus = inject<MenuItemType[]>("minimenus");
|
const minimenus = inject<MenuItemType[]>("minimenus");
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const dialog = useDialog();
|
||||||
|
|
||||||
const moreMenuVisible = ref(false);
|
const moreMenuVisible = ref(false);
|
||||||
const moreMenuRootVisible = ref(false);
|
const moreMenuRootVisible = ref(false);
|
||||||
|
|
||||||
const currentUser = inject<User>("currentUser");
|
const currentUser = inject<User>("currentUser");
|
||||||
|
const apiUrl = inject<string>("apiUrl");
|
||||||
|
|
||||||
const handleRouteToProfile = () => {
|
const handleRouteToProfile = () => {
|
||||||
router.push({ path: `/users/${currentUser?.metadata.name}/detail` });
|
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(() => {
|
const currentRole = computed(() => {
|
||||||
return JSON.parse(
|
return JSON.parse(
|
||||||
currentUser?.metadata.annotations?.[
|
currentUser?.metadata.annotations?.[
|
||||||
|
@ -86,12 +108,33 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
||||||
</VTag>
|
</VTag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<FloatingDropdown
|
||||||
class="profile-control cursor-pointer rounded p-1 transition-all hover:bg-gray-100"
|
class="profile-control cursor-pointer rounded p-1 transition-all hover:bg-gray-100"
|
||||||
@click="handleRouteToProfile"
|
|
||||||
>
|
>
|
||||||
<IconMore />
|
<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>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
<main class="content w-full overflow-y-auto pb-12 mb-safe md:pb-0">
|
<main class="content w-full overflow-y-auto pb-12 mb-safe md:pb-0">
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
IconPages,
|
IconPages,
|
||||||
IconUserSettings,
|
IconUserSettings,
|
||||||
} from "@halo-dev/components";
|
} 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 Fuse from "fuse.js";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
|
@ -356,6 +356,8 @@ watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
|
handleBuildSearchIndex();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
globalSearchInput.value?.focus();
|
globalSearchInput.value?.focus();
|
||||||
}, 100);
|
}, 100);
|
||||||
|
@ -369,10 +371,6 @@ watch(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
handleBuildSearchIndex();
|
|
||||||
});
|
|
||||||
|
|
||||||
const onVisibleChange = (visible: boolean) => {
|
const onVisibleChange = (visible: boolean) => {
|
||||||
emit("update:visible", visible);
|
emit("update:visible", visible);
|
||||||
};
|
};
|
||||||
|
|
|
@ -214,6 +214,7 @@ async function initApp() {
|
||||||
} finally {
|
} finally {
|
||||||
app.provide<MenuGroupType[]>("menus", menus);
|
app.provide<MenuGroupType[]>("menus", menus);
|
||||||
app.provide<MenuItemType[]>("minimenus", minimenus);
|
app.provide<MenuItemType[]>("minimenus", minimenus);
|
||||||
|
app.provide<string>("apiUrl", import.meta.env.VITE_API_URL);
|
||||||
|
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
|
|
|
@ -3,9 +3,12 @@ import { IconShieldUser, IconUserLine, VButton } from "@halo-dev/components";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
import qs from "qs";
|
import qs from "qs";
|
||||||
import logo from "@/assets/logo.svg";
|
import logo from "@/assets/logo.svg";
|
||||||
import { onMounted, ref } from "vue";
|
import { inject, onBeforeMount, onMounted, ref } from "vue";
|
||||||
import { submitForm } from "@formkit/vue";
|
import { submitForm } from "@formkit/vue";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
|
import axios from "axios";
|
||||||
|
import type { User } from "@halo-dev/api-client";
|
||||||
|
import { setFocus } from "@/formkit/utils/focus";
|
||||||
|
|
||||||
interface LoginForm {
|
interface LoginForm {
|
||||||
_csrf: string;
|
_csrf: string;
|
||||||
|
@ -18,6 +21,7 @@ const loginForm = ref<LoginForm>({
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
const handleGenerateToken = async () => {
|
const handleGenerateToken = async () => {
|
||||||
|
@ -29,24 +33,35 @@ const handleGenerateToken = async () => {
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
await fetch(`${import.meta.env.VITE_API_URL}/login`, {
|
await axios.post(
|
||||||
method: "POST",
|
`${import.meta.env.VITE_API_URL}/login`,
|
||||||
headers: {
|
qs.stringify(loginForm.value),
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
{
|
||||||
},
|
withCredentials: true,
|
||||||
credentials: "include",
|
headers: {
|
||||||
redirect: "manual",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
body: qs.stringify(loginForm.value),
|
},
|
||||||
});
|
}
|
||||||
await router.push({ name: "Dashboard" });
|
);
|
||||||
await router.go(0);
|
localStorage.setItem("logged_in", "true");
|
||||||
|
router.go(0);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to login", e);
|
console.error("Failed to login", e);
|
||||||
|
alert("登录失败,用户名或密码错误");
|
||||||
|
loginForm.value.password = "";
|
||||||
|
setFocus("passwordInput");
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
const currentUser = inject<User>("currentUser");
|
||||||
|
if (currentUser) {
|
||||||
|
router.push({ name: "Dashboard" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
handleGenerateToken();
|
handleGenerateToken();
|
||||||
});
|
});
|
||||||
|
@ -61,7 +76,7 @@ onMounted(() => {
|
||||||
name="login-form"
|
name="login-form"
|
||||||
:actions="false"
|
:actions="false"
|
||||||
type="form"
|
type="form"
|
||||||
:config="{ animation: 'none' }"
|
:config="{ validationVisibility: 'submit' }"
|
||||||
@submit="handleLogin"
|
@submit="handleLogin"
|
||||||
@keyup.enter="submitForm('login-form')"
|
@keyup.enter="submitForm('login-form')"
|
||||||
>
|
>
|
||||||
|
@ -79,6 +94,7 @@ onMounted(() => {
|
||||||
</template>
|
</template>
|
||||||
</FormKit>
|
</FormKit>
|
||||||
<FormKit
|
<FormKit
|
||||||
|
id="passwordInput"
|
||||||
:validation-messages="{
|
:validation-messages="{
|
||||||
required: '请输入密码',
|
required: '请输入密码',
|
||||||
}"
|
}"
|
||||||
|
@ -92,7 +108,12 @@ onMounted(() => {
|
||||||
</template>
|
</template>
|
||||||
</FormKit>
|
</FormKit>
|
||||||
</FormKit>
|
</FormKit>
|
||||||
<VButton block type="secondary" @click="submitForm('login-form')">
|
<VButton
|
||||||
|
block
|
||||||
|
:loading="loading"
|
||||||
|
type="secondary"
|
||||||
|
@click="submitForm('login-form')"
|
||||||
|
>
|
||||||
登录
|
登录
|
||||||
</VButton>
|
</VButton>
|
||||||
</div>
|
</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) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
if (to.name === "Setup" || to.name === "Login") {
|
if (to.name === "Setup" || to.name === "Login") {
|
||||||
next();
|
next();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const systemStateStore = useSystemStatesStore();
|
const systemStateStore = useSystemStatesStore();
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { createRouter, createWebHashHistory } from "vue-router";
|
||||||
import routesConfig from "@/router/routes.config";
|
import routesConfig from "@/router/routes.config";
|
||||||
import { setupPermissionGuard } from "./guards/permission";
|
import { setupPermissionGuard } from "./guards/permission";
|
||||||
import { setupCheckStatesGuard } from "./guards/check-states";
|
import { setupCheckStatesGuard } from "./guards/check-states";
|
||||||
|
import { setupAuthCheckGuard } from "./guards/auth-check";
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||||
|
@ -9,6 +10,7 @@ const router = createRouter({
|
||||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setupAuthCheckGuard(router);
|
||||||
setupPermissionGuard(router);
|
setupPermissionGuard(router);
|
||||||
setupCheckStatesGuard(router);
|
setupCheckStatesGuard(router);
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ axiosInstance.interceptors.response.use(
|
||||||
},
|
},
|
||||||
async (error) => {
|
async (error) => {
|
||||||
if (error.response.status === 401) {
|
if (error.response.status === 401) {
|
||||||
|
localStorage.removeItem("logged_in");
|
||||||
router.push({
|
router.push({
|
||||||
name: "Login",
|
name: "Login",
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue