| @@ -1,6 +1,6 @@ | |||
| <template> | |||
| <div> | |||
| <v-data-table class="pa-3 pt-5" :headers="headers" :items="items" :height="fullHeight*7/9" | |||
| <v-data-table class="" :headers="headers" :items="items" | |||
| fixed-header | |||
| :footer-props="{'items-per-page-options': [30, 40, 50, 60]}" | |||
| :items-per-page="30"> | |||
| @@ -12,8 +12,63 @@ | |||
| inset | |||
| vertical | |||
| /> | |||
| <p v-if="mockSearch===true" class="mb-0">搜尋條件: 資產群組: H8 儲存設備</p> | |||
| <div class="row align-center"> | |||
| <p v-if="haveSearch" class="mb-0 pa-0">搜尋條件:</p> | |||
| <div v-for="(val, key, index) in searchItem" :key="index" class="pa-0 ma-0"> | |||
| <v-chip color="secondary" v-if="val !== null"> | |||
| {{mappingHeaders[key]}} 包含: {{val}} | |||
| <v-icon @click="clearSinglePropInSearchItem(key)">mdi-close</v-icon> | |||
| </v-chip> | |||
| </div> | |||
| </div> | |||
| <v-spacer/> | |||
| <!--匯入檔案對話--> | |||
| <v-dialog | |||
| v-model="dialogImport" | |||
| @click:outside="close" | |||
| :retain-focus="false" | |||
| max-width="500" | |||
| > | |||
| <template v-slot:activator="{ on, attrs }"> | |||
| <v-btn | |||
| color="white" | |||
| class="primary mr-5" | |||
| v-bind="attrs" | |||
| v-on="on" | |||
| icon | |||
| > | |||
| <v-icon>mdi-file-import-outline</v-icon> | |||
| </v-btn> | |||
| </template> | |||
| <v-card> | |||
| <v-card-title> | |||
| <span class="headline font-weight-bold">匯入</span> | |||
| </v-card-title> | |||
| <v-card-text> | |||
| <v-container> | |||
| <input id="file" type="file" @change="onFileChange" /> | |||
| </v-container> | |||
| </v-card-text> | |||
| <v-card-actions> | |||
| <v-spacer></v-spacer> | |||
| <v-btn | |||
| color="blue darken-1" | |||
| text | |||
| @click="close" | |||
| > | |||
| 關閉 | |||
| </v-btn> | |||
| <v-btn | |||
| color="blue darken-1" | |||
| text | |||
| @click="importFile" | |||
| > | |||
| 匯入 | |||
| </v-btn> | |||
| </v-card-actions> | |||
| </v-card> | |||
| </v-dialog> | |||
| <v-btn | |||
| color="white" | |||
| class="primary mr-5" | |||
| @@ -49,32 +104,30 @@ | |||
| <v-row> | |||
| <v-col | |||
| v-for="(val, key, index) in searchItem" | |||
| v-for="(header, index) in headers" | |||
| :key="index" | |||
| cols="12" | |||
| sm="6" | |||
| md="4" | |||
| > | |||
| <div v-if="headers[index].text !== '編號'"> | |||
| <div v-if="headers[index] && headers[index].text !== '編號'"> | |||
| <v-text-field | |||
| dense | |||
| v-if="isTextField(key) && headers[index].text" | |||
| :label="headers[index].text" | |||
| :disabled="isDisabled(key)" | |||
| v-bind:value="val" | |||
| :persistent-hint="isRequire(key)" | |||
| v-if="isTextField(header.value) && headers[index].text" | |||
| :label="header.text" | |||
| :disabled="isDisabled(header.value)" | |||
| v-model="searchItem[header.value]" | |||
| :persistent-hint="isRequire(header.value)" | |||
| hint="必填" | |||
| v-on:input="oninput(searchItem, key, $event)" | |||
| /> | |||
| <v-select | |||
| dense | |||
| v-if="isSelect(key) && headers[index].text" | |||
| :persistent-hint="isRequire(key)" | |||
| v-if="isSelect(header.value) && headers[index].text" | |||
| :persistent-hint="isRequire(header.value)" | |||
| hint="必選" | |||
| :label="headers[index].text" | |||
| :value="val" | |||
| v-on:input="oninput(searchItem, key, $event)" | |||
| :items="selectItem[key]" | |||
| :label="header.text" | |||
| v-model="searchItem[header.value]" | |||
| :items="selectItem[header.value]" | |||
| item-text="item" | |||
| item-value="item" | |||
| /> | |||
| @@ -88,6 +141,13 @@ | |||
| <v-btn | |||
| color="blue darken-1" | |||
| text | |||
| @click="clearSearchItem" | |||
| > | |||
| 清空 | |||
| </v-btn> | |||
| <v-btn | |||
| color="blue darken-1" | |||
| text | |||
| @click="close" | |||
| > | |||
| 關閉 | |||
| @@ -95,7 +155,7 @@ | |||
| <v-btn | |||
| color="blue darken-1" | |||
| text | |||
| @click="mockSearch=true" | |||
| @click="search" | |||
| > | |||
| 搜尋 | |||
| </v-btn> | |||
| @@ -128,32 +188,32 @@ | |||
| <v-row> | |||
| <v-col | |||
| v-for="(val, key, index) in insertItem" | |||
| v-for="(header, index) in headers" | |||
| :key="index" | |||
| cols="12" | |||
| sm="6" | |||
| md="4" | |||
| > | |||
| <div v-if="headers[index].text !== '編號'"> | |||
| <div v-if="headers[index] && headers[index].text !== '編號'"> | |||
| <v-text-field | |||
| dense | |||
| v-if="isTextField(key) && headers[index].text" | |||
| :label="headers[index].text" | |||
| :disabled="isDisabled(key)" | |||
| v-bind:value="val" | |||
| :persistent-hint="isRequire(key)" | |||
| v-if="isTextField(header.value) && headers[index].text" | |||
| :label="header.text" | |||
| :disabled="isDisabled(header.value)" | |||
| v-bind:value="insertItem[header.value]" | |||
| :persistent-hint="isRequire(header.value)" | |||
| hint="必填" | |||
| v-on:input="oninput(insertItem, key, $event)" | |||
| v-on:input="oninput(insertItem, header.value, $event)" | |||
| /> | |||
| <v-select | |||
| dense | |||
| v-if="isSelect(key) && headers[index].text" | |||
| :persistent-hint="isRequire(key)" | |||
| v-if="isSelect(header.value) && headers[index].text" | |||
| :persistent-hint="isRequire(header.value)" | |||
| hint="必選" | |||
| :label="headers[index].text" | |||
| :value="val" | |||
| v-on:input="oninput(insertItem, key, $event)" | |||
| :items="selectItem[key]" | |||
| :label="header.text" | |||
| :value="insertItem[header.value]" | |||
| v-on:input="oninput(insertItem, header.value, $event)" | |||
| :items="selectItem[header.value]" | |||
| item-text="item" | |||
| item-value="item" | |||
| /> | |||
| @@ -213,33 +273,36 @@ | |||
| <v-row> | |||
| <v-col | |||
| v-for="(val, key, index) in modifyItem" | |||
| v-for="(header, index) in headers" | |||
| :key="index" | |||
| cols="12" | |||
| sm="6" | |||
| md="4" | |||
| class="d-flex" | |||
| > | |||
| <v-text-field | |||
| v-if="isTextField(key) && headers[index].text" | |||
| :label="headers[index].text" | |||
| :disabled="isDisabled(key)" | |||
| v-bind:value="val" | |||
| :persistent-hint="isRequire(key)" | |||
| hint="必填" | |||
| v-on:input="oninput(modifyItem, key, $event)" | |||
| /> | |||
| <v-select | |||
| v-if="isSelect(key) && headers[index].text" | |||
| :persistent-hint="isRequire(key)" | |||
| hint="必選" | |||
| :label="headers[index].text" | |||
| :value="val" | |||
| v-on:input="oninput(modifyItem, key, $event)" | |||
| :items="selectItem[key]" | |||
| item-text="item" | |||
| item-value="item" | |||
| /> | |||
| <div v-if="headers[index] && headers[index].text !== '編號'"> | |||
| <v-text-field | |||
| dense | |||
| v-if="isTextField(header.value) && headers[index].text" | |||
| :label="header.text" | |||
| :disabled="isDisabled(header.value)" | |||
| v-bind:value="modifyItem[header.value]" | |||
| :persistent-hint="isRequire(header.value)" | |||
| hint="必填" | |||
| v-on:input="oninput(modifyItem, header.value, $event)" | |||
| /> | |||
| <v-select | |||
| dense | |||
| v-if="isSelect(header.value) && headers[index].text" | |||
| :persistent-hint="isRequire(header.value)" | |||
| hint="必選" | |||
| :label="header.text" | |||
| :value="modifyItem[header.value]" | |||
| v-on:input="oninput(modifyItem, header.value, $event)" | |||
| :items="selectItem[header.value]" | |||
| item-text="item" | |||
| item-value="item" | |||
| /> | |||
| </div> | |||
| </v-col> | |||
| </v-row> | |||
| </v-container> | |||
| @@ -266,6 +329,9 @@ | |||
| </div> | |||
| </template> | |||
| <script> | |||
| let cancel; | |||
| let CancelToken; | |||
| let cancel2; | |||
| export default { | |||
| name: '', | |||
| data() { | |||
| @@ -276,6 +342,7 @@ | |||
| dialogSearch: false, | |||
| title: '', | |||
| headers: [], | |||
| mappingHeaders: {}, | |||
| cols: {}, | |||
| items: [], | |||
| isselect: [], | |||
| @@ -285,17 +352,19 @@ | |||
| selectItem: {}, | |||
| searchItem: {}, | |||
| mockSearch: false, | |||
| haveSearch:false, | |||
| dialogImport: false, | |||
| file: null, | |||
| } | |||
| }, | |||
| async mounted() { | |||
| CancelToken = this.$axios.CancelToken; | |||
| this.getHeaders(); | |||
| this.getInventories(); | |||
| this.getTitle(); | |||
| this.getSelectItem(); | |||
| this.fullHeight = window.innerHeight; | |||
| console.log(this.fullHeight); | |||
| window.onresize = () => { | |||
| // this.fullWidth = window.innerWidth; | |||
| this.fullHeight = window.innerHeight; | |||
| }; | |||
| }, | |||
| @@ -305,7 +374,7 @@ | |||
| }, | |||
| }, | |||
| watch: { | |||
| async tablename() { | |||
| tablename() { | |||
| if (this.$route.params.tablename) { | |||
| this.headers = []; | |||
| this.items = []; | |||
| @@ -337,12 +406,20 @@ | |||
| }, | |||
| getHeaders() { | |||
| this.headers.push({'text': '', 'value': 'actions', sortable: false}); | |||
| this.$axios.get(`/headers?tablename=${this.tablename}`).then((resp) => { | |||
| if (cancel) { | |||
| cancel(); | |||
| } | |||
| this.$axios.get(`/headers?tablename=${this.tablename}`,{ | |||
| cancelToken: new CancelToken(((c) => { | |||
| cancel = c; | |||
| })), | |||
| }).then((resp) => { | |||
| this.cols = resp.data.data; | |||
| resp.data.data.forEach((item) => { | |||
| let header = {'text': null, 'value': null}; | |||
| header.text = item.descript; | |||
| header.value = item.colname; | |||
| this.mappingHeaders[item.colname] = item.descript; | |||
| this.headers.push(header); | |||
| if (item.isselect === 'true') { | |||
| this.isselect.push(item.colname); | |||
| @@ -356,7 +433,14 @@ | |||
| }); | |||
| }, | |||
| getInventories() { | |||
| this.$axios.get(`/inventory?tablename=${this.tablename}`).then((resp) => { | |||
| if (cancel2) { | |||
| cancel2(); | |||
| } | |||
| this.$axios.get(`/inventory?tablename=${this.tablename}`,{ | |||
| cancelToken: new CancelToken(((c) => { | |||
| cancel = c; | |||
| })), | |||
| }).then((resp) => { | |||
| this.items = resp.data.data; | |||
| }); | |||
| }, | |||
| @@ -373,6 +457,7 @@ | |||
| this.dialogInsert = false; | |||
| this.dialogModify = false; | |||
| this.dialogSearch = false; | |||
| this.dialogImport = false; | |||
| }, | |||
| isDisabled(key) { | |||
| const disabledKey = ['id']; | |||
| @@ -408,9 +493,56 @@ | |||
| alert('已刪除'); | |||
| }); | |||
| } | |||
| console.log(item); | |||
| }, | |||
| search() { | |||
| this.searchItem.tablename = this.tablename; | |||
| this.$axios.post(`/search`, this.searchItem).then((resp) => { | |||
| delete this.searchItem.tablename; | |||
| this.items = resp.data.data; | |||
| this.haveSearch = true; | |||
| this.dialogSearch = false; | |||
| }); | |||
| }, | |||
| clearSinglePropInSearchItem(key) { | |||
| console.log(key); | |||
| this.searchItem[key] = null; | |||
| console.log(this.searchItem); | |||
| this.haveSearch = false; | |||
| Object.keys(this.searchItem).forEach(function(key){ | |||
| if (self.searchItem[key] !== null) { | |||
| this.haveSearch = true; | |||
| } | |||
| }); | |||
| }, | |||
| async clearSearchItem() { | |||
| const self = this; | |||
| Object.keys(this.searchItem).forEach(function(key){ | |||
| self.searchItem[key] = null; | |||
| }); | |||
| // this.searchItem = {}; | |||
| // await this.cols.forEach((item) => { | |||
| // this.searchItem[item.colname] = null; | |||
| // | |||
| // }); | |||
| this.haveSearch = false; | |||
| }, | |||
| onFileChange(e) { | |||
| const files = e.target.files || e.dataTransfer.files; | |||
| if (files.length > 0) { | |||
| console.log(files[0].name); | |||
| // eslint-disable-next-line prefer-destructuring | |||
| this.file = files[0]; | |||
| } | |||
| }, | |||
| importFile() { | |||
| const formData = new FormData(); | |||
| formData.append('file', this.file); | |||
| this.$axios.post(`/importFile`, formData).then(() => { | |||
| document.getElementById('file').value = null; | |||
| this.getInventories(); | |||
| this.file = null; | |||
| this.dialogImport = false; | |||
| }); | |||
| }, | |||
| exportFile() { | |||
| @@ -103,6 +103,30 @@ | |||
| <version>9.2.1.jre8</version> | |||
| </dependency> | |||
| <!-- https://mvnrepository.com/artifact/org.apache.poi/poi --> | |||
| <!-- <dependency>--> | |||
| <!-- <groupId>org.apache.poi</groupId>--> | |||
| <!-- <artifactId>poi</artifactId>--> | |||
| <!-- <version>5.0.0</version>--> | |||
| <!-- </dependency>--> | |||
| <!-- <dependency>--> | |||
| <!-- <groupId>org.apache.poi</groupId>--> | |||
| <!-- <artifactId>poi-ooxml</artifactId>--> | |||
| <!-- <version>3.9</version>--> | |||
| <!-- </dependency>--> | |||
| <dependency> | |||
| <groupId>org.apache.poi</groupId> | |||
| <artifactId>poi-ooxml</artifactId> | |||
| <version>4.1.0</version> | |||
| </dependency> | |||
| <!-- https://mvnrepository.com/artifact/org.apache.poi/poi --> | |||
| <dependency> | |||
| <groupId>org.apache.poi</groupId> | |||
| <artifactId>poi</artifactId> | |||
| <version>4.1.0</version> | |||
| </dependency> | |||
| </dependencies> | |||
| @@ -5,9 +5,13 @@ import com.moze.rms.dao.InventoryDAO; | |||
| import com.moze.rms.dao.MappingColDAO; | |||
| import com.moze.rms.dao.MappingTableDAO; | |||
| import com.moze.rms.entity.dto.SelectItemDTO; | |||
| import com.moze.rms.utils.ExcelImporter; | |||
| import org.apache.poi.openxml4j.exceptions.InvalidFormatException; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.web.bind.annotation.*; | |||
| import org.springframework.web.multipart.MultipartFile; | |||
| import java.io.*; | |||
| import java.util.*; | |||
| import java.util.function.Function; | |||
| import java.util.stream.Collectors; | |||
| @@ -81,5 +85,30 @@ public class InventoryController { | |||
| } | |||
| @PostMapping("/search") | |||
| public JsonResult searchInventory(@RequestBody Map<String, Object> data) { | |||
| System.out.println(data); | |||
| return new JsonResult(StatusCode.SUCCESS, inventoryDAO.search(data)); | |||
| } | |||
| @PostMapping("/importFile") | |||
| public JsonResult importFile(@RequestParam(required = false) MultipartFile file) throws IOException, InvalidFormatException { | |||
| File f = File.createTempFile(file.getOriginalFilename().split("\\.")[0], file.getOriginalFilename().split("\\.")[1]); | |||
| System.out.println(f.getName()); | |||
| file.transferTo(f); | |||
| ExcelImporter excelImporter2 = new ExcelImporter(f); | |||
| System.out.println(excelImporter2.readAll()); | |||
| f.deleteOnExit(); | |||
| return new JsonResult(StatusCode.SUCCESS, null); | |||
| } | |||
| } | |||
| @@ -44,7 +44,7 @@ public interface AssertDAO extends SqlObject { | |||
| Handle handle = this.getHandle(); | |||
| String sql = "create table " + data.get("assertGroupTablename") + | |||
| "(" + | |||
| " id int not null" + | |||
| " id int not null IDENTITY(1,1)" + | |||
| " constraint " + data.get("assertGroupTablename") + "_pk" + | |||
| " primary key nonclustered," + | |||
| " type nvarchar(256)," + | |||
| @@ -155,7 +155,7 @@ public interface AssertDAO extends SqlObject { | |||
| //新增欄位sql | |||
| default void addColToTable(MappingCol m) { | |||
| Handle handle = this.getHandle(); | |||
| String sql = "ALTER TABLE " + m.getTablename() + " ADD " + m.getColname() + " " + "varchar"; | |||
| String sql = "ALTER TABLE " + m.getTablename() + " ADD " + m.getColname() + " " + "nvarchar(256)"; | |||
| handle.createUpdate(sql).execute(); | |||
| } | |||
| @@ -72,4 +72,24 @@ public interface InventoryDAO extends SqlObject { | |||
| handle.execute(sql); | |||
| } | |||
| @RegisterBeanMapper(Object.class) | |||
| default List<Map<String, Object>> search(Map<String, Object> data) { | |||
| Handle handle = this.getHandle(); | |||
| String sql = "select * from " + data.get("tablename") + " where"; | |||
| data.remove("tablename"); | |||
| Iterator<Map.Entry<String, Object>> iterator = data.entrySet().iterator(); | |||
| while (iterator.hasNext()) { | |||
| Map.Entry<String, Object> next = iterator.next(); | |||
| String key = next.getKey(); | |||
| Object value = next.getValue(); | |||
| System.out.println(key); | |||
| if (key != "tablename" && key != "id" && value != null) { | |||
| sql += " " + key + " like '%" + value + "%' and"; | |||
| } | |||
| } | |||
| sql = sql.substring(0, sql.length() - 3); | |||
| System.out.println(sql); | |||
| return handle.createQuery(sql).mapToMap().list(); | |||
| } | |||
| } | |||
| @@ -11,6 +11,6 @@ import java.util.List; | |||
| public interface MappingColDAO { | |||
| @SqlQuery("select * from mapping.mapping_col where tablename = ? order by [index];") | |||
| @SqlQuery("select * from mapping.mapping_col where tablename = ? order by convert(int, [index]);") | |||
| List<MappingCol> findByTable(String tablename); | |||
| } | |||
| @@ -0,0 +1,176 @@ | |||
| package com.moze.rms.utils; | |||
| import java.io.FileNotFoundException; | |||
| import java.io.FileOutputStream; | |||
| import java.io.IOException; | |||
| import java.util.HashSet; | |||
| import java.util.LinkedHashMap; | |||
| import java.util.LinkedList; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import java.util.Set; | |||
| import org.apache.poi.ss.usermodel.Cell; | |||
| import org.apache.poi.ss.usermodel.CellStyle; | |||
| import org.apache.poi.ss.usermodel.Font; | |||
| import org.apache.poi.ss.usermodel.IndexedColors; | |||
| import org.apache.poi.ss.usermodel.Row; | |||
| import org.apache.poi.ss.usermodel.VerticalAlignment; | |||
| import org.apache.poi.xssf.usermodel.XSSFSheet; | |||
| import org.apache.poi.xssf.usermodel.XSSFWorkbook; | |||
| public class ExcelExpoter { | |||
| private String outputPath; | |||
| private String nullString; | |||
| private Set<String> columnAutoSized = new HashSet<>(); | |||
| List<SheetInfo> sheets = new LinkedList<>(); | |||
| public ExcelExpoter(String outputPath) { | |||
| this.outputPath = outputPath; | |||
| this.nullString = "<NULL>"; | |||
| } | |||
| public ExcelExpoter(String outputPath, String nullString) { | |||
| this.outputPath = outputPath; | |||
| this.nullString = nullString; | |||
| } | |||
| // public void export() throws FileNotFoundException, IOException { | |||
| // try (FileOutputStream outputStream = new FileOutputStream(outputPath); | |||
| // XSSFWorkbook workbook = new XSSFWorkbook()) { | |||
| // for(SheetInfo sheet : sheets) { | |||
| // exportSheet(workbook, sheet.getSheetName(), sheet.getHeaderMap(), sheet.getData()); | |||
| // } | |||
| // System.out.println("Start flush out workbook!"); | |||
| // workbook.write(outputStream); | |||
| // System.out.println("Created excel: " + outputPath); | |||
| // } | |||
| // } | |||
| public void addSheet(String sheetName, Map<String, String> headerMap, List<?> data) { | |||
| sheets.add(new SheetInfo(sheetName, headerMap, data)); | |||
| } | |||
| // public void export(String sheetName, Map<String, String> headerMap, List<?> data) throws FileNotFoundException, IOException { | |||
| // try (FileOutputStream outputStream = new FileOutputStream(outputPath); | |||
| // XSSFWorkbook workbook = new XSSFWorkbook()) { | |||
| // exportSheet(workbook, sheetName, headerMap, data); | |||
| // System.out.println("Start flush out workbook!"); | |||
| // workbook.write(outputStream); | |||
| // System.out.println("Created excel: " + outputPath); | |||
| // } | |||
| // } | |||
| private void exportSheet(XSSFWorkbook workbook, String sheetName, Map<String, String> headerMap, List<?> data) { | |||
| XSSFSheet sheet = workbook.createSheet(sheetName); | |||
| int rowCount = 0; | |||
| // If Header not set | |||
| if (headerMap == null) { | |||
| headerMap = new LinkedHashMap<>(); | |||
| if (!data.isEmpty()) { | |||
| for(String k: JsonUtil.pojo2Map(data.get(0)).keySet()) { | |||
| headerMap.put(k, k); | |||
| } | |||
| } | |||
| } | |||
| Font headerFont = workbook.createFont(); | |||
| headerFont.setBold(true); | |||
| headerFont.setFontHeightInPoints((short) 12); | |||
| headerFont.setColor(IndexedColors.BLACK.getIndex()); | |||
| CellStyle headerCellStyle = workbook.createCellStyle(); | |||
| headerCellStyle.setFont(headerFont); | |||
| // Create Header Row | |||
| Row row = sheet.createRow(rowCount++); | |||
| int columnCount = 0; | |||
| for(String v: headerMap.values()) { | |||
| Cell cell = row.createCell(columnCount++); | |||
| cell.setCellValue(v); | |||
| cell.setCellStyle(headerCellStyle); | |||
| } | |||
| // Create Other rows and cells | |||
| CellStyle style = workbook.createCellStyle(); | |||
| style.setWrapText(true); | |||
| style.setVerticalAlignment(VerticalAlignment.TOP); | |||
| for (Object item : data) { | |||
| @SuppressWarnings("unchecked") | |||
| Map<String, Object> obj = item instanceof Map ? (Map<String, Object>)item: JsonUtil.pojo2Map(item); | |||
| row = sheet.createRow(rowCount++); | |||
| columnCount = 0; | |||
| for(String k: headerMap.keySet()) { | |||
| Object v = obj.get(k); | |||
| Cell cell = row.createCell(columnCount++); | |||
| String value = v == null ? nullString : v.toString(); | |||
| cell.setCellValue(value); | |||
| cell.setCellStyle(style); | |||
| } | |||
| if (rowCount % 10000 == 0) { | |||
| System.out.println("Created row " + rowCount); | |||
| } | |||
| if (rowCount == 350) | |||
| autoSizeColumn(sheet); | |||
| } | |||
| System.out.println("[" + sheetName + "] Created all row " + rowCount); | |||
| autoSizeColumn(sheet); | |||
| } | |||
| private void autoSizeColumn(XSSFSheet sheet) { | |||
| if (columnAutoSized.contains(sheet.getSheetName())) | |||
| return; | |||
| System.out.println("Start auto size columns"); | |||
| int maxWidth = 256 * 120; | |||
| int minWidth = 256 * 16; | |||
| int rowCount = sheet.getRow(0).getLastCellNum(); | |||
| System.out.println(rowCount); | |||
| for (int j = 0; j < rowCount; j++) { | |||
| sheet.autoSizeColumn(j); | |||
| if (sheet.getColumnWidth(j) > maxWidth) { | |||
| sheet.setColumnWidth(j, maxWidth); | |||
| } | |||
| if (sheet.getColumnWidth(j) < minWidth) { | |||
| sheet.setColumnWidth(j, minWidth); | |||
| } | |||
| } | |||
| columnAutoSized.add(sheet.getSheetName()); | |||
| System.out.println("End auto size columns"); | |||
| } | |||
| private static class SheetInfo { | |||
| private String sheetName; | |||
| private Map<String, String> headerMap; | |||
| private List<?> data; | |||
| public SheetInfo(String sheetName, Map<String, String> headerMap, List<?> data) { | |||
| super(); | |||
| this.sheetName = sheetName; | |||
| this.headerMap = headerMap; | |||
| this.data = data; | |||
| } | |||
| public String getSheetName() { | |||
| return sheetName; | |||
| } | |||
| public void setSheetName(String sheetName) { | |||
| this.sheetName = sheetName; | |||
| } | |||
| public Map<String, String> getHeaderMap() { | |||
| return headerMap; | |||
| } | |||
| public void setHeaderMap(Map<String, String> headerMap) { | |||
| this.headerMap = headerMap; | |||
| } | |||
| public List<?> getData() { | |||
| return data; | |||
| } | |||
| public void setData(List<?> data) { | |||
| this.data = data; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,194 @@ | |||
| package com.moze.rms.utils; | |||
| import java.io.File; | |||
| import java.io.FileInputStream; | |||
| import java.io.IOException; | |||
| import java.text.SimpleDateFormat; | |||
| import java.util.ArrayList; | |||
| import java.util.Iterator; | |||
| import java.util.LinkedHashMap; | |||
| import java.util.LinkedList; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import java.util.stream.Collectors; | |||
| import org.apache.poi.openxml4j.exceptions.InvalidFormatException; | |||
| import org.apache.poi.ss.usermodel.Cell; | |||
| import org.apache.poi.ss.usermodel.CellType; | |||
| import org.apache.poi.ss.usermodel.DataFormatter; | |||
| import org.apache.poi.ss.usermodel.DateUtil; | |||
| import org.apache.poi.ss.usermodel.Row; | |||
| import org.apache.poi.ss.usermodel.Sheet; | |||
| import org.apache.poi.ss.usermodel.Workbook; | |||
| import org.apache.poi.ss.usermodel.WorkbookFactory; | |||
| public class ExcelImporter { | |||
| private File file; | |||
| SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); | |||
| public ExcelImporter(File file) { | |||
| this.file = file; | |||
| } | |||
| public ExcelImporter(String filePath) { | |||
| this(new File(filePath)); | |||
| } | |||
| public Map<String, List<Map<String, String>>> readAll() throws IOException, InvalidFormatException { | |||
| File excelFile = file; | |||
| Map<String, List<Map<String, String>>> ret = new LinkedHashMap<>(); | |||
| try (FileInputStream fis = new FileInputStream(excelFile); | |||
| Workbook workbook = WorkbookFactory.create(fis)) { | |||
| for (int sn=0; sn < workbook.getNumberOfSheets(); sn++) { | |||
| if (workbook.isSheetHidden(sn) || workbook.isSheetVeryHidden(sn)) { | |||
| continue; | |||
| } | |||
| Sheet sheet = workbook.getSheetAt(sn); | |||
| List<Map<String, String>> data = readSheet(sheet, 0); | |||
| ret.put(sheet.getSheetName(), data); | |||
| } | |||
| } | |||
| return ret; | |||
| } | |||
| /** | |||
| * read a sheet | |||
| * @param sheetName | |||
| * @param headerRow - header row number, 0 based | |||
| * @return | |||
| * @throws IOException | |||
| */ | |||
| public List<Map<String, String>> readSheet(String sheetName, int headerRow) throws IOException { | |||
| File excelFile = file; | |||
| try (FileInputStream fis = new FileInputStream(excelFile); | |||
| Workbook workbook = WorkbookFactory.create(fis)) { | |||
| Sheet sheet = workbook.getSheet(sheetName); | |||
| if (sheet == null) { | |||
| throw new IOException("Sheet " + sheetName + " not exist!"); | |||
| } | |||
| return readSheet(sheet, headerRow); | |||
| } | |||
| } | |||
| public List<Map<String, String>> readSheet(Sheet sheet, int headerRow) { | |||
| List<Map<String, String>> ret = new LinkedList<>(); | |||
| // we iterate on rows | |||
| Iterator<Row> rowIt = sheet.iterator(); | |||
| List<String> headerValues = null; | |||
| int headerLength = 0; | |||
| while (rowIt.hasNext()) { | |||
| Row row = rowIt.next(); | |||
| if (row.getRowNum() < headerRow) | |||
| continue; | |||
| List<String> rowValues = readRow(row); | |||
| headerValues = rowValues.stream().map(this::getFirstLine).collect(Collectors.toList()); | |||
| headerLength = headerValues.size(); | |||
| break; | |||
| } | |||
| while (rowIt.hasNext()) { | |||
| Row row = rowIt.next(); | |||
| List<String> rowValues = readRow(row, headerValues, headerLength); | |||
| if (row.getRowNum() == headerRow) { | |||
| headerValues = rowValues.stream().map(this::getFirstLine).collect(Collectors.toList()); | |||
| continue; | |||
| } | |||
| // if (row.getZeroHeight() || row.getFirstCellNum() == -1) | |||
| // continue; | |||
| // break at empty line; | |||
| Integer strlen = rowValues.stream().map(String::length).reduce(0, Integer::sum); | |||
| if (strlen == 0) | |||
| break; | |||
| // if (rowValues.isEmpty() || rowValues.get(0).isEmpty()) | |||
| // break; | |||
| Map<String, String> pairs = getRowPairs(headerValues, rowValues); | |||
| ret.add(pairs); | |||
| } | |||
| return ret; | |||
| } | |||
| public List<String> readRow(Row row) { | |||
| List<String> ret = new ArrayList<>(); | |||
| Iterator<Cell> cellIterator = row.cellIterator(); | |||
| DataFormatter dataFormatter = new DataFormatter(); | |||
| while (cellIterator.hasNext()) { | |||
| Cell cell = cellIterator.next(); | |||
| // if (cell.getSheet().isColumnHidden(cell.getAddress().getColumn())) | |||
| // continue; | |||
| String value = null; | |||
| if (cell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(cell)) | |||
| value = df.format(cell.getDateCellValue()); | |||
| else | |||
| value = dataFormatter.formatCellValue(cell); | |||
| ret.add(value.trim()); | |||
| } | |||
| return ret; | |||
| } | |||
| /* 沒有加最大行數參數的版本,會有略掉空白 cell 的問題 | |||
| * | |||
| */ | |||
| public List<String> readRow(Row row, List<String> headers, int length) { | |||
| List<String> ret = new ArrayList<>(); | |||
| DataFormatter dataFormatter = new DataFormatter(); | |||
| for (int i = 0; i < length; i++) { | |||
| Cell cell = row.getCell(i); | |||
| if (cell == null) { | |||
| ret.add(""); | |||
| continue; | |||
| } | |||
| String value = null; | |||
| if (cell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(cell)) | |||
| value = df.format(cell.getDateCellValue()); | |||
| else | |||
| value = dataFormatter.formatCellValue(cell); | |||
| ret.add(value.trim()); | |||
| } | |||
| return ret; | |||
| } | |||
| private String getFirstLine(String str) { | |||
| if (str == null) | |||
| return null; | |||
| return str.split("\n")[0].trim(); | |||
| } | |||
| public Map<String, String> getRowPairs(List<String> headerValues, List<String> rowValues) { | |||
| Map<String, String> ret = new LinkedHashMap<>(); | |||
| for (int i = 0; i < headerValues.size(); i++) { | |||
| String key = headerValues.get(i); | |||
| String value = i >= rowValues.size() ? "" : rowValues.get(i); | |||
| ret.put(key, value); | |||
| } | |||
| return ret; | |||
| } | |||
| public static boolean isValidSheet(List<Map<String, String>> sheetData, List<String> headerList) { | |||
| if (sheetData.isEmpty()) | |||
| return false; | |||
| Map<String, String> row1 = sheetData.get(0); | |||
| int[] count = new int[]{0}; | |||
| row1.forEach((key, v) -> { | |||
| if (headerList.contains(key)) | |||
| count[0]++; | |||
| }); | |||
| return count[0] == headerList.size(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| package com.moze.rms.utils; | |||
| import java.util.LinkedHashMap; | |||
| import java.util.Map; | |||
| import com.fasterxml.jackson.annotation.JsonInclude; | |||
| import com.fasterxml.jackson.databind.DeserializationFeature; | |||
| import com.fasterxml.jackson.databind.ObjectMapper; | |||
| public interface JsonUtil { | |||
| static ObjectMapper mapper = new ObjectMapper() | |||
| .setSerializationInclusion(JsonInclude.Include.NON_NULL) | |||
| .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); | |||
| public static <T> T map2Pojo(Map<String, Object> map, Class<T> clazz) { | |||
| T ret = mapper.convertValue(map, clazz); | |||
| return ret; | |||
| } | |||
| public static Map<String, Object> pojo2Map(Object obj) { | |||
| @SuppressWarnings("unchecked") | |||
| Map<String, Object> ret = mapper.convertValue(obj, LinkedHashMap.class); | |||
| return ret; | |||
| } | |||
| } | |||
| @@ -1,2 +1,6 @@ | |||
| spring.profiles.active=dev | |||
| #spring.profiles.active=pro | |||
| spring.servlet.multipart.max-file-size=100MB | |||
| spring.servlet.multipart.max-request-size=1000MB | |||