This commit is contained in:
@@ -352,11 +352,253 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- File Preview Modal -->
|
||||
<div id="filePreviewModal" class="modal fixed inset-0 z-[70] flex items-center justify-center p-4" style="background-color: rgba(0,0,0,0.7);">
|
||||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-5xl max-h-[95vh] flex flex-col relative animate-fade-in overflow-hidden">
|
||||
<button class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 z-10 p-2 rounded-full hover:bg-gray-100 transition-colors" onclick="closeModal('filePreviewModal')">
|
||||
<i class="fas fa-times text-xl"></i>
|
||||
</button>
|
||||
|
||||
<div class="px-6 py-4 border-b border-gray-100 bg-gray-50 flex justify-between items-center">
|
||||
<h2 id="filePreviewTitle" class="text-lg font-bold text-gray-900 flex items-center truncate">
|
||||
<i id="filePreviewIcon" class="fas fa-file-pdf text-red-500 mr-2"></i>
|
||||
<span id="filePreviewName">文件预览</span>
|
||||
</h2>
|
||||
<div class="flex items-center gap-2 ml-4">
|
||||
<a id="fileDownloadBtn" href="#" download class="inline-flex items-center px-3 py-1.5 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors">
|
||||
<i class="fas fa-download mr-1.5"></i>下载
|
||||
</a>
|
||||
<button onclick="openFileNewTab()" class="inline-flex items-center px-3 py-1.5 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors">
|
||||
<i class="fas fa-external-link-alt mr-1.5"></i>新窗口打开
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-hidden bg-gray-100 relative">
|
||||
<iframe id="filePreviewFrame" class="w-full h-full min-h-[60vh]" frameborder="0"></iframe>
|
||||
<div id="imagePreviewContainer" class="hidden w-full h-full overflow-auto flex items-center justify-center p-4">
|
||||
<img id="imagePreviewImg" class="max-w-full max-h-full object-contain rounded shadow-lg" src="" alt="图片预览">
|
||||
</div>
|
||||
<div id="filePreviewLoading" class="absolute inset-0 flex items-center justify-center bg-gray-100">
|
||||
<div class="text-center">
|
||||
<i class="fas fa-spinner fa-spin text-4xl text-blue-500 mb-3"></i>
|
||||
<p class="text-gray-500">正在加载文件...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="filePreviewError" class="hidden absolute inset-0 flex items-center justify-center bg-gray-100">
|
||||
<div class="text-center">
|
||||
<i class="fas fa-exclamation-circle text-4xl text-red-500 mb-3"></i>
|
||||
<p class="text-gray-600 mb-3">文件加载失败</p>
|
||||
<p class="text-sm text-gray-400">请尝试下载文件或在新窗口中打开</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-3 bg-white border-t border-gray-200 text-xs text-gray-500 flex justify-between">
|
||||
<span>如文件无法预览,请直接下载或在浏览器中打开</span>
|
||||
<span id="filePreviewHint"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script src="https://cdn.staticfile.net/marked/11.1.1/marked.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
|
||||
<script>
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
|
||||
</script>
|
||||
<script>
|
||||
let currentPreviewFile = { url: '', name: '', type: '' };
|
||||
|
||||
function previewFile(url, name, type) {
|
||||
currentPreviewFile = { url: decodeURIComponent(url), name: decodeURIComponent(name), type: type };
|
||||
|
||||
const modal = document.getElementById('filePreviewModal');
|
||||
const titleEl = document.getElementById('filePreviewTitle');
|
||||
const iconEl = document.getElementById('filePreviewIcon');
|
||||
const nameEl = document.getElementById('filePreviewName');
|
||||
const downloadBtn = document.getElementById('fileDownloadBtn');
|
||||
const frame = document.getElementById('filePreviewFrame');
|
||||
const imageContainer = document.getElementById('imagePreviewContainer');
|
||||
const imageImg = document.getElementById('imagePreviewImg');
|
||||
const loading = document.getElementById('filePreviewLoading');
|
||||
const error = document.getElementById('filePreviewError');
|
||||
const hint = document.getElementById('filePreviewHint');
|
||||
|
||||
frame.style.display = 'none';
|
||||
imageContainer.classList.add('hidden');
|
||||
loading.classList.remove('hidden');
|
||||
error.classList.add('hidden');
|
||||
|
||||
const fileUrl = decodeURIComponent(url);
|
||||
const fileName = decodeURIComponent(name);
|
||||
const fileType = type || '';
|
||||
|
||||
titleEl.className = 'text-lg font-bold text-gray-900 flex items-center truncate';
|
||||
nameEl.textContent = fileName;
|
||||
downloadBtn.href = fileUrl;
|
||||
downloadBtn.download = fileName;
|
||||
|
||||
loading.classList.remove('hidden');
|
||||
error.classList.add('hidden');
|
||||
frame.style.display = 'block';
|
||||
|
||||
let iconClass = 'fas fa-file text-gray-500';
|
||||
let previewUrl = fileUrl;
|
||||
let hintText = '';
|
||||
|
||||
if (fileType === 'pdf') {
|
||||
iconClass = 'fas fa-file-pdf text-red-500';
|
||||
previewUrl = fileUrl;
|
||||
hintText = '提示:PDF文件使用本地PDF.js预览器加载';
|
||||
} else if (fileType === 'ppt' || fileType === 'pptx') {
|
||||
iconClass = 'fas fa-file-powerpoint text-orange-500';
|
||||
previewUrl = '';
|
||||
hintText = '提示:PPT文件需要下载后查看,或使用WPS/Office打开';
|
||||
} else if (fileType === 'image') {
|
||||
iconClass = 'fas fa-image text-green-500';
|
||||
previewUrl = 'image';
|
||||
hintText = '提示:点击图片可查看大图';
|
||||
} else if (fileType === 'doc' || fileType === 'docx') {
|
||||
iconClass = 'fas fa-file-word text-blue-500';
|
||||
previewUrl = '';
|
||||
hintText = '提示:Word文档需要下载后查看,或使用WPS/Word打开';
|
||||
} else {
|
||||
previewUrl = fileUrl;
|
||||
hintText = '此文件类型不支持在线预览,请下载后查看';
|
||||
}
|
||||
|
||||
iconEl.className = iconClass + ' mr-2';
|
||||
hint.textContent = hintText;
|
||||
|
||||
if (!previewUrl) {
|
||||
loading.classList.add('hidden');
|
||||
error.classList.remove('hidden');
|
||||
frame.style.display = 'none';
|
||||
document.querySelector('#filePreviewError p:first-of-type').textContent = '此文件类型暂不支持在线预览';
|
||||
document.querySelector('#filePreviewError p:last-of-type').textContent = '请点击上方"下载"按钮保存文件后查看';
|
||||
modal.classList.add('active');
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileType === 'pdf') {
|
||||
loading.innerHTML = '<div class="text-center"><i class="fas fa-spinner fa-spin text-4xl text-blue-500 mb-3"></i><p class="text-gray-500">正在加载PDF文件...</p></div>';
|
||||
renderPdfPreview(fileUrl, frame, loading, error);
|
||||
} else if (fileType === 'image') {
|
||||
imageImg.src = fileUrl;
|
||||
imageImg.onload = () => {
|
||||
loading.classList.add('hidden');
|
||||
imageContainer.classList.remove('hidden');
|
||||
};
|
||||
imageImg.onerror = () => {
|
||||
loading.classList.add('hidden');
|
||||
error.classList.remove('hidden');
|
||||
error.querySelector('p:first-of-type').textContent = '图片加载失败';
|
||||
error.querySelector('p:last-of-type').textContent = '请点击"下载"按钮保存图片后查看';
|
||||
};
|
||||
} else {
|
||||
frame.style.display = 'block';
|
||||
frame.onload = function() {
|
||||
loading.classList.add('hidden');
|
||||
};
|
||||
frame.onerror = function() {
|
||||
loading.classList.add('hidden');
|
||||
};
|
||||
frame.src = previewUrl;
|
||||
}
|
||||
|
||||
modal.classList.add('active');
|
||||
}
|
||||
|
||||
async function renderPdfPreview(url, frameEl, loadingEl, errorEl) {
|
||||
try {
|
||||
const loadingText = loadingEl.querySelector('p');
|
||||
|
||||
let arrayBuffer;
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error('无法加载PDF文件');
|
||||
arrayBuffer = await response.arrayBuffer();
|
||||
} catch (fetchErr) {
|
||||
console.warn('PDF fetch failed, trying iframe method:', fetchErr);
|
||||
frameEl.style.display = 'block';
|
||||
frameEl.src = url;
|
||||
loadingEl.classList.add('hidden');
|
||||
frameEl.onload = () => loadingEl.classList.add('hidden');
|
||||
frameEl.onerror = () => {
|
||||
loadingEl.classList.add('hidden');
|
||||
showPdfError(errorEl, 'PDF文件加载失败,请下载后查看');
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.style.cssText = 'width:100%;height:100%;overflow:auto;background:#525659;padding:20px;';
|
||||
|
||||
const scale = 1.2;
|
||||
const pageContainer = document.createElement('div');
|
||||
pageContainer.style.cssText = 'background:white;margin:0 auto;box-shadow:0 2px 10px rgba(0,0,0,0.3);width:' + (595 * scale) + 'px;';
|
||||
|
||||
const pageNum = Math.min(pdf.numPages, 20);
|
||||
|
||||
for (let i = 1; i <= pageNum; i++) {
|
||||
const page = await pdf.getPage(i);
|
||||
const viewport = page.getViewport({ scale: scale });
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.style.cssText = 'display:block;margin:0 auto;width:100%;';
|
||||
const context = canvas.getContext('2d');
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
|
||||
await page.render({ canvasContext: context, viewport: viewport }).promise;
|
||||
pageContainer.appendChild(canvas);
|
||||
}
|
||||
|
||||
if (pdf.numPages > 20) {
|
||||
const moreInfo = document.createElement('div');
|
||||
moreInfo.style.cssText = 'text-align:center;padding:20px;color:#999;background:white;margin-top:10px;';
|
||||
moreInfo.textContent = `... 还有 ${pdf.numPages - 20} 页未显示,请下载完整查看`;
|
||||
pageContainer.appendChild(moreInfo);
|
||||
}
|
||||
|
||||
container.appendChild(pageContainer);
|
||||
frameEl.style.display = 'none';
|
||||
loadingEl.classList.add('hidden');
|
||||
|
||||
const parent = frameEl.parentElement;
|
||||
const oldContainer = parent.querySelector('#pdfRenderContainer');
|
||||
if (oldContainer) oldContainer.remove();
|
||||
|
||||
parent.insertBefore(container, frameEl);
|
||||
container.id = 'pdfRenderContainer';
|
||||
|
||||
} catch (err) {
|
||||
console.error('PDF加载失败:', err);
|
||||
loadingEl.classList.add('hidden');
|
||||
frameEl.style.display = 'block';
|
||||
frameEl.src = url;
|
||||
frameEl.onload = () => loadingEl.classList.add('hidden');
|
||||
frameEl.onerror = () => {
|
||||
loadingEl.classList.add('hidden');
|
||||
showPdfError(errorEl, 'PDF文件无法预览,请下载后查看');
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function showPdfError(errorEl, message) {
|
||||
errorEl.classList.remove('hidden');
|
||||
errorEl.querySelector('p:first-of-type').textContent = message;
|
||||
errorEl.querySelector('p:last-of-type').textContent = '请点击"下载"按钮保存文件后查看';
|
||||
}
|
||||
|
||||
function openFileNewTab() {
|
||||
window.open(currentPreviewFile.url, '_blank');
|
||||
}
|
||||
/**
|
||||
* 更新单个维度分数显示并计算总分
|
||||
*/
|
||||
@@ -563,25 +805,49 @@ async function viewProject(id) {
|
||||
filesList.innerHTML = data.files.map(file => {
|
||||
let iconClass = 'fas fa-file';
|
||||
let iconColor = 'text-gray-500';
|
||||
if (file.file_type === 'ppt' || file.file_type === 'pptx') {
|
||||
let canPreview = false;
|
||||
let fileType = file.file_type || '';
|
||||
|
||||
if (fileType === 'ppt' || fileType === 'pptx') {
|
||||
iconClass = 'fas fa-file-powerpoint';
|
||||
iconColor = 'text-orange-500';
|
||||
} else if (file.file_type === 'pdf') {
|
||||
canPreview = true;
|
||||
} else if (fileType === 'pdf') {
|
||||
iconClass = 'fas fa-file-pdf';
|
||||
iconColor = 'text-red-500';
|
||||
} else if (file.file_type === 'video') {
|
||||
canPreview = true;
|
||||
} else if (fileType === 'video') {
|
||||
iconClass = 'fas fa-video';
|
||||
iconColor = 'text-purple-500';
|
||||
} else if (file.file_type === 'image') {
|
||||
} else if (fileType === 'image') {
|
||||
iconClass = 'fas fa-image';
|
||||
iconColor = 'text-green-500';
|
||||
canPreview = true;
|
||||
} else if (fileType === 'doc' || fileType === 'docx') {
|
||||
iconClass = 'fas fa-file-word';
|
||||
iconColor = 'text-blue-500';
|
||||
canPreview = true;
|
||||
}
|
||||
|
||||
const fileName = file.name || '未命名文件';
|
||||
const previewBtn = canPreview ? `
|
||||
<button onclick="previewFile('${encodeURIComponent(file.file_url)}', '${encodeURIComponent(fileName)}', '${fileType}')"
|
||||
class="ml-2 px-2 py-1 text-xs font-medium text-blue-600 bg-blue-50 rounded hover:bg-blue-100 transition-colors">
|
||||
<i class="fas fa-eye mr-1"></i>预览
|
||||
</button>
|
||||
` : '';
|
||||
|
||||
return `
|
||||
<a href="${file.file_url}" target="_blank" class="flex items-center p-3 bg-white rounded-lg border border-gray-200 hover:bg-gray-50 hover:border-blue-300 transition-colors group">
|
||||
<div class="flex items-center p-3 bg-white rounded-lg border border-gray-200 hover:bg-gray-50 hover:border-blue-300 transition-colors group">
|
||||
<i class="${iconClass} ${iconColor} text-xl mr-3 group-hover:scale-110 transition-transform"></i>
|
||||
<span class="flex-1 text-sm font-medium text-gray-700 truncate">${file.name || '未命名文件'}</span>
|
||||
<i class="fas fa-external-link-alt text-gray-400 group-hover:text-blue-500"></i>
|
||||
</a>
|
||||
<div class="flex-1 min-w-0">
|
||||
<span class="text-sm font-medium text-gray-700 block truncate">${fileName}</span>
|
||||
</div>
|
||||
<a href="${file.file_url}" download="${fileName}" class="ml-2 p-2 text-gray-400 hover:text-blue-500 transition-colors" title="下载">
|
||||
<i class="fas fa-download"></i>
|
||||
</a>
|
||||
${previewBtn}
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user