Compare commits

...

7 Commits

5 changed files with 320 additions and 125 deletions

View File

@ -11,7 +11,7 @@
/> />
</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">
@ -23,9 +23,12 @@
<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,9 +43,12 @@
</div> </div>
</template> </template>
<script> <script setup>
export default { import { computed } from 'vue';
props: { import { defineProps } from 'vue';
// Props
const props = defineProps({
img: { img: {
type: String, type: String,
default: null, default: null,
@ -63,6 +69,10 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
views: {
type: Number,
default: 0,
},
likes: { likes: {
type: Number, type: Number,
default: 0, default: 0,
@ -71,18 +81,22 @@ export default {
type: Number, type: Number,
default: 0, default: 0,
}, },
}, });
methods: {
formatDate(dateString) { // computed
const date = new Date(dateString); 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( return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(
date.getDate() date.getDate()
).padStart(2, "0")} ${String(date.getHours()).padStart(2, "0")}:${String( ).padStart(2, "0")} ${String(date.getHours()).padStart(2, "0")}:${String(
date.getMinutes() date.getMinutes()
).padStart(2, "0")}`; ).padStart(2, "0")}`;
}, });
},
};
</script> </script>
<style> <style>

View File

@ -10,6 +10,7 @@
: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"
/> />

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(navigateFirstPage)"
<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(prePage)"
: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(nextPage)"
: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(navigateLastPage)"
</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,6 +1,6 @@
<template> <template>
<div class="input-group mb-3 d-flex"> <div class="input-group mb-3 d-flex">
<input type="text" class="form-control bg-white" placeholder="Search" @change="search" /> <input type="text" class="form-control" placeholder="Search" @change="search" />
<button type="button" class="btn btn-primary"><i class="bx bx-search bx-md"></i></button> <button type="button" class="btn btn-primary"><i class="bx bx-search bx-md"></i></button>
</div> </div>
</template> </template>

View File

@ -1,24 +1,59 @@
<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">
<!-- 검색창 -->
<div class="col">
<search-bar @update:data="search" /> <search-bar @update:data="search" />
</div>
</div>
<!-- 리스트 --> <div class="row">
<div class="row g-3"> <!-- 정렬 셀렉트 박스 -->
<div class="mt-8"> <div class="col-md-3 mb-4">
<select class="form-select" @change="handleSortChange">
<option value="">정렬 선택</option>
<option value="view">조회수</option>
<option value="date">날짜</option>
</select>
</div>
<!-- 글쓰기 버튼 -->
<div class="col-auto ms-auto 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 class="row g-3 mt-8">
<h3>공지사항</h3>
<board-card :posts="noticeList" @click="goDetail" />
</div>
<!-- 일반 게시물 리스트 -->
<div class="row g-3 mt-8">
<h3>일반게시판</h3>
<board-card :posts="generalList" @click="goDetail" />
</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>
@ -35,75 +70,129 @@ 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('');
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 = (event) => {
list.value.filter((item) => const value = event.target.value;
item.title.toLowerCase().includes(searchText.value.toLowerCase()) if (value === 'view') {
) selectedOrder.value = 'view';
); sortDirection.value = 'desc';
} else if (value === 'date') {
selectedOrder.value = 'date';
sortDirection.value = 'desc';
} else {
selectedOrder.value = '';
sortDirection.value = 'desc';
};
fetchGeneralPosts(1);
};
// // ()
const currentPage = ref(1); // const fetchGeneralPosts = async (page = 1) => {
const itemsPerPage = 5; // const response = await axios.get("board/general", {
params: {
// page: page,
const paginatedList = computed(() => { orderBy: selectedOrder.value,
const start = (currentPage.value - 1) * itemsPerPage; sortDirection: sortDirection.value,
const end = start + itemsPerPage; searchKeyword: searchText.value
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;
} }
}; });
// if (response.data && response.data.data) {
const fetchPosts = async () => { const data = response.data.data;
const response = await axios.get("board/general"); //
console.log(response.data.data.list) generalList.value = data.list.map((post, index) => ({
if (response.data && response.data.data && Array.isArray(response.data.data.list)) {
list.value = response.data.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 || 0,
comments: post.commentCount || 0,
}));
//
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,
likes: post.likeCount || 0,
comments: post.commentCount || 0,
})); }));
} 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>