Vue.jsとFirebaseでポートフォリオサイトを作った話

# はじめに

おはようございます。こんにちは。こんばんは。
Watataku です。:blush:
今回は題名にもあるようにVue.jsとFirebaseでポートフォリオサイトを作った話をします。

# 成果物

- [https://watataku-portfolio.web.app](https://watataku-portfolio.web.app)

〜〜 [Github](https://github.com/watataku8911/watataku-portfolio)にもコードを公開しています。〜〜

# 対象者

- ポートフォリオサイトを作リたい方
- Vue.js を触ったことがない方
- Firebase を使ったことがない方

# このサイトでやってないこと

- IE 対応

# 今回の用件

### デザイン

- レスポンシブ！スマホ・PC 両対応

Web デザイン自体は得意ではないのですが、デザインはがんばりたい。

### 機能

- ① 自分の作ったアプリやサイトを載せるページ
- ② 自己紹介やスキルセット書くページ
- ③ コンタクトフォーム(Slack 送信)
- ④ Web から新しく作成したアプリやサイトを追加できる管理ページ

基本的には自己紹介(スキルセット)や自分の作ったアプリやサイトを載せるサイトです。

# Firebase の機能と作ったものの対応

[https://qiita.com/watataku8911/items/94274031a4e49c9cc9a0](https://qiita.com/watataku8911/items/94274031a4e49c9cc9a0)←firebase の導入法

| 使った Firebase の機能 | 作ったポートフォリオサイトの機能 |
| :--------------------- | :------------------------------- |
| Authentication         | 管理者のログイン                 |
| Firestore              | 作品情報                         |
| Storage                | 作品画像                         |
| Hosting                | デプロイ先                       |

# 作ったものと解説
ここから、作ったものの機能ベースでVueやFirebaseのどんな機能を使ったのか紹介します。

## 管理画面のログイン

### Firebase Auth でログイン

最初に Firebase コンソールの Auth 機能でメールアドレス・パスワードログインを有効化します。
![watataku-portfolio_-_Authentication_-_Firebase_コンソール.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116696/94ec3e03-7931-f2ca-97ca-f6e635188360.png)


```vue:login.vue

<template>
	<div class="login">
		<input type="text" v-model="email">
		<input type="text" v-model="passwd">
		<button @click="login">ログイン</button>
	</div>
</template>

<script>
import firebase from "firebase"
export default {
	data() {
		return {
			email: "",
			passwd: ""
		}
	},
	methods: {
		login() {
			firebase
				.auth()
				.signInWithEmailAndPassword(this.email, this.passwd)
				.then(() => {
					// ログイン成功
				})
				.catch(() => {
					// ログイン失敗
				});
		}
	}
}
</script>
```

上記はメールアドレス/パスワードログインするためのサンプルコードです。
もっと詳しく知りたい方はこちらの記事をご覧ください。
[Vue.js + Firebase を使って爆速でユーザ認証を実装する](https://qiita.com/sin_tanaka/items/ea149a33bd9e4b388241)

## 自分の作ったアプリやサイトの情報を Firestore(DB)に格納する

DB に格納する物は以下。

- アプリ/サイト名
- アプリ/サイトの URL
- アプリ/サイトの画像(画像の URL)
- アプリ/サイトの説明
- カテゴリ
- 作成時間
- 更新時間
- 削除時間
- 削除フラグ

### Firebase Storage にファイルをアップロード

```js

onDrop(event) {
	const fileList = event.target.files || event.dataTransfer.files;
	let errFlg;
	let blob;

	if (fileList[0].type == "image/png") {
		blob = new Blob(fileList, { type: "image/png" });
		errFlg = false;
	} else if (fileList[0].type == "image/jpg") {
		blob = new Blob(fileList, { type: "image/jpg" });
		errFlg = false;
	} else if (fileList[0].type == "image/jpeg") {
		blob = new Blob(fileList, { type: "image/jpeg" });
		errFlg = false;
	} else if (fileList[0].type == "image/gif") {
		blob = new Blob(fileList, { type: "image/gif" });
		errFlg = false;
	} else {
		errFlg = true;
	}

	if (!errFlg) {
		this.uploadImage(blob, fileList);
	}
},
uploadImage(blob, fileList) {
	const storageRef = firebase.storage().ref();
	const mountainsRef = storageRef.child(
		fileList[0].name
	);
	mountainsRef.put(blob).then(() => {
	mountainsRef
		.getDownloadURL()
		.then(url => {
			// 変数:urlにアップロードした画像の保存先URLが帰ってくる。そのURLをFirestoreに突っ込む。
		})
	});
},
```

### Firestore にデータを格納

```js

const db = firebase.firestore();
db.collection("works").add({
	title: *********,
	url: *******,
	description: **********,
	imagePath: **********,
	category_id: *********,
	created_at: firebase.firestore.Timestamp.now(),
	updated_at: firebase.firestore.Timestamp.now(),
	deleted_flg: false,
	deleted_at: ""
}).then(() => {
	// 成功
}).catch(() => {
	// 失敗
})
```

### Firestore のデータを取得する

```js
const db = firebase.firestore();
db.collection("works")
  .get()
  .then((query) => {
    const workList = [];
    query.forEach((doc) => {
      const data = doc.data();
      workList.push([doc.id, data]);
    });
  });
```

最終的に出来上がった作品一覧ページ ↓
![Watataku’s_ポートフォリオサイト___WOKES.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116696/762190ca-3f38-4623-f4ba-64eb4ddf8b7b.png)

## 作品詳細ページ

### VueRouter による個別詳細ページ

```vue

<router-link :to="{ name: 'detail', params: { id: '8aPIoNyUdP944zOcBUef' }}">リンク</router-link>
```

```js

export default {
  name: "detail",
  data() {
    return {
      id: this.$router.params.id, // 8aPIoNyUdP944zOcBUef
    };
  },
  created() {
    // ドキュメントIDのデータを取得
    db.collection("works")
      .doc(this.id)
      .get()
      .then((snapshot) => {
        const data = snapshot.data();
      });
  },
};
```

[https://qiita.com/watataku8911/items/ac040f4671c0f9a62bd4](https://qiita.com/watataku8911/items/ac040f4671c0f9a62bd4)← Firestore などの詳しいこと。

最終的に出来上がった作品詳細ページ(取得したドキュメント ID によって変わる。)↓
![Watataku’s_ポートフォリオサイト___DETAILE.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116696/481928ab-95b0-8ebe-f7ac-70150037370a.png)



## コンタクトフォーム

### Slack 送信

以下のページにアクセスし、WEBHOOKURL を取得する。
[https://api.slack.com/](https://api.slack.com/)
また、詳細な取得方法については[こちら](https://api.slack.com/messaging/webhooks)をご覧下さい。

以下が実際に slack に送信するコードです。

```js

const payload = {
	text: "送信したい文言"
};

const WEBHOOK_URL = "取得したWEBHOOKURLを記載する";
fetch(WEBHOOK_URL, {
	method: "POST",
	body: JSON.stringify(payload),
}).then(() => {
	alert("送信が完了しました。追ってご連絡いたします🙌");
});
```

# 今後取り組みたいこと

- 作品ごとに OGP 対応する

# まとめ

- Firebase はいいぞー:point_up_2:
- ポートフォリオサイトをがっつり作ると Vue と Firebase の基本機能を一通り習得できるよ。:point_up_2:

# 最後に

この記事を見てポートフォリオサイト作ってみようかなー、って思ってくれる人がいたらとても嬉しいです。
以上が Vue.js と Firebase でポートフォリオサイトを作った話でした。
もしまちがい等などがございましたら教えていただけると幸いです。:smiley: