diff --git a/packages/shared/src/layouts/BasicLayout.vue b/packages/shared/src/layouts/BasicLayout.vue index f4fd0673c..e399218ba 100644 --- a/packages/shared/src/layouts/BasicLayout.vue +++ b/packages/shared/src/layouts/BasicLayout.vue @@ -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("menus"); const minimenus = inject("minimenus"); const route = useRoute(); const router = useRouter(); +const dialog = useDialog(); const moreMenuVisible = ref(false); const moreMenuRootVisible = ref(false); const currentUser = inject("currentUser"); +const apiUrl = inject("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); -
-
+ +
diff --git a/src/components/global-search/GlobalSearchModal.vue b/src/components/global-search/GlobalSearchModal.vue index d50b614d5..1f972b898 100644 --- a/src/components/global-search/GlobalSearchModal.vue +++ b/src/components/global-search/GlobalSearchModal.vue @@ -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); }; diff --git a/src/main.ts b/src/main.ts index a82939e65..56de89255 100644 --- a/src/main.ts +++ b/src/main.ts @@ -214,6 +214,7 @@ async function initApp() { } finally { app.provide("menus", menus); app.provide("minimenus", minimenus); + app.provide("apiUrl", import.meta.env.VITE_API_URL); app.use(router); app.mount("#app"); diff --git a/src/modules/system/users/Login.vue b/src/modules/system/users/Login.vue index db7f7cdfc..e2eeb0486 100644 --- a/src/modules/system/users/Login.vue +++ b/src/modules/system/users/Login.vue @@ -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({ 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("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(() => { { - + 登录 diff --git a/src/router/guards/auth-check.ts b/src/router/guards/auth-check.ts new file mode 100644 index 000000000..f937eafe0 --- /dev/null +++ b/src/router/guards/auth-check.ts @@ -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(); + }); +} diff --git a/src/router/guards/check-states.ts b/src/router/guards/check-states.ts index acb3fff5e..10b7a3eb6 100644 --- a/src/router/guards/check-states.ts +++ b/src/router/guards/check-states.ts @@ -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(); diff --git a/src/router/index.ts b/src/router/index.ts index d3f43d71c..ce86dac46 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -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); diff --git a/src/utils/api-client.ts b/src/utils/api-client.ts index 5750a1d2b..eae345260 100644 --- a/src/utils/api-client.ts +++ b/src/utils/api-client.ts @@ -48,6 +48,7 @@ axiosInstance.interceptors.response.use( }, async (error) => { if (error.response.status === 401) { + localStorage.removeItem("logged_in"); router.push({ name: "Login", });