一、安装依赖

xlsxjs库

npm install xlxs -s

二、封装解析函数 + 重复数据校验函数(代码有删减,根据需求自行替换)

1、核心解析函数

import * as XLSX from 'xlsx';

// 核心解析函数
export const parseExcelFile = (file) => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();

    reader.onload = (e) => {
      try {
        const data = new Uint8Array(e.target.result);

        const workbook = XLSX.read(data, { type: 'array' });

        // 解析所有工作表数据
        const results = {};
        workbook.SheetNames.forEach((sheetName) => {
          const worksheet = workbook.Sheets[sheetName];

          // 获取表头和数据行
          const header = [];
          const body = [];

          // 获取所有单元格范围
          const range = XLSX.utils.decode_range(worksheet['!ref']);


          // 遍历所有行 (从第2行开始,根据自己需要选择index)
          for (let rowNum = 1; rowNum <= range.e.r; rowNum++) {
            const row = [];

            // 遍历所有列
            for (let colNum = range.s.c; colNum <= range.e.c; colNum++) {
              const cellAddress = XLSX.utils.encode_cell({ r: rowNum, c: colNum });
              const cell = worksheet[cellAddress];
              row.push(cell ? cell.v : '');
            }

            // 第一行作为表头
            if (rowNum === 1) {
              header.push(...row);
            } else if (rowNum > 2) {
              body.push(row);
            }
          }


          // 转换为对象数组
          const jsonData = body.map((row) => {
            return header.reduce((obj, key, index) => {
              obj[key] = row[index];
              return obj;
            }, {});
          });

          results[sheetName] = jsonData;
        });

        resolve(results);
      } catch (error) {
        reject(error);
      }
    };

    reader.onerror = (error) => reject(error);
    reader.readAsArrayBuffer(file);
  });
};

2、重复校验工具

// 重复校验工具;
export const checkDuplicates = (data) => {
  const keyMap = new Map();
  const duplicates = [];

  data.forEach((row, index) => {
    const compositeKey = [
      row.key1,
      row.key2,
      row.key3,
      row.key4,
      row.key1,
      row.key5,
    ].join('__');

    if (keyMap.has(compositeKey)) {
      duplicates.push({
        line: index + 3, // Excel 行号从2开始
        data: compositeKey.split('__'),
      });
    } else {
      keyMap.set(compositeKey, true);
    }
  });

  return {
    uniqueData: data
      .map((i, n) => ({ ...i, line: n + 3 }))
      .filter((row, index) => !duplicates.some((d) => d.line === index + 3)), // 剔除重复数据
    duplicates,
  };
};

三、实际应用(实例使用的是vue3 + td)

<script setup>
import { reactive, ref } from 'vue';
import {
  MessagePlugin,
  Button as TButton,
  Table as TTable,
  Dialog as TDialog,
  Upload as TUpload,
} from 'tdesign-vue-next';
import { parseExcelFile } from '../utils/excelParser';
import { checkDuplicates } from '../utils/checkDuplicates';

const emit = defineEmits(['upload-success']);

const visible = ref(false);

/* 重复数据 */
const duplicates = ref([]);

/* 不重复数据 */
const uniqueData = ref([]);

const transformBooleanField = (value) => {
  const mapping = { 是: true, 否: false };
  const normalized = String(value).trim();

  if (!(normalized in mapping)) {
    console.warn(`无效的布尔值字段: ${value}`);
    return null; // 或根据需求返回 undefined
  }

  return mapping[normalized];
};

const processBatchData = (data) => {
  return data.map((item) => ({
    key1: item['名称1'],
    key2: item['名称2'],
    key3: item['名称3'],
    key4: item['名称4'],
    key5: transformBooleanField(item['名称5']),
    key6: transformBooleanField(item['名称6']),
  }));
};

const AllDataPagination = reactive({
  defaultCurrent: 1,
  defaultPageSize: 10,
  current: 1,
  pageSize: 10,
  total: uniqueData.value.length,
  pageSizeOptions: [10, 20, 50, 100, 200],
});

// 文件选择回调
const beforeUpload = async (files) => {
  console.log('files', files);

  const file = files.raw;
  if (!file) return;

  try {
    // 解析Excel
    const rawData = await parseExcelFile(file);

    const mapData = processBatchData(rawData['xxx批量导入模版'] || []);

    // 重复数据校验(根据当前需要)
    const { uniqueData: filteredData, duplicates: dupList } = checkDuplicates(mapData);

    duplicates.value = dupList;
    uniqueData.value = filteredData;
    AllDataPagination.total = filteredData.length || 0;
    if (dupList.length > 0) {
      visible.value = true;
    } else {
      confirmUpload();
    }
  } catch (error) {
    MessagePlugin.error(error.message);
  }
};

// 确认上传
const confirmUpload = () => {
  emit('upload-success', uniqueData.value);
  visible.value = false;
  MessagePlugin.success('导入成功');
};

const onPageChange = async (pageInfo) => {
  AllDataPagination.current = pageInfo.current;
  AllDataPagination.pageSize = pageInfo.pageSize;
};

const sameRowColumns = [
  {
    colKey: 'line',
    title: '行号',
    width: 120,
  },
  {
    colKey: 'row',
    title: '数据内容',
    cell: (h, { row }) => row.data.join('__'),
  },
];
const allDataColumns = [
  { colKey: 'serial-number', title: '序号' },
  { colKey: 'line', title: '行号' },
  {
    colKey: 'key1',
    title: '型号',
  },
  {
    colKey: 'key2',
    title: '名称',
  },
  {
    colKey: 'key3',
    title: '版本号',
  },
  {
    colKey: 'key4',
    title: '地址',
    // cell: (h, { row }) => row.data.join('|'),
  },
  {
    colKey: 'key5',
    title: '是否对修改',
    cell: (h, { row }) => (row.key5=== true ? '是' : '否'),
  },
  {
    colKey: 'key6',
    title: '是否分发',
    cell: (h, { row }) => (row.key6=== true ? '是' : '否'),
  },
];
</script>

<template>
  <div class="t-upload-container">
    <!-- TD上传组件 -->
    <t-upload action="" :auto-upload="false" accept=".xlsx,.xls" :before-upload="beforeUpload">
      <t-button theme="primary">
        <template #icon><t-icon name="upload" /></template>
        批量导入开源组件
      </t-button>
    </t-upload>

    <!-- TD对话框 -->
    <t-dialog
      v-model:visible="visible"
      header="重复数据提示"
      :on-confirm="confirmUpload"
      :on-close="() => (visible = false)"
      width="80vw"
      placement="center"
    >
      <div class="duplicate-content">
        <p>发现 {{ duplicates.length }} 条重复数据:</p>
        <t-table :data="duplicates" :columns="sameRowColumns" row-key="line" :max-height="300"> </t-table>
      </div>
      <div class="duplicate-content">
        <t-table
          :data="uniqueData"
          :columns="allDataColumns"
          row-key="line"
          :max-height="600"
          :pagination="AllDataPagination"
          @page-change="onPageChange"
        >
        </t-table>
      </div>
    </t-dialog>
  </div>
</template>

<style scoped>
.t-upload-container {
  margin: 20px;
}

.duplicate-content {
  padding: 10px;
}

.duplicate-content p {
  margin-bottom: 15px;
  color: var(--td-text-color-secondary);
}
</style>

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐