refactor: use tanstack query to refactor attachments-related fetching (#878)

#### What type of PR is this?

/kind improvement

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

使用 [TanStack Query](https://github.com/TanStack/query) 重构附件相关数据请求的相关逻辑。

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

Ref https://github.com/halo-dev/halo/issues/3360

#### Special notes for your reviewer:

测试方式:

1. 测试附件管理页面的筛选、存储策略、分组等业务。
2. 测试附件选择模态框组件。

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

```release-note
None
```
pull/879/head
Ryan Wang 2023-02-23 17:08:12 +08:00 committed by GitHub
parent 43c5effcd0
commit 0f8e5fad80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 198 additions and 333 deletions

View File

@ -339,7 +339,7 @@ const editor = useEditor({
}); });
// image drag and paste upload // image drag and paste upload
const { policies } = useFetchAttachmentPolicy({ fetchOnMounted: true }); const { policies } = useFetchAttachmentPolicy();
type Task = { type Task = {
file: File; file: File;
@ -349,7 +349,7 @@ type Task = {
const uploadQueue: queueAsPromised<Task> = fastq.promise(asyncWorker, 1); const uploadQueue: queueAsPromised<Task> = fastq.promise(asyncWorker, 1);
async function asyncWorker(arg: Task): Promise<void> { async function asyncWorker(arg: Task): Promise<void> {
if (!policies.value.length) { if (!policies.value?.length) {
Toast.warning("目前没有可用的存储策略"); Toast.warning("目前没有可用的存储策略");
return; return;
} }

View File

@ -51,10 +51,8 @@ const policyVisible = ref(false);
const uploadVisible = ref(false); const uploadVisible = ref(false);
const detailVisible = ref(false); const detailVisible = ref(false);
const { policies } = useFetchAttachmentPolicy({ fetchOnMounted: true }); const { policies } = useFetchAttachmentPolicy();
const { groups, handleFetchGroups } = useFetchAttachmentGroup({ const { groups, handleFetchGroups } = useFetchAttachmentGroup();
fetchOnMounted: true,
});
const selectedGroup = ref<Group>(); const selectedGroup = ref<Group>();
@ -85,26 +83,24 @@ const SortItems: SortItem[] = [
const selectedPolicy = ref<Policy>(); const selectedPolicy = ref<Policy>();
const selectedUser = ref<User>(); const selectedUser = ref<User>();
const keyword = ref<string>("");
const selectedSortItem = ref<SortItem>(); const selectedSortItem = ref<SortItem>();
const selectedSortItemValue = computed(() => { const selectedSortItemValue = computed(() => {
return selectedSortItem.value?.value; return selectedSortItem.value?.value;
}); });
function handleSelectPolicy(policy: Policy | undefined) { function handleSelectPolicy(policy: Policy | undefined) {
selectedPolicy.value = policy; selectedPolicy.value = policy;
handleFetchAttachments({ page: 1 }); page.value = 1;
} }
function handleSelectUser(user: User | undefined) { function handleSelectUser(user: User | undefined) {
selectedUser.value = user; selectedUser.value = user;
handleFetchAttachments({ page: 1 }); page.value = 1;
} }
function handleSortItemChange(sortItem?: SortItem) { function handleSortItemChange(sortItem?: SortItem) {
selectedSortItem.value = sortItem; selectedSortItem.value = sortItem;
handleFetchAttachments({ page: 1 }); page.value = 1;
} }
function handleKeywordChange() { function handleKeywordChange() {
@ -112,12 +108,12 @@ function handleKeywordChange() {
if (keywordNode) { if (keywordNode) {
keyword.value = keywordNode._value as string; keyword.value = keywordNode._value as string;
} }
handleFetchAttachments({ page: 1 }); page.value = 1;
} }
function handleClearKeyword() { function handleClearKeyword() {
keyword.value = ""; keyword.value = "";
handleFetchAttachments({ page: 1 }); page.value = 1;
} }
const hasFilters = computed(() => { const hasFilters = computed(() => {
@ -134,19 +130,24 @@ function handleClearFilters() {
selectedUser.value = undefined; selectedUser.value = undefined;
selectedSortItem.value = undefined; selectedSortItem.value = undefined;
keyword.value = ""; keyword.value = "";
handleFetchAttachments({ page: 1 }); page.value = 1;
} }
const keyword = ref<string>("");
const page = ref<number>(1);
const size = ref<number>(20);
const { const {
attachments, attachments,
selectedAttachment, selectedAttachment,
selectedAttachments, selectedAttachments,
checkedAll, checkedAll,
loading, isLoading,
isFetching,
total,
handleFetchAttachments, handleFetchAttachments,
handleSelectNext, handleSelectNext,
handleSelectPrevious, handleSelectPrevious,
handlePaginationChange,
handleDelete, handleDelete,
handleDeleteInBatch, handleDeleteInBatch,
handleCheckAll, handleCheckAll,
@ -159,6 +160,8 @@ const {
user: selectedUser, user: selectedUser,
keyword: keyword, keyword: keyword,
sort: selectedSortItemValue, sort: selectedSortItemValue,
page: page,
size: size,
}); });
const handleMove = async (group: Group) => { const handleMove = async (group: Group) => {
@ -209,21 +212,16 @@ const onDetailModalClose = () => {
selectedAttachment.value = undefined; selectedAttachment.value = undefined;
nameQuery.value = undefined; nameQuery.value = undefined;
nameQueryAttachment.value = undefined; nameQueryAttachment.value = undefined;
handleFetchAttachments({ mute: true }); handleFetchAttachments();
}; };
const onUploadModalClose = () => { const onUploadModalClose = () => {
routeQueryAction.value = undefined; routeQueryAction.value = undefined;
handleFetchAttachments({ mute: true });
};
const onGroupChange = () => {
handleReset();
handleFetchAttachments(); handleFetchAttachments();
}; };
const getPolicyName = (name: string | undefined) => { const getPolicyName = (name: string | undefined) => {
const policy = policies.value.find((p) => p.metadata.name === name); const policy = policies.value?.find((p) => p.metadata.name === name);
return policy?.spec.displayName; return policy?.spec.displayName;
}; };
@ -548,7 +546,7 @@ onMounted(() => {
> >
<IconRefreshLine <IconRefreshLine
v-tooltip="`刷新`" v-tooltip="`刷新`"
:class="{ 'animate-spin text-gray-900': loading }" :class="{ 'animate-spin text-gray-900': isFetching }"
class="h-4 w-4 text-gray-600 group-hover:text-gray-900" class="h-4 w-4 text-gray-600 group-hover:text-gray-900"
/> />
</div> </div>
@ -562,15 +560,15 @@ onMounted(() => {
<div :style="`${viewType === 'list' ? 'padding:12px 16px 0' : ''}`"> <div :style="`${viewType === 'list' ? 'padding:12px 16px 0' : ''}`">
<AttachmentGroupList <AttachmentGroupList
v-model:selected-group="selectedGroup" v-model:selected-group="selectedGroup"
@select="onGroupChange" @select="handleReset"
@update="handleFetchGroups" @update="handleFetchGroups"
@reload-attachments="handleFetchAttachments" @reload-attachments="handleFetchAttachments"
/> />
</div> </div>
<VLoading v-if="loading" /> <VLoading v-if="isLoading" />
<Transition v-else-if="!attachments.total" appear name="fade"> <Transition v-else-if="!attachments?.length" appear name="fade">
<VEmpty <VEmpty
message="当前分组没有附件,你可以尝试刷新或者上传附件" message="当前分组没有附件,你可以尝试刷新或者上传附件"
title="当前分组没有附件" title="当前分组没有附件"
@ -600,7 +598,7 @@ onMounted(() => {
role="list" role="list"
> >
<VCard <VCard
v-for="(attachment, index) in attachments.items" v-for="(attachment, index) in attachments"
:key="index" :key="index"
:body-class="['!p-0']" :body-class="['!p-0']"
:class="{ :class="{
@ -680,10 +678,7 @@ onMounted(() => {
class="box-border h-full w-full divide-y divide-gray-100" class="box-border h-full w-full divide-y divide-gray-100"
role="list" role="list"
> >
<li <li v-for="(attachment, index) in attachments" :key="index">
v-for="(attachment, index) in attachments.items"
:key="index"
>
<VEntity :is-selected="isChecked(attachment)"> <VEntity :is-selected="isChecked(attachment)">
<template <template
v-if=" v-if="
@ -797,11 +792,10 @@ onMounted(() => {
<template #footer> <template #footer>
<div class="bg-white sm:flex sm:items-center sm:justify-end"> <div class="bg-white sm:flex sm:items-center sm:justify-end">
<VPagination <VPagination
:page="attachments.page" v-model:page="page"
:size="attachments.size" v-model:size="size"
:total="attachments.total" :total="total"
:size-options="[60, 120, 200]" :size-options="[60, 120, 200]"
@change="handlePaginationChange"
/> />
</div> </div>
</template> </template>

View File

@ -1,13 +1,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import { VButton, VModal, VSpace, VTag } from "@halo-dev/components"; import { VButton, VModal, VSpace, VTag } from "@halo-dev/components";
import LazyImage from "@/components/image/LazyImage.vue"; import LazyImage from "@/components/image/LazyImage.vue";
import type { Attachment, Policy } from "@halo-dev/api-client"; import type { Attachment } from "@halo-dev/api-client";
import prettyBytes from "pretty-bytes"; import prettyBytes from "pretty-bytes";
import { ref, watch, watchEffect } from "vue"; import { computed, ref } from "vue";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import { isImage } from "@/utils/image"; import { isImage } from "@/utils/image";
import { formatDatetime } from "@/utils/date"; import { formatDatetime } from "@/utils/date";
import { useFetchAttachmentGroup } from "../composables/use-attachment-group"; import { useFetchAttachmentGroup } from "../composables/use-attachment-group";
import { useQuery } from "@tanstack/vue-query";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -27,43 +28,40 @@ const emit = defineEmits<{
(event: "close"): void; (event: "close"): void;
}>(); }>();
const { groups, handleFetchGroups } = useFetchAttachmentGroup(); const { groups } = useFetchAttachmentGroup();
const policy = ref<Policy>();
const onlyPreview = ref(false); const onlyPreview = ref(false);
watchEffect(async () => { const policyName = computed(() => {
if (props.attachment) { return props.attachment?.spec.policyName;
const { policyName } = props.attachment.spec;
if (!policyName) {
return;
}
const { data } =
await apiClient.extension.storage.policy.getstorageHaloRunV1alpha1Policy({
name: policyName,
});
policy.value = data;
}
}); });
watch( const { data: policy } = useQuery({
() => props.visible, queryKey: ["attachment-policy", policyName],
(newValue) => { queryFn: async () => {
if (newValue) { if (!policyName.value) {
handleFetchGroups(); return;
} }
}
); const { data } =
await apiClient.extension.storage.policy.getstorageHaloRunV1alpha1Policy({
name: policyName.value,
});
return data;
},
refetchOnWindowFocus: false,
enabled: computed(() => !!policyName.value),
});
const getGroupName = (name: string | undefined) => { const getGroupName = (name: string | undefined) => {
const group = groups.value.find((group) => group.metadata.name === name); const group = groups.value?.find((group) => group.metadata.name === name);
return group?.spec.displayName || name; return group?.spec.displayName || name;
}; };
const onVisibleChange = (visible: boolean) => { const onVisibleChange = (visible: boolean) => {
emit("update:visible", visible); emit("update:visible", visible);
if (!visible) { if (!visible) {
policy.value = undefined;
onlyPreview.value = false; onlyPreview.value = false;
emit("close"); emit("close");
} }

View File

@ -163,9 +163,9 @@ const handleDeleteWithAttachments = (group: Group) => {
}; };
watch( watch(
() => groups.value.length, () => groups.value?.length,
() => { () => {
const allGroups = [...defaultGroups, ...groups.value]; const allGroups = [...defaultGroups, ...(groups.value || [])];
const groupIndex = allGroups.findIndex( const groupIndex = allGroups.findIndex(
(group) => group.metadata.name === routeQuery.value (group) => group.metadata.name === routeQuery.value
); );
@ -178,9 +178,8 @@ watch(
onMounted(async () => { onMounted(async () => {
await handleFetchGroups(); await handleFetchGroups();
if (routeQuery.value && !props.readonly) { if (routeQuery.value && !props.readonly) {
const allGroups = [...defaultGroups, ...groups.value]; const allGroups = [...defaultGroups, ...(groups.value || [])];
const group = allGroups.find( const group = allGroups.find(
(group) => group.metadata.name === routeQuery.value (group) => group.metadata.name === routeQuery.value
); );

View File

@ -12,7 +12,7 @@ import {
Toast, Toast,
} from "@halo-dev/components"; } from "@halo-dev/components";
import AttachmentPolicyEditingModal from "./AttachmentPolicyEditingModal.vue"; import AttachmentPolicyEditingModal from "./AttachmentPolicyEditingModal.vue";
import { ref, watch } from "vue"; import { ref } from "vue";
import type { Policy, PolicyTemplate } from "@halo-dev/api-client"; import type { Policy, PolicyTemplate } from "@halo-dev/api-client";
import { formatDatetime } from "@/utils/date"; import { formatDatetime } from "@/utils/date";
import { import {
@ -21,7 +21,7 @@ import {
} from "../composables/use-attachment-policy"; } from "../composables/use-attachment-policy";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
const props = withDefaults( withDefaults(
defineProps<{ defineProps<{
visible: boolean; visible: boolean;
}>(), }>(),
@ -35,11 +35,8 @@ const emit = defineEmits<{
(event: "close"): void; (event: "close"): void;
}>(); }>();
const { policies, loading, handleFetchPolicies } = useFetchAttachmentPolicy(); const { policies, isLoading, handleFetchPolicies } = useFetchAttachmentPolicy();
const { policyTemplates, handleFetchPolicyTemplates } = const { policyTemplates } = useFetchAttachmentPolicyTemplate();
useFetchAttachmentPolicyTemplate({
fetchOnMounted: false,
});
const selectedPolicy = ref<Policy>(); const selectedPolicy = ref<Policy>();
@ -105,16 +102,6 @@ const onEditingModalClose = () => {
selectedPolicy.value = undefined; selectedPolicy.value = undefined;
handleFetchPolicies(); handleFetchPolicies();
}; };
watch(
() => props.visible,
(visible) => {
if (visible) {
handleFetchPolicyTemplates();
handleFetchPolicies();
}
}
);
</script> </script>
<template> <template>
<VModal <VModal
@ -150,7 +137,7 @@ watch(
</FloatingDropdown> </FloatingDropdown>
</template> </template>
<VEmpty <VEmpty
v-if="!policies.length && !loading" v-if="!policies?.length && !isLoading"
message="当前没有可用的存储策略,你可以尝试刷新或者新建策略" message="当前没有可用的存储策略,你可以尝试刷新或者新建策略"
title="当前没有可用的存储策略" title="当前没有可用的存储策略"
> >

View File

@ -25,14 +25,9 @@ const emit = defineEmits<{
(event: "close"): void; (event: "close"): void;
}>(); }>();
const { groups, handleFetchGroups } = useFetchAttachmentGroup({ const { groups } = useFetchAttachmentGroup();
fetchOnMounted: false, const { policies, handleFetchPolicies } = useFetchAttachmentPolicy();
}); const { policyTemplates } = useFetchAttachmentPolicyTemplate();
const { policies, handleFetchPolicies } = useFetchAttachmentPolicy({
fetchOnMounted: false,
});
const { policyTemplates, handleFetchPolicyTemplates } =
useFetchAttachmentPolicyTemplate();
const selectedGroupName = useLocalStorage("attachment-upload-group", ""); const selectedGroupName = useLocalStorage("attachment-upload-group", "");
const selectedPolicyName = useLocalStorage("attachment-upload-policy", ""); const selectedPolicyName = useLocalStorage("attachment-upload-policy", "");
@ -45,12 +40,13 @@ watch(
() => { () => {
if (selectedGroupName.value === "") return; if (selectedGroupName.value === "") return;
const group = groups.value.find( const group = groups.value?.find(
(group) => group.metadata.name === selectedGroupName.value (group) => group.metadata.name === selectedGroupName.value
); );
if (!group) { if (!group) {
selectedGroupName.value = selectedGroupName.value = groups.value?.length
groups.value.length > 0 ? groups.value[0].metadata.name : ""; ? groups.value[0].metadata.name
: "";
} }
} }
); );
@ -58,12 +54,13 @@ watch(
watch( watch(
() => policies.value, () => policies.value,
() => { () => {
const policy = policies.value.find( const policy = policies.value?.find(
(policy) => policy.metadata.name === selectedPolicyName.value (policy) => policy.metadata.name === selectedPolicyName.value
); );
if (!policy) { if (!policy) {
selectedPolicyName.value = selectedPolicyName.value = policies.value?.length
policies.value.length > 0 ? policies.value[0].metadata.name : ""; ? policies.value[0].metadata.name
: "";
} }
} }
); );
@ -87,7 +84,7 @@ const handleOpenCreateNewPolicyModal = (policyTemplate: PolicyTemplate) => {
const onEditingModalClose = async () => { const onEditingModalClose = async () => {
await handleFetchPolicies(); await handleFetchPolicies();
policyToCreate.value = policies.value[0]; policyToCreate.value = policies.value?.[0];
}; };
const onVisibleChange = (visible: boolean) => { const onVisibleChange = (visible: boolean) => {
@ -102,9 +99,6 @@ watch(
() => props.visible, () => props.visible,
(newValue) => { (newValue) => {
if (newValue) { if (newValue) {
handleFetchGroups();
handleFetchPolicies();
handleFetchPolicyTemplates();
uploadVisible.value = true; uploadVisible.value = true;
} else { } else {
const uploadVisibleTimer = setTimeout(() => { const uploadVisibleTimer = setTimeout(() => {
@ -131,7 +125,7 @@ watch(
<div <div
v-for="(group, index) in [ v-for="(group, index) in [
{ metadata: { name: '' }, spec: { displayName: '未分组' } }, { metadata: { name: '' }, spec: { displayName: '未分组' } },
...groups, ...(groups || []),
]" ]"
:key="index" :key="index"
:class="{ :class="{
@ -200,7 +194,7 @@ watch(
</template> </template>
</FloatingDropdown> </FloatingDropdown>
</div> </div>
<div v-if="policies.length <= 0" class="mb-3"> <div v-if="!policies?.length" class="mb-3">
<VAlert <VAlert
title="没有存储策略" title="没有存储策略"
description="在上传之前,需要新建一个存储策略" description="在上传之前,需要新建一个存储策略"

View File

@ -38,20 +38,22 @@ const emit = defineEmits<{
}>(); }>();
const selectedGroup = ref<Group>(); const selectedGroup = ref<Group>();
const page = ref(1);
const size = ref(60);
const { const {
attachments, attachments,
loading, isLoading,
total,
selectedAttachment, selectedAttachment,
selectedAttachments, selectedAttachments,
handleFetchAttachments, handleFetchAttachments,
handlePaginationChange,
handleSelect, handleSelect,
handleSelectPrevious, handleSelectPrevious,
handleSelectNext, handleSelectNext,
handleReset, handleReset,
isChecked, isChecked,
} = useAttachmentControl({ group: selectedGroup }); } = useAttachmentControl({ group: selectedGroup, page, size });
const uploadVisible = ref(false); const uploadVisible = ref(false);
const detailVisible = ref(false); const detailVisible = ref(false);
@ -64,21 +66,14 @@ const handleOpenDetail = (attachment: Attachment) => {
selectedAttachment.value = attachment; selectedAttachment.value = attachment;
detailVisible.value = true; detailVisible.value = true;
}; };
const onGroupChange = () => {
handleReset();
handleFetchAttachments();
};
await handleFetchAttachments();
</script> </script>
<template> <template>
<AttachmentGroupList <AttachmentGroupList
v-model:selected-group="selectedGroup" v-model:selected-group="selectedGroup"
readonly readonly
@select="onGroupChange" @select="handleReset"
/> />
<div v-if="attachments.total > 0" class="mb-5"> <div v-if="attachments?.length" class="mb-5">
<VButton @click="uploadVisible = true"> <VButton @click="uploadVisible = true">
<template #icon> <template #icon>
<IconUpload class="h-full w-full" /> <IconUpload class="h-full w-full" />
@ -87,7 +82,7 @@ await handleFetchAttachments();
</VButton> </VButton>
</div> </div>
<VEmpty <VEmpty
v-if="!attachments.total && !loading" v-if="!attachments?.length && !isLoading"
message="当前没有附件,你可以尝试刷新或者上传附件" message="当前没有附件,你可以尝试刷新或者上传附件"
title="当前没有附件" title="当前没有附件"
> >
@ -109,7 +104,7 @@ await handleFetchAttachments();
role="list" role="list"
> >
<VCard <VCard
v-for="(attachment, index) in attachments.items" v-for="(attachment, index) in attachments"
:key="index" :key="index"
:body-class="['!p-0']" :body-class="['!p-0']"
:class="{ :class="{
@ -171,11 +166,10 @@ await handleFetchAttachments();
</div> </div>
<div class="mt-4 bg-white sm:flex sm:items-center sm:justify-end"> <div class="mt-4 bg-white sm:flex sm:items-center sm:justify-end">
<VPagination <VPagination
:page="attachments.page" v-model:page="page"
:size="attachments.size" v-model:size="size"
:total="attachments.total" :total="total"
:size-options="[60, 120, 200]" :size-options="[60, 120, 200]"
@change="handlePaginationChange"
/> />
</div> </div>
<AttachmentUploadModal <AttachmentUploadModal

View File

@ -1,58 +1,35 @@
import { onMounted, onUnmounted, ref, type Ref } from "vue"; import type { Ref } from "vue";
import type { Group } from "@halo-dev/api-client"; import type { Group } from "@halo-dev/api-client";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import { useQuery } from "@tanstack/vue-query";
interface useFetchAttachmentGroupReturn { interface useFetchAttachmentGroupReturn {
groups: Ref<Group[]>; groups: Ref<Group[] | undefined>;
loading: Ref<boolean>; isLoading: Ref<boolean>;
handleFetchGroups: () => void; handleFetchGroups: () => void;
} }
export function useFetchAttachmentGroup(options?: { export function useFetchAttachmentGroup(): useFetchAttachmentGroupReturn {
fetchOnMounted: boolean; const { data, isLoading, refetch } = useQuery<Group[]>({
}): useFetchAttachmentGroupReturn { queryKey: ["attachment-groups"],
const { fetchOnMounted } = options || {}; queryFn: async () => {
const groups = ref<Group[]>([] as Group[]);
const loading = ref<boolean>(false);
const refreshInterval = ref();
const handleFetchGroups = async () => {
try {
clearInterval(refreshInterval.value);
loading.value = true;
const { data } = const { data } =
await apiClient.extension.storage.group.liststorageHaloRunV1alpha1Group(); await apiClient.extension.storage.group.liststorageHaloRunV1alpha1Group();
groups.value = data.items; return data.items;
},
const deletedGroups = groups.value.filter( refetchInterval(data) {
const deletingGroups = data?.filter(
(group) => !!group.metadata.deletionTimestamp (group) => !!group.metadata.deletionTimestamp
); );
if (deletedGroups.length) { return deletingGroups?.length ? 1000 : false;
refreshInterval.value = setInterval(() => { },
handleFetchGroups(); refetchOnWindowFocus: false,
}, 1000);
}
} catch (e) {
console.error("Failed to fetch attachment groups", e);
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchOnMounted && handleFetchGroups();
});
onUnmounted(() => {
clearInterval(refreshInterval.value);
}); });
return { return {
groups, groups: data,
loading, isLoading,
handleFetchGroups, handleFetchGroups: refetch,
}; };
} }

View File

@ -1,97 +1,58 @@
import { onMounted, onUnmounted, ref } from "vue";
import type { Ref } from "vue"; import type { Ref } from "vue";
import type { Policy, PolicyTemplate } from "@halo-dev/api-client"; import type { Policy, PolicyTemplate } from "@halo-dev/api-client";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import { useQuery } from "@tanstack/vue-query";
interface useFetchAttachmentPolicyReturn { interface useFetchAttachmentPolicyReturn {
policies: Ref<Policy[]>; policies: Ref<Policy[] | undefined>;
loading: Ref<boolean>; isLoading: Ref<boolean>;
handleFetchPolicies: () => void; handleFetchPolicies: () => void;
} }
interface useFetchAttachmentPolicyTemplatesReturn { interface useFetchAttachmentPolicyTemplatesReturn {
policyTemplates: Ref<PolicyTemplate[]>; policyTemplates: Ref<PolicyTemplate[] | undefined>;
loading: Ref<boolean>; isLoading: Ref<boolean>;
handleFetchPolicyTemplates: () => void; handleFetchPolicyTemplates: () => void;
} }
export function useFetchAttachmentPolicy(options?: { export function useFetchAttachmentPolicy(): useFetchAttachmentPolicyReturn {
fetchOnMounted: boolean; const { data, isLoading, refetch } = useQuery<Policy[]>({
}): useFetchAttachmentPolicyReturn { queryKey: ["attachment-policies"],
const { fetchOnMounted } = options || {}; queryFn: async () => {
const policies = ref<Policy[]>([] as Policy[]);
const loading = ref<boolean>(false);
const refreshInterval = ref();
const handleFetchPolicies = async () => {
try {
clearInterval(refreshInterval.value);
loading.value = true;
const { data } = const { data } =
await apiClient.extension.storage.policy.liststorageHaloRunV1alpha1Policy(); await apiClient.extension.storage.policy.liststorageHaloRunV1alpha1Policy();
policies.value = data.items; return data.items;
},
const deletedPolicies = policies.value.filter( refetchInterval(data) {
const deletingPolicies = data?.filter(
(policy) => !!policy.metadata.deletionTimestamp (policy) => !!policy.metadata.deletionTimestamp
); );
return deletingPolicies?.length ? 1000 : false;
if (deletedPolicies.length) { },
refreshInterval.value = setInterval(() => { refetchOnWindowFocus: false,
handleFetchPolicies();
}, 1000);
}
} catch (e) {
console.error("Failed to fetch attachment policies", e);
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchOnMounted && handleFetchPolicies();
});
onUnmounted(() => {
clearInterval(refreshInterval.value);
}); });
return { return {
policies, policies: data,
loading, isLoading,
handleFetchPolicies, handleFetchPolicies: refetch,
}; };
} }
export function useFetchAttachmentPolicyTemplate(options?: { export function useFetchAttachmentPolicyTemplate(): useFetchAttachmentPolicyTemplatesReturn {
fetchOnMounted: boolean; const { data, isLoading, refetch } = useQuery<PolicyTemplate[]>({
}): useFetchAttachmentPolicyTemplatesReturn { queryKey: ["attachment-policy-templates"],
const { fetchOnMounted } = options || {}; queryFn: async () => {
const policyTemplates = ref<PolicyTemplate[]>([] as PolicyTemplate[]);
const loading = ref<boolean>(false);
const handleFetchPolicyTemplates = async () => {
try {
loading.value = true;
const { data } = const { data } =
await apiClient.extension.storage.policyTemplate.liststorageHaloRunV1alpha1PolicyTemplate(); await apiClient.extension.storage.policyTemplate.liststorageHaloRunV1alpha1PolicyTemplate();
policyTemplates.value = data.items; return data.items;
} catch (e) { },
console.error("Failed to fetch attachment policy templates", e); refetchOnWindowFocus: false,
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchOnMounted && handleFetchPolicyTemplates();
}); });
return { return {
policyTemplates, policyTemplates: data,
loading, isLoading,
handleFetchPolicyTemplates, handleFetchPolicyTemplates: refetch,
}; };
} }

View File

@ -1,32 +1,21 @@
import type { import type { Attachment, Group, Policy, User } from "@halo-dev/api-client";
Attachment,
AttachmentList,
Group,
Policy,
User,
} from "@halo-dev/api-client";
import type { Ref } from "vue"; import type { Ref } from "vue";
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import type { AttachmentLike } from "@halo-dev/console-shared"; import type { AttachmentLike } from "@halo-dev/console-shared";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import { Dialog, Toast } from "@halo-dev/components"; import { Dialog, Toast } from "@halo-dev/components";
import type { Content, Editor } from "@halo-dev/richtext-editor"; import type { Content, Editor } from "@halo-dev/richtext-editor";
import { onBeforeRouteLeave } from "vue-router"; import { useQuery } from "@tanstack/vue-query";
interface useAttachmentControlReturn { interface useAttachmentControlReturn {
attachments: Ref<AttachmentList>; attachments: Ref<Attachment[] | undefined>;
loading: Ref<boolean>; isLoading: Ref<boolean>;
isFetching: Ref<boolean>;
selectedAttachment: Ref<Attachment | undefined>; selectedAttachment: Ref<Attachment | undefined>;
selectedAttachments: Ref<Set<Attachment>>; selectedAttachments: Ref<Set<Attachment>>;
checkedAll: Ref<boolean>; checkedAll: Ref<boolean>;
handleFetchAttachments: (options?: { mute?: boolean; page?: number }) => void; total: Ref<number>;
handlePaginationChange: ({ handleFetchAttachments: () => void;
page,
size,
}: {
page: number;
size: number;
}) => void;
handleSelectPrevious: () => void; handleSelectPrevious: () => void;
handleSelectNext: () => void; handleSelectNext: () => void;
handleDelete: (attachment: Attachment) => void; handleDelete: (attachment: Attachment) => void;
@ -41,124 +30,95 @@ interface useAttachmentSelectReturn {
onAttachmentSelect: (attachments: AttachmentLike[]) => void; onAttachmentSelect: (attachments: AttachmentLike[]) => void;
} }
export function useAttachmentControl(filterOptions?: { export function useAttachmentControl(filterOptions: {
policy?: Ref<Policy | undefined>; policy?: Ref<Policy | undefined>;
group?: Ref<Group | undefined>; group?: Ref<Group | undefined>;
user?: Ref<User | undefined>; user?: Ref<User | undefined>;
keyword?: Ref<string | undefined>; keyword?: Ref<string | undefined>;
sort?: Ref<string | undefined>; sort?: Ref<string | undefined>;
page: Ref<number>;
size: Ref<number>;
}): useAttachmentControlReturn { }): useAttachmentControlReturn {
const { user, policy, group, keyword, sort } = filterOptions || {}; const { user, policy, group, keyword, sort, page, size } = filterOptions;
const attachments = ref<AttachmentList>({
page: 1,
size: 60,
total: 0,
items: [],
first: true,
last: false,
hasNext: false,
hasPrevious: false,
totalPages: 0,
});
const loading = ref<boolean>(false);
const selectedAttachment = ref<Attachment>(); const selectedAttachment = ref<Attachment>();
const selectedAttachments = ref<Set<Attachment>>(new Set<Attachment>()); const selectedAttachments = ref<Set<Attachment>>(new Set<Attachment>());
const checkedAll = ref(false); const checkedAll = ref(false);
const refreshInterval = ref();
const handleFetchAttachments = async (options?: { const total = ref(0);
mute?: boolean; const hasPrevious = ref(false);
page?: number; const hasNext = ref(false);
}) => {
try {
clearInterval(refreshInterval.value);
if (!options?.mute) {
loading.value = true;
}
if (options?.page) {
attachments.value.page = options.page;
}
const { data, isLoading, isFetching, refetch } = useQuery<Attachment[]>({
queryKey: ["attachments", policy, keyword, group, user, page, size, sort],
queryFn: async () => {
const { data } = await apiClient.attachment.searchAttachments({ const { data } = await apiClient.attachment.searchAttachments({
policy: policy?.value?.metadata.name, policy: policy?.value?.metadata.name,
displayName: keyword?.value, displayName: keyword?.value,
group: group?.value?.metadata.name, group: group?.value?.metadata.name,
ungrouped: group?.value?.metadata.name === "ungrouped", ungrouped: group?.value?.metadata.name === "ungrouped",
uploadedBy: user?.value?.metadata.name, uploadedBy: user?.value?.metadata.name,
page: attachments.value.page, page: page?.value,
size: attachments.value.size, size: size?.value,
sort: [sort?.value as string].filter(Boolean), sort: [sort?.value as string].filter(Boolean),
}); });
attachments.value = data;
const deletedAttachments = attachments.value.items.filter( total.value = data.total;
hasPrevious.value = data.hasPrevious;
hasNext.value = data.hasNext;
return data.items;
},
refetchInterval(data) {
const deletingAttachments = data?.filter(
(attachment) => !!attachment.metadata.deletionTimestamp (attachment) => !!attachment.metadata.deletionTimestamp
); );
return deletingAttachments?.length ? 3000 : false;
if (deletedAttachments.length) { },
refreshInterval.value = setInterval(() => { refetchOnWindowFocus: false,
handleFetchAttachments({ mute: true });
}, 3000);
}
} catch (e) {
console.error("Failed to fetch attachments", e);
} finally {
loading.value = false;
}
};
onBeforeRouteLeave(() => {
clearInterval(refreshInterval.value);
}); });
const handlePaginationChange = async ({
page,
size,
}: {
page: number;
size: number;
}) => {
attachments.value.page = page;
attachments.value.size = size;
await handleFetchAttachments();
};
const handleSelectPrevious = async () => { const handleSelectPrevious = async () => {
const { items, hasPrevious } = attachments.value; if (!data.value) return;
const index = items.findIndex(
const index = data.value?.findIndex(
(attachment) => (attachment) =>
attachment.metadata.name === selectedAttachment.value?.metadata.name attachment.metadata.name === selectedAttachment.value?.metadata.name
); );
if (index === undefined) return;
if (index > 0) { if (index > 0) {
selectedAttachment.value = items[index - 1]; selectedAttachment.value = data.value[index - 1];
return; return;
} }
if (index === 0 && hasPrevious) { if (index === 0 && hasPrevious) {
attachments.value.page--; page.value--;
await handleFetchAttachments(); await refetch();
selectedAttachment.value = selectedAttachment.value = data.value[data.value.length - 1];
attachments.value.items[attachments.value.items.length - 1];
} }
}; };
const handleSelectNext = async () => { const handleSelectNext = async () => {
const { items, hasNext } = attachments.value; if (!data.value) return;
const index = items.findIndex(
const index = data.value?.findIndex(
(attachment) => (attachment) =>
attachment.metadata.name === selectedAttachment.value?.metadata.name attachment.metadata.name === selectedAttachment.value?.metadata.name
); );
if (index < items.length - 1) {
selectedAttachment.value = items[index + 1]; if (index === undefined) return;
if (index < data.value?.length - 1) {
selectedAttachment.value = data.value[index + 1];
return; return;
} }
if (index === items.length - 1 && hasNext) {
attachments.value.page++; if (index === data.value.length - 1 && hasNext) {
await handleFetchAttachments(); page.value++;
selectedAttachment.value = attachments.value.items[0]; await refetch();
selectedAttachment.value = data.value[0];
} }
}; };
@ -185,7 +145,7 @@ export function useAttachmentControl(filterOptions?: {
} catch (e) { } catch (e) {
console.error("Failed to delete attachment", e); console.error("Failed to delete attachment", e);
} finally { } finally {
await handleFetchAttachments(); await refetch();
} }
}, },
}); });
@ -214,7 +174,7 @@ export function useAttachmentControl(filterOptions?: {
} catch (e) { } catch (e) {
console.error("Failed to delete attachments", e); console.error("Failed to delete attachments", e);
} finally { } finally {
await handleFetchAttachments(); await refetch();
} }
}, },
}); });
@ -222,7 +182,7 @@ export function useAttachmentControl(filterOptions?: {
const handleCheckAll = (checkAll: boolean) => { const handleCheckAll = (checkAll: boolean) => {
if (checkAll) { if (checkAll) {
attachments.value.items.forEach((attachment) => { data.value?.forEach((attachment) => {
selectedAttachments.value.add(attachment); selectedAttachments.value.add(attachment);
}); });
} else { } else {
@ -242,7 +202,7 @@ export function useAttachmentControl(filterOptions?: {
watch( watch(
() => selectedAttachments.value.size, () => selectedAttachments.value.size,
(newValue) => { (newValue) => {
checkedAll.value = newValue === attachments.value.items?.length; checkedAll.value = newValue === data.value?.length;
} }
); );
@ -256,20 +216,21 @@ export function useAttachmentControl(filterOptions?: {
}; };
const handleReset = () => { const handleReset = () => {
attachments.value.page = 1; page.value = 1;
selectedAttachment.value = undefined; selectedAttachment.value = undefined;
selectedAttachments.value.clear(); selectedAttachments.value.clear();
checkedAll.value = false; checkedAll.value = false;
}; };
return { return {
attachments, attachments: data,
loading, isLoading,
isFetching,
selectedAttachment, selectedAttachment,
selectedAttachments, selectedAttachments,
checkedAll, checkedAll,
handleFetchAttachments, total,
handlePaginationChange, handleFetchAttachments: refetch,
handleSelectPrevious, handleSelectPrevious,
handleSelectNext, handleSelectNext,
handleDelete, handleDelete,