Существует несколько стандартных вариантов для загрузки CSV-файла и создания записей, но иногда, в зависимости от бизнес-требований, вам потребуется индивидуальное и интуитивно понятное решение для загрузки файла и запуска пользовательского процесса для создания/обновления записей. В этом посте рассказывается, как загрузить CSV, проанализировать содержимое файла и создать записи учетной записи, а в конце отобразить простой отзыв для пользователя. Это очень простой LWC для разбора CSV-файла и создания записей, и он может стать отправной точкой для более надежного решения.
Я видел несколько сообщений, объясняющих, как анализировать файлы CSV, и были разные решения: анализ CSV с использованием метода Apex, компонентов ауры, LWC, методов Javascript, выполняющих некоторые простые операции анализа. В нашем случае мы будем использовать существующую библиотеку для разбора файла и облегчения нашей жизни: Papa Parse. Эта библиотека имеет хорошие методы и параметры конфигурации для анализа CSV-файла. Вы можете выбрать, что будет символом-разделителем, есть ли у файла заголовок или нет, заключены ли столбцы в кавычки, какой символ и так далее.
Так как эта библиотека принимает на вход объект Файл, мы будем использовать Lightning-ввод с типом файл, чтобы получить файл от пользователя. После выбора файла синтаксический анализатор Papa проанализирует файл и вернет список объектов, каждый из которых представляет собой строку из файла. Для использования библиотеки необходимо загрузить файл .js как статический ресурс и импортировать его в LWC:
import {LightningElement, track} from 'lwc'; import { loadScript } from 'lightning/platformResourceLoader'; import PARSER from '@salesforce/resourceUrl/PapaParse'; export default class ImportAccounts extends LightningElement { parserInitialized = false; renderedCallback() { if(!this.parserInitialized){ loadScript(this, PARSER) .then(() => { this.parserInitialized = true; }) .catch(error => console.error(error)); } } }
Вот HTML-часть LWC:
<template> <lightning-card title="Import Accounts" icon-name="standard:account"> <lightning-spinner if:true={loading}></lightning-spinner> <div class="slds-p-around_medium"> <lightning-input type="file" label="CSV file" multiple="false" accept=".csv" onchange={handleInputChange}></lightning-input> </div> <template if:true={rows.length}> <div class="slds-p-around_medium"> <lightning-datatable key-field="key" hide-checkbox-column data={rows} columns={columns}></lightning-datatable> <div class="slds-p-around_small slds-align_absolute-center"> <lightning-button variant="neutral" label="Cancel" title="Cancel" onclick={cancel} class="slds-m-left_x-small"></lightning-button> <lightning-button variant="brand" label="Create" title="Create" onclick={createAccounts} class="slds-m-left_x-small"></lightning-button> </div> </div> </template> </lightning-card> </template>
При выборе файла будет выполняться следующая функция для разбора CSV-файла:
handleInputChange(event){ if(event.target.files.length > 0){ const file = event.target.files[0]; this.loading = true; Papa.parse(file, { quoteChar: '"', header: 'true', complete: (results) => { this._rows = results.data; this.loading = false; }, error: (error) => { console.error(error); this.loading = false; } }) } }
Атрибут complete – это функция обратного вызова, которая получает массив объектов со строками из CSV-файла. При этом у нас есть геттер для отображения строк в таблице данных молнии, чтобы пользователь мог предварительно просмотреть строки. Мы не ограничиваем количество отображаемых записей, но рассмотрите возможность сделать это, если вы собираетесь иметь дело с большим количеством записей. Добытчик:
get rows(){ if(this._rows){ return this._rows.map((row, index) => { row.key = index; if(this.results[index]){ row.result = this.results[index].id || this.results[index].error; } return row; }) } return []; }
Не обращайте внимания на if(this.results[index]) на данный момент, его объяснение будет позже. Существует кнопка для создания записей учетной записи, загружаемых из CSV и отображаемых в таблице данных. Он вызовет следующую функцию:
createAccounts(){ const accountsToCreate = this.rows.map(row => { const fields = {}; fields[NAME_FIELD.fieldApiName] = row.AccountName; fields[DESCRIPTION_FIELD.fieldApiName] = row.Description; const recordInput = { apiName: ACCOUNT_OBJECT.objectApiName, fields }; return createRecord(recordInput); }); if(accountsToCreate.length){ this.loading = true; Promise.allSettled(accountsToCreate) .then(results => this._results = results) .catch(error => console.error(error)) .finally(() => this.loading = false); } }
Здесь мы используем createRecordиз Lightning/uiRecordApiдля создания записей учетной записи и Promise.allSettledдля создания всех записей и получения результатов, которые будут отображаться в таблице данных вместе с записями. Вы должны были заметить, что мы используем (и определяем) только два столбца для создания записей (поля «Имя» и «Описание»). Вы можете изменить эту часть, чтобы принять больше столбцов, если это необходимо. В будущем я улучшу этот компонент, чтобы пользователь мог настраивать столбцы.
Мы определили геттер для преобразования результатов нужным нам образом, а затем использовали эту информацию вместе со строками, чтобы данные отображались пользователю. Для успешно созданных записей мы отображаем идентификаторы записей. При возникновении ошибки мы показываем сообщение об ошибке.
get results(){ if(this._results){ return this._results.map(r => { const result = {}; result.success = r.status === 'fulfilled'; result.id = result.success ? r.value.id : undefined; result.error = !result.success ? r.reason.body.message : undefined; return result; }); } return []; }
Вот весь контроллер JS:
import {LightningElement, track} from 'lwc'; import { loadScript } from 'lightning/platformResourceLoader'; import { createRecord } from 'lightning/uiRecordApi'; import PARSER from '@salesforce/resourceUrl/PapaParse'; import ACCOUNT_OBJECT from '@salesforce/schema/Account'; import NAME_FIELD from '@salesforce/schema/Account.Name'; import DESCRIPTION_FIELD from '@salesforce/schema/Account.Description'; export default class ImportAccounts extends LightningElement { parserInitialized = false; loading = false; @track _results; @track _rows; get columns(){ const columns = [ { label: 'Account Name', fieldName: 'AccountName' }, { label: 'Description', fieldName: 'Description' } ]; if(this.results.length){ columns.push({ label: 'Result',fieldName: 'result' }); } return columns; } get rows(){ if(this._rows){ return this._rows.map((row, index) => { row.key = index; if(this.results[index]){ row.result = this.results[index].id || this.results[index].error; } return row; }) } return []; } get results(){ if(this._results){ return this._results.map(r => { const result = {}; result.success = r.status === 'fulfilled'; result.id = result.success ? r.value.id : undefined; result.error = !result.success ? r.reason.body.message : undefined; return result; }); } return []; } renderedCallback() { if(!this.parserInitialized){ loadScript(this, PARSER) .then(() => { this.parserInitialized = true; }) .catch(error => console.error(error)); } } handleInputChange(event){ if(event.target.files.length > 0){ const file = event.target.files[0]; this.loading = true; Papa.parse(file, { quoteChar: '"', header: 'true', complete: (results) => { this._rows = results.data; this.loading = false; }, error: (error) => { console.error(error); this.loading = false; } }) } } createAccounts(){ const accountsToCreate = this.rows.map(row => { const fields = {}; fields[NAME_FIELD.fieldApiName] = row.AccountName; fields[DESCRIPTION_FIELD.fieldApiName] = row.Description; const recordInput = { apiName: ACCOUNT_OBJECT.objectApiName, fields }; return createRecord(recordInput); }); if(accountsToCreate.length){ this.loading = true; Promise.allSettled(accountsToCreate) .then(results => this._results = results) .catch(error => console.error(error)) .finally(() => this.loading = false); } } cancel(){ this._rows = undefined; this._results = undefined; } }
В своем следующем сообщении в блоге я улучшу этот компонент, чтобы динамически получать поля из объекта Account и позволять пользователю сопоставлять столбцы из CSV-файла с полями. Быть в курсе!