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
Ryan Wang 2022-10-11 17:00:14 +08:00 committed by GitHub
parent db39c3a1d7
commit 64aa137bab
8 changed files with 104 additions and 22 deletions

View File

@ -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">

View File

@ -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);
};

View File

@ -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");

View File

@ -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>

View File

@ -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();
});
}

View File

@ -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();

View File

@ -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);

View File

@ -48,6 +48,7 @@ axiosInstance.interceptors.response.use(
},
async (error) => {
if (error.response.status === 401) {
localStorage.removeItem("logged_in");
router.push({
name: "Login",
});