20260904232445

This commit is contained in:
oneao committed 2026-09-04 23:24:45 +08:00
1 parent 4e167df0f0
commit 1b01a31252
19 files changed
+1099 -1013

No files matched your search

+151
View File
@@ -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'),
)