보드코맨트머지지

This commit is contained in:
dyhj625 2025-02-18 10:33:45 +09:00
parent 62c97e0a18
commit e376e5ff59
4 changed files with 446 additions and 249 deletions

View File

@ -1,60 +1,105 @@
<template>
<div>
<BoardProfile :profileName="comment.author" :showDetail="false" :author="true" :isChild="isChild" />
<BoardProfile
:unknown="unknown"
:boardId="comment.boardId"
:profileName="comment.author"
:date="comment.createdAt"
:comment="comment"
:showDetail="false"
:author="true"
:isLike="!isLike"
:isPassword="isPassword"
@editClick="editClick"
@deleteClick="deleteClick"
@submitPassword="submitPassword"
@updateReaction="handleUpdateReaction"
@toggleEdit="emit('toggleEdit', comment.commentId, true)"
/>
<div class="mt-6">
<p class="m-0">{{ comment.content }}</p>
<template v-if="isEditTextarea">
<textarea v-model="editedContent" class="form-control"></textarea>
<div class="mt-2 d-flex justify-content-end">
<button class="btn btn-secondary me-2" @click="emit('toggleEdit', comment.commentId, false)">취소</button>
<button class="btn btn-primary" @click="submitEdit">수정 완료</button>
</div>
</template>
<template v-else>
<p class="m-0">{{ comment.content }}</p>
</template>
</div>
<PlusButton v-if="isPlusButton" @click="toggleComment" class="mt-6"/>
<BoardComentArea v-if="isComment" @submit="submitComment"/>
<BoardCommentArea v-if="isComment" @submitComment="submitComment"/>
<!-- 대댓글 -->
<ul v-if="comment.children && comment.children.length" class="list-unstyled">
<li
v-for="child in comment.children"
:key="child.id"
class="pt-8 ps-10"
v-for="child in comment.children"
:key="child.commentId"
class="mt-8 pt-6 ps-10 border-top"
>
<BoardComment :comment="child" :isPlusButton="false" :isChild="true" @submitComment="addChildComment" />
<BoardComment
:comment="child"
:unknown="unknown"
:isPlusButton="false"
:isLike="true"
@submitComment="submitComment"
@updateReaction="handleUpdateReaction"
/>
</li>
</ul>
<!-- <ul class="list-unstyled twoDepth">
<li>
<BoardProfile profileName=곤데리2 :showDetail="false" />
<div class="mt-2">저도 궁금합니다.</div>
<BoardComentArea v-if="comment" />
<BoardCommentArea v-if="comment" />
</li>
</ul> -->
<!-- <BoardProfile profileName=곤데리 :showDetail="false" />
<div class="mt-2">저도 궁금합니다.</div>
<PlusButton @click="toggleComment"/>
<BoardComentArea v-if="comment" /> -->
<BoardCommentArea v-if="comment" /> -->
</div>
</template>
<script setup>
import { defineProps, defineEmits, ref } from 'vue';
import BoardProfile from './BoardProfile.vue';
import BoardComentArea from './BoardComentArea.vue';
import BoardCommentArea from './BoardCommentArea.vue';
import PlusButton from '../button/PlusBtn.vue';
import { ref } from 'vue';
import { defineEmits } from 'vue';
const props = defineProps({
comment: {
type: Object,
required: true,
},
unknown: {
type: Boolean,
default: true,
},
isPlusButton: {
type: Boolean,
default: true,
},
isChild: {
isLike: {
type: Boolean,
default: false,
},
isEditTextarea: {
type: Boolean,
default: false
},
isPassword: {
type: Boolean,
default: false,
},
});
// emits
const emit = defineEmits(['submitComment']);
const emit = defineEmits(['submitComment', 'updateReaction', 'toggleEdit', 'editClick']);
//
const isComment = ref(false);
@ -63,29 +108,31 @@ const toggleComment = () => {
};
//
const addChildComment = (parentId, newComment) => {
emit('submitComment', parentId, newComment);
const submitComment = (newComment) => {
emit('submitComment', { parentId: props.comment.commentId, ...newComment });
isComment.value = false;
};
// ,
const handleUpdateReaction = (reactionData) => {
emit('updateReaction', {
boardId: props.comment.boardId,
commentId: props.comment.commentId,
...reactionData
});
};
//
const editClick = (data) => {
emit('editClick', data);
};
//
const editedContent = ref(props.comment.content);
const submitEdit = () => {
emit('submitComment', { commentId: props.comment.commentId, content: editedContent.value });
emit('toggleEdit', props.comment.commentId, false); //
};
</script>
<style scoped>
/* .twoDepth {
margin-top: 10px;
padding-left: 40px;
}
.list-unstyled > li ~ li {
margin-top: 10px;
}
.btn-text-primary {
padding-left: 0;
}
.btn-text-primary:hover,
.btn-text-primary:active,
.btn-text-primary:focus {
background-color: transparent
} */
</style>

View File

@ -2,47 +2,66 @@
<ul class="list-unstyled mt-10">
<li
v-for="comment in comments"
:key="comment.id"
class="mt-8"
:key="comment.commentId"
class="mt-6 border-bottom pb-6"
>
<BoardComment :comment="comment" @submitComment="addComment" />
<BoardComment
:unknown="unknown"
:comment="comment"
:isPassword="isPassword"
@editClick="editClick"
@deleteClick="deleteClick"
@submitPassword="submitPassword"
@submitComment="submitComment"
@updateReaction="(reactionData) => handleUpdateReaction(reactionData, comment.commentId)"
/>
<!-- @updateReaction="handleUpdateReaction" -->
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue';
import { defineProps, defineEmits } from 'vue';
import BoardComment from './BoardComment.vue'
const comments = ref([
{
id: 1,
author: '홍길동',
content: '저도 궁금합니다.',
children: [
{
id: 2,
author: '사용자1',
content: '저도요!',
},
{
id: 3,
author: '사용자2',
content: '저도..',
},
],
},
{
id: 4,
author: '사용자4',
content: '흥미로운 주제네요.',
children: [],
},
{
id: 5,
author: '사용자5',
content: '우오아아아아아앙',
children: [],
},
]);
const props = defineProps({
comments: {
type: Array,
required: true,
default: () => []
},
unknown: {
type: Boolean,
default: true,
},
isPassword: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['submitComment', 'updateReaction', 'editClick']);
const submitComment = (replyData) => {
emit('submitComment', replyData);
};
const handleUpdateReaction = (reactionData, commentId) => {
// console.log('📢 BoardCommentList :', reactionData);
// console.log('📌 ID>>>>:', commentId);
const updatedReactionData = {
...reactionData,
commentId: commentId
};
// console.log('🚀 :', updatedReactionData);
emit('updateReaction', updatedReactionData);
}
const editClick = (data) => {
emit('editClick', data);
};
</script>

View File

@ -1,7 +1,7 @@
<template>
<div class="d-flex align-items-center flex-wrap">
<div class="d-flex align-items-center">
<div class="avatar me-2" v-if="unknown">
<div v-if="!unknown" class="avatar me-2">
<img src="/img/avatars/2.png" alt="Avatar" class="rounded-circle" />
</div>
<div class="me-2">
@ -13,73 +13,71 @@
<i class="fa-regular fa-eye"></i> {{ views }}
</span>
<span>
<i class="bx bx-comment"></i> {{ comments }}
<i class="bx bx-comment"></i> {{ commentNum }}
</span>
</template>
</div>
</div>
</div>
<div class="ms-auto btn-area">
<template v-if="showDetail">
<div class="text-end">
<EditButton @click="handleEdit" />
<DeleteButton @click="handleDelete" />
<!-- 버튼 영역 -->
<div class="ms-auto text-end">
<!-- 수정, 삭제 버튼 -->
<template v-if="author || showDetail">
<EditButton @click.stop="editClick" />
<DeleteButton @click.stop="deleteClick" />
</template>
<div class="mt-3" v-if="isPassword && unknown">
<div class="input-group">
<input
type="password"
class="form-control"
v-model="password"
placeholder="비밀번호 입력"
/>
<button class="btn btn-primary" type="button" @click="handleSubmit">확인</button>
</div>
<span v-if="passwordAlert" class="invalid-feedback d-block text-start">{{ passwordAlert }}</span>
</div>
<!-- 좋아요, 싫어요 버튼 (댓글에서만 표시) -->
<BoardRecommendBtn
v-if="isLike"
:boardId="boardId"
:comment="props.comment"
@updateReaction="handleUpdateReaction"
/>
<!-- 비밀번호 입력창 (익명일 경우) -->
<div v-if="isPassword && unknown" class="mt-3">
<div class="input-group">
<input
type="password"
class="form-control"
v-model="password"
placeholder="비밀번호 입력"
/>
<button class="btn btn-primary" @click="$emit('submitPassword', password)">확인</button>
</div>
</template>
<template v-else>
<template v-if="author">
<EditButton />
<DeleteButton />
<!-- <button class="btn author btn-label-primary btn-icon" @click="handleEdit">
<i class='bx bx-edit-alt'></i>
</button>
<button class="btn author btn-label-primary btn-icon" @click="handleDelete">
<i class='bx bx-trash'></i>
</button> -->
</template>
<BoardRecommendBtn v-if="!isChild" :isRecommend="false" />
</template>
<span v-if="props.passwordAlert" class="invalid-feedback d-block text-start">{{ props.passwordAlert }}</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, defineProps } from 'vue';
import { useRouter } from 'vue-router';
import axios from '@api';
import { ref, defineProps, defineEmits } from 'vue';
import DeleteButton from '../button/DeleteBtn.vue';
import EditButton from '../button/EditBtn.vue';
import BoardRecommendBtn from '../button/BoardRecommendBtn.vue';
// Vue Router
const router = useRouter();
const isPassword = ref(false);
const password = ref('');
const passwordAlert = ref(false);
const lastClickedButton = ref('');
// Props
const props = defineProps({
comment: {
type: Object,
required: true,
},
boardId: {
type: Number,
required: true
required: false
},
commentId: {
type: Number,
required: false,
},
profileName: {
type: String,
default: '익명',
default: '익명 사용자',
},
unknown: {
type: Boolean,
@ -89,6 +87,7 @@ const props = defineProps({
type: Boolean,
default: true,
},
// :
author: {
type: Boolean,
default: false,
@ -101,113 +100,49 @@ const props = defineProps({
type: Number,
default: 0,
},
comments: {
commentNum: {
type: Number,
default: 0,
},
isChild: {
isLike: {
type: Boolean,
default: false,
},
isPassword: {
type: Boolean,
default: false,
},
passwordAlert: {
type: String,
default: false,
}
});
const emit = defineEmits(['togglePasswordInput']);
const emit = defineEmits(['togglePasswordInput', 'updateReaction', 'editClick', 'deleteClick', 'updatePasswordAlert']);
//
const handleEdit = () => {
if (props.unknown) {
togglePassword('edit');
} else {
router.push({ name: 'BoardEdit', params: { id: props.boardId } });
}
//
const editClick = () => {
emit('editClick', props.unknown);
};
//
const handleDelete = () => {
if (props.unknown) {
togglePassword('delete');
} else {
deletePost();
}
//
const deleteClick = () => {
emit('deleteClick', props.unknown);
};
//
const togglePassword = (button) => {
if (lastClickedButton.value === button) {
isPassword.value = !isPassword.value;
} else {
isPassword.value = true;
}
lastClickedButton.value = button;
const handleUpdateReaction = (reactionData) => {
// console.log("🔥 BoardProfile / ");
// console.log("📌 ID:", props.boardId);
// console.log("📌 ID ( ):", props.comment?.commentId);
// console.log("📌 reactionData:", reactionData);
emit("updateReaction", {
boardId: props.boardId,
commentId: props.comment?.commentId,
...reactionData,
});
};
//
const handleSubmit = async () => {
if (!password.value) {
passwordAlert.value = '비밀번호를 입력해주세요.';
return;
}
try {
const requestData = {
LOCBRDPWD: password.value,
LOCBRDSEQ: 288
}
const postResponse = await axios.post(`board/${props.boardId}/password`, requestData);
if (postResponse.data.code === 200) {
if (postResponse.data.data === true) {
isPassword.value = false;
if (lastClickedButton.value === 'edit') {
router.push({ name: 'BoardEdit', params: { id: props.boardId } });
} else if (lastClickedButton.value === 'delete') {
await deletePost();
}
lastClickedButton.value = null;
} else {
passwordAlert.value = '비밀번호가 일치하지 않습니다.';
}
} else {
passwordAlert.value = '비밀번호가 일치하지 않습니다.';
}
} catch (error) {
// 401
if (error.response && error.response.status === 401) {
passwordAlert.value = '비밀번호가 일치하지 않습니다.';
} else if (error.response) {
alert(`오류 발생: ${error.response.data.message || '서버 오류'}`);
} else {
alert('네트워크 오류가 발생했습니다. 다시 시도해주세요.');
}
}
};
const deletePost = async () => {
if (confirm('정말 삭제하시겠습니까?')) {
try {
const response = await axios.delete(`board/${props.boardId}`, {
data: { LOCBRDSEQ: props.boardId }
});
if (response.data.code === 200) {
alert('게시물이 삭제되었습니다.');
router.push({ name: 'BoardList' });
} else {
alert('삭제 실패: ' + response.data.message);
}
} catch (error) {
if (error.response) {
alert(`삭제 실패: ${error.response.data.message || '서버 오류'}`);
} else {
alert('네트워크 오류가 발생했습니다. 다시 시도해주세요.');
}
}
}
};
</script>

View File

@ -8,9 +8,17 @@
<BoardProfile
:boardId="currentBoardId"
:profileName="profileName"
:unknown="unknown"
:author="isAuthor"
:views="views"
:comments="comments"
:commentNum="commentNum"
:date="formattedBoardDate"
:isLike="false"
:isPassword="isPassword"
:passwordAlert="passwordAlert"
@editClick="editClick"
@deleteClick="deleteClick"
@submitPassword="submitPassword"
class="pb-6 border-bottom"
/>
</div>
@ -64,17 +72,28 @@
</li>
</ul> -->
<!-- 댓글 영역 -->
<BoardComentArea />
<!-- 수정 버튼 -->
<!-- <button class="btn btn-primary" @click="goToEditPage">
수정
</button> -->
<!-- 댓글 입력 영역 -->
<BoardCommentArea
:profileName="profileName"
:unknown="unknown"
@submitComment="handleCommentSubmit"
/>
<!-- <BoardCommentArea :profileName="profileName" :unknown="unknown" /> -->
</div>
<div class="card-footer">
<BoardCommentList/>
<!-- 댓글 목록 -->
<div class="card-footer">
<BoardCommentList
:unknown="unknown"
:comments="comments"
:isEditTextarea="isEditTextarea"
:isPassword="isPassword"
@editClick="editClick"
@deleteClick="deleteClick"
@submitPassword="submitPassword"
@updateReaction="handleUpdateReaction"
@submitComment="handleCommentReply"
/>
<Pagination/>
</div>
</div>
@ -84,7 +103,7 @@
</template>
<script setup>
import BoardComentArea from '@c/board/BoardComentArea.vue';
import BoardCommentArea from '@/components/board/BoardCommentArea.vue';
import BoardProfile from '@c/board/BoardProfile.vue';
import BoardCommentList from '@/components/board/BoardCommentList.vue';
import BoardRecommendBtn from '@/components/button/BoardRecommendBtn.vue';
@ -94,7 +113,7 @@ import { useRoute, useRouter } from 'vue-router';
import axios from '@api';
//
const profileName = ref('익명 사용자');
const profileName = ref('');
const boardTitle = ref('제목 없음');
const boardContent = ref('');
const date = ref('');
@ -103,17 +122,25 @@ const likes = ref(0);
const dislikes = ref(0);
const likeClicked = ref(false);
const dislikeClicked = ref(false);
const comments = ref(0);
const commentNum = ref(0);
const attachment = ref(false);
const comments = ref([]);
// ID
const route = useRoute();
const router = useRouter();
const currentBoardId = ref(Number(route.params.id));
const unknown = computed(() => profileName.value === '익명 사용자');
const currentUserId = ref('김자바'); // id
const authorId = ref(null); // id
const isAuthor = computed(() => currentUserId.value === authorId.value);
const isEditTextarea = ref({});
const passwordAlert = ref("");
const isPassword = ref(false);
const lastClickedButton = ref("");
//
const goToEditPage = () => {
router.push({ name: 'BoardEdit', params: { id: currentBoardId.value } });
};
//
const fetchBoardDetails = async () => {
@ -125,6 +152,13 @@ const fetchBoardDetails = async () => {
// const boardDetail = data.boardDetail || {};
profileName.value = data.author || '익명 사용자';
//
profileName.value = '익명 사용자';
// :
authorId.value = data.author;
boardTitle.value = data.title || '제목 없음';
boardContent.value = data.content || '';
date.value = data.date || '';
@ -132,10 +166,7 @@ const fetchBoardDetails = async () => {
likes.value = data.likeCount || 0;
dislikes.value = data.dislikeCount || 0;
attachment.value = data.hasAttachment || null;
comments.value = data.commentCount || 0;
// const response2 = await axios.post(`board/${currentBoardId.value}/password`);
// console.log(response2)
commentNum.value = data.commentCount || 0;
} catch (error) {
alert('게시물 데이터를 불러오는 중 오류가 발생했습니다.');
@ -144,50 +175,215 @@ const fetchBoardDetails = async () => {
// ,
const handleUpdateReaction = async ({ boardId, commentId, isLike, isDislike }) => {
// console.log(type, boardId)
try {
const requestData = {
LOCBRDSEQ: boardId,
LOCCMTSEQ: commentId,
MEMBERSEQ: 1, // 1
const aa = await axios.post(`/board/${boardId}/${commentId}/reaction`, {
LOCBRDSEQ: boardId, // id
LOCCMTSEQ: commentId, // id
// MEMBERSEQ: 1, // 1
LOCGOBGOD: isLike ? 'T' : 'F',
LOCGOBBAD: isDislike ? 'T' : 'F'
};
});
console.log("좋아요 API 응답 데이터:", aa.data);
console.log(requestData)
const postResponse = await axios.post(`/board/${boardId}/${commentId}/reaction`, requestData);
// await axios.post(`board/${boardId}/${commentId}/reaction`, { type });
const response = await axios.get(`board/${boardId}`);
const updatedData = response.data.data;
console.log('post요청 결과:', postResponse.data);
console.log('get요청 결과(좋아요):', response.data.data.likeCount);
likes.value = updatedData.likeCount;
dislikes.value = updatedData.dislikeCount;
likeClicked.value = isLike;
dislikeClicked.value = isDislike;
console.log('반응 결과:', postResponse.data);
// console.log(updatedData)
} catch (error) {
alert('반응을 업데이트하는 중 오류 발생');
}
};
//
const formattedBoardDate = computed(() => {
const dateObj = new Date(date.value);
return `${dateObj.getFullYear()}-${String(dateObj.getMonth() + 1).padStart(2, '0')}-${String(dateObj.getDate()).padStart(2, '0')} ${String(dateObj.getHours()).padStart(2, '0')}:${String(dateObj.getMinutes()).padStart(2, '0')}`;
});
//
const fetchComments = async () => {
try {
const response = await axios.get(`board/${currentBoardId.value}/comments`, {
params: { LOCBRDSEQ: currentBoardId.value }
});
console.log("목록 API 응답 데이터:", response.data);
let allComments = response.data.data.list.map(comment => ({
commentId: comment.LOCCMTSEQ, // id
boardId: comment.LOCBRDSEQ,
parentId: comment.LOCCMTPNT, // id
author: comment.author || "익명 사용자", //
content: comment.LOCCMTRPY, //
createdAt: formattedDate(comment.LOCCMTRDT), //
children: []
}));
allComments.sort((a, b) => b.commentId - a.commentId);
let commentMap = {};
let rootComments = [];
allComments.forEach(comment => {
commentMap[comment.commentId] = comment;
});
allComments.forEach(comment => {
if (comment.parentId && commentMap[comment.parentId]) {
commentMap[comment.parentId].children.push(comment);
} else {
rootComments.push(comment);
}
});
comments.value = rootComments;
// console.log(" comments :", comments.value);
} catch (error) {
console.error('댓글 목록 불러오기 오류:', error);
}
};
//
const handleCommentSubmit = async ({ comment, password }) => {
try {
const response = await axios.post(`board/${currentBoardId.value}/comment`, {
LOCBRDSEQ: currentBoardId.value,
LOCCMTRPY: comment,
LOCCMTPWD: password || null,
LOCCMTPNT: 1
});
// console.log('📥 :', response.data);
if (response.status === 200) {
console.log('댓글 작성 성공:', response.data.message);
await fetchComments();
} else {
console.error('댓글 작성 실패:', response.data.message);
}
} catch (error) {
console.error('댓글 작성 중 오류 발생:', error);
}
};
const handleCommentReply = async (reply) => {
const response = await axios.post(`board/${currentBoardId.value}/comment`, {
LOCBRDSEQ: currentBoardId.value,
LOCCMTRPY: reply.comment,
LOCCMTPWD: reply.password || null,
LOCCMTPNT: reply.parentId
});
if (response.status === 200) {
console.log('대댓글 작성 성공:', response.data.message);
await fetchComments();
} else {
console.error('대댓글 작성 실패:', response.data.message);
}
}
const editClick = (unknown) => {
if (unknown) {
togglePassword("edit");
} else {
router.push({ name: "BoardEdit", params: { id: currentBoardId.value } });
}
};
const deleteClick = (unknown) => {
if (unknown) {
togglePassword("delete");
} else {
deletePost();
}
};
const togglePassword = (button) => {
if (lastClickedButton.value === button) {
isPassword.value = !isPassword.value;
} else {
isPassword.value = true;
}
lastClickedButton.value = button;
};
const submitPassword = async (inputPassword) => {
console.log(inputPassword)
if (!inputPassword) {
passwordAlert.value = "비밀번호를 입력해주세요.";
return;
}
try {
const requestData = {
LOCBRDPWD: inputPassword,
LOCBRDSEQ: 288
};
const postResponse = await axios.post(`board/${currentBoardId.value}/password`, requestData);
if (postResponse.data.code === 200 && postResponse.data.data === true) {
isPassword.value = false;
passwordAlert.value = "";
if (lastClickedButton.value === "edit") {
router.push({ name: "BoardEdit", params: { id: currentBoardId.value } });
} else if (lastClickedButton.value === "delete") {
await deletePost();
}
lastClickedButton.value = null;
} else {
passwordAlert.value = "비밀번호가 일치하지 않습니다.";
}
} catch (error) {
if (error.response && error.response.status === 401) {
passwordAlert.value = "비밀번호가 일치하지 않습니다.";
} else if (error.response) {
alert(`오류 발생: ${error.response.data.message || "서버 오류"}`);
} else {
alert("네트워크 오류가 발생했습니다. 다시 시도해주세요.");
}
}
};
const deletePost = async () => {
if (confirm("정말 삭제하시겠습니까?")) {
try {
const response = await axios.delete(`board/${currentBoardId.value}`, {
data: { LOCBRDSEQ: currentBoardId.value }
});
if (response.data.code === 200) {
alert("게시물이 삭제되었습니다.");
router.push({ name: "BoardList" });
} else {
alert("삭제 실패: " + response.data.message);
}
} catch (error) {
if (error.response) {
alert(`삭제 실패: ${error.response.data.message || "서버 오류"}`);
} else {
alert("네트워크 오류가 발생했습니다. 다시 시도해주세요.");
}
}
}
};
//
const formattedDate = (dateString) => {
if (!dateString) return "날짜 없음";
const dateObj = new Date(dateString);
return `${dateObj.getFullYear()}-${String(dateObj.getMonth() + 1).padStart(2, '0')}-${String(dateObj.getDate()).padStart(2, '0')} ${String(dateObj.getHours()).padStart(2, '0')}:${String(dateObj.getMinutes()).padStart(2, '0')}`;
};
const formattedBoardDate = computed(() => formattedDate(date.value));
//
onMounted(() => {
fetchBoardDetails();
fetchBoardDetails()
fetchComments()
});
</script>