Merge branch 'main' into board-ji

This commit is contained in:
dyhj625 2025-03-11 10:34:51 +09:00
commit ccc195435c
7 changed files with 138 additions and 72 deletions

View File

@ -81,6 +81,34 @@ const common = {
return true; return true;
}, },
/**
* 에디터에 내용이 있는지 확인
*
* @param { Quill } content
* @returns true: 없음, false: 있음
*/
isNotValidContent(content) {
if (!content.value?.ops?.length) return true;
// 이미지 포함 여부 확인
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);
// 텍스트 또는 이미지가 하나라도 있으면 유효한 내용
return !(hasText || hasImage);
},
/**
* 확인
*
* @param {ref} text ex) inNotValidInput(data.value);
* @returns
*/
isNotValidInput(text) {
return text.trim().length === 0;
},
}; };
export default { export default {

View File

@ -31,7 +31,7 @@
</template> </template>
<script setup> <script setup>
import { defineProps, ref } from 'vue'; import { defineProps, ref, watch } from 'vue';
// lists prop // lists prop
const props = defineProps({ const props = defineProps({
@ -43,21 +43,29 @@ const props = defineProps({
type: Boolean, type: Boolean,
required: false, required: false,
}, },
selectedCategory: {
type: [String, Number],
default: null,
required: false,
},
}); });
// //
const selectedCategory = ref(null); const selectedCategory = ref(props.selectedCategory);
const emit = defineEmits();
const emit = defineEmits(['update:data']);
const selectCategory = (cate) => { const selectCategory = (cate) => {
selectedCategory.value = selectedCategory.value === cate ? null : cate; selectedCategory.value = selectedCategory.value === cate ? null : cate;
emit('update:data', selectedCategory.value); emit('update:data', selectedCategory.value);
}; };
watch(() => props.selectedCategory, (newVal) => {
selectedCategory.value = newVal;
});
</script> </script>
<style scoped> <style scoped>
@media (max-width: 768px) { @media (max-width: 768px) {
.cate-list { .cate-list {
overflow-x: scroll; overflow-x: scroll;

View File

@ -8,7 +8,7 @@
{{ title }} {{ title }}
</h5> </h5>
<p v-if="isProjectExpired" class="btn-icon btn-danger rounded-2"><i class='bx bx-power-off'></i></p> <p v-if="isProjectExpired" class="btn-icon btn-danger rounded-2"><i class='bx bx-power-off'></i></p>
<div v-if="!isProjectExpired"> <div v-if="!isProjectExpired" class="d-flex gap-1">
<EditBtn @click.stop="openEditModal" /> <EditBtn @click.stop="openEditModal" />
<DeleteBtn v-if="isProjectCreator" @click.stop="handleDelete" class="ms-1"/> <DeleteBtn v-if="isProjectCreator" @click.stop="handleDelete" class="ms-1"/>
</div> </div>
@ -128,6 +128,7 @@
title="종료일" title="종료일"
type="date" type="date"
name="endDay" name="endDay"
:min="todays"
:modelValue="selectedProject.PROJCTEND" :modelValue="selectedProject.PROJCTEND"
@update:modelValue="selectedProject.PROJCTEND = $event" @update:modelValue="selectedProject.PROJCTEND = $event"
/> />
@ -160,7 +161,7 @@
</template> </template>
<script setup> <script setup>
import { defineProps, onMounted, ref, computed, watch } from 'vue'; import { defineProps, onMounted, ref, computed, watch, inject } from 'vue';
import UserList from '@c/user/UserList.vue'; import UserList from '@c/user/UserList.vue';
import CenterModal from '@c/modal/CenterModal.vue'; import CenterModal from '@c/modal/CenterModal.vue';
import $api from '@api'; import $api from '@api';
@ -253,6 +254,12 @@ const isProjectCreator = computed(() => {
return user.value?.id === props.projctCreatorId; return user.value?.id === props.projctCreatorId;
}); });
// dayjs
const dayjs = inject('dayjs');
// YYYY-MM-DD
const todays = dayjs().format('YYYY-MM-DD');
// ( ) // ( )
const isProjectExpired = computed(() => { const isProjectExpired = computed(() => {
if (!props.enddate) return false; if (!props.enddate) return false;
@ -356,19 +363,6 @@ const hasChanges = computed(() => {
selectedProject.value.PROJCTDES !== props.description || selectedProject.value.PROJCTDES !== props.description ||
selectedProject.value.PROJCTCOL !== props.projctCol; selectedProject.value.PROJCTCOL !== props.projctCol;
}); });
//
watch(
() => selectedProject.value,
() => {
const start = new Date(selectedProject.value.PROJCTSTR);
const end = new Date(selectedProject.value.PROJCTEND);
if (end < start) {
selectedProject.value.PROJCTEND = selectedProject.value.PROJCTSTR;
}
},
{ deep: true, flush: 'post' }
);
// //
const handleUpdate = () => { const handleUpdate = () => {

View File

@ -69,6 +69,7 @@
:type="'date'" :type="'date'"
name="endDay" name="endDay"
:modelValue="endDay" :modelValue="endDay"
:min = "today"
@update:modelValue="endDay = $event" @update:modelValue="endDay = $event"
/> />

View File

@ -24,9 +24,10 @@
<button <button
v-if="!data.localVote.LOCVOTDDT" v-if="!data.localVote.LOCVOTDDT"
type="button" type="button"
class="bx btn btn-danger" class="btn btn-label-danger btn-icon"
@click="endBtn(data.localVote.LOCVOTSEQ)" @click="endBtn(data.localVote.LOCVOTSEQ)"
>종료</button> ><i class="bx bx-power-off"></i>
</button>
<DeleteBtn v-if="!data.localVote.LOCVOTDDT" @click="voteDelete(data.localVote.LOCVOTSEQ)" /> <DeleteBtn v-if="!data.localVote.LOCVOTDDT" @click="voteDelete(data.localVote.LOCVOTSEQ)" />
</div> </div>
<p v-if="data.localVote.LOCVOTDDT" class="btn-icon btn-danger rounded-2"><i class="bx bx-power-off"></i></p> <p v-if="data.localVote.LOCVOTDDT" class="btn-icon btn-danger rounded-2"><i class="bx bx-power-off"></i></p>
@ -104,7 +105,6 @@ voteEndDate.setDate(voteEndDate.getDate() + 1);
const isVoteEnded = computed(() => { const isVoteEnded = computed(() => {
return currentDate > voteEndDate; return currentDate > voteEndDate;
}); });
const emit = defineEmits(['addContents','checkedNames','endVoteId','voteEnded','randomList','voteDelete','updateVote']); const emit = defineEmits(['addContents','checkedNames','endVoteId','voteEnded','randomList','voteDelete','updateVote']);
onMounted(() => { onMounted(() => {
if (isVoteEnded.value && !props.data.localVote.LOCVOTDDT) { if (isVoteEnded.value && !props.data.localVote.LOCVOTDDT) {

View File

@ -10,7 +10,15 @@
<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" v-model="title" /> <FormInput
title="제목"
name="title"
:is-essential="true"
:is-alert="titleAlert"
v-model="title"
@update:alert="titleAlert = $event"
@input.once="validateTitle"
/>
<!-- 첨부파일 업로드 --> <!-- 첨부파일 업로드 -->
<FormFile <FormFile
@ -43,7 +51,6 @@
<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-red">*</span>
<div class="invalid-feedback" :class="contentAlert ? 'display-block' : ''">내용을 확인해주세요.</div>
</label> </label>
<div class="col-md-12"> <div class="col-md-12">
<QEditor <QEditor
@ -53,6 +60,7 @@
:initialData="content" :initialData="content"
/> />
</div> </div>
<div v-if="contentAlert" class="invalid-feedback d-block">내용을 확인해주세요.</div>
</div> </div>
<!-- 버튼 --> <!-- 버튼 -->
@ -74,10 +82,15 @@
import QEditor from '@c/editor/QEditor.vue'; import QEditor from '@c/editor/QEditor.vue';
import FormInput from '@c/input/FormInput.vue'; import FormInput from '@c/input/FormInput.vue';
import FormFile from '@c/input/FormFile.vue'; import FormFile from '@c/input/FormFile.vue';
import { ref, onMounted, computed, watch } from 'vue'; import { ref, onMounted, computed, watch, inject } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useToastStore } from '@s/toastStore';
import axios from '@api'; import axios from '@api';
//
const $common = inject('common');
const toastStore = useToastStore();
// //
const title = ref(''); const title = ref('');
const content = ref(''); const content = ref('');
@ -134,51 +147,18 @@
router.push('/board'); router.push('/board');
}; };
// //
const updateBoard = async () => { const checkValidation = () => {
// contentAlert.value = $common.isNotValidContent(content);
if (!title.value) { titleAlert.value = $common.isNotValidInput(title.value);
titleAlert.value = true;
return; if (titleAlert.value || contentAlert.value || !isFileValid.value) {
if (titleAlert.value) {
title.value = '';
} }
titleAlert.value = false; return true;
} else {
if (!content.value) { return false;
contentAlert.value = true;
return;
}
contentAlert.value = false;
try {
//
const boardData = {
LOCBRDTTL: title.value,
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();
Object.entries(boardData).forEach(([key, value]) => {
formData.append(key, value);
});
fileArray.forEach((file, idx) => {
formData.append('files', file);
});
await axios.put(`board/${currentBoardId.value}`, formData, { isFormData: true });
alert('게시물이 수정되었습니다.');
goList();
} catch (error) {
console.error('게시물 수정 중 오류 발생:', error);
alert('게시물 수정에 실패했습니다.');
} }
}; };
@ -219,6 +199,60 @@
}; };
////////////////// fileSection[E] //////////////////// ////////////////// 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;
try {
//
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);
});
await axios.put(`board/${currentBoardId.value}`, formData, { isFormData: true });
toastStore.onToast('게시물이 수정되었습니다.', 's');
goList();
} catch (error) {
console.error('게시물 수정 중 오류 발생:', error);
toastStore.onToast('게시물 수정에 실패했습니다.');
}
};
// //
onMounted(() => { onMounted(() => {
if (currentBoardId.value) { if (currentBoardId.value) {

View File

@ -9,7 +9,7 @@
<DictAlphabetFilter @update:data="handleSelectedAlphabetChange" :indexCategory="indexCategory" :selectedAl="selectedAlphabet" /> <DictAlphabetFilter @update:data="handleSelectedAlphabetChange" :indexCategory="indexCategory" :selectedAl="selectedAlphabet" />
<!-- 카테고리 --> <!-- 카테고리 -->
<div v-if="cateList.length"> <div v-if="cateList.length">
<CategoryBtn :lists="cateList" @update:data="handleSelectedCategoryChange" :showAll="true"/> <CategoryBtn :lists="cateList" @update:data="handleSelectedCategoryChange" :showAll="true" :selectedCategory="selectedCategory" />
</div> </div>
<!-- 작성 --> <!-- 작성 -->
<div v-if="writeStore.isItemActive(999999)" class="mt-5 card p-5"> <div v-if="writeStore.isItemActive(999999)" class="mt-5 card p-5">
@ -205,6 +205,7 @@
} }
getwordList(); getwordList();
getIndex(); getIndex();
selectedCategory.value = 'all';
if(res.data.data == '2'){ if(res.data.data == '2'){
const newCategory = { label: data, value: category }; const newCategory = { label: data, value: category };
cateList.value = [...cateList.value,newCategory]; cateList.value = [...cateList.value,newCategory];