diff --git a/src/modules/contents/attachments/composables/use-attachment.ts b/src/modules/contents/attachments/composables/use-attachment.ts index b77d0e826..7eebbf2c1 100644 --- a/src/modules/contents/attachments/composables/use-attachment.ts +++ b/src/modules/contents/attachments/composables/use-attachment.ts @@ -11,6 +11,7 @@ import type { AttachmentLike } from "@halo-dev/console-shared"; import { apiClient } from "@/utils/api-client"; import { Dialog } from "@halo-dev/components"; import type { Content, Editor } from "@halo-dev/richtext-editor"; +import { onBeforeRouteLeave } from "vue-router"; interface useAttachmentControlReturn { attachments: Ref; @@ -63,9 +64,12 @@ export function useAttachmentControl(filterOptions?: { const selectedAttachment = ref(); const selectedAttachments = ref>(new Set()); const checkedAll = ref(false); + const refreshInterval = ref(); const handleFetchAttachments = async () => { try { + clearInterval(refreshInterval.value); + loading.value = true; const { data } = await apiClient.attachment.searchAttachments({ policy: policy?.value?.metadata.name, @@ -76,6 +80,16 @@ export function useAttachmentControl(filterOptions?: { size: attachments.value.size, }); attachments.value = data; + + const deletedAttachments = attachments.value.items.filter( + (attachment) => !!attachment.metadata.deletionTimestamp + ); + + if (deletedAttachments.length) { + refreshInterval.value = setInterval(() => { + handleFetchAttachments(); + }, 3000); + } } catch (e) { console.error("Failed to fetch attachments", e); } finally { @@ -83,6 +97,10 @@ export function useAttachmentControl(filterOptions?: { } }; + onBeforeRouteLeave(() => { + clearInterval(refreshInterval.value); + }); + const handlePaginationChange = async ({ page, size, diff --git a/src/modules/contents/comments/CommentList.vue b/src/modules/contents/comments/CommentList.vue index 4875a766f..aa1af273f 100644 --- a/src/modules/contents/comments/CommentList.vue +++ b/src/modules/contents/comments/CommentList.vue @@ -21,6 +21,7 @@ import type { } from "@halo-dev/api-client"; import { onMounted, ref, watch } from "vue"; import { apiClient } from "@/utils/api-client"; +import { onBeforeRouteLeave } from "vue-router"; const comments = ref({ page: 1, @@ -37,9 +38,12 @@ const checkAll = ref(false); const selectedComment = ref(); const selectedCommentNames = ref([]); const keyword = ref(""); +const refreshInterval = ref(); const handleFetchComments = async () => { try { + clearInterval(refreshInterval.value); + loading.value = true; const { data } = await apiClient.comment.listComments({ page: comments.value.page, @@ -47,10 +51,19 @@ const handleFetchComments = async () => { approved: selectedApprovedFilterItem.value.value, sort: selectedSortFilterItem.value.value, keyword: keyword.value, - ownerKind: "User", ownerName: selectedUser.value?.metadata.name, }); comments.value = data; + + const deletedComments = comments.value.items.filter( + (comment) => !!comment.comment.metadata.deletionTimestamp + ); + + if (deletedComments.length) { + refreshInterval.value = setInterval(() => { + handleFetchComments(); + }, 3000); + } } catch (error) { console.log("Failed to fetch comments", error); } finally { @@ -58,6 +71,10 @@ const handleFetchComments = async () => { } }; +onBeforeRouteLeave(() => { + clearInterval(refreshInterval.value); +}); + const handlePaginationChange = ({ page, size, diff --git a/src/modules/contents/comments/components/CommentListItem.vue b/src/modules/contents/comments/components/CommentListItem.vue index 512346af2..e3b0fc8b2 100644 --- a/src/modules/contents/comments/components/CommentListItem.vue +++ b/src/modules/contents/comments/components/CommentListItem.vue @@ -20,10 +20,10 @@ import type { SinglePage, } from "@halo-dev/api-client"; import { formatDatetime } from "@/utils/date"; -import { computed, provide, ref, watch, type Ref } from "vue"; +import { computed, onMounted, provide, ref, watch, type Ref } from "vue"; import ReplyListItem from "./ReplyListItem.vue"; import { apiClient } from "@/utils/api-client"; -import type { RouteLocationRaw } from "vue-router"; +import { onBeforeRouteLeave, type RouteLocationRaw } from "vue-router"; import cloneDeep from "lodash.clonedeep"; import { usePermission } from "@/utils/permission"; @@ -50,6 +50,7 @@ const hoveredReply = ref(); const loading = ref(false); const showReplies = ref(false); const replyModal = ref(false); +const refreshInterval = ref(); provide>("hoveredReply", hoveredReply); @@ -115,11 +116,23 @@ const handleApprove = async () => { const handleFetchReplies = async () => { try { + clearInterval(refreshInterval.value); + loading.value = true; const { data } = await apiClient.reply.listReplies({ commentName: props.comment.comment.metadata.name, }); replies.value = data.items; + + const deletedReplies = replies.value.filter( + (reply) => !!reply.reply.metadata.deletionTimestamp + ); + + if (deletedReplies.length) { + refreshInterval.value = setInterval(() => { + handleFetchReplies(); + }, 3000); + } } catch (error) { console.error("Failed to fetch comment replies", error); } finally { @@ -127,6 +140,14 @@ const handleFetchReplies = async () => { } }; +onMounted(() => { + clearInterval(refreshInterval.value); +}); + +onBeforeRouteLeave(() => { + clearInterval(refreshInterval.value); +}); + watch( () => showReplies.value, (newValue) => { diff --git a/src/modules/contents/pages/SinglePageList.vue b/src/modules/contents/pages/SinglePageList.vue index a70aa246b..b13a54428 100644 --- a/src/modules/contents/pages/SinglePageList.vue +++ b/src/modules/contents/pages/SinglePageList.vue @@ -31,7 +31,7 @@ import type { } from "@halo-dev/api-client"; import { apiClient } from "@/utils/api-client"; import { formatDatetime } from "@/utils/date"; -import { RouterLink } from "vue-router"; +import { onBeforeRouteLeave, RouterLink } from "vue-router"; import cloneDeep from "lodash.clonedeep"; import { usePermission } from "@/utils/permission"; @@ -58,9 +58,12 @@ const settingModal = ref(false); const selectedSinglePage = ref(); const selectedSinglePageWithContent = ref(); const checkAll = ref(false); +const refreshInterval = ref(); const handleFetchSinglePages = async () => { try { + clearInterval(refreshInterval.value); + loading.value = true; let contributors: string[] | undefined; @@ -80,6 +83,16 @@ const handleFetchSinglePages = async () => { contributor: contributors, }); singlePages.value = data; + + const deletedSinglePages = singlePages.value.items.filter( + (singlePage) => !!singlePage.page.metadata.deletionTimestamp + ); + + if (deletedSinglePages.length) { + refreshInterval.value = setInterval(() => { + handleFetchSinglePages(); + }, 3000); + } } catch (error) { console.error("Failed to fetch single pages", error); } finally { @@ -87,6 +100,10 @@ const handleFetchSinglePages = async () => { } }; +onBeforeRouteLeave(() => { + clearInterval(refreshInterval.value); +}); + const handlePaginationChange = ({ page, size, diff --git a/src/modules/contents/posts/PostList.vue b/src/modules/contents/posts/PostList.vue index fa0e463d9..0c4931666 100644 --- a/src/modules/contents/posts/PostList.vue +++ b/src/modules/contents/posts/PostList.vue @@ -39,6 +39,7 @@ import { formatDatetime } from "@/utils/date"; import { usePostCategory } from "@/modules/contents/posts/categories/composables/use-post-category"; import { usePostTag } from "@/modules/contents/posts/tags/composables/use-post-tag"; import { usePermission } from "@/utils/permission"; +import { onBeforeRouteLeave } from "vue-router"; const { currentUserHasPermission } = usePermission(); @@ -64,9 +65,12 @@ const selectedPost = ref(null); const selectedPostWithContent = ref(null); const checkedAll = ref(false); const selectedPostNames = ref([]); +const refreshInterval = ref(); const handleFetchPosts = async () => { try { + clearInterval(refreshInterval.value); + loading.value = true; let categories: string[] | undefined; @@ -101,6 +105,16 @@ const handleFetchPosts = async () => { contributor: contributors, }); posts.value = data; + + const deletedPosts = posts.value.items.filter( + (post) => !!post.post.metadata.deletionTimestamp + ); + + if (deletedPosts.length) { + refreshInterval.value = setInterval(() => { + handleFetchPosts(); + }, 3000); + } } catch (e) { console.error("Failed to fetch posts", e); } finally { @@ -108,6 +122,10 @@ const handleFetchPosts = async () => { } }; +onBeforeRouteLeave(() => { + clearInterval(refreshInterval.value); +}); + const handlePaginationChange = ({ page, size, diff --git a/src/modules/contents/posts/categories/composables/use-post-category.ts b/src/modules/contents/posts/categories/composables/use-post-category.ts index 40041b283..25c23b34c 100644 --- a/src/modules/contents/posts/categories/composables/use-post-category.ts +++ b/src/modules/contents/posts/categories/composables/use-post-category.ts @@ -1,10 +1,11 @@ import { apiClient } from "@/utils/api-client"; import type { Category } from "@halo-dev/api-client"; -import type { Ref } from "vue"; +import { onUnmounted, type Ref } from "vue"; import { onMounted, ref } from "vue"; import type { CategoryTree } from "@/modules/contents/posts/categories/utils"; import { buildCategoriesTree } from "@/modules/contents/posts/categories/utils"; import { Dialog } from "@halo-dev/components"; +import { onBeforeRouteLeave } from "vue-router"; interface usePostCategoryReturn { categories: Ref; @@ -22,9 +23,12 @@ export function usePostCategory(options?: { const categories = ref([] as Category[]); const categoriesTree = ref([] as CategoryTree[]); const loading = ref(false); + const refreshInterval = ref(); const handleFetchCategories = async () => { try { + clearInterval(refreshInterval.value); + loading.value = true; const { data } = await apiClient.extension.category.listcontentHaloRunV1alpha1Category({ @@ -33,6 +37,16 @@ export function usePostCategory(options?: { }); categories.value = data.items; categoriesTree.value = buildCategoriesTree(data.items); + + const deletedCategories = categories.value.filter( + (category) => !!category.metadata.deletionTimestamp + ); + + if (deletedCategories.length) { + refreshInterval.value = setInterval(() => { + handleFetchCategories(); + }, 3000); + } } catch (e) { console.error("Failed to fetch categories", e); } finally { @@ -40,6 +54,14 @@ export function usePostCategory(options?: { } }; + onUnmounted(() => { + clearInterval(refreshInterval.value); + }); + + onBeforeRouteLeave(() => { + clearInterval(refreshInterval.value); + }); + const handleDelete = async (category: CategoryTree) => { Dialog.warning({ title: "确定要删除该分类吗?", diff --git a/src/modules/contents/posts/tags/composables/use-post-tag.ts b/src/modules/contents/posts/tags/composables/use-post-tag.ts index 9b8bf8479..9d2542103 100644 --- a/src/modules/contents/posts/tags/composables/use-post-tag.ts +++ b/src/modules/contents/posts/tags/composables/use-post-tag.ts @@ -1,8 +1,9 @@ import { apiClient } from "@/utils/api-client"; import type { Tag } from "@halo-dev/api-client"; -import type { Ref } from "vue"; +import { onUnmounted, type Ref } from "vue"; import { onMounted, ref } from "vue"; import { Dialog } from "@halo-dev/components"; +import { onBeforeRouteLeave } from "vue-router"; interface usePostTagReturn { tags: Ref; @@ -18,9 +19,12 @@ export function usePostTag(options?: { const tags = ref([] as Tag[]); const loading = ref(false); + const refreshInterval = ref(); const handleFetchTags = async () => { try { + clearInterval(refreshInterval.value); + loading.value = true; const { data } = await apiClient.extension.tag.listcontentHaloRunV1alpha1Tag({ @@ -29,6 +33,16 @@ export function usePostTag(options?: { }); tags.value = data.items; + + const deletedTags = tags.value.filter( + (tag) => !!tag.metadata.deletionTimestamp + ); + + if (deletedTags.length) { + refreshInterval.value = setInterval(() => { + handleFetchTags(); + }, 3000); + } } catch (e) { console.error("Failed to fetch tags", e); } finally { @@ -36,6 +50,14 @@ export function usePostTag(options?: { } }; + onUnmounted(() => { + clearInterval(refreshInterval.value); + }); + + onBeforeRouteLeave(() => { + clearInterval(refreshInterval.value); + }); + const handleDelete = async (tag: Tag) => { Dialog.warning({ title: "确定要删除该标签吗?", diff --git a/src/modules/interface/menus/Menus.vue b/src/modules/interface/menus/Menus.vue index 67980d86a..e65e9a75a 100644 --- a/src/modules/interface/menus/Menus.vue +++ b/src/modules/interface/menus/Menus.vue @@ -12,7 +12,7 @@ import { import MenuItemEditingModal from "./components/MenuItemEditingModal.vue"; import MenuItemListItem from "./components/MenuItemListItem.vue"; import MenuList from "./components/MenuList.vue"; -import { ref } from "vue"; +import { onUnmounted, ref } from "vue"; import { apiClient } from "@/utils/api-client"; import type { Menu, MenuItem } from "@halo-dev/api-client"; import cloneDeep from "lodash.clonedeep"; @@ -25,6 +25,7 @@ import { resetMenuItemsTreePriority, } from "./utils"; import { useDebounceFn } from "@vueuse/core"; +import { onBeforeRouteLeave } from "vue-router"; const menuItems = ref([] as MenuItem[]); const menuTreeItems = ref([] as MenuTreeItem[]); @@ -34,9 +35,12 @@ const selectedParentMenuItem = ref(); const loading = ref(false); const menuListRef = ref(); const menuItemEditingModal = ref(); +const refreshInterval = ref(); const handleFetchMenuItems = async () => { try { + clearInterval(refreshInterval.value); + loading.value = true; if (!selectedMenu.value?.spec.menuItems) { @@ -53,6 +57,16 @@ const handleFetchMenuItems = async () => { menuItems.value = data.items; // Build the menu tree menuTreeItems.value = buildMenuItemsTree(data.items); + + const deletedMenuItems = menuItems.value.filter( + (menuItem) => !!menuItem.metadata.deletionTimestamp + ); + + if (deletedMenuItems.length) { + refreshInterval.value = setInterval(() => { + handleFetchMenuItems(); + }, 3000); + } } catch (e) { console.error("Failed to fetch menu items", e); } finally { @@ -60,6 +74,14 @@ const handleFetchMenuItems = async () => { } }; +onUnmounted(() => { + clearInterval(refreshInterval.value); +}); + +onBeforeRouteLeave(() => { + clearInterval(refreshInterval.value); +}); + const handleOpenEditingModal = (menuItem: MenuTreeItem) => { selectedMenuItem.value = convertMenuTreeItemToMenuItem(menuItem); menuItemEditingModal.value = true; diff --git a/src/modules/interface/menus/components/MenuList.vue b/src/modules/interface/menus/components/MenuList.vue index 0d26fb09f..74f4be205 100644 --- a/src/modules/interface/menus/components/MenuList.vue +++ b/src/modules/interface/menus/components/MenuList.vue @@ -10,11 +10,12 @@ import { VEntityField, } from "@halo-dev/components"; import MenuEditingModal from "./MenuEditingModal.vue"; -import { onMounted, ref } from "vue"; +import { onMounted, onUnmounted, ref } from "vue"; import type { Menu } from "@halo-dev/api-client"; import { apiClient } from "@/utils/api-client"; import { useRouteQuery } from "@vueuse/router"; import { usePermission } from "@/utils/permission"; +import { onBeforeRouteLeave } from "vue-router"; const { currentUserHasPermission } = usePermission(); @@ -36,10 +37,13 @@ const menus = ref([] as Menu[]); const loading = ref(false); const selectedMenuToUpdate = ref(); const menuEditingModal = ref(false); +const refreshInterval = ref(); const handleFetchMenus = async () => { selectedMenuToUpdate.value = undefined; try { + clearInterval(refreshInterval.value); + loading.value = true; const { data } = await apiClient.extension.menu.listv1alpha1Menu(); @@ -54,6 +58,16 @@ const handleFetchMenus = async () => { emit("update:selectedMenu", updatedMenu); } } + + const deletedMenus = menus.value.filter( + (menu) => !!menu.metadata.deletionTimestamp + ); + + if (deletedMenus.length) { + refreshInterval.value = setInterval(() => { + handleFetchMenus(); + }, 3000); + } } catch (e) { console.error("Failed to fetch menus", e); } finally { @@ -61,6 +75,14 @@ const handleFetchMenus = async () => { } }; +onUnmounted(() => { + clearInterval(refreshInterval.value); +}); + +onBeforeRouteLeave(() => { + clearInterval(refreshInterval.value); +}); + const menuQuery = useRouteQuery("menu"); const handleSelect = (menu: Menu) => { emit("update:selectedMenu", menu); diff --git a/src/modules/interface/themes/components/ThemeListModal.vue b/src/modules/interface/themes/components/ThemeListModal.vue index 3d4fa3beb..75575a4dc 100644 --- a/src/modules/interface/themes/components/ThemeListModal.vue +++ b/src/modules/interface/themes/components/ThemeListModal.vue @@ -23,6 +23,7 @@ import { computed, ref, watch } from "vue"; import type { Theme } from "@halo-dev/api-client"; import { apiClient } from "@/utils/api-client"; import { usePermission } from "@/utils/permission"; +import { onBeforeRouteLeave } from "vue-router"; const { currentUserHasPermission } = usePermission(); @@ -51,6 +52,7 @@ const themes = ref([] as Theme[]); const loading = ref(false); const themeInstall = ref(false); const creating = ref(false); +const refreshInterval = ref(); const modalTitle = computed(() => { return activeTab.value === "installed" ? "已安装的主题" : "未安装的主题"; @@ -58,13 +60,31 @@ const modalTitle = computed(() => { const handleFetchThemes = async () => { try { + clearInterval(refreshInterval.value); + loading.value = true; const { data } = await apiClient.theme.listThemes({ page: 0, size: 0, uninstalled: activeTab.value !== "installed", + page: 0, + size: 0, }); themes.value = data.items; + + if (activeTab.value !== "installed") { + return; + } + + const deletedThemes = themes.value.filter( + (theme) => !!theme.metadata.deletionTimestamp + ); + + if (deletedThemes.length) { + refreshInterval.value = setInterval(() => { + handleFetchThemes(); + }, 3000); + } } catch (e) { console.error("Failed to fetch themes", e); } finally { @@ -72,6 +92,10 @@ const handleFetchThemes = async () => { } }; +onBeforeRouteLeave(() => { + clearInterval(refreshInterval.value); +}); + watch( () => activeTab.value, () => { @@ -159,6 +183,8 @@ watch( (visible) => { if (visible) { handleFetchThemes(); + } else { + clearInterval(refreshInterval.value); } } ); diff --git a/src/modules/system/plugins/PluginList.vue b/src/modules/system/plugins/PluginList.vue index 98b4c865f..4aceba2de 100644 --- a/src/modules/system/plugins/PluginList.vue +++ b/src/modules/system/plugins/PluginList.vue @@ -18,6 +18,7 @@ import { onMounted, ref } from "vue"; import { apiClient } from "@/utils/api-client"; import type { PluginList } from "@halo-dev/api-client"; import { usePermission } from "@/utils/permission"; +import { onBeforeRouteLeave } from "vue-router"; const { currentUserHasPermission } = usePermission(); @@ -34,9 +35,12 @@ const plugins = ref({ const loading = ref(false); const pluginInstall = ref(false); const keyword = ref(""); +const refreshInterval = ref(); const handleFetchPlugins = async () => { try { + clearInterval(refreshInterval.value); + loading.value = true; const { data } = await apiClient.plugin.listPlugins({ @@ -50,6 +54,16 @@ const handleFetchPlugins = async () => { }); plugins.value = data; + + const deletedPlugins = plugins.value.items.filter( + (plugin) => !!plugin.metadata.deletionTimestamp + ); + + if (deletedPlugins.length) { + refreshInterval.value = setInterval(() => { + handleFetchPlugins(); + }, 3000); + } } catch (e) { console.error("Failed to fetch plugins", e); } finally { @@ -57,6 +71,10 @@ const handleFetchPlugins = async () => { } }; +onBeforeRouteLeave(() => { + clearInterval(refreshInterval.value); +}); + const handlePaginationChange = ({ page, size, diff --git a/src/modules/system/plugins/components/PluginListItem.vue b/src/modules/system/plugins/components/PluginListItem.vue index 232e58220..6270f266a 100644 --- a/src/modules/system/plugins/components/PluginListItem.vue +++ b/src/modules/system/plugins/components/PluginListItem.vue @@ -69,6 +69,11 @@ const { isStarted, changeStatus, uninstall } = usePluginLifeCycle(plugin); /> + + +