fix: splash screen issue caused by refreshing the list at regular intervals (halo-dev/console#708)

#### What type of PR is this?

/kind bug
/milestone 2.0

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

修复因为 https://github.com/halo-dev/console/pull/703 中添加了 Loading 动画但是没有考虑到定时刷新列表导致页面闪动的问题。

#### Special notes for your reviewer:

/cc @halo-dev/sig-halo-console 

测试方式:删除任意资源,检查列表定时刷新的时候是否出现 loading 状态。

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

```release-note
None
```
pull/3445/head
Ryan Wang 2022-11-24 22:51:07 +08:00 committed by GitHub
parent e7ac73f0b6
commit 73676ee815
13 changed files with 179 additions and 135 deletions

View File

@ -93,17 +93,17 @@ const selectedSortItemValue = computed(() => {
function handleSelectPolicy(policy: Policy | undefined) {
selectedPolicy.value = policy;
handleFetchAttachments(1);
handleFetchAttachments({ page: 1 });
}
function handleSelectUser(user: User | undefined) {
selectedUser.value = user;
handleFetchAttachments(1);
handleFetchAttachments({ page: 1 });
}
function handleSortItemChange(sortItem?: SortItem) {
selectedSortItem.value = sortItem;
handleFetchAttachments(1);
handleFetchAttachments({ page: 1 });
}
function handleKeywordChange() {
@ -111,12 +111,12 @@ function handleKeywordChange() {
if (keywordNode) {
keyword.value = keywordNode._value as string;
}
handleFetchAttachments(1);
handleFetchAttachments({ page: 1 });
}
function handleClearKeyword() {
keyword.value = "";
handleFetchAttachments(1);
handleFetchAttachments({ page: 1 });
}
const hasFilters = computed(() => {
@ -133,7 +133,7 @@ function handleClearFilters() {
selectedUser.value = undefined;
selectedSortItem.value = undefined;
keyword.value = "";
handleFetchAttachments(1);
handleFetchAttachments({ page: 1 });
}
const {
@ -208,16 +208,12 @@ const onDetailModalClose = () => {
selectedAttachment.value = undefined;
nameQuery.value = undefined;
nameQueryAttachment.value = undefined;
setTimeout(() => {
handleFetchAttachments();
}, 200);
handleFetchAttachments({ mute: true });
};
const onUploadModalClose = () => {
routeQueryAction.value = undefined;
setTimeout(() => {
handleFetchAttachments();
}, 200);
handleFetchAttachments({ mute: true });
};
const onGroupChange = () => {

View File

@ -19,7 +19,7 @@ interface useAttachmentControlReturn {
selectedAttachment: Ref<Attachment | undefined>;
selectedAttachments: Ref<Set<Attachment>>;
checkedAll: Ref<boolean>;
handleFetchAttachments: (page?: number) => void;
handleFetchAttachments: (options?: { mute?: boolean; page?: number }) => void;
handlePaginationChange: ({
page,
size,
@ -68,14 +68,19 @@ export function useAttachmentControl(filterOptions?: {
const checkedAll = ref(false);
const refreshInterval = ref();
const handleFetchAttachments = async (page?: number) => {
const handleFetchAttachments = async (options?: {
mute?: boolean;
page?: number;
}) => {
try {
clearInterval(refreshInterval.value);
loading.value = true;
if (!options?.mute) {
loading.value = true;
}
if (page) {
attachments.value.page = page;
if (options?.page) {
attachments.value.page = options.page;
}
const { data } = await apiClient.attachment.searchAttachments({
@ -96,7 +101,7 @@ export function useAttachmentControl(filterOptions?: {
if (deletedAttachments.length) {
refreshInterval.value = setInterval(() => {
handleFetchAttachments();
handleFetchAttachments({ mute: true });
}, 3000);
}
} catch (e) {

View File

@ -44,14 +44,19 @@ const selectedCommentNames = ref<string[]>([]);
const keyword = ref("");
const refreshInterval = ref();
const handleFetchComments = async (page?: number) => {
const handleFetchComments = async (options?: {
mute?: boolean;
page?: number;
}) => {
try {
clearInterval(refreshInterval.value);
loading.value = true;
if (!options?.mute) {
loading.value = true;
}
if (page) {
comments.value.page = page;
if (options?.page) {
comments.value.page = options.page;
}
const { data } = await apiClient.comment.listComments({
@ -70,7 +75,7 @@ const handleFetchComments = async (page?: number) => {
if (deletedComments.length) {
refreshInterval.value = setInterval(() => {
handleFetchComments();
handleFetchComments({ mute: true });
}, 3000);
}
} catch (error) {
@ -238,7 +243,7 @@ const handleApprovedFilterItemChange = (filterItem: {
}) => {
selectedApprovedFilterItem.value = filterItem;
selectedCommentNames.value = [];
handleFetchComments(1);
handleFetchComments({ page: 1 });
};
const handleSortFilterItemChange = (filterItem: {
@ -247,12 +252,12 @@ const handleSortFilterItemChange = (filterItem: {
}) => {
selectedSortFilterItem.value = filterItem;
selectedCommentNames.value = [];
handleFetchComments(1);
handleFetchComments({ page: 1 });
};
function handleSelectUser(user: User | undefined) {
selectedUser.value = user;
handleFetchComments(1);
handleFetchComments({ page: 1 });
}
function handleKeywordChange() {
@ -260,12 +265,12 @@ function handleKeywordChange() {
if (keywordNode) {
keyword.value = keywordNode._value as string;
}
handleFetchComments(1);
handleFetchComments({ page: 1 });
}
function handleClearKeyword() {
keyword.value = "";
handleFetchComments(1);
handleFetchComments({ page: 1 });
}
const hasFilters = computed(() => {
@ -282,7 +287,7 @@ function handleClearFilters() {
selectedSortFilterItem.value = SortFilterItems[0];
selectedUser.value = undefined;
keyword.value = "";
handleFetchComments(1);
handleFetchComments({ page: 1 });
}
</script>
<template>
@ -479,7 +484,7 @@ function handleClearFilters() {
<CommentListItem
:comment="comment"
:is-selected="checkSelection(comment)"
@reload="handleFetchComments"
@reload="handleFetchComments({ mute: true })"
>
<template #checkbox>
<input

View File

@ -10,6 +10,7 @@ import {
VEmpty,
IconAddCircle,
IconExternalLinkLine,
VLoading,
} from "@halo-dev/components";
import ReplyCreationModal from "./ReplyCreationModal.vue";
import type {
@ -114,13 +115,18 @@ const handleApprove = async () => {
}
};
const handleFetchReplies = async () => {
const handleFetchReplies = async (options?: { mute?: boolean }) => {
try {
clearInterval(refreshInterval.value);
loading.value = true;
if (!options?.mute) {
loading.value = true;
}
const { data } = await apiClient.reply.listReplies({
commentName: props.comment.comment.metadata.name,
page: 0,
size: 0,
});
replies.value = data.items;
@ -130,7 +136,7 @@ const handleFetchReplies = async () => {
if (deletedReplies.length) {
refreshInterval.value = setInterval(() => {
handleFetchReplies();
handleFetchReplies({ mute: true });
}, 3000);
}
} catch (error) {
@ -185,7 +191,7 @@ const onTriggerReply = (reply: ListedReply) => {
const onReplyCreationModalClose = () => {
selectedReply.value = undefined;
handleFetchReplies();
handleFetchReplies({ mute: true });
};
// Subject ref processing
@ -391,33 +397,35 @@ const subjectRefResult = computed(() => {
<div
class="ml-8 mt-3 divide-y divide-gray-100 rounded-base border-t border-gray-100 pt-3"
>
<VEmpty
v-if="!replies.length && !loading"
message="你可以尝试刷新或者创建新回复"
title="当前没有回复"
>
<template #actions>
<VSpace>
<VButton @click="handleFetchReplies"></VButton>
<VButton type="secondary" @click="replyModal = true">
<template #icon>
<IconAddCircle class="h-full w-full" />
</template>
创建新回复
</VButton>
</VSpace>
</template>
</VEmpty>
<ReplyListItem
v-for="reply in replies"
v-else
:key="reply.reply.metadata.name"
:class="{ 'hover:bg-white': showReplies }"
:reply="reply"
:replies="replies"
@reload="handleFetchReplies"
@reply="onTriggerReply"
></ReplyListItem>
<VLoading v-if="loading" />
<Transition v-else-if="!replies.length" appear name="fade">
<VEmpty message="你可以尝试刷新或者创建新回复" title="当前没有回复">
<template #actions>
<VSpace>
<VButton @click="handleFetchReplies"></VButton>
<VButton type="secondary" @click="replyModal = true">
<template #icon>
<IconAddCircle class="h-full w-full" />
</template>
创建新回复
</VButton>
</VSpace>
</template>
</VEmpty>
</Transition>
<Transition v-else appear name="fade">
<div>
<ReplyListItem
v-for="reply in replies"
:key="reply.reply.metadata.name"
:class="{ 'hover:bg-white': showReplies }"
:reply="reply"
:replies="replies"
@reload="handleFetchReplies({ mute: true })"
@reply="onTriggerReply"
></ReplyListItem>
</div>
</Transition>
</div>
</template>
</VEntity>

View File

@ -45,14 +45,19 @@ const checkedAll = ref(false);
const refreshInterval = ref();
const keyword = ref("");
const handleFetchSinglePages = async (page?: number) => {
const handleFetchSinglePages = async (options?: {
mute?: boolean;
page?: number;
}) => {
try {
clearInterval(refreshInterval.value);
loading.value = true;
if (!options?.mute) {
loading.value = true;
}
if (page) {
singlePages.value.page = page;
if (options?.page) {
singlePages.value.page = options.page;
}
const { data } = await apiClient.singlePage.listSinglePages({
@ -72,7 +77,7 @@ const handleFetchSinglePages = async (page?: number) => {
if (deletedSinglePages.length) {
refreshInterval.value = setInterval(() => {
handleFetchSinglePages();
handleFetchSinglePages({ mute: true });
}, 3000);
}
} catch (error) {
@ -212,12 +217,12 @@ function handleKeywordChange() {
if (keywordNode) {
keyword.value = keywordNode._value as string;
}
handleFetchSinglePages(1);
handleFetchSinglePages({ page: 1 });
}
function handleClearKeyword() {
keyword.value = "";
handleFetchSinglePages(1);
handleFetchSinglePages({ page: 1 });
}
</script>

View File

@ -58,11 +58,16 @@ const selectedPageNames = ref<string[]>([]);
const checkedAll = ref(false);
const refreshInterval = ref();
const handleFetchSinglePages = async (page?: number) => {
const handleFetchSinglePages = async (options?: {
mute?: boolean;
page?: number;
}) => {
try {
clearInterval(refreshInterval.value);
loading.value = true;
if (!options?.mute) {
loading.value = true;
}
let contributors: string[] | undefined;
const labelSelector: string[] = ["content.halo.run/deleted=false"];
@ -77,8 +82,8 @@ const handleFetchSinglePages = async (page?: number) => {
);
}
if (page) {
singlePages.value.page = page;
if (options?.page) {
singlePages.value.page = options.page;
}
const { data } = await apiClient.singlePage.listSinglePages({
@ -105,8 +110,8 @@ const handleFetchSinglePages = async (page?: number) => {
if (abnormalSinglePages.length) {
refreshInterval.value = setInterval(() => {
handleFetchSinglePages();
}, 1000);
handleFetchSinglePages({ mute: true });
}, 3000);
}
} catch (error) {
console.error("Failed to fetch single pages", error);
@ -142,7 +147,7 @@ const handleOpenSettingModal = async (singlePage: SinglePage) => {
const onSettingModalClose = () => {
selectedSinglePage.value = undefined;
handleFetchSinglePages();
handleFetchSinglePages({ mute: true });
};
const handleSelectPrevious = async () => {
@ -361,22 +366,22 @@ const keyword = ref("");
function handleVisibleItemChange(visibleItem: VisibleItem) {
selectedVisibleItem.value = visibleItem;
handleFetchSinglePages(1);
handleFetchSinglePages({ page: 1 });
}
const handleSelectUser = (user?: User) => {
selectedContributor.value = user;
handleFetchSinglePages(1);
handleFetchSinglePages({ page: 1 });
};
function handlePublishStatusItemChange(publishStatusItem: PublishStatusItem) {
selectedPublishStatusItem.value = publishStatusItem;
handleFetchSinglePages(1);
handleFetchSinglePages({ page: 1 });
}
function handleSortItemChange(sortItem?: SortItem) {
selectedSortItem.value = sortItem;
handleFetchSinglePages(1);
handleFetchSinglePages({ page: 1 });
}
function handleKeywordChange() {
@ -384,12 +389,12 @@ function handleKeywordChange() {
if (keywordNode) {
keyword.value = keywordNode._value as string;
}
handleFetchSinglePages(1);
handleFetchSinglePages({ page: 1 });
}
function handleClearKeyword() {
keyword.value = "";
handleFetchSinglePages(1);
handleFetchSinglePages({ page: 1 });
}
const hasFilters = computed(() => {
@ -408,7 +413,7 @@ function handleClearFilters() {
selectedPublishStatusItem.value = PublishStatusItems[0];
selectedSortItem.value = undefined;
keyword.value = "";
handleFetchSinglePages(1);
handleFetchSinglePages({ page: 1 });
}
</script>

View File

@ -64,11 +64,16 @@ const checkedAll = ref(false);
const selectedPostNames = ref<string[]>([]);
const refreshInterval = ref();
const handleFetchPosts = async (page?: number) => {
const handleFetchPosts = async (options?: {
mute?: boolean;
page?: number;
}) => {
try {
clearInterval(refreshInterval.value);
loading.value = true;
if (!options?.mute) {
loading.value = true;
}
let categories: string[] | undefined;
let tags: string[] | undefined;
@ -96,8 +101,8 @@ const handleFetchPosts = async (page?: number) => {
);
}
if (page) {
posts.value.page = page;
if (options?.page) {
posts.value.page = options.page;
}
const { data } = await apiClient.post.listPosts({
@ -126,8 +131,8 @@ const handleFetchPosts = async (page?: number) => {
if (abnormalPosts.length) {
refreshInterval.value = setInterval(() => {
handleFetchPosts();
}, 1000);
handleFetchPosts({ mute: true });
}, 3000);
}
} catch (e) {
console.error("Failed to fetch posts", e);
@ -164,7 +169,7 @@ const handleOpenSettingModal = async (post: Post) => {
const onSettingModalClose = () => {
selectedPost.value = undefined;
handleFetchPosts();
handleFetchPosts({ mute: true });
};
const handleSelectPrevious = async () => {
@ -376,32 +381,32 @@ const keyword = ref("");
function handleVisibleItemChange(visibleItem: VisibleItem) {
selectedVisibleItem.value = visibleItem;
handleFetchPosts(1);
handleFetchPosts({ page: 1 });
}
function handlePublishStatusItemChange(publishStatusItem: PublishStatuItem) {
selectedPublishStatusItem.value = publishStatusItem;
handleFetchPosts(1);
handleFetchPosts({ page: 1 });
}
function handleSortItemChange(sortItem?: SortItem) {
selectedSortItem.value = sortItem;
handleFetchPosts(1);
handleFetchPosts({ page: 1 });
}
function handleCategoryChange(category?: Category) {
selectedCategory.value = category;
handleFetchPosts(1);
handleFetchPosts({ page: 1 });
}
function handleTagChange(tag?: Tag) {
selectedTag.value = tag;
handleFetchPosts(1);
handleFetchPosts({ page: 1 });
}
function handleContributorChange(user?: User) {
selectedContributor.value = user;
handleFetchPosts(1);
handleFetchPosts({ page: 1 });
}
function handleKeywordChange() {
@ -409,12 +414,12 @@ function handleKeywordChange() {
if (keywordNode) {
keyword.value = keywordNode._value as string;
}
handleFetchPosts(1);
handleFetchPosts({ page: 1 });
}
function handleClearKeyword() {
keyword.value = "";
handleFetchPosts(1);
handleFetchPosts({ page: 1 });
}
function handleClearFilters() {
@ -425,7 +430,7 @@ function handleClearFilters() {
selectedTag.value = undefined;
selectedContributor.value = undefined;
keyword.value = "";
handleFetchPosts(1);
handleFetchPosts({ page: 1 });
}
const hasFilters = computed(() => {

View File

@ -11,7 +11,7 @@ interface usePostCategoryReturn {
categories: Ref<Category[]>;
categoriesTree: Ref<CategoryTree[]>;
loading: Ref<boolean>;
handleFetchCategories: () => void;
handleFetchCategories: (fetchOptions?: { mute?: boolean }) => void;
handleDelete: (category: CategoryTree) => void;
}
@ -25,11 +25,13 @@ export function usePostCategory(options?: {
const loading = ref(false);
const refreshInterval = ref();
const handleFetchCategories = async () => {
const handleFetchCategories = async (fetchOptions?: { mute?: boolean }) => {
try {
clearInterval(refreshInterval.value);
loading.value = true;
if (!fetchOptions?.mute) {
loading.value = true;
}
const { data } =
await apiClient.extension.category.listcontentHaloRunV1alpha1Category({
page: 0,
@ -44,7 +46,7 @@ export function usePostCategory(options?: {
if (deletedCategories.length) {
refreshInterval.value = setInterval(() => {
handleFetchCategories();
handleFetchCategories({ mute: true });
}, 3000);
}
} catch (e) {

View File

@ -8,7 +8,7 @@ import { onBeforeRouteLeave } from "vue-router";
interface usePostTagReturn {
tags: Ref<Tag[]>;
loading: Ref<boolean>;
handleFetchTags: () => void;
handleFetchTags: (fetchOptions?: { mute?: boolean }) => void;
handleDelete: (tag: Tag) => void;
}
@ -21,11 +21,13 @@ export function usePostTag(options?: {
const loading = ref(false);
const refreshInterval = ref();
const handleFetchTags = async () => {
const handleFetchTags = async (fetchOptions?: { mute?: boolean }) => {
try {
clearInterval(refreshInterval.value);
loading.value = true;
if (!fetchOptions?.mute) {
loading.value = true;
}
const { data } =
await apiClient.extension.tag.listcontentHaloRunV1alpha1Tag({
page: 0,
@ -40,7 +42,7 @@ export function usePostTag(options?: {
if (deletedTags.length) {
refreshInterval.value = setInterval(() => {
handleFetchTags();
handleFetchTags({ mute: true });
}, 3000);
}
} catch (e) {

View File

@ -38,11 +38,13 @@ const menuListRef = ref();
const menuItemEditingModal = ref();
const refreshInterval = ref();
const handleFetchMenuItems = async () => {
const handleFetchMenuItems = async (options?: { mute?: boolean }) => {
try {
clearInterval(refreshInterval.value);
loading.value = true;
if (!options?.mute) {
loading.value = true;
}
if (!selectedMenu.value?.spec.menuItems) {
return;
@ -65,7 +67,7 @@ const handleFetchMenuItems = async () => {
if (deletedMenuItems.length) {
refreshInterval.value = setInterval(() => {
handleFetchMenuItems();
handleFetchMenuItems({ mute: true });
}, 3000);
}
} catch (e) {
@ -113,7 +115,7 @@ const onMenuItemSaved = async (menuItem: MenuItem) => {
}
await menuListRef.value.handleFetchMenus();
await handleFetchMenuItems();
await handleFetchMenuItems({ mute: true });
};
const handleUpdateInBatch = useDebounceFn(async () => {
@ -131,7 +133,7 @@ const handleUpdateInBatch = useDebounceFn(async () => {
console.log("Failed to update menu items", e);
} finally {
await menuListRef.value.handleFetchMenus();
await handleFetchMenuItems();
await handleFetchMenuItems({ mute: true });
}
}, 500);
@ -191,7 +193,7 @@ const handleDelete = async (menuItem: MenuTreeItem) => {
<MenuList
ref="menuListRef"
v-model:selected-menu="selectedMenu"
@select="handleFetchMenuItems"
@select="handleFetchMenuItems()"
/>
</div>
<div class="flex-1">
@ -229,7 +231,7 @@ const handleDelete = async (menuItem: MenuTreeItem) => {
>
<template #actions>
<VSpace>
<VButton @click="handleFetchMenuItems"> </VButton>
<VButton @click="handleFetchMenuItems()"> </VButton>
<VButton
v-permission="['system:menus:manage']"
type="primary"

View File

@ -41,12 +41,14 @@ const selectedMenuToUpdate = ref<Menu>();
const menuEditingModal = ref<boolean>(false);
const refreshInterval = ref();
const handleFetchMenus = async () => {
const handleFetchMenus = async (options?: { mute?: boolean }) => {
selectedMenuToUpdate.value = undefined;
try {
clearInterval(refreshInterval.value);
loading.value = true;
if (!options?.mute) {
loading.value = true;
}
const { data } = await apiClient.extension.menu.listv1alpha1Menu();
menus.value = data.items;
@ -67,7 +69,7 @@ const handleFetchMenus = async () => {
if (deletedMenus.length) {
refreshInterval.value = setInterval(() => {
handleFetchMenus();
handleFetchMenus({ mute: true });
}, 3000);
}
} catch (e) {
@ -187,7 +189,7 @@ onMounted(handleFetchPrimaryMenuName);
<MenuEditingModal
v-model:visible="menuEditingModal"
:menu="selectedMenuToUpdate"
@close="handleFetchMenus"
@close="handleFetchMenus()"
@created="handleSelect"
/>
<VCard :body-class="['!p-0']" title="菜单">
@ -196,7 +198,7 @@ onMounted(handleFetchPrimaryMenuName);
<VEmpty message="你可以尝试刷新或者新建菜单" title="当前没有菜单">
<template #actions>
<VSpace>
<VButton size="sm" @click="handleFetchMenus"> </VButton>
<VButton size="sm" @click="handleFetchMenus()"> </VButton>
</VSpace>
</template>
</VEmpty>

View File

@ -59,11 +59,13 @@ const modalTitle = computed(() => {
const { activatedTheme } = storeToRefs(useThemeStore());
const handleFetchThemes = async () => {
const handleFetchThemes = async (options?: { mute?: boolean }) => {
try {
clearInterval(refreshInterval.value);
loading.value = true;
if (!options?.mute) {
loading.value = true;
}
const { data } = await apiClient.theme.listThemes({
page: 0,
size: 0,
@ -81,7 +83,7 @@ const handleFetchThemes = async () => {
if (deletedThemes.length) {
refreshInterval.value = setInterval(() => {
handleFetchThemes();
handleFetchThemes({ mute: true });
}, 3000);
}
} catch (e) {
@ -223,7 +225,7 @@ const handleOpenPreview = (theme: Theme) => {
>
<template #actions>
<VSpace>
<VButton :loading="loading" @click="handleFetchThemes">
<VButton :loading="loading" @click="handleFetchThemes()">
刷新
</VButton>
<VButton

View File

@ -41,14 +41,19 @@ const pluginInstall = ref(false);
const keyword = ref("");
const refreshInterval = ref();
const handleFetchPlugins = async (page?: number) => {
const handleFetchPlugins = async (options?: {
mute?: boolean;
page?: number;
}) => {
try {
clearInterval(refreshInterval.value);
loading.value = true;
if (!options?.mute) {
loading.value = true;
}
if (page) {
plugins.value.page = page;
if (options?.page) {
plugins.value.page = options.page;
}
const { data } = await apiClient.plugin.listPlugins({
@ -69,7 +74,7 @@ const handleFetchPlugins = async (page?: number) => {
if (deletedPlugins.length) {
refreshInterval.value = setInterval(() => {
handleFetchPlugins();
handleFetchPlugins({ mute: true });
}, 3000);
}
} catch (e) {
@ -139,12 +144,12 @@ const selectedSortItem = ref<SortItem>();
function handleEnabledItemChange(enabledItem: EnabledItem) {
selectedEnabledItem.value = enabledItem;
handleFetchPlugins(1);
handleFetchPlugins({ page: 1 });
}
function handleSortItemChange(sortItem?: SortItem) {
selectedSortItem.value = sortItem;
handleFetchPlugins(1);
handleFetchPlugins({ page: 1 });
}
function handleKeywordChange() {
@ -152,12 +157,12 @@ function handleKeywordChange() {
if (keywordNode) {
keyword.value = keywordNode._value as string;
}
handleFetchPlugins(1);
handleFetchPlugins({ page: 1 });
}
function handleClearKeyword() {
keyword.value = "";
handleFetchPlugins(1);
handleFetchPlugins({ page: 1 });
}
const hasFilters = computed(() => {
@ -172,14 +177,14 @@ function handleClearFilters() {
selectedEnabledItem.value = undefined;
selectedSortItem.value = undefined;
keyword.value = "";
handleFetchPlugins(1);
handleFetchPlugins({ page: 1 });
}
</script>
<template>
<PluginUploadModal
v-if="currentUserHasPermission(['system:plugins:manage'])"
v-model:visible="pluginInstall"
@close="handleFetchPlugins"
@close="handleFetchPlugins()"
/>
<VPageHeader title="插件">
@ -320,7 +325,7 @@ function handleClearFilters() {
>
<template #actions>
<VSpace>
<VButton @click="handleFetchPlugins"></VButton>
<VButton @click="handleFetchPlugins()"></VButton>
<VButton
v-permission="['system:plugins:manage']"
type="secondary"
@ -342,7 +347,7 @@ function handleClearFilters() {
role="list"
>
<li v-for="(plugin, index) in plugins.items" :key="index">
<PluginListItem :plugin="plugin" @reload="handleFetchPlugins" />
<PluginListItem :plugin="plugin" @reload="handleFetchPlugins()" />
</li>
</ul>
</Transition>