implement dynamic route registration for tools

This commit is contained in:
jingrow 2025-11-21 13:02:11 +08:00
parent 2c030be331
commit 712bb7242d
5 changed files with 201 additions and 5 deletions

View File

@ -29,4 +29,13 @@ import { useAuthStore } from '../shared/stores/auth'
const authStore = useAuthStore()
authStore.initAuth()
// 初始化动态工具路由 - 使用 router.isReady() 确保路由已完全初始化
import { useToolsStore } from '../shared/stores/tools'
router.isReady().then(() => {
const toolsStore = useToolsStore()
toolsStore.initToolRoutes(router)
}).catch((error) => {
console.error('Failed to initialize tool routes:', error)
})
app.mount('#app')

View File

@ -12,6 +12,7 @@ const router = createRouter({
},
{
path: '/',
name: 'AppLayout',
component: () => import('../layouts/AppLayout.vue'),
meta: { requiresAuth: true },
children: [

View File

@ -1,6 +1,7 @@
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import { t } from '../i18n'
import { registerToolRoute, unregisterToolRoute, syncToolRoutes, registerAllToolRoutes } from '../utils/dynamicRoutes'
export interface Tool {
id: string
@ -120,7 +121,7 @@ export const useToolsStore = defineStore('tools', () => {
})
// 添加用户工具
function addUserTool(tool: Tool) {
function addUserTool(tool: Tool, router?: any, componentPath?: string) {
const defaultToolsCount = getDefaultTools().filter(
t => !hiddenDefaultToolIds.value.includes(t.id)
).length
@ -129,6 +130,11 @@ export const useToolsStore = defineStore('tools', () => {
tool.isDefault = false
userTools.value.push(tool)
saveUserTools(userTools.value)
// 如果是路由类型工具,注册路由(如果提供了 router
if (tool.type === 'route' && tool.routeName && router) {
registerToolRoute(router, tool, componentPath)
}
}
// 更新用户工具
@ -141,7 +147,9 @@ export const useToolsStore = defineStore('tools', () => {
}
// 删除用户工具
function deleteUserTool(toolId: string) {
function deleteUserTool(toolId: string, router?: any) {
const tool = userTools.value.find(t => t.id === toolId)
userTools.value = userTools.value.filter(t => t.id !== toolId)
// 重新分配 order
const defaultToolsCount = getDefaultTools().filter(
@ -151,6 +159,11 @@ export const useToolsStore = defineStore('tools', () => {
t.order = defaultToolsCount + index + 1
})
saveUserTools(userTools.value)
// 如果是路由类型工具,移除路由(如果提供了 router
if (tool?.type === 'route' && tool.routeName && router) {
unregisterToolRoute(router, tool.routeName)
}
}
// 隐藏默认工具
@ -181,6 +194,16 @@ export const useToolsStore = defineStore('tools', () => {
saveUserTools(userTools.value)
}
// 初始化工具路由(在应用启动时调用)
function initToolRoutes(router: any) {
registerAllToolRoutes(router, userTools.value)
}
// 同步工具路由(移除不存在的,添加新的)
function syncRoutes(router: any) {
syncToolRoutes(router, userTools.value)
}
return {
userTools,
hiddenDefaultToolIds,
@ -192,7 +215,9 @@ export const useToolsStore = defineStore('tools', () => {
hideDefaultTool,
showDefaultTool,
updateUserToolsOrder,
getDefaultTools
getDefaultTools,
initToolRoutes,
syncRoutes
}
})

View File

@ -0,0 +1,159 @@
/**
*
*
*/
import type { Router, RouteRecordRaw } from 'vue-router'
import type { Tool } from '../stores/tools'
/**
*
* @param router Vue Router
* @param tool
* @param componentPath src/views
*/
export function registerToolRoute(
router: Router,
tool: Tool,
componentPath?: string
): boolean {
if (tool.type !== 'route' || !tool.routeName) {
return false
}
// 检查路由是否已存在
if (router.hasRoute(tool.routeName)) {
console.warn(`Route ${tool.routeName} already exists, skipping registration`)
return false
}
// 确定组件路径
// 1. 如果提供了 componentPath使用它
// 2. 如果工具有 componentPath 属性,使用它
// 3. 默认使用 tools/{routeName}.vue
const finalComponentPath =
componentPath ||
(tool as any).componentPath ||
`../../views/tools/${tool.routeName}.vue`
// 构建路由路径
// 默认使用 tools/{id},但可以通过 tool.routePath 自定义
const routePath = (tool as any).routePath || `tools/${tool.id}`
// 创建路由配置,添加组件加载错误处理
const route: RouteRecordRaw = {
path: routePath,
name: tool.routeName,
component: () => import(finalComponentPath).catch((error) => {
console.error(`Failed to load tool component: ${finalComponentPath}`, error)
// 返回一个简单的错误组件
return {
template: '<div style="padding: 20px; text-align: center;"><h3>Component Not Found</h3><p>Failed to load: {{ path }}</p></div>',
data() {
return { path: finalComponentPath }
}
}
}),
meta: {
requiresAuth: true,
toolId: tool.id,
toolName: tool.name
}
}
try {
// 将路由添加到 AppLayout 的 children 下
// 如果 AppLayout 路由不存在,尝试添加到根路由
if (router.hasRoute('AppLayout')) {
router.addRoute('AppLayout', route)
} else {
// 查找父路由path 为 '/' 的路由)
const parentRoute = router.getRoutes().find((r: any) => r.path === '/' && r.name !== 'Login')
if (parentRoute) {
router.addRoute(parentRoute.name || parentRoute.path, route)
} else {
// 如果找不到父路由,直接添加到根路由
router.addRoute(route)
}
}
console.log(`Tool route registered: ${tool.routeName} -> ${routePath}`)
return true
} catch (error) {
console.error(`Failed to register tool route ${tool.routeName}:`, error)
return false
}
}
/**
*
* @param router Vue Router
* @param routeName
*/
export function unregisterToolRoute(router: Router, routeName: string): boolean {
if (!router.hasRoute(routeName)) {
return false
}
try {
router.removeRoute(routeName)
console.log(`Tool route removed: ${routeName}`)
return true
} catch (error) {
console.error(`Failed to remove tool route ${routeName}:`, error)
return false
}
}
/**
*
* @param router Vue Router
* @param tools
*/
export function registerAllToolRoutes(router: Router, tools: Tool[]): void {
const routeTools = tools.filter(t => t.type === 'route' && t.routeName && !t.isDefault)
routeTools.forEach(tool => {
registerToolRoute(router, tool)
})
console.log(`Registered ${routeTools.length} tool routes`)
}
/**
*
* @param router Vue Router
* @param currentTools
*/
export function syncToolRoutes(router: Router, currentTools: Tool[]): void {
// 获取所有已注册的路由
const registeredRoutes = router.getRoutes()
// 找出所有工具路由(通过 meta.toolId 识别)
const toolRoutes = registeredRoutes.filter((route) =>
route.meta?.toolId && !route.meta?.isDefault
)
// 当前应该存在的工具路由名称
const currentRouteNames = new Set(
currentTools
.filter(t => t.type === 'route' && t.routeName && !t.isDefault)
.map(t => t.routeName!)
)
// 移除不再存在的工具路由
toolRoutes.forEach((route) => {
if (route.name && typeof route.name === 'string' && !currentRouteNames.has(route.name)) {
unregisterToolRoute(router, route.name)
}
})
// 添加新的工具路由
currentTools
.filter(t => t.type === 'route' && t.routeName && !t.isDefault)
.forEach(tool => {
if (tool.routeName && !router.hasRoute(tool.routeName)) {
registerToolRoute(router, tool)
}
})
}

View File

@ -444,7 +444,8 @@ function handleSaveTool() {
isDefault: false
}
toolsStore.addUserTool(newTool)
//
toolsStore.addUserTool(newTool, router)
message.success(t('Tool added successfully'))
}
@ -475,7 +476,8 @@ function handleDeleteTool(tool: Tool) {
positiveText: t('Delete'),
negativeText: t('Cancel'),
onPositiveClick: () => {
toolsStore.deleteUserTool(tool.id)
//
toolsStore.deleteUserTool(tool.id, router)
message.success(t('Tool deleted successfully'))
}
})