Vue3 - 게시판 CRUD - UI 구성, 라우터 맵핑

Corner·2023년 1월 25일
0

Vue.js 실무

목록 보기
12/14
post-thumbnail

게시판 CRUD를 하기 위해 우선 페이지 컴포넌트를 만들고 라우터 맵핑을 진행한다.

views/posts 경로에 PostCreateView.vue, PostListView.vue, PostEditView.vue, PostDetailView.vue CRUD 역할을 할 4개의 페이지 컴포넌트를 생성한다. UI는 대충 하자. 이게 중요한게 아니니깐.

그런다음 이 페이지를 라우터로 이동할 수 있게끔 맵핑해주어야 한다. router/index.js로 이동.

기존에 있던 컴포넌트 임포트 방식과 동일하게 컴포넌트를 추가한다.

import PostCreateView from '@/views/posts/PostCreateView.vue';
import PostDetailView from '@/views/posts/PostDetailView.vue';
import PostEditView from '@/views/posts/PostEditView.vue';
import PostListView from '@/views/posts/PostListView.vue';

그리고 routes에 이어서 맵핑 추가

		{
        path: '/posts',
        name: 'PostList',
        component: PostListView
    },
    {
        path: '/posts/create',
        name: 'PostCreate',
        component: PostCreateView
    },
    {
        path: '/posts/:id',
        name: 'PostDetail',
        component: PostDetailView
    },
    {
        path: '/posts/:id/edit',
        name: 'PostEdit',
        component: PostEditView
    }

설명해주자면 :id는 같은 url인데 다른 데이터가 존재해야하는 곳에 :네임 이런식으로 콜론과 params 이름을 붙이면 된다.

예를 들어보자, 유저가 150명이다. 근데 유저 이름이 제 각각 다르다. A, B, C 그럼 페이지 라우터를 /A /B /C 이렇게 하나하나 수정할 수 없는 노릇이다. 그래서 :paramName 이런식으로 맵핑하여 $route.params.id 이런식으로 라우트의 파람 값을 가져와서 데이터 를 불러오는 방식으로 사용하는 것이 훨~씬 낫다.

$route.params를 콘솔이나 템플릿 코드안에 값을 찍어보면 이해될 것임.

그 외에도 query, hash를 이용할 수 있음

/post?search=Daniel >> $route.query.search는 Daniel 값이 찍히는 것을 알 수 있음.

해쉬는 /search#hashValue$route.hash를 찍어보면 >> #hashValue 값이 출력됨

이제 Header.vue를 좀 꾸미고 클릭 이벤트를 구현하여 라우터를 이동시키자.

<div class="collapse navbar-collapse" id="navbarSupportedContent">
  <ul class="navbar-nav me-auto">
    <li class="nav-item">
      <RouterLink class="nav-link" active-class="active" to="/">Home</RouterLink>
    </li>
    <li class="nav-item">
      <RouterLink class="nav-link" active-class="active" to="/about">About</RouterLink>
    </li>
    <li class="nav-item">
      <RouterLink class="nav-link" active-class="active" to="/posts">게시글</RouterLink>
    </li>
  </ul>
  <div class="d-flex">
    <button class="btn btn-outline-light" type="button" @click="goPage">글쓰기</button>
  </div>
</div>

그 다음 goPage() 함수 기능을 작성

<script setup>
import { useRouter } from 'vue-router';

const router = useRouter();
const goPage = () => {
    router.push('/posts/create');
};
</script>

이제 Bootstrap을 활용하여 마크업을 진행한다. (알아서)

부트스트랩의 Form control 예제 코드를 긁어와서 수정한다.

PostCreateView.vue

<template>
    <div>
        <h2>게시글 등록</h2>
        <hr class="my-4" />
        <form @submit.prevent.stop>
            <div class="mb-3">
                <label for="title" class="form-label">제목</label>
                <input type="text" class="form-control" id="title" />
            </div>
            <div class="mb-3">
                <label for="content" class="form-label">내용</label>
                <textarea class="form-control" id="content" rows="3"></textarea>
            </div>
            <div class="pt-4">
                <button type="button" class="btn btn-outline-dark me-2" @click="goBack">목록</button>
                <button type="submit" class="btn btn-primary">저장</button>
            </div>
        </form>
    </div>
</template>

그 다음 PostLiveView.vue를 마크업 할건데 부트스트랩 Cards 컴포넌트의 마음에 드는 예제 코드를 사용한다.

우선, components/posts경로를 생성하고, PostItem.vue를 만들어서 마크업과 Script 코드에 Setup 방식으로 Props를 정의한다.

<script setup>
defineProps({
    title: {
        type: String,
        required: true // 필수 값
    },
    content: {
        type: String,
        required: true
    },
    createdAt: {
        type: [String, Date, Number] // String, Date, Number 중 들어오는 타입 허용
    }
});
</script>

<template>
    <div class="card">
        <div class="card-body">
            <h5 class="card-title">{{ title }}</h5>
            <p class="card-text">{{ content }}</p>
            <p class="text-muted">
                {{ createdAt }}
            </p>
        </div>
    </div>
</template>

<style scoped></style>

이제 PostLiveView.vue로 가서 리스트에 뿌려줄 아이템 컴포넌트를 소환한다.

<script setup>
import PostItem from '@/components/posts/PostItem.vue';
</script>

<template>
 	<div>
    <h2>게시글 리스트</h2>
    <hr class="my-4" />
    <PostItem />
	</div>
</template>

Api

src/api 경로에 posts.js를 만들자.

// axios
const posts = [
    { id: 1, title: '제목1', content: '내용1', createdAt: '2021-02-11' },
    { id: 2, title: '제목2', content: '내용2', createdAt: '2023-02-01' },
    { id: 3, title: '제목3', content: '내용3', createdAt: '2023-03-01' },
    { id: 4, title: '제목4', content: '내용4', createdAt: '2023-04-01' },
    { id: 5, title: '제목5', content: '내용5', createdAt: '2023-05-01' },
    { id: 6, title: '제목6', content: '내용6', createdAt: '2023-06-01' },
    { id: 7, title: '제목7', content: '내용7', createdAt: '2023-07-01' }
];

export function getPosts() {
    return posts;
}

이제부터 아래는 소스코드이다.

PostListView.vue

<script setup>
import PostItem from '@/components/posts/PostItem.vue';
import { getPosts } from '@/api/posts';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
const posts = ref([]);
const router = useRouter();

const fetchPosts = () => {
    posts.value = getPosts();
};
fetchPosts();

const goPage = id => {
    // router.push(`/posts/${id}`);
    // 다른 방법
    router.push({
        // 이름을 가진 라우터는 이렇게
        name: 'PostDetail',
        params: {
            id
        }
    });
};
</script>

<template>
    <div>
        <h2>게시글 리스트</h2>
        <hr class="my-4" />
        <div class="row g-3">
            <div class="col-4" v-for="post in posts" :key="post.id">
                <PostItem :title="post.title" :content="post.content" :created-at="post.createdAt" @click="goPage(post.id)" />
            </div>
        </div>
    </div>
</template>

<style scoped></style>

postCreateView.vue

<script setup>
import { useRouter } from 'vue-router';

const router = useRouter();

const goListPage = () => {
    router.push({ name: 'PostList' });
};
</script>

<template>
    <div>
        <h2>게시글 등록</h2>
        <hr class="my-4" />
        <form @submit.prevent.stop>
            <div class="mb-3">
                <label for="title" class="form-label">제목</label>
                <input type="text" class="form-control" id="title" />
            </div>
            <div class="mb-3">
                <label for="content" class="form-label">내용</label>
                <textarea class="form-control" id="content" rows="3"></textarea>
            </div>
            <div class="pt-4">
                <button type="button" class="btn btn-outline-dark me-2" @click="goListPage">목록</button>
                <button type="submit" class="btn btn-primary">저장</button>
            </div>
        </form>
    </div>
</template>

<style scoped></style>

PostDetailView.vue

<script setup>
import { useRouter, useRoute } from 'vue-router';

const route = useRoute();
const router = useRouter();
const id = route.params.id;

const goListPage = () => {
    router.push({
        name: 'PostList'
    });
};

const goEditPage = () => {
    router.push({
        name: 'PostEdit',
        params: {
            id
        }
    });
};
</script>

<template>
    <div>
        <h2>제목</h2>
        <p>내용</p>
        <p class="text-muted">2020-01-01</p>
        <hr class="my-4" />
        <div class="row g-2">
            <div class="col-auto">
                <button class="btn btn-outline-dark">이전글</button>
            </div>
            <div class="col-auto">
                <button class="btn btn-outline-dark">다음글</button>
            </div>
            <div class="col-auto me-auto"></div>
            <div class="col-auto">
                <button class="btn btn-outline-dark" @click="goListPage">목록</button>
            </div>
            <div class="col-auto">
                <button class="btn btn-outline-primary" @click="goEditPage">수정</button>
            </div>
            <div class="col-auto">
                <button class="btn btn-outline-danger">삭제</button>
            </div>
        </div>
    </div>
</template>

<style scoped></style>

PostEditView.vue

<script setup>
import { useRoute, useRouter } from 'vue-router';

const router = useRouter();
const route = useRoute();

const goDetailPage = () => {
    router.push({
        name: 'PostDetail',
        params: {
            id: route.params.id
        }
    });
};
</script>

<template>
    <div>
        <h2>게시글 수정</h2>
        <hr class="my-4" />
        <form @submit.prevent.stop>
            <div class="mb-3">
                <label for="title" class="form-label">제목</label>
                <input type="text" class="form-control" id="title" />
            </div>
            <div class="mb-3">
                <label for="content" class="form-label">내용</label>
                <textarea class="form-control" id="content" rows="3"></textarea>
            </div>
            <div class="pt-4">
                <button type="button" class="btn btn-outline-danger me-2" @click="goDetailPage">취소</button>
                <button type="submit" class="btn btn-primary">수정</button>
            </div>
        </form>
    </div>
</template>

<style scoped></style>

TheHeader.vue 글쓰기 버튼에 함수를 추가한다.

<div class="d-flex">
  <button class="btn btn-outline-light" type="button" @click="goPage">글쓰기</button>
</div>

<script setup>

import { useRouter } from 'vue-router';

const router = useRouter();
const goPage = () => {
    router.push({
        name: 'PostCreate'
    });
};
profile
Full-stack Engineer. email - corner3499@kakao.com,

0개의 댓글