게시판 수정중
This commit is contained in:
parent
8f79ed6680
commit
27c9392d68
@ -75,7 +75,7 @@
|
||||
const $common = inject('common');
|
||||
const comment = ref('');
|
||||
const password = ref('');
|
||||
const isCheck = ref(props.unknown);
|
||||
const isCheck = ref(false);
|
||||
const textAlert = ref('');
|
||||
const passwordAlert2 = ref('');
|
||||
|
||||
|
||||
@ -61,7 +61,7 @@
|
||||
📌 {{ notice.title }}
|
||||
<span v-if="notice.commentCount" class="comment-count">[ {{ notice.commentCount }} ]</span>
|
||||
<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="Array.isArray(notice.hasAttachment) && notice.hasAttachment.length > 0" class="bi bi-paperclip"></i>
|
||||
<span v-if="isNewPost(notice.rawDate)" class="badge bg-danger text-white ms-2 fs-tiny">N</span>
|
||||
</td>
|
||||
<td class="text-center">{{ notice.author }}</td>
|
||||
@ -79,7 +79,7 @@
|
||||
{{ post.title }}
|
||||
<span v-if="post.commentCount" class="comment-count">[ {{ post.commentCount }} ]</span>
|
||||
<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="Array.isArray(post.hasAttachment) && post.hasAttachment.length > 0" class="bi bi-paperclip"></i>
|
||||
<span v-if="isNewPost(post.rawDate)" class="badge bg-danger text-white ms-2 fs-tiny">N</span>
|
||||
</td>
|
||||
<td class="text-center">{{ post.author }}</td>
|
||||
@ -206,7 +206,7 @@ const fetchGeneralPosts = async (page = 1) => {
|
||||
rawDate: post.date,
|
||||
date: formatDate(post.date), // 날짜 변환 적용
|
||||
views: post.cnt || 0,
|
||||
hasAttachment: post.hasAttachment || false,
|
||||
hasAttachment: post.hasAttachment,
|
||||
img: post.firstImageUrl || null,
|
||||
commentCount : post.commentCount
|
||||
}));
|
||||
@ -247,7 +247,7 @@ const fetchNoticePosts = async () => {
|
||||
date: formatDate(post.date),
|
||||
rawDate: post.date,
|
||||
views: post.cnt || 0,
|
||||
hasAttachment: post.hasAttachment || false,
|
||||
hasAttachment: post.hasAttachment,
|
||||
img: post.firstImageUrl || null,
|
||||
commentCount : post.commentCount
|
||||
}));
|
||||
|
||||
@ -17,47 +17,35 @@
|
||||
:isAuthor="isAuthor"
|
||||
@editClick="editClick"
|
||||
@deleteClick="deleteClick"
|
||||
/>
|
||||
|
||||
<!-- 비밀번호 입력창 (익명일 경우) -->
|
||||
<div v-if="isPassword && unknown" class="mt-3 w-25 ms-auto">
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
v-model="password"
|
||||
placeholder="비밀번호 입력"
|
||||
@input="password = password.replace(/\s/g, '')"
|
||||
/>
|
||||
<button class="btn btn-primary" @click="submitPassword">확인</button>
|
||||
</div>
|
||||
<span v-if="passwordAlert" class="invalid-feedback d-block text-start">{{ passwordAlert }}</span>
|
||||
</div>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 게시글 내용 -->
|
||||
<div class="card-body">
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap mb-6 gap-2">
|
||||
<!-- 제목 섹션 -->
|
||||
<div class="me-1">
|
||||
<h5 class="mb-4">{{ boardTitle }}</h5>
|
||||
</div>
|
||||
|
||||
<!-- 첨부파일 섹션 -->
|
||||
<div v-if="attachment" class="btn-group">
|
||||
<!-- 첨부파일 다운로드 버튼 -->
|
||||
<div v-if="attachments.length" class="btn-group">
|
||||
<button type="button" class="btn btn-label-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa-solid fa-download me-2"></i>
|
||||
첨부파일
|
||||
<!-- (<span class="attachment-num">{{ dropdownItems.length }}</span>) -->
|
||||
첨부파일 ({{ attachments.length }}개)
|
||||
</button>
|
||||
<!-- <ul class="dropdown-menu">
|
||||
<li v-for="(item, index) in dropdownItems" :key="index">
|
||||
<a class="dropdown-item" href="javascript:void(0);">
|
||||
{{ item.label }}
|
||||
<ul class="dropdown-menu">
|
||||
<li v-for="(attachment, index) in attachments" :key="index">
|
||||
<a
|
||||
class="dropdown-item"
|
||||
:href="getFileDownloadUrl(attachment)"
|
||||
:download="attachment.originalName + '.' + attachment.extension"
|
||||
>
|
||||
{{ attachment.originalName }}.{{ attachment.extension }}
|
||||
</a>
|
||||
</li>
|
||||
</ul> -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -75,15 +63,8 @@
|
||||
:likeClicked="likeClicked"
|
||||
:dislikeClicked="dislikeClicked"
|
||||
@updateReaction="handleUpdateReaction"
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 첨부파일 목록 -->
|
||||
<!-- <ul v-if="attachments.length" class="attachments mt-4 list-unstyled">
|
||||
<li v-for="(attachment, index) in attachments" :key="index" class="mb-2">
|
||||
<a :href="attachment.url" target="_blank" class="text-decoration-none">{{ attachment.name }}</a>
|
||||
</li>
|
||||
</ul> -->
|
||||
|
||||
<!-- 댓글 입력 영역 -->
|
||||
<BoardCommentArea
|
||||
@ -174,6 +155,13 @@ const commentsWithAuthStatus = computed(() => {
|
||||
return updatedComments;
|
||||
});
|
||||
|
||||
const attachments = ref([]);
|
||||
// 첨부파일 다운로드 URL 생성
|
||||
const getFileDownloadUrl = (attachment) => {
|
||||
return `board/files/download?path=${encodeURIComponent(attachment.path)}`;
|
||||
};
|
||||
|
||||
|
||||
|
||||
const password = ref('');
|
||||
const passwordAlert = ref("");
|
||||
@ -212,7 +200,7 @@ const fetchBoardDetails = async () => {
|
||||
try {
|
||||
const response = await axios.get(`board/${currentBoardId.value}`);
|
||||
const data = response.data.data;
|
||||
|
||||
console.log(data)
|
||||
|
||||
// API 응답 데이터 반영
|
||||
// const boardDetail = data.boardDetail || {};
|
||||
@ -228,6 +216,7 @@ const fetchBoardDetails = async () => {
|
||||
dislikes.value = data.dislikeCount || 0;
|
||||
attachment.value = data.hasAttachment || null;
|
||||
commentNum.value = data.commentCount || 0;
|
||||
attachments.value = data.attachments || [];
|
||||
|
||||
} catch (error) {
|
||||
alert('게시물 데이터를 불러오는 중 오류가 발생했습니다.');
|
||||
@ -461,10 +450,10 @@ const findCommentById = (commentId, commentsList) => {
|
||||
|
||||
// 댓글 수정(대댓글 포함)
|
||||
const editComment = (comment) => {
|
||||
password.value = '';
|
||||
passwordCommentAlert.value = '';
|
||||
password.value = '';
|
||||
passwordCommentAlert.value = '';
|
||||
currentPasswordCommentId.value = null;
|
||||
|
||||
|
||||
const targetComment = findCommentById(comment.commentId, comments.value);
|
||||
|
||||
if (!targetComment) {
|
||||
@ -493,7 +482,7 @@ const editComment = (comment) => {
|
||||
} else {
|
||||
// 다른 모든 댓글의 수정창 닫기
|
||||
closeAllEditTextareas();
|
||||
|
||||
|
||||
// 비밀번호 입력
|
||||
targetComment.isEditTextarea = false;
|
||||
toggleCommentPassword(comment, "edit");
|
||||
@ -537,7 +526,7 @@ const toggleCommentPassword = (comment, button) => {
|
||||
passwordCommentAlert.value = '';
|
||||
} else {
|
||||
currentPasswordCommentId.value = comment.commentId; // 비밀번호 창 열기
|
||||
password.value = '';
|
||||
password.value = '';
|
||||
passwordCommentAlert.value = '';
|
||||
}
|
||||
|
||||
|
||||
@ -60,14 +60,26 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 첨부파일 업로드 -->
|
||||
<FormFile
|
||||
title="첨부파일"
|
||||
name="files"
|
||||
:is-alert="attachFilesAlert"
|
||||
@update:data="attachFiles = $event"
|
||||
@update:data="handleFileUpload"
|
||||
@update:isValid="isFileValid = $event"
|
||||
/>
|
||||
|
||||
<!-- 실시간 반영된 파일 개수 표시 -->
|
||||
<p class="text-muted mt-1">첨부파일: {{ fileCount }} / 5개</p>
|
||||
<p v-if="fileError" class="text-danger">{{ fileError }}</p>
|
||||
|
||||
<ul class="list-group mt-2" v-if="attachFiles.length">
|
||||
<li v-for="(file, index) in attachFiles" :key="index" class="list-group-item d-flex justify-content-between align-items-center">
|
||||
{{ file.name }}
|
||||
<button class="close-btn" @click="removeFile(index)">✖</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- 내용 입력 (에디터) -->
|
||||
<div class="mb-4">
|
||||
<label class="col-md-2 col-form-label">
|
||||
@ -92,7 +104,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, getCurrentInstance, watch } from 'vue';
|
||||
import { ref, onMounted, getCurrentInstance, watch, computed } from 'vue';
|
||||
import QEditor from '@c/editor/QEditor.vue';
|
||||
import FormInput from '@c/input/FormInput.vue';
|
||||
import FormFile from '@c/input/FormFile.vue';
|
||||
@ -107,8 +119,7 @@ const categoryList = ref([]);
|
||||
const title = ref('');
|
||||
const password = ref('');
|
||||
const categoryValue = ref(null);
|
||||
const content = ref({ ops: [] }); // Delta 데이터 유지
|
||||
const attachFiles = ref(null);
|
||||
const content = ref({ ops: [] });
|
||||
const isFileValid = ref(true);
|
||||
|
||||
const titleAlert = ref(false);
|
||||
@ -117,8 +128,10 @@ const contentAlert = ref(false);
|
||||
const categoryAlert = ref(false);
|
||||
const attachFilesAlert = ref(false);
|
||||
|
||||
const { appContext } = getCurrentInstance();
|
||||
const $common = appContext.config.globalProperties.$common;
|
||||
const attachFiles = ref([]);
|
||||
const maxFiles = 5;
|
||||
const maxSize = 10 * 1024 * 1024;
|
||||
const fileError = ref('');
|
||||
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
@ -137,12 +150,37 @@ onMounted(() => {
|
||||
fetchCategories();
|
||||
});
|
||||
|
||||
/** ✅ 제목 유효성 검사 */
|
||||
const fileCount = computed(() => attachFiles.value.length);
|
||||
|
||||
const handleFileUpload = (files) => {
|
||||
const validFiles = files.filter(file => file.size <= maxSize);
|
||||
if (files.some(file => file.size > maxSize)) {
|
||||
fileError.value = '파일 크기가 10MB를 초과할 수 없습니다.';
|
||||
return;
|
||||
}
|
||||
if (attachFiles.value.length + validFiles.length > maxFiles) {
|
||||
fileError.value = `최대 ${maxFiles}개의 파일만 업로드할 수 있습니다.`;
|
||||
return;
|
||||
}
|
||||
fileError.value = '';
|
||||
attachFiles.value = [...attachFiles.value, ...validFiles].slice(0, maxFiles);
|
||||
};
|
||||
|
||||
const removeFile = (index) => {
|
||||
attachFiles.value.splice(index, 1);
|
||||
if (attachFiles.value.length <= maxFiles) {
|
||||
fileError.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
watch(attachFiles, () => {
|
||||
isFileValid.value = attachFiles.value.length <= maxFiles;
|
||||
});
|
||||
|
||||
const validateTitle = () => {
|
||||
titleAlert.value = title.value.trim().length === 0;
|
||||
};
|
||||
|
||||
/** ✅ 비밀번호 유효성 검사 (공백 제거) */
|
||||
const validatePassword = () => {
|
||||
if (categoryValue.value === 300102) {
|
||||
password.value = password.value.replace(/\s/g, ''); // 공백 제거
|
||||
@ -152,7 +190,6 @@ const validatePassword = () => {
|
||||
}
|
||||
};
|
||||
|
||||
/** ✅ 내용 유효성 검사 */
|
||||
const validateContent = () => {
|
||||
if (!content.value?.ops?.length) {
|
||||
contentAlert.value = true;
|
||||
@ -168,7 +205,7 @@ const validateContent = () => {
|
||||
contentAlert.value = !(hasText || hasImage);
|
||||
};
|
||||
|
||||
/** ✅ 글쓰기 */
|
||||
/** 글쓰기 */
|
||||
const write = async () => {
|
||||
validateTitle();
|
||||
validatePassword();
|
||||
@ -189,10 +226,10 @@ const write = async () => {
|
||||
|
||||
const { data: boardResponse } = await axios.post('board', boardData);
|
||||
const boardId = boardResponse.data;
|
||||
|
||||
// ✅ 첨부파일 업로드 (비동기 병렬 처리)
|
||||
// 첨부파일 업로드 (비동기 병렬 처리)
|
||||
if (attachFiles.value && attachFiles.value.length > 0) {
|
||||
await Promise.all(attachFiles.value.map(async (file) => {
|
||||
console.log(file);
|
||||
const formData = new FormData();
|
||||
const fileNameWithoutExt = file.name.replace(/\.[^/.]+$/, '');
|
||||
|
||||
@ -200,12 +237,11 @@ const write = async () => {
|
||||
formData.append('CMNFLEORG', fileNameWithoutExt);
|
||||
formData.append('CMNFLEEXT', file.name.split('.').pop());
|
||||
formData.append('CMNFLESIZ', file.size);
|
||||
formData.append('CMNFLEPAT', 'boardfile');
|
||||
formData.append('file', file);
|
||||
formData.append('file', file); // 📌 실제 파일 추가
|
||||
|
||||
await axios.post(`board/${boardId}/attachments`, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
await axios.post(`board/${boardId}/attachments`, formData,
|
||||
{ isFormData : true }
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
@ -217,12 +253,12 @@ const write = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
/** ✅ 목록으로 이동 */
|
||||
/** 목록으로 이동 */
|
||||
const goList = () => {
|
||||
router.push('/board');
|
||||
};
|
||||
|
||||
/** ✅ `content` 변경 감지하여 자동 유효성 검사 실행 */
|
||||
/** `content` 변경 감지하여 자동 유효성 검사 실행 */
|
||||
watch(content, () => {
|
||||
validateContent();
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user