DatePicker.vue 4.5 KB

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