vant Calendar组件,显示单个月份,可切换月份,展开与收起显示日期功能

本文介绍了如何使用 TypeScript 和 Vant UI 的 calendar 组件实现一个可交互的月历,包括切换月份、日期的收起与展开,以及数据绑定和样式控制的详细步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用场景描述:

1.显示单个月的日期数据,点击切换月份数据
请添加图片描述
 
2.点击日期收起日历(显示一个星期),再点开展开日历(显示整个月)
请添加图片描述
ps:本例子用的是typescript写的

html模板
<div class="calendar">
    <div class="calendar-top">
      <div class="calendar-top-title">学习表</div>
      <div class="calendar-top-button">
        <img
          src="../../assets/img/outside/icxxbgsy18.png"
          alt=""
          class="button-icon"
          @click="arrowLeft()"
        />
        <div class="calendar-top-time" @click="clickDate">
          {{ year }}-{{ month + 1 }}
        </div>
        <img
          src="../../assets/img/outside/icxxbgsy17.png"
          alt=""
          class="button-icon"
          @click="arrowRight()"
        />
      </div>
    </div>
    <van-calendar
      class="calendar-main"
      :class="showAllMonth ? 'open' : 'close'"
      :show-title="false"
      :poppable="false"
      :show-confirm="false"
      :show-mark="false"
      :default-date="defaultDate"
      :min-date="minDate"
      :max-date="maxDate"
      :formatter="formatter"
      @select="selectDay"
      first-day-of-week="1"
      row-height="35px"
      ref="calendar"
    />
  </div>
模板解析

主要用到vant的calendar组件,组件上方显示所选月份加按钮控制切换月份,分别是arrowLeft()函数和arrowRight()函数。
请添加图片描述

显示单个月的日期数据,点击切换月份数据

1.创建一个函数,用于设置当前显示的月份日历,主要是修改min-date和max-date。在初始化数据时(例如onMounted)调用,并将当前月份作为参数传入。

//设置当前显示日历
const setDate = (month: number) => {
  //计算传入的月份天数
  const daycount = new Date(year.value, month + 1, 0).getDate()
  //将1号设置成可选择最小日期
  let monthMin = new Date(year.value, month, 1)
  //将传入月份的最后一天设置成可选择最大日期
  let monthMax = new Date(year.value, month, daycount)
  //已在setup中初始化minDate和maxDate(minDate=ref())
  minDate.value = monthMin
  maxDate.value = monthMax
}

2.切换上个月的日历

const arrowLeft = function () {
  //将当前所在月份减一,并传入setDate()函数
  month.value --
  setDate(month.value)
}

3.切换下个月的日历

const arrowRight= function () {
  //将当前所在月份加一,并传入setDate()函数
  month.value ++
  setDate(month.value)
}

可在arrowLeft ()和arrowRight()中做些判断限制可切换的月份,例如month.value不能一直减减到负数,或者不能一直加加到超过12,偷个懒这里就不写啦~~

点击日期收起日历,再点开展开日历

//获取实例(vue3写法)
import { getCurrentInstance } from "vue"
const { proxy } = getCurrentInstance() as any

//点击日期响应函数
const clickDate = function () {
  //showAllMonth:是否展开日历
  showAllMonth.value = !showAllMonth.value
  let calendarMain = <HTMLElement>(
    document.getElementsByClassName("calendar-main")[0]
  )
  if (showAllMonth.value) {
    //展开,用样式控制
    calendarMain.style.height = "225px"
  } else {
   //收起,用样式控制
    calendarMain.style.height = "70px"
    //获得当前选择的日期(本例子是将选中日期的时间戳存在store中)
    //转时间戳是本例子项目需要,也可以不转时间戳直接date形式就行
    let date = new Date(baseStore.selectTimestamp * 1000)
    //将选中日期重置到当前选中的日期,不然收起后选中的是1号,并且是显示1号所在的那个星期
    proxy.$refs.calendar.reset(date)
  }
}
//选中任意日期响应函数
const selectDay = function (day: any) {
  // console.log(day)//选中的日期
  const timestamp = Date.parse(day) / 1000 //转时间戳
  //存store
  baseStore.$patch((state) => {
    state.selectTimestamp = timestamp
  })
}

两个功能分享就到此为止啦,其他都是通过样式来控制的,修改vant组件中的样式。需要用到::v-deep来选择元素类才能选中到,例如::v-deep(.van-calendar)。vant组件的样式类名可通过浏览器中按f12选中元素来查看类名。
参考一下:

::v-deep(.van-calendar) {
  width: 106%;
  margin-left: -3%;
  background: none;
}
//在数字前面加上周字
::v-deep(.van-calendar__weekday::before) {
  content: "周";
  color: rgba($color: #000000, $alpha: 0.5);
}
---------------------------------------------这是一条分割线---------------------------------------------

满足下评论区伙计的要求,贴下源码,自己平时写不出到处搜的时候也很无助,希望能帮到各位码友(隔了太久我已经忘记当时的思路了QAQ)

<template>
  <div class="calendar">
    <div class="calendar-top">
      <div class="calendar-top-title">学习表</div>
      <div class="calendar-top-button">
        <img
          src="../../assets/img/outside/icxxbgsy18.png"
          alt=""
          class="button-icon"
          @click="arrowLeft()"
        />
        <div class="calendar-top-time" @click="clickDate">
          {{ year }}-{{ month + 1 }}
        </div>
        <img
          src="../../assets/img/outside/icxxbgsy17.png"
          alt=""
          class="button-icon"
          @click="arrowRight()"
        />
      </div>
    </div>
    <van-calendar
      class="calendar-main"
      :show-title="false"
      :poppable="false"
      :show-confirm="false"
      :show-mark="false"
      :default-date="defaultDate"
      :min-date="minDate"
      :max-date="maxDate"
      :formatter="formatter"
      @select="selectDay"
      first-day-of-week="1"
      row-height="35px"
      ref="calendar"
    />
  </div>
</template>

<script lang="ts" setup>
import {
  ref,
  reactive,
  watch,
  onMounted,
  computed,
  onActivated,
  getCurrentInstance,
} from "vue"
import { GetCalendar } from "../../api/appBase"
import { useUserStore } from "../../store/model/user"
import { useBaseStore } from "../../store/model/appBase"
const userStore = useUserStore()
const baseStore = useBaseStore()
const { proxy } = getCurrentInstance() as any

let dayDatas = reactive({ days: [] })

let defaultDate = ref(new Date())
let year = ref(defaultDate.value.getFullYear())
let month = ref(defaultDate.value.getMonth())
let currentMonth = ref(defaultDate.value.getMonth())
let showAllMonth = ref(false)

let minDate = ref()
let maxDate = ref()

const formatter = (day: any) => {
  // console.log(dayDatas)
  //给有数据的日期加点
  let isHasData = false
  let dayTimestamp = Date.parse(day.date)
  dayDatas.days.forEach((item) => {
    if (dayTimestamp == item["date"] * 1000) isHasData = true
  })
  if (isHasData) {
    day.className = "addDot"
  }
  //日期被选中并且有数据
  let selectDayTimestamp = baseStore.selectTimestamp * 1000
  if (dayTimestamp == selectDayTimestamp && isHasData) {
    day.className = "addDot_Select"
  }
  //当天日期加样式
  let todayTimestamp = new Date(new Date().toDateString()).getTime()
  if (dayTimestamp == todayTimestamp) {
    day.className = "calendarToday"
  }
  //当天日期有数据
  if (dayTimestamp == todayTimestamp && isHasData) {
    day.className = "addDot_calendarToday"
  }
  return day
}

//初始化 从接口获得数据
const GetCalendarData = function (year: any, month: any) {
  const grade = userStore.getGrade
  const mode = 0
  GetCalendar({ year, month, stage: grade, mode }).then((res) => {
    // console.log(res.data)
    dayDatas.days = res.data
  })
}

//设置当前显示日历
const setDate = (month: number) => {
  const daycount = new Date(year.value, month + 1, 0).getDate()
  let monthMin = new Date(year.value, month, 1)
  let monthMax = new Date(year.value, month, daycount)
  minDate.value = monthMin
  maxDate.value = monthMax
}

//切换上个月
const arrowLeft = function () {
  // console.log("<")
  if (month.value != currentMonth.value - 1) {
    month.value = currentMonth.value - 1
    setDate(month.value)
    GetCalendarData(year.value, month.value + 1)
    let day = new Date(
      year.value,
      month.value,
      new Date(year.value, month.value + 1, 0).getDate()
    )
    selectDay(day)
  } else {
    proxy.$toast({
      message: "仅保留本月和上个月的记录哦",
      icon: new URL(`../../assets/img/outside/icxxbgsy28.png`, import.meta.url)
        .href,
      className: "toast-c",
    })
  }
}

//切换下个月
const arrowRight = function () {
  // console.log(">")
  if (month.value < currentMonth.value) {
    month.value++
    setDate(month.value)
    GetCalendarData(year.value, month.value + 1)
    let day = new Date(year.value, month.value, 1)
    selectDay(day)
  } else {
    proxy.$toast({
      message: "仅保留本月和上个月的记录哦",
      icon: new URL(`../../assets/img/outside/icxxbgsy28.png`, import.meta.url)
        .href,
      className: "toast-c",
    })
  }
}

const selectDay = function (day: any) {
  // console.log(day)
  const timestamp = Date.parse(day) / 1000
  baseStore.$patch((state) => {
    state.selectTimestamp = timestamp
  })
}

const clickDate = function () {
  showAllMonth.value = !showAllMonth.value
  let calendarMain = <HTMLElement>(
    document.getElementsByClassName("calendar-main")[0]
  )
  if (showAllMonth.value) {
    calendarMain.style.height = "225px"
  } else {
    calendarMain.style.height = "68px"
    let date = new Date(baseStore.selectTimestamp * 1000)
    proxy.$refs.calendar.reset(date)
  }
}   

//特属于keepAlive的一个生命周期,activated在页面每次进入都会执行
onActivated(() => {
  if (baseStore.selectTimestamp != 0) {
    defaultDate.value = new Date(baseStore.selectTimestamp * 1000)
  }
  year.value = defaultDate.value.getFullYear()
  month.value = defaultDate.value.getMonth()
  GetCalendarData(year.value, month.value + 1)
  setDate(month.value)
  let calendarMain = <HTMLElement>(
    document.getElementsByClassName("calendar-main")[0]
  )
  calendarMain.style.height = "70px"
})

watch(
  () => userStore.getGrade,
  (newVal, oldVal) => {
    GetCalendarData(year.value, month.value + 1)
  }
)
</script>

<style lang="scss" scoped>
.calendar {
  // border: 1px solid red;
  margin-bottom: 46px !important;
  .calendar-top {
    margin-bottom: 16px;
    display: flex;
    justify-content: space-between;
    .calendar-top-title {
      font-size: 32px;
      font-weight: bold;
    }
    .calendar-top-button {
      display: flex;
      font-size: 28px;
      align-items: center;
      .button-icon {
        width: 32px;
        height: 32px;
      }
      .calendar-top-time {
        color: rgba($color: #007aea, $alpha: 0.5);
        margin: 0 10px;
      }
    }
  }

  .calendar-weekdays {
    display: flex;
    height: 50px;
    justify-content: space-between;
    align-items: center;
    // border: 1px solid red;
    .calendar-weekdays-item {
      width: 50px;
      font-size: 20px;
      color: rgba($color: #000000, $alpha: 0.5);
      // border: 1px solid red
    }
  }
  .calendar-days {
    display: grid;
    grid-template-columns: repeat(7, 8%);
    grid-row-gap: 14px;
    height: 50px;
    justify-content: space-between;
    align-items: center;
    .calendar-days-item {
      flex-shrink: 0;
      width: 50px;
      font-size: 28px;
      color: #000000;
      // border: 1px solid red;
    }
  }
}
::v-deep(.van-calendar) {
  width: 106%;
  margin-left: -3%;
  background: none;
}
::v-deep(.van-calendar__body) {
  overflow: hidden;
}
::v-deep(.van-calendar__header) {
  box-shadow: none;
}
// ::v-deep(.van-calendar__day) {
//   margin-bottom: 6px !important;
// }
::v-deep(.van-calendar__selected-day) {
  border-radius: 50%;
  background-color: #59afff !important;
  z-index: 2;
  position: absolute;
}
::v-deep(.van-calendar__selected-day::after) {
  background-color: #fff !important;
}
::v-deep(.van-calendar__header-subtitle) {
  display: none;
}
::v-deep(.van-calendar__weekday) {
  color: rgba($color: #000000, $alpha: 0.5);
}
::v-deep(.van-calendar__weekday::before) {
  content: "周";
  color: rgba($color: #000000, $alpha: 0.5);
}
//有数据日期加点
::v-deep(.addDot) {
  position: relative;
}
::v-deep(.addDot::after) {
  position: absolute;
  content: "";
  width: 12px;
  height: 12px;
  top: 26Px;
  left: 45px;
  border-radius: 50%;
  background-color: #0084ff;
}
//当天日期
::v-deep(.calendarToday) {
  position: relative;
  color: #fff;
  // font-size: 0;
  // z-index: 1;
}
::v-deep(.calendarToday::before) {
  width: 35Px;
  height: 35Px;
  line-height: 35Px;
  position: absolute;
  top: 0;
  content: "今";
  text-align: center;
  font-size: 30px;
  border-radius: 50%;
  background-color: #ffae34;
  // z-index: -1;
}
//当天日期并且有数据
::v-deep(.addDot_calendarToday::after) {
  position: absolute;
  content: "";
  width: 12px;
  height: 12px;
  top: 26Px;
  left: 45px;
  border-radius: 50%;
  background-color: #fff;
  z-index: 4;
}
::v-deep(.addDot_calendarToday::before) {
  width: 35Px;
  height: 35Px;
  line-height: 35Px;
  position: absolute;
  top: 0;
  content: "今";
  text-align: center;
  font-size: 30px;
  border-radius: 50%;
  color: #fff;
  background-color: #ffae34;
}
//有数据并且被选中
::v-deep(.addDot_Select::after) {
  position: absolute;
  content: "";
  width: 12px;
  height: 12px;
  top: 26Px;
  left: 45px;
  border-radius: 50%;
  background-color: #fff;
  z-index: 3;
}
//被选中显示的今字
::v-deep(.calendarToday .van-calendar__selected-day) {
  font-size: 0;
}
::v-deep(.calendarToday .van-calendar__selected-day::before) {
  content: "今";
  z-index: 9;
  font-size: 30px;
  height: 35Px;
  line-height: 35Px;
}
::v-deep(.addDot_calendarToday .van-calendar__selected-day) {
  font-size: 0;
}
::v-deep(.addDot_calendarToday .van-calendar__selected-day::before) {
  content: "今";
  z-index: 9;
  font-size: 30px;
  height: 35Px;
  line-height: 35Px;
}
</style>

<style lang="scss">
.toast-c {
  width: 450px;
  height: 60px;
  padding: 22px 40px;
  min-height: 60px;
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  .van-toast__icon {
    font-size: 48px;
    margin-right: 10px;
  }
  .van-toast__text {
    font-size: 28px;
    margin-top: 0;
  }
}
</style>
### van-calendar 组件使用方法 #### 基本用法 `van-calendar` 是 Vant UI 库中的日历组件,用于展示和选择日期。基本的 HTML 结构如下: ```html <van-calendar v-model="show" /> ``` 此代码会创建一个弹出的日历窗口,其可见状态由 `v-model` 控制。 #### 设置最小和最大可选日期 为了限制用户可以选择的日期范围,可以使用 `min-date` 和 `max-date` 属性来指定最早的可用日期以及最晚的可用日期[^2]。 ```javascript export default { data() { return { show: false, minDate: new Date(2010, 0, 1), maxDate: new Date(2010, 0, 31), }; }, }; ``` 上述配置使得用户仅能在2010年1月1日至2010年1月31日之间挑选日期。 #### 处理初始化显示为空白的问题 当首次加载页面时,可能会遇到日历区域呈现空白的情况。对此问题的一种解决方案是在 `van-calendar` 上绑定 `@open` 事件监听器,并通过 JavaScript 手动触发一次滚动操作以促使界面更新[^1]。 ```javascript methods: { openCalendar() { this.$nextTick(() => { const calendarDom = document.querySelector('.van-calendar__body'); if (calendarDom) { let back = calendarDom.scrollTop; setTimeout(() => { back = calendarDom.scrollTop; calendarDom.scrollTop = back - 2; }, 10); setTimeout(() => { calendarDom.scrollTop = back; }, 100); } }); } } ``` 这段脚本会在每次打开日历时执行微调滚动条位置的操作,从而避免视觉上的空白现象。 #### 微信小程序环境下的注意事项 对于微信小程序开发人员来说,在使用 `vant-weapp` 版本的日历插件时需要注意默认选中日期可能不会按照预期工作的问题。这是因为一旦设定了 `min-date` 或者 `max-date` 参数之后,默认选中的今天日期将会失去作用。此时应该显式地设置 `default-date` 并确保它位于允许范围内[^3]。 另外,针对某些特定版本的小程序 SDK 可能存在 bug 导致无法正确渲染禁用按钮的状态。可以通过修改源码文件内的逻辑判断函数 `getButtonDisabled()` 来修复这个问题[^4]。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值