From 93518fd03146b68ac8e6c5b47d4a2aa6199809bc Mon Sep 17 00:00:00 2001 From: Nick Zeng Date: Mon, 20 Apr 2026 13:13:17 +0800 Subject: [PATCH] add route group sync --- .../route-groups/__tests__/index.spec.ts | 34 +++++++++++++++++++ frontend/src/pages/route-groups/index.vue | 26 ++++++++++++-- frontend/src/stores/route-group.ts | 6 ++++ 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/route-groups/__tests__/index.spec.ts b/frontend/src/pages/route-groups/__tests__/index.spec.ts index 4183d84..e837a3d 100644 --- a/frontend/src/pages/route-groups/__tests__/index.spec.ts +++ b/frontend/src/pages/route-groups/__tests__/index.spec.ts @@ -157,6 +157,22 @@ describe('useRouteGroupStore', () => { expect(api.get).toHaveBeenCalledWith('/api/v1/routes', undefined) }) }) + + describe('syncRoutes', () => { + it('calls sync API and refreshes groups + routes', async () => { + const store = useRouteGroupStore() + vi.mocked(api.post).mockResolvedValueOnce(undefined) + vi.mocked(api.get) + .mockResolvedValueOnce(mockGroups) + .mockResolvedValueOnce(mockRoutes) + + await store.syncRoutes() + + expect(api.post).toHaveBeenCalledWith('/api/v1/routes/sync') + expect(api.get).toHaveBeenCalledWith('/api/v1/route-groups') + expect(api.get).toHaveBeenCalledWith('/api/v1/routes', undefined) + }) + }) }) // ─── Page 组件测试 ─── @@ -177,6 +193,7 @@ describe('RouteGroupsPage', () => { EditOutlined: { template: '' }, DeleteOutlined: { template: '' }, SaveOutlined: { template: '' }, + SyncOutlined: { template: '' }, }, }, }) @@ -242,6 +259,23 @@ describe('RouteGroupsPage', () => { expect(wrapper.text()).toContain('未分组') }) + it('renders 同步路由 button and calls syncRoutes on click', async () => { + await mountPage() + + const syncBtn = wrapper.findAll('.ant-btn').find((b) => b.text().includes('同步路由')) + expect(syncBtn).toBeDefined() + + vi.mocked(api.post).mockResolvedValueOnce(undefined) + vi.mocked(api.get) + .mockResolvedValueOnce(mockGroups) + .mockResolvedValueOnce(mockRoutes) + + await syncBtn!.trigger('click') + await flushPromises() + + expect(api.post).toHaveBeenCalledWith('/api/v1/routes/sync') + }) + it('calls batchAssignRoutes on save button click', async () => { await mountPage() diff --git a/frontend/src/pages/route-groups/index.vue b/frontend/src/pages/route-groups/index.vue index 56e6b76..e38a24a 100644 --- a/frontend/src/pages/route-groups/index.vue +++ b/frontend/src/pages/route-groups/index.vue @@ -6,6 +6,7 @@ import { EditOutlined, DeleteOutlined, SaveOutlined, + SyncOutlined, } from '@ant-design/icons-vue' const groupStore = useRouteGroupStore() @@ -55,6 +56,7 @@ const localCheckedKeys = ref<{ checked: (string | number)[], halfChecked: (strin halfChecked: [], }) const assignSaving = ref(false) +const syncing = ref(false) // 当前选中组的名称 const selectedGroupName = computed(() => { @@ -148,6 +150,19 @@ async function handleDelete(group: RouteGroupRecord) { } } +async function handleSyncRoutes() { + syncing.value = true + try { + await groupStore.syncRoutes() + message.success('路由同步成功') + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : '同步失败' + message.error(msg) + } finally { + syncing.value = false + } +} + async function handleGroupChange(routeId: number, groupId: number | null) { try { await groupStore.assignRouteToGroup(routeId, groupId) @@ -171,9 +186,14 @@ onMounted(async () => { diff --git a/frontend/src/stores/route-group.ts b/frontend/src/stores/route-group.ts index a055694..9985138 100644 --- a/frontend/src/stores/route-group.ts +++ b/frontend/src/stores/route-group.ts @@ -86,6 +86,11 @@ export const useRouteGroupStore = defineStore('route-group', () => { await Promise.all([fetchGroups(), fetchRoutes()]) } + async function syncRoutes() { + await api.post('/api/v1/routes/sync') + await Promise.all([fetchGroups(), fetchRoutes()]) + } + // 按首段资源名分组,避免同名叶节点与目录共存导致 UI 混乱 const routeTree = computed(() => { const root: RouteTreeNode[] = [] @@ -167,6 +172,7 @@ export const useRouteGroupStore = defineStore('route-group', () => { fetchRoutes, assignRouteToGroup, batchAssignRoutes, + syncRoutes, routeTree, checkedRouteKeys, }