👻

投稿(画像、メモ)by vue3+firebase(storage,firestore)備忘録

2021/12/29に公開

はじめに

投稿するための作成画面と投稿したリストを表示する画面を作りました。
画像を含めているため、storage(画像登録)とfirestoreを利用。
これベースで応用は出来るかなと思っています。
デザインは考慮していません。

環境

完成

フォルダ構成

firestore構成

ユースケース

firebaseがメインなので、細かいケースは作りません。

  • 投稿の新規作成
  • 投稿の一覧取得

コード(環境構築部分は除く)

トップページ(親コンポーネント)

top.vue
<template>
<div class="ly_top">
  <h1>投稿</h1>
  <div class="ly_top_inner">
    <div v-if="displayList">
      <button @click="toCreate">新規作成</button>
    </div>
    <div v-else>
      <button @click="toList">戻る</button>
    </div>

    <div v-if="displayList">
      <list></list>
    </div>
    <div v-if="displayCreate">
      <create></create>
    </div>
  </div>
</div>
</template>
<script lang="ts">
import { ref } from 'vue'
import create from './create.vue'
import list from './list.vue'
export default({
  components:{
    create,
    list
  },
  setup() {
    const displayList = ref<boolean>(true)
    const displayCreate = ref<boolean>(false)

    const toCreate=()=>{
      displayList.value = false
      displayCreate.value = true
    }
    const toList=()=>{
      displayList.value = true
      displayCreate.value = false
    }

    return{
      displayList,
      displayCreate,
      toCreate,
      toList
    }
    
  },
})
</script>
<style lang="scss">
  h1{
    padding:15px;
    background-color:blue;
    color:white;
  }
  .ly_top_inner{
    max-width: 1000px;
    margin:auto;
  }
</style>

投稿作成部分(子コンポーネント)

create.vue
<template>
  <h2>作成</h2>
  <div class="ly_input">
    <div class="ly_input_inner">
      タイトル:<input type="text" v-model="title"><br>
    </div>
    <div class="ly_input_inner">
      メモ:<input type="text" v-model="memo"><br>
    </div>
    <div class="ly_input_inner">
      画像:<input type="file" accept="image/jpeg,image/png,image/gif" @change="uploadFile">  
    </div>
    <button @click="create">登録</button>
  </div>
</template>
<script lang="ts">
import { defineComponent,ref } from 'vue'
import { createFirebase} from './firebase'

export default defineComponent({
  setup() {
    const title = ref<string>()
    const memo = ref<string>()
    const fileData =ref<string>()

    const uploadFile = (event:any)=>{
      fileData.value = event.target.files[0]
    }
    const create=()=>{
      if(!title.value || !memo.value || !fileData.value ){
        alert ('すべて入力してください。')
        return
      }
      createFirebase(title.value,memo.value,fileData.value)
    }
    return{
      create,
      title,
      memo,
      uploadFile,
      fileData
    }
  },
})
</script>
<style lang="scss">
h2{
  border-left:10px solid blue;
  padding-left:5px; ;
}

.ly_input{
  padding:10px;
  max-width: 900px;
  margin:auto;
}
.ly_input_inner{
  margin:10px;
}
</style>

投稿リスト部分(子コンポーネント)

list.vue
<template>
  <h2>リスト</h2>
  <div class="ly_article">
    <div class="ly_article_inner"  v-for="article in articles" :key="article">
      <div class="bl_article">{{ article.title }}</div>
      <div class="bl_article"><img :src=article.filePath></div>
      <div class="bl_article">{{ article.memo }}</div>
    </div>
  </div>
</template>
<script>
import { defineComponent, onMounted ,ref } from 'vue'
import { fetchFirebase } from './firebase'

export default defineComponent({
  setup() {
    const articles =ref()
    onMounted(()=>{
      fetchFirebase()
        .then((data)=>{
          articles.value = data
        })
    })
    return{
      articles
    }    
  },
})
</script>
<style lang="scss">
h2{
  border-left:10px solid blue;
  padding-left:5px; ;
}
.ly_article{
  display: flex;
  flex-wrap: wrap;
  padding:10px;
  max-width: 1000px;
  margin:auto;
  .ly_article_inner{
    flex:0 0 250px;
    padding: 20px;
    margin:5px;
    background-color: rgb(197, 197, 243);
  }
  .bl_article{
    text-align: center;
  }
  
}
</style>

firebase 操作部分

  • 登録(createFirebase)
  • データ取得(fetchFirebase)
     ・・・画像は、firestoreから取得。登録時に画像pathをfirestoreに保存
firebase.ts
import { getFirestore, addDoc,collection,serverTimestamp, getDocs } from "firebase/firestore";
import { getDownloadURL, getStorage, ref, uploadBytesResumable } from "firebase/storage";

export const createFirebase=(title:string,memo:string,fileData:any)=>{
  const metadata={
    contentType: 'image/jpeg',
  }
  const storage = getStorage();
  const imageRef = ref(storage, '/' + title);
  uploadBytesResumable(imageRef, fileData, metadata)
    .then((snapshot) => {
      getDownloadURL(snapshot.ref)
        .then((url) => {
          addDoc(collection(getFirestore(), "articles"), {
            title: title,
            memo: memo,
            filePath : url,
            createDate: serverTimestamp()
          })
            .then(()=>{
              console.log('success')
            })
            .catch((e)=>{
              console.log('fail',e)
            })
        });
    })
    .catch((error) => {
      console.error('Upload failed', error);
    });
}

export const fetchFirebase=async()=>{
  const data:Array<any>=[]
  const querySnapshot = await getDocs(collection(getFirestore(), "articles"))
  querySnapshot.forEach((doc) => {
    // doc.data() is never undefined for query doc snapshots
    data.push(doc.data())
  })
  return data
}

以上になります。

Discussion