vue:功能:table动态合并+前端导出

news2024/9/23 9:24:07

场景:第一列WM 名字相同,行内合并,后面的列合并的行数根据第一列合并的行来。第二列‘累计请假天数'根据合并的几列显示数值。后续需合并的列以第一列合并行数为基准

注)当前数据表头固定,行内数据不固定。以第一列WM为判断条件,相同名字的那几行数据合并单元格。合并相同名字的那几行数据,后面的列有需要和第一列合并条件一样。

1、根据查询条件:日期。查询近三月日期数据。表头的日期是根据查询的日期条件的前三个月来。日期格式月份转换成英文格式。

2、导出的数据和页面保持一致

注)合并数据,后端提供的数据可能不是按照人在一起的顺序,前端需要处理下数据格式,把同一个人的放在一起

注)本示例是用 vue3+element plus 实现的。

效果图:

一、完整代码实现

1、页面代码

<template>
  <!-- 搜索工作栏 -->
  <el-form ref="queryFormRef" :inline="true" :model="queryParams" class="-mb-15px searchFlex2">
    <div class="left">
      <el-form-item label="日期" prop="startDate">
        <el-date-picker
          v-model="queryParams.startDate"
          type="month"
          value-format="YYYY-MM"
          :placeholder="t('common.selectText')"
          :clearable="false"
          @change="handleQuery"
          class="!w-140px"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" circle @click="handleQuery">
          <Icon icon="ep:search" />
        </el-button>
        <el-button type="primary" circle @click="resetQuery">
          <Icon icon="ep:refresh" />
        </el-button>
      </el-form-item>
    </div>
    <div class="right">
      <el-button plain type="success" @click="handleExport">
        <Icon icon="ep:download" />
        {{ t('action.export') }}
      </el-button>
    </div>
  </el-form>

  <!-- 列表 -->
  <ContentWrap>
    <el-table v-loading="loading" :data="list" :span-method="objectSpanMethod">
      <el-table-column
        label="WM"
        prop="name"
        show-overflow-tooltip
        min-width="100"
        align="center"
      />
      <el-table-column
        label="Total Leave days"
        prop="leaveDays"
        show-overflow-tooltip
        min-width="120"
        align="center"
      />
      <el-table-column
        label="Paid Leave Date"
        prop="paidLeaveDate"
        show-overflow-tooltip
        min-width="120"
        align="center"
      />
      <el-table-column label="Actual working days of previous 3 months" align="center">
        <el-table-column
          :label="
            handleFilterMonth(dayjs(queryParams.startDate).subtract(3, 'month').format('YYYY-MM'))
          "
          prop="actualDaysOneMonth"
          show-overflow-tooltip
          min-width="100"
          align="center"
        />
        <el-table-column
          :label="
            handleFilterMonth(dayjs(queryParams.startDate).subtract(2, 'month').format('YYYY-MM'))
          "
          prop="actualDaysTwoMonth"
          show-overflow-tooltip
          min-width="100"
          align="center"
        />
        <el-table-column
          :label="
            handleFilterMonth(dayjs(queryParams.startDate).subtract(1, 'month').format('YYYY-MM'))
          "
          prop="actualDaysThreeMonth"
          show-overflow-tooltip
          min-width="100"
          align="center"
        />
      </el-table-column>
      <el-table-column label="Payout of commission 3 months" align="center">
        <el-table-column
          :label="
            handleFilterMonth(dayjs(queryParams.startDate).subtract(3, 'month').format('YYYY-MM'))
          "
          prop="payoutCommissionOneMonthPrice"
          show-overflow-tooltip
          min-width="100"
          align="center"
        />
        <el-table-column
          :label="
            handleFilterMonth(dayjs(queryParams.startDate).subtract(2, 'month').format('YYYY-MM'))
          "
          prop="payoutCommissionTwoMonthPrice"
          show-overflow-tooltip
          min-width="100"
          align="center"
        />
        <el-table-column
          :label="
            handleFilterMonth(dayjs(queryParams.startDate).subtract(1, 'month').format('YYYY-MM'))
          "
          prop="payoutCommissionThreeMonthPrice"
          show-overflow-tooltip
          min-width="100"
          align="center"
        />
      </el-table-column>
      <el-table-column
        label="Average commission / day"
        prop="averageCommission"
        show-overflow-tooltip
        min-width="140"
        align="center"
      />
      <el-table-column
        label="Commission during leave"
        prop="commissionDuringLeave"
        show-overflow-tooltip
        min-width="120"
        :formatter="
          (row) =>
            row.commissionDuringLeave === null || row.commissionDuringLeave === ''
              ? '0.00'
              : row.commissionDuringLeave
        "
        align="center"
      />
      <el-table-column
        label="Leave Payout"
        prop="leavePayout"
        show-overflow-tooltip
        min-width="120"
        :formatter="
          (row) => (row.leavePayout === null || row.leavePayout === '' ? '0.00' : row.leavePayout)
        "
        align="center"
      />
      <el-table-column
        label="Total Leave Payout"
        prop="totalLeavePayout"
        show-overflow-tooltip
        min-width="120"
        align="center"
      />
    </el-table>
  </ContentWrap>
</template>

<script lang="ts" setup>
import dayjs from 'dayjs'
import * as api from '@/api/trade/LeaveDay'
import useConfig from '@/views/trade/LeavePay/hooks/useConfig'
const { handleFilterMonth } = useConfig()
import OutExcelSheet from '@/hooks/web/outToExcelManySheet'

defineOptions({ name: 'LeaveDay' })

const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化

const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
  startDate: dayjs().subtract(1, 'month').format('YYYY-MM')
})
const queryFormRef = ref() // 搜索的表单

/** 搜索按钮操作 */
const handleQuery = () => {
  getList()
}

/** 重置按钮操作 */
const resetQuery = () => {
  queryFormRef.value.resetFields()
  handleQuery()
}

/** 查询列表 */
const getList = async () => {
  // mock 数据
  // loading.value = false
  // list.value = tableList.value
  // 列表数据
  loading.value = true
  try {
    // const data = await api.getLeavePayPage(queryParams)
    // mock数据
    const data = tableList.value

    // 1、TotalLeavePayOut 根据 同一个name 的 LeavePayOut 的总和
    const names = [...new Set(data.map((item) => item.name))]
    const updatedDemo = names.map((name) => {
      const list = data.filter((val) => val.name === name)
      const sum = list.reduce((accumulator, current) => {
        return accumulator + Number(current.leavePayout || 0)
      }, 0)

      return {
        name,
        list,
        sum: sum.toString()
      }
    })
    // console.log('updatedDemo', updatedDemo)
    // 2、导出表格时所需的合计值
    totalSum.value = updatedDemo.reduce((accumulator, item) => {
      if (item.sum) {
        return accumulator + parseFloat(item.sum)
      }
      return accumulator
    }, 0)
    console.log('累计值', totalSum.value)

    // 3、数据是没有按顺序来的,需处理成一个人的假期放在一起,方便合并数据
    const newList = updatedDemo.flatMap((item) => item.list)
    console.log(newList)

    // 3、合计的值放到每行对应的数据里
    const result = newList.map((item) => {
      const matchingItem = updatedDemo.find((val) => val.name === item.name)
      if (matchingItem) {
        item.totalLeavePayout = matchingItem.sum
      }
      return item
    })
    console.log('result', result)

    // 4、最终数据
    list.value = result
  } finally {
    loading.value = false
  }
}

// mock数据
const tableList = ref([
  {
    name: 'karla',
    leaveDays: null,
    paidLeaveDate: '2023-01-01',
    actualDaysOneMonth: '10',
    actualDaysTwoMonth: '18',
    actualDaysThreeMonth: '18',
    payoutCommissionOneMonthPrice: '10000',
    payoutCommissionTwoMonthPrice: '20000',
    payoutCommissionThreeMonthPrice: '30000',
    averageCommission: '1400',
    commissionDuringLeave: '640',
    leavePayout: '760',
    totalLeavePayout: ''
  },
  {
    name: 'karla',
    leaveDays: null,
    paidLeaveDate: '2023-01-04',
    actualDaysOneMonth: '10',
    actualDaysTwoMonth: '18',
    actualDaysThreeMonth: '18',
    payoutCommissionOneMonthPrice: '10000',
    payoutCommissionTwoMonthPrice: '20000',
    payoutCommissionThreeMonthPrice: '30000',
    averageCommission: '1400',
    commissionDuringLeave: '1600',
    leavePayout: '0',
    totalLeavePayout: ''
  },
  {
    name: 'karla',
    leaveDays: null,
    paidLeaveDate: '2023-01-06',
    actualDaysOneMonth: '10',
    actualDaysTwoMonth: '18',
    actualDaysThreeMonth: '18',
    payoutCommissionOneMonthPrice: '10000',
    payoutCommissionTwoMonthPrice: '20000',
    payoutCommissionThreeMonthPrice: '30000',
    averageCommission: '1400',
    commissionDuringLeave: '1800',
    leavePayout: '0',
    totalLeavePayout: ''
  },
  {
    name: 'karla',
    leaveDays: null,
    paidLeaveDate: '2023-01-24',
    actualDaysOneMonth: '10',
    actualDaysTwoMonth: '18',
    actualDaysThreeMonth: '18',
    payoutCommissionOneMonthPrice: '10000',
    payoutCommissionTwoMonthPrice: '20000',
    payoutCommissionThreeMonthPrice: '30000',
    averageCommission: '1400',
    commissionDuringLeave: '',
    leavePayout: '0',
    totalLeavePayout: ''
  },
  {
    name: 'karla',
    leaveDays: null,
    paidLeaveDate: '2023-01-18',
    actualDaysOneMonth: '10',
    actualDaysTwoMonth: '18',
    actualDaysThreeMonth: '18',
    payoutCommissionOneMonthPrice: '10000',
    payoutCommissionTwoMonthPrice: '20000',
    payoutCommissionThreeMonthPrice: '30000',
    averageCommission: '1400',
    commissionDuringLeave: '1200',
    leavePayout: '200',
    totalLeavePayout: ''
  },
  {
    name: 'York',
    leaveDays: null,
    paidLeaveDate: '2023-01-18',
    actualDaysOneMonth: '28',
    actualDaysTwoMonth: '24',
    actualDaysThreeMonth: '18',
    payoutCommissionOneMonthPrice: '10000',
    payoutCommissionTwoMonthPrice: '20000',
    payoutCommissionThreeMonthPrice: '30000',
    averageCommission: '1500',
    commissionDuringLeave: '1800',
    leavePayout: '0',
    totalLeavePayout: ''
  },
  {
    name: 'York',
    leaveDays: null,
    paidLeaveDate: '2023-01-24',
    actualDaysOneMonth: '28',
    actualDaysTwoMonth: '24',
    actualDaysThreeMonth: '18',
    payoutCommissionOneMonthPrice: '10000',
    payoutCommissionTwoMonthPrice: '20000',
    payoutCommissionThreeMonthPrice: '30000',
    averageCommission: '1500',
    commissionDuringLeave: '700',
    leavePayout: '800',
    totalLeavePayout: ''
  },
  {
    name: 'Caleb',
    leaveDays: null,
    paidLeaveDate: '2023-01-29',
    actualDaysOneMonth: '22',
    actualDaysTwoMonth: '15',
    actualDaysThreeMonth: '17',
    payoutCommissionOneMonthPrice: '8899.12',
    payoutCommissionTwoMonthPrice: '7833',
    payoutCommissionThreeMonthPrice: '1455.63',
    averageCommission: '1366.8',
    commissionDuringLeave: '734.8',
    leavePayout: '632',
    totalLeavePayout: ''
  }
])
const exportList = ref([
  [
    'WM',
    'Total Leave days',
    'Paid Leave Date',
    'Actual working days of previous 3 months\t(第一个月)',
    'Actual working days of previous 3 months\t(第二个月)',
    'Actual working days of previous 3 months\t(第三个月)',
    'Payout of commission 3 months\t第一个月)',
    'Payout of commission 3 months\t(第二个月)',
    'Payout of commission 3 months\t(第三个月)',
    'Average commission / day',
    'Commission during leave',
    'Leave Payout',
    'Total Leave Payout'
  ],
  [
    'karla',
    5,
    '2023-01-01',
    '10',
    '18',
    '18',
    '10000',
    '20000',
    '30000',
    '1400',
    '640',
    '760',
    '0.00'
  ],
  [
    'karla',
    5,
    '2023-01-04',
    '10',
    '18',
    '18',
    '10000',
    '20000',
    '30000',
    '1400',
    '1600',
    '0.00',
    '0.00'
  ],
  [
    'karla',
    5,
    '2023-01-06',
    '10',
    '18',
    '18',
    '10000',
    '20000',
    '30000',
    '1400',
    '1800',
    '0.00',
    '0.00'
  ],
  [
    'karla',
    5,
    '2023-01-24',
    '10',
    '18',
    '18',
    '10000',
    '20000',
    '30000',
    '1400',
    '0.00',
    '0.00',
    '0.00'
  ],
  [
    'karla',
    5,
    '2023-01-18',
    '10',
    '18',
    '18',
    '10000',
    '20000',
    '30000',
    '1400',
    '1600',
    '0.00',
    '0.00'
  ],
  [
    'York',
    2,
    '2023-01-18',
    '28',
    '24',
    '18',
    '10000',
    '20000',
    '30000',
    '1500',
    '1800',
    '0.00',
    '666'
  ],
  [
    'York',
    2,
    '2023-01-24',
    '28',
    '24',
    '18',
    '10000',
    '20000',
    '30000',
    '1500',
    '700',
    '800',
    '666'
  ],
  [
    'Caleb',
    1,
    '2023-01-29',
    '22',
    '15',
    '17',
    '8899.12',
    '7833',
    '1455.63',
    '1366.8',
    '734.8',
    '632',
    '0.00'
  ]
])

const saveRow = ref() // 存放的就是第一列中同name的合并
const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
  // 第一列(WM)行合并
  if (columnIndex === 0) {
    // 1、取出tableList中每条数据的name,并去重
    const names = [...new Set(list.value.map((item) => item.name))]

    // 2、对表格数据按照name进行排序,并保持原始顺序
    const sortedData = names.reduce((acc, name) => {
      const matchingItems = list.value.filter((item) => item.name === name)
      return [...acc, ...matchingItems]
    }, [])

    // 3、匹配出names中的符合名字的第条数据的索引和最后一条索引。放在arr的数组里
    const arr = names.map((name) => {
      const firstIndex = sortedData.findIndex((item) => item.name === name)
      const lastIndex = sortedData.reduceRight((acc, item, index) => {
        if (item.name === name && acc === -1) {
          return index
        }
        return acc
      }, -1)
      return { firstNameIndex: firstIndex, lastNameIndex: lastIndex }
    })
    saveRow.value = arr

    // 4、把当列数据中相同的数据起始和结束的单元格进行合并
    const matchingData = arr.find(
      (item) => item.firstNameIndex <= rowIndex && rowIndex <= item.lastNameIndex
    )

    // 5、结果
    if (matchingData) {
      if (rowIndex === matchingData.firstNameIndex) {
        return {
          rowspan: matchingData.lastNameIndex - matchingData.firstNameIndex + 1,
          colspan: 1
        }
      } else {
        return {
          rowspan: 0,
          colspan: 0
        }
      }
    }
  }
  // 需合并列:根据第一列中需要合并的行数saveRow.value 来合并对应列下的行内合并
  if ([1, 3, 4, 5, 6, 7, 8, 12].includes(columnIndex)) {
    const matchingData = saveRow.value.find(
      (item) => item.firstNameIndex <= rowIndex && rowIndex <= item.lastNameIndex
    )

    if (matchingData) {
      if (rowIndex === matchingData.firstNameIndex) {
        const params = {
          rowspan: matchingData.lastNameIndex - matchingData.firstNameIndex + 1,
          colspan: 1,
          value: matchingData.lastNameIndex - matchingData.firstNameIndex + 1
        }
        list.value[rowIndex].leaveDays = params.value
        return params
      } else {
        return {
          rowspan: 0,
          colspan: 0
        }
      }
    }
  }
}

/** 导出 */
const exportLoading = ref(false)
const handleExport = async () => {
  try {
    // 导出的二次确认
    await message.exportConfirm()
    exportLoading.value = true

    const columnsHeader = {
      name: 'WM',
      leaveDays: 'Total Leave days',
      paidLeaveDate: 'Paid Leave Date',
      actualDaysOneMonth: handleFilterMonth(
        dayjs(queryParams.startDate).subtract(3, 'month').format('YYYY-MM')
      ),
      actualDaysTwoMonth: handleFilterMonth(
        dayjs(queryParams.startDate).subtract(2, 'month').format('YYYY-MM')
      ),
      actualDaysThreeMonth: handleFilterMonth(
        dayjs(queryParams.startDate).subtract(1, 'month').format('YYYY-MM')
      ),
      payoutCommissionOneMonthPrice: handleFilterMonth(
        dayjs(queryParams.startDate).subtract(3, 'month').format('YYYY-MM')
      ),
      payoutCommissionTwoMonthPrice: handleFilterMonth(
        dayjs(queryParams.startDate).subtract(2, 'month').format('YYYY-MM')
      ),
      payoutCommissionThreeMonthPrice: handleFilterMonth(
        dayjs(queryParams.startDate).subtract(1, 'month').format('YYYY-MM')
      ),
      averageCommission: 'Average commission/day',
      commissionDuringLeave: 'Commission during leave',
      leavePayout: 'Leave Payout',
      totalLeavePayout: 'Total Leave Payout'
    }

    const sheet1 = {
      name: 'LeavePay',
      data: [
        [
          'WM',
          'Total Leave days',
          'Paid Leave Date',
          'Actual working days of previous 3 months',
          '',
          '',
          'Payout of commission 3 months',
          '',
          '',
          'Average commission / day',
          'Commission during leave',
          'Leave Payout',
          'Total Leave Payout'
        ],
        // ...exportList.value,
        ...OutExcelSheet.handleExcelTable(columnsHeader, list.value),
        ['', '', '', '', '', '', '', '', '', '', '', 'Total', '999']
      ],
      merges: [],
      rowHeights: [{ hpx: 20 }, { hpx: 20 }]
    }

    // 合并:第0列、第1列、第三列、第四列、第五列、第六列、第七列和第八列的相同值进行行合并
    const mergedRows = new Map()
    for (let i = 1; i < sheet1.data.length; i++) {
      const cellValue0 = sheet1.data[i][0]
      const cellValue1 = sheet1.data[i][1]
      const cellValue3 = sheet1.data[i][3]
      const cellValue4 = sheet1.data[i][4]
      const cellValue5 = sheet1.data[i][5]
      const cellValue6 = sheet1.data[i][6]
      const cellValue7 = sheet1.data[i][7]
      const cellValue8 = sheet1.data[i][8]
      const prevValue0 = sheet1.data[i - 1][0]
      const prevValue1 = sheet1.data[i - 1][1]
      const prevValue3 = sheet1.data[i - 1][3]
      const prevValue4 = sheet1.data[i - 1][4]
      const prevValue5 = sheet1.data[i - 1][5]
      const prevValue6 = sheet1.data[i - 1][6]
      const prevValue7 = sheet1.data[i - 1][7]
      const prevValue8 = sheet1.data[i - 1][8]

      if (
        cellValue0 === prevValue0 &&
        cellValue1 === prevValue1 &&
        cellValue3 === prevValue3 &&
        cellValue4 === prevValue4 &&
        cellValue5 === prevValue5 &&
        cellValue6 === prevValue6 &&
        cellValue7 === prevValue7 &&
        cellValue8 === prevValue8
      ) {
        if (mergedRows.has(cellValue0)) {
          // 更新合并的结束行索引
          mergedRows.get(cellValue0).end = i
        } else {
          // 添加新的合并信息
          mergedRows.set(cellValue0, { start: i - 1, end: i })
        }
      }
    }

    // 添加行合并信息到 mergesHeader
    for (const [value, { start, end }] of mergedRows.entries()) {
      sheet1.merges.push({ s: { r: start, c: 0 }, e: { r: end, c: 0 } })
      sheet1.merges.push({ s: { r: start, c: 1 }, e: { r: end, c: 1 } })
      sheet1.merges.push({ s: { r: start, c: 3 }, e: { r: end, c: 3 } })
      sheet1.merges.push({ s: { r: start, c: 4 }, e: { r: end, c: 4 } })
      sheet1.merges.push({ s: { r: start, c: 5 }, e: { r: end, c: 5 } })
      sheet1.merges.push({ s: { r: start, c: 6 }, e: { r: end, c: 6 } })
      sheet1.merges.push({ s: { r: start, c: 7 }, e: { r: end, c: 7 } })
      sheet1.merges.push({ s: { r: start, c: 8 }, e: { r: end, c: 8 } })
      sheet1.merges.push({ s: { r: start, c: 12 }, e: { r: end, c: 12 } })
    }

    const mergesHeader = [
      // 行合并
      { s: { r: 0, c: 3 }, e: { r: 0, c: 5 } },
      { s: { r: 0, c: 6 }, e: { r: 0, c: 8 } },
      // 列合并(r 表示行索引,c 表示列索引)
      { s: { r: 0, c: 0 }, e: { r: 1, c: 0 } }, // 第0列的第0行和第1行合并
      { s: { r: 0, c: 1 }, e: { r: 1, c: 1 } }, // 第1列的第0行和第1行合并
      { s: { r: 0, c: 2 }, e: { r: 1, c: 2 } }, // 第2列的第1行和第1行合并
      { s: { r: 0, c: 9 }, e: { r: 1, c: 9 } },
      { s: { r: 0, c: 10 }, e: { r: 1, c: 10 } },
      { s: { r: 0, c: 11 }, e: { r: 1, c: 11 } },
      { s: { r: 0, c: 12 }, e: { r: 1, c: 12 } }
    ]

    const sheetData = [sheet1]

    OutExcelSheet.exportSheetExcel(sheetData, mergesHeader, `LeavePay导出.xlsx`, true)
  } catch {
  } finally {
    exportLoading.value = false
  }
}

/** 初始化 **/
onMounted(() => {
  getList()
})
</script>

2、封装的方法(导出)

import XLSX from 'xlsx-js-style'
import FileSaver from 'file-saver'

export default {
  exportSheetExcel(sheetData, mergerArr, fileName = 'karlaExport.xlsx', statusBorder = false) {
    const wb = XLSX.utils.book_new() // 创建一个新工作簿

    for (let i = 0; i < sheetData.length; i++) {
      const sheet = sheetData[i]

      // 检查数据项是否存在
      if (!sheet.data) {
        continue // 如果数据项不存在,则跳过当前循环
      }

      const ws = XLSX.utils.aoa_to_sheet(sheet.data) // 将数据数组转换为工作表

      // 设置合并单元格
      ws['!merges'] =
        sheet.merges && sheet.merges.length > 0
          ? [...sheet.merges, ...(mergerArr || [])]
          : mergerArr

      // 设置列宽为自适应
      if (sheet.data.length > 0) {
        ws['!cols'] = sheet.data[0].map((_, index) => ({ wch: 15 }))
      }

      // 设置行高
      if (sheet.rowHeights && sheet.rowHeights.length > 0) {
        ws['!rows'] = sheet.rowHeights.map((height) => ({ hpt: height, hpx: height }))
      }

      const borderAll = {
        top: { style: 'thin' },
        bottom: { style: 'thin' },
        left: { style: 'thin' },
        right: { style: 'thin' }
      }

      // 设置单元格样式
      for (const key in ws) {
        if (ws.hasOwnProperty(key)) {
          const cell = ws[key]
          if (cell && typeof cell === 'object') {
            // 判断是否为最后一行【核心】
            const rowIndex = parseInt(key.replace(/[A-Z]/g, ''))
            if (statusBorder && rowIndex === sheet.data.length) {
              if (cell.s && cell.s.border) {
                delete cell.s.border.right // 删除右侧边框样式
              }
              // 内容居中
              cell.s = cell.s || {}
              cell.s.alignment = cell.s.alignment || {}
              cell.s.alignment.horizontal = 'center'
              cell.s.alignment.vertical = 'center'
              cell.s.alignment.wrapText = true
            } else {
              cell.s = {
                border: borderAll,
                alignment: {
                  horizontal: 'center',
                  vertical: 'center',
                  wrapText: true
                },
                font: {
                  sz: 12,
                  color: {
                    rgb: '000000'
                  }
                },
                numFmt: 'General',
                fill: {
                  fgColor: { rgb: 'FFFFFF' }
                }
              }
            }
          }
        }
      }

      XLSX.utils.book_append_sheet(wb, ws, sheet.name) // 将工作表添加到工作簿并指定名称
    }

    const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }) // 将工作簿转换为数组

    const file = new Blob([wbout], { type: 'application/octet-stream' }) // 创建Blob对象
    FileSaver.saveAs(file, fileName) // 下载文件
  },
  // 二维数组中空的数据设置为 0【数据处理】
  emptyValues(array, defaultValue) {
    for (let i = 0; i < array.length; i++) {
      for (let j = 0; j < array[i].length; j++) {
        if (array[i][j] === null || array[i][j] === undefined || array[i][j] === '') {
          array[i][j] = defaultValue
        }
      }
    }
    return array
  },
  // 生成excel列表数据【格式转换】
  handleExcelTable(columnHeader, list) {
    if (list.length === 0) return []

    // 表头
    const tableColumn = Object.keys([columnHeader][0])

    // 表格生成的数据
    const sheet = [tableColumn]
    list.forEach((item) => {
      const row = tableColumn.map((column) => item[column])
      sheet.push(row)
    })

    // 表头匹配对应的中文
    const firstRow = sheet[0].map((column) => columnHeader[column])
    sheet[0] = firstRow

    return sheet || []
  }
}

3、处理方法代码

import dayjs from 'dayjs'
export default function useConfig() {
  const monthArray = [
    { month: '一月', abbreviation: 'Jan', number: '01' },
    { month: '二月', abbreviation: 'Feb', number: '02' },
    { month: '三月', abbreviation: 'Mar', number: '03' },
    { month: '四月', abbreviation: 'Apr', number: '04' },
    { month: '五月', abbreviation: 'May', number: '05' },
    { month: '六月', abbreviation: 'Jun', number: '06' },
    { month: '七月', abbreviation: 'Jul', number: '07' },
    { month: '八月', abbreviation: 'Aug', number: '08' },
    { month: '九月', abbreviation: 'Sep', number: '09' },
    { month: '十月', abbreviation: 'Oct', number: '10' },
    { month: '十一月', abbreviation: 'Nov', number: '11' },
    { month: '十二月', abbreviation: 'Dec', number: '12' }
  ]

  const handleFilterMonth = (val) => {
    const year = val.substring(0, 4)
    const month = val.substring(5, 7)
    const matchedData = monthArray.find((item) => month === item.number)?.abbreviation || month
    return `${year}-${matchedData}`
  }

  /** 获取两月之间的那个月 */
  const getMiddleMonth = (startDateStr, endDateStr) => {
    const startDate = dayjs(startDateStr).startOf('month')
    const endDate = dayjs(endDateStr).startOf('month')

    const middleMonth = startDate.add(1, 'month').format('YYYY-MM')
    return middleMonth
  }

  return {
    monthArray,
    handleFilterMonth,
    getMiddleMonth
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1518378.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

主板维修一般多少钱?电脑主板常见维修方法交换法

修主板一般要多少钱&#xff1f; 下面就让我们一起来了解一下吧。 电脑主板维修价格根据损坏程度不同而不同 1、电容器最容易损坏。 如果只是更换电容的话&#xff0c;大约需要50元左右。 2、如果主板上的电路损坏&#xff0c;面积越大&#xff0c;价格就越贵&#xff0c;可…

产品必会的6个Axure使用技巧(高阶)

1. 事件也可以复制/剪切/粘贴 只需要选中事件&#xff0c;复制/剪切&#xff0c;再选择其它事件&#xff0c;即可粘贴到这个事件上。同时支持跨页面的复制粘贴。 2. Axure支持复制粘贴Excel里的表格 具体操作 在excel里复制具体内容&#xff0c;如下图&#xff1a; 进入axur…

ConcurrentHashMap 为什么不能插入 null?

1、典型回答 简单来说&#xff0c;ConcurrentHashMap 不允许插入 null 值是JDK 源码规定的&#xff0c;如下源码所示(此源码基于JDK 1.8)&#xff1a; 从上述源码可以看出&#xff0c;在添加方法的第一句就加了判断&#xff1a;如果 key 值为 null 或者是 value 值为 null&…

Java 世界破破烂烂,电音小猫缝缝补补

Java 世界破破烂烂&#xff0c;电音小猫缝缝补补 Java 通用代码生成器光 2.4.0 电音之王尝鲜版六正在研发&#xff0c;昨天发布了介绍视频&#xff0c;请见&#xff1a; https://www.bilibili.com/video/BV1yD421j7UP/ 电音之王尝鲜版六支持哑数据模式&#xff0c;支持枚举。…

VMware workstation的安装

VMware workstation安装&#xff1a; 1.双击VMware-workstation-full-9.0.0-812388.exe 2.点击next进行安装 选择安装方式 Typical&#xff1a;典型安装 Custom&#xff1a;自定义安装 选择程序安装位置 点击change选择程序安装位置&#xff0c;然后点击next 选择是否自动…

安泰ATA-7050高压放大器在静电纺丝研究中的应用

20世纪末以来&#xff0c;纳米技术的概念逐渐家喻户晓。目前&#xff0c;纳米材料已经越来越广泛地运用于人们的生活和工作中。到目前为止&#xff0c;以为纳米结构材料的研究发展迅速&#xff0c;并已经实现了在纳米电子器件、光学器件、传感器及生物医药运用中。 以为纳米结构…

AWTK 开源串口屏开发(13) - 计算器应用

1. 功能 计算器是一个很常见的应用&#xff0c;比如在电子秤中&#xff0c;可能就需要这样一个应用。在计算器中会用到一些有意思的知识点&#xff0c;比如嵌入键盘&#xff0c;在数字输入或密码输入是也会用到。 这里我们实现一个简单的计算器&#xff0c;不需要编写代码&am…

小程序(H5)连接微信公众号

报错信息 配自定义菜单 微信公众号没有配“白名单” 配业务域名

【数据结构】模拟实现二叉搜索树

文章目录 1. 二叉搜索树的实现2. 二叉搜索树的应用3. 改造二叉搜索树为 KV 结构4. 二叉搜索树的性能分析 1. 二叉搜索树的实现 namespace key {template<class K>struct BSTreeNode{typedef BSTreeNode<K> Node;Node* _left;Node* _right;K _key;BSTreeNode(const…

基于单片机的智能电机保护器设计

摘 要 电动机大量使用在现在工业生产和我们的日常生活中。但在实际的使用过程中&#xff0c;电动由于各种各样的原因经常出现故障导致电机损坏。因此保证电动机的正常运行十分必要。随着电机保护技术的迅速发展&#xff0c;智能电机保护器应运而生。智能电机保护器在今后的生产…

【数据结构】串的模式匹配(KMP+朴素模式匹配)

2.串的模式匹配 什么是字符串的模式匹配&#xff1f; 在主串中找到与模式串相同的子串&#xff0c;并返回其所在位置。 模式串&#xff1a;要匹配的一串。注&#xff1a;子串是主串的一部分&#xff0c;一定在主串中存在&#xff0c;但模式串不一定在主串中找得到。 2.1 朴素模…

upload文件上传漏洞复现

什么是文件上传漏洞&#xff1a; 文件上传漏洞是指由于程序员在对用户文件上传部分的控制不足或者处理缺陷&#xff0c;而导致的用户可以越过其本身权限向服务器上上传可执行的动态脚本文件。这里上传的文件可以是木马&#xff0c;病毒&#xff0c;恶意脚本或者WebShell等。“…

uniapp-vue3 项目初始化集成配置【开箱即用】

地址 https://gitee.com/charrie/vue3-uniapp-init 部分截图展示 技术说明 采用vue3viteuniapp技术栈&#xff0c;setup语法糖编码方式使用完全免费的sard-uniapp组件库引入unocss量子化样式引擎&#xff0c;动态css不用自己写样式&#xff0c;引用class即可&#xff0c;降低…

计算机网络(6)-----传输层

目录 一.传输层 二.UDP协议 1.UDP的特点&#xff1a; 2.UDP的首部格式&#xff1a; 3.UDP校验的过程&#xff1a; 三.TCP协议 1.TCP协议的特点 2.TCP报文段首部格式 3.TCP的连接管理 &#xff08;1&#xff09;连接建立&#xff08;三次握手&#xff09; &#xff0…

Spring Web MVC入门(1)

什么是Spring Web MVC? 定义:Spring Web MVC是基于Servlet构建的原始Web框架,从一开始就包含在Spring框架中.它的正式名称"Spring Web MVC"来自其源模块的名称(Spring-webmvc),但是它通常被称为"Spring MVC". 什么是Servlet? Servlet是一种实现动态页面…

【C++】排序算法

目录 一、排序算法概述 二、初级排序算法 三、进阶排序算法 四、分治思想排序 五、哈希思想排序 六、分割思想排序 一、排序算法概述 在C语言中&#xff0c;通常需要手写排序算法实现对数组或链表的排序&#xff0c;但是在C中&#xff0c;标准库中的<algorithm>头…

【兔子机器人】实现从初始状态到站立

一、遥想星空up主的方法 由于我有卡位结构&#xff0c;无法做到劈腿&#xff0c;而且底盘也不一样&#xff0c;无法使用此方法 但是其代码思想是可以借鉴的。 参考视频&#xff1a; 【【开源啦&#xff01;】无刷轮腿平衡机器人】 【精准空降到 01:16】 https://www.bilibili…

C++类和对象一

#include <iostream> using namespace std;//设计一个学生类 class CStudent {public: //公有成员void InputData(){cout << "请输入学号";cin >> sno;cout << "请输入姓名";cin >> sname;cout << "请输入分…

RabbitMq踩坑记录

1、连接报错&#xff1a;Broker not available; cannot force queue declarations during start: java.io.IOException 2.1、原因&#xff1a;端口不对 2.2、解决方案&#xff1a; 检查你的连接配置&#xff0c;很可能是你的yml里面的端口配置的是15672&#xff0c;更改为5672即…

第110讲:Mycat实践指南:指定Hash算法分片下的水平分表详解

文章目录 1.应用指定Hash算法分片的概念2.使用应用指定Hash算法分片对某张表进行水平拆分2.1.在所有的分片节点中创建表结构2.2.配置Mycat实现应用指定Hash算法分片的水平分表2.2.1.配置Schema配置文件2.2.2.配置Rule分片规则配置文件2.2.3.配置Server配置文件2.2.4.重启Mycat …