Files
qingdao-employment-service/static/upload.html
2025-12-17 18:10:06 +08:00

1035 lines
27 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;
}
.code-card {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
border-radius: 16px;
padding: 20px;
margin-bottom: 25px;
text-align: center;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.code-label {
font-size: 14px;
color: #666;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.code-value {
font-size: 22px;
color: #2d3436;
font-weight: 700;
letter-spacing: 2px;
padding: 12px;
background: white;
border-radius: 12px;
margin-top: 8px;
word-break: break-all;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.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-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;
}
.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-message {
position: fixed;
top: 50%;
left: 20px;
right: 20px;
transform: translateY(-50%);
background: rgb(238, 238, 238);
border-radius: 16px;
padding: 30px 25px;
text-align: center;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
z-index: 1001;
display: none;
animation: popUp 0.3s ease;
}
@keyframes popUp {
from {
opacity: 0;
transform: translateY(-40%) scale(0.9);
}
to {
opacity: 1;
transform: translateY(-50%) scale(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;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1 class="title">文件上传</h1>
<p class="subtitle">选择文件上传到终端</p>
</div>
<div class="main-content">
<div class="upload-section">
<h3 class="section-title">📎 选择文件</h3>
<div class="upload-area" id="uploadArea">
<div class="upload-icon">📁</div>
<div class="upload-text">点击选择文件</div>
<div class="upload-hint">支持图片、文档、文本等格式<br />单个文件不超过10MB</div>
<input
type="file"
id="fileInput"
class="file-input"
multiple
accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt"
/>
</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-message" id="statusMessage">
<span class="status-icon"></span>
<h3 class="status-title" id="statusTitle">上传成功!</h3>
<p class="status-desc" id="statusDesc">文件已成功上传到电脑端</p>
<button class="status-btn" id="statusBtn">完成</button>
</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>
// 获取URL中的code参数
const urlParams = new URLSearchParams(window.location.search);
const sessionId = urlParams.get('sessionId');
const uploadApi = urlParams.get('uploadApi');
console.log(sessionId);
console.log(uploadApi);
// DOM元素
const uploadArea = document.getElementById('uploadArea');
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 statusMessage = document.getElementById('statusMessage');
const statusTitle = document.getElementById('statusTitle');
const statusDesc = document.getElementById('statusDesc');
const statusBtn = document.getElementById('statusBtn');
const imagePreviewModal = document.getElementById('imagePreviewModal');
const previewImage = document.getElementById('previewImage');
const closePreview = document.getElementById('closePreview');
// 状态变量
let selectedFiles = [];
let isUploading = false;
// 初始化
function init() {
// 事件监听
fileInput.addEventListener('change', handleFileSelect);
clearBtn.addEventListener('click', clearAllFiles);
uploadBtn.addEventListener('click', startUpload);
statusBtn.addEventListener('click', hideStatus);
closePreview.addEventListener('click', () => {
imagePreviewModal.classList.remove('show');
document.body.style.overflow = '';
});
// 图片预览模态框点击外部关闭
imagePreviewModal.addEventListener('click', (e) => {
if (e.target === imagePreviewModal) {
imagePreviewModal.classList.remove('show');
document.body.style.overflow = '';
}
});
// 拖拽上传
setupDragAndDrop();
// 防止页面滚动
document.body.addEventListener('touchmove', preventScroll, { passive: false });
}
// 处理文件选择
function handleFileSelect(e) {
if (e.target.files.length) {
handleFiles(e.target.files);
// 重置input允许选择相同文件
fileInput.value = '';
}
}
// 设置拖拽上传
function setupDragAndDrop() {
uploadArea.addEventListener('dragover', (e) => {
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 (e.dataTransfer.files.length) {
handleFiles(e.dataTransfer.files);
}
});
}
// 处理文件
function handleFiles(files) {
const maxSize = 20 * 1024 * 1024; // 20MB
for (let file of files) {
// 检查文件大小
if (file.size > maxSize) {
showError(`文件 ${file.name} 超过20MB限制`);
continue;
}
// 检查是否已存在
if (selectedFiles.some((f) => f.name === file.name && f.size === file.size)) {
continue;
}
// 添加到文件列表
selectedFiles.push(file);
addFileToList(file);
}
updateUI();
}
// 检查是否为图片文件
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 addFileToList(file) {
const li = document.createElement('li');
li.className = 'file-item';
li.dataset.name = file.name;
// 检查是否为图片
const isImage = isImageType(file) && isImageFile(file.name);
// 获取文件图标
const fileIcon = getFileIcon(file.name);
// 创建HTML
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" onclick="removeFile('${escapeHtml(file.name)}', ${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 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 clearAllFiles() {
selectedFiles = [];
fileList.innerHTML = '';
updateUI();
}
// 更新UI状态
function updateUI() {
const hasFiles = selectedFiles.length > 0;
// 显示/隐藏文件列表
selectedFilesContainer.classList.toggle('show', hasFiles);
// 更新按钮状态
clearBtn.disabled = !hasFiles || isUploading;
uploadBtn.disabled = !hasFiles || isUploading;
// 更新上传按钮文本
if (hasFiles) {
const totalSize = selectedFiles.reduce((sum, file) => sum + file.size, 0);
uploadBtn.innerHTML = `<span>⬆️</span> 上传 (${selectedFiles.length}个, ${formatFileSize(
totalSize
)})`;
} else {
uploadBtn.innerHTML = `<span>⬆️</span> 开始上传`;
}
}
// 开始上传
async function startUpload() {
if (isUploading || selectedFiles.length === 0) 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++;
if (progressBar) {
progressBar.style.width = '0%';
}
}
}
// 上传完成
hideLoading();
if (successCount > 0) {
showSuccess(`成功上传 ${successCount} 个文件${failCount > 0 ? `${failCount} 个失败` : ''}`);
// 清空文件列表
clearAllFiles();
} 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 showSuccess(message) {
statusMessage.className = 'status-message status-success';
statusTitle.textContent = '上传成功!';
statusDesc.textContent = message;
statusMessage.style.display = 'block';
document.body.style.overflow = 'hidden';
}
// 显示错误提示
function showError(message) {
statusMessage.className = 'status-message status-error';
statusTitle.textContent = '出错了';
statusDesc.textContent = message;
statusMessage.style.display = 'block';
document.body.style.overflow = 'hidden';
}
// 隐藏状态提示
function hideStatus() {
statusMessage.style.display = 'none';
document.body.style.overflow = '';
}
// 防止页面滚动
function preventScroll(e) {
if (
loadingOverlay.classList.contains('show') ||
statusMessage.style.display === 'block' ||
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>