diff --git a/src/views/ebiz/underwriting/js/navigate.js b/src/views/ebiz/underwriting/js/navigate.js index baba3d5aa..7ebe2434f 100644 --- a/src/views/ebiz/underwriting/js/navigate.js +++ b/src/views/ebiz/underwriting/js/navigate.js @@ -20,43 +20,31 @@ export function navigateRouter({ path = '', name = '', title = '', params = {} } routerInfo: { query: params } } if (path) { - const route = routes.find(item => { - if (item.children) { - // 可能后面 children 存在多个,目前暂时先判定一层。 待优化 - return item.children.find(childItem => `${item.path}/${childItem.path}` === path) - } - return item.path === path - }) - if (!route) { + // 查找路由支持任意深度的嵌套 + const routeInfo = findRouteByPath(routes, path) + if (!routeInfo) { console.error('在 routers 中无法找到该路径,请检查路径是否正确') - return + return false } options.extra = { - title: route.meta && route.meta.title ? route.meta.title : '', + title: routeInfo.route.meta && routeInfo.route.meta.title ? routeInfo.route.meta.title : '', url: location.origin + '/#' + path + '?' + processJson(params) } options.routerInfo = { ...options.routerInfo, path } } else if (name) { - const route = routes.find(item => { - if (item.children) { - // 可能后面 children 存在多个,目前暂时先判定一层。 待优化 - return item.children.find(childItem => childItem.name === name) - } - return item.name === name - }) - if (!route) { - console.error('在 routers 中无法找到该路径,请检查路径是否正确') - return + // 通过名称查找路由也支持任意深度嵌套 + const routeInfo = findRouteByName(routes, name) + if (!routeInfo) { + console.error('在 routers 中无法找到该名称,请检查名称是否正确') + return false } - // 如何存在子元素, 需要重新更改 path 地址 - if (route.children) { - route.path = `${route.path}/${route.children.find(childItem => childItem.name === name).path}` - } - console.log(route) + + // 构建完整的路径 + const fullPath = buildFullPath(routeInfo.routeStack) options.extra = { - title: route.meta && route.meta.title ? route.meta.title : '', - url: location.origin + '/#' + route.path + '?' + processJson(params) + title: routeInfo.route.meta && routeInfo.route.meta.title ? routeInfo.route.meta.title : '', + url: location.origin + '/#' + fullPath + '?' + processJson(params) } options.routerInfo = { ...options.routerInfo, name } @@ -65,6 +53,130 @@ export function navigateRouter({ path = '', name = '', title = '', params = {} } jump(options) } +/** + * 递归查找具有指定路径的路由(支持多层嵌套) + * @param routes {import("vue-router").RouteConfig[]} 路由数组 + * @param targetPath {String}目标路径 + * @param parentPath {String}父级路径 + * @return {undefined| { + * route: import("vue-router").RouteConfig, + * fullPath: String + * }} + */ +function findRouteByPath(routes, targetPath, parentPath = '') { + if (!routes || !Array.isArray(routes)) return undefined + + // 规范化目标路径,移除多余的斜杠 + targetPath = targetPath.replace(/\/+/g, '/') + + for (const route of routes) { + // 构造当前路由的完整路径 + let currentPath = parentPath + if (currentPath && !currentPath.endsWith('/') && route.path && !route.path.startsWith('/')) { + currentPath += '/' + route.path + } else { + currentPath += route.path + } + + // 移除多余的斜杠 + currentPath = currentPath.replace(/\/+/g, '/') + + // 检查当前路由是否匹配 + if (currentPath === targetPath) { + return { + route: route, + fullPath: currentPath + } + } + + // 递归检查子路由 + if (route.children && route.children.length > 0) { + const matchedChild = findRouteByPath(route.children, targetPath, currentPath) + + if (matchedChild) { + return matchedChild + } + } + } + + return undefined +} + +/** + * 递归查找具有指定名称的路由(支持多层嵌套) + * @param routes 路由数组 + * @param targetName 目标名称 + * @param routeStack 用于追踪路由层级的堆栈 + * @return {undefined| { + * route: import("vue-router").RouteConfig, + * routeStack: import("vue-router").RouteConfig[] + * }} + */ +function findRouteByName(routes, targetName, routeStack = []) { + if (!routes || !Array.isArray(routes)) return undefined + + for (const route of routes) { + // 创建当前路由的堆栈副本 + const currentStack = [...routeStack, route] + + // 检查当前路由是否匹配 + if (route.name === targetName) { + return { + route: route, + routeStack: currentStack + } + } + + // 递归检查子路由 + if (route.children && route.children.length > 0) { + const matchedChild = findRouteByName(route.children, targetName, currentStack) + + if (matchedChild) { + return matchedChild + } + } + } + + return undefined +} + +/** + * 根据路由堆栈构建完整路径 + * @param routeStack 路由堆栈 + * @returns string + */ +function buildFullPath(routeStack) { + if (!routeStack || routeStack.length === 0) return '' + + // 提取所有路由的路径并正确拼接 + let fullPath = '' + routeStack.forEach((route, index) => { + // 特殊处理第一个路由 + if (index === 0) { + fullPath += route.path + } else { + if (fullPath && !fullPath.endsWith('/') && route.path && !route.path.startsWith('/')) { + fullPath += '/' + route.path + } else if (fullPath.endsWith('/') && route.path.startsWith('/')) { + // 如果fullPath以/结尾,而route.path以/开头,则需要去掉一个/ + fullPath += route.path.substring(1) + } else { + fullPath += route.path + } + } + }) + + // 移除多余的斜杠 + fullPath = fullPath.replace(/\/+/g, '/') + + // 确保不会在末尾留下斜杠(除非是根路径) + if (fullPath.length > 1 && fullPath.endsWith('/')) { + fullPath = fullPath.slice(0, -1) + } + + return fullPath +} + /** * 处理 json 键值对,到 url 中 */ @@ -79,4 +191,4 @@ function processJson(json) { } } return url.substring(0, url.length - 1) -} +} \ No newline at end of file diff --git a/tests/unit/underwriting/navigate.spec.js b/tests/unit/underwriting/navigate.spec.js new file mode 100644 index 000000000..be0165e6d --- /dev/null +++ b/tests/unit/underwriting/navigate.spec.js @@ -0,0 +1,329 @@ +// 模拟路由配置数据,基于实际项目结构 +const mockRoutes = [ + { + path: '/underwriting', + name: 'PreliminaryUnderwriting', + component: () => import('@/views/ebiz/underwriting/PreliminaryUnderwritingContainer.vue'), + children: [ + { + path: 'list', + name: 'UnderwritingOrderList', + meta: { title: '预核保订单列表' }, + component: () => import('@/views/ebiz/underwriting/OrderList.vue') + }, + { + path: 'data-collection', + name: 'UnderwritingDataCollection', + meta: { title: '信息录入' }, + component: () => import('@/views/ebiz/underwriting/UnderwritingDataCollection.vue') + }, + { + path: 'supplementary-information', + name: 'UnderwritingSupplementaryInformation', + meta: { title: '补传资料' }, + component: () => import('@/views/ebiz/underwriting/SupplementaryInformation.vue') + }, + { + path: 'result', + name: 'UnderwritingResult', + meta: { title: '预核保结果' }, + component: () => import('@/views/ebiz/underwriting/Result.vue') + }, + { + path: 'contract-sign', + name: 'UnderwritingContractSign', + meta: { title: '签名确认' }, + component: () => import('@/views/ebiz/underwriting/SignContract.vue') + }, + { + path: 'document-info', + name: 'UnderwritingDocumentInfo', + meta: { title: '预核保资料' }, + component: () => import('@/views/ebiz/underwriting/DocumentInfo.vue') + } + ] + }, + { + path: '/other', + name: 'Other', + component: () => import('@/views/other/OtherContainer.vue'), + children: [ + { + path: 'nested', + name: 'Nested', + component: () => import('@/views/other/Nested.vue'), + children: [ + { + path: 'deep', + name: 'DeepNested', + meta: { title: '深层嵌套路由' }, + component: () => import('@/views/other/DeepNested.vue') + } + ] + } + ] + }, + { + path: '/', + name: 'Home', + component: () => import('@/views/Home.vue') + }, + { + path: '/simple', + name: 'SimpleRoute', + meta: { title: '简单路由' }, + component: () => import('@/views/Simple.vue') + } +]; + +/** + * 递归查找具有指定路径的路由(支持多层嵌套) + * @param routes 路由数组 + * @param targetPath 目标路径 + * @param parentPath 父级路径 + * @returns 匹配的路由信息或undefined + */ +function findRouteByPath(routes, targetPath, parentPath = '') { + if (!routes || !Array.isArray(routes)) return undefined + + // 规范化目标路径,移除多余的斜杠 + targetPath = targetPath.replace(/\/+/g, '/') + + for (const route of routes) { + // 构造当前路由的完整路径 + let currentPath = parentPath + if (currentPath && !currentPath.endsWith('/') && route.path && !route.path.startsWith('/')) { + currentPath += '/' + route.path + } else { + currentPath += route.path + } + + // 移除多余的斜杠 + currentPath = currentPath.replace(/\/+/g, '/') + + // 检查当前路由是否匹配 + if (currentPath === targetPath) { + return { + route: route, + fullPath: currentPath + } + } + + // 递归检查子路由 + if (route.children && route.children.length > 0) { + const matchedChild = findRouteByPath(route.children, targetPath, currentPath) + + if (matchedChild) { + return matchedChild + } + } + } + + return undefined +} + +/** + * 递归查找具有指定名称的路由(支持多层嵌套) + * @param routes 路由数组 + * @param targetName 目标名称 + * @param routeStack 用于追踪路由层级的堆栈 + * @returns 包含路由和路由堆栈的对象,或undefined + */ +function findRouteByName(routes, targetName, routeStack = []) { + if (!routes || !Array.isArray(routes)) return undefined + + for (const route of routes) { + // 创建当前路由的堆栈副本 + const currentStack = [...routeStack, route] + + // 检查当前路由是否匹配 + if (route.name === targetName) { + return { + route: route, + routeStack: currentStack + } + } + + // 递归检查子路由 + if (route.children && route.children.length > 0) { + const matchedChild = findRouteByName(route.children, targetName, currentStack) + + if (matchedChild) { + return matchedChild + } + } + } + + return undefined +} + +/** + * 根据路由堆栈构建完整路径 + * @param routeStack 路由堆栈 + * @returns 完整路径字符串 + */ +function buildFullPath(routeStack) { + if (!routeStack || routeStack.length === 0) return '' + + // 提取所有路由的路径并正确拼接 + let fullPath = '' + routeStack.forEach((route, index) => { + // 特殊处理第一个路由 + if (index === 0) { + fullPath += route.path + } else { + if (fullPath && !fullPath.endsWith('/') && route.path && !route.path.startsWith('/')) { + fullPath += '/' + route.path + } else if (fullPath.endsWith('/') && route.path.startsWith('/')) { + // 如果fullPath以/结尾,而route.path以/开头,则需要去掉一个/ + fullPath += route.path.substring(1) + } else { + fullPath += route.path + } + } + }) + + // 移除多余的斜杠 + fullPath = fullPath.replace(/\/+/g, '/') + + // 确保不会在末尾留下斜杠(除非是根路径) + if (fullPath.length > 1 && fullPath.endsWith('/')) { + fullPath = fullPath.slice(0, -1) + } + + return fullPath +} + +describe('Underwriting Navigate Functions', () => { + describe('findRouteByPath', () => { + it('应该能找到根路径路由', () => { + const result = findRouteByPath(mockRoutes, '/') + expect(result).not.toBeUndefined() + expect(result.route.name).toBe('Home') + }) + + it('应该能找到简单路径路由', () => { + const result = findRouteByPath(mockRoutes, '/simple') + expect(result).not.toBeUndefined() + expect(result.route.name).toBe('SimpleRoute') + }) + + it('应该能找到一级嵌套路由', () => { + const result = findRouteByPath(mockRoutes, '/underwriting') + expect(result).not.toBeUndefined() + expect(result.route.name).toBe('PreliminaryUnderwriting') + }) + + it('应该能找到二级嵌套路由', () => { + const result = findRouteByPath(mockRoutes, '/underwriting/list') + expect(result).not.toBeUndefined() + expect(result.route.name).toBe('UnderwritingOrderList') + }) + + it('应该能找到三级嵌套路由', () => { + const result = findRouteByPath(mockRoutes, '/other/nested/deep') + expect(result).not.toBeUndefined() + expect(result.route.name).toBe('DeepNested') + }) + + it('应该返回undefined当找不到路由时', () => { + const result = findRouteByPath(mockRoutes, '/nonexistent') + expect(result).toBeUndefined() + }) + + it('应该正确处理带有多个斜杠的路径', () => { + // 实际上我们查找的是 /underwriting/list,只是输入路径中有多余的斜杠 + const result = findRouteByPath(mockRoutes, '/underwriting///list') + expect(result).not.toBeUndefined() + expect(result.route.name).toBe('UnderwritingOrderList') + }) + }) + + describe('findRouteByName', () => { + it('应该通过名称找到根路径路由', () => { + const result = findRouteByName(mockRoutes, 'Home') + expect(result).not.toBeUndefined() + expect(result.route.name).toBe('Home') + }) + + it('应该通过名称找到简单路径路由', () => { + const result = findRouteByName(mockRoutes, 'SimpleRoute') + expect(result).not.toBeUndefined() + expect(result.route.name).toBe('SimpleRoute') + }) + + it('应该通过名称找到一级嵌套路由', () => { + const result = findRouteByName(mockRoutes, 'PreliminaryUnderwriting') + expect(result).not.toBeUndefined() + expect(result.route.name).toBe('PreliminaryUnderwriting') + }) + + it('应该通过名称找到二级嵌套路由', () => { + const result = findRouteByName(mockRoutes, 'UnderwritingOrderList') + expect(result).not.toBeUndefined() + expect(result.route.name).toBe('UnderwritingOrderList') + }) + + it('应该通过名称找到三级嵌套路由', () => { + const result = findRouteByName(mockRoutes, 'DeepNested') + expect(result).not.toBeUndefined() + expect(result.route.name).toBe('DeepNested') + }) + + it('应该返回undefined当找不到路由时', () => { + const result = findRouteByName(mockRoutes, 'NonExistent') + expect(result).toBeUndefined() + }) + }) + + describe('buildFullPath', () => { + it('应该正确构建根路径', () => { + const routeStack = [{ path: '/' }] + const result = buildFullPath(routeStack) + expect(result).toBe('/') + }) + + it('应该正确构建简单路径', () => { + const routeStack = [ + { path: '/' }, + { path: 'simple' } + ] + const result = buildFullPath(routeStack) + expect(result).toBe('/simple') + }) + + it('应该正确构建嵌套路径', () => { + const routeStack = [ + { path: '/underwriting' }, + { path: 'list' } + ] + const result = buildFullPath(routeStack) + expect(result).toBe('/underwriting/list') + }) + + it('应该正确构建深层嵌套路径', () => { + const routeStack = [ + { path: '/other' }, + { path: 'nested' }, + { path: 'deep' } + ] + const result = buildFullPath(routeStack) + expect(result).toBe('/other/nested/deep') + }) + + it('应该正确处理空路由堆栈', () => { + const result = buildFullPath([]) + expect(result).toBe('') + }) + + it('应该正确处理多余的斜杠', () => { + const routeStack = [ + { path: '/underwriting/' }, + { path: '/list/' } + ] + const result = buildFullPath(routeStack) + // 由于路径规范化处理,结果应该是 /underwriting/list 而不是 /underwriting//list + expect(result).toBe('/underwriting/list') + }) + }) +}) \ No newline at end of file