| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | <script setup lang="ts"> | 
					
						
							| 
									
										
										
										
											2022-10-20 18:47:20 +00:00
										 |  |  | /*! sheetjs (C) SheetJS -- https://sheetjs.com */ | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | import { ref, onMounted } from "vue"; | 
					
						
							|  |  |  | import VueTableLite from "vue3-table-lite/ts"; | 
					
						
							|  |  |  | import { read, utils, WorkSheet, writeFile } from "xlsx"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type DataSet = { [index: string]: WorkSheet; }; | 
					
						
							|  |  |  | type Row = any[]; | 
					
						
							|  |  |  | type RowCB = (row: Row) => string; | 
					
						
							|  |  |  | type Column = { field: string; label: string; display: RowCB; }; | 
					
						
							|  |  |  | type RowCol = { rows: Row[]; cols: Column[]; }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const currFileName = ref<string>(""); | 
					
						
							|  |  |  | const currSheet = ref<string>(""); | 
					
						
							|  |  |  | const sheets = ref<string[]>([]); | 
					
						
							|  |  |  | const workBook = ref<DataSet>({} as DataSet); | 
					
						
							|  |  |  | const rows = ref<Row[]>([]); | 
					
						
							|  |  |  | const columns = ref<Column[]>([]); | 
					
						
							| 
									
										
										
										
											2023-11-04 05:05:26 +00:00
										 |  |  | const loading = ref<boolean>(true); | 
					
						
							|  |  |  | const paging = ref<boolean>(true); | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | const exportTypes: string[] = ["xlsx", "xlsb", "csv", "html"]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | let cell = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function resetCell() { | 
					
						
							|  |  |  |   cell = 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const getRowsCols = ( data: DataSet, sheetName: string ): RowCol => ({ | 
					
						
							|  |  |  |   rows: utils.sheet_to_json<Row>(data[sheetName], {header:1}), | 
					
						
							|  |  |  |   cols: Array.from({ | 
					
						
							|  |  |  |     length: utils.decode_range(data[sheetName]["!ref"]||"A1").e.c + 1 | 
					
						
							|  |  |  |   }, (_, i) => (<Column>{ field: String(i), label: utils.encode_col(i), display: makeDisplay(i) })) | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const makeDisplay = (col: number): RowCB => (row: Row) => `<span
 | 
					
						
							|  |  |  |   style="user-select: none; display: block" | 
					
						
							|  |  |  |   onblur="endEdit(event)" ondblclick="startEdit(event)" | 
					
						
							|  |  |  |   position="${Math.floor(cell++ / columns.value.length)}.${col}" | 
					
						
							|  |  |  |   onkeydown="endEdit(event)">${row?.[col] ?? " "}</span>`;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | (window as any).startEdit = function (ev: MouseEvent) { | 
					
						
							|  |  |  |   (ev?.target as HTMLSpanElement).contentEditable = "true"; | 
					
						
							|  |  |  |   (ev?.target as HTMLSpanElement).focus(); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | (window as any).endEdit = function (ev: FocusEvent | KeyboardEvent) { | 
					
						
							|  |  |  |   if (typeof (ev as KeyboardEvent).key == "undefined" || (ev as KeyboardEvent).key === "Enter") { | 
					
						
							|  |  |  |     const pos = (ev.target as HTMLSpanElement)?.getAttribute("position")?.split("."); | 
					
						
							|  |  |  |     if(!pos) return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     (ev?.target as HTMLSpanElement).contentEditable = "true"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     rows.value[+pos[0]][+pos[1]] = (ev.target as HTMLSpanElement).innerText; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     workBook.value[currSheet.value] = utils.json_to_sheet(rows.value, { | 
					
						
							|  |  |  |       header: columns.value.map((col: Column) => col.field), | 
					
						
							|  |  |  |       skipHeader: true, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function importAB(ab: ArrayBuffer, name: string): Promise<void> { | 
					
						
							| 
									
										
										
										
											2023-11-04 05:05:26 +00:00
										 |  |  |   loading.value = true; | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  |   const data = read(ab); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   currFileName.value = name; | 
					
						
							|  |  |  |   currSheet.value = data.SheetNames?.[0]; | 
					
						
							|  |  |  |   sheets.value = data.SheetNames; | 
					
						
							|  |  |  |   workBook.value = data.Sheets; | 
					
						
							| 
									
										
										
										
											2023-11-04 05:05:26 +00:00
										 |  |  |   loading.value = false; | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   selectSheet(currSheet.value); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function importFile(ev: Event): Promise<void> { | 
					
						
							|  |  |  |   const file = (ev.target as HTMLInputElement)?.files?.[0]; | 
					
						
							|  |  |  |   if(!file) return; | 
					
						
							|  |  |  |   await importAB(await file.arrayBuffer(), file.name); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function exportFile(type: string): void { | 
					
						
							|  |  |  |   const wb = utils.book_new(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sheets.value.forEach((sheet) => { | 
					
						
							|  |  |  |     utils.book_append_sheet(wb, workBook.value[sheet], sheet); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-24 00:30:01 +00:00
										 |  |  |   writeFile(wb, `SheetJSVTL.${type}`); | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function selectSheet(sheet: string): void { | 
					
						
							|  |  |  |   const { rows: newRows, cols: newCols } = getRowsCols(workBook.value, sheet); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   resetCell(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   currSheet.value = sheet; | 
					
						
							| 
									
										
										
										
											2023-11-04 05:05:26 +00:00
										 |  |  |   columns.value = newCols; | 
					
						
							|  |  |  |   rows.value = newRows; | 
					
						
							|  |  |  |   paging.value = newRows.length > 50 | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-26 04:16:13 +00:00
										 |  |  | /* Download from https://docs.sheetjs.com/pres.numbers */ | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | onMounted(async() => { | 
					
						
							| 
									
										
										
										
											2024-04-26 04:16:13 +00:00
										 |  |  |   const response = await fetch("https://docs.sheetjs.com/pres.numbers"); | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  |   await importAB(await response.arrayBuffer(), "pres.numbers"); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | </script> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <template> | 
					
						
							|  |  |  |   <header class="imp-exp"> | 
					
						
							|  |  |  |     <div class="import"> | 
					
						
							|  |  |  |       <input type="file" id="import" @change="importFile" /> | 
					
						
							|  |  |  |       <label for="import">import</label> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |     <span v-if="currFileName">{{ currFileName }}</span> | 
					
						
							|  |  |  |     <div class="export" v-if="currFileName"> | 
					
						
							|  |  |  |       <span>export</span> | 
					
						
							|  |  |  |       <ul> | 
					
						
							|  |  |  |         <li v-for="(type, idx) in exportTypes" :key="idx" @click="exportFile(type)"> | 
					
						
							|  |  |  |           {{ `.${type}` }} | 
					
						
							|  |  |  |         </li> | 
					
						
							|  |  |  |       </ul> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |   </header> | 
					
						
							|  |  |  |   <div class="sheets"> | 
					
						
							|  |  |  |     <span | 
					
						
							|  |  |  |       v-for="(sheet, idx) in sheets" | 
					
						
							|  |  |  |       :key="idx" | 
					
						
							|  |  |  |       @click="selectSheet(sheet)" | 
					
						
							|  |  |  |       :class="[currSheet === sheet ? 'selected' : '']" | 
					
						
							|  |  |  |     > | 
					
						
							|  |  |  |       {{ sheet }} | 
					
						
							|  |  |  |     </span> | 
					
						
							|  |  |  |   </div> | 
					
						
							| 
									
										
										
										
											2023-11-04 05:05:26 +00:00
										 |  |  |   <vue-table-lite :is-loading="loading" :page-size="50" :columns="columns" :is-hide-paging="paging" :rows="rows"></vue-table-lite> | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | </template> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <style> | 
					
						
							|  |  |  | .imp-exp { | 
					
						
							|  |  |  |   display: flex; | 
					
						
							|  |  |  |   justify-content: space-between; | 
					
						
							|  |  |  |   padding: 0.5rem; | 
					
						
							|  |  |  |   font-family: mono; | 
					
						
							|  |  |  |   color: #212529; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .import { | 
					
						
							|  |  |  |   font-size: medium; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .import input { | 
					
						
							|  |  |  |   position: absolute; | 
					
						
							|  |  |  |   opacity: 0; | 
					
						
							|  |  |  |   cursor: pointer; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .import label { | 
					
						
							|  |  |  |   background-color: white; | 
					
						
							|  |  |  |   border: 1px solid; | 
					
						
							|  |  |  |   padding: 0.3rem; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .export:hover { | 
					
						
							|  |  |  |   border-bottom: none; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .export:hover ul { | 
					
						
							|  |  |  |   display: block; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .export span { | 
					
						
							|  |  |  |   padding: 0.3rem; | 
					
						
							|  |  |  |   border: 1px solid; | 
					
						
							|  |  |  |   cursor: pointer; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .export ul { | 
					
						
							|  |  |  |   display: none; | 
					
						
							|  |  |  |   position: absolute; | 
					
						
							|  |  |  |   z-index: 5; | 
					
						
							|  |  |  |   background-color: white; | 
					
						
							|  |  |  |   list-style: none; | 
					
						
							|  |  |  |   padding: 0.3rem; | 
					
						
							|  |  |  |   border: 1px solid; | 
					
						
							|  |  |  |   margin-top: 0.3rem; | 
					
						
							|  |  |  |   border-top: none; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .export ul li { | 
					
						
							|  |  |  |   padding: 0.3rem; | 
					
						
							|  |  |  |   text-align: center; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .export ul li:hover { | 
					
						
							|  |  |  |   background-color: lightgray; | 
					
						
							|  |  |  |   cursor: pointer; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .sheets { | 
					
						
							|  |  |  |   display: flex; | 
					
						
							|  |  |  |   justify-content: center; | 
					
						
							|  |  |  |   margin: 0.3rem; | 
					
						
							|  |  |  |   color: #212529; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .sheets span { | 
					
						
							|  |  |  |   border: 1px solid; | 
					
						
							|  |  |  |   padding: 0.5rem; | 
					
						
							|  |  |  |   margin: 0.3rem; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .sheets span:hover:not(.selected) { | 
					
						
							|  |  |  |   background-color: lightgray; | 
					
						
							|  |  |  |   cursor: pointer; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .selected { | 
					
						
							|  |  |  |   background-color: #343a40; | 
					
						
							|  |  |  |   color: white; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | </style> |