首页 > 其他分享 >vue3+uniapp手写日历组件

vue3+uniapp手写日历组件

时间:2024-04-07 15:31:40浏览次数:28  
标签:uniapp const value month date vue3 currentDate 手写 day

为了满足产品需求,在日历中可以添加排班信息,点击日期可以获取排班日期详细数据,自定义日历样式,并且支持手动折叠和展开,折叠时只显示当前周的日期,由于现有组件库修改起来比较麻烦,自己就手写了一个日历组件,下面是我代码及思路。

代码写的不好仅供参考,如有异议欢迎评论指正,感谢。

一个完整日历组价包括,标题年月,上个月和下个月,内容分为星期标题,'日', '一', '二', '三', '四', '五', '六',以及对应的日期排列。

日历每月日期生成排列

  1. 首先,通过 computed 函数创建一个计算属性 days即每个月的日期,这个属性的值会根据当前日期的变化而变化。

  2. 然后,获取当前日期信息,包括当前年份 (year)、当前月份 (month)、当前日期 (currentDay)。

  3. 获取今天的日期信息,包括今天的年份 (todayYear)、今天的月份 (todayMonth)。

  4. 判断当前月份是否为当前年份的当月,并计算本月的天数 (daysInMonth)、上个月的天数 (daysInLastMonth)、本月第一天是星期几 (firstDayOfMonth)。

  5. 根据需要生成日历的显示数据:

    • 如果当前月份是当前年份的当月,并且状态为折叠状态 (state.isCollapsed),则生成本月的一个部分日历,以当前日期所在的周为基准,从当前周的开始日到结束日。
    • 否则,生成完整的一个月的日历。
  6. 对于生成的日历数据,如果是上个月的日期,设置 isLastMonth 为 true,如果是下个月的日期,设置 isNextMonth 为 true,如果是当月的日期,设置 isLastMonth 和 isNextMonth 为 false。同时,判断该日期是否有课程安排 (isClassesDate),并将日期、日期对应的数字、是否为上个月、是否为下个月、是否有课程安排等信息存入 days 数组中。

  7. 最后返回生成的日期数据数组 days。

代码:

const days = computed(() => {
      const today = new Date()
      const year = currentDate.value.getFullYear()
      const month = currentDate.value.getMonth()
      const currentDay = currentDate.value.getDate()
      const todayYear = today.getFullYear()
      const todayMonth = today.getMonth()
      const isCurrentMonth = year === todayYear && month === todayMonth
      const daysInMonth = new Date(year, month + 1, 0).getDate()
      const daysInLastMonth = new Date(year, month, 0).getDate()
      const firstDayOfMonth = new Date(year, month, 1).getDay()
      const days = []
      let day = 1
      let lastMonthDay = daysInLastMonth - firstDayOfMonth + 1
      let nextMonthDay = 1

      if (isCurrentMonth && state.isCollapsed) {
        const todayDate = today.getDate()
        const startOfWeek = todayDate - today.getDay() // 当前周的开始日
        const endOfWeek = startOfWeek + 6 // 当前周的结束日

        for (let day = startOfWeek; day <= endOfWeek; day++) {
          const date = new Date(year, month, day)
          let isClassesDate = props.isClassesDataList.some(
            (item) => item.classesDate === dayjs(date).format('YYYY-MM-DD') && item.isClasses
          )
          days.push({ date, day: date.getDate(), isLastMonth: false, isNextMonth: false, isClassesDate })
        }
      } else {
        for (let i = 0; i < 5 * 7; i++) {
          if (i < firstDayOfMonth) {
            days.push({
              date: new Date(year, month - 1, lastMonthDay),
              day: lastMonthDay,
              isLastMonth: true,
              isNextMonth: false,
              isClassesDate: false
            })
            lastMonthDay++
          } else if (i >= firstDayOfMonth + daysInMonth) {
            days.push({
              date: new Date(year, month + 1, nextMonthDay),
              day: nextMonthDay,
              isLastMonth: false,
              isNextMonth: true,
              isClassesDate: false
            })
            nextMonthDay++
          } else {
            const date = new Date(year, month, day)
            // dayjs().format('YYYY-MM-DD')
            // console.log(date, 'datedatedate')
            // days.push({ date, day, isLastMonth: false, isNextMonth: false })
            let isClassesDate = props.isClassesDataList.some(
              (item) => item.classesDate === dayjs(date).format('YYYY-MM-DD') && item.isClasses == 1
            )
            // console.log(date, 'datedatedate')
            // console.log(props.isClassesDataList)
            // console.log(dayjs(date).format('YYYY-MM-DD'))

            // console.log(isClassesDate)

            days.push({ date, day, isLastMonth: false, isNextMonth: false, isClassesDate })
            day++
          }
        }
      }
      console.log(days)
      return days
    })

注:此代码有使用时间库dayjs,需另外安装引用

安装:

npm install dayjs --save

使用:

// 引入

dayjs import dayjs from 'dayjs'

// 输出当前时间

console.log(dayjs())

标题生成

我这里标题格式是yyyy年mm月,根据currentDate更新后的日期获得年月,代码如下:

const title = computed(() => {

const year = currentDate.value.getFullYear()

const month = currentDate.value.getMonth() + 1 // getMonth() 返回的是 0-11 的值,需要加1

// 使用模板字符串来格式化日期,确保月份和日期为两位数

return `${year}年${month.toString().padStart(2, '0')}月`

})

点击上下月按钮,更改月份,以及请求每天排班信息数据:

const prevMonth = () => {

currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() - 1, 1)

emit('monthSelectClasses', { startDate: firstDayOfMonth.value, endDate: lastDayOfMonth.value })

}

const nextMonth = () => {

currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() + 1, 1)

emit('monthSelectClasses', { startDate: firstDayOfMonth.value, endDate: lastDayOfMonth.value })

}

// 主要代码如上,其余代码是一些转换日期方法,日期选择器弹出框代码逻辑以及日历样式仅供参考。

<template>

<view class="calendar">

<view class="header">

<image

class=""

style="width: 32rpx; height: 32rpx; margin-left: 20rpx"

src="../../static/left.png"

@click="prevMonth"

>

</image>

<!-- @click="datePickerShow" -->

<view class="title" @click="datePickerShow">{{ title }}</view>

<image

class=""

style="width: 32rpx; height: 32rpx; margin-left: 20rpx"

src="../../static/right.png"

@click="nextMonth"

>

</image>

</view>

<view class="weekdays">

<view v-for="day in daysOfWeek" :key="day" class="day">{{ day }}</view>

</view>

<view class="days">

<view

v-for="(day, dayIndex) in days"

class="daysItem"

:key="dayIndex"

v-show="!isCollapsed || dayIndex < 7"

:class="{

today: isToday(day),

selected: isSelected(day),

notCurrentMonth: isNotCurrentMonth(day)

}"

@click="select(day)"

>

<span :class="{ 'today-class': isToday(day) }">{{ isToday(day) ? '今日' : day.day }}</span>

<span class="isClasses" v-if="day.isClassesDate">有排班</span>

</view>

</view>

<view @click="toggleCollapse" class="expand-btn">

<image

style="width: 32rpx; height: 32rpx"

src="../../static/top.png"

class="arrow"

:class="{ 'arrow-up': isCollapsed }"

/>

</view>

<!-- <m-popup v-model:visible="showDatePicker" height="auto" position="bottom" title="选择年月"> -->

<!-- <m-date-picker

:modelValue="isSelectDate"

:min-date="minDate"

:max-date="maxDate"

v-model:visible="showDatePicker"

poppable

type="yearmonth"

@cancel="cancelPicker"

@confirm="confirmPicker"

@handleConfirm="handleConfirm"

/> -->

<!-- </m-popup> -->

<PickerDate

:isOpen="showDatePicker"

modelValue="yyyy-MM"

:currentDate="isSelectDate"

@onConfirmPickerDate="onConfirmPickerDate"

@onClosePickerDate="onClosePickerDate"

/>

</view>

</template>

<script lang="ts">

import { ref, computed, reactive, toRefs, watch } from 'vue'

import { onl oad, onShow } from '@dcloudio/uni-app'

import dayjs from 'dayjs'

import PickerDate from './pickerdate'

import { userBehaviorTrackAjax } from '@/utils/track'

export default {

name: 'FeiCalendar',

components: {

PickerDate

},

props: {

selectedDate: Date,

isClassesDataList: {

type: Array,

default: () => []

}

},

emits: ['update:selectedDate', 'monthSelectClasses', 'getData'],

setup(props, { emit }) {

const weekdays = ['日', '一', '二', '三', '四', '五', '六']

const currentDate = ref(new Date())

const selectedDate = ref(props.selectedDate || currentDate.value)

const daysOfWeek = computed(() => {

return weekdays

})

const state = reactive({

isCollapsed: true,

showDatePicker: false,

selectDate: '',

isSelectDate: ref(new Date()).value

})

const days = computed(() => {

const today = new Date()

const year = currentDate.value.getFullYear()

const month = currentDate.value.getMonth()

const currentDay = currentDate.value.getDate()

const todayYear = today.getFullYear()

const todayMonth = today.getMonth()

const isCurrentMonth = year === todayYear && month === todayMonth

const daysInMonth = new Date(year, month + 1, 0).getDate()

const daysInLastMonth = new Date(year, month, 0).getDate()

const firstDayOfMonth = new Date(year, month, 1).getDay()

const days = []

let day = 1

let lastMonthDay = daysInLastMonth - firstDayOfMonth + 1

let nextMonthDay = 1

if (isCurrentMonth && state.isCollapsed) {

const todayDate = today.getDate()

const startOfWeek = todayDate - today.getDay() // 当前周的开始日

const endOfWeek = startOfWeek + 6 // 当前周的结束日

for (let day = startOfWeek; day <= endOfWeek; day++) {

const date = new Date(year, month, day)

let isClassesDate = props.isClassesDataList.some(

(item) => item.classesDate === dayjs(date).format('YYYY-MM-DD') && item.isClasses

)

days.push({ date, day: date.getDate(), isLastMonth: false, isNextMonth: false, isClassesDate })

}

} else {

for (let i = 0; i < 5 * 7; i++) {

if (i < firstDayOfMonth) {

days.push({

date: new Date(year, month - 1, lastMonthDay),

day: lastMonthDay,

isLastMonth: true,

isNextMonth: false,

isClassesDate: false

})

lastMonthDay++

} else if (i >= firstDayOfMonth + daysInMonth) {

days.push({

date: new Date(year, month + 1, nextMonthDay),

day: nextMonthDay,

isLastMonth: false,

isNextMonth: true,

isClassesDate: false

})

nextMonthDay++

} else {

const date = new Date(year, month, day)

let isClassesDate = props.isClassesDataList.some(

(item) => item.classesDate === dayjs(date).format('YYYY-MM-DD') && item.isClasses == 1

)

days.push({ date, day, isLastMonth: false, isNextMonth: false, isClassesDate })

day++

}

}

}

console.log(days)

return days

})

const title = computed(() => {

const year = currentDate.value.getFullYear()

const month = currentDate.value.getMonth() + 1 // getMonth() 返回的是 0-11 的值,需要加1

// const day = currentDate.value.getDate()

// 使用模板字符串来格式化日期,确保月份和日期为两位数

return `${year}年${month.toString().padStart(2, '0')}月`

})

const prevMonth = () => {

currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() - 1, 1)

console.log(currentDate.value)

emit('monthSelectClasses', { startDate: firstDayOfMonth.value, endDate: lastDayOfMonth.value })

}

const nextMonth = () => {

currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() + 1, 1)

emit('monthSelectClasses', { startDate: firstDayOfMonth.value, endDate: lastDayOfMonth.value })

}

const isToday = (day: any) => {

const today = new Date()

return day.date.toDateString() === today.toDateString()

}

const isSelected = (day: any) => {

// console.log(day.date.toDateString() === selectedDate.value.toDateString())

return day.date.toDateString() === selectedDate.value.toDateString()

}

const isNotCurrentMonth = (day: any) => {

return day.isLastMonth || day.isNextMonth

}

// 是否折叠日历

const toggleCollapse = () => {

state.isCollapsed = !state.isCollapsed

userBehaviorTrackAjax({

page_name: 'doctor_scheduling',

event_name: 'click_view_more',

result: state.isCollapsed ? 1 : 0

})

}

const formattedDate = computed(() => {

const date = selectedDate.value

const year = date.getFullYear()

const month = (date.getMonth() + 1).toString().padStart(2, '0')

const day = date.getDate().toString().padStart(2, '0')

return `${year}-${month}-${day}`

})

const firstDayOfMonth = computed(() => {

const year = currentDate.value.getFullYear()

const month = currentDate.value.getMonth()

const firstDayOfMonth = new Date(year, month, 1)

// 获取当月第一天是周几

const dayOfWeek = firstDayOfMonth.getDay()

// 计算日历页第一个日期

const firstDate = new Date(firstDayOfMonth)

firstDate.setDate(firstDayOfMonth.getDate() - dayOfWeek + 1)

return firstDate.toISOString().substring(0, 10)

})

const lastDayOfMonth = computed(() => {

const year = currentDate.value.getFullYear()

const month = currentDate.value.getMonth()

const lastDayOfMonth = new Date(year, month + 1, 0)

// 获取当月最后一天是周几

const dayOfWeek = lastDayOfMonth.getDay()

// 计算日历页最后一个日期

const lastDate = new Date(lastDayOfMonth)

lastDate.setDate(lastDayOfMonth.getDate() + (6 - dayOfWeek) + 1)

return lastDate.toISOString().substring(0, 10)

})

// 获取选择日期的排班医生信息

const select = (day: any) => {

selectedDate.value = day.date

emit('update:selectedDate', day.date)

emit('getActivityData', formattedDate)

emit('getData', formattedDate)

}

// 打开时间选择器

const datePickerShow = () => {

state.isSelectDate = currentDate.value

state.showDatePicker = true

}

// 将选择器的时间转为可使用的中国标准时间格式

const formatDate = computed(() => {

const dateParts = state.selectDate.match(/(\d+)/g)

if (dateParts) {

const year = dateParts[0]

const month = dateParts[1]

// const day = dateParts[2]

const day = '1'

const date = new Date(year, month - 1, day)

const options = {

weekday: 'short',

month: 'short',

day: '2-digit',

year: 'numeric',

hour: '2-digit',

minute: '2-digit',

second: '2-digit',

timeZoneName: 'short'

}

return date.toLocaleString('en-US', options).replace(',', '')

} else {

return ''

}

})

// 时间选择器

const onConfirmPickerDate = (props) => {

state.selectDate = `${props.year}年${props.month}月`

currentDate.value = new Date(formatDate.value)

emit('monthSelectClasses', { startDate: firstDayOfMonth.value, endDate: lastDayOfMonth.value })

state.showDatePicker = false

}

const onClosePickerDate = (props) => {

state.showDatePicker = false

}

// 监听 currentDate.value 的变化

watch(

() => currentDate,

() => {

// console.log(currentDate.value, '监听数据222!')

},

{ deep: true, immediate: true }

)

onLoad(() => {

emit('monthSelectClasses', { startDate: firstDayOfMonth.value, endDate: lastDayOfMonth.value })

})

return {

daysOfWeek,

datePickerShow,

// cancelPicker,

// handleConfirm,

// confirmPicker,

onClosePickerDate,

onConfirmPickerDate,

// isToday,

days,

years: '2024',

month: '03',

chineseStandardTimeString: '',

...toRefs(state),

formattedDate,

formatDate,

firstDayOfMonth,

lastDayOfMonth,

toggleCollapse,

title,

prevMonth,

nextMonth,

isToday,

isSelected,

isNotCurrentMonth,

select,

minDate: new Date(2020, 0, 1),

maxDate: new Date(3024, 5, 1),

columnsType: ['year', 'month']

}

}

}

</script>

<style lang="scss" scoped>

.calendar {

max-width: 500px;

margin: 0 auto;

font-family: Arial, sans-serif;

background: #ffffff;

border-radius: 32rpx;

padding: 20rpx 10rpx;

/* z-index: 999999999 !important; */

position: relative;

top: 0;

left: 0;

}

::v-deep {

.picker__bar {

color: #333333 !important;

}

.picker__ok {

color: #333333 !important;

}

.icon-close {

color: #333333 !important;

}

}

.header {

display: flex;

justify-content: space-between;

align-items: center;

margin-bottom: 10px;

padding: 0 10px;

}

.title {

font-family: PingFangSC, PingFang SC;

font-weight: 500;

font-size: 32rpx;

color: #333333;

}

.weekdays {

display: flex;

justify-content: space-around;

margin-bottom: 10px;

font-weight: 400;

font-size: 24rpx;

color: #999999;

}

.day {

width: 30px;

height: 30px;

display: flex;

justify-content: center;

align-items: center;

border-radius: 50%;

}

.days {

display: grid;

grid-template-columns: repeat(7, 1fr);

grid-gap: 10px;

.daysItem {

display: flex;

align-items: center;

flex-direction: column;

justify-content: flex-start;

padding-top: 10rpx;

border: 1rpx solid #e6e6e6;

border-radius: 16rpx;

/* justify-content: space-evenly; */

height: 94rpx;

color: #333333;

font-weight: 600;

.isClasses {

color: #109cac;

font-size: 20rpx;

font-weight: 500;

margin-top: 10rpx;

}

}

.today-class {

font-size: 24rpx;

}

}

.expand-btn {

text-align: center;

margin-top: 20rpx;

/* margin-bottom: 5rpx; */

.arrow-up {

transform: rotate(180deg);

}

}

.today {

/* background-color: lightblue; */

border-radius: 16rpx;

}

.selected {

background: #2ca4ad;

border-radius: 16rpx;

color: white !important;

span {

color: #ffffff !important;

}

}

.notCurrentMonth {

color: #ccc !important;

}

::v-deep {

.picker__ok {

color: rgb(159, 159, 160);

}

.icon-close {

font-size: 30rpx !important;

}

}

/* .date-picker {

position: absolute;

bottom: 0;

} */

</style>

标签:uniapp,const,value,month,date,vue3,currentDate,手写,day
From: https://blog.csdn.net/weixin_44045991/article/details/137461123

相关文章

  • 手写bind
    首先写一个bind的简单示例:'usestrict'functionfn(){console.log('this::',this)console.log('arguments::',arguments)}//fn()//这里调用时this在严格模式下是undefined,非严格模式下是Windowvarfn1=fn.bind('str',1,2,3);//这里把this改......
  • 基于vue3的Crontab组件
    网上找的没有满意的,决定从若依前后端分离其前端vue2中的crontab进行转换,先上效果若依: 改后:  v2转v3没什么难度,其中有大量的将this.***替换为***.value,笔者写了个正则替换,希望可以帮助大家this.(\w+)$1.value 需要注意的有,在v2中【this.$refs[refName......
  • uniapp 微信小程序分享到微信和微信朋友圈
    initMenu(){uni.showShareMenu({withShareTicket:true,//设置下方的Menus菜单,才能够让发送给朋友与分享到朋友圈两个按钮可以点击menus:['shareAppMessage','shareTimeline']})},//发送给朋友onShareAppMessa......
  • 2.手写JavaScript广度和深度优先遍历二叉树
    一、核心思想:1.深度遍历:依靠栈先进后出的机制,分别设置返回结果的数组和栈数组,首先判断栈非空,对每个结点,将其出栈并把值push到结果数组,判断是否有右左孩子,分别将其加入栈中,循环执行上述操作。否则返回结果数组。2.广度遍历:依靠队列先进先出的机制,分别设置返回结果的数组和队......
  • vue3的ref和reactive函数
    在vue3中需要引入ref和reactive函数对变量进行声明 首先引入ref,reactive函数,vue3不同于vue2的是,变量的声明需要写在setup函数中,(vue2是直接写在data函数中)其中ref主要是对一些基础数据变量声明,如string,number,boolean,而reactive则是对复杂的,入对象进行声明最后,定义......
  • 【CANN训练营笔记】OrangePI AIPro 体验手写体识别模型训练与推理
    CANN简介当我们谈到香橙派AIPro的时候,总会把她和昇腾生态关联起来,因为在昇腾芯片的加持下,这款开发板有着出色的算力,被众多开发者追捧。而谈到昇腾芯片,我们不得不提上层的AI异构计算架构CANN。异构计算架构CANN(ComputeArchitectureforNeuralNetworks)是华为针对AI场......
  • 从0到1搭建一个Vue3+Electron的框架
    1.前言:上篇文章中使用到了Vue+Electron框架,这一篇文章主要讲解这个框架如何搭建2.Vue3+Vite项目搭建执行命令行,创建Vue3+Vite脚手架:npmcreatevite或yarncreatevite修改脚手架中的无用部分删除src/components下的所有文件修改src/App.vue内容<!--*@......
  • Java登陆第三十八天——VUE3生命周期、钩子函数、组件拼接、组件传参(组件通信)
    生命周期之前在Servlet等也学习过生命周期,相同的,VUE组件也有生命周期。VUE组件完全解析到浏览器,会经过如下过程。(简单理解)vue组件被实例化的过程,称为,组件实例化。组件实例解析到浏览器中,称为,挂载。组件实例从浏览器中删除,称为,卸载。钩子函数vue组件解析到浏览器的......
  • 基于深度学习的手写数字和符号识别系统(网页版+YOLOv8/v7/v6/v5代码+训练数据集)
    摘要:在本篇博客中,我们深入研究了基于YOLOv8/v7/v6/v5的手写数字和符号识别系统。本系统的核心采用了YOLOv8技术,并整合了YOLOv7、YOLOv6、YOLOv5算法来进行性能指标的对比分析。我们详细地回顾了国内外在手写数字和符号识别领域的研究现状,并对使用到的数据集处理方法、算法原理、模......
  • Vue3 + TypeScript + Vite 初始项目搭建(ESLint、Prettier、Sass、Stylelint、husky、p
    仓库地址仓库地址:https://gitee.com/tongchaowei/vue-ts-vite-template项目源码下载:https://gitee.com/tongchaowei/vue-ts-vite-template/releases全局安装pnpm包管理工具执行如下命令在系统全局安装pnpm包管理工具:npmipnpm-g使用Vite脚手架创建Vue3......