Merge branch 'main' into khj

This commit is contained in:
khj0414 2025-01-21 15:52:40 +09:00
commit 3681ed9031
8 changed files with 425 additions and 219 deletions

View File

@ -4,14 +4,14 @@
<router-view></router-view> <router-view></router-view>
</template> </template>
</component> </component>
<ToastModal /> <ToastModal />
</template> </template>
<script setup> <script setup>
import { computed } from 'vue'; import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import NormalLayout from './layouts/NormalLayout.vue'; import NormalLayout from './layouts/NormalLayout.vue';
import NoLayout from './layouts/NoLayout.vue'; import NoLayout from './layouts/NoLayout.vue';
import ToastModal from '@c/modal/ToastModal.vue'; import ToastModal from '@c/modal/ToastModal.vue';
const route = useRoute(); const route = useRoute();

View File

@ -2,7 +2,7 @@
<div class="mb-2 row"> <div class="mb-2 row">
<label :for="name" class="col-md-2 col-form-label" :class="isLabel ? 'd-block' : 'd-none'"> <label :for="name" class="col-md-2 col-form-label" :class="isLabel ? 'd-block' : 'd-none'">
{{ title }} {{ title }}
<span :class="isEssential ? 'text-red' : 'none'">*</span> <span class="text-danger">*</span>
</label> </label>
<div class="col-md-10"> <div class="col-md-10">
<input <input

View File

@ -11,21 +11,29 @@
/> />
</div> </div>
<!-- 게시물 내용 섹션 --> <!-- 게시물 내용 섹션 -->
<div class="col-md-10"> <div :class="contentColClass">
<div class="card-body"> <div class="card-body">
<!-- 태그 --> <!-- 태그 -->
<h6 class="badge rounded-pill bg-primary text-white mb-2"> <h6 class="badge rounded-pill bg-primary text-white mb-2">
{{ category }} {{ category }}
</h6> </h6>
<!-- 제목 --> <!-- 제목 -->
<h5 class="card-title">{{ title }}</h5> <h5 class="card-title">
{{ title }}
<span class="text-muted me-3" v-if="attachment">
<i class="fa-solid fa-paperclip"></i>
</span>
</h5>
<!-- 본문 --> <!-- 본문 -->
<p class="card-text str_wrap">{{ content }}</p> <p class="card-text str_wrap">{{ content }}</p>
<!-- 날짜 --> <!-- 날짜 -->
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<small class="text-muted">{{ formatDate(date) }}</small> <small class="text-muted">{{ formattedDate }}</small>
<!-- 좋아요와 댓글 --> <!-- 조회수, 좋아요, 댓글 -->
<div> <div>
<span class="text-muted me-3">
<i class="fa-regular fa-eye"></i> {{ views || 0 }}
</span>
<span class="text-muted me-3"> <span class="text-muted me-3">
<i class="bx bx-like"></i> {{ likes || 0 }} <i class="bx bx-like"></i> {{ likes || 0 }}
</span> </span>
@ -40,49 +48,64 @@
</div> </div>
</template> </template>
<script> <script setup>
export default { import { computed } from 'vue';
props: { import { defineProps } from 'vue';
img: {
type: String, // Props
default: null, const props = defineProps({
}, img: {
category: { type: String,
type: String, default: null,
required: true,
},
title: {
type: String,
required: true,
},
content: {
type: String,
required: true,
},
date: {
type: String,
required: true,
},
likes: {
type: Number,
default: 0,
},
comments: {
type: Number,
default: 0,
},
}, },
methods: { category: {
formatDate(dateString) { type: String,
const date = new Date(dateString); required: false,
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(
date.getDate()
).padStart(2, "0")} ${String(date.getHours()).padStart(2, "0")}:${String(
date.getMinutes()
).padStart(2, "0")}`;
},
}, },
}; title: {
type: String,
required: true,
},
content: {
type: String,
required: true,
},
date: {
type: String,
required: true,
},
views: {
type: Number,
default: 0,
},
likes: {
type: Number,
default: 0,
},
comments: {
type: Number,
default: 0,
},
attachment: {
type: Boolean,
default: false,
}
});
// computed
const contentColClass = computed(() => {
return props.img ? 'col-md-10 col-12' : 'col-md-12';
});
// formattedDate computed
const formattedDate = computed(() => {
const date = new Date(props.date);
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(
date.getDate()
).padStart(2, "0")} ${String(date.getHours()).padStart(2, "0")}:${String(
date.getMinutes()
).padStart(2, "0")}`;
});
</script> </script>
<style> <style>

View File

@ -10,8 +10,10 @@
:title="post.title" :title="post.title"
:content="post.content" :content="post.content"
:date="post.date" :date="post.date"
:views="post.views"
:likes="post.likes" :likes="post.likes"
:comments="post.comments" :comments="post.comments"
:attachment="post.attachment"
/> />
</div> </div>
</div> </div>

View File

@ -1,35 +1,126 @@
<template> <template>
<nav aria-label="Page navigation"> <nav aria-label="Page navigation">
<ul class="pagination pagination-rounded justify-content-center"> <ul class="pagination pagination-rounded justify-content-center">
<!-- <li class="page-item first"> <!-- 페이지 이동 -->
<a class="page-link" href="javascript:void(0);"><i class="tf-icon bx bx-chevrons-left bx-sm"></i></a> <li
</li> --> class="page-item first"
<!-- <li class="page-item prev"> @click="emitPageChange(1)"
<a class="page-link" href="javascript:void(0);"><i class="tf-icon bx bx-chevron-left bx-sm"></i></a> :class="{ disabled: isFirstPage }"
</li> --> >
<li class="page-item active"> <a class="page-link" href="javascript:void(0);">
<a class="page-link" href="javascript:void(0);">1</a> <i class="tf-icon bx bx-chevrons-left bx-sm"></i>
</a>
</li> </li>
<li class="page-item">
<a class="page-link" href="javascript:void(0);">2</a> <!-- 이전 페이지 이동 -->
<li
class="page-item prev"
@click="emitPageChange(navigateFirstPage-1)"
:class="{ disabled: !hasPreviousPage }"
>
<a class="page-link" href="javascript:void(0);">
<i class="tf-icon bx bx-chevron-left bx-sm"></i>
</a>
</li> </li>
<li class="page-item">
<a class="page-link" href="javascript:void(0);">3</a> <!-- 페이지 번호들 -->
<li
v-for="page in navigatepageNums"
:key="page"
:class="['page-item', { active: page === currentPage }]"
@click="emitPageChange(page)"
>
<a class="page-link" href="javascript:void(0);">{{ page }}</a>
</li> </li>
<li class="page-item">
<a class="page-link" href="javascript:void(0);">4</a> <!-- 다음 페이지 이동 -->
<li
class="page-item next"
@click="emitPageChange(navigateLastPage+1)"
:class="{ disabled: !hasNextPage }"
>
<a class="page-link" href="javascript:void(0);">
<i class="tf-icon bx bx-chevron-right bx-sm"></i>
</a>
</li> </li>
<li class="page-item">
<a class="page-link" href="javascript:void(0);">5</a> <!-- 마지막 페이지 이동 -->
</li> <li
<li class="page-item next"> class="page-item last"
<a class="page-link" href="javascript:void(0);"><i class="tf-icon bx bx-chevron-right bx-sm"></i></a> @click="emitPageChange(pages)"
</li> :class="{ disabled: isLastPage }"
<li class="page-item last"> >
<a class="page-link" href="javascript:void(0);"><i class="tf-icon bx bx-chevrons-right bx-sm"></i></a> <a class="page-link" href="javascript:void(0);">
<i class="tf-icon bx bx-chevrons-right bx-sm"></i>
</a>
</li> </li>
</ul> </ul>
</nav> </nav>
</template> </template>
<script setup></script>
<script setup>
import { defineProps, defineEmits } from 'vue';
// Props
const props = defineProps({
currentPage: {
type: Number,
required: true
},
pages: {
type: Number,
required: true
},
prePage: {
type: Number,
required: true
},
nextPage: {
type: Number,
required: true
},
isFirstPage: {
type: Boolean,
required: true
},
isLastPage: {
type: Boolean,
required: true
},
hasPreviousPage: {
type: Boolean,
required: true
},
hasNextPage: {
type: Boolean,
required: true
},
navigatePages: {
type: Number,
required: true
},
navigatepageNums: {
type: Array,
required: true
},
navigateFirstPage: {
type: Number,
required: true
},
navigateLastPage: {
type: Number,
required: true
}
});
//
const emit = defineEmits(['update:currentPage']);
//
const emitPageChange = (page) => {
if (page !== props.currentPage && page >= 1 && page <= props.pages) {
emit('update:currentPage', page);
}
};
</script>

View File

@ -1,24 +1,66 @@
<template> <template>
<div class="container-xxl flex-grow-1 container-p-y"> <div class="container-xxl flex-grow-1 container-p-y">
<!-- 검색 --> <div class="row mb-4">
<search-bar @update:data="search" /> <!-- 검색창 -->
<div class="col">
<search-bar @update:data="search" />
</div>
</div>
<!-- 리스트 --> <div class="row">
<div class="row g-3"> <!-- 글쓰기 -->
<div class="mt-8"> <div class="mb-4">
<router-link to="/board/write"> <router-link to="/board/write">
<WriteButton /> <WriteButton />
</router-link> </router-link>
</div> </div>
</div>
<board-card :posts="paginatedList" @click="goDetail" /> <!-- 공지사항 리스트 -->
<div v-if="pagination.currentPage === 1" class="mb-8">
<div class="row g-3">
<h3>공지사항</h3>
</div>
<div class="row">
<BoardCardList :posts="noticeList" @click="goDetail" />
</div>
</div>
<!-- 페이지네이션 --> <!-- 일반 리스트 -->
<div>
<div class="row g-3">
<h3 class="col">일반게시판</h3>
<!-- 셀렉트 박스 -->
<div class="col-12 col-md-auto">
<select class="form-select" v-model="selectedOrder" @change="handleSortChange">
<option value="date">최신날짜</option>
<option value="views">조회수</option>
</select>
</div>
</div>
<div class="row">
<BoardCardList :posts="generalList" @click="goDetail" />
</div>
</div>
<!-- 페이지네이션 -->
<div class="row g-3">
<div class="mt-8"> <div class="mt-8">
<pagination <Pagination
:current-page="currentPage" :currentPage="pagination.currentPage"
:total-pages="totalPages" :pages="pagination.pages"
@update:page="changePage" :prePage="pagination.prePage"
:nextPage="pagination.nextPage"
:isFirstPage="pagination.isFirstPage"
:isLastPage="pagination.isLastPage"
:hasPreviousPage="pagination.hasPreviousPage"
:hasNextPage="pagination.hasNextPage"
:navigatePages="pagination.navigatePages"
:navigatepageNums="pagination.navigatepageNums"
:navigateFirstPage="pagination.navigateFirstPage"
:navigateLastPage="pagination.navigateLastPage"
@update:currentPage="handlePageChange"
/> />
</div> </div>
</div> </div>
@ -27,7 +69,7 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import BoardCard from '@/components/list/BoardCardList.vue'; import BoardCardList from '@/components/list/BoardCardList.vue';
import Pagination from '@c/pagination/Pagination.vue'; import Pagination from '@c/pagination/Pagination.vue';
import SearchBar from '@c/search/SearchBar.vue'; import SearchBar from '@c/search/SearchBar.vue';
import router from '@/router'; import router from '@/router';
@ -35,75 +77,118 @@ import WriteButton from '@c/button/WriteBtn.vue';
import axios from '@api'; import axios from '@api';
// //
const list = ref([]); const generalList = ref([]);
const noticeList = ref([]);
const searchText = ref(''); const searchText = ref('');
const selectedOrder = ref('date');
const sortDirection = ref('desc');
const pagination = ref({
currentPage: 1,
pages: 1,
prePage: 0,
nextPage: 1,
isFirstPage: true,
isLastPage: false,
hasPreviousPage: false,
hasNextPage: false,
navigatePages: 10,
navigatepageNums: [1],
navigateFirstPage: 1,
navigateLastPage: 1
});
// //
const goDetail = (id) => { const goDetail = (id) => {
console.log('Navigating to ID:', id)
router.push({ name: 'BoardDetail', params: { id } }); router.push({ name: 'BoardDetail', params: { id } });
}; };
// //
const search = (e) => { const search = (e) => {
searchText.value = e.trim(); searchText.value = e.trim();
fetchGeneralPosts(1);
fetchNoticePosts(searchText.value);
}; };
// //
const filteredList = computed(() => const handleSortChange = () => {
list.value.filter((item) => fetchGeneralPosts(1);
item.title.toLowerCase().includes(searchText.value.toLowerCase())
)
);
//
const currentPage = ref(1); //
const itemsPerPage = 5; //
//
const paginatedList = computed(() => {
const start = (currentPage.value - 1) * itemsPerPage;
const end = start + itemsPerPage;
return filteredList.value.slice(start, end);
});
//
const totalPages = computed(() => {
return Math.ceil(filteredList.value.length / itemsPerPage);
});
//
const changePage = (page) => {
if (page >= 1 && page <= totalPages.value) {
currentPage.value = page;
}
}; };
// // ()
const fetchPosts = async () => { const fetchGeneralPosts = async (page = 1) => {
const response = await axios.get("board/general"); const response = await axios.get("board/general", {
console.log(response.data.data.list) params: {
page: page,
orderBy: selectedOrder.value,
sortDirection: sortDirection.value,
searchKeyword: searchText.value
}
});
if (response.data && response.data.data && Array.isArray(response.data.data.list)) { if (response.data && response.data.data) {
list.value = response.data.data.list.map((post, index) => ({ const data = response.data.data;
//
generalList.value = data.list.map((post, index) => ({
...post, ...post,
id: post.id || index, id: post.id || index,
img: post.img || null, img: post.img || null,
likes: post.likes || 0, views: post.cnt || 0,
comments: post.comments || 0, likes: post.likeCount != null ? post.likeCount : null,
comments: post.commentCount != null ? post.commentCount : null,
attachment: post.hasAttachment || false,
}));
//
pagination.value = {
currentPage: data.pageNum,
pages: data.pages,
prePage: data.prePage,
nextPage: data.nextPage,
isFirstPage: data.isFirstPage,
isLastPage: data.isLastPage,
hasPreviousPage: data.hasPreviousPage,
hasNextPage: data.hasNextPage,
navigatePages: data.navigatePages,
navigatepageNums: data.navigatepageNums,
navigateFirstPage: data.navigateFirstPage,
navigateLastPage: data.navigateLastPage
};
} else {
console.error("데이터 오류:", response.data);
}
};
// ()
const fetchNoticePosts = async () => {
const response = await axios.get("board/notices", {
params: {
searchKeyword: searchText.value
}
});
if (response.data && response.data.data && Array.isArray(response.data.data)) {
noticeList.value = response.data.data.map((post, index) => ({
...post,
id: post.id || index,
img: post.img || null,
views: post.cnt || 0,
attachment: post.hasAttachment || false,
})); }));
} else { } else {
console.error("Unexpected API response structure:", response.data); console.error("데이터 오류:", response.data);
}
};
//
const handlePageChange = (page) => {
if (page !== pagination.value.currentPage) {
fetchGeneralPosts(page);
} }
}; };
// //
onMounted(() => { onMounted(() => {
fetchPosts(); fetchGeneralPosts();
fetchNoticePosts();
}); });
</script> </script>
<style>
/* 필요에 따라 스타일 추가 */
</style>

View File

@ -5,17 +5,17 @@
<div class="card"> <div class="card">
<!-- 프로필 헤더 --> <!-- 프로필 헤더 -->
<div class="card-header"> <div class="card-header">
<BoardProfile :boardId="currentBoardId.value" :profileName="profileName" /> <BoardProfile :boardId="currentBoardId" :profileName="profileName" />
</div> </div>
<!-- 게시글 내용 --> <!-- 게시글 내용 -->
<div class="card-body"> <div class="card-body">
<h5 class="mb-4">{{ boardTitle }}</h5> <h5 class="mb-4">{{ boardTitle }}</h5>
<!-- HTML 콘텐츠 렌더링 --> <!-- HTML 콘텐츠 렌더링 -->
<div class="board-content" v-html="boardContent"></div> <div class="board-content text-body" style="line-height: 1.6;" v-html="convertedContent"></div>
<!-- 첨부파일 목록 --> <!-- 첨부파일 목록 -->
<ul v-if="attachments.length" class="attachments mt-4"> <ul v-if="attachments.length" class="attachments mt-4 list-unstyled">
<li v-for="(attachment, index) in attachments" :key="index"> <li v-for="(attachment, index) in attachments" :key="index" class="mb-2">
<a :href="attachment.url" target="_blank">{{ attachment.name }}</a> <a :href="attachment.url" target="_blank" class="text-decoration-none">{{ attachment.name }}</a>
</li> </li>
</ul> </ul>
<!-- 댓글 영역 --> <!-- 댓글 영역 -->
@ -23,10 +23,7 @@
</div> </div>
<!-- 수정 버튼 --> <!-- 수정 버튼 -->
<div class="card-footer d-flex justify-content-end"> <div class="card-footer d-flex justify-content-end">
<button <button class="btn btn-primary" @click="goToEditPage">
class="btn btn-primary"
@click="goToEditPage"
>
수정 수정
</button> </button>
</div> </div>
@ -42,11 +39,14 @@ import BoardProfile from '@c/board/BoardProfile.vue';
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import axios from '@api'; import axios from '@api';
import Quill from 'quill';
import DOMPurify from 'dompurify';
// //
const profileName = ref('익명 사용자'); const profileName = ref('익명 사용자');
const boardTitle = ref('제목 없음'); const boardTitle = ref('제목 없음');
const boardContent = ref('내용 없음'); const boardContent = ref('');
const convertedContent = ref('내용 없음');
const comments = ref([]); const comments = ref([]);
const attachments = ref([]); const attachments = ref([]);
@ -54,7 +54,6 @@ const attachments = ref([]);
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const currentBoardId = ref(Number(route.params.id)); const currentBoardId = ref(Number(route.params.id));
console.log(currentBoardId.value)
// //
const goToEditPage = () => { const goToEditPage = () => {
@ -71,7 +70,22 @@ const fetchBoardDetails = async () => {
const boardDetail = data.boardDetail || {}; const boardDetail = data.boardDetail || {};
profileName.value = boardDetail.author || '익명 사용자'; profileName.value = boardDetail.author || '익명 사용자';
boardTitle.value = boardDetail.title || '제목 없음'; boardTitle.value = boardDetail.title || '제목 없음';
boardContent.value = boardDetail.content || '내용 없음'; boardContent.value = boardDetail.content || '';
// Quill Delta HTML
if (boardContent.value) {
try {
const quillContainer = document.createElement('div');
const quillInstance = new Quill(quillContainer);
quillInstance.setContents(JSON.parse(boardContent.value));
convertedContent.value = DOMPurify.sanitize(quillContainer.innerHTML);
} catch (parseError) {
console.error('Delta 데이터 변환 오류:', parseError);
convertedContent.value = '내용을 표시할 수 없습니다.';
}
} else {
convertedContent.value = '내용 없음';
}
attachments.value = data.attachments || []; attachments.value = data.attachments || [];
comments.value = data.comments || []; comments.value = data.comments || [];
@ -83,7 +97,6 @@ const fetchBoardDetails = async () => {
// //
onMounted(() => { onMounted(() => {
console.log('Route Params:', route.params);
fetchBoardDetails(); fetchBoardDetails();
}); });
</script> </script>

View File

@ -9,36 +9,69 @@
<div class="col-xl-12"> <div class="col-xl-12">
<div class="card-body"> <div class="card-body">
<FormInput title="제목" name="title" :is-essential="true" :is-alert="titleAlert" @update:data="title = $event" />
<FormSelect title="카테고리" name="cate" :is-essential="true" :data="categoryList" @update:data="category = $event" />
<FormInput <FormInput
v-show="category == 1" title="제목"
title="비밀번호" name="title"
name="pw"
type="password"
:is-essential="true" :is-essential="true"
:is-alert="passwordAlert" :is-alert="titleAlert"
@update:data="password = $event" v-model="title"
/> />
<FormFile title="첨부파일" name="files" :is-alert="attachFilesAlert" @update:data="attachFiles = $event" /> <!-- 카테고리 선택 -->
<div class="mb-4 d-flex align-items-center">
<label class="col-md-2 col-form-label">카테고리 <span class="text-danger">*</span></label>
<div class="d-flex flex-wrap align-items-center mt-3 ms-1">
<div
v-for="(categoryName, index) in categoryList"
:key="index"
class="form-check me-3"
>
<input
class="form-check-input"
type="radio"
:id="`category-${index}`"
:value="index"
v-model="category"
/>
<label class="form-check-label" :for="`category-${index}`">
{{ categoryName }}
</label>
</div>
</div>
</div>
<!-- 비밀번호 필드 -->
<div v-if="category === 1" class="mb-4">
<FormInput
title="비밀번호"
name="pw"
type="password"
:is-essential="true"
:is-alert="passwordAlert"
v-model="password"
/>
</div>
<FormFile
title="첨부파일"
name="files"
:is-alert="attachFilesAlert"
@update:data="attachFiles = $event"
/>
<div class="mb-4"> <div class="mb-4">
<label for="html5-tel-input" class="col-md-2 col-form-label"> <label for="html5-tel-input" class="col-md-2 col-form-label">
내용 내용
<span class="text-red">*</span> <span class="text-danger">*</span>
<div class="invalid-feedback" :class="contentAlert ? 'display-block' : ''">내용을 확인해주세요.</div> <div class="invalid-feedback" :class="contentAlert ? 'display-block' : ''">내용을 확인해주세요.</div>
</label> </label>
<div class="col-md-12"> <div class="col-md-12">
<!-- <TEditor @update:data="content = $event"/> -->
<QEditor @update:data="content = $event" /> <QEditor @update:data="content = $event" />
</div> </div>
</div> </div>
<div class="mb-4 d-flex justify-content-end"> <div class="mb-4 d-flex justify-content-end">
<button type="button" class="btn btn-info right" @click="goList"><i class='bx bx-left-arrow-alt'></i></button> <button type="button" class="btn btn-info" @click="goList"><i class='bx bx-left-arrow-alt'></i></button>
<button type="button" class="btn btn-primary ms-1" @click="write"><i class='bx bx-check'></i></button> <button type="button" class="btn btn-primary ms-1" @click="write"><i class='bx bx-check'></i></button>
</div> </div>
</div> </div>
@ -49,114 +82,73 @@
<script setup> <script setup>
import QEditor from '@c/editor/QEditor.vue'; import QEditor from '@c/editor/QEditor.vue';
import TEditor from '@c/editor/TEditor.vue';
import FormInput from '@c/input/FormInput.vue'; import FormInput from '@c/input/FormInput.vue';
import FormSelect from '@c/input/FormSelect.vue';
import FormFile from '@c/input/FormFile.vue'; import FormFile from '@c/input/FormFile.vue';
import { ref, watch } from 'vue'; import { ref } from 'vue';
import router from '@/router'; import router from '@/router';
import axios from '@api'; import axios from '@api';
const categoryList = ['자유', '익명', '공지사항']; const categoryList = ['자유', '익명', '공지사항']; //
// input !!
const title = ref(''); const title = ref('');
const password = ref(''); const password = ref('');
const category = ref(0); const category = ref(0); // 0
const content = ref(''); const content = ref('');
const attachFiles = ref(null); const attachFiles = ref(null);
//input const titleAlert = ref(false);
const titleAlert = ref(true);
const passwordAlert = ref(false); const passwordAlert = ref(false);
const contentAlert = ref(false); const contentAlert = ref(false);
const attachFilesAlert = ref(false); const attachFilesAlert = ref(false);
const goList = () => { const goList = () => {
// ,
router.push('/board'); router.push('/board');
}; };
const write = async () => { const write = async () => {
// titleAlert.value = !title.value;
if (!title.value) { passwordAlert.value = category.value === 1 && !password.value;
titleAlert.value = true; contentAlert.value = !content.value;
return;
} else {
titleAlert.value = false;
}
if (category.value === 1 && !password.value) { if (titleAlert.value || passwordAlert.value || contentAlert.value) {
passwordAlert.value = true;
return; return;
} else {
passwordAlert.value = false;
}
if (!content.value) {
contentAlert.value = true;
return;
} else {
contentAlert.value = false;
} }
try { try {
//
const boardData = { const boardData = {
LOCBRDTTL: title.value, LOCBRDTTL: title.value,
LOCBRDCON: content.value, LOCBRDCON: content.value,
LOCBRDPWD: category.value === 1 ? password.value : null, LOCBRDPWD: category.value === 1 ? password.value : null,
LOCBRDTYP: category.value === 1 ? 'S' : 'F', // !! LOCBRDTYP: category.value === 1 ? 'S' : 'F',
// MEMBERSEQ: id()
}; };
// API
const { data: boardResponse } = await axios.post('board', boardData); const { data: boardResponse } = await axios.post('board', boardData);
const boardId = boardResponse.data.boardId; const boardId = boardResponse.data.CMNBRDSEQ;
//
if (attachFiles.value && attachFiles.value.length > 0) { if (attachFiles.value && attachFiles.value.length > 0) {
for (const file of attachFiles.value) { for (const file of attachFiles.value) {
const realName = file.name.substring(0, file.name.lastIndexOf('.'));
const fileInfo = {
path: "/uploads", // ( )
originalName: realName, //
extension: file.name.split('.').pop(), //
registrantId: 1, // ID ( )
};
const formData = new FormData(); const formData = new FormData();
formData.append("MEMBERSEQ",registrantId); // const fileNameWithoutExt = file.name.replace(/\.[^/.]+$/, '');
formData.append("CMNFLEPAT", fileInfo.path); //
formData.append("CMNFLENAM", fileInfo.originalName); // ()
formData.append("CMNFLEORG", fileInfo.originalName); // ()
formData.append("CMNFLEEXT", fileInfo.extension); //
formData.append("CMNFLESIZ", file.size); //
formData.append("CMNFLEREG", fileInfo.registrantId); // ID
const response = await axios.post(`board/${boardId}/attachments`, formData, { formData.append('CMNBRDSEQ', boardId);
formData.append('CMNFLEORG', fileNameWithoutExt);
formData.append('CMNFLEEXT', file.name.split('.').pop());
formData.append('CMNFLESIZ', file.size);
formData.append('CMNFLEPAT', 'boardfile');
formData.append('file', file);
await axios.post(`board/${boardId}/attachments`, formData, {
headers: { headers: {
"Content-Type": "multipart/form-data", 'Content-Type': 'multipart/form-data',
}, },
}); });
} }
} }
alert("게시물이 작성되었습니다."); alert('게시물이 작성되었습니다.');
goList(); goList();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
alert("게시물 작성 중 오류가 발생했습니다."); alert('게시물 작성 중 오류가 발생했습니다.');
} }
}; };
</script> </script>
<style>
.text-red {
color: red;
text-align: center;
}
</style>