311 lines
9.0 KiB
Vue
311 lines
9.0 KiB
Vue
<template>
|
|
|
|
<div class="container-xxl flex-grow-1 container-p-y">
|
|
<div class="card app-calendar-wrapper">
|
|
<div class="row g-0">
|
|
<div class="col app-calendar-sidebar border-end" id="app-calendar-sidebar">
|
|
<div class="border-bottom p-6 my-sm-0 mb-4">
|
|
<button class="btn btn-primary btn-toggle-sidebar w-100" @click="showModal">
|
|
<i class="bx bx-plus bx-16px me-2"></i>
|
|
<span class="align-middle">Add Event</span>
|
|
</button>
|
|
</div>
|
|
<div class="px-3 pt-2">
|
|
<flat-pickr v-model="selectedDate" :config="flateCalendarConfig" @on-change="onDateChange"></flat-pickr>
|
|
</div>
|
|
<hr class="mb-6 mx-n4 mt-3" />
|
|
<div class="px-6 pb-2">
|
|
<div>
|
|
<h5>Event</h5>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="col app-calendar-content">
|
|
<div class="card shadow-none border-0">
|
|
<div class="card-body pb-0">
|
|
<full-calendar
|
|
ref="fullCalendarRef"
|
|
:events="calendarEvents"
|
|
:options="calendarOptions"
|
|
defaultView="dayGridMonth"
|
|
class="flatpickr-calendar-only"
|
|
>
|
|
</full-calendar>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<center-modal :display="isModalVisible" @close="isModalVisible = $event">
|
|
<template #title> Add Event </template>
|
|
<template #body>
|
|
<FormInput title="이벤트 제목" name="event" :is-essential="true" :is-alert="eventAlert" @update:data="eventTitle = $event" />
|
|
<FormInput
|
|
title="이벤트 날짜"
|
|
type="date"
|
|
name="eventDate"
|
|
:is-essential="true"
|
|
:is-alert="eventDateAlert"
|
|
@update:data="eventDate = $event"
|
|
/>
|
|
</template>
|
|
<template #footer>
|
|
<button @click="addEvent">추가</button>
|
|
</template>
|
|
</center-modal>
|
|
</template>
|
|
|
|
<script setup>
|
|
import FullCalendar from '@fullcalendar/vue3';
|
|
import dayGridPlugin from '@fullcalendar/daygrid';
|
|
import interactionPlugin from '@fullcalendar/interaction';
|
|
import CenterModal from '@c/modal/CenterModal.vue';
|
|
import { inject, onMounted, reactive, ref } from 'vue';
|
|
import axios from '@api';
|
|
import { isEmpty } from '@/common/utils';
|
|
import FormInput from '../input/FormInput.vue';
|
|
import FlatPickr from 'vue-flatpickr-component';
|
|
import 'flatpickr/dist/flatpickr.min.css'
|
|
import '@/assets/css/app-calendar.css'
|
|
import { useThemeStore } from '@s/darkmode';
|
|
|
|
const { isDarkMode } = useThemeStore();
|
|
|
|
// 기본만 넣었는데 기능 추가해야할듯
|
|
|
|
const key1 = 'vrkIY7jUy82WRHIozbBtz4n2L89jAqNDY%2B4DmNpmasTex%2FNDySN7FDYwBqPj1p%2BxXLg13BzYfgHPt6eipWhH8Q%3D%3D';
|
|
const key2 = 'vrkIY7jUy82WRHIozbBtz4n2L89jAqNDY+4DmNpmasTex/NDySN7FDYwBqPj1p+xXLg13BzYfgHPt6eipWhH8Q==';
|
|
const dayjs = inject('dayjs');
|
|
const fullCalendarRef = ref(null);
|
|
const calendarEvents = ref([]);
|
|
const isModalVisible = ref(false);
|
|
const eventAlert = ref(false);
|
|
const eventDateAlert = ref(false);
|
|
const eventTitle = ref('');
|
|
const eventDate = ref('');
|
|
const selectedDate = ref(null);
|
|
|
|
const fetchData = () => {
|
|
const date = fullCalendarRef.value?.getApi().currentData.viewTitle;
|
|
const dateArr = date.split(' ');
|
|
let contectYear = dateArr[0].trim();
|
|
let contectMonth = dateArr[1].trim();
|
|
const regex = /\D/g;
|
|
contectYear = parseInt(contectYear.replace(regex, ''), 10);
|
|
contectMonth = parseInt(contectMonth.replace(regex, ''), 10);
|
|
const yearArr = [];
|
|
const monthArr = [];
|
|
|
|
for (let offset = -1; offset <= 1; offset++) {
|
|
let newMonth = contectMonth + offset;
|
|
let newYear = contectYear;
|
|
|
|
// 월이 1보다 작으면 작년으로 이동
|
|
if (newMonth < 1) {
|
|
newMonth += 12;
|
|
newYear -= 1;
|
|
}
|
|
|
|
if (newMonth > 12) {
|
|
newMonth -= 12;
|
|
newYear += 1;
|
|
}
|
|
|
|
yearArr.push(newYear);
|
|
monthArr.push(String(newMonth).padStart(2, '0'));
|
|
}
|
|
|
|
callApi(yearArr, monthArr);
|
|
};
|
|
|
|
//이 통신은 key 노출 땜 시 backserver로 옮겨야할듯
|
|
//실제 개발 할 때 back으로 옮김
|
|
const callApi = (yearArr, monthArr) => {
|
|
const callUrl = 'http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo';
|
|
for (let i = 0; i < yearArr.length; i++) {
|
|
axios
|
|
.get(callUrl, {
|
|
params: {
|
|
solYear: String(yearArr[i]),
|
|
solMonth: String(monthArr[i]),
|
|
serviceKey: key2,
|
|
},
|
|
})
|
|
.then(data => {
|
|
const itemData = data.data.response.body.items.item;
|
|
if (!isEmpty(itemData)) {
|
|
if (Array.isArray(itemData)) {
|
|
for (const item of itemData) {
|
|
const locdate = item.locdate;
|
|
dateFormat(locdate);
|
|
}
|
|
} else {
|
|
const locdate = itemData.locdate;
|
|
dateFormat(locdate);
|
|
}
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error('공휴일 불러오기 실패');
|
|
});
|
|
}
|
|
};
|
|
|
|
const dateFormat = locdate => {
|
|
const day = dayjs(String(locdate), 'YYYYMMDD');
|
|
const formatDate = day.format('YYYY년 M월 D일');
|
|
const targetEle = document.querySelector(`[aria-label="${formatDate}"]`);
|
|
|
|
if (targetEle != null) {
|
|
targetEle.style.color = 'red';
|
|
}
|
|
};
|
|
|
|
const handleDateClick = arg => {
|
|
console.log(arg.dateStr);
|
|
};
|
|
|
|
const moveCalendar = (value = 0) => {
|
|
const calendarApi = fullCalendarRef.value?.getApi();
|
|
|
|
if (value == 1) {
|
|
calendarApi.prev();
|
|
} else if (value == 2) {
|
|
calendarApi.next();
|
|
} else if (value == 3) {
|
|
calendarApi.today();
|
|
}
|
|
|
|
fetchData();
|
|
};
|
|
|
|
const showModal = () => {
|
|
isModalVisible.value = true;
|
|
};
|
|
|
|
const closeModal = () => {
|
|
isModalVisible.value = false;
|
|
eventTitle.value = '';
|
|
eventDate.value = '';
|
|
};
|
|
|
|
const addEvent = () => {
|
|
if (!checkEvent()) {
|
|
calendarEvents.value.push({ title: eventTitle.value, start: eventDate.value });
|
|
closeModal();
|
|
}
|
|
};
|
|
|
|
const checkEvent = () => {
|
|
const title = isEmpty(eventTitle.value);
|
|
const date = isEmpty(eventDate.value);
|
|
|
|
return title || date;
|
|
};
|
|
|
|
const onDateChange = selectedDates => {
|
|
console.log('변경된 날짜:', selectedDates);
|
|
};
|
|
|
|
const flateCalendarConfig = reactive({
|
|
dateFormat: 'Y-m-d', // 날짜 포맷
|
|
enableTime: false, // 시간 선택 여부
|
|
inline: true,
|
|
});
|
|
|
|
const calendarOptions = reactive({
|
|
plugins: [dayGridPlugin, interactionPlugin],
|
|
initialView: 'dayGridMonth',
|
|
// initialDate: this.contectDate, 선택날짜
|
|
headerToolbar: {
|
|
left: 'today',
|
|
center: 'title',
|
|
right: 'prev,next',
|
|
},
|
|
locale: 'kr', // 한국어 설정
|
|
events: calendarEvents,
|
|
eventOrder: 'sortIdx',
|
|
selectable: true,
|
|
dateClick: handleDateClick,
|
|
droppable: false,
|
|
customButtons: {
|
|
prev: {
|
|
text: 'PREV',
|
|
click: () => {
|
|
moveCalendar(1);
|
|
},
|
|
},
|
|
today: {
|
|
text: 'TODAY',
|
|
click: () => {
|
|
moveCalendar(3);
|
|
},
|
|
},
|
|
next: {
|
|
text: 'NEXT',
|
|
click: () => {
|
|
moveCalendar(2);
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const loadFlatpickrTheme = async () => {
|
|
if (isDarkMode) {
|
|
await import('flatpickr/dist/themes/dark.css');
|
|
} else {
|
|
await import('flatpickr/dist/themes/light.css');
|
|
}
|
|
}
|
|
|
|
|
|
onMounted(async () => {
|
|
await loadFlatpickrTheme();
|
|
fetchData();
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.fc-day-sun .fc-col-header-cell-cushion,
|
|
.fc-day-sun a {
|
|
color: red;
|
|
}
|
|
|
|
.fc-day-sat .fc-col-header-cell-cushion,
|
|
.fc-day-sat a {
|
|
color: blue;
|
|
}
|
|
|
|
.flatpickr-calendar-only input.flatpickr-input {
|
|
display: none;
|
|
}
|
|
.flatpickr-input {
|
|
display: none;
|
|
}
|
|
|
|
|
|
.pt-2.px-3 {
|
|
position: relative; /* Flatpickr의 위치 기준이 될 부모 */
|
|
}
|
|
|
|
.flatpickr-calendar {
|
|
position: relative !important; /* 부모 내부에 맞게 그리기 */
|
|
display: block !important;
|
|
width: 100% !important; /* 부모 크기에 맞게 확장 */
|
|
height: auto !important; /* 내용에 따라 높이 조정 */
|
|
z-index: 1; /* 부모 위에만 표시 */
|
|
}
|
|
|
|
.flatpickr-rContainer,.dayContainer,.flatpickr-days{
|
|
display: inline-block !important;
|
|
width: 100% !important; /* 부모 안에서 전체 너비 차지 */
|
|
box-sizing: border-box !important; /* 패딩 포함 계산 */
|
|
}
|
|
</style>
|