当前位置:首页 » 《随便一记》 » 正文

前端之vue 封装自定义日历

25 人参与  2024年05月25日 14:50  分类 : 《随便一记》  评论

点击全文阅读


关于自定义日历

工作需要,现有框架封装的日历无法满足需求,又找不到更好的插件的情况下,咋办??自己写个呗!

效果图和功能说明

先看看效果图

在这里插入图片描述

其实基本界面就这样了,和其他没啥区别。
但是既然要单独封装一个,那肯定有其他可扩展的地方,不然就没意义了

功能说明

1、基本日历功能
2、可以自定义标题头部
3、可自定义顶部时间以及月份切换部分
4、日历单元格样式可以自定义
5、星期一栏可以自定义
6、可以在日历里面插入数据:这一点是最重要的,也是必须要自己封装的重要因素

使用

封装为日历组件,直接引用即可。需要扩展其他功能,可以看组件里面的props哦,这是可以接收的参数。当然,如果用props不能满足的,那就看看里面的slot插槽部分咯。

这是props定义接收的参数,每个参数均有注释,这里不多做介绍。
 props: {      initDate:{        type:[String,Date,Number],        default:()=>new Date()      },//初始化日期      width:{        type:[String,Number],        default:'100%'      },//日历宽度      height:{        type:[String,Number],        default:'100%'      },//日历高度      calendarClass:String,//日历自定义样式类      titleClass:String,//年月标题自定义样式类      titleH:{        type:[String,Number],        default:'35px'      },//年月标题高度      titleBk:{        type:String,        default:'#ffffff'      },//年月标题颜色      bodyBk:{        type:String,        default:'#ffffff'      },//日历体背景      bodyClass:String,//日历体自定义样式      dateDefaultClass:String,//日期自定义默认类名      dateActivDateClass:String,//日期自定义选中类名      dateDisabledDateClass:String,//日期自定义不可见类名      titleDateConnector:String,//标题日期连接符      insertData:{        type:Array,        default:()=>[]      },//自定义拼接数据      weeks:{        type:Array,        default:()=>['日','一','二','三','四','五','六']      },//周数据      dateProp:{        type:String,        default:'date'      },//自定义表示时间的字段      calenCellClass:String,//日历单元格自定义样式      firstRowCellClass:String,//日历第一行单元格自定义样式      firstColumCellClass:String,//日历第一列单元格自定义样式      cellBorder:Boolean,//是否有边框      cellTitleHeight:{        type:[Number,String],        default:'40px'      },//日历标题高度      cellTitleColor:{        type:String,        default:'#333333'      },//日历标题颜色      range:{        type:Array,        default:()=>[]      },//日期范围    },
更多个性化功能请看slot部分

在这里插入图片描述

插槽使用示例:
不熟悉插槽的伙伴可以去vue官网补补:https://cn.vuejs.org/v2/guide/components-slots.html

 <calendar>                    <template #calendarTitle>                        <div class="rowStart calendarTitle">                            <i class="el-icon-date yellow font20"></i>                            <strong class="font16" style="margin-left:5px;">日历标题</strong>                        </div>                    </template>                    <template #calendarTop="{currentYear,currentMonth,changeMonth}">                        <div class="rowBtween canlendar-top-box">                            <div class="rowStart year-back-box">                                <strong class="font24">{{currentYear}}年{{currentMonth}}月</strong>                                <span class="blue back-today font14" @click="changeMonth(new Date())">返回今天</span>                            </div>                            <div class="change-month-box  rowBtween">                                <i class="el-icon-arrow-left el-icon blue" @click="changeMonth(0)"></i>                                <span></span>                                <i class="el-icon-arrow-right el-icon blue" @click="changeMonth(1)"></i>                            </div>                        </div>                    </template>                </calendar>

其他不多说了,最后上代码。由于组件代码放在一个页面写重了点,因此分成了几个文件
所有data里面的变量,props,还有函数均有说明。
先看canlendar.vue

<template>  <div class="custom-calendar" :style="{width:calendarWidth,height:calendarHeight}" :class="calendarClass">    <div class="calendar-topBox">      <slot name="calendarTitle"></slot>      <slot name="calendarTop" :currentYear="currentYear" :currentMonth="currentMonth+1" :changeMonth="changeMonth">        <div class="calendar-title rowBtween" :style="{height:titleHeight,background:titleBk}" :class="titleClass">          <strong class="left">{{currentYear}}{{titleDateConnector || '年'}}{{currentMonth+1}}{{titleDateConnector ? '' : '月'}}</strong>          <div class="right rowBtween">            <span @click="changeMonth(0)">&lt;</span>            <span @click="changeMonth(1)">&gt;</span>           </div>        </div>      </slot>    </div>    <div class="calendar-body" :style="{background:bodyBk}" :class="bodyClass">      <div class="bodyTitleBox rowCenter">        <slot name="weeks">          <span class="body-title rowCenter" :ref="index ? '' : 'calenCellTitle'" :style="{height:cellTitleHeight,color:cellTitleColor}" v-for="(week,index) in weeks" :key="index">{{week}}</span>        </slot>      </div>      <div class="calen-content rowCenter">        <div class="calen-cell" :id="'calen'+index" :ref="index ? '' : 'calenCell'" :class="[calenCellClass,index<7 ? 'firstRowCellClass': '',index%7===0 ? 'firstColumCellClass' : '']"  :style="{...calenCellStyle,...cellBorderStyle(index)}" v-for="(day,index) in calendarList" :key="index" @click="choose(day)">          <slot name="day" :day="day">            <span class="dateSpan rowCenter" :class="day.dateClass">{{day.day}}</span>          </slot>          <slot name="haveDataTag" :hasData="day.hasData" :isThis="day.isThis">            <div v-show="day.hasData && day.isThis"></div>          </slot>        </div>      </div>    </div>  </div></template><script>  import {isValidDate,isNumber} from "./validate"  import {getStartTimeEndTimeInfoFun,insertDataToCalendar} from './util'  export default {    name: "Calendar",    props: {      initDate:{        type:[String,Date,Number],        default:()=>new Date()      },//初始化日期      width:{        type:[String,Number],        default:'100%'      },//日历宽度      height:{        type:[String,Number],        default:'100%'      },//日历高度      calendarClass:String,//日历自定义样式类      titleClass:String,//年月标题自定义样式类      titleH:{        type:[String,Number],        default:'35px'      },//年月标题高度      titleBk:{        type:String,        default:'#ffffff'      },//年月标题颜色      bodyBk:{        type:String,        default:'#ffffff'      },//日历体背景      bodyClass:String,//日历体自定义样式      dateDefaultClass:String,//日期自定义默认类名      dateActivDateClass:String,//日期自定义选中类名      dateDisabledDateClass:String,//日期自定义不可见类名      titleDateConnector:String,//标题日期连接符      insertData:{        type:Array,        default:()=>[]      },//自定义拼接数据      weeks:{        type:Array,        default:()=>['日','一','二','三','四','五','六']      },//周数据      dateProp:{        type:String,        default:'date'      },//自定义表示时间的字段      calenCellClass:String,//日历单元格自定义样式      firstRowCellClass:String,//日历第一行单元格自定义样式      firstColumCellClass:String,//日历第一列单元格自定义样式      cellBorder:Boolean,//是否有边框      cellTitleHeight:{        type:[Number,String],        default:'40px'      },//日历标题高度      cellTitleColor:{        type:String,        default:'#333333'      },//日历标题颜色      range:{        type:Array,        default:()=>[]      },//日期范围    },    data() {      return {        calendarList: [],//日历数据        activeDay: {},//选中日期信息        currentYear: '',//显示的年        currentMonth: '',//显示的月        chooseDate: '',//选择的日期        calenCellHeight:0,//日历单元格高度        calenBotyTitleCellStyle:{},//日历顶部单元格样式        calenCellStyle:{},//日历单元格样式        calendarWidth:'100%',//日历宽度        calendarHeight:'100%',//日历高度        titleHeight:'35px',//年月标题高度        dateCellDefaultClass:'',//日历单元格默认类名        dateCellActiClass:'',//日历单元格选中类名        dateCellDisabledClass:'',//日历不可见单元格类名        cellBorderStyle:(index)=>{return {}},//日历单元格边框样式        bodyCellTitleHeight:'40px'      }    },    async created(){      console.log(this.calenCellClass)      let {width,height,titleH,dateDefaultClass,dateActivDateClass,dateDisabledDateClass,cellTitleHeight}=this      this.calendarWidth=(typeof width==='number' || isNumber(width)) ? (width+'px') : width      this.calendarHeight=(typeof height==='number' || isNumber(height)) ? (height+'px') : height      this.titleHeight=(typeof cellTitleHeight==='number' || isNumber(cellTitleHeight)) ? (cellTitleHeight+'px') : cellTitleHeight      this.bodyCellTitleHeight=(typeof titleH==='number' || isNumber(titleH)) ? (titleH+'px') : titleH      this.dateCellDefaultClass=dateDefaultClass || 'dateDefaultCss'      this.dateCellActiClass=dateActivDateClass || 'dateActiveCss'      this.dateCellDisabledClass=dateDisabledDateClass || 'disableDateCss'      let {dateTime,isValid}=isValidDate(this.initDate)      if(!isValid) return      let year=dateTime.getFullYear()      let month=dateTime.getMonth()      let date=dateTime.getDate()      await this.init(year,month,date)      insertDataToCalendar(this.insertData,this.calendarList,this.dateProp)    },    methods: {      //初始化日历      async init(year, month, date,config={}) {        this.currentYear = year        this.currentMonth = month        let daysInMonth = new Date(year, month+1,0).getDate()//得到当前月份的天数  28,29,30,31        let firstDayInWeek = new Date(year, month,1).getDay()//获取当月的一号是星期几        let lastDayInWeek = new Date(year,month,daysInMonth).getDay()//获取当月最后一天是星期几        this.calendarList = await this.createcalendarList(year, month, date, firstDayInWeek, lastDayInWeek, daysInMonth,config)        return this.calendarList      },      //创建日历      createcalendarList(year, month, date, firstDayInWeek, lastDayInWeek, daysInMonth,config={}) {        return new Promise((resolve)=>{          let thisYear=new Date().getFullYear()          let thisMonth=new Date().getMonth()          if(thisYear===year && thisMonth===month){            date=new Date().getDate()          }          let {isChangeMonth}=config          let { calenStartDate,calenEndDate,calenStartYear,calenStartMonth,dateRangeCode}=getStartTimeEndTimeInfoFun(this.range[0],this.range[1])          // console.log(calenStartDate,calenEndDate,dateRangeCode,calenStartMonth)          if(!isChangeMonth && dateRangeCode){            //日期范围有效            this.currentYear = calenStartYear            this.currentMonth = calenStartMonth          }          let calendarList = []          //1号不在星期天,补全前面的日期信息          // console.log(firstDayInWeek)          if (firstDayInWeek !== 0) {            let prevMonthLastDate=new Date(year,month,0).getDate()            for (let j = firstDayInWeek-1; j >= 0; j--) {              calendarList.push({                dateClass: this.dateCellDisabledClass,                year,                month,                day: prevMonthLastDate - j,                week: new Date(year,month-1,prevMonthLastDate - j).getDay(),                isThis: false              })            }          }          // 添加日期信息          for (let i = 0; i < daysInMonth; i++) {            let dateClass=''            let isRange=true            if(!dateRangeCode || isChangeMonth){              //日期范围值无效,已月份为范围              dateClass= (i + 1) === date ? this.dateCellActiClass : this.dateCellDefaultClass            }else{              //日期范围值有效              if(((i + 1) === date) && (calenStartDate<=(i+1) && (i+1)<=calenEndDate)){                dateClass=this.dateCellActiClass              }else if(calenStartDate<=(i+1) && (i+1)<=calenEndDate){                dateClass=this.dateCellDefaultClass              }else{                dateClass=this.dateCellDisabledClass                isRange=false              }            }            let day = {                dateClass,                year,                month:month+1,                day: i + 1,                week:new Date(year,month,i + 1).getDay(),                isThis: true,                isRange              }            ;((i + 1) === date) && (this.activeDay = day)            calendarList.push(day)          }          //如果当月的最后一天不是星期六,后面补全日期信息          if (lastDayInWeek !== 6) {            for (let i = 0; i < 6 - lastDayInWeek; i++) {              calendarList.push({                dateClass: this.dateCellDisabledClass,                year,                month: month + 2,                day: i + 1,                week: new Date(year,month+1,i + 1).getDay(),                isThis: false              })            }          }          // 如果单元格有边框,执行边框函数          if(this.cellBorder){            this.cellBorderStyle=index=> {              return {                'border-right-style':'solid',                'border-bottom-style':'solid',                'border-width':'1px',                'border-color':'#dddddd',                'border-top-style':index<7 ? 'solid' : '',                'border-left-style':index%7===0 ? 'solid' : '',              }            }          }          resolve(calendarList)        })      },      //点击选择日期      choose(day) {        console.log(day)        let {isRange,isThis}=day        if (isThis && isRange) {          //本月          this.activeDay.dateClass = this.dateCellDefaultClass          this.activeDay = day          day.dateClass = this.dateCellActiClass           let month=day.month <10 ? '0'+day.month : day.month           let myDay=day.day <10 ? '0'+day.day : day.day          this.chooseDate = day.year + '-' + month + '-' + myDay          this.$emit('getChooseDate', this.chooseDate)        }      },      // 月份改变      async changeMonth(value) {          let year=null          let month=null          let day=1          if(value===1 || value===0){              //翻页操作              if(value){                  //向上翻                  if (this.currentMonth === 11) {                      this.currentYear++                      this.currentMonth = 0                  } else {                      this.currentMonth++                  }              }else{                  // 向下翻                  if (this.currentMonth === 0) {                      this.currentYear--                      this.currentMonth = 11                  } else {                      this.currentMonth--                  }              }              year=this.currentYear              month=this.currentMonth          }else{              //时间格式              if(typeof value !=='number' && typeof value !=='string' && !(value instanceof Date)){                  console.error(`${value} 时间格式错误 `)                  return              }              if(typeof value ==='string'){                  value=value.replace(/-/g,'/')              }              year=new Date(value).getFullYear()              month=new Date(value).getMonth()              day=new Date(value).getDate()          }        await this.init(year, month,day,{isChangeMonth:true})//等待日历先创建完成        insertDataToCalendar(this.insertData,this.calendarList,this.dateProp)        this.$emit('monthChange')      },    },    watch:{      //监听插入数据      insertData:{        deep:true,        handler:function (value) {          console.log(value)          insertDataToCalendar(value,this.calendarList,this.dateProp)        },      },      //监听日期范围变化      range:{        deep:true,        handler:async function ([startTime,endTime]) {          if(startTime && endTime){            await this.init(this.currentYear, this.currentMonth,1,)//等待日历先创建完成            insertDataToCalendar(this.insertData,this.calendarList,this.dateProp)          }        },      }    }  }</script><style lang="less" scoped>  @import "./style";  @import "./flex";</style>

工具文件util.js

//获取开始日期结束日期最终信息export function getStartTimeEndTimeInfoFun(startDate,endDate){  let start=checkStartTimeEndTimeFun(startDate)  let end=checkStartTimeEndTimeFun(endDate)  let calenStartDateResult=start ? getNewDateFun(start) : false  let calenStartYear=calenStartDateResult ? calenStartDateResult.calendarAddYear : ''  let calenStartMonth=calenStartDateResult ? calenStartDateResult.calendarAddMonth : ''  let calenStartDate=calenStartDateResult ? calenStartDateResult.calendarAddDate : ''  let calenEndDateResult=end ? getNewDateFun(end) : false  let calenEndYear=calenEndDateResult ? calenEndDateResult.calendarAddYear : ''  let calenEndMonth=calenEndDateResult ? calenEndDateResult.calendarAddMonth : ''  let calenEndDate=calenEndDateResult ? calenEndDateResult.calendarAddDate : ''  if(calenStartYear!==calenEndYear || calenStartMonth!==calenEndMonth || !calenEndDate || !calenStartDate || (calenStartDate>calenEndDate)){    (start || end) && console.error(`日期范围仅支持本月范围选择或日期格式错误${startDate}  ${endDate}`)    return {dateRangeCode:0}  }  return{    calenStartYear,    calenStartMonth:calenStartMonth-1,    calenStartDate,    calenEndYear,    calenEndMonth:calenEndMonth-1,    calenEndDate,    dateRangeCode:1  }}//检查开始日期和结束日期export function checkStartTimeEndTimeFun(date){  if(typeof date!=='string' && (typeof date==='string' && !date) && typeof date!=='number' && !(date instanceof Date)){    //非时间可能的数据格式,或者明确时间没有值,跳过    return ''  }  if(typeof date==='number' && date.length<10){    console.error(`日期${date}格式错误,请使用正确的时间戳格式,例如:1593669468000`)    return ''  }  if(typeof date==='string' && date){    //传递过来的时间格式时字符串,此处不可以用parseInt,因为parseInt会将类似2020-02-02解析为2020    if(!/-/.test(date) && !/\//.test(date && !Number(date))){      console.error(`日期${date}格式错误,请使用正确的字符串日期格式,例如:2020-06-06,或2020/06/06`)      return ''    }    if(!/-/.test(date) && !/\//.test(date) && Number(date)){      date=Number(date)    }  }  return date}//将传入的时间生成新的值并返回export function getNewDateFun(date){  let calendarAddYear=null  let calendarAddMonth=null  let calendarAddDate=null  if(typeof date==='string'){    //字符串,可能时2020-03-04或2020/02/03或03-02或03/02    let dateArr=/-/.test(date) ? date.split('-') : date.split('/')    if(dateArr.length===3){      //年月日      calendarAddYear=parseInt(dateArr[0])      calendarAddMonth=parseInt(dateArr[1])      calendarAddDate=parseInt(dateArr[2])    }else if(dateArr.length===2){      //月日,年默认为今年      calendarAddYear=new Date().getFullYear()      calendarAddMonth=parseInt(dateArr[0])      calendarAddDate=parseInt(dateArr[1])    }else{      //其他未知情况,报错      console.error(`日期${date}格式错误,请使用正确的字符串日期格式,例如:2020-06-06,或2020/06/06`)      return false    }  }else{    //时间戳和时间对象    if(typeof date==='number'){      //时间戳转为时间对象      date=new Date(date)    }    calendarAddYear=date.getFullYear()    calendarAddMonth=date.getMonth()+1    calendarAddDate=date.getDate()  }  return{    calendarAddYear,    calendarAddMonth,    calendarAddDate  }}//将数据插入日历export function insertDataToCalendar(value,calendarList,dateProp){  if(!value.length || !(value instanceof Array)){    return  }  for(let i=0;i<value.length;i++){    let date=value[i][dateProp]    // console.log(date)    if(typeof date!=='string' && (typeof date==='string' && !date) && typeof date!=='number' && !(date instanceof Date)){      //非时间可能的数据格式,或者明确时间没有值,跳过      continue    }    if(typeof date==='number' && date.length<10){      console.error(`日期${date}格式错误,请使用正确的时间戳格式,例如:1593669468000`)      continue    }    if(typeof date==='string' && date){      //传递过来的时间格式时字符串,此处不可以用parseInt,因为parseInt会将类似2020-02-02解析为2020      if(!/-/.test(date) && !/\//.test(date && !Number(date))){        console.error(`日期${date}格式错误,请使用正确的字符串日期格式,例如:2020-06-06,或2020/06/06`)        continue      }      if(!/-/.test(date) && !/\//.test(date) && Number(date)){        date=Number(date)      }    }    let caldarTimeResult=getNewDateFun(date)    if(!caldarTimeResult) return    let {calendarAddYear,calendarAddMonth,calendarAddDate}=caldarTimeResult    value[i]={...value[i],calendarAddYear,calendarAddMonth,calendarAddDate}  }  for(let i=0;i<calendarList.length;i++){    let filterCalendarList=value.filter(item=>item.calendarAddYear===calendarList[i].year)//先筛选出当前年的    filterCalendarList=filterCalendarList.filter(item=>item.calendarAddMonth===calendarList[i].month)//再筛选出当前月的    filterCalendarList=filterCalendarList.filter(item=>item.calendarAddDate===calendarList[i].day)//最后筛选出当前天的    // console.log(filterCalendarList)    if(!filterCalendarList.length){      // 没有值,跳过      continue    }    // 将传过来的数据插入原日历数据    calendarList.splice(i,1,{...calendarList[i],...filterCalendarList[0],hasData:true})  }}

日期验证文件validate.js

/** * 判断日期格式是否正确,正确返回日期 */export function isValidDate(dateTime) {    let yourDate=dateTime    try {        ;(typeof dateTime === 'string') && (dateTime=dateTime.replace(/-/g,'/'))        dateTime=new Date(dateTime)        if(dateTime instanceof Date && !isNaN(dateTime.getTime())){            return {                dateTime,                isValid:true,            }        }else{            console.error(`日期 ${yourDate} 格式错误`)            return {                isValid:false,            }        }    }catch (err){        console.error(err)    }}/** * 判断是否为数字,整数或者小数 */export function isNumber(val) {    let regPos = /^\d+(\.\d+)?$/ //非负浮点数    let regNeg = /^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$/ //负浮点数    return regPos.test(val) || regNeg.test(val)}

最后是样式文件style.less和flex.less
style.less

html,body,ul,li,p,span,div{  margin:0;  padding:0;  -webkit-box-sizing: border-box;  -moz-box-sizing: border-box;  box-sizing: border-box;}.dateDefaultCss{  color: black;  cursor: pointer}.dateActiveCss{  color: white;  cursor: pointer;  background:#317ef2;}.disableDateCss{  color: #bfbfbf;  cursor: default}.custom-calendar {  .calendar-title {    padding:0 30px 0 10px;    .left {      font-size: 105%;    }    .right {      width:70px;      border:solid 1px #dddddd;      padding:0 10px;      span{        font-size: 20px;      }      span:hover {        cursor: pointer;        color: #3a82fa;      }    }    margin-bottom: 5px;    color: #000;  }  .calendar-body {    padding-bottom:10px;    .bodyTitleBox{      .body-title {        width:14%;      }    }    .calen-content{      flex-wrap:wrap;    }    .calen-cell {      color: #000;      display: table-cell;      text-align: center;      vertical-align: middle;      width:14%;      margin-bottom: 5px;      .dateSpan{        padding: 3px;        -webkit-border-radius: 50%;        -moz-border-radius: 50%;        border-radius: 50%;        width:20px;        height:20px;        margin:0 auto;      }      div{        width: 3px;        height: 3px;        border-radius: 50%;        background: #3a82fa;        margin: 0 auto;      }    }  }}

flex.less

/*flex布局样式 *//* flex布局 */.rowStart {  display: flex;  flex-direction: row;  justify-content: flex-start;  align-items: center;}.rowAround {  display: flex;  flex-direction: row;  justify-content: space-around;  align-items: center;}.rowBtween {  display: flex;  flex-direction: row;  justify-content: space-between;  align-items: center;}.rowCenter {  display: flex;  flex-direction: row;  justify-content: center;  align-items: center;}.rowEnd {  display: flex;  flex-direction: row;  justify-content: flex-end;  align-items: center;}.columnStart {  display: flex;  flex-direction: column;  justify-content: flex-start;  align-items: center;}.columnAround {  display: flex;  flex-direction: column;  justify-content: space-around;  align-items: center;}.columnBtween {  display: flex;  flex-direction: column;  justify-content: space-between;  align-items: center;}.columnCenter {  display: flex;  flex-direction: column;  justify-content: center;  align-items: center;}.columnEnd {  display: flex;  flex-direction: column;  justify-content: flex-end;  align-items: center;}

点击全文阅读


本文链接:http://zhangshiyu.com/post/113420.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1