Files
FE_CPONE/test/vuex/cpone-import-test-desc/components/VueCsvImport.vue
2026-04-27 10:13:31 +07:00

350 lines
9.0 KiB
Vue

<template>
<div class="vue-csv-uploader">
<div class="form">
<div class="vue-csv-uploader-part-one">
<div
class="form-check form-group csv-import-checkbox"
v-if="headers === null"
>
<slot
name="hasHeaders"
:headers="hasHeaders"
:toggle="toggleHasHeaders"
>
<input
:class="checkboxClass"
type="checkbox"
:id="makeId('hasHeaders')"
:value="hasHeaders"
@change="toggleHasHeaders"
/>
<label class="form-check-label" :for="makeId('hasHeaders')">
File Has Headers
</label>
</slot>
</div>
<div class="form-group csv-import-file">
<input
ref="csv"
type="file"
:disabled="loading_save"
@change.prevent="validFileMimeType"
:class="inputClass"
name="csv"
/>
<slot name="error" v-if="showErrorMessage">
<div class="invalid-feedback d-block">
File type is invalid
</div>
</slot>
</div>
<div class="form-group">
<slot name="next" :load="load">
<button
type="submit"
:disabled="disabledNextButton"
:class="buttonClass"
@click.prevent="load"
>
{{ loadBtnText }}
</button>
</slot>
</div>
</div>
<div class="vue-csv-uploader-part-two">
<div class="vue-csv-mapping" v-if="sample">
<table :class="tableClass">
<slot name="thead">
<thead>
<tr>
<th>Field</th>
<th>CSV Column</th>
</tr>
</thead>
</slot>
<tbody>
<tr v-for="(field, key) in fieldsToMap" :key="key">
<td>{{ field.label }}</td>
<td>
<select
:class="tableSelectClass"
:name="`csv_uploader_map_${key}`"
v-model="map[field.key]"
>
<option :value="null" v-if="canIgnore">Ignore</option>
<option
v-for="(column, key) in firstRow"
:key="key"
:value="key"
>{{ column }}</option
>
</select>
</td>
</tr>
</tbody>
</table>
<div class="form-group" v-if="url">
<slot name="submit" :submit="submit">
<input
type="submit"
:disabled="loading_save"
:class="buttonClass"
@click.prevent="submit"
:value="submitBtnText"
/>
</slot>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { drop, every, forEach, get, isArray, map, set } from "lodash";
import axios from "axios";
import Papa from "papaparse";
import mimeTypes from "mime-types";
export default {
props: {
value: Array,
url: {
type: String,
},
mapFields: {
required: true,
},
callback: {
type: Function,
default: () => ({}),
},
catch: {
type: Function,
default: () => ({}),
},
finally: {
type: Function,
default: () => ({}),
},
parseConfig: {
type: Object,
default() {
return {};
},
},
headers: {
default: null,
},
loadBtnText: {
type: String,
default: "Next",
},
submitBtnText: {
type: String,
default: "Submit",
},
tableClass: {
type: String,
default: "table",
},
checkboxClass: {
type: String,
default: "form-check-input",
},
buttonClass: {
type: String,
default: "btn btn-primary",
},
inputClass: {
type: String,
default: "form-control-file",
},
validation: {
type: Boolean,
default: true,
},
fileMimeTypes: {
type: Array,
default: () => {
return [
"text/csv",
"text/x-csv",
"application/vnd.ms-excel",
"text/plain",
];
},
},
tableSelectClass: {
type: String,
default: "form-control",
},
canIgnore: {
type: Boolean,
default: false,
},
},
data: () => ({
form: {
csv: null,
},
fieldsToMap: [],
map: {},
hasHeaders: true,
csv: null,
sample: null,
isValidFileMimeType: false,
fileSelected: false,
}),
created() {
this.hasHeaders = this.headers;
if (isArray(this.mapFields)) {
this.fieldsToMap = map(this.mapFields, (item) => {
return {
key: item,
label: item,
};
});
} else {
this.fieldsToMap = map(this.mapFields, (label, key) => {
return {
key: key,
label: label,
};
});
}
},
methods: {
submit() {
const _this = this;
this.form.csv = this.buildMappedCsv();
this.$emit("input", this.form.csv);
if (this.url) {
axios
.post(this.url, this.form)
.then((response) => {
_this.callback(response);
})
.catch((response) => {
_this.catch(response);
})
.finally((response) => {
_this.finally(response);
});
} else {
_this.callback(this.form.csv);
}
},
buildMappedCsv() {
const _this = this;
let csv = this.hasHeaders ? drop(this.csv) : this.csv;
return map(csv, (row) => {
let newRow = {};
forEach(_this.map, (column, field) => {
set(newRow, field, get(row, column));
});
return newRow;
});
},
validFileMimeType() {
let file = this.$refs.csv.files[0];
const mimeType =
file.type === "" ? mimeTypes.lookup(file.name) : file.type;
if (file) {
this.fileSelected = true;
this.isValidFileMimeType = this.validation
? this.validateMimeType(mimeType)
: true;
} else {
this.isValidFileMimeType = !this.validation;
this.fileSelected = false;
}
},
validateMimeType(type) {
return this.fileMimeTypes.indexOf(type) > -1;
},
load() {
const _this = this;
this.readFile((output) => {
_this.sample = get(
Papa.parse(output, { preview: 2, skipEmptyLines: true }),
"data"
);
_this.csv = get(Papa.parse(output, { skipEmptyLines: true }), "data");
});
},
readFile(callback) {
let file = this.$refs.csv.files[0];
if (file) {
let reader = new FileReader();
reader.readAsText(file, "UTF-8");
reader.onload = function (evt) {
callback(evt.target.result);
};
reader.onerror = function () {};
}
},
toggleHasHeaders() {
this.hasHeaders = !this.hasHeaders;
},
makeId(id) {
return `${id}${this._uid}`;
},
},
watch: {
map: {
deep: true,
handler: function (newVal) {
if (!this.url) {
let hasAllKeys = Array.isArray(this.mapFields)
? every(this.mapFields, function (item) {
return newVal.hasOwnProperty(item);
})
: every(this.mapFields, function (item, key) {
return newVal.hasOwnProperty(key);
});
if (hasAllKeys) {
this.submit();
}
}
},
},
sample(newVal, oldVal) {
if (newVal !== null) {
this.fieldsToMap.forEach((field) => {
newVal[0].forEach((columnName, index) => {
if (field.key === columnName) {
this.map[field.key] = index;
}
});
});
}
},
},
computed: {
firstRow() {
return get(this, "sample.0");
},
showErrorMessage() {
return this.fileSelected && !this.isValidFileMimeType;
},
disabledNextButton() {
return !this.isValidFileMimeType;
},
},
};
</script>