【vue3】:Excel导入导出解决方案

2022/7/19 方案vue3element-plus

# Excel 导入导出

# # 导入

方式一:点击按钮,选中文件,进行上传

方式二:拖拽文件进行上传

两者的实现步骤如下:

  • 获取文件信息

  • 解析文件(核心)npm i xlsx@0.17.0

  • 生成数据结构,发送给后端


方式一:点击按钮,选中文件,进行上传

工具函数

// 获取表头(通用方式)
export const getHeaderRow = (sheet) => {
  const headers = [];
  const range = XLSX.utils.decode_range(sheet["!ref"]);
  let C;
  const R = range.s.r;
  /* start in the first row */
  for (C = range.s.c; C <= range.e.c; ++C) {
    /* walk every column in the range */
    const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })];
    /* find the cell in the first row */
    let hdr = "UNKNOWN " + C; // <-- replace with your desired default
    if (cell && cell.t) hdr = XLSX.utils.format_cell(cell);
    headers.push(hdr);
  }
  return headers;
};

export const USER_RELATIONS = {
  姓名: "username",
  联系方式: "mobile",
  角色: "role",
  开通时间: "openTime",
};

// 数据处理
const generateData = (results) => {
  const arr = [];
  results.forEach((item) => {
    const userInfo = {};

    Object.keys(item).forEach((key) => {
      if (USER_RELATIONS[key] === "openTime") {
        userInfo[USER_RELATIONS[key]] = formatDate(item[key]);
        return;
      }
      userInfo[USER_RELATIONS[key]] = item[key];
    });
    arr.push(userInfo);
  });
  return arr;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<el-button :loading="loading" type="primary" @click="handleUpload"></el-button>
<input
  ref="excelUploadInput"
  type="file"
  accept=".xlsx, .xls"
  @change="handleChange"
/>
<script setup>
import XLSX from "xlsx";
const excelUploadInput = ref(null);
const handleUpload = () => {
  excelUploadInput.value.click();
};

const handleChange = (e) => {
  const files = e.target.files;
  const rawFile = files[0];
  if (!rawFile) return;
  upload(rawFile);
};

// 触发上传事件
const upload = (rawFile) => {
  // 读取文件(异步)
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    // 该事件在读取操作完成时触发
    reader.onload = (e) => {
      // 1. 获取解析到的数据
      const data = e.target.result;
      // 2. 利用 XLSX 对数据进行解析
      const workbook = XLSX.read(data, { type: "array" });
      // 3. 获取第一张表格(工作簿)名称
      const firstSheetName = workbook.SheetNames[0];
      // 4. 只读取 Sheet1(第一张表格)的数据
      const worksheet = workbook.Sheets[firstSheetName];
      // 5. 解析数据表头
      const header = getHeaderRow(worksheet);
      // 6. 解析数据体
      const results = XLSX.utils.sheet_to_json(worksheet);
      // 7. 传入解析之后的数据
      generateData({ header, results });
      // 8. 异步完成
      resolve();
    };
    // 启动读取指定的 Blob 或 File 内容
    reader.readAsArrayBuffer(rawFile);
  });
};
const generateData = ({ header, results }) => {
  const updateData = generateData(results);
  // 将数据发送给后端进行存储
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

方法二:拖拽文件进行上传

拖拽主要是实现一个拖拽的一个过程,上传解析跟方法一一样

<div
  class="drop"
  @drop.stop.prevent="handleDrop"
  @dragover.stop.prevent="handleDragover"
  @dragenter.stop.prevent="handleDragover"
>
</div>
<script setup>
// 当元素或选中的文本在可释放目标上被释放时触发
const handleDrop = (e) => {
  // 上传中
  if (loading.value) return;
  const files = e.dataTransfer.files;
  if (files.length !== 1) {
    ElMessage.error("必须有一个文件");
    return;
  }
  const rawFile = files[0];
  if (!isExcel(rawFile)) {
    ElMessage.error("文件必须时.xlsx, .xls, .csv格式");
    return;
  }
  upload(rawFile);
};
// 当元素或选中的文本被拖到一个可释放目标上时触发
const handleDragover = (e) => {
  e.dataTransfer.dropEffect = "copy";
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  • drop:当元素或选中的文本在可释放目标上被释放时触发
  • dragover:当元素或选中的文本被拖到一个可释放目标上时触发
  • dragenter:当元素或选中的文本被拖到一个可释放目标上时触发

# #导出

npm i file-saver@2.0.5

json 转 二维数组工具函数

import { saveAs } from "file-saver";
import XLSX from "xlsx";

function datenum(v, date1904) {
  if (date1904) v += 1462;
  var epoch = Date.parse(v);
  return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
}

function sheet_from_array_of_arrays(data, opts) {
  var ws = {};
  var range = {
    s: {
      c: 10000000,
      r: 10000000,
    },
    e: {
      c: 0,
      r: 0,
    },
  };
  for (var R = 0; R != data.length; ++R) {
    for (var C = 0; C != data[R].length; ++C) {
      if (range.s.r > R) range.s.r = R;
      if (range.s.c > C) range.s.c = C;
      if (range.e.r < R) range.e.r = R;
      if (range.e.c < C) range.e.c = C;
      var cell = {
        v: data[R][C],
      };
      if (cell.v == null) continue;
      var cell_ref = XLSX.utils.encode_cell({
        c: C,
        r: R,
      });

      if (typeof cell.v === "number") cell.t = "n";
      else if (typeof cell.v === "boolean") cell.t = "b";
      else if (cell.v instanceof Date) {
        cell.t = "n";
        cell.z = XLSX.SSF._table[14];
        cell.v = datenum(cell.v);
      } else cell.t = "s";

      ws[cell_ref] = cell;
    }
  }
  if (range.s.c < 10000000) ws["!ref"] = XLSX.utils.encode_range(range);
  return ws;
}

function Workbook() {
  if (!(this instanceof Workbook)) return new Workbook();
  this.SheetNames = [];
  this.Sheets = {};
}

function s2ab(s) {
  var buf = new ArrayBuffer(s.length);
  var view = new Uint8Array(buf);
  for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff;
  return buf;
}

export const export_json_to_excel = ({
  multiHeader = [],
  header,
  data,
  filename,
  merges = [],
  autoWidth = true,
  bookType = "xlsx",
} = {}) => {
  // 1. 设置文件名称
  filename = filename || "excel-list";
  // 2. 把数据解析为数组,并把表头添加到数组的头部
  data = [...data];
  data.unshift(header);
  // 3. 解析多表头,把多表头的数据添加到数组头部(二维数组)
  for (let i = multiHeader.length - 1; i > -1; i--) {
    data.unshift(multiHeader[i]);
  }
  // 4. 设置 Excel 表工作簿(第一张表格)名称
  var ws_name = "SheetJS";
  // 5. 生成工作簿对象
  var wb = new Workbook();
  // 6. 将 data 数组(json格式)转化为 Excel 数据格式
  var ws = sheet_from_array_of_arrays(data);
  // 7. 合并单元格相关(['A1:A2', 'B1:D1', 'E1:E2'])
  if (merges.length > 0) {
    if (!ws["!merges"]) ws["!merges"] = [];
    merges.forEach((item) => {
      ws["!merges"].push(XLSX.utils.decode_range(item));
    });
  }
  // 8. 单元格宽度相关
  if (autoWidth) {
    /*设置 worksheet 每列的最大宽度*/
    const colWidth = data.map((row) =>
      row.map((val) => {
        /*先判断是否为null/undefined*/
        if (val == null) {
          return {
            wch: 10,
          };
        } else if (val.toString().charCodeAt(0) > 255) {
          /*再判断是否为中文*/
          return {
            wch: val.toString().length * 2,
          };
        } else {
          return {
            wch: val.toString().length,
          };
        }
      })
    );
    /*以第一行为初始值*/
    let result = colWidth[0];
    for (let i = 1; i < colWidth.length; i++) {
      for (let j = 0; j < colWidth[i].length; j++) {
        if (result[j]["wch"] < colWidth[i][j]["wch"]) {
          result[j]["wch"] = colWidth[i][j]["wch"];
        }
      }
    }
    ws["!cols"] = result;
  }

  // 9. 添加工作表(解析后的 excel 数据)到工作簿
  wb.SheetNames.push(ws_name);
  wb.Sheets[ws_name] = ws;
  // 10. 写入数据
  var wbout = XLSX.write(wb, {
    bookType: bookType,
    bookSST: false,
    type: "binary",
  });
  // 11. 下载数据
  saveAs(
    new Blob([s2ab(wbout)], {
      type: "application/octet-stream",
    }),
    `${filename}.${bookType}`
  );
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// 待转成Excel 的数据结构
[
 { username: '张三'
  mobile: '16473829349'
  role: '经理'
  openTime: '2022-07--19'},
 { username: '张三'
  mobile: '16473829349'
  role: '经理'
  openTime: '2022-07--19'},
{ username: '张三'
  mobile: '16473829349'
  role: '经理'
  openTime: '2022-07--19'},
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import dayjs from "dayjs";

const onConfirm = async () => {
  // 获取导出数据
  const allUser = (await getUserManageAllList()).list;
  // 导入工具包
  const excel = await import("@/utils/Export2Excel");
  const data = formatJson(allUser);

  excel.export_json_to_excel({
    // excel 表头
    header: Object.keys(USER_RELATIONS),
    // excel 数据(二维数组结构)
    data,
    // 文件名称
    filename: excelName.value || exportDefaultName,
    // 是否自动列宽
    autoWidth: true,
    // 文件类型
    bookType: "xlsx",
  });
};
const formatJson = (data) => {
  headers = {
    姓名: "username",
    联系方式: "mobile",
    角色: "role",
    开通时间: "openTime",
  };
  return rows.map((item) => {
    return Object.keys(headers).map((key) => {
      // 角色特殊处理
      if (headers[key] === "role") {
        const roles = item[headers[key]];
        return JSON.stringify(roles.map((role) => role.title));
      }
      // 时间
      if (headers[key] === "openTime") {
        return dayjs(item[headers[key]]).format(format);
      }
      return item[headers[key]];
    });
  });
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44