refactor: use tanstack query to refactor singlePage-related fetching (halo-dev/console#886)

#### 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. 测试页面管理列表的数据请求 + 条件筛选无异常即可。

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

```release-note
None
```
pull/3445/head
Ryan Wang 2023-02-24 17:58:21 +08:00 committed by GitHub
parent 27ee803263
commit a7ddf37fb2
2 changed files with 294 additions and 351 deletions

View File

@ -17,93 +17,57 @@ import {
VLoading, VLoading,
Toast, Toast,
} from "@halo-dev/components"; } from "@halo-dev/components";
import { onMounted, ref, watch } from "vue"; import { ref, watch } from "vue";
import type { ListedSinglePageList, SinglePage } from "@halo-dev/api-client"; import type { ListedSinglePage, SinglePage } from "@halo-dev/api-client";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import { formatDatetime } from "@/utils/date"; import { formatDatetime } from "@/utils/date";
import { onBeforeRouteLeave, RouterLink } from "vue-router"; import { RouterLink } from "vue-router";
import cloneDeep from "lodash.clonedeep"; import cloneDeep from "lodash.clonedeep";
import { usePermission } from "@/utils/permission"; import { usePermission } from "@/utils/permission";
import { getNode } from "@formkit/core"; import { getNode } from "@formkit/core";
import FilterTag from "@/components/filter/FilterTag.vue"; import FilterTag from "@/components/filter/FilterTag.vue";
import { useQuery } from "@tanstack/vue-query";
const { currentUserHasPermission } = usePermission(); const { currentUserHasPermission } = usePermission();
const singlePages = ref<ListedSinglePageList>({
page: 1,
size: 50,
total: 0,
items: [],
first: true,
last: false,
hasNext: false,
hasPrevious: false,
totalPages: 0,
});
const loading = ref(false);
const selectedPageNames = ref<string[]>([]); const selectedPageNames = ref<string[]>([]);
const checkedAll = ref(false); const checkedAll = ref(false);
const refreshInterval = ref();
const keyword = ref(""); const keyword = ref("");
const handleFetchSinglePages = async (options?: { const page = ref(1);
mute?: boolean; const size = ref(20);
page?: number; const total = ref(0);
}) => {
try {
clearInterval(refreshInterval.value);
if (!options?.mute) {
loading.value = true;
}
if (options?.page) {
singlePages.value.page = options.page;
}
const {
data: singlePages,
isLoading,
isFetching,
refetch,
} = useQuery<ListedSinglePage[]>({
queryKey: ["deleted-singlePages", page, size, keyword],
queryFn: async () => {
const { data } = await apiClient.singlePage.listSinglePages({ const { data } = await apiClient.singlePage.listSinglePages({
labelSelector: [`content.halo.run/deleted=true`], labelSelector: [`content.halo.run/deleted=true`],
page: singlePages.value.page, page: page.value,
size: singlePages.value.size, size: size.value,
keyword: keyword.value, keyword: keyword.value,
}); });
singlePages.value = data; total.value = data.total;
const deletedSinglePages = singlePages.value.items.filter( return data.items;
},
refetchOnWindowFocus: false,
refetchInterval(data) {
const deletedSinglePages = data?.filter(
(singlePage) => (singlePage) =>
!!singlePage.page.metadata.deletionTimestamp || !!singlePage.page.metadata.deletionTimestamp ||
!singlePage.page.spec.deleted !singlePage.page.spec.deleted
); );
return deletedSinglePages?.length ? 3000 : false;
if (deletedSinglePages.length) { },
refreshInterval.value = setInterval(() => {
handleFetchSinglePages({ mute: true });
}, 3000);
}
} catch (error) {
console.error("Failed to fetch deleted single pages", error);
} finally {
loading.value = false;
}
};
onBeforeRouteLeave(() => {
clearInterval(refreshInterval.value);
}); });
const handlePaginationChange = ({
page,
size,
}: {
page: number;
size: number;
}) => {
singlePages.value.page = page;
singlePages.value.size = size;
handleFetchSinglePages();
};
const checkSelection = (singlePage: SinglePage) => { const checkSelection = (singlePage: SinglePage) => {
return selectedPageNames.value.includes(singlePage.metadata.name); return selectedPageNames.value.includes(singlePage.metadata.name);
}; };
@ -113,7 +77,7 @@ const handleCheckAllChange = (e: Event) => {
if (checked) { if (checked) {
selectedPageNames.value = selectedPageNames.value =
singlePages.value.items.map((singlePage) => { singlePages.value?.map((singlePage) => {
return singlePage.page.metadata.name; return singlePage.page.metadata.name;
}) || []; }) || [];
} else { } else {
@ -132,7 +96,7 @@ const handleDeletePermanently = async (singlePage: SinglePage) => {
name: singlePage.metadata.name, name: singlePage.metadata.name,
} }
); );
await handleFetchSinglePages(); await refetch();
Toast.success("删除成功"); Toast.success("删除成功");
}, },
@ -154,7 +118,7 @@ const handleDeletePermanentlyInBatch = async () => {
); );
}) })
); );
await handleFetchSinglePages(); await refetch();
selectedPageNames.value = []; selectedPageNames.value = [];
Toast.success("删除成功"); Toast.success("删除成功");
@ -175,7 +139,7 @@ const handleRecovery = async (singlePage: SinglePage) => {
singlePage: singlePageToUpdate, singlePage: singlePageToUpdate,
} }
); );
await handleFetchSinglePages(); await refetch();
Toast.success("恢复成功"); Toast.success("恢复成功");
}, },
@ -189,7 +153,7 @@ const handleRecoveryInBatch = async () => {
onConfirm: async () => { onConfirm: async () => {
await Promise.all( await Promise.all(
selectedPageNames.value.map((name) => { selectedPageNames.value.map((name) => {
const singlePage = singlePages.value.items.find( const singlePage = singlePages.value?.find(
(item) => item.page.metadata.name === name (item) => item.page.metadata.name === name
)?.page; )?.page;
@ -197,16 +161,21 @@ const handleRecoveryInBatch = async () => {
return Promise.resolve(); return Promise.resolve();
} }
singlePage.spec.deleted = false;
return apiClient.extension.singlePage.updatecontentHaloRunV1alpha1SinglePage( return apiClient.extension.singlePage.updatecontentHaloRunV1alpha1SinglePage(
{ {
name: singlePage.metadata.name, name: singlePage.metadata.name,
singlePage: singlePage, singlePage: {
...singlePage,
spec: {
...singlePage.spec,
deleted: false,
},
},
} }
); );
}) })
); );
await handleFetchSinglePages(); await refetch();
selectedPageNames.value = []; selectedPageNames.value = [];
Toast.success("恢复成功"); Toast.success("恢复成功");
@ -215,23 +184,21 @@ const handleRecoveryInBatch = async () => {
}; };
watch(selectedPageNames, (newValue) => { watch(selectedPageNames, (newValue) => {
checkedAll.value = newValue.length === singlePages.value.items?.length; checkedAll.value = newValue.length === singlePages.value?.length;
}); });
onMounted(handleFetchSinglePages);
// Filters // Filters
function handleKeywordChange() { function handleKeywordChange() {
const keywordNode = getNode("keywordInput"); const keywordNode = getNode("keywordInput");
if (keywordNode) { if (keywordNode) {
keyword.value = keywordNode._value as string; keyword.value = keywordNode._value as string;
} }
handleFetchSinglePages({ page: 1 }); page.value = 1;
} }
function handleClearKeyword() { function handleClearKeyword() {
keyword.value = ""; keyword.value = "";
handleFetchSinglePages({ page: 1 }); page.value = 1;
} }
</script> </script>
@ -307,10 +274,10 @@ function handleClearKeyword() {
<div class="flex flex-row gap-2"> <div class="flex flex-row gap-2">
<div <div
class="group cursor-pointer rounded p-1 hover:bg-gray-200" class="group cursor-pointer rounded p-1 hover:bg-gray-200"
@click="handleFetchSinglePages()" @click="refetch()"
> >
<IconRefreshLine <IconRefreshLine
: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>
@ -320,15 +287,15 @@ function handleClearKeyword() {
</div> </div>
</div> </div>
</template> </template>
<VLoading v-if="loading" /> <VLoading v-if="isLoading" />
<Transition v-else-if="!singlePages.items.length" appear name="fade"> <Transition v-else-if="!singlePages?.length" appear name="fade">
<VEmpty <VEmpty
message="你可以尝试刷新或者返回自定义页面管理" message="你可以尝试刷新或者返回自定义页面管理"
title="没有自定义页面被放入回收站" title="没有自定义页面被放入回收站"
> >
<template #actions> <template #actions>
<VSpace> <VSpace>
<VButton @click="handleFetchSinglePages"></VButton> <VButton @click="refetch"></VButton>
<VButton <VButton
v-permission="['system:singlepages:view']" v-permission="['system:singlepages:view']"
:route="{ name: 'SinglePages' }" :route="{ name: 'SinglePages' }"
@ -345,7 +312,7 @@ function handleClearKeyword() {
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 v-for="(singlePage, index) in singlePages.items" :key="index"> <li v-for="(singlePage, index) in singlePages" :key="index">
<VEntity :is-selected="checkSelection(singlePage.page)"> <VEntity :is-selected="checkSelection(singlePage.page)">
<template <template
v-if="currentUserHasPermission(['system:singlepages:manage'])" v-if="currentUserHasPermission(['system:singlepages:manage'])"
@ -445,11 +412,10 @@ function handleClearKeyword() {
<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="singlePages.page" v-model:page="page"
:size="singlePages.size" v-model:size="size"
:total="singlePages.total" :total="total"
:size-options="[20, 30, 50, 100]" :size-options="[20, 30, 50, 100]"
@change="handlePaginationChange"
/> />
</div> </div>
</template> </template>

View File

@ -26,273 +26,27 @@ import {
} from "@halo-dev/components"; } from "@halo-dev/components";
import SinglePageSettingModal from "./components/SinglePageSettingModal.vue"; import SinglePageSettingModal from "./components/SinglePageSettingModal.vue";
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue"; import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
import { computed, onMounted, ref, watch } from "vue"; import { computed, ref, watch } from "vue";
import type { import type { ListedSinglePage, SinglePage, User } from "@halo-dev/api-client";
ListedSinglePageList,
SinglePage,
User,
} from "@halo-dev/api-client";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import { formatDatetime } from "@/utils/date"; import { formatDatetime } from "@/utils/date";
import { onBeforeRouteLeave, RouterLink } from "vue-router"; import { RouterLink } from "vue-router";
import cloneDeep from "lodash.clonedeep"; import cloneDeep from "lodash.clonedeep";
import { usePermission } from "@/utils/permission"; import { usePermission } from "@/utils/permission";
import { singlePageLabels } from "@/constants/labels"; import { singlePageLabels } from "@/constants/labels";
import FilterTag from "@/components/filter/FilterTag.vue"; import FilterTag from "@/components/filter/FilterTag.vue";
import FilterCleanButton from "@/components/filter/FilterCleanButton.vue"; import FilterCleanButton from "@/components/filter/FilterCleanButton.vue";
import { getNode } from "@formkit/core"; import { getNode } from "@formkit/core";
import { useQuery } from "@tanstack/vue-query";
const { currentUserHasPermission } = usePermission(); const { currentUserHasPermission } = usePermission();
const singlePages = ref<ListedSinglePageList>({
page: 1,
size: 20,
total: 0,
items: [],
first: true,
last: false,
hasNext: false,
hasPrevious: false,
totalPages: 0,
});
const loading = ref(false);
const settingModal = ref(false); const settingModal = ref(false);
const selectedSinglePage = ref<SinglePage>(); const selectedSinglePage = ref<SinglePage>();
const selectedPageNames = ref<string[]>([]); const selectedPageNames = ref<string[]>([]);
const checkedAll = ref(false); const checkedAll = ref(false);
const refreshInterval = ref();
const handleFetchSinglePages = async (options?: {
mute?: boolean;
page?: number;
}) => {
try {
clearInterval(refreshInterval.value);
if (!options?.mute) {
loading.value = true;
}
let contributors: string[] | undefined;
const labelSelector: string[] = ["content.halo.run/deleted=false"];
if (selectedContributor.value) {
contributors = [selectedContributor.value.metadata.name];
}
if (selectedPublishStatusItem.value.value !== undefined) {
labelSelector.push(
`${singlePageLabels.PUBLISHED}=${selectedPublishStatusItem.value.value}`
);
}
if (options?.page) {
singlePages.value.page = options.page;
}
const { data } = await apiClient.singlePage.listSinglePages({
labelSelector,
page: singlePages.value.page,
size: singlePages.value.size,
visible: selectedVisibleItem.value.value,
sort: selectedSortItem.value?.sort,
sortOrder: selectedSortItem.value?.sortOrder,
keyword: keyword.value,
contributor: contributors,
});
singlePages.value = data;
const abnormalSinglePages = singlePages.value.items.filter((singlePage) => {
const { spec, metadata, status } = singlePage.page;
return (
spec.deleted ||
(spec.publish &&
metadata.labels?.[singlePageLabels.PUBLISHED] !== "true") ||
(spec.releaseSnapshot === spec.headSnapshot && status?.inProgress)
);
});
if (abnormalSinglePages.length) {
refreshInterval.value = setInterval(() => {
handleFetchSinglePages({ mute: true });
}, 3000);
}
} catch (error) {
console.error("Failed to fetch single pages", error);
} finally {
loading.value = false;
}
};
onBeforeRouteLeave(() => {
clearInterval(refreshInterval.value);
});
const handlePaginationChange = ({
page,
size,
}: {
page: number;
size: number;
}) => {
singlePages.value.page = page;
singlePages.value.size = size;
handleFetchSinglePages();
};
const handleOpenSettingModal = async (singlePage: SinglePage) => {
const { data } =
await apiClient.extension.singlePage.getcontentHaloRunV1alpha1SinglePage({
name: singlePage.metadata.name,
});
selectedSinglePage.value = data;
settingModal.value = true;
};
const onSettingModalClose = () => {
selectedSinglePage.value = undefined;
handleFetchSinglePages({ mute: true });
};
const handleSelectPrevious = async () => {
const { items, hasPrevious } = singlePages.value;
const index = items.findIndex(
(singlePage) =>
singlePage.page.metadata.name === selectedSinglePage.value?.metadata.name
);
if (index > 0) {
const { data } =
await apiClient.extension.singlePage.getcontentHaloRunV1alpha1SinglePage({
name: items[index - 1].page.metadata.name,
});
selectedSinglePage.value = data;
return;
}
if (index === 0 && hasPrevious) {
singlePages.value.page--;
await handleFetchSinglePages();
selectedSinglePage.value =
singlePages.value.items[singlePages.value.items.length - 1].page;
}
};
const handleSelectNext = async () => {
const { items, hasNext } = singlePages.value;
const index = items.findIndex(
(singlePage) =>
singlePage.page.metadata.name === selectedSinglePage.value?.metadata.name
);
if (index < items.length - 1) {
const { data } =
await apiClient.extension.singlePage.getcontentHaloRunV1alpha1SinglePage({
name: items[index + 1].page.metadata.name,
});
selectedSinglePage.value = data;
return;
}
if (index === items.length - 1 && hasNext) {
singlePages.value.page++;
await handleFetchSinglePages();
selectedSinglePage.value = singlePages.value.items[0].page;
}
};
const checkSelection = (singlePage: SinglePage) => {
return (
singlePage.metadata.name === selectedSinglePage.value?.metadata.name ||
selectedPageNames.value.includes(singlePage.metadata.name)
);
};
const handleCheckAllChange = (e: Event) => {
const { checked } = e.target as HTMLInputElement;
if (checked) {
selectedPageNames.value =
singlePages.value.items.map((singlePage) => {
return singlePage.page.metadata.name;
}) || [];
} else {
selectedPageNames.value = [];
}
};
const handleDelete = async (singlePage: SinglePage) => {
Dialog.warning({
title: "确定要删除该自定义页面吗?",
description: "该操作会将自定义页面放入回收站,后续可以从回收站恢复",
confirmType: "danger",
onConfirm: async () => {
const singlePageToUpdate = cloneDeep(singlePage);
singlePageToUpdate.spec.deleted = true;
await apiClient.extension.singlePage.updatecontentHaloRunV1alpha1SinglePage(
{
name: singlePage.metadata.name,
singlePage: singlePageToUpdate,
}
);
await handleFetchSinglePages();
Toast.success("删除成功");
},
});
};
const handleDeleteInBatch = async () => {
Dialog.warning({
title: "确定要删除选中的自定义页面吗?",
description: "该操作会将自定义页面放入回收站,后续可以从回收站恢复",
confirmType: "danger",
onConfirm: async () => {
await Promise.all(
selectedPageNames.value.map((name) => {
const page = singlePages.value.items.find(
(item) => item.page.metadata.name === name
)?.page;
if (!page) {
return Promise.resolve();
}
page.spec.deleted = true;
return apiClient.extension.singlePage.updatecontentHaloRunV1alpha1SinglePage(
{
name: page.metadata.name,
singlePage: page,
}
);
})
);
await handleFetchSinglePages();
selectedPageNames.value = [];
Toast.success("删除成功");
},
});
};
const getPublishStatus = (singlePage: SinglePage) => {
const { labels } = singlePage.metadata;
return labels?.[singlePageLabels.PUBLISHED] === "true" ? "已发布" : "未发布";
};
const isPublishing = (singlePage: SinglePage) => {
const { spec, status, metadata } = singlePage;
return (
(spec.publish &&
metadata.labels?.[singlePageLabels.PUBLISHED] !== "true") ||
(spec.releaseSnapshot === spec.headSnapshot && status?.inProgress)
);
};
watch(selectedPageNames, (newValue) => {
checkedAll.value = newValue.length === singlePages.value.items?.length;
});
onMounted(handleFetchSinglePages);
// Filters // Filters
interface VisibleItem { interface VisibleItem {
label: string; label: string;
value?: "PUBLIC" | "INTERNAL" | "PRIVATE"; value?: "PUBLIC" | "INTERNAL" | "PRIVATE";
@ -375,22 +129,22 @@ const keyword = ref("");
function handleVisibleItemChange(visibleItem: VisibleItem) { function handleVisibleItemChange(visibleItem: VisibleItem) {
selectedVisibleItem.value = visibleItem; selectedVisibleItem.value = visibleItem;
handleFetchSinglePages({ page: 1 }); page.value = 1;
} }
const handleSelectUser = (user?: User) => { const handleSelectUser = (user?: User) => {
selectedContributor.value = user; selectedContributor.value = user;
handleFetchSinglePages({ page: 1 }); page.value = 1;
}; };
function handlePublishStatusItemChange(publishStatusItem: PublishStatusItem) { function handlePublishStatusItemChange(publishStatusItem: PublishStatusItem) {
selectedPublishStatusItem.value = publishStatusItem; selectedPublishStatusItem.value = publishStatusItem;
handleFetchSinglePages({ page: 1 }); page.value = 1;
} }
function handleSortItemChange(sortItem?: SortItem) { function handleSortItemChange(sortItem?: SortItem) {
selectedSortItem.value = sortItem; selectedSortItem.value = sortItem;
handleFetchSinglePages({ page: 1 }); page.value = 1;
} }
function handleKeywordChange() { function handleKeywordChange() {
@ -398,12 +152,12 @@ function handleKeywordChange() {
if (keywordNode) { if (keywordNode) {
keyword.value = keywordNode._value as string; keyword.value = keywordNode._value as string;
} }
handleFetchSinglePages({ page: 1 }); page.value = 1;
} }
function handleClearKeyword() { function handleClearKeyword() {
keyword.value = ""; keyword.value = "";
handleFetchSinglePages({ page: 1 }); page.value = 1;
} }
const hasFilters = computed(() => { const hasFilters = computed(() => {
@ -422,8 +176,232 @@ function handleClearFilters() {
selectedPublishStatusItem.value = PublishStatusItems[0]; selectedPublishStatusItem.value = PublishStatusItems[0];
selectedSortItem.value = undefined; selectedSortItem.value = undefined;
keyword.value = ""; keyword.value = "";
handleFetchSinglePages({ page: 1 }); page.value = 1;
} }
const page = ref(1);
const size = ref(20);
const total = ref(0);
const hasNext = ref(false);
const hasPrevious = ref(false);
const {
data: singlePages,
isLoading,
isFetching,
refetch,
} = useQuery<ListedSinglePage[]>({
queryKey: [
"singlePages",
selectedContributor,
selectedPublishStatusItem,
page,
size,
selectedVisibleItem,
selectedSortItem,
keyword,
],
queryFn: async () => {
let contributors: string[] | undefined;
const labelSelector: string[] = ["content.halo.run/deleted=false"];
if (selectedContributor.value) {
contributors = [selectedContributor.value.metadata.name];
}
if (selectedPublishStatusItem.value.value !== undefined) {
labelSelector.push(
`${singlePageLabels.PUBLISHED}=${selectedPublishStatusItem.value.value}`
);
}
const { data } = await apiClient.singlePage.listSinglePages({
labelSelector,
page: page.value,
size: size.value,
visible: selectedVisibleItem.value.value,
sort: selectedSortItem.value?.sort,
sortOrder: selectedSortItem.value?.sortOrder,
keyword: keyword.value,
contributor: contributors,
});
total.value = data.total;
hasNext.value = data.hasNext;
hasPrevious.value = data.hasPrevious;
return data.items;
},
refetchOnWindowFocus: false,
refetchInterval(data) {
const abnormalSinglePages = data?.filter((singlePage) => {
const { spec, metadata, status } = singlePage.page;
return (
spec.deleted ||
(spec.publish &&
metadata.labels?.[singlePageLabels.PUBLISHED] !== "true") ||
(spec.releaseSnapshot === spec.headSnapshot && status?.inProgress)
);
});
return abnormalSinglePages?.length ? 3000 : false;
},
});
const handleOpenSettingModal = async (singlePage: SinglePage) => {
const { data } =
await apiClient.extension.singlePage.getcontentHaloRunV1alpha1SinglePage({
name: singlePage.metadata.name,
});
selectedSinglePage.value = data;
settingModal.value = true;
};
const onSettingModalClose = () => {
selectedSinglePage.value = undefined;
refetch();
};
const handleSelectPrevious = async () => {
if (!singlePages.value) return;
const index = singlePages.value.findIndex(
(singlePage) =>
singlePage.page.metadata.name === selectedSinglePage.value?.metadata.name
);
if (index > 0) {
const { data } =
await apiClient.extension.singlePage.getcontentHaloRunV1alpha1SinglePage({
name: singlePages.value[index - 1].page.metadata.name,
});
selectedSinglePage.value = data;
return;
}
if (index === 0 && hasPrevious.value) {
page.value--;
await refetch();
selectedSinglePage.value =
singlePages.value[singlePages.value.length - 1].page;
}
};
const handleSelectNext = async () => {
if (!singlePages.value) return;
const index = singlePages.value.findIndex(
(singlePage) =>
singlePage.page.metadata.name === selectedSinglePage.value?.metadata.name
);
if (index < singlePages.value.length - 1) {
const { data } =
await apiClient.extension.singlePage.getcontentHaloRunV1alpha1SinglePage({
name: singlePages.value[index + 1].page.metadata.name,
});
selectedSinglePage.value = data;
return;
}
if (index === singlePages.value.length - 1 && hasNext.value) {
page.value++;
await refetch();
selectedSinglePage.value = singlePages.value[0].page;
}
};
const checkSelection = (singlePage: SinglePage) => {
return (
singlePage.metadata.name === selectedSinglePage.value?.metadata.name ||
selectedPageNames.value.includes(singlePage.metadata.name)
);
};
const handleCheckAllChange = (e: Event) => {
const { checked } = e.target as HTMLInputElement;
if (checked) {
selectedPageNames.value =
singlePages.value?.map((singlePage) => {
return singlePage.page.metadata.name;
}) || [];
} else {
selectedPageNames.value = [];
}
};
const handleDelete = async (singlePage: SinglePage) => {
Dialog.warning({
title: "确定要删除该自定义页面吗?",
description: "该操作会将自定义页面放入回收站,后续可以从回收站恢复",
confirmType: "danger",
onConfirm: async () => {
const singlePageToUpdate = cloneDeep(singlePage);
singlePageToUpdate.spec.deleted = true;
await apiClient.extension.singlePage.updatecontentHaloRunV1alpha1SinglePage(
{
name: singlePage.metadata.name,
singlePage: singlePageToUpdate,
}
);
await refetch();
Toast.success("删除成功");
},
});
};
const handleDeleteInBatch = async () => {
Dialog.warning({
title: "确定要删除选中的自定义页面吗?",
description: "该操作会将自定义页面放入回收站,后续可以从回收站恢复",
confirmType: "danger",
onConfirm: async () => {
await Promise.all(
selectedPageNames.value.map((name) => {
const page = singlePages.value?.find(
(item) => item.page.metadata.name === name
)?.page;
if (!page) {
return Promise.resolve();
}
return apiClient.extension.singlePage.updatecontentHaloRunV1alpha1SinglePage(
{
name: page.metadata.name,
singlePage: {
...page,
spec: {
...page.spec,
deleted: true,
},
},
}
);
})
);
await refetch();
selectedPageNames.value = [];
Toast.success("删除成功");
},
});
};
const getPublishStatus = (singlePage: SinglePage) => {
const { labels } = singlePage.metadata;
return labels?.[singlePageLabels.PUBLISHED] === "true" ? "已发布" : "未发布";
};
const isPublishing = (singlePage: SinglePage) => {
const { spec, status, metadata } = singlePage;
return (
(spec.publish &&
metadata.labels?.[singlePageLabels.PUBLISHED] !== "true") ||
(spec.releaseSnapshot === spec.headSnapshot && status?.inProgress)
);
};
watch(selectedPageNames, (newValue) => {
checkedAll.value = newValue.length === singlePages.value?.length;
});
</script> </script>
<template> <template>
@ -649,11 +627,11 @@ function handleClearFilters() {
<div class="flex flex-row gap-2"> <div class="flex flex-row gap-2">
<div <div
class="group cursor-pointer rounded p-1 hover:bg-gray-200" class="group cursor-pointer rounded p-1 hover:bg-gray-200"
@click="handleFetchSinglePages()" @click="refetch()"
> >
<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>
@ -663,12 +641,12 @@ function handleClearFilters() {
</div> </div>
</div> </div>
</template> </template>
<VLoading v-if="loading" /> <VLoading v-if="isLoading" />
<Transition v-else-if="!singlePages.items.length" appear name="fade"> <Transition v-else-if="!singlePages?.length" appear name="fade">
<VEmpty message="你可以尝试刷新或者新建页面" title="当前没有页面"> <VEmpty message="你可以尝试刷新或者新建页面" title="当前没有页面">
<template #actions> <template #actions>
<VSpace> <VSpace>
<VButton @click="handleFetchSinglePages"></VButton> <VButton @click="refetch"></VButton>
<VButton <VButton
v-permission="['system:singlepages:manage']" v-permission="['system:singlepages:manage']"
:route="{ name: 'SinglePageEditor' }" :route="{ name: 'SinglePageEditor' }"
@ -688,7 +666,7 @@ function handleClearFilters() {
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 v-for="(singlePage, index) in singlePages.items" :key="index"> <li v-for="(singlePage, index) in singlePages" :key="index">
<VEntity :is-selected="checkSelection(singlePage.page)"> <VEntity :is-selected="checkSelection(singlePage.page)">
<template <template
v-if="currentUserHasPermission(['system:singlepages:manage'])" v-if="currentUserHasPermission(['system:singlepages:manage'])"
@ -838,11 +816,10 @@ function handleClearFilters() {
<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="singlePages.page" v-model:page="page"
:size="singlePages.size" v-model:size="size"
:total="singlePages.total" :total="total"
:size-options="[20, 30, 50, 100]" :size-options="[20, 30, 50, 100]"
@change="handlePaginationChange"
/> />
</div> </div>
</template> </template>