Merge branch 'main' of http://192.168.0.251:3000/localnet/localhost-front
All checks were successful
LocalNet_front/pipeline/head This commit looks good
All checks were successful
LocalNet_front/pipeline/head This commit looks good
This commit is contained in:
commit
aa22023ca3
@ -7,7 +7,6 @@
|
||||
/* board */
|
||||
.board-content img {
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
@ -520,6 +519,24 @@
|
||||
|
||||
/* project list */
|
||||
|
||||
.hidden-start-input {
|
||||
position: absolute;
|
||||
top: 103%;
|
||||
left: 20%;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.hidden-end-input {
|
||||
position: absolute;
|
||||
top: 113.3%;
|
||||
left: 20%;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.map {
|
||||
top: -160px;
|
||||
left: -5px;
|
||||
@ -684,6 +701,16 @@
|
||||
|
||||
/* BoardComment end */
|
||||
|
||||
/* vote */
|
||||
.hidden-date-input {
|
||||
position: absolute;
|
||||
top: 31.5%;
|
||||
left: 17%;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 권한부여 */
|
||||
.user-card-container {
|
||||
display: flex;
|
||||
|
||||
@ -1,23 +1,10 @@
|
||||
/* @font-face {
|
||||
font-family: 'GmarketSansMedium';
|
||||
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2001@1.1/GmarketSansMedium.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
body {
|
||||
font-family: 'GmarketSansMedium', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
} */
|
||||
|
||||
/* 1) */
|
||||
/* @font-face {
|
||||
font-family: 'NanumSquareRound';
|
||||
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_two@1.0/NanumSquareRound.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'NanumSquareRound', sans-serif;
|
||||
font-size: 16px;
|
||||
@ -25,13 +12,13 @@ body {
|
||||
color: #333;
|
||||
} */
|
||||
|
||||
/* 2) */
|
||||
/* @font-face {
|
||||
font-family: 'Pretendard-Regular';
|
||||
src: url('https://cdn.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff') format('woff');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Pretendard-Regular', sans-serif;
|
||||
font-size: 16px;
|
||||
@ -39,20 +26,117 @@ body {
|
||||
color: #333;
|
||||
} */
|
||||
|
||||
/* 3) */
|
||||
/* @font-face {
|
||||
font-family: 'S-CoreDream-3Light';
|
||||
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_six@1.2/S-CoreDream-3Light.woff') format('woff');
|
||||
font-family: 'NEXON Lv1 Gothic OTF';
|
||||
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@2.1/NEXON Lv1 Gothic OTF.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'S-CoreDream-3Light', sans-serif;
|
||||
font-family: 'NEXON Lv1 Gothic OTF', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
} */
|
||||
|
||||
/* 4) */
|
||||
/* @font-face {
|
||||
font-family: 'SUITE-Regular';
|
||||
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_2304-2@1.0/SUITE-Regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
body {
|
||||
font-family: 'SUITE-Regular', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
} */
|
||||
|
||||
/* 5) */
|
||||
/* @font-face {
|
||||
font-family: 'GoyangIlsan';
|
||||
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_one@1.0/GoyangIlsan.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
body {
|
||||
font-family: 'GoyangIlsan', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
} */
|
||||
|
||||
/* 6) */
|
||||
/* @font-face {
|
||||
font-family: 'GowunDodum-Regular';
|
||||
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_2108@1.1/GowunDodum-Regular.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
body {
|
||||
font-family: 'GowunDodum-Regular', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
} */
|
||||
|
||||
/* 7) */
|
||||
/* @font-face {
|
||||
font-family: 'EASTARJET-Medium';
|
||||
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_231029@1.1/EASTARJET-Medium.woff2') format('woff2');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
body {
|
||||
font-family: 'EASTARJET-Medium', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
} */
|
||||
|
||||
/* 8) */
|
||||
/* @font-face {
|
||||
font-family: 'HakgyoansimChulseokbuTTF-B';
|
||||
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/2408-5@1.0/HakgyoansimChulseokbuTTF-B.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
body {
|
||||
font-family: 'HakgyoansimChulseokbuTTF-B', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
} */
|
||||
|
||||
/* 9) */
|
||||
/* @font-face {
|
||||
font-family: 'GongGothicMedium';
|
||||
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_20-10@1.0/GongGothicMedium.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
body {
|
||||
font-family: 'GongGothicMedium', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
} */
|
||||
|
||||
|
||||
/* @font-face {
|
||||
font-family: 'MangoDdobak-B';
|
||||
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/2405-3@1.1/MangoDdobak-B.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
}
|
||||
body {
|
||||
font-family: 'MangoDdobak-B', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
} */
|
||||
|
||||
/* 나눔고딕 */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Nanum+Gothic&display=swap');
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
:unknown="comment.author === '익명'"
|
||||
:isCommentAuthor="isCommentAuthor"
|
||||
:boardId="comment.boardId"
|
||||
:profileName="comment.nickname ? comment.nickname : comment.author"
|
||||
:profileName="displayName"
|
||||
:date="comment.createdAt"
|
||||
:comment="comment"
|
||||
:profileImg="comment.profileImg"
|
||||
@ -76,10 +76,15 @@
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
nickname: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isCommentAuthor: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
isPlusButton: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
@ -113,6 +118,10 @@
|
||||
editCommentAlert: String,
|
||||
});
|
||||
|
||||
const displayName = computed(() => {
|
||||
return props.nickname? props.nickname: props.comment.author;
|
||||
});
|
||||
|
||||
// emits 정의
|
||||
const emit = defineEmits([
|
||||
'submitComment',
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
:isCommentAuthor="comment.isCommentAuthor"
|
||||
:isEditTextarea="comment.isEditTextarea"
|
||||
:isDeleted="isDeleted"
|
||||
:nickname="comment.nickname"
|
||||
:isCommentPassword="isCommentPassword"
|
||||
:passwordCommentAlert="passwordCommentAlert || ''"
|
||||
:currentPasswordCommentId="currentPasswordCommentId"
|
||||
@ -32,6 +33,7 @@
|
||||
:isPlusButton="false"
|
||||
:isLike="true"
|
||||
:isCommentProfile="true"
|
||||
:nickname="child.nickname"
|
||||
:isCommentAuthor="child.isCommentAuthor"
|
||||
:isCommentPassword="isCommentPassword"
|
||||
:currentPasswordCommentId="currentPasswordCommentId"
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
</div>
|
||||
|
||||
<div class="me-2">
|
||||
<h6 class="mb-0">{{ profileName }}</h6>
|
||||
<h6 class="mb-0">{{ profileName ? profileName : nickname }}</h6>
|
||||
<div class="profile-detail">
|
||||
<span>{{ date }}</span>
|
||||
<template v-if="showDetail">
|
||||
@ -62,6 +62,10 @@
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
nickname: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
unknown: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<button class="vac-btn vac-btn-warning rounded-circle d-flex align-items-center justify-content-center"
|
||||
:class="{ active: halfDayType === 'AM' }"
|
||||
@click="toggleHalfDay('AM')">
|
||||
<i class="bi bi-sun"></i>
|
||||
<i class="bi bi-sun d-flex"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -16,7 +16,7 @@
|
||||
<button class="vac-btn vac-btn-info rounded-circle d-flex align-items-center justify-content-center"
|
||||
:class="{ active: halfDayType === 'PM' }"
|
||||
@click="toggleHalfDay('PM')">
|
||||
<i class="bi bi-moon"></i>
|
||||
<i class="bi bi-moon d-flex"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -26,7 +26,7 @@
|
||||
<button class="vac-btn vac-btn-primary rounded-circle d-flex align-items-center justify-content-center"
|
||||
:class="{ active: halfDayType === 'FULL' }"
|
||||
@click="toggleHalfDay('FULL')">
|
||||
<i class="bi bi-calendar"></i>
|
||||
<i class="bi bi-calendar d-flex"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -71,7 +71,8 @@
|
||||
const editor = ref(null); // 에디터 DOM 참조
|
||||
const font = ref('nanum-gothic'); // 기본 폰트
|
||||
const fontSize = ref('16px'); // 기본 폰트 크기
|
||||
const emit = defineEmits(['update:data']);
|
||||
const emit = defineEmits(['update:data', 'update:uploadedImgList']);
|
||||
const uploadedImgList = ref([]); // 에디터에 이미지 첨부시 업데이트 된 파일 인덱스 번호 리스트
|
||||
|
||||
onMounted(() => {
|
||||
// 툴바에서 선택할 수 있는 폰트 목록 설정
|
||||
@ -112,6 +113,13 @@
|
||||
quillInstance.format('size', fontSize.value);
|
||||
});
|
||||
|
||||
// 이미지 첨부 리스트가 변경 되었을때
|
||||
watch(uploadedImgList, () => {
|
||||
console.log(!23);
|
||||
emit('update:uploadedImgList', uploadedImgList.value);
|
||||
console.log('uploadedImgList.value: ', uploadedImgList.value);
|
||||
});
|
||||
|
||||
// 초기 데이터가 있을 경우, HTML 형식으로 삽입
|
||||
if (props.initialData) {
|
||||
quillInstance.setContents(JSON.parse(props.initialData));
|
||||
@ -150,7 +158,15 @@
|
||||
|
||||
// 이미지 서버에 업로드 후 URL 받기
|
||||
uploadImageToServer(formData)
|
||||
.then(serverImageUrl => {
|
||||
.then(data => {
|
||||
const uploadImgIdx = data?.fileIndex; // 업로드 된 파일 DB 인덱스
|
||||
const serverImageUrl = data?.fileUrl; // 업로드 된 파일 url
|
||||
|
||||
// 업로드 된 파일 인덱스 (게시글 저장 시 해당 인덱스 번호에 게시글 인덱스를 업데이트)
|
||||
if (uploadImgIdx) {
|
||||
uploadedImgList.value = [...uploadedImgList.value, uploadImgIdx];
|
||||
}
|
||||
|
||||
const baseUrl = $api.defaults.baseURL.replace(/api\/$/, '');
|
||||
const fullImageUrl = `${baseUrl}${serverImageUrl.replace(/\\/g, '/')}`;
|
||||
|
||||
@ -201,7 +217,7 @@
|
||||
// Throw the error so the caller knows something went wrong
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 삭제된 이미지 확인
|
||||
function checkForDeletedImages() {
|
||||
@ -216,4 +232,3 @@
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@ -19,6 +19,9 @@
|
||||
@focusout="$emit('focusout', modelValue)"
|
||||
@input="handleInput"
|
||||
/>
|
||||
<div v-if="isBtn" class="ms-2">
|
||||
<slot name="append"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="invalid-feedback" :class="isAlert ? 'd-block' : ''">{{ title }}을 확인해주세요.</div>
|
||||
<div class="invalid-feedback" :class="isCateAlert ? 'd-block' : ''">카테고리 중복입니다.</div>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<span :class="isEssential ? 'link-danger' : 'd-none'">*</span>
|
||||
</label>
|
||||
<div class="col-md-12">
|
||||
<div v-if="useInputGroup" class="input-group mb-3">
|
||||
<div v-if="useInputGroup" class="input-group mb-1">
|
||||
<input
|
||||
:id="name"
|
||||
class="form-control"
|
||||
@ -14,7 +14,6 @@
|
||||
:value="computedValue"
|
||||
:disabled="disabled"
|
||||
:maxLength="maxlength"
|
||||
:minLength="minlength"
|
||||
:placeholder="title"
|
||||
@blur="$emit('blur')"
|
||||
/>
|
||||
@ -30,7 +29,6 @@
|
||||
:value="computedValue"
|
||||
:disabled="disabled"
|
||||
:maxLength="maxlength"
|
||||
:minLength="minlength"
|
||||
:placeholder="title"
|
||||
@blur="$emit('blur')"
|
||||
/>
|
||||
|
||||
@ -130,6 +130,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 시작일 -->
|
||||
<div class="date-picker-wrapper" @click="focusStartDateInput">
|
||||
<FormInput
|
||||
title="시작일"
|
||||
type="date"
|
||||
@ -138,7 +140,11 @@
|
||||
:modelValue="selectedProject.PROJCTSTR"
|
||||
@update:modelValue="selectedProject.PROJCTSTR = $event"
|
||||
/>
|
||||
<input ref="startDateInput" type="date" v-model="selectedProject.PROJCTSTR" class="hidden-start-input">
|
||||
</div>
|
||||
|
||||
<!-- 종료일 -->
|
||||
<div class="date-picker-wrapper" @click="focusEndDateInput">
|
||||
<FormInput
|
||||
title="종료일"
|
||||
type="date"
|
||||
@ -147,6 +153,8 @@
|
||||
:modelValue="selectedProject.PROJCTEND"
|
||||
@update:modelValue="selectedProject.PROJCTEND = $event"
|
||||
/>
|
||||
<input ref="endDateInput" type="date" v-model="selectedProject.PROJCTEND" class="hidden-end-input">
|
||||
</div>
|
||||
|
||||
<FormInput
|
||||
title="설명"
|
||||
@ -199,6 +207,31 @@ const toastStore = useToastStore();
|
||||
const userStore = useUserInfoStore();
|
||||
const projectStore = useProjectStore();
|
||||
|
||||
const startDateInput = ref(null);
|
||||
const endDateInput = ref(null);
|
||||
|
||||
const focusStartDateInput = () => {
|
||||
nextTick(() => {
|
||||
if (startDateInput.value) {
|
||||
startDateInput.value.focus();
|
||||
setTimeout(() => {
|
||||
startDateInput.value.showPicker?.();
|
||||
}, 50);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const focusEndDateInput = () => {
|
||||
nextTick(() => {
|
||||
if (endDateInput.value) {
|
||||
endDateInput.value.focus();
|
||||
setTimeout(() => {
|
||||
endDateInput.value.showPicker?.();
|
||||
}, 50);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Props 정의
|
||||
const props = defineProps({
|
||||
title: {
|
||||
@ -548,3 +581,13 @@ onMounted(async () => {
|
||||
|
||||
|
||||
</script>
|
||||
<style>
|
||||
.hidden-date-input {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -69,6 +69,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="date-picker-wrapper" @click="focusStartDateInput">
|
||||
<FormInput
|
||||
title="시작 일"
|
||||
name="startDay"
|
||||
@ -77,15 +78,20 @@
|
||||
:modelValue="startDay"
|
||||
v-model="startDay"
|
||||
/>
|
||||
<input ref="startDateInput" type="date" v-model="startDay" class="hidden-start-input">
|
||||
</div>
|
||||
|
||||
<div class="date-picker-wrapper" @click="focusEndDateInput">
|
||||
<FormInput
|
||||
title="종료 일"
|
||||
:type="'date'"
|
||||
name="endDay"
|
||||
:type="'date'"
|
||||
:modelValue="endDay"
|
||||
:min = "startDay"
|
||||
:min="startDay"
|
||||
@update:modelValue="endDay = $event"
|
||||
/>
|
||||
<input ref="endDateInput" type="date" v-model="endDay" class="hidden-end-input">
|
||||
</div>
|
||||
|
||||
<FormInput
|
||||
title="설명"
|
||||
@ -161,6 +167,31 @@
|
||||
const nameAlert = ref(false);
|
||||
const addressAlert = ref(false);
|
||||
|
||||
const startDateInput = ref(null);
|
||||
const endDateInput = ref(null);
|
||||
|
||||
const focusStartDateInput = () => {
|
||||
nextTick(() => {
|
||||
if (startDateInput.value) {
|
||||
startDateInput.value.focus();
|
||||
setTimeout(() => {
|
||||
startDateInput.value.showPicker?.();
|
||||
}, 50);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const focusEndDateInput = () => {
|
||||
nextTick(() => {
|
||||
if (endDateInput.value) {
|
||||
endDateInput.value.focus();
|
||||
setTimeout(() => {
|
||||
endDateInput.value.showPicker?.();
|
||||
}, 50);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const addressData = ref({
|
||||
postcode: '',
|
||||
address: '',
|
||||
|
||||
@ -28,7 +28,6 @@
|
||||
</button>
|
||||
<DeleteBtn v-if="!data.localVote.LOCVOTDDT" @click="voteDelete(data.localVote.LOCVOTSEQ)" />
|
||||
</div>
|
||||
<p v-if="data.localVote.LOCVOTDDT" class="btn-icon btn-danger rounded-2 pe-none"><i class="bx bx-power-off"></i></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -53,7 +52,7 @@
|
||||
<small v-if="yesVotetotal != 0 && !data.localVote.LOCVOTDDT">투표 완료 : 종료시 투표 결과가 나타납니다.</small>
|
||||
<!-- 투표 결과 -->
|
||||
<div v-if="data.localVote.LOCVOTDDT" class="mt-3">
|
||||
<vote-result-list :data="topVoters" @randomList="randomList" :randomResultNum="data.localVote.LOCVOTRES" :locvotreg="data.localVote.LOCVOTREG"/>
|
||||
<vote-result-list :data="topVoters" @randomList="randomList" :randomResultNum="data.localVote.LOCVOTRES" :locvotreg="data.localVote.LOCVOTREG" />
|
||||
</div>
|
||||
<!-- 투표완/미완 인원 -->
|
||||
<vote-user-list
|
||||
@ -68,7 +67,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import EditBtn from '@c/button/EditBtn.vue';
|
||||
import DeleteBtn from '@c/button/DeleteBtn.vue';
|
||||
import voteUserList from '@c/voteboard/voteUserList.vue';
|
||||
@ -105,9 +104,10 @@ const topVoters = computed(() => {
|
||||
|
||||
const baseUrl = $api.defaults.baseURL.replace(/api\/$/, '');
|
||||
const userStore = useUserInfoStore();
|
||||
const currentDate = new Date();
|
||||
const voteEndDate = new Date(props.data.localVote.formatted_LOCVOTEDT.replace(' ', 'T'));
|
||||
voteEndDate.setDate(voteEndDate.getDate() + 1);
|
||||
const offset = new Date().getTimezoneOffset() * 60000
|
||||
const today = new Date(Date.now() - offset);
|
||||
const currentDate = today.toISOString().substring(0,16);
|
||||
const voteEndDate = props.data.localVote.LOCVOTEDT.substring(0,16);
|
||||
// 종료 여부 계산
|
||||
const isVoteEnded = computed(() => {
|
||||
return currentDate > voteEndDate;
|
||||
@ -117,7 +117,19 @@ onMounted(() => {
|
||||
if (isVoteEnded.value && !props.data.localVote.LOCVOTDDT) {
|
||||
emit('voteEnded', { id: props.data.localVote.LOCVOTSEQ });
|
||||
}
|
||||
checkVoteCompletion();
|
||||
});
|
||||
// 데이터 변경 감지
|
||||
watch(() => props.data.localVote.total_voted, () => {
|
||||
checkVoteCompletion();
|
||||
});
|
||||
|
||||
// 종료 체크 함수
|
||||
const checkVoteCompletion = () => {
|
||||
if (props.data.localVote.total_votable === props.data.localVote.total_voted && props.data.localVote.LOCVOTDDT == '') {
|
||||
emit('voteEnded', { id: props.data.localVote.LOCVOTSEQ });
|
||||
}
|
||||
};
|
||||
const addContents = (itemList, voteId) =>{
|
||||
emit('addContents',itemList,voteId)
|
||||
}
|
||||
@ -125,7 +137,6 @@ const checkedNames = (numList) =>{
|
||||
emit('checkedNames',numList);
|
||||
}
|
||||
const endBtn = (voteid) =>{
|
||||
voteEndDate.setTime(currentDate.getTime()); // 현재 날짜로 설정
|
||||
emit('endVoteId',voteid);
|
||||
}
|
||||
const voteDelete = (voteid) =>{
|
||||
|
||||
@ -17,16 +17,23 @@
|
||||
:is-alert="contentAlerts[index]"
|
||||
v-model="item.content"
|
||||
:is-btn="true"
|
||||
@keyup="ValidHandler('content' + (index + 1))"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<delete-btn @click="removeItem(index)" />
|
||||
</template>
|
||||
</form-input>
|
||||
<link-input v-model="item.url" class="mb-1"/>
|
||||
<form-input
|
||||
:title="'URL ' + (index + data.length + 1)"
|
||||
:name="'url' + index"
|
||||
v-model="item.url"
|
||||
:is-essential="false"
|
||||
class="mb-1"
|
||||
/>
|
||||
</div>
|
||||
<div class="d-flex justify-content align-items-center mt-3">
|
||||
<plus-btn @click="addItem" :disabled="total >= 10" />
|
||||
<button class="btn btn-primary btn-icon m-1" @click="addContentSave(item.LOCVOTSEQ)" :disabled="isSaveDisabled">
|
||||
<plus-btn @click="addItem" :disabled=" total >= 10" />
|
||||
<button class="btn btn-primary btn-icon m-1" @click="addContentSave(item.LOCVOTSEQ ,index)" :disabled="isSaveDisabled">
|
||||
<i class="bx bx-check"></i>
|
||||
</button>
|
||||
</div>
|
||||
@ -46,20 +53,19 @@ import SaveBtn from '@c/button/SaveBtn.vue'
|
||||
import FormInput from '@c/input/FormInput.vue';
|
||||
import voteCardCheckList from '@c/voteboard/voteCardCheckList.vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import LinkInput from "@/components/voteboard/voteLinkInput.vue";
|
||||
import { voteCommon } from '@s/voteCommon';
|
||||
import DeleteBtn from "@c/button/DeleteBtn.vue";
|
||||
import { useToastStore } from '@s/toastStore';
|
||||
import router from '@/router';
|
||||
const toastStore = useToastStore();
|
||||
const contentAlerts = ref(false);
|
||||
const contentAlerts = ref([false, false]);
|
||||
const titleAlert = ref(false);
|
||||
const title = ref('');
|
||||
const rink = ref('');
|
||||
const { itemList, addItem, removeItem } = voteCommon(true);
|
||||
const total = computed(() => props.total + itemList.value.length);
|
||||
const isSaveDisabled = computed(() => {
|
||||
return itemList.value.length === 0 || itemList.value.every(item => !item.content.trim());
|
||||
return itemList.value.length === 0 || itemList.value.every(item => !item.content.trim() && !item.url.trim());
|
||||
});
|
||||
const props = defineProps({
|
||||
data: {
|
||||
@ -77,12 +83,35 @@ const props = defineProps({
|
||||
});
|
||||
const emit = defineEmits(['addContents','checkedNames']);
|
||||
//항목추가
|
||||
const addContentSave = (voteId) =>{
|
||||
const addContentSave = (voteId,index) =>{
|
||||
let valid = true;
|
||||
const filteredItemList = itemList.value.filter(item => item.content && item.content.trim() !== '');
|
||||
|
||||
itemList.value.forEach((item, index) => {
|
||||
if (!item.content.trim() && item.url.trim()) {
|
||||
contentAlerts.value[index] = true;
|
||||
valid = false;
|
||||
} else {
|
||||
contentAlerts.value[index] = false;
|
||||
}
|
||||
});
|
||||
|
||||
if(valid){
|
||||
emit('addContents',filteredItemList,voteId);
|
||||
itemList.value = [{ content: "", url: "" }];
|
||||
removeItem();
|
||||
}
|
||||
}
|
||||
|
||||
const ValidHandler = (field) => {
|
||||
if (field.startsWith('content')) {
|
||||
const index = parseInt(field.replace('content', '')) - 1;
|
||||
if (!isNaN(index)) {
|
||||
contentAlerts.value[index] = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const checkedNames = ref([]); // 선택된 값 저장
|
||||
const updateCheckedNames = (newValues) => {
|
||||
checkedNames.value = newValues;
|
||||
|
||||
@ -57,6 +57,7 @@
|
||||
v-if="contentLoaded"
|
||||
@update:data="content = $event"
|
||||
@update:imageUrls="imageUrls = $event"
|
||||
@update:uploadedImgList="handleUpdateEditorImg"
|
||||
:initialData="content"
|
||||
/>
|
||||
</div>
|
||||
@ -115,7 +116,7 @@
|
||||
const attachFilesAlert = ref(false);
|
||||
const isFileValid = ref(true);
|
||||
const delFileIdx = ref([]); // 제외할 기존 첨부파일 ID
|
||||
const additionalFiles = ref([]); // 새로 추가할 첨부파일
|
||||
const editorUploadedImgList = ref([]);
|
||||
|
||||
// 게시물 데이터 로드
|
||||
const fetchBoardDetails = async () => {
|
||||
@ -145,6 +146,10 @@
|
||||
contentLoaded.value = true;
|
||||
};
|
||||
|
||||
const handleUpdateEditorImg = item => {
|
||||
editorUploadedImgList.value = item;
|
||||
};
|
||||
|
||||
// 기존 첨부파일명을 노출
|
||||
const addDisplayFileName = fileInfos =>
|
||||
fileInfos.map(file => ({
|
||||
@ -247,6 +252,11 @@
|
||||
boardData.delFileIdx = [...delFileIdx.value];
|
||||
}
|
||||
|
||||
// 에디터에 업로드 된 이미지 인덱스 목록
|
||||
if (editorUploadedImgList.value && editorUploadedImgList.value.length > 0) {
|
||||
boardData.editorUploadedImgList = [...editorUploadedImgList.value];
|
||||
}
|
||||
|
||||
const fileArray = newFileFilter(attachFiles);
|
||||
const formData = new FormData();
|
||||
|
||||
|
||||
@ -101,7 +101,7 @@
|
||||
<span v-if="isNewPost(post.rawDate)" class="box-new badge text-white ms-2 fs-tiny">N</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">{{ post.author }}</td>
|
||||
<td class="text-center">{{ post.nickname ? post.nickname : post.author }}</td>
|
||||
<td class="text-center">{{ post.date }}</td>
|
||||
<td class="text-center">{{ post.views }}</td>
|
||||
</tr>
|
||||
@ -219,6 +219,7 @@
|
||||
date: formatDate(post.date), // 날짜 변환 적용
|
||||
views: post.cnt || 0,
|
||||
hasAttachment: post.hasAttachment,
|
||||
nickname: post.nickname || null,
|
||||
img: post.firstImageUrl || null,
|
||||
commentCount: post.commentCount,
|
||||
}));
|
||||
|
||||
@ -8,10 +8,11 @@
|
||||
<div class="pb-5 border-bottom">
|
||||
<BoardProfile
|
||||
:boardId="currentBoardId"
|
||||
:profileName="profileName"
|
||||
:profileName="displayName"
|
||||
:unknown="unknown"
|
||||
:profileImg="profileImg"
|
||||
:views="views"
|
||||
:nickname="nickname"
|
||||
:commentNum="commentNum"
|
||||
:date="formattedBoardDate"
|
||||
:isLike="false"
|
||||
@ -151,6 +152,7 @@
|
||||
const profileName = ref('');
|
||||
const boardTitle = ref('제목 없음');
|
||||
const boardContent = ref('');
|
||||
const nickname = ref('');
|
||||
const date = ref('');
|
||||
const views = ref(0);
|
||||
const likes = ref(0);
|
||||
@ -174,6 +176,10 @@
|
||||
const authorId = ref(''); // 작성자 id
|
||||
const editCommentAlert = ref({}); //댓글, 대댓글 오류 메세지 객체
|
||||
|
||||
const displayName = computed(() => {
|
||||
return nickname.value && unknown.value ? nickname.value : profileName.value;
|
||||
});
|
||||
|
||||
const isAuthor = computed(() => currentUserId.value === authorId.value);
|
||||
const commentsWithAuthStatus = computed(() => {
|
||||
const updatedComments = comments.value.map(comment => ({
|
||||
@ -257,6 +263,7 @@
|
||||
boardContent.value = boardData.content || '';
|
||||
profileImg.value = boardData.profileImg || '';
|
||||
date.value = boardData.date || '';
|
||||
nickname.value = boardData.nickname || '';
|
||||
views.value = boardData.cnt || 0;
|
||||
likes.value = boardData.likeCount || 0;
|
||||
dislikes.value = boardData.dislikeCount || 0;
|
||||
@ -322,7 +329,7 @@
|
||||
likeCount: comment.likeCount || 0,
|
||||
dislikeCount: comment.dislikeCount || 0,
|
||||
profileImg: comment.profileImg || '',
|
||||
nickname: comment.LOCCMTNIC,
|
||||
nickname: comment.LOCCMTNIC || '',
|
||||
likeClicked: comment.likeClicked || false,
|
||||
dislikeClicked: comment.dislikeClicked || false,
|
||||
createdAtRaw: comment.LOCCMTRDT, // 작성일
|
||||
@ -352,7 +359,7 @@
|
||||
parentId: reply.LOCCMTPNT, // 부모 댓글 ID
|
||||
content: reply.LOCCMTRPY || '내용 없음',
|
||||
createdAtRaw: reply.LOCCMTRDT,
|
||||
nickname: reply.LOCCMTNIC,
|
||||
nickname: reply.LOCCMTNIC || '',
|
||||
// createdAt: formattedDate(reply.LOCCMTRDT),
|
||||
//createdAtRaw: new Date(reply.LOCCMTUDT),
|
||||
createdAt: formattedDate(reply.LOCCMTUDT) + (reply.LOCCMTUDT !== reply.LOCCMTRDT ? ' (수정됨)' : ''),
|
||||
@ -435,7 +442,7 @@
|
||||
LOCCMTRPY: reply.comment,
|
||||
LOCCMTPWD: reply.password || null,
|
||||
LOCCMTPNT: reply.parentId,
|
||||
LOCCMTNIC: data.isCheck ? data.nickname : null,
|
||||
LOCCMTNIC: reply.isCheck ? reply.nickname : null,
|
||||
LOCBRDTYP: reply.isCheck ? '300102' : null,
|
||||
});
|
||||
|
||||
|
||||
@ -42,6 +42,15 @@
|
||||
|
||||
<!-- 비밀번호 필드 (익명게시판 선택 시 활성화) -->
|
||||
<div v-if="categoryValue === 300102" class="mb-4">
|
||||
<FormInput
|
||||
title="닉네임"
|
||||
name="nickname"
|
||||
:is-essential="true"
|
||||
:is-alert="nicknameAlert"
|
||||
v-model="nickname"
|
||||
@update:alert="nicknameAlert = $event"
|
||||
@input="validateNickname"
|
||||
/>
|
||||
<FormInput
|
||||
title="비밀번호"
|
||||
name="pw"
|
||||
@ -83,7 +92,7 @@
|
||||
<div class="mb-4">
|
||||
<label class="col-md-2 col-form-label"> 내용 <span class="text-danger">*</span> </label>
|
||||
<div class="col-md-12">
|
||||
<QEditor @update:data="content = $event" />
|
||||
<QEditor @update:data="content = $event" @update:uploadedImgList="handleUpdateEditorImg" />
|
||||
</div>
|
||||
<div class="invalid-feedback mt-1" :class="contentAlert ? 'd-block' : 'd-none'">내용을 입력해주세요.</div>
|
||||
</div>
|
||||
@ -112,12 +121,14 @@
|
||||
const toastStore = useToastStore();
|
||||
const categoryList = ref([]);
|
||||
const title = ref('');
|
||||
const nickname = ref("");
|
||||
const password = ref('');
|
||||
const categoryValue = ref(null);
|
||||
const content = ref({ ops: [] });
|
||||
const isFileValid = ref(true);
|
||||
|
||||
const titleAlert = ref(false);
|
||||
const nicknameAlert = ref(false);
|
||||
const passwordAlert = ref(false);
|
||||
const contentAlert = ref(false);
|
||||
const categoryAlert = ref(false);
|
||||
@ -127,6 +138,7 @@
|
||||
const maxFiles = 5;
|
||||
const maxSize = 10 * 1024 * 1024;
|
||||
const fileError = ref('');
|
||||
const editorUploadedImgList = ref([]);
|
||||
|
||||
const fetchCategories = async () => {
|
||||
const response = await axios.get('board/categories');
|
||||
@ -143,6 +155,10 @@
|
||||
|
||||
const fileCount = computed(() => attachFiles.value.length);
|
||||
|
||||
const handleUpdateEditorImg = item => {
|
||||
editorUploadedImgList.value = item;
|
||||
};
|
||||
|
||||
const handleFileUpload = files => {
|
||||
const validFiles = files.filter(file => file.size <= maxSize);
|
||||
if (files.some(file => file.size > maxSize)) {
|
||||
@ -172,6 +188,14 @@
|
||||
titleAlert.value = title.value.trim().length === 0;
|
||||
};
|
||||
|
||||
const validateNickname = () => {
|
||||
if (categoryValue.value === 300102) {
|
||||
nicknameAlert.value = nickname.value.trim().length === 0;
|
||||
} else {
|
||||
nicknameAlert.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const validatePassword = () => {
|
||||
if (categoryValue.value === 300102) {
|
||||
password.value = password.value.replace(/\s/g, ''); // 공백 제거
|
||||
@ -199,11 +223,12 @@
|
||||
/** 글쓰기 */
|
||||
const write = async () => {
|
||||
validateTitle();
|
||||
validateNickname();
|
||||
validatePassword();
|
||||
validateContent();
|
||||
categoryAlert.value = categoryValue.value == null;
|
||||
|
||||
if (titleAlert.value || passwordAlert.value || contentAlert.value || categoryAlert.value || !isFileValid.value) {
|
||||
if (titleAlert.value || nicknameAlert.value || passwordAlert.value || contentAlert.value || categoryAlert.value || !isFileValid.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -211,10 +236,16 @@
|
||||
const boardData = {
|
||||
LOCBRDTTL: title.value,
|
||||
LOCBRDCON: JSON.stringify(content.value), // Delta 포맷을 JSON으로 변환
|
||||
LOCBRDNIC: categoryValue.value === 300102 ? nickname.value : null,
|
||||
LOCBRDPWD: categoryValue.value === 300102 ? password.value : null,
|
||||
LOCBRDTYP: categoryValue.value,
|
||||
};
|
||||
|
||||
// 에디터에 업로드 된 이미지 인덱스 목록
|
||||
if (editorUploadedImgList.value && editorUploadedImgList.value.length > 0) {
|
||||
boardData.editorUploadedImgList = [...editorUploadedImgList.value];
|
||||
}
|
||||
|
||||
const { data: boardResponse } = await axios.post('board', boardData);
|
||||
const boardId = boardResponse.data;
|
||||
// 첨부파일 업로드 (비동기 병렬 처리)
|
||||
|
||||
@ -180,7 +180,6 @@ function handleDateClick(info) {
|
||||
|
||||
// 이미 활성화된 날짜를 한 번 더 클릭하면 비활성화
|
||||
if (currentValue && currentValue !== "delete") {
|
||||
console.log("🛑 활성화된 날짜 비활성화:", clickedDateStr);
|
||||
selectedDates.value.delete(clickedDateStr);
|
||||
updateCalendarEvents();
|
||||
return;
|
||||
@ -203,7 +202,6 @@ function handleDateClick(info) {
|
||||
|
||||
// 버튼을 눌렀을 때 - 기존 휴가 삭제 후 새로운 값 추가
|
||||
if (isMyVacation) {
|
||||
console.log("🗑 기존 휴가 삭제 후 새로운 상태 추가:", clickedDateStr);
|
||||
selectedDates.value.set(clickedDateStr, "delete");
|
||||
}
|
||||
|
||||
@ -647,7 +645,7 @@ onMounted(async () => {
|
||||
const dpEl = calendarDatepicker.value;
|
||||
dpEl.style.display = 'block';
|
||||
dpEl.style.position = 'fixed';
|
||||
dpEl.style.top = '25%';
|
||||
dpEl.style.top = '18%';
|
||||
dpEl.style.left = '50%';
|
||||
dpEl.style.transform = 'translate(-50%, -50%)';
|
||||
dpEl.style.zIndex = '9999';
|
||||
|
||||
@ -132,17 +132,18 @@ const checkedNames = (numList) => {
|
||||
}
|
||||
//투표종료
|
||||
const endVoteId = (endVoteId) => {
|
||||
console.log('endVoteId',endVoteId)
|
||||
$api.patch('vote/updateEndData',{
|
||||
endVoteId :endVoteId
|
||||
}).then((res)=>{
|
||||
if(res.data.status === 'OK'){
|
||||
toastStore.onToast('투표가 종료되었습니다.', 's');
|
||||
getvoteList();
|
||||
}
|
||||
})
|
||||
}
|
||||
//기한 지난 투표 종료
|
||||
const voteEnded = async (id) =>{
|
||||
console.log('voteEnded',id)
|
||||
await endVoteId(id.id);
|
||||
}
|
||||
//투표 삭제
|
||||
|
||||
@ -20,16 +20,22 @@
|
||||
v-model="title"
|
||||
@keyup="ValidHandler('title')"
|
||||
/>
|
||||
<div class="date-picker-container" @click="focusDateInput">
|
||||
<form-input
|
||||
title="종료날짜"
|
||||
name="endDate"
|
||||
type="date"
|
||||
type="datetime-local"
|
||||
:is-essential="true"
|
||||
:is-alert="endDateAlert"
|
||||
v-model="endDate"
|
||||
:min="today"
|
||||
@change="ValidHandlerendDate"
|
||||
:min="minDate"
|
||||
@input="ValidHandlerendDate"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 숨겨진 input 태그를 사용하여 강제로 포커스 -->
|
||||
<input ref="dateInput" type="datetime-local" v-model="endDate" class="hidden-date-input">
|
||||
|
||||
<!-- 항목 입력 반복 -->
|
||||
<div v-for="(item, index) in itemList" :key="index">
|
||||
<form-input
|
||||
@ -45,8 +51,14 @@
|
||||
<delete-btn @click="removeItem(index)" :disabled="index < 2" />
|
||||
</template>
|
||||
</form-input>
|
||||
|
||||
<link-input v-model="item.url" class="mb-1"/>
|
||||
<form-input
|
||||
:title="'URL ' + (index + 1)"
|
||||
:name="'url' + index"
|
||||
v-model="item.url"
|
||||
:is-essential="false"
|
||||
class="mb-1"
|
||||
/>
|
||||
<!-- <link-input v-model="item.url" class="mb-1"/> -->
|
||||
</div>
|
||||
|
||||
<plus-btn @click="addItem" :disabled="itemList.length >= 10" class="mb-3" />
|
||||
@ -90,7 +102,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, toRaw } from "vue";
|
||||
import { nextTick, onMounted, ref, toRaw } from "vue";
|
||||
import UserList from "@c/user/UserList.vue";
|
||||
import formInput from "@c/input/FormInput.vue";
|
||||
import { useToastStore } from "@s/toastStore";
|
||||
@ -98,13 +110,15 @@ import PlusBtn from "@c/button/PlusBtn.vue";
|
||||
import DeleteBtn from "@c/button/DeleteBtn.vue";
|
||||
import $api from "@api";
|
||||
import router from "@/router";
|
||||
import LinkInput from "@/components/voteboard/voteLinkInput.vue";
|
||||
import { voteCommon } from '@s/voteCommon';
|
||||
import { useUserStore } from '@s/userList';
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const today = new Date().toISOString().substring(0, 10);
|
||||
// const offset = new Date().getTimezoneOffset() * 60000
|
||||
// const today = new Date(Date.now() - offset);
|
||||
// today.setDate(today.getDate() + 1);
|
||||
// const minDate = today.toISOString().substring(0, 16);
|
||||
const toastStore = useToastStore();
|
||||
const activeUserList = ref([]);
|
||||
const disabledUsers = ref([]);
|
||||
@ -119,12 +133,29 @@ const userListTotal = ref(0);
|
||||
const addvoteitem = ref(false);
|
||||
const addvotemulti= ref(false);
|
||||
|
||||
const dateInput = ref(null);
|
||||
|
||||
const focusDateInput = () => {
|
||||
if (dateInput.value) {
|
||||
dateInput.value.showPicker(); // 달력 자동 열기 (일부 브라우저에서 지원)
|
||||
dateInput.value.focus(); // 포커스 이동
|
||||
}
|
||||
};
|
||||
|
||||
const minDate = ref('');
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
const offset = new Date().getTimezoneOffset() * 60000;
|
||||
const today = new Date(Date.now() - offset);
|
||||
today.setDate(today.getDate() + 1);
|
||||
minDate.value = today.toISOString().substring(0, 16);
|
||||
});
|
||||
});
|
||||
|
||||
const userSet = ({ userList, userTotal }) => {
|
||||
activeUserList.value = userList;
|
||||
userListTotal.value = userTotal;
|
||||
};
|
||||
|
||||
|
||||
const handleUserListUpdate = ({ activeUsers, disabledUsers: updatedDisabledUsers }) => {
|
||||
activeUserList.value = activeUsers;
|
||||
disabledUsers.value = updatedDisabledUsers;
|
||||
@ -135,7 +166,6 @@ const handleUserListUpdate = ({ activeUsers, disabledUsers: updatedDisabledUsers
|
||||
UserListAlert.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const saveValid = () => {
|
||||
let valid = true;
|
||||
if (disabledUsers.value.length === 0) {
|
||||
@ -175,7 +205,6 @@ const saveValid = () => {
|
||||
}
|
||||
};
|
||||
const saveVote = () => {
|
||||
console.log('itemList',itemList)
|
||||
const filteredItemList = itemList.value.filter(item => item.content && item.content.trim() !== '');
|
||||
const unwrappedUserList = toRaw(activeUserList.value);
|
||||
const listId = unwrappedUserList.map(item => ({
|
||||
|
||||
Loading…
Reference in New Issue
Block a user