/** * 前端路由常量(工程规范 04 / 11:路由集中管理,禁止散落字符串) * * 约定:`/<模块>/<功能>`;模块 key、基础路径与页面目录一一对应。 * 例如:管理中心(system)→ /system/users → pages/system/users。 */ export const ROUTES = { home: '/', login: '/login', system: { root: '/system', users: '/system/users', userNew: '/system/users/new', userDetail: '/system/users/:id', roles: '/system/roles', roleNew: '/system/roles/new', roleDetail: '/system/roles/:id', }, unauthorized: '/401', forbidden: '/403', notFound: '/404', serverError: '/500', } as const; export type RoutePath = | typeof ROUTES.home | typeof ROUTES.login | typeof ROUTES.system.root | typeof ROUTES.system.users | typeof ROUTES.system.userNew | typeof ROUTES.system.userDetail | typeof ROUTES.system.roles | typeof ROUTES.system.roleNew | typeof ROUTES.system.roleDetail | typeof ROUTES.unauthorized | typeof ROUTES.forbidden | typeof ROUTES.notFound | typeof ROUTES.serverError; /** 用户详情深链:`/system/users/{id}` */ export function userDetailPath(userId: string): string { return `${ROUTES.system.users}/${encodeURIComponent(userId)}`; } /** 角色详情 / 编辑器深链:`/system/roles/{id}` */ export function roleDetailPath(roleId: string): string { return `${ROUTES.system.roles}/${encodeURIComponent(roleId)}`; } /** 新建角色深链;带来源角色时作为复制模板预填(特殊权限不随复制带入) */ export function roleNewPath(sourceRoleId?: string): string { return sourceRoleId === undefined ? ROUTES.system.roleNew : `${ROUTES.system.roleNew}?from=${encodeURIComponent(sourceRoleId)}`; } /** * 校验回跳地址:只允许站内绝对路径(以单个 `/` 开头), * 拒绝协议相对地址(`//evil.com`)与外部 URL,避免开放重定向。 */ export function sanitizeRedirect(value: string | null | undefined): string | null { if (value === null || value === undefined) { return null; } const trimmed = value.trim(); if (!trimmed.startsWith('/') || trimmed.startsWith('//')) { return null; } if (trimmed.startsWith(ROUTES.login)) { return null; } return trimmed; } /** 登录页地址;带 redirect 时回跳原路径(仅接受站内相对路径) */ export function loginPath(redirect?: string): string { const target = sanitizeRedirect(redirect); return target === null ? ROUTES.login : `${ROUTES.login}?redirect=${encodeURIComponent(target)}`; }