Vue로 PWA 개발 - 그랜파 개발자
Vue 프로젝트 Beta Test : mylog, 일상의 기록
개발이 진행됨에 따라 소스 코드를 계속 추가해 갑니다.
홈페이지의 마이로그 목록에서 마이로그를 선택하면 상세보기로 이동합니다. 상세 보기에서는 댓글과 답글을 쓸 수 있고, 구독과 수정도 할 수 있습니다.
마이로그 상세보기
마이로그는 단문 서비스이므로 마이로그 전체 목록 보기에서 각 마이로그 내용 전체를 볼 수 있습니다. 그럼에도 상세보기가 있는 이유는 마이로그에 대한 댓글과 댓글에 대한 답글을 위한 것입니다. 그리고 조회수를 카운트 합니다. 전체 목록에서 내용을 볼 수 있는데, 굳이 마이로그를 선택하여 상세 보기 페이지가 열릴 때 조회수를 카운트하는 것이 어색하긴 합니다.
- 마이로그 리스트에서 마이로그를 선택하면 상세보기 페이지가 열립니다.
- 상세보기 페이지가 열리면서 조회수를 업데이트 합니다.
- 선택한 마이로그를 보여주고, 마이로그를 구독 또는 수정할 수 있습니다.
- 마이로그에 대한 댓글과 답글을 보여 줍니다.
- 마이로그 저자의 마이로그 이름을 상단에 보여주고, 그것을 클릭하면 마이로그 저자의 홈으로 이동하여 저자의 모든 마이로그를 볼 수 있습니다.
1. 마이로그 선택
마이로그 전체 목록에서 마이로그를 클릭하면 해당 마이로그가 선택되고, 선택한 마이로그의 mylogId를 가지고 상세 보기 페이지로 이동합니다.
src/views/HomeView.vue
<!-- src/views/HomeView.vue -->
<template>
<v-container>
. . .
<v-row>
<v-col v-for="mylog in mylogs" :key="mylog.id" @click="viewMylog(mylog.id)" cols="12">
<v-card>
<v-card-title style="font-size:1em">{{ mylog.title }}</v-card-title>
<!-- eslint-disable -->
<v-card-text style="font-size:1em" class="mt-n2 mb-n6" v-html="sanitizeContent(mylog.content)"></v-card-text>
<!-- eslint-enable -->
<v-card-subtitle>
<b>댓글</b> <span v-if="mylog.commentCount > 0"> {{mylog.commentCount }} </span>
{{ mylog.userName }} . {{ formatDate(mylog.createdAt) }}
<span v-if="mylog.views > 0" > ({{ mylog.views }})</span>
</v-card-subtitle>
</v-card>
</v-col>
</v-row>
. . .
</div>
</v-container>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
import sanitizeHtml from 'sanitize-html';
export default {
name:'HomeView',
data() {
return {
//mylogs: [],
};
},
. . .
methods: {
...mapActions('mylogs', ['resetError']),
viewMylog(mylogId) {
this.$router.push({ name: "mylog", params: { id: mylogId } });
},
. . .
};
</script>
2. 마이로그 상세 보기
파라미터로 mylogId를 받아 mylogId로 마이로그를 로드하여 마이로그를 나타냅니다.
src/views/MyLogView.vue
<!-- src/views/MyLogView.vue -->
<template>
<v-container>
<v-card v-if="mylog">
<!--글쓴이의 마이로그 이름-->
<v-card-title v-if=author style="cursor: pointer; font-size:1em" @click="goToUserMylogs(author.id)">
{{ getMylogName }}<v-icon>mdi-chevron-right</v-icon>
</v-card-title>
<!-- 마이로그 제목 -->
<v-card-title v-if=author style="cursor: pointer; font-size:1em">
{{ mylog.title }}
</v-card-title>
<!-- 마이로그 내용 -->
<!-- eslint-disable -->
<v-card-text style="font-size:1em" v-html="content"></v-card-text>
<!-- eslint-enable -->
<!-- 글쓴이 이름, 작성일, 조회수 -->
<v-card-subtitle style="text-align:right">
{{ mylog.userName }} . {{ mylog.createdAt.toDate().toISOString().split('T')[0] }}
<span v-if="mylog.views > 0" > ({{ mylog.views }})</span>
</v-card-subtitle>
<v-card-actions >
<v-spacer></v-spacer>
<!-- 구독 버튼 -->
<v-btn small text v-if="!isAuthor && user" @click="toggleSubscription">
{{ isSubscribed ? "구독 취소" : "구독" }} <v-icon>mdi-check-circle</v-icon>
</v-btn>
<!-- 게시물 수정 버튼 -->
<v-btn small text v-if="isAuthor" @click="goToEditMylog()">
수정 <v-icon>mdi-pencil</v-icon>
</v-btn>
</v-card-actions>
. . .
</v-card>
. . .
</v-container>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
import sanitizeHtml from 'sanitize-html';
export default {
data() {
return {
content: '',
. . .
author: null, // 저자의 이름을 가져오기 위해
};
},
async created() {
// 마이로그 가져오기
const mylogId = this.$route.params.id; // Get the article ID from the route parameters
await this.fetchMylog(mylogId); // 마이로그 아이디로 마이로그를 가져온다.
this.content = this.sanitizeContent(this.mylog.content); // 내용에 포함된 html을 안전한 html로 변경한다.
// 마이로그 저자 찾기 - 상태에 로드되어 있는 전체 회원 정보를 이용한다.
const users = this.$store.state.auth.users;
this.author = users.find(user => user.id === this.mylog.userId);
. . .
},
computed: {
...mapGetters('auth',['user']),
...mapGetters('mylogs',['mylog', 'comments', 'isSubscribed', 'subscriptionId', 'loading']),
getMylogName() {
return this.author.mylogname;
},
// 저자인가? - 작성자와 로그인한 사용자 비교
isAuthor() {
return this.$store.state.auth.user && this.$store.state.auth.user.id === this.mylog.userId;
},
},
methods: {
...mapActions('mylogs', ['fetchMylog', 'resetError', 'updateViewCount','addComment', 'fetchComments','deleteComment',
'addReply', 'deleteReply', 'checkSubscription', 'subscribeToUser', 'unsubscribeFromUser']),
// content를 안전한 html로 바꿔준다.
sanitizeContent(content) {
return sanitizeHtml(content.replace(/\n/g, '<br>'), {
allowedTags: ['b', 'i', 'em', 'strong', 'p', 'br'],
allowedAttributes: {}
});
},
. . .
formatDate(date) {
if (date && date.toDate) {
return date.toDate().toISOString().replace('T', ' ').substring(0, 16);
}
return '';
},
. . .
// 로그인한 회원이 마이로그 작성자인가?
isOwner(userId) {
if(!this.$store.state.auth.user)
return false;
return userId === this.$store.state.auth.user.id;
},
. . .
};
</script>
src/store/modules/mylogs.js
// src/store/modules/mylogs.js
import { v4 as uuidv4 } from 'uuid';
import { db, doc, collection, getDoc, getDocs, setDoc, addDoc,updateDoc, deleteDoc, arrayUnion, increment, where } from "@/firebase";
import { query, orderBy } from "@/firebase";
const state = {
. . .
mylog: null,
. . .
};
const mutations = {
. . .
setMylog(state, mylog) {
state.mylog = mylog;
},
. . .
};
const actions = {
. . .
// mylogId로 마이로그를 로드한다.
async fetchMylog({ commit }, mylogId) {
//console.log('mylogId:', mylogId);
commit('setLoading', true);
try {
const docRef = doc(db, "mylogs", mylogId); // Reference to the specific article document
const docSnap = await getDoc(docRef); // Fetch the document snapshot
if (docSnap.exists()) {
const mylog = docSnap.data(); // Set article data
commit('setMylog', mylog);
} else {
console.error("mylog가 없습니다.");
}
} catch (error) {
commit('setError', error);
} finally {
commit('setLoading', false);
}
},
. . .
};
const getters = {
. . .
mylog: state => state.mylog,
. . .
};
export default {
namespaced: true,
state,
mutations,
actions,
getters
};
Vue PWA 프로젝트, mylog 코드
'Vue PWA mylog' 카테고리의 다른 글
mylog 조회수 (1) | 2024.11.15 |
---|---|
mylog 수정 (1) | 2024.11.14 |
mylog 전체 보기 (0) | 2024.11.11 |
mylog 계정 (0) | 2024.11.11 |
mylog 로그인 (1) | 2024.11.10 |