fix: it cannot be re-uploaded when the user's avatar cannot be obtained (#5270)

#### What type of PR is this?

/area console
/kind bug
/milestone 2.12.x

#### What this PR does / why we need it:

修复当头像因为后端服务无法正常变更时,前端因为一直处于 refetch 状态导致无法重新上传的问题。

#### Which issue(s) this PR fixes:

Fixes https://github.com/halo-dev/halo/issues/4852

#### Special notes for your reviewer:

1. 使用 https://github.com/AirboZH/plugin-uposs 存储服务创建一个存储策略。
2. 用户设置中的头像存储位置改为此存储策略。
3. 上传头像,观测是否一直处于重新获取的状态。

#### Does this PR introduce a user-facing change?

```release-note
优化头像上传,防止因为后端服务异常导致无法重新上传。
```
pull/5276/head
Ryan Wang 2024-01-29 16:06:02 +08:00 committed by GitHub
parent cf72cbccbd
commit 192b238e37
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 41 additions and 43 deletions

View File

@ -5,7 +5,6 @@ import {
VTabbar,
VDropdown,
VDropdownItem,
VLoading,
} from "@halo-dev/components";
import { computed, provide, ref, type Ref } from "vue";
import { useRoute } from "vue-router";
@ -15,7 +14,6 @@ import UserPasswordChangeModal from "./components/UserPasswordChangeModal.vue";
import { usePermission } from "@/utils/permission";
import { useQuery } from "@tanstack/vue-query";
import { useI18n } from "vue-i18n";
import { rbacAnnotations } from "@/constants/annotations";
import UserAvatar from "@/components/user-avatar/UserAvatar.vue";
import type { Raw } from "vue";
import type { Component } from "vue";
@ -45,7 +43,6 @@ const { params } = useRoute();
const {
data: user,
isFetching,
isLoading,
refetch,
} = useQuery({
@ -56,13 +53,6 @@ const {
});
return data;
},
refetchInterval: (data) => {
const annotations = data?.user.metadata.annotations;
return annotations?.[rbacAnnotations.AVATAR_ATTACHMENT_NAME] !==
annotations?.[rbacAnnotations.LAST_AVATAR_ATTACHMENT_NAME]
? 1000
: false;
},
enabled: computed(() => !!params.name),
});
@ -105,8 +95,7 @@ function handleRouteToUC() {
<div class="flex items-center justify-between">
<div class="flex flex-row items-center gap-5">
<div class="group relative h-20 w-20">
<VLoading v-if="isFetching" class="h-full w-full" />
<UserAvatar v-else />
<UserAvatar :name="user?.user.metadata.name" />
</div>
<div class="block">
<h1 class="truncate text-lg font-bold text-gray-900">

View File

@ -11,21 +11,23 @@ import {
VSpace,
Toast,
Dialog,
VLoading,
} from "@halo-dev/components";
import { ref, defineAsyncComponent, type Ref } from "vue";
import type { DetailedUser } from "@halo-dev/api-client";
import { usePermission } from "@/utils/permission";
import { useQueryClient } from "@tanstack/vue-query";
import { useQuery, useQueryClient } from "@tanstack/vue-query";
import { useI18n } from "vue-i18n";
import { useFileDialog } from "@vueuse/core";
import { inject } from "vue";
import { computed } from "vue";
import { rbacAnnotations } from "@/constants/annotations";
const props = withDefaults(
defineProps<{
name?: string;
isCurrentUser?: boolean;
}>(),
{
name: "-",
isCurrentUser: false,
}
);
@ -34,6 +36,31 @@ const queryClient = useQueryClient();
const { currentUserHasPermission } = usePermission();
const { t } = useI18n();
const { data: avatar, isFetching } = useQuery({
queryKey: ["user-avatar", props.name, props.isCurrentUser],
queryFn: async () => {
const { data } = props.isCurrentUser
? await apiClient.user.getCurrentUserDetail()
: await apiClient.user.getUserDetail({
name: props.name,
});
const annotations = data?.user.metadata.annotations;
// Check avatar has been updated. if not, we need retry.
if (
annotations?.[rbacAnnotations.AVATAR_ATTACHMENT_NAME] !==
annotations?.[rbacAnnotations.LAST_AVATAR_ATTACHMENT_NAME]
) {
throw new Error("Avatar is not updated");
}
return data.user.spec.avatar || "";
},
retry: 5,
retryDelay: 1000,
});
const UserAvatarCropper = defineAsyncComponent(
() => import("./UserAvatarCropper.vue")
);
@ -48,8 +75,6 @@ const { open, reset, onChange } = useFileDialog({
multiple: false,
});
const user = inject<Ref<DetailedUser | undefined>>("user");
const userAvatarCropper = ref<IUserAvatarCropperType>();
const visibleCropperModal = ref(false);
const originalFile = ref<File>() as Ref<File>;
@ -67,18 +92,15 @@ onChange((files) => {
const uploadSaving = ref(false);
const handleUploadAvatar = () => {
userAvatarCropper.value?.getCropperFile().then((file) => {
if (!user?.value) {
return;
}
uploadSaving.value = true;
apiClient.user
.uploadUserAvatar({
name: props.isCurrentUser ? "-" : user.value.user.metadata.name,
name: props.isCurrentUser ? "-" : props.name,
file: file,
})
.then(() => {
queryClient.invalidateQueries({ queryKey: ["user-avatar"] });
queryClient.invalidateQueries({ queryKey: ["user-detail"] });
handleCloseCropperModal();
})
@ -99,15 +121,12 @@ const handleRemoveCurrentAvatar = () => {
confirmText: t("core.common.buttons.confirm"),
cancelText: t("core.common.buttons.cancel"),
onConfirm: async () => {
if (!user?.value) {
return;
}
apiClient.user
.deleteUserAvatar({
name: props.isCurrentUser ? "-" : user.value.user.metadata.name,
name: props.isCurrentUser ? "-" : props.name,
})
.then(() => {
queryClient.invalidateQueries({ queryKey: ["user-avatar"] });
queryClient.invalidateQueries({ queryKey: ["user-detail"] });
})
.catch(() => {
@ -128,15 +147,16 @@ const changeUploadAvatar = () => {
};
const hasAvatar = computed(() => {
return !!user?.value?.user.spec.avatar;
return !!avatar.value;
});
</script>
<template>
<div class="group h-full w-full">
<VLoading v-if="isFetching" class="h-full w-full" />
<div v-else class="group h-full w-full">
<VAvatar
:src="user?.user.spec.avatar"
:alt="user?.user.spec.displayName"
:src="avatar"
:alt="props.name"
circle
width="100%"
height="100%"

View File

@ -5,7 +5,6 @@ import {
VTabbar,
VDropdown,
VDropdownItem,
VLoading,
} from "@halo-dev/components";
import { computed, provide, ref, type Ref } from "vue";
import type { DetailedUser } from "@halo-dev/api-client";
@ -13,7 +12,6 @@ import ProfileEditingModal from "./components/ProfileEditingModal.vue";
import PasswordChangeModal from "./components/PasswordChangeModal.vue";
import { useQuery } from "@tanstack/vue-query";
import { useI18n } from "vue-i18n";
import { rbacAnnotations } from "@/constants/annotations";
import UserAvatar from "@/components/user-avatar/UserAvatar.vue";
import type { Raw } from "vue";
import type { Component } from "vue";
@ -41,7 +39,6 @@ const passwordChangeModal = ref(false);
const {
data: user,
isFetching,
isLoading,
refetch,
} = useQuery({
@ -50,13 +47,6 @@ const {
const { data } = await apiClient.user.getCurrentUserDetail();
return data;
},
refetchInterval: (data) => {
const annotations = data?.user.metadata.annotations;
return annotations?.[rbacAnnotations.AVATAR_ATTACHMENT_NAME] !==
annotations?.[rbacAnnotations.LAST_AVATAR_ATTACHMENT_NAME]
? 1000
: false;
},
});
provide<Ref<DetailedUser | undefined>>("user", user);
@ -110,8 +100,7 @@ const activeTab = useRouteQuery<string>("tab", tabs[0].id, {
<div class="flex items-center justify-between">
<div class="flex flex-row items-center gap-5">
<div class="group relative h-20 w-20">
<VLoading v-if="isFetching" class="h-full w-full" />
<UserAvatar v-else is-current-user />
<UserAvatar :name="user?.user.metadata.name" is-current-user />
</div>
<div class="block">
<h1 class="truncate text-lg font-bold text-gray-900">