Commit 018b36335da06e9b4b0e2960ac25ad6f5b733af7

Authored by ww
1 parent eb689cfa

perf: 优化模版下载可使用csv文件格式

... ... @@ -3,10 +3,10 @@
3 3 import { InboxOutlined } from '@ant-design/icons-vue';
4 4 import { computed } from 'vue';
5 5 import StepContainer from './StepContainer.vue';
6   - import XLSX, { CellObject } from 'xlsx';
7 6 import { basicProps } from './props';
8 7 import { UploadFileParseValue } from './type';
9 8 import { useMessage } from '/@/hooks/web/useMessage';
  9 + import { readCsvFile, readXLSXFile } from './config';
10 10
11 11 const props = defineProps({
12 12 ...basicProps,
... ... @@ -30,58 +30,14 @@
30 30 status: string;
31 31 }
32 32
33   - const readFile = async (file: File): Promise<UploadFileParseValue | boolean> => {
34   - /**
35   - * @description 读取表头
36   - * @param sheet 工作表
37   - * @param range 区间
38   - * @param headerRow 表头行
39   - */
40   - const getTableHeader = (sheet: XLSX.WorkSheet, range: [number, number], headerRow = 1) => {
41   - const [startColumn, endColumn] = range;
42   - const header: string[] = [];
43   - for (let i = startColumn; i <= endColumn; i++) {
44   - const columnIndex = XLSX.utils.encode_col(i) + headerRow;
45   - const value = (sheet[columnIndex] as CellObject).v;
46   - header.push(value as string);
47   - }
48   - return header;
49   - };
  33 + const { createMessage } = useMessage();
50 34
51   - return new Promise((resolve, reject) => {
52   - const fileReader = new FileReader();
53   - fileReader.onload = (event: ProgressEvent) => {
54   - try {
55   - const data = (event.target as FileReader).result as string;
56   -
57   - const result = XLSX.read(data, { type: 'binary' });
58   -
59   - const sheetName = result.SheetNames.at(0);
60   - const workbook = result.Sheets;
61   - const sheet = workbook[sheetName as string];
62   - const sheetRange = sheet['!ref'];
63   -
64   - const {
65   - s: { c: startColumn },
66   - e: { c: endColumn },
67   - } = XLSX.utils.decode_range(sheetRange!);
68   -
69   - const header = getTableHeader(sheet, [startColumn, endColumn]);
70   - const content = XLSX.utils.sheet_to_json(sheet, { range: sheetRange }) as Recordable[];
71   -
72   - resolve({ header, content });
73   - } catch (error) {
74   - const { createMessage } = useMessage();
75   - createMessage.error('请检查xlsx文件是否正确');
76   - throw error;
77   - }
78   - };
79   -
80   - fileReader.onerror = () => {
81   - reject(false);
82   - };
83   - fileReader.readAsBinaryString(file);
84   - });
  35 + const readFile = async (file: File) => {
  36 + try {
  37 + return file.type.includes('csv') ? await readCsvFile(file) : await readXLSXFile(file);
  38 + } catch (e) {
  39 + createMessage.error('请检查文件是否正确');
  40 + }
85 41 };
86 42
87 43 const handleParseFile = async ({ file, onSuccess, onError }: FileRequestParams) => {
... ... @@ -120,7 +76,7 @@
120 76 <Upload.Dragger
121 77 :fileList="fileList"
122 78 :customRequest="handleParseFile"
123   - accept=".xlsx"
  79 + accept=".xlsx,.csv"
124 80 name="file"
125 81 :remove="handleRemove"
126 82 >
... ...
... ... @@ -8,7 +8,7 @@ import { FormSchema } from '/@/components/Form';
8 8 import { BasicColumn } from '/@/components/Table';
9 9 import { copyTransFun } from '/@/utils/fnUtils';
10 10 import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
11   -import XLSX from 'xlsx';
  11 +import XLSX, { CellObject } from 'xlsx';
12 12
13 13 export enum FieldsEnum {
14 14 ORGANIZATION_ID = 'organizationId',
... ... @@ -335,13 +335,94 @@ export const columnTypeSchema: BasicColumn[] = [
335 335 },
336 336 ];
337 337
338   -export function exportTemplate() {
  338 +const IMPORT_DEVICE_TEMPLATE = [
  339 + ['设备名称', '访问令牌'],
  340 + ['温湿度设备', 'admin'],
  341 +];
  342 +
  343 +export function exportXLSXTemplate() {
339 344 const book = XLSX.utils.book_new();
340   - const sheet = XLSX.utils.aoa_to_sheet([
341   - ['设备名称', '访问令牌'],
342   - ['温湿度设备', 'admin'],
343   - ]);
  345 + const sheet = XLSX.utils.aoa_to_sheet(IMPORT_DEVICE_TEMPLATE);
344 346
345 347 XLSX.utils.book_append_sheet(book, sheet, '设备导入模版');
346 348 XLSX.writeFile(book, 'template.xlsx', { bookType: 'xlsx' });
347 349 }
  350 +
  351 +function createFileReader(
  352 + file: File | Blob,
  353 + type: 'buffer' | 'dataUrl' | 'string' = 'string'
  354 +): Promise<FileReader['result']> {
  355 + return new Promise((resolve, reject) => {
  356 + const fileReader = new FileReader();
  357 + fileReader.onload = () => {
  358 + if (fileReader.readyState === FileReader.DONE) {
  359 + resolve(fileReader.result);
  360 + }
  361 + };
  362 +
  363 + fileReader.onerror = (e) => {
  364 + reject(e);
  365 + };
  366 +
  367 + if (type === 'buffer') {
  368 + fileReader.readAsArrayBuffer(file);
  369 + } else if (type === 'dataUrl') {
  370 + fileReader.readAsDataURL(file);
  371 + } else {
  372 + fileReader.readAsText(file);
  373 + }
  374 + });
  375 +}
  376 +
  377 +function parseWorkbook(workbook: XLSX.WorkBook) {
  378 + /**
  379 + * @description 读取表头
  380 + * @param sheet 工作表
  381 + * @param range 区间
  382 + * @param headerRow 表头行
  383 + */
  384 + const getTableHeader = (sheet: XLSX.WorkSheet, range: [number, number], headerRow = 1) => {
  385 + const [startColumn, endColumn] = range;
  386 + const header: string[] = [];
  387 + for (let i = startColumn; i <= endColumn; i++) {
  388 + const columnIndex = XLSX.utils.encode_col(i) + headerRow;
  389 + const value = (sheet[columnIndex] as CellObject).v;
  390 + header.push(value as string);
  391 + }
  392 + return header;
  393 + };
  394 +
  395 + const sheetName = workbook.SheetNames.at(0);
  396 + const sheets = workbook.Sheets;
  397 + const sheet = sheets[sheetName as string];
  398 + const sheetRange = sheet['!ref'];
  399 +
  400 + const {
  401 + s: { c: startColumn },
  402 + e: { c: endColumn },
  403 + } = XLSX.utils.decode_range(sheetRange!);
  404 +
  405 + const header = getTableHeader(sheet, [startColumn, endColumn]);
  406 + const content = XLSX.utils.sheet_to_json(sheet, { range: sheetRange }) as Recordable[];
  407 +
  408 + return { header, content };
  409 +}
  410 +
  411 +export async function readCsvFile(file: File) {
  412 + let data = await createFileReader(file);
  413 +
  414 + const blob = new Blob(['\ufeff' + data], { type: 'text/csv;charset=utf-8' });
  415 +
  416 + data = await createFileReader(blob, 'buffer');
  417 +
  418 + const workbook = XLSX.read(data, { type: 'array', raw: true, codepage: 96 });
  419 +
  420 + return parseWorkbook(workbook);
  421 +}
  422 +
  423 +export async function readXLSXFile(file: File) {
  424 + const data = await createFileReader(file, 'buffer');
  425 + const workbook = XLSX.read(data, { type: 'binary' });
  426 +
  427 + return parseWorkbook(workbook);
  428 +}
... ...
... ... @@ -9,7 +9,7 @@
9 9 import CreateEntity from './CreateEntity.vue';
10 10 import CompleteResult from './CompleteResult.vue';
11 11 import { ImportDeviceResponse } from '/@/api/device/model/batchImportModel';
12   - import { exportTemplate } from './config';
  12 + import { exportXLSXTemplate } from './config';
13 13
14 14 const emit = defineEmits(['importFinally']);
15 15
... ... @@ -79,8 +79,7 @@
79 79 };
80 80
81 81 const handleTemplateDownload = () => {
82   - exportTemplate();
83   - // downloadFile(csvTemplate, 'template', 'text/csv,chartset=UTF-8', 'csv');
  82 + exportXLSXTemplate();
84 83 };
85 84 </script>
86 85
... ... @@ -143,7 +142,7 @@
143 142 </Steps.Step>
144 143 </Steps>
145 144 <Tooltip
146   - title="注意:模板表头的第一列为设备名称,第二列为访问令牌。新增列时,名称和访问令牌不能重复。下载的模板建议使用文本打开,如使用wps打开请另外为新的csv文件。"
  145 + title="注意:模板表头的第一列为设备名称,第二列为访问令牌。新增列时,名称和访问令牌不能重复。"
147 146 >
148 147 <Button
149 148 type="text"
... ...