DatePicker.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. <template>
  2. <div class="admin--datepicker">
  3. <input
  4. ref="dateInput"
  5. type="text"
  6. :value="modelValue"
  7. :placeholder="placeholder"
  8. :class="['admin--form-input', { 'is-required': required, 'is-disabled': disabled }]"
  9. :required="required"
  10. :disabled="disabled"
  11. readonly
  12. >
  13. </div>
  14. </template>
  15. <script setup>
  16. import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
  17. import flatpickr from 'flatpickr'
  18. import { Korean } from 'flatpickr/dist/l10n/ko'
  19. import 'flatpickr/dist/flatpickr.min.css'
  20. const props = defineProps({
  21. modelValue: {
  22. type: String,
  23. default: ''
  24. },
  25. placeholder: {
  26. type: String,
  27. default: '날짜 선택'
  28. },
  29. required: {
  30. type: Boolean,
  31. default: false
  32. },
  33. mode: {
  34. type: String,
  35. default: 'single', // single, range
  36. validator: (value) => ['single', 'range'].includes(value)
  37. },
  38. minDate: {
  39. type: String,
  40. default: null
  41. },
  42. maxDate: {
  43. type: String,
  44. default: null
  45. },
  46. disabled: {
  47. type: Boolean,
  48. default: false
  49. }
  50. })
  51. const emit = defineEmits(['update:modelValue'])
  52. const dateInput = ref(null)
  53. let flatpickrInstance = null
  54. onMounted(() => {
  55. flatpickrInstance = flatpickr(dateInput.value, {
  56. locale: Korean,
  57. dateFormat: 'Y-m-d',
  58. mode: props.mode,
  59. minDate: props.minDate,
  60. maxDate: props.maxDate,
  61. defaultDate: props.modelValue || null,
  62. // disabled일 때 캘린더 열림 방지
  63. clickOpens: !props.disabled,
  64. // 모바일에서 native date input으로 전환 차단 (placeholder가 "연도-월-일" 으로 깨지는 원인)
  65. disableMobile: true,
  66. onChange: (_selectedDates, dateStr) => {
  67. emit('update:modelValue', dateStr)
  68. },
  69. // 월/년 선택 드롭다운 활성화
  70. monthSelectorType: 'dropdown',
  71. // 시간 선택 비활성화
  72. enableTime: false,
  73. // 주말 강조
  74. onDayCreate: (_dObj, _dStr, _fp, dayElem) => {
  75. const day = dayElem.dateObj.getDay()
  76. if (day === 0) {
  77. dayElem.classList.add('weekend-sunday')
  78. } else if (day === 6) {
  79. dayElem.classList.add('weekend-saturday')
  80. }
  81. }
  82. })
  83. })
  84. // modelValue 변경 감지
  85. watch(() => props.modelValue, (newValue) => {
  86. if (flatpickrInstance && newValue !== flatpickrInstance.input.value) {
  87. flatpickrInstance.setDate(newValue || null, false)
  88. }
  89. })
  90. // minDate, maxDate 변경 감지
  91. watch([() => props.minDate, () => props.maxDate], ([newMinDate, newMaxDate]) => {
  92. if (flatpickrInstance) {
  93. flatpickrInstance.set('minDate', newMinDate)
  94. flatpickrInstance.set('maxDate', newMaxDate)
  95. }
  96. })
  97. onBeforeUnmount(() => {
  98. if (flatpickrInstance) {
  99. flatpickrInstance.destroy()
  100. }
  101. })
  102. </script>
  103. <style scoped>
  104. .admin--datepicker {
  105. position: relative;
  106. }
  107. .admin--datepicker .admin--form-input {
  108. cursor: pointer;
  109. background: var(--admin-bg-tertiary, #252525);
  110. color: var(--admin-text-primary, #ffffff) !important;
  111. border: 1px solid var(--admin-border-color, #333333);
  112. }
  113. .admin--datepicker .admin--form-input:read-only {
  114. background-color: #fff;
  115. color: var(--admin-text-primary, #ffffff) !important;
  116. }
  117. .admin--datepicker .admin--form-input::placeholder {
  118. color: var(--admin-text-muted, #666666);
  119. }
  120. .admin--datepicker .admin--form-input:focus {
  121. outline: none;
  122. /* box-shadow: 0 0 0 3px rgba(187, 10, 48, 0.1); */
  123. }
  124. /* 다크 테마 커스터마이징 */
  125. :deep(.flatpickr-calendar.dark) {
  126. background: #2c3e50;
  127. border-color: #34495e;
  128. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  129. }
  130. :deep(.flatpickr-calendar.dark .flatpickr-months) {
  131. background: #34495e;
  132. }
  133. :deep(.flatpickr-calendar.dark .flatpickr-current-month .flatpickr-monthDropdown-months) {
  134. background: #2c3e50;
  135. color: #ecf0f1;
  136. }
  137. :deep(.flatpickr-calendar.dark .flatpickr-day.selected) {
  138. background: #3498db;
  139. border-color: #2980b9;
  140. }
  141. :deep(.flatpickr-calendar.dark .flatpickr-day.selected:hover) {
  142. background: #2980b9;
  143. }
  144. :deep(.flatpickr-calendar.dark .flatpickr-day:hover) {
  145. background: #34495e;
  146. }
  147. :deep(.flatpickr-calendar.dark .flatpickr-day.today) {
  148. border-color: #3498db;
  149. }
  150. :deep(.flatpickr-calendar.dark .flatpickr-day.today:hover) {
  151. background: #34495e;
  152. border-color: #3498db;
  153. }
  154. /* 주말 색상 */
  155. :deep(.flatpickr-day.weekend-sunday) {
  156. color: #e74c3c !important;
  157. }
  158. :deep(.flatpickr-day.weekend-saturday) {
  159. color: #3498db !important;
  160. }
  161. :deep(.flatpickr-day.weekend-sunday.selected),
  162. :deep(.flatpickr-day.weekend-saturday.selected) {
  163. color: #fff !important;
  164. }
  165. /* Range 모드 스타일 */
  166. :deep(.flatpickr-day.inRange) {
  167. background: rgba(52, 152, 219, 0.2);
  168. border-color: transparent;
  169. box-shadow: -5px 0 0 rgba(52, 152, 219, 0.2), 5px 0 0 rgba(52, 152, 219, 0.2);
  170. }
  171. :deep(.flatpickr-day.startRange),
  172. :deep(.flatpickr-day.endRange) {
  173. background: #3498db;
  174. color: #fff;
  175. }
  176. </style>