class FileNameSorter { /** * 判断是否为纯数字字符串 * @param {any} value - 要检查的值 * @returns {boolean} 是否为纯数字 */ static isPureNumber(value) { if (typeof value !== 'string') return false; return /^\d+$/.test(value); } /** * 获取排序键值 * @param {any} item - 要排序的项目 * @returns {Array} 排序键值数组 */ static getSortKey(item) { const str = String(item); const isNumber = this.isPureNumber(str); // 排序优先级: [类型优先级, 数字值/字符串值] if (isNumber) { // 纯数字: 类型优先级为0, 使用数字值排序 return [0, BigInt(str)]; } else { // 非纯数字: 类型优先级为1, 使用字符串排序 return [1, str.toLowerCase()]; } } /** * 文件名排序 * @param {Array} arr - 要排序的数组 * @param {Object} options - 排序选项 * @returns {Array} 排序后的数组 */ static sortFileNames(arr, options = {}) { if (!Array.isArray(arr)) { throw new TypeError('输入必须是一个数组'); } const { direction = 'asc', // 'asc' 升序, 'desc' 降序 caseSensitive = false, // 是否区分大小写 trim = true, // 是否去除前后空格 key = null } = options; // 创建数组的副本 const result = [...arr]; return result.sort((a, b) => { let aStr = key ? String(a[key]).split(".")[0] : String(a); let bStr = key ? String(b[key]).split(".")[0] : String(b); // 处理空格 if (trim) { aStr = aStr.trim(); bStr = bStr.trim(); } // 处理大小写 if (!caseSensitive) { aStr = aStr.toLowerCase(); bStr = bStr.toLowerCase(); } // 获取排序键值 const aKey = this.getSortKey(aStr); const bKey = this.getSortKey(bStr); // 比较类型优先级 if (aKey[0] !== bKey[0]) { // 纯数字(0) < 非纯数字(1) return direction === 'asc' ? aKey[0] - bKey[0] : bKey[0] - aKey[0]; } // 相同类型下的比较 if (aKey[0] === 0) { // 都是纯数字,按数字大小比较 if (aKey[1] < bKey[1]) return direction === 'asc' ? -1 : 1; if (aKey[1] > bKey[1]) return direction === 'asc' ? 1 : -1; return 0; } else { // 都是非纯数字,按字符串比较 if (aStr < bStr) return direction === 'asc' ? -1 : 1; if (aStr > bStr) return direction === 'asc' ? 1 : -1; return 0; } }); } /** * 自然排序 - 处理文件名中的数字序列 * 例如: file1, file2, file10 * @param {Array} arr - 要排序的数组 * @param {Object} options - 排序选项 * @returns {Array} 排序后的数组 */ static naturalSort(arr, options = {}) { const { direction = 'asc', caseSensitive = false } = options; return [...arr].sort((a, b) => { const aStr = String(a); const bStr = String(b); // 检查是否都是纯数字 const aIsPureNumber = this.isPureNumber(aStr); const bIsPureNumber = this.isPureNumber(bStr); // 如果一个纯数字,一个非纯数字 if (aIsPureNumber && !bIsPureNumber) { return direction === 'asc' ? -1 : 1; } if (!aIsPureNumber && bIsPureNumber) { return direction === 'asc' ? 1 : -1; } // 都是纯数字,按数字比较 if (aIsPureNumber && bIsPureNumber) { const aNum = BigInt(aStr); const bNum = BigInt(bStr); if (aNum < bNum) return direction === 'asc' ? -1 : 1; if (aNum > bNum) return direction === 'asc' ? 1 : -1; return 0; } // 都是非纯数字,使用自然排序算法 return this.naturalCompare(aStr, bStr, direction, caseSensitive); }); } /** * 自然比较算法 * @private */ static naturalCompare(a, b, direction = 'asc', caseSensitive = false) { let aStr = caseSensitive ? a : a.toLowerCase(); let bStr = caseSensitive ? b : b.toLowerCase(); const regex = /(\d+|\D+)/g; const aParts = aStr.match(regex) || []; const bParts = bStr.match(regex) || []; const maxLength = Math.max(aParts.length, bParts.length); for (let i = 0; i < maxLength; i++) { const aPart = aParts[i] || ''; const bPart = bParts[i] || ''; const aIsNumber = /^\d+$/.test(aPart); const bIsNumber = /^\d+$/.test(bPart); // 如果都是数字,按数字比较 if (aIsNumber && bIsNumber) { const aNum = BigInt(aPart); const bNum = BigInt(bPart); if (aNum < bNum) return direction === 'asc' ? -1 : 1; if (aNum > bNum) return direction === 'asc' ? 1 : -1; continue; } // 都是文本,按字符串比较 if (aPart < bPart) return direction === 'asc' ? -1 : 1; if (aPart > bPart) return direction === 'asc' ? 1 : -1; } return 0; } /** * 分组排序 - 将纯数字和非纯数字分开显示 * @param {Array} arr - 要排序的数组 * @param {Object} options - 排序选项 * @returns {Array} 排序后的数组 */ static groupSort(arr, options = {}) { const { direction = 'asc', groupOrder = 'numbers-first' // 'numbers-first' 或 'names-first' } = options; // 分组 const numbers = []; const names = []; arr.forEach(item => { const str = String(item); if (this.isPureNumber(str)) { numbers.push(item); } else { names.push(item); } }); // 对各组排序 const sortedNumbers = this.sortFileNames(numbers, { direction }); const sortedNames = this.sortFileNames(names, { direction }); // 按指定顺序合并 if (groupOrder === 'numbers-first') { return [...sortedNumbers, ...sortedNames]; } else { return [...sortedNames, ...sortedNumbers]; } } /** * 文件扩展名排序 - 先按扩展名排序,再按文件名排序 * @param {Array} arr - 要排序的数组 * @param {Object} options - 排序选项 * @returns {Array} 排序后的数组 */ static sortByExtension(arr, options = {}) { const { direction = 'asc', extensionsFirst = false // 扩展名优先还是文件名优先 } = options; return [...arr].sort((a, b) => { const aStr = String(a); const bStr = String(b); // 分离文件名和扩展名 const getFileParts = (str) => { const lastDotIndex = str.lastIndexOf('.'); if (lastDotIndex > 0) { return { name: str.substring(0, lastDotIndex), ext: str.substring(lastDotIndex + 1) }; } return { name: str, ext: '' }; }; const aParts = getFileParts(aStr); const bParts = getFileParts(bStr); let compareResult; if (extensionsFirst) { // 先比较扩展名 compareResult = this.sortFileNames([aParts.ext, bParts.ext], { direction })[0] === aParts.ext ? -1 : 1; if (compareResult !== 0) return compareResult; // 扩展名相同,再比较文件名 return this.sortFileNames([aParts.name, bParts.name], { direction })[0] === aParts.name ? -1 : 1; } else { // 先比较文件名 compareResult = this.sortFileNames([aParts.name, bParts.name], { direction })[0] === aParts.name ? -1 : 1; if (compareResult !== 0) return compareResult; // 文件名相同,再比较扩展名 return this.sortFileNames([aParts.ext, bParts.ext], { direction })[0] === aParts.ext ? -1 : 1; } }); } /** * 测试和演示函数 */ static demonstrate() { const testData = [ '100', '23', '1', '003', 'file.txt', '10', '5', 'alpha', 'beta', 'gamma', '00100', 'zebra', 'apple', 'banana', '123', 'file10.txt', 'file2.txt', 'File1.txt', 'file20.txt', 'image.jpg', 'document.pdf', 'data.xlsx' ]; console.log('=== 文件名排序演示 ===\n'); console.log('原始数据:'); console.log(testData); console.log('\n1. 基本排序 (纯数字 < 非纯数字):'); const basicSorted = this.sortFileNames(testData); console.log(basicSorted); console.log('\n2. 自然排序 (智能数字识别):'); const naturalSorted = this.naturalSort(testData); console.log(naturalSorted); console.log('\n3. 分组排序 (数字组和文件组):'); const groupSorted = this.groupSort(testData); console.log(groupSorted); console.log('\n4. 降序排序:'); const descSorted = this.sortFileNames(testData, { direction: 'desc' }); console.log(descSorted); console.log('\n5. 按扩展名排序:'); const extSorted = this.sortByExtension(testData); console.log(extSorted); console.log('\n6. 大小写敏感排序:'); const caseSensitiveSorted = this.sortFileNames(testData, { caseSensitive: true }); console.log(caseSensitiveSorted); // 验证排序规则 console.log('\n=== 排序规则验证 ==='); const validationData = ['100', '23', 'file.txt', '1', 'alpha', '003', '10']; console.log('验证数据:', validationData); console.log('排序结果:', this.sortFileNames(validationData)); console.log('规则验证:'); console.log(' - 纯数字: 1, 3(003), 10, 23, 100 (按数字大小)'); console.log(' - 非纯数字: alpha, file.txt (按字母顺序)'); console.log(' - 非纯数字 > 纯数字'); return { basic: basicSorted, natural: naturalSorted, group: groupSorted, descending: descSorted, byExtension: extSorted, caseSensitive: caseSensitiveSorted }; } } if (typeof module !== 'undefined' && module.exports) { module.exports = FileNameSorter; } else if (typeof window !== 'undefined') { window.FileNameSorter = FileNameSorter; }