게시글 에디터 이미지 수정, 제거 로직 추가

This commit is contained in:
nevermoregb 2025-03-24 14:01:06 +09:00
parent a72eb1f81a
commit 52d520f5e4
5 changed files with 90 additions and 15 deletions

View File

@ -3,4 +3,5 @@ VITE_DOMAIN = https://192.168.0.251:5173/
VITE_SERVER = https://192.168.0.251:10300/ VITE_SERVER = https://192.168.0.251:10300/
VITE_API_URL = https://192.168.0.251:10300/api/ VITE_API_URL = https://192.168.0.251:10300/api/
VITE_TEST_URL = https://192.168.0.251:10300/test/ VITE_TEST_URL = https://192.168.0.251:10300/test/
VITE_SERVER_IMG_URL = https://192.168.0.251:10300/upload/img/
VITE_KAKAO_MAP_KEY=6f092e8f45ee81186bb6d8408f66a492 VITE_KAKAO_MAP_KEY=6f092e8f45ee81186bb6d8408f66a492

View File

@ -3,4 +3,5 @@ VITE_DOMAIN = http://localhost:5173/
VITE_SERVER = http://localhost:10325/ VITE_SERVER = http://localhost:10325/
VITE_API_URL = http://localhost:10325/api/ VITE_API_URL = http://localhost:10325/api/
VITE_TEST_URL = http://localhost:10325/test/ VITE_TEST_URL = http://localhost:10325/test/
VITE_SERVER_IMG_URL = http://localhost:10325/upload/img/
VITE_KAKAO_MAP_KEY=6f092e8f45ee81186bb6d8408f66a492 VITE_KAKAO_MAP_KEY=6f092e8f45ee81186bb6d8408f66a492

View File

@ -45,7 +45,7 @@
<button class="ql-code-block">Code Block</button> <button class="ql-code-block">Code Block</button>
</div> </div>
<!-- 에디터가 표시될 div --> <!-- 에디터가 표시될 div -->
<div ref="editor"></div> <div id="qEditor" ref="editor"></div>
<!-- Alert 메시지 표시 --> <!-- Alert 메시지 표시 -->
<div class="invalid-feedback" :class="isAlert ? 'd-block' : ''">내용을 확인해주세요.</div> <div class="invalid-feedback" :class="isAlert ? 'd-block' : ''">내용을 확인해주세요.</div>
</div> </div>
@ -71,8 +71,10 @@
const editor = ref(null); // DOM const editor = ref(null); // DOM
const font = ref('nanum-gothic'); // const font = ref('nanum-gothic'); //
const fontSize = ref('16px'); // const fontSize = ref('16px'); //
const emit = defineEmits(['update:data', 'update:uploadedImgList']); const emit = defineEmits(['update:data', 'update:uploadedImgList', 'update:deleteImgIndexList']);
const uploadedImgList = ref([]); // const uploadedImgList = ref([]); //
const initImageIndex = ref([]); //
const deleteImgIndexList = ref([]); //
onMounted(() => { onMounted(() => {
// //
@ -113,16 +115,20 @@
quillInstance.format('size', fontSize.value); quillInstance.format('size', fontSize.value);
}); });
// //
watch(uploadedImgList, () => { watch(uploadedImgList, () => {
console.log(!23);
emit('update:uploadedImgList', uploadedImgList.value); emit('update:uploadedImgList', uploadedImgList.value);
console.log('uploadedImgList.value: ', uploadedImgList.value); });
// ()
watch(deleteImgIndexList, () => {
emit('update:deleteImgIndexList', deleteImgIndexList.value);
}); });
// , HTML // , HTML
if (props.initialData) { if (props.initialData) {
quillInstance.setContents(JSON.parse(props.initialData)); quillInstance.setContents(JSON.parse(props.initialData));
initCheckImageIndex();
} }
// //
@ -141,6 +147,8 @@
checkForDeletedImages(); // checkForDeletedImages(); //
} }
}); });
checkDeletedImages();
emit('update:data', quillInstance.getContents()); emit('update:data', quillInstance.getContents());
}); });
@ -165,10 +173,12 @@
// ( ) // ( )
if (uploadImgIdx) { if (uploadImgIdx) {
uploadedImgList.value = [...uploadedImgList.value, uploadImgIdx]; uploadedImgList.value = [...uploadedImgList.value, uploadImgIdx];
initImageIndex.value = [...initImageIndex.value, uploadImgIdx];
} }
const baseUrl = $api.defaults.baseURL.replace(/api\/$/, ''); const baseUrl = $api.defaults.baseURL.replace(/api\/$/, '');
const fullImageUrl = `${baseUrl}${serverImageUrl.replace(/\\/g, '/')}`; //const fullImageUrl = `${baseUrl}${serverImageUrl.replace(/\\/g, '/')}`;
const fullImageUrl = `${baseUrl}${serverImageUrl.replace(/\\/g, '/')}?imgIndex=${uploadImgIdx}`; // index
const range = quillInstance.getSelection(); const range = quillInstance.getSelection();
quillInstance.insertEmbed(range.index, 'image', fullImageUrl); // quillInstance.insertEmbed(range.index, 'image', fullImageUrl); //
@ -221,7 +231,7 @@
// //
function checkForDeletedImages() { function checkForDeletedImages() {
const editorImages = document.querySelectorAll('#editor img'); const editorImages = document.querySelectorAll('#qEditor img');
const currentImages = new Set(Array.from(editorImages).map(img => img.src)); // const currentImages = new Set(Array.from(editorImages).map(img => img.src)); //
imageUrls.forEach(url => { imageUrls.forEach(url => {
@ -230,5 +240,41 @@
} }
}); });
} }
//
function initCheckImageIndex() {
const editorImages = document.querySelectorAll('#qEditor img');
const currentImages = new Set(Array.from(editorImages).map(img => img.src)); //
currentImages.forEach(url => {
const index = getImgIndex(url);
if (index) {
initImageIndex.value.push(Number(index));
}
});
}
// index
function getImgIndex(url) {
const params = new URLSearchParams(url.split('?')[1]);
return params.get('imgIndex');
}
//
function checkDeletedImages() {
const editorImages = document.querySelectorAll('#qEditor img');
const currentImages = new Set(Array.from(editorImages).map(img => img.src));
// init
const tempDeleteImgIndex = [...initImageIndex.value];
currentImages.forEach(url => {
const imgIndex = getImgIndex(url);
if (imgIndex) {
const index = tempDeleteImgIndex.indexOf(imgIndex);
tempDeleteImgIndex.splice(index, 1);
}
});
deleteImgIndexList.value = tempDeleteImgIndex;
}
}); });
</script> </script>

View File

@ -58,6 +58,7 @@
@update:data="content = $event" @update:data="content = $event"
@update:imageUrls="imageUrls = $event" @update:imageUrls="imageUrls = $event"
@update:uploadedImgList="handleUpdateEditorImg" @update:uploadedImgList="handleUpdateEditorImg"
@update:deleteImgIndexList="handleDeleteEditorImg"
:initialData="content" :initialData="content"
/> />
</div> </div>
@ -117,6 +118,7 @@
const isFileValid = ref(true); const isFileValid = ref(true);
const delFileIdx = ref([]); // ID const delFileIdx = ref([]); // ID
const editorUploadedImgList = ref([]); const editorUploadedImgList = ref([]);
const editorDeleteImgList = ref([]);
// //
const fetchBoardDetails = async () => { const fetchBoardDetails = async () => {
@ -150,6 +152,10 @@
editorUploadedImgList.value = item; editorUploadedImgList.value = item;
}; };
const handleDeleteEditorImg = item => {
editorDeleteImgList.value = item;
};
// //
const addDisplayFileName = fileInfos => const addDisplayFileName = fileInfos =>
fileInfos.map(file => ({ fileInfos.map(file => ({
@ -231,11 +237,6 @@
titleAlert.value = title.value.trim().length === 0; titleAlert.value = title.value.trim().length === 0;
}; };
/** `content` 변경 감지하여 자동 유효성 검사 실행 */
// watch(content, () => {
// contentAlert.value = $common.isNotValidContent(content);
// });
// //
const updateBoard = async () => { const updateBoard = async () => {
if (checkValidation()) return; if (checkValidation()) return;
@ -257,6 +258,11 @@
boardData.editorUploadedImgList = [...editorUploadedImgList.value]; boardData.editorUploadedImgList = [...editorUploadedImgList.value];
} }
//
if (editorDeleteImgList.value && editorDeleteImgList.value.length > 0) {
boardData.editorDeleteImgList = [...editorDeleteImgList.value];
}
const fileArray = newFileFilter(attachFiles); const fileArray = newFileFilter(attachFiles);
const formData = new FormData(); const formData = new FormData();

View File

@ -92,7 +92,11 @@
<div class="mb-4"> <div class="mb-4">
<label class="col-md-2 col-form-label"> 내용 <span class="text-danger">*</span> </label> <label class="col-md-2 col-form-label"> 내용 <span class="text-danger">*</span> </label>
<div class="col-md-12"> <div class="col-md-12">
<QEditor @update:data="content = $event" @update:uploadedImgList="handleUpdateEditorImg" /> <QEditor
@update:data="content = $event"
@update:uploadedImgList="handleUpdateEditorImg"
@update:deleteImgIndexList="handleDeleteEditorImg"
/>
</div> </div>
<div class="invalid-feedback mt-1" :class="contentAlert ? 'd-block' : 'd-none'">내용을 입력해주세요.</div> <div class="invalid-feedback mt-1" :class="contentAlert ? 'd-block' : 'd-none'">내용을 입력해주세요.</div>
</div> </div>
@ -121,7 +125,7 @@
const toastStore = useToastStore(); const toastStore = useToastStore();
const categoryList = ref([]); const categoryList = ref([]);
const title = ref(''); const title = ref('');
const nickname = ref(""); const nickname = ref('');
const password = ref(''); const password = ref('');
const categoryValue = ref(null); const categoryValue = ref(null);
const content = ref({ ops: [] }); const content = ref({ ops: [] });
@ -139,6 +143,7 @@
const maxSize = 10 * 1024 * 1024; const maxSize = 10 * 1024 * 1024;
const fileError = ref(''); const fileError = ref('');
const editorUploadedImgList = ref([]); const editorUploadedImgList = ref([]);
const editorDeleteImgList = ref([]);
const fetchCategories = async () => { const fetchCategories = async () => {
const response = await axios.get('board/categories'); const response = await axios.get('board/categories');
@ -159,6 +164,10 @@
editorUploadedImgList.value = item; editorUploadedImgList.value = item;
}; };
const handleDeleteEditorImg = item => {
editorDeleteImgList.value = item;
};
const handleFileUpload = files => { const handleFileUpload = files => {
const validFiles = files.filter(file => file.size <= maxSize); const validFiles = files.filter(file => file.size <= maxSize);
if (files.some(file => file.size > maxSize)) { if (files.some(file => file.size > maxSize)) {
@ -228,7 +237,14 @@
validateContent(); validateContent();
categoryAlert.value = categoryValue.value == null; categoryAlert.value = categoryValue.value == null;
if (titleAlert.value || nicknameAlert.value || passwordAlert.value || contentAlert.value || categoryAlert.value || !isFileValid.value) { if (
titleAlert.value ||
nicknameAlert.value ||
passwordAlert.value ||
contentAlert.value ||
categoryAlert.value ||
!isFileValid.value
) {
return; return;
} }
@ -246,6 +262,11 @@
boardData.editorUploadedImgList = [...editorUploadedImgList.value]; boardData.editorUploadedImgList = [...editorUploadedImgList.value];
} }
//
if (editorDeleteImgList.value && editorDeleteImgList.value.length > 0) {
boardData.editorDeleteImgList = [...editorDeleteImgList.value];
}
const { data: boardResponse } = await axios.post('board', boardData); const { data: boardResponse } = await axios.post('board', boardData);
const boardId = boardResponse.data; const boardId = boardResponse.data;
// ( ) // ( )