|
|
/** * 文件预览工具类 * 支持图片、视频、音频、PDF、TXT、Excel、Word、PPT等格式的预览 */import XLSX from 'xlsx'
// 文件类型分类
const FILE_TYPES = { image: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico'], video: ['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv', 'ogg'], audio: ['mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a', 'wma'], pdf: ['pdf'], txt: ['txt', 'log', 'json', 'xml', 'csv', 'html', 'htm', 'css', 'js'], excel: ['xls', 'xlsx'], word: ['doc', 'docx'], ppt: ['ppt', 'pptx']}
/** * 获取文件后缀 */export function getFileSuffix(fileName) { if (!fileName || !fileName.includes('.')) return '' return fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase()}
/** * 获取文件类型 */export function getFileType(fileName) { const suffix = getFileSuffix(fileName) for (const [type, extensions] of Object.entries(FILE_TYPES)) { if (extensions.includes(suffix)) return type } return 'unknown'}
/** * 获取MIME类型 */export function getMimeType(suffix) { const mimeMap = { // 图片
jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', gif: 'image/gif', bmp: 'image/bmp', webp: 'image/webp', svg: 'image/svg+xml', ico: 'image/x-icon', // 视频
mp4: 'video/mp4', avi: 'video/x-msvideo', mov: 'video/quicktime', wmv: 'video/x-ms-wmv', flv: 'video/x-flv', webm: 'video/webm', mkv: 'video/x-matroska', ogg: 'video/ogg', // 音频
mp3: 'audio/mpeg', wav: 'audio/wav', flac: 'audio/flac', aac: 'audio/aac', m4a: 'audio/mp4', wma: 'audio/x-ms-wma', // 文档
pdf: 'application/pdf', txt: 'text/plain', log: 'text/plain', json: 'application/json', xml: 'application/xml', csv: 'text/csv', html: 'text/html', htm: 'text/html', css: 'text/css', js: 'application/javascript', // Office
xls: 'application/vnd.ms-excel', xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', doc: 'application/msword', docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', ppt: 'application/vnd.ms-powerpoint', pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' } return mimeMap[suffix] || 'application/octet-stream'}
/** * 将Blob转换为ArrayBuffer * @param {Blob} blob - Blob对象 * @returns {Promise<ArrayBuffer>} */export function blobToArrayBuffer(blob) { return new Promise((resolve, reject) => { const reader = new FileReader() reader.onload = () => resolve(reader.result) reader.onerror = () => reject(new Error('Blob转换失败')) reader.readAsArrayBuffer(blob) })}
/** * 预览图片文件 * @param {Blob} blobData - 文件数据 * @param {String} fileName - 文件名 */export function previewImage(blobData, fileName) { try { const fileURL = URL.createObjectURL(blobData) const previewWindow = window.open('', '_blank') if (!previewWindow) { // 如果弹窗被阻止,尝试直接打开
window.open(fileURL, '_blank') return true } previewWindow.document.write(`
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>图片预览 - ${fileName}</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: Arial, sans-serif; background: #1a1a1a; min-height: 100vh; display: flex; flex-direction: column; } .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px 20px; display: flex; justify-content: space-between; align-items: center; } .header h2 { font-size: 16px; font-weight: 500; } .toolbar { display: flex; gap: 10px; } .toolbar button { background: rgba(255,255,255,0.2); border: none; color: white; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 12px; } .toolbar button:hover { background: rgba(255,255,255,0.3); } .image-container { flex: 1; display: flex; justify-content: center; align-items: center; padding: 20px; overflow: auto; } .image-container img { max-width: 100%; max-height: calc(100vh - 80px); object-fit: contain; box-shadow: 0 4px 20px rgba(0,0,0,0.5); transition: transform 0.3s; } </style> </head> <body> <div class="header"> <h2>📷 ${fileName}</h2> <div class="toolbar"> <button onclick="zoomIn()">放大 +</button> <button onclick="zoomOut()">缩小 -</button> <button onclick="resetZoom()">重置</button> <button onclick="downloadImage()">下载</button> </div> </div> <div class="image-container"> <img id="previewImg" src="${fileURL}" alt="${fileName}"> </div> <script> var scale = 1; var img = document.getElementById('previewImg'); function zoomIn() { scale = Math.min(scale + 0.25, 5); img.style.transform = 'scale(' + scale + ')'; } function zoomOut() { scale = Math.max(scale - 0.25, 0.25); img.style.transform = 'scale(' + scale + ')'; } function resetZoom() { scale = 1; img.style.transform = 'scale(1)'; } function downloadImage() { var a = document.createElement('a'); a.href = '${fileURL}'; a.download = '${fileName}'; a.click(); } <\/script> </body> </html> `)
previewWindow.document.close() return true } catch (error) { console.error('图片预览失败:', error) return false }}
/** * 预览视频文件 * @param {Blob} blobData - 文件数据 * @param {String} fileName - 文件名 * @param {String} mimeType - MIME类型 */export function previewVideo(blobData, fileName, mimeType) { try { const fileURL = URL.createObjectURL(blobData) const previewWindow = window.open('', '_blank') if (!previewWindow) { window.open(fileURL, '_blank') return true } previewWindow.document.write(`
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>视频播放 - ${fileName}</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: Arial, sans-serif; background: #1a1a1a; min-height: 100vh; display: flex; flex-direction: column; } .header { background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); color: white; padding: 12px 20px; } .header h2 { font-size: 16px; font-weight: 500; } .video-container { flex: 1; display: flex; justify-content: center; align-items: center; padding: 20px; } video { max-width: 100%; max-height: calc(100vh - 80px); box-shadow: 0 4px 20px rgba(0,0,0,0.5); } </style> </head> <body> <div class="header"> <h2>🎬 ${fileName}</h2> </div> <div class="video-container"> <video controls autoplay> <source src="${fileURL}" type="${mimeType}"> 您的浏览器不支持视频播放 </video> </div> </body> </html> `)
previewWindow.document.close() return true } catch (error) { console.error('视频预览失败:', error) return false }}
/** * 预览音频文件 * @param {Blob} blobData - 文件数据 * @param {String} fileName - 文件名 * @param {String} mimeType - MIME类型 */export function previewAudio(blobData, fileName, mimeType) { try { const fileURL = URL.createObjectURL(blobData) const previewWindow = window.open('', '_blank') if (!previewWindow) { window.open(fileURL, '_blank') return true } previewWindow.document.write(`
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>音频播放 - ${fileName}</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: Arial, sans-serif; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); min-height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; } .audio-card { background: rgba(255,255,255,0.1); backdrop-filter: blur(10px); border-radius: 16px; padding: 40px; text-align: center; box-shadow: 0 8px 32px rgba(0,0,0,0.3); } .icon { font-size: 64px; margin-bottom: 20px; } .title { color: white; font-size: 18px; margin-bottom: 30px; word-break: break-all; max-width: 400px; } audio { width: 350px; } </style> </head> <body> <div class="audio-card"> <div class="icon">🎵</div> <div class="title">${fileName}</div> <audio controls autoplay> <source src="${fileURL}" type="${mimeType}"> 您的浏览器不支持音频播放 </audio> </div> </body> </html> `)
previewWindow.document.close() return true } catch (error) { console.error('音频预览失败:', error) return false }}
/** * 预览PDF文件 * @param {Blob} blobData - 文件数据 * @param {String} fileName - 文件名 */export function previewPDF(blobData, fileName) { try { const fileURL = URL.createObjectURL(blobData) const previewWindow = window.open('', '_blank') if (!previewWindow) { window.open(fileURL, '_blank') return true } previewWindow.document.write(`
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>PDF预览 - ${fileName}</title> <style> * { margin: 0; padding: 0; } body { height: 100vh; } iframe { width: 100%; height: 100%; border: none; } </style> </head> <body> <iframe src="${fileURL}"></iframe> </body> </html> `)
previewWindow.document.close() return true } catch (error) { console.error('PDF预览失败:', error) return false }}
/** * 预览文本文件 * @param {Blob} blobData - 文件数据 * @param {String} fileName - 文件名 */export async function previewText(blobData, fileName) { try { const text = await blobData.text() const previewWindow = window.open('', '_blank') if (!previewWindow) { throw new Error('无法打开预览窗口') } // 转义HTML特殊字符
const escapedText = text .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') previewWindow.document.write(`
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>文本预览 - ${fileName}</title> <style> body { font-family: 'Consolas', 'Monaco', monospace; padding: 20px; background: #1e1e1e; color: #d4d4d4; line-height: 1.6; } .header { background: #333; margin: -20px -20px 20px -20px; padding: 12px 20px; color: #fff; } pre { white-space: pre-wrap; word-wrap: break-word; } </style> </head> <body> <div class="header">📄 ${fileName}</div> <pre>${escapedText}</pre> </body> </html> `)
previewWindow.document.close() return true } catch (error) { console.error('文本预览失败:', error) return false }}
/** * 预览Excel文件 * @param {ArrayBuffer} arrayBuffer - 文件数据(ArrayBuffer格式) * @param {String} fileName - 文件名 */export function previewExcel(arrayBuffer, fileName) { try { const workbook = XLSX.read(arrayBuffer, { type: 'array' }) const sheetName = workbook.SheetNames[0] const worksheet = workbook.Sheets[sheetName] const htmlString = XLSX.utils.sheet_to_html(worksheet, { editable: false }) // 预先生成所有sheet的HTML
const sheetsHtml = workbook.SheetNames.map(name => { return XLSX.utils.sheet_to_html(workbook.Sheets[name], { editable: false }) }) // 创建预览窗口
const previewWindow = window.open('', '_blank') if (!previewWindow) { throw new Error('无法打开预览窗口,请检查浏览器是否阻止了弹出窗口') } previewWindow.document.write(`
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Excel预览 - ${fileName}</title> <style> body { font-family: Arial, sans-serif; padding: 20px; background: #f5f5f5; } .header { background: #409EFF; color: white; padding: 15px 20px; margin: -20px -20px 20px -20px; } .header h2 { margin: 0; font-size: 18px; } .sheet-tabs { background: #fff; padding: 10px; margin-bottom: 10px; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .sheet-tab { display: inline-block; padding: 8px 16px; margin-right: 5px; background: #f0f0f0; border-radius: 4px; cursor: pointer; } .sheet-tab.active { background: #409EFF; color: white; } .table-container { background: white; padding: 15px; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); overflow: auto; max-height: calc(100vh - 180px); } table { border-collapse: collapse; width: 100%; } th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; white-space: nowrap; } th { background: #f5f7fa; font-weight: 600; position: sticky; top: 0; } tr:nth-child(even) { background: #fafafa; } tr:hover { background: #f0f7ff; } </style> </head> <body> <div class="header"> <h2>Excel预览 - ${fileName}</h2> </div> <div class="sheet-tabs"> ${workbook.SheetNames.map((name, index) => `<span class="sheet-tab ${index === 0 ? 'active' : ''}" onclick="showSheet(${index})">${name}</span>` ).join('')} </div> <div class="table-container" id="tableContainer"> ${htmlString} </div> <script> var sheets = ${JSON.stringify(sheetsHtml)}; function showSheet(index) { document.getElementById('tableContainer').innerHTML = sheets[index]; var tabs = document.querySelectorAll('.sheet-tab'); tabs.forEach(function(tab, i) { tab.className = 'sheet-tab' + (i === index ? ' active' : ''); }); } <\/script> </body> </html> `)
previewWindow.document.close() return true } catch (error) { console.error('Excel预览失败:', error) return false }}
/** * 预览Word文件(下载后打开) * @param {Blob} blobData - 文件数据(Blob格式) * @param {String} fileName - 文件名 */export function previewWord(blobData, fileName) { // 对于Word文件,直接下载让用户用本地软件打开
const url = URL.createObjectURL(blobData) // 创建下载链接
const link = document.createElement('a') link.href = url link.download = fileName link.click() URL.revokeObjectURL(url) return true}
/** * 预览PPT文件(下载后打开) * @param {Blob} blobData - 文件数据(Blob格式) * @param {String} fileName - 文件名 */export function previewPPT(blobData, fileName) { // 对于PPT文件,直接下载让用户用本地软件打开
const url = URL.createObjectURL(blobData) // 创建下载链接
const link = document.createElement('a') link.href = url link.download = fileName link.click() URL.revokeObjectURL(url) return true}
/** * 通用文件预览方法 * @param {ArrayBuffer|Blob} data - 文件数据(可能是Blob或ArrayBuffer) * @param {String} fileName - 文件名 * @returns {Promise} 预览结果 */export async function previewFile(data, fileName) { const suffix = getFileSuffix(fileName) const fileType = getFileType(fileName) const mimeType = getMimeType(suffix) try { // 确保data是正确的格式
let blobData = data let arrayBufferData = null // 如果data是Blob,保存引用;如果不是,创建Blob
if (!(data instanceof Blob)) { blobData = new Blob([data], { type: mimeType }) } switch (fileType) { case 'image': // 图片预览 - 在新窗口中显示带工具栏的预览
if (previewImage(blobData, fileName)) { return { success: true, type: 'image' } } else { throw new Error('图片预览失败') } case 'video': // 视频预览 - 在新窗口中播放
if (previewVideo(blobData, fileName, mimeType)) { return { success: true, type: 'video' } } else { throw new Error('视频预览失败') } case 'audio': // 音频预览 - 在新窗口中播放
if (previewAudio(blobData, fileName, mimeType)) { return { success: true, type: 'audio' } } else { throw new Error('音频预览失败') } case 'pdf': // PDF预览
if (previewPDF(blobData, fileName)) { return { success: true, type: 'pdf' } } else { throw new Error('PDF预览失败') } case 'txt': // 文本文件预览
if (await previewText(blobData, fileName)) { return { success: true, type: 'txt' } } else { throw new Error('文本预览失败') } case 'excel': // Excel文件需要ArrayBuffer格式
arrayBufferData = await blobToArrayBuffer(blobData) if (previewExcel(arrayBufferData, fileName)) { return { success: true, type: 'excel' } } else { throw new Error('Excel预览失败') } case 'word': // Word文件下载后打开
previewWord(blobData, fileName) return { success: true, type: 'word', message: 'Word文件已下载,请使用本地软件打开' } case 'ppt': // PPT文件下载后打开
previewPPT(blobData, fileName) return { success: true, type: 'ppt', message: 'PPT文件已下载,请使用本地软件打开' } default: // 未知类型,下载处理
const downloadUrl = URL.createObjectURL(blobData) const link = document.createElement('a') link.href = downloadUrl link.download = fileName link.click() URL.revokeObjectURL(downloadUrl) return { success: true, type: 'download', message: '文件已下载' } } } catch (error) { console.error('文件预览错误:', error) throw error }}
export default { getFileSuffix, getFileType, getMimeType, previewImage, previewVideo, previewAudio, previewPDF, previewText, previewExcel, previewWord, previewPPT, previewFile}
|