localhost-front/src/views/board/BoardEdit.vue
2025-03-17 11:14:42 +09:00

288 lines
9.9 KiB
Vue

<template>
<div class="container-xxl flex-grow-1 container-p-y">
<div class="card">
<div class="pb-4 rounded-top">
<div class="container py-12 px-xl-10 px-4" style="padding-bottom: 0px !important">
<h3 class="text-center mb-2 mt-4"> 수정</h3>
</div>
</div>
<div class="col-xl-12">
<div class="card-body">
<!-- 제목 입력 -->
<FormInput
title="제목"
name="title"
:is-essential="true"
:is-alert="titleAlert"
v-model="title"
@update:alert="titleAlert = $event"
@input.once="validateTitle"
/>
<!-- 첨부파일 업로드 -->
<FormFile
title="첨부파일"
name="files"
:is-alert="attachFilesAlert"
@update:data="handleFileUpload"
@update:isValid="isFileValid = $event"
/>
<!-- 실시간 반영된 파일 개수 표시 -->
<div>
<p class="text-muted mt-1">첨부파일: {{ fileCount }} / 5개</p>
<p v-if="fileError" class="text-danger">{{ fileError }}</p>
<ul class="list-group mb-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, file)">✖</button>
</li>
</ul>
</div>
<!-- 내용 입력 -->
<div class="mb-4">
<label for="html5-tel-input" class="col-md-2 col-form-label">
내용
<span class="text-red">*</span>
</label>
<div class="col-md-12">
<QEditor
v-if="contentLoaded"
@update:data="content = $event"
@update:imageUrls="imageUrls = $event"
:initialData="content"
/>
</div>
<div v-if="contentAlert" class="invalid-feedback d-block">내용을 확인해주세요.</div>
</div>
<!-- 버튼 -->
<div class="mb-4 d-flex justify-content-end">
<button type="button" class="btn btn-info right" @click="goBack">
<i class="bx bx-left-arrow-alt"></i>
</button>
<button type="button" class="btn btn-primary ms-1" @click="updateBoard">
<i class="bx bx-check"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import QEditor from '@c/editor/QEditor.vue';
import FormInput from '@c/input/FormInput.vue';
import FormFile from '@c/input/FormFile.vue';
import { ref, onMounted, computed, watch, inject } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useToastStore } from '@s/toastStore';
import { useBoardAccessStore } from '@s/useBoardAccessStore';
import axios from '@api';
// 공통
const $common = inject('common');
const toastStore = useToastStore();
const accessStore = useBoardAccessStore();
// 상태 변수
const title = ref('');
const content = ref('');
// 경고 상태
const titleAlert = ref(false);
const contentAlert = ref(false);
const contentLoaded = ref(false);
// 라우터
const router = useRouter();
const route = useRoute();
const currentBoardId = ref(route.params.id); // 라우트에서 ID 가져오기
// 파일
const maxFiles = 5;
const maxSize = 10 * 1024 * 1024;
const attachFiles = ref([]);
const fileError = ref('');
const attachFilesAlert = ref(false);
const isFileValid = ref(true);
const delFileIdx = ref([]); // 제외할 기존 첨부파일 ID
const additionalFiles = ref([]); // 새로 추가할 첨부파일
// 게시물 데이터 로드
const fetchBoardDetails = async () => {
// 수정 데이터 전송
let password = accessStore.password;
const params = {
password: `${password}` || '',
};
//const response = await axios.get(`board/${currentBoardId.value}`);
const { data } = await axios.post(`board/${currentBoardId.value}`, params);
if (data.code !== 200) {
//toastStore.onToast(data.message, 'e');
alert(data.message, 'e');
router.back();
return;
}
const boardData = data.data;
// 기존 첨부파일 추가
if (boardData.hasAttachment && boardData.attachments.length > 0) {
attachFiles.value = addDisplayFileName([...boardData.attachments]);
}
// 데이터 설정
title.value = boardData.title || '제목 없음';
content.value = boardData.content || '내용 없음';
contentLoaded.value = true;
};
// 기존 첨부파일명을 노출
const addDisplayFileName = fileInfos =>
fileInfos.map(file => ({
...file,
name: `${file.originalName}.${file.extension}`,
}));
// 목록 페이지로 이동
const goList = () => {
accessStore.$reset();
router.push('/board');
};
// 전 페이지로 이동
const goBack = () => {
accessStore.$reset();
router.back();
};
// 유효성 확인
const checkValidation = () => {
contentAlert.value = $common.isNotValidContent(content);
titleAlert.value = $common.isNotValidInput(title.value);
if (titleAlert.value || contentAlert.value || !isFileValid.value) {
if (titleAlert.value) {
title.value = '';
}
return true;
} else {
return false;
}
};
////////////////// fileSection[S] ////////////////////
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, file) => {
if (file.id) delFileIdx.value.push(file.id);
attachFiles.value.splice(index, 1);
if (attachFiles.value.length <= maxFiles) {
fileError.value = '';
}
};
watch(attachFiles, () => {
isFileValid.value = attachFiles.value.length <= maxFiles;
});
const newFileFilter = attachFiles => {
const copyFiles = [...attachFiles.value];
return copyFiles.filter(item => !item.id);
};
////////////////// fileSection[E] ////////////////////
/** `content` 변경 감지하여 자동 유효성 검사 실행 */
watch(content, () => {
contentAlert.value = $common.isNotValidContent(content);
});
// 글 제목 유효성
const validateTitle = () => {
titleAlert.value = title.value.trim().length === 0;
};
/** `content` 변경 감지하여 자동 유효성 검사 실행 */
// watch(content, () => {
// contentAlert.value = $common.isNotValidContent(content);
// });
// 게시물 수정
const updateBoard = async () => {
if (checkValidation()) return;
// 수정 데이터 전송
const boardData = {
LOCBRDTTL: title.value.trim(),
LOCBRDCON: JSON.stringify(content.value),
LOCBRDSEQ: currentBoardId.value,
};
// 업로드 된 첨부파일의 삭제목록
if (delFileIdx.value && delFileIdx.value.length > 0) {
boardData.delFileIdx = [...delFileIdx.value];
}
const fileArray = newFileFilter(attachFiles);
const formData = new FormData();
// formData에 boardData 추가
Object.entries(boardData).forEach(([key, value]) => {
formData.append(key, value);
});
// formData에 새로 추가한 파일 추가
fileArray.forEach((file, idx) => {
formData.append('files', file);
});
const { data } = await axios.put(`board/${currentBoardId.value}`, formData, { isFormData: true });
if (data.code === 200) {
toastStore.onToast('게시물이 수정되었습니다.', 's');
goList();
} else {
toastStore.onToast('게시물 수정에 실패했습니다.', 'e');
}
};
// 컴포넌트 마운트 시 데이터 로드
onMounted(async () => {
if (currentBoardId.value) {
fetchBoardDetails();
} else {
console.error('잘못된 게시물 ID:', currentBoardId.value);
}
});
</script>
<style>
.text-red {
color: red;
text-align: center;
}
</style>