功能分析
目前学到功能有以下几点
- 日历的日期展示(核心组件,计算当月天数,第一天是星期几,以及上下月日期的连接)
- 切换月份联动日期修改
- 定位当前日期
功能实现
初始化
使用 vue 官网提供的命令即可 npm create vue@latest ,要把使用 ts 的选项勾选上,因为这里我们用到了 ts
组件分析
划分了两个组件 HeaderCom 和 CalendarMonth
- HeaderCom 用于按钮切换日期展示,以及定位当前日期
- CalendarMonth 用于日期的展示
两者的父组件为 CalendarCom组件
具体操作
安装 dayjs 日期库,这里使用 dayjs 提供的 api 来获取时间 npm i dayjs , 安装 sass 预处理器 npm i sass -D
我这里选择直接在 views 中新建了 Calendar 文件夹,所以后面说的 Calendar 文件夹 都是对应 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

我们首先来实现在展示日期组件的功能吧,借个图来分析一下,这个组件分为 星期、日期两个部分,首先来实现下星期的渲染
<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>
此时日期组件的顶部就渲染好了

接下来就是日期渲染了,日期渲染就是拿到第一天的日期,计算是星期几,然后把前面空余的数据填补成上个月的末尾日期,后面超出的数据填补成下个月月初日期
这个时候有小伙伴就会问,怎么获取第一天,又怎么获取前一个月的时间和后一个月的时间呢?别着急呀,这些功能 dayjs 都给我们提供好了方法了,当然用 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月的天数,第一天是星期几,起始日期,结束日期
我们在CalendarMonth 组件中实现一个 getAllDay 的方法,返回一个日期数组,并且给 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
/**
* 获取日期数据方法
* @param date 传入日期
*/
const getAllDay = (date: Dayjs) => {
const startDate = date.startOf('month')
const day = startDate.day() // 当月第一天是星期几
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 // 是否为当月日期
}
}
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;
}
}
}
}

可以看到日期已经渲染出来了
Calendar / HeaderCom.vue
日期渲染出来后,需要有操作按钮切换月份,那这一部分放在了 HeaderCom 的组件中,让我们来实现一下
<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 组件切换对应月份并且在切换月份的同时让相应的日期对应上,那么具体实现是这样
<!-- 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>()
/**
* 获取日期数据方法
* @param date 传入日期
*/
const getAllDay = (date: Dayjs) => {
const startDate = date.startOf('month')
const day = startDate.day() // 当月第一天是星期几
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 // 是否为当月日期
}
}
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;
}
}
}
最后实现的效果是这样

到这里,这个日历的主要核心功能差不多就实现了
小结
那么我们来回顾一下日历组件的一些核心的地方吧:
- 首先是日期的获取,这个要熟练使用dayjs库或者Date的api使用
- 其次就是对日期的计算,当月时间的判断,获取当月上下月时间的方法
- 切换月份重新渲染日期组件数据
总结
以上就是基于vue3+TypeScript实现一个简易的Calendar组件的详细内容,更多关于vue3 TypeScript实现Calendar组件的资料请关注其它相关文章!

