Vue.jsでExcelデータ読み込み

前回Vuex、Vue Routerを使用するといっていたのですが、
今回はまだ使いません。

仕事でExcelでデータ管理をしていて
そのデータを取り込めたら便利だと思いググりながら作ってみました。

この記事がかなり参考になりました。
ほぼそのままのところも多いです。
https://qiita.com/okoppe8/items/995b57d4e3d6d512b916

ファイル名は何でもいいですが
Excel.htmlという名前で作成します。

まずはコード全文
Excel.html

<html>

<head>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.14.1/xlsx.full.min.js"></script>
</head>

<body>

    <div id="app">
        <!-- select file -->
        <section class="section" v-if="showTable === false">
            <div class="container">
                <div class="file is-medium">
                    <label class="file-label">
                        <input class="file-input" type="file" name="resume" v-on:change="onFileChange">
                        <span class="file-cta">
                            <span class="file-label">
                                Choose a file…
                            </span>
                        </span>
                    </label>
                </div>
            </div>
        </section>
        <!-- select file End -->

        <!-- Main -->
        <section class="section" v-if="showTable">
            <div class="container">
                <div class="search">
                    <input class="input" type="text" placeholder="Search for…" v-model="searchWord">
                </div>
                <div class="card-area">
                    <div class="columns is-multiline">
                        <div class="column is-one-quarter" v-for="(item, index) in filteredItems" v-bind:key="index">
                            <div class="card">
                                <figure class="image is-1by1">
                                    <a class="mainImg"><img class="modal-button" v-on:click="openModal(item.id)"
                                            v-bind:src="dict + '/' + item.id + '.jpg'"></a>
                                </figure>
                                <div class="card-content">
                                    <div class="media">
                                        <div class="media-content">
                                            <p class="title is-4"> {{ item.id }} </p>
                                            <p class="subtitle is-5"> {{ item.price }} </p>
                                        </div>
                                    </div>
                                    <div class="content">
                                        {{ item.comment }}
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </section>
        <!-- Main End -->

        <!-- Modal -->
        <transition name="fade">
            <div v-bind:class="{ 'is-active' : showModal, 'modal' : showModal }" v-if="showModal">
                <div class="modal-background" v-on:click="closeModal"></div>
                <div class="modal-content">
                    <figure class="image is-square">
                        <img v-bind:src="dict + '/' + zoomPic + '.jpg'">
                    </figure>
                </div>
            </div>
        </transition>
        <!-- Modal End-->
    </div>

    <script>
        const app = new Vue({
            el: '#app',
            data: {
                showTable: false,
                showModal: false,
                zoomPic: '',
                searchWord: '',
                dict: 'photo',
                items: [],
            },
            computed: {
                filteredItems: function () {
                    return this.items.filter(item => {
                        return item.id.toLowerCase().includes(this.searchWord.toLowerCase())
                    })
                }
            },
            methods: {
                onFileChange: function (event) {
                    this.showTable = true;
                    this.handleFile(event)
                },

                fixdata: function (resultData) {
                    let o = "",
                        l = 0,
                        w = 10240;
                    for (; l < resultData.byteLength / w; ++l) o += String.fromCharCode.apply(null, new Uint8Array(resultData.slice(l * w,
                        l * w + w)));
                    o += String.fromCharCode.apply(null, new Uint8Array(resultData.slice(l * w)));
                    return o;
                },

                to_json: function (workbook, X) {
                    let result = {};
                    workbook.SheetNames.forEach(function (sheetName) {
                        const roa = X.utils.sheet_to_json(
                            workbook.Sheets[sheetName],
                            {
                                raw: true,
                            });
                        if (roa.length > 0) {
                            result[sheetName] = roa;
                        }
                    });
                    return result;
                },

                handleFile: function (e) {
                    const X = XLSX;
                    const files = e.target.files;
                    const f = files[0];

                    const reader = new FileReader();

                    const loadReader = (e) => {
                        const resultData = e.target.result;
                        const arr = this.fixdata(resultData);
                        const wb = X.read(btoa(arr), {
                            type: 'base64',
                            cellDates: true,
                        });
                        const output = this.to_json(wb, X);
                        for (let i of output['Sheet1']) {
                            this.items.push(i);
                        }
                    };
                    reader.readAsArrayBuffer(f);
                    reader.onload = loadReader;
                },
                openModal: function (item) {
                    this.zoomPic = item;
                    this.showModal = true
                },
                closeModal: function () {
                    this.showModal = false
                },
            },
        })
    </script>

    <style>
        img {
            object-fit: contain;
        }

        .search {
            position: fixed;
            top: 0;
            z-index: 10;
        }

        .fade-enter-active,
        .fade-leave-active {
            transition: opacity 1s
        }

        .fade-enter,
        .fade-leave-to {
            opacity: 0
        }

        .modal-background {
            opacity: 0.4
        }

        .mainImg {
            overflow: hidden;
        }

        .mainImg img {
            display: block;
            transition-duration: 0.3s;
        }

        .mainImg img:hover {
            transform: scale(0.9);
            transition-duration: 0.3s;
        }
    </style>

</body>

</html>

ちょうど200行ですね。

headでCDN版の
Vue.js
Bluma
xlsx
を読み込んでいます。
xlsxを使っているのはExcelを読み込むためです。


読み込むExcelのファイルは
セルA1: id
セルB1: price
セルC1: comment
と書いてあり
2行目からitemがずらっと書いている前提です。

Excelの1行目を自分用に変更する場合は
コード40、41、46、90行目のitem.idのidを変更した値に変更
同様に
47行目のpriceを変更
51行目のcommentを変更すると使用できます。

Excelのデータを読み込むとBulmaのCardで表が作成されます。
表の画像は
Excel.htmlと同じディレクトリのphotoフォルダに入っている前提です。
画像の入っているフォルダを変更する場合、
84行目のphotoを変更したらOKです。

表を表示後
上のサーチバーでidを検索できます。

このコードで
特に重要だなと思ったところは
アロー関数です。 () =>  ←こういうの

Vue.jsではdataにアクセスするために
よくthisを使用します。

アロー関数はthisを束縛するという効果があるため
Vue.jsではややこしいのでできるだけ使わないようにしようと思っていましたが
実は使うとすごく便利でした。

例えば132行目でアロー関数を使用していますが
ここはonloadからのクロージャになっていますので
functionだとクロージャ内からthisでdataにアクセスできません。

そこでアロー関数を使用してthisを束縛することで
クロージャの中でthisが使えるようになり快適になります。


Excelでデータ管理している方はぜひ使ってみてください。

それでは!