DatePicker 日付選択 - Vant 4
日付を選択するためのピッカーコンポーネントで、年/月/日の選択が可能です。
📦 導入
インポート
js
import { DatePicker } from 'vant';🔨 基本的な使い方
基礎的な使い方
value 属性を使用して、選択された日付を制御します。columns-type 属性で表示する列のタイプを設定します。
vue
<template>
<div class="demo-date-picker">
<van-field
v-model="value"
is-link
label="日付"
placeholder="日付を選択"
@click="showPicker = true"
/>
<van-popup v-model:show="showPicker" round position="bottom">
<van-date-picker
v-model="currentDate"
title="日付を選択"
:columns-type="['year', 'month', 'day']"
@confirm="onConfirm"
@cancel="showPicker = false"
/>
</van-popup>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const showPicker = ref(false);
const currentDate = ref(['2024', '01', '01']);
const value = computed({
get() {
return currentDate.value.join('-');
},
set(val) {
if (val) {
currentDate.value = val.split('-');
}
}
});
const onConfirm = ({ selectedValues }) => {
currentDate.value = selectedValues;
showPicker.value = false;
};
</script>オプションタイプ
columns-type 属性で、表示する列のタイプをカスタマイズできます。
vue
<template>
<div class="demo-date-picker-types">
<!-- 年月日 -->
<van-date-picker
v-model="date1"
title="年月日"
:columns-type="['year', 'month', 'day']"
/>
<!-- 年月 -->
<van-date-picker
v-model="date2"
title="年月"
:columns-type="['year', 'month']"
/>
<!-- 月日 -->
<van-date-picker
v-model="date3"
title="月日"
:columns-type="['month', 'day']"
/>
<!-- 年 -->
<van-date-picker
v-model="date4"
title="年"
:columns-type="['year']"
/>
</div>
</template>
<script setup>
import { ref } from 'vue';
const date1 = ref(['2024', '01', '01']);
const date2 = ref(['2024', '01']);
const date3 = ref(['01', '01']);
const date4 = ref(['2024']);
</script>オプションのフォーマット
formatter 属性で、各列の表示テキストをカスタマイズできます。
vue
<template>
<van-date-picker
v-model="currentDate"
title="カスタムフォーマット"
:columns-type="['year', 'month', 'day']"
:formatter="formatter"
/>
</template>
<script setup>
import { ref } from 'vue';
const currentDate = ref(['2024', '01', '01']);
const formatter = (type, option) => {
if (type === 'year') {
option.text += '年';
} else if (type === 'month') {
option.text += '月';
} else if (type === 'day') {
option.text += '日';
}
return option;
};
</script>オプションのフィルタリング
filter 属性で、各列に表示されるオプションをカスタマイズできます。
vue
<template>
<van-date-picker
v-model="currentDate"
title="曜日でフィルタリング"
:columns-type="['year', 'month', 'day']"
:filter="filter"
/>
</template>
<script setup>
import { ref } from 'vue';
const currentDate = ref(['2024', '01', '01']);
const filter = (type, options, values) => {
// 日付列のみフィルタリング
if (type === 'day') {
// 土曜日と日曜日を除外
const year = values[0];
const month = values[1];
return options.filter((option) => {
const date = new Date(year, month - 1, option.value);
const day = date.getDay();
return day !== 0 && day !== 6;
});
}
return options;
};
</script>API
Props
| パラメータ | 説明 | 型 | デフォルト値 |
|---|---|---|---|
| v-model | 選択された日付 | string[] | - |
| columns-type | 列のタイプ | string[] | ['year', 'month', 'day'] |
| title | ピッカーのタイトル | string | '' |
| confirm-button-text | 確認ボタンのテキスト | string | '確認' |
| cancel-button-text | キャンセルボタンのテキスト | string | 'キャンセル' |
| formatter | オプションのフォーマット関数 | (type, option) => option | - |
| filter | オプションのフィルタリング関数 | (type, options, values) => options | - |
| min-date | 最小日付制限 | Date | 十年前 |
| max-date | 最大日付制限 | Date | 十年後 |
| min-year | 最小年 | number | 1900 |
| max-year | 最大年 | number | 2100 |
| show-toolbar | ツールバーを表示するかどうか | boolean | true |
| loading | ローディング状態 | boolean | false |
| readonly | 読み取り専用 | boolean | false |
| item-height | オプションの高さ | number | 44 |
| visible-option-num | 可視オプションの数 | number | 6 |
| swipe-duration | スワイプアニメーションの期間 | number | 100 |
Events
| イベント名 | 説明 | コールバック引数 |
|---|---|---|
| confirm | 確認ボタンがクリックされたときにトリガー | { selectedValues, selectedOptions } |
| cancel | キャンセルボタンがクリックされたときにトリガー | - |
| change | 選択が変更されたときにトリガー | { selectedValues, selectedOptions } |
Slots
| 名前 | 説明 | パラメータ |
|---|---|---|
| toolbar | ツールバーのカスタムコンテンツ | - |
| title | タイトルのカスタムコンテンツ | - |
| confirm | 確認ボタンのカスタムコンテンツ | - |
| cancel | キャンセルボタンのカスタムコンテンツ | - |
| option | オプションのカスタムコンテンツ | option: { text, value } |
| columns-top | 列の上のカスタムコンテンツ | - |
| columns-bottom | 列の下のカスタムコンテンツ | - |
メソッド
| メソッド名 | 説明 | パラメータ | 戻り値 |
|---|---|---|---|
| confirm | 現在の選択を確認する | - | void |
| cancel | 現在の選択をキャンセルする | - | void |
| getSelectedOptions | 選択されたオプションを取得する | - | object[] |
型定義
コンポーネントの型定義は、次のようにインポートできます:
js
import type { DatePickerProps, DatePickerColumnType } from 'vant';テーマカスタマイズ
CSS 変数
次の CSS 変数を設定することで、スタイルをカスタマイズできます。カスタマイズ方法については、テーマカスタマイズ を参照してください。
| 変数名 | 説明 | デフォルト値 |
|---|---|---|
| --van-date-picker-active-color | アクティブな状態のテキスト色 | var(--van-primary-color) |
| --van-date-picker-text-color | テキスト色 | var(--van-text-color) |
| --van-date-picker-disabled-opacity | 無効状態の不透明度 | var(--van-disabled-opacity) |
| --van-date-picker-option-height | オプションの高さ | 44px |
💡 ベストプラクティス
日付範囲の設定
日付の範囲を合理的に設定することで、ユーザーエクスペリエンスを向上させることができます。
vue
<template>
<van-popup v-model:show="showPicker" round position="bottom">
<van-date-picker
v-model="selectedDate"
title="日付を選択"
:columns-type="['year', 'month', 'day']"
:min-date="minDate"
:max-date="maxDate"
:formatter="formatter"
@confirm="onConfirm"
@cancel="showPicker = false"
/>
</van-popup>
</template>
<script setup>
import { ref } from 'vue';
const showPicker = ref(false);
const selectedDate = ref(['2024', '01', '01']);
// 合理的な日付範囲を設定
const minDate = new Date(2020, 0, 1);
const maxDate = new Date(2030, 11, 31);
// フォーマット表示
const formatter = (type, option) => {
const suffixMap = {
year: '年',
month: '月',
day: '日'
};
option.text += suffixMap[type] || '';
return option;
};
// 確認選択
const onConfirm = ({ selectedValues }) => {
selectedDate.value = selectedValues;
showPicker.value = false;
};
// 日付をフォーマット表示
const formatDate = (date) => {
if (!date || date.length < 3) return '日付を選択してください';
return `${date[0]}年${date[1]}月${date[2]}日`;
};
</script>レスポンシブな日付範囲設定
javascript
// 日付範囲を動的に設定
const setupDateRange = (type) => {
const now = new Date();
const ranges = {
// 誕生日選択:100年前から今日まで
birthday: {
min: new Date(now.getFullYear() - 100, 0, 1),
max: now
},
// 予約選択:今日から30日後まで
appointment: {
min: now,
max: new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000)
},
// 履歴記録:10年前から今日まで
history: {
min: new Date(now.getFullYear() - 10, 0, 1),
max: now
},
// 計画安排:今日から1年後まで
planning: {
min: now,
max: new Date(now.getFullYear() + 1, now.getMonth(), now.getDate())
}
};
return ranges[type] || ranges.appointment;
};
// 使用例
const { min: minDate, max: maxDate } = setupDateRange('birthday');スマートなデフォルト値設定
javascript
// スマートにデフォルト日付を設定
const getSmartDefaultDate = (scenario) => {
const now = new Date();
const year = now.getFullYear().toString();
const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getDate().toString().padStart(2, '0');
const scenarios = {
// 誕生日シナリオ:デフォルト30年前
birthday: [(year - 30).toString(), month, day],
// 予約シナリオ:デフォルト明日
appointment: [
year,
month,
(now.getDate() + 1).toString().padStart(2, '0')
],
// 記念日シナリオ:デフォルト今日
anniversary: [year, month, day],
// 計画シナリオ:デフォルト来週
planning: [
year,
month,
(now.getDate() + 7).toString().padStart(2, '0')
]
};
return scenarios[scenario] || [year, month, day];
};💡 使用技巧
多言語日付フォーマット
javascript
// 国際化日付フォーマット
const createI18nFormatter = (locale = 'zh-CN') => {
const formatters = {
'zh-CN': {
year: (text) => `${text}年`,
month: (text) => `${text}月`,
day: (text) => `${text}日`
},
'en-US': {
year: (text) => text,
month: (text) => new Date(2000, parseInt(text) - 1).toLocaleDateString('en-US', { month: 'short' }),
day: (text) => `${text}${getOrdinalSuffix(parseInt(text))}`
},
'ja-JP': {
year: (text) => `${text}年`,
month: (text) => `${text}月`,
day: (text) => `${text}日`
}
};
const currentFormatter = formatters[locale] || formatters['zh-CN'];
return (type, option) => {
if (currentFormatter[type]) {
option.text = currentFormatter[type](option.text);
}
return option;
};
};
// 英語序数詞接尾辞
const getOrdinalSuffix = (num) => {
const j = num % 10;
const k = num % 100;
if (j === 1 && k !== 11) return 'st';
if (j === 2 && k !== 12) return 'nd';
if (j === 3 && k !== 13) return 'rd';
return 'th';
};
// 使用例
const formatter = createI18nFormatter('en-US');高度なフィルタリング機能
javascript
// 平日フィルター
const createWorkdayFilter = () => {
return (type, options) => {
if (type === 'day') {
return options.filter(option => {
const date = new Date(2024, 0, parseInt(option.value)); // 2024年1月を基準に使用
const dayOfWeek = date.getDay();
return dayOfWeek !== 0 && dayOfWeek !== 6; // 週末を除外
});
}
return options;
};
};
// 特別な日付フィルター
const createSpecialDateFilter = (excludeDates = []) => {
return (type, options, values) => {
if (type === 'day' && values[0] && values[1]) {
const year = parseInt(values[0]);
const month = parseInt(values[1]) - 1;
return options.filter(option => {
const day = parseInt(option.value);
const dateStr = `${year}-${(month + 1).toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
return !excludeDates.includes(dateStr);
});
}
return options;
};
};
// 四半期フィルター
const createQuarterFilter = () => {
return (type, options) => {
if (type === 'month') {
// 四半期の最初の月のみ表示:1月、4月、7月、10月
return options.filter(option => {
const month = parseInt(option.value);
return [1, 4, 7, 10].includes(month);
});
}
return options;
};
};動的な列タイプ切り替え
vue
<template>
<div class="dynamic-date-picker">
<!-- モード選択 -->
<van-radio-group v-model="pickerMode" direction="horizontal">
<van-radio name="full">完全な日付</van-radio>
<van-radio name="yearMonth">年月</van-radio>
<van-radio name="monthDay">月日</van-radio>
<van-radio name="year">年のみ</van-radio>
</van-radio-group>
<!-- 日付ピッカー -->
<van-date-picker
v-model="selectedDate"
:columns-type="currentColumnsType"
:formatter="formatter"
@change="onDateChange"
/>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
const pickerMode = ref('full');
const selectedDate = ref(['2024', '01', '01']);
// モードに応じて列タイプを動的に設定
const currentColumnsType = computed(() => {
const modeMap = {
full: ['year', 'month', 'day'],
yearMonth: ['year', 'month'],
monthDay: ['month', 'day'],
year: ['year']
};
return modeMap[pickerMode.value];
});
// モードの変更を監視し、選択値を調整
watch(pickerMode, (newMode) => {
const now = new Date();
const year = now.getFullYear().toString();
const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getDate().toString().padStart(2, '0');
const defaultValues = {
full: [year, month, day],
yearMonth: [year, month],
monthDay: [month, day],
year: [year]
};
selectedDate.value = defaultValues[newMode];
});
const formatter = (type, option) => {
const suffixMap = { year: '年', month: '月', day: '日' };
option.text += suffixMap[type] || '';
return option;
};
const onDateChange = ({ selectedValues }) => {
console.log('日付が変更されました:', selectedValues);
};
</script>🔧 よくある質問と解決策
iOS 日付の互換性問題
javascript
// iOS で安全な日付作成方法
const createSafeDate = (year, month, day) => {
// iOS は 'YYYY-MM-DD' 形式をサポートしていないため、'YYYY/MM/DD' を使用する必要があります
const safeYear = year || new Date().getFullYear();
const safeMonth = month || 1;
const safeDay = day || 1;
// 方法1:スラッシュ区切りを使用
return new Date(`${safeYear}/${safeMonth}/${safeDay}`);
// 方法2:コンストラクタを使用(推奨)
// return new Date(safeYear, safeMonth - 1, safeDay);
};
// 日付範囲設定のベストプラクティス
const setupDateRangeSafely = () => {
// ❌ 間違った記述 - iOS で失敗する可能性があります
// const minDate = new Date('2020-01-01');
// ✅ 正しい記述 - クロスプラットフォーム互換
const minDate = new Date(2020, 0, 1); // 月は0から始まります
const maxDate = new Date(2030, 11, 31);
return { minDate, maxDate };
};パフォーマンス最適化のヒント
javascript
// 大規模データの最適化
const optimizeForLargeData = () => {
// 仮想スクロールを使用してDOMノードを減らす
const visibleOptionNum = 5; // 可視オプション数を減らす
// オプションの遅延読み込み
const lazyLoadOptions = (type, currentValues) => {
if (type === 'year') {
// 現在の年の前後10年のみ読み込む
const currentYear = parseInt(currentValues[0]) || new Date().getFullYear();
const startYear = currentYear - 10;
const endYear = currentYear + 10;
return Array.from({ length: endYear - startYear + 1 }, (_, i) => ({
text: (startYear + i).toString(),
value: (startYear + i).toString()
}));
}
return [];
};
return { visibleOptionNum, lazyLoadOptions };
};
// デバウンス最適化
const createDebouncedHandler = (handler, delay = 300) => {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => handler(...args), delay);
};
};
// 使用例
const debouncedChange = createDebouncedHandler((values) => {
console.log('日付が変更されました:', values);
// 複雑なビジネスロジックを実行
}, 300);データ検証とエラー処理
javascript
// 日付の有効性検証
const validateDateSelection = (selectedValues, minDate, maxDate) => {
if (!selectedValues || selectedValues.length === 0) {
return { valid: false, error: '日付を選択してください' };
}
try {
const [year, month, day] = selectedValues;
const selectedDate = new Date(
parseInt(year),
parseInt(month) - 1,
parseInt(day)
);
// 日付が有効かどうかを確認
if (isNaN(selectedDate.getTime())) {
return { valid: false, error: '無効な日付です' };
}
// 許可された範囲内にあるかどうかを確認
if (minDate && selectedDate < minDate) {
return { valid: false, error: '日付は最小日付より前にできません' };
}
if (maxDate && selectedDate > maxDate) {
return { valid: false, error: '日付は最大日付より後にできません' };
}
return { valid: true, date: selectedDate };
} catch (error) {
return { valid: false, error: '日付形式が正しくありません' };
}
};
// エラー処理コンポーネント
const DatePickerWithValidation = {
setup() {
const selectedDate = ref([]);
const errorMessage = ref('');
const onConfirm = ({ selectedValues }) => {
const validation = validateDateSelection(
selectedValues,
minDate.value,
maxDate.value
);
if (validation.valid) {
selectedDate.value = selectedValues;
errorMessage.value = '';
// 確認ロジックを実行
} else {
errorMessage.value = validation.error;
// エラーメッセージを表示
showToast(validation.error);
}
};
return { selectedDate, errorMessage, onConfirm };
}
};🎨 デザインのインスピレーション
テーマ化された日付ピッカー
css
/* 春のテーマ */
.date-picker-spring {
--van-picker-option-text-color: #52c41a;
--van-picker-option-selected-text-color: #389e0d;
--van-picker-toolbar-height: 44px;
--van-picker-action-text-color: #52c41a;
}
.date-picker-spring .van-picker__toolbar {
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc1 100%);
}
/* 夏のテーマ */
.date-picker-summer {
--van-picker-option-text-color: #1890ff;
--van-picker-option-selected-text-color: #096dd9;
--van-picker-action-text-color: #1890ff;
}
.date-picker-summer .van-picker__toolbar {
background: linear-gradient(135deg, #87ceeb 0%, #98d8e8 100%);
}
/* 秋のテーマ */
.date-picker-autumn {
--van-picker-option-text-color: #fa8c16;
--van-picker-option-selected-text-color: #d46b08;
--van-picker-action-text-color: #fa8c16;
}
.date-picker-autumn .van-picker__toolbar {
background: linear-gradient(135deg, #ffd89b 0%, #19547b 100%);
}
/* 冬のテーマ */
.date-picker-winter {
--van-picker-option-text-color: #722ed1;
--van-picker-option-selected-text-color: #531dab;
--van-picker-action-text-color: #722ed1;
}
.date-picker-winter .van-picker__toolbar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}アニメーション効果の強化
css
/* オプション切り替えアニメーション */
.date-picker-animated .van-picker-column__item {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.date-picker-animated .van-picker-column__item--selected {
transform: scale(1.1);
font-weight: bold;
text-shadow: 0 0 8px rgba(24, 144, 255, 0.3);
}
/* ツールバーアニメーション */
.date-picker-animated .van-picker__toolbar {
animation: slideInDown 0.3s ease-out;
}
@keyframes slideInDown {
from {
transform: translateY(-100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
/* オプションリストアニメーション */
.date-picker-animated .van-picker__columns {
animation: fadeInUp 0.4s ease-out;
}
@keyframes fadeInUp {
from {
transform: translateY(20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
/* フローティング効果 */
.date-picker-floating {
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.95);
}クリエイティブなインタラクション効果
vue
<template>
<div class="creative-date-picker">
<!-- 3D フリップ効果 -->
<div class="flip-container" :class="{ flipped: isFlipped }">
<div class="flipper">
<div class="front">
<van-cell
title="日付を選択"
:value="displayDate"
@click="showPicker"
/>
</div>
<div class="back">
<van-date-picker
v-model="selectedDate"
@confirm="onConfirm"
@cancel="hidePicker"
/>
</div>
</div>
</div>
<!-- パーティクル効果の背景 -->
<div class="particles" v-if="showParticles">
<div
v-for="i in 20"
:key="i"
class="particle"
:style="getParticleStyle(i)"
></div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const isFlipped = ref(false);
const showParticles = ref(false);
const selectedDate = ref(['2024', '01', '01']);
const displayDate = computed(() => {
const [year, month, day] = selectedDate.value;
return `${year}年${month}月${day}日`;
});
const showPicker = () => {
isFlipped.value = true;
showParticles.value = true;
};
const hidePicker = () => {
isFlipped.value = false;
showParticles.value = false;
};
const onConfirm = ({ selectedValues }) => {
selectedDate.value = selectedValues;
hidePicker();
};
const getParticleStyle = (index) => {
const angle = (index * 18) % 360;
const radius = 100 + Math.random() * 50;
const x = Math.cos(angle * Math.PI / 180) * radius;
const y = Math.sin(angle * Math.PI / 180) * radius;
return {
left: `calc(50% + ${x}px)`,
top: `calc(50% + ${y}px)`,
animationDelay: `${index * 0.1}s`
};
};
</script>
<style scoped>
.flip-container {
perspective: 1000px;
width: 100%;
height: 300px;
}
.flipper {
transition: transform 0.6s;
transform-style: preserve-3d;
position: relative;
width: 100%;
height: 100%;
}
.flipped .flipper {
transform: rotateY(180deg);
}
.front, .back {
backface-visibility: hidden;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.back {
transform: rotateY(180deg);
}
.particle {
position: absolute;
width: 4px;
height: 4px;
background: #1890ff;
border-radius: 50%;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0px) scale(1); opacity: 1; }
50% { transform: translateY(-20px) scale(1.2); opacity: 0.7; }
}
</style>🚀 高度な機能拡張
スマート日付推奨システム
javascript
// スマート日付推奨エンジン
class SmartDateRecommender {
constructor() {
this.userHistory = [];
this.preferences = {};
}
// ユーザーの選択履歴を記録
recordSelection(date, context) {
this.userHistory.push({
date,
context,
timestamp: Date.now()
});
// 履歴を合理的な範囲内に保つ
if (this.userHistory.length > 100) {
this.userHistory.shift();
}
this.updatePreferences();
}
// ユーザーの好みを更新
updatePreferences() {
const recentSelections = this.userHistory.slice(-20);
// 好まれる曜日を分析
const dayOfWeekCount = {};
recentSelections.forEach(({ date }) => {
const dayOfWeek = new Date(date).getDay();
dayOfWeekCount[dayOfWeek] = (dayOfWeekCount[dayOfWeek] || 0) + 1;
});
this.preferences.preferredDayOfWeek = Object.keys(dayOfWeekCount)
.reduce((a, b) => dayOfWeekCount[a] > dayOfWeekCount[b] ? a : b);
// 好まれる時間帯を分析
const monthCount = {};
recentSelections.forEach(({ date }) => {
const month = new Date(date).getMonth();
monthCount[month] = (monthCount[month] || 0) + 1;
});
this.preferences.preferredMonth = Object.keys(monthCount)
.reduce((a, b) => monthCount[a] > monthCount[b] ? a : b);
}
// スマート推奨を生成
getRecommendations(context, count = 5) {
const now = new Date();
const recommendations = [];
// コンテキストに基づく推奨
switch (context) {
case 'meeting':
// 会議推奨:平日、午前中
recommendations.push(...this.getWorkdayRecommendations(now, count));
break;
case 'birthday':
// 誕生日推奨:履歴の誕生日選択に基づく
recommendations.push(...this.getBirthdayRecommendations(count));
break;
case 'vacation':
// 休暇推奨:週末または祝日
recommendations.push(...this.getVacationRecommendations(now, count));
break;
default:
recommendations.push(...this.getGeneralRecommendations(now, count));
}
return recommendations.slice(0, count);
}
// 平日推奨
getWorkdayRecommendations(baseDate, count) {
const recommendations = [];
let date = new Date(baseDate);
while (recommendations.length < count) {
date.setDate(date.getDate() + 1);
const dayOfWeek = date.getDay();
// 週末をスキップ
if (dayOfWeek !== 0 && dayOfWeek !== 6) {
recommendations.push({
date: new Date(date),
reason: '平日推奨',
confidence: 0.8
});
}
}
return recommendations;
}
// 誕生日推奨
getBirthdayRecommendations(count) {
const recommendations = [];
const currentYear = new Date().getFullYear();
// 履歴の誕生日選択に基づく推奨
const birthdayHistory = this.userHistory.filter(h => h.context === 'birthday');
birthdayHistory.forEach(({ date }) => {
const birthDate = new Date(date);
const thisYearBirthday = new Date(currentYear, birthDate.getMonth(), birthDate.getDate());
recommendations.push({
date: thisYearBirthday,
reason: '履歴の誕生日記録',
confidence: 0.9
});
});
return recommendations.slice(0, count);
}
// 休暇推奨
getVacationRecommendations(baseDate, count) {
const recommendations = [];
let date = new Date(baseDate);
while (recommendations.length < count) {
date.setDate(date.getDate() + 1);
const dayOfWeek = date.getDay();
// 週末を推奨
if (dayOfWeek === 0 || dayOfWeek === 6) {
recommendations.push({
date: new Date(date),
reason: '週末推奨',
confidence: 0.7
});
}
}
return recommendations;
}
// 一般的な推奨
getGeneralRecommendations(baseDate, count) {
const recommendations = [];
// 明日
const tomorrow = new Date(baseDate);
tomorrow.setDate(tomorrow.getDate() + 1);
recommendations.push({
date: tomorrow,
reason: '明日',
confidence: 0.6
});
// 来週の同日
const nextWeek = new Date(baseDate);
nextWeek.setDate(nextWeek.getDate() + 7);
recommendations.push({
date: nextWeek,
reason: '来週の同日',
confidence: 0.5
});
// 来月の同日
const nextMonth = new Date(baseDate);
nextMonth.setMonth(nextMonth.getMonth() + 1);
recommendations.push({
date: nextMonth,
reason: '来月の同日',
confidence: 0.4
});
return recommendations.slice(0, count);
}
}
// 使用例
const recommender = new SmartDateRecommender();
// 日付ピッカーに推奨機能を統合
const DatePickerWithRecommendations = {
setup() {
const recommendations = ref([]);
const loadRecommendations = (context) => {
recommendations.value = recommender.getRecommendations(context);
};
const selectRecommendation = (recommendation) => {
const date = recommendation.date;
selectedDate.value = [
date.getFullYear().toString(),
(date.getMonth() + 1).toString().padStart(2, '0'),
date.getDate().toString().padStart(2, '0')
];
// 選択を記録
recommender.recordSelection(date, currentContext.value);
};
return {
recommendations,
loadRecommendations,
selectRecommendation
};
}
};複数日付ピッカー
vue
<template>
<div class="multi-date-picker">
<div class="selected-dates">
<h3>選択済みの日付 ({{ selectedDates.length }})</h3>
<div class="date-chips">
<van-tag
v-for="(date, index) in selectedDates"
:key="index"
closeable
@close="removeDate(index)"
>
{{ formatDate(date) }}
</van-tag>
</div>
</div>
<van-date-picker
v-model="currentDate"
@confirm="addDate"
:min-date="minDate"
:max-date="maxDate"
/>
<div class="actions">
<van-button @click="clearAll" type="default">すべてクリア</van-button>
<van-button @click="confirmSelection" type="primary">選択を確認</van-button>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const selectedDates = ref([]);
const currentDate = ref(['2024', '01', '01']);
const minDate = new Date(2020, 0, 1);
const maxDate = new Date(2030, 11, 31);
const addDate = ({ selectedValues }) => {
const dateStr = selectedValues.join('-');
// 既に存在するかどうかを確認
const exists = selectedDates.value.some(date =>
date.join('-') === dateStr
);
if (!exists) {
selectedDates.value.push([...selectedValues]);
selectedDates.value.sort((a, b) => {
const dateA = new Date(a[0], a[1] - 1, a[2]);
const dateB = new Date(b[0], b[1] - 1, b[2]);
return dateA - dateB;
});
} else {
showToast('この日付は既に選択されています');
}
};
const removeDate = (index) => {
selectedDates.value.splice(index, 1);
};
const clearAll = () => {
selectedDates.value = [];
};
const confirmSelection = () => {
if (selectedDates.value.length === 0) {
showToast('少なくとも1つの日付を選択してください');
return;
}
emit('confirm', selectedDates.value);
};
const formatDate = (date) => {
return `${date[0]}年${date[1]}月${date[2]}日`;
};
</script>
<style scoped>
.selected-dates {
padding: 16px;
background: #f7f8fa;
margin-bottom: 16px;
}
.date-chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 8px;
}
.actions {
display: flex;
gap: 16px;
padding: 16px;
}
</style>📚 関連リソース
技術ドキュメント
- Date オブジェクトの詳細 - JavaScript 日付処理
- Intl.DateTimeFormat - 国際化日付フォーマット
- CSS 時間関数 - CSS 時間関数
デザインガイドライン
- モバイル日付選択デザイン - Material Design 日付ピッカー
- iOS 日付ピッカーガイド - Apple デザインガイドライン
- アクセシビリティ日付選択 - ARIA 日付ピッカーパターン
ユーザーエクスペリエンス
- 日付入力の使いやすさ - 日付ピッカーのベストプラクティス
- モバイル時間選択体験 - モバイルデザインパターン
- フォームの日付選択 - フラストレーションを避けるデザイン
関連コンポーネント
- TimePicker 時間選択 - 時間選択コンポーネント
- Picker ピッカー - 基本ピッカーコンポーネント
- Calendar カレンダー - カレンダーコンポーネント
- Popup ポップアップ - ポップアップコンポーネント
- Cell セル - セルコンポーネント
- Field 入力フィールド - フォーム入力コンポーネント
- Form フォーム - フォームコンポーネント