💯

firestoreからランキングを作成する

2021/03/31に公開

firestoreに保存した時間から経過時間を計測し、ランキングを表示します。

手順

1.ログイン情報(ユーザID、氏名)と同時に開始時間をデータベース(以下DB)に保管する
2.終了ボタンを押し、終了時間をDBに保管する
3.終了時間から開始時間を引き算し、経過時間を計算する
4.経過時間の順にランキングを表示する

実装について

・Nuxt.js(v2.14.5)、Node.js(v13.14.0)、firebase(v8.2.6)を使用
・順位はCSSのlist-style: decimalを使用する
・手順1については下記を参考にしていただければと思います。
https://zenn.dev/kaito_takase/articles/30024379683f05

コード

page/page01.vue
<template>
  <div class="page01">
    <div>
      <p class="page01__info">{{ userInfo.number }} {{ userInfo.name }}</p>
    </div>
    <div class="page01__top">
      <div class="page01__last">
        <button @click="finishBtn" class="page01__last--submit" v-if="submitBtn">終了ボタン</button>
      </div>
    </div>
  </div>
</template>

<script>
import firebase from '@/plugins/firebase';
const db = firebase.firestore();
const usersCollection = db.collection('users');

export default {
  data() {
    return {
      submitBtn: true,
    };
  },
  computed: {
    userInfo() {
      return this.$store.getters['userInfo'];
    },
  },
  methods: {
    async finishBtn() {
      //全て回答した時
      this.submitBtn = false;
      const inputNmber = this.$store.state.userInfo.number;

      let promise = new Promise((resolve) => {
        // 終了時間をDBに保存する
        resolve(
          usersCollection
            .where('number', '==', inputNmber)
            .get()
            .then(function (querySnapshot) {
              querySnapshot.forEach(function (doc) {
                usersCollection.doc(doc.id).set(
                  {
                    finishTime: firebase.firestore.FieldValue.serverTimestamp(), //終了時間
                  },
                  { merge: true }
                );
              });
            })
            .catch(function (error) {
              console.log('Error getting documents: ', error);
            })
        );
      });
      promise.then(() => {
        // 経過時間を計算し、DBに保存する
        setTimeout(() => {
          usersCollection
            .where('number', '==', inputNmber)
            .get()
            .then(function (querySnapshot) {
              querySnapshot.forEach(function (doc) {
                const finishUserTime = doc.get('finishTime'); //DBに保存された終了時間を取得する
                const startUserTime = doc.get('startTime'); //DBに保存された開始時間を取得する
                const totalTime = finishUserTime - startUserTime;

                //分秒へ変換
                const displayMinutes = Math.floor(((totalTime % (24 * 60 * 60)) % (60 * 60)) / 60); //分
                const displaySeconds = Math.floor(((totalTime % (24 * 60 * 60)) % (60 * 60)) % 60); //秒
                const seconds = ((totalTime % (24 * 60 * 60)) % (60 * 60)) % 60; //秒(小数点以下を含む)
                const few = String(seconds).split('.')[1]; //小数点以下を取得
                const displayFew = few.substr(0, 2); //少数を左から2つ切り取る
                const displayTime = displayMinutes + '分' + displaySeconds + '秒' + displayFew;

                usersCollection
                  .where('number', '==', inputNmber)
                  .get()
                  .then(function (querySnapshot) {
                    querySnapshot.forEach(function (doc) {
                      usersCollection.doc(doc.id).set(
                        {
                          totalTime: totalTime, //経過時間
                          displayTime: displayTime, //経過時間を分秒に変換した値
                        },
                        { merge: true }
                      );
                    });
                  })
                  .catch(function (error) {
                    console.log('Error getting documents: ', error);
                  });
              });
            })
            .catch(function (error) {
              console.log('Error getting documents: ', error);
            });
          this.$router.push('/ranking/');
        }, 3000);
      });
    },
  },
};
</script>

page/ranking.vue
<template>
  <div class="ranking">
    <ol class="ranking__list">
      <li v-for="(ranking, index) in rankings" :key="index" class="ranking__list--item">
        <p class="ranking__list--item-text">位 {{ ranking.number }} {{ ranking.name }}: {{ ranking.displayTime }}</p>
      </li>
    </ol>
  </div>
</template>

<script>
import firebase from '@/plugins/firebase';
const db = firebase.firestore();
const usersCollection = db.collection('users');

export default {
  data() {
    return {
      rankings: [],
    };
  },
  mounted() {
    usersCollection
      .orderBy('totalTime')
      .get()
      .then((snap) => {
        const array = [];
        snap.forEach((doc) => {
          array.push(doc.data());
        });
        this.rankings = array;
      });
  },
};
</script>
ranking.scss
.ranking {
  
    &__list {
    margin: 0 auto 0 130px;

    &--item {
      list-style: decimal;
      font-size: 30px;

      &-text {
        margin: 0 0 0 -10px;
      }
    }
  }
}

説明

page/page01.vue

userInfo.numberとuserInfo.nameでstoreに保存してあるログイン情報を表示

page/page01.vue
<template>
  <div class="page01">
    <div>
      <p class="page01__info">{{ userInfo.number }} {{ userInfo.name }}</p>
    </div>
  </div>
</template>

page/page01.vue

storeからuserInfoの情報を取得

page/page01.vue
<script>
     :
     :
  computed: {
    userInfo() {
      return this.$store.getters['userInfo'];
    },
  },
     :
     :

page/page01.vue

一度しか終了ボタンを押せないように「this.submitBtn = false;」とする。

page/page01.vue
<script>
     :
      this.submitBtn = false;
     :
     :

page/page01.vue

DBに保存されているnumberフィールドに対して、ユーザID(inputNmber)で検索し、
一致するものに「finishTime」として終了時間を追加する

page/page01.vue
<script>
      let promise = new Promise((resolve) => {
        // 終了時間をDBに保存する
        resolve(
          usersCollection
            .where('number', '==', inputNmber)
            .get()
            .then(function (querySnapshot) {
              querySnapshot.forEach(function (doc) {
                usersCollection.doc(doc.id).set(
                  {
                    finishTime: firebase.firestore.FieldValue.serverTimestamp(), //終了時間
                  },
                  { merge: true }
                );
              });
            })
            .catch(function (error) {
              console.log('Error getting documents: ', error);
            })
        );
      });
     :
     :

page/page01.vue

再度DBに保存されているnumberフィールドに対して、ユーザID(inputNmber)で検索し、
一致するものから「startTime」と「finishTime」を取得する。

page/page01.vue
     :
     :
usersCollection
  .where('number', '==', inputNmber)
  .get()
  .then(function (querySnapshot) {
    querySnapshot.forEach(function (doc) {
      const finishUserTime = doc.get('finishTime'); //DBに保存された終了時間を取得する
      const startUserTime = doc.get('startTime'); //DBに保存された開始時間を取得する
     :
     :

page/page01.vue

取得した終了時間(finishUserTime)から開始時間(startUserTime)を引き算し、経過時間(totalTime)を求める。
求めた値のままだと見にくいので分秒に直します。(displayTime)
小数点は第2位まで表示する。

page/page01.vue
     :
     :
const totalTime = finishUserTime - startUserTime;
//分秒へ変換
const displayMinutes = Math.floor(((totalTime % (24 * 60 * 60)) % (60 * 60)) / 60); //分
const displaySeconds = Math.floor(((totalTime % (24 * 60 * 60)) % (60 * 60)) % 60); //秒
const seconds = ((totalTime % (24 * 60 * 60)) % (60 * 60)) % 60; //秒(小数点以下を含む)
const few = String(seconds).split('.')[1]; //小数点以下を取得
const displayFew = few.substr(0, 2); //少数を左から2つ切り取る
const displayTime = displayMinutes + '分' + displaySeconds + '秒' + displayFew;
     :
     :

page/page01.vue

「totalTime」と「displayTime」をDBに追加する

page/page01.vue
     :
     :
usersCollection
  .where('number', '==', inputNmber)
  .get()
  .then(function (querySnapshot) {
    querySnapshot.forEach(function (doc) {
      usersCollection.doc(doc.id).set(
        {
	  totalTime: totalTime, //経過時間
	  displayTime: displayTime, //経過時間を分秒に変換した値
	},
	{ merge: true }
      );
    });
  })
  .catch(function (error) {
    console.log('Error getting documents: ', error);
   });
 });
})
     :
     :

page/page01.vue

DBに追加したら/rankingに遷移する。

page/page01.vue
this.$router.push('/ranking/');

page/ranking.vue

順位はリストで表示する。
その際にランキングにはユーザID、氏名、経過時間を表示する。

page/ranking.vue
<template>
  <div class="ranking">
    <ol class="ranking__list">
      <li v-for="(ranking, index) in rankings" :key="index" class="ranking__list--item">
        <p class="ranking__list--item-text">位 {{ ranking.number }} {{ ranking.name }}: {{ ranking.displayTime }}</p>
      </li>
    </ol>
  </div>
</template>

page/ranking.vue

DBに保存されているtotalTimeフィールドに対して、経過時間の少ない(早い)順で並び替えをする。
そのデータを配列に入れ、表示する

page/ranking.vue
<script>
     :
     :
export default {
  data() {
    return {
      rankings: [],
    };
  },
  mounted() {
    usersCollection
      .orderBy('totalTime')
      .get()
      .then((snap) => {
        const array = [];
        snap.forEach((doc) => {
          array.push(doc.data());
        });
        this.rankings = array;
      });
  },
};
</script>

以上となります。

最後に

初めてFirebaseを使用して作成したので、もっと良い記述方法あれば教えてください。
良ければ記事をLike、Twitterのフォローをお願いします。

参考

https://firebase.google.com/docs/firestore/query-data/order-limit-data?hl=ja
https://webcat.work/time-conversion/

Discussion