Files
qingdao-employment-service/static/upload.html
Apcallover 44c297aac2 flat: ok
2025-12-19 16:10:39 +08:00

1389 lines
51 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title>文件上传</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei', sans-serif;
background: linear-gradient(to bottom, #4995ff 0%, #ffffff 100%);
min-height: 100vh;
padding: 0;
color: #333;
}
.container {
min-height: 100vh;
padding: 20px 20px;
display: flex;
flex-direction: column;
position: relative;
}
.header {
text-align: center;
padding: 15px 20px;
color: white;
}
.title {
font-size: 28px;
font-weight: 600;
margin-bottom: 8px;
letter-spacing: 1px;
}
.subtitle {
font-size: 15px;
opacity: 0.9;
line-height: 1.5;
max-width: 280px;
margin: 0 auto;
}
.main-content {
flex: 1;
background: white;
border-radius: 20px;
padding: 25px 20px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
margin-top: 10px;
}
.upload-section {
margin-bottom: 25px;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #2d3436;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.upload-area {
border: 2px dashed #d1d8e0;
border-radius: 16px;
padding: 15px 6px;
text-align: center;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background: #f8f9fa;
position: relative;
overflow: hidden;
}
.upload-area:active {
transform: scale(0.98);
background: #f1f3f5;
border-color: #667eea;
}
.upload-area.dragover {
border-color: #667eea;
background: rgba(102, 126, 234, 0.05);
}
.upload-area.disabled {
opacity: 0.6;
cursor: not-allowed;
background: #f5f5f5;
border-color: #e0e0e0;
}
.upload-area.disabled:active {
transform: none;
}
.upload-icon {
font-size: 48px;
color: #667eea;
margin-bottom: 15px;
display: block;
}
.upload-text {
font-size: 16px;
color: #2d3436;
margin-bottom: 8px;
font-weight: 500;
}
.upload-hint {
font-size: 13px;
color: #7b8a9b;
line-height: 1.5;
}
.file-input {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
opacity: 0;
cursor: pointer;
}
.file-input:disabled {
cursor: not-allowed;
}
.selected-files {
display: none;
}
.selected-files.show {
display: block;
}
.file-list {
list-style: none;
}
.file-item {
display: flex;
align-items: center;
padding: 16px;
background: #f8f9fa;
border-radius: 14px;
margin-bottom: 12px;
border: 1px solid #e9ecef;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 文件图标容器样式 */
.file-icon-container {
width: 60px;
height: 60px;
border-radius: 10px;
margin-right: 15px;
flex-shrink: 0;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
/* 图片缩略图样式 */
.thumbnail {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 8px;
}
/* 非图片文件图标样式 */
.file-icon {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 24px;
}
.file-info {
flex: 1;
min-width: 0;
margin-right: 10px;
}
.file-name {
font-size: 15px;
color: #2d3436;
margin-bottom: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 500;
}
.file-size {
font-size: 12px;
color: #7b8a9b;
}
.file-progress {
width: 100%;
height: 4px;
background: #e9ecef;
border-radius: 2px;
margin-top: 8px;
overflow: hidden;
display: none;
}
.file-progress-bar {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
width: 0%;
transition: width 0.3s ease;
}
.remove-btn {
background: none;
border: none;
color: #ff6b6b;
font-size: 40px;
cursor: pointer;
padding: 8px;
margin-left: 5px;
flex-shrink: 0;
}
.action-buttons {
display: flex;
gap: 12px;
margin-top: 30px;
position: sticky;
bottom: 20px;
z-index: 10;
}
.action-btn {
flex: 1;
padding: 18px 14px;
border: none;
border-radius: 14px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.action-btn:active {
transform: translateY(2px);
}
.upload-btn {
background: linear-gradient(135deg, #4191fe 0%, #a5c6f7 100%);
color: white;
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
.upload-btn:disabled {
background: #d1d8e0;
box-shadow: none;
cursor: not-allowed;
transform: none !important;
}
.clear-btn {
background: #f8f9fa;
color: #666;
border: 1px solid #c8cacb;
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
flex-direction: column;
}
.loading-overlay.show {
display: flex;
}
.spinner {
width: 60px;
height: 60px;
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 20px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.loading-text {
font-size: 16px;
color: #2d3436;
font-weight: 500;
}
/* 状态提示框容器 */
.status-messages-container {
pointer-events: none;
width: 100%;
}
/* 状态提示框 */
.status-message {
background: rgb(238, 238, 238);
border-radius: 16px;
padding: 30px 25px;
text-align: center;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
margin: 0 auto 20px;
display: none;
animation: popUp 0.3s ease;
max-width: 85%;
pointer-events: auto;
position: fixed;
top: 45%;
left: 0;
right: 0;
z-index: 1001;
transform: translateY(-50%);
}
/* 点击关闭按钮 */
.status-message .close-btn {
position: absolute;
top: 10px;
right: 10px;
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: #666;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.status-message .close-btn:hover {
background: rgba(0, 0, 0, 0.1);
}
@keyframes popUp {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.status-icon {
font-size: 60px;
margin-bottom: 20px;
display: block;
}
.status-success .status-icon {
color: #4caf50;
}
.status-error .status-icon {
color: #ff6b6b;
}
.status-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 10px;
color: #2d3436;
}
.status-desc {
font-size: 14px;
color: #7b8a9b;
margin-bottom: 25px;
line-height: 1.5;
}
.status-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 12px;
padding: 16px;
font-size: 16px;
font-weight: 600;
width: 100%;
cursor: pointer;
}
.footer {
text-align: center;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid rgba(170, 170, 170, 0.8);
color: #7d7d7d;
font-size: 16px;
position: relative;
z-index: 1;
}
/* 图片预览模态框 */
.image-preview-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.9);
display: none;
align-items: center;
justify-content: center;
z-index: 1002;
}
.image-preview-modal.show {
display: flex;
}
.preview-image {
max-width: 90%;
max-height: 90%;
object-fit: contain;
border-radius: 8px;
}
.close-preview {
position: absolute;
top: 20px;
right: 20px;
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
font-size: 24px;
width: 44px;
height: 44px;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
@media (max-width: 360px) {
.container {
padding: 15px 15px 30px;
}
.header {
padding: 20px 15px;
}
.main-content {
padding: 20px 15px;
}
.action-btn {
padding: 16px;
font-size: 15px;
}
.file-icon-container {
width: 50px;
height: 50px;
}
.file-icon {
font-size: 20px;
}
}
/* 限制提示样式 */
.limit-info {
background: #fff8e1;
border: 1px solid #ffd54f;
border-radius: 12px;
padding: 12px 16px;
margin-bottom: 20px;
font-size: 14px;
color: #f57c00;
display: none;
align-items: center;
gap: 8px;
animation: slideIn 0.3s ease;
}
.limit-info.show {
display: flex;
}
.limit-icon {
font-size: 18px;
flex-shrink: 0;
}
.limit-text {
flex: 1;
}
/* 限制警告提示 */
.limit-warning-info {
background: #ffebee;
border: 1px solid #ffcdd2;
border-radius: 12px;
padding: 12px 16px;
margin-bottom: 20px;
font-size: 14px;
color: #d32f2f;
display: none;
align-items: center;
gap: 8px;
animation: slideIn 0.3s ease;
}
.limit-warning-info.show {
display: flex;
}
.limit-warning-icon {
font-size: 18px;
flex-shrink: 0;
}
.limit-warning-text {
flex: 1;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1 class="title">文件上传</h1>
<p class="subtitle">选择文件上传到终端</p>
</div>
<div class="main-content">
<!-- 限制提示 -->
<div class="limit-info" id="limitInfo">
<span class="limit-icon">⚠️</span>
<div class="limit-text" id="limitText">加载中...</div>
</div>
<!-- 总数量限制警告 -->
<div class="limit-warning-info" id="limitWarningInfo">
<span class="limit-warning-icon">⚠️</span>
<div class="limit-warning-text" id="limitWarningText">已超过文件上传总数限制</div>
</div>
<div class="upload-section">
<h3 class="section-title">📎 选择文件</h3>
<div class="upload-area" id="uploadArea">
<div class="upload-icon">📁</div>
<div class="upload-text" id="uploadText">点击选择文件</div>
<div class="upload-hint" id="uploadHint">支持图片、文档、文本等格式</div>
<input type="file" id="fileInput" class="file-input" multiple
accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.csv,.ppt,.pptx,.txt,.md" />
</div>
</div>
<div class="selected-files" id="selectedFiles">
<h3 class="section-title">📋 已选文件</h3>
<ul class="file-list" id="fileList"></ul>
</div>
<div class="action-buttons">
<button class="action-btn clear-btn" id="clearBtn" disabled><span>🗑️</span> 清空</button>
<button class="action-btn upload-btn" id="uploadBtn" disabled><span>⬆️</span> 开始上传</button>
</div>
</div>
<div class="footer">
<p>请确保您上传的文件符合相关法律法规</p>
</div>
</div>
<!-- 加载遮罩层 -->
<div class="loading-overlay" id="loadingOverlay">
<div class="spinner"></div>
<div class="loading-text" id="loadingText">上传中,请稍候...</div>
</div>
<!-- 状态提示框容器 -->
<div class="status-messages-container" id="statusMessagesContainer"></div>
<!-- 图片预览模态框 -->
<div class="image-preview-modal" id="imagePreviewModal">
<img class="preview-image" id="previewImage" src="" alt="预览图片" />
<button class="close-preview" id="closePreview">×</button>
</div>
<script type="module">
import {
FileValidator
} from './js/FileValidator.js'; //文件校验JS
// 创建文件校验器实例
const fileValidator = new FileValidator();
// 获取URL中的参数
const urlParams = new URLSearchParams(window.location.search);
const sessionId = urlParams.get('sessionId');
const uploadApi = urlParams.get('uploadApi');
const fileCountParam = urlParams.get('fileCount');
// 配置常量
const MAX_FILE_COUNT = fileCountParam ? parseInt(fileCountParam) : 2; // 从URL参数获取默认为2
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
console.log('Session ID:', sessionId);
console.log('Upload API:', uploadApi);
console.log('Max file count:', MAX_FILE_COUNT);
// DOM元素
const uploadArea = document.getElementById('uploadArea');
const uploadText = document.getElementById('uploadText');
const uploadHint = document.getElementById('uploadHint');
const fileInput = document.getElementById('fileInput');
const fileList = document.getElementById('fileList');
const selectedFilesContainer = document.getElementById('selectedFiles');
const clearBtn = document.getElementById('clearBtn');
const uploadBtn = document.getElementById('uploadBtn');
const loadingOverlay = document.getElementById('loadingOverlay');
const loadingText = document.getElementById('loadingText');
const statusMessagesContainer = document.getElementById('statusMessagesContainer');
const imagePreviewModal = document.getElementById('imagePreviewModal');
const previewImage = document.getElementById('previewImage');
const closePreview = document.getElementById('closePreview');
const limitInfo = document.getElementById('limitInfo');
const limitText = document.getElementById('limitText');
const limitWarningInfo = document.getElementById('limitWarningInfo');
const limitWarningText = document.getElementById('limitWarningText');
// 状态变量
let selectedFiles = [];
let isUploading = false;
let uploadedCount = 0; // 已上传的总文件数量
let statusMessageCount = 0; // 弹窗计数器
// 初始化
function init() {
// 更新限制提示文本
updateLimitText();
// 显示限制提示
limitInfo.classList.add('show');
// 检查本地存储中已上传的文件数量
checkUploadedCount();
// 事件监听
fileInput.addEventListener('change', handleFileSelect);
clearBtn.addEventListener('click', clearAllFiles);
uploadBtn.addEventListener('click', startUpload);
closePreview.addEventListener('click', () => {
imagePreviewModal.classList.remove('show');
document.body.style.overflow = '';
});
// 添加事件委托来处理状态消息的关闭按钮
statusMessagesContainer.addEventListener('click', (e) => {
if (e.target.matches('[data-action="close"]')) {
const messageId = e.target.dataset.messageId;
closeStatusMessage(messageId);
}
});
// 添加事件委托来处理删除按钮
fileList.addEventListener('click', (e) => {
if (e.target.classList.contains('remove-btn')) {
const name = e.target.dataset.name;
const size = parseInt(e.target.dataset.size);
removeFile(name, size);
}
});
// 图片预览模态框点击外部关闭
imagePreviewModal.addEventListener('click', (e) => {
if (e.target === imagePreviewModal) {
imagePreviewModal.classList.remove('show');
document.body.style.overflow = '';
}
});
// 拖拽上传
setupDragAndDrop();
// 防止页面滚动
document.body.addEventListener('touchmove', preventScroll, {
passive: false
});
// 更新UI
updateUI();
}
// 更新限制提示文本
function updateLimitText() {
if (MAX_FILE_COUNT <= 0) {
limitText.textContent = '未设置可上传文件数量';
} else {
limitText.textContent = `总共可上传 ${MAX_FILE_COUNT} 个文件每个文件不超过10MB已上传: ${uploadedCount}`;
}
}
// 检查已上传的文件数量
function checkUploadedCount() {
if (sessionStorage.getItem('sessionId') && sessionStorage.getItem('sessionId') != sessionId) {
sessionStorage.setItem('uploadedFileCount', '0');
}
// // 使用sessionStorage存储当前会话的上传数量
// // 如果要永久存储可以改用localStorage
const storedCount = sessionStorage.getItem('uploadedFileCount');
uploadedCount = storedCount ? parseInt(storedCount) : 0;
// 更新警告提示
updateWarningText();
}
// 更新警告提示
function updateWarningText() {
if (MAX_FILE_COUNT > 0 && uploadedCount >= MAX_FILE_COUNT) {
limitWarningInfo.classList.add('show');
limitWarningText.textContent = `已达文件上传总数限制(${MAX_FILE_COUNT}个)`;
} else {
limitWarningInfo.classList.remove('show');
}
}
// 保存已上传的文件数量
function saveUploadedCount() {
sessionStorage.setItem('uploadedFileCount', uploadedCount.toString());
updateLimitText();
updateWarningText();
}
// 处理文件选择
function handleFileSelect(e) {
if (e.target.files.length) {
handleFiles(e.target.files);
// 重置input允许选择相同文件
fileInput.value = '';
}
}
// 设置拖拽上传
function setupDragAndDrop() {
uploadArea.addEventListener('dragover', (e) => {
// 如果已达总数限制,禁止拖拽
if (MAX_FILE_COUNT > 0 && uploadedCount >= MAX_FILE_COUNT) {
e.preventDefault();
return;
}
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
// 如果已达总数限制,禁止拖拽上传
if (MAX_FILE_COUNT > 0 && uploadedCount >= MAX_FILE_COUNT) {
showError(`已超过文件上传总数限制(${MAX_FILE_COUNT}个)`);
return;
}
if (e.dataTransfer.files.length) {
handleFiles(e.dataTransfer.files);
}
});
}
// 处理文件
async function handleFiles(files) {
// 检查是否已达到总文件数量上限
if (MAX_FILE_COUNT > 0 && uploadedCount >= MAX_FILE_COUNT) {
showError(`已超过文件上传总数限制(${MAX_FILE_COUNT}个)`);
return;
}
// 检查当前选择的文件是否会导致超过总数限制
const potentialTotalCount = uploadedCount + files.length;
if (MAX_FILE_COUNT > 0 && potentialTotalCount > MAX_FILE_COUNT) {
showError(`最多只能上传 ${MAX_FILE_COUNT} 个文件,已上传 ${uploadedCount}`);
return;
}
// 检查是否已达到本次选择的文件数量上限
if (selectedFiles.length >= MAX_FILE_COUNT) {
showError(`最多只能选择 ${MAX_FILE_COUNT} 个文件`);
return;
}
for (let file of files) {
console.log(file);
// 检查是否已达到本次选择的文件数量上限
if (selectedFiles.length >= MAX_FILE_COUNT) {
showError(`最多只能选择 ${MAX_FILE_COUNT} 个文件,已忽略多余文件`);
break;
}
// 检查是否会导致超过总数限制
if (MAX_FILE_COUNT > 0 && uploadedCount + selectedFiles.length >= MAX_FILE_COUNT) {
showError(`已超过文件上传总数限制(${MAX_FILE_COUNT}个)`);
break;
}
// 检查文件大小
if (file.size > MAX_FILE_SIZE) {
showError(`文件 ${file.name} 超过10MB限制`);
continue;
}
// 检查是否已存在
if (selectedFiles.some((f) => f.name === file.name && f.size === file.size)) {
continue;
}
fileValidator
.validate(file)
.then(() => {
console.log(1111);
selectedFiles.push(file);
addFileToList(file);
updateUI();
})
.catch((err) => {
showError(file.name + err);
});
}
}
// 检查是否为图片文件
function isImageFile(filename) {
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp'];
const ext = filename.split('.').pop().toLowerCase();
return imageExtensions.includes(ext);
}
// 检查是否为图片类型
function isImageType(file) {
return file.type.startsWith('image/');
}
// 根据文件名获取图标
function getFileIcon(filename) {
const ext = filename.split('.').pop().toLowerCase();
const icons = {
// 图片
jpg: '🖼️',
jpeg: '🖼️',
png: '🖼️',
gif: '🖼️',
bmp: '🖼️',
svg: '🖼️',
webp: '🖼️',
// 文档
pdf: '📝',
doc: '📝',
docx: '📝',
txt: '📄',
// 表格
xls: '📊',
xlsx: '📊',
csv: '📊',
// 演示文稿
ppt: '📽️',
pptx: '📽️',
// 默认
default: '📁',
};
return icons[ext] || icons.default;
}
// 移除文件
function removeFile(name, size) {
selectedFiles = selectedFiles.filter((f) => !(f.name === name && f.size === size));
const fileItem = document.querySelector(`.file-item[data-name="${name}"]`);
if (fileItem) {
fileItem.style.animation = 'slideIn 0.3s ease reverse';
setTimeout(() => {
fileItem.remove();
updateUI();
}, 150);
}
}
// 添加文件到列表
function addFileToList(file) {
const li = document.createElement('li');
li.className = 'file-item';
li.dataset.name = file.name;
li.dataset.size = file.size; // 添加 size 到 dataset
// 检查是否为图片
const isImage = isImageType(file) && isImageFile(file.name);
// 获取文件图标
const fileIcon = getFileIcon(file.name);
// 创建HTML移除 onclick改为 data 属性)
li.innerHTML = `
<div class="file-icon-container" id="icon-${escapeHtml(file.name)}">
${
isImage
? `<img class="thumbnail" src="#" alt="${escapeHtml(file.name)}" />`
: `<div class="file-icon">${fileIcon}</div>`
}
</div>
<div class="file-info">
<div class="file-name">${escapeHtml(file.name)}</div>
<div class="file-size">${formatFileSize(file.size)}</div>
<div class="file-progress">
<div class="file-progress-bar" id="progress-${escapeHtml(file.name)}"></div>
</div>
</div>
<button class="remove-btn" data-name="${escapeHtml(file.name)}" data-size="${file.size}">×</button>
`;
fileList.appendChild(li);
// 如果是图片,生成缩略图
if (isImage) {
generateThumbnail(file, `icon-${escapeHtml(file.name)}`);
// 为图片缩略图添加点击预览功能
const iconContainer = document.getElementById(`icon-${escapeHtml(file.name)}`);
iconContainer.style.cursor = 'pointer';
iconContainer.addEventListener('click', () => {
previewFullImage(file);
});
}
}
// 生成缩略图
function generateThumbnail(file, containerId) {
const reader = new FileReader();
reader.onload = function(e) {
const container = document.getElementById(containerId);
if (container) {
const img = container.querySelector('.thumbnail');
img.src = e.target.result;
// 添加加载完成后的淡入效果
img.onload = function() {
img.style.opacity = '0';
setTimeout(() => {
img.style.transition = 'opacity 0.3s ease';
img.style.opacity = '1';
}, 10);
};
}
};
reader.readAsDataURL(file);
}
// 预览完整图片
function previewFullImage(file) {
const reader = new FileReader();
reader.onload = function(e) {
previewImage.src = e.target.result;
imagePreviewModal.classList.add('show');
document.body.style.overflow = 'hidden';
};
reader.readAsDataURL(file);
}
// 格式化文件大小
function formatFileSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
// 清空所有文件
function clearAllFiles() {
selectedFiles = [];
fileList.innerHTML = '';
updateUI();
}
// 更新UI状态
function updateUI() {
const hasFiles = selectedFiles.length > 0;
const isTotalLimitReached = MAX_FILE_COUNT > 0 && uploadedCount >= MAX_FILE_COUNT;
// 显示/隐藏文件列表
selectedFilesContainer.classList.toggle('show', hasFiles);
// 检查是否已达总文件数量上限
if (isTotalLimitReached) {
// 禁用所有上传相关功能
uploadArea.classList.add('disabled');
fileInput.disabled = true;
uploadText.textContent = '文件上传总数已达上限';
uploadHint.innerHTML = `总共可上传 ${MAX_FILE_COUNT} 个文件<br />已上传: ${uploadedCount}`;
// 禁用按钮
clearBtn.disabled = true;
uploadBtn.disabled = true;
// 显示限制警告提示
limitWarningInfo.classList.add('show');
limitWarningText.textContent = `已超过文件上传总数限制(${MAX_FILE_COUNT}个)`;
} else {
// 更新上传区域状态
uploadArea.classList.remove('disabled');
// 根据文件数量更新上传区域
if (MAX_FILE_COUNT <= 0) {
// 如果没有设置文件数量限制,始终可以上传
uploadText.textContent = '点击选择文件';
uploadHint.innerHTML = '支持图片、文档、文本等格式<br />文件大小不超过10MB';
fileInput.disabled = false;
} else if (selectedFiles.length >= MAX_FILE_COUNT) {
uploadText.textContent = `已达到最大文件数量(${MAX_FILE_COUNT}个)`;
uploadHint.innerHTML = `最多${MAX_FILE_COUNT}个文件每个不超过10MB<br /><strong>已选满</strong>`;
fileInput.disabled = true;
} else {
uploadText.textContent = '点击选择文件';
uploadHint.innerHTML =
`支持图片、文档、文本等格式<br />最多${MAX_FILE_COUNT}个文件每个不超过10MB<br />已上传: ${uploadedCount}/${MAX_FILE_COUNT}`;
fileInput.disabled = false;
}
// 更新按钮状态
clearBtn.disabled = !hasFiles || isUploading;
uploadBtn.disabled = !hasFiles || isUploading;
}
// 更新上传按钮文本
if (hasFiles && !isTotalLimitReached) {
const totalSize = selectedFiles.reduce((sum, file) => sum + file.size, 0);
uploadBtn.innerHTML = `<span>⬆️</span> 上传 (${
selectedFiles.length
}/${MAX_FILE_COUNT}, ${formatFileSize(totalSize)})`;
} else {
uploadBtn.innerHTML = `<span>⬆️</span> 开始上传`;
}
}
// 开始上传
async function startUpload() {
if (isUploading || selectedFiles.length === 0) return;
// 检查是否已达总数限制
if (MAX_FILE_COUNT > 0 && uploadedCount >= MAX_FILE_COUNT) {
showError(`已超过文件上传总数限制(${MAX_FILE_COUNT}个)`);
return;
}
// 检查是否会导致超过总数限制
const potentialTotalCount = uploadedCount + selectedFiles.length;
if (MAX_FILE_COUNT > 0 && potentialTotalCount > MAX_FILE_COUNT) {
showError(`最多只能上传 ${MAX_FILE_COUNT} 个文件,已上传 ${uploadedCount}`);
return;
}
isUploading = true;
updateUI();
showLoading('正在准备上传...');
try {
// 更新加载文本
loadingText.textContent = `正在上传 ${selectedFiles.length} 个文件...`;
// 遍历上传所有文件
let successCount = 0;
let failCount = 0;
for (let i = 0; i < selectedFiles.length; i++) {
const file = selectedFiles[i];
try {
// 更新进度显示
loadingText.textContent = `正在上传:${file.name} (${i + 1}/${selectedFiles.length})`;
const progressBar = document.querySelector(`#progress-${CSS.escape(file.name)}`);
// 调用上传API
const result = await uploadFile(file, sessionId);
if (result.success) {
successCount++;
// 更新文件进度条
if (progressBar) {
progressBar.style.width = '100%';
}
} else {
failCount++;
if (progressBar) {
progressBar.style.width = '0%';
}
}
} catch (error) {
console.error(`文件上传失败: ${file.name}`, error);
failCount++;
const progressBar = document.querySelector(`#progress-${CSS.escape(file.name)}`);
if (progressBar) {
progressBar.style.width = '0%';
}
}
}
// 上传完成
hideLoading();
if (successCount > 0) {
// 更新已上传的文件数量
uploadedCount += successCount;
saveUploadedCount();
showSuccess(`成功上传 ${successCount} 个文件${failCount > 0 ? `${failCount} 个失败` : ''}`);
// 清空文件列表
clearAllFiles();
// 更新UI
updateUI();
sessionStorage.setItem('sessionId', sessionId);
} else {
showError('文件上传失败,请重试');
}
} catch (error) {
console.error('上传过程中出错:', error);
hideLoading();
showError('上传过程中发生错误');
} finally {
isUploading = false;
updateUI();
}
}
// 上传单个文件
async function uploadFile(file, sessionId) {
const formData = new FormData();
formData.append('file', file);
formData.append('sessionId', sessionId);
formData.append('filename', file.name);
formData.append('size', file.size);
formData.append('type', file.type);
// 显示文件进度条
const progressBar = document.querySelector(`#progress-${CSS.escape(file.name)}`);
if (progressBar) {
progressBar.parentElement.style.display = 'block';
progressBar.style.width = '50%';
}
try {
const response = await fetch(uploadApi, {
method: 'POST',
body: formData,
});
if (progressBar) {
progressBar.style.width = '100%';
}
const result = await response.json();
return {
success: result.code == 200,
data: result,
};
} catch (error) {
if (progressBar) {
progressBar.style.width = '0%';
}
throw error;
}
}
// 显示加载中
function showLoading(text) {
loadingText.textContent = text;
loadingOverlay.classList.add('show');
document.body.style.overflow = 'hidden';
}
// 隐藏加载中
function hideLoading() {
loadingOverlay.classList.remove('show');
document.body.style.overflow = '';
}
// 创建状态提示框
function createStatusMessage(type, title, desc, autoClose = false, closeTime = 3000) {
// 创建唯一的ID
const messageId = 'status-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
// 计算当前弹窗的偏移量每个弹窗下移20px
const offset = statusMessageCount * 40;
// 创建弹窗HTML添加自定义的偏移样式
const messageHtml = `
<div class="status-message status-${type}" id="${messageId}"
data-message-id="${messageId}"
style="margin-top: ${offset}px;">
<button class="close-btn" data-action="close" data-message-id="${messageId}">×</button>
<span class="status-icon">${type === 'success' ? '✅' : '❌'}</span>
<h3 class="status-title">${escapeHtml(title)}</h3>
<p class="status-desc">${escapeHtml(desc)}</p>
<button class="status-btn" data-action="close" data-message-id="${messageId}">完成</button>
</div>
`;
// 添加到容器
statusMessagesContainer.innerHTML += messageHtml;
// 显示弹窗
const messageElement = document.getElementById(messageId);
messageElement.style.display = 'block';
// 增加弹窗计数
statusMessageCount++;
// 自动关闭
if (autoClose) {
setTimeout(() => {
closeStatusMessage(messageId);
}, closeTime);
}
// 限制最大显示数量最多显示5个
const messages = statusMessagesContainer.querySelectorAll('.status-message');
if (messages.length > 5) {
// 移除最早的消息
const oldestMessage = messages[0];
closeStatusMessage(oldestMessage.id);
}
return messageId;
}
// 关闭状态提示框
function closeStatusMessage(messageId) {
const messageElement = document.getElementById(messageId);
if (messageElement) {
// 添加淡出动画
messageElement.style.animation = 'popUp 0.3s ease reverse';
messageElement.style.opacity = '0';
setTimeout(() => {
messageElement.remove();
// 减少弹窗计数
statusMessageCount--;
// 重新计算并更新剩余弹窗的位置
updateRemainingStatusMessages();
// 检查是否还有消息显示
const remainingMessages = statusMessagesContainer.querySelectorAll('.status-message');
if (remainingMessages.length === 0) {
document.body.style.overflow = '';
}
}, 300);
}
}
// 更新剩余弹窗的位置
function updateRemainingStatusMessages() {
const messages = statusMessagesContainer.querySelectorAll('.status-message');
messages.forEach((message, index) => {
const offset = index * 20; // 每个弹窗下移20px
message.style.marginTop = `${offset}px`;
});
}
// 隐藏所有状态提示
function hideAllStatus() {
const messages = statusMessagesContainer.querySelectorAll('.status-message');
messages.forEach((message) => {
closeStatusMessage(message.id);
});
document.body.style.overflow = '';
}
// 显示成功提示(创建新弹窗)
function showSuccess(message, options = {}) {
const {
autoClose = true, closeTime = 3000
} = options;
return createStatusMessage('success', '上传成功!', message, autoClose, closeTime);
}
// 显示错误提示(创建新弹窗)
function showError(message, options = {}) {
const {
autoClose = false, closeTime = 5000
} = options;
return createStatusMessage('error', '出错了', message, autoClose, closeTime);
}
// 修改防止页面滚动函数
function preventScroll(e) {
const messages = statusMessagesContainer.querySelectorAll('.status-message');
const hasMessages = messages.length > 0;
if (
loadingOverlay.classList.contains('show') ||
hasMessages ||
imagePreviewModal.classList.contains('show')
) {
e.preventDefault();
}
}
// HTML转义
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', init);
// 阻止默认拖放行为
document.addEventListener('dragover', (e) => e.preventDefault());
document.addEventListener('drop', (e) => e.preventDefault());
</script>
</body>
</html>