記錄--前端如何優雅導出多表頭xlsx

来源:https://www.cnblogs.com/smileZAZ/archive/2023/06/15/17483787.html
-Advertisement-
Play Games

這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 xlsx導出是比較前後端開發過程中都比較常見的一個功能。但傳統的二維表格可能很難能滿足我們對業務的需求,因為當數據的維度和層次比較多時,二維表格很難以清晰和壓縮的方式展現所有的信息,所以我們也就經常能碰到多級表頭開發了。 demo ...


這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

前言

xlsx導出是比較前後端開發過程中都比較常見的一個功能。但傳統的二維表格可能很難能滿足我們對業務的需求,因為當數據的維度和層次比較多時,二維表格很難以清晰和壓縮的方式展現所有的信息,所以我們也就經常能碰到多級表頭開發了。

demo

每當我們新使用一個插件的時候,我們都可以看著官方文檔去新建立一個demo,然後去嘗試一下效果,這有助於我們分析錯誤。

npm i xlsx -S
function exportFile() {
  const ws = utils.json_to_sheet([])
  const wb = utils.book_new()
  utils.sheet_add_aoa(ws, [
    [1, 2, 3, 4, 5, 6, 7, 8, 9], 
    ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
  ], { origin: 'A1' })
  utils.book_append_sheet(wb, ws, 'Data')
  writeFileXLSX(wb, 'SheetJSVueAoO.xlsx')
}
exportFile()

demo已經成功了,xlsx已經下載下來了。

需求分析

  1. 新建一個表格
  2. 根據表頭將表格進行合併
  3. 對合併後的表頭進行內容填充
  4. 填入數據內容

效果如上圖(時間原因就先不寫xlsx的樣式了)。

需求實現

  1. 合併單元格: 需要指定開始的行和列以及結束的行和列,如{ 's': { 'r': 0, 'c': 0 }, 'e': { 'r': 3, 'c': 0 } },計算好需要合併的單元格後統一賦值給!merges屬性。
  2. 合併單元格後填充內容:由多個合併後的單元格填入內容時,應該也按照多個單元格填入,只是第一個有內容,其他按空填入即可。
  3. 表頭結束後我們可以指定在某一行繼續填入內容,即可繼續填入數據內容。
function exportFile() {
  const ws = utils.json_to_sheet([])
  ws['!merges'] = [
    { 's': { 'r': 0, 'c': 0 }, 'e': { 'r': 3, 'c': 0 } },
    { 's': { 'r': 0, 'c': 1 }, 'e': { 'r': 3, 'c': 1 } },
    { 's': { 'r': 0, 'c': 2 }, 'e': { 'r': 3, 'c': 2 } },
    { 's': { 'r': 0, 'c': 3 }, 'e': { 'r': 0, 'c': 8 } },
    { 's': { 'r': 1, 'c': 3 }, 'e': { 'r': 3, 'c': 3 } },
    { 's': { 'r': 1, 'c': 4 }, 'e': { 'r': 1, 'c': 7 } },
    { 's': { 'r': 2, 'c': 4 }, 'e': { 'r': 3, 'c': 4 } },
    { 's': { 'r': 2, 'c': 5 }, 'e': { 'r': 3, 'c': 5 } },
    { 's': { 'r': 2, 'c': 6 }, 'e': { 'r': 2, 'c': 7 } },
    { 's': { 'r': 1, 'c': 8 }, 'e': { 'r': 3, 'c': 8 } },
    { 's': { 'r': 0, 'c': 9 }, 'e': { 'r': 3, 'c': 9 } }
  ] // 合併單元格內容
  const wb = utils.book_new()
  utils.book_append_sheet(wb, ws, 'Data')
  utils.sheet_add_aoa(ws, [
    ['序號', '姓名', '性別', '公司概況', '', '', '', '', '', '備註'],
    ['', '', '', '職位', '項目', '', '', '', '公司名稱'],
    ['', '', '', '', '項目時長', '項目描述', '金額', ''],
    ['', '', '', '', '', '', '總金額', '利潤']
  ], { origin: 'A1' }) // 表頭內容
  utils.sheet_add_aoa(ws, [
    [0, '張三', '男', '區域經理', '3天', '暫無描述', 998, 9.98, '阿裡巴巴', '暫無'],
    [1, '李四', '女', 'CEO', '30天', '穩了', 998, 9.98, '中石油', '暫無']
  ], { origin: 'A5' }) // 數據內容
  writeFileXLSX(wb, `${+new Date()}.xlsx`)
}
好的,大功告成,今天就先到這裡?

這東西也太醜了吧,我是一個開發,我不是來這裡數格子的。看看上面的代碼,我都不好意思說是我自己寫的。要不到同事電腦上提交一下吧?

數據分析

[
    { 's': { 'r': 0, 'c': 0 }, 'e': { 'r': 3, 'c': 0 } },
    { 's': { 'r': 0, 'c': 1 }, 'e': { 'r': 3, 'c': 1 } },
    { 's': { 'r': 0, 'c': 2 }, 'e': { 'r': 3, 'c': 2 } },
    { 's': { 'r': 0, 'c': 3 }, 'e': { 'r': 0, 'c': 8 } },
    { 's': { 'r': 1, 'c': 3 }, 'e': { 'r': 3, 'c': 3 } },
    { 's': { 'r': 1, 'c': 4 }, 'e': { 'r': 1, 'c': 7 } },
    { 's': { 'r': 2, 'c': 4 }, 'e': { 'r': 3, 'c': 4 } },
    { 's': { 'r': 2, 'c': 5 }, 'e': { 'r': 3, 'c': 5 } },
    { 's': { 'r': 2, 'c': 6 }, 'e': { 'r': 2, 'c': 7 } },
    { 's': { 'r': 1, 'c': 8 }, 'e': { 'r': 3, 'c': 8 } },
    { 's': { 'r': 0, 'c': 9 }, 'e': { 'r': 3, 'c': 9 } }
]

我想要轉成上面的數據結構,r從0開始,最大值就是它的深度,c從0開始,最大值就是它的廣度。因為這是一個多級表頭,每一級都會出現比上一級相等或更多子級的情況,我好像已經把答案說到嘴邊了。對,就是用樹形結構將其轉換處理。

我們結合上面已轉換好的列表結構和下麵準備轉換的樹形結構,比如現在要合併第一個單元格序號,我們應該先找到起始位置,也就是0,0,這個很好確定;我們單單從當前節點並不能判斷真正的結束位置,我們應該找到同級節點的最大深度,也就是公司概況->項目->金額->總金額,深度為3。所以它的結束位置應該為3,0

當我們要合併橫向單元格的時候,比如公司概況,它下邊有三個子節點分別是職位,項目,公司名稱,而子節點下方仍有不同的子節點,此時我們就應該去獲取它們的每個子節點的每層子節點的總長度 - 1,為什麼要 - 1,因為當前節點和第一個子節點占用的是同一個col,因此可以需要減一。也就是說,如果公司概況的起始點為0,3,那麼它的終止位置由此可推:職位+項目+公司名稱-1+項目時長+項目描述+金額-1+總金額+利潤-1 = 5。所以終點位置為0,3+5 => 0,8

const mergedCells = [ 
    { name: '序號', prop: 'id' },
    { name: '姓名', prop: 'name' },
    { name: '性別', prop: 'sex' },
    { 
        name: '公司概況', 
        children: [ 
            { name: '職位', prop: 'jobTitle' },
            { 
                name: '項目', children: [
                    { name: '項目時長', prop: 'projectTime' },
                    { name: '項目描述', prop: 'projectDesc' },
                    { 
                        name: '金額', 
                        children: [
                            { name: '總金額', prop: 'total' },
                            { name: '利潤', prop: 'profit' }
                        ] 
                    } 
                ] 
            }, 
            { name: '公司名稱', prop: 'companyName' }
        ] 
    },
    { name: '備註', prop: 'remark' } 
]

思路分析

  1. 找到當前節點的深度和廣度
  2. 根據當前節點深度和廣度,生成當前節點單元格開始與結束位置
  3. 根據當前節點深度和廣度,生成表頭數據結構
  4. 根據最大深度位置,生成表單列表數據

代碼實現

tips: 如果你對樹結構的遍歷還不太熟悉,可以看看【前端不求人】樹形結構和一維數組,一笑泯恩仇

獲取當前節點最大廣度和最大深度

  1. 遞歸發現當前已無子節點時,就返回0,然後每返回一層就遞增1,每次返回時都獲取當前節點的最大值,這樣就能獲得最深層數。
  2. 遞歸記錄每層每個子節點的長度 - 1,這樣就能獲取當前列表的最大寬度。
  3. 我們使用map做記錄,下次獲取就不需要重新計算了。
const map = new Map()
const getCellsSize = list => {
    if (map.has(list)) { return map.get(list) }
    if (list?.length) {
        let rows = -1, cols = list.length - 1
        list.forEach(item => {
            if (item.children) {
                const size = getCellsSize(item.children)
                rows = Math.max(size[0], rows)
                cols += size[1]
            }
        })
        map.set(list, [rows + 1, cols])
        return [rows + 1, cols]
    }
}

合併單元格開始和結束位置

  1. 獲取當前節點的開始和結束位置
  2. 當前節點無子節點,單元格寬為1,高為整個根節點的最大深度
  3. 當前節點有子節點,單元格高為1,寬為當前節點的寬,即最大廣度
const size = getCellsSize(headers)
const headerMerge = []
const mergeHeadersCell = (headers, row, col) => {
    for (let i = 0, len = headers.length;i < len;i++) {
        const cell = headers[i]
        if (!cell.children?.length) {
            if (row === size[0]) { continue }
            headerMerge.push({ s: { r: row, c: col + i }, e: { r: size[0], c: col + i } })
        } else {
            const size = map.get(cell.children)
            headerMerge.push({ s: { r: row, c: col + i }, e: { r: row, c: col + size[1] + i }})
            mergeHeadersCell(cell.children, row + 1, col + i)
            col += size[1]
        }
    }
}

多表頭值填充

  1. 我們聲明一個headerValue的空數組來記錄表頭內容
  2. headerValue應該是一個二維數組,headerValue[i][j]代表第i行第j列的內容
  3. 當發現當前節點有children,直接獲取當前節點的寬度,該寬度就是合併後空白單元格的個數。
  4. 當發現當前節點並沒有headerValue,表示前面的節點被縱向合併了,因此應該直接加上這些空白單元格的節點
  const headerValue = []
  const getHeadersValue = (headers, row, col) => {
    if (!headerValue[row]) {
      headerValue[row] = new Array(col).fill('')
    }
    for (let i = 0, len = headers.length; i < len; i++) {
      const cell = headers[i]
      headerValue[row].push(cell.name)
      if (cell.children?.length) {
        const len = getCellsSize(cell.children)[1]
        const emptyNameList = new Array(len).fill('')
        headerValue[row].push(...emptyNameList)
        getHeadersValue(cell.children, row + 1, col + i)
      }
    }
  }

獲取列表prop

  1. 繼續遞歸mergedCells
  2. 收集無葉子節點的prop值
  3. 將prop值依次放進一個數組中以備後續使用
const bodyMapList = []
const getBodyMapList = list => {
    if (list?.length) {
        list.forEach(item => {
            !item.children ? bodyMapList.push(item.prop) : getBodyMapList(item.children)
        })
    }
}

list.map(item => bodyMapList.map(key => item[key]))

以上就是核心代碼展示啦,如果想看完整代碼,可以到github觀看,歡迎star。

總結

我們通過計算當前樹節點的大小,就可以獲取該節點的廣度和深度,通過廣度和深度又可以讓我們進一步去演算當前節點是否需要去合併其他單元格,是否需要生成空白單元格的數據內容。生成表格內容則只需要將最子層節點的prop收集,然後對應取值即可。

本文轉載於:

https://juejin.cn/post/7243435843145678907

如果對您有所幫助,歡迎您點個關註,我會定時更新技術文檔,大家一起討論學習,一起進步。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 大家好,我是 god23bin。歡迎來到《一分鐘學一個 Linux 命令》系列,每天只需一分鐘,記住一個 Linux 命令不成問題。今天需要你花兩分鐘時間來學習下,因為今天要介紹的是兩個常用的搜索命令:find 和 grep 命令。 ...
  • 從北京到新疆、從中國到南非,從奧運會場館的水泥混凝土到錢塘江畔拔地而起的高樓房產,無不存在著這個集團的身影。 歷經60多年滄桑巨變,某市屬大型國有控股建築產業集團已發展成為一家中國500強企業、A+H整體上市公司,然而,該集團的實力與決心,遠不限於此。 為讓數據更好支撐業務,該集團與袋鼠雲合作,通過 ...
  • 摘要:雲資料庫RDS for MySQL支持參數模板的導入和導出功能。 本文分享自華為雲社區《【雲小課】【第56課】RDS for MySQL參數模板一鍵導入導出,參數配置輕鬆搞定》,作者:資料庫的小雲妹。 雲資料庫RDS for MySQL支持參數模板的導入和導出功能。 導入參數模板:導入後會生成 ...
  • # 基於Elasticsearch 為電商提供商品數據大數據查詢 # 前言 對於現代電商的產品,維度的多員花,與一套強大的搜索引擎,那是非常必要的。今天我們主要是描述我們在從事電商搜索引擎過程中的遇到的一些問題和經驗分享。 # 過程 ### 數據準備 >> 1、我們準備為我們需要做查找的數據做好一張 ...
  • 消息推送作為App運營日常使用的用戶促活和召回手段,是與用戶建立持續互動和連接的良好方式。[推送服務](https://developer.huawei.com/consumer/cn/hms/huawei-pushkit?ha_source=hms1)(Push Kit)是華為提供的消息推送平臺, ...
  • > Kotlin的協程自推出以來,受到了越來越多Android開發者的追捧。另一方面由於它龐大的API,也將相當一部分開發者拒之門外。本篇試圖從協程的幾個重要概念入手,在複雜API中還原出它本來的面目,以全新的角度帶讀者走進Kotlin協程世界。 ### 什麼是協程 在很多有關協程的文章中,描述協程 ...
  • ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/44a0e0fd567c4421bc94be83e84f6dce~tplv-k3u1fbpfcp-zoom-1.image) ## 一、版本說明 XCode 15 beta 發佈於 2023 ...
  • # 一、需求 1、瞭解IMS相關知識體系 2、RILD 與 RILJ、IMS回調消息的機制 # 二、相關概念 ## 2.1 IMS IMS全稱是IP Multimedia Subsystem,中文意義為IP多媒體子系統。IMS是一種基於IP基礎結構,能夠融合數據、話音和移動等網路技術的系統。 **I ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...