add route group sync

This commit is contained in:
2026-04-20 13:13:17 +08:00
parent 95ec0f16aa
commit 93518fd031
3 changed files with 63 additions and 3 deletions
@@ -157,6 +157,22 @@ describe('useRouteGroupStore', () => {
expect(api.get).toHaveBeenCalledWith('/api/v1/routes', undefined) 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 组件测试 ─── // ─── Page 组件测试 ───
@@ -177,6 +193,7 @@ describe('RouteGroupsPage', () => {
EditOutlined: { template: '<span class="icon-edit" />' }, EditOutlined: { template: '<span class="icon-edit" />' },
DeleteOutlined: { template: '<span class="icon-delete" />' }, DeleteOutlined: { template: '<span class="icon-delete" />' },
SaveOutlined: { template: '<span class="icon-save" />' }, SaveOutlined: { template: '<span class="icon-save" />' },
SyncOutlined: { template: '<span class="icon-sync" />' },
}, },
}, },
}) })
@@ -242,6 +259,23 @@ describe('RouteGroupsPage', () => {
expect(wrapper.text()).toContain('未分组') 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 () => { it('calls batchAssignRoutes on save button click', async () => {
await mountPage() await mountPage()
+20
View File
@@ -6,6 +6,7 @@ import {
EditOutlined, EditOutlined,
DeleteOutlined, DeleteOutlined,
SaveOutlined, SaveOutlined,
SyncOutlined,
} from '@ant-design/icons-vue' } from '@ant-design/icons-vue'
const groupStore = useRouteGroupStore() const groupStore = useRouteGroupStore()
@@ -55,6 +56,7 @@ const localCheckedKeys = ref<{ checked: (string | number)[], halfChecked: (strin
halfChecked: [], halfChecked: [],
}) })
const assignSaving = ref(false) const assignSaving = ref(false)
const syncing = ref(false)
// 当前选中组的名称 // 当前选中组的名称
const selectedGroupName = computed(() => { 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) { async function handleGroupChange(routeId: number, groupId: number | null) {
try { try {
await groupStore.assignRouteToGroup(routeId, groupId) await groupStore.assignRouteToGroup(routeId, groupId)
@@ -171,9 +186,14 @@ onMounted(async () => {
<a-col :span="8"> <a-col :span="8">
<a-card title="路由组" :loading="groupStore.loading"> <a-card title="路由组" :loading="groupStore.loading">
<template #extra> <template #extra>
<a-space>
<a-button size="small" :loading="syncing" @click="handleSyncRoutes">
<SyncOutlined /> 同步路由
</a-button>
<a-button type="primary" size="small" @click="openCreateModal"> <a-button type="primary" size="small" @click="openCreateModal">
<PlusOutlined /> 新建 <PlusOutlined /> 新建
</a-button> </a-button>
</a-space>
</template> </template>
<!-- 未分组入口 --> <!-- 未分组入口 -->
+6
View File
@@ -86,6 +86,11 @@ export const useRouteGroupStore = defineStore('route-group', () => {
await Promise.all([fetchGroups(), fetchRoutes()]) await Promise.all([fetchGroups(), fetchRoutes()])
} }
async function syncRoutes() {
await api.post('/api/v1/routes/sync')
await Promise.all([fetchGroups(), fetchRoutes()])
}
// 按首段资源名分组,避免同名叶节点与目录共存导致 UI 混乱 // 按首段资源名分组,避免同名叶节点与目录共存导致 UI 混乱
const routeTree = computed(() => { const routeTree = computed(() => {
const root: RouteTreeNode[] = [] const root: RouteTreeNode[] = []
@@ -167,6 +172,7 @@ export const useRouteGroupStore = defineStore('route-group', () => {
fetchRoutes, fetchRoutes,
assignRouteToGroup, assignRouteToGroup,
batchAssignRoutes, batchAssignRoutes,
syncRoutes,
routeTree, routeTree,
checkedRouteKeys, checkedRouteKeys,
} }