Commit 22e6ddfef34f6a09db286b3a707e5745d251e16e
Merge branch 'perf/device-import' into 'main_dev'
perf: 优化模版下载可使用csv文件格式 See merge request yunteng/thingskit-front!1096
Showing
3 changed files
with
99 additions
and
63 deletions
| @@ -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" |