前端Excel表格導入導出,包括合並單元格,表格自定義樣式等


表格數據導入

讀取導入Excel表格數據這里采用的是 xlsx 插件

  npm i xlsx

讀取excel需要通過 XLSX.read(data, {type: type}) 方法來實現,返回一個叫WorkBook的對象,type主要取值如下:
  • base64: 以base64方式讀取;
  • binary: BinaryString格式(byte n is data.charCodeAt(n))
  • string: UTF8編碼的字符串;
  • buffer: nodejs Buffer;
  • array: Uint8Array,8位無符號數組;
  • file: 文件的路徑(僅nodejs下支持);

同時需要用到插件自身的工具類XLSX.utils來對worksheet進行解析

  • XLSX.utils.sheet_to_csv:生成CSV格式
  • XLSX.utils.sheet_to_txt:生成純文本格式
  • XLSX.utils.sheet_to_html:生成HTML格式
  • XLSX.utils.sheet_to_json:輸出JSON格式

下面用一個 vue 的組件來演示一下
<template>
  <div class="read_excel_file">
    <slot></slot>
    <input
      class="file-input"
      ref="readexcel_input"
      type="file"
      :accept="SheetJSFT"
      @change="change"
    />
  </div>
</template>
<script>
import XLSX from 'xlsx'
export default {
  data() {
    return {
      SheetJSFT: '.xlsx',
    }
  },
  mounted() {
    // 綁定插槽點擊觸發導入文件
    if (this.$slots && this.$slots.default && this.$slots.default.length > 0) {
      this.$slots.default[0].elm.addEventListener(
        'click',
        this.openExcel.bind(this),
      )
    }
  },
  methods: {
    // 點擊打開導入excel
    openExcel() {
      let uploadBtn = this.$refs['readexcel_input']
      uploadBtn.click()
    },
    // 文件導入時
    change(evt) {
      const files = evt.target.files
      if (!/\.xlsx$/.test(files[0].name)) {
        this.$emit('validate', false)
        this.$emit('file-change', {
          name: files[0].name,
        })
        console.error('請選擇xlsx格式文件')
        return false
      } else {
        this.$emit('validate', true)
      }
      if (files && files[0]) this.file(files[0])
    },
    // 數據讀取
    file(file) {
      const reader = new FileReader()
      reader.onload = e => {
        const bstr = e.target.result
        const wb = XLSX.read(bstr, { type: 'binary' })  // 這里使用type為binary
        const wsname = wb.SheetNames[0]
        const ws = wb.Sheets[wsname]
        const data = XLSX.utils.sheet_to_json(ws, {
          header: 1,
          // blankrows: false,
        })  // 讀取json格式
        this.formatData(data, file.name, ws['!merges'])
      }
      reader.readAsBinaryString(file)
    },
    // 數據格式化
    formatData(list, name, merges) {
      let arr = []
      for (let i = 1; i < list.length; i++) {
        if (list[i].length > 0) {
          let obj = {}
          list[i].map((v, j) => {
            obj[list[0][j]] = v
          })
          arr.push(obj)
        }
      }
      this.$emit('file-change', {
        header: list[0],
        body: arr,
        name: name,
        merges: merges,
      })
      // 必須清空input的value屬性,不然第二次選擇同樣的文件,不會觸發change事件。
      this.$refs['readexcel_input'].value = ''
    },
  },
}
</script>
<style lang="scss" scoped>
.read_excel_file {
  display: inline-block;
  .file-input {
    width: 0;
    height: 0;
  }
}
</style>

組件的具體使用如下

<template>
  <div class="cs">
    <ReadExcel @file-change="excelFileChange">
      <div class="import-button">導入</div>
    </ReadExcel>
  </div>
</template>

<script>
import ReadExcel from './components/ReadExcel.vue'
export default {
  components: {
    ReadExcel,
  },
  methods: {
    excelFileChange(data) {
      let { merges, body, name, header } = data
      console.log(merges, body, name, header)
    },
  },
}
</script>

接下來,我們嘗試導入以下的表格


導出的數據(merges, body, name, header)如下

如此,便拿到了表格中的數據


導出excel表格

這里展示兩種導出表格的方法


1、xlsx插件導出

這里還是用到xlsx插件

這里直接做一個小demo展示

      function exportExcel(header, body, merges, wscols, wsrows, fileName) {
        body.unshift(header)
        const ws = XLSX.utils.aoa_to_sheet(body)
        const wb = XLSX.utils.book_new()
        ws['!cols'] = wscols
        ws['!rows'] = wsrows
        ws['!merges'] = merges
        XLSX.utils.book_append_sheet(wb, ws, 'Sheet1')
        // 生成excel
        XLSX.writeFile(wb, `${fileName}.xlsx`)
      }
      // 設置表格頭部
      let header = [
        '*手機號碼',
        '*訂單編號',
        '*商品編號',
        '*數量',
        '*訂單支付類型',
        '*訂單總金額¥',
        '*訂單實付金額¥',
      ]
      // 設置表格數據
      let body = [
        ['18600000002', 'svp000002', 'sp002', '1', '微信支付', '1100', '1100'],
        [undefined, undefined, 'sp003', '2', undefined, undefined, undefined],
        [undefined, undefined, 'sp004', '1', undefined, undefined, undefined],
      ]
      // 設置合並單元格
      let merges = [
        { e: { c: 0, r: 3 }, s: { c: 0, r: 1 } },
        { e: { c: 1, r: 3 }, s: { c: 1, r: 1 } },
        { e: { c: 4, r: 3 }, s: { c: 4, r: 1 } },
        { e: { c: 5, r: 3 }, s: { c: 5, r: 1 } },
        { e: { c: 6, r: 3 }, s: { c: 6, r: 1 } },
      ]
      // 指定每一列的寬度
      let wscols = [
        { wch: 20 },
        { wch: 20 },
        { wch: 30 },
        { wch: 30 },
        { wch: 30 },
        { wch: 30 },
        { wch: 30 },
      ]
      // 指定每一行的高度
      let wsrows = [{ hpx: 20 }]
      exportExcel(header, body, merges, wscols, wsrows, '生成excel文件')

導出結果如下


導出是導出成功,但是只有光禿禿的數據

那么這里如果要設置單元格的樣式該怎么做呢?

如果單純使用xlsx插件是無法設置單元格的樣式的,似乎有個xlsx的pro專業版可以做到,但是是收費的; 也有使用免費的xlsx-style實現設置樣式

npm i xlsx-style

用xlsx-style確實可以實現設置樣式,但是像我們一開始導入的excel文件中的表頭中(如下圖),有一個單元格中存在兩種不同顏色的文本的情況,

這種情況下,筆者用xlsx-style也實現不了,所以這里不詳訴xlsx-style的使用方法,我們試下第二種導出excel的方法


2、使用html table標簽導出excel

這里是用table標簽直接生成excel文件

直接上代碼

      function tableHtmlCompute(str) {
        return (
          "<html xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:x='urn:schemas-microsoft-com:office:excel'><head><!--[if gte mso 9]><xml>" +
          '<x:ExcelWorkbook>' +
          '<x:ExcelWorksheets>' +
          '<x:ExcelWorksheet>' +
          '<x:WorksheetOptions><x:Print><x:ValidPrinterInfo /></x:Print></x:WorksheetOptions>' +
          '</x:ExcelWorksheet>' +
          '</x:ExcelWorksheets>' +
          '</x:ExcelWorkbook></xml><![endif]--> ' +
          '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head><body>' +
          '<table border="1" cellspacing="1" cellpadding="1">' +
          '<tr>' + // 這里是表頭
          '<td style="background:rgb(217,225,242);mso-number-format:@"><span style="color: rgb(255,0,0)">*</span><span>手機號碼</span></td>' +
          '<td style="background:rgb(217,225,242);mso-number-format:@"><span style="color: rgb(255,0,0)">*</span><span>訂單編號</span></td>' +
          '<td style="background:rgb(217,225,242);mso-number-format:@"><span style="color: rgb(255,0,0)">*</span><span>商品編號</span></td>' +
          '<td style="background:rgb(217,225,242);mso-number-format:@"><span style="color: rgb(255,0,0)">*</span><span>數量</span></td>' +
          '<td style="background:rgb(217,225,242);mso-number-format:@"><span style="color: rgb(255,0,0)">*</span><span>訂單支付類型</span></td>' +
          '<td style="background:rgb(217,225,242);mso-number-format:@"><span style="color: rgb(255,0,0)">*</span><span>訂單總金額¥</span></td>' +
          '<td style="background:rgb(217,225,242);mso-number-format:@"><span style="color: rgb(255,0,0)">*</span><span>訂單實付金額¥</span></td>' +
          '</tr>' +
          '<tr>' +
          '<td style="background:rgb(217,225,242);mso-number-format:@"><span style="color: rgb(255,0,0)">必填項</span><br style="mso-data-placement:same-cell"/><span>1、學員購買商品收貨填寫的手機號</span></td>' +
          '<td style="background:rgb(217,225,242);mso-number-format:@"><span style="color: rgb(255,0,0)">必填項</span><br style="mso-data-placement:same-cell"/><span>1、填寫導入的訂單在售賣平台的訂單編號</span></td>' +
          '<td style="background:rgb(217,225,242);mso-number-format:@"><span style="color: rgb(255,0,0)">必填項</span><br style="mso-data-placement:same-cell"/><span>1、上架商品時候填寫的“售賣平台商品編號”</span></td>' +
          '<td style="background:rgb(217,225,242);mso-number-format:@"><span style="color: rgb(255,0,0)">必填項</span><br style="mso-data-placement:same-cell"/><span>1、購買商品的總數量</span><br style="mso-data-placement:same-cell"/><span>2、填寫的數量>=1,且為整數。</span></td>' +
          '<td style="background:rgb(217,225,242);mso-number-format:@"><span style="color: rgb(255,0,0)">必填+單選</span><br style="mso-data-placement:same-cell"/><span>1、選項</span><br style="mso-data-placement:same-cell"/><span>微信支付</span><br style="mso-data-placement:same-cell"/><span>支付寶支付</span><br style="mso-data-placement:same-cell"/><span>網銀支付</span><br style="mso-data-placement:same-cell"/><span>pos機支付</span><br style="mso-data-placement:same-cell"/><span>銀行匯款</span></td>' +
          '<td style="background:rgb(217,225,242);mso-number-format:@"><span style="color: rgb(255,0,0)">必填項</span></td>' +
          '<td style="background:rgb(217,225,242);mso-number-format:@"><span style="color: rgb(255,0,0)">必填項</span></td>' +
          '</tr>' +
          str +
          '</table></body></html>'
        )
      }
      function excelExport(str) {
        let html = tableHtmlCompute(str)
        let blob = new Blob([html], {
          type: 'text/plain;charset=utf-8',
        })
        //解決中文亂碼問題
        blob = new Blob([String.fromCharCode(0xfeff), blob], {
          type: blob.type,
        })
        let a = document.createElement('a')
        a.style.display = 'none'
        // 利用URL.createObjectURL()方法為 a 元素生成 blob URL
        a.href = URL.createObjectURL(blob)
        // 設置文件名
        a.download = 'excel名字.xlsx'
        document.body.appendChild(a)
        a.click()
        document.body.removeChild(a)
      }
      let body = [
        {
          mobile: '18600000002',
          orderNumber: 'svp000002',
          products: [
            {
              productNumber: 'sp002',
              quantity: 1,
            },
            {
              productNumber: 'sp003',
              quantity: 2,
            },
            {
              productNumber: 'sp004',
              quantity: 1,
            },
          ],
          paymentType: '微信支付',
          total: 1100,
          paymenteds: 1100,
        },
      ]
      let header = [
        'mobile',
        'orderNumber',
        'productNumber',
        'quantity',
        'paymentType',
        'total',
        'paymenteds',
      ]
      let productsHeader = ['productNumber', 'quantity']
      let html = ''
      body.forEach(e => {
        if (e.products && e.products.length) {
          e.products.forEach((item, i) => {
            let str = '<tr>'
            if (i) {
              productsHeader.forEach(key => {
                str += `<td style="mso-number-format:\\@"><span>${
                  item[key] ? item[key] : ''
                }</span></td>`
              })
              str += '</tr>'
            } else {
              // i===0;每一條數據的頭部
              header.forEach(key => {
                if (productsHeader.includes(key)) {
                  str += `<td style="mso-number-format:\\@"><span>${
                    item[key] ? item[key] : ''
                  }</span></td>`
                } else {
                  let val = e[key] ? e[key] : ''
                  str += `<td style="mso-number-format:\\@"  rowspan=${e.products.length}><span>${val}</span></td>`
                }
              })
              str += '</tr>'
            }
            html += str
          })
        }
      })
      excelExport(html)

導出結果如下

這里有以下幾個注意點

1、上面有展示了如何在同一單元格內換行,如果需要在同一單元格內換行,需要用到以下代碼

  <br style="mso-data-placement:same-cell"/>

如果單純只寫br標簽,不加后面的style="mso-data-placement:same-cell", 則它會出現兩個單元格合並在一起的情況

2、style="mso-number-format:\@" 這個樣式可以讓單元格的格式被識別為文本,避免不必要的格式自動轉換


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM