# 🏅 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 代表週二,以此類推。

下方範例示範改成從週一開始顯示 :
```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>
```
設定後佈局呈現的畫面如下,水平方向代表 「列」的設定,垂直方向代表 「欄」的設定 :

如果我們想要根據不同螢幕尺寸調整欄數和列數,可以使用 [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` 的對齊位置。

`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://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]()|
-->