mirror of https://github.com/halo-dev/halo-admin
feat: automatically refresh the list when it has data that is being deleted (#661)
#### What type of PR is this? /kind feature /milestone 2.0 #### What this PR does / why we need it: 优化部分数据列表的逻辑,支持在检测出有正在删除的数据时,自动定时刷新列表。 #### Special notes for your reviewer: /cc @halo-dev/sig-halo-console 测试方式: 1. 进入任意一个数据列表,比如文章。 2. 删除一个文章,观察是否有自动刷新列表。 3. 切换路由,检查自动刷新是否停止。 #### Does this PR introduce a user-facing change? ```release-note 优化部分数据列表的逻辑,支持在检测出有正在删除的数据时,自动定时刷新列表。 ```pull/665/head
parent
cbcdbf306c
commit
8b6c70e99d
|
@ -11,6 +11,7 @@ import type { AttachmentLike } from "@halo-dev/console-shared";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { Dialog } from "@halo-dev/components";
|
import { Dialog } 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";
|
||||||
|
|
||||||
interface useAttachmentControlReturn {
|
interface useAttachmentControlReturn {
|
||||||
attachments: Ref<AttachmentList>;
|
attachments: Ref<AttachmentList>;
|
||||||
|
@ -63,9 +64,12 @@ export function useAttachmentControl(filterOptions?: {
|
||||||
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 () => {
|
const handleFetchAttachments = async () => {
|
||||||
try {
|
try {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const { data } = await apiClient.attachment.searchAttachments({
|
const { data } = await apiClient.attachment.searchAttachments({
|
||||||
policy: policy?.value?.metadata.name,
|
policy: policy?.value?.metadata.name,
|
||||||
|
@ -76,6 +80,16 @@ export function useAttachmentControl(filterOptions?: {
|
||||||
size: attachments.value.size,
|
size: attachments.value.size,
|
||||||
});
|
});
|
||||||
attachments.value = data;
|
attachments.value = data;
|
||||||
|
|
||||||
|
const deletedAttachments = attachments.value.items.filter(
|
||||||
|
(attachment) => !!attachment.metadata.deletionTimestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deletedAttachments.length) {
|
||||||
|
refreshInterval.value = setInterval(() => {
|
||||||
|
handleFetchAttachments();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to fetch attachments", e);
|
console.error("Failed to fetch attachments", e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -83,6 +97,10 @@ export function useAttachmentControl(filterOptions?: {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onBeforeRouteLeave(() => {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
});
|
||||||
|
|
||||||
const handlePaginationChange = async ({
|
const handlePaginationChange = async ({
|
||||||
page,
|
page,
|
||||||
size,
|
size,
|
||||||
|
|
|
@ -21,6 +21,7 @@ import type {
|
||||||
} from "@halo-dev/api-client";
|
} from "@halo-dev/api-client";
|
||||||
import { onMounted, ref, watch } from "vue";
|
import { onMounted, ref, watch } from "vue";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
|
import { onBeforeRouteLeave } from "vue-router";
|
||||||
|
|
||||||
const comments = ref<ListedCommentList>({
|
const comments = ref<ListedCommentList>({
|
||||||
page: 1,
|
page: 1,
|
||||||
|
@ -37,9 +38,12 @@ const checkAll = ref(false);
|
||||||
const selectedComment = ref<ListedComment>();
|
const selectedComment = ref<ListedComment>();
|
||||||
const selectedCommentNames = ref<string[]>([]);
|
const selectedCommentNames = ref<string[]>([]);
|
||||||
const keyword = ref("");
|
const keyword = ref("");
|
||||||
|
const refreshInterval = ref();
|
||||||
|
|
||||||
const handleFetchComments = async () => {
|
const handleFetchComments = async () => {
|
||||||
try {
|
try {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const { data } = await apiClient.comment.listComments({
|
const { data } = await apiClient.comment.listComments({
|
||||||
page: comments.value.page,
|
page: comments.value.page,
|
||||||
|
@ -47,10 +51,19 @@ const handleFetchComments = async () => {
|
||||||
approved: selectedApprovedFilterItem.value.value,
|
approved: selectedApprovedFilterItem.value.value,
|
||||||
sort: selectedSortFilterItem.value.value,
|
sort: selectedSortFilterItem.value.value,
|
||||||
keyword: keyword.value,
|
keyword: keyword.value,
|
||||||
ownerKind: "User",
|
|
||||||
ownerName: selectedUser.value?.metadata.name,
|
ownerName: selectedUser.value?.metadata.name,
|
||||||
});
|
});
|
||||||
comments.value = data;
|
comments.value = data;
|
||||||
|
|
||||||
|
const deletedComments = comments.value.items.filter(
|
||||||
|
(comment) => !!comment.comment.metadata.deletionTimestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deletedComments.length) {
|
||||||
|
refreshInterval.value = setInterval(() => {
|
||||||
|
handleFetchComments();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Failed to fetch comments", error);
|
console.log("Failed to fetch comments", error);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -58,6 +71,10 @@ const handleFetchComments = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onBeforeRouteLeave(() => {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
});
|
||||||
|
|
||||||
const handlePaginationChange = ({
|
const handlePaginationChange = ({
|
||||||
page,
|
page,
|
||||||
size,
|
size,
|
||||||
|
|
|
@ -20,10 +20,10 @@ import type {
|
||||||
SinglePage,
|
SinglePage,
|
||||||
} from "@halo-dev/api-client";
|
} from "@halo-dev/api-client";
|
||||||
import { formatDatetime } from "@/utils/date";
|
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 ReplyListItem from "./ReplyListItem.vue";
|
||||||
import { apiClient } from "@/utils/api-client";
|
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 cloneDeep from "lodash.clonedeep";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ const hoveredReply = ref<ListedReply>();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const showReplies = ref(false);
|
const showReplies = ref(false);
|
||||||
const replyModal = ref(false);
|
const replyModal = ref(false);
|
||||||
|
const refreshInterval = ref();
|
||||||
|
|
||||||
provide<Ref<ListedReply | undefined>>("hoveredReply", hoveredReply);
|
provide<Ref<ListedReply | undefined>>("hoveredReply", hoveredReply);
|
||||||
|
|
||||||
|
@ -115,11 +116,23 @@ const handleApprove = async () => {
|
||||||
|
|
||||||
const handleFetchReplies = async () => {
|
const handleFetchReplies = async () => {
|
||||||
try {
|
try {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const { data } = await apiClient.reply.listReplies({
|
const { data } = await apiClient.reply.listReplies({
|
||||||
commentName: props.comment.comment.metadata.name,
|
commentName: props.comment.comment.metadata.name,
|
||||||
});
|
});
|
||||||
replies.value = data.items;
|
replies.value = data.items;
|
||||||
|
|
||||||
|
const deletedReplies = replies.value.filter(
|
||||||
|
(reply) => !!reply.reply.metadata.deletionTimestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deletedReplies.length) {
|
||||||
|
refreshInterval.value = setInterval(() => {
|
||||||
|
handleFetchReplies();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch comment replies", error);
|
console.error("Failed to fetch comment replies", error);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -127,6 +140,14 @@ const handleFetchReplies = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeRouteLeave(() => {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => showReplies.value,
|
() => showReplies.value,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
|
|
|
@ -31,7 +31,7 @@ import type {
|
||||||
} from "@halo-dev/api-client";
|
} 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 { RouterLink } from "vue-router";
|
import { onBeforeRouteLeave, RouterLink } from "vue-router";
|
||||||
import cloneDeep from "lodash.clonedeep";
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
|
|
||||||
|
@ -58,9 +58,12 @@ const settingModal = ref(false);
|
||||||
const selectedSinglePage = ref<SinglePage>();
|
const selectedSinglePage = ref<SinglePage>();
|
||||||
const selectedSinglePageWithContent = ref<SinglePageRequest>();
|
const selectedSinglePageWithContent = ref<SinglePageRequest>();
|
||||||
const checkAll = ref(false);
|
const checkAll = ref(false);
|
||||||
|
const refreshInterval = ref();
|
||||||
|
|
||||||
const handleFetchSinglePages = async () => {
|
const handleFetchSinglePages = async () => {
|
||||||
try {
|
try {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
let contributors: string[] | undefined;
|
let contributors: string[] | undefined;
|
||||||
|
@ -80,6 +83,16 @@ const handleFetchSinglePages = async () => {
|
||||||
contributor: contributors,
|
contributor: contributors,
|
||||||
});
|
});
|
||||||
singlePages.value = data;
|
singlePages.value = data;
|
||||||
|
|
||||||
|
const deletedSinglePages = singlePages.value.items.filter(
|
||||||
|
(singlePage) => !!singlePage.page.metadata.deletionTimestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deletedSinglePages.length) {
|
||||||
|
refreshInterval.value = setInterval(() => {
|
||||||
|
handleFetchSinglePages();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch single pages", error);
|
console.error("Failed to fetch single pages", error);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -87,6 +100,10 @@ const handleFetchSinglePages = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onBeforeRouteLeave(() => {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
});
|
||||||
|
|
||||||
const handlePaginationChange = ({
|
const handlePaginationChange = ({
|
||||||
page,
|
page,
|
||||||
size,
|
size,
|
||||||
|
|
|
@ -39,6 +39,7 @@ import { formatDatetime } from "@/utils/date";
|
||||||
import { usePostCategory } from "@/modules/contents/posts/categories/composables/use-post-category";
|
import { usePostCategory } from "@/modules/contents/posts/categories/composables/use-post-category";
|
||||||
import { usePostTag } from "@/modules/contents/posts/tags/composables/use-post-tag";
|
import { usePostTag } from "@/modules/contents/posts/tags/composables/use-post-tag";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
|
import { onBeforeRouteLeave } from "vue-router";
|
||||||
|
|
||||||
const { currentUserHasPermission } = usePermission();
|
const { currentUserHasPermission } = usePermission();
|
||||||
|
|
||||||
|
@ -64,9 +65,12 @@ const selectedPost = ref<Post | null>(null);
|
||||||
const selectedPostWithContent = ref<PostRequest | null>(null);
|
const selectedPostWithContent = ref<PostRequest | null>(null);
|
||||||
const checkedAll = ref(false);
|
const checkedAll = ref(false);
|
||||||
const selectedPostNames = ref<string[]>([]);
|
const selectedPostNames = ref<string[]>([]);
|
||||||
|
const refreshInterval = ref();
|
||||||
|
|
||||||
const handleFetchPosts = async () => {
|
const handleFetchPosts = async () => {
|
||||||
try {
|
try {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
let categories: string[] | undefined;
|
let categories: string[] | undefined;
|
||||||
|
@ -101,6 +105,16 @@ const handleFetchPosts = async () => {
|
||||||
contributor: contributors,
|
contributor: contributors,
|
||||||
});
|
});
|
||||||
posts.value = data;
|
posts.value = data;
|
||||||
|
|
||||||
|
const deletedPosts = posts.value.items.filter(
|
||||||
|
(post) => !!post.post.metadata.deletionTimestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deletedPosts.length) {
|
||||||
|
refreshInterval.value = setInterval(() => {
|
||||||
|
handleFetchPosts();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to fetch posts", e);
|
console.error("Failed to fetch posts", e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -108,6 +122,10 @@ const handleFetchPosts = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onBeforeRouteLeave(() => {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
});
|
||||||
|
|
||||||
const handlePaginationChange = ({
|
const handlePaginationChange = ({
|
||||||
page,
|
page,
|
||||||
size,
|
size,
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import type { Category } from "@halo-dev/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 { onMounted, ref } from "vue";
|
||||||
import type { CategoryTree } from "@/modules/contents/posts/categories/utils";
|
import type { CategoryTree } from "@/modules/contents/posts/categories/utils";
|
||||||
import { buildCategoriesTree } from "@/modules/contents/posts/categories/utils";
|
import { buildCategoriesTree } from "@/modules/contents/posts/categories/utils";
|
||||||
import { Dialog } from "@halo-dev/components";
|
import { Dialog } from "@halo-dev/components";
|
||||||
|
import { onBeforeRouteLeave } from "vue-router";
|
||||||
|
|
||||||
interface usePostCategoryReturn {
|
interface usePostCategoryReturn {
|
||||||
categories: Ref<Category[]>;
|
categories: Ref<Category[]>;
|
||||||
|
@ -22,9 +23,12 @@ export function usePostCategory(options?: {
|
||||||
const categories = ref<Category[]>([] as Category[]);
|
const categories = ref<Category[]>([] as Category[]);
|
||||||
const categoriesTree = ref<CategoryTree[]>([] as CategoryTree[]);
|
const categoriesTree = ref<CategoryTree[]>([] as CategoryTree[]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const refreshInterval = ref();
|
||||||
|
|
||||||
const handleFetchCategories = async () => {
|
const handleFetchCategories = async () => {
|
||||||
try {
|
try {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const { data } =
|
const { data } =
|
||||||
await apiClient.extension.category.listcontentHaloRunV1alpha1Category({
|
await apiClient.extension.category.listcontentHaloRunV1alpha1Category({
|
||||||
|
@ -33,6 +37,16 @@ export function usePostCategory(options?: {
|
||||||
});
|
});
|
||||||
categories.value = data.items;
|
categories.value = data.items;
|
||||||
categoriesTree.value = buildCategoriesTree(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) {
|
} catch (e) {
|
||||||
console.error("Failed to fetch categories", e);
|
console.error("Failed to fetch categories", e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -40,6 +54,14 @@ export function usePostCategory(options?: {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeRouteLeave(() => {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
});
|
||||||
|
|
||||||
const handleDelete = async (category: CategoryTree) => {
|
const handleDelete = async (category: CategoryTree) => {
|
||||||
Dialog.warning({
|
Dialog.warning({
|
||||||
title: "确定要删除该分类吗?",
|
title: "确定要删除该分类吗?",
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import type { Tag } from "@halo-dev/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 { onMounted, ref } from "vue";
|
||||||
import { Dialog } from "@halo-dev/components";
|
import { Dialog } from "@halo-dev/components";
|
||||||
|
import { onBeforeRouteLeave } from "vue-router";
|
||||||
|
|
||||||
interface usePostTagReturn {
|
interface usePostTagReturn {
|
||||||
tags: Ref<Tag[]>;
|
tags: Ref<Tag[]>;
|
||||||
|
@ -18,9 +19,12 @@ export function usePostTag(options?: {
|
||||||
|
|
||||||
const tags = ref<Tag[]>([] as Tag[]);
|
const tags = ref<Tag[]>([] as Tag[]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const refreshInterval = ref();
|
||||||
|
|
||||||
const handleFetchTags = async () => {
|
const handleFetchTags = async () => {
|
||||||
try {
|
try {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const { data } =
|
const { data } =
|
||||||
await apiClient.extension.tag.listcontentHaloRunV1alpha1Tag({
|
await apiClient.extension.tag.listcontentHaloRunV1alpha1Tag({
|
||||||
|
@ -29,6 +33,16 @@ export function usePostTag(options?: {
|
||||||
});
|
});
|
||||||
|
|
||||||
tags.value = data.items;
|
tags.value = data.items;
|
||||||
|
|
||||||
|
const deletedTags = tags.value.filter(
|
||||||
|
(tag) => !!tag.metadata.deletionTimestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deletedTags.length) {
|
||||||
|
refreshInterval.value = setInterval(() => {
|
||||||
|
handleFetchTags();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to fetch tags", e);
|
console.error("Failed to fetch tags", e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -36,6 +50,14 @@ export function usePostTag(options?: {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeRouteLeave(() => {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
});
|
||||||
|
|
||||||
const handleDelete = async (tag: Tag) => {
|
const handleDelete = async (tag: Tag) => {
|
||||||
Dialog.warning({
|
Dialog.warning({
|
||||||
title: "确定要删除该标签吗?",
|
title: "确定要删除该标签吗?",
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
import MenuItemEditingModal from "./components/MenuItemEditingModal.vue";
|
import MenuItemEditingModal from "./components/MenuItemEditingModal.vue";
|
||||||
import MenuItemListItem from "./components/MenuItemListItem.vue";
|
import MenuItemListItem from "./components/MenuItemListItem.vue";
|
||||||
import MenuList from "./components/MenuList.vue";
|
import MenuList from "./components/MenuList.vue";
|
||||||
import { ref } from "vue";
|
import { onUnmounted, ref } from "vue";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import type { Menu, MenuItem } from "@halo-dev/api-client";
|
import type { Menu, MenuItem } from "@halo-dev/api-client";
|
||||||
import cloneDeep from "lodash.clonedeep";
|
import cloneDeep from "lodash.clonedeep";
|
||||||
|
@ -25,6 +25,7 @@ import {
|
||||||
resetMenuItemsTreePriority,
|
resetMenuItemsTreePriority,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
import { useDebounceFn } from "@vueuse/core";
|
import { useDebounceFn } from "@vueuse/core";
|
||||||
|
import { onBeforeRouteLeave } from "vue-router";
|
||||||
|
|
||||||
const menuItems = ref<MenuItem[]>([] as MenuItem[]);
|
const menuItems = ref<MenuItem[]>([] as MenuItem[]);
|
||||||
const menuTreeItems = ref<MenuTreeItem[]>([] as MenuTreeItem[]);
|
const menuTreeItems = ref<MenuTreeItem[]>([] as MenuTreeItem[]);
|
||||||
|
@ -34,9 +35,12 @@ const selectedParentMenuItem = ref<MenuItem>();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const menuListRef = ref();
|
const menuListRef = ref();
|
||||||
const menuItemEditingModal = ref();
|
const menuItemEditingModal = ref();
|
||||||
|
const refreshInterval = ref();
|
||||||
|
|
||||||
const handleFetchMenuItems = async () => {
|
const handleFetchMenuItems = async () => {
|
||||||
try {
|
try {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
if (!selectedMenu.value?.spec.menuItems) {
|
if (!selectedMenu.value?.spec.menuItems) {
|
||||||
|
@ -53,6 +57,16 @@ const handleFetchMenuItems = async () => {
|
||||||
menuItems.value = data.items;
|
menuItems.value = data.items;
|
||||||
// Build the menu tree
|
// Build the menu tree
|
||||||
menuTreeItems.value = buildMenuItemsTree(data.items);
|
menuTreeItems.value = buildMenuItemsTree(data.items);
|
||||||
|
|
||||||
|
const deletedMenuItems = menuItems.value.filter(
|
||||||
|
(menuItem) => !!menuItem.metadata.deletionTimestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deletedMenuItems.length) {
|
||||||
|
refreshInterval.value = setInterval(() => {
|
||||||
|
handleFetchMenuItems();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to fetch menu items", e);
|
console.error("Failed to fetch menu items", e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -60,6 +74,14 @@ const handleFetchMenuItems = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeRouteLeave(() => {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
});
|
||||||
|
|
||||||
const handleOpenEditingModal = (menuItem: MenuTreeItem) => {
|
const handleOpenEditingModal = (menuItem: MenuTreeItem) => {
|
||||||
selectedMenuItem.value = convertMenuTreeItemToMenuItem(menuItem);
|
selectedMenuItem.value = convertMenuTreeItemToMenuItem(menuItem);
|
||||||
menuItemEditingModal.value = true;
|
menuItemEditingModal.value = true;
|
||||||
|
|
|
@ -10,11 +10,12 @@ import {
|
||||||
VEntityField,
|
VEntityField,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import MenuEditingModal from "./MenuEditingModal.vue";
|
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 type { Menu } from "@halo-dev/api-client";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { useRouteQuery } from "@vueuse/router";
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
|
import { onBeforeRouteLeave } from "vue-router";
|
||||||
|
|
||||||
const { currentUserHasPermission } = usePermission();
|
const { currentUserHasPermission } = usePermission();
|
||||||
|
|
||||||
|
@ -36,10 +37,13 @@ const menus = ref<Menu[]>([] as Menu[]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const selectedMenuToUpdate = ref<Menu>();
|
const selectedMenuToUpdate = ref<Menu>();
|
||||||
const menuEditingModal = ref<boolean>(false);
|
const menuEditingModal = ref<boolean>(false);
|
||||||
|
const refreshInterval = ref();
|
||||||
|
|
||||||
const handleFetchMenus = async () => {
|
const handleFetchMenus = async () => {
|
||||||
selectedMenuToUpdate.value = undefined;
|
selectedMenuToUpdate.value = undefined;
|
||||||
try {
|
try {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
const { data } = await apiClient.extension.menu.listv1alpha1Menu();
|
const { data } = await apiClient.extension.menu.listv1alpha1Menu();
|
||||||
|
@ -54,6 +58,16 @@ const handleFetchMenus = async () => {
|
||||||
emit("update:selectedMenu", updatedMenu);
|
emit("update:selectedMenu", updatedMenu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deletedMenus = menus.value.filter(
|
||||||
|
(menu) => !!menu.metadata.deletionTimestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deletedMenus.length) {
|
||||||
|
refreshInterval.value = setInterval(() => {
|
||||||
|
handleFetchMenus();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to fetch menus", e);
|
console.error("Failed to fetch menus", e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -61,6 +75,14 @@ const handleFetchMenus = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeRouteLeave(() => {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
});
|
||||||
|
|
||||||
const menuQuery = useRouteQuery("menu");
|
const menuQuery = useRouteQuery("menu");
|
||||||
const handleSelect = (menu: Menu) => {
|
const handleSelect = (menu: Menu) => {
|
||||||
emit("update:selectedMenu", menu);
|
emit("update:selectedMenu", menu);
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { computed, ref, watch } from "vue";
|
||||||
import type { Theme } from "@halo-dev/api-client";
|
import type { Theme } from "@halo-dev/api-client";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
|
import { onBeforeRouteLeave } from "vue-router";
|
||||||
|
|
||||||
const { currentUserHasPermission } = usePermission();
|
const { currentUserHasPermission } = usePermission();
|
||||||
|
|
||||||
|
@ -51,6 +52,7 @@ const themes = ref<Theme[]>([] as Theme[]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const themeInstall = ref(false);
|
const themeInstall = ref(false);
|
||||||
const creating = ref(false);
|
const creating = ref(false);
|
||||||
|
const refreshInterval = ref();
|
||||||
|
|
||||||
const modalTitle = computed(() => {
|
const modalTitle = computed(() => {
|
||||||
return activeTab.value === "installed" ? "已安装的主题" : "未安装的主题";
|
return activeTab.value === "installed" ? "已安装的主题" : "未安装的主题";
|
||||||
|
@ -58,13 +60,31 @@ const modalTitle = computed(() => {
|
||||||
|
|
||||||
const handleFetchThemes = async () => {
|
const handleFetchThemes = async () => {
|
||||||
try {
|
try {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const { data } = await apiClient.theme.listThemes({
|
const { data } = await apiClient.theme.listThemes({
|
||||||
page: 0,
|
page: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
uninstalled: activeTab.value !== "installed",
|
uninstalled: activeTab.value !== "installed",
|
||||||
|
page: 0,
|
||||||
|
size: 0,
|
||||||
});
|
});
|
||||||
themes.value = data.items;
|
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) {
|
} catch (e) {
|
||||||
console.error("Failed to fetch themes", e);
|
console.error("Failed to fetch themes", e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -72,6 +92,10 @@ const handleFetchThemes = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onBeforeRouteLeave(() => {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => activeTab.value,
|
() => activeTab.value,
|
||||||
() => {
|
() => {
|
||||||
|
@ -159,6 +183,8 @@ watch(
|
||||||
(visible) => {
|
(visible) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
handleFetchThemes();
|
handleFetchThemes();
|
||||||
|
} else {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { onMounted, ref } from "vue";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import type { PluginList } from "@halo-dev/api-client";
|
import type { PluginList } from "@halo-dev/api-client";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
|
import { onBeforeRouteLeave } from "vue-router";
|
||||||
|
|
||||||
const { currentUserHasPermission } = usePermission();
|
const { currentUserHasPermission } = usePermission();
|
||||||
|
|
||||||
|
@ -34,9 +35,12 @@ const plugins = ref<PluginList>({
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const pluginInstall = ref(false);
|
const pluginInstall = ref(false);
|
||||||
const keyword = ref("");
|
const keyword = ref("");
|
||||||
|
const refreshInterval = ref();
|
||||||
|
|
||||||
const handleFetchPlugins = async () => {
|
const handleFetchPlugins = async () => {
|
||||||
try {
|
try {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
const { data } = await apiClient.plugin.listPlugins({
|
const { data } = await apiClient.plugin.listPlugins({
|
||||||
|
@ -50,6 +54,16 @@ const handleFetchPlugins = async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
plugins.value = data;
|
plugins.value = data;
|
||||||
|
|
||||||
|
const deletedPlugins = plugins.value.items.filter(
|
||||||
|
(plugin) => !!plugin.metadata.deletionTimestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deletedPlugins.length) {
|
||||||
|
refreshInterval.value = setInterval(() => {
|
||||||
|
handleFetchPlugins();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to fetch plugins", e);
|
console.error("Failed to fetch plugins", e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -57,6 +71,10 @@ const handleFetchPlugins = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onBeforeRouteLeave(() => {
|
||||||
|
clearInterval(refreshInterval.value);
|
||||||
|
});
|
||||||
|
|
||||||
const handlePaginationChange = ({
|
const handlePaginationChange = ({
|
||||||
page,
|
page,
|
||||||
size,
|
size,
|
||||||
|
|
|
@ -69,6 +69,11 @@ const { isStarted, changeStatus, uninstall } = usePluginLifeCycle(plugin);
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</VEntityField>
|
</VEntityField>
|
||||||
|
<VEntityField v-if="plugin?.metadata.deletionTimestamp">
|
||||||
|
<template #description>
|
||||||
|
<VStatusDot v-tooltip="`删除中`" state="warning" animate />
|
||||||
|
</template>
|
||||||
|
</VEntityField>
|
||||||
<VEntityField>
|
<VEntityField>
|
||||||
<template #description>
|
<template #description>
|
||||||
<a
|
<a
|
||||||
|
|
Loading…
Reference in New Issue