This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 8d7201fe5 fix(web): recover pagination from server after deletes
(#2992)
8d7201fe5 is described below
commit 8d7201fe528fba389373f41ff06865bce5ec8ad4
Author: aias00 <[email protected]>
AuthorDate: Fri Sep 4 15:40:50 2026 +0800
fix(web): recover pagination from server after deletes (#2992)
Topic, Consumer, and ACL pages should reconcile destructive actions through
their server-backed pagination responses so counts, last-page recovery, and
failed selections stay coherent after deletes.
Constraint: base rocketmq-studio already includes #2841 page clamp
behavior; preserve that behavior while adding #2687 reload semantics
Rejected: merging the stale source branch | its historical base carries
broad unrelated changes
Tested: npm test -- src/pages/instance/__tests__/TopicPage.test.tsx
src/pages/instance/__tests__/ConsumerPage.test.tsx
src/pages/instance/__tests__/AclPage.test.tsx
Tested: npm run lint
Tested: npm run build
Confidence: high
Scope-risk: narrow
Signed-off-by: liuhy <[email protected]>
---
web/src/pages/instance/__tests__/AclPage.test.tsx | 46 +++++++-
.../pages/instance/__tests__/ConsumerPage.test.tsx | 116 +++++++++++++++++++++
.../pages/instance/__tests__/TopicPage.test.tsx | 116 ++++++++++++++++++++-
web/src/pages/instance/acl.tsx | 6 +-
web/src/pages/instance/consumer.tsx | 68 ++++++------
web/src/pages/instance/topic.tsx | 73 +++++++------
6 files changed, 356 insertions(+), 69 deletions(-)
diff --git a/web/src/pages/instance/__tests__/AclPage.test.tsx
b/web/src/pages/instance/__tests__/AclPage.test.tsx
index 309903968..e916f9c76 100644
--- a/web/src/pages/instance/__tests__/AclPage.test.tsx
+++ b/web/src/pages/instance/__tests__/AclPage.test.tsx
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-import { App, message } from 'antd';
+import { App, Modal, message } from 'antd';
import { act, fireEvent, render, screen, waitFor, within } from
'@testing-library/react';
import userEvent from '@testing-library/user-event';
import type React from 'react';
@@ -131,6 +131,50 @@ describe('ACL page', () => {
expect(aclService.pageAclUsers).toHaveBeenCalledTimes(1);
});
+ it('reloads the server rule page after deleting one ACL rule', async () => {
+ const user = userEvent.setup();
+ const confirmSpy = vi.spyOn(Modal, 'confirm').mockImplementation((config)
=> {
+ void config.onOk?.();
+ return { destroy: vi.fn(), update: vi.fn() } as unknown as
ReturnType<typeof Modal.confirm>;
+ });
+ vi.mocked(aclService.deleteAclRule).mockResolvedValue(undefined);
+ vi.mocked(aclService.listAclRules)
+ .mockResolvedValueOnce({
+ items: [
+ {
+ id: 1,
+ principal: 'remote-user',
+ resource: 'remote-topic',
+ resourceType: 'Topic',
+ resourcePattern: 'LITERAL',
+ actions: ['PUB'],
+ decision: 'ALLOW',
+ scope: 'cluster',
+ aclVersion: 2,
+ gmtCreate: '2026-07-23T00:00:00Z',
+ },
+ ],
+ total: 1,
+ page: 1,
+ size: 20,
+ })
+ .mockResolvedValueOnce({
+ items: [],
+ total: 0,
+ page: 1,
+ size: 20,
+ });
+ renderWithProviders(<AclPage />);
+
+ const row = await screen.findByRole('row', { name: /remote-topic/ });
+ await user.click(within(row).getByRole('button', { name: /删除/ }));
+
+ await waitFor(() =>
expect(aclService.deleteAclRule).toHaveBeenCalledWith(1, undefined));
+ await waitFor(() =>
expect(aclService.listAclRules).toHaveBeenCalledTimes(2));
+ expect(screen.queryByText('remote-topic')).not.toBeInTheDocument();
+ confirmSpy.mockRestore();
+ });
+
it('shows a non-copyable placeholder when an ACL user has no access key',
async () => {
const user = userEvent.setup();
vi.mocked(aclService.pageAclUsers).mockResolvedValue({
diff --git a/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
b/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
index 761ea2858..12f8e9256 100644
--- a/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
+++ b/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
@@ -330,6 +330,122 @@ describe('Consumer page', () => {
);
});
+ it('reloads the server page after deleting one consumer group', async () => {
+ const user = userEvent.setup();
+ const confirmSpy = vi.spyOn(Modal, 'confirm').mockImplementation((config)
=> {
+ void config.onOk?.();
+ return { destroy: vi.fn(), update: vi.fn() } as unknown as
ReturnType<typeof Modal.confirm>;
+ });
+
vi.mocked(consumerService.deleteConsumerGroup).mockResolvedValue(undefined);
+ vi.mocked(consumerService.listConsumerGroupPage)
+ .mockResolvedValueOnce(groupPage([group]))
+ .mockResolvedValueOnce(groupPage([]));
+ renderWithProviders(<ConsumerPage />);
+
+ const row = await screen.findByRole('row', { name: /remote-cg/ });
+ await user.click(within(row).getByRole('button', { name: /删除/ }));
+
+ await waitFor(() =>
+
expect(consumerService.deleteConsumerGroup).toHaveBeenCalledWith('remote-cg',
'instance-1'),
+ );
+ await waitFor(() =>
expect(consumerService.listConsumerGroupPage).toHaveBeenCalledTimes(2));
+ expect(screen.getByText('管理消费者组订阅关系与消费进度,共 0 个
Group')).toBeInTheDocument();
+ confirmSpy.mockRestore();
+ });
+
+ it('moves back from an emptied last consumer group page after batch
deletion', async () => {
+ const user = userEvent.setup();
+ const confirmSpy = vi.spyOn(Modal, 'confirm').mockImplementation((config)
=> {
+ void config.onOk?.();
+ return { destroy: vi.fn(), update: vi.fn() } as unknown as
ReturnType<typeof Modal.confirm>;
+ });
+ const firstPage = buildGroups(20).map((item, index) => ({
+ ...item,
+ name: `cg-${String(index + 1).padStart(2, '0')}`,
+ }));
+ const lastGroup = { ...group, name: 'cg-21' };
+ let deletedLastPage = false;
+ vi.mocked(consumerService.listConsumerGroupPage).mockImplementation(async
(params) => {
+ if (params?.page === 2) {
+ return groupPage(deletedLastPage ? [] : [lastGroup], {
+ total: deletedLastPage ? 20 : 21,
+ page: 2,
+ size: 20,
+ });
+ }
+ return groupPage(firstPage, { total: deletedLastPage ? 20 : 21, page: 1,
size: 20 });
+ });
+
vi.mocked(consumerService.batchDeleteConsumerGroups).mockImplementation(async
() => {
+ deletedLastPage = true;
+ return {
+ deleted: ['cg-21'],
+ failed: [],
+ };
+ });
+ renderWithProviders(<ConsumerPage />);
+
+ expect(await screen.findByText('cg-01')).toBeInTheDocument();
+ await user.click(document.querySelector('.ant-pagination-item-2') as
HTMLElement);
+ expect(await screen.findByText('cg-21')).toBeInTheDocument();
+ await user.click(screen.getAllByRole('checkbox')[0]);
+ await user.click(screen.getByRole('button', { name: /删除 \(1\)$/ }));
+
+ await waitFor(() =>
+ expect(consumerService.batchDeleteConsumerGroups).toHaveBeenCalledWith(
+ ['cg-21'],
+ 'instance-1',
+ ),
+ );
+ await waitFor(() =>
expect(screen.queryByText('cg-21')).not.toBeInTheDocument());
+ expect(screen.getByText('cg-01')).toBeInTheDocument();
+ expect(consumerService.listConsumerGroupPage).toHaveBeenLastCalledWith({
+ instanceId: 'instance-1',
+ search: undefined,
+ page: 1,
+ pageSize: 20,
+ });
+ confirmSpy.mockRestore();
+ });
+
+ it('keeps failed consumer groups selected after a partially successful batch
deletion', async () => {
+ const user = userEvent.setup();
+ const confirmSpy = vi.spyOn(Modal, 'confirm').mockImplementation((config)
=> {
+ void config.onOk?.();
+ return { destroy: vi.fn(), update: vi.fn() } as unknown as
ReturnType<typeof Modal.confirm>;
+ });
+ const groups = ['cg-01', 'cg-02', 'cg-03'].map((name) => ({ ...group, name
}));
+ vi.mocked(consumerService.listConsumerGroupPage)
+ .mockResolvedValueOnce(groupPage(groups))
+ .mockResolvedValueOnce(groupPage([{ ...group, name: 'cg-02' }]));
+ vi.mocked(consumerService.batchDeleteConsumerGroups).mockResolvedValue({
+ deleted: ['cg-01', 'cg-03'],
+ failed: ['cg-02'],
+ });
+ renderWithProviders(<ConsumerPage />);
+
+ expect(await screen.findByText('cg-01')).toBeInTheDocument();
+ await user.click(screen.getAllByRole('checkbox')[0]);
+ await user.click(screen.getByRole('button', { name: /删除 \(3\)$/ }));
+
+ await waitFor(() =>
+ expect(consumerService.batchDeleteConsumerGroups).toHaveBeenCalledWith(
+ ['cg-01', 'cg-02', 'cg-03'],
+ 'instance-1',
+ ),
+ );
+ await waitFor(() =>
expect(screen.queryByText('cg-01')).not.toBeInTheDocument());
+ expect(screen.getByText('cg-02')).toBeInTheDocument();
+ expect(screen.queryByText('cg-03')).not.toBeInTheDocument();
+ expect(screen.getByRole('button', { name: /删除 \(1\)$/
})).toBeInTheDocument();
+ expect(consumerService.listConsumerGroupPage).toHaveBeenLastCalledWith({
+ instanceId: 'instance-1',
+ search: undefined,
+ page: 1,
+ pageSize: 20,
+ });
+ confirmSpy.mockRestore();
+ });
+
it('submits the canonical global delivery order type', async () => {
const user = userEvent.setup();
const confirmSpy = vi.spyOn(Modal, 'confirm').mockImplementation((config)
=> {
diff --git a/web/src/pages/instance/__tests__/TopicPage.test.tsx
b/web/src/pages/instance/__tests__/TopicPage.test.tsx
index b230c8ef7..ed4f1f301 100644
--- a/web/src/pages/instance/__tests__/TopicPage.test.tsx
+++ b/web/src/pages/instance/__tests__/TopicPage.test.tsx
@@ -395,6 +395,43 @@ describe('TopicPage', () => {
expect(within(getTableBody()).getByText('topic-15')).toBeInTheDocument();
});
+ it('reloads the server page after deleting one topic', async () => {
+ const user = userEvent.setup();
+ const topic = buildTopics(1)[0];
+ let call = 0;
+ topicServiceMocks.listTopicsPage.mockImplementation(async () => {
+ call += 1;
+ return call === 1
+ ? {
+ items: [topic],
+ total: 1,
+ page: 1,
+ size: 20,
+ }
+ : {
+ items: [],
+ total: 0,
+ page: 1,
+ size: 20,
+ };
+ });
+ topicServiceMocks.deleteTopic.mockResolvedValue(undefined);
+ renderWithProviders();
+
+ const row = await screen.findByRole('row', { name: /topic-01/ });
+ await user.click(within(row).getByRole('button', { name: /删除/ }));
+ const dialog = (await screen.findByText(/确定要删除 Topic「topic-01」/)).closest(
+ '.ant-modal',
+ ) as HTMLElement;
+ await user.click(within(dialog).getByRole('button', { name: /删\s*除/ }));
+
+ await waitFor(() =>
+ expect(topicServiceMocks.deleteTopic).toHaveBeenCalledWith('topic-01',
'instance-proxy-1'),
+ );
+ await waitFor(() =>
expect(topicServiceMocks.listTopicsPage).toHaveBeenCalledTimes(2));
+ expect(screen.getByText('共 0 个 Topic')).toBeInTheDocument();
+ });
+
it('keeps the selected instance when rebuilding a topic without a broker
route', async () => {
const user = userEvent.setup();
const topic = { ...buildTopics(1)[0], instanceId: 'instance-a' };
@@ -482,7 +519,20 @@ describe('TopicPage', () => {
it('keeps failed topics selected after a partially successful batch
deletion', async () => {
const user = userEvent.setup();
- mockTopicsList(buildTopics(3));
+ const remainingTopics = [buildTopics(3)[1]];
+ topicServiceMocks.listTopicsPage
+ .mockResolvedValueOnce({
+ items: buildTopics(3),
+ total: 3,
+ page: 1,
+ size: 20,
+ })
+ .mockResolvedValueOnce({
+ items: remainingTopics,
+ total: 1,
+ page: 1,
+ size: 20,
+ });
topicServiceMocks.batchDeleteTopics.mockResolvedValue({
deleted: ['topic-01', 'topic-03'],
failed: ['topic-02'],
@@ -501,6 +551,70 @@ describe('TopicPage', () => {
expect(screen.queryByText('topic-03')).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: /删除 \(1\)$/
})).toBeInTheDocument();
expect(screen.getByText('已删除 2 个 Topic,1 个删除失败')).toBeInTheDocument();
+ expect(topicServiceMocks.listTopicsPage).toHaveBeenLastCalledWith({
+ instanceId: 'instance-proxy-1',
+ type: undefined,
+ search: undefined,
+ page: 1,
+ pageSize: 20,
+ });
+ });
+
+ it('moves back from an emptied last topic page after batch deletion', async
() => {
+ const user = userEvent.setup();
+ const firstPage = buildTopics(20);
+ const secondPage = [buildTopics(21)[20]];
+ let deletedLastPage = false;
+ topicServiceMocks.listTopicsPage.mockImplementation(async (params) => {
+ if (params?.page === 2) {
+ return {
+ items: deletedLastPage ? [] : secondPage,
+ total: deletedLastPage ? 20 : 21,
+ page: 2,
+ size: 20,
+ };
+ }
+ return {
+ items: firstPage,
+ total: deletedLastPage ? 20 : 21,
+ page: 1,
+ size: 20,
+ };
+ });
+ topicServiceMocks.batchDeleteTopics.mockImplementation(async () => {
+ deletedLastPage = true;
+ return {
+ deleted: ['topic-21'],
+ failed: [],
+ };
+ });
+ renderWithProviders();
+
+ expect(await screen.findByText('topic-01')).toBeInTheDocument();
+ await user.click(document.querySelector('.ant-pagination-item-2') as
HTMLElement);
+ expect(await screen.findByText('topic-21')).toBeInTheDocument();
+ await user.click(screen.getAllByRole('checkbox')[0]);
+ await user.click(screen.getByRole('button', { name: /删除 \(1\)$/ }));
+ const dialog = (await screen.findByText(/确定要删除选中的 1 个 Topic/)).closest(
+ '.ant-modal',
+ ) as HTMLElement;
+ await user.click(within(dialog).getByRole('button', { name: /删\s*除/ }));
+
+ await waitFor(() =>
+ expect(topicServiceMocks.batchDeleteTopics).toHaveBeenCalledWith(
+ ['topic-21'],
+ 'instance-proxy-1',
+ ),
+ );
+ await waitFor(() =>
expect(screen.queryByText('topic-21')).not.toBeInTheDocument());
+ expect(screen.getByText('topic-01')).toBeInTheDocument();
+ expect(topicServiceMocks.listTopicsPage).toHaveBeenLastCalledWith({
+ instanceId: 'instance-proxy-1',
+ type: undefined,
+ search: undefined,
+ page: 1,
+ pageSize: 20,
+ });
});
it('filters topics by the instance from the route and shows its endpoint',
async () => {
diff --git a/web/src/pages/instance/acl.tsx b/web/src/pages/instance/acl.tsx
index 58f8924d1..c04566cc4 100644
--- a/web/src/pages/instance/acl.tsx
+++ b/web/src/pages/instance/acl.tsx
@@ -340,11 +340,7 @@ const AclPageContent = ({
const handleDeleteRule = async (id: AclEntityId) => {
try {
await deleteAclRule(id, selectedInstanceId);
- if (rules.length === 1 && rulePage > 1) {
- setRulePage((prev) => prev - 1);
- } else {
- setRuleRefreshKey((prev) => prev + 1);
- }
+ setRuleRefreshKey((prev) => prev + 1);
message.success(t('acl.ruleDeleted'));
} catch {
message.error(t('common.operationFailed'));
diff --git a/web/src/pages/instance/consumer.tsx
b/web/src/pages/instance/consumer.tsx
index 7ddf7da24..2c9c9b998 100644
--- a/web/src/pages/instance/consumer.tsx
+++ b/web/src/pages/instance/consumer.tsx
@@ -327,6 +327,40 @@ const ConsumerPageContent = ({
}, []);
const selectedGroupName = selectedGroup?.name;
+ const loadConsumerGroupPage = useCallback(
+ async (pageToLoad: number, pageSizeToLoad: number, silent = false) => {
+ if (!selectedInstanceId) return undefined;
+ const requestId = ++groupRequestIdRef.current;
+ if (!silent) setLoading(true);
+ try {
+ const result = await listConsumerGroupPage({
+ instanceId: selectedInstanceId,
+ search: search.trim() || undefined,
+ page: pageToLoad,
+ pageSize: pageSizeToLoad,
+ });
+ if (requestId === groupRequestIdRef.current) {
+ setGroups(result.items);
+ setTotalGroups(result.total);
+ if (result.items.length === 0 && result.total > 0 && pageToLoad > 1)
{
+ setPage(Math.max(1, Math.ceil(result.total / pageSizeToLoad)));
+ }
+ }
+ return requestId === groupRequestIdRef.current ? result : undefined;
+ } catch {
+ if (requestId === groupRequestIdRef.current)
message.error(t('consumer.fetchListFailed'));
+ return undefined;
+ } finally {
+ if (requestId === groupRequestIdRef.current) setLoading(false);
+ }
+ },
+ [t, selectedInstanceId, search],
+ );
+
+ const reloadConsumerGroupPageAfterDelete = useCallback(async () => {
+ await loadConsumerGroupPage(page, pageSize);
+ }, [loadConsumerGroupPage, page, pageSize]);
+
useEffect(() => {
if (!selectedInstanceId) {
groupRequestIdRef.current += 1;
@@ -342,35 +376,13 @@ const ConsumerPageContent = ({
}
const silent = silentRefreshRef.current;
silentRefreshRef.current = false;
- const requestId = ++groupRequestIdRef.current;
const timer = window.setTimeout(() => {
- if (!silent) setLoading(true);
- void listConsumerGroupPage({
- instanceId: selectedInstanceId,
- search: search.trim() || undefined,
- page,
- pageSize,
- })
- .then((result) => {
- if (requestId === groupRequestIdRef.current) {
- setGroups(result.items);
- setTotalGroups(result.total);
- if (result.items.length === 0 && result.total > 0 && page > 1) {
- setPage(Math.max(1, Math.ceil(result.total / pageSize)));
- }
- }
- })
- .catch(() => {
- if (requestId === groupRequestIdRef.current)
message.error(t('consumer.fetchListFailed'));
- })
- .finally(() => {
- if (requestId === groupRequestIdRef.current) setLoading(false);
- });
+ void loadConsumerGroupPage(page, pageSize, silent);
}, 0);
return () => {
window.clearTimeout(timer);
};
- }, [t, selectedInstanceId, search, page, pageSize, instancesLoading,
refreshKey]);
+ }, [selectedInstanceId, page, pageSize, instancesLoading, refreshKey,
loadConsumerGroupPage]);
useEffect(() => {
if (!autoRefresh || !selectedInstanceId) {
@@ -978,7 +990,7 @@ const ConsumerPageContent = ({
cancelText: '取消',
onOk: async () => {
await deleteConsumerGroup(record.name, selectedInstanceId ||
undefined);
- setGroups((prev) => prev.filter((group) => group.name !==
record.name));
+ await reloadConsumerGroupPageAfterDelete();
setSelectedRowKeys((prev) => prev.filter((key) => key !==
record.name));
message.success(`消费组 ${record.name} 已删除`);
},
@@ -1409,14 +1421,12 @@ const ConsumerPageContent = ({
names,
selectedInstanceId || undefined,
);
- setGroups((prev) => prev.filter((g) =>
!deleted.includes(g.name)));
+ if (deleted.length > 0) await
reloadConsumerGroupPageAfterDelete();
if (failed.length > 0) {
message.warning(
`已删除 ${deleted.length} 个,失败 ${failed.length}
个:${failed.join(', ')}`,
);
- setSelectedRowKeys((prev) =>
- prev.filter((key) => !deleted.includes(String(key))),
- );
+ setSelectedRowKeys(failed);
} else {
message.success(`已删除 ${deleted.length} 个 Group`);
setSelectedRowKeys([]);
diff --git a/web/src/pages/instance/topic.tsx b/web/src/pages/instance/topic.tsx
index 8ec7335f8..e029b5da7 100644
--- a/web/src/pages/instance/topic.tsx
+++ b/web/src/pages/instance/topic.tsx
@@ -431,6 +431,42 @@ const TopicPage = () => {
],
);
+ const loadTopicPage = useCallback(
+ async (pageToLoad: number, pageSizeToLoad: number) => {
+ if (!selectedInstanceId) return undefined;
+ const requestId = ++topicRequestIdRef.current;
+ setLoading(true);
+ try {
+ const result = await listTopicsPage({
+ instanceId: selectedInstanceId,
+ type: typeFilter || undefined,
+ search: searchText.trim() || undefined,
+ page: pageToLoad,
+ pageSize: pageSizeToLoad,
+ });
+ if (requestId === topicRequestIdRef.current) {
+ setTopics(result.items);
+ setTotalTopics(result.total);
+ if (result.items.length === 0 && result.total > 0 && pageToLoad > 1)
{
+ setTablePage(Math.max(1, Math.ceil(result.total /
pageSizeToLoad)));
+ }
+ }
+ return requestId === topicRequestIdRef.current ? result : undefined;
+ } catch {
+ if (requestId === topicRequestIdRef.current)
+ message.error('Topic 列表加载失败,请稍后重试');
+ return undefined;
+ } finally {
+ if (requestId === topicRequestIdRef.current) setLoading(false);
+ }
+ },
+ [selectedInstanceId, typeFilter, searchText],
+ );
+
+ const reloadTopicPageAfterDelete = useCallback(async () => {
+ await loadTopicPage(tablePage, tablePageSize);
+ }, [loadTopicPage, tablePage, tablePageSize]);
+
useEffect(() => {
if (!selectedInstanceId) {
topicRequestIdRef.current += 1;
@@ -444,38 +480,14 @@ const TopicPage = () => {
window.clearTimeout(resetTimer);
};
}
- const requestId = ++topicRequestIdRef.current;
const timer = window.setTimeout(() => {
- setLoading(true);
- void listTopicsPage({
- instanceId: selectedInstanceId,
- type: typeFilter || undefined,
- search: searchText.trim() || undefined,
- page: tablePage,
- pageSize: tablePageSize,
- })
- .then((result) => {
- if (requestId === topicRequestIdRef.current) {
- setTopics(result.items);
- setTotalTopics(result.total);
- if (result.items.length === 0 && result.total > 0 && tablePage >
1) {
- setTablePage(Math.max(1, Math.ceil(result.total /
tablePageSize)));
- }
- }
- })
- .catch(() => {
- if (requestId === topicRequestIdRef.current)
- message.error('Topic 列表加载失败,请稍后重试');
- })
- .finally(() => {
- if (requestId === topicRequestIdRef.current) setLoading(false);
- });
+ void loadTopicPage(tablePage, tablePageSize);
}, 0);
return () => {
window.clearTimeout(timer);
};
- }, [selectedInstanceId, typeFilter, searchText, tablePage, tablePageSize,
instancesLoading]);
+ }, [selectedInstanceId, tablePage, tablePageSize, instancesLoading,
loadTopicPage]);
// ─── Filtered data ─────────────────────────────────────────────
const filteredTopics = useMemo(
@@ -642,7 +654,7 @@ const TopicPage = () => {
onOk: async () => {
try {
await deleteTopic(topic.name, selectedInstanceId || undefined);
- setTopics((previous) => previous.filter((item) => item.name !==
topic.name));
+ await reloadTopicPageAfterDelete();
message.success(`Topic「${topic.name}」已删除`);
} catch {
message.error('删除 Topic 失败,请稍后重试');
@@ -1500,12 +1512,7 @@ const TopicPage = () => {
names,
selectedInstanceId || undefined,
);
- if (deleted.length > 0) {
- const deletedNames = new Set(deleted);
- setTopics((previous) =>
- previous.filter((topic) =>
!deletedNames.has(topic.name)),
- );
- }
+ if (deleted.length > 0) await
reloadTopicPageAfterDelete();
setSelectedRowKeys(failed);
if (failed.length === 0) {