基于vue3+TypeScript實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Calendar組件
功能分析
目前學(xué)到功能有以下幾點(diǎn)
- 日歷的日期展示(核心組件,計(jì)算當(dāng)月天數(shù),第一天是星期幾,以及上下月日期的連接)
- 切換月份聯(lián)動(dòng)日期修改
- 定位當(dāng)前日期
功能實(shí)現(xiàn)
初始化
使用 vue 官網(wǎng)提供的命令即可 npm create vue@latest ,要把使用 ts 的選項(xiàng)勾選上,因?yàn)檫@里我們用到了 ts
組件分析
劃分了兩個(gè)組件 HeaderCom 和 CalendarMonth
- HeaderCom 用于按鈕切換日期展示,以及定位當(dāng)前日期
- CalendarMonth 用于日期的展示
兩者的父組件為 CalendarCom組件
具體操作
安裝 dayjs 日期庫(kù),這里使用 dayjs 提供的 api 來(lái)獲取時(shí)間 npm i dayjs , 安裝 sass 預(yù)處理器 npm i sass -D
我這里選擇直接在 views 中新建了 Calendar 文件夾,所以后面說(shuō)的 Calendar 文件夾 都是對(duì)應(yīng) src/views/Calendar
Calendar / CalendarCom.vue
新建CalendarCom.vue,日歷組件的整體,包含了上面所說(shuō)的兩個(gè)子組件,具體代碼如下
<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

我們首先來(lái)實(shí)現(xiàn)在展示日期組件的功能吧,借個(gè)圖來(lái)分析一下,這個(gè)組件分為 星期、日期兩個(gè)部分,首先來(lái)實(shí)現(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í)日期組件的頂部就渲染好了

接下來(lái)就是日期渲染了,日期渲染就是拿到第一天的日期,計(jì)算是星期幾,然后把前面空余的數(shù)據(jù)填補(bǔ)成上個(gè)月的末尾日期,后面超出的數(shù)據(jù)填補(bǔ)成下個(gè)月月初日期
這個(gè)時(shí)候有小伙伴就會(huì)問(wèn),怎么獲取第一天,又怎么獲取前一個(gè)月的時(shí)間和后一個(gè)月的時(shí)間呢?別著急呀,這些功能 dayjs 都給我們提供好了方法了,當(dāng)然用 Date 對(duì)象也可以獲取這些
// 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 星期一
測(cè)試這些方法,我們知道2024年4月的天數(shù),第一天是星期幾,起始日期,結(jié)束日期
我們?cè)?strong>CalendarMonth 組件中實(shí)現(xiàn)一個(gè) getAllDay 的方法,返回一個(gè)日期數(shù)組,并且給 Calendar 組件添加一個(gè) value 屬性,傳入一個(gè)初始時(shí)間,獲取到要展示的日期后渲染到頁(yè)面即可
<!-- 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'), // 上個(gè)月末尾幾天日期
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)渲染出來(lái)了
Calendar / HeaderCom.vue
日期渲染出來(lái)后,需要有操作按鈕切換月份,那這一部分放在了 HeaderCom 的組件中,讓我們來(lái)實(shí)現(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;
}
}
}

那么到這一步,我們接下來(lái)要做的就是點(diǎn)擊按鈕后通知 Calendar 組件切換對(duì)應(yīng)月份并且在切換月份的同時(shí)讓相應(yīng)的日期對(duì)應(yīng)上,那么具體實(shí)現(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'])
// 切換上一個(gè)月
const preMonthHandler = () => {
emit('preMonth')
}
// 切換下一個(gè)月
const nextMonthHandler = () => {
emit('nextMonth')
}
// 點(diǎn)擊按鈕獲取今天日期
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'), // 上個(gè)月末尾幾天日期
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;
}
}
}
最后實(shí)現(xiàn)的效果是這樣

到這里,這個(gè)日歷的主要核心功能差不多就實(shí)現(xiàn)了
小結(jié)
那么我們來(lái)回顧一下日歷組件的一些核心的地方吧:
- 首先是日期的獲取,這個(gè)要熟練使用dayjs庫(kù)或者Date的api使用
- 其次就是對(duì)日期的計(jì)算,當(dāng)月時(shí)間的判斷,獲取當(dāng)月上下月時(shí)間的方法
- 切換月份重新渲染日期組件數(shù)據(jù)
總結(jié)
以上就是基于vue3+TypeScript實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Calendar組件的詳細(xì)內(nèi)容,更多關(guān)于vue3 TypeScript實(shí)現(xiàn)Calendar組件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue3+vant4實(shí)現(xiàn)pdf文件上傳與預(yù)覽組件
這篇文章主要介紹了vue3如何結(jié)合vant4實(shí)現(xiàn)簡(jiǎn)單的pdf文件上傳與預(yù)覽組件,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-04-04
Vue2實(shí)現(xiàn)自適應(yīng)屏幕大小的兩種方法詳解
這篇文章主要為大家詳細(xì)介紹了Vue2實(shí)現(xiàn)自適應(yīng)屏幕大小的兩種方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03
vue3項(xiàng)目目錄結(jié)構(gòu)示例詳解
更好的了解項(xiàng)目的目錄結(jié)構(gòu),能更好的去開(kāi)發(fā)項(xiàng)目,下面這篇文章主要給大家介紹了關(guān)于vue3項(xiàng)目目錄結(jié)構(gòu)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02
Element?plus?表單中下拉框的空值問(wèn)題解決
有時(shí)候會(huì)碰到查詢條件需要用下拉框來(lái)區(qū)分的時(shí)候,比如需要區(qū)分全部?|?啟用?|?停用三個(gè)狀態(tài),這時(shí)一般會(huì)給全部的value值設(shè)置為'',但是這樣會(huì)導(dǎo)致下拉框無(wú)法選中對(duì)應(yīng)的全部選項(xiàng),本文就來(lái)介紹一下這個(gè)問(wèn)題解決,感興趣的可以了解一下2024-11-11
vue?長(zhǎng)列表數(shù)據(jù)刷新的實(shí)現(xiàn)及思考
這篇文章主要為大家介紹了vue?長(zhǎng)列表數(shù)據(jù)刷新的實(shí)現(xiàn)及思考,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
vue如何實(shí)時(shí)往數(shù)組里追加數(shù)據(jù)
這篇文章主要介紹了vue如何實(shí)時(shí)往數(shù)組里追加數(shù)據(jù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04
Vue3使用ref與reactive創(chuàng)建響應(yīng)式對(duì)象的示例代碼
這篇文章主要詳細(xì)介紹了Vue3使用ref與reactive創(chuàng)建響應(yīng)式對(duì)象的方法步驟,文中通過(guò)代碼示例和圖文結(jié)合的方式給大家介紹的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2024-02-02

