excel格式:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Excel數據文件下載工具</title>
<!-- 引入必要的庫 -->
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Arial', sans-serif;
}
body {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f8f9fa;
}
.container {
background: white;
border-radius: 10px;
box-shadow: 0 2px 15px rgba(0,0,0,0.1);
padding: 30px;
}
h1 {
color: #333;
margin-bottom: 20px;
text-align: center;
font-size: 28px;
}
.description {
text-align: center;
color: #666;
margin-bottom: 30px;
line-height: 1.6;
}
.feature-note {
background-color: #fff3cd;
border-left: 4px solid #ffc107;
padding: 15px;
margin: 20px 0;
border-radius: 0 5px 5px 0;
}
.upload-section {
border: 2px dashed #007bff;
border-radius: 10px;
padding: 40px 20px;
text-align: center;
margin-bottom: 30px;
cursor: pointer;
transition: all 0.3s;
}
.upload-section:hover {
background-color: #f8f9fa;
}
.upload-icon {
font-size: 48px;
color: #007bff;
margin-bottom: 20px;
}
#excelFile {
display: none;
}
.file-info {
margin: 20px 0;
padding: 15px;
background-color: #e9f7fe;
border-radius: 5px;
display: none;
}
.controls {
display: flex;
justify-content: center;
gap: 15px;
margin-bottom: 30px;
flex-wrap: wrap;
}
button {
padding: 10px 25px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
display: flex;
align-items: center;
gap: 8px;
}
#downloadAll {
background-color: #28a745;
color: white;
}
#downloadAll:hover {
background-color: #218838;
}
#downloadZip {
background-color: #17a2b8;
color: white;
}
#downloadZip:hover {
background-color: #138496;
}
#clearData {
background-color: #dc3545;
color: white;
}
#clearData:hover {
background-color: #c82333;
}
.data-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 30px;
display: none;
}
.data-table th, .data-table td {
padding: 12px 15px;
border: 1px solid #ddd;
text-align: left;
}
.data-table th {
background-color: #007bff;
color: white;
}
.data-table tr:nth-child(even) {
background-color: #f8f9fa;
}
.data-table tr:hover {
background-color: #e9f7fe;
}
.path-cell {
max-width: 400px;
word-break: break-all;
}
.filename-preview {
font-size: 12px;
color: #28a745;
margin-top: 5px;
font-style: italic;
}
.download-btn {
background-color: #007bff;
color: white;
padding: 6px 12px;
font-size: 14px;
border-radius: 4px;
cursor: pointer;
border: none;
}
.download-btn:hover {
background-color: #0056b3;
}
.multi-path {
color: #dc3545;
font-weight: bold;
}
.progress-container {
margin: 20px 0;
height: 8px;
background-color: #eee;
border-radius: 4px;
overflow: hidden;
display: none;
}
.progress-bar {
height: 100%;
background-color: #28a745;
width: 0%;
transition: width 0.3s;
}
.status-message {
text-align: center;
padding: 10px;
margin: 10px 0;
border-radius: 5px;
display: none;
}
.success {
background-color: #d4edda;
color: #155724;
display: block;
}
.error {
background-color: #f8d7da;
color: #721c24;
display: block;
}
.loading {
background-color: #d1ecf1;
color: #0c5460;
display: block;
}
.empty-state {
text-align: center;
padding: 50px 0;
color: #666;
}
.empty-state i {
font-size: 48px;
margin-bottom: 20px;
color: #ccc;
}
.file-count {
background-color: #007bff;
color: white;
padding: 5px 15px;
border-radius: 20px;
display: inline-block;
margin-bottom: 20px;
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
<div class="container">
<h1><i class="fas fa-file-excel"></i> Excel數據文件下載工具</h1>
<div class="description">
<p>上傳包含文件下載信息的Excel文件(需包含path和name列),支持path列中逗號分隔的多個鏈接</p>
<p><strong>格式要求:</strong>Excel文件需包含兩列 - path(文件鏈接,多個鏈接用逗號分隔)和name(文件名稱)</p>
</div>
<div class="feature-note">
<strong>功能:</strong>當同一個人有多個文件時,會自動在文件名後添加序號(如:張三1.pdf、張三2.jpg)
</div>
<!-- 文件上傳區域 -->
<div class="upload-section" onclick="document.getElementById('excelFile').click()">
<div class="upload-icon">
<i class="fas fa-cloud-upload-alt"></i>
</div>
<h3>點擊上傳Excel文件</h3>
<p class="text-muted">支持.xlsx, .xls格式</p>
<input type="file" id="excelFile" accept=".xlsx,.xls" onchange="handleFileUpload(event)">
</div>
<!-- 文件信息 -->
<div class="file-info" id="fileInfo">
<h4><i class="fas fa-file-excel"></i> <span id="fileName">文件名</span></h4>
<p>數據行數: <span id="rowCount">0</span> | 文件總數: <span id="totalFileCount">0</span></p>
</div>
<!-- 控制按鈕 -->
<div class="controls">
<button id="downloadAll" disabled><i class="fas fa-download"></i> 下載所有文件</button>
<button id="downloadZip" disabled><i class="fas fa-file-archive"></i> 下載壓縮包</button>
<button id="clearData"><i class="fas fa-trash"></i> 清空數據</button>
</div>
<!-- 狀態消息 -->
<div class="status-message" id="statusMessage"></div>
<!-- 進度條 -->
<div class="progress-container" id="progressContainer">
<div class="progress-bar" id="progressBar"></div>
</div>
<!-- 文件總數標記 -->
<div id="fileCountDisplay" class="file-count" style="display: none;"></div>
<!-- 數據表格 -->
<table class="data-table" id="dataTable">
<thead>
<tr>
<th>序號</th>
<th>名稱</th>
<th>文件路徑</th>
<th>生成文件名</th>
<th>操作</th>
</tr>
</thead>
<tbody id="tableBody">
<!-- 數據將動態生成 -->
</tbody>
</table>
</div>
<script>
// 全局變量存儲解析的數據
let excelData = [];
let allFileUrls = [];
// DOM元素
const excelFileInput = document.getElementById('excelFile');
const fileInfoDiv = document.getElementById('fileInfo');
const fileNameSpan = document.getElementById('fileName');
const rowCountSpan = document.getElementById('rowCount');
const totalFileCountSpan = document.getElementById('totalFileCount');
const dataTable = document.getElementById('dataTable');
const tableBody = document.getElementById('tableBody');
const downloadAllBtn = document.getElementById('downloadAll');
const downloadZipBtn = document.getElementById('downloadZip');
const clearDataBtn = document.getElementById('clearData');
const statusMessage = document.getElementById('statusMessage');
const progressContainer = document.getElementById('progressContainer');
const progressBar = document.getElementById('progressBar');
const fileCountDisplay = document.getElementById('fileCountDisplay');
// 初始化事件監聽
document.addEventListener('DOMContentLoaded', () => {
clearDataBtn.addEventListener('click', clearAllData);
downloadAllBtn.addEventListener('click', downloadAllFiles);
downloadZipBtn.addEventListener('click', downloadAllAsZip);
});
// 處理Excel文件上傳
function handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;
// 檢查文件類型
const fileExtension = file.name.split('.').pop().toLowerCase();
if (fileExtension !== 'xlsx' && fileExtension !== 'xls') {
showStatus('請上傳Excel文件(.xlsx或.xls)', 'error');
return;
}
showStatus('正在解析Excel文件...', 'loading');
const reader = new FileReader();
reader.onload = function(e) {
try {
// 解析Excel文件
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
// 獲取第一個工作表
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
// 轉換為JSON
const jsonData = XLSX.utils.sheet_to_json(worksheet);
// 驗證數據格式
if (!validateExcelData(jsonData)) {
showStatus('Excel格式錯誤:必須包含path和name列', 'error');
return;
}
// 處理數據(拆分逗號分隔的path)
processExcelData(jsonData);
// 顯示數據
displayExcelData();
// 更新文件信息
fileNameSpan.textContent = file.name;
rowCountSpan.textContent = jsonData.length;
totalFileCountSpan.textContent = allFileUrls.length;
fileInfoDiv.style.display = 'block';
dataTable.style.display = 'table';
fileCountDisplay.style.display = 'inline-block';
fileCountDisplay.textContent = `總計 ${allFileUrls.length} 個文件`;
// 啓用按鈕
downloadAllBtn.disabled = false;
downloadZipBtn.disabled = false;
showStatus('Excel文件解析成功', 'success');
} catch (error) {
showStatus(`解析失敗: ${error.message}`, 'error');
console.error(error);
}
};
reader.readAsArrayBuffer(file);
}
// 驗證Excel數據格式
function validateExcelData(data) {
if (data.length === 0) return false;
// 檢查第一行是否包含path和name列(不區分大小寫)
const firstRow = data[0];
const keys = Object.keys(firstRow).map(key => key.toLowerCase());
return keys.includes('path') && keys.includes('name');
}
// 提取學生姓名(從"-"前獲取)
function extractStudentName(fullName) {
if (!fullName) return '未知名稱';
if (fullName.includes('-')) {
return fullName.split('-')[0].trim();
}
return fullName.trim();
}
// 處理Excel數據(拆分逗號分隔的path)
function processExcelData(jsonData) {
excelData = [];
allFileUrls = [];
// 首先構建所有文件的列表
let allFiles = [];
jsonData.forEach((row, index) => {
// 標準化列名(不區分大小寫)
const rowData = {};
Object.keys(row).forEach(key => {
const lowerKey = key.toLowerCase();
rowData[lowerKey] = row[key];
});
const originalName = rowData.name || `未命名_${index + 1}`;
const studentName = extractStudentName(originalName);
const pathStr = (rowData.path || '').toString().trim();
// 拆分逗號分隔的path
const paths = pathStr.split(',').map(path => path.trim()).filter(path => path);
// 添加到總文件列表
paths.forEach(path => {
allFiles.push({
path: path,
originalName: originalName,
studentName: studentName,
rowIndex: index
});
});
});
// 統計每個學生的文件數量
const studentFileCount = {};
allFiles.forEach(file => {
if (!studentFileCount[file.studentName]) {
studentFileCount[file.studentName] = 0;
}
studentFileCount[file.studentName]++;
});
// 為每個學生的文件分配序號並生成最終數據
const studentCurrentIndex = {};
jsonData.forEach((row, index) => {
const rowData = {};
Object.keys(row).forEach(key => {
const lowerKey = key.toLowerCase();
rowData[lowerKey] = row[key];
});
const originalName = rowData.name || `未命名_${index + 1}`;
const studentName = extractStudentName(originalName);
const pathStr = (rowData.path || '').toString().trim();
// 初始化當前序號
if (!studentCurrentIndex[studentName]) {
studentCurrentIndex[studentName] = 0;
}
// 拆分逗號分隔的path
const paths = pathStr.split(',').map(path => path.trim()).filter(path => path);
// 為每個路徑生成帶序號的文件名
const fileItems = [];
paths.forEach(path => {
if (path) {
// 增加當前計數器
studentCurrentIndex[studentName]++;
// 獲取文件擴展名
const extension = path.split('.').pop().split('?')[0].toLowerCase();
// 生成帶序號的文件名
let newFileName = studentName;
if (studentFileCount[studentName] > 1) {
newFileName += studentCurrentIndex[studentName];
}
newFileName += `.${extension}`;
// 添加到文件列表
fileItems.push({
path: path,
newFileName: newFileName,
originalName: originalName,
studentName: studentName,
index: studentCurrentIndex[studentName]
});
// 添加到總文件列表
allFileUrls.push({
url: path,
name: studentName,
newFileName: newFileName,
originalRow: index + 1,
fileIndex: studentCurrentIndex[studentName]
});
}
});
// 存儲處理後的數據
excelData.push({
originalName: originalName,
studentName: studentName,
files: fileItems,
hasMultiplePaths: fileItems.length > 1
});
});
}
// 顯示Excel數據
function displayExcelData() {
tableBody.innerHTML = '';
excelData.forEach((item, index) => {
if (item.files && item.files.length > 0) {
item.files.forEach((file, fileIndex) => {
const row = document.createElement('tr');
// 序號(只在第一個文件顯示行號)
const indexCell = document.createElement('td');
if (fileIndex === 0) {
indexCell.textContent = index + 1;
indexCell.rowSpan = item.files.length;
}
// 名稱(只在第一個文件顯示)
const nameCell = document.createElement('td');
if (fileIndex === 0) {
nameCell.textContent = item.studentName;
nameCell.rowSpan = item.files.length;
}
// 路徑
const pathCell = document.createElement('td');
pathCell.className = 'path-cell';
pathCell.textContent = file.path || '';
// 生成的文件名
const fileNameCell = document.createElement('td');
fileNameCell.innerHTML = `<strong>${file.newFileName || ''}</strong>`;
if (item.files.length > 1) {
fileNameCell.innerHTML += `<div class="filename-preview">(${item.studentName}${file.index})</div>`;
}
// 操作按鈕
const actionCell = document.createElement('td');
const downloadBtn = document.createElement('button');
downloadBtn.className = 'download-btn';
downloadBtn.innerHTML = '<i class="fas fa-download"></i> 下載';
downloadBtn.onclick = () => {
if (file.path && file.newFileName) {
downloadSingleFileWithNewName(file.path, file.newFileName);
} else {
showStatus('文件信息不完整', 'error');
}
};
actionCell.appendChild(downloadBtn);
// 添加單元格到行
if (fileIndex === 0) {
row.appendChild(indexCell);
row.appendChild(nameCell);
}
row.appendChild(pathCell);
row.appendChild(fileNameCell);
row.appendChild(actionCell);
// 添加行到表格
tableBody.appendChild(row);
});
}
});
}
// 下載單個文件(使用新的帶序號的名稱)
function downloadSingleFileWithNewName(url, newFileName) {
if (!url || !newFileName) {
showStatus('文件信息不完整', 'error');
return;
}
showStatus(`正在下載: ${newFileName}`, 'loading');
try {
// 創建下載鏈接
const link = document.createElement('a');
link.href = url;
link.download = newFileName;
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
setTimeout(() => {
showStatus(`文件已下載: ${newFileName}`, 'success');
}, 500);
} catch (error) {
showStatus(`下載失敗: ${newFileName}`, 'error');
console.error('下載失敗:', error);
}
}
// 下載所有文件
function downloadAllFiles() {
if (!allFileUrls || allFileUrls.length === 0) {
showStatus('沒有可下載的文件', 'error');
return;
}
showStatus(`開始下載所有 ${allFileUrls.length} 個文件...`, 'loading');
progressContainer.style.display = 'block';
// 逐個下載文件,添加延遲避免瀏覽器限制
allFileUrls.forEach((fileInfo, index) => {
setTimeout(() => {
if (fileInfo.url && fileInfo.newFileName) {
downloadSingleFileWithNewName(fileInfo.url, fileInfo.newFileName);
}
// 更新進度
updateProgress((index + 1) / allFileUrls.length * 100);
// 最後一個文件
if (index === allFileUrls.length - 1) {
setTimeout(() => {
showStatus('所有文件下載已啓動', 'success');
updateProgress(0);
progressContainer.style.display = 'none';
}, 1000);
}
}, index * 300); // 間隔300ms
});
}
// 下載所有文件為壓縮包
function downloadAllAsZip() {
if (!allFileUrls || allFileUrls.length === 0) {
showStatus('沒有可下載的文件', 'error');
return;
}
showStatus('正在下載並壓縮文件...', 'loading');
progressContainer.style.display = 'block';
const zip = new JSZip();
let completed = 0;
const totalFiles = allFileUrls.length;
// 下載所有文件並添加到壓縮包
allFileUrls.forEach((fileInfo, index) => {
if (!fileInfo.url || !fileInfo.newFileName) {
completed++;
if (completed === totalFiles) {
generateZipFile(zip);
}
return;
}
fetchFile(fileInfo.url)
.then(blob => {
completed++;
// 使用帶序號的文件名添加到壓縮包
// 按學生姓名創建文件夾
const folder = zip.folder(fileInfo.name) || zip;
folder.file(fileInfo.newFileName, blob);
// 更新進度
updateProgress(completed / totalFiles * 100);
// 所有文件處理完成
if (completed === totalFiles) {
generateZipFile(zip);
}
})
.catch(error => {
console.error(`下載失敗: ${fileInfo.url}`, error);
completed++;
// 繼續處理其他文件
if (completed === totalFiles) {
generateZipFile(zip);
}
});
});
}
// 獲取文件Blob
function fetchFile(url) {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP錯誤! 狀態碼: ${response.status}`);
}
return response.blob();
});
}
// 生成壓縮文件
function generateZipFile(zip) {
showStatus('正在生成壓縮文件...', 'loading');
zip.generateAsync({
type: 'blob',
compression: 'DEFLATE',
compressionOptions: { level: 6 }
}, metadata => {
// 更新壓縮進度
updateProgress(50 + metadata.percent / 2);
})
.then(content => {
// 下載壓縮文件
const zipFileName = `學生文件_${new Date().getFullYear()}${(new Date().getMonth()+1).toString().padStart(2,'0')}${new Date().getDate().toString().padStart(2,'0')}.zip`;
saveAs(content, zipFileName);
updateProgress(100);
setTimeout(() => {
progressContainer.style.display = 'none';
showStatus(`壓縮包已生成: ${zipFileName}`, 'success');
updateProgress(0);
}, 500);
})
.catch(error => {
showStatus(`壓縮失敗: ${error.message}`, 'error');
progressContainer.style.display = 'none';
updateProgress(0);
});
}
// 更新進度條
function updateProgress(percent) {
if (progressBar) {
progressBar.style.width = `${percent}%`;
}
}
// 顯示狀態消息
function showStatus(message, type) {
if (statusMessage) {
statusMessage.textContent = message;
statusMessage.className = `status-message ${type}`;
// 自動清除成功消息
if (type === 'success') {
setTimeout(() => {
if (statusMessage) {
statusMessage.className = 'status-message';
}
}, 3000);
}
}
}
// 清空所有數據
function clearAllData() {
excelData = [];
allFileUrls = [];
// 重置UI
if (tableBody) tableBody.innerHTML = '';
if (fileInfoDiv) fileInfoDiv.style.display = 'none';
if (dataTable) dataTable.style.display = 'none';
if (fileCountDisplay) fileCountDisplay.style.display = 'none';
if (statusMessage) statusMessage.className = 'status-message';
// 禁用按鈕
if (downloadAllBtn) downloadAllBtn.disabled = true;
if (downloadZipBtn) downloadZipBtn.disabled = true;
// 重置文件輸入
if (excelFileInput) excelFileInput.value = '';
showStatus('數據已清空', 'success');
}
</script>
</body>
</html>
本文章為轉載內容,我們尊重原作者對文章享有的著作權。如有內容錯誤或侵權問題,歡迎原作者聯繫我們進行內容更正或刪除文章。