Untitled
unknown
javascript
2 years ago
13 kB
4
Indexable
<script> export default { name: 'FDatetimePicker', inheritAttrs: false, customOptions: {} } </script> <script setup> import { ref, watch, computed, unref } from 'vue' import { BFormGroup, BInputGroup, BFormInput, BInputGroupAppend, BFormInvalidFeedback } from 'bootstrap-vue-3' import { FSelect } from '../select' import { getDateString } from '../../services/date.service' import { generateId } from '../utils' const props = defineProps({ modelValue: { type: null }, rules: { type: [String], default: null }, mode: { type: String, default: 'datetime', validator: value => ['datetime', 'date', 'time', 'month'].includes(value) }, dateConfig: { type: Object, default: () => ({}) }, timeConfig: { type: Object, default: () => ({}) }, monthConfig: { type: Object, default: () => ({}) }, label: { type: String, default: '' }, editable: { type: Boolean, default: false }, disabled: { type: Boolean, default: false } }) const emit = defineEmits(['update:modelValue']) const { date, isValidDate, onClickDateInputClearIcon } = useDate() const { time, isValidTime, timeSelectList, onClickTimeInputClearIcon } = useTime() const { month, minMonth, maxMonth, getMonthForInputTag } = useMonth() const { isVisibleDateInput, isVisibleTimeInput, isVisibleClearIcon } = useTemplateConditions() const validationRules = computed(() => { const rules = [] if (props.rules) { rules.push(unref(props.rules)) } if (props.dateConfig.min || props.dateConfig.max) { const min = `${props.dateConfig.min} 00:00:00:000` || '1900-01-01 00:00:00:000' const max = `${props.dateConfig.max} 23:59:59:999` || '2100-12-31 23:59:59:999' rules.push(`date_between:${min},${max}`) } if (props.dateConfig.weekdaysOnly) { rules.push('date_weekdays') } return rules.join('|') }) useModelValueWatchers() function useDate() { const date = ref(null) const isValidDate = date => { const dateValue = new Date(date) return dateValue !== 'Invalid Date' && !isNaN(dateValue) } const onClickDateInputClearIcon = () => { date.value = null } return { date, isValidDate, onClickDateInputClearIcon } } function useTime() { const time = ref(null) const timeSelectList = computed(() => { if (props.timeConfig.intervals) { return getTimeSelectListWithInterval() } else { return getTimeSelectList() } }) const isValidTime = time => { return /^([0-1]?[0-9]|2[0-4]):([0-5][0-9])(:[0-5][0-9])?$/.test(time) } const onClickTimeInputClearIcon = () => { time.value = null } const getTimeList = ({ minHour, maxHour, minMinute, maxMinute, stepHours, stepMinutes, onlyStepUpHours }) => { let date = new Date() let maxDate = new Date() date.setHours(minHour) date.setMinutes(minMinute) maxDate.setHours(maxHour) maxDate.setMinutes(maxMinute) const timeList = [] while (date <= maxDate) { const hour = date.getHours() const minute = date.getMinutes() timeList.push( `${hour.toString().padStart(2, '0')}:${minute .toString() .padStart(2, '0')}` ) if (onlyStepUpHours) { date.setHours(date.getHours() + stepHours) } else { date.setMinutes(date.getMinutes() + stepMinutes) } } return timeList } const getTimeSelectList = () => { const stepHours = props.timeConfig.stepHours || 1 const stepMinutes = props.timeConfig.stepMinutes || 15 let minHour = 0 let maxHour = 23 let minMinute = 0 let maxMinute = 59 if (props.timeConfig.min) { const timeParts = props.timeConfig.min.split(':') minHour = parseInt(timeParts[0]) minMinute = parseInt(timeParts[1]) } if (props.timeConfig.max) { const timeParts = props.timeConfig.max.split(':') maxHour = parseInt(timeParts[0]) maxMinute = parseInt(timeParts[1]) } const onlyStepUpHours = props.timeConfig.stepHours ? true : false const timeList = getTimeList({ minHour, minMinute, maxHour, maxMinute, stepHours, stepMinutes, onlyStepUpHours }) return timeList } const getTimeSelectListWithInterval = () => { let parentTimeList = [] for (const interval of props.timeConfig.intervals) { let minHour, minMinute, maxHour, maxMinute minHour = parseInt(interval.min.split(':')[0]) minMinute = parseInt(interval.min.split(':')[1]) maxHour = parseInt(interval.max.split(':')[0]) maxMinute = parseInt(interval.max.split(':')[1]) const stepHours = interval.stepHours || 1 const stepMinutes = interval.stepMinutes || 15 const onlyStepUpHours = interval.stepHours ? true : false const timeList = getTimeList({ minHour, minMinute, maxHour, maxMinute, stepHours, stepMinutes, onlyStepUpHours }) parentTimeList = [...parentTimeList, ...timeList] } return parentTimeList } return { time, isValidTime, timeSelectList, onClickTimeInputClearIcon } } function useMonth() { const month = ref(null) const minMonth = computed( () => getMonthForInputTag(props.monthConfig.min) || '1900-01' ) const maxMonth = computed( () => getMonthForInputTag(props.monthConfig.max) || '2100-12' ) const isValidMonth = month => { return ( /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.test(month) || /^([0-9]{4})-([0-9]{2})$/.test(month) ) } const getMonthForInputTag = month => { if (isValidMonth(month) && month.length >= 7) { return month.slice(0, 7) } return month } return { month, isValidMonth, minMonth, maxMonth, getMonthForInputTag } } function useModelValueWatchers() { if (props.mode === 'datetime') { watch( () => props.modelValue, newValue => { if (!newValue) return const datetimeString = getDateString(new Date(newValue), { time: true, timezone: true }) const datetimeParts = datetimeString.split('T') const timeParts = datetimeParts[1].split(':') date.value = datetimeParts[0] time.value = `${timeParts[0]}:${timeParts[1]}` }, { immediate: true } ) watch( () => date.value, newValue => { let value = null if (!time.value) { const { defaultValue } = props.timeConfig time.value = defaultValue } if ( newValue && time.value && isValidDate(newValue) && isValidTime(time.value) ) { value = getDateString(new Date(`${newValue} ${time.value}`), { time: true, timezone: true }) if (!isValidDate(value)) value = null } emit('update:modelValue', value) } ) watch( () => time.value, newValue => { let value = null if ( newValue && date.value && isValidTime(newValue) && isValidDate(date.value) ) { value = getDateString(new Date(`${date.value} ${newValue}`), { time: true, timezone: true }) if (!isValidDate(value)) value = null } emit('update:modelValue', value) } ) } else if (props.mode === 'date') { watch( () => props.modelValue, newValue => { date.value = newValue }, { immediate: true } ) watch( () => date.value, newValue => { const { startOfDay, endOfDay } = props.dateConfig let value = null if (newValue && isValidDate(newValue)) { if (startOfDay) { value = getDateString(new Date(`${newValue} 00:00:00:000`), { time: true, timezone: true }) } else if (endOfDay) { value = getDateString(new Date(`${newValue} 23:59:59:999`), { time: true, timezone: true }) } else { value = newValue } } emit('update:modelValue', value) } ) } else if (props.mode === 'time') { watch( () => props.modelValue, newValue => { time.value = newValue }, { immediate: true } ) watch( () => time.value, newValue => { let value = null if (newValue && isValidTime(newValue)) { value = newValue } emit('update:modelValue', value) } ) } else if (props.mode === 'month') { watch( () => props.modelValue, newValue => { month.value = getMonthForInputTag(newValue) }, { immediate: true } ) watch( () => month.value, newValue => { let value = null value = getMonthForInputTag(newValue) emit('update:modelValue', value) } ) } } function useTemplateConditions() { const isVisibleDateInput = computed( () => props.mode === 'datetime' || props.mode === 'date' ) const isVisibleTimeInput = computed( () => (props.mode === 'datetime' || props.mode === 'time') && !props.timeConfig.select ) const isVisibleClearIcon = computed(() => { return !props.disabled && !props.editable }) return { isVisibleDateInput, isVisibleTimeInput, isVisibleClearIcon } } </script> <template> <ValidationField v-slot="{ errorMessage }" as="div" class="f-datetime-picker" :model-value="props.modelValue" :name="generateId()" :rules="validationRules" :validate-on-blur="false" :validate-on-change="false" :validate-on-input="false" :validate-on-model-update="true" > <b-form-group :label="label"> <b-input-group> <div v-if="isVisibleDateInput" class="input-area" :class="{ 'date-input': props.mode === 'datetime', fixed: props.dateConfig.fixed }" > <b-form-input v-bind="dateConfig" v-model="date" ref="dateInputRef" :class="{ 'editable-disabled': !props.editable, 'is-invalid': errorMessage }" :disabled="props.disabled" :min="props.dateConfig.min || '1900-01-01'" :max="props.dateConfig.max || '2100-12-31'" :name="generateId()" type="date" /> <f-icon v-if="isVisibleClearIcon && date" bi class="clear-input" icon="x" @click="onClickDateInputClearIcon" /> </div> <div v-if="isVisibleTimeInput" class="input-area time-input" :class="{ fixed: props.mode === 'datetime' || props.timeConfig.fixed }" > <b-form-input v-bind="timeConfig" v-model="time" ref="timeInputRef" :class="{ 'editable-disabled': !props.editable }" :disabled="props.disabled" :name="generateId()" :placeholder="props.timeConfig.placeholder || 'HH:mm'" type="time" /> <f-icon v-if="isVisibleClearIcon && time" bi class="clear-input" icon="x" @click="onClickTimeInputClearIcon" /> </div> <input v-if="props.mode === 'month'" v-model="month" class="form-control" :class="{ 'is-invalid': errorMessage }" :disabled="props.disabled" :name="generateId()" type="month" :min="minMonth" :max="maxMonth" /> <f-select v-if="props.mode === 'time' && props.timeConfig.select" v-model="time" class="time-select" :class="{ fixed: props.timeConfig.fixed }" :disabled="props.disabled" :name="generateId()" :options="timeSelectList" :placeholder="props.timeConfig.placeholder || 'HH:mm'" value-type="string" :rules="rules" :is-error-message-visible="false" /> <b-input-group-append v-if="props.mode === 'datetime' && props.timeConfig.select" class="input-append" > <f-select v-model="time" :rules="rules" class="time-select fixed" :disabled="props.disabled" :name="generateId()" :options="timeSelectList" :placeholder="props.timeConfig.placeholder || 'HH:mm'" value-type="string" :is-error-message-visible="false" /> </b-input-group-append> <b-form-invalid-feedback id="inputLiveFeedback" :force-show="!!errorMessage" > {{ errorMessage }} </b-form-invalid-feedback> </b-input-group> </b-form-group> </ValidationField> </template>
Editor is loading...