Parcourir la source

feat: 业务模块导出功能

chenlei il y a 2 mois
Parent
commit
264c637e6f

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 31293
package-lock.json


+ 1 - 0
package.json

@@ -20,6 +20,7 @@
     "docxtemplater": "^3.61.1",
     "docxtemplater-image-module-free": "^1.1.1",
     "echarts": "^5.6.0",
+    "exceljs": "^4.4.0",
     "file-saver": "^2.0.5",
     "js-base64": "^3.7.3",
     "js-export-excel": "^1.1.4",

BIN
public/templates/1.docx


BIN
public/templates/10.docx


BIN
public/templates/11.docx


BIN
public/templates/2.docx


BIN
public/templates/3.docx


BIN
public/templates/4.docx


BIN
public/templates/8.docx


BIN
public/templates/9.docx


+ 19 - 17
src/components/MyTable/index.tsx

@@ -40,6 +40,24 @@ export interface MyTableMethods {
   form: FormInstance<any>
 }
 
+export const myTableTransferSize = (item: any) => {
+  let danWei = resJiLianFu(item.sizeUnit, ' ')
+
+  let txt1 = item.sizeL ? `通长${item.sizeL}` : ''
+  let txt2 = item.sizeW ? `通宽${item.sizeW}` : ''
+  let txt3 = item.sizeH ? `通高${item.sizeH}` : ''
+
+  txt1 = txt1 ? txt1 + danWei : ''
+  txt2 = txt2 ? txt2 + danWei : ''
+  txt3 = txt3 ? txt3 + danWei : ''
+
+  let arr = [txt1, txt2, txt3]
+  arr = arr.filter(v => v)
+
+  if (!txt1 && !txt2 && !txt3) return '(空)'
+  else return arr.join(' - ')
+}
+
 const MyTable = forwardRef<MyTableMethods, MyTableProps>(
   (
     {
@@ -145,23 +163,7 @@ const MyTable = forwardRef<MyTableMethods, MyTableProps>(
           txtC: (item: any) =>
             v[1] === '年代' && item[v[2]] === '其他' ? '其他' : resJiLianFu(item[v[2]]),
           // 尺寸
-          size: (item: any) => {
-            let danWei = resJiLianFu(item.sizeUnit, ' ')
-
-            let txt1 = item.sizeL ? `通长${item.sizeL}` : ''
-            let txt2 = item.sizeW ? `通宽${item.sizeW}` : ''
-            let txt3 = item.sizeH ? `通高${item.sizeH}` : ''
-
-            txt1 = txt1 ? txt1 + danWei : ''
-            txt2 = txt2 ? txt2 + danWei : ''
-            txt3 = txt3 ? txt3 + danWei : ''
-
-            let arr = [txt1, txt2, txt3]
-            arr = arr.filter(v => v)
-
-            if (!txt1 && !txt2 && !txt3) return '(空)'
-            else return arr.join(' - ')
-          },
+          size: myTableTransferSize,
           img: (item: any) =>
             v[3] && !item[v[2]] ? (
               <div dangerouslySetInnerHTML={{ __html: v[3] }}></div>

+ 2 - 1
src/pages/B_enterTibet/B1collect/B1edit/index.tsx

@@ -29,6 +29,7 @@ import ZupFileTable from '@/components/ZupFileTable'
 import { FourTableType, TypeinfoXLSX } from '../type'
 import B1upXLSX from './B1upXLSX'
 import ZupFile from '@/components/ZupFile'
+import { EXPORT_WORD_ENUM } from '@/utils/exportTemplates'
 
 function B1edit() {
   const { key, id } = useParams<any>()
@@ -258,7 +259,7 @@ function B1edit() {
           </Button>
         ) : null}
 
-        {EXbtnFu(topInfo)}
+        {EXbtnFu(topInfo, [EXPORT_WORD_ENUM.COLLECTION_LIST])}
 
         {btnFlagFu2(topInfo)['删除'] ? (
           <MyPopconfirm

+ 2 - 1
src/pages/B_enterTibet/B2identify/B2edit/index.tsx

@@ -30,6 +30,7 @@ import classNames from 'classnames'
 import B2MoAdd from './B2MoAdd'
 import { B2keyFliterFu } from '../data'
 import { FourTableType } from '../../B1collect/type'
+import { EXPORT_WORD_ENUM } from '@/utils/exportTemplates'
 
 function B2edit() {
   const { key, id } = useParams<any>()
@@ -305,7 +306,7 @@ function B2edit() {
           </Button>
         ) : null}
 
-        {EXbtnFu(topInfo)}
+        {EXbtnFu(topInfo, [EXPORT_WORD_ENUM.CERTIFICATE])}
 
         {btnFlagFu2(topInfo)['删除'] ? (
           <MyPopconfirm

+ 4 - 1
src/pages/B_enterTibet/B34typeIn/B34edit/index.tsx

@@ -29,6 +29,7 @@ import ZupFileTable from '@/components/ZupFileTable'
 import Y1cathet from '@/pages/Y_goodsDetails/Y1cathet'
 import ZGaddNow from '@/components/ZGaddNow'
 import B34MoAdd from '../B34MoAdd'
+import { EXPORT_WORD_ENUM } from '@/utils/exportTemplates'
 
 type Props = {
   type: '入馆' | '入藏'
@@ -354,7 +355,9 @@ function B34edit({ type }: Props) {
           </Button>
         ) : null}
 
-        {EXbtnFu(topInfo)}
+        {EXbtnFu(topInfo, [
+          type === '入馆' ? EXPORT_WORD_ENUM.VOUCHER : EXPORT_WORD_ENUM.STORAGE_VOUCHER
+        ])}
 
         {btnFlagFu2(topInfo)['删除'] ? (
           <MyPopconfirm

+ 2 - 1
src/pages/C_goodsManage/C21wealth/C21edit/index.tsx

@@ -25,6 +25,7 @@ import {
 } from '@/store/action/C21wealth'
 import { MEDIA_TYPES } from '../constants'
 import { IC21Detail } from '../types'
+import { EXPORT_WORD_ENUM } from '@/utils/exportTemplates'
 
 export const pageTitTxtObj = {
   1: '新增',
@@ -321,7 +322,7 @@ function C21edit() {
           </Button>
         ) : null}
 
-        {EXbtnFu(topInfo)}
+        {EXbtnFu(topInfo, [EXPORT_WORD_ENUM.FORM_FOR_DIGITAL])}
 
         {btnFlagFu2(topInfo)['删除'] ? (
           <MyPopconfirm

+ 2 - 1
src/pages/D_storeManage/D4impStor/D4edit/index.tsx

@@ -36,6 +36,7 @@ import TextArea from 'antd/es/input/TextArea'
 import D4check, { D4checkArrType } from './D4check'
 import { API_goodsInfo } from '@/store/action/C1ledger'
 import { findFirstDuplicate } from '../data'
+import { EXPORT_WORD_ENUM } from '@/utils/exportTemplates'
 
 export const pageTitTxtObj = {
   1: '新增',
@@ -462,7 +463,7 @@ function D4edit() {
           </Button>
         ) : null}
 
-        {EXbtnFu(topInfo)}
+        {EXbtnFu(topInfo, [EXPORT_WORD_ENUM.SUB_PUT_IN])}
 
         {btnFlagFu2(topInfo)['删除'] ? (
           <MyPopconfirm

+ 2 - 1
src/pages/D_storeManage/D5moveStor/D5edit/index.tsx

@@ -36,6 +36,7 @@ import ZflowTable from '@/components/ZflowTable'
 import ZupFileTable from '@/components/ZupFileTable'
 import Y1cathet from '@/pages/Y_goodsDetails/Y1cathet'
 import ZGaddNow from '@/components/ZGaddNow'
+import { EXPORT_WORD_ENUM } from '@/utils/exportTemplates'
 
 function D5edit() {
   // 获取库房设置列表  用于分库缩写下拉
@@ -473,7 +474,7 @@ function D5edit() {
           </Button>
         ) : null}
 
-        {EXbtnFu(topInfo)}
+        {EXbtnFu(topInfo, [EXPORT_WORD_ENUM.COLLECTION_RELOCATION])}
 
         {btnFlagFu2(topInfo)['删除'] ? (
           <MyPopconfirm

+ 6 - 1
src/pages/D_storeManage/D6putsStor/D6edit/index.tsx

@@ -34,6 +34,7 @@ import ZupFileTable from '@/components/ZupFileTable'
 import Y1cathet from '@/pages/Y_goodsDetails/Y1cathet'
 import ZGaddNow from '@/components/ZGaddNow'
 import D6batchInput from './D6batchInput'
+import { EXPORT_WORD_ENUM } from '@/utils/exportTemplates'
 
 function D6edit() {
   // 获取库房设置列表/入馆凭证号-用于分库缩写下拉
@@ -515,7 +516,11 @@ function D6edit() {
           </Button>
         ) : null}
 
-        {EXbtnFu(topInfo)}
+        {EXbtnFu(topInfo, [
+          EXPORT_WORD_ENUM.SUB_PUT_BACK,
+          EXPORT_WORD_ENUM.HALL_PUT_BACK,
+          EXPORT_WORD_ENUM.BORROW
+        ])}
 
         {btnFlagFu2(topInfo)['删除'] ? (
           <MyPopconfirm

+ 2 - 1
src/pages/D_storeManage/D7check/D7edit/index.tsx

@@ -29,6 +29,7 @@ import dayjs from 'dayjs'
 import { areAllCheckersFilled } from '@/utils/objects'
 import { C1GoodType } from '@/pages/A3_ledger/C1ledger/type'
 import { BatchFillingModal } from '../components/BatchFillingModal'
+import { EXPORT_WORD_ENUM } from '@/utils/exportTemplates'
 
 export const pageTitTxtObj = {
   1: '新增',
@@ -300,7 +301,7 @@ function D7edit() {
           </Button>
         ) : null}
 
-        {EXbtnFu(topInfo)}
+        {EXbtnFu(topInfo, [EXPORT_WORD_ENUM.COLLECTION_INVENTORY])}
 
         {btnFlagFu2(topInfo)['删除'] ? (
           <MyPopconfirm

+ 0 - 1
src/pages/D_storeManage/D7check/components/StocktakingModal/index.tsx

@@ -1,4 +1,3 @@
-import MyPopconfirm from '@/components/MyPopconfirm'
 import MyTable from '@/components/MyTable'
 import { D7_APIGoodList } from '@/store/action/D7check'
 import { MessageFu } from '@/utils/message'

+ 2 - 1
src/pages/D_storeManage/D8cancel/D8edit/index.tsx

@@ -30,6 +30,7 @@ import ZGaddNow from '@/components/ZGaddNow'
 import classNames from 'classnames'
 import Y1cathet from '@/pages/Y_goodsDetails/Y1cathet'
 import { API_goodsInfo } from '@/store/action/C1ledger'
+import { EXPORT_WORD_ENUM } from '@/utils/exportTemplates'
 
 const tableArrTemp = [
   { name: '注销后去向', key: 'txt1' },
@@ -380,7 +381,7 @@ function D8edit() {
           </Button>
         ) : null}
 
-        {EXbtnFu(topInfo)}
+        {EXbtnFu(topInfo, [EXPORT_WORD_ENUM.CANCEL_VOUCHER])}
 
         {btnFlagFu2(topInfo)['删除'] ? (
           <MyPopconfirm

+ 19 - 3
src/pages/Y_goodsDetails/Y2look/index.tsx

@@ -9,8 +9,8 @@ import Y22com from '../Y1cathet/Y22com'
 import Y33com from '../Y1cathet/Y33com'
 import Y44com from '../Y1cathet/Y44com'
 import { C1GoodType } from '@/pages/A3_ledger/C1ledger/type'
-import { API_goodsInfo } from '@/store/action/C1ledger'
-import { EXPORT_WORD_ENUM, exportWordHandler } from '@/utils/exportWordTemplates'
+import { API_getInfoLogList, API_goodsInfo } from '@/store/action/C1ledger'
+import { EXPORT_WORD_ENUM, exportWordHandler } from '@/utils/exportTemplates'
 import { API_goodFileList } from '@/store/action/C2files'
 import { C3_APIfocus, C3_APIfocusNo } from '@/store/action/C3focus'
 import { MessageFu } from '@/utils/message'
@@ -32,7 +32,7 @@ function Y2look() {
 
   const handleExport = async () => {
     const res = await API_goodFileList(sId)
-    exportWordHandler(EXPORT_WORD_ENUM.COLLECTION_CARD, {
+    exportWordHandler(EXPORT_WORD_ENUM.COLLECTION_ARCHIVES, {
       ...info,
       imagePages: [
         {
@@ -42,6 +42,18 @@ function Y2look() {
       ]
     })
   }
+  const handleExport2 = async () => {
+    const res = await API_getInfoLogList({
+      typeName: '藏品鉴定',
+      pageNum: 1,
+      pageSize: 999,
+      goodsId: sId
+    })
+    exportWordHandler(EXPORT_WORD_ENUM.COLLECTION_CARD, {
+      ...info,
+      records: res.data.records
+    })
+  }
 
   useEffect(() => {
     getInfoFu(sId)
@@ -226,6 +238,10 @@ function Y2look() {
                 导出藏品档案
               </Button>
               &emsp;
+              <Button type='primary' onClick={handleExport2}>
+                导出藏品卡片
+              </Button>
+              &emsp;
               <Dropdown menu={{ items: items1 }} placement='bottom' arrow>
                 <Button type='primary'>档案管理</Button>
               </Dropdown>

+ 24 - 34
src/utils/EXBtn.tsx

@@ -1,46 +1,36 @@
 import { Button, Dropdown, MenuProps } from 'antd'
-import { EXPORT_WORD_ENUM, exportWordHandler } from './exportWordTemplates'
+import { EXPORT_TEMPLATE_MAP, EXPORT_WORD_ENUM, exportWordHandler } from './exportTemplates'
 
-// 待完善
-
-const items: MenuProps['items'] = [
-  {
-    key: EXPORT_WORD_ENUM.BORROW,
-    label: <span className='Y2xia'>借用藏品点交凭证文件</span>
-  },
-  {
-    key: EXPORT_WORD_ENUM.HALL_PUT_BACK,
-    label: <span className='Y2xia'>藏品馆内提退凭单</span>
-  },
-  {
-    key: EXPORT_WORD_ENUM.SUB_PUT_BACK,
-    label: <span className='Y2xia'>分库藏品提退出入库记录单</span>
-  },
-  {
-    key: EXPORT_WORD_ENUM.SUB_PUT_IN,
-    label: <span className='Y2xia'>分库藏品入库记录单</span>
-  },
-  {
-    key: EXPORT_WORD_ENUM.VOUCHER,
-    label: <span className='Y2xia'>入馆凭证</span>
-  },
-  {
-    key: EXPORT_WORD_ENUM.FORM_FOR_DIGITAL,
-    label: <span className='Y2xia'>藏品图片及相关数字化信息使用申请单</span>
-  }
-]
+const items: MenuProps['items'] = []
+const templateKeys = Object.keys(EXPORT_TEMPLATE_MAP).map(i => Number(i)) as EXPORT_WORD_ENUM[]
+for (let i = 0; i < templateKeys.length; i++) {
+  const key = templateKeys[i]
+  items.push({
+    key,
+    label: <span className='Y2xia'>{EXPORT_TEMPLATE_MAP[key].fileName}</span>
+  })
+}
 
-// 查看详情的导出页面
+/**
+ * 导出按钮
+ * @param data 数据
+ * @param keys 模板keys
+ */
+export const EXbtnFu = (data?: any, keys = templateKeys) => {
+  const menus = items.filter(i => keys.includes(i?.key as EXPORT_WORD_ENUM))
 
-export const EXbtnFu = (data?: any) => {
-  const handleExport: MenuProps['onClick'] = async ({ key }) => {
+  const handleExport: ({ key }: { key: unknown }) => Promise<void> = async ({ key }) => {
     const k = Number(key) as EXPORT_WORD_ENUM
     exportWordHandler(k, data)
   }
 
-  return (
-    <Dropdown menu={{ items, onClick: handleExport }} placement='top' arrow>
+  return menus.length > 1 ? (
+    <Dropdown menu={{ items: menus, onClick: handleExport }} placement='top' arrow>
       <Button type='primary'>导出</Button>
     </Dropdown>
+  ) : (
+    <Button type='primary' onClick={handleExport.bind(undefined, { key: menus[0]?.key })}>
+      导出
+    </Button>
   )
 }

+ 738 - 0
src/utils/exportTemplates.ts

@@ -0,0 +1,738 @@
+import { cloneDeep } from 'lodash'
+import ExcelJS from 'exceljs'
+import { exportWordDocx, getBase64Sync } from './exportWord'
+import { baseURL } from './http'
+import { resJiLianFu } from './history'
+import {
+  arrangeImages,
+  calculateRowCharLines,
+  numberToChinese,
+  removeHtmlTags
+} from './exportWordUtils'
+import { myTableTransferSize } from '@/components/MyTable'
+import dayjs from 'dayjs'
+
+export enum EXPORT_WORD_ENUM {
+  /** 借用藏品点交凭证 */
+  BORROW = 1,
+  /** 藏品馆内提退凭单 */
+  HALL_PUT_BACK = 2,
+  /** 分库藏品提退出入库记录单 */
+  SUB_PUT_BACK = 3,
+  /** 分库藏品入库记录单 */
+  SUB_PUT_IN = 4,
+  /** 入馆凭证 */
+  VOUCHER = 5,
+  /** 藏品图片及相关数字化信息使用申请单 */
+  FORM_FOR_DIGITAL = 6,
+  /** 藏品档案 */
+  COLLECTION_ARCHIVES = 7,
+
+  /** 拟征集藏品清单 */
+  COLLECTION_LIST = 8,
+  /** 文物鉴定书 */
+  CERTIFICATE = 9,
+  /** 入藏凭证 */
+  STORAGE_VOUCHER = 10,
+  /** 藏品卡片 */
+  COLLECTION_CARD = 11,
+  /** 藏品注销凭证 */
+  CANCEL_VOUCHER = 12,
+  /** 藏品移库 */
+  COLLECTION_RELOCATION = 13,
+  /** 藏品盘点记录单 */
+  COLLECTION_INVENTORY = 14
+}
+
+export interface ITEMPLATE {
+  fileName: string
+  templateName?: string
+  options?: Record<string, any>
+  perLine?: Record<string, number>
+  row?: Record<string, number>
+}
+
+export const EXPORT_TEMPLATE_MAP: Record<EXPORT_WORD_ENUM, ITEMPLATE> = {
+  [EXPORT_WORD_ENUM.COLLECTION_INVENTORY]: {
+    fileName: '义乌市博物馆分库藏品盘点记录单',
+    templateName: '11.docx'
+  },
+  [EXPORT_WORD_ENUM.COLLECTION_RELOCATION]: {
+    fileName: '义乌博物馆分库藏品移库记录单',
+    templateName: '10.docx'
+  },
+  [EXPORT_WORD_ENUM.CANCEL_VOUCHER]: {
+    fileName: '馆藏品注销凭证',
+    options: {
+      sheetHeader: [
+        '藏品总账号',
+        '藏品分类号',
+        '名称',
+        '年代',
+        '数量',
+        '尺寸、重量',
+        '完残情况',
+        '登记账页码数',
+        '注销后去向',
+        '注销原因',
+        '备注'
+      ],
+      sheetFilter: [
+        'num',
+        'numType',
+        'name',
+        'dictAge',
+        'pcs',
+        'size',
+        'torn',
+        '',
+        'txt1',
+        'txt2',
+        'txt3'
+      ],
+      columnWidths: [10, 10, 10, 15, 10, 15, 15, 12, 15, 15, 15]
+    }
+  },
+  [EXPORT_WORD_ENUM.CERTIFICATE]: {
+    fileName: '义乌市博物馆馆藏文物鉴定书',
+    options: {
+      sheetHeader: [
+        '编号',
+        '总登记号',
+        '分类号',
+        '文物名称',
+        '图片',
+        '数量',
+        '质地',
+        '时代',
+        '尺寸(cm)或重量(g)',
+        '现状概述',
+        '鉴定等级',
+        '备注'
+      ],
+      sheetFilter: [
+        'index',
+        'num',
+        'numType',
+        'name',
+        'img',
+        'pcs',
+        'dictTexture',
+        'dictAge',
+        'size',
+        'dictTorn',
+        'dictLevel',
+        'intro'
+      ],
+      columnWidths: [5, 12, 7, 15, 10, 7, 15, 35, 20, 10, 10, 20]
+    }
+  },
+  [EXPORT_WORD_ENUM.COLLECTION_LIST]: {
+    fileName: '拟征集藏品清单',
+    options: {
+      sheetHeader: ['序号', '藏品名称', '初定年代', '报价(万元)', '原持有人', '征集方式'],
+      sheetFilter: ['index', 'name', 'dictAgeFirst', 'offer', 'holder', 'source'],
+      columnWidths: [5, 15, 40, 15, 10, 10]
+    }
+  },
+  [EXPORT_WORD_ENUM.BORROW]: {
+    fileName: '义乌市博物馆借用藏品点交凭证',
+    templateName: '1.docx',
+    // 每个字段单行最大字符数
+    perLine: {
+      num: 9,
+      name: 14,
+      rtf: 11,
+      dictAge: 6,
+      dictTexture3: 6
+    },
+    row: {
+      // 首页最大行数
+      maxRowFirstPage: 1,
+      // 尾页最大行数
+      maxRowLastPage: 2,
+      // 每页最大行数
+      maxRowPage: 5,
+      // 首页单行最大字符行数
+      maxFirstCharLine: 9,
+      // 尾页单行最大字符行数
+      maxLastCharLine: 11,
+      // 每页最大字符行数
+      maxCharLine: 27,
+      // 表格每行最小字符行数
+      minRowCharLine: 4
+    }
+  },
+  [EXPORT_WORD_ENUM.HALL_PUT_BACK]: {
+    fileName: '义乌市博物馆藏品馆内提退凭单',
+    templateName: '2.docx',
+    // 每个字段单行最大字符数
+    perLine: {
+      num: 9,
+      name: 24,
+      pcsUnit: 4,
+      rtf: 6
+    },
+    row: {
+      // 首页最大行数
+      maxRowFirstPage: 3,
+      // 尾页最大行数
+      maxRowLastPage: 6,
+      // 每页最大行数
+      maxRowPage: 8,
+      // 首页单行最大字符行数
+      maxFirstCharLine: 20,
+      // 尾页单行最大字符行数
+      maxLastCharLine: 18,
+      // 每页最大字符行数
+      maxCharLine: 26,
+      // 表格每行最小字符行数
+      minRowCharLine: 3
+    }
+  },
+  [EXPORT_WORD_ENUM.SUB_PUT_BACK]: {
+    fileName: '义乌市博物馆分库藏品提退出入库记录单',
+    templateName: '3.docx'
+  },
+  [EXPORT_WORD_ENUM.SUB_PUT_IN]: {
+    fileName: '义乌市博物馆分库藏品入库记录单',
+    templateName: '4.docx'
+  },
+  [EXPORT_WORD_ENUM.VOUCHER]: {
+    fileName: '义乌市博物馆入馆凭证',
+    templateName: '5.docx'
+  },
+  [EXPORT_WORD_ENUM.FORM_FOR_DIGITAL]: {
+    fileName: '义乌市博物馆藏品图片及相关数字化信息使用申请单',
+    templateName: '6.docx'
+  },
+  [EXPORT_WORD_ENUM.COLLECTION_ARCHIVES]: {
+    fileName: '藏品档案样本电子版',
+    templateName: '7.docx'
+  },
+  [EXPORT_WORD_ENUM.STORAGE_VOUCHER]: {
+    fileName: '义乌市博物馆入藏凭证',
+    templateName: '8.docx'
+  },
+  [EXPORT_WORD_ENUM.COLLECTION_CARD]: {
+    fileName: '藏品卡片',
+    templateName: '9.docx'
+  }
+}
+
+/**
+ * 计算表格数据的分页情况
+ */
+const calcTablePages = (rows: any[], config: Required<ITEMPLATE>) => {
+  if (rows.length === 0) return 0
+
+  let totalPages = 0
+  let currentPageRows: any[] = []
+  let currentPageCharLines = 0
+  let isFirstPage = true
+  let isLastPage = false
+
+  for (let i = 0; i < rows.length; i++) {
+    const row = rows[i]
+    const rowCharLines = calculateRowCharLines(row, config.perLine)
+
+    currentPageCharLines += Math.max(rowCharLines, config.row.minRowCharLine)
+    currentPageRows.push(row)
+
+    // 判断是否超出首页
+    if (isFirstPage && currentPageCharLines > config.row.maxFirstCharLine) {
+      isFirstPage = false
+      totalPages++
+      currentPageCharLines -= config.row.maxFirstCharLine
+
+      // 如果剩下行数还超出尾页最大行数,也先预处理
+      if (currentPageCharLines > config.row.maxLastCharLine) {
+        totalPages++
+        currentPageCharLines -= config.row.maxLastCharLine
+      }
+    }
+
+    // 确定当前页的限制
+    const maxRows = isFirstPage
+      ? config.row.maxRowFirstPage
+      : isLastPage
+      ? config.row.maxRowLastPage
+      : config.row.maxRowPage
+
+    const maxCharLines = isFirstPage
+      ? config.row.maxFirstCharLine
+      : isLastPage
+      ? config.row.maxLastCharLine
+      : config.row.maxCharLine
+
+    // 检查是否换页
+    const isPageFull = currentPageRows.length >= maxRows || currentPageCharLines >= maxCharLines
+
+    if (isPageFull || i === rows.length - 1) {
+      totalPages +=
+        Math.floor(currentPageCharLines / maxCharLines) +
+        (currentPageCharLines % maxCharLines > 0 ? 1 : 0)
+      currentPageRows = []
+      currentPageCharLines = 0
+      isFirstPage = false
+      isLastPage = i === rows.length - 1
+    }
+  }
+
+  return totalPages
+}
+
+/**
+ * 根据业务类型导出数据
+ */
+export const exportWordHandler = async (type: EXPORT_WORD_ENUM, data: Record<any, any>) => {
+  let temp = cloneDeep(data)
+  let page = 1
+  let excelHandler: (worksheet: ExcelJS.Worksheet) => void = () => {}
+  const date = dayjs()
+  const item = EXPORT_TEMPLATE_MAP[type]
+
+  temp.sizeUnit && (temp.sizeUnit = resJiLianFu(temp.sizeUnit))
+  temp.qualityUnit && (temp.qualityUnit = resJiLianFu(temp.qualityUnit))
+  temp.dictAge && (temp.dictAge = resJiLianFu(temp.dictAge))
+  temp.pcsUnit && (temp.pcsUnit = resJiLianFu(temp.pcsUnit))
+  temp.goods =
+    (Array.isArray(temp.snaps) && temp.snaps.length
+      ? temp.snaps.map((i: any) => JSON.parse(i.snap || ''))
+      : temp.collects) ?? []
+
+  if (temp.goods.length) {
+    for (let i = 0; i < temp.goods.length; i++) {
+      const good = temp.goods[i]
+      good.index = i + 1
+      good.rtf && (good.rtf = removeHtmlTags(JSON.parse(good.rtf).txtArr[0].txt))
+      try {
+        good.thumb && (good.thumb = await getBase64Sync(baseURL + good.thumb))
+      } catch (err) {
+        console.log('thumb conversion to base64 faild:', err)
+        good.thumb = ''
+      }
+      good.dictAge && (good.dictAge = resJiLianFu(good.dictAge))
+      good.pcsUnit && (good.pcsUnit = resJiLianFu(good.pcsUnit))
+      if (good.pcs) {
+        good._pcs = good.pcs
+        good.pcs = good.pcs + good.pcsUnit
+      }
+      good.dictTexture3 && (good.dictTexture = resJiLianFu(good.dictTexture3))
+      good.dictTorn && (good.dictTorn = resJiLianFu(good.dictTorn))
+      good.source && (good.source = resJiLianFu(good.source))
+      good.dictAgeFirst && (good.dictAgeFirst = resJiLianFu(good.dictAgeFirst))
+      good.qualityUnit && (good.qualityUnit = resJiLianFu(good.qualityUnit))
+      if (good.sizeUnit || good.quality)
+        good.size = good.sizeUnit
+          ? myTableTransferSize(good.sizeUnit)
+          : good.quality + good.qualityUnit
+    }
+  } else {
+    temp.goods = []
+  }
+
+  // @ts-ignore
+  if (item.perLine) {
+    // @ts-ignore
+    page = calcTablePages(temp.goods, item)
+  }
+
+  switch (type) {
+    case EXPORT_WORD_ENUM.BORROW:
+      temp = {
+        ...temp,
+        group: numberToChinese(temp.goods.length),
+        num: numberToChinese(temp.goods.reduce((sum: number, i: any) => sum + (i._pcs || 0), 0)),
+        page: numberToChinese(page),
+        borrowDate: dayjs(temp.createTime).format('YYYY年MM月DD日')
+      }
+      break
+    case EXPORT_WORD_ENUM.HALL_PUT_BACK:
+      temp = {
+        ...temp,
+        year: date.format('YYYY'),
+        group: numberToChinese(temp.goods.length),
+        page: numberToChinese(page)
+      }
+      break
+    case EXPORT_WORD_ENUM.SUB_PUT_BACK:
+      temp = {
+        ...temp,
+        year: date.format('YYYY'),
+        month: date.format('MM'),
+        day: date.format('DD')
+      }
+      break
+    case EXPORT_WORD_ENUM.VOUCHER:
+    case EXPORT_WORD_ENUM.STORAGE_VOUCHER:
+      temp = {
+        ...temp,
+        year: date.format('YYYY'),
+        month: date.format('MM'),
+        day: date.format('DD'),
+        group: numberToChinese(temp.goods.length),
+        page: numberToChinese(page)
+      }
+      break
+    case EXPORT_WORD_ENUM.FORM_FOR_DIGITAL:
+      temp = {
+        ...temp,
+        year: date.format('YYYY'),
+        month: date.format('MM'),
+        day: date.format('DD')
+      }
+      break
+    case EXPORT_WORD_ENUM.SUB_PUT_IN:
+      temp = {
+        ...temp,
+        year: date.format('YYYY'),
+        month: date.format('MM'),
+        day: date.format('DD'),
+        sonSource: resJiLianFu(temp.sonSource),
+        createTime: dayjs(temp.createTime).format('YYYY年MM月DD日')
+      }
+      break
+    case EXPORT_WORD_ENUM.COLLECTION_RELOCATION:
+      temp = {
+        ...temp,
+        year: date.format('YYYY'),
+        month: date.format('MM'),
+        day: date.format('DD'),
+        date: dayjs(temp.date).format('YYYY年MM月DD日')
+      }
+      break
+    case EXPORT_WORD_ENUM.COLLECTION_CARD:
+      temp = {
+        ...temp,
+        year: date.format('YYYY'),
+        month: date.format('MM'),
+        day: date.format('DD'),
+        pcs: temp.pcs + temp.pcsUnit,
+        source: resJiLianFu(temp.source),
+        size: myTableTransferSize(temp),
+        quality: temp.quality + temp.qualityUnit,
+        dictTexture3: resJiLianFu(temp.dictTexture3),
+        rtf: removeHtmlTags(JSON.parse(temp.rtf).txtArr[0].txt),
+        deptName: (temp.records || []).length ? temp.records[0].deptName : '',
+        recordDate: (temp.records || []).length ? temp.records[0].createTime : '',
+        records: (temp.records || []).map(
+          (item: any) =>
+            `[${temp.dictLevel}],[${item.createTime}][${item.deptName}][${item.creatorName}]意见。`
+        )
+      }
+      break
+    case EXPORT_WORD_ENUM.COLLECTION_ARCHIVES:
+      temp.imagePages = await arrangeImages(temp.imagePages)
+      break
+    case EXPORT_WORD_ENUM.COLLECTION_LIST:
+      temp.goods.push({
+        index: '合计',
+        name: temp.goods.length,
+        dictAgeFirst: '合计',
+        offer: temp.goods.reduce((sum: number, cur: any) => sum + Number(cur.offer), 0)
+      })
+
+      excelHandler = worksheet => {
+        worksheet.insertRow(1, ['拟征集藏品清单'])
+        worksheet.mergeCells('A1:F1')
+        const mergedCell = worksheet.getCell('A1')
+        mergedCell.style = {
+          fill: {
+            type: 'pattern',
+            pattern: 'solid',
+            fgColor: { argb: 'cce8cf' }
+          },
+          font: {
+            name: '黑体',
+            size: 18
+          }
+        }
+
+        const borderStyle: ExcelJS.Border = {
+          style: 'thin',
+          color: { argb: 'FF000000' }
+        }
+
+        for (let row = 2; row <= temp.goods.length + 2; row++) {
+          for (let col = 1; col <= 6; col++) {
+            const cell = worksheet.getCell(row, col)
+            cell.border = {
+              top: borderStyle,
+              left: borderStyle,
+              bottom: borderStyle,
+              right: borderStyle
+            }
+          }
+        }
+
+        worksheet.eachRow(row => {
+          row.eachCell(cell => {
+            cell.alignment = {
+              vertical: 'middle',
+              horizontal: 'center',
+              wrapText: true
+            }
+          })
+        })
+      }
+      break
+    case EXPORT_WORD_ENUM.CERTIFICATE:
+      const resultText = Object.entries(
+        // @ts-ignore
+        temp.goods.reduce((acc, { dictLevel }) => {
+          acc[dictLevel] = (acc[dictLevel] || 0) + 1
+          return acc
+        }, {})
+      )
+        // @ts-ignore
+        .filter(([_, count]) => count > 0)
+        .map(
+          ([level, count]) =>
+            `${level === '一般' ? level + '珍贵文物' : level + '文物'}${count}件/组;\n`
+        )
+        .join(';')
+
+      temp.goods.push({
+        index: '鉴\n定\n人\n员',
+        num: temp.authUser,
+        img: '鉴\n定\n单\n位',
+        pcs: temp.authUnit,
+        dictAge: '核\n准\n单\n位',
+        size: temp.confirmUnit,
+        dictTorn: '说\n\n明',
+        dictLevel: `鉴定日期:${temp.date}\n鉴定总数${temp.goods.length}件/组,其中:\n${resultText}`
+      })
+
+      excelHandler = worksheet => {
+        const lastIndex = temp.goods.length + 2
+
+        worksheet.eachRow(row => {
+          row.eachCell(cell => {
+            cell.style = {
+              font: {
+                name: '宋体',
+                size: 10
+              }
+            }
+            cell.alignment = {
+              vertical: 'middle',
+              horizontal: 'center',
+              wrapText: true
+            }
+          })
+        })
+
+        worksheet.insertRow(1, ['单位:义乌市博物馆'])
+        worksheet.mergeCells('A1:H1')
+        worksheet.mergeCells('I1:L1')
+        worksheet.mergeCells(`B${lastIndex}:D${lastIndex}`)
+        worksheet.mergeCells(`F${lastIndex}:G${lastIndex}`)
+        worksheet.mergeCells(`K${lastIndex}:L${lastIndex}`)
+
+        const titleCell = worksheet.getCell('A1')
+        const dateCell = worksheet.getCell('I1')
+        const lastCell = worksheet.getRow(lastIndex)
+        const borderStyle: ExcelJS.Border = {
+          style: 'thin',
+          color: { argb: 'FF000000' }
+        }
+
+        lastCell.height = 80
+        titleCell.style = {
+          fill: {
+            type: 'pattern',
+            pattern: 'solid',
+            fgColor: { argb: 'cce8cf' }
+          },
+          font: {
+            size: 16
+          },
+          alignment: {
+            horizontal: 'left',
+            vertical: 'middle'
+          }
+        }
+        dateCell.value = '日期:' + date.format('YYYY年MM月DD日')
+        dateCell.style = {
+          fill: {
+            type: 'pattern',
+            pattern: 'solid',
+            fgColor: { argb: 'cce8cf' }
+          },
+          font: {
+            size: 16
+          },
+          alignment: {
+            horizontal: 'right',
+            vertical: 'middle'
+          }
+        }
+
+        for (let row = 1; row <= lastIndex; row++) {
+          for (let col = 1; col <= 12; col++) {
+            const cell = worksheet.getCell(row, col)
+            cell.border = {
+              top: borderStyle,
+              left: borderStyle,
+              bottom: borderStyle,
+              right: borderStyle
+            }
+          }
+        }
+      }
+      break
+    case EXPORT_WORD_ENUM.CANCEL_VOUCHER:
+      temp.goods.push({
+        num: '法定代表人:',
+        dictAge: '保管部负责人:',
+        torn: '保管员:',
+        txt1: '珍贵文物注销审批文号:'
+      })
+
+      excelHandler = worksheet => {
+        const lastIndex = temp.goods.length + 4
+        const borderStyle: ExcelJS.Border = {
+          style: 'thin',
+          color: { argb: 'FF000000' }
+        }
+
+        worksheet.eachRow(row => {
+          row.eachCell(cell => {
+            cell.style = {
+              font: {
+                name: '宋体',
+                size: 10
+              }
+            }
+            cell.alignment = {
+              vertical: 'middle',
+              horizontal: 'center',
+              wrapText: true
+            }
+          })
+        })
+
+        worksheet.insertRow(1, [])
+        worksheet.mergeCells('A1:K1')
+        worksheet.getCell('A1').value = {
+          richText: [
+            { font: { size: 20, name: '黑体' }, text: '馆藏品注销凭证 ' },
+            { font: { size: 11 }, text: '(编号:             ) ' }
+          ]
+        }
+        worksheet.getRow(1).height = 60
+        worksheet.getCell('A1').style = {
+          alignment: {
+            horizontal: 'center',
+            vertical: 'middle'
+          }
+        }
+        worksheet.insertRow(2, [date.format('YYYY年MM月DD日')])
+        worksheet.mergeCells('A2:K2')
+        worksheet.getRow(2).height = 30
+        worksheet.getCell('A2').style = {
+          font: {
+            size: 11,
+            name: '仿宋_GB2312'
+          },
+          alignment: {
+            horizontal: 'right',
+            vertical: 'middle'
+          }
+        }
+        worksheet.insertRow(3, [
+          `现将藏品_${temp.goods.length}_件注销,清单开列如下,本凭证连同附页全份共______页(含附件)`
+        ])
+        worksheet.mergeCells('A3:K3')
+        worksheet.getRow(3).height = 30
+        worksheet.getCell('A3').style = {
+          font: {
+            size: 11,
+            name: '仿宋_GB2312'
+          },
+          alignment: {
+            horizontal: 'center',
+            vertical: 'middle'
+          }
+        }
+        worksheet.mergeCells(`A${lastIndex}:C${lastIndex}`)
+        worksheet.mergeCells(`D${lastIndex}:F${lastIndex}`)
+        worksheet.mergeCells(`G${lastIndex}:H${lastIndex}`)
+        worksheet.mergeCells(`I${lastIndex}:K${lastIndex}`)
+
+        for (let row = 1; row <= lastIndex; row++) {
+          for (let col = 1; col <= 11; col++) {
+            const cell = worksheet.getCell(row, col)
+            cell.border = {
+              top: borderStyle,
+              left: borderStyle,
+              bottom: borderStyle,
+              right: borderStyle
+            }
+          }
+        }
+      }
+      break
+  }
+
+  if (!item.templateName) {
+    // 没有templateName则输出excel
+    const workbook = new ExcelJS.Workbook()
+    const worksheet = workbook.addWorksheet('Sheet1', {
+      properties: { defaultRowHeight: 30 },
+      views: [{ showGridLines: false }],
+      pageSetup: {
+        horizontalCentered: true,
+        verticalCentered: true,
+        paperSize: 9
+      }
+    })
+
+    worksheet.addRow(item.options?.sheetHeader)
+    temp.goods.forEach((good: Record<string, any>, goodIndex: number) => {
+      const _temp: string[] = []
+      item.options?.sheetFilter.forEach((key: string, index: number) => {
+        _temp.push(good[key])
+
+        // if (key === 'thumb' && Boolean(good[key])) {
+        //   // 插入图片
+        //   const imageId = workbook.addImage({
+        //     base64: good[key],
+        //     extension: 'jpeg'
+        //   })
+        //   worksheet.addImage(
+        //     imageId,
+        //     `${getExcelColumnLetter(index)}${goodIndex + 2}:${getExcelColumnLetter(index)}${
+        //       goodIndex + 2
+        //     }`
+        //   )
+        // }
+      })
+      worksheet.addRow(_temp)
+    })
+
+    // 设置宽度
+    item.options?.columnWidths.forEach((width: number, index: number) => {
+      worksheet.getColumn(index + 1).width = width
+    })
+
+    excelHandler(worksheet)
+
+    const buffer = await workbook.xlsx.writeBuffer()
+    const blob = new Blob([buffer], {
+      type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+    })
+
+    const url = URL.createObjectURL(blob)
+    const a = document.createElement('a')
+    a.href = url
+    a.download = item.fileName + '.xlsx'
+    a.click()
+
+    // 释放内存
+    setTimeout(() => URL.revokeObjectURL(url), 100)
+  } else {
+    exportWordDocx(`./templates/${item.templateName}`, temp, item.fileName)
+  }
+}

+ 0 - 250
src/utils/exportWordTemplates.ts

@@ -1,250 +0,0 @@
-import { cloneDeep } from 'lodash'
-import { exportWordDocx, getBase64Sync } from './exportWord'
-import { baseURL } from './http'
-import { resJiLianFu } from './history'
-import {
-  arrangeImages,
-  calculateRowCharLines,
-  numberToChinese,
-  removeHtmlTags
-} from './exportWordUtils'
-
-export enum EXPORT_WORD_ENUM {
-  /** 借用藏品点交凭证 */
-  BORROW = 1,
-  /** 藏品馆内提退凭单 */
-  HALL_PUT_BACK = 2,
-  /** 分库藏品提退出入库记录单 */
-  SUB_PUT_BACK = 3,
-  /** 分库藏品入库记录单 */
-  SUB_PUT_IN = 4,
-  /** 入馆凭证 */
-  VOUCHER = 5,
-  /** 藏品图片及相关数字化信息使用申请单 */
-  FORM_FOR_DIGITAL = 6,
-  /** 藏品卡片 */
-  COLLECTION_CARD = 7
-}
-
-const WORD_FILE_NAME_MAP = {
-  [EXPORT_WORD_ENUM.BORROW]: {
-    fileName: '义乌市博物馆借用藏品点交凭证',
-    templateName: '1.docx',
-    // 每个字段单行最大字符数
-    perLine: {
-      num: 9,
-      name: 14,
-      rtf: 11,
-      dictTexture3: 6
-    },
-    row: {
-      // 首页最大行数
-      maxRowFirstPage: 1,
-      // 尾页最大行数
-      maxRowLastPage: 2,
-      // 每页最大行数
-      maxRowPage: 5,
-      // 首页单行最大字符行数
-      maxFirstCharLine: 9,
-      // 尾页单行最大字符行数
-      maxLastCharLine: 11,
-      // 每页最大字符行数
-      maxCharLine: 27,
-      // 表格每行最小字符行数
-      minRowCharLine: 4
-    }
-  },
-  [EXPORT_WORD_ENUM.HALL_PUT_BACK]: {
-    fileName: '义乌市博物馆藏品馆内提退凭单',
-    templateName: '2.docx',
-    // 每个字段单行最大字符数
-    perLine: {
-      num: 9,
-      name: 24,
-      pcsUnit: 4,
-      rtf: 6
-    },
-    row: {
-      // 首页最大行数
-      maxRowFirstPage: 3,
-      // 尾页最大行数
-      maxRowLastPage: 6,
-      // 每页最大行数
-      maxRowPage: 8,
-      // 首页单行最大字符行数
-      maxFirstCharLine: 20,
-      // 尾页单行最大字符行数
-      maxLastCharLine: 18,
-      // 每页最大字符行数
-      maxCharLine: 26,
-      // 表格每行最小字符行数
-      minRowCharLine: 3
-    }
-  },
-  [EXPORT_WORD_ENUM.SUB_PUT_BACK]: {
-    fileName: '义乌市博物馆分库藏品提退出入库记录单',
-    templateName: '3.docx'
-  },
-  [EXPORT_WORD_ENUM.SUB_PUT_IN]: {
-    fileName: '义乌市博物馆分库藏品入库记录单',
-    templateName: '4.docx'
-  },
-  [EXPORT_WORD_ENUM.VOUCHER]: {
-    fileName: '义乌市博物馆入馆凭证',
-    templateName: '5.docx'
-  },
-  [EXPORT_WORD_ENUM.FORM_FOR_DIGITAL]: {
-    fileName: '义乌市博物馆藏品图片及相关数字化信息使用申请单',
-    templateName: '6.docx'
-  },
-  [EXPORT_WORD_ENUM.COLLECTION_CARD]: {
-    fileName: '义乌市博物馆藏品卡片',
-    templateName: '7.docx'
-  }
-}
-
-/**
- * 计算表格数据的分页情况
- */
-const calcTablePages = (rows: any[], config: (typeof WORD_FILE_NAME_MAP)[2]) => {
-  if (rows.length === 0) return 0
-
-  let totalPages = 0
-  let currentPageRows: any[] = []
-  let currentPageCharLines = 0
-  let isFirstPage = true
-  let isLastPage = false
-
-  for (let i = 0; i < rows.length; i++) {
-    const row = rows[i]
-    const rowCharLines = calculateRowCharLines(row, config.perLine)
-
-    currentPageCharLines += Math.max(rowCharLines, config.row.minRowCharLine)
-    currentPageRows.push(row)
-
-    // 判断是否超出首页
-    if (isFirstPage && currentPageCharLines > config.row.maxFirstCharLine) {
-      isFirstPage = false
-      totalPages++
-      currentPageCharLines -= config.row.maxFirstCharLine
-
-      // 如果剩下行数还超出尾页最大行数,也先预处理
-      if (currentPageCharLines > config.row.maxLastCharLine) {
-        totalPages++
-        currentPageCharLines -= config.row.maxLastCharLine
-      }
-    }
-
-    // 确定当前页的限制
-    const maxRows = isFirstPage
-      ? config.row.maxRowFirstPage
-      : isLastPage
-      ? config.row.maxRowLastPage
-      : config.row.maxRowPage
-
-    const maxCharLines = isFirstPage
-      ? config.row.maxFirstCharLine
-      : isLastPage
-      ? config.row.maxLastCharLine
-      : config.row.maxCharLine
-
-    // 检查是否换页
-    const isPageFull = currentPageRows.length >= maxRows || currentPageCharLines >= maxCharLines
-
-    if (isPageFull || i === rows.length - 1) {
-      totalPages +=
-        Math.floor(currentPageCharLines / maxCharLines) +
-        (currentPageCharLines % maxCharLines > 0 ? 1 : 0)
-      currentPageRows = []
-      currentPageCharLines = 0
-      isFirstPage = false
-      isLastPage = i === rows.length - 1
-    }
-  }
-
-  return totalPages
-}
-
-/**
- * 根据业务类型导出数据
- */
-export const exportWordHandler = async (type: EXPORT_WORD_ENUM, data: Record<any, any>) => {
-  let temp = cloneDeep(data)
-  let page = 1
-  const date = new Date()
-  const item = WORD_FILE_NAME_MAP[type]
-
-  temp.sizeUnit && (temp.sizeUnit = resJiLianFu(temp.sizeUnit))
-  temp.qualityUnit && (temp.qualityUnit = resJiLianFu(temp.qualityUnit))
-  temp.dictAge && (temp.dictAge = resJiLianFu(temp.dictAge))
-  temp.goods =
-    (Array.isArray(temp.snaps) && temp.snaps.length
-      ? temp.snaps.map((i: any) => JSON.parse(i.snap || ''))
-      : temp.collects) ?? []
-
-  if (temp.goods.length) {
-    for (let i = 0; i < temp.goods.length; i++) {
-      const good = temp.goods[i]
-      good.index = i + 1
-      good.rtf && (good.rtf = removeHtmlTags(JSON.parse(good.rtf).txtArr[0].txt))
-      good.thumb && (good.thumb = await getBase64Sync(baseURL + good.thumb))
-      good.dictAge && (good.dictAge = resJiLianFu(good.dictAge))
-      good.pcsUnit && (good.pcsUnit = resJiLianFu(good.pcsUnit))
-      good.dictTexture3 && (good.dictTexture = resJiLianFu(good.dictTexture3))
-      good.dictTorn && (good.dictTorn = resJiLianFu(good.dictTorn))
-      good.source && (good.source = resJiLianFu(good.source))
-    }
-  } else {
-    temp.goods = []
-  }
-
-  // @ts-ignore
-  if (item.perLine) {
-    // @ts-ignore
-    page = calcTablePages(temp.goods, item)
-  }
-
-  switch (type) {
-    case EXPORT_WORD_ENUM.BORROW:
-      temp = {
-        ...temp,
-        group: numberToChinese(temp.goods.length),
-        num: numberToChinese(
-          temp.goods.reduce((sum: number, item: any) => sum + (item.pcs || 0), 0)
-        ),
-        page: numberToChinese(page)
-      }
-      break
-    case EXPORT_WORD_ENUM.HALL_PUT_BACK:
-      temp = {
-        ...temp,
-        year: date.getFullYear(),
-        group: numberToChinese(temp.goods.length),
-        page: numberToChinese(page)
-      }
-      break
-    case EXPORT_WORD_ENUM.VOUCHER:
-      temp = {
-        ...temp,
-        year: date.getFullYear(),
-        month: date.getMonth() + 1,
-        day: date.getDay(),
-        group: numberToChinese(temp.goods.length),
-        page: numberToChinese(page)
-      }
-      break
-    case EXPORT_WORD_ENUM.FORM_FOR_DIGITAL:
-      temp = {
-        ...temp,
-        year: date.getFullYear(),
-        month: date.getMonth() + 1,
-        day: date.getDay()
-      }
-      break
-    case EXPORT_WORD_ENUM.COLLECTION_CARD:
-      temp.imagePages = await arrangeImages(temp.imagePages)
-      break
-  }
-
-  exportWordDocx(`./templates/${item.templateName}`, temp, item.fileName)
-}

+ 12 - 0
src/utils/exportWordUtils.ts

@@ -189,3 +189,15 @@ export const removeHtmlTags = (html: string) => {
     .replace(/&apos;/g, "'") // 替换HTML ' 实体
     .trim() // 去除首尾空格
 }
+
+export const getExcelColumnLetter = (index: number) => {
+  let result = ''
+  let remaining = index
+
+  do {
+    result = String.fromCharCode(65 + (remaining % 26)) + result
+    remaining = Math.floor(remaining / 26) - 1
+  } while (remaining >= 0)
+
+  return result
+}

+ 392 - 11
yarn.lock

@@ -1363,6 +1363,31 @@
   resolved "https://registry.npmmirror.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b"
   integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==
 
+"@fast-csv/format@4.3.5":
+  version "4.3.5"
+  resolved "https://registry.npmmirror.com/@fast-csv/format/-/format-4.3.5.tgz#90d83d1b47b6aaf67be70d6118f84f3e12ee1ff3"
+  integrity sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==
+  dependencies:
+    "@types/node" "^14.0.1"
+    lodash.escaperegexp "^4.1.2"
+    lodash.isboolean "^3.0.3"
+    lodash.isequal "^4.5.0"
+    lodash.isfunction "^3.0.9"
+    lodash.isnil "^4.0.0"
+
+"@fast-csv/parse@4.3.6":
+  version "4.3.6"
+  resolved "https://registry.npmmirror.com/@fast-csv/parse/-/parse-4.3.6.tgz#ee47d0640ca0291034c7aa94039a744cfb019264"
+  integrity sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==
+  dependencies:
+    "@types/node" "^14.0.1"
+    lodash.escaperegexp "^4.1.2"
+    lodash.groupby "^4.6.0"
+    lodash.isfunction "^3.0.9"
+    lodash.isnil "^4.0.0"
+    lodash.isundefined "^3.0.1"
+    lodash.uniq "^4.5.0"
+
 "@floating-ui/core@^1.0.0":
   version "1.6.0"
   resolved "https://registry.npmmirror.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1"
@@ -2337,6 +2362,11 @@
   dependencies:
     undici-types "~5.26.4"
 
+"@types/node@^14.0.1":
+  version "14.18.63"
+  resolved "https://registry.npmmirror.com/@types/node/-/node-14.18.63.tgz#1788fa8da838dbb5f9ea994b834278205db6ca2b"
+  integrity sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==
+
 "@types/node@^16.18.3":
   version "16.18.83"
   resolved "https://registry.npmmirror.com/@types/node/-/node-16.18.83.tgz#681d1a20676d24fc47e2da3934536304097a81d8"
@@ -3027,6 +3057,51 @@ anymatch@^3.0.3, anymatch@~3.1.2:
     normalize-path "^3.0.0"
     picomatch "^2.0.4"
 
+archiver-utils@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.npmmirror.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2"
+  integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==
+  dependencies:
+    glob "^7.1.4"
+    graceful-fs "^4.2.0"
+    lazystream "^1.0.0"
+    lodash.defaults "^4.2.0"
+    lodash.difference "^4.5.0"
+    lodash.flatten "^4.4.0"
+    lodash.isplainobject "^4.0.6"
+    lodash.union "^4.6.0"
+    normalize-path "^3.0.0"
+    readable-stream "^2.0.0"
+
+archiver-utils@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.npmmirror.com/archiver-utils/-/archiver-utils-3.0.4.tgz#a0d201f1cf8fce7af3b5a05aea0a337329e96ec7"
+  integrity sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==
+  dependencies:
+    glob "^7.2.3"
+    graceful-fs "^4.2.0"
+    lazystream "^1.0.0"
+    lodash.defaults "^4.2.0"
+    lodash.difference "^4.5.0"
+    lodash.flatten "^4.4.0"
+    lodash.isplainobject "^4.0.6"
+    lodash.union "^4.6.0"
+    normalize-path "^3.0.0"
+    readable-stream "^3.6.0"
+
+archiver@^5.0.0:
+  version "5.3.2"
+  resolved "https://registry.npmmirror.com/archiver/-/archiver-5.3.2.tgz#99991d5957e53bd0303a392979276ac4ddccf3b0"
+  integrity sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==
+  dependencies:
+    archiver-utils "^2.1.0"
+    async "^3.2.4"
+    buffer-crc32 "^0.2.1"
+    readable-stream "^3.6.0"
+    readdir-glob "^1.1.2"
+    tar-stream "^2.2.0"
+    zip-stream "^4.1.0"
+
 arg@^5.0.2:
   version "5.0.2"
   resolved "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c"
@@ -3190,6 +3265,11 @@ async@^3.2.3:
   resolved "https://registry.npmmirror.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
   integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
 
+async@^3.2.4:
+  version "3.2.6"
+  resolved "https://registry.npmmirror.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce"
+  integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==
+
 asynciterator.prototype@^1.0.0:
   version "1.0.0"
   resolved "https://registry.npmmirror.com/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz#8c5df0514936cdd133604dfcc9d3fb93f09b2b62"
@@ -3388,6 +3468,11 @@ balanced-match@^1.0.0:
   resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
   integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
 
+base64-js@^1.3.1:
+  version "1.5.1"
+  resolved "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
+  integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
+
 batch@0.6.1:
   version "0.6.1"
   resolved "https://registry.npmmirror.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
@@ -3404,6 +3489,11 @@ bfj@^7.0.2:
     jsonpath "^1.1.1"
     tryer "^1.0.1"
 
+big-integer@^1.6.17:
+  version "1.6.52"
+  resolved "https://registry.npmmirror.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85"
+  integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==
+
 big.js@^5.2.2:
   version "5.2.2"
   resolved "https://registry.npmmirror.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
@@ -3414,6 +3504,23 @@ binary-extensions@^2.0.0:
   resolved "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
   integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
 
+binary@~0.3.0:
+  version "0.3.0"
+  resolved "https://registry.npmmirror.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79"
+  integrity sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==
+  dependencies:
+    buffers "~0.1.1"
+    chainsaw "~0.1.0"
+
+bl@^4.0.3:
+  version "4.1.0"
+  resolved "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
+  integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
+  dependencies:
+    buffer "^5.5.0"
+    inherits "^2.0.4"
+    readable-stream "^3.4.0"
+
 blob.js@^1.0.1:
   version "1.0.1"
   resolved "https://registry.npmmirror.com/blob.js/-/blob.js-1.0.1.tgz#547b449b252c855313e837b53d15b41d000ea1d2"
@@ -3424,6 +3531,11 @@ bluebird@^3.7.2:
   resolved "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
   integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
 
+bluebird@~3.4.1:
+  version "3.4.7"
+  resolved "https://registry.npmmirror.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
+  integrity sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==
+
 body-parser@1.20.1:
   version "1.20.1"
   resolved "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
@@ -3532,11 +3644,34 @@ bser@2.1.1:
   dependencies:
     node-int64 "^0.4.0"
 
+buffer-crc32@^0.2.1, buffer-crc32@^0.2.13:
+  version "0.2.13"
+  resolved "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
+  integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
+
 buffer-from@^1.0.0:
   version "1.1.2"
   resolved "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
   integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
 
+buffer-indexof-polyfill@~1.0.0:
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c"
+  integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==
+
+buffer@^5.5.0:
+  version "5.7.1"
+  resolved "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
+  integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
+  dependencies:
+    base64-js "^1.3.1"
+    ieee754 "^1.1.13"
+
+buffers@~0.1.1:
+  version "0.1.1"
+  resolved "https://registry.npmmirror.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
+  integrity sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==
+
 builtin-modules@^3.1.0:
   version "3.3.0"
   resolved "https://registry.npmmirror.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
@@ -3619,6 +3754,13 @@ cfb@^1.1.4:
     adler-32 "~1.3.0"
     crc-32 "~1.2.0"
 
+chainsaw@~0.1.0:
+  version "0.1.0"
+  resolved "https://registry.npmmirror.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98"
+  integrity sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==
+  dependencies:
+    traverse ">=0.3.0 <0.4"
+
 chalk@^2.4.1, chalk@^2.4.2:
   version "2.4.2"
   resolved "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@@ -3828,6 +3970,16 @@ commondir@^1.0.1:
   resolved "https://registry.npmmirror.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
   integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==
 
+compress-commons@^4.1.2:
+  version "4.1.2"
+  resolved "https://registry.npmmirror.com/compress-commons/-/compress-commons-4.1.2.tgz#6542e59cb63e1f46a8b21b0e06f9a32e4c8b06df"
+  integrity sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==
+  dependencies:
+    buffer-crc32 "^0.2.13"
+    crc32-stream "^4.0.2"
+    normalize-path "^3.0.0"
+    readable-stream "^3.6.0"
+
 compressible@~2.0.16:
   version "2.0.18"
   resolved "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
@@ -3956,11 +4108,19 @@ cosmiconfig@^7.0.0:
     path-type "^4.0.0"
     yaml "^1.10.0"
 
-crc-32@~1.2.0:
+crc-32@^1.2.0, crc-32@~1.2.0:
   version "1.2.2"
   resolved "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff"
   integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
 
+crc32-stream@^4.0.2:
+  version "4.0.3"
+  resolved "https://registry.npmmirror.com/crc32-stream/-/crc32-stream-4.0.3.tgz#85dd677eb78fa7cad1ba17cc506a597d41fc6f33"
+  integrity sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==
+  dependencies:
+    crc-32 "^1.2.0"
+    readable-stream "^3.4.0"
+
 cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
   version "7.0.3"
   resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@@ -4196,6 +4356,11 @@ dayjs@^1.11.10, dayjs@^1.11.7, dayjs@^1.9.1:
   resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0"
   integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==
 
+dayjs@^1.8.34:
+  version "1.11.13"
+  resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c"
+  integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
+
 debug@2.6.9, debug@^2.6.0:
   version "2.6.9"
   resolved "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -4514,6 +4679,13 @@ draftjs-utils@^0.9.4:
   resolved "https://registry.npmmirror.com/draftjs-utils/-/draftjs-utils-0.9.4.tgz#976c61aa133dbbbfedd65ae1dd6627d7b98c6f08"
   integrity sha512-KYjABSbGpJrwrwmxVj5UhfV37MF/p0QRxKIyL+/+QOaJ8J9z1FBKxkblThbpR0nJi9lxPQWGg+gh+v0dAsSCCg==
 
+duplexer2@~0.1.4:
+  version "0.1.4"
+  resolved "https://registry.npmmirror.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
+  integrity sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==
+  dependencies:
+    readable-stream "^2.0.2"
+
 duplexer@^0.1.2:
   version "0.1.2"
   resolved "https://registry.npmmirror.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
@@ -4586,6 +4758,13 @@ encoding@^0.1.11:
   dependencies:
     iconv-lite "^0.6.2"
 
+end-of-stream@^1.4.1:
+  version "1.4.4"
+  resolved "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
+  integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
+  dependencies:
+    once "^1.4.0"
+
 enhanced-resolve@^5.15.0:
   version "5.15.0"
   resolved "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35"
@@ -5070,6 +5249,21 @@ events@^3.2.0:
   resolved "https://registry.npmmirror.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
   integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
 
+exceljs@^4.4.0:
+  version "4.4.0"
+  resolved "https://registry.npmmirror.com/exceljs/-/exceljs-4.4.0.tgz#cfb1cb8dcc82c760a9fc9faa9e52dadab66b0156"
+  integrity sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==
+  dependencies:
+    archiver "^5.0.0"
+    dayjs "^1.8.34"
+    fast-csv "^4.3.1"
+    jszip "^3.10.1"
+    readable-stream "^3.6.0"
+    saxes "^5.0.1"
+    tmp "^0.2.0"
+    unzipper "^0.10.11"
+    uuid "^8.3.0"
+
 execa@^5.0.0:
   version "5.1.1"
   resolved "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
@@ -5153,6 +5347,14 @@ express@^4.17.3:
     utils-merge "1.0.1"
     vary "~1.1.2"
 
+fast-csv@^4.3.1:
+  version "4.3.6"
+  resolved "https://registry.npmmirror.com/fast-csv/-/fast-csv-4.3.6.tgz#70349bdd8fe4d66b1130d8c91820b64a21bc4a63"
+  integrity sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==
+  dependencies:
+    "@fast-csv/format" "4.3.5"
+    "@fast-csv/parse" "4.3.6"
+
 fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
   version "3.1.3"
   resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@@ -5393,6 +5595,11 @@ fresh@0.5.2:
   resolved "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
   integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
 
+fs-constants@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
+  integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
+
 fs-extra@^10.0.0:
   version "10.1.0"
   resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
@@ -5427,6 +5634,16 @@ fsevents@^2.3.2, fsevents@~2.3.2:
   resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
   integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
 
+fstream@^1.0.12:
+  version "1.0.12"
+  resolved "https://registry.npmmirror.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045"
+  integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==
+  dependencies:
+    graceful-fs "^4.1.2"
+    inherits "~2.0.0"
+    mkdirp ">=0.5 0"
+    rimraf "2"
+
 function-bind@^1.1.2:
   version "1.1.2"
   resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
@@ -5522,7 +5739,7 @@ glob@^10.3.10:
     minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
     path-scurry "^1.10.1"
 
-glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
+glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.3:
   version "7.2.3"
   resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
   integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@@ -5588,7 +5805,7 @@ gopd@^1.0.1:
   dependencies:
     get-intrinsic "^1.1.3"
 
-graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
+graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
   version "4.2.11"
   resolved "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
   integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@@ -5862,11 +6079,21 @@ identity-obj-proxy@^3.0.0:
   dependencies:
     harmony-reflect "^1.4.6"
 
+ieee754@^1.1.13:
+  version "1.2.1"
+  resolved "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
+  integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
+
 ignore@^5.2.0:
   version "5.3.1"
   resolved "https://registry.npmmirror.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
   integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
 
+immediate@~3.0.5:
+  version "3.0.6"
+  resolved "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
+  integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
+
 immer@^9.0.7:
   version "9.0.21"
   resolved "https://registry.npmmirror.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176"
@@ -5916,7 +6143,7 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
+inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3:
   version "2.0.4"
   resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -7021,6 +7248,16 @@ jszip-utils@^0.1.0:
   resolved "https://registry.npmmirror.com/jszip-utils/-/jszip-utils-0.1.0.tgz#8c04cdedcdb291e83f055f5b261b3a3188ceca0b"
   integrity sha512-tBNe0o3HAf8vo0BrOYnLPnXNo5A3KsRMnkBFYjh20Y3GPYGfgyoclEMgvVchx0nnL+mherPi74yLPIusHUQpZg==
 
+jszip@^3.10.1:
+  version "3.10.1"
+  resolved "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2"
+  integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
+  dependencies:
+    lie "~3.3.0"
+    pako "~1.0.2"
+    readable-stream "~2.3.6"
+    setimmediate "^1.0.5"
+
 keyv@^4.5.3:
   version "4.5.4"
   resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
@@ -7063,6 +7300,13 @@ launch-editor@^2.6.0:
     picocolors "^1.0.0"
     shell-quote "^1.8.1"
 
+lazystream@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.npmmirror.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638"
+  integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==
+  dependencies:
+    readable-stream "^2.0.5"
+
 leven@^3.1.0:
   version "3.1.0"
   resolved "https://registry.npmmirror.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
@@ -7084,6 +7328,13 @@ levn@~0.3.0:
     prelude-ls "~1.1.2"
     type-check "~0.3.2"
 
+lie@~3.3.0:
+  version "3.3.0"
+  resolved "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
+  integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
+  dependencies:
+    immediate "~3.0.5"
+
 lilconfig@^2.0.3, lilconfig@^2.1.0:
   version "2.1.0"
   resolved "https://registry.npmmirror.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
@@ -7099,6 +7350,11 @@ lines-and-columns@^1.1.6:
   resolved "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
   integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
 
+listenercount@~1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npmmirror.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937"
+  integrity sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==
+
 loader-runner@^4.2.0:
   version "4.3.0"
   resolved "https://registry.npmmirror.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1"
@@ -7145,11 +7401,66 @@ lodash.debounce@^4.0.8:
   resolved "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
   integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
 
+lodash.defaults@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.npmmirror.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
+  integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==
+
+lodash.difference@^4.5.0:
+  version "4.5.0"
+  resolved "https://registry.npmmirror.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c"
+  integrity sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==
+
+lodash.escaperegexp@^4.1.2:
+  version "4.1.2"
+  resolved "https://registry.npmmirror.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
+  integrity sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==
+
+lodash.flatten@^4.4.0:
+  version "4.4.0"
+  resolved "https://registry.npmmirror.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
+  integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==
+
 lodash.flow@^3.5.0:
   version "3.5.0"
   resolved "https://registry.npmmirror.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a"
   integrity sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==
 
+lodash.groupby@^4.6.0:
+  version "4.6.0"
+  resolved "https://registry.npmmirror.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1"
+  integrity sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==
+
+lodash.isboolean@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
+  integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
+
+lodash.isequal@^4.5.0:
+  version "4.5.0"
+  resolved "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
+  integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
+
+lodash.isfunction@^3.0.9:
+  version "3.0.9"
+  resolved "https://registry.npmmirror.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051"
+  integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==
+
+lodash.isnil@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.npmmirror.com/lodash.isnil/-/lodash.isnil-4.0.0.tgz#49e28cd559013458c814c5479d3c663a21bfaa6c"
+  integrity sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==
+
+lodash.isplainobject@^4.0.6:
+  version "4.0.6"
+  resolved "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
+  integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
+
+lodash.isundefined@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.npmmirror.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz#23ef3d9535565203a66cefd5b830f848911afb48"
+  integrity sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==
+
 lodash.memoize@^4.1.2:
   version "4.1.2"
   resolved "https://registry.npmmirror.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@@ -7165,6 +7476,11 @@ lodash.sortby@^4.7.0:
   resolved "https://registry.npmmirror.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
   integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==
 
+lodash.union@^4.6.0:
+  version "4.6.0"
+  resolved "https://registry.npmmirror.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88"
+  integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==
+
 lodash.uniq@^4.5.0:
   version "4.5.0"
   resolved "https://registry.npmmirror.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
@@ -7338,7 +7654,7 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
   dependencies:
     brace-expansion "^1.1.7"
 
-minimatch@^5.0.1:
+minimatch@^5.0.1, minimatch@^5.1.0:
   version "5.1.6"
   resolved "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
   integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
@@ -7362,7 +7678,7 @@ minimist@^1.2.0, minimist@^1.2.6:
   resolved "https://registry.npmmirror.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c"
   integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==
 
-mkdirp@~0.5.1:
+"mkdirp@>=0.5 0", mkdirp@~0.5.1:
   version "0.5.6"
   resolved "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
   integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
@@ -7610,7 +7926,7 @@ on-headers@~1.0.2:
   resolved "https://registry.npmmirror.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
   integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
 
-once@^1.3.0:
+once@^1.3.0, once@^1.4.0:
   version "1.4.0"
   resolved "https://registry.npmmirror.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
   integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
@@ -7710,6 +8026,11 @@ pako@^2.1.0:
   resolved "https://registry.npmmirror.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86"
   integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==
 
+pako@~1.0.2:
+  version "1.0.11"
+  resolved "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
+  integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
+
 param-case@^3.0.4:
   version "3.0.4"
   resolved "https://registry.npmmirror.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"
@@ -9146,7 +9467,7 @@ read-cache@^1.0.0:
   dependencies:
     pify "^2.3.0"
 
-readable-stream@^2.0.1:
+readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@~2.3.6:
   version "2.3.8"
   resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
   integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
@@ -9159,7 +9480,7 @@ readable-stream@^2.0.1:
     string_decoder "~1.1.1"
     util-deprecate "~1.0.1"
 
-readable-stream@^3.0.6:
+readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
   version "3.6.2"
   resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
   integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
@@ -9168,6 +9489,13 @@ readable-stream@^3.0.6:
     string_decoder "^1.1.1"
     util-deprecate "^1.0.1"
 
+readdir-glob@^1.1.2:
+  version "1.1.3"
+  resolved "https://registry.npmmirror.com/readdir-glob/-/readdir-glob-1.1.3.tgz#c3d831f51f5e7bfa62fa2ffbe4b508c640f09584"
+  integrity sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==
+  dependencies:
+    minimatch "^5.1.0"
+
 readdirp@~3.6.0:
   version "3.6.0"
   resolved "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
@@ -9385,6 +9713,13 @@ reusify@^1.0.4:
   resolved "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
   integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
 
+rimraf@2:
+  version "2.7.1"
+  resolved "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
+  integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
+  dependencies:
+    glob "^7.1.3"
+
 rimraf@^3.0.0, rimraf@^3.0.2:
   version "3.0.2"
   resolved "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
@@ -9660,7 +9995,7 @@ set-function-name@^2.0.0, set-function-name@^2.0.1:
     functions-have-names "^1.2.3"
     has-property-descriptors "^1.0.2"
 
-setimmediate@^1.0.5:
+setimmediate@^1.0.5, setimmediate@~1.0.4:
   version "1.0.5"
   resolved "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
   integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
@@ -10166,6 +10501,17 @@ tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1:
   resolved "https://registry.npmmirror.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
   integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
 
+tar-stream@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
+  integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
+  dependencies:
+    bl "^4.0.3"
+    end-of-stream "^1.4.1"
+    fs-constants "^1.0.0"
+    inherits "^2.0.3"
+    readable-stream "^3.1.1"
+
 temp-dir@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmmirror.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e"
@@ -10268,6 +10614,11 @@ tiny-warning@^1.0.0:
   resolved "https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
   integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
 
+tmp@^0.2.0:
+  version "0.2.3"
+  resolved "https://registry.npmmirror.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae"
+  integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==
+
 tmpl@1.0.5:
   version "1.0.5"
   resolved "https://registry.npmmirror.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
@@ -10319,6 +10670,11 @@ tr46@^2.1.0:
   dependencies:
     punycode "^2.1.1"
 
+"traverse@>=0.3.0 <0.4":
+  version "0.3.9"
+  resolved "https://registry.npmmirror.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9"
+  integrity sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==
+
 tryer@^1.0.1:
   version "1.0.1"
   resolved "https://registry.npmmirror.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
@@ -10534,6 +10890,22 @@ unquote@~1.1.1:
   resolved "https://registry.npmmirror.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544"
   integrity sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==
 
+unzipper@^0.10.11:
+  version "0.10.14"
+  resolved "https://registry.npmmirror.com/unzipper/-/unzipper-0.10.14.tgz#d2b33c977714da0fbc0f82774ad35470a7c962b1"
+  integrity sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==
+  dependencies:
+    big-integer "^1.6.17"
+    binary "~0.3.0"
+    bluebird "~3.4.1"
+    buffer-indexof-polyfill "~1.0.0"
+    duplexer2 "~0.1.4"
+    fstream "^1.0.12"
+    graceful-fs "^4.2.2"
+    listenercount "~1.0.1"
+    readable-stream "~2.3.6"
+    setimmediate "~1.0.4"
+
 upath@^1.2.0:
   version "1.2.0"
   resolved "https://registry.npmmirror.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
@@ -10592,7 +10964,7 @@ utils-merge@1.0.1:
   resolved "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
   integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
 
-uuid@^8.3.2:
+uuid@^8.3.0, uuid@^8.3.2:
   version "8.3.2"
   resolved "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
   integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
@@ -11196,6 +11568,15 @@ yocto-queue@^0.1.0:
   resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
   integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
 
+zip-stream@^4.1.0:
+  version "4.1.1"
+  resolved "https://registry.npmmirror.com/zip-stream/-/zip-stream-4.1.1.tgz#1337fe974dbaffd2fa9a1ba09662a66932bd7135"
+  integrity sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==
+  dependencies:
+    archiver-utils "^3.0.4"
+    compress-commons "^4.1.2"
+    readable-stream "^3.6.0"
+
 zrender@5.6.1:
   version "5.6.1"
   resolved "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz#e08d57ecf4acac708c4fcb7481eb201df7f10a6b"