Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 5x 1x 1x 5x 5x 5x 3x 3x 5x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | <template>
<div>
<!-- 親要素を activator にする -->
<BaseRequiredField
is="v-text-field"
:model-value="displayValue"
@update:model-value="onInput"
:label-key="labelKey"
:label="label"
:required="required"
readonly
v-bind="$attrs"
>
<template v-for="(_, name) in $slots" #[name]="slotProps">
<slot :name="name" v-bind="slotProps"></slot>
</template>
</BaseRequiredField>
<v-menu
v-model="menu"
activator="parent"
:close-on-content-click="false"
transition="scale-transition"
offset-y
>
<v-locale-provider :locale="resolvedLocale">
<v-date-picker
:model-value="pickerDate"
@update:model-value="onDateSelected"
:min="min"
:max="max"
:title="pickerTitle"
/>
</v-locale-provider>
</v-menu>
</div>
</template>
<script setup lang="ts">
// region import
import { computed, ref, watch } from 'vue'
import BaseRequiredField from './BaseRequiredField.vue'
import { getBrowserLocale, normalizeToDateString, parsePickerDate } from '@/utils'
// endregion import
defineOptions({ name: 'RequiredDatePicker' })
// region props
const props = withDefaults(defineProps<{
modelValue?: string | null
labelKey?: string
label?: string
required?: boolean
min?: string
max?: string
locale?: string
dateFormat?: 'yyyy-mm-dd' | 'yyyy-mm' | 'mm-dd'
}>(), {
dateFormat: 'yyyy-mm-dd'
})
// endregion props
// region emits
const emit = defineEmits<{
(e: 'update:modelValue', v: string | null): void
}>()
// endregion emits
// region state
const menu = ref(false)
const innerValue = ref<string | Date | null>(props.modelValue ?? null)
// endregion state
// region computed
const resolvedLocale = computed(() => props.locale?.trim() || getBrowserLocale('ja'))
const pickerDate = computed(() => {
return parsePickerDate(innerValue.value)
})
// 表示用の値(とりあえずそのまま、必要ならここでフォーマット)
// const displayValue = computed(() => normalizeToDateString(innerValue.value) ?? '')
const displayValue = computed(() => {
const normalized = normalizeToDateString(innerValue.value)
if (!normalized) return ''
if (props.dateFormat === 'yyyy-mm') {
return normalized.substring(0, 7)
}
if (props.dateFormat === 'mm-dd') {
return normalized.substring(5, 10)
}
return normalized
})
const pickerTitle = computed(() => props.label ?? '')
// endregion computed
// region watchers
watch(
() => props.modelValue,
(val) => {
if (val !== innerValue.value) {
innerValue.value = val ?? null
}
}
)
// endregion watchers
// region handlers
// テキストフィールド側を直接編集されたとき(基本 readonly だが一応ケア)
const onInput = (value: string) => {
const normalized = normalizeToDateString(value)
innerValue.value = normalized
emit('update:modelValue', normalized)
}
// DatePicker で日付を選択したとき
const onDateSelected = (value: unknown) => {
const normalized = normalizeToDateString(value)
// フォーマット指定がある場合は、ここで整形して格納することも可能だが、
// データとしては YYYY-MM-DD (または YYYY-MM-01) の完全な日付文字列を保持し、
// 表示のみ切り替えるのが安全な場合が多い。
// ただし、バックエンドが YYYY-MM を期待している場合はここで切る。
let result = normalized
if (normalized && props.dateFormat === 'yyyy-mm') {
result = normalized.substring(0, 7)
} else if (normalized && props.dateFormat === 'mm-dd') {
// mm-dd だけ保存するのは稀だが、要件に合わせて実装
result = normalized.substring(5, 10)
}
innerValue.value = result
emit('update:modelValue', result)
menu.value = false
}
// endregion handlers
</script>
|