You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
641 lines
19 KiB
641 lines
19 KiB
/**
|
|
* 文件预览工具类
|
|
* 支持图片、视频、音频、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
|
|
}
|