vue table复杂表格导出excel(支持多表头、合并单元格、边框、居中、背景等自定义样式)

目录

需要导出的table表格样式

安装

vue代码及JS实现片段

栗子


需要导出的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>

参考链接

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值