- 预览Excel 文件的时候,需要用到xlsx 库,可以使用npm安装
npm install xlsx
- 预览pdf文件可以直接使用 PDF.js 官方查看器(https://mozilla.github.io/pdf.js/web/viewer.html),通过嵌入 iframe 来加载该查看器,并传递 PDF 文件 URL
- 预览word文件可以使用Microsoft 提供的 Office Online Viewer,通过 iframe 将 Office Online Viewer 嵌入到你的 Vue 3 应用中,并传递 Word 文件的 URL
参考代码如下:
<NButton type="primary" size="small" class="color-white font-size-12px pt-8px pb-8px pl-22px pr-22px h-32px rd-5px mt-16px mr-16px" @click="previewFile">预览</NButton>
<NModal v-model:show="showPreview" style="width: 90%; max-width: 1200px;">
<NCard :title="previewTitle" :bordered="false" size="huge">
<template #header-extra>
<n-button @click="closePreview" circle style="background-color: transparent;--n-border:none;--n-border-hover:none">
<template #icon>
<!-- <n-icon><close-icon /></n-icon> -->
<icon-material-symbols:close-rounded class="text-icon" />
</template>
</n-button>
</template>
<div v-if="fileType === 'excel'" class="excel-preview-container">
<table class="excel-table">
<tbody>
<tr v-for="(row, rowIndex) in excelData" :key="rowIndex">
<td v-for="(cell, cellIndex) in row" :key="cellIndex" :rowspan="cell.rowSpan" :colspan="cell.colSpan" :style="getCellStyle(cell.style)">
{{ cell.value }}
</td>
</tr>
</tbody>
</table>
</div>
<div v-else-if="fileType === 'pdf'" class="pdf-preview-container">
<iframe v-if="!pdfError" :src="previewUrl" style="width: 100%; height: 80vh; border: none;"></iframe>
<div v-else class="error-message">{{ pdfError }}</div>
</div>
<iframe v-else-if="previewUrl" :src="previewUrl" style="width: 100%; height: 80vh; border: none;"></iframe>
<div v-else class="text-center">
<p>暂无可预览的文件</p>
</div>
</NCard>
</NModal>
import * as XLSX from 'xlsx';
const fileType = ref(''); // 文件类型
const previewUrl = ref(''); // 预览URL
const excelData = ref<any[][]>([]); // Excel数据
const pdfError = ref(''); // 加载pdf错误信息
const showPreview = ref(false); // 是否显示预览窗口
// 预览窗口标题
const previewTitle = computed(() => {
switch (fileType.value) {
case 'excel': return 'Excel文件预览';
case 'pdf': return 'PDF文件预览';
case 'word': return 'Word文件预览';
default: return '文件预览';
}
});
// 获取文件类型
const getFileType = (fileUrl: string): string => {
const extension = fileUrl.split('.').pop()?.toLowerCase() || '';
switch (extension) {
case 'xls':
case 'xlsx':
return 'excel';
case 'pdf':
return 'pdf';
case 'doc':
case 'docx':
return 'word';
default:
return 'unknown';
}
};
// 文件处理
const previewFile = async () => {
if (url.value) { // 预览文件路径
fileType.value = getFileType(url.value);
console.log("文件类型:", fileType.value);
console.log("文件URL:", url.value);
switch (fileType.value) {
case 'excel':
try {
console.log("开始加载Excel文件");
const response = await fetch(url.value);
console.log("Fetch响应状态:", response.status);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const arrayBuffer = await response.arrayBuffer();
console.log("获取到ArrayBuffer");
const data = new Uint8Array(arrayBuffer);
console.log("创建Uint8Array");
const workbook = XLSX.read(data, {type: 'array'});
console.log("XLSX读取成功");
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
// const jsonData = XLSX.utils.sheet_to_json(worksheet, {header: 1});
// console.log("转换为JSON数据:", jsonData);
// excelData.value = jsonData;
// console.log("Excel数据处理完成,excelData:", excelData.value);
// 处理合并单元格
const merges = worksheet['!merges'] || [];
const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1');
const processedData: CellData[][] = [];
for (let R = range.s.r; R <= range.e.r; ++R) {
const row: CellData[] = [];
for (let C = range.s.c; C <= range.e.c; ++C) {
const cellAddress = XLSX.utils.encode_cell({r: R, c: C});
const cell = worksheet[cellAddress];
let cellData: CellData = {
value: cell ? XLSX.utils.format_cell(cell) : '',
rowSpan: 1,
colSpan: 1,
style: cell?.s || {}
};
// 检查是否是合并单元格的一部分
const mergeCell = merges.find(m =>
R >= m.s.r && R <= m.e.r && C >= m.s.c && C <= m.e.c
);
if (mergeCell) {
if (R === mergeCell.s.r && C === mergeCell.s.c) {
// 这是合并单元格的左上角
cellData.rowSpan = mergeCell.e.r - mergeCell.s.r + 1;
cellData.colSpan = mergeCell.e.c - mergeCell.s.c + 1;
} else {
// 这是合并单元格的其他部分,跳过
continue;
}
}
row.push(cellData);
}
processedData.push(row);
}
excelData.value = processedData;
console.log("Excel数据处理完成,excelData:", excelData.value);
} catch (error) {
console.error('加载Excel文件时出错:', error);
window.$message.error(`加载Excel文件时出错: ${error.message}`);
}
break;
case 'pdf':
try {
// 检查 URL 是否有效
const response = await fetch(url.value, { method: 'HEAD' });
if (!response.ok) {
throw new Error(`无法访问 PDF 文件: ${response.statusText}`);
}
// 使用 PDF.js 预览 PDF
previewUrl.value = `https://mozilla.github.io/pdf.js/web/viewer.html?file=${encodeURIComponent(url.value)}`;
pdfError.value = '';
} catch (error) {
console.error('加载 PDF 文件时出错:', error);
pdfError.value = `加载 PDF 文件时出错: ${error.message}`;
window.$message.error(pdfError.value);
}
break;
case 'word':
// 使用Microsoft Office Online Viewer
previewUrl.value = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(url.value)}`;
break;
default:
window.$message.warning("不支持的文件类型");
return;
}
showPreview.value = true;
} else {
window.$message.warning("暂无可预览的文件");
}
};
const closePreview = () => {
showPreview.value = false;
excelData.value = [];
previewUrl.value = '';
};
// excel 单元格样式
const getCellStyle = (style: any) => {
const cellStyle: Record<string, string> = {};
if (style.fill?.fgColor?.rgb) {
cellStyle.backgroundColor = `#${style.fill.fgColor.rgb.substring(2)}`;
}
if (style.font?.bold) {
cellStyle.fontWeight = 'bold';
}
if (style.font?.italic) {
cellStyle.fontStyle = 'italic';
}
if (style.font?.underline) {
cellStyle.textDecoration = 'underline';
}
if (style.alignment?.horizontal) {
cellStyle.textAlign = style.alignment.horizontal;
}
if (style.alignment?.vertical) {
cellStyle.verticalAlign = style.alignment.vertical;
}
return cellStyle;
};
.excel-preview-container {
max-height: 80vh;
overflow: auto;
}
.excel-table {
border-collapse: collapse;
width: 100%;
font-family: Arial, sans-serif;
font-size: 14px;
th, td {
border: 1px solid #e0e0e0;
padding: 8px;
text-align: left;
}
th {
background-color: #1E1F25;
font-weight: bold;
position: sticky;
top: 0;
z-index: 1;
}
tr:nth-child(even) {
background-color: #1E1F25;
}
}
.pdf-preview-container {
width: 100%;
height: 80vh;
overflow: auto;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。