add route group sync
This commit is contained in:
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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-button type="primary" size="small" @click="openCreateModal">
|
<a-space>
|
||||||
<PlusOutlined /> 新建
|
<a-button size="small" :loading="syncing" @click="handleSyncRoutes">
|
||||||
</a-button>
|
<SyncOutlined /> 同步路由
|
||||||
|
</a-button>
|
||||||
|
<a-button type="primary" size="small" @click="openCreateModal">
|
||||||
|
<PlusOutlined /> 新建
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 未分组入口 -->
|
<!-- 未分组入口 -->
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user