重构:文件系统模块化架构,优化应用启动流程
This commit is contained in:
321
web/src/utils/fileUtils.js
Normal file
321
web/src/utils/fileUtils.js
Normal file
@@ -0,0 +1,321 @@
|
||||
/**
|
||||
* 文件工具函数集合
|
||||
*
|
||||
* @module utils/fileUtils
|
||||
* @description 提供文件相关的通用工具函数,避免代码重复
|
||||
*/
|
||||
|
||||
import { FILE_ICON_MAP, FILE_ICONS, BYTE_UNITS, FILE_SIZE_FORMAT, FILE_EXTENSIONS } from './constants'
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
* @param {number} bytes - 文件大小(字节)
|
||||
* @returns {string} 格式化后的文件大小字符串
|
||||
*
|
||||
* @example
|
||||
* formatBytes(1024) // "1.00 KB"
|
||||
* formatBytes(1048576) // "1.00 MB"
|
||||
* formatBytes(0) // "0 B"
|
||||
*/
|
||||
export function formatBytes(bytes) {
|
||||
if (!bytes || bytes === 0) return '0 B'
|
||||
|
||||
const unit = FILE_SIZE_FORMAT.UNIT
|
||||
const decimals = FILE_SIZE_FORMAT.DECIMAL_PLACES
|
||||
|
||||
if (bytes < unit) return bytes + ' B'
|
||||
|
||||
const exp = Math.floor(Math.log(bytes) / Math.log(unit))
|
||||
const value = bytes / Math.pow(unit, exp)
|
||||
const unitSymbol = BYTE_UNITS[1][exp - 1] + 'B'
|
||||
|
||||
return value.toFixed(decimals) + ' ' + unitSymbol
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件路径中提取文件名
|
||||
* @param {string} path - 文件路径
|
||||
* @returns {string} 文件名
|
||||
*
|
||||
* @example
|
||||
* getFileName('/home/user/docs/file.txt') // "file.txt"
|
||||
* getFileName('C:\\Users\\user\\file.txt') // "file.txt"
|
||||
* getFileName('file.txt') // "file.txt"
|
||||
*/
|
||||
export function getFileName(path) {
|
||||
if (!path) return ''
|
||||
|
||||
// 统一分隔符为正斜杠
|
||||
const normalizedPath = path.replace(/\\/g, '/')
|
||||
|
||||
// 分割路径并取最后一部分
|
||||
const parts = normalizedPath.split('/')
|
||||
|
||||
return parts[parts.length - 1] || path
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件路径中提取文件扩展名
|
||||
* @param {string} path - 文件路径
|
||||
* @returns {string} 扩展名(小写,不含点号)
|
||||
*
|
||||
* @example
|
||||
* getFileExtension('/path/to/file.txt') // "txt"
|
||||
* getFileExtension('/path/to/file.TXT') // "txt"
|
||||
* getFileExtension('/path/to/file') // ""
|
||||
*/
|
||||
export function getFileExtension(path) {
|
||||
if (!path) return ''
|
||||
|
||||
const fileName = getFileName(path)
|
||||
const lastDotIndex = fileName.lastIndexOf('.')
|
||||
|
||||
if (lastDotIndex === -1 || lastDotIndex === fileName.length - 1) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return fileName.substring(lastDotIndex + 1).toLowerCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件信息获取对应的图标
|
||||
* @param {Object} fileInfo - 文件信息对象
|
||||
* @param {boolean} fileInfo.is_dir - 是否为目录
|
||||
* @param {string} fileInfo.name - 文件名
|
||||
* @returns {string} 文件图标(emoji)
|
||||
*
|
||||
* @example
|
||||
* getFileIcon({ is_dir: true }) // "📁"
|
||||
* getFileIcon({ is_dir: false, name: 'image.png' }) // "🖼️"
|
||||
* getFileIcon({ is_dir: false, name: 'document.pdf' }) // "📕"
|
||||
*/
|
||||
export function getFileIcon(fileInfo) {
|
||||
if (!fileInfo) return FILE_ICONS.FILE
|
||||
|
||||
// 如果是目录
|
||||
if (fileInfo.is_dir) {
|
||||
return FILE_ICONS.FOLDER
|
||||
}
|
||||
|
||||
// 获取文件扩展名
|
||||
const ext = getFileExtension(fileInfo.name)
|
||||
|
||||
// 从映射表中查找图标
|
||||
return FILE_ICON_MAP.get(ext) || FILE_ICONS.FILE
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断文件是否为图片
|
||||
* @param {string} path - 文件路径
|
||||
* @returns {boolean} 是否为图片文件
|
||||
*/
|
||||
export function isImageFile(path) {
|
||||
const ext = getFileExtension(path)
|
||||
return FILE_EXTENSIONS.IMAGE.includes(ext)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断文件是否为视频
|
||||
* @param {string} path - 文件路径
|
||||
* @returns {boolean} 是否为视频文件
|
||||
*/
|
||||
export function isVideoFile(path) {
|
||||
const ext = getFileExtension(path)
|
||||
return [...FILE_EXTENSIONS.VIDEO_BROWSER, ...FILE_EXTENSIONS.VIDEO_EXTERNAL].includes(ext)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断文件是否为音频
|
||||
* @param {string} path - 文件路径
|
||||
* @returns {boolean} 是否为音频文件
|
||||
*/
|
||||
export function isAudioFile(path) {
|
||||
const ext = getFileExtension(path)
|
||||
return FILE_EXTENSIONS.AUDIO.includes(ext)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断文件是否为PDF
|
||||
* @param {string} path - 文件路径
|
||||
* @returns {boolean} 是否为PDF文件
|
||||
*/
|
||||
export function isPdfFile(path) {
|
||||
return getFileExtension(path) === 'pdf'
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化文件路径(将反斜杠转换为正斜杠,并进行URL编码)
|
||||
* @param {string} path - 原始路径
|
||||
* @param {boolean} encode - 是否进行URL编码(用于URL路径)
|
||||
* @returns {string} 规范化后的路径
|
||||
*
|
||||
* @example
|
||||
* normalizeFilePath('C:\\Users\\user\\file.txt') // "C:/Users/user/file.txt"
|
||||
* normalizeFilePath('/home/user/file.txt') // "/home/user/file.txt"
|
||||
* normalizeFilePath('E:/中文路径/file.pdf', true) // "E:/%E4%B8%AD%E6%96%87%E8%B7%AF%E5%BE%84/file.pdf"
|
||||
*/
|
||||
export function normalizeFilePath(path, encode = false) {
|
||||
if (!path) return ''
|
||||
const normalized = path.replace(/\\/g, '/')
|
||||
|
||||
// 如果需要编码,则使用 encodeURIComponent
|
||||
if (encode) {
|
||||
const parts = normalized.split('/')
|
||||
// 只对包含需要编码字符的路径段进行编码
|
||||
// Windows 路径格式: E:/path/to/file,第一部分是盘符(如 E:),不应编码
|
||||
return parts.map((segment, index) => {
|
||||
// 盘符部分(如 "E:")不编码
|
||||
if (index === 0 && /^[A-Za-z]:$/.test(segment)) {
|
||||
return segment
|
||||
}
|
||||
// 检查是否需要编码(包含非ASCII字符或特殊字符)
|
||||
if (/[^A-Za-z0-9\-_.~]/.test(segment)) {
|
||||
return encodeURIComponent(segment)
|
||||
}
|
||||
return segment
|
||||
}).join('/')
|
||||
}
|
||||
|
||||
return normalized
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件类型的友好名称
|
||||
* @param {string} path - 文件路径
|
||||
* @returns {string} 文件类型名称
|
||||
*
|
||||
* @example
|
||||
* getFileTypeName('image.png') // "PNG图片"
|
||||
* getFileTypeName('document.pdf') // "PDF文档"
|
||||
* getFileTypeName('unknown.xyz') // "XYZ文件"
|
||||
*/
|
||||
export function getFileTypeName(path) {
|
||||
const ext = getFileExtension(path)
|
||||
const extUpper = ext.toUpperCase()
|
||||
|
||||
// 图片
|
||||
if (['JPG', 'JPEG', 'PNG', 'GIF', 'BMP', 'SVG', 'WEBP'].includes(extUpper)) {
|
||||
return `${extUpper}图片`
|
||||
}
|
||||
|
||||
// 视频
|
||||
if (['MP4', 'WEBM', 'AVI', 'MKV'].includes(extUpper)) {
|
||||
return `${extUpper}视频`
|
||||
}
|
||||
|
||||
// 音频
|
||||
if (['MP3', 'WAV', 'FLAC', 'AAC'].includes(extUpper)) {
|
||||
return `${extUpper}音频`
|
||||
}
|
||||
|
||||
// PDF
|
||||
if (extUpper === 'PDF') {
|
||||
return 'PDF文档'
|
||||
}
|
||||
|
||||
// 文档
|
||||
if (['DOC', 'DOCX', 'XLS', 'XLSX', 'PPT', 'PPTX'].includes(extUpper)) {
|
||||
return `${extUpper}文档`
|
||||
}
|
||||
|
||||
// 代码
|
||||
if (['JS', 'TS', 'PY', 'JAVA', 'GO', 'RS', 'CPP'].includes(extUpper)) {
|
||||
return `${extUpper}代码`
|
||||
}
|
||||
|
||||
// 默认返回扩展名
|
||||
return ext ? `${extUpper}文件` : '文件'
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断文件是否为二进制文件
|
||||
* @param {string} path - 文件路径
|
||||
* @returns {boolean} 是否为二进制文件
|
||||
*/
|
||||
export function isBinaryFile(path) {
|
||||
const ext = getFileExtension(path)
|
||||
const binaryExtensions = [
|
||||
'exe', 'dll', 'so', 'dylib', // 可执行文件
|
||||
'zip', 'rar', '7z', 'tar', 'gz', 'bz2', // 压缩文件
|
||||
'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', // Office文档
|
||||
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'mp3', 'mp4', // 媒体文件
|
||||
'eot', 'ttf', 'otf', 'woff', 'woff2', // 字体文件
|
||||
]
|
||||
return binaryExtensions.includes(ext)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查路径是否为绝对路径
|
||||
* @param {string} path - 文件路径
|
||||
* @returns {boolean} 是否为绝对路径
|
||||
*
|
||||
* @example
|
||||
* isAbsolutePath('C:\\Users') // true (Windows)
|
||||
* isAbsolutePath('/home/user') // true (Unix)
|
||||
* isAbsolutePath('folder/file') // false
|
||||
*/
|
||||
export function isAbsolutePath(path) {
|
||||
if (!path) return false
|
||||
|
||||
// Windows路径:盘符开头
|
||||
if (/^[A-Za-z]:\\/.test(path)) return true
|
||||
|
||||
// Unix路径:以 / 开头
|
||||
if (path.startsWith('/')) return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接路径片段
|
||||
* @param {...string} parts - 路径片段
|
||||
* @returns {string} 拼接后的路径
|
||||
*
|
||||
* @example
|
||||
* joinPaths('/home', 'user', 'docs') // "/home/user/docs"
|
||||
* joinPaths('C:\\Users', 'user') // "C:\\Users\\user"
|
||||
*/
|
||||
export function joinPaths(...parts) {
|
||||
return parts.join('/').replace(/\/+/g, '/')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取父目录路径
|
||||
* @param {string} path - 文件路径
|
||||
* @returns {string} 父目录路径
|
||||
*
|
||||
* @example
|
||||
* getParentPath('/home/user/docs/file.txt') // "/home/user/docs"
|
||||
* getParentPath('/home/user/docs/') // "/home/user"
|
||||
*/
|
||||
export function getParentPath(path) {
|
||||
if (!path) return ''
|
||||
|
||||
const normalizedPath = normalizeFilePath(path)
|
||||
const lastSlashIndex = normalizedPath.lastIndexOf('/')
|
||||
|
||||
if (lastSlashIndex <= 0) {
|
||||
return '/' // 根目录
|
||||
}
|
||||
|
||||
return normalizedPath.substring(0, lastSlashIndex)
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理文件名,移除非法字符
|
||||
* @param {string} filename - 原始文件名
|
||||
* @param {string} [replacement='_'] - 替换字符
|
||||
* @returns {string} 清理后的文件名
|
||||
*
|
||||
* @example
|
||||
* sanitizeFileName('file/name.txt') // "file_name.txt"
|
||||
* sanitizeFileName('file:name.txt', '-') // "file-name.txt"
|
||||
*/
|
||||
export function sanitizeFileName(filename, replacement = '_') {
|
||||
if (!filename) return ''
|
||||
|
||||
// Windows不允许的字符: < > : " / \ | ? *
|
||||
const illegalChars = /[<>:"/\\|?*]/g
|
||||
|
||||
return filename.replace(illegalChars, replacement)
|
||||
}
|
||||
Reference in New Issue
Block a user