SUPPORT-8772:add new fileUpload
This commit is contained in:
parent
ceecd93b79
commit
0c1c5bbdcf
21 changed files with 2282 additions and 20 deletions
|
|
@ -1,6 +1,10 @@
|
|||
import javax.servlet.MultipartConfigElement;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRegistration;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
|
||||
import org.springframework.web.util.IntrospectorCleanupListener;
|
||||
|
||||
|
|
@ -9,6 +13,7 @@ import org.springframework.web.util.IntrospectorCleanupListener;
|
|||
* Spring scans for initializers automatically
|
||||
*/
|
||||
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(WebAppInitializer.class);
|
||||
|
||||
public void onStartup(ServletContext servletContext) throws ServletException {
|
||||
super.onStartup(servletContext);
|
||||
|
|
@ -28,4 +33,39 @@ public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServlet
|
|||
protected Class<?>[] getServletConfigClasses() {
|
||||
return new Class[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
|
||||
|
||||
// read from env or assign default values
|
||||
int maxFileSize = parseOrDefault("ERVU_FILE_UPLOAD_MAX_FILE_SIZE", 5242880);
|
||||
int maxRequestSize = parseOrDefault("ERVU_FILE_UPLOAD_MAX_REQUEST_SIZE", 6291456);
|
||||
int fileSizeThreshold = parseOrDefault("ERVU_FILE_UPLOAD_FILE_SIZE_THRESHOLD", 0);
|
||||
|
||||
MultipartConfigElement multipartConfigElement = new MultipartConfigElement(
|
||||
"/tmp",
|
||||
maxFileSize,
|
||||
maxRequestSize,
|
||||
fileSizeThreshold);
|
||||
registration.setMultipartConfig(multipartConfigElement);
|
||||
|
||||
LOGGER.info("Max file upload size is set to: " + multipartConfigElement.getMaxFileSize());
|
||||
LOGGER.info("Max file upload request size is set to: " + multipartConfigElement.getMaxRequestSize());
|
||||
LOGGER.info("File size threshold is set to: " + multipartConfigElement.getFileSizeThreshold());
|
||||
|
||||
}
|
||||
|
||||
private int parseOrDefault(String envVar, int defaultVal) {
|
||||
String envVarValue = System.getenv(envVar);
|
||||
if (envVar == null) {
|
||||
LOGGER.info("Environment variable {} is null, using default value: {}", envVar, defaultVal);
|
||||
return defaultVal;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(envVarValue);
|
||||
} catch (NumberFormatException e) {
|
||||
LOGGER.info("Environment variable {} is not an integer, using default value: {}", envVar, defaultVal);
|
||||
return defaultVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
package ru.micord.ervu_dashboard.controller;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import ru.micord.ervu_dashboard.service.ErvuFileUploadService;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/upload")
|
||||
public class ErvuFileUploadController {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ErvuFileUploadController.class);
|
||||
private final ErvuFileUploadService fileUploadService;
|
||||
|
||||
public ErvuFileUploadController(ErvuFileUploadService fileUploadService) {
|
||||
this.fileUploadService = fileUploadService;
|
||||
}
|
||||
|
||||
@PostMapping()
|
||||
public ResponseEntity<?> uploadFile(@RequestParam("file") MultipartFile file) {
|
||||
if (file.isEmpty()) {
|
||||
return ResponseEntity.badRequest().body("No file provided.");
|
||||
}
|
||||
try {
|
||||
fileUploadService.save(file);
|
||||
return ResponseEntity.ok().body("File successfully uploaded.");
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
return ResponseEntity.internalServerError().body("An error occurred while uploading file.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package ru.micord.ervu_dashboard.dao;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jooq.DSLContext;
|
||||
import org.jooq.InsertOnDuplicateSetMoreStep;
|
||||
import org.jooq.TableField;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.micord.ervu_dashboard.db_beans.appeals.tables.records.AppealsListRecord;
|
||||
|
||||
import static ru.micord.ervu_dashboard.db_beans.appeals.Tables.APPEALS_LIST;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
@Repository
|
||||
public class ErvuFileUploadDao {
|
||||
private final DSLContext dslContext;
|
||||
|
||||
public ErvuFileUploadDao(DSLContext dslContext) {
|
||||
this.dslContext = dslContext;
|
||||
}
|
||||
|
||||
public void save(List<Map<String, String>> records) {
|
||||
LocalDateTime uploadDate = LocalDateTime.now();
|
||||
|
||||
var queries = records.stream().map(record -> {
|
||||
Map<TableField<AppealsListRecord, ?>, Object> fieldsMap = createFieldsMap(record, uploadDate);
|
||||
Map<TableField<AppealsListRecord, ?>, Object> updateFieldsMap = new HashMap<>(fieldsMap);
|
||||
updateFieldsMap.remove(APPEALS_LIST.NUMBER);
|
||||
return dslContext.insertInto(APPEALS_LIST)
|
||||
.set(fieldsMap)
|
||||
.onConflict(APPEALS_LIST.NUMBER)
|
||||
.doUpdate()
|
||||
.set(updateFieldsMap)
|
||||
.where(APPEALS_LIST.NUMBER.eq((Integer)fieldsMap.get(APPEALS_LIST.NUMBER)));
|
||||
}).toList();
|
||||
|
||||
|
||||
dslContext.batch(queries).execute();
|
||||
}
|
||||
|
||||
private Map<TableField<AppealsListRecord, ?>, Object> createFieldsMap(Map<String, String> valueMap, LocalDateTime uploadDate) {
|
||||
Map<TableField<AppealsListRecord, ?>, Object> fieldsMap = new HashMap<>();
|
||||
fieldsMap.put(APPEALS_LIST.NUMBER, Integer.valueOf(valueMap.get(APPEALS_LIST.NUMBER.getComment())));
|
||||
fieldsMap.put(APPEALS_LIST.EPGU_NUM, valueMap.get(APPEALS_LIST.EPGU_NUM.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.SOURCE, valueMap.get(APPEALS_LIST.SOURCE.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.REGION, valueMap.get(APPEALS_LIST.REGION.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.CATEGORY, valueMap.get(APPEALS_LIST.CATEGORY.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.SUBCATEGORY, valueMap.get(APPEALS_LIST.SUBCATEGORY.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.FACT, valueMap.get(APPEALS_LIST.FACT.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.INCOMING_ORG, valueMap.get(APPEALS_LIST.INCOMING_ORG.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.CURRENT_ORG, valueMap.get(APPEALS_LIST.CURRENT_ORG.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.INCOMING_DATE, valueMap.get(APPEALS_LIST.INCOMING_DATE.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.PLAN_END_DATE, valueMap.get(APPEALS_LIST.PLAN_END_DATE.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.FACT_END_DATE, valueMap.get(APPEALS_LIST.FACT_END_DATE.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.STAGE, valueMap.get(APPEALS_LIST.STAGE.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.STATUS, valueMap.get(APPEALS_LIST.STATUS.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.OVERDUE, valueMap.get(APPEALS_LIST.OVERDUE.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.FAST_TRACK, valueMap.get(APPEALS_LIST.FAST_TRACK.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.FZ, valueMap.get(APPEALS_LIST.FZ.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.SOLUTION_TYPE, valueMap.get(APPEALS_LIST.SOLUTION_TYPE.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.SENT_BY_EMAIL_TO_NOT_CONNECTED_FOIV, valueMap.get(APPEALS_LIST.SENT_BY_EMAIL_TO_NOT_CONNECTED_FOIV.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.RESPONSE_EVALUTION, valueMap.get(APPEALS_LIST.RESPONSE_EVALUTION.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.RECONSIDERATION, valueMap.get(APPEALS_LIST.RECONSIDERATION.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.COORDINATOR_ORG, valueMap.get(APPEALS_LIST.COORDINATOR_ORG.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.COORDINATOR_FIO, valueMap.get(APPEALS_LIST.COORDINATOR_FIO.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.CONTRACTOR_ORG, valueMap.get(APPEALS_LIST.CONTRACTOR_ORG.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.CONTRACTOR_FIO, valueMap.get(APPEALS_LIST.CONTRACTOR_FIO.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.HEAD_ORG, valueMap.get(APPEALS_LIST.HEAD_ORG.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.HEAD_FIO, valueMap.get(APPEALS_LIST.HEAD_FIO.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.ISSUE_DATE, valueMap.get(APPEALS_LIST.ISSUE_DATE.getComment()));
|
||||
fieldsMap.put(APPEALS_LIST.UPLOAD_DATE, Timestamp.valueOf(uploadDate));
|
||||
return fieldsMap;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package ru.micord.ervu_dashboard.exception;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
public class FileUploadException extends RuntimeException {
|
||||
|
||||
public FileUploadException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public FileUploadException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public FileUploadException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package ru.micord.ervu_dashboard.exception;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
public class MissingHeaderException extends RuntimeException {
|
||||
|
||||
public MissingHeaderException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MissingHeaderException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MissingHeaderException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package ru.micord.ervu_dashboard.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import ru.micord.ervu_dashboard.dao.ErvuFileUploadDao;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
@Service
|
||||
public class ErvuFileUploadService {
|
||||
private final FileParser xlsFileParser;
|
||||
private final ErvuFileUploadDao fileUploadDao;
|
||||
|
||||
public ErvuFileUploadService(FileParser xlsFileParser, ErvuFileUploadDao fileUploadDao) {
|
||||
this.xlsFileParser = xlsFileParser;
|
||||
this.fileUploadDao = fileUploadDao;
|
||||
}
|
||||
|
||||
public void save(MultipartFile file) {
|
||||
List<Map<String, String>> maps = xlsFileParser.parseFile(file);
|
||||
fileUploadDao.save(maps);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package ru.micord.ervu_dashboard.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
|
||||
public interface FileParser {
|
||||
|
||||
List<Map<String, String>> parseFile(MultipartFile file);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
package ru.micord.ervu_dashboard.service;
|
||||
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import ru.micord.ervu_dashboard.exception.FileUploadException;
|
||||
import ru.micord.ervu_dashboard.exception.MissingHeaderException;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
@Service
|
||||
public class XlsParser implements FileParser {
|
||||
private static final List<String> REQUIRED_HEADERS = Arrays.asList(
|
||||
"Номер", "Номер ЕПГУ", "Источник", "Верхнеуровневый ЛКО", "Категория", "Подкатегория",
|
||||
"Факт", "Организация, в которую поступило сообщение",
|
||||
"Организация, в которой находится сообщение",
|
||||
"Дата поступления", "Дата планируемого завершения работ",
|
||||
"Дата фактического завершения работ",
|
||||
"Стадия", "Статус", "Просрочено", "Фаст-трек", "ФЗ", "Тип решения",
|
||||
"Направлено по email в ФОИВ, не подключенный к ПОС", "Оценка ответа заявителем",
|
||||
"Повторное рассмотрение", "Организация координатора", "ФИО координатора",
|
||||
"Организация исполнителя", "ФИО исполнителя", "Организация руководителя", "ФИО руководителя",
|
||||
"Дата вынесения"
|
||||
);
|
||||
|
||||
@Override
|
||||
public List<Map<String, String>> parseFile(MultipartFile file) {
|
||||
List<Map<String, String>> records = new ArrayList<>();
|
||||
try (InputStream inputStream = file.getInputStream();
|
||||
Workbook workbook = WorkbookFactory.create(inputStream)) {
|
||||
|
||||
Sheet sheet = workbook.getSheetAt(0);
|
||||
Iterator<Row> rowIterator = sheet.iterator();
|
||||
|
||||
List<String> headers = new ArrayList<>();
|
||||
if (rowIterator.hasNext()) {
|
||||
Row headerRow = rowIterator.next();
|
||||
for (Cell cell : headerRow) {
|
||||
headers.add(cell.getStringCellValue());
|
||||
}
|
||||
validateHeaders(headers);
|
||||
}
|
||||
while (rowIterator.hasNext()) {
|
||||
Row row = rowIterator.next();
|
||||
Map<String, String> rowData = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < headers.size(); i++) {
|
||||
Cell cell = row.getCell(i, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
|
||||
rowData.put(headers.get(i), getCellValue(cell));
|
||||
}
|
||||
records.add(rowData);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new FileUploadException("Error parsing the file", e);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
private void validateHeaders(List<String> headers) {
|
||||
for (String requiredHeader : REQUIRED_HEADERS) {
|
||||
if (!headers.contains(requiredHeader)) {
|
||||
throw new MissingHeaderException("Required header is missing:" + requiredHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getCellValue(Cell cell) {
|
||||
switch (cell.getCellType()) {
|
||||
case STRING:
|
||||
return cell.getStringCellValue();
|
||||
case NUMERIC:
|
||||
if (DateUtil.isCellDateFormatted(cell)) {
|
||||
return cell.getLocalDateTimeCellValue().toString();
|
||||
}
|
||||
else {
|
||||
return String.valueOf((int) cell.getNumericCellValue());
|
||||
}
|
||||
case BOOLEAN:
|
||||
return String.valueOf(cell.getBooleanCellValue());
|
||||
case BLANK:
|
||||
return "";
|
||||
default:
|
||||
return cell.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getRequiredHeaders(String rawHeaders) {
|
||||
return Arrays.stream(rawHeaders.split(";"))
|
||||
.map(String::trim)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
|
@ -104,5 +104,13 @@
|
|||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0002" author="adel.ka" dbms="postgresql">
|
||||
<comment>add primary key</comment>
|
||||
<addPrimaryKey
|
||||
columnNames="number"
|
||||
constraintName="pk_appeals_list"
|
||||
tableName="appeals_list"
|
||||
schemaName="appeals" />
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
|
|
|||
|
|
@ -5,3 +5,8 @@ DB_APP_PASSWORD=ervu-dashboard
|
|||
DB_APP_HOST=10.10.31.119
|
||||
DB_APP_PORT=5432
|
||||
DB_APP_NAME=ervu-dashboard-copy
|
||||
|
||||
ERVU_FILE_UPLOAD_XLSX_REQUIRED_HEADERS='Номер;Номер ЕПГУ;Источник;Верхнеуровневый ЛКО;Категория;Подкатегория;Факт;Организация, в которую поступило сообщение;Организация, в которой находится сообщение;Дата поступления;Дата планируемого завершения работ;Дата фактического завершения работ;Стадия;Статус;Просрочено;Фаст-трек;ФЗ;Тип решения;Направлено по email в ФОИВ, не подключенный к ПОС;Оценка ответа заявителем;Повторное рассмотрение;Организация координатора;ФИО координатора;Организация исполнителя;ФИО исполнителя;Организация руководителя;ФИО руководителя;'
|
||||
ERVU_FILE_UPLOAD_MAX_FILE_SIZE=5242880
|
||||
ERVU_FILE_UPLOAD_MAX_REQUEST_SIZE=6291456
|
||||
ERVU_FILE_UPLOAD_FILE_SIZE_THRESHOLD=0
|
||||
5
frontend/package-lock.json
generated
5
frontend/package-lock.json
generated
|
|
@ -4997,6 +4997,11 @@
|
|||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true
|
||||
},
|
||||
"ng2-file-upload": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://repo.micord.ru/repository/npm-all/ng2-file-upload/-/ng2-file-upload-1.3.0.tgz",
|
||||
"integrity": "sha512-Pudxik6LWYsT8hNiEW7RfjgGWAnvfQywxwJYMdt1snTUe+KnlRc/QqPv3QEQW6plXTanuLkYz/TbqilSfSHOsw=="
|
||||
},
|
||||
"ngx-cookie": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://repo.micord.ru/repository/npm-all/ngx-cookie/-/ngx-cookie-3.0.1.tgz",
|
||||
|
|
|
|||
|
|
@ -49,10 +49,11 @@
|
|||
"jsgantt-improved": "2.0.10-cg",
|
||||
"moment": "2.30.1",
|
||||
"moment-timezone": "0.5.46",
|
||||
"ngx-treeview": "10.0.2-micord.2",
|
||||
"ng2-file-upload": "^1.3.0",
|
||||
"ngx-cookie": "3.0.1",
|
||||
"ngx-international-phone-number": "1.0.6",
|
||||
"ngx-toastr": "10.2.0-cg",
|
||||
"ngx-treeview": "10.0.2-micord.2",
|
||||
"popper.js": "1.14.7",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rxjs": "6.4.0",
|
||||
|
|
|
|||
|
|
@ -1540,6 +1540,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
.webbpm .ervu-file-upload .file-drop-zone {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 152px 0 66px 0;
|
||||
border: 1px dashed #0e243b;
|
||||
border-radius: 16px;
|
||||
background: #a58888;
|
||||
}
|
||||
|
||||
/* ErvuFileUpload end *
|
||||
|
||||
/*
|
||||
grid-checkbox
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
<div [id]="getObjectId()"
|
||||
class="ervu-file-upload"
|
||||
[ngbTooltip]="tooltip | emptyIfNull">
|
||||
<div ng2FileDrop
|
||||
[uploader]="uploader"
|
||||
class="file-drop-zone"
|
||||
*ngIf="(!maxFilesToUpload || uploader.queue.length < maxFilesToUpload) && isDropZoneVisible">
|
||||
<span class="select-file-field-text">{{selectFileFieldText}}</span>
|
||||
<button class="select-file-btn" (click)="openFileChooseDialog()">{{selectFileButtonName}}</button>
|
||||
</div>
|
||||
<!-- input is out of file-drop-zone because after change ngIf condition input doesn't firing events -->
|
||||
<input type="file"
|
||||
class="file-input"
|
||||
ng2FileSelect
|
||||
[uploader]="uploader"
|
||||
[multiple]="!maxFilesToUpload || maxFilesToUpload > 1"
|
||||
[accept]="getExtensions()"
|
||||
hidden>
|
||||
<div class="selected-file-list" *ngIf="isFilesListVisible">
|
||||
<div class="selected-file" *ngFor="let item of uploader.queue">
|
||||
<span class="selected-file-name">{{item?.file?.name}}</span>
|
||||
<span class="selected-file-size" *ngIf="displayFileSize">{{item?.file?.size/1024/1024 | number: '.2'}} MB</span>
|
||||
<button class="selected-file-delete-btn" (click)="removeFile(item)">{{removeFileButtonName}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="file-upload-progress" *ngIf="displayProgressBar && isProgressBarVisible">
|
||||
<div class="file-upload-progress-bar"
|
||||
role="progressbar"
|
||||
[ngStyle]="{ 'width': uploader.progress + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
229
frontend/src/ts/ervu-dashboard/component/field/ErvuFileUpload.ts
Normal file
229
frontend/src/ts/ervu-dashboard/component/field/ErvuFileUpload.ts
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
import {
|
||||
AppConfigService,
|
||||
Event,
|
||||
InputControl,
|
||||
MessagesService,
|
||||
NotNull,
|
||||
UnsupportedOperationError,
|
||||
Visible,
|
||||
} from "@webbpm/base-package";
|
||||
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef,} from "@angular/core";
|
||||
import {FileItem, FileLikeObject, FileUploader} from "ng2-file-upload";
|
||||
|
||||
@Component({
|
||||
selector: 'ervu-file-upload',
|
||||
templateUrl: './../../../../../src/resources/template/ervu-dashboard/ErvuFileUpload.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ErvuFileUpload extends InputControl {
|
||||
private static readonly BACKEND_URL: string = "backend.context";
|
||||
|
||||
@NotNull("true") public selectFileFieldText: string;
|
||||
@NotNull("true") public selectFileButtonName: string;
|
||||
@NotNull("true") public removeFileButtonName: string;
|
||||
public maxFileSizeMb: number;
|
||||
public extensionFilter: string[] = [];
|
||||
public maxFilesToUpload: number;
|
||||
@NotNull("true") public displayFileSize: boolean = false;
|
||||
@NotNull("true") public displayProgressBar: boolean = false;
|
||||
@Visible("false") public uploader: FileUploader;
|
||||
|
||||
@Visible("false") public fileAddedEvent: Event<any> = new Event<any>();
|
||||
@Visible("false") public fileDeletedEvent: Event<any> = new Event<any>();
|
||||
@Visible("false") public fileUploadStartEvent: Event<any> = new Event<any>();
|
||||
@Visible("false") public fileUploadEndEvent: Event<any> = new Event<any>();
|
||||
@Visible("false") public fileUploadFailedEvent: Event<any> = new Event<any>();
|
||||
|
||||
public isDropZoneVisible: boolean = true;
|
||||
public isFilesListVisible: boolean = true;
|
||||
public isProgressBarVisible: boolean = false;
|
||||
|
||||
private fileInputEl: HTMLInputElement;
|
||||
private url: string = '/backend/upload';
|
||||
private messagesService: MessagesService;
|
||||
private isUploadErrorOccurred = false;
|
||||
private appConfigService: AppConfigService;
|
||||
|
||||
constructor(el: ElementRef, cd: ChangeDetectorRef) {
|
||||
super(el, cd);
|
||||
this.uploader = new FileUploader({url: this.url});
|
||||
}
|
||||
|
||||
initialize() {
|
||||
super.initialize();
|
||||
this.messagesService = this.injector.get(MessagesService);
|
||||
this.appConfigService = this.injector.get(AppConfigService);
|
||||
this.url =
|
||||
`/${this.appConfigService.getParamValue(ErvuFileUpload.BACKEND_URL)}/upload`;
|
||||
|
||||
this.uploader.setOptions({
|
||||
url: this.url,
|
||||
autoUpload: false,
|
||||
filters: [{
|
||||
name: 'extension', fn: (item: any): boolean => {
|
||||
if (!this.extensionFilter.length) {
|
||||
return true;
|
||||
}
|
||||
const fileExtension = item.name.substring(
|
||||
item.name.lastIndexOf('.') + 1, item.name.length);
|
||||
for (let ext of this.extensionFilter) {
|
||||
if (ext.toUpperCase() === fileExtension.toUpperCase()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}],
|
||||
maxFileSize: this.maxFileSizeMb
|
||||
? this.maxFileSizeMb * 1024 * 1024
|
||||
: undefined,
|
||||
queueLimit: this.maxFilesToUpload
|
||||
? this.maxFilesToUpload
|
||||
: undefined,
|
||||
headers: [{
|
||||
name: "Client-Time-Zone",
|
||||
value: Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
}]
|
||||
});
|
||||
|
||||
this.setUploaderMethods();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
super.ngAfterViewInit();
|
||||
this.fileInputEl = this.el.nativeElement.querySelector('.file-input');
|
||||
}
|
||||
|
||||
openFileChooseDialog() {
|
||||
this.fileInputEl.click();
|
||||
}
|
||||
|
||||
@Visible() uploadFiles() {
|
||||
this.uploader.uploadAll();
|
||||
}
|
||||
|
||||
removeFile(item: FileItem) {
|
||||
item.remove();
|
||||
this.fileInputEl.value = null;
|
||||
this.fileDeletedEvent.trigger();
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
private setUploaderMethods() {
|
||||
this.uploader.onBeforeUploadItem = (fileItem: FileItem) => {
|
||||
|
||||
//refresh headers
|
||||
this.uploader.setOptions({
|
||||
headers: [{
|
||||
name: "Client-Time-Zone",
|
||||
value: Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
}]
|
||||
});
|
||||
this.fileUploadStartEvent.trigger();
|
||||
this.isDropZoneVisible = false;
|
||||
this.isFilesListVisible = false;
|
||||
this.isProgressBarVisible = true;
|
||||
this.cd.markForCheck();
|
||||
};
|
||||
|
||||
this.uploader.onErrorItem = (item: FileItem, response: string) => {
|
||||
this.fileUploadFailedEvent.trigger();
|
||||
this.uploader.cancelAll();
|
||||
this.messagesService.error(`Не удалось отправить следующие файлы: ${item.file.name},` +
|
||||
` ${this.uploader.getNotUploadedItems()
|
||||
.map(notUploadeditem => notUploadeditem.file.name)
|
||||
.join(', ')}.`);
|
||||
this.uploader.clearQueue();
|
||||
this.isDropZoneVisible = true;
|
||||
this.isFilesListVisible = true;
|
||||
this.isProgressBarVisible = false;
|
||||
this.isUploadErrorOccurred = true;
|
||||
this.cd.markForCheck();
|
||||
};
|
||||
|
||||
this.uploader.onCompleteAll = () => {
|
||||
if (!this.isUploadErrorOccurred) {
|
||||
this.uploader.clearQueue();
|
||||
this.fileUploadEndEvent.trigger();
|
||||
this.isProgressBarVisible = false;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
};
|
||||
|
||||
this.uploader.onAfterAddingFile = (fileItem: FileItem) => {
|
||||
this.fileAddedEvent.trigger();
|
||||
}
|
||||
|
||||
this.uploader.onWhenAddingFileFailed = (item: FileLikeObject, filter: any, options: any) => {
|
||||
switch (filter.name) {
|
||||
case "fileSize":
|
||||
this.messagesService.error(
|
||||
`Размер файла ${item.name} превышает предельно допустимый = ${this.maxFileSizeMb} MB`);
|
||||
break;
|
||||
case "queueLimit":
|
||||
this.messagesService.error(`Не удалось добавить файл ${item.name}. ` +
|
||||
`Достигнуто максимальное количество файлов для загрузки = ${this.maxFilesToUpload}`);
|
||||
break;
|
||||
case "extension":
|
||||
this.messagesService.error(`Файл ${item.name} имеет недопустимое расширение.`);
|
||||
break;
|
||||
default:
|
||||
this.messagesService.error(`Не удалось добавить файл ${item.name}.`);
|
||||
}
|
||||
this.fileInputEl.value = null;
|
||||
this.cd.markForCheck();
|
||||
};
|
||||
}
|
||||
|
||||
@Visible() getExtensions(): string {
|
||||
if (this.extensionFilter) {
|
||||
return this.extensionFilter
|
||||
.map(s => '.' + s)
|
||||
.join(',')
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
subscribeToModelChange() {
|
||||
//empty because there is no ngModel here
|
||||
}
|
||||
|
||||
unsubscribeToModelChange() {
|
||||
//empty because there is no ngModel here
|
||||
}
|
||||
|
||||
@Visible() getPresentationValue(): string {
|
||||
let fileNames: string = '';
|
||||
let fileItems: FileItem[] = this.uploader.queue;
|
||||
if (fileItems) {
|
||||
fileItems.forEach((fileItem) => {
|
||||
fileNames += fileItem.file.name;
|
||||
});
|
||||
}
|
||||
return fileNames;
|
||||
}
|
||||
|
||||
@Visible() getValue(): File[] {
|
||||
return this.uploader.queue.map(fileItem => fileItem._file);
|
||||
}
|
||||
|
||||
getValueAsModel(): any {
|
||||
throw new UnsupportedOperationError("Unsupported operation");
|
||||
}
|
||||
|
||||
setValue(value: any): any {
|
||||
throw new UnsupportedOperationError("Unsupported operation");
|
||||
}
|
||||
|
||||
@Visible()
|
||||
public reset() {
|
||||
//don't use super because there is no ngModel here
|
||||
this.uploader.clearQueue();
|
||||
this.fileInputEl.value = null;
|
||||
this.isDropZoneVisible = true;
|
||||
this.isFilesListVisible = true;
|
||||
this.isProgressBarVisible = false;
|
||||
this.isUploadErrorOccurred = false;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,8 @@ import {DropdownTreeViewComponent} from "../../component/field/DropdownTreeViewC
|
|||
import {DropdownTreeviewSelectComponent} from "../../component/external/ngx-treeview/dropdown-treeview-select/dropdown-treeview-select.component";
|
||||
import {TreeviewModule} from "ngx-treeview";
|
||||
import {DataDateComponent} from "./component/data-date.component";
|
||||
import {ErvuFileUpload} from "../../ervu-dashboard/component/field/ErvuFileUpload";
|
||||
import {FileUploadModule} from "ng2-file-upload";
|
||||
|
||||
registerLocaleData(localeRu);
|
||||
export const DIRECTIVES = [
|
||||
|
|
@ -37,7 +39,8 @@ export const DIRECTIVES = [
|
|||
forwardRef(() => FilterContainer),
|
||||
forwardRef(() => DropdownTreeViewComponent),
|
||||
forwardRef(() => DropdownTreeviewSelectComponent),
|
||||
forwardRef(() => DataDateComponent)
|
||||
forwardRef(() => DataDateComponent),
|
||||
forwardRef(() => ErvuFileUpload)
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
|
@ -52,7 +55,8 @@ export const DIRECTIVES = [
|
|||
AgGridModule,
|
||||
RouterModule,
|
||||
InternationalPhoneNumberModule,
|
||||
TreeviewModule.forRoot()
|
||||
TreeviewModule.forRoot(),
|
||||
FileUploadModule
|
||||
],
|
||||
declarations: [
|
||||
DIRECTIVES
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@
|
|||
'tslib': 'npm:tslib/tslib.js',
|
||||
'ngx-international-phone-number': 'npm:ngx-international-phone-number/ngx-international-phone-number.umd.js',
|
||||
'google-libphonenumber': 'npm:google-libphonenumber/dist/libphonenumber.js',
|
||||
'ngx-treeview': 'npm:ngx-treeview'
|
||||
'ngx-treeview': 'npm:ngx-treeview',
|
||||
'ng2-file-upload': 'npm:ng2-file-upload/bundles/ng2-file-upload.umd.js'
|
||||
},
|
||||
packages: {
|
||||
'webbpm': { main: 'main', defaultExtension: 'js'},
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@
|
|||
'tslib': 'npm:tslib/tslib.js',
|
||||
'ngx-international-phone-number': 'npm:ngx-international-phone-number/ngx-international-phone-number.umd.js',
|
||||
'google-libphonenumber': 'npm:google-libphonenumber/dist/libphonenumber.js',
|
||||
'ng2-dropdown-treeview': 'npm:ng2-dropdown-treeview'
|
||||
'ng2-dropdown-treeview': 'npm:ng2-dropdown-treeview',
|
||||
'ng2-file-upload': 'npm:ng2-file-upload/bundles/ng2-file-upload.umd.js'
|
||||
},
|
||||
packages: {
|
||||
'preview': { main: './modules/preview/preview.main', defaultExtension: 'js'},
|
||||
|
|
|
|||
2
pom.xml
2
pom.xml
|
|
@ -19,7 +19,7 @@
|
|||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<enable.version.in.url>false</enable.version.in.url>
|
||||
<joda-time.version>2.9.2</joda-time.version>
|
||||
<webbpm-platform.version>3.185.0</webbpm-platform.version>
|
||||
<webbpm-platform.version>3.187.0-SNAPSHOT</webbpm-platform.version>
|
||||
<wbp.overall-timeout>72000</wbp.overall-timeout>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
|
|
|
|||
20
resources/src/main/resources/ErvuFileUpload.component
Normal file
20
resources/src/main/resources/ErvuFileUpload.component
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<xmlComponent>
|
||||
<id>f4a465bc-4712-4965-9855-42502cfcf7ec</id>
|
||||
<name>ErvuFileUpload</name>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.185.1</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.185.0</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
<rootObject id="0e7967fd-2bce-4181-a3f5-f4537ea94598">
|
||||
<name>ErvuFileUpload</name>
|
||||
<container>false</container>
|
||||
<childrenReordered>false</childrenReordered>
|
||||
</rootObject>
|
||||
</xmlComponent>
|
||||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue