Merge branch 'main' into wordDict

This commit is contained in:
dyhj625 2025-02-28 14:53:16 +09:00
commit 6df2f00306
4 changed files with 70 additions and 40 deletions

View File

@ -15,6 +15,7 @@
:disabled="disabled"
:min="min"
@focusout="$emit('focusout', modelValue)"
@input="handleInput"
/>
<div class="invalid-feedback" :class="isAlert ? 'd-block' : ''">
{{ title }} 확인해주세요.
@ -92,11 +93,6 @@ const inputValue = ref(props.modelValue);
//
watch(inputValue, (newValue) => {
emits('update:modelValue', newValue);
// `alert` false
if (newValue.trim() !== '') {
emits('update:alert', false);
}
});
//
@ -106,6 +102,13 @@ watch(() => props.modelValue, (newValue) => {
}
});
const handleInput = (event) => {
const newValue = event.target.value.slice(0, props.maxlength);
if (newValue.trim() !== '') {
emits('update:alert', false);
}
};
</script>

View File

@ -38,7 +38,7 @@
<div class="ms-12 position-relative">
{{ address }} {{ addressdtail }}
<!-- 팝오버 -->
<div v-if="isPopoverVisible" class="position-absolute w-100 map text-end">
<div v-if="isPopoverVisible" class="position-absolute map ">
<button type="button" class="btn-close popover-close" @click.stop="isPopoverVisible = !isPopoverVisible"></button>
<div class="card">
<div class="card-body p-1">
@ -46,8 +46,7 @@
v-if="coordinates"
:lat="coordinates.lat"
:lng="coordinates.lng"
:draggable="false"
class="w-100 h-px-200"
class="w-px-200 h-px-200"
>
<KakaoMapMarker
:lat="coordinates.lat"

View File

@ -49,6 +49,7 @@
:is-essential="true"
:is-label="true"
:is-common="true"
:is-color="true"
:data="colorList"
@update:data="color = $event"
/>

View File

@ -9,7 +9,6 @@
<div class="col-xl-12">
<div class="card-body">
<!-- 제목 입력 -->
<FormInput
title="제목"
name="title"
@ -75,7 +74,7 @@
내용 <span class="text-danger">*</span>
</label>
<div class="col-md-12">
<QEditor @update:data="updateContent" />
<QEditor @update:data="content = $event" />
</div>
<div class="invalid-feedback mt-1" :class="contentAlert ? 'd-block' : 'd-none'">
내용을 입력해주세요.
@ -93,22 +92,22 @@
</template>
<script setup>
import { ref, onMounted, getCurrentInstance, watch } from 'vue';
import QEditor from '@c/editor/QEditor.vue';
import FormInput from '@c/input/FormInput.vue';
import FormFile from '@c/input/FormFile.vue';
import { getCurrentInstance, ref, onMounted } from 'vue';
import SaveButton from '@c/button/SaveBtn.vue';
import BackButton from '@c/button/BackBtn.vue';
import { useToastStore } from '@s/toastStore';
import router from '@/router';
import axios from '@api';
import SaveButton from '@c/button/SaveBtn.vue';
import BackButton from '@c/button/BackBtn.vue'
import { useToastStore } from '@s/toastStore';
const toastStore = useToastStore();
const categoryList = ref([]);
const title = ref('');
const password = ref('');
const categoryValue = ref(null);
const content = ref('');
const content = ref({ ops: [] }); // Delta
const attachFiles = ref(null);
const isFileValid = ref(true);
@ -138,44 +137,43 @@ onMounted(() => {
fetchCategories();
});
// : ,
/** ✅ 제목 유효성 검사 */
const validateTitle = () => {
titleAlert.value = title.value.trim().length === 0;
};
// :
/** ✅ 비밀번호 유효성 검사 (공백 제거) */
const validatePassword = () => {
password.value = password.value.replace(/\s/g, ""); //
passwordAlert.value = password.value.length === 0;
if (categoryValue.value === 300102) {
password.value = password.value.replace(/\s/g, ''); //
passwordAlert.value = password.value.length === 0;
} else {
passwordAlert.value = false;
}
};
//
const updateContent = (data) => {
let rawText = '';
if (typeof data === 'object' && data.ops) {
rawText = data.ops.map(op => (typeof op.insert === 'string' ? op.insert : '')).join('').trim();
} else if (typeof data === 'string') {
rawText = data.replace(/(<([^>]+)>)/gi, "").trim();
} else {
rawText = '';
/** ✅ 내용 유효성 검사 */
const validateContent = () => {
if (!content.value?.ops?.length) {
contentAlert.value = true;
return;
}
content.value = rawText.length > 0 ? data : "";
contentAlert.value = rawText.length === 0;
};
/** 페이지 이동 (목록으로 이동) */
const goList = () => {
router.push('/board');
//
const hasImage = content.value.ops.some(op => op.insert && typeof op.insert === 'object' && op.insert.image);
//
const hasText = content.value.ops.some(op => typeof op.insert === 'string' && op.insert.trim().length > 0);
//
contentAlert.value = !(hasText || hasImage);
};
/** ✅ 글쓰기 */
const write = async () => {
validateTitle();
validatePassword();
categoryAlert.value = !categoryValue.value;
contentAlert.value = content.value.length === 0;
validateContent();
categoryAlert.value = categoryValue.value == null;
if (titleAlert.value || passwordAlert.value || contentAlert.value || categoryAlert.value || !isFileValid.value) {
return;
@ -184,7 +182,7 @@ const write = async () => {
try {
const boardData = {
LOCBRDTTL: title.value,
LOCBRDCON: $common.deltaAsJson(content.value),
LOCBRDCON: JSON.stringify(content.value), // Delta JSON
LOCBRDPWD: categoryValue.value === 300102 ? password.value : null,
LOCBRDTYP: categoryValue.value
};
@ -192,6 +190,25 @@ 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) => {
const formData = new FormData();
const fileNameWithoutExt = file.name.replace(/\.[^/.]+$/, '');
formData.append('CMNBRDSEQ', boardId);
formData.append('CMNFLEORG', fileNameWithoutExt);
formData.append('CMNFLEEXT', file.name.split('.').pop());
formData.append('CMNFLESIZ', file.size);
formData.append('CMNFLEPAT', 'boardfile');
formData.append('file', file);
await axios.post(`board/${boardId}/attachments`, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
}));
}
toastStore.onToast('게시물이 작성되었습니다.', 's');
goList();
} catch (error) {
@ -199,4 +216,14 @@ const write = async () => {
toastStore.onToast('게시물 작성 중 오류가 발생했습니다.', 'e');
}
};
/** ✅ 목록으로 이동 */
const goList = () => {
router.push('/board');
};
/** ✅ `content` 변경 감지하여 자동 유효성 검사 실행 */
watch(content, () => {
validateContent();
});
</script>