导入导出依赖:
npm install [email protected]
npm install [email protected] --save
- 安装xlsx-style,运行报错
This relative module was not found: ./cptable in ./node_modules/[email protected]@xlsx-style/dist/cpexcel.js
- 解决报错
在\node_modules\xlsx-style\dist\cpexcel.js 807行 的var cpt = require('./cpt' + 'able');
改为:var cpt = cptable;
打印依赖:
npm install [email protected] --save
<template>
<div>
<el-upload
action="#"
:before-upload="beforeUpload"
:show-file-list="false"
accept=".xlsx, .xls"
>
<el-button slot="trigger" size="small" type="primary">选取文件el-button>
el-upload>
<el-table :data="tableData">
<el-table-column prop="日期" label="日期" width="180"> el-table-column>
<el-table-column prop="姓名" label="姓名" width="180"> el-table-column>
<el-table-column prop="地址" label="地址"> el-table-column>
el-table>
div>
template>
<script>
import XLSX from 'xlsx'
export default {
name: 'importExcel',
data () {
return {
tableData: [],
}
},
methods: {
beforeUpload (file) {
console.log(file, '--文件');
this.file2XLSX(file).then((res) => {
console.log('可以继续对res数据进行二次处理')
this.tableData = res[0].sheet
})
return false
},
// excel导入方法
file2XLSX (file) {
return new Promise(function (resolve, reject) {
// 通过FileReader对象读取文件
const reader = new FileReader()
// 读取为二进制字符串
reader.readAsBinaryString(file)
reader.onload = function (e) {
console.log(e, '读取文件成功的e');
// 获取读取文件成功的结果值
const data = e.target.result
// XLSX.read解析数据,按照type 的类型解析
let wb = XLSX.read(data, {
type: 'binary' // 二进制
})
console.log(wb, '---->解析后的数据')
// 存储获取到的数据
const result = []
// 工作表名称的有序列表
wb.SheetNames.forEach(sheetName => {
result.push({
// 工作表名称
sheetName: sheetName,
// 利用 sheet_to_json 方法将 excel 转成 json 数据
sheet: XLSX.utils.sheet_to_json(wb.Sheets[sheetName])
})
})
resolve(result)
}
})
}
},
}
script>
<template>
<div>
<el-button type="primary" @click="exportSelectData"
>导出行数据(json_to_sheet)el-button
>
<el-button type="primary" @click="exportTableData"
>导出table数据(也会导出合并单元格)(table_to_sheet)el-button
>
<el-button type="primary" @click="exportTableDataFormAoa"
>导出二维数据的table数据(aoa_to_sheet)el-button
>
<el-button type="primary" @click="exportTableDataCellMerging"
>导出合并单元格table数据(aoa_to_sheet)el-button
>
<el-table
:data="tableData"
@selection-change="handleSelectionChange"
ref="tableDataRef"
id="table1"
>
<el-table-column type="selection" width="55"> el-table-column>
<el-table-column prop="date" label="日期" width="180"> el-table-column>
<el-table-column prop="name" label="姓名" width="180"> el-table-column>
<el-table-column prop="address" label="地址"> el-table-column>
el-table>
div>
template>
<script>
import XLSX from 'xlsx'
export default {
name: 'importExcel',
data () {
return {
selectionList: [],
tableData: [{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}]
}
},
methods: {
// 获取选择的行数据
handleSelectionChange (val) {
this.selectionList = val;
console.log(this.selectionList, '--行数据');
},
// 导出选择的行数据
exportSelectData () {
// 对选择的表格数据处理:添加标题
let arr = this.selectionList.map(item => {
return {
日期: item.date,
姓名: item.name,
地址: item.address
}
})
// 将json数据变为sheet数据
// json_to_sheet: 将一个由对象组成的数组转成sheet;
let sheet = XLSX.utils.json_to_sheet(arr)
// 新建表格
let book = XLSX.utils.book_new()
// 在表格中插入一个sheet
XLSX.utils.book_append_sheet(book, sheet, "sheet1")
// 通过xlsx的writeFile方法将文件写入
XLSX.writeFile(book, `user${new Date().getTime()}.xls`)
},
// 导出table数据
exportTableData () {
// 获取dom元素(2种方式)
// let table1 = document.querySelector("#table1"); // 原生dom
let table = this.$refs.tableDataRef.$el
// table_to_sheet: 将一个table dom直接转成sheet,会自动识别colspan和rowspan并将其转成对应的单元格合并;
let sheet = XLSX.utils.table_to_sheet(table)
let book = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(book, sheet, "sheet1")
XLSX.writeFile(book, `user${new Date().getTime()}.xls`)
},
// 导出一个二维数组
exportTableDataFormAoa () {
let aoa = [
['姓名', '性别', '年龄', '注册时间'],
['张三', '男', 18, new Date()],
['李四', '女', 22, new Date()]
];
// 将一个二维数组转成sheet
// aoa_to_sheet: 这个工具类最强大也最实用了,将一个二维数组转成sheet,会自动处理number、string、boolean、date等类型数据;
let sheet = XLSX.utils.aoa_to_sheet(aoa);
let book = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(book, sheet, "sheet1")
XLSX.writeFile(book, `user${new Date().getTime()}.xls`)
},
// 导出合并单元格的table数据
exportTableDataCellMerging () {
let aoa = [
['主要信息', null, null, '其它信息'], // 特别注意合并的地方后面预留2个null
['姓名', '性别', '年龄', '注册时间'],
['张三', '男', 18, new Date()],
['李四', '女', 22, new Date()]
];
let sheet = XLSX.utils.aoa_to_sheet(aoa);
// 设置合并的单元格
sheet['!merges'] = [
// 设置A1-C1的单元格合并
{ s: { r: 0, c: 0 }, e: { r: 0, c: 2 } }
];
let book = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(book, sheet, "sheet1")
XLSX.writeFile(book, `user${new Date().getTime()}.xls`)
}
}
}
script>
参考
- 对sheet二次处理的参考:
https://blog.csdn.net/tian_i/article/details/84327329
<template>
<div>
<el-button @click="exportExcel()">导出带样式的excelel-button>
div>
template>
<script>
import XLSX from 'xlsx'
import XLSXStyle from 'xlsx-style';
export default {
name: 'exportExcelStyle',
methods: {
exportExcel () {
let data = [['时间', '电压'], ['2021-12-01 08:57:12', '3.14'], ['2021-12-01 08:58:20', '3.15']];
let titles = ['时间', '电压']
var sheet = XLSX.utils.json_to_sheet(data, {
skipHeader: true,
});
/**设置标题头背景色 */
for (const key in sheet) {
// 第一行,表头
if (key.replace(/[^0-9]/ig, '') === '1') {
sheet[key].s = {
fill: { //背景色
fgColor: { rgb: 'C0C0C0' }
},
font: {//字体
name: '宋体',
sz: 12,
bold: true
},
border: {//边框
bottom: {
style: 'thin',
color: 'FF000000'
}
},
alignment: {
horizontal: 'center' //水平居中
}
}
}
// 指定单元格样式
if (key === 'A1') {
sheet[key].s = {
...sheet[key].s,
fill: { //背景色
fgColor: { rgb: 'E4DFEC' }
}
}
}
// 列宽
let colsP = titles.map(item => {
let obj = {
'wch': 25 //列宽
}
return obj;
})
sheet['!cols'] = colsP;//列宽
// // 每列的列宽
// sheet["!cols"] = [{
// wpx: 70 //单元格列宽
// }, {
// wpx: 70
// }, {
// wpx: 70
// }, {
// wpx: 70
// }, {
// wpx: 150
// }, {
// wpx: 120
// }];
}
let fileName = 'Excel文件.xlsx'
let sheetName = 'Excel文件'
this.openDownload(this.sheet2blob(sheet, sheetName), fileName);
},
sheet2blob (sheet, sheetName) {
let wb = XLSX.utils.book_new();
wb.SheetNames.push(sheetName)
wb.Sheets[sheetName] = sheet;
// 必须使用xlsx-style才能生成指定样式
var wbout = XLSXStyle.write(wb, { bookType: '', bookSST: false, type: 'binary' })
var blob = new Blob([s2ab(wbout)], { type: "" }, sheetName);
// 字符串转ArrayBuffer
function s2ab (s) {
var buf = new ArrayBuffer(s.length);
var view = new Uint8Array(buf);
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff;
return buf;
}
return blob;
},
openDownload (url, saveName) {
if (typeof url == "object" && url instanceof Blob) {
url = URL.createObjectURL(url); // 创建blob地址
}
var aLink = document.createElement("a");
aLink.href = url;
aLink.download = saveName || ""; // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效
var event;
if (window.MouseEvent) event = new MouseEvent("click");
else {
event = document.createEvent("MouseEvents");
event.initMouseEvent(
"click",
true,
false,
window,
0,
0,
0,
0,
0,
false,
false,
false,
false,
0,
null
);
}
aLink.dispatchEvent(event);
}
},
}
script>
html和css就不写了,主要记录下js功能实现的过程
// 导出函数
export async function exportBtn (payloadList) {
// payloadList是table表格勾选的内容数组
if (payloadList.length === 0) {
this.$message({
type: 'warning',
message: '请选择要导出的记录!'
})
} else {
// 最终生成sheet的aoelist
let finalList = []
// 一、准备枚举值
// 1.写死
let ywlxenum = [
{ label: '预审选址', value: 'YSXZ' },
{ label: '土地储备', value: 'TDCBGM' },
{ label: '农转用报批', value: 'YDBP' },
{ label: '规划条件', value: 'GHTJ' },
{ label: '行政划拨', value: 'XZHB' },
{ label: '公开出让', value: 'GKCR' },
{ label: '建设用地规划许可', value: 'JSYDGHXK' },
{ label: '建设工程规划类许可证核发', value: 'JSGCGHXK' },
{ label: '建设工程竣工规划核实', value: 'JGGHHY' },
{ label: '竣工验收备案', value: 'JGYSBA' }
]
// 2.通过请求获取
let ydxzenum = []
await getEnumByValue({ value: 'TDLYXZ' }).then(data => {
ydxzenum = mapListFunc(data.fieldEnum)
})
// 二、通过请求获取数据
let resList = [] // 保存请求数据
for (let i = 0; i < payloadList.length; i++) {
await this.queryInfo({ id: payloadList[i].xmguid }).then(res => {
// 这里是判断了返回值里还包含了list数组
if (res.data.bizGhtjGhqkList.length !== 0) {
let bizGhtjGhqkList = res.data.bizGhtjGhqkList
bizGhtjGhqkList.forEach(item => {
resList.push({ ...res.data.bizSlsq, ...res.data.bizGhtj, ...item })
})
} else {
resList.push({ ...res.data.bizSlsq, ...res.data.bizGhtj })
}
})
}
// 三、映射关系list
let mappingList = [
{ field: 'xmmc', value: '项目名称', merge: true }, // merge代表单元格是否合并
{ field: 'ydxz', value: '用地性质', enum: true, merge: true }, // enum代表是否是枚举值
{ field: 'dkbh', value: '地块编号' },
]
// 四、添加标题
let titleList = []
mappingList.forEach(item => {
titleList.push(item.value)
})
finalList.push(titleList)
// 五、添加内容
resList.forEach(row => {
// 行的list
let ctnList = []
let hyfltemp = []
let ydxztemp = []
mappingList.forEach(item => {
if (item.enum) {
// 带枚举值的处理
switch (item.field) {
case 'hyfl':
hyfltemp = row[item.field] && row[item.field].split(',')
ctnList.push(queryEnumVal(hyflenum, hyfltemp && hyfltemp[(hyfltemp.length - 1)]))
break
case 'ydxz':
ydxztemp = row[item.field] && row[item.field].split(',')
ctnList.push(queryEnumVal(ydxzenum, ydxztemp && ydxztemp[(ydxztemp.length - 1)]))
break
case 'pzjg':
ctnList.push(queryEnumVal(pzjgenum, row[item.field]))
break
case 'ywlx':
ctnList.push(queryEnumVal(ywlxenum, row[item.field]))
break
case 'cbywlx':
ctnList.push(queryEnumVal(cbywlxenum, row[item.field]))
break
default:
break
}
} else {
// 常规
ctnList.push(row[item.field])
}
})
finalList.push(ctnList)
})
// 六、处理合并单元格
let mergeArr = []
let { indices } = unipFunc(resList, 'xmguid')
mappingList.forEach((item, index) => {
if (item.merge) {
indices.forEach(itemlist => {
if (itemlist.length > 1) {
mergeArr.push({
s: { r: itemlist[0] + 1, c: index },
e: { r: itemlist[itemlist.length - 1] + 1, c: index }
})
}
})
}
})
// 七、生成sheet
let sheet = XLSX.utils.aoa_to_sheet(finalList)
// 八、合并单元格和添加样式
sheet['!merges'] = mergeArr
Object.keys(sheet).forEach((item, index) => {
if (sheet[item].t) {
sheet[item].s = { // 对齐方式相关样式
alignment: {
vertical: 'center', // 垂直对齐方式
horizontal: 'center' // 水平对齐方式
// wrapText: true // 自动换行
}
}
}
})
// 九、导出excel
openDownloadDialog(sheet2blob(sheet), new Date().getTime() + '.xlsx' || '表名.xlsx')
}
}
function queryEnumVal (enumList, field) {
// 获取枚举值对应的key
let enumVal = ''
enumList.forEach(item => {
if (item.value === field) {
enumVal = item.label
}
})
return enumVal
}
function mapListFunc (params) {
// 处理枚举值
let list = []
list = params.map(item => {
item.value = item.enumValue
item.label = item.enumName
return item
})
return list
}
// 处理数据重复值
function unipFunc (list, objKey) {
let key = {} // 存储的 key 是type的值,value是在indeces中对应数组的下标
let indices = [] // 数组中每一个值是一个数组,数组中的每一个元素是原数组中相同type的下标
list.map((item, index) => {
// 根据对应字段 分类(type)
let itemKey = item[objKey]
let _index = key[itemKey]
if (_index !== undefined) {
indices[_index].push(index)
} else {
key[itemKey] = indices.length
indices.push([index])
}
})
// 归类结果
let result = []
let resultIndex = []
indices.map((item) => {
item.map((index) => {
if (item.length > 1) {
result.push(list[index])
resultIndex.push(index)
}
})
})
return { result, resultIndex, indices }
}
// 下载excel
function openDownloadDialog (url, saveName) {
if (typeof url === 'object' && url instanceof Blob) {
url = URL.createObjectURL(url) // 创建blob地址
}
var aLink = document.createElement('a')
aLink.href = url
aLink.download = saveName || '' // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效
var event
if (window.MouseEvent) event = new MouseEvent('click')
else {
event = document.createEvent('MouseEvents')
event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
}
aLink.dispatchEvent(event)
}
// 字符串转ArrayBuffer
function s2ab (s) {
var buf = new ArrayBuffer(s.length)
var view = new Uint8Array(buf)
for (var i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF
return buf
}
// 将一个sheet转成最终的excel文件的blob对象,然后利用URL.createObjectURL下载
function sheet2blob (sheet, sheetName) {
sheetName = sheetName || 'sheet1'
let workbook = XLSX.utils.book_new()
workbook.SheetNames.push(sheetName)
workbook.Sheets[sheetName] = sheet
// 生成excel的配置项
var wopts = {
bookType: 'xlsx', // 要生成的文件类型
bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
type: 'binary'
}
var wbout = XLSXStyle.write(workbook, wopts)
var blob = new Blob([s2ab(wbout)], { type: 'application/octet-stream' })
return blob
}
参考
- 带样式的导出参考代码:
https://blog.csdn.net/weixin_39246975/article/details/121639072- 别人对xlsx-style的二次封装:
https://blog.csdn.net/weixin_51947053/article/details/127370479
<template>
<div>
<p>点击下面的按钮,可将页面进行打印p>
<div id="printDiv">
<p>打印内容 p>
<p>打印内容 p>
<p>打印内容 p>
<p>打印内容 p>
div>
<button @click="print">打印页面内容button>
div>
template>
<script>
export default{
methods: {
print(){
window.print()
}
}
}
script>
// vue2.x 版本
npm install vue-print-nb --save
// vue3.x 版本
npm install vue3-print-nb --save
// vue2.x版本 -- 全局引入:在项目中入口文件 main.js 文件中全局引入 vue-print-nb
import Vue from 'vue'
import Print from 'vue-print-nb'
Vue.use(Print)
// 局部引入报错,还不知道咋解决,建议是全局引入
// vue2.x版本 -- 在需要打印功能的页面引入 vue-print-nb
import print from 'vue-print-nb'
export default{
directives: { print }
}
// vue3.x版本 -- 全局引入:在项目中入口文件 main.js 文件中全局引入 vue3-print-nb
import {createApp} from 'vue'
import App from './App'
import Print from 'vue3-print-nb'
const app = createApp(App)
app.use(Print)
app.mount('#app')
// vue3.x版本 -- 在需要打印功能的页面引入 vue3-print-nb
import print from 'vue3-print-nb'
export default{
directives: { print }
}
<template>
<div>
<p>点击下面的按钮,可将div里的内容区域进行打印p>
<div id="printDiv">
<p>打印内容 p>
<p>打印内容 p>
<p>打印内容 p>
<p>打印内容 p>
div>
<button v-print="'#printDiv'">打印id为printDiv的div区域内容button>
div>
template>
<script>
export default{
data(){
return{}
}
}
script>
<template>
<div>
<p>点击下面的按钮,可将div里的内容区域进行打印p>
<div id="printDiv">
<p>打印内容 p>
<p>打印内容 p>
<p>打印内容 p>
<p>打印内容 p>
div>
<button v-print="'printSet'">打印id为printDiv的div区域内容button>
div>
template>
<script>
export default{
data(){
return{
printSet: {
id: 'printDiv',
extraCss: "https://cdn.bootcdn.net/ajax/libs/animate.css/4.1.1/animate.compat.css, https://cdn.bootcdn.net/ajax/libs/hover.css/2.3.1/css/hover-min.css",
extraHead: '',
beforeOpenCallback (vue) {
console.log('打开之前')
},
openCallback (vue) {
console.log('执行了打印')
},
closeCallback (vue) {
console.log('关闭了打印工具')
}
}
}
}
}
script>
<template>
<button v-print="'printSet'">打印网址button>
template>
<script>
export default{
data(){
return{
printSet: {
url: 'http://localhost:8080/',
beforeOpenCallback (vue) {
console.log('打开之前')
},
openCallback (vue) {
console.log('执行了打印')
},
closeCallback (vue) {
console.log('关闭了打印工具')
}
}
}
}
}
script>
<template>
<button v-print="'printSet'">打印+预览功能button>
template>
<script>
export default{
data(){
return{
printSet: {
url: 'http://localhost:8080/', // 打印网页预览 如果想要打印本地预览,那么可以不用提供url,需提供打印区域的id,例如: id: 'printDiv'
preview: true,
previewTitle: 'Test Title',
previewBeforeOpenCallback (vue) {
console.log('正在加载预览窗口')
},
previewOpenCallback (vue) {
console.log('已经加载完预览窗口')
},
beforeOpenCallback (vue) {
console.log('打开之前')
},
openCallback (vue) {
console.log('执行了打印')
},
closeCallback (vue) {
console.log('关闭了打印工具')
}
}
}
}
}
script>
<template>
<button v-print="'printSet'">打印+预览功能button>
template>
<script>
export default{
data(){
return{
printSet: {
asyncUrl (reslove, vue) {
setTimeout(() => {
reslove('http://localhost:8080/')
}, 2000)
}, // 异步url
preview: true,
previewTitle: 'Test Title',
previewBeforeOpenCallback (vue) {
console.log('正在加载预览窗口')
},
previewOpenCallback (vue) {
console.log('已经加载完预览窗口')
},
beforeOpenCallback (vue) {
console.log('打开之前')
},
openCallback (vue) {
console.log('执行了打印')
},
closeCallback (vue) {
console.log('关闭了打印工具')
}
}
}
}
}
script>
<template>
<div>
<div ref="printDiv">
<p>打印内容区域p>
<p>打印内容区域p>
<p>打印内容区域p>
<p>打印内容区域p>
<p>打印内容区域p>
实现区域不打印方式1:设置class为 no-print 即可实现该区域不打印
<p class="no-print">不要打印的内容区域p>
// 实现区域不打印方式2: 自定义不打印区域的class名
<p class="do-not-print-div">不要打印的内容区域p>
div>
<button @click="printBtnClick">打印按钮button>
div>
template>
<script>
export default{
data(){
return {}
},
methods:{
printBtnClick(){
// 注意必须使用ref指定打印区域,如果通过id或者class,那么wenpack打包后打印区域会为空
this.$print(this.$refs.printDiv)
// 实现区域不打印方式2
this.$print(this.$refs.print, { noPrint: '.do-not-print-div' })
},
}
}
script>
1、id: String // 范围打印 ID,必填值
2、standard: String // 文档类型(仅打印本地范围)
3、extraHead: String // 在节点中添加DOM节点,并用,(Print local range only)分隔多个节点
4、extraCss: String // 新的 CSS 样式表,并使用,(仅打印本地范围)分隔多个节点
5、popTitle: String // 标签内容(仅打印局部范围)
6、openCallback: Function // 调用打印工具成功回调函数
7、closeCallback: Function // 关闭打印工具成功回调函数
8、beforeOpenCallback: Function // 调用打印工具前的回调函数
9、url: String // 打印指定的 URL。(不允许同时设置ID)
10、asyncUrl: Function // 异步网址:通过 'resolve()' 和 Vue 返回 URL
11、preview: Boolean // 预览
12、previewTitle: String // 预览标题
13、previewPrintBtnLabel: String // 预览按钮的名称
14、zIndex: String,Number // 预览CSS:z-index
15、previewBeforeOpenCallback: Function // 启动预览工具前的回调函数
16、previewOpenCallback: Function // 预览工具完全打开后的回调函数
17、clickMounted: Function //点击打印按钮的回调函数