mirror of https://github.com/halo-dev/halo-admin
refactor: managing logged-in user states with pinia (#699)
#### 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 ```pull/703/head
parent
b79eccb6a2
commit
d8805f30a0
|
@ -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<User>("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);
|
|||
</div>
|
||||
<RoutesMenu :menus="menus" />
|
||||
<div class="current-profile">
|
||||
<div v-if="currentUser?.spec.avatar" class="profile-avatar">
|
||||
<div v-if="userStore.currentUser?.spec.avatar" class="profile-avatar">
|
||||
<VAvatar
|
||||
:src="currentUser?.spec.avatar"
|
||||
:alt="currentUser?.spec.displayName"
|
||||
:src="userStore.currentUser?.spec.avatar"
|
||||
:alt="userStore.currentUser?.spec.displayName"
|
||||
size="md"
|
||||
circle
|
||||
></VAvatar>
|
||||
</div>
|
||||
<div class="profile-name">
|
||||
<div class="flex text-sm font-medium">
|
||||
{{ currentUser?.spec.displayName }}
|
||||
{{ userStore.currentUser?.spec.displayName }}
|
||||
</div>
|
||||
<div class="flex">
|
||||
<VTag>
|
||||
|
@ -237,7 +240,7 @@ onMounted(generateMenus);
|
|||
type="secondary"
|
||||
:route="{
|
||||
name: 'UserDetail',
|
||||
params: { name: currentUser?.metadata.name },
|
||||
params: { name: userStore.currentUser?.metadata.name },
|
||||
}"
|
||||
>
|
||||
个人资料
|
||||
|
|
17
src/main.ts
17
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<User>("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) {
|
||||
|
|
|
@ -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<User>("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 },
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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<LoginForm>({
|
||||
_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<User>("currentUser");
|
||||
if (currentUser) {
|
||||
if (!userStore.isAnonymous) {
|
||||
router.push({ name: "Dashboard" });
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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<UserList>({
|
|||
});
|
||||
const selectedUserNames = ref<string[]>([]);
|
||||
const selectedUser = ref<User>();
|
||||
const currentUser = inject<User>("currentUser");
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
let fuse: Fuse<User> | 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(() => {
|
|||
修改密码
|
||||
</VButton>
|
||||
<VButton
|
||||
v-if="currentUser?.metadata.name !== user.metadata.name"
|
||||
v-if="
|
||||
userStore.currentUser?.metadata.name !== user.metadata.name
|
||||
"
|
||||
v-close-popper
|
||||
block
|
||||
@click="handleOpenGrantPermissionModal(user)"
|
||||
|
@ -472,7 +476,9 @@ onMounted(() => {
|
|||
分配角色
|
||||
</VButton>
|
||||
<VButton
|
||||
v-if="currentUser?.metadata.name !== user.metadata.name"
|
||||
v-if="
|
||||
userStore.currentUser?.metadata.name !== user.metadata.name
|
||||
"
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
<script lang="ts" setup>
|
||||
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<User>("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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue