From d8805f30a086b23d6a0b737b39f8e82e13c68e65 Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Wed, 23 Nov 2022 16:39:29 +0800 Subject: [PATCH] refactor: managing logged-in user states with pinia (#699) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind improvement /milestone 2.0 #### What this PR does / why we need it: 使用 Pinia 管理已授权用户信息,并提供判断是否是 `anonymousUser` 的方式。 适配 https://github.com/halo-dev/halo/pull/2729 #### Special notes for your reviewer: /cc @halo-dev/sig-halo-console 测试流程: 1. Halo 需要切换到 https://github.com/halo-dev/halo/pull/2729 的分支。 2. 测试 Console 端的登录、退出等流程。 #### Does this PR introduce a user-facing change? ```release-note None ``` --- src/layouts/BasicLayout.vue | 21 ++++++++------- src/main.ts | 17 +++++------- .../dashboard/widgets/QuickLinkWidget.vue | 8 +++--- src/modules/system/users/Login.vue | 12 ++++++--- src/modules/system/users/UserList.vue | 16 ++++++++---- .../components/UserPasswordChangeModal.vue | 7 ++--- src/router/guards/auth-check.ts | 6 ++++- src/stores/user.ts | 26 +++++++++++++++++++ 8 files changed, 77 insertions(+), 36 deletions(-) create mode 100644 src/stores/user.ts diff --git a/src/layouts/BasicLayout.vue b/src/layouts/BasicLayout.vue index 1b3125f1..4a616bc0 100644 --- a/src/layouts/BasicLayout.vue +++ b/src/layouts/BasicLayout.vue @@ -11,7 +11,6 @@ import { } from "@halo-dev/components"; import { RoutesMenu } from "@/components/menu/RoutesMenu"; import type { MenuGroupType, MenuItemType } from "@halo-dev/console-shared"; -import type { User } from "@halo-dev/api-client"; import IconLogo from "~icons/core/logo?width=5rem&height=2rem"; import { RouterView, @@ -19,13 +18,14 @@ import { useRouter, type RouteRecordRaw, } from "vue-router"; -import { computed, inject, onMounted, onUnmounted, ref } from "vue"; +import { computed, onMounted, onUnmounted, ref } from "vue"; import axios from "axios"; import GlobalSearchModal from "@/components/global-search/GlobalSearchModal.vue"; import { coreMenuGroups } from "@/router/routes.config"; import sortBy from "lodash.sortby"; import { useRoleStore } from "@/stores/role"; import { hasPermission } from "@/utils/permission"; +import { useUserStore } from "@/stores/user"; const route = useRoute(); const router = useRouter(); @@ -33,7 +33,7 @@ const router = useRouter(); const moreMenuVisible = ref(false); const moreMenuRootVisible = ref(false); -const currentUser = inject("currentUser"); +const userStore = useUserStore(); const handleLogout = () => { Dialog.warning({ @@ -43,6 +43,9 @@ const handleLogout = () => { await axios.post(`${import.meta.env.VITE_API_URL}/logout`, undefined, { withCredentials: true, }); + + await userStore.fetchCurrentUser(); + router.replace({ name: "Login" }); } catch (error) { console.error("Failed to logout", error); @@ -53,7 +56,7 @@ const handleLogout = () => { const currentRole = computed(() => { return JSON.parse( - currentUser?.metadata.annotations?.[ + userStore.currentUser?.metadata.annotations?.[ "rbac.authorization.halo.run/role-names" ] || "[]" )[0]; @@ -203,17 +206,17 @@ onMounted(generateMenus);
-
+
- {{ currentUser?.spec.displayName }} + {{ userStore.currentUser?.spec.displayName }}
@@ -237,7 +240,7 @@ onMounted(generateMenus); type="secondary" :route="{ name: 'UserDetail', - params: { name: currentUser?.metadata.name }, + params: { name: userStore.currentUser?.metadata.name }, }" > 个人资料 diff --git a/src/main.ts b/src/main.ts index 7fa378e0..f13149c7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,12 +13,12 @@ import { setupComponents } from "./setup/setupComponents"; import { coreModules } from "./modules"; import { useScriptTag } from "@vueuse/core"; import { usePluginStore } from "@/stores/plugin"; -import type { User } from "@halo-dev/api-client"; import { hasPermission } from "@/utils/permission"; import { useRoleStore } from "@/stores/role"; import type { RouteRecordRaw } from "vue-router"; import { useThemeStore } from "./stores/theme"; import { useSystemStatesStore } from "./stores/system-states"; +import { useUserStore } from "./stores/user"; const app = createApp(App); @@ -177,13 +177,7 @@ async function loadPluginModules() { } } -let currentUser: User | undefined = undefined; - -async function loadCurrentUser() { - const { data: user } = await apiClient.user.getCurrentUserDetail(); - app.provide("currentUser", user); - currentUser = user; - +async function loadUserPermissions() { const { data: currentPermissions } = await apiClient.user.getPermissions({ name: "-", }); @@ -228,12 +222,14 @@ async function initApp() { try { loadCoreModules(); - await loadCurrentUser(); + const userStore = useUserStore(); + await userStore.fetchCurrentUser(); - if (!currentUser) { + if (userStore.isAnonymous) { return; } + await loadUserPermissions(); await loadActivatedTheme(); try { @@ -242,6 +238,7 @@ async function initApp() { console.error("Failed to load plugins", e); } + // load system setup state const systemStateStore = useSystemStatesStore(); await systemStateStore.fetchSystemStates(); } catch (e) { diff --git a/src/modules/dashboard/widgets/QuickLinkWidget.vue b/src/modules/dashboard/widgets/QuickLinkWidget.vue index b69a9212..258d63aa 100644 --- a/src/modules/dashboard/widgets/QuickLinkWidget.vue +++ b/src/modules/dashboard/widgets/QuickLinkWidget.vue @@ -11,10 +11,10 @@ import { VCard, IconUserLine, } from "@halo-dev/components"; -import { inject, markRaw, ref, type Component } from "vue"; +import { markRaw, ref, type Component } from "vue"; import { useRouter } from "vue-router"; -import type { User } from "@halo-dev/api-client"; import ThemePreviewModal from "@/modules/interface/themes/components/preview/ThemePreviewModal.vue"; +import { useUserStore } from "@/stores/user"; interface Action { icon: Component; @@ -23,7 +23,7 @@ interface Action { permissions?: string[]; } -const currentUser = inject("currentUser"); +const userStore = useUserStore(); const router = useRouter(); @@ -36,7 +36,7 @@ const actions: Action[] = [ action: () => { router.push({ name: "UserDetail", - params: { name: currentUser?.metadata.name }, + params: { name: userStore.currentUser?.metadata.name }, }); }, }, diff --git a/src/modules/system/users/Login.vue b/src/modules/system/users/Login.vue index 517eeeee..0e387dca 100644 --- a/src/modules/system/users/Login.vue +++ b/src/modules/system/users/Login.vue @@ -6,14 +6,14 @@ import { Toast, } from "@halo-dev/components"; import qs from "qs"; -import { inject, onBeforeMount, onMounted, ref } from "vue"; +import { 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"; import IconLogo from "~icons/core/logo?width=5rem&height=2rem"; import { randomUUID } from "@/utils/id"; +import { useUserStore } from "@/stores/user"; interface LoginForm { _csrf: string; @@ -21,6 +21,8 @@ interface LoginForm { password: string; } +const userStore = useUserStore(); + const loginForm = ref({ _csrf: "", username: "", @@ -48,6 +50,9 @@ const handleLogin = async () => { }, } ); + + await userStore.fetchCurrentUser(); + localStorage.setItem("logged_in", "true"); router.go(0); } catch (e) { @@ -61,8 +66,7 @@ const handleLogin = async () => { }; onBeforeMount(() => { - const currentUser = inject("currentUser"); - if (currentUser) { + if (!userStore.isAnonymous) { router.push({ name: "Dashboard" }); } }); diff --git a/src/modules/system/users/UserList.vue b/src/modules/system/users/UserList.vue index f3def5fd..7dfee77b 100644 --- a/src/modules/system/users/UserList.vue +++ b/src/modules/system/users/UserList.vue @@ -19,7 +19,7 @@ import { import UserEditingModal from "./components/UserEditingModal.vue"; import UserPasswordChangeModal from "./components/UserPasswordChangeModal.vue"; import GrantPermissionModal from "./components/GrantPermissionModal.vue"; -import { computed, inject, onMounted, ref, watch } from "vue"; +import { computed, onMounted, ref, watch } from "vue"; import { apiClient } from "@/utils/api-client"; import type { User, UserList } from "@halo-dev/api-client"; import { rbacAnnotations } from "@/constants/annotations"; @@ -27,6 +27,7 @@ import { formatDatetime } from "@/utils/date"; import { useRouteQuery } from "@vueuse/router"; import Fuse from "fuse.js"; import { usePermission } from "@/utils/permission"; +import { useUserStore } from "@/stores/user"; const { currentUserHasPermission } = usePermission(); @@ -48,7 +49,8 @@ const users = ref({ }); const selectedUserNames = ref([]); const selectedUser = ref(); -const currentUser = inject("currentUser"); + +const userStore = useUserStore(); let fuse: Fuse | undefined = undefined; @@ -118,7 +120,7 @@ const handleDeleteInBatch = async () => { confirmType: "danger", onConfirm: async () => { const userNamesToDelete = selectedUserNames.value.filter( - (name) => name != currentUser?.metadata.name + (name) => name != userStore.currentUser?.metadata.name ); await Promise.all( userNamesToDelete.map((name) => { @@ -464,7 +466,9 @@ onMounted(() => { 修改密码 { 分配角色 import { VButton, VModal, VSpace } from "@halo-dev/components"; import SubmitButton from "@/components/button/SubmitButton.vue"; -import { inject, ref, watch } from "vue"; +import { ref, watch } from "vue"; import type { User } from "@halo-dev/api-client"; import { apiClient } from "@/utils/api-client"; import cloneDeep from "lodash.clonedeep"; import { reset } from "@formkit/core"; import { setFocus } from "@/formkit/utils/focus"; +import { useUserStore } from "@/stores/user"; const props = withDefaults( defineProps<{ @@ -24,7 +25,7 @@ const emit = defineEmits<{ (event: "close"): void; }>(); -const currentUser = inject("currentUser"); +const userStore = useUserStore(); interface PasswordChangeFormState { password: string; @@ -69,7 +70,7 @@ const handleChangePassword = async () => { const changePasswordRequest = cloneDeep(formState.value); delete changePasswordRequest.password_confirm; - if (props.user?.metadata.name === currentUser?.metadata.name) { + if (props.user?.metadata.name === userStore.currentUser?.metadata.name) { await apiClient.user.changePassword({ name: "-", changePasswordRequest, diff --git a/src/router/guards/auth-check.ts b/src/router/guards/auth-check.ts index f937eafe..f2c5f393 100644 --- a/src/router/guards/auth-check.ts +++ b/src/router/guards/auth-check.ts @@ -1,3 +1,4 @@ +import { useUserStore } from "@/stores/user"; import type { Router } from "vue-router"; export function setupAuthCheckGuard(router: Router) { @@ -6,7 +7,10 @@ export function setupAuthCheckGuard(router: Router) { next(); return; } - if (localStorage.getItem("logged_in") !== "true") { + + const userStore = useUserStore(); + + if (localStorage.getItem("logged_in") !== "true" || userStore.isAnonymous) { next({ name: "Login" }); return; } diff --git a/src/stores/user.ts b/src/stores/user.ts new file mode 100644 index 00000000..d1d575b5 --- /dev/null +++ b/src/stores/user.ts @@ -0,0 +1,26 @@ +import { apiClient } from "@/utils/api-client"; +import type { User } from "@halo-dev/api-client"; +import { defineStore } from "pinia"; + +interface UserStoreState { + currentUser?: User; + isAnonymous: boolean; +} + +export const useUserStore = defineStore("user", { + state: (): UserStoreState => ({ + currentUser: undefined, + isAnonymous: true, + }), + actions: { + async fetchCurrentUser() { + try { + const { data } = await apiClient.user.getCurrentUserDetail(); + this.currentUser = data; + this.isAnonymous = data.metadata.name === "anonymousUser"; + } catch (e) { + console.error("Failed to fetch current user", e); + } + }, + }, +});