😽

2022年4月現在 Vue3 + Vuetify3(Beta)でどこまでできるか

2022/04/10に公開

昨年、Zenn に Vue を使ったアプリの作り方について投稿しました。

https://zenn.dev/uedayou/articles/c1e76684d3d094

上記投稿時点で Vue3 を使うことはできましたが、使おうと思っていた UIフレームワークの Vuetify が Vue3 に対応していなかったので、Vue2 を使っていました。

あれから半年以上たって、Vue3 対応の Vuetify3 がアルファからベータ版になり、
公式サイトを見るといろいろできそうな感じに見えたので、試しに Vue3 + Vuetify3 で Webアプリを作ってみました。

目標

先日、オープンデータカタログサイトリストの Web API を以下のサイトに公開しました。

https://uedayou.net/ldapinavi/data.go.jp/dbsites

ここで公開したデータの検索サイトを作ろうと思います。

一応目標として、このサイト(React + Material-UIで作成)のような見た目を目指すことにしました。

https://uedayou.net/ldapinavi/

出来たもの

こんな感じにしあがりました。

https://uedayou.net/odsites/

Vuetify3 についていくつか思ったように動かない部分がありましたが、目標に近い形で作れたと思います。
作成にあたって以下を利用しています。

  • Vite
  • Vue3
  • Vuetify3(Beta 0)
  • Vue Router4
  • Tailwind CSS

以下では、Vuetify3 を利用している部分を中心に紹介します。

ナビゲーションバー

v-app-bar をそのまま使いました。問題なく動きました。

https://next.vuetifyjs.com/en/components/app-bars/

<v-app-bar class="select-none">
  <v-app-bar-title>
    <span class="cursor-pointer" @click="router.push('/')">オープンデータカタログ検索</span>
  </v-app-bar-title>
  <v-btn icon @click="dialog = true">
    <v-icon>mdi-information-outline</v-icon>
  </v-btn>
</v-app-bar>

フッター

v-footer を使います。問題なしです。

https://next.vuetifyjs.com/en/components/footers/

<v-footer>
  <v-card elevation="0" rounded="0" class="text-center w-full">
    <v-card-text>
      <a href="https://uedayou.net/" target="_blank" rel="noopener noreferrer" class="no-underline"> @uedayou </a>
      {{ ' ' + new Date().getFullYear() }}. Some rights reserved.
    </v-card-text>
  </v-card>
</v-footer>

検索フィールド

v-text-field を使いました。フィールド内側のアイコンは append-inner-icon 外側は append-icon として定義できるようです。
押したときの挙動は、@click:append-inner@click:append に指定できます。

https://next.vuetifyjs.com/en/components/text-fields/

<v-text-field
  label="検索"
  placeholder="検索したい語を入力してください"
  variant="outlined"
  hide-details="true"
  :append-inner-icon="query.length > 0 ? 'mdi-close' : ''"
  append-icon="mdi-magnify"
  v-model="query"
  @click:append-inner="query = '', reset()"
  @click:append="search(query)"
  @keyup.enter="search(query)"
></v-text-field>

アイコンのカーソルをポインターにしたかったので、以下も追加しています。

<style lang="scss">
.v-field,
.v-input {
  &__append {
    &,
    &-inner {
      @apply cursor-pointer;
    }
  }
}
</style>

ページネーション

v-pagination でシンプルに実現できます。
https://next.vuetifyjs.com/en/components/paginations/

<v-pagination v-model="page" :length="Math.ceil(results.length / itemsPerPage)"></v-pagination>

テーブル

v-table を使います。
アプリ内では各オープンデータカタログサイトの詳細を表示する部分で使っています。

https://next.vuetifyjs.com/en/components/tables/

<v-table v-if="site">
  <tbody>
    <tr>
      <td>名称</td>
      <td>{{ title }}</td>
    </tr>
    <tr>
      <td>URL</td>
      <td>
        <a :href="site.url" target="_blank" rel="noopener noreferrer">{{ site.url }}</a>
      </td>
    </tr>
    ...
  </tbody>
</v-table>

タグ表示

v-chip をタグ表示に使いました。

https://next.vuetifyjs.com/en/components/chips/

<v-chip v-for="term in site.terms" color="blue" variant="outlined">
  {{ term }}
</v-chip>

検索結果表示

v-card を使って検索結果を表示しています。幅等のスタイルを Tailwind CSS で調整しています。

https://next.vuetifyjs.com/en/components/cards/

<v-card
  v-for="item in results.slice((page - 1) * itemsPerPage || 0, page * itemsPerPage)"
  :key="item.id"
  class="p-4 grid items-center text-center select-none"
>
  <div v-if="top" @click="router.push(`/${item.type}`)" class="text-sm text-gray-500 cursor-pointer">
    {{ getTypename(item.type) }}
  </div>
  ...
</v-card>

カテゴリの表示

v-expansion-panels で検索カテゴリが不要な場合、隠すようにしています。

https://next.vuetifyjs.com/en/components/expansion-panels/

<v-expansion-panels class="md:mx-4 my-4" v-model="panel">
  <v-expansion-panel>
    <v-expansion-panel-title class="select-none"> 提供団体 </v-expansion-panel-title>
    <v-expansion-panel-text>
      <div class="grid sm:grid-cols-4 gap-4 items-stretch">
        <v-card v-for="type in types" @click="router.push(`/${type.id}`)" class="p-4 text-center cursor-pointer">
          {{ type.name }}
        </v-card>
      </div>
    </v-expansion-panel-text>
  </v-expansion-panel>
</v-expansion-panels>

アプリの詳細表示

アプリの詳細を v-dialog を使って表示するようにしました。ナビゲーションバーの (i) ボタンを押すことで表示できます。
デフォルトのまま使うとサイズや表示位置がおかしくなってしまったので<style>タグ内でオーバーライド、本文がはみ出る場合にスクロールさせるため :class="{ 'overflow-y-auto': dialog }"を指定しました。
動いていますがこれが正しい方法かどうかは疑問です。

https://next.vuetifyjs.com/en/components/dialogs/

<v-row justify="center">
  <v-dialog v-model="dialog">
    <v-card class="rounded-lg" :class="{ 'overflow-y-auto': dialog }">
      <v-card-title>
        <span class="text-h5">オープンデータカタログ検索について</span>
      </v-card-title>
      <v-card-text class="grid gap-x-2 gap-y-4">
        <p>ここでは、日本国内で公開されるオープンデータカタログサイトを検索することができます。</p>
        ...
      </v-card-text>
      <v-card-actions>
        <v-spacer></v-spacer>
        <v-btn text @click="dialog = false"> 閉じる </v-btn>
      </v-card-actions>
    </v-card>
  </v-dialog>
</v-row>
<style lang="scss">
.v-dialog {
  .v-overlay {
    &__content {
      max-height: calc(100% - 48px) !important;
      max-width: calc(100% - 2rem) !important;
      @apply flex flex-col m-4;
    }
  }
}
</style>

おわりに

Vue3 と まだ初期ベータ(2022年4月現在)の Vuetify3 を使って Webアプリを作ってみました。
Vuetify3 は今でも一部を除き問題なく動くものが多いような感じでした。
用途によっては現時点でも Vuetify3 を使ってもいいかもしれません。

また、コード部を Vue 3.2 から使える script setup 構文で書きましたが、慣れが必要な部分もありますが他の言語よりも圧倒的に少ないコード量で Webアプリを作ることができました。
コンポーネントが import で直接使えたりとさっと作りたい場合は魅力的な機能だと思います。

<script setup lang="ts">
import PrefPanel from './PrefPanel.vue'
import TypePanel from './TypePanel.vue'
</script>

<template>
  <TypePanel />
  <PrefPanel />
  ...
</template>

script setup 構文について以下のページを参考にさせていただきました。
https://zenn.dev/azukiazusa/articles/676d88675e4e74

ここで作成した Webアプリのソースコードを GitHub で公開しています。
あくまでも Vuetify3 の初期ベータの実装であることを留意してください。

https://github.com/uedayou/opendata-sites

データセット(JSON)を変更してある程度手直しすれば、他のデータの検索アプリとしても流用できそうに思います。
そういったニーズがあれば、活用してみてください。

Discussion