[JavaScript] 纯文本查看 复制代码
document.addEventListener('DOMContentLoaded', function() {
// 获取DOM元素
const fileInput = document.getElementById('file-input');
const uploadBtn = document.getElementById('upload-btn');
const uploadArea = document.getElementById('upload-area');
const previewContainer = document.getElementById('preview-container');
const filenameList = document.getElementById('filename-list');
const filenameActions = document.getElementById('filename-actions');
const generateBtn = document.getElementById('generate-btn');
const resetBtn = document.getElementById('reset-btn');
const newFilenameTextarea = document.getElementById('new-filename');
const clearAllBtn = document.getElementById('clear-all');
const downloadAllBtn = document.getElementById('download-all');
const filenameSearch = document.getElementById('filename-search'); // 新增搜索框元素
// 初始化PDF.js(确保已通过CDN引入)
pdfjsLib.GlobalWorkerOptions.workerSrc = 'js/pdf.worker.min.js';
// 初始化变量
let files = [];
let selectedPreviewIndex = -1;
let selectedFilenameIndex = -1;
let generatedNames = [];
let originalNames = [];
// 显示操作按钮区域
filenameActions.style.display = 'flex';
// 初始化上传区域状态
updateUploadAreaState();
// 点击上传按钮触发文件选择(无文件时才响应)
uploadBtn.addEventListener('click', () => {
if (files.length === 0) fileInput.click();
});
// 文件选择处理
fileInput.addEventListener('change', handleFiles);
// 上传区域点击事件(无文件时才响应)
uploadArea.addEventListener('click', function(e) {
if (e.target.closest('.preview-item')) return;
if (files.length === 0) fileInput.click();
});
// 拖放区域事件处理
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
if (files.length === 0) {
uploadArea.style.borderColor = '#3498db';
uploadArea.style.backgroundColor = '#f8f9fa';
}
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.style.borderColor = '#bdc3c7';
uploadArea.style.backgroundColor = 'transparent';
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.style.borderColor = '#bdc3c7';
uploadArea.style.backgroundColor = 'transparent';
if (e.dataTransfer.files.length && files.length === 0) {
fileInput.files = e.dataTransfer.files;
handleFiles({ target: fileInput });
}
});
// 更新上传区域状态
function updateUploadAreaState() {
if (files.length > 0) {
uploadArea.style.cursor = 'default';
uploadArea.style.borderColor = '#e0e0e0';
uploadArea.title = '请先清空现有文件再上传新文件';
uploadArea.classList.add('disabled');
} else {
uploadArea.style.cursor = 'pointer';
uploadArea.style.borderColor = '#bdc3c7';
uploadArea.title = '点击或拖放文件到此处上传';
uploadArea.classList.remove('disabled');
}
}
// 在这里添加搜索功能 ↓
filenameSearch.addEventListener('input', function() {
const searchTerm = this.value.trim().toLowerCase();
const buttons = document.querySelectorAll('.filename-btn');
let firstMatch = null;
buttons.forEach(button => {
const text = button.textContent.toLowerCase();
const isMatch = searchTerm === '' || text.includes(searchTerm);
button.classList.toggle('highlight', isMatch && searchTerm !== '');
if (isMatch && searchTerm !== '' && !firstMatch) {
firstMatch = button;
}
});
// 滚动到第一个匹配项
if (firstMatch) {
firstMatch.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
});
// 处理选择的文件
function handleFiles(e) {
const newFiles = Array.from(e.target.files);
if (newFiles.length === 0) return;
newFiles.forEach(file => {
file.previewHidden = false;
file.buttonHidden = false;
file.customName = null;
file.isRenamed = false;
});
files = [...files, ...newFiles];
originalNames = files.map(file => file.name);
generatedNames = [];
renderPreviews();
renderFilenameList();
updateUploadAreaState();
fileInput.value = '';
}
async function renderPDFPreview(file, container) {
try {
container.innerHTML = '<div class="pdf-loading">加载PDF中...</div>';
const pdfData = await readFileAsArrayBuffer(file);
const pdf = await pdfjsLib.getDocument({
data: pdfData,
cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.11.338/cmaps/',
cMapPacked: true
}).promise;
const page = await pdf.getPage(1);
const viewport = page.getViewport({ scale: 1.0 });
// 计算高质量缩略图的缩放比例
const targetWidth = 250; // 缩略图宽度
const scale = (targetWidth * 2) / viewport.width; // 2倍分辨率提高清晰度
const scaledViewport = page.getViewport({ scale });
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d', { willReadFrequently: true });
// 设置高质量画布
canvas.width = scaledViewport.width;
canvas.height = scaledViewport.height;
canvas.style.width = `${targetWidth}px`; // 显示尺寸
canvas.style.height = 'auto';
// 高质量渲染
await page.render({
canvasContext: context,
viewport: scaledViewport,
intent: 'print', // 使用打印质量
enableWebGL: true // 启用硬件加速
}).promise;
container.innerHTML = '';
container.appendChild(canvas);
return {
canvas,
naturalWidth: viewport.width,
naturalHeight: viewport.height,
scaleRatio: viewport.width / scaledViewport.width
};
} catch (error) {
console.error('PDF渲染失败:', error);
container.innerHTML = '<div class="preview-error">PDF预览加载失败</div>';
return null;
}
}
// 渲染预览(支持PDF和图片)
async function renderPreviews() {
previewContainer.innerHTML = '';
for (const [index, file] of files.entries()) {
if (file.previewHidden) continue;
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';
previewItem.dataset.index = index;
if (index === selectedPreviewIndex) {
previewItem.classList.add('selected');
}
const displayName = file.customName || file.name;
const isPDF = isPDFFile(file);
const isImage = isImageFile(file);
previewItem.innerHTML = `
<div class="preview-img-container">
${isPDF ? '<div class="pdf-loading">加载PDF中...</div>' :
isImage ? '<div class="loading-spinner"></div>' :
'<i class="fas fa-file"></i>'}
</div>
<div class="preview-name" title="${displayName}">
${truncateFilename(displayName)}
</div>
${(isPDF || isImage) ? '<div class="magnifier-glass"></div>' : ''}
`;
previewContainer.appendChild(previewItem);
try {
const imgContainer = previewItem.querySelector('.preview-img-container');
if (isPDF) {
// PDF渲染流程
const result = await renderPDFPreview(file, imgContainer);
if (result) {
setupMagnifier(previewItem, {
type: 'pdf',
source: result.canvas,
naturalWidth: result.naturalWidth,
naturalHeight: result.naturalHeight
});
}
} else if (isImage) {
// 图片渲染流程
const imgUrl = await readFileAsDataURL(file);
const img = new Image();
img.onload = function() {
// 保持原始宽高比
const maxWidth = 250;
const ratio = Math.min(maxWidth / img.naturalWidth, 1);
img.style.width = `${img.naturalWidth * ratio}px`;
img.style.height = 'auto';
imgContainer.innerHTML = '';
imgContainer.appendChild(img);
// 延迟确保图片完全渲染
setTimeout(() => {
setupMagnifier(previewItem, {
type: 'image',
source: img,
naturalWidth: img.naturalWidth,
naturalHeight: img.naturalHeight
});
}, 50);
};
img.onerror = function() {
imgContainer.innerHTML = `
<div class="preview-error">
<i class="fas fa-exclamation-triangle"></i>
<div>图片加载失败</div>
</div>
`;
};
img.src = imgUrl;
img.className = 'preview-img';
img.alt = displayName;
}
} catch (error) {
console.error(`预览加载失败 (${file.name}):`, error);
previewItem.querySelector('.preview-img-container').innerHTML = `
<div class="preview-error">
<i class="fas fa-exclamation-triangle"></i>
<div>预览加载失败</div>
</div>
`;
}
// 点击事件处理
previewItem.addEventListener('click', (e) => {
if (e.target.closest('.magnifier-glass')) return;
e.stopPropagation();
selectedPreviewIndex = selectedPreviewIndex === index ? -1 : index;
document.querySelectorAll('.preview-item').forEach(item => {
item.classList.toggle('selected', parseInt(item.dataset.index) === selectedPreviewIndex);
});
checkPairSelection();
});
}
}
// 修改setupMagnifier函数,使用全局放大镜
let globalMagnifier = null;
function setupMagnifier(previewItem, options) {
const container = previewItem.querySelector('.preview-img-container');
// 创建或复用全局放大镜
if (!globalMagnifier) {
globalMagnifier = document.createElement('div');
globalMagnifier.className = 'magnifier-glass';
document.body.appendChild(globalMagnifier);
// 放大镜基础样式
Object.assign(globalMagnifier.style, {
position: 'fixed',
width: '300px',
height: '300px',
borderRadius: '50%',
border: '2px solid #fff',
backgroundRepeat: 'no-repeat',
pointerEvents: 'none',
zIndex: '9999',
display: 'none',
boxShadow: '0 0 10px rgba(0,0,0,0.5)',
transform: 'translate(-50%, -50%)',
left: '0',
top: '0',
opacity: '0.9',
// 硬件加速
transform: 'translateZ(0)',
backfaceVisibility: 'hidden',
perspective: '1000px',
willChange: 'transform'
});
}
const sourceUrl = sourceToDataURL(options.source);
let lastUpdate = 0;
const updateInterval = 16; // ~60fps
// 节流鼠标移动处理
const handleMouseMove = (e) => {
const now = Date.now();
if (now - lastUpdate < updateInterval) return;
lastUpdate = now;
const containerRect = container.getBoundingClientRect();
const x = e.clientX - containerRect.left;
const y = e.clientY - containerRect.top;
const xRatio = x / containerRect.width;
const yRatio = y / containerRect.height;
if (xRatio < 0 || yRatio < 0 || xRatio > 1 || yRatio > 1) {
globalMagnifier.style.display = 'none';
return;
}
globalMagnifier.style.display = 'block';
globalMagnifier.style.left = `${e.clientX}px`;
globalMagnifier.style.top = `${e.clientY}px`;
const bgX = xRatio * options.naturalWidth * 2 - 100;
const bgY = yRatio * options.naturalHeight * 2 - 100;
globalMagnifier.style.backgroundImage = `url("${sourceUrl}")`;
globalMagnifier.style.backgroundPosition = `-${bgX}px -${bgY}px`;
};
// 事件监听
container.addEventListener('mousemove', handleMouseMove);
container.addEventListener('mouseleave', () => {
globalMagnifier.style.display = 'none';
});
// 确保容器有正确的定位上下文
container.style.position = 'relative';
}
// 添加在现有辅助函数附近
function sourceToDataURL(element) {
if (element instanceof HTMLCanvasElement) {
return element.toDataURL();
} else if (element.src) {
return element.src;
}
return '';
}
function isPDFFile(file) {
return file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf');
}
function isImageFile(file) {
return file.type.startsWith('image/');
}
// 辅助函数:读取文件为ArrayBuffer(用于PDF)
function readFileAsArrayBuffer(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsArrayBuffer(file);
});
}
// 辅助函数:读取文件为DataURL(用于图片)
function readFileAsDataURL(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
// 检查配对选择
function checkPairSelection() {
if (selectedPreviewIndex !== -1 && selectedFilenameIndex !== -1) {
const selectedFile = files[selectedPreviewIndex];
const newName = generatedNames[selectedFilenameIndex];
const ext = selectedFile.name.split('.').pop();
selectedFile.customName = newName.includes('.') ? newName : `${newName}.${ext}`;
selectedFile.isRenamed = true;
selectedFile.previewHidden = true;
files[selectedFilenameIndex].buttonHidden = true;
selectedPreviewIndex = -1;
selectedFilenameIndex = -1;
renderPreviews();
renderFilenameList();
}
}
// 生成文件名按钮点击
generateBtn.addEventListener('click', () => {
const inputText = newFilenameTextarea.value.trim();
if (!inputText) {
alert('请输入有效的文件名');
return;
}
generatedNames = parseFilenamesInput(inputText)
.map((name, index) => {
if (index >= files.length) return name;
const ext = files[index].name.split('.').pop();
return name.includes('.') ? name : `${name}.${ext}`;
});
renderFilenameList();
});
// 渲染文件名列表
function renderFilenameList() {
filenameList.innerHTML = '';
generatedNames.forEach((name, index) => {
if (files[index]?.buttonHidden) return;
const btn = document.createElement('button');
btn.className = 'filename-btn';
btn.textContent = `${index + 1}. ${name}`;
btn.dataset.index = index;
btn.title = name;
if (index === selectedFilenameIndex) {
btn.classList.add('active');
}
btn.addEventListener('click', () => {
if (selectedFilenameIndex === index) {
selectedFilenameIndex = -1;
btn.classList.remove('active');
} else {
document.querySelectorAll('.filename-btn').forEach(btn => {
btn.classList.remove('active');
});
selectedFilenameIndex = index;
btn.classList.add('active');
}
checkPairSelection();
});
filenameList.appendChild(btn);
});
// 如果有搜索词,重新触发搜索以保持高亮
if (filenameSearch.value.trim()) {
filenameSearch.dispatchEvent(new Event('input'));
}
}
// 解析多行文件名输入
function parseFilenamesInput(text) {
let names = text.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0);
if (names.length === 1) {
names = names[0].split(/[, ]+/).filter(name => name.length > 0);
}
return names;
}
// 文件名截断显示
function truncateFilename(name) {
return name.length > 20 ?
name.substring(0, 17) + '...' :
name;
}
// 重置按钮点击
resetBtn.addEventListener('click', () => {
newFilenameTextarea.value = '';
generatedNames = [];
selectedPreviewIndex = -1;
selectedFilenameIndex = -1;
files.forEach(file => {
file.previewHidden = false;
file.buttonHidden = false;
file.customName = null;
file.isRenamed = false;
});
renderPreviews();
renderFilenameList();
updateUploadAreaState();
});
// 清空所有
clearAllBtn.addEventListener('click', () => {
files = [];
generatedNames = [];
originalNames = [];
selectedPreviewIndex = -1;
selectedFilenameIndex = -1;
previewContainer.innerHTML = '';
filenameList.innerHTML = '';
newFilenameTextarea.value = '';
updateUploadAreaState();
});
// 确保JSZip已加载
function ensureJSZipLoaded() {
return new Promise((resolve, reject) => {
if (typeof JSZip !== 'undefined') {
resolve();
} else {
const script = document.createElement('script');
script.src = 'js/jszip.min.js';
script.onload = resolve;
script.onerror = () => {
reject(new Error('无法加载JSZip库'));
};
document.head.appendChild(script);
}
});
}
// 下载全部
downloadAllBtn.addEventListener('click', async () => {
const filesToDownload = files.filter(file =>
file.previewHidden && file.isRenamed
);
if (filesToDownload.length === 0) {
alert('没有可下载的文件');
return;
}
try {
await ensureJSZipLoaded();
const zip = new JSZip();
filesToDownload.forEach(file => {
zip.file(file.customName, file);
});
const content = await zip.generateAsync({ type: 'blob' });
const url = URL.createObjectURL(content);
const a = document.createElement('a');
a.href = url;
a.download = 'renamed_files.zip';
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
} catch (error) {
console.error('打包下载失败:', error);
alert(`打包下载失败: ${error.message}`);
}
});
});