블로그 글쓰기
계정을 만들고, 사용자 인증, 글의 카테고리까지 만들었습니다.
이제 글쓰기 기능을 구현해야 할 때입니다.
1. 글쓰기 요구 사항
글을 쓰는 시나리오를 생각해 봅니다.
우선 로그인해야 합니다. 회원만 글을 쓸 수 있기 때문입니다.
Menu Drawer에 글쓰기 메뉴 항목 외에,
글쓰기 페이지를 열기 위한 아이콘도 화면 하단에 둘 생각입니다.
글쓰기 아이콘을 누르면 글쓰기 페이지가 열립니다.
글쓰기 페이지에 등록된 카테고리를 나타내어 선택할 수 있도록 합니다.
글의 제목과 내용을 쓰고 저장을 누르면 글이 저장됩니다.
2. 글의 데이터 항목
사용자는 카테고리를 선택하고,
글의 제목과 내용을 쓴 후 저장합니다.
글이 저장될 때는 어떤 것들이 추가될까요?
글의 카테고리, 제목, 내용 외에도
글쓴이의 userId, 이름, 글을 저장한 날, 글을 최종 수정한 날 등이 생각납니다.
DB :
- firestore의 posts 컬렉션
posts 컬렉션의 문서 항목
- category: 선택한 카테고리 이름
- title: 사용자가 입력한 글의 제목
- content: 사용자가 입력한 글의 내용
- userId: 로그인한 사용자의 id
- userName: 로그인한 사용자의 이름
- createdAt: 글을 저장한 날짜와 시간
- modifiedAt: 글의 최종 수정 날짜와 시간
새글 저장
async addPost({ commit }, post) {
const docRef = await addDoc(collection(db, "posts"), post);
},
3. 글쓰기 기능 분석
글쓰기 페이지의 UI에 필요한 것들입니다.
카테고리 목록을 셀렉트 박스에 넣어 카테고리를 선택할 수 있고
제목을 입력할 수 있는 text field,
내용은 여러 줄의 입력이 필요하니 textarea를 사용하고
글을 저장할 수 있는 저장 버튼이 필요합니다.
4. 카테고리 로드
글쓰기 페이지를 열면 카테고리를 로드하여 셀렉트 박스에 넣어야 합니다.
카테고리는 로그인할 때 미리 로드 되어 있으므로
페이지가 열릴 때에는 로그된 카테고리에서 카테고리 이름만 배열로 추출하여
셀렉트 박스에 넣습니다.
computed: {
. . .
...mapState('post', ['loading', 'categories']),
. . .
},
async mounted() {
// 미리 state에 로드되어 있는 카테고리에서 이름만 가져온다.
this.myCategories = this.categories.map(category => category.name);
this.myCategories.unshift('카테고리 없음');
},
async fetchCategories({ commit }, { userId }) {
if (!userId) return;
try {
// 사용자 ID로 필터링된 카테고리 가져오기
const q = query(
collection(db, "categories"),
where("userId", "==", userId)
);
const querySnapshot = await getDocs(q);
let categories = [];
categories = querySnapshot.docs.map((doc) => ({
id: doc.id, ...doc.data(),
}));
// 로드한 카테고리를 상태에 저장한다.
commit("setCatogories", categories);
} catch (error) {
alert("Error fetching categories : " + error.message);
}
},
WriteView.vue
<!-- src/views/WriteView.vue -->
<template>
<v-container>
<v-card>
<v-card-title>{{ getMyBlogName }}</v-card-title>
<v-form class="pa-3 mt-n4" @submit.prevent="submitPost">
<v-select v-model="selectedCategory" :items="myCategories" label="카테고리를 선택하세요"></v-select>
<v-text-field v-model="title" label="제목" required></v-text-field>
<v-textarea class="mt-n4" style="overflow-y: hidden;" v-model="content" label="내용" rows="5" required></v-textarea>
<div class="mt-n3" style="text-align: right">
<v-btn type="submit" color="primary">저장</v-btn>
</div>
</v-form>
</v-card>
<div class="text-center">
<v-progress-circular v-if="loading" indeterminate></v-progress-circular>
</div>
</v-container>
</template>
<script>
import { mapActions, mapState } from "vuex";
import router from '@/router'; // Vue Router import
export default {
data() {
return {
title: "",
content: "",
myCategories: [],
selectedCategory: null, // 선택된 카테고리
};
},
computed: {
...mapState('auth',['user', 'profile']),
...mapState('post', ['loading', 'categories']),
getMyBlogName() {
if(this.profile)
return this.profile.blogName;
}
},
methods: {
...mapActions('post', ['fetchCategories','addPost']),
async submitPost() {
if (!this.title || !this.content) {
alert("Please fill in both fields.");
return;
}
if(this.user) {
await this.addPost({
category: this.selectedCategory,
title: this.title, // 사용자가 입력한 마이로그 제목
content: this.content, // 사용자가 입력한 마이로그 내용
userId: this.user.uid, // 로그인한 사용자의 id
userName: this.profile.name, // 로그인한 사용자의 이름
createdAt: new Date() // 저장하는 현재 시간
})
router.push("/"); // home으로
}
},
},
async mounted() {
// 카테고리를 로드한다.
await this.fetchCategories({userId: this.profile.userId});
this.myCategories = this.categories.map(category => category.name);
},
};
</script>