Merge remote-tracking branch 'origin/main' into board-select
This commit is contained in:
commit
0274183e2e
@ -5,17 +5,17 @@
|
||||
<div class="card">
|
||||
<!-- 프로필 헤더 -->
|
||||
<div class="card-header">
|
||||
<BoardProfile :boardId="currentBoardId.value" :profileName="profileName" />
|
||||
<BoardProfile :boardId="currentBoardId" :profileName="profileName" />
|
||||
</div>
|
||||
<!-- 게시글 내용 -->
|
||||
<div class="card-body">
|
||||
<h5 class="mb-4">{{ boardTitle }}</h5>
|
||||
<!-- 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">
|
||||
<li v-for="(attachment, index) in attachments" :key="index">
|
||||
<a :href="attachment.url" target="_blank">{{ attachment.name }}</a>
|
||||
<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>
|
||||
<!-- 댓글 영역 -->
|
||||
@ -23,10 +23,7 @@
|
||||
</div>
|
||||
<!-- 수정 버튼 -->
|
||||
<div class="card-footer d-flex justify-content-end">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="goToEditPage"
|
||||
>
|
||||
<button class="btn btn-primary" @click="goToEditPage">
|
||||
글 수정
|
||||
</button>
|
||||
</div>
|
||||
@ -42,11 +39,14 @@ import BoardProfile from '@c/board/BoardProfile.vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import axios from '@api';
|
||||
import Quill from 'quill';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
// 게시물 데이터 상태
|
||||
const profileName = ref('익명 사용자');
|
||||
const boardTitle = ref('제목 없음');
|
||||
const boardContent = ref('내용 없음');
|
||||
const boardContent = ref('');
|
||||
const convertedContent = ref('내용 없음');
|
||||
const comments = ref([]);
|
||||
const attachments = ref([]);
|
||||
|
||||
@ -70,7 +70,22 @@ const fetchBoardDetails = async () => {
|
||||
const boardDetail = data.boardDetail || {};
|
||||
profileName.value = boardDetail.author || '익명 사용자';
|
||||
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 || [];
|
||||
comments.value = data.comments || [];
|
||||
|
||||
@ -9,36 +9,52 @@
|
||||
|
||||
<div class="col-xl-12">
|
||||
<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
|
||||
v-show="category == 1"
|
||||
v-show="category === 1"
|
||||
title="비밀번호"
|
||||
name="pw"
|
||||
type="password"
|
||||
:is-essential="true"
|
||||
: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">
|
||||
<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>
|
||||
</label>
|
||||
<div class="col-md-12">
|
||||
<!-- <TEditor @update:data="content = $event"/> -->
|
||||
<QEditor @update:data="content = $event" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -49,114 +65,75 @@
|
||||
|
||||
<script setup>
|
||||
import QEditor from '@c/editor/QEditor.vue';
|
||||
import TEditor from '@c/editor/TEditor.vue';
|
||||
import FormInput from '@c/input/FormInput.vue';
|
||||
import FormSelect from '@c/input/FormSelect.vue';
|
||||
import FormFile from '@c/input/FormFile.vue';
|
||||
import { ref, watch } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import router from '@/router';
|
||||
import axios from '@api';
|
||||
|
||||
const categoryList = ['자유', '익명', '공지사항'];
|
||||
// input 경고문 만들어야함!!
|
||||
const title = ref('');
|
||||
const password = ref('');
|
||||
const category = ref(0);
|
||||
const content = ref('');
|
||||
const attachFiles = ref(null);
|
||||
|
||||
//input 경고창 관리
|
||||
const titleAlert = ref(true);
|
||||
const titleAlert = ref(false);
|
||||
const passwordAlert = ref(false);
|
||||
const contentAlert = ref(false);
|
||||
const attachFilesAlert = ref(false);
|
||||
|
||||
|
||||
const goList = () => {
|
||||
// 목록으로 이동 나중엔 페이지 정보 ,검색 정보도 붙여야됨
|
||||
router.push('/board');
|
||||
};
|
||||
|
||||
const write = async () => {
|
||||
// 입력값 유효성 검사
|
||||
if (!title.value) {
|
||||
titleAlert.value = true;
|
||||
return;
|
||||
} else {
|
||||
titleAlert.value = false;
|
||||
}
|
||||
// 모든 필드 검사
|
||||
titleAlert.value = !title.value; // 제목이 비어 있으면 true
|
||||
passwordAlert.value = category.value === 1 && !password.value; // 익명 카테고리일 때 비밀번호 비어 있으면 true
|
||||
contentAlert.value = !content.value; // 내용이 비어 있으면 true
|
||||
|
||||
if (category.value === 1 && !password.value) {
|
||||
passwordAlert.value = true;
|
||||
// 필수 값 중 하나라도 비어 있으면 함수 종료
|
||||
if (titleAlert.value || passwordAlert.value || contentAlert.value) {
|
||||
return;
|
||||
} else {
|
||||
passwordAlert.value = false;
|
||||
}
|
||||
|
||||
if (!content.value) {
|
||||
contentAlert.value = true;
|
||||
return;
|
||||
} else {
|
||||
contentAlert.value = false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 게시물 작성 데이터 준비
|
||||
const boardData = {
|
||||
LOCBRDTTL: title.value,
|
||||
LOCBRDCON: content.value,
|
||||
LOCBRDPWD: category.value === 1 ? password.value : null,
|
||||
LOCBRDTYP: category.value === 1 ? 'S' : 'F', //공지사항 추가해야함!!
|
||||
// MEMBERSEQ: 로그인이용자 id(세션)
|
||||
LOCBRDTYP: category.value === 1 ? 'S' : 'F',
|
||||
};
|
||||
|
||||
// 게시물 작성 API 호출
|
||||
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) {
|
||||
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();
|
||||
formData.append("MEMBERSEQ",registrantId); // 첨부 파일
|
||||
formData.append("CMNFLEPAT", fileInfo.path); // 파일 경로
|
||||
formData.append("CMNFLENAM", fileInfo.originalName); // 파일 명(확장자제외)
|
||||
formData.append("CMNFLEORG", fileInfo.originalName); // 원본 파일명(확장자제외)
|
||||
formData.append("CMNFLEEXT", fileInfo.extension); // 파일 확장자
|
||||
formData.append("CMNFLESIZ", file.size); // 파일 크기
|
||||
formData.append("CMNFLEREG", fileInfo.registrantId); // 등록자 ID
|
||||
formData.append('CMNBRDSEQ', boardId);
|
||||
formData.append('CMNFLEORG', file.name);
|
||||
formData.append('CMNFLEEXT', file.name.split('.').pop());
|
||||
formData.append('CMNFLESIZ', file.size);
|
||||
formData.append('CMNFLEPAT', 'boardfile');
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await axios.post(`board/${boardId}/attachments`, formData, {
|
||||
await axios.post(`board/${boardId}/attachments`, formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
alert("게시물이 작성되었습니다.");
|
||||
alert('게시물이 작성되었습니다.');
|
||||
goList();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert("게시물 작성 중 오류가 발생했습니다.");
|
||||
alert('게시물 작성 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.text-red {
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user