Merge branch 'main' into board-ji
This commit is contained in:
commit
ccc195435c
@ -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 {
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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 = () => {
|
||||||
|
|||||||
@ -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"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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];
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user