From da82e82e7c0398bc3b9d6276c4e7eec32f42d0a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BF=97=E5=BC=BA?= <357099073@qq.com> Date: Mon, 26 Jan 2026 15:57:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dcomponent=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/router/dynamicRoutes.js | 35 +++---- src/utils/pathResolver.js | 97 +++++++------------ src/utils/request.js | 10 +- .../apps/cms/articles/components/edit.vue | 4 +- src/views/layouts/EmptyLayout.vue | 5 +- 5 files changed, 64 insertions(+), 87 deletions(-) diff --git a/src/router/dynamicRoutes.js b/src/router/dynamicRoutes.js index 98349b5..c67f0be 100644 --- a/src/router/dynamicRoutes.js +++ b/src/router/dynamicRoutes.js @@ -1,45 +1,46 @@ import { createComponentLoader } from '@/utils/pathResolver'; +import { h, resolveComponent as resolveVueComponent } from 'vue'; // 递归转换嵌套菜单为嵌套路由 export function convertMenusToRoutes(menus) { if (!menus || menus.length === 0) return []; return menus.map(menu => { - // 基础路由配置 const route = { - path: menu.path || '', // 优先使用菜单path,无则为空字符串 + path: menu.path || '', name: `menu_${menu.id}`, meta: { - icon: menu.icon, title: menu.title, + icon: menu.icon, id: menu.id, - parentId: menu.pid, - menuPath: menu.path, componentPath: menu.component_path } }; - // 处理组件路径 + // 1. 处理组件加载 if (menu.type === 4) { - // 单页类型:使用单页组件,根据路由从单页表获取内容 + // 单页类型 route.component = () => import('@/views/onepage/index.vue'); - route.meta.menuType = 4; // 标记为单页类型 - } else if (menu.component_path) { - // 页面类型:直接使用component_path加载组件 + } else if (menu.component_path && menu.component_path.trim() !== '') { + // 正常页面 route.component = createComponentLoader(menu.component_path); } else if (menu.children && menu.children.length > 0) { - // 无组件但有子路由的父菜单,添加默认空组件(避免路由报错) + // 目录节点:必须给组件,否则父级无法渲染子级 route.component = () => import('@/views/layouts/EmptyLayout.vue'); - // 为父菜单添加重定向,指向第一个有效子路由 - const firstValidChild = findFirstValidRoute(menu.children); - if (firstValidChild && firstValidChild.path) { - route.redirect = firstValidChild.path; - } + } else { + // 异常:既没路径也没子菜单 + route.component = () => import('@/views/404/404.vue'); } - // 递归处理子菜单为子路由 + // 2. 递归子路由 if (menu.children && menu.children.length > 0) { route.children = convertMenusToRoutes(menu.children); + + // 目录节点添加重定向,防止点击父菜单页面空白 + const firstChild = menu.children[0]; + if (firstChild && firstChild.path) { + route.redirect = firstChild.path; + } } return route; diff --git a/src/utils/pathResolver.js b/src/utils/pathResolver.js index c1b81c6..e3bfce2 100644 --- a/src/utils/pathResolver.js +++ b/src/utils/pathResolver.js @@ -11,25 +11,19 @@ const pathMap = new Map(); // 初始化路径映射 Object.keys(viewsModules).forEach(relativePath => { - // relativePath: ../views/dashboard/index.vue - - // 1. 别名格式: @/views/dashboard/index.vue - const aliasPath = relativePath.replace('../views', '@/views'); - pathMap.set(aliasPath, viewsModules[relativePath]); - - // 2. 数据库格式: /dashboard/index.vue 或 /apps/cms/articles/index.vue - const dbPath = relativePath.replace('../views/', '/'); - pathMap.set(dbPath, viewsModules[relativePath]); - - // 3. 相对路径格式: dashboard/index.vue - const relativePathFormat = relativePath.replace('../views/', ''); - pathMap.set(relativePathFormat, viewsModules[relativePath]); - - // 4. 原始相对路径也保留 + // relativePath 示例: ../views/system/users.vue + + // 统一去掉扩展名进行存储,方便各种格式匹配 + const baseNoExt = relativePath.replace('../views/', '').replace('.vue', ''); + + // 1. 存储标准路径 pathMap.set(relativePath, viewsModules[relativePath]); - - // 5. 完整路径格式(用于调试) - pathMap.set(`/views${dbPath}`, viewsModules[relativePath]); + // 2. 存储 @/views 路径 + pathMap.set(relativePath.replace('../views', '@/views'), viewsModules[relativePath]); + // 3. 存储 /system/users 格式 + pathMap.set(`/${baseNoExt}`, viewsModules[relativePath]); + // 4. 存储 system/users 格式 + pathMap.set(baseNoExt, viewsModules[relativePath]); }); /** @@ -41,37 +35,26 @@ Object.keys(viewsModules).forEach(relativePath => { * @returns {Function|null} 返回模块加载器函数,找不到时返回 null */ export function resolveComponent(path) { - if (!path) { - return null; - } - - // 尝试多种路径格式 - const searchPaths = [ - path, // 原始路径 - // 如果是以 / 开头的数据库格式,转换为别名格式 - path.startsWith('/') && !path.startsWith('@/') ? `@/views${path}` : null, - // 如果是相对路径但不在 views 下,添加 @/views 前缀 - !path.includes('@') && !path.includes('/views') && !path.startsWith('/') - ? `@/views/${path}` - : null, - ].filter(Boolean); - - // 遍历所有可能的路径格式 - for (const searchPath of searchPaths) { - const loader = pathMap.get(searchPath); - if (loader) { - return loader; - } - } - - // 如果精确匹配失败,尝试模糊匹配(按文件名) - const fileName = path.split('/').pop(); + if (!path) return null; + + // 预处理 path:去掉可能的 .vue 后缀统一查找 + const cleanPath = path.replace('.vue', ''); + + // 尝试直接匹配 + const loader = pathMap.get(path) || pathMap.get(cleanPath); + if (loader) return loader; + + // 数据库格式补全匹配 (针对 /system/users) + const dbFormat = cleanPath.startsWith('/') ? cleanPath : `/${cleanPath}`; + if (pathMap.get(dbFormat)) return pathMap.get(dbFormat); + + // 模糊匹配:文件名匹配 + const fileName = cleanPath.split('/').pop(); for (const [mappedPath, loader] of pathMap.entries()) { - if (mappedPath.endsWith(fileName)) { + if (mappedPath.endsWith(`${fileName}.vue`) || mappedPath.endsWith(fileName)) { return loader; } } - return null; } @@ -82,22 +65,16 @@ export function resolveComponent(path) { */ export function createComponentLoader(componentPath) { const loader = resolveComponent(componentPath); - - if (loader) { - return loader; - } - - // 找不到组件时,返回一个占位组件 - console.warn(`⚠️ 组件未找到: ${componentPath}`); + if (loader) return loader; + + console.error(`❌ [路由错误] 未找到组件: ${componentPath}`); + + // 返回一个标准的 Vue 组件对象,确保 Router 不报错 return () => Promise.resolve({ - default: { - template: ` -
-

组件加载失败

-

路径: ${componentPath}

-

请检查组件文件是否存在

-
- ` + name: 'ComponentNotFound', + render: () => { + import('element-plus').then(El => El.ElMessage.error(`路径错误: ${componentPath}`)); + return h('div', { style: 'padding:20px; color:red;' }, `组件路径不存在: ${componentPath}`); } }); } diff --git a/src/utils/request.js b/src/utils/request.js index b45702d..0e64ba6 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -15,7 +15,7 @@ service.interceptors.request.use( config.headers['Authorization'] = `Bearer ${token}`; } - // 禁止 GET 请求缓存:添加时间戳参数 + // 禁止 GET 请求缓存:添加时间戳参数到 query string if (config.method === 'get') { config.params = { ...config.params, @@ -23,10 +23,10 @@ service.interceptors.request.use( }; } - // POST 请求也添加时间戳防止缓存 - if (config.method === 'post') { - config.data = { - ...config.data, + // POST/PUT/PATCH 请求也添加时间戳到 query string 防止缓存 + if (['post', 'put', 'patch'].includes(config.method?.toLowerCase())) { + config.params = { + ...config.params, _t: Date.now() }; } diff --git a/src/views/apps/cms/articles/components/edit.vue b/src/views/apps/cms/articles/components/edit.vue index 93a87b8..cac8d51 100644 --- a/src/views/apps/cms/articles/components/edit.vue +++ b/src/views/apps/cms/articles/components/edit.vue @@ -146,7 +146,7 @@ const cateOptions = ref([]); const form = reactive({ title: '', - author: '美天科技', + author: '云泽网', cate: '', content: '', image: '', @@ -394,7 +394,7 @@ function resetForm() { // 重置表单数据 Object.assign(form, { title: '', - author: '美天科技', + author: '云泽网', cate: '', content: '', image: '', diff --git a/src/views/layouts/EmptyLayout.vue b/src/views/layouts/EmptyLayout.vue index 8596e23..953755f 100644 --- a/src/views/layouts/EmptyLayout.vue +++ b/src/views/layouts/EmptyLayout.vue @@ -3,6 +3,5 @@ - - \ No newline at end of file +// 简单的容器,用于嵌套路由展示 + \ No newline at end of file