# 🏅 Day 23 - VCalendar 套件運用 ## 今日學習目標 - 學習將 VCalendar 套件整合進 Nuxt3 - 學習在 Nuxt3 使用 VCalendar 日曆套件 ## 安裝 VCalendar 開啟終端機,輸入以下指令安裝 VCalendar v3.1.2 以及 @popperjs/core 。 ```jsx npm install v-calendar@3.1.2 @popperjs/core ``` > ❗ 需注意 : Vue 版本需為 3.2 以上, @popperjs/core 的版本需為 2.0 以上。 ## 匯入 VCalendar 套件的功能 安裝完成後,參考 [官方文件](https://vcalendar.io/getting-started/installation.html#use-plugin) 的安裝步驟選擇合適的匯入方式。這裡將示範使用 Nuxt 插件 ( Plugin ) 的方式來全域註冊元件。 ### 步驟一. 建立插件 首先,在 `/plugins/vcalendar.js` 建立插件,透過 Nuxt 插件來加入 VCalendar 套件。 ```jsx // plugins/vcalendar.js export default defineNuxtPlugin((nuxtApp) => {}); ``` ### 步驟二. 匯入元件 匯入 VCalendar 套件的 CSS 檔案、`DatePicker`、`Calendar`元件以及 `setupCalendar` 。 ```jsx // plugins/vcalendar.js import { setupCalendar, DatePicker, Calendar } from "v-calendar"; import "v-calendar/style.css"; ``` ### 步驟三. 註冊元件 使用 `nuxtApp.vueApp.component` 將 `VCalendar` 和 `VDatePicker` 註冊為全域元件,並將 `setupCalendar` 加入 Vue 全域插件中。 ```jsx // plugins/vcalendar.js import { setupCalendar, DatePicker, Calendar } from "v-calendar"; import "v-calendar/style.css"; export default defineNuxtPlugin((nuxtApp) => { nuxtApp.vueApp.use(setupCalendar, {}); // 啟用 setupCalendar 預設設定 nuxtApp.vueApp.component("VCalendar", Calendar); // 註冊 VCalendar 為全域元件 nuxtApp.vueApp.component("VDatePicker", DatePicker); // 註冊 VDatePicker 為全域元件 }); ``` ### 步驟四. 使用元件 最後,在 `/pages/index.vue` 使用元件。`VCalendar` 用於呈現日曆,不具備日期選取的功能;而 `VDatePicker` 基於 `VCalendar` 元件提供日期選取功能,因此需要加入 v-model 綁定時間格式的響應性資料,以下是基本用法: ```jsx // /pages/index.vue <script setup> const date = ref(new Date()); </script> <template> <VCalendar /> <VDatePicker v-model="date"/> </template> ``` ## 設定 VDatePicker 元件 以下將介紹 VDatePicker 元件的功能。VDatePicker 元件包含 VCalendar 元件的 [所有功能](https://vcalendar.io/calendar/api.html) ,因此在教學中也會使用到 VCalendar 的參數。 ### 選取日期範圍 使用 [v-model.range 指令](https://vcalendar.io/examples/date-picker-time-rules.html#date-range-with-rules) ,傳入包含 `start`( 開始日期 ) 與 `end` ( 結束日期 ) 屬性的響應性物件 ,提供日期範圍選取功能: ```html <!-- pages/datepicker.vue --> <script setup> // 預設選取 2024 年 12 月 24 日至 2024 年 12 月 25 日之間 const date = ref({ start: new Date(2024, 11, 24), end: new Date(2024, 11, 25), }); </script> <template> <h2>v-model.range 選取時間範圍</h2> <VDatePicker v-model.range="date" /> </template> ``` ### 設定日期選取的顏色 加入 [color 屬性](https://vcalendar.io/calendar/theme.html#colors) 設定日期選取的顏色。可以使用 [內建](https://vcalendar.io/calendar/theme.html#colors) 的 10 種顏色或是透過 [CSS 變數](https://vcalendar.io/calendar/theme.html#css-variables) 自定義顏色。以下示範如何自定義顏色: 1. 設定放置 CSS 變數的樣式 .vc-sky-blue 。 2. 在 VDatePicker 元件的 color 屬性填入 `sky-blue` ( `vc-sky-blue` 的選擇器名稱去掉 `.vc-` 前綴 ) 3. 因為是在 datepicker.vue 頁面覆蓋 VCalendar 元件的樣式,所以需要在外層加入一層容器 .date-picker ,並使用 [深度選擇器 **`:deep()`**](https://zh-hk.vuejs.org/api/sfc-css-features#child-component-root-elements)  來修改內部的 CSS 樣式。 ```html <!-- pages/datepicker.vue --> <script setup> const date = ref({ start: new Date(2024, 11, 24), end: new Date(2024, 11, 25), }); </script> <template> <div class="date-picker"> <p>使用自訂顏色</p> <VDatePicker v-model.range="date" color="sky-blue" /> </div> </template> <style scoped> .date-picker :deep(.vc-sky-blue) { --vc-accent-50: #f0f9ff; --vc-accent-100: #e0f2fe; --vc-accent-200: #bae6fd; --vc-accent-300: #7dd3fc; --vc-accent-400: #38bdf8; --vc-accent-500: #0ea5e9; --vc-accent-600: #0284c7; --vc-accent-700: #0369a1; --vc-accent-800: #075985; --vc-accent-900: #0c4a6e; } </style> ``` ### 設定日期解析的格式 加入 [mask 屬性](https://vcalendar.io/i18n/masks.html#masks) 設定日期解析的格式。以 `title` 和 `modelValue` 為例,`title` 用於修改日曆標題的顯示格式;而 `modelValue` 則用於設定日曆選取的時間格式,需要搭配 `v-model.string` 指令使用。 例如,下方的設定將 `masks` 變數中的日曆標題格式設定為 “西元 YYYY 年 MM 月”,同時將日曆選取的時間格式設定為 "YYYY-MM-DD"(年-月-日)。這樣在初始化時,`isoDate` 變數將以 YYYY-MM-DD 格式綁定,並確保在選取日期時保持相同的格式。 ```jsx // pages/datepicker.vue <script setup> const isoDate = ref({ start: new Date(2024, 11, 24).toISOString().split("T")[0], // YYYY-MM-DD 格式 end: new Date(2024, 11, 25).toISOString().split("T")[0], // YYYY-MM-DD 格式 }); const masks = ref({ title: "西元 YYYY 年 MM 月", modelValue: "YYYY-MM-DD", }); </script> <template> <VDatePicker v-model.range.string="isoDate" :masks="masks" /> </template> ``` ### 設定日曆的起始天數 加入 [first-day-of-week 屬性](https://vcalendar.io/i18n/locales.html) 設定日曆顯示的第一天。此屬性用於調整下圖紅框處的排序,可以填入 1 到 7 的數字,數字 1 代表週日,2 代表週一,3 代表週二,以此類推。 ![day23-1](https://hackmd.io/_uploads/B1nNRaTf1l.png) 下方範例示範改成從週一開始顯示 : ```html <!-- pages/datepicker.vue --> <script setup> const date = ref({ start: new Date(2024, 11, 24), end: new Date(2024, 11, 25), }); const weekNumber = ref(2); </script> <template> <!-- 日曆從週一開始顯示 --> <VDatePicker v-model.range="date" :first-day-of-week="weekNumber" /> </template> ``` ### 限制日期可選取範圍 加入 [min-date 和 max-date 屬性](https://vcalendar.io/calendar/navigation.html#min-max-dates) 限制日期的選取範圍。例如下方範例將範圍限制在 2024 年 12 月 5 日至 2024 年 12 月 29日之間 : ```html <!-- pages/datepicker.vue --> <script setup> // 預設選取 2024 年 12 月 24 日至 2024 年 12 月 25 日之間 const date = ref({ start: new Date(2024, 11, 24), end: new Date(2024, 11, 25), }); // 最早可選取的日期 ( 2024 年 12 月 5 日 ) const minDate = ref(new Date(2024, 11, 5)); // 最晚可選取的日期 ( 2024 年 12 月 29 日 ) const maxDate = ref(new Date(2024, 11, 29)); </script> <template> <VDatePicker v-model.range="date" :min-date="minDate" :max-date="maxDate" /> </template> ``` ### 多欄 ( **Columns ) 與**多列 ( **Rows )** 佈局 加入 [:row 與 :columns 屬性](https://vcalendar.io/calendar/layouts.html#multiple-rows-columns) 並傳入數字來建立多欄和多列的日曆佈局,例如下方範例的 :columns="3" 與 :rows="2” 建立了 3 欄 2 列的佈局 : ```html <!-- pages/datepicker.vue --> <script setup> const date = ref({ start: new Date(2024, 11, 24), end: new Date(2024, 11, 25), }); </script> <template> <!-- 使用屬性綁定 ( v-bind ) 的 :columns 與 :row 屬性分別傳入數字 3 和數字 2 ,建立了 3 欄 2列 的佈局 --> <VDatePicker v-model.range="date" :columns="3" :rows="2" /> </template> ``` 設定後佈局呈現的畫面如下,水平方向代表 「列」的設定,垂直方向代表 「欄」的設定 : ![day23-4](https://hackmd.io/_uploads/BksKR6TGkl.png) 如果我們想要根據不同螢幕尺寸調整欄數和列數,可以使用 [vue-screen-utils](https://github.com/nathanreyes/vue-screen-utils) 套件來實作。首先開啟終端機輸入以下安裝指令 : ```bash npm install vue-screen-utils ``` 安裝後,在頁面元件匯入 `vue-screen-utils` 套件的 `useScreens` 方法。傳入物件參數來設定螢幕尺寸 ( 如 `{ md: '768px' }` ) ,並解構出 `mapCurrent` 方法提供給 :columns 與 :rows 屬性設定欄數**與**行數。 ```jsx // 匯入 useScreens 方法 import { useScreens } from 'vue-screen-utils'; // 設定螢幕尺寸並解構 mapCurrent const { mapCurrent } = useScreens({ // 螢幕尺寸為 mobile-first ( 從小尺寸到大尺寸 ) xs: '0px', // 等同於 (min-width: 0px) sm: '640px', // 等同於 (min-width: 640px) md: '768px', // 等同於 (min-width: 768px) lg: '1024px', // 等同於 (min-width: 1024px) }); // 使用 mapCurrent 設定螢幕尺寸對應的欄數 // { lg: 2 } => 在 lg 尺寸 ( 1024px ) 以上使用 2 欄 // 1 => 沒有配對到螢幕尺寸時,預設使用 1 欄 const columns = mapCurrent({ lg: 2 }, 1); ``` 接下來在 `pages/datepicker.vue` 頁面實做以下需求的範例 : - 767px 以下的螢幕尺寸呈現 1 欄與 1 列 - 768px ~ 1199px 之間的螢幕尺寸呈現 2 欄與 2 列 - 1200px 以上的螢幕尺寸呈現 3 欄與 2 列 ```html <!-- pages/datepicker.vue --> <script setup> import { useScreens } from "vue-screen-utils"; const date = ref({ start: new Date(2024, 11, 24), end: new Date(2024, 11, 25), }); const { mapCurrent } = useScreens({ md: "768px", // 等同於 (min-width: 768px) lg: "1200px", // 等同於 (min-width: 1024px) }); // 767px 以下 1 欄,768px 以上 2 欄,1200px 以上 3 欄 const columns = mapCurrent({ md: 2, lg: 3 }, 1); // 767px 以下 1 列,768px 以上 2 列,1200px 以上 2 列 const rows = mapCurrent({ md: 2, lg: 2 }, 1); </script> <template> <ClientOnly> <p>欄數:{{ columns }} 列數:{{ rows }}</p> <VDatePicker v-model.range="date" :columns="columns" :rows="rows" /> </ClientOnly> </template> ``` ### 與父層容器等寬 加入 [expanded 屬性](https://vcalendar.io/calendar/layouts.html#full-width) 讓 VDatePicker 元件填滿父層容器的寬度。 ```html <!-- pages/datepicker.vue --> <template> <div class="container mt-5"> <div class="row justify-content-center"> <div class="col-lg-10"> <!-- VDatePicker與父層元素等寬 --> <VDatePicker v-model.range="date" expanded /> </div> </div> </div> </template> ``` 也可以透過屬性綁定 ( v-bind ) 的 `:expanded` ,傳入布林值 `true` 或 `false` 決定是否要加入 `expanded` 屬性,使元件與父層容器等寬,例如: ```html <!-- pages/datepicker.vue --> <script setup> // true -> 加入 expanded 屬性 // false -> 不加入 expanded 屬性 const isExpended = ref(true); </script> <template> <div class="container mt-5"> <div class="row justify-content-center"> <div class="col-lg-10"> <!--因為 :expanded 傳入的 isExpended 資料的值為 true,所以 VDatePicker 與父層元素等寬 --> <VDatePicker v-model.range="date" :expanded="isExpended" /> </div> </div> </div> </template> ``` ### 調整日曆標題的對齊 加入 [title-position 屬性](https://vcalendar.io/calendar/layouts.html#title-positioning) 更改日曆頂部月份年份標題的對齊方式。此屬性將調整下圖紅框處 `12月 2024` 的對齊位置。 ![day23-5](https://hackmd.io/_uploads/S12GJApfJe.png) `title-position` 屬性的預設值是 `center`(置中),可以設定 `title-position="left"` 將標題靠左對齊,或者使用 `title-position="right"` 將標題靠右對齊: ```jsx // pages/datepicker.vue <template> <!-- 預設為置中 ( center ) --> <VDatePicker v-model.range="date" /> <!-- 調整為靠左對齊 ( left ) --> <VDatePicker v-model.range="date" title-position="left" /> <!-- 調整為靠右對齊 ( right ) --> <VDatePicker v-model.range="date" title-position="right" /> </template> ``` <br> > 今日學習的[範例 Code - 資料夾: day23-vcalendar-example](https://github.com/hexschool/nuxt-daily-tasks-2024) ## 題目 請 fork 這一份 [模板](https://github.com/jasonlu0525/nuxt3-live-question/tree/day23-vcalendar) ,完成以下條件 : - 模板中的 `package.json` 已包含 VCalendar 套件,請將它安裝並整合到 Nuxt3 中。 - 請在 `/pages/index.vue` 作答,完成以下日曆選取功能的設定 : - 在 767px 以下的手機版型顯示 1 欄 1 列 ;在 768px 以上的平板與電腦版型顯示 2 欄 1 列。此功能需要使用 [vue-screen-utils](https://github.com/nathanreyes/vue-screen-utils) 套件進行響應式調整,該套件已在 `package.json` 中提供。 - 在 767px 以下的手機版型,日曆元件的寬度呈現滿寬。 - 日曆標題在 767px 以下置中對齊,768px 以上靠左對齊。 - 日曆標題的日期格式需顯示`「西元 YYYY 年 MM 月」`。選取日期的格式需調整為 `"YYYY-MM-DD"`(年-月-日)。 - 日曆初始化時,預設的起始日期為當天,結束日期為當天的下一天。例如,當前日期為 12 月 1 日,則預設起始日期為 12 月 1 日,結束日期為 12 月 2 日。取得下一天日期的方法可以考這一篇 [文章](https://www.cythilya.tw/2017/05/17/javascript-date-add-days/) 。 - 限制選取日期範圍,最早可選當天,最晚可選下一年的同一天。例如,當前日期是 2024 年 12 月 1 日,最早可選 2024 年 12 月 1 日,最晚可選 2025 年 12 月 1 日。 - 使用 CSS 變數覆蓋 VCalendar 套件的預設樣式,將選取日期的背景顏色修改為 `#000000`,選取區間的背景顏色修改為 `#f9f9f9`。 ## 回報流程 將答案上傳至 GitHub 並複製 GitHub repo 連結貼至底下回報就算完成了喔 ! 解答位置請參考下圖(需打開程式碼的部分觀看) ![](https://i.imgur.com/vftL5i0.png) <!-- 解答 : https://github.com/jasonlu0525/nuxt3-live-answer/tree/day23-vcalendar --> 回報區 --- | # | Discord | Github / 答案 | |---|---|---| | 1 | 眼睛 | [Github](https://github.com/Thrizzacode/nuxt3-live-question/tree/day23-vcalendar) | | 2 | dragon |[Github](https://github.com/peterlife0617/2024-nuxt-training-homework01/tree/feature/day23)| | 3 | Steven |[Github](https://github.com/y7516552/nuxt3-live-question/tree/day23)| | 4 | Johnson |[Github](https://github.com/tttom3669/2024_hex_nuxt_daily/tree/day23-vcalendar)| | 5 | Rocky |[Github](https://github.com/WuRocky/Nuxt-Day23-VCalendar)| | 6 | LinaChen |[Github](https://github.com/Lina-SHU/nuxt3-live-question)| | 7 | hsin yu |[Github](https://github.com/dogwantfly/nuxt3-daily-task-live-question/tree/day23-vcalendar)| <!-- | --- | --- |[Github]()| -->