feat(文件预览): 添加对PDF、Word和Excel文件的预览支持
添加新的文件预览组件,支持PDF、Word(docx)、Excel(xlsx/xls)文件类型预览 重构图片预览为独立组件,并添加相关依赖包
This commit is contained in:
238
klp-ui/src/components/FilePreview/preview/xls/index.vue
Normal file
238
klp-ui/src/components/FilePreview/preview/xls/index.vue
Normal file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div class="xls-preview">
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>正在加载Excel文件...</p>
|
||||
</div>
|
||||
|
||||
<!-- 错误信息 -->
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
|
||||
<!-- Excel内容展示 -->
|
||||
<div v-if="workbook" class="workbook-container">
|
||||
<!-- 工作表标签 -->
|
||||
<div class="sheet-tabs">
|
||||
<button v-for="(sheet, index) in workbook.SheetNames" :key="index"
|
||||
:class="{ active: activeSheetIndex === index }" @click="activeSheetIndex = index">
|
||||
{{ sheet }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 表格内容区域 -->
|
||||
<div class="sheet-content">
|
||||
<table v-if="activeSheetData">
|
||||
<tbody>
|
||||
<tr v-for="(row, rowIndex) in activeSheetData" :key="rowIndex">
|
||||
<td v-for="(cell, colIndex) in row" :key="colIndex" :class="{
|
||||
'header-cell': rowIndex === 0,
|
||||
'odd-row': rowIndex % 2 === 1
|
||||
}">
|
||||
{{ cell || '' }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as XLSX from 'xlsx';
|
||||
|
||||
export default {
|
||||
name: "XlsPreview",
|
||||
props: {
|
||||
src: {
|
||||
type: String,
|
||||
required: true,
|
||||
description: "Excel文件的URL或路径"
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
workbook: null, // 解析后的Excel工作簿
|
||||
activeSheetIndex: 0, // 当前激活的工作表索引
|
||||
loading: false, // 加载状态
|
||||
error: null // 错误信息
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
src: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
this.loadAndParseExcel();
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 获取当前激活的工作表数据
|
||||
activeSheetData () {
|
||||
if (!this.workbook || !this.workbook.SheetNames.length) return null;
|
||||
|
||||
const sheetName = this.workbook.SheetNames[this.activeSheetIndex];
|
||||
const worksheet = this.workbook.Sheets[sheetName];
|
||||
|
||||
// 将工作表转换为二维数组
|
||||
return XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 加载并解析Excel文件
|
||||
*/
|
||||
async loadAndParseExcel () {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
this.workbook = null;
|
||||
|
||||
try {
|
||||
// 验证URL格式
|
||||
if (!this.src || (this.src.startsWith('http') && !this.isValidUrl(this.src))) {
|
||||
throw new Error('无效的文件路径');
|
||||
}
|
||||
|
||||
// 加载文件
|
||||
const response = await fetch(this.src);
|
||||
if (!response.ok) {
|
||||
throw new Error(`加载失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
// 转换为ArrayBuffer
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
|
||||
// 解析Excel
|
||||
this.workbook = XLSX.read(arrayBuffer, {
|
||||
type: 'array',
|
||||
cellDates: true, // 解析日期类型
|
||||
cellText: false // 保持单元格原始类型
|
||||
});
|
||||
|
||||
// 重置到第一个工作表
|
||||
this.activeSheetIndex = 0;
|
||||
} catch (err) {
|
||||
this.error = err.message;
|
||||
console.error('Excel处理错误:', err);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 验证URL格式
|
||||
*/
|
||||
isValidUrl (url) {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.xls-preview {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 70vh;
|
||||
}
|
||||
|
||||
/* 加载状态样式 */
|
||||
.loading {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: 0 auto 16px;
|
||||
border: 4px solid #e5e7eb;
|
||||
border-top-color: #3b82f6;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 错误信息样式 */
|
||||
.error {
|
||||
padding: 20px;
|
||||
color: #dc2626;
|
||||
background-color: #fee2e2;
|
||||
border-bottom: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
/* 工作表标签样式 */
|
||||
.sheet-tabs {
|
||||
display: flex;
|
||||
background-color: #f9fafb;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sheet-tabs button {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 14px;
|
||||
color: #4b5563;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.sheet-tabs button:hover:not(.active) {
|
||||
background-color: #f3f4f6;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.sheet-tabs button.active {
|
||||
border-bottom-color: #3b82f6;
|
||||
color: #3b82f6;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 表格内容样式 */
|
||||
.sheet-content {
|
||||
overflow: auto;
|
||||
max-height: 600px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #e5e7eb;
|
||||
min-width: 80px;
|
||||
font-size: 14px;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.header-cell {
|
||||
background-color: #f3f4f6;
|
||||
font-weight: 500;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.odd-row {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user