/** * 跨平台路径拼接助手。 * * 前端仅用于「预览」派生路径,实际创建目录由后端完成。为了与后端 * PathUtils.joinPath 的行为保持一致,这里统一: * - 将反斜杠 \ 归一化为正斜杠 / * - 去除父路径末尾多余的分隔符(但保留文件系统根 `/` 与 Windows 盘符根) * - 去除子路径开头多余的分隔符 * - 以单个 / 连接 * * 注意:这只是显示层的规范化,不做真实文件系统解析。 */ /** 归一化根路径:转 POSIX 分隔符;去掉结尾斜杠,但保留 `/` 与 `C:` 形式的根。 */ export function normalizeRoot(raw: string): string { if (!raw) { return ''; } const posix = raw.replace(/\\/g, '/'); // 纯根斜杠:'/'、'//' → '/' if (/^\/+$/.test(posix)) { return '/'; } // Windows 盘符根:'C:'、'C:/'、'C://' → 'C:' const driveRoot = posix.match(/^([A-Za-z]:)\/+$/); if (driveRoot) { return driveRoot[1]; } if (/^[A-Za-z]:$/.test(posix)) { return posix; } return posix.replace(/\/+$/, ''); } /** 以规范化方式拼接父/子路径,返回 POSIX 形式字符串。 */ export function joinPath(parent: string, child: string): string { const parentPosix = normalizeRoot(parent); if (!parentPosix) { return child ? child.replace(/\\/g, '/') : ''; } if (!child) { return parentPosix; } const childPosix = child.replace(/\\/g, '/').replace(/^\/+/, ''); // 文件系统根:'/' + 'Input' → '/Input' if (parentPosix === '/') { return `/${childPosix}`; } return `${parentPosix}/${childPosix}`; } /** * 校验路径是否合法(与后端 PathUtils.isValidPath 对齐)。 * Windows 盘符后的冒号是合法的,其余情况下不允许出现 < > " | ? *。 * 文件系统根 `/` 与 Windows 盘符根(`C:` / `C:/`)视为合法。 */ export function isValidPath(path: string): boolean { if (!path || !path.trim()) { return false; } let pathToValidate = path.trim().replace(/\\/g, '/'); // POSIX 根 if (pathToValidate === '/' || /^\/+$/.test(pathToValidate)) { return true; } // Windows 盘符根 if (/^[A-Za-z]:\/?$/.test(pathToValidate)) { return true; } // 去掉 Windows 盘符再检查非法字符 if (/^[A-Za-z]:\//.test(pathToValidate)) { pathToValidate = pathToValidate.substring(2); } return !/[<>"|?*]/.test(pathToValidate); }