공통 모달 생성하기
부트스트랩을 사용하고 싶었으나, 왠지모르게 자꾸 실패해서 모달을 만들었습니다. (배보다 배꼽이 더 커져버림) 먼저 공통 모달을 어떻게 생성 했는지 부터 확인 해볼까요?
설명은 주석으로 달겠습니다.
<script setup lang="ts">
import { library } from '@fortawesome/fontawesome-svg-core';
import { faCircleXmark } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
library.add(faCircleXmark);
// 부모 컴포넌트로부터 받을 데이터 입니다.
const props = defineProps({
isOpen: Boolean
});
// 자식 컴포넌트로부터 보낼 데이터 입니다.
const emits = defineEmits(['close']);
// 닫기 버튼을 누르면 위의 데이터를 보냅니다.
const handleClose = () => {
emits('close');
};
</script>
<template>
<div v-if="props.isOpen"> <!-- 부모 컴포넌트로부터 받을 데이터 -->
<div class="modal-overlay"> <!-- 모달 뒤에 있을 배경 -->
<div class="modal-content"> <!-- 모달 -->
<div class="modal-button">
<button @click="handleClose"> <!-- 모달 닫기 버튼 -->
<font-awesome-icon icon="fa-solid fa-circle-xmark" class="close-icon"/>
</button>
</div>
<slot></slot> <!-- 컴포넌트가 들어갈 자리 -->
</div>
</div>
</div>
</template>
<style lang="scss">
@import "../../styles/common/container.scss";
@import "../../styles/common/button.scss";
@import "../../styles/colors/_color.scss";
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: flex-start;
justify-content: center;
}
.modal-content {
margin-top: 60px;
background: white;
padding: 35px 40px 60px 40px;
border-radius: 12px;
min-width: 350px;
width: auto;
max-height: 670px;
overflow-y: scroll;
}
.modal-button {
@include container(row, flex-end, center, 100%, 100%)
}
.modal-button button {
border: none;
background-color: transparent;
padding: 0;
margin: 0;
}
.close-icon{
font-size: 23px;
color: $gray600;
}
</style>
HTML
복사
공통모달 사용하기
그럼 이제 이 모달을 어떻게 사용하는지 알아보겠습니다. 그전에 먼저 컴포넌트의 구조부터 살펴볼까요?
CeateBoard.vue
자식 컴포넌트는 부모 컴포넌트(BoardList.vue) 로 emit 을 사용하여 데이터를 전송합니다.
CreateBoard.vue 는 TheModal.vue 의 자식 컴포넌트가 아닙니다. 삽입되었다고 보면 됩니다.
설명은 주석으로 달겠습니다.
<script setup lang="ts">
import { ref } from "vue";
import {createBoard} from "../../../api/projcet/BoardApi";
const title = ref('');
const emits = defineEmits(['board-created']);
const handleProject = async () => {
try {
const response = await createBoard({ title: title.value });
if (response.status === 200) {
// console.log('Board created successfully');
// 만약 200 상태를 받으면, 위에서 지정한 데이터가 부모 컴포넌트로 전송될거에요.
emits('board-created');
}
} catch (error) {
console.error(error);
}
};
</script>
<template> <!-- 모달의 slot 부분에 들어갈 부분입니다. -->
<div class="board-modal-content">
<div class="board-modal-title">
프로젝트 생성
</div>
<form>
<div>
<div style="margin-top: 10px">
<div class="board-modal-label">프로젝트 이름을 입력해주세요.</div>
<input v-model="title" type="text">
</div>
<!-- 버튼을 누르게 되면 handleProject 함수가 실행 되겠군요! -->
<button @click.prevent="handleProject" class="board-modal-button" type="submit">생성 하기</button>
</div>
</form>
</div>
</template>
HTML
복사
BoardList.vue
설명은 주석으로 달겠습니다.
<script setup lang="ts">
import {onMounted, ref} from "vue";
import {deleteBoard, displayBoards} from "../../../api/projcet/BoardApi.ts";
import TheModal from "../../common/TheModal.vue";
import CreateBoard from "./CreateBoard.vue";
import { library } from '@fortawesome/fontawesome-svg-core';
import { faPenToSquare, faSquarePlus } from '@fortawesome/free-regular-svg-icons';
import { faTrashCan } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import UpdateBoard from "./UpdateBoard.vue";
import {useProjectStore} from "../../../store/ProjectStore.ts";
library.add(faPenToSquare, faTrashCan, faSquarePlus);
const projectStore = useProjectStore();
const selectProject = (boardId, boardTitle) => {
projectStore.setCurrentBoard(boardId, boardTitle);
};
const boardsData = ref([]);
// 여러 모달이 존재하는 경우, 상태를 분리해주세요.
const isCreateModalOpen = ref(false);
const isUpdateModalOpen = ref(false);
// 생성 모달이 오픈되었을때 값을 변경해주어야 합니다.
const openCreateModal = () => {
isCreateModalOpen.value = true;
};
const selectedBoardId = ref('');
const selectedBoardTitle = ref('');
const openUpdateModal = (boardId, boardTitle) => {
selectedBoardId.value = boardId;
selectedBoardTitle.value = boardTitle;
isUpdateModalOpen.value = true;
};
// 모달이 닫힐때 또한 값을 변경해주어야 겠죠.
const closeModal = () => {
isCreateModalOpen.value = false;
isUpdateModalOpen.value = false;
};
/**
이는 추가 구현사항입니다. 저의 경우 특정 요청에서 200 응답을 받았을 때 모달 창이 닫히게
구현하고 싶어서 아래와 같이 함수를 작성해주었습니다.
**/
const onBoardCreated = () => {
closeModal();
setTimeout(fetchBoards, 300);
};
const handleDeleteBoard = async (boardId: string) => {
try {
await deleteBoard(boardId);
await fetchBoards();
} catch (error) {
console.error(error);
}
};
const fetchBoards = async () => {
// console.log('Fetching boards...');
try {
const response = await displayBoards();
boardsData.value = response.data;
} catch (error) {
console.error(error);
}
};
onMounted(fetchBoards);
</script>
<template>
<div class="project-container">
<div class="title">
프로젝트
</div>
<!-- 모달창을 여는 데 사용할 버튼이나 요소를 만들어주세요 -->
<div class="button-container" @click="openCreateModal">
<font-awesome-icon icon="fa-regular fa-square-plus" style="margin-right: 8px"/>
<span>프로젝트 생성</span>
</div>
<div class="projects-list">
<router-link :to="`/project/${board.boardId}`" class="project" v-for="board in boardsData" :key="board.boardId" @click="selectProject(board.boardId, board.boardTitle)">
<div class="project-content">
<div class="project-title">{{ board.boardTitle }}</div>
<div class="project-member-container">
<div v-for="member in board.members" :key="member.memberId" class="badge-created">
{{ member.username }}
</div>
</div>
</div>
<div class="board-button-container">
<font-awesome-icon icon="fa-regular fa-pen-to-square" @click="openUpdateModal(board.boardId, board.boardTitle)" style="margin-right: 15px;" class="board-icon"/>
<font-awesome-icon icon="fa-solid fa-trash-can" @click="handleDeleteBoard(board.boardId)" class="board-icon"/>
</div>
</router-link>
</div>
</div>
<!-- 아래에 모달 관련 요소들만 작성하면 한결 깔끔 하답니다. -->
<TheModal :is-open="isCreateModalOpen" @close="closeModal">
<CreateBoard @board-created="onBoardCreated"/>
</TheModal>
<TheModal :is-open="isUpdateModalOpen" @close="closeModal">
<UpdateBoard
:boardId="selectedBoardId"
:boardTitle="selectedBoardTitle"
@board-created="onBoardCreated"/>
</TheModal>
</template>
HTML
복사
•
:is-open 는 부모 컴포넌트의 데이터인 isCreatedModelOpen 과 연결하기 위해 사용합니다.
◦
: 기호는 Vue.js 템플릿 문법에서 동적 속성(또는 props)을 나타냅니다. 이 기호는 v-bind: 의 축약형이며, 부모 컴포넌트의 데이터를 자식 컴포넌트의 속성에 바인딩 하기 위해 사용됩니다.
•
@board-created=”onBoardCreated” 가 의미하는 바는 다음과 같습니다. “자식 컴포넌트에서 board-created 라는 데이터를 보내면 onBoardCreated 함수를 호출해라” emit 으로 보낸 데이터와 같아야 하므로, 이점 꼭 유의해주세요.