目录
需要导出的table表格样式
常规
导出的excel效果图
复杂(多表头、合并单元格等等)
导出的excel效果图
安装
npm install file-saver --save
npm install xlsx --save
npm install xlsx-style --save
引入
import XLSX from "xlsx";
import XLSXStyle from "xlsx-style";
import FileSaver from "file-saver";
vue 2.0版本xlsx-style ./cptable' 报错:Can't resolve './cptable' in 'xxxx\nautical-front\node_modules_xlsx
解决方案
在 webpack.base.conf.js 文件下,添加cptable变量
module.exports = {
context: path.resolve(__dirname, '../'),
entry: {
app: './src/main.js'
},
externals: {
'vue': 'Vue',
'vuex': 'Vuex',
'vue-resource': 'VueResource',
'element-ui': 'ELEMENT',
'echarts': 'echarts',
'vue-router': 'VueRouter',
'axios': 'axios',
'vue-i18n': 'VueI18n',
'./cptable':'var cptable'
},
.....
.....
.....
解释一点,XLSX能自动解析table的数据结构
我用的是element-ui的table,最终渲染后,其实也就是普通的自己手打的<table>
vue代码及JS实现片段
excel导出的JS工具类封装,创建exportTableUtil.js
export default {
/**
*
* @param elt
* @param sheetName
* @param fileName 导出的文件名
* @param titleNum 标题行数
*/
exportTable(elt,sheetName,fileName,titleNum){
let wb = XLSX.utils.table_to_book(elt, { sheet: sheetName, raw: true });
let range = XLSX.utils.decode_range(wb.Sheets[sheetName]['!ref']);
//单元格边框样式
let borderStyle = {
top: {
style: "thin",
color: { rgb: "000000" }
},
bottom: {
style: "thin",
color: { rgb: "000000" }
},
left: {
style: "thin",
color: { rgb: "000000" }
},
right: {
style: "thin",
color: { rgb: "000000" }
}
};
let cWidth = [];
for (let C = range.s.c; C < range.e.c; ++C) { //SHEET列
let len = 100; //默认列宽
let len_max = 400; //最大列宽
for (let R = range.s.r; R <= range.e.r; ++R) { //SHEET行
let cell = { c: C, r: R }; //二维 列行确定一个单元格
let cell_ref = XLSX.utils.encode_cell(cell); //单元格 A1、A2
if(wb.Sheets[sheetName][cell_ref]){
// if (R == 0){
if (R < titleNum){
wb.Sheets[sheetName][cell_ref].s = { //设置第一行单元格的样式 style
font:{
sz:15,
color:{rgb:'060B0E'},
bold:true
},
alignment:{
horizontal:'center',
vertical:'center',
},
fill:{
fgColor:{rgb:'E4E4E4'},
},
border: borderStyle,//用上面定义好的边框样式
};
}else {
wb.Sheets[sheetName][cell_ref].s = {
alignment:{
horizontal:'left',
vertical:'center',
},
border: borderStyle,//用上面定义好的边框样式
};
}
//动态自适应:计算列宽
let va = JSON.parse(JSON.stringify(wb.Sheets[sheetName][cell_ref].v));
var card1 = JSON.parse(JSON.stringify(va)).match(/[\u4e00-\u9fa5]/g); //匹配中文
var card11 = "";
if (card1){
card11 = card1.join("")
}
var card2 = JSON.parse(JSON.stringify(va)).replace(/([^\u0000-\u00FF])/g,""); //剔除中文
let st = 0;
if (card11){
// st += card11.length * 16 //中文字节码长度
st += card11.length * 20 //中文字节码长度
}
if (card2){
// st += card2.length * 8 //非中文字节码长度
st += card2.length * 10 //非中文字节码长度
}
if (st > len){
len = st;
}
}
}
if (len > len_max){//最大宽度
len = len_max;
}
cWidth.push({'wpx':len}); //列宽
}
wb.Sheets[sheetName]['!cols'] = cWidth;
var wopts = { bookType: 'xlsx', bookSST: false, type: 'binary' };
var wbout = XLSXStyle.write(wb, wopts); //一定要用XLSXStyle不要用XLSX,XLSX是没有格式的!
FileSaver(new Blob([this.s2ab(wbout)], { type: "" }), fileName + '.xlsx');
},
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;
},
}
import XLSX from "xlsx";
import XLSXStyle from "xlsx-style";
import FileSaver from "file-saver";
栗子
1、正常定义一个el-table,重点是需要在外面嵌套一个div,声明一个id,后面需要用到,即:
<div id="exportData" .....>
<div id="exportData" class="css_page_body" ref="css_page_body">
<el-table border
v-loading="loading"
:data="tableData"
:row-key="getRowKeys"
ref="headTableRef"
:height="getPageBodyHeight">
<el-table-column prop="cla" :label="$t('登记人')" show-overflow-tooltip></el-table-column>
<el-table-column prop="clb" :label="$t('登记时间')" show-overflow-tooltip></el-table-column>
<el-table-column prop="clc" :label="$t('炉位号')" show-overflow-tooltip></el-table-column>
<el-table-column prop="cld" :label="$t('异常类型')" show-overflow-tooltip></el-table-column>
<el-table-column prop="cle" :label="$t('物料品种')" show-overflow-tooltip></el-table-column>
<el-table-column prop="clf" :label="$t('等级')" show-overflow-tooltip></el-table-column>
<el-table-column prop="clf1" :label="$t('异常数量')" show-overflow-tooltip></el-table-column>
<el-table-column prop="clf2" :label="$t('异常放入料车')" show-overflow-tooltip></el-table-column>
</el-table>
</div>
2、定义触发导出功能的按钮
<el-form-item style="float: right">
<el-button type="success" icon="el-icon-download" @click="exportData" :disabled="tableData.length <= 0">{{$t('数据导出')}}</el-button>
</el-form-item>
3、记得引入封装的导出工具类
import exportTableUtil from '@/utils/exportTableUtil.js'
4、导出功能按钮的方法
只有一行表头的调用
exportData(){
let elt = document.getElementById("exportData");
exportTableUtil.exportTable(elt,"找头区物料异常表","找头区物料异常表导出",1);
},
存在两(多)行表头的调用
exportData(){
let elt = document.getElementById("exportData");
exportTableUtil.exportTable(elt,"加捻区物料异常表","加捻区物料异常表导出",2);
},
附:栗子的页面完整代码(页面加入了页面自适应和监听的逻辑)
<template>
<div class="ProcessingInfo" ref="css_page">
<div class="css_page_adapter">
<div class="css_page_head" ref="css_page_head">
<div class="css_page_condition" ref="page_area_condition">
<el-form inline>
<el-form-item prop="time">
<el-date-picker
style="width: 180px;"
:picker-options="pickerStartOptions"
format="yyyy-MM-dd"
v-model.trim="condition.startTime"
type="date"
:placeholder="$t('common.startTime')">
</el-date-picker>
{{" - "}}
<el-date-picker
style="width: 180px;"
:picker-options="pickerEndOptions"
format="yyyy-MM-dd"
v-model.trim="condition.endTime"
type="date"
:placeholder="$t('common.endTime')">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="search" icon="el-icon-search">{{$t('common.query')}}</el-button>
</el-form-item>
<el-form-item>
<el-button @click="clearAll">{{ $t('common.clearAll') }}</el-button>
</el-form-item>
<el-form-item style="float: right">
<el-button type="success" icon="el-icon-download" @click="exportData" :disabled="tableData.length <= 0">{{$t('common.dataExport')}}</el-button>
</el-form-item>
</el-form>
</div>
</div>
<div id="exportData" class="css_page_body" ref="css_page_body">
<el-table border
v-loading="loading"
:data="tableData"
:row-key="getRowKeys"
ref="headTableRef"
@selection-change=""
:height="getPageBodyHeight">
<el-table-column prop="process" :label="$t('加捻区异常物料等级信息')" show-overflow-tooltip >
<el-table-column prop="operator" :label="$t('登记人')" show-overflow-tooltip ></el-table-column>
<el-table-column prop="operateTime" :label="$t('登记时间')" show-overflow-tooltip width="140"></el-table-column>
<el-table-column prop="type" :label="$t('异常类型')" show-overflow-tooltip ></el-table-column>
<el-table-column prop="materialDTO.materialName" :label="$t('物料品种')" show-overflow-tooltip ></el-table-column>
<el-table-column prop="materialDTO.specName" :label="$t('等级')" show-overflow-tooltip ></el-table-column>
<el-table-column prop="quantity" :label="$t('数量')" show-overflow-tooltip ></el-table-column>
<el-table-column prop="containerCode" :label="$t('异常放入料车')" show-overflow-tooltip ></el-table-column>
</el-table-column>
<el-table-column prop="head" :label="$t('找头区入库信息')" show-overflow-tooltip>
<el-table-column prop="containerCode" :label="$t('异常发现料车')" show-overflow-tooltip ></el-table-column>
<el-table-column prop="headEnterTime" :label="$t('入库时间')" show-overflow-tooltip width="140"></el-table-column>
<el-table-column prop="headOutTime" :label="$t('出库时间')" show-overflow-tooltip width="140"></el-table-column>
<el-table-column prop="headEnterOperatorName" :label="$t('操作人')" show-overflow-tooltip ></el-table-column>
</el-table-column>
</el-table>
</div>
</div>
</div>
</template>
<script>
/* import XLSX from "xlsx";
import XLSXStyle from "xlsx-style";
import FileSaver from "file-saver";*/
import exportTableUtil from '@/utils/exportTableUtil.js'
export default {
name: "ProcessingInfo",
props: ["selectedTime"],
data() {
return {
loading: false,
tableData: [],
condition: {
startTime:null,
endTime:null,
typeNameKeyWord: '',
},
queryCondition: {},
pagination: {
page: 1,
pageSize: 20,
total: 0,
},
totalPage: 5,
countTotal: 20,
selectedIds:[],
checked:false,
otherHeight:0,
pageHeight:0,
pickerStartOptions: {
disabledDate: (time) => {
if (this.condition.endTime !== "" && this.condition.endTime) {
return time.getTime() > this.condition.endTime || time.getTime() > Date.now();
} else {
return time.getTime() > Date.now();
}
},
},
pickerEndOptions: {
disabledDate: (time) => {
if (this.condition.startTime !== "" && this.condition.startTime) {
return time.getTime() < this.condition.startTime || time.getTime() > Date.now();
} else {
return time.getTime() > Date.now();
}
},
},
}
},
created() {
this.tableData = []
for (let i = 1; i <=this.countTotal ; i++) {
this.tableData.push({"operateTime": this.$moment().format("YYYY-MM-DD HH:mm:ss"),"operator": "operator-"+i,"quantity": i,
"containerCode": "code-"+i,"headEnterTime": this.$moment().format("YYYY-MM-DD HH:mm:ss"),
"headOutTime": this.$moment().format("YYYY-MM-DD HH:mm:ss")})
}
},
watch:{
'$route'(val, oldVal) {
if (val.path === '/material_abnormal') {
this.search();
}
},
},
mounted() {
//同步找头区查询条件的时间参数
this.$set(this.condition,'startTime',this.selectedTime.startTime)
this.$set(this.condition,'endTime',this.selectedTime.endTime)
this.search();
this.watchSize();
this.lazyLoading();
},
activated() {
},
destroyed(){
this.setCondition()
},
computed: {
getPageBodyHeight() {
// console.log('计算结果:',this.pageHeight - this.otherHeight);
return this.pageHeight - this.otherHeight;
},
},
methods: {
search() {
Object.assign(this.queryCondition, this.condition);
if (this.queryCondition.startTime != null){
this.queryCondition.startTime = this.$moment(this.queryCondition.startTime).format("YYYY-MM-DDTHH:mm:ss")
}
if (this.queryCondition.endTime != null) {
this.queryCondition.endTime = this.$moment(this.queryCondition.endTime).add(1, 'days').format("YYYY-MM-DDTHH:mm:ss")
}
this.listPageData(1);
},
listPageData(page) {
const paginationData = {
page: page,
pageSize: this.pagination.pageSize,
};
// Object.assign(paginationData, this.queryCondition);
paginationData.condition = this.queryCondition;
console.log("paginationData",paginationData)
return;
this.loading=true;
/* this.$baseRequest.containerTypeApi.queryPageByCondition(paginationData).then(res =>{
this.loading=false;
this.tableData = res.data.data.list;
this.pagination.total = res.data.data.total;
})*/
},
defaultDataTime(){
this.condition = {
startTime: this.$moment().subtract(1,'months').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
endTime: this.$moment().startOf('day').format("YYYY-MM-DD HH:mm:ss"),
}
//this.$moment().add(1, 'days').format("YYYY-MM-DDTHH:mm:ss"),
},
tableDateFormat(row, column) {
return this.$moment(row.createTime).format("YYYY-MM-DD HH:mm:ss");
},
getRowKeys(row) {
return row.id;
},
handleSelectionChange(rowList){
let ids = [];
rowList.forEach(function (row) {
ids.push(row.id)
});
this.selectedIds = ids;
if (this.tableData().length === rowList.length){
this.checked = true
}else {
this.checked = false
}
},
//全选
selectAllBottom() {
if (this.checked){
this.$refs['headTableRef'].toggleAllSelection();
}else {
this.$refs['headTableRef'].clearSelection();
}
},
clearAll(){
this.condition.typeNameKeyWord = ''
this.condition.startTime = null
this.condition.endTime = null
},
setCondition(){
this.$emit('setTime',this.condition.startTime,this.condition.endTime);
},
lazyLoading(){
let dom = document.querySelector(".el-table__body-wrapper");
dom.addEventListener("scroll", (v) => {
const scrollDistance = dom.scrollHeight - dom.scrollTop - dom.clientHeight;
// console.log("鼠标滑动-scrollDistance",scrollDistance)
// if (scrollDistance <= 0) { //分辨率问题,如果设置 100% ,滑倒最底部,scrollDistance的值 可能为 0.201 到 -0.201
if (scrollDistance <= 1) {
//等于0证明已经到底,可以请求接口
if (this.pagination.page >= this.totalPage) {
//判断是否到达底部
// this.$message.warning("我~是有底线的 (~ ̄▽ ̄)~");
console.log("我~是有底线的 (~ ̄▽ ̄)~")
}
if (this.pagination.page < this.totalPage) {
//当前页数小于总页数就请求
this.pagination.page ++; //当前页数自增
console.log("页面已经到达底部,可以请求接口,请求第 "+ this.pagination.page + " 页数据");
var cIndex = this.countTotal + 10 ;
for (let i = (this.countTotal + 1) ; i <= cIndex ; i++) {
this.tableData.push({"operateTime": this.$moment().format("YYYY-MM-DD HH:mm:ss"),"operator": "operator-"+i,"quantity": i,
"containerCode": "code-"+i,"headEnterTime": this.$moment().format("YYYY-MM-DD HH:mm:ss"),
"headOutTime": this.$moment().format("YYYY-MM-DD HH:mm:ss")})
}
this.countTotal += 10 ;
//this.getData();
// this.append();
}
}
});
},
exportData(){
let elt = document.getElementById("exportData");
exportTableUtil.exportTable(elt,"加捻区物料异常表","加捻区物料异常表导出",2);
},
//页面适配
watchSize() {
const _this = this;
// 监听页面宽/高变化
_this.$watchSize().listenTo(this.$refs.css_page, (element) => {
_this.adapterPageHeight(_this)
});
// 监听头部(条件)宽/高变化
_this.$watchSize().listenTo(this.$refs.css_page_head, (element) => {
_this.adapterPageHeight(_this)
});
},
adapterPageHeight(_this){
_this.$nextTick(() => { // 这里填写监听改变后的操作
this.pageHeight = this.$refs.css_page.offsetHeight;
var css_page_head = this.$refs.css_page_head.offsetHeight;
this.otherHeight = css_page_head + 100;
// console.log('调整后:',this.otherHeight)
});
},
}
}
</script>
<style lang="scss">
.ProcessingInfo {
overflow-y: hidden;
width: 100%;
height: 100%;
.css_page_adapter{
width: 100%;
/*min-width: 1000px;*/
.css_page_head{
margin-top: 5px;
.css_title{
padding-bottom: 10px;
border-bottom: #e9e9e9 1px solid;
.css_title_text {
margin-left: 1%;
font-size: 20px;
padding: 5px 10px;
border: 4px none #0097FE;
border-left-style: solid;
color: #A9A9A9;
}
}
.css_page_condition{
margin-top: -5px;
.el-form-item--mini.el-form-item, .el-form-item--small.el-form-item {
margin-bottom: 12px;
}
}
}
.css_page_body{
}
.css_page_bottom {
height: 35px;
background-color: #e7e5e5;
border: 1px solid #dcd5d5;
}
}
}
</style>