Help us understand the problem. What is going on with this article?

Vue.js+Firebaseプロジェクト作成(Hosting、Authentication、Firestore、Storage、Functions)

書いてあること

  • Vue.jsプロジェクトからFirebaseの下記機能を利用する
    • Hosting
    • Authentication
    • Cloud Firestore
    • Cloud Storage
      • Cloud Functions`

環境

  • CentOS Linux release 7.6.1810 (Core)
  • Node.js v10.16.0
  • Npm 6.10.0
  • Vue 3.9.1
  • Firebase CLI 7.1.1

前提

下記で作成したVue.jsプロジェクト、Firebaseプロジェクトを利用する。
Vue.jsのプロジェクト作成
Firebaseプロジェクト作成方法

作成したプロジェクト

↓に置いてあります。
vue-firebase-project

Firebase初期化

プラグインでFirebaseの初期化を行う。

src/plugins/firebase.js
import firebase from 'firebase'

const firebaseConfig = {
  apiKey: '<apiKey>',
  authDomain: '<authDomain>',
  databaseURL: '<databaseURL>',
  projectId: '<projectId>',
  storageBucket: '<storageBucket>',
  messagingSenderId: '<messagingSenderId> ',
  appId: '<appId>',
}
firebase.initializeApp(firebaseConfig)

export default firebase

AuthenticationによるGoogleログイン認証

ルーティング設定

src/router.js
import firebase from '@/plugins/firebase'
import Login from './views/auth/login.vue'
import Error from './views/auth/error.vue'
import Home from './views/home.vue'

()

    {
      path: '/auth/login',
      name: 'auth/login',
      component: Login,
    },
    {
      path: '/auth/error',
      name: 'auth/error',
      component: Error,
    },
    {
      path: '/',
      name: 'home',
      component: Home,
    },

()

router.beforeResolve((to, from, next) => {
  const funcLoginAuthentication = firebase.functions().httpsCallable('funcLoginAuthentication')

  funcLoginAuthentication()
    .then(res => {
      const user = res.data
      // Googleログインしている場合
      if (user.isLogin) {
        // xxxxx.comドメインの場合
        if (to.name === 'auth/login') {
          next({ name: 'home' })
        } else {
          next()
        }
      } else {
        // xxxxx.comドメイン以外の場合
        if (to.name === 'auth/error') {
          next()
        } else {
          next({ name: 'auth/error' })
        }
      }
    })
    .catch(() => {
      // Googleログインしていない場合
      if (to.name === 'auth/login') {
        next()
      } else {
        next({ name: 'auth/login' })
      }
    })
})

export default router

ログイン

src/views/auth/login.vue
<template>
  <div class="login">
    <h1>Login</h1>
    <img alt="Vue logo" src="../../assets/logo.png" />
    <Login />
  </div>
</template>

<script>
import Login from '@/components/auth/Login.vue'

export default {
  name: 'login',
  components: {
    Login,
  },
}
</script>
src/components/auth/Login.vue
<template>
  <div class="login">
    <button @click="doLogin()">Login</button>
  </div>
</template>

<script>
import * as firebase from 'firebase'

export default {
  name: 'Login',
  methods: {
    doLogin: () => {
      const provider = new firebase.auth.GoogleAuthProvider()
      // Googleアカウントの選択を強制
      provider.setCustomParameters({ prompt: 'select_account consent' })
      firebase
        .auth()
        .signInWithRedirect(provider)
        .then()
    },
  },
}
</script>

ログアウト

src/components/auth/Logout.vue
<template>
  <div class="footer" style="margin-top: 50px;">
    <button @click="doLogout()">Logout</button>
  </div>
</template>

<script>
import firebase from '@/plugins/firebase'

export default {
  methods: {
    doLogout() {
      firebase
        .auth()
        .signOut()
        .then(() => {
          alert('ログアウトしました。')
          this.$router.push({ name: 'auth/login' })
        })
        .catch(error => {
          alert(error)
        })
    },
  },
}
</script>

ログインエラー

src/views/auth/error.vue
<template>
  <div class="error">
    <h1 style="color: tomato;">Error</h1>
    <p>指定したメールアドレスではログインできません</p>
    <Logout />
  </div>
</template>

<script>
import Logout from '@/components/auth/Logout.vue'

export default {
  name: 'error',
  components: {
    Logout,
  },
}
</script>

ホーム画面

src/views/home.vue
<template>
  <div class="home">
    <h1>Home</h1>
    <img alt="Vue logo" src="../assets/logo.png" />
    <Home :lists="this.lists" />
    <Logout />
  </div>
</template>

<script>
import Home from '@/components/Home.vue'
import Logout from '@/components/auth/Logout.vue'

export default {
  name: 'home',
  data() {
    return {
      lists: [
        { id: 1, name: 'スタッフ一覧', url: '/staff' },
        { id: 2, name: 'タスク一覧', url: '/task' },
        { id: 3, name: 'ストレージ', url: '/storage' },
      ],
    }
  },
  components: {
    Home,
    Logout,
  },
}
</script>
src/components/Home.vue
<template>
  <div class="home">
    <div v-for="item in lists" :key="item.id" style="margin-top: 20px; margin-bottom: 20px;">
      <router-link :to="item.url">
        {{ item.name }}
      </router-link>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Home',
  props: ['lists'],
}
</script>

ログイン状況確認(Functions)

functions/index.js
import * as functions from 'firebase-functions'

()

export const funcLoginAuthentication = functions.https.onCall((data, context) => {
  const user = context.auth.token
  const isLogin = user.email.indexOf('@xxxxx.com') !== -1
  return {
    isLogin: isLogin,
    uid: user.uid,
    name: user.name,
    email: user.email,
    picture: user.picture,
  }
})

コンポーネントからFirestore操作

コレクション・ドキュメントを準備

image.png

ルーティング設定

src/router.js
import StaffIndex from './views/staff/index.vue'
import StaffAdd from './views/staff/add.vue'
import StaffUpdate from './views/staff/update.vue'

()

    {
      path: '/staff',
      name: 'staff',
      component: StaffIndex,
    },
    {
      path: '/staff/add',
      name: 'staff/add',
      component: StaffAdd,
    },
    {
      path: '/staff/update/:id',
      name: 'staff/update',
      component: StaffUpdate,
    },

スタッフ一覧

src/views/staff/index.vue
<template>
  <div class="staff-index">
    <h1>Staff</h1>
    <div style="margin-bottom: 30px;">
      <router-link to="/">Home</router-link>
    </div>
    <StaffLists />
    <Logout />
  </div>
</template>

<script>
import StaffLists from '@/components/staff/StaffLists.vue'
import Logout from '@/components/auth/Logout.vue'

export default {
  name: 'staff-index',
  components: {
    StaffLists,
    Logout,
  },
}
</script>
src/components/staff/StaffLists.vue
<template>
  <div class="staff-lists">
    <div>
      <button @click="doAdd()">Add</button>
    </div>
    <table style="border: solid #333 1px; margin: 30px auto 0;">
      <thead>
        <tr>
          <th>Index</th>
          <th>id</th>
          <th>name</th>
          <th>mail</th>
          <th>age</th>
          <th>createdAt</th>
          <th></th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(item, index) in items" :key="item.id">
          <td>{{ index }}</td>
          <td>{{ item.id }}</td>
          <td>{{ item.name }}</td>
          <td>{{ item.mail }}</td>
          <td>{{ item.age }}</td>
          <td>{{ item.createdAt | format }}</td>
          <td>
            <button @click="doUpdate(item.id)">Update</button>
            <button @click="doDelete(item.id)">delete</button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
import firebase from '@/plugins/firebase'
import moment from 'moment'

export default {
  name: 'StaffLists',
  data() {
    return {
      db: null,
      items: [],
    }
  },
  methods: {
    doAdd() {
      this.$router.push({ name: 'staff/add' })
    },
    doUpdate(modId) {
      this.$router.push({ name: 'staff/update', params: { id: modId } })
    },
    doDelete(delId) {
      if (!confirm('削除してよろしいですか?')) {
        return
      }
      this.db
        .collection('people')
        .doc(delId)
        .delete()
    },
  },
  created() {
    this.db = firebase.firestore()
    this.db
      .collection('people')
      // .orderBy('age')
      .orderBy('age', 'desc')
      .onSnapshot(querySnapshot => {
        this.items = [] // 取得結果を初期化
        querySnapshot.forEach(doc => {
          // console.log(doc.id) // 自動ID
          // console.log(doc.data()) // 各フィールド
          // ドキュメントの自動ID、各フィールドを連結して配列へ追加
          this.items.push(Object.assign({ id: doc.id }, doc.data()))
        })
      })
  },
  filters: {
    // 日付書式指定
    format: data => {
      return moment(data.toDate()).format('YYYY-MM-DD HH:mm:ss')
    },
  },
}
</script>

スタッフ追加

src/views/staff/add.vue
<template>
  <div class="staff-add">
    <h1>Staff Add</h1>
    <StaffAddForm />
    <Logout />
  </div>
</template>

<script>
import StaffAddForm from '@/components/staff/StaffAddForm.vue'
import Logout from '@/components/auth/Logout.vue'

export default {
  name: 'staff-add-form',
  components: {
    StaffAddForm,
    Logout,
  },
}
</script>
src/components/staff/StaffAddForm.vue
<template>
  <div class="staff-add-form">
    <table style="border: solid #333 1px; margin: 10px auto 0;">
      <tbody>
        <tr>
          <th>Name</th>
          <td>
            <input type="text" v-model="formData.name" />
          </td>
        </tr>
        <tr>
          <th>Mail</th>
          <td>
            <input type="text" v-model="formData.mail" />
          </td>
        </tr>
        <tr>
          <th>Age</th>
          <td>
            <input type="number" v-model="formData.age" />
          </td>
        </tr>
        <tr>
          <th></th>
          <td>
            <button @click="doAdd()">Add</button>
            <button @click="doCancel()">Cancel</button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
import firebase from '@/plugins/firebase'

export default {
  name: 'StaffAddForm',
  data() {
    return {
      db: null,
      people: null,
      formData: {
        name: null,
        mail: null,
        age: null,
        createdAt: null,
      },
    }
  },
  methods: {
    doAdd() {
      this.formData.createdAt = new Date()
      this.people
        .add(this.formData)
        .then(res => {
          alert(`Complete document add: ${res.id}`)
        })
        .catch(error => {
          alert(`Error document add: ${error}`)
        })
      this.$router.push({ name: 'staff' })
    },
    doCancel() {
      this.$router.push({ name: 'staff' })
    },
  },
  created() {
    this.db = firebase.firestore()
    this.people = this.db.collection('people')
  },
}
</script>

スタッフ更新

src/views/staff/update.vue
<template>
  <div class="staff-update">
    <h1>Staff Update</h1>
    <StaffUpdateForm />
    <Logout />
  </div>
</template>

<script>
import StaffUpdateForm from '@/components/staff/StaffUpdateForm.vue'
import Logout from '@/components/auth/Logout.vue'

export default {
  name: 'staff-update-form',
  components: {
    StaffUpdateForm,
    Logout,
  },
}
</script>
src/components/staff/StaffUpdateForm.vue
<template>
  <div class="staff-add-form">
    <table style="border: solid #333 1px; margin: 10px auto 0;">
      <tbody>
        <tr>
          <th>Name</th>
          <td>
            <input type="text" v-model="formData.name" />
          </td>
        </tr>
        <tr>
          <th>Mail</th>
          <td>
            <input type="text" v-model="formData.mail" />
          </td>
        </tr>
        <tr>
          <th>Age</th>
          <td>
            <input type="number" v-model="formData.age" />
          </td>
        </tr>
        <tr>
          <th></th>
          <td>
            <button @click="doUpdate()">Update</button>
            <button @click="doCancel()">Cancel</button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
import firebase from '@/plugins/firebase'

export default {
  name: 'StaffUpdateForm',
  data() {
    return {
      db: null,
      people: null,
      id: null,
      peopleData: null,
      formData: {
        name: null,
        mail: null,
        age: null,
        createdAt: null,
      },
    }
  },
  methods: {
    doUpdate() {
      this.formData.createdAt = new Date()
      this.people
        .doc(this.id)
        .set(this.formData)
        .then(() => {
          alert('Complete document Update')
        })
        .catch(error => {
          alert(`Error document Update: ${error}`)
        })
      this.$router.push({ name: 'staff' })
    },
    doCancel() {
      this.$router.push({ name: 'staff' })
    },
  },
  created() {
    this.db = firebase.firestore()
    this.people = this.db.collection('people')

    // Firebaseからデータ取得
    this.id = this.$route.params.id
    this.people
      .doc(this.id)
      .get()
      .then(doc => {
        if (doc.exists) {
          this.peopleData = doc.data()

          // 取得したデータをFormへ表示
          this.formData.name = this.peopleData.name
          this.formData.mail = this.peopleData.mail
          this.formData.age = this.peopleData.age
        } else {
          alert('Not Found Document')
          this.$router.push({ name: 'staff' })
        }
      })
      .catch(error => {
        console.log(`Error: ${error}`)
        this.$router.push({ name: 'staff' })
      })
  },
}
</script>

FunctionsからFirestore操作

コレクション・ドキュメントを準備

image.png

ルーティング設定

src/router.js
import TaskIndex from './views/task/index.vue'
import TaskAdd from './views/task/add.vue'
import TaskUpdate from './views/task/update.vue'

()

    {
      path: '/task',
      name: 'task',
      component: TaskIndex,
    },
    {
      path: '/task/add',
      name: 'task/add',
      component: TaskAdd,
    },
    {
      path: '/task/update/:id',
      name: 'task/update',
      component: TaskUpdate,
    },

タスク一覧

src/views/task/index.vue
<template>
  <div class="task-index">
    <h1>Task</h1>
    <div style="margin-bottom: 30px;">
      <router-link to="/">Home</router-link>
    </div>
    <TaskLists />
    <Logout />
  </div>
</template>

<script>
import TaskLists from '@/components/task/TaskLists.vue'
import Logout from '@/components/auth/Logout.vue'

export default {
  name: 'task-index',
  components: {
    TaskLists,
    Logout,
  },
}
</script>
src/components/task/TaskLists.vue
<template>
  <div class="task-lists">
    <button @click="doAdd()">Add</button>
    <table style="border: solid #333 1px; margin: 30px auto 0;">
      <thead>
        <tr>
          <th>Index</th>
          <th>id</th>
          <th>name</th>
          <th>description</th>
          <th>done</th>
          <th></th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(item, index) in items" :key="item.id">
          <td>{{ index }}</td>
          <td>{{ item.id }}</td>
          <td>{{ item.name }}</td>
          <td>{{ item.description }}</td>
          <td>{{ item.done }}</td>
          <td>
            <button @click="doUpdate(item.id)">Update</button>
            <button @click="doDelete(item.id)">delete</button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
import firebase from '@/plugins/firebase'

export default {
  name: 'TaskLists',
  data() {
    return {
      items: [],
    }
  },
  methods: {
    doAdd() {
      this.$router.push({ name: 'task/add' })
    },
    doUpdate(modId) {
      this.$router.push({ name: 'task/update', params: { id: modId } })
    },
    async doDelete(delId) {
      if (!confirm('削除してよろしいですか?')) {
        return
      }
      const funcDeleteTask = firebase.functions().httpsCallable('funcDeleteTask')
      await funcDeleteTask({
        id: delId,
      })
        .then(res => {
          console.log(res)
        })
        .catch(err => {
          console.log(err)
        })

      // データ取得
      const funcGetTaskLists = firebase.functions().httpsCallable('funcGetTaskLists')
      await funcGetTaskLists()
        .then(res => {
          this.items = res.data.items
        })
        .catch(err => {
          console.log(err)
        })
    },
  },
  created() {
    // データ取得
    const funcGetTaskLists = firebase.functions().httpsCallable('funcGetTaskLists')
    funcGetTaskLists()
      .then(res => {
        this.items = res.data.items
      })
      .catch(err => {
        console.log(err)
      })
  },
}
</script>

タスク追加

src/views/task/add.vue
<template>
  <div class="task-add">
    <h1>Task Add</h1>
    <TaskAddForm />
    <Logout />
  </div>
</template>

<script>
import TaskAddForm from '@/components/task/TaskAddForm.vue'
import Logout from '@/components/auth/Logout.vue'

export default {
  name: 'staff-add-form',
  components: {
    TaskAddForm,
    Logout,
  },
}
</script>
src/components/task/TaskAddForm.vue
<template>
  <div class="staff-add-form">
    <table style="border: solid #333 1px; margin: 10px auto 0;">
      <tbody>
        <tr>
          <th>Name</th>
          <td>
            <input type="text" v-model="formData.name" />
          </td>
        </tr>
        <tr>
          <th>Description</th>
          <td>
            <textarea rows="3" v-model="formData.description" />
          </td>
        </tr>
        <tr>
          <th>Done</th>
          <td>
            <input type="radio" id="false" value="false" v-model="formData.done" />
            <label for="false">False</label>
            <input type="radio" id="true" value="true" v-model="formData.done" />
            <label for="true">True</label>
          </td>
        </tr>
        <tr>
          <th></th>
          <td>
            <button @click="doAdd()">Add</button>
            <button @click="doCancel()">Cancel</button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
import firebase from '@/plugins/firebase'

export default {
  name: 'TaskAddForm',
  data() {
    return {
      formData: {
        name: null,
        description: null,
        done: null,
      },
    }
  },
  methods: {
    doAdd() {
      const funcAddTask = firebase.functions().httpsCallable('funcAddTask')
      this.formData.done = this.formData.done === 'true'
      funcAddTask({
        task: this.formData,
      })
        .then(res => {
          alert(`Complete document add: ${res.id}`)
          console.log(res)
        })
        .catch(error => {
          alert(`Error document add: ${error}`)
        })
      this.$router.push({ name: 'task' })
    },
    doCancel() {
      this.$router.push({ name: 'task' })
    },
  },
  created() {
    this.db = firebase.firestore()
    this.people = this.db.collection('people')
  },
}
</script>

タスク更新

src/views/task/update.vue
<template>
  <div class="task-update">
    <h1>Task Update</h1>
    <TaskUpdateForm />
    <Logout />
  </div>
</template>

<script>
import TaskUpdateForm from '@/components/task/TaskUpdateForm.vue'
import Logout from '@/components/auth/Logout.vue'

export default {
  name: 'staff-update-form',
  components: {
    TaskUpdateForm,
    Logout,
  },
}
</script>
src/components/task/TaskUpdateForm.vue
<template>
  <div class="task-update-form">
    <table style="border: solid #333 1px; margin: 10px auto 0;">
      <tbody>
        <tr>
          <th>Name</th>
          <td>
            <input type="text" v-model="formData.name" />
          </td>
        </tr>
        <tr>
          <th>Description</th>
          <td>
            <textarea rows="3" v-model="formData.description" />
          </td>
        </tr>
        <tr>
          <th>Done</th>
          <td>
            <input type="radio" id="false" value="false" v-model="formData.done" />
            <label for="false">False</label>
            <input type="radio" id="true" value="true" v-model="formData.done" />
            <label for="true">True</label>
          </td>
        </tr>
        <tr>
          <th></th>
          <td>
            <button @click="doUpdate()">Update</button>
            <button @click="doCancel()">Cancel</button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
import firebase from '@/plugins/firebase'

export default {
  name: 'TaskUpdateForm',
  data() {
    return {
      id: null,
      taskData: null,
      formData: {
        name: null,
        description: null,
        done: null,
      },
    }
  },
  methods: {
    doUpdate() {
      const funcUpdateTask = firebase.functions().httpsCallable('funcUpdateTask')
      this.formData.done = this.formData.done === 'true'
      funcUpdateTask({
        id: this.id,
        task: this.formData,
      })
        .then(res => {
          alert('Complete document Update')
        })
        .catch(error => {
          alert(`Error document Update: ${error}`)
        })
      this.$router.push({ name: 'task' })
    },
    doCancel() {
      this.$router.push({ name: 'task' })
    },
  },
  created() {
    // Firebaseからデータ取得
    this.id = this.$route.params.id
    const funcGetTask = firebase.functions().httpsCallable('funcGetTask')
    funcGetTask({
      id: this.id,
    })
      .then(res => {
        this.taskData = res.data
        if (this.taskData.status) {
          // 取得したデータをFormへ表示
          this.formData.name = this.taskData.name
          this.formData.description = this.taskData.description
          this.formData.done = this.taskData.done
        } else {
          alert('Not Found Document')
          this.$router.push({ name: 'task' })
        }
      })
      .catch(error => {
        console.log(`Error: ${error}`)
        this.$router.push({ name: 'task' })
      })
  },
}
</script>

Firestore API(Functions)

functions/index.js
admin.initializeApp()
const db = admin.firestore()

()

export const funcGetTaskLists = functions.https.onCall(async (data, context) => {
  const response = {
    status: null,
    error: null,
    items: [],
  }
  await db
    .collection('task')
    .get()
    .then(snapshot => {
      response.status = true
      snapshot.forEach(doc => {
        response.items.push(Object.assign({ id: doc.id }, doc.data()))
      })
    })
    .catch(error => {
      response.status = false
      response.error = error
    })
  return response
})

export const funcGetTask = functions.https.onCall(async (data, context) => {
  const response = {
    status: null,
    error: null,
  }
  await db
    .collection('task')
    .doc(data.id)
    .get()
    .then(doc => {
      if (doc.exists) {
        const taskData = doc.data()
        response.status = true
        response.name = taskData.name
        response.description = taskData.description
        response.done = taskData.done
        response.createdAt = taskData.createdAt
      } else {
        response.status = false
      }
    })
    .catch(error => {
      response.status = false
      response.error = error
    })
  return response
})

export const funcAddTask = functions.https.onCall(async (data, context) => {
  const response = {}
  const task = data.task
  task.createdAt = new Date()
  await db
    .collection('task')
    .add(task)
    .then(res => {
      response.status = true
      response.id = res.id
    })
    .catch(error => {
      response.status = false
      response.error = error
    })
  return response
})

export const funcUpdateTask = functions.https.onCall(async (data, context) => {
  const response = {}
  const id = data.id
  const task = data.task
  task.createdAt = new Date()
  await db
    .collection('task')
    .doc(id)
    .set(task)
    .then(() => {
      response.status = true
    })
    .catch(error => {
      response.status = false
      response.error = error
    })
  return response
})

export const funcDeleteTask = functions.https.onCall(async (data, context) => {
  const response = {}
  const id = data.id
  await db
    .collection('task')
    .doc(id)
    .delete()
    .then(() => {
      response.status = true
    })
    .catch(error => {
      response.status = false
      response.error = error
    })
  return response
})

コンポーネント・FunctionsからStorage操作

コレクションを準備

image.png

ルーティング設定

src/router.js
import StorageIndex from './views/storage/index.vue'
import Storage01 from './views/storage/storage01.vue'
import Storage02 from './views/storage/storage02.vue'
import Storage03 from './views/storage/storage03.vue'
import Storage04 from './views/storage/storage04.vue'
import Storage05 from './views/storage/storage05.vue'

()

    {
      path: '/storage',
      name: 'storage',
      component: StorageIndex,
    },
    {
      path: '/storage/01',
      name: 'storage/01',
      component: Storage01,
    },
    {
      path: '/storage/02',
      name: 'storage/02',
      component: Storage02,
    },
    {
      path: '/storage/03',
      name: 'storage/03',
      component: Storage03,
    },
    {
      path: '/storage/04',
      name: 'storage/04',
      component: Storage04,
    },
    {
      path: '/storage/05',
      name: 'storage/05',
      component: Storage05,
    },

Storage動作確認用のメニュー画面

src/views/storage/index.vue
<template>
  <div class="storage-index">
    <h1>Storage</h1>
    <div style="margin-bottom: 30px;">
      <div style="margin-bottom: 20px;"><router-link to="/">Home</router-link></div>
      <div style="margin-bottom: 20px;"><router-link to="/storage/01">Storage01</router-link></div>
      <div style="margin-bottom: 20px;"><router-link to="/storage/02">Storage02</router-link></div>
      <div style="margin-bottom: 20px;"><router-link to="/storage/03">Storage03</router-link></div>
      <div style="margin-bottom: 20px;"><router-link to="/storage/04">Storage04</router-link></div>
      <div style="margin-bottom: 20px;"><router-link to="/storage/05">Storage05</router-link></div>
    </div>
    <Logout />
  </div>
</template>

<script>
import Logout from '@/components/auth/Logout.vue'

export default {
  name: 'storage-index',
  components: {
    Logout,
  },
}
</script>

アップロードしたテキストファイルの内容を表示

src/views/storage/storage01.vue
<template>
  <div class="storage01">
    <h1>Storage01</h1>
    <div style="margin-bottom: 30px;">
      <router-link to="/">Home</router-link>
    </div>
    <Storage01 />
    <Logout />
  </div>
</template>

<script>
import Storage01 from '@/components/storage/Storage01.vue'
import Logout from '@/components/auth/Logout.vue'

export default {
  name: 'storage01',
  components: {
    Logout,
    Storage01,
  },
}
</script>
src/components/storage/Storage01.vue
<template>
  <div class="storage01">
    <div v-for="(item, index) in items" :key="index" style="margin-bottom: 10px;">
      {{ item }}
    </div>
  </div>
</template>

<script>
import firebase from '@/plugins/firebase'

export default {
  name: 'Storage01',
  data() {
    return {
      storage: null,
      txtRef: null,
      items: [],
    }
  },
  created() {
    this.storage = firebase.storage()
    this.txtRef = this.storage.ref('upload/sample.txt')
    this.txtRef
      .getDownloadURL()
      .then(url => {
        console.log(url)
        const xhr = new XMLHttpRequest()
        xhr.responseType = 'text'
        xhr.onload = event => {
          const data = xhr.responseText
          const array = data.split('\r\n')
          for (const index in array) {
            this.items.push(array[index])
          }
          console.log(this.items)
        }
        xhr.open('GET', url)
        xhr.send()
      })
      .catch(error => {
        console.log(error)
      })
  },
}
</script>

アップロードした画像ファイルを表示

src/views/storage/storage02.vue
<template>
  <div class="storage02">
    <h1>Storage02</h1>
    <div style="margin-bottom: 30px;">
      <router-link to="/">Home</router-link>
    </div>
    <Storage02 />
    <Logout />
  </div>
</template>

<script>
import Storage02 from '@/components/storage/Storage02.vue'
import Logout from '@/components/auth/Logout.vue'

export default {
  name: 'storage02',
  components: {
    Logout,
    Storage02,
  },
}
</script>
src/components/storage/Storage02.vue
<template>
  <div class="storage02">
    <img :src="img.url" :alt="img.alt" />
  </div>
</template>

<script>
import firebase from '@/plugins/firebase'

export default {
  name: 'Storage02',
  data() {
    return {
      storage: null,
      imgRef: null,
      img: {
        url: null,
        alt: null,
      },
    }
  },
  created() {
    this.storage = firebase.storage()
    this.imgRef = this.storage.ref('image.jpg')
    this.imgRef
      .getDownloadURL()
      .then(url => {
        console.log(url)
        this.img.url = url
      })
      .catch(error => {
        console.log(error)
      })
  },
}
</script>

メタデータを取得

src/views/storage/storage04.vue
<template>
  <div class="storage04">
    <h1>Storage04</h1>
    <div style="margin-bottom: 30px;">
      <router-link to="/">Home</router-link>
    </div>
    <Storage04 />
    <Logout />
  </div>
</template>

<script>
import Storage04 from '@/components/storage/Storage04.vue'
import Logout from '@/components/auth/Logout.vue'

export default {
  name: 'storage04',
  components: {
    Logout,
    Storage04,
  },
}
</script>
src/components/storage/Storage04.vue
<template>
  <div class="storage04">
    <div style="margin-bottom: 20px;">
      <input type="text" v-model="formData.fname" />
    </div>
    <div>
      <button @click="getMetadata()">Metadata</button>
    </div>
  </div>
</template>

<script>
import firebase from '@/plugins/firebase'

export default {
  name: 'Storage04',
  data() {
    return {
      storage: null,
      ref: null,
      formData: {
        fname: null,
      },
    }
  },
  methods: {
    getMetadata() {
      this.ref = this.storage.ref(this.formData.fname)
      this.ref
        .getMetadata()
        .then(metadata => {
          console.log(metadata)
          alert('Get metadata')
        })
        .catch(error => {
          console.log(error)
        })
    },
  },
  created() {
    this.storage = firebase.storage()
  },
}
</script>

選択したファイルをストレージへアップロード、ファイル情報をFirestoreへ保存

src/views/storage/storage05.vue
<template>
  <div class="storage05">
    <h1>Storage05</h1>
    <div style="margin-bottom: 30px;">
      <router-link to="/">Home</router-link>
    </div>
    <Storage05 />
    <Logout />
  </div>
</template>

<script>
import Storage05 from '@/components/storage/Storage05.vue'
import Logout from '@/components/auth/Logout.vue'

export default {
  name: 'storage05',
  components: {
    Logout,
    Storage05,
  },
}
</script>
src/components/storage/Storage05.vue
<template>
  <div class="storage05">
    <div style="margin-bottom: 20px;">
      <input type="text" v-model="formData.name" placeholder="名前" />
    </div>
    <div style="margin-bottom: 20px;">
      <textarea v-model="formData.description" cols="50" rows="5" />
    </div>
    <div style="margin-bottom: 20px;">
      <input type="file" name="file" ref="file" @change="selectedFile" />
    </div>
    <div>
      <table style="border: solid #333 1px; margin: 30px auto 0;" v-if="formData.files.length">
        <thead>
          <tr>
            <th>Index</th>
            <th>ファイル名</th>
            <th>タイプ</th>
            <th>サイズ</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="(item, index) in formData.files" :key="index">
            <td>{{ index }}</td>
            <td>{{ item.name }}</td>
            <td>{{ item.type }}</td>
            <td>{{ item.size }}</td>
            <td><button @click="doDelete(index)"></button></td>
          </tr>
        </tbody>
      </table>
    </div>
    <div style="margin-top: 30px;">
      <button @click="doUpload">Upload</button>
    </div>
  </div>
</template>

<script>
import firebase from '@/plugins/firebase'
import moment from 'moment'

export default {
  name: 'Storage05',
  data() {
    return {
      storage: null,
      db: null,
      ref: null,
      formData: {
        name: null,
        description: null,
        files: [],
      },
      files: [],
    }
  },
  methods: {
    // 選択されたファイルの情報を保存
    selectedFile(event) {
      event.preventDefault()
      this.formData.files.push({
        file: event.target.files[0],
        name: event.target.files[0].name,
        type: event.target.files[0].type,
        size: event.target.files[0].size,
      })
      // 選択したファイルをリセット
      this.$refs.file.type = 'text'
      this.$refs.file.type = 'file'
    },
    doDelete(index) {
      if (!confirm(`${this.formData.files[index].name} を削除します。よろしいですか?`)) {
        return
      }
      this.formData.files.splice(index, 1)
    },
    async doUpload(event) {
      const dir = moment().format('YYYYMMDDHHmmss')
      // Storageへファイルをアップロード
      await this.formData.files.forEach(async item => {
        // console.log(item.file)
        this.ref = this.storage.ref(`${dir}/${item.name}`)
        // ファイルをアップロード
        await this.ref
          .put(item.file)
          .then(snapshot => {
            console.log(snapshot)
          })
          .catch(error => {
            console.log(error)
          })
      })

      // Firestoreへデータを保存
      this.formData.files.forEach(item => {
        this.files.push({
          name: item.name,
          downloadUrl: `https://firebasestorage.googleapis.com/v0/b/vue-firebase-project-xxxxx.appspot.com/o/${encodeURIComponent(
            dir + '/' + item.name
          )}?alt=media`,
        })
      })
      this.db.collection('storage').add({
        user: firebase.auth().currentUser.displayName,
        name: this.formData.name,
        description: this.formData.description,
        files: this.files,
      })

      alert('Upload complete')
      this.formData.name = null
      this.formData.description = null
      this.formData.files = []
    },
  },
  created() {
    this.storage = firebase.storage()
    this.db = firebase.firestore()
    console.log(firebase.auth().currentUser)
  },
}
</script>

メタデータ設定、アップロードしたファイル情報を保存(Functions)

functions/index.js
const storage = admin.storage()

()

export const setMetaData = functions.storage.object().onFinalize((object, context) => {
  const ref = storage.bucket().file(object.name)
  const metaData = {
    cacheControl: 'max-age=300',
  }
  ref
    .setMetadata(metaData)
    .then(res => {
      console.info(res)
    })
    .catch(error => {
      console.error(error)
    })

  db.collection('storage_func')
    .add({
      bucket: object.bucket,
      contentType: object.contentType,
      id: object.id,
      md5Hash: object.md5Hash,
      mediaLink: object.mediaLink,
      name: object.name,
      selfLink: object.selfLink,
      size: object.size,
      storageClass: object.storageClass,
      timeCreated: object.timeCreated,
      downloadUrl: `https://firebasestorage.googleapis.com/v0/b/${object.bucket}/o/${encodeURIComponent(
        object.name
      )}?alt=media`,
    })
    .then(res => {
      console.log(res)
    })
    .catch(error => {
      console.log(error)
    })
})

メモ

Firebaseの扱い方がそもそもこれでよいのか?わからないため、こう書いたほうが効率的などあればどなたか教えてください。
個人的に各機能の個人的な理解で終わっているため、どこかでVuetify、Elementなどで多少見た目も組み合わせて使ってみようかと。

yoshi0518
社内SEによる個人メモ。ほぼ自己学習なので間違っていたらすいません。。。
https://www.n-asset.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away