Vue로 PWA 개발

60. myLog 날짜별 조회수 구현

그랜파 개발자 2024. 11. 3. 16:33

Vue로 PWA 개발 - 그랜파 개발자

 

날짜별 조회수는 기간을 입력하면 기간동안의 각 날짜에 대한 마이로그 조회수를 보여줍니다.

마이로그는 한 사람의 회원이 하루 몇번을 조회해도 하루 한번만 카운트합니다. 마이로그는 여러개가 있으며, 각각의 마이로그를 조회한 사람 또한 여러명입니다. 마이로그 날짜별 조회수는 전체 마이로그에 대해 해당 날짜에 몇명의 회원이 조회를 했는지 집계를 합니다. 이를 구하기 위하여 각 마이로그에 대해 주어진 날짜에 조회한 회원의 수를 구해서 전체 마이로그에 대해 합산을 하면 해달 날짜의 조회수를 구할 수 있습니다.

마이로그 조회수는 views collection에 저장됩니다. views collection은 다음과 같은 구조를 가집니다.

 

1. views 컬렉션 구조

 

/views/{mylogId}/users/{userId or anonymousId}/[lastViewed]

  1. views 컬렉션 : 각 마이로그에 대한 조회 기록을 저장하는 최상위 컬렉션입니다.
  2. mylogId(Document) : 각 게시글의 고유 ID입니다.
  3. users 서브 컬렉션 : 마이로그를 조회한 사용자들의 컬렉션으로 회원은 userId를, 비회원은 고유한 anonymousId를 사용하여 사용자별로 문서를 만듭니다.
  4. userId or anonymousId (Document) : 회원인 경우, userId는 사용자의 고유 ID입니다
  5. 조회 기록 필드 : lastViewed: 사용자가 마이로그를 조회한 시간을 저장합니다.

예시 데이터:

/views/s4stpEErC7d80RJOTHcX/users/e68ffaa1-fe5c-4b47-87a8-d8873d0d7b38 ["2024-09-24", "2024-09-25", "2024-09-29"]

  1. s4stpEErC7d80RJOTHcX: mylogId
  2. e68ffaa1-fe5c-4b47-87a8-d8873d0d7b38: 회원 사용자 ID(비회원은 고유한 anonymousId)
  3. ["2024-09-24", "2024-09-25", "2024-09-29"] : lastViewed: 사용자가 마이로그를 조회한 날짜.

 

2. 마이로그 날짜별 조회수를 구현

 

위와 같은 구조를 가진 views collection으로 주어진 날짜의 조회수 기능을 구현해 봅시다. 마이로그 날짜별 조회수를 구현하기 위하여 다음의 단계를 수행합니다.

  1. 나의 모든 마이로그의 mylogId를 구합니다.
  2. 입력한 기간의 각 날짜를 구합니다.
  3. 하나의 마이로그에 대해 users subcollection을 구합니다.
  4. users subcollection의 lastViewed 배열에 주어진 날짜가 있으면 조회수를 증가시킵니다.
  5. 위의 과정을 전체 mylogId에 대해 수행하여 날짜별 조회수 집계 기능을 완성합니다.

 

3. 날짜별 조회수

 

4. src/views/ManageView.vue

<!-- src/views/ManageView.vue -->
<template>
  <v-container>
    <v-row class="mx-0">
      <v-col cols="12" md="6">
        <span class="text-h6">{{ getMylogName }}</span>
      </v-col>
    </v-row>

    <v-row class="mx-0">
      <v-col cols="12" md="6">
        <!-- Start Date Input -->
        <v-text-field label="Start Date" v-model="startDate" type="date"></v-text-field>
      </v-col>

      <v-col cols="12" md="6">
        <!-- End Date Input -->
        <v-text-field label="End Date" v-model="endDate" type="date"></v-text-field>
      </v-col>
    </v-row>

    <v-btn @click="calculateViewByDates"> 날자별 조회수 </v-btn>

    <v-divider class="my-5"></v-divider>

    <v-list> 
      <v-list-item-group>
        <v-list-item v-for="(view, index) in dateView" :key="index">
          <v-list-item-content>
            <v-list-item-title>{{ view.date }} : {{ view.view }}</v-list-item-title>
          </v-list-item-content>
        </v-list-item>
      </v-list-item-group>
    </v-list>
  </v-container>
</template>

<script>
import { mapActions, mapGetters } from "vuex";

export default {
  data() {
    return {
      startDate: "", // User-input start date
      endDate: "",   // User-input end date
      dateList: [],   // List of dates to display
      dateView: []
    };
  },
  computed: {
    ...mapGetters('mylogs',['userMylogs', 'views']),
    getMylogName() {
      return this.$store.state.auth.user.mylogname;
    }
  },
  methods: {
    ...mapActions('mylogs', ['fetchUserMylogs', 'getMylogViewByDate']),

    goToMylogDetail(mylogId) {
      this.$router.push({ name: 'MyLogView', params: { id: mylogId } });
    },  

    async calculateViewByDates() {
      // Ensure the start and end dates are valid before calculating
      if (!this.startDate || !this.endDate || new Date(this.startDate) > new Date(this.endDate)) {
        this.dateList = [];
        return;
      }

      // 이전에 조회 내용이 있으면 삭제
      if(this.dateView.length > 0) {
        this.dateView = [];
      }

      // Call the function to get the list of dates between the start and end date
      this.dateList = this.getDatesInRange(this.startDate, this.endDate);

      // 회원의 전체 마이로그에 대해 날짜별 조회수 구하기
      //1. 회원의 mylogId를 구한다.      
      const mylogIds = [];
      this.userMylogs.forEach((mylog) => {
        mylogIds.push(mylog.id);     
      });

      let cnt;     
      // 2. 날짜별 조회수를 구한다.
      for(var n=0; n < this.dateList.length; n++) {        
        cnt = 0;
        for(var i=0 ; i < mylogIds.length ; i++) {          
          // 각 mylogId와 날짜로 조회
          await this.getMylogViewByDate({mylogId: mylogIds[i], date: this.dateList[n]});
          cnt += this.views;
        }
        this.dateView.push({date: this.dateList[n], view: cnt});
      }
    },

    // Function to calculate dates between startDate and endDate
    getDatesInRange(startDate, endDate) {
      const start = new Date(startDate);
      const end = new Date(endDate);
      const dateArray = [];
      let currentDate = start;

      // Loop to generate dates between start and end date
      while (currentDate <= end) {
        dateArray.push(new Date(currentDate).toISOString().split("T")[0]);
        currentDate.setDate(currentDate.getDate() + 1);
      }
      return dateArray;
    }
  },
  mounted() {
    const userId = this.$store.state.auth.user.id;
    this.fetchUserMylogs(userId); // 본인 작성 글 가져오기    
  }
};
</script>

<style scoped>
.v-btn {
  margin-top: 20px;
}
</style>

설명

  1. 페이지가 마운트 될 때 (mounted()) this.fetchUserMylogs(userId); 가 마이로그 목록을 가져와 userMylogs에 넣습니다.
  2. 날짜별 조회수 버튼을 누르면 calculateViewByDates() 함수가 호출됩니다.
  3. 이 함수는 마이로그 목록에서 mylogIds를 구하고, 입력한 기간에 포함된 각 날짜를 구해 getMylogViewByDate() 함수를 호출하여 해당 날짜의 조회수를 구합니다.
  4. 이들 조회수를 화면에 나타냅니다.
  5. fetchUserMylogs(), getMylogViewByDate()는 아래 src/store/modules/mylogs.js를 참조합니다.

 

5. src/store/modules/mylogs.js

Copy// src/store/modules/mylogs.js
import router from '@/router';  // Vue Router import
import { v4 as uuidv4 } from 'uuid';
import { db, doc, collection, getDoc, getDocs, addDoc, setDoc, updateDoc } from "@/firebase";
import { deleteDoc, query, orderBy, arrayUnion, increment, where  } from "@/firebase";

const state = {
  . . .
  userMylogs: [],     // 본인이 작성한 글 저장
  . . .
  views: 0        // 조회수
};

const mutations = {
  . . .
  setUserMylogs(state, mylogs) {
    state.userMylogs = mylogs;
  },
  . . . 
  setViews(state, views) {  // 조회수
    state.views = views;
  },
};

const actions = {
   . . .

   // mylogId와 날짜로 조회수 구하기
   async getMylogViewByDate({ commit }, todayView) {
    // Reference to the 'users' subcollection 
    const usersRef = collection(db, 'views', todayView.mylogId, 'users');
    try {
      // users subcollection을 읽는다.
      const viewsSnapshot = await getDocs(usersRef);
      // users subcollection의 전체 document를 읽는다.
      let cnt = 0;
      for (const viewDoc of viewsSnapshot.docs) {
        // 조회날짜 배열 속에 해당 날짜가 있으면 조회수를 증가시킨다.           
        if(viewDoc.data().lastViewed.includes(todayView.date)) {
          cnt++;
        }            
      }
      //console.log("cnt: ", cnt);
      commit('setViews', cnt);
    } catch (error) {
      console.error("Error getting user views from multiple postIds:", error);
    }
  },

  . . .

  // 현재 사용자가 작성한 게시물 불러오기 - 작성일 역순 정렬
  async fetchUserMylogs({ commit }, userId) {
    if (userId) { 
      try {
        const q = query(collection(db, "mylogs"), where("userId", "==", userId), orderBy("createdAt", "desc"));
        const querySnapshot = await getDocs(q);
        const mylogs = [];
        querySnapshot.forEach((doc) => {
          mylogs.push({ id: doc.id, ...doc.data() });
        }); 
        commit('setUserMylogs', mylogs); // 상태에 저장
      } catch (error) {
        console.error("본인 글 가져오기 실패:", error);
        commit('setError', error);  
      }
    }
  },
  . . .
};

const getters = {
  . . .
  userMylogs: state => state.userMylogs,
  . . .
  views: state => state.views   // 조회수
};

export default {
  namespaced: true,
  state,
  mutations,
  actions,
  getters
};

'Vue로 PWA 개발' 카테고리의 다른 글

62. mylog 끝  (0) 2024.11.03
61. myLog 보기 정렬  (0) 2024.11.03
59. myLog 날짜별 조회수  (1) 2024.11.03
58. mylog 통계  (0) 2024.11.03
57. mylog 구독자 알림 전송  (0) 2024.11.02