mirror of https://github.com/halo-dev/halo
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
parent
27ee803263
commit
a7ddf37fb2
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue