first commit + add fe component vue accone

This commit is contained in:
2026-05-07 17:10:10 +07:00
parent fd8e4d694b
commit 38a182641a
327 changed files with 263946 additions and 0 deletions

View File

@@ -0,0 +1,194 @@
<template>
<div class="catalog-column-chooser">
<a href="#" class="header-tool" @click="toggleHeaders">
{{ withHeaders ? text[lang].file.withoutHeaders : text[lang].file.withHeaders }}
</a>
<br><br>
<div v-for="(column, index) in localUserColumns">
<div class="row">
<div class="col-md-6">
<div class="panel panel-success" :class="{ 'panel-success': column.selection, 'panel-danger': !column.selection }">
<div class="panel-heading">
<p v-if="column.selection">{{ column.selection.name }}</p>
<p v-else>{{ text[lang].chooseColumn }}</p>
</div>
<div class="panel-body">
<multiselect
:id="index"
v-model="column.selection"
deselect-label=""
:selected-label="text[lang].selected"
:select-label="text[lang].select"
track-by="name"
label="name"
:placeholder="text[lang].selectColumn"
:options="column.options"
:searchable="false"
:allow-empty="false"
@select="onSelectChange"
></multiselect>
</div>
</div>
</div>
<div class="col-md-6">
<div class="panel panel-primary">
<div class="panel-heading">
{{ text[lang].columnData }} <i>"{{ column.name }}"</i>
</div>
<div class="panel-body">
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th class="text-center">{{ column.name }}</th>
</tr>
</thead>
<tbody>
<tr v-for="data in column.displayedData">
<td>{{ data }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="text-right" v-if="showValidateButton">
<button @click="validate" id="validate-columns" class="btn btn-primary">{{ text[lang].submit }}</button>
</div>
<simplert
:useRadius="true"
:useIcon="true"
ref="simplert">
</simplert>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect';
import Simplert from 'vue2-simplert';
import _ from 'lodash';
import text from '../lang';
export default {
name: 'ColumnChooser',
components: { Multiselect, Simplert },
props: {
userColumns: {
type: Array,
required: true,
validator: columns => columns.every(column => _.has(column, 'name') && _.has(column, 'data')),
},
columns: {
type: Array,
required: true,
validator: columns => columns.every(column => _.has(column, 'name') && _.has(column, 'value')),
},
showValidateButton: {
type: Boolean,
default: () => true,
},
lang: {
type: String,
default: () => 'en',
},
},
watch: {
userColumns(newColumns) {
this.fillLocalUserColumns(newColumns);
},
},
methods: {
onSelectChange({ value: selectedValue }, id) { // eslint-disable-line
if (this.optionalValues.indexOf(selectedValue) !== -1) return;
_.forEach(this.localUserColumns, (column, index) => {
if (index !== id) {
const hasSelectionConflict = column.selection && column.selection.value === selectedValue;
if (hasSelectionConflict) {
column.selection = null; // eslint-disable-line
}
}
});
},
validate() {
const hasMadeAllSelections = this.localUserColumns.every(column => column.selection !== null);
if (!hasMadeAllSelections) {
this.showError(text[this.lang].error.selectColumn); // eslint-disable-line
} else {
const selectedRequiredValues = _
.map(this.localUserColumns, 'selection.value')
.filter(value => this.requiredValues.indexOf(value) !== -1);
const missingValues = _.difference(this.requiredValues, selectedRequiredValues);
if (missingValues.length > 0) {
this.showError(`${text[this.lang].error.missingColumns} ${missingValues.join(', ')}`); // eslint-disable-line
return;
}
this.results = this.localUserColumns.map(localColumn => ({
column: localColumn.selection.value,
data: localColumn.data,
}));
this.$emit('on-validate', this.results);
}
},
fillLocalUserColumns(newColumns) {
this.localUserColumns = newColumns.map(column => ({
name: column.name,
displayedData: _.take(column.data, 4),
data: column.data,
options: _.clone(this.columns),
selection: null,
}));
},
toggleHeaders() {
if (this.withHeaders) {
this.localUserColumns.forEach((column, index) => {
const data = [column.name].concat(column.data);
column.name = `${text[this.lang].column} ${index + 1}`; // eslint-disable-line
column.data = data; // eslint-disable-line
column.displayedData = _.take(column.data, 4); // eslint-disable-line
});
} else {
this.localUserColumns.forEach((column) => {
column.name = column.data.shift(); // eslint-disable-line
column.displayedData = _.take(column.data, 4); // eslint-disable-line
});
}
this.withHeaders = !this.withHeaders;
},
showError(message) {
this.$refs.simplert.openSimplert({
title: text[this.lang].error.title,
message,
type: 'error',
customCloseBtnText: text[this.lang].close,
});
},
},
data() {
return {
withHeaders: true,
localUserColumns: [],
requiredValues: [],
optionalValues: [],
results: [],
text,
};
},
mounted() {
this.fillLocalUserColumns(this.userColumns);
this.optionalValues = this.columns
.filter(column => column.isOptional)
.map(column => column.value);
this.requiredValues = this.columns
.filter(column => !column.isOptional)
.map(column => column.value);
},
};
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>

View File

@@ -0,0 +1,194 @@
<template>
<div class="catalog-column-chooser">
<a href="#" class="header-tool" @click="toggleHeaders">
{{ withHeaders ? text[lang].file.withoutHeaders : text[lang].file.withHeaders }}
</a>
<br><br>
<div v-for="(column, index) in localUserColumns">
<div class="row">
<div class="col-md-6">
<div class="panel panel-success" :class="{ 'panel-success': column.selection, 'panel-danger': !column.selection }">
<div class="panel-heading">
<p v-if="column.selection">{{ column.selection.name }}</p>
<p v-else>{{ text[lang].chooseColumn }}</p>
</div>
<div class="panel-body">
<multiselect
:id="index"
v-model="column.selection"
deselect-label=""
:selected-label="text[lang].selected"
:select-label="text[lang].select"
track-by="name"
label="name"
:placeholder="text[lang].selectColumn"
:options="column.options"
:searchable="false"
:allow-empty="false"
@select="onSelectChange"
></multiselect>
</div>
</div>
</div>
<div class="col-md-6">
<div class="panel panel-primary">
<div class="panel-heading">
{{ text[lang].columnData }} <i>"{{ column.name }}"</i>
</div>
<div class="panel-body">
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th class="text-center">{{ column.name }}</th>
</tr>
</thead>
<tbody>
<tr v-for="data in column.displayedData">
<td>{{ data }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="text-right" v-if="showValidateButton">
<button @click="validate" id="validate-columns" class="btn btn-primary">{{ text[lang].submit }}</button>
</div>
<simplert
:useRadius="true"
:useIcon="true"
ref="simplert">
</simplert>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect';
import Simplert from 'vue2-simplert';
import _ from 'lodash';
import text from '../lang';
export default {
name: 'ColumnChooser',
components: { Multiselect, Simplert },
props: {
userColumns: {
type: Array,
required: true,
validator: columns => columns.every(column => _.has(column, 'name') && _.has(column, 'data')),
},
columns: {
type: Array,
required: true,
validator: columns => columns.every(column => _.has(column, 'name') && _.has(column, 'value')),
},
showValidateButton: {
type: Boolean,
default: () => true,
},
lang: {
type: String,
default: () => 'en',
},
},
watch: {
userColumns(newColumns) {
this.fillLocalUserColumns(newColumns);
},
},
methods: {
onSelectChange({ value: selectedValue }, id) { // eslint-disable-line
if (this.optionalValues.indexOf(selectedValue) !== -1) return;
_.forEach(this.localUserColumns, (column, index) => {
if (index !== id) {
const hasSelectionConflict = column.selection && column.selection.value === selectedValue;
if (hasSelectionConflict) {
column.selection = null; // eslint-disable-line
}
}
});
},
validate() {
const hasMadeAllSelections = this.localUserColumns.every(column => column.selection !== null);
if (!hasMadeAllSelections) {
this.showError(text[this.lang].error.selectColumn); // eslint-disable-line
} else {
const selectedRequiredValues = _
.map(this.localUserColumns, 'selection.value')
.filter(value => this.requiredValues.indexOf(value) !== -1);
const missingValues = _.difference(this.requiredValues, selectedRequiredValues);
if (missingValues.length > 0) {
this.showError(`${text[this.lang].error.missingColumns} ${missingValues.join(', ')}`); // eslint-disable-line
return;
}
this.results = this.localUserColumns.map(localColumn => ({
column: localColumn.selection.value,
data: localColumn.data,
}));
this.$emit('on-validate', this.results);
}
},
fillLocalUserColumns(newColumns) {
this.localUserColumns = newColumns.map(column => ({
name: column.name,
displayedData: _.take(column.data, 4),
data: column.data,
options: _.clone(this.columns),
selection: null,
}));
},
toggleHeaders() {
if (this.withHeaders) {
this.localUserColumns.forEach((column, index) => {
const data = [column.name].concat(column.data);
column.name = `${text[this.lang].column} ${index + 1}`; // eslint-disable-line
column.data = data; // eslint-disable-line
column.displayedData = _.take(column.data, 4); // eslint-disable-line
});
} else {
this.localUserColumns.forEach((column) => {
column.name = column.data.shift(); // eslint-disable-line
column.displayedData = _.take(column.data, 4); // eslint-disable-line
});
}
this.withHeaders = !this.withHeaders;
},
showError(message) {
this.$refs.simplert.openSimplert({
title: text[this.lang].error.title,
message,
type: 'error',
customCloseBtnText: text[this.lang].close,
});
},
},
data() {
return {
withHeaders: true,
localUserColumns: [],
requiredValues: [],
optionalValues: [],
results: [],
text,
};
},
mounted() {
this.fillLocalUserColumns(this.userColumns);
this.optionalValues = this.columns
.filter(column => column.isOptional)
.map(column => column.value);
this.requiredValues = this.columns
.filter(column => !column.isOptional)
.map(column => column.value);
},
};
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>

View File

@@ -0,0 +1,132 @@
<template>
<div class="parse-file">
<div class="alert alert-danger" v-if="error">
{{ error }}
</div>
<div
v-if="!file"
:class="['dropzone-area', dragging ? 'dropzone-over' : '']"
drag-over="handleDragOver"
@dragenter="dragging=true"
@dragleave="dragging=false"
>
<div class="dropzone-text">
<span class="dropzone-title">{{ text[lang].file.select }}</span>
<span class="dropzone-info" v-if="help">{{ help }}</span>
</div>
<input type="file" @change="upload">
</div>
<div class="dropzone-preview" v-else>
<button @click="file=null" class="btn btn-primary">{{ text[lang].file.tryAgain }}</button>
</div>
</div>
</template>
<script>
import parseFile from '../parser';
import text from '../lang';
export default {
name: 'ParseFile',
props: {
help: {
type: String,
},
lang: {
type: String,
default: () => 'en',
},
},
methods: {
upload(event) {
const files = event.target.files || event.dataTransfer.files;
if (files.length !== 1) {
this.error = text[this.lang].error.fileSelection;
return;
}
this.file = event.target.files[0];
parseFile(this.file, text[this.lang]).then((results) => {
this.error = null;
this.$emit('fileDataReceived', results);
}).catch((error) => {
this.error = error;
this.file = null;
});
},
reset() {
this.error = null;
this.file = null;
},
},
data() {
return {
error: null,
dragging: false,
file: null,
text,
};
},
};
</script>
<style scoped>
.dropzone-over {
border: 1px solid red !important;
}
.dropzone-area {
height: 200px;
position: relative;
border: 2px dashed #CBCBCB;
}
.dropzone-area:hover {
border: 2px dashed #2E94C4;
}
.dropzone-area .dropzone-title {
color: #1975A0;
}
.dropzone-area input {
position: absolute;
cursor: pointer;
top: 0px;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
}
.dropzone-text {
position: absolute;
top: 50%;
text-align: center;
transform: translate(0, -50%);
width: 100%;
}
.dropzone-text span {
display: block;
font-family: Arial, Helvetica;
line-height: 1.9;
}
.dropzone-title {
font-size: 13px;
color: #787878;
letter-spacing: 0.4px;
}
.dropzone-info {
font-size: 12px;
color: #A8A8A8;
letter-spacing: 0.4px;
}
.dropzone-button {
position: absolute;
top: 10px;
right: 10px;
display: none;
}
</style>

View File

@@ -0,0 +1,107 @@
<template>
<div class="xls-csv-parser">
<parse-file @fileDataReceived="fileDataReceived" :help="help" ref="parseFile" :lang="lang"></parse-file>
<br><br>
<column-chooser
v-if="showColumnChooser"
ref="columnChooser"
:userColumns="userColumns"
:columns="columns"
:showValidateButton="showValidateButton"
:lang="lang"
@on-validate="onValidate"
></column-chooser>
<div class="parser-hidden-columns-input" v-for="(result, i) in results">
<input type="hidden" v-for="(data, i) in result.data" :name="`${result.column}[${i}]`" :value="data">
</div>
<simplert
:useRadius="true"
:useIcon="true"
ref="simplert">
</simplert>
</div>
</template>
<script>
import _ from 'lodash';
import Simplert from 'vue2-simplert';
import ColumnChooser from './ColumnChooser';
import ParseFile from './ParseFile';
import text from '../lang';
export default {
name: 'XlsCsvParser',
components: { ColumnChooser, ParseFile, Simplert },
props: {
columns: {
type: Array,
required: true,
validator: columns => columns.every(column => _.has(column, 'name') && _.has(column, 'value')),
},
validateButtonId: {
type: String,
default: () => null,
},
help: {
type: String,
},
lang: {
type: String,
validator: language => ['en', 'fr'].indexOf(language) !== -1,
default: () => 'en',
},
},
methods: {
fileDataReceived(fileData) {
this.results = [];
const requiredColumns = this.columns.filter(column => !column.isOptional);
if (fileData.length < requiredColumns.length) {
const message = `${text[this.lang].error.columnNumber}<br> ${this.columns.map(column => column.name).join('<br> ')}`;
this.showError(message);
return;
}
this.userColumns = fileData;
this.showColumnChooser = true;
},
onValidate(results) {
this.results = results;
this.$emit('on-validate', results);
},
validate(event) {
event.preventDefault();
if (!this.showColumnChooser) {
this.showError(text[this.lang].error.fileNumber);
} else {
this.$refs.columnChooser.validate();
}
},
showError(message) {
this.$refs.simplert.openSimplert({
title: text[this.lang].error.title,
message,
type: 'error',
customCloseBtnText: text[this.lang].close,
onClose: () => {
this.$refs.parseFile.reset();
},
});
},
},
mounted() {
if (this.validateButtonId) {
this.showValidateButton = false;
document.getElementById(this.validateButtonId).addEventListener('click', this.validate);
}
},
data() {
return {
showColumnChooser: false,
showHiddenInputs: false,
showValidateButton: true,
results: [],
userColumns: [],
};
},
};
</script>