| @@ -1,6 +1,6 @@ | |||||
| <template> | <template> | ||||
| <div> | <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 | fixed-header | ||||
| :footer-props="{'items-per-page-options': [30, 40, 50, 60]}" | :footer-props="{'items-per-page-options': [30, 40, 50, 60]}" | ||||
| :items-per-page="30"> | :items-per-page="30"> | ||||
| @@ -12,8 +12,63 @@ | |||||
| inset | inset | ||||
| vertical | 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-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 | <v-btn | ||||
| color="white" | color="white" | ||||
| class="primary mr-5" | class="primary mr-5" | ||||
| @@ -49,32 +104,30 @@ | |||||
| <v-row> | <v-row> | ||||
| <v-col | <v-col | ||||
| v-for="(val, key, index) in searchItem" | |||||
| v-for="(header, index) in headers" | |||||
| :key="index" | :key="index" | ||||
| cols="12" | cols="12" | ||||
| sm="6" | sm="6" | ||||
| md="4" | md="4" | ||||
| > | > | ||||
| <div v-if="headers[index].text !== '編號'"> | |||||
| <div v-if="headers[index] && headers[index].text !== '編號'"> | |||||
| <v-text-field | <v-text-field | ||||
| dense | 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="必填" | hint="必填" | ||||
| v-on:input="oninput(searchItem, key, $event)" | |||||
| /> | /> | ||||
| <v-select | <v-select | ||||
| dense | 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="必選" | 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-text="item" | ||||
| item-value="item" | item-value="item" | ||||
| /> | /> | ||||
| @@ -88,6 +141,13 @@ | |||||
| <v-btn | <v-btn | ||||
| color="blue darken-1" | color="blue darken-1" | ||||
| text | text | ||||
| @click="clearSearchItem" | |||||
| > | |||||
| 清空 | |||||
| </v-btn> | |||||
| <v-btn | |||||
| color="blue darken-1" | |||||
| text | |||||
| @click="close" | @click="close" | ||||
| > | > | ||||
| 關閉 | 關閉 | ||||
| @@ -95,7 +155,7 @@ | |||||
| <v-btn | <v-btn | ||||
| color="blue darken-1" | color="blue darken-1" | ||||
| text | text | ||||
| @click="mockSearch=true" | |||||
| @click="search" | |||||
| > | > | ||||
| 搜尋 | 搜尋 | ||||
| </v-btn> | </v-btn> | ||||
| @@ -128,32 +188,32 @@ | |||||
| <v-row> | <v-row> | ||||
| <v-col | <v-col | ||||
| v-for="(val, key, index) in insertItem" | |||||
| v-for="(header, index) in headers" | |||||
| :key="index" | :key="index" | ||||
| cols="12" | cols="12" | ||||
| sm="6" | sm="6" | ||||
| md="4" | md="4" | ||||
| > | > | ||||
| <div v-if="headers[index].text !== '編號'"> | |||||
| <div v-if="headers[index] && headers[index].text !== '編號'"> | |||||
| <v-text-field | <v-text-field | ||||
| dense | 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="必填" | hint="必填" | ||||
| v-on:input="oninput(insertItem, key, $event)" | |||||
| v-on:input="oninput(insertItem, header.value, $event)" | |||||
| /> | /> | ||||
| <v-select | <v-select | ||||
| dense | 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="必選" | 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-text="item" | ||||
| item-value="item" | item-value="item" | ||||
| /> | /> | ||||
| @@ -213,33 +273,36 @@ | |||||
| <v-row> | <v-row> | ||||
| <v-col | <v-col | ||||
| v-for="(val, key, index) in modifyItem" | |||||
| v-for="(header, index) in headers" | |||||
| :key="index" | :key="index" | ||||
| cols="12" | cols="12" | ||||
| sm="6" | sm="6" | ||||
| md="4" | 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-col> | ||||
| </v-row> | </v-row> | ||||
| </v-container> | </v-container> | ||||
| @@ -266,6 +329,9 @@ | |||||
| </div> | </div> | ||||
| </template> | </template> | ||||
| <script> | <script> | ||||
| let cancel; | |||||
| let CancelToken; | |||||
| let cancel2; | |||||
| export default { | export default { | ||||
| name: '', | name: '', | ||||
| data() { | data() { | ||||
| @@ -276,6 +342,7 @@ | |||||
| dialogSearch: false, | dialogSearch: false, | ||||
| title: '', | title: '', | ||||
| headers: [], | headers: [], | ||||
| mappingHeaders: {}, | |||||
| cols: {}, | cols: {}, | ||||
| items: [], | items: [], | ||||
| isselect: [], | isselect: [], | ||||
| @@ -285,17 +352,19 @@ | |||||
| selectItem: {}, | selectItem: {}, | ||||
| searchItem: {}, | searchItem: {}, | ||||
| mockSearch: false, | mockSearch: false, | ||||
| haveSearch:false, | |||||
| dialogImport: false, | |||||
| file: null, | |||||
| } | } | ||||
| }, | }, | ||||
| async mounted() { | async mounted() { | ||||
| CancelToken = this.$axios.CancelToken; | |||||
| this.getHeaders(); | this.getHeaders(); | ||||
| this.getInventories(); | this.getInventories(); | ||||
| this.getTitle(); | this.getTitle(); | ||||
| this.getSelectItem(); | this.getSelectItem(); | ||||
| this.fullHeight = window.innerHeight; | this.fullHeight = window.innerHeight; | ||||
| console.log(this.fullHeight); | |||||
| window.onresize = () => { | window.onresize = () => { | ||||
| // this.fullWidth = window.innerWidth; | |||||
| this.fullHeight = window.innerHeight; | this.fullHeight = window.innerHeight; | ||||
| }; | }; | ||||
| }, | }, | ||||
| @@ -305,7 +374,7 @@ | |||||
| }, | }, | ||||
| }, | }, | ||||
| watch: { | watch: { | ||||
| async tablename() { | |||||
| tablename() { | |||||
| if (this.$route.params.tablename) { | if (this.$route.params.tablename) { | ||||
| this.headers = []; | this.headers = []; | ||||
| this.items = []; | this.items = []; | ||||
| @@ -337,12 +406,20 @@ | |||||
| }, | }, | ||||
| getHeaders() { | getHeaders() { | ||||
| this.headers.push({'text': '', 'value': 'actions', sortable: false}); | 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; | this.cols = resp.data.data; | ||||
| resp.data.data.forEach((item) => { | resp.data.data.forEach((item) => { | ||||
| let header = {'text': null, 'value': null}; | let header = {'text': null, 'value': null}; | ||||
| header.text = item.descript; | header.text = item.descript; | ||||
| header.value = item.colname; | header.value = item.colname; | ||||
| this.mappingHeaders[item.colname] = item.descript; | |||||
| this.headers.push(header); | this.headers.push(header); | ||||
| if (item.isselect === 'true') { | if (item.isselect === 'true') { | ||||
| this.isselect.push(item.colname); | this.isselect.push(item.colname); | ||||
| @@ -356,7 +433,14 @@ | |||||
| }); | }); | ||||
| }, | }, | ||||
| getInventories() { | 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; | this.items = resp.data.data; | ||||
| }); | }); | ||||
| }, | }, | ||||
| @@ -373,6 +457,7 @@ | |||||
| this.dialogInsert = false; | this.dialogInsert = false; | ||||
| this.dialogModify = false; | this.dialogModify = false; | ||||
| this.dialogSearch = false; | this.dialogSearch = false; | ||||
| this.dialogImport = false; | |||||
| }, | }, | ||||
| isDisabled(key) { | isDisabled(key) { | ||||
| const disabledKey = ['id']; | const disabledKey = ['id']; | ||||
| @@ -408,9 +493,56 @@ | |||||
| alert('已刪除'); | alert('已刪除'); | ||||
| }); | }); | ||||
| } | } | ||||
| console.log(item); | |||||
| }, | }, | ||||
| search() { | 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() { | exportFile() { | ||||
| @@ -103,6 +103,30 @@ | |||||
| <version>9.2.1.jre8</version> | <version>9.2.1.jre8</version> | ||||
| </dependency> | </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> | </dependencies> | ||||
| @@ -5,9 +5,13 @@ import com.moze.rms.dao.InventoryDAO; | |||||
| import com.moze.rms.dao.MappingColDAO; | import com.moze.rms.dao.MappingColDAO; | ||||
| import com.moze.rms.dao.MappingTableDAO; | import com.moze.rms.dao.MappingTableDAO; | ||||
| import com.moze.rms.entity.dto.SelectItemDTO; | 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.beans.factory.annotation.Autowired; | ||||
| import org.springframework.web.bind.annotation.*; | import org.springframework.web.bind.annotation.*; | ||||
| import org.springframework.web.multipart.MultipartFile; | |||||
| import java.io.*; | |||||
| import java.util.*; | import java.util.*; | ||||
| import java.util.function.Function; | import java.util.function.Function; | ||||
| import java.util.stream.Collectors; | 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(); | Handle handle = this.getHandle(); | ||||
| String sql = "create table " + data.get("assertGroupTablename") + | String sql = "create table " + data.get("assertGroupTablename") + | ||||
| "(" + | "(" + | ||||
| " id int not null" + | |||||
| " id int not null IDENTITY(1,1)" + | |||||
| " constraint " + data.get("assertGroupTablename") + "_pk" + | " constraint " + data.get("assertGroupTablename") + "_pk" + | ||||
| " primary key nonclustered," + | " primary key nonclustered," + | ||||
| " type nvarchar(256)," + | " type nvarchar(256)," + | ||||
| @@ -155,7 +155,7 @@ public interface AssertDAO extends SqlObject { | |||||
| //新增欄位sql | //新增欄位sql | ||||
| default void addColToTable(MappingCol m) { | default void addColToTable(MappingCol m) { | ||||
| Handle handle = this.getHandle(); | 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(); | handle.createUpdate(sql).execute(); | ||||
| } | } | ||||
| @@ -72,4 +72,24 @@ public interface InventoryDAO extends SqlObject { | |||||
| handle.execute(sql); | 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 { | 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); | 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=dev | ||||
| #spring.profiles.active=pro | #spring.profiles.active=pro | ||||
| spring.servlet.multipart.max-file-size=100MB | |||||
| spring.servlet.multipart.max-request-size=1000MB | |||||