Merge remote-tracking branch 'origin/main' into board-select

This commit is contained in:
kimdaae328 2025-01-21 13:15:58 +09:00
commit 0274183e2e
2 changed files with 70 additions and 78 deletions

View File

@ -5,17 +5,17 @@
<div class="card"> <div class="card">
<!-- 프로필 헤더 --> <!-- 프로필 헤더 -->
<div class="card-header"> <div class="card-header">
<BoardProfile :boardId="currentBoardId.value" :profileName="profileName" /> <BoardProfile :boardId="currentBoardId" :profileName="profileName" />
</div> </div>
<!-- 게시글 내용 --> <!-- 게시글 내용 -->
<div class="card-body"> <div class="card-body">
<h5 class="mb-4">{{ boardTitle }}</h5> <h5 class="mb-4">{{ boardTitle }}</h5>
<!-- HTML 콘텐츠 렌더링 --> <!-- HTML 콘텐츠 렌더링 -->
<div class="board-content" v-html="boardContent"></div> <div class="board-content text-body" style="line-height: 1.6;" v-html="convertedContent"></div>
<!-- 첨부파일 목록 --> <!-- 첨부파일 목록 -->
<ul v-if="attachments.length" class="attachments mt-4"> <ul v-if="attachments.length" class="attachments mt-4 list-unstyled">
<li v-for="(attachment, index) in attachments" :key="index"> <li v-for="(attachment, index) in attachments" :key="index" class="mb-2">
<a :href="attachment.url" target="_blank">{{ attachment.name }}</a> <a :href="attachment.url" target="_blank" class="text-decoration-none">{{ attachment.name }}</a>
</li> </li>
</ul> </ul>
<!-- 댓글 영역 --> <!-- 댓글 영역 -->
@ -23,10 +23,7 @@
</div> </div>
<!-- 수정 버튼 --> <!-- 수정 버튼 -->
<div class="card-footer d-flex justify-content-end"> <div class="card-footer d-flex justify-content-end">
<button <button class="btn btn-primary" @click="goToEditPage">
class="btn btn-primary"
@click="goToEditPage"
>
수정 수정
</button> </button>
</div> </div>
@ -42,11 +39,14 @@ import BoardProfile from '@c/board/BoardProfile.vue';
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import axios from '@api'; import axios from '@api';
import Quill from 'quill';
import DOMPurify from 'dompurify';
// //
const profileName = ref('익명 사용자'); const profileName = ref('익명 사용자');
const boardTitle = ref('제목 없음'); const boardTitle = ref('제목 없음');
const boardContent = ref('내용 없음'); const boardContent = ref('');
const convertedContent = ref('내용 없음');
const comments = ref([]); const comments = ref([]);
const attachments = ref([]); const attachments = ref([]);
@ -70,7 +70,22 @@ const fetchBoardDetails = async () => {
const boardDetail = data.boardDetail || {}; const boardDetail = data.boardDetail || {};
profileName.value = boardDetail.author || '익명 사용자'; profileName.value = boardDetail.author || '익명 사용자';
boardTitle.value = boardDetail.title || '제목 없음'; boardTitle.value = boardDetail.title || '제목 없음';
boardContent.value = boardDetail.content || '내용 없음'; boardContent.value = boardDetail.content || '';
// Quill Delta HTML
if (boardContent.value) {
try {
const quillContainer = document.createElement('div');
const quillInstance = new Quill(quillContainer);
quillInstance.setContents(JSON.parse(boardContent.value));
convertedContent.value = DOMPurify.sanitize(quillContainer.innerHTML);
} catch (parseError) {
console.error('Delta 데이터 변환 오류:', parseError);
convertedContent.value = '내용을 표시할 수 없습니다.';
}
} else {
convertedContent.value = '내용 없음';
}
attachments.value = data.attachments || []; attachments.value = data.attachments || [];
comments.value = data.comments || []; comments.value = data.comments || [];

View File

@ -9,36 +9,52 @@
<div class="col-xl-12"> <div class="col-xl-12">
<div class="card-body"> <div class="card-body">
<FormInput title="제목" name="title" :is-essential="true" :is-alert="titleAlert" @update:data="title = $event" /> <FormInput
title="제목"
name="title"
:is-essential="true"
:is-alert="titleAlert"
v-model="title"
/>
<FormSelect title="카테고리" name="cate" :is-essential="true" :data="categoryList" @update:data="category = $event" /> <FormSelect
title="카테고리"
name="cate"
:is-essential="true"
:data="categoryList"
@update:data="category = $event"
/>
<FormInput <FormInput
v-show="category == 1" v-show="category === 1"
title="비밀번호" title="비밀번호"
name="pw" name="pw"
type="password" type="password"
:is-essential="true" :is-essential="true"
:is-alert="passwordAlert" :is-alert="passwordAlert"
@update:data="password = $event" v-model="password"
/> />
<FormFile title="첨부파일" name="files" :is-alert="attachFilesAlert" @update:data="attachFiles = $event" /> <FormFile
title="첨부파일"
name="files"
:is-alert="attachFilesAlert"
@update:data="attachFiles = $event"
/>
<div class="mb-4"> <div class="mb-4">
<label for="html5-tel-input" class="col-md-2 col-form-label"> <label for="html5-tel-input" class="col-md-2 col-form-label">
내용 내용
<span class="text-red">*</span> <span class="text-danger">*</span>
<div class="invalid-feedback" :class="contentAlert ? 'display-block' : ''">내용을 확인해주세요.</div> <div class="invalid-feedback" :class="contentAlert ? 'display-block' : ''">내용을 확인해주세요.</div>
</label> </label>
<div class="col-md-12"> <div class="col-md-12">
<!-- <TEditor @update:data="content = $event"/> -->
<QEditor @update:data="content = $event" /> <QEditor @update:data="content = $event" />
</div> </div>
</div> </div>
<div class="mb-4 d-flex justify-content-end"> <div class="mb-4 d-flex justify-content-end">
<button type="button" class="btn btn-info right" @click="goList"><i class='bx bx-left-arrow-alt'></i></button> <button type="button" class="btn btn-info" @click="goList"><i class='bx bx-left-arrow-alt'></i></button>
<button type="button" class="btn btn-primary ms-1" @click="write"><i class='bx bx-check'></i></button> <button type="button" class="btn btn-primary ms-1" @click="write"><i class='bx bx-check'></i></button>
</div> </div>
</div> </div>
@ -49,114 +65,75 @@
<script setup> <script setup>
import QEditor from '@c/editor/QEditor.vue'; import QEditor from '@c/editor/QEditor.vue';
import TEditor from '@c/editor/TEditor.vue';
import FormInput from '@c/input/FormInput.vue'; import FormInput from '@c/input/FormInput.vue';
import FormSelect from '@c/input/FormSelect.vue'; import FormSelect from '@c/input/FormSelect.vue';
import FormFile from '@c/input/FormFile.vue'; import FormFile from '@c/input/FormFile.vue';
import { ref, watch } from 'vue'; import { ref } from 'vue';
import router from '@/router'; import router from '@/router';
import axios from '@api'; import axios from '@api';
const categoryList = ['자유', '익명', '공지사항']; const categoryList = ['자유', '익명', '공지사항'];
// input !!
const title = ref(''); const title = ref('');
const password = ref(''); const password = ref('');
const category = ref(0); const category = ref(0);
const content = ref(''); const content = ref('');
const attachFiles = ref(null); const attachFiles = ref(null);
//input const titleAlert = ref(false);
const titleAlert = ref(true);
const passwordAlert = ref(false); const passwordAlert = ref(false);
const contentAlert = ref(false); const contentAlert = ref(false);
const attachFilesAlert = ref(false); const attachFilesAlert = ref(false);
const goList = () => { const goList = () => {
// ,
router.push('/board'); router.push('/board');
}; };
const write = async () => { const write = async () => {
// //
if (!title.value) { titleAlert.value = !title.value; // true
titleAlert.value = true; passwordAlert.value = category.value === 1 && !password.value; // true
return; contentAlert.value = !content.value; // true
} else {
titleAlert.value = false;
}
if (category.value === 1 && !password.value) { //
passwordAlert.value = true; if (titleAlert.value || passwordAlert.value || contentAlert.value) {
return; return;
} else {
passwordAlert.value = false;
}
if (!content.value) {
contentAlert.value = true;
return;
} else {
contentAlert.value = false;
} }
try { try {
//
const boardData = { const boardData = {
LOCBRDTTL: title.value, LOCBRDTTL: title.value,
LOCBRDCON: content.value, LOCBRDCON: content.value,
LOCBRDPWD: category.value === 1 ? password.value : null, LOCBRDPWD: category.value === 1 ? password.value : null,
LOCBRDTYP: category.value === 1 ? 'S' : 'F', // !! LOCBRDTYP: category.value === 1 ? 'S' : 'F',
// MEMBERSEQ: id()
}; };
// API
const { data: boardResponse } = await axios.post('board', boardData); const { data: boardResponse } = await axios.post('board', boardData);
const boardId = boardResponse.data.boardId; const boardId = boardResponse.data.CMNBRDSEQ;
//
if (attachFiles.value && attachFiles.value.length > 0) { if (attachFiles.value && attachFiles.value.length > 0) {
for (const file of attachFiles.value) { for (const file of attachFiles.value) {
const realName = file.name.substring(0, file.name.lastIndexOf('.'));
const fileInfo = {
path: "/uploads", // ( )
originalName: realName, //
extension: file.name.split('.').pop(), //
registrantId: 1, // ID ( )
};
const formData = new FormData(); const formData = new FormData();
formData.append("MEMBERSEQ",registrantId); // formData.append('CMNBRDSEQ', boardId);
formData.append("CMNFLEPAT", fileInfo.path); // formData.append('CMNFLEORG', file.name);
formData.append("CMNFLENAM", fileInfo.originalName); // () formData.append('CMNFLEEXT', file.name.split('.').pop());
formData.append("CMNFLEORG", fileInfo.originalName); // () formData.append('CMNFLESIZ', file.size);
formData.append("CMNFLEEXT", fileInfo.extension); // formData.append('CMNFLEPAT', 'boardfile');
formData.append("CMNFLESIZ", file.size); // formData.append('file', file);
formData.append("CMNFLEREG", fileInfo.registrantId); // ID
const response = await axios.post(`board/${boardId}/attachments`, formData, { await axios.post(`board/${boardId}/attachments`, formData, {
headers: { headers: {
"Content-Type": "multipart/form-data", 'Content-Type': 'multipart/form-data',
}, },
}); });
} }
} }
alert("게시물이 작성되었습니다."); alert('게시물이 작성되었습니다.');
goList(); goList();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
alert("게시물 작성 중 오류가 발생했습니다."); alert('게시물 작성 중 오류가 발생했습니다.');
} }
}; };
</script> </script>
<style>
.text-red {
color: red;
text-align: center;
}
</style>