Calendar 日曆 - Vant 4
Calendar 日曆
📅 介紹
想像一下,你正在規劃一次浪漫的旅行,或者安排一個重要的商務會議 🗓️。這時候,一個好用的日曆元件就像是你的貼心助手!
Calendar 日曆元件就是這樣一個神奇的工具 ✨。它不僅顏值超高,功能還特別強大:
- 🎯 單日選擇:輕點一下,鎖定那個特殊的日子
- 🎨 多日選擇:想選幾天就選幾天,自由度滿分
- 📅 區間選擇:開始和結束,一氣呵成
- 🌈 自訂樣式:想要什麼顏色,隨你心意
無論是預訂酒店、安排會議還是設定生日提醒,Calendar 都能給你最直觀、最便捷的日期選擇體驗!就像有了一個會讀心術的日曆管家 🎭。
📦 引入
透過以下方式來全域註冊元件,更多註冊方式請參考元件註冊。
import { createApp } from'vue';
import { Calendar } from'vant';
const app = createApp();
app.use(Calendar);🎯 程式碼演示
選擇切換模式
想像一下翻閱一本厚厚的日曆本 📖,一頁頁翻找目標日期是不是很累?預設情況下,Calendar 會把所有月份像攤煎餅一樣平鋪展示,雖然一目了然,但月份太多時就像在資訊海洋裡游泳 🏊♀️。
這時候 switch-mode 屬性就像給日曆裝上了「傳送門」 ⚡!設定後會出現年月切換按鈕,讓使用者像坐火箭一樣快速跳轉到目標月份。再也不用在茫茫日期海中慢慢尋找啦!
選擇單個日期
就像在日曆上用紅筆圈出重要日子一樣簡單 🔴!這個例子展示了如何把 Calendar 和 Cell 單元格完美搭配使用。
使用者輕輕一點,日期就被「捕獲」了,然後 confirm 事件會像信使一樣把好消息傳遞給你 📮。整個過程行雲流水,就像點餐一樣簡單!
import { ref } from'vue';
export default { setup() {
const date = ref('');
const show = ref(false);
const formatDate = (date) => {
return`${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;
};
const onConfirm = (value) => {
show.value = false;
date.value = formatDate(value);
};
return { date, show, onConfirm, };
}
};選擇多個日期
有時候一個日期怎麼夠用?比如你要安排一週的健身計劃,或者標記所有的節假日 🎉!
把 type 設定為 multiple,Calendar 就變身成了「貪心的收集家」 🗂️,可以一口氣選擇多個日期。這時候 confirm 事件會貼心地把所有選中的日期打包成一個陣列送給你,就像收集郵票一樣有序!
import { ref } from'vue';
export default {
setup() {
const text = ref('');
const show = ref(false);
const onConfirm = (dates) => { show.value = false; text.value = `選擇了 ${dates.length} 個日期`; };
return { text, show, onConfirm, };
},
};選擇日期區間
計劃一次完美的假期?安排一個專案週期?這時候你需要的不是零散的日期,而是一個完整的時間段 🌅🌇!
將 type 設定為 range,Calendar 就像一個專業的時間管家,幫你圈定一個完整的日期區間。選擇完成後,它會很貼心地把開始日期和結束日期分別放在陣列的第一位和第二位,就像給你一張標註了起點和終點的地圖 🗺️!
import { ref } from'vue';
export default {
setup() {
const date = ref('');
const show = ref(false);
const formatDate = (date) => `${date.getMonth() + 1}/${date.getDate()}`;
const onConfirm = (values) => { const [start, end] = values; show.value = false; date.value = `${formatDate(start)} - ${formatDate(end)}`; };
return { date, show, onConfirm, };
},
};Tips: 默认情况下,日期区间的起止时间不能为同一天,可以通过设置 allow-same-day 属性来允许选择同一天。
快捷選擇
有時候,確認按鈕就像是多餘的「門檻」 🚪。想要更加流暢的體驗?
把 show-confirm 設定為 false,確認按鈕就會悄悄隱身!這樣使用者一選擇日期,confirm 事件就會立刻響應,就像閃電一樣快 ⚡。特別適合那些追求極簡體驗的場景!
自定義顏色
誰說日曆只能是藍色的?你的品牌色是什麼,日曆就可以是什麼顏色 🎨!
透過 color 屬性,你可以讓日曆穿上任何你喜歡的「外衣」。無論是選中的日期還是底部的按鈕,都會乖乖地換上新裝。就像給日曆做了一次時尚改造 💄!
自定義日期範圍
有些日期是「禁區」 🚫,有些日期才是「黃金時段」 ✨!
透過 min-date 和 max-date 這對「守門員」,你可以精確控制使用者能選擇的日期範圍。就像給日曆畫了一個圈,圈外的日期都變成了「看得見摸不著」的存在。特別適合預訂系統、活動報名等有時間限制的場景!
import { ref } from'vue';
export default { setup() { const show = ref(false);
return { show, minDate: newDate(2010, 0, 1), maxDate: newDate(2010, 0, 31), }; }, };自定義按鈕文字
「確認」這個詞太普通了?想要更有個性的按鈕文案 💬?
用 confirm-text 可以給按鈕換個更酷的說法,比如「就是它了!」、「選定」、「Go!」等等。而 confirm-disabled-text 則負責在按鈕不可用時給使用者一個貼心的提示。就像給按鈕配了專屬的「台詞」 🎭!
自定義日期文案
想讓你的日曆更有「人情味」?比如在特殊日子顯示「生日快樂」、「發工資」、「週末愉快」 🎂?
formatter 函數就像是日曆的「化妝師」 💄,它可以給每一個日期格子添加個性化的內容。無論是頂部提示、底部說明,還是改變日期文字本身,都能輕鬆搞定。讓你的日曆從此告別單調,變得生動有趣!
exportdefault { setup() { constformatter = (day) => { const month = day.date.getMonth() + 1; const date = day.date.getDate(); if (month === 5) { if (date === 1) { day.topInfo = '勞動節'; } elseif (date === 4) { day.topInfo = '青年節'; } elseif (date === 11) { day.text = '今天'; } } if (day.type === 'start') { day.bottomInfo = '入住'; } elseif (day.type === 'end') { day.bottomInfo = '離店'; } return day; }; return { formatter, }; }, };自定義彈出位置
透過 position 屬性自定義彈出層的彈出位置,可選值為 top、left、right。
日期區間最大範圍
選擇日期區間時,可以透過 max-range 屬性來指定最多可選天數,選擇的範圍超過最多可選天數時,會彈出相應的提示文案。
自定義週起始日
透過 first-day-of-week 屬性設定一週從哪天開始。
平鋪展示
將 poppable 設定為 false,日曆會直接展示在頁面內,而不是以彈層的形式出現。
API
Props
| 參數 | 說明 | 類型 | 預設值 |
|---|---|---|---|
| type | 選擇類型:single 表示選擇單個日期,multiple 表示選擇多個日期,range 表示選擇日期區間 | string | single |
switch-mode v4.9.0 | 切換模式:none 平鋪展示所有月份,不展示切換按鈕,month 支援按月切換,展示上個月/下個月按鈕,year-month 支援按年切換,也支援按月切換,展示上一年/下一年,上個月/下個月按鈕 | string | none |
| title | 日曆標題 | string | 日期選擇 |
| color | 主題色,對底部按鈕和選中日期生效 | string | #1989fa |
| min-date | 可選擇的最小日期 | Date | switch-mode 為 none 時為當前日期 | | max-date | 可選擇的最大日期 | Date | switch-mode 為 none 時為當前日期的六個月後 | | default-date | 預設選中的日期,type 為 multiple 或 range 時為陣列,傳入 null 表示預設不選擇 | Date | Date[] | null | 今天 | | row-height | 日期行高 | number | string | 64 | | formatter | 日期格式化函數 | (day: Day) => Day | - | | poppable | 是否以彈層的形式展示日曆 | boolean | true | | lazy-render | 是否只渲染可視區域的內容 | boolean | true | | show-mark | 是否顯示月份背景浮水印 | boolean | true | | show-title | 是否展示日曆標題 | boolean | true | | show-subtitle | 是否展示日曆副標題(年月) | boolean | true | | show-confirm | 是否展示確認按鈕 | boolean | true | | readonly | 是否為唯讀狀態,唯讀狀態下不能選擇日期 | boolean | false | | confirm-text | 確認按鈕的文字 | string | 確認 | | confirm-disabled-text | 確認按鈕處於禁用狀態時的文字 | string | 確認 | | first-day-of-week | 設定週起始日 | 0-6 | 0 |
Calendar Poppable Props
當 Calendar 的 poppable 為 true 時,支援以下 props:
| 參數 | 說明 | 類型 | 預設值 |
|---|---|---|---|
| v-model:show | 是否顯示日曆彈窗 | boolean | false |
| position | 彈出位置,可選值為 top``right``left | string | bottom |
| round | 是否顯示圓角彈窗 | boolean | true |
| close-on-popstate | 是否在頁面回退時自動關閉 | boolean | true |
| close-on-click-overlay | 是否在點擊遮罩層後關閉 | boolean | true |
| safe-area-inset-top | 是否開啟頂部安全區適配 | boolean | false |
| safe-area-inset-bottom | 是否開啟底部安全區適配 | boolean | true |
| teleport | 指定掛載的節點,等同於 Teleport 元件的 to 屬性 | string | Element | - |
Calendar Range Props
當 Calendar 的 type 為 range 時,支援以下 props:
| 參數 | 說明 | 類型 | 預設值 |
|---|---|---|---|
| max-range | 日期區間最多可選天數 | *number | string* |
| range-prompt | 範圍選擇超過最多可選天數時的提示文案 | string | 最多選擇 xx 天 |
| show-range-prompt | 範圍選擇超過最多可選天數時,是否展示提示文案 | boolean | true |
| allow-same-day | 是否允許日期範圍的起止時間為同一天 | boolean | false |
Calendar Multiple Props
當 Calendar 的 type 為 multiple 時,支援以下 props:
| 參數 | 說明 | 類型 | 預設值 |
|---|---|---|---|
| max-range | 日期最多可選天數 | *number | string* |
| range-prompt | 選擇超過最多可選天數時的提示文案 | string | 最多選擇 xx 天 |
Day 數據結構
日曆中的每個日期都對應一個 Day 物件,透過formatter屬性可以自訂 Day 物件的內容
| 鍵名 | 說明 | 類型 |
|---|---|---|
| date | 日期對應的 Date 物件 | Date |
| type | 日期類型,可選值為 selected、start、middle、end、disabled、start-end、multiple-selected、multiple-middle、placeholder | string |
| text | 中間顯示的文字 | string |
| topInfo | 上方的提示資訊 | string |
| bottomInfo | 下方的提示資訊 | string |
| className | 額外類名 | string |
Events
| 事件名 | 說明 | 回調參數 |
|---|---|---|
| select | 點擊並選中任意日期時觸發 | *value: Date |
| confirm | 日期選擇完成後觸發,若 show-confirm 為 true,則點擊確認按鈕後觸發 | *value: Date |
| open | 打開彈出層時觸發 | - |
| close | 關閉彈出層時觸發 | - |
| opened | 打開彈出層且動畫結束後觸發 | - |
| closed | 關閉彈出層且動畫結束後觸發 | - |
| unselect | 當日曆元件的 type 為 multiple 時,取消選中日期時觸發 | value: Date |
| month-show | 當某個月份進入可視區域時觸發(switch-mode 為 none 時生效) | { date: Date, title: string } |
| over-range | 範圍選擇超過最多可選天數時觸發 | - |
| click-subtitle | 點擊日曆副標題時觸發 | event: MouseEvent |
click-disabled-date v4.7.0 | 點擊禁用日期時觸發 | *value: Date |
click-overlay v4.9.16 | 點擊遮罩層時觸發 | event: MouseEvent |
| panel-change | 日曆面板切換時觸發(switch-mode 不為 none 時生效) | { date: Date } |
Slots
| 名稱 | 說明 | 參數 |
|---|---|---|
| title | 自訂標題 | - |
| subtitle | 自訂日曆副標題 | { text: string, date?: Date } |
month-title v4.0.9 | 自訂每個月份的小標題 | { text: string, date: Date } |
| footer | 自訂底部區域內容 | - |
| confirm-text | 自訂確認按鈕的內容 | { disabled: boolean } |
| top-info | 自訂日期上方的提示資訊 | day: Day |
| bottom-info | 自訂日期下方的提示資訊 | day: Day |
| text | 自訂日期內容 | day: Day |
| prev-month | 自訂上個月按鈕 | { disabled: boolean } |
| prev-year | 自訂上一年按鈕 | { disabled: boolean } |
| next-month | 自訂下個月按鈕 | { disabled: boolean } |
| next-year | 自訂下一年按鈕 | { disabled: boolean } |
方法
透過 ref 可以獲取到 Calendar 實例並呼叫實例方法,詳見元件實例方法。
| 方法名 | 說明 | 參數 | 返回值 |
|---|---|---|---|
| reset | 將選中的日期重置到指定日期,未傳參時會重置到預設日期 | *date?: Date | Date[]* |
| scrollToDate | 滾動到某個日期 | date: Date | - |
| getSelectedDate | 獲取選中的日期 | - | *Date |
類型定義
元件匯出以下類型定義:
import type { CalendarSwitchMode, CalendarType, CalendarProps, CalendarDayItem, CalendarDayType, CalendarInstance, } from'vant';CalendarInstance 是元件實例的類型,用法如下:
import { ref } from'vue';
import type { CalendarInstance } from'vant';
const calendarRef = ref<CalendarInstance>();
calendarRef.value?.reset();主題定制
樣式變數
元件提供了下列 CSS 變數,可用於自訂樣式,使用方法請參考 ConfigProvider 元件。
| 名稱 | 預設值 | 描述 |
|---|---|---|
| --van-calendar-background | var(--van-background-2) | - |
| --van-calendar-popup-height | 80% | - |
| --van-calendar-header-shadow | 0 2px 10px rgba(125, 126, 128, 0.16) | - |
| --van-calendar-header-title-height | 44px | - |
| --van-calendar-header-title-font-size | var(--van-font-size-lg) | - |
| --van-calendar-header-subtitle-font-size | var(--van-font-size-md) | - |
| --van-calendar-header-action-width | 28px | - |
| --van-calendar-header-action-color | var(--van-text-color) | - |
| --van-calendar-header-action-disabled-color | var(--van-text-color-3) | - |
| --van-calendar-weekdays-height | 30px | - |
| --van-calendar-weekdays-font-size | var(--van-font-size-sm) | - |
| --van-calendar-month-title-font-size | var(--van-font-size-md) | - |
| --van-calendar-month-mark-color | fade(var(--van-gray-2), 80%) | - |
| --van-calendar-month-mark-font-size | 160px | - |
| --van-calendar-day-height | 64px | - |
| --van-calendar-day-font-size | var(--van-font-size-lg) | - |
| --van-calendar-day-margin-bottom | 4px | - |
| --van-calendar-day-disabled-color | var(--van-text-color-3) | - |
| --van-calendar-range-edge-color | var(--van-white) | - |
| --van-calendar-range-edge-background | var(--van-primary-color) | - |
| --van-calendar-range-middle-color | var(--van-primary-color) | - |
| --van-calendar-range-middle-background-opacity | 0.1 | - |
| --van-calendar-selected-day-size | 54px | - |
| --van-calendar-selected-day-color | var(--van-white) | - |
| --van-calendar-selected-day-background | var(--van-primary-color) | - |
| --van-calendar-info-font-size | var(--van-font-size-xs) | - |
| --van-calendar-info-line-height | var(--van-line-height-xs) | - |
| --van-calendar-confirm-button-height | 36px | - |
| --van-calendar-confirm-button-margin | 7px 0 | - |
常見問題
如何在 formatter 中使用非同步返回的資料?
如果需要在 formatter 中使用非同步返回的資料,可以使用計算屬性動態建立 formatter 函數,範例如下:
const asyncData = ref();
const formatter = computed(() => { if (!asyncData.value) { return(day) => day; }
return(day) => { day.bottomInfo = asyncData.value; return day; }; });
setTimeout(() => { asyncData.value = '後端文案'; }, 3000);在 iOS 系統上初始化元件失敗?
如果你遇到了在 iOS 上無法渲染元件的問題,請確認在建立 Date 物件時沒有使用new Date('2020-01-01')這樣的寫法,iOS 不支援以中劃線分隔的日期格式,正確寫法是new Date('2020/01/01')。
對此問題的詳細解釋:stackoverflow。
或者,你應該採用一種在各個系統和瀏覽器上相容性更好的寫法:new Date(2020, 0, 1),但是需要注意的是,月份是從 0 開始的。
🎯 最佳實踐
💡 使用場景推薦
Calendar 元件就像一把萬能鑰匙,適用於各種需要日期選擇的場景:
🏨 酒店預訂系統
// 設置入住和離店日期,限制最少住宿天數
<van-calendar
type="range"
:min-date="new Date()"
:max-range="30"
:formatter="hotelFormatter"
/>📅 會議室預訂
// 只允許選擇工作日,排除週末
<van-calendar
type="multiple"
:formatter="workdayFormatter"
:min-date="new Date()"
/>🎂 生日提醒設置
// 單日選擇,可以選擇過去的日期
<van-calendar
type="single"
:max-date="new Date()"
title="選擇生日"
/>⚡ 效能最佳化小貼士
- 合理使用 lazy-render:對於月份較多的日曆,開啟懶渲染可以顯著提升效能
- 避免頻繁更新 formatter:formatter 函數應該保持穩定,避免在每次渲染時建立新函數
- 使用 switch-mode:當需要展示大量月份時,使用切換模式而不是平鋪模式
🎨 設計建議
- 顏色搭配:選擇與品牌色調和諧的主題色,避免過於鮮豔的顏色
- 文案友好:使用使用者熟悉的語言,比如「選擇日期」比「Date Selection」更親切
- 回饋及時:在使用者操作後及時給予視覺回饋,提升互動體驗
🔗 相關元件
- DatePicker 日期選擇器 - 適合精確到時分秒的日期時間選擇
- Cell 單元格 - 常與 Calendar 搭配使用,作為觸發器
- Popup 彈出層 - Calendar 的彈窗模式基於此元件實現
- Picker 選擇器 - 另一種日期選擇的互動方式
📚 延伸閱讀
💡 小提示:Calendar 元件不僅僅是一個日期選擇工具,更是提升使用者體驗的重要元件。合理運用它的各種特性,可以讓你的應用更加人性化和專業!