1. 项目概述
在Web开发中,经常需要处理Excel数据的展示和导出需求。传统做法是直接下载Excel文件让用户用本地软件打开,但这种方式体验割裂,无法实现即时预览。本文将分享一套完整的Vue.js解决方案,使用ExcelJS库实现Excel数据的生成、HTML预览、导出和打印功能。
这个方案的核心价值在于:
- 完全在浏览器端处理Excel数据,无需后端介入
- 实现所见即所得的Excel预览效果
- 保持Excel原样导出和打印
- 支持Vue2/Vue3项目(本文以Vue2为例)
2. 环境准备与ExcelJS基础
2.1 安装与引入ExcelJS
首先安装ExcelJS库:
bash复制npm install exceljs
在Vue组件中引入:
javascript复制import ExcelJS from "exceljs"
2.2 创建工作簿与工作表
ExcelJS的基本操作单位是工作簿(Workbook)和工作表(Worksheet):
javascript复制const wb = new ExcelJS.Workbook()
const ws = wb.addWorksheet("Sheet1") // 添加工作表
提示:一个工作簿可以包含多个工作表,适合处理复杂数据场景
2.3 设置表格基本结构
设置列定义(表头):
javascript复制ws.columns = [
{ header: 'ID', key: 'id', width: 10 },
{ header: '姓名', key: 'name', width: 20 },
{ header: '出生日期', key: 'dob', width: 15 }
]
添加数据行的两种方式:
javascript复制// 方式1:按key添加(推荐)
ws.addRow({id: 1, name: '张三', dob: '1990-01-01'})
// 方式2:按数组顺序添加
ws.addRow([2, '李四', '1992-05-15'])
3. 表格样式深度定制
3.1 单元格样式设置
ExcelJS支持丰富的单元格样式设置:
javascript复制const cell = ws.getCell('A1')
cell.font = {
name: '微软雅黑',
size: 12,
bold: true,
color: { argb: 'FF0000' } // 红色字体
}
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFFF00' } // 黄色背景
}
cell.border = {
top: { style: 'thin', color: { argb: '000000' } },
left: { style: 'thin', color: { argb: '000000' } }
}
3.2 合并单元格
合并A1到D1区域:
javascript复制ws.mergeCells('A1:D1')
3.3 行高与列宽调整
javascript复制// 设置行高
const row = ws.getRow(1)
row.height = 30
// 设置列宽
ws.getColumn(2).width = 25
4. Excel转HTML预览实现
4.1 核心转换逻辑
将Excel数据转换为HTML表格的关键是遍历每个单元格,提取样式和内容:
javascript复制async readExcel(file) {
const workbook = new ExcelJS.Workbook()
const buffer = await file.arrayBuffer()
await workbook.xlsx.load(buffer)
let html = '<table style="border-collapse: collapse; width: 100%">'
workbook.eachSheet((worksheet) => {
worksheet.eachRow((row) => {
html += '<tr>'
row.eachCell((cell) => {
const styles = this.getCellStyles(cell)
html += `<td style="${styles}">${cell.value || ''}</td>`
})
html += '</tr>'
})
})
html += '</table>'
return html
}
4.2 样式转换细节
处理Excel样式到CSS的转换:
javascript复制getCellStyles(cell) {
const styles = []
// 字体样式
if(cell.font) {
if(cell.font.bold) styles.push('font-weight: bold')
if(cell.font.size) styles.push(`font-size: ${cell.font.size}pt`)
if(cell.font.color) {
styles.push(`color: #${cell.font.color.argb.substring(2)}`)
}
}
// 背景色
if(cell.fill && cell.fill.fgColor) {
styles.push(`background-color: #${cell.fill.fgColor.argb.substring(2)}`)
styles.push('-webkit-print-color-adjust: exact') // 解决打印背景色丢失
}
// 边框
if(cell.border) {
// 处理四个方向的边框...
}
return styles.join('; ')
}
4.3 处理合并单元格
合并单元格需要特殊处理:
javascript复制// 在readExcel方法中添加
const merges = worksheet.model.merges || []
merges.forEach(mergeRange => {
const [start, end] = mergeRange.split(':')
const startCell = worksheet.getCell(start)
const endCell = worksheet.getCell(end)
// 计算行列跨度
const rowspan = endCell.row - startCell.row + 1
const colspan = endCell.col - startCell.col + 1
// 生成合并单元格HTML
html += `<td rowspan="${rowspan}" colspan="${colspan}">${startCell.value}</td>`
})
5. 文件导出功能实现
5.1 安装file-saver
bash复制npm install file-saver --save
5.2 导出Excel文件
javascript复制import { saveAs } from 'file-saver'
exportExcel() {
this.workbook.xlsx.writeBuffer().then(buffer => {
const blob = new Blob([buffer], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
saveAs(blob, '导出数据.xlsx')
})
}
6. 打印功能实现
6.1 安装打印插件
bash复制npm install vue-print-nb --save
6.2 全局配置
在main.js中:
javascript复制import Print from 'vue-print-nb'
Vue.use(Print)
6.3 打印配置
javascript复制data() {
return {
printConfig: {
id: 'print-area',
popTitle: '数据报表',
extraHead: '<meta charset="UTF-8">',
extraCss: `
@page { size: auto; margin: 5mm; }
body { -webkit-print-color-adjust: exact; }
`
}
}
}
模板中使用:
html复制<div id="print-area" v-html="tableHtml"></div>
<button v-print="printConfig">打印</button>
7. 实战经验与避坑指南
7.1 常见问题解决
-
打印背景色丢失
- 解决方案:添加CSS样式
-webkit-print-color-adjust: exact
- 解决方案:添加CSS样式
-
中文字体不生效
- 确保在打印样式中指定中文字体:
css复制@media print { body { font-family: "Microsoft YaHei", sans-serif; } }
- 确保在打印样式中指定中文字体:
-
分页打印问题
- 在需要分页的位置添加:
html复制<div style="page-break-after: always;"></div>
- 在需要分页的位置添加:
7.2 性能优化建议
-
大数据量处理
- 对于超过1000行的数据,建议分页加载
- 使用worksheet.eachRow的skip和limit参数
-
样式处理优化
- 将通用样式提取到CSS类中,减少内联样式
- 使用CSS变量管理颜色值
-
内存管理
- 处理完成后手动释放内存:
javascript复制this.workbook = null
- 处理完成后手动释放内存:
8. 完整组件实现
以下是整合所有功能的完整组件示例:
javascript复制<template>
<div>
<el-upload
action=""
:auto-upload="false"
:show-file-list="false"
:on-change="handleFileUpload"
accept=".xlsx,.xls"
>
<el-button type="primary">上传Excel</el-button>
</el-upload>
<div id="print-area" v-html="tableHtml"></div>
<el-button @click="exportExcel">导出Excel</el-button>
<el-button v-print="printConfig">打印</el-button>
</div>
</template>
<script>
import ExcelJS from 'exceljs'
import { saveAs } from 'file-saver'
export default {
data() {
return {
workbook: null,
tableHtml: '',
printConfig: {
id: 'print-area',
popTitle: '数据报表',
extraHead: '<meta charset="UTF-8">',
extraCss: `
@page { size: auto; margin: 5mm; }
body { -webkit-print-color-adjust: exact; }
`
}
}
},
methods: {
async handleFileUpload(file) {
try {
this.workbook = new ExcelJS.Workbook()
const buffer = await file.raw.arrayBuffer()
await this.workbook.xlsx.load(buffer)
this.tableHtml = await this.convertToHtml(this.workbook)
} catch (error) {
console.error('文件处理失败:', error)
}
},
async convertToHtml(workbook) {
// 实现转换逻辑...
},
exportExcel() {
// 实现导出逻辑...
}
}
}
</script>
<style>
#print-area table {
width: 100%;
border-collapse: collapse;
}
#print-area td, #print-area th {
border: 1px solid #ddd;
padding: 8px;
}
@media print {
@page {
size: auto;
margin: 5mm;
}
body {
-webkit-print-color-adjust: exact;
font-family: "Microsoft YaHei", sans-serif;
}
}
</style>
9. 扩展功能建议
-
数据验证
- 添加上传文件类型和大小校验
- 实现Excel数据格式校验
-
模板功能
- 预定义常用表格模板
- 支持用户保存自定义模板
-
交互增强
- 添加表格编辑功能
- 实现行/列的动态增删
-
多sheet支持
- 显示sheet切换标签
- 支持sheet的增删改
这套方案在实际项目中已经验证过稳定性,特别是在金融报表、数据看板等场景下表现优异。关键在于样式转换的完整性和打印输出的准确性,这也是本文重点解决的问题。