2020/05/25

[第3回]Nuxt.js+Vuetifyでレイアウトを整える

vuetifynuxtjsmaterial-design

今回は、Nuxt.js + Vuetifyベースのアプリのレイアウトを整理します。
Nuxt.jsにはlayoutsという機能があります。
共通のheaderやfooterなどはlayoutsのリソースで定義し、各pageからは、layoutの種類を指定することで、headerやfooterなどの記述をキレイにまとめることができるようになっています。

以下のような構成になっています。

gdrive-manager
└── client
    ├── pages <- ここに各ページを定義して、どのlayoutを指定するかのプロパティを記述できます
    └── layouts <- これです

note

前回の続きです。
まだご覧になっていない方は、こちらからご覧ください。

不要なリソースの削除

まずは、初回に作成したアプリから不要なデータを削除しましょう。
VuetifyをDesign Frameworkとして採用していると、

VuetifyのLogoデータなどが自動で作成されているので、そちらを削除します。

以下のように編集しましょう

  1. 以下のファイルを削除します。

    • client/components/Logo.vue
    • client/components/VuetifyLogo.vue
    • client/pages/inspire.vue
    • client/static/icon.png
    • client/static/v.png
    • client/static/vuetify-logo.svg
  2. faviconを更新します

    • client/static/favicon.ico
      • こちらのファイルを使って上書きしてください
  3. defaultのlayoutファイルを編集します。

    • client/layouts/default.vue

以下のように編集してください。

<template>
  <v-app dark>
    <v-app-bar
      color="primary"
      :clipped-left="$vuetify.breakpoint.lgAndUp"
      clipped-right
      app
    >
      <v-toolbar-title
        style="width: 300px"
        class="ml-0 pl-4"
        @click="$router.push('/')"
      >
        <span class="hidden-sm-and-down">{{ title }}</span>
      </v-toolbar-title>
    </v-app-bar>
    <v-content>
      <v-container>
        <nuxt />
      </v-container>
    </v-content>
  </v-app>
</template>

<script>
export default {
  data() {
    return {
      title: 'GDrive Manager'
    }
  }
}
</script>
  1. topページを編集します。
    • client/pages/index.vue

以下のように編集してください。

<template>
  <v-row justify="center">
    <v-col cols="12" md="8">
      <p class="display-1">{{ title }}</p>
    </v-col>
  </v-row>
</template>

<script>
export default {
  data() {
    return {
      title: '[第3回]Nuxt.js+Vuetifyでレイアウトを整える'
    }
  }
}
</script>

ここで、いったん起動してみて、表示を確認してみましょう。
ターミナルで以下を実行してみてください。

% yarn run dev

http://localhost:3000/にアクセスして、以下のように表示されていればOKです。

remove-vuetify-resource-min.png

Vuetifyについて

VuetifyはMaterial DesingをベースとしたVue.js用のフレームワークとなっています。

例えば、Buttonsをご覧いただきたいのですが

<v-btn color="primary">Primary Button</v-btn>

とマークアップするだけで、Material Designベースのボタンを表示することができます。
Buttonだけではなく、様々なUI部品が揃っていてとても便利です。

Vuetifyで開発するときはUIコンポーネントで定義されているものをよく読んで、利用するようにしましょう。

今回はアプリのレイアウトを整えるのがテーマですので、アプリケーションレイアウト — Vuetify.jsというページがとても参考になります。UIコンポーネントだけでなく、こちらのページもよく読んでおくと良いでしょう。

レイアウトを編集しよう

今回の本題です。実際にレイアウトファイルを整備していきましょう。

VuetifyのThemeを編集しよう

client/assets/variables.scss

以下の行を追加します。

$active-color: #6B38FB;

client/assets/overrides.sass

以下の内容でファイルを作成します。
こちらは、VuetifyのCSSをカスタマイズ(上書き)するためのファイルです。

.v-application
  .primary--text
    color: #6B38FB !important
    caret-color: #6B38FB !important

  a
    cursor: pointer
    color: rgba(0, 0, 0, 0.87)
    background-color: transparent

.v-list-item--active
  color: $active-color!important

client/nuxt.config.js

先ほど作った、overrides.sassを読み込むようにします。

   /*
    ** Global CSS
    */
   css: [{ src: '~/assets/overrides.sass', lang: 'sass' }],

さらに、vuetify moduleの設定箇所を以下のように修正します。
lightテーマを使用して、primaryとsecondaryの色を指定しています。
こう記述しておくことで、後々 color=“primary” や color=“secondary” などと記述することができて便利です。

  /*
   ** vuetify module configuration
   ** https://github.com/nuxt-community/vuetify-module
   */
  vuetify: {
    customVariables: ['~/assets/variables.scss'],
    theme: {
      dark: false,
      themes: {
        light: {
          primary: '#FFFFFF',
          secondary: '#6B38FB'
        },
        dark: {
          primary: '#FFFFFF',
          secondary: '#6B38FB'
        }
      }
    }
  },

client/layouts/default.vue

  • app barのタイトルの横にハンバーガーメニューを表示させましょう
    <v-app-bar
      color="primary"
      :clipped-left="$vuetify.breakpoint.lgAndUp"
      clipped-right
      app
    >
      <v-app-bar-nav-icon @click.stop="toggleDrawer" /> <- この行を追加
      <v-toolbar-title
        style="width: 300px"
        class="ml-0 pl-4"
        @click="$router.push('/')"
      >
  • ユーザーアバターとログアウトボタンを表示させましょう
    • 以下のコードをの閉じタグの後に追加しましょう
      <v-spacer />
      <v-menu
        offset-y
        transition="scroll-y-transition"
        :close-on-click="true"
        :close-on-content-click="true"
      >
        <template v-slot:activator="{ on }">
          <v-btn icon large class="mr-1" v-on="on">
            <v-avatar size="38px" item>
              <v-icon dark x-large>mdi-account-circle</v-icon>
            </v-avatar>
          </v-btn>
        </template>
        <v-list dense>
          <v-list-item
            v-for="(item, index) in avatarMenuItems"
            :key="index"
            @click="avatarMenuItemClick(item.href)"
          >
            <v-list-item-action>
              <v-icon>{{ item.icon }}</v-icon>
            </v-list-item-action>
            <v-list-item-content>
              <v-list-item-title>
                {{ item.text }}
              </v-list-item-title>
            </v-list-item-content>
          </v-list-item>
        </v-list>
      </v-menu>
  • navigation drawerを表示させましょう
    • 以下のコードをの閉じタグの後に追加しましょう
    <v-navigation-drawer
      v-model="drawer"
      :clipped="$vuetify.breakpoint.lgAndUp"
      app
    >
    </v-navigation-drawer>
  • footerを表示させましょう
    • 以下のコードをの閉じタグの後に追加しましょう
    <v-footer app>
      <span>&copy; {{ new Date().getFullYear() }} {{ author }}</span>
    </v-footer>
  • scriptを修正しましょう
    • dataに drawer / avatarMenuItems / authorプロパティを追加します
      • avatarをクリックすると出てくるメニューは今後追加する可能性も考慮して、配列で定義してあります。
    • methodsプロパティを追加して、toggleDrawer / avatarMenuItemClick関数を定義しましょう
<script>
export default {
  data() {
    return {
      drawer: null,
      title: 'GDrive Manager',
      avatarMenuItems: [
        { icon: 'mdi-logout', text: 'Logout', href: '/logout' }
      ],
      author: 'Kumanote,LLC.'
    }
  },
  methods: {
    toggleDrawer() {
      this.drawer = !this.drawer
    },
    avatarMenuItemClick(href) {
      switch (href) {
        case '/logout':
          window.alert('TODO implement logout')
          break
        default:
          this.$router.push(href)
          break
      }
    }
  }
}
</script>

全体としては以下のようになります。

<template>
  <v-app dark>
    <v-app-bar
      color="primary"
      :clipped-left="$vuetify.breakpoint.lgAndUp"
      clipped-right
      app
    >
      <v-app-bar-nav-icon @click.stop="toggleDrawer" />
      <v-toolbar-title
        style="width: 300px"
        class="ml-0 pl-4"
        @click="$router.push('/')"
      >
        <span class="hidden-sm-and-down">{{ title }}</span>
      </v-toolbar-title>
      <v-spacer />
      <v-menu
        offset-y
        transition="scroll-y-transition"
        :close-on-click="true"
        :close-on-content-click="true"
      >
        <template v-slot:activator="{ on }">
          <v-btn icon large class="mr-1" v-on="on">
            <v-avatar size="38px" item>
              <v-icon dark x-large>mdi-account-circle</v-icon>
            </v-avatar>
          </v-btn>
        </template>
        <v-list dense>
          <v-list-item
            v-for="(item, index) in avatarMenuItems"
            :key="index"
            @click="avatarMenuItemClick(item.href)"
          >
            <v-list-item-action>
              <v-icon>{{ item.icon }}</v-icon>
            </v-list-item-action>
            <v-list-item-content>
              <v-list-item-title>
                {{ item.text }}
              </v-list-item-title>
            </v-list-item-content>
          </v-list-item>
        </v-list>
      </v-menu>
    </v-app-bar>
    <v-navigation-drawer
      v-model="drawer"
      :clipped="$vuetify.breakpoint.lgAndUp"
      app
    >
    </v-navigation-drawer>
    <v-content>
      <v-container>
        <nuxt />
      </v-container>
    </v-content>
    <v-footer app>
      <span>&copy; {{ new Date().getFullYear() }} {{ author }}</span>
    </v-footer>
  </v-app>
</template>

<script>
export default {
  data() {
    return {
      drawer: null,
      title: 'GDrive Manager',
      avatarMenuItems: [
        { icon: 'mdi-logout', text: 'Logout', href: '/logout' }
      ],
      author: 'Kumanote,LLC.'
    }
  },
  methods: {
    toggleDrawer() {
      this.drawer = !this.drawer
    },
    avatarMenuItemClick(href) {
      switch (href) {
        case '/logout':
          window.alert('TODO implement logout')
          break
        default:
          this.$router.push(href)
          break
      }
    }
  }
}
</script>

最後に、ローカルサーバー起動してみて、表示を確認してみましょう。
ターミナルで以下を実行してみてください。

% yarn run dev

http://localhost:3000/にアクセスして、以下のように表示されていればOKです。

default-layout-min.png

global side navigation、global header, global footerなどを組み込むことができました。

今回はこれで以上になります。

今回作成したファイルは GithubのPull Request からもご覧いただけます。

次回は、ログイン用の画面のレイアウトを作成して、ログイン画面を作っていこうと思います。