1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Node.js + Vue 實(shí)現(xiàn) Excel 導(dǎo)出與保存

        共 18900字,需瀏覽 38分鐘

         ·

        2021-07-18 09:33

        (給前端大學(xué)加星標(biāo),提升前端技能.)
        作者:可愛(ài)子
        https://juejin.cn/post/6953882449235410951

        我們的項(xiàng)目是前端用vue,服務(wù)端用node.js,這個(gè)excel導(dǎo)出我們已經(jīng)用了一年,我們目前用到的無(wú)非是圖片導(dǎo)出,文本導(dǎo)出,調(diào)調(diào)excel單元格距離等.

        這個(gè)node端的封裝是經(jīng)過(guò)同事不斷的review(感謝同事),俺不斷的修改優(yōu)化后的代碼,當(dāng)時(shí)也是我第一次接觸node.js,只想告訴你,用它,穩(wěn)穩(wěn)的!

        node.js服務(wù)端代碼

        1.拿到需要導(dǎo)出的數(shù)據(jù)之后如何使用(假數(shù)據(jù)模擬,下面是頁(yè)面)

        image.png
         constructor(prop) {
            super(prop)
            // 定義excel頭部數(shù)據(jù)
            this.header = [
              { header'游戲'key'gameInfo'width30 },
              { header'宣傳圖片'key'image'width15 },
              { header'游戲詳情頁(yè)'key'path'width15 },
              { header'狀態(tài)'key'activeStatus'width30 },
              { header'排序權(quán)重'key'sort'width30 },
              { header'最近編輯時(shí)間'key'updateTime'width30 },
              { header'最近編輯人'key'operatorName'width30 },
            ]
          }
         /**
           * 導(dǎo)出游戲管理數(shù)據(jù)
           */

          async exportGameEndGameManage() {
            const { list } = await this.commonGameEndGameManage(true)
            console.log(list, 'list')
            const baseExcelInfo = {
              data: list,
              filename'gameManageList',
              headerthis.header,
              sheetName'游戲管理列表',
              imageKeys: [
                {
                  name'image',
                  imgWidth'100',
                  imgHeight'100',
                },
              ],
            }
            await this.service.common.exportFile.exportExcel(baseExcelInfo)
          }
        復(fù)制代碼

        list就是拿到的數(shù)據(jù),打印如下
        baseExcelInfo用來(lái)定義基本的參數(shù)
        data 表示的是excel的數(shù)據(jù)來(lái)源
        filename 是文件名(但是前端的excel導(dǎo)出會(huì)進(jìn)行覆蓋)
        header表示的是表格的頭部
        sheetName表示的是excel的表名
        imageKeys:圖片的信息:字段名稱(chēng),圖片的寬高,但是只要有圖片,name必須設(shè)

        image.png

        很重要的一點(diǎn)就是,假設(shè)從表里面的數(shù)據(jù)返回的status是1,那么我肯定導(dǎo)出的不能1,應(yīng)該是對(duì)應(yīng)的一個(gè)中文,所以在導(dǎo)出前,應(yīng)該進(jìn)行處理,這個(gè)處理應(yīng)該是在服務(wù)端來(lái)做,而不是前端做一遍,然后為了導(dǎo)出這個(gè)功能重新做一遍舉個(gè)例子

        /**
           *  公共游戲管理數(shù)據(jù)
           *  @param { Boolean } isExport 是否導(dǎo)出
           */

          async commonGameEndGameManage(isExport) {
            const activeStatus = { // 這個(gè)按道理寫(xiě)在constructor里面哈
              1'打開(kāi)',
              2'關(guān)閉',
            }
            const { ctx, app } = this
            const { limit, offset } = this.paginationDeal(ctx.request.query)
            const isPagi = isExport ? {} : { limit, offset }
            const { list, total } = await ctx.service.operateManage.gameEndPage.
            getGameEndGameManage({ isPagi })
            const data = list.map(node => {
              const { status, ...params } = node.toJSON()
              const activeStatus = activeStatus[status]
              return { activeStatus, status, ...params }
            })
            return { list: data, total }
          }
        復(fù)制代碼

        2.exportExcel的封裝

        首先安裝對(duì)應(yīng)的包 npm install exceljs --save
        然后復(fù)制下面的代碼就好了


        'use strict'

        const Service = require('egg').Service
        // 引入exceljs
        const Excel = require('exceljs')

        // 導(dǎo)出文件相關(guān)服務(wù)
        class exportFileService extends Service {
          constructor(prop) {
            super(prop)
            this.defaultViews = [
              {
                x0,
                y0,
                width10000,
                height20000,
                firstSheet0,
                activeTab1,
                visibility'visible',
              },
            ]
            this.fontName = 'Arial Unicode MS'
            this.font = { namethis.fontName, family4size13 }
            this.fill = { type'pattern'pattern'solid'fgColor: { argb'FF8DB4E2' } }
            this.border = { style'thin'color: { argb'cccccc' } }
          }
          /**
           * 導(dǎo)出excel
           * @param { Object } config 傳入的excel對(duì)象
           * @param { Array } config.data excel的數(shù)據(jù)
           * @param { String } config.filename excel文件名
           * @param { Array } config.header excel的頭部
           * @param { String } config.sheetName 表名
           * @param { Array } config.imageKeys 需要轉(zhuǎn)化圖片的key
           * @param { String } config.creator 創(chuàng)建表的人
           * @param { String } config.lastModifiedBy 最后修改表的人
           * @param { String } config.imageKeys.imgWidth 圖片的寬度
           * @param { String } config.imageKeys.imgHeight 圖片的高度
           * */

          async exportExcel({
            data = [],
            filename = 'file',
            header,
            sheetName = 'sheet1',
            imageKeys = [],
            creator = 'me',
            lastModifiedBy = 'her',
          }) {
            const { ctx } = this
            const workbook = new Excel.Workbook()
            // 設(shè)置屬性 -創(chuàng)建著以及最后修改的人
            workbook.creator = creator
            workbook.lastModifiedBy = lastModifiedBy

            // 時(shí)間獲取一次就好
            const now = new Date()
            workbook.created = now
            workbook.modified = now
            workbook.lastPrinted = now
            const worksheet = workbook.addWorksheet(sheetName)
            // 設(shè)置打開(kāi)時(shí)候的視圖-設(shè)置位置
            workbook.views = this.defaultViews
            // 使工作表可見(jiàn)
            worksheet.state = 'visible'
            worksheet.columns = header

            for (let i = 1; i <= header.length; i++) {
              worksheet.getColumn(i).alignment = { vertical'middle'horizontal'center' }
              worksheet.getColumn(i).font = { name'Arial Unicode MS' }
            }
            worksheet.addRows(data)
            // 處理圖片
            const imageList = this.getImageList(imageKeys, data, header)
            // 添加圖片到sheet
            await this.addPicToSheet(imageList, imageKeys, workbook, worksheet)
            // 多級(jí)表頭
            const headerOPtion = header.filter((item, index) => {
              if (item.type && item.type === 'multi') {
                header.splice(index, 1)
                return item
              }
              return item.type && item.type === 'multi'
            })
            // 多級(jí)表頭重置設(shè)置表頭
            if (headerOPtion.length) {
              headerOPtion[0].headerText.forEach((text, index) => {
                const borderAttr = { topthis.border, left
                this.border, bottomthis.border, rightthis.border, index }
                const headerAttr = [
                  {
                    attr'values',
                    value: text,
                  },
                  {
                    attr'font',
                    valuethis.font,
                  },
                  {
                    attr'fill',
                    valuethis.fill,
                  },
                  {
                    attr'border',
                    value: borderAttr,
                  },
                ]
                headerAttr.map(item => {
                  worksheet.getRow(index + 1)[item.attr] = item.value
                  return worksheet
                })
              })
              headerOPtion[0].mergeOption.forEach(merge => {
                worksheet.mergeCells(merge)
              })
            } else {
              // 設(shè)置表頭樣式
              worksheet.getRow(1).font = this.font
              worksheet.getRow(1).fill = this.fill
            }
            const bufferContent = await workbook.xlsx.writeBuffer()

            // 設(shè)置
            ctx.set('Content-disposition'`attachment;filename=${filename}.xlsx`)
            // 返回文件buffer
            ctx.body = bufferContent
          }
          // 設(shè)置圖片大小
          getImageList(imageKeys, data, header) {
            return imageKeys.map(
              key => data.map(
                (item, index) => ({
                  key,
                  url: item[key.name],
                  colthis.app.utils.index.getIndexByKey(header, key.name) + 1,
                  row: index + 2,
                  width: key.imgWidth,
                  height: key.imgHeight,
                })
              )
            )
          }
          // 添加圖片到sheet
          async addPicToSheet(imageList, imageKeys, workbook, worksheet) {
            if (imageKeys.length > 0) {
              await Promise.all(imageList.map(async imgArr => {
                return await Promise.all(imgArr.map(item => {
                  const { url, width, height, row, col } = item
                  // 因?yàn)橛械膱D片是不存在的需要判斷
                  if (url) {
                    return this.app.utils.index.getBase64(url, this.ctx).then(res => {
                      if (!url) return
                      const imgType = url.split('?')[0].substring(url.split('?')[0].
                      lastIndexOf('.') + 1).toLowerCase()
                      const id = workbook.addImage({
                        base64: res,
                        extension: imgType,
                      })
                      worksheet.addImage(id, {
                        tl: { col: col - 1row: row - 1 },
                        ext: { width, height },
                      })
                      worksheet.getRow(row).height = height
                      // // 去掉背景鏈接
                      worksheet.getRow(row).getCell(item.key.name).value = ''
                    })
                  }
                  return item
                }))
              }))
            }
          }
        }

        module.exports = exportFileService
        復(fù)制代碼

        3.調(diào)用下載接口后node.js返回的信息

        前端看到的就是一個(gè)二進(jìn)制文件流

        image.png
        image.png

        推薦了解 content-type: application/octet-stream

        前端代碼

        接口

        // 文件導(dǎo)出
        export function exportFile(url, params{
          return request({
            responseType'blob',
            headers: {
              'Content-Type''application/json',
            },
            timeout1000 * 60,
            url: url,
            method'get',
            params: {
              query: qs.stringify(params),
            },
          })
        }
        復(fù)制代碼

        utils

        /**
         * 本地保存文件并導(dǎo)出
         * @param { Object } Obj 導(dǎo)出文件參數(shù)對(duì)象
         * @param { Blob } file 文件資源
         * @param { String } fileName 文件名稱(chēng)(注意:包含后綴)
         */

        export function loacalSaveFile({ file, fileName, option = { type: 'application/vnd.ms-excel' }}{
          const ieKit = judgeBrowser('ie')
          const blobData = new Blob([file], option) // 生成 Blob文件
          if (ieKit && navigator.msSaveBlob) {
            navigator.msSaveBlob && navigator.msSaveBlob(blobData, fileName)
          } else {
            // 其他
            const save_link = document.createElement('a')
            const url = URL.createObjectURL(file) // 創(chuàng)建url
            save_link.href = url
            save_link.download = fileName
            document.body.appendChild(save_link)
            save_link.click()
            setTimeout(() => {
              document.body.removeChild(save_link)
              window.URL.revokeObjectURL(url) // 回收url
            }, 0)
          }
        }
        復(fù)制代碼

        調(diào)用

        const file = await exportFile(this.exportItem.apiUrl, data)
        loacalSaveFile({ file, fileName`${this.exportItem.fileName}.xlsx` })
        復(fù)制代碼

        效果


        點(diǎn)贊和在看就是最大的支持??

        瀏覽 26
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            《乳色吐息》免费 | 伊人综合视频 | 五十路熟妇无码AV在线 | 中文字幕精品一区二区三区精品 | 色色网站 | 日逼99 | 神尾舞BEST神尾舞 | 性生活特级片 | 中国老太卖婬hd最新版 | 国产尤物一区二区三区 |