Fix tool route registration and page refresh issues

This commit is contained in:
jingrow 2025-11-21 17:28:20 +08:00
parent 82dc1bfc7e
commit ea874654bd
4 changed files with 33 additions and 103 deletions

View File

@ -29,7 +29,6 @@ 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()

View File

@ -190,15 +190,39 @@ const router = createRouter({
]
})
// 路由守卫
router.beforeEach(async (to, _from, next) => {
const authStore = useAuthStore()
// 初始化认证状态内部会检查cookie是否过期
if (!authStore.isAuthenticated) {
await authStore.initAuth()
}
if (to.path.startsWith('/tools/') && to.name !== 'Tools' && to.name !== 'RemoveBackground') {
const routeExists = to.matched.length > 0 || router.getRoutes().some(r => {
const fullPath = r.path.startsWith('/') ? r.path : `/${r.path}`
return fullPath === to.path
})
if (!routeExists) {
const { useToolsStore } = await import('../../shared/stores/tools')
const toolsStore = useToolsStore()
toolsStore.initToolRoutes(router)
const routeExistsAfter = router.getRoutes().some(r => {
const fullPath = r.path.startsWith('/') ? r.path : `/${r.path}`
return fullPath === to.path
})
if (routeExistsAfter) {
next({ ...to, replace: true })
return
} else {
next('/tools')
return
}
}
}
if (to.meta.requiresAuth && !authStore.isLoggedIn) {
next('/login')
} else if (to.path === '/login' && authStore.isLoggedIn) {

View File

@ -101,25 +101,20 @@ export const useToolsStore = defineStore('tools', () => {
const userTools = ref<Tool[]>(loadUserTools())
const hiddenDefaultToolIds = ref<string[]>(loadHiddenDefaultTools())
// 合并显示所有工具:默认工具 + 用户工具
const allTools = computed(() => {
// 获取默认工具(排除隐藏的)
const defaultTools = getDefaultTools()
.filter(tool => !hiddenDefaultToolIds.value.includes(tool.id))
.map(tool => ({ ...tool, isDefault: true }))
// 用户工具
const userToolsList = [...userTools.value]
.map(tool => ({ ...tool, isDefault: false }))
// 合并并排序默认工具在前用户工具在后各自按order排序
return [
...defaultTools.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)),
...userToolsList.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
]
})
// 获取隐藏的默认工具列表
const hiddenTools = computed(() => {
return getDefaultTools()
.filter(tool => hiddenDefaultToolIds.value.includes(tool.id))
@ -148,7 +143,6 @@ export const useToolsStore = defineStore('tools', () => {
}
}
// 更新用户工具
function updateUserTool(toolId: string, updates: Partial<Tool>) {
const index = userTools.value.findIndex(t => t.id === toolId)
if (index >= 0) {
@ -161,18 +155,12 @@ export const useToolsStore = defineStore('tools', () => {
async function deleteUserTool(toolId: string, router?: Router) {
const tool = userTools.value.find(t => t.id === toolId)
console.log('删除工具:', { toolId, tool, fromMarketplace: tool?.fromMarketplace, marketplaceId: tool?.marketplaceId, toolName: tool?.toolName, componentPath: tool?.componentPath })
// 确定工具名称tool_name优先使用 toolName否则从 componentPath 提取
let toolName: string | null = null
if (tool) {
// 优先使用 toolName实际的 tool_name用于文件系统
if (tool.toolName) {
toolName = tool.toolName
}
// 否则从 componentPath 提取工具名称格式tools/{tool_name}/{tool_name}.vue
else if (tool.componentPath) {
} else if (tool.componentPath) {
const match = tool.componentPath.match(/tools\/([^\/]+)\//)
if (match && match[1]) {
toolName = match[1]
@ -180,27 +168,19 @@ export const useToolsStore = defineStore('tools', () => {
}
}
// 如果找到了工具名称,尝试删除文件系统
if (toolName) {
try {
console.log('调用后端API删除工具文件工具名称:', toolName)
// 调用后端API删除工具文件
const response = await axios.post(`/jingrow/uninstall-tool/${toolName}`, {}, {
await axios.post(`/jingrow/uninstall-tool/${toolName}`, {}, {
headers: {
...get_session_api_headers()
}
})
console.log('删除工具文件成功:', response.data)
} catch (error: any) {
console.error('删除工具文件失败:', error.response?.data || error.message || '未知错误')
// 即使删除文件失败也继续删除store中的工具
}
} else {
console.log('无法确定工具名称,跳过文件删除')
}
userTools.value = userTools.value.filter(t => t.id !== toolId)
// 重新分配 order
const defaultToolsCount = getDefaultTools().filter(
t => !hiddenDefaultToolIds.value.includes(t.id)
).length
@ -209,13 +189,11 @@ export const useToolsStore = defineStore('tools', () => {
})
saveUserTools(userTools.value)
// 移除路由(如果提供了 router
if (tool && tool.routeName && router) {
unregisterToolRoute(router, tool.routeName)
}
}
// 隐藏默认工具
function hideDefaultTool(toolId: string) {
if (!hiddenDefaultToolIds.value.includes(toolId)) {
hiddenDefaultToolIds.value.push(toolId)
@ -223,13 +201,11 @@ export const useToolsStore = defineStore('tools', () => {
}
}
// 显示隐藏的默认工具
function showDefaultTool(toolId: string) {
hiddenDefaultToolIds.value = hiddenDefaultToolIds.value.filter(id => id !== toolId)
saveHiddenDefaultTools(hiddenDefaultToolIds.value)
}
// 更新用户工具顺序
function updateUserToolsOrder(newOrder: Tool[]) {
const defaultToolsCount = getDefaultTools().filter(
t => !hiddenDefaultToolIds.value.includes(t.id)
@ -243,7 +219,6 @@ export const useToolsStore = defineStore('tools', () => {
saveUserTools(userTools.value)
}
// 初始化工具路由(在应用启动时调用)
function initToolRoutes(router: Router) {
registerAllToolRoutes(router, userTools.value)
}

View File

@ -1,115 +1,65 @@
/**
*
*
*/
import type { Router, RouteRecordRaw } from 'vue-router'
import type { Tool } from '../stores/tools'
/**
* snake_case PascalCase
*/
function snakeToPascal(snakeStr: string): string {
const components = snakeStr.split('_')
return components.map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('')
}
/**
* tool_name routeNamePascalCase
*/
function generateRouteName(toolName: string): string {
return snakeToPascal(toolName)
}
/**
* tool_name routePathtools/{tool_name}
*/
function generateRoutePath(toolName: string): string {
return `tools/${toolName}`
}
/**
* routeName routePath
* 使 tool.marketplaceId tool_name使 tool.name
* 使 tool_name使 name
*/
export function ensureToolRoutes(tool: Tool): Tool {
// 确定基础名称:优先使用 marketplaceId即 tool_name稳定标识符如果没有则使用 name工具显示名称最后回退到 id
const baseName = tool.marketplaceId || tool.name || tool.id
const routeNameBase = tool.marketplaceId || tool.name || tool.id
const routePathBase = tool.toolName || tool.marketplaceId || tool.name || tool.id
// 如果没有 routeName基于基础名称生成
if (!tool.routeName) {
tool.routeName = generateRouteName(baseName)
tool.routeName = generateRouteName(routeNameBase)
}
// 如果没有 routePath基于基础名称生成
if (!tool.routePath) {
tool.routePath = generateRoutePath(baseName)
tool.routePath = generateRoutePath(routePathBase)
}
return tool
}
/**
*
* @param router Vue Router
* @param tool
* @param componentPath src/views
*/
export function registerToolRoute(
router: Router,
tool: Tool,
componentPath?: string
): boolean {
// 确保工具具有 routeName 和 routePath
const toolWithRoutes = ensureToolRoutes({ ...tool })
// 如果没有 routeName无法注册路由
if (!toolWithRoutes.routeName) {
return false
}
// 检查路由是否已存在
if (router.hasRoute(toolWithRoutes.routeName)) {
console.warn(`Route ${toolWithRoutes.routeName} already exists, skipping registration`)
return false
}
// 确定组件路径和路由路径
// componentPath 格式tools/{tool_name}/{tool_name}.vue相对于 src/views
// 需要转换为相对于当前文件的路径:../../views/tools/{tool_name}/{tool_name}.vue
let finalComponentPath: string
if (componentPath) {
// 如果 componentPath 已经是相对于 src/views 的路径(如 tools/test_tool/test_tool.vue
// 需要转换为相对于当前文件的路径
finalComponentPath = `../../views/${componentPath}`
} else if (toolWithRoutes.componentPath) {
// 如果 tool 对象中有 componentPath
finalComponentPath = `../../views/${toolWithRoutes.componentPath}`
} else {
// 默认路径:使用 toolName 或 id
const toolDirName = toolWithRoutes.toolName || toolWithRoutes.id
finalComponentPath = `../../views/tools/${toolDirName}/${toolDirName}.vue`
}
const routePath = toolWithRoutes.routePath || `tools/${toolWithRoutes.toolName || toolWithRoutes.id}`
// 创建路由配置,添加组件加载错误处理
console.log(`Registering tool route:`, {
routeName: toolWithRoutes.routeName,
routePath,
componentPath: finalComponentPath,
toolName: toolWithRoutes.toolName,
id: toolWithRoutes.id
})
const route: RouteRecordRaw = {
path: routePath,
name: toolWithRoutes.routeName,
component: () => {
console.log(`Loading tool component from: ${finalComponentPath}`)
return import(finalComponentPath).catch((error) => {
console.error(`Failed to load tool component: ${finalComponentPath}`, error)
// 返回一个简单的错误组件,显示详细错误信息
return {
name: 'ToolComponentError',
template: `
@ -130,10 +80,7 @@ export function registerToolRoute(
}
try {
// 将路由添加到 AppLayout 的 children 下
// AppLayout 路由应该在应用启动时已存在
router.addRoute('AppLayout', route)
console.log(`Tool route registered: ${toolWithRoutes.routeName} -> ${routePath}`)
return true
} catch (error) {
console.error(`Failed to register tool route ${toolWithRoutes.routeName}:`, error)
@ -141,11 +88,6 @@ export function registerToolRoute(
}
}
/**
*
* @param router Vue Router
* @param routeName
*/
export function unregisterToolRoute(router: Router, routeName: string): boolean {
if (!router.hasRoute(routeName)) {
return false
@ -153,7 +95,6 @@ export function unregisterToolRoute(router: Router, routeName: string): boolean
try {
router.removeRoute(routeName)
console.log(`Tool route removed: ${routeName}`)
return true
} catch (error) {
console.error(`Failed to remove tool route ${routeName}:`, error)
@ -161,19 +102,10 @@ export function unregisterToolRoute(router: Router, routeName: string): boolean
}
}
/**
*
* @param router Vue Router
* @param tools
*/
export function registerAllToolRoutes(router: Router, tools: Tool[]): void {
// 过滤出用户工具routeName 会在 registerToolRoute 中自动生成)
const routeTools = tools.filter(t => !t.isDefault)
routeTools.forEach(tool => {
registerToolRoute(router, tool)
registerToolRoute(router, tool, tool.componentPath)
})
console.log(`Registered ${routeTools.length} tool routes`)
}