目录
  • 功能分析
  • 功能实现
    • 初始化
    • 组件分析
    • 具体操作
      • Calendar / CalendarCom.vue
      • Calendar / CalendarMonth.vue
      • Calendar / HeaderCom.vue
      • 小结
  • 总结

    功能分析

    目前学到功能有以下几点

    • 日历的日期展示(核心组件,计算当月天数,第一天是星期几,以及上下月日期的连接)
    • 切换月份联动日期修改
    • 定位当前日期

    功能实现

    初始化

    使用 vue 官网提供的命令即可 npm create vue@latest ,要把使用 ts 的选项勾选上,因为这里我们用到了 ts

    组件分析

    划分了两个组件 HeaderComCalendarMonth

    • 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

    基于vue3+TypeScript实现一个简易的Calendar组件

    我们首先来实现在展示日期组件的功能吧,借个图来分析一下,这个组件分为 星期、日期两个部分,首先来实现下星期的渲染

    <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>
    
    

    此时日期组件的顶部就渲染好了

    基于vue3+TypeScript实现一个简易的Calendar组件

    接下来就是日期渲染了,日期渲染就是拿到第一天的日期,计算是星期几,然后把前面空余的数据填补成上个月的末尾日期,后面超出的数据填补成下个月月初日期

    这个时候有小伙伴就会问,怎么获取第一天,又怎么获取前一个月的时间和后一个月的时间呢?别着急呀,这些功能 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;
          }
        }
      }
    }
    
    

    基于vue3+TypeScript实现一个简易的Calendar组件

    可以看到日期已经渲染出来了

    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;
        }
      }
    }
    
    

    基于vue3+TypeScript实现一个简易的Calendar组件

    那么到这一步,我们接下来要做的就是点击按钮后通知 Calendar 组件切换对应月份并且在切换月份的同时让相应的日期对应上,那么具体实现是这样

    <!-- HeaderCom -->
    <!-- HeaderCom -->
    <template>
      <div class="calendar-header">
        <div class="calendar-header-left">
          <div class="calendar-header-icon" @click="preMonthHandler">&lt;</div>
          <div class="calendar-header-value">{{ curMonth.format('YYYY 年 MM 月') }}</div>
          <div class="calendar-header-icon" @click="nextMonthHandler">&gt;</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;
        }
      }
    }
    
    

    最后实现的效果是这样

    基于vue3+TypeScript实现一个简易的Calendar组件

    到这里,这个日历的主要核心功能差不多就实现了

    小结

    那么我们来回顾一下日历组件的一些核心的地方吧:

    • 首先是日期的获取,这个要熟练使用dayjs库或者Date的api使用
    • 其次就是对日期的计算,当月时间的判断,获取当月上下月时间的方法
    • 切换月份重新渲染日期组件数据

    总结

    以上就是基于vue3+TypeScript实现一个简易的Calendar组件的详细内容,更多关于vue3 TypeScript实现Calendar组件的资料请关注其它相关文章!

    声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。