Vue.js + Firebase を用いてポートフォリオ作ってみた

※ 本投稿は[TechCommit Advent Calendar 2019](https://qiita.com/advent-calendar/2019/tech-commit) 11日目の記事です。

## はじめに
普段はWeb製作会社でコーダーのアルバイトをしている大学4回生です。内定先の会社でVue.jsを使っているということもあり、Vue.jsで簡単に何か作成してみようと思いポートフォリオを作成しました。

今回はVue.js + Vuetify + Vue Router を用いてSPAのポートフォリオを作成し、Firebaseにホスティングしたのでその流れ等について書いています!

実際に作成したポートフォリオ
https://vue-portfolio-25939.firebaseapp.com

![無題のプレゼンテーション.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298333/731ac9c5-c712-23a6-79a1-1546fa1274f4.jpeg)
![無題のプレゼンテーション (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298333/805c3169-6eda-5f7e-2efe-9bf3f9e1b610.jpeg)


## 環境
- Vue CLI v4.1.1
- Node.js v12.6.0
- npm v6.9.0

## ポートフォリオを作成する前に考えたこと

### 何を掲載するか

```

- トップページ
- 簡単な自己紹介
- 作品
- スキル
- お問い合わせ
```

ざっとこんな感じで構成することにしました。

### Vue.jsでどのようなものが作れるかを確認
「Vue.js ポートフォリオ」で検索すると多くの記事が出てくるので、それを一通り読みました。
<br>
**参考になった記事**

- [フロント未学習の大学生が1週間でVue.jsを使ったポートフォリオを作った話](https://qiita.com/p1ass/items/a01578b782f17f573510)
- [Vueを学び、SPA対応のポートフォリオサイトを自作するまでの道のり](https://qiita.com/guru_taka/items/c54b11b9cc2c924e23c1)
- [【Vue.js入門】独学1週間でSPA対応の簡易ポートフォリオサイトを自作してみた！](https://yusai.me/vue-js-portfolio/)
- [Vue.jsでドラクエ風のポートフォリオを作った話](https://qiita.com/iwathi/items/8f054d10590f32b438a4)
- [Vue.js + Firebaseでポートフォリオを作ろう！](https://qiita.com/sosmii/items/f097bf8b2209b9d75d1a)
- [Vue.js + Firebase functionsでお問い合わせフォームを作成する](https://qiita.com/ryo2132/items/7cdd6c86dd418095f74a)

### デザインを考える
[Cacoo](https://cacoo.com)というサービスを使って簡単にワイヤーフレームを作ってみました。

シート6枚までは無料で使えるので今回作成するポートフォリオのようにページ数の少ないコンテンツであれば無料枠でも充分です。

実際に作成した簡単なワイヤーフレーム
PC
<img width="840" alt="スクリーンショット 2019-11-10 9.08.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298333/8b5cd6c8-64d4-98af-bc65-9c1da14a63cb.png">

SP
<img width="810" alt="スクリーンショット 2019-11-10 9.31.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298333/3d9311b6-6cf6-b68a-97e7-b74d69174a5f.png">

実装していくと「やっぱりここをこういう風にしたい」と思ったりもして、多少変更しました。


##実際にポートフォリオを作成していく

### 仕様

- デザインはVuetifyを使用
- SPA構築（Vue Router）
- テキストアニメーション
- モーダルウィンドウ
- お問い合わせフォーム（Firebase Functionsを用いてメールフォーム作成）
- アイコンはfont-awesomeを使用

### Vuetify + Vue Router

`vue add vuetify`でvuetifyを追加、モードはDefaultを選択。
Vue Routerに関しては、`vue create`時にRouterを選択し、vue-routerを使えるようにしました。

```src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Top from '@/components/Top'
import Profile from '@/components/Profile'
import Work from '@/components/Work'
import Skill from '@/components/Skill'
import ContactForm from '@/components/ContactForm'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    component: Top
  },
  {
    path: '/profile',
    component: Profile
  },
  {
    path: '/works',
    component: Work
  },
  {
    path: '/skills',
    component: Skill
  },
  {
    path: '/contact',
    component: ContactForm
  },
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

```

```src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import vuetify from './plugins/vuetify';
import '@fortawesome/fontawesome-free/css/all.css'

Vue.config.productionTip = false

new Vue({
  router,
  vuetify,
  render: h => h(App)
}).$mount('#app')

```

単一ファイルコンポーネントを用いての実装だったので、`App.vue`には以下のように記述。

```src/App.vue
<template>
  <v-app>
    <v-content>
      <header-menu></header-menu>
      <router-view/>
    </v-content>
  </v-app>
</template>

<script>
import HeaderMenu from './components/HeaderMenu';

export default {
  name: 'App',

  components: {
    HeaderMenu,
  },

  data: () => ({
    //
  }),
};
</script>
```

ちなみにヘッダーメニュー部分は以下のような感じです。`hidden-sm-and-down`、`hidden-md-and-up`をクラスに付与することにより、smサイズの時にテキストを隠し、アイコンを表示。mdサイズの時にアイコンを隠し、テキストを表示させています。

```src/components/HeaderMenu.vue
<template>
  <v-toolbar
    color="teal"
    dark
    class="d-flex justify-center"
  >
    <v-toolbar-items v-for="(item, index) in items" :key="index">
        <v-btn text class="hidden-sm-and-down">
          <router-link v-bind:to="item.path">
            {{ item.title }}
          </router-link>
        </v-btn>

        <v-btn text class="hidden-md-and-up">
          <router-link v-bind:to="item.path">
            <v-icon medium>{{ item.icon }}</v-icon>
          </router-link>
        </v-btn>
    </v-toolbar-items>
  </v-toolbar>
</template>

<script>
export default {
  name: 'app',
  data () {
    return {
      items: [
        {
          title: 'トップ',
          icon: 'fas fa-home',
          path: '/'
        },
        {
          title: 'プロフィール',
          icon: 'far fa-id-badge',
          path: '/profile'
        },
        {
          title: '作品',
          icon: 'fas fa-folder',
          path: '/works'
        },
        {
          title: 'スキル',
          icon: 'fas fa-code',
          path: '/skills'
        },
        {
          title: 'お問い合わせ',
          icon: 'far fa-envelope',
          path: '/contact'
        }
      ]
    }
  }
}
</script>

```

### お問い合わせフォーム

送信サーバとしてgmailを使うため、環境変数に以下を追加

```
$ firebase functions:config:set gmail.email="gmailID" gmail.password="gmailPassword" admin.email="adminAddress"
```

バリデーションはv-text-fieldのrulesオプションを使って実装しました。

```ContactForm.vue
(略)

<v-text-field
  v-model="contactForm.name"
  :rules="contactFormValidation.nameRules"
  label="名前"
  required
></v-text-field>

(略)
<script>
import { functions } from '@/plugins/firebase'

export default {
    data: () => ({
      contactForm: {
        name: '',
      },
      contactFormValidation: {
        valid: false,
        nameRules: [v => !!v || '名前は必須項目です'],
      },
    })
}
</script>
(略)
```

### ビルドとデプロイ
`npm run build`でビルドし、`firebase deploy`でデプロイ完了です！

## まとめ
今回は初めて、vue.jsを用いてポートフォリオを作成しました。今後はサーバーサイドと連携させたものを作りたいと思っています！

間違っているところなどありましたら、コメントで教えていただけると幸いです。