This commit is contained in:
khj0414 2025-02-27 15:44:57 +09:00
commit 1063d82a29
6 changed files with 129 additions and 67 deletions

View File

@ -9,15 +9,16 @@
:comment="comment" :comment="comment"
:showDetail="false" :showDetail="false"
:isLike="!isLike" :isLike="!isLike"
:isCommentPassword="comment.isCommentPassword" :isCommentPassword="isCommentPassword"
:isCommentProfile="true" :isCommentProfile="true"
@editClick="aaaa" @editClick="aaaa"
@deleteClick="$emit('deleteClick', comment)" @deleteClick="$emit('deleteClick', comment)"
@updateReaction="handleUpdateReaction" @updateReaction="handleUpdateReaction"
/> />
<!-- <P>Commentpassssss: {{isCommentPassword}}</P> -->
<!-- :author="true" --> <!-- :author="true" -->
<!-- 댓글 비밀번호 입력창 (익명일 경우) --> <!-- 댓글 비밀번호 입력창 (익명일 경우) -->
<div v-if="isCommentPassword && unknown" class="mt-3 w-25 ms-auto"> <div v-if="isCommentPassword === comment.commentId && unknown" class="mt-3 w-25 ms-auto">
<div class="input-group"> <div class="input-group">
<input <input
type="password" type="password"
@ -37,8 +38,9 @@
<template v-if="comment.isEditTextarea"> <template v-if="comment.isEditTextarea">
<textarea v-model="localEditedContent" class="form-control"></textarea> <textarea v-model="localEditedContent" class="form-control"></textarea>
<div class="mt-2 d-flex justify-content-end"> <div class="mt-2 d-flex justify-content-end">
<button class="btn btn-secondary me-2" @click="$emit('cancelEdit', comment)">취소</button> <!-- <button class="btn btn-secondary me-2" @click="$emit('cancelEdit', comment)">취소</button> -->
<button class="btn btn-primary" @click="submitEdit">수정</button> <!-- <button class="btn btn-primary" @click="submitEdit">수정</button> -->
<SaveBtn class="btn btn-primary" @click="submitEdit"></SaveBtn>
</div> </div>
</template> </template>
<template v-else> <template v-else>
@ -59,6 +61,7 @@
<!-- <p>대댓글 데이터(JSON): {{ JSON.stringify(child, null, 2) }}</p> --> <!-- <p>대댓글 데이터(JSON): {{ JSON.stringify(child, null, 2) }}</p> -->
<!-- <p>comment child: {{ comment.children }}</p> --> <!-- <p>comment child: {{ comment.children }}</p> -->
<!-- :unknown="child.author === '익명'" --> <!-- :unknown="child.author === '익명'" -->
<p>child.isCommentPassword: {{ child.isCommentPassword }}</p>
<BoardComment <BoardComment
:comment="child" :comment="child"
:unknown="child.author === '익명'" :unknown="child.author === '익명'"
@ -66,6 +69,7 @@
:isLike="true" :isLike="true"
:isCommentProfile="true" :isCommentProfile="true"
:isCommentAuthor="child.isCommentAuthor" :isCommentAuthor="child.isCommentAuthor"
:isCommentPassword="isCommentPassword"
@editClick="$emit('editClick', $event)" @editClick="$emit('editClick', $event)"
@deleteClick="$emit('deleteClick', child)" @deleteClick="$emit('deleteClick', child)"
@submitEdit="(comment, editedContent) => $emit('submitEdit', comment, editedContent)" @submitEdit="(comment, editedContent) => $emit('submitEdit', comment, editedContent)"
@ -83,6 +87,7 @@ import { defineProps, defineEmits, ref, computed, watch } from 'vue';
import BoardProfile from './BoardProfile.vue'; import BoardProfile from './BoardProfile.vue';
import BoardCommentArea from './BoardCommentArea.vue'; import BoardCommentArea from './BoardCommentArea.vue';
import PlusButton from '../button/PlusBtn.vue'; import PlusButton from '../button/PlusBtn.vue';
import SaveBtn from '../button/SaveBtn.vue';
const props = defineProps({ const props = defineProps({
comment: { comment: {
@ -91,7 +96,7 @@ const props = defineProps({
}, },
unknown: { unknown: {
type: Boolean, type: Boolean,
default: true, default: false,
}, },
isCommentAuthor: { isCommentAuthor: {
type: Boolean, type: Boolean,

View File

@ -4,11 +4,11 @@
<!-- 댓글 입력 섹션 --> <!-- 댓글 입력 섹션 -->
<div class="d-flex justify-content-start align-items-top"> <div class="d-flex justify-content-start align-items-top">
<!-- 프로필섹션 --> <!-- 프로필섹션 -->
<div class="avatar-wrapper"> <!-- <div class="avatar-wrapper">
<div v-if="!unknown" class="avatar me-4"> <div v-if="!unknown" class="avatar me-4">
<img src="/img/avatars/11.png" alt="Avatar" class="rounded-circle"> <img src="/img/avatars/11.png" alt="Avatar" class="rounded-circle">
</div> </div>
</div> </div> -->
<!-- 텍스트박스 --> <!-- 텍스트박스 -->
<div class="w-100"> <div class="w-100">
<textarea <textarea
@ -95,7 +95,6 @@ function handleCommentSubmit() {
password: isCheck.value ? password.value : '', password: isCheck.value ? password.value : '',
isCheck: isCheck.value, isCheck: isCheck.value,
LOCBRDTYP, // '300102' LOCBRDTYP, // '300102'
isCheck: isCheck.value
}); });
} }

View File

@ -9,16 +9,16 @@
:unknown="unknown" :unknown="unknown"
:comment="comment" :comment="comment"
:isCommentAuthor="comment.isCommentAuthor" :isCommentAuthor="comment.isCommentAuthor"
:isCommentPassword="comment.isCommentPassword"
:isEditTextarea="comment.isEditTextarea" :isEditTextarea="comment.isEditTextarea"
:isCommentPassword="isCommentPassword"
:passwordCommentAlert="passwordCommentAlert" :passwordCommentAlert="passwordCommentAlert"
@editClick="$emit('editClick', comment)" @editClick="handleEditClick"
@deleteClick="$emit('deleteClick', comment)" @deleteClick="handleDeleteClick"
@submitPassword="submitPassword" @submitPassword="submitPassword"
@submitComment="submitComment" @submitComment="submitComment"
@commentDeleted="handleCommentDeleted" @commentDeleted="handleCommentDeleted"
@submitEdit="(comment, editedContent) => $emit('submitEdit', comment, editedContent)" @submitEdit="handleSubmitEdit"
@cancelEdit="$emit('cancelEdit', comment)" @cancelEdit="handleCancelEdit"
@updateReaction="(reactionData) => handleUpdateReaction(reactionData, comment.commentId, comment.boardId)" @updateReaction="(reactionData) => handleUpdateReaction(reactionData, comment.commentId, comment.boardId)"
/> />
</li> </li>
@ -76,4 +76,28 @@ const handleUpdateReaction = (reactionData, commentId, boardId) => {
const submitPassword = (comment, password) => { const submitPassword = (comment, password) => {
emit('submitPassword', comment, password); emit('submitPassword', comment, password);
}; };
const handleEditClick = (comment) => {
emit('editClick', comment);
};
const handleSubmitEdit = (comment, editedContent) => {
emit("submitEdit", comment, editedContent);
};
const handleDeleteClick = (comment) => {
if (comment.parentId) {
emit('deleteClick', comment); //
} else {
emit('deleteClick', comment); //
}
};
const handleCancelEdit = (comment) => {
if (comment.parentId) {
emit('cancelEdit', comment); //
} else {
emit('cancelEdit', comment); //
}
};
</script> </script>

View File

@ -23,7 +23,7 @@
<div class="ms-auto text-end"> <div class="ms-auto text-end">
<!-- 수정, 삭제 버튼 --> <!-- 수정, 삭제 버튼 -->
<!-- <template v-if="isAuthor || showDetail"> --> <!-- <template v-if="isAuthor || showDetail"> -->
<template v-if="isCommentProfile ? isCommentAuthor : isAuthor"> <template v-if="unknown || isCommentAuthor || isAuthor">
<EditButton @click.stop="editClick" /> <EditButton @click.stop="editClick" />
<DeleteButton @click.stop="deleteClick" /> <DeleteButton @click.stop="deleteClick" />
</template> </template>
@ -62,7 +62,6 @@ const props = defineProps({
}, },
profileName: { profileName: {
type: String, type: String,
default: '익명',
default: '', default: '',
}, },
unknown: { unknown: {
@ -102,13 +101,12 @@ const emit = defineEmits(['updateReaction', 'editClick', 'deleteClick']);
// //
const editClick = () => { const editClick = () => {
console.log('클릭 확인') emit('editClick', { ...props.comment, unknown: props.unknown });
emit('editClick', props.unknown);
}; };
// //
const deleteClick = () => { const deleteClick = () => {
emit('deleteClick', props.unknown); emit('deleteClick', { ...props.comment, unknown: props.unknown });
}; };
const handleUpdateReaction = (reactionData) => { const handleUpdateReaction = (reactionData) => {

View File

@ -4,7 +4,7 @@
<div class="card-header"> <div class="card-header">
<!-- 검색창 --> <!-- 검색창 -->
<div class="container col-6 mt-12 mb-8"> <div class="container col-6 mt-12 mb-8">
<search-bar @update:data="search" /> <search-bar @update:data="search" @keyup.enter="searchOnEnter"/>
</div> </div>
</div> </div>
<div class="card-datatable"> <div class="card-datatable">
@ -57,7 +57,7 @@
class="bg-label-gray fw-bold" class="bg-label-gray fw-bold"
@click="goDetail(notice.id)"> @click="goDetail(notice.id)">
<td class="text-center">공지</td> <td class="text-center">공지</td>
<td> <td class="cursor-pointer">
📌 {{ notice.title }} 📌 {{ notice.title }}
<i v-if="notice.img" class="bi bi-image me-1"></i> <i v-if="notice.img" class="bi bi-image me-1"></i>
<i v-if="notice.hasAttachment" class="bi bi-paperclip"></i> <i v-if="notice.hasAttachment" class="bi bi-paperclip"></i>
@ -74,7 +74,7 @@
class="invert-bg-white" class="invert-bg-white"
@click="goDetail(post.realId)"> @click="goDetail(post.realId)">
<td class="text-center">{{ post.id }}</td> <td class="text-center">{{ post.id }}</td>
<td> <td class="cursor-pointer">
{{ post.title }} {{ post.title }}
<i v-if="post.img" class="bi bi-image me-1"></i> <i v-if="post.img" class="bi bi-image me-1"></i>
<i v-if="post.hasAttachment" class="bi bi-paperclip"></i> <i v-if="post.hasAttachment" class="bi bi-paperclip"></i>
@ -86,6 +86,12 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<!-- 검색 결과가 없을 -->
<div v-if="generalList.length === 0">
<p class="text-center pt-10 mt-2 mb-0 text-muted">
검색 결과가 없습니다.
</p>
</div>
</div> </div>
</div> </div>
<div class="card-footer"> <div class="card-footer">
@ -189,10 +195,10 @@ const fetchGeneralPosts = async (page = 1) => {
}); });
if (data?.data) { if (data?.data) {
console.log(data) // console.log(data)
const totalPosts = data.data.total; // const totalPosts = data.data.total; //
console.log('📌 API 응답 데이터:', data.data); // console.log('📌 API :', data.data);
generalList.value = data.data.list.map((post, index) => ({ generalList.value = data.data.list.map((post, index) => ({
realId: post.id, realId: post.id,
@ -251,6 +257,18 @@ const fetchNoticePosts = async () => {
} }
}; };
// Enter
const searchOnEnter = (event) => {
const searchTextValue = event.target.value.trim();
if (!searchTextValue || searchTextValue[0] === ' ') {
return; //
}
searchText.value = searchTextValue;
fetchGeneralPosts(1);
};
// //
const handlePageChange = (page) => { const handlePageChange = (page) => {
if (page !== pagination.value.currentPage) { if (page !== pagination.value.currentPage) {

View File

@ -106,7 +106,7 @@
<BoardCommentList <BoardCommentList
:unknown="unknown" :unknown="unknown"
:comments="commentsWithAuthStatus" :comments="commentsWithAuthStatus"
:isCommentPassword="isCommentPassword" :isCommentPassword="Boolean(isCommentPassword)"
:isEditTextarea="isEditTextarea" :isEditTextarea="isEditTextarea"
:passwordCommentAlert="passwordCommentAlert" :passwordCommentAlert="passwordCommentAlert"
@editClick="editComment" @editClick="editComment"
@ -170,7 +170,7 @@ const commentsWithAuthStatus = computed(() => {
isCommentAuthor: comment.authorId === currentUserId.value, isCommentAuthor: comment.authorId === currentUserId.value,
children: comment.children.map(reply => ({ children: comment.children.map(reply => ({
...reply, ...reply,
isCommentAuthor: reply.authorId === currentUserId.value isCommentAuthor: reply.authorId === currentUserId.value,
})) }))
})); }));
// console.log(" commentsWithAuthStatus :", updatedComments); // console.log(" commentsWithAuthStatus :", updatedComments);
@ -220,8 +220,8 @@ const fetchBoardDetails = async () => {
// //
// profileName.value = ''; // profileName.value = '';
console.log("📌 게시글 작성자:", profileName.value); // // console.log("📌 :", profileName.value); //
console.log("🔍 익명 여부 (unknown.value):", unknown.value); // // console.log("🔍 (unknown.value):", unknown.value); //
authorId.value = data.authorId; // id authorId.value = data.authorId; // id
@ -263,7 +263,8 @@ const handleUpdateReaction = async ({ boardId, commentId, isLike, isDislike }) =
// console.log(" :", updatedData); // console.log(" :", updatedData);
} catch (error) { } catch (error) {
alert('반응을 업데이트하는 중 오류 발생'); alert('오류가 발생했습니다.');
// console.log(' .');
} }
}; };
@ -285,7 +286,8 @@ const handleCommentReaction = async ({ boardId, commentId, isLike, isDislike })
await fetchComments(); await fetchComments();
} catch (error) { } catch (error) {
alert('댓글 반응을 업데이트하는 중 오류 발생'); alert('오류가 발생했습니다.');
// console.log(' ');
} }
}; };
@ -370,7 +372,8 @@ const fetchComments = async (page = 1) => {
} catch (error) { } catch (error) {
console.log('댓글 목록 불러오기 오류:', error); alert('오류가 발생했습니다.');
// alert(' :', error);
} }
}; };
@ -382,7 +385,6 @@ const handleCommentSubmit = async (data, isCheck) => {
} }
const { comment, password } = data; const { comment, password } = data;
const LOCBRDTYP = data.LOCBRDTYP || null;
if (!comment || comment.trim() === "") { if (!comment || comment.trim() === "") {
commentAlert.value = '댓글을 입력해주세요.'; commentAlert.value = '댓글을 입력해주세요.';
@ -402,19 +404,21 @@ const handleCommentSubmit = async (data, isCheck) => {
LOCCMTRPY: comment, LOCCMTRPY: comment,
LOCCMTPWD: isCheck ? password : '', LOCCMTPWD: isCheck ? password : '',
LOCCMTPNT: 1, LOCCMTPNT: 1,
LOCBRDTYP LOCBRDTYP: unknown.value ? "300102" : null
}); });
if (response.status === 200) { if (response.status === 200) {
console.log('댓글 작성 성공:', response.data.message); // console.log(' :', response.data.message);
passwordAlert.value = ''; passwordAlert.value = '';
commentAlert.value = ''; commentAlert.value = '';
await fetchComments(); await fetchComments();
} else { } else {
console.error('댓글 작성 실패:', response.data.message); // console.error(' :', response.data.message);
alert("댓글 작성을 실패했습니다.")
} }
} catch (error) { } catch (error) {
console.error('댓글 작성 중 오류 발생:', error); // console.error(' :', error);
alert("오류가 발생했습니다.")
} }
}; };
@ -431,19 +435,21 @@ const handleCommentReply = async (reply) => {
if (response.status === 200) { if (response.status === 200) {
if (response.data.code === 200) { // if (response.data.code === 200) { //
console.log('대댓글 작성 성공:', response.data); // console.log(' :', response.data);
await fetchComments(); // await fetchComments(); //
} else { } else {
console.log('대댓글 작성 실패 - 서버 응답:', response.data); // console.log(' - :', response.data);
alert('대댓글 작성 실패했습니다.'); alert('대댓글 작성 실패했습니다.');
} }
} }
} catch (error) { } catch (error) {
console.error('대댓글 작성 중 오류 발생:', error); // console.error(' :', error);
if (error.response) { if (error.response) {
console.error('서버 응답 에러:', error.response.data); // console.error(' :', error.response.data);
alert("오류가 발생했습니다.");
} }
alert('대댓글 작성 중 오류가 발생했습니다.'); alert("오류가 발생했습니다.");
} }
} }
@ -482,36 +488,37 @@ const findCommentById = (commentId, commentsList) => {
// //
const editComment = (comment) => { const editComment = (comment) => {
console.log('대댓글 수정 버튼 클릭')
// //
const targetComment = findCommentById(comment.commentId, comments.value); const targetComment = findCommentById(comment.commentId, comments.value);
if (!targetComment) { if (!targetComment) {
console.log("대댓글을 찾을 수 없음:", comment.commentId);
return; return;
} }
// text , //
targetComment.isEditTextarea = !targetComment.isEditTextarea; const isMyComment = comment.authorId === currentUserId.value;
const isAnonymous = comment.author === "익명";
// if (isMyComment) {
if (unknown.value) { //
console.log('익명 코멘트인가?') targetComment.isEditTextarea = true;
} else if (isAnonymous) {
//
toggleCommentPassword(comment, "edit"); toggleCommentPassword(comment, "edit");
} else {
// console.log(" - ");
alert("수정이 불가능합니다");
} }
} }
// //
const deleteComment = async (comment) => { const deleteComment = async (comment) => {
console.log('🗑 댓글 삭제 시도:', comment);
// //
const isMyComment = comment.authorId === currentUserId.value; const isMyComment = comment.authorId === currentUserId.value;
if (unknown.value && !isMyComment) { if (unknown.value && !isMyComment) {
console.log('🛑 익명 사용자의 댓글 삭제 시도 (비밀번호 필요)');
if (comment.isEditTextarea) { if (comment.isEditTextarea) {
// , // ,
comment.isEditTextarea = false; comment.isEditTextarea = false;
@ -521,26 +528,23 @@ const deleteComment = async (comment) => {
toggleCommentPassword(comment, "delete"); toggleCommentPassword(comment, "delete");
} }
} else { } else {
console.log('✅ 로그인 사용자 댓글 삭제 진행');
deleteReplyComment(comment); deleteReplyComment(comment);
} }
}; };
// //
const toggleCommentPassword = (comment, button) => { const toggleCommentPassword = (comment, button) => {
if (lastCommentClickedButton.value === button && comment.isCommentPassword) { if (lastCommentClickedButton.value === button && isCommentPassword.value === comment.commentId) {
comment.isCommentPassword = false; isCommentPassword.value = false; //
} else { } else {
// isCommentPassword.value = comment.commentId; //
comments.value.forEach(c => (c.isCommentPassword = false));
//
comment.isCommentPassword = true;
} }
lastCommentClickedButton.value = button; lastCommentClickedButton.value = button;
}; };
const togglePassword = (button) => { const togglePassword = (button) => {
if (lastClickedButton.value === button) { if (lastClickedButton.value === button) {
isPassword.value = !isPassword.value; isPassword.value = !isPassword.value;
@ -597,16 +601,21 @@ const submitPassword = async () => {
// ( ) // ( )
const submitCommentPassword = async (comment, password) => { const submitCommentPassword = async (comment, password) => {
// console.log(" :", password);
// console.log(" ID:", comment.commentId);
if (!password) { if (!password) {
passwordCommentAlert.value = "비밀번호를 입력해주세요."; passwordCommentAlert.value = "비밀번호를 입력해주세요.";
return; return;
} }
try { try {
// console.log(' ')
const response = await axios.post(`board/comment/${comment.commentId}/password`, { const response = await axios.post(`board/comment/${comment.commentId}/password`, {
LOCCMTPWD: password, LOCCMTPWD: password,
LOCCMTSEQ: comment.commentId, LOCCMTSEQ: comment.commentId,
}); });
// console.log(" :", response.data);
if (response.data.code === 200 && response.data.data === true) { if (response.data.code === 200 && response.data.data === true) {
passwordCommentAlert.value = ""; passwordCommentAlert.value = "";
@ -624,9 +633,14 @@ const submitCommentPassword = async (comment, password) => {
} }
lastCommentClickedButton.value = null; lastCommentClickedButton.value = null;
} else { } else {
// console.log(" ");
passwordCommentAlert.value = "비밀번호가 일치하지 않습니다."; passwordCommentAlert.value = "비밀번호가 일치하지 않습니다.";
} }
} catch (error) { } catch (error) {
// console.log("🚨 :", error.response?.data || error);
// if (error.response?.status === 401) {
// console.log(" 401 : ( )");
// }
passwordCommentAlert.value = "비밀번호가 일치하지 않습니다"; passwordCommentAlert.value = "비밀번호가 일치하지 않습니다";
} }
}; };
@ -675,7 +689,7 @@ const deleteReplyComment = async (comment) => {
alert("댓글 삭제에 실패했습니다."); alert("댓글 삭제에 실패했습니다.");
} }
} catch (error) { } catch (error) {
console.log("댓글 삭제 중 오류 발생:", error); // console.log(" :", error);
alert("댓글 삭제 중 오류가 발생했습니다."); alert("댓글 삭제 중 오류가 발생했습니다.");
} }
}; };
@ -698,13 +712,16 @@ const handleSubmitEdit = async (comment, editedContent) => {
targetComment.content = editedContent; // targetComment.content = editedContent; //
targetComment.isEditTextarea = false; // targetComment.isEditTextarea = false; //
} else { } else {
console.warn("❌ 수정할 댓글을 찾을 수 없음"); // console.warn(" ");
alert("수정할 댓글을 찾을 수 없습니다.");
} }
} else { } else {
console.log("❌ 댓글 수정 실패:", response.data); // console.log(" :", response.data);
alert("댓글 수정 실패했습니다.");
} }
} catch (error) { } catch (error) {
console.error("댓글 수정 중 오류 발생:", error); // console.error(" :", error);
alert("댓글 수정 중 오류 발생했습니다.");
} }
}; };
@ -713,10 +730,11 @@ const handleCancelEdit = (comment) => {
const targetComment = findCommentById(comment.commentId, comments.value); const targetComment = findCommentById(comment.commentId, comments.value);
if (targetComment) { if (targetComment) {
console.log("✅ 원본 데이터 찾음, 수정 취소 처리 가능:", targetComment); // console.log(" , :", targetComment);
targetComment.isEditTextarea = false; targetComment.isEditTextarea = false;
} else { } else {
console.error("❌ 원본 데이터 찾을 수 없음, 수정 취소 실패"); // console.error(" , ");
alert("수정 취소를 실패했습니다.");
} }
}; };
@ -747,7 +765,7 @@ const handleCommentDeleted = (deletedCommentId) => {
} }
} }
console.error("❌ 삭제할 댓글을 찾을 수 없음:", deletedCommentId); // console.error(" :", deletedCommentId);
}; };