task-2(壳层/路由): 冻结 /admin-vue/ base 与 History 路由契约为单一事实源
新增 src/config/app.ts(APP_BASE_PATH/ensureAppBasePath/joinAdminPath), vite.config base、router createWebHistory、AdminLayout logo 路径统一消费常量, 禁止散落字面量;8 用例覆盖归一化/幂等/空段/单段/非法输入与字面量单源。
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* /admin-vue/ 基准路径与 History 路由约定(任务 2 冻结的单一事实源)。
|
||||
* Vite base、Vue Router history base、Nginx location 三者必须共用此常量。
|
||||
*/
|
||||
export const APP_BASE_PATH = '/admin-vue/'
|
||||
|
||||
/** 前端路由一律使用 History 模式,不引入 hash(旧 #tab 兼容不在目标契约内)。 */
|
||||
export const APP_HISTORY_MODE = 'history' as const
|
||||
|
||||
function invalidBaseMessage(value: unknown): string {
|
||||
return `管理后台 base 路径非法: ${JSON.stringify(value)},必须以 / 开头且形如 /xxx/,不允许空值或 ..`
|
||||
}
|
||||
|
||||
/** 校验并归一化 base 路径:保证以 / 开头、无 ..、无内部连续斜杠、以单个 / 结尾。 */
|
||||
export function ensureAppBasePath(value: string): string {
|
||||
if (typeof value !== 'string' || value.length === 0) throw new Error(invalidBaseMessage(value))
|
||||
if (!value.startsWith('/')) throw new Error(invalidBaseMessage(value))
|
||||
if (value.includes('//')) throw new Error(invalidBaseMessage(value))
|
||||
if (value.includes('..')) throw new Error(invalidBaseMessage(value))
|
||||
return value.endsWith('/') ? value : `${value}/`
|
||||
}
|
||||
|
||||
/** 把后台页面相对路径段拼成绝对 URL(不含 base 前缀的历史兼容子路径段由路由负责)。 */
|
||||
export function joinAdminPath(...segments: string[]): string {
|
||||
const parts: string[] = []
|
||||
for (const segment of segments) {
|
||||
for (const raw of segment.split('/')) {
|
||||
const part = raw.trim()
|
||||
if (!part || part === '.') continue
|
||||
if (part === '..') {
|
||||
throw new Error(`admin 路由段不允许出现 ..(越权跳转): ${segments.join('/')}`)
|
||||
}
|
||||
parts.push(part)
|
||||
}
|
||||
}
|
||||
if (parts.length === 0) return APP_BASE_PATH
|
||||
return `${APP_BASE_PATH}${parts.join('/')}`
|
||||
}
|
||||
Reference in New Issue
Block a user