20260904232445
This commit is contained in:
1 parent
4e167df0f0
commit
1b01a31252
19 files changed
+1099
-1013
No files matched your search
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* 静态路由表(唯一定义处)。
|
||||
*
|
||||
* 单独拆出本文件的原因:「菜单管理」的「路由地址」需要从这里派生可选页面路由,
|
||||
* 若路由表仍写在 index.js 里,页面组件就会反向依赖「路由实例 + 守卫」模块,
|
||||
* 形成 页面 → router/index → 守卫 → store 的循环依赖风险。
|
||||
* 约定:
|
||||
* - routes.js :只声明路由表与由其派生的纯数据(无副作用、可被任意模块安全 import)
|
||||
* - index.js :只负责创建 router 实例、注册守卫、导出默认实例
|
||||
*/
|
||||
import DefaultLayout from '@/layouts/DefaultLayout.vue'
|
||||
|
||||
/**
|
||||
* 路由表。
|
||||
* 受保护页面挂在 DefaultLayout 下(layout 的 meta.requiresAuth 由守卫统一校验);
|
||||
* 需要按业务模块鉴权的页面,在 meta 上配 moduleId,守卫校验无权限时跳 /dashboard。
|
||||
*
|
||||
* 路由分组(/module、/test):父节点只写 path + redirect + children,**不写 component**。
|
||||
* vue-router 的 RouterView 会跳过 matched 中没有 components 的记录,子页面因此仍渲染在
|
||||
* DefaultLayout 的那个 router-view 上,keep-alive 层级不变。
|
||||
* 切勿给分组节点配「内部再放一个 router-view」的容器组件:那样页面会下沉一层,
|
||||
* 直接脱离 DefaultLayout 的 keep-alive,页面缓存失效。
|
||||
* 子路由 path 写相对路径('module-management'),完整路径由父级拼接,
|
||||
* 调整分组前缀时只需改父节点一处。
|
||||
*/
|
||||
export const routes = [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import('@/views/login/index.vue'),
|
||||
meta: { title: '登录' },
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: DefaultLayout,
|
||||
redirect: '/dashboard',
|
||||
meta: { requiresAuth: true },
|
||||
children: [
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'dashboard',
|
||||
component: () => import('@/views/dashboard/index.vue'),
|
||||
meta: { title: '工作台', componentName: 'Dashboard' },
|
||||
},
|
||||
// 组件库演练场:纳入业务布局(带侧栏 / 顶栏 / Tabs)
|
||||
{
|
||||
path: '/demo',
|
||||
name: 'demo',
|
||||
component: () => import('@/views/demo/index.vue'),
|
||||
meta: { title: '组件库', componentName: 'Demo' },
|
||||
},
|
||||
// 附件组件演示
|
||||
{
|
||||
path: '/file-demo',
|
||||
name: 'file-demo',
|
||||
component: () => import('@/views/file-demo/index.vue'),
|
||||
meta: { title: '附件组件演示', componentName: 'FileDemo' },
|
||||
},
|
||||
// 详情页:同一组件按参数复用,但每个 fullPath 独立缓存、关闭即销毁
|
||||
// (per-param 的命名组件由 DefaultLayout 的 getPageWrapper 按路由动态生成)
|
||||
{
|
||||
path: '/detail/:id',
|
||||
name: 'detail',
|
||||
component: () => import('@/views/detail/DetailPage.vue'),
|
||||
meta: { title: '详情', cacheByKey: true },
|
||||
},
|
||||
// 模块中心:元数据驱动的配置页集合
|
||||
// 分组节点(见上方「路由分组」注释:不写 component,子页写相对路径)
|
||||
{
|
||||
path: '/module',
|
||||
redirect: '/module/module-management',
|
||||
children: [
|
||||
// 模块管理:元数据驱动的模块配置中心(先静态路由,权限/动态菜单后续接入)
|
||||
{
|
||||
path: 'module-management',
|
||||
name: 'module-module-management',
|
||||
component: () => import('@/views/module/module-management/index.vue'),
|
||||
meta: { title: '模块管理', componentName: 'ModuleManagement' },
|
||||
},
|
||||
// 菜单管理:占位页面,待实现
|
||||
{
|
||||
path: 'menu-management',
|
||||
name: 'module-menu-management',
|
||||
component: () => import('@/views/module/menu-management/index.vue'),
|
||||
meta: { title: '菜单管理', componentName: 'MenuManagement' },
|
||||
},
|
||||
// 多语言类型:系统支持的语言清单维护(s_i18n_type)
|
||||
{
|
||||
path: 'i18n-type',
|
||||
name: 'module-i18n-type',
|
||||
component: () => import('@/views/module/i18n-type/index.vue'),
|
||||
meta: { title: '多语言类型', componentName: 'I18nTypeManagement' },
|
||||
},
|
||||
],
|
||||
},
|
||||
// 业务页面后续在此追加,配置 meta.moduleId 后自动走权限校验
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
redirect: '/dashboard',
|
||||
},
|
||||
]
|
||||
|
||||
/** 不作为菜单入口的路由:独立布局、无需登录即可访问的页面 */
|
||||
const EXCLUDED_PATHS = new Set(['/login'])
|
||||
|
||||
/**
|
||||
* 收集可作为菜单入口的页面路由(递归展开布局容器与分组节点)。
|
||||
* 过滤规则:
|
||||
* - 含 children 的节点(布局容器 / 分组节点)不是最终页面,只向下递归;
|
||||
* - 含动态段(:id)或通配(*)的路由无法作为固定菜单入口;
|
||||
* - 登录页等独立布局页排除在外。
|
||||
* @param {Array} list 路由节点数组,默认整张路由表
|
||||
* @param {string} parentPath 父节点完整路径,用于拼接分组内子路由的相对路径
|
||||
* @returns {{ value: string, label: string }[]} 选项(value 为完整 path)
|
||||
*/
|
||||
export function collectMenuRoutes(list = routes, parentPath = '') {
|
||||
const result = []
|
||||
for (const route of list || []) {
|
||||
const rawPath = route.path || ''
|
||||
// 绝对路径原样使用;相对路径(分组节点的 children)拼到父路径上
|
||||
const path = rawPath.startsWith('/') ? rawPath : joinPath(parentPath, rawPath)
|
||||
if (Array.isArray(route.children) && route.children.length) {
|
||||
result.push(...collectMenuRoutes(route.children, path))
|
||||
continue
|
||||
}
|
||||
if (!path.startsWith('/')) continue
|
||||
if (path.includes(':') || path.includes('*')) continue
|
||||
if (EXCLUDED_PATHS.has(path)) continue
|
||||
const title = route.meta?.title
|
||||
result.push({ value: path, label: title ? `${path}(${title})` : path })
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** 拼接父路径与子路径(相对路径形态):'/module' + 'i18n-type' → '/module/i18n-type' */
|
||||
function joinPath(parentPath, childPath) {
|
||||
const parent = String(parentPath || '').replace(/\/+$/, '')
|
||||
const child = String(childPath || '').replace(/^\/+/, '')
|
||||
if (!parent) return child ? `/${child}` : ''
|
||||
return child ? `${parent}/${child}` : parent
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单管理「路由地址」下拉选项:按路径排序,配合 Select 的本地包含匹配(like)筛选。
|
||||
* label 同时含路径与标题,输入 "/module" 或 "模块" 都能命中。
|
||||
*/
|
||||
export const menuRouteOptions = collectMenuRoutes().toSorted((a, b) =>
|
||||
a.value.localeCompare(b.value, 'zh-Hans-CN'),
|
||||
)
|
||||
Reference in new issue
Block a user