基于vue3+TypeScript實現(xiàn)一個簡易的Calendar組件
功能分析
目前學(xué)到功能有以下幾點
- 日歷的日期展示(核心組件,計算當(dāng)月天數(shù),第一天是星期幾,以及上下月日期的連接)
- 切換月份聯(lián)動日期修改
- 定位當(dāng)前日期
功能實現(xiàn)
初始化
使用 vue 官網(wǎng)提供的命令即可 npm create vue@latest
,要把使用 ts 的選項勾選上,因為這里我們用到了 ts
組件分析
劃分了兩個組件 HeaderCom 和 CalendarMonth
- HeaderCom 用于按鈕切換日期展示,以及定位當(dāng)前日期
- CalendarMonth 用于日期的展示
兩者的父組件為 CalendarCom組件
具體操作
安裝 dayjs 日期庫,這里使用 dayjs 提供的 api 來獲取時間 npm i dayjs
, 安裝 sass 預(yù)處理器 npm i sass -D
我這里選擇直接在 views 中新建了 Calendar 文件夾,所以后面說的 Calendar 文件夾 都是對應(yīng) src/views/Calendar
Calendar / CalendarCom.vue
新建CalendarCom.vue,日歷組件的整體,包含了上面所說的兩個子組件,具體代碼如下
<template> <div class="calendar"> </div> </template> <script setup lang="ts"> </script> <style scoped> @import './index.scss'; </style>
新建 Calendar / index.scss 樣式文件 引入樣式
.calendar { width: 100%; }
Calendar / CalendarMonth.vue
我們首先來實現(xiàn)在展示日期組件的功能吧,借個圖來分析一下,這個組件分為 星期、日期兩個部分,首先來實現(xiàn)下星期的渲染
<template> <div class="calendar-month"> <div class="calendar-month-week-list"> <div v-for="(item, index) in weekList" :key="index" class="calendar-month-week-list-item"> {{ item }} </div> </div> </div> </template> <script setup lang="ts"> const weekList = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'] </script> <style scoped> @import './index.scss'; </style>
.calendar-month { &-week-list { display: flex; width: 100%; box-sizing: border-box; border-bottom: 1px solid #ccc; &-item { padding: 20px 16px; text-align: left; color: #7d7d7d; flex: 1; } } }
父組件引入該組件
<template> <div> <CalendarCom /> </div> </template> <script setup lang="ts"> import CalendarCom from './Calendar/CalendarCom.vue' </script> <style scoped></style>
此時日期組件的頂部就渲染好了
接下來就是日期渲染了,日期渲染就是拿到第一天的日期,計算是星期幾,然后把前面空余的數(shù)據(jù)填補成上個月的末尾日期,后面超出的數(shù)據(jù)填補成下個月月初日期
這個時候有小伙伴就會問,怎么獲取第一天,又怎么獲取前一個月的時間和后一個月的時間呢?別著急呀,這些功能 dayjs 都給我們提供好了方法了,當(dāng)然用 Date 對象也可以獲取這些
// test.js import dayjs from 'dayjs' console.log(dayjs('2024-04-29').daysInMonth()) // 30 console.log(dayjs('2024-04-29').startOf('month').format('YYYY-MM-DD')) // 2024-04-01 console.log(dayjs('2024-04-29').endOf('month').format('YYYY-MM-DD')) // 2024-04-30 console.log(dayjs('2024-04-29').startOf('month').day()) // 1 星期一
測試這些方法,我們知道2024年4月的天數(shù),第一天是星期幾,起始日期,結(jié)束日期
我們在CalendarMonth 組件中實現(xiàn)一個 getAllDay 的方法,返回一個日期數(shù)組,并且給 Calendar 組件添加一個 value 屬性,傳入一個初始時間,獲取到要展示的日期后渲染到頁面即可
<!-- HomeView --> <template> <div> <CalendarCom :value="dayjs('2024-05-01')" /> </div> </template> <script setup lang="ts"> import dayjs from 'dayjs' import CalendarCom from './Calendar/CalendarCom.vue' </script> <style scoped></style>
<!-- CalendarCom --> <template> <div class="calendar"> <CalendarMonth :value="props.value" /> </div> </template> <script setup lang="ts"> import type { Dayjs } from 'dayjs' import CalendarMonth from './CalendarMonth.vue' export interface CalendarProps { value: Dayjs } const props = defineProps<CalendarProps>() </script> <style scoped> @import './index.scss'; </style>
<!-- CalendarMonth --> <template> <div class="calendar-month"> <!-- 星期列表 --> <div class="calendar-month-week-list"> <div v-for="(item, index) in weekList" :key="index" class="calendar-month-week-list-item"> {{ item }} </div> </div> <!-- 日期展示 --> <div class="calendar-month-body"> <div class="calendar-month-body-row"> <div :class="[ 'calendar-month-body-cell', item.currentMonth ? 'calendar-month-body-cell-current' : '' ]" v-for="(item, index) in alldays" :key="index" > {{ item.date.date() }} </div> </div> </div> </div> </template> <script setup lang="ts"> import type { Dayjs } from 'dayjs' import type { CalendarProps } from './CalendarCom.vue' import { ref } from 'vue' const weekList = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'] interface CalendarMonthProps extends CalendarProps {} const props = defineProps<CalendarMonthProps>() const { value } = props /** * 獲取日期數(shù)據(jù)方法 * @param date 傳入日期 */ const getAllDay = (date: Dayjs) => { const startDate = date.startOf('month') const day = startDate.day() // 當(dāng)月第一天是星期幾 const daysInfo = new Array<{ date: Dayjs; currentMonth: boolean }>(6 * 7) for (let i = 0; i < day; i++) { daysInfo[i] = { date: startDate.subtract(day - i, 'day'), // 上個月末尾幾天日期 currentMonth: false // 是否為當(dāng)月日期 } } for (let i = day; i < daysInfo.length; i++) { const calcDate = startDate.add(i - day, 'day') daysInfo[i] = { date: calcDate, currentMonth: calcDate.month() === date.month() } } return daysInfo } const alldays = ref(getAllDay(value)) </script> <style scoped> @import './index.scss'; </style>
.calendar { width: 100%; } .calendar-month { &-week-list { display: flex; width: 100%; box-sizing: border-box; border-bottom: 1px solid #ccc; &-item { padding: 20px 16px; text-align: left; color: #7d7d7d; flex: 1; } } &-body { &-row { display: flex; flex-wrap: wrap; } &-cell { width: calc(100% / 7); padding: 10px; height: 100px; box-sizing: border-box; border-bottom: 1px solid #ccc; border-right: 1px solid #ccc; color: #ccc; &-current { color: #000; } } } }
可以看到日期已經(jīng)渲染出來了
Calendar / HeaderCom.vue
日期渲染出來后,需要有操作按鈕切換月份,那這一部分放在了 HeaderCom 的組件中,讓我們來實現(xiàn)一下
<template> <div class="calendar-header"> <div class="calendar-header-left"> <div class="calendar-header-icon"><</div> <div class="calendar-header-value">2024年4月</div> <div class="calendar-header-icon">></div> <button class="calendar-header-btn">今天</button> </div> </div> </template> <script setup lang="ts"></script> <style scoped> @import './index.scss'; </style>
.calendar-header { &-left { display: flex; align-items: center; height: 40px; } &-value { font-size: 16px; } &-icon { width: 28px; height: 28px; line-height: 28px; border-radius: 50%; text-align: center; font-size: 12px; user-select: none; cursor: pointer; margin-right: 12px; &:not(:first-child) { margin: 0 12px; } &:hover { background: #ccc; } } &-btn { background: #eee; cursor: pointer; border: 0; padding: 0 15px; line-height: 28px; &:hover { background: #ccc; } } }
那么到這一步,我們接下來要做的就是點擊按鈕后通知 Calendar 組件切換對應(yīng)月份并且在切換月份的同時讓相應(yīng)的日期對應(yīng)上,那么具體實現(xiàn)是這樣
<!-- HeaderCom --> <!-- HeaderCom --> <template> <div class="calendar-header"> <div class="calendar-header-left"> <div class="calendar-header-icon" @click="preMonthHandler"><</div> <div class="calendar-header-value">{{ curMonth.format('YYYY 年 MM 月') }}</div> <div class="calendar-header-icon" @click="nextMonthHandler">></div> <button class="calendar-header-btn" @click="todayHandler">今天</button> </div> </div> </template> <script setup lang="ts"> import type { Dayjs } from 'dayjs' interface HeaderProps { curMonth: Dayjs } const props = defineProps<HeaderProps>() const emit = defineEmits(['preMonth', 'nextMonth', 'today']) // 切換上一個月 const preMonthHandler = () => { emit('preMonth') } // 切換下一個月 const nextMonthHandler = () => { emit('nextMonth') } // 點擊按鈕獲取今天日期 const todayHandler = () => { emit('today') } </script> <style scoped> @import './index.scss'; </style>
<!-- CalendarCom --> <template> <div class="calendar"> <HeaderCom @preMonth="preMonthHandler" @nextMonth="nextMonthHandler" :curMonth="curMonth" @today="todayHandler" /> <CalendarMonth :value="curValue" :curMonth="curMonth" @ClickCell="handleClickCell" /> </div> </template> <script setup lang="ts"> import type { Dayjs } from 'dayjs' import CalendarMonth from './CalendarMonth.vue' import HeaderCom from './HeaderCom.vue' import { ref } from 'vue' import dayjs from 'dayjs' export interface CalendarProps { value: Dayjs } const props = defineProps<CalendarProps>() let curValue = ref(props.value) let curMonth = ref(props.value) const preMonthHandler = () => { curMonth.value = curMonth.value.subtract(1, 'month') } const nextMonthHandler = () => { curMonth.value = curMonth.value.add(1, 'month') } const todayHandler = () => { const date = dayjs(Date.now()) curMonth.value = date curValue.value = date } const handleClickCell = (date: Dayjs) => { curValue.value = date } </script> <style scoped> @import './index.scss'; </style>
<!-- CalendarMonth --> <template> <div class="calendar-month"> <!-- 星期列表 --> <div class="calendar-month-week-list"> <div v-for="(item, index) in weekList" :key="index" class="calendar-month-week-list-item"> {{ item }} </div> </div> <!-- 日期展示 --> <div class="calendar-month-body"> <div class="calendar-month-body-row"> <div :class="[ 'calendar-month-body-cell', item.currentMonth ? 'calendar-month-body-cell-current' : '' ]" v-for="(item, index) in alldays" :key="index" @click="handleClickCell(item.date)" > <div :class=" value.format('YYYY-MM-DD') === item.date.format('YYYY-MM-DD') ? 'calendar-month-body-cell-selected' : '' " > {{ item.date.date() }} </div> </div> </div> </div> </div> </template> <script setup lang="ts"> import type { Dayjs } from 'dayjs' import type { CalendarProps } from './CalendarCom.vue' import { ref, watch } from 'vue' const weekList = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'] interface CalendarMonthProps extends CalendarProps { curMonth: Dayjs } const props = defineProps<CalendarMonthProps>() /** * 獲取日期數(shù)據(jù)方法 * @param date 傳入日期 */ const getAllDay = (date: Dayjs) => { const startDate = date.startOf('month') const day = startDate.day() // 當(dāng)月第一天是星期幾 const daysInfo = new Array<{ date: Dayjs; currentMonth: boolean }>(6 * 7) for (let i = 0; i < day; i++) { daysInfo[i] = { date: startDate.subtract(day - i, 'day'), // 上個月末尾幾天日期 currentMonth: false // 是否為當(dāng)月日期 } } for (let i = day; i < daysInfo.length; i++) { const calcDate = startDate.add(i - day, 'day') daysInfo[i] = { date: calcDate, currentMonth: calcDate.month() === date.month() } } return daysInfo } const alldays = ref(getAllDay(props.curMonth)) watch( () => props.curMonth, (newV) => { alldays.value = getAllDay(newV) } ) const emit = defineEmits(['clickCell']) const handleClickCell = (date: Dayjs) => { emit('clickCell', date) } </script> <style scoped> @import './index.scss'; </style>
.calendar { width: 100%; } .calendar-month { &-week-list { display: flex; width: 100%; box-sizing: border-box; border-bottom: 1px solid #ccc; &-item { padding: 20px 16px; text-align: left; color: #7d7d7d; flex: 1; } } &-body { &-row { display: flex; flex-wrap: wrap; } &-cell { width: calc(100% / 7); padding: 10px; height: 100px; box-sizing: border-box; border-bottom: 1px solid #ccc; border-right: 1px solid #ccc; color: #ccc; &-current { color: #000; } &-selected { background: blue; width: 28px; height: 28px; line-height: 28px; text-align: center; color: #fff; border-radius: 50%; cursor: pointer; } } } } .calendar-header { &-left { display: flex; align-items: center; height: 40px; } &-value { font-size: 16px; } &-icon { width: 28px; height: 28px; line-height: 28px; border-radius: 50%; text-align: center; font-size: 12px; user-select: none; cursor: pointer; margin-right: 12px; &:not(:first-child) { margin: 0 12px; } &:hover { background: #ccc; } } &-btn { background: #eee; cursor: pointer; border: 0; padding: 0 15px; line-height: 28px; &:hover { background: #ccc; } } }
最后實現(xiàn)的效果是這樣
到這里,這個日歷的主要核心功能差不多就實現(xiàn)了
小結(jié)
那么我們來回顧一下日歷組件的一些核心的地方吧:
- 首先是日期的獲取,這個要熟練使用dayjs庫或者Date的api使用
- 其次就是對日期的計算,當(dāng)月時間的判斷,獲取當(dāng)月上下月時間的方法
- 切換月份重新渲染日期組件數(shù)據(jù)
總結(jié)
以上就是基于vue3+TypeScript實現(xiàn)一個簡易的Calendar組件的詳細(xì)內(nèi)容,更多關(guān)于vue3 TypeScript實現(xiàn)Calendar組件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue3+vant4實現(xiàn)pdf文件上傳與預(yù)覽組件
這篇文章主要介紹了vue3如何結(jié)合vant4實現(xiàn)簡單的pdf文件上傳與預(yù)覽組件,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-04-04Vue2實現(xiàn)自適應(yīng)屏幕大小的兩種方法詳解
這篇文章主要為大家詳細(xì)介紹了Vue2實現(xiàn)自適應(yīng)屏幕大小的兩種方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03vue?長列表數(shù)據(jù)刷新的實現(xiàn)及思考
這篇文章主要為大家介紹了vue?長列表數(shù)據(jù)刷新的實現(xiàn)及思考,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Vue3使用ref與reactive創(chuàng)建響應(yīng)式對象的示例代碼
這篇文章主要詳細(xì)介紹了Vue3使用ref與reactive創(chuàng)建響應(yīng)式對象的方法步驟,文中通過代碼示例和圖文結(jié)合的方式給大家介紹的非常詳細(xì),具有一定的參考價值,需要的朋友可以參考下2024-02-02