Commit 22e6ddfef34f6a09db286b3a707e5745d251e16e

Authored by xp.Huang
2 parents fc335a7a 018b3633

Merge branch 'perf/device-import' into 'main_dev'

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

See merge request yunteng/thingskit-front!1096
@@ -3,10 +3,10 @@ @@ -3,10 +3,10 @@
3 import { InboxOutlined } from '@ant-design/icons-vue'; 3 import { InboxOutlined } from '@ant-design/icons-vue';
4 import { computed } from 'vue'; 4 import { computed } from 'vue';
5 import StepContainer from './StepContainer.vue'; 5 import StepContainer from './StepContainer.vue';
6 - import XLSX, { CellObject } from 'xlsx';  
7 import { basicProps } from './props'; 6 import { basicProps } from './props';
8 import { UploadFileParseValue } from './type'; 7 import { UploadFileParseValue } from './type';
9 import { useMessage } from '/@/hooks/web/useMessage'; 8 import { useMessage } from '/@/hooks/web/useMessage';
  9 + import { readCsvFile, readXLSXFile } from './config';
10 10
11 const props = defineProps({ 11 const props = defineProps({
12 ...basicProps, 12 ...basicProps,
@@ -30,58 +30,14 @@ @@ -30,58 +30,14 @@
30 status: string; 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 const handleParseFile = async ({ file, onSuccess, onError }: FileRequestParams) => { 43 const handleParseFile = async ({ file, onSuccess, onError }: FileRequestParams) => {
@@ -120,7 +76,7 @@ @@ -120,7 +76,7 @@
120 <Upload.Dragger 76 <Upload.Dragger
121 :fileList="fileList" 77 :fileList="fileList"
122 :customRequest="handleParseFile" 78 :customRequest="handleParseFile"
123 - accept=".xlsx" 79 + accept=".xlsx,.csv"
124 name="file" 80 name="file"
125 :remove="handleRemove" 81 :remove="handleRemove"
126 > 82 >
@@ -8,7 +8,7 @@ import { FormSchema } from '/@/components/Form'; @@ -8,7 +8,7 @@ import { FormSchema } from '/@/components/Form';
8 import { BasicColumn } from '/@/components/Table'; 8 import { BasicColumn } from '/@/components/Table';
9 import { copyTransFun } from '/@/utils/fnUtils'; 9 import { copyTransFun } from '/@/utils/fnUtils';
10 import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const'; 10 import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
11 -import XLSX from 'xlsx'; 11 +import XLSX, { CellObject } from 'xlsx';
12 12
13 export enum FieldsEnum { 13 export enum FieldsEnum {
14 ORGANIZATION_ID = 'organizationId', 14 ORGANIZATION_ID = 'organizationId',
@@ -335,13 +335,94 @@ export const columnTypeSchema: BasicColumn[] = [ @@ -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 const book = XLSX.utils.book_new(); 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 XLSX.utils.book_append_sheet(book, sheet, '设备导入模版'); 347 XLSX.utils.book_append_sheet(book, sheet, '设备导入模版');
346 XLSX.writeFile(book, 'template.xlsx', { bookType: 'xlsx' }); 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,7 +9,7 @@
9 import CreateEntity from './CreateEntity.vue'; 9 import CreateEntity from './CreateEntity.vue';
10 import CompleteResult from './CompleteResult.vue'; 10 import CompleteResult from './CompleteResult.vue';
11 import { ImportDeviceResponse } from '/@/api/device/model/batchImportModel'; 11 import { ImportDeviceResponse } from '/@/api/device/model/batchImportModel';
12 - import { exportTemplate } from './config'; 12 + import { exportXLSXTemplate } from './config';
13 13
14 const emit = defineEmits(['importFinally']); 14 const emit = defineEmits(['importFinally']);
15 15
@@ -79,8 +79,7 @@ @@ -79,8 +79,7 @@
79 }; 79 };
80 80
81 const handleTemplateDownload = () => { 81 const handleTemplateDownload = () => {
82 - exportTemplate();  
83 - // downloadFile(csvTemplate, 'template', 'text/csv,chartset=UTF-8', 'csv'); 82 + exportXLSXTemplate();
84 }; 83 };
85 </script> 84 </script>
86 85
@@ -143,7 +142,7 @@ @@ -143,7 +142,7 @@
143 </Steps.Step> 142 </Steps.Step>
144 </Steps> 143 </Steps>
145 <Tooltip 144 <Tooltip
146 - title="注意:模板表头的第一列为设备名称,第二列为访问令牌。新增列时,名称和访问令牌不能重复。下载的模板建议使用文本打开,如使用wps打开请另外为新的csv文件。" 145 + title="注意:模板表头的第一列为设备名称,第二列为访问令牌。新增列时,名称和访问令牌不能重复。"
147 > 146 >
148 <Button 147 <Button
149 type="text" 148 type="text"