Last active
January 3, 2024 07:57
-
-
Save skysan87/c7dc124e30494c5e5dfc8e79f31b1632 to your computer and use it in GitHub Desktop.
[Vue 3][TailwindCSS 3] CSV Reader on Browser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="ja"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>CSV Viewer</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/encoding-japanese/2.0.0/encoding.min.js"></script> | |
<script type="importmap"> | |
{ | |
"imports": { | |
"Vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js" | |
} | |
} | |
</script> | |
</head> | |
<body> | |
<div id="app"> | |
<div class="app-container"> | |
<!-- header --> | |
<div class="grid-header p-2"> | |
<button class="border bg-white active:bg-blue-200 p-1" @click="openFile">ファイルを開く</button> | |
</div> | |
<!-- table --> | |
<div class="grid-table-view overflow-auto"> | |
<div class="grid-table"> | |
<div class="row header sticky top-0 z-20"> | |
<div class="cell bg-gray-200 sticky left-0"></div> | |
<div class="cell bg-gray-200" v-for="(cell, j) in header" :key="`cell-${j}`">{{ cell }}</div> | |
</div> | |
<div class="row" v-for="(row, i) in rows" :key="`cell-${i}`" @click="rowSelected(i)"> | |
<div class="cell bg-white sticky left-0 z-10">{{ i + 1 }}</div> | |
<div class="cell bg-white" v-for="(cell, j) in row" :key="`cell-${j}`">{{ cell }}</div> | |
</div> | |
</div> | |
</div> | |
<!-- row data --> | |
<div class="grid-record-view overflow-hidden flex flex-col"> | |
<div class="flex-none p-2"> | |
<button class="border bg-white p-1" @click="selectedIndex = -1">Clear</button> | |
<span class="pl-2" v-if="selectedIndex > -1">{{ selectedIndex + 1}} 行目</span> | |
</div> | |
<div class="flex-1 overflow-auto"> | |
<div class="grid-table fixed-table pointer-events-none w-full"> | |
<div class="row" v-for="(cell, j) in rows[selectedIndex]" :key="`cell-${j}`"> | |
<div class="cell bg-gray-200">{{ header[j] }}</div> | |
<div class="cell bg-white">{{ cell }}</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</body> | |
<script> | |
async function openFilePicker () { | |
const pickerOpts = { | |
types: [ | |
{ | |
accept: { | |
'text/*': ['.csv', '.tsv'] | |
} | |
}, | |
], | |
excludeAcceptAllOption: true, | |
multiple: false | |
} | |
const [fileHandle] = await showOpenFilePicker(pickerOpts) | |
const file = await fileHandle.getFile() | |
const extension = file.name.split('.').pop() | |
const parseType = { | |
'csv': ',', | |
'tsv': '\t' | |
} | |
return { | |
text: toText(await file.arrayBuffer()), | |
splitter: parseType[extension] | |
} | |
} | |
function toText (arrayBuffer) { | |
const unit8Array = new Uint8Array(arrayBuffer) | |
const detectedEncoding = Encoding.detect(unit8Array) | |
console.log('File Encoding: ', detectedEncoding) | |
if (detectedEncoding === 'UTF8') { | |
Encoding.codeToString(unit8Array) | |
} | |
const unicodeArray = Encoding.convert(unit8Array, { from: detectedEncoding, to: 'UNICODE' }) | |
return Encoding.codeToString(unicodeArray) | |
} | |
</script> | |
<script type="module"> | |
import { createApp, ref } from 'Vue' | |
createApp({ | |
setup () { | |
const header = ref([]) | |
const rows = ref([]) | |
const selectedIndex = ref(-1) | |
const openFile = async () => { | |
const { text, splitter } = await openFilePicker() | |
parse(text, splitter) | |
} | |
const parse = (text, splitter) => { | |
header.value.length = 0 | |
rows.value.length = 0 | |
text.split('\n') | |
.forEach(row => { | |
if (row !== '') { | |
rows.value.push(row.split(splitter)) | |
} | |
}) | |
header.value = rows.value.shift() | |
} | |
const rowSelected = (rowIndex) => { | |
selectedIndex.value = rowIndex | |
} | |
return { | |
header, | |
rows, | |
selectedIndex, | |
openFile, | |
rowSelected | |
} | |
} | |
}).mount('#app') | |
</script> | |
<style> | |
.app-container { | |
display: grid; | |
height: 100vh; | |
width: 100vw; | |
grid-template-rows: 50px auto; | |
grid-template-columns: 70% 30%; | |
grid-template-areas: "header header" | |
"table-view record-view"; | |
} | |
.grid-header { | |
grid-area: header; | |
background-color: #FF69A3; | |
} | |
.grid-table-view { | |
grid-area: table-view; | |
background-color: #75A9FF; | |
} | |
.grid-record-view { | |
grid-area: record-view; | |
background-color: #D0FF43; | |
} | |
.fixed-table { | |
table-layout: fixed; | |
} | |
.grid-table { | |
display: table; | |
border-left: 1px solid black; | |
font-size: 1rem; | |
} | |
.row { | |
display: table-row; | |
} | |
.row:hover>.cell { | |
background-color: red !important; | |
} | |
.grid-record-view .grid-table>.row:first-child>.cell { | |
border-top: 1px solid black; | |
} | |
.header { | |
pointer-events: none; | |
} | |
.cell { | |
display: table-cell; | |
border-right: 1px solid black; | |
padding: 2px; | |
border-bottom: 1px solid black; | |
} | |
.header>.cell { | |
border-top: 1px solid black; | |
} | |
</style> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment