346 lines
11 KiB
JavaScript
346 lines
11 KiB
JavaScript
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;
|
|
} |