Vue로 PWA 개발 - 그랜파 개발자.
ChatGPT의 PWA 예제의 소스 코드로 ChatGPT의 Router 예제와 PWA 예제를 병합하였습니다.
예제 소스 코드의 경우 ChatGPT가 제공한 코드와 직접 코딩한 코드에 차이가 있을 수 있습니다. 실행하는 과정에서 맞지 않는 코드들은 수정하여 실행하도록 하였습니다.
프로젝트가 진행되면 소스 코드는 계속 추가될 것입니다.
src/firebase.js
// src/firebase.js
import { initializeApp } from "firebase/app";
import { getFirestore, collection, getDocs, query } from "firebase/firestore";
const firebaseConfig = {
apiKey: process.env.VUE_APP_FIREBASE_API_KEY,
authDomain: process.env.VUE_APP_FIREBASE_AUTH_DOMAIN,
projectId: process.env.VUE_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.VUE_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.VUE_APP_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.VUE_APP_FIREBASE_APP_ID,
};
// npm install dotenv - env가 정상 동작하지 않을 때 설치 필요함
// Initialize Firebase
const app = initializeApp(firebaseConfig);
// Initialize Firestore
const db = getFirestore(app);
export { db, collection, getDocs, query };
src/router/index.js
// src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/views/Home.vue';
import PostList from '@/views/PostList.vue';
import About from '@/views/About.vue';
import Contact from '@/views/Contact.vue';
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/post',
name: 'Post',
component: PostList,
},
{
path: '/about',
name: 'About',
component: About,
},
{
path: '/contact',
name: 'Contact',
component: Contact,
},
];
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
src/store/index.js
// src/store/index.js
import Vue from "vue";
import Vuex from "vuex";
import { db } from "@/firebase";
import { collection, query, getDocs, addDoc, updateDoc, doc } from "firebase/firestore";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
posts: []
},
mutations: {
setPosts(state, posts) {
state.posts = posts;
},
addPost(state, post) {
state.posts.push(post);
},
updatePost(state, updatedPost) {
const index = state.posts.findIndex(post => post.id === updatedPost.id);
if (index !== -1) {
Vue.set(state.posts, index, updatedPost);
}
}
},
actions: {
async fetchPosts({ commit }) {
const querySnapshot = await getDocs(collection(db, "posts"));
const posts = querySnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
commit("setPosts", posts);
},
async addPost({ commit }, post) {
const docRef = await addDoc(collection(db, "posts"), post);
commit("addPost", { id: docRef.id, ...post });
},
async updatePost({ commit }, { id, post }) {
const docRef = doc(db, "posts", id);
await updateDoc(docRef, post);
commit("updatePost", { id, ...post });
}
},
getters: {
allPosts: state => state.posts
}
});
src/App.vue
<!-- src/App.vue -->
<template>
<v-app>
<!-- App Bar -->
<v-app-bar color="primary" dark app>
<v-app-bar-nav-icon @click="toggleDrawer" />
<v-toolbar-title>My App</v-toolbar-title>
</v-app-bar>
<!-- Navigation Drawer -->
<v-navigation-drawer v-model="drawer" app>
<v-list>
<v-list-item-group>
<v-list-item
v-for="(item, index) in menuItems"
:key="index"
@click="navigateTo(item)"
>
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-navigation-drawer>
<!-- Main Content -->
<v-main>
<v-container>
<router-view /> <!-- 라우터가 여기에 컴포넌트를 렌더링 -->
</v-container>
</v-main>
<!-- Footer -->
<v-footer app color="secondary" dark>
<v-container>
<p>© {{ new Date().getFullYear() }} My App. All rights reserved.</p>
</v-container>
</v-footer>
</v-app>
</template>
<script>
export default {
data() {
return {
drawer: false,
menuItems: [
{ title: 'Home', icon: 'mdi-home', path: '/' },
{ title: 'Post', icon: 'mdi-pencil', path: '/post' },
{ title: 'About', icon: 'mdi-information', path: '/about' },
{ title: 'Contact', icon: 'mdi-phone', path: '/contact' },
],
};
},
methods: {
toggleDrawer() {
this.drawer = !this.drawer;
},
navigateTo(item) {
this.drawer = false; // 메뉴 클릭 시 Drawer 닫기
// 같은 페이지를 라우팅하면 에러가 발생한다.
if(this.$route.name !== item.title )
this.$router.push(item.path);
},
},
};
</script>
src/views/PostList.vue
<!-- src/views/PostList.vue -->
<template>
<v-container>
<v-btn @click="openDialog">글 작성하기</v-btn>
<v-dialog v-model="dialog" max-width="500px">
<v-card>
<v-card-title>새 글 작성</v-card-title>
<v-card-text>
<v-text-field label="제목" v-model="newPost.title"></v-text-field>
<v-textarea label="내용" v-model="newPost.content"></v-textarea>
</v-card-text>
<v-card-actions>
<v-btn color="primary" @click="savePost">저장</v-btn>
<v-btn color="secondary" @click="closeDialog">취소</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-list>
<v-list-item v-for="post in posts" :key="post.id" @click="editPost(post)">
<v-list-item-content>
<v-list-item-title>{{ post.title }}</v-list-item-title>
<v-list-item-subtitle>{{ post.content }}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
</v-container>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
export default {
data() {
return {
dialog: false,
newPost: {
title: "",
content: ""
}
};
},
computed: {
...mapGetters(["allPosts"]),
posts() {
return this.allPosts;
}
},
methods: {
...mapActions(["fetchPosts", "addPost", "updatePost"]),
openDialog() {
this.newPost = { title: "", content: "" };
this.dialog = true;
},
closeDialog() {
this.dialog = false;
},
savePost() {
if (this.newPost.id) {
this.updatePost({ id: this.newPost.id, post: this.newPost });
} else {
this.addPost(this.newPost);
}
this.closeDialog();
},
editPost(post) {
this.newPost = { ...post };
this.dialog = true;
}
},
created() {
this.fetchPosts();
}
};
</script>
Vue 프로젝트 Beta Test : mylog, 일상의 기록
'그랜파 개발자의 프론트엔드 공부-Vue' 카테고리의 다른 글
Vue로 PWA 개발, ChatGPT에게 프론트엔드 앱에 사용할 수 있는 사용자 인증 기능을 물었습니다. (0) | 2024.12.05 |
---|---|
Vue로 PWA 개발, ChatGPT에게 프론트엔드 앱에 사용할 firebase Authentication을 물었습니다. (1) | 2024.12.04 |
Vue로 PWA 개발, ChatGPT의 프론트엔드 앱 CRUD 예제 워크플로우 (1) | 2024.12.02 |
Vue로 PWA 개발, ChatGPT에게 프론트엔드 앱을 만들어 달라고 했습니다. (1) | 2024.12.01 |
Vue로 PWA 개발, ChatGPT의 프론트엔드 앱 Router 예제를 실행해 봅시다. (0) | 2024.11.30 |