board-0117머지

This commit is contained in:
dyhj625 2025-01-20 16:23:30 +09:00
parent c52b3ecd95
commit 882284bbb4
4 changed files with 319 additions and 124 deletions

View File

@ -11,7 +11,7 @@
/>
</div>
<!-- 게시물 내용 섹션 -->
<div class="col-md-10">
<div :class="contentColClass">
<div class="card-body">
<!-- 태그 -->
<h6 class="badge rounded-pill bg-primary text-white mb-2">
@ -23,9 +23,12 @@
<p class="card-text str_wrap">{{ content }}</p>
<!-- 날짜 -->
<div class="d-flex justify-content-between">
<small class="text-muted">{{ formatDate(date) }}</small>
<!-- 좋아요와 댓글 -->
<small class="text-muted">{{ formattedDate }}</small>
<!-- 조회수, 좋아요, 댓글 -->
<div>
<span class="text-muted me-3">
<i class="fa-regular fa-eye"></i> {{ views || 0 }}
</span>
<span class="text-muted me-3">
<i class="bx bx-like"></i> {{ likes || 0 }}
</span>
@ -40,49 +43,60 @@
</div>
</template>
<script>
export default {
props: {
img: {
type: String,
default: null,
},
category: {
type: String,
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,
},
<script setup>
import { computed } from 'vue';
import { defineProps } from 'vue';
// Props
const props = defineProps({
img: {
type: String,
default: null,
},
methods: {
formatDate(dateString) {
const date = new Date(dateString);
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")}`;
},
category: {
type: String,
required: true,
},
};
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,
},
});
// 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>
<style>

View File

@ -10,6 +10,7 @@
:title="post.title"
:content="post.content"
:date="post.date"
:views="post.views"
:likes="post.likes"
:comments="post.comments"
/>

View File

@ -1,35 +1,126 @@
<template>
<nav aria-label="Page navigation">
<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 prev">
<a class="page-link" href="javascript:void(0);"><i class="tf-icon bx bx-chevron-left bx-sm"></i></a>
</li> -->
<li class="page-item active">
<a class="page-link" href="javascript:void(0);">1</a>
<!-- 페이지 이동 -->
<li
class="page-item first"
@click="emitPageChange(navigateFirstPage)"
:class="{ disabled: isFirstPage }"
>
<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">
<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 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 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 class="page-item">
<a class="page-link" href="javascript:void(0);">5</a>
</li>
<li class="page-item next">
<a class="page-link" href="javascript:void(0);"><i class="tf-icon bx bx-chevron-right bx-sm"></i></a>
</li>
<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>
<!-- 마지막 페이지 이동 -->
<li
class="page-item last"
@click="emitPageChange(navigateLastPage)"
:class="{ disabled: isLastPage }"
>
<a class="page-link" href="javascript:void(0);">
<i class="tf-icon bx bx-chevrons-right bx-sm"></i>
</a>
</li>
</ul>
</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,59 @@
<template>
<div class="container-xxl flex-grow-1 container-p-y">
<!-- 검색 -->
<search-bar @update:data="search" />
<!-- 상단 : 검색창, 정렬 셀렉트 박스, 글쓰기 버튼 -->
<div class="row mb-4">
<!-- 검색창 -->
<div class="col">
<search-bar @update:data="search" />
</div>
</div>
<!-- 리스트 -->
<div class="row g-3">
<div class="mt-8">
<div class="row">
<!-- 정렬 셀렉트 박스 -->
<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">
<WriteButton />
</router-link>
</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">
<pagination
:current-page="currentPage"
:total-pages="totalPages"
@update:page="changePage"
<Pagination
:currentPage="pagination.currentPage"
:pages="pagination.pages"
: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>
@ -35,75 +70,129 @@ import WriteButton from '@c/button/WriteBtn.vue';
import axios from '@api';
//
const list = ref([]);
const generalList = ref([]);
const noticeList = 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) => {
console.log('Navigating to ID:', id)
router.push({ name: 'BoardDetail', params: { id } });
};
//
const search = (e) => {
searchText.value = e.trim();
fetchGeneralPosts(1);
fetchNoticePosts(searchText.value);
};
//
const filteredList = computed(() =>
list.value.filter((item) =>
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 handleSortChange = (event) => {
const value = event.target.value;
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 fetchPosts = async () => {
const response = await axios.get("board/general");
console.log(response.data.data.list)
// ()
const fetchGeneralPosts = async (page = 1) => {
const response = await axios.get("board/general", {
params: {
page: page,
orderBy: selectedOrder.value,
sortDirection: sortDirection.value,
searchKeyword: searchText.value
}
});
if (response.data && response.data.data && Array.isArray(response.data.data.list)) {
list.value = response.data.data.list.map((post, index) => ({
if (response.data && response.data.data) {
const data = response.data.data;
//
generalList.value = data.list.map((post, index) => ({
...post,
id: post.id || index,
img: post.img || null,
likes: post.likes || 0,
comments: post.comments || 0,
views: post.cnt || 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 {
console.error("Unexpected API response structure:", response.data);
console.error("데이터 오류:", response.data);
}
};
//
const handlePageChange = (page) => {
if (page !== pagination.value.currentPage) {
fetchGeneralPosts(page);
}
};
//
onMounted(() => {
fetchPosts();
fetchGeneralPosts();
fetchNoticePosts();
});
</script>
<style>
/* 필요에 따라 스타일 추가 */
</style>