/** 通用工具函数(不依赖任何 store,避免循环引用) */ /** 是否为外链 */ export function isExternal(path: string): boolean { return /^(https?:|mailto:|tel:)/.test(path) } /** kebab-case / snake_case → PascalCase(Icon 组件用) */ export function toPascalCase(str: string): string { return (str || '') .split(/[-_\s]+/) .filter(Boolean) .map((s) => s.charAt(0).toUpperCase() + s.slice(1)) .join('') } /** 去掉首尾斜杠 */ export function trimSlash(path: string): string { return (path || '').replace(/^\/+|\/+$/g, '') } /** 拼接路径,自动去重斜杠 */ export function joinPath(...parts: string[]): string { return ( '/' + parts .map((p) => trimSlash(p)) .filter(Boolean) .join('/') ) } /** 把时间戳格式化为 yyyy-MM-dd HH:mm:ss */ export function formatDateTime(input?: number | string | Date, withTime = true): string { if (input === undefined || input === null || input === '') return '-' const d = input instanceof Date ? input : new Date(typeof input === 'number' ? input : String(input).replace(/-/g, '/')) if (isNaN(d.getTime())) return '-' const p = (n: number) => String(n).padStart(2, '0') const date = `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}` return withTime ? `${date} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}` : date } /** 列表 → 树(后端没直接给树时使用;parentId 为 null/0/undefined 视为根) */ export function buildTree>( list: T[], idKey = 'id', parentKey = 'parentId', childrenKey = 'children' ): T[] { const map = new Map() const roots: T[] = [] ;(list || []).forEach((item) => map.set(item[idKey], { ...item, [childrenKey]: [] })) ;(list || []).forEach((item) => { const node = map.get(item[idKey])! const parent = map.get(item[parentKey]) if (parent && parent !== node) { ;(parent[childrenKey] as T[]).push(node) } else { roots.push(node) } }) return roots } /** 简易防抖 */ export function debounce void>(fn: T, delay = 300) { let timer: any = null return (...args: Parameters) => { if (timer) clearTimeout(timer) timer = setTimeout(() => fn(...args), delay) } }