记新项目中遇到的有关农历,日历组件的所有问题。


最近项目涉及到日程管理,所以需要用到日历组件,重点是需要带有农历的日历组件。历时一个多星期,基本上市面上可见的所有vue的日历组件我都尝试过了,都有各种的问题或者不符合要求,可谓是离谱,让我对日期,日历,各种组件变得非常了解。感觉解决完我变强了许多,下面记录一下我的项目历程。

我所用过的,尝试过的日历组件:

  1.ant-design-vue的日历组件

  2.vant的日历组件

  3.vue-full-calendar

  4.lunar-calendar-vue2

  5.vue-lunar-calendar-pro

  6.vue-lunar-full-calendar

  7.@fullcalendar/vue

最后解决完采用的是,pc端@fullcalendar/vue,移动端vant。

1.ant-design-vue,a-calendar

  一开始老大没有跟我说一定要加农历,我花了两天去研究ant-design-vue的日历怎么使用,怎么渲染,然后自己写了一个日程管理。感觉月视图部分如果不需要农历,我这个还是可以的。

月视图部分

 周视图部分

 日视图部分

 说一说我写这个遇到的难点吧。

(1)周,日视图的渲染

  这部分的难点在于我们周视图这里面的东西是打竖渲染的,跟我们平常用的table是不同的,table是打横的,唯一相似的是list组件,但是list只能有一个子元素,写的也不像,找了很久没有找到比较合适的组件来写这个周视图部分,最后只好自己去写一个。思路的话就是一行一行渲染,头部周一周二这些标题部分是固定不变的,用一个data定义渲染。然后其他写一个table,table的第一列写一个自动从9.00生成到18.00的函数获取,里面的内容看需要添加。用两个数组可以渲染。

   首先头部的数组

第一天的数据

 

 其他时间数据的生成,从9点到18点。

 数据得到后在table中循环两次即可渲染出来。日视图部分会比较简单,类似于这个的时间渲染。

(2)如果往日历中写入事件,渲染

  这个一开始也是卡了很久,因为官方给的demo是calendar中的事件的渲染是每个月都渲染一次的,那种if ==才渲染,根本很难想到怎么渲染具体的日子,不可能每一个数据都写一次==来判断。

 后来找了很久,找到了个解决办法。应该是一个自动渲染来匹配当前日期的方法。将下图的数据修改为listData数组然后通过dateCellRender来渲染。

 

(3)moment.js的使用,如何根据按钮切换当前的时间,上一天下一天,上一个月等。

这也是我第一次使用moment.js,直接说最难的先把,问题卡在我想通过左右箭头切换我当前的日期,比如10号,变成9,8,7...本来理想中我是以为调用moment().substract(1,'days')就可以了,按道理这样调用了这个方法,当前日期应该会累加。但是最后的结果是只能在当前日的上一天下一天切换,这个moment对象没有改变。其中还想过用自己写一个累加器,记录+的次数,可行是可行,但是如果-了几次后马上+这个逻辑就有点问题了,比较麻烦处理。后来在群上问了人才发现,蠢了,因为如果不将改变后的moment()保存,继续改变这个moment()的话是不生效的。解决代码

2.vue-lunar-calendar-pro

  这个组件虽说是满足农历,但是相对于我想要实现的功能的话,差太多东西了,而且写事件进去也比较麻烦,所以舍弃了。

3.vue-full-calendar

  这个组件的话就是致命的缺点,没有农历

4.vue-lunar-full-calendar

  这个组件一开始我还以为找到了希望,能解决这个问题。确实效果很好,带了农历。

 但是我尝试了很久后发现这个存在一个bug,就是当当天存在事件的时候,周,日视图会报错,我本来以为是我的代码存在着哪里有问题,但是我后面去下了官网的demo,也报同样的错误,然后假如不是当天的事件,周视图那里超过今天垮日的事件的渲染也存在问题。

 在查了很多资料,问人也无果后我最终只能选择放弃这个组件了,主要是从官网下的demo也有这个问题,我就放弃了。希望有知道问题的大佬能教教。

5.lunar-calendar-vue2

  这个是一个成功的农历组件,作为显示一个小的日历还是可以的,当时也存在一个问题,就是无法标记日期。先上成功的图片。

 这个问题我觉得也很离谱,当时卡了很久。

 这是官网给的api,这里的markDate属性,他懒得写使用的demo也算了,写个如[2,6,8]结果传进去毫无反应,并且报错。最后我觉得是格式的错误,用了下new Date()格式的数据,虽然没有报错,但是日期还是无法标记。找了半天都找不到解决办法,后来在处理完大的农历组件的时候,突然茅塞顿开,用'2020-07-29'这种格式的数据放进去试了试,还真可以。我属实吐了,这写清楚能有多难,搞到我搁这恩整了半天。

6.@fullcalendar/vue 先上成功的图片

 

最后成功的版本,是fullcalendar官方版本的vue版,还是官方的东西nb,但是这中途也有一些坑,能解决还是得益于无意中发现的一篇博客https://blog.csdn.net/cha1919/article/details/107081026

非常感谢这个姐姐,不然真的解决不了这个问题。这个解决的思路是通过通过calendar.js将当前日期转换成农历,然后根据calendar.js返回的对象获取农历日期跟新历日期重新渲染月视图。

下面写写步骤吧,基本都是跟这个姐姐的一样,不过他的那里存在一个小问题已经在原博客上面给博主留言解决了,然后少发了一个formatDate的js文件,虽然不是很难但是也卡了我一下。

首先引入@fullcalendar/vue

yarn add @fullcalendar/vue @fullcalendar/core @fullcalendar/daygrid @fullcalendar/interaction @fullcalendar/list @fullcalendar/timegrid 

引入组件

    
    

下面formatDate的代码

  export default function formatDate(item){
     return item.getFullYear()+'-'+(item.getMonth()+1)+'-'+item.getDate()
  }

下面calendar的代码(农历转换的js文件)

https://github.com/jjonline/calendar.js

JS代码

// bootstrap主题
import "bootstrap/dist/css/bootstrap.css";
import "@fortawesome/fontawesome-free/css/all.css";
// 日历组件
import { Calendar } from "@fullcalendar/core";
import bootstrapPlugin from "@fullcalendar/bootstrap";
import FullCalendar from "@fullcalendar/vue";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction";
import listPlugin from "@fullcalendar/list";
import { calendar } from "@/util/calendar";
import { formatDate } from "@/util/index";
export default {
  components: {
    FullCalendar, // 日历组件
  },
  data() {
    return {// 选日历
      select: "myC",
      // 选人
      selectP: "my",
      // 文本
      content: "",
      // 选全天农历
      day: false,
      cal: false,
      // 时间选择
      startDate: "",
      startTime: "",
      endDate: "",
      endTime: "",
      // 重复类型
      repeat: "unrepeat",
      // 关联url
      url: "test-url",
      // 地址
      address: "test-address",
      // 今天的日期
      today: new Date(),
      // 日历信息的配置
      calendarOptions: {
        views: {
          //对应月视图
          dayGridMonth: {
            displayEventTime: true, //是否显示时间
            dayCellContent(item) {
              let _date = formatDate(item.date).split("-");
              let _dateF = calendar.solar2lunar(_date[0], _date[1], _date[2]);
              //   

${_dateF.IDayCn}

return { html: `
${_dateF.cDay}
${_dateF.IDayCn}
`, }; }, }, timeGridWeek: { slotMinTime: "09:00", //周视图开始时间 slotMaxTime: "20:00", //周视图结束时间 displayEventTime: true, //是否显示时间 dayHeaderContent(item) { let _date = formatDate(item.date).split("-"); let _dateF = calendar.solar2lunar(_date[0], _date[1], _date[2]); return { html: `

${_dateF.ncWeek.slice( 2 )}

${ _dateF.IDayCn }

`, }; }, }, timeGridDay: { slotMinTime: "09:00", //日视图开始时间 slotMaxTime: "20:00", //日视图结束时间 displayEventTime: true, //是否显示时间 dayHeaderContent(item){ return {html:`

${item.text}

`} } }, }, plugins: [ bootstrapPlugin, dayGridPlugin, interactionPlugin, timeGridPlugin, listPlugin, ], // 插件 //事件 events: [ { title: "event 1", date: "2020-07-01", classNames: ["cal"] }, { title: "event 2", date: "2020-07-02", classNames: ["inv"] }, { title: "公司会议", start: "2020-07-22 09:05", end: "2020-07-23 12:00", classNames: ["cal"], }, { title: "部门会议", start: new Date(), classNames: ["cal"] }, ], // 头部导航 headerToolbar: { left: "prev,next Day", center: "title", right: "today dayGridMonth,timeGridWeek,timeGridDay,listDay Create", }, // 按钮信息 buttonText: { today: "今天", month: "月", week: "周", day: "日", list: "列表视图", }, // 自定义按钮 customButtons: { Day: { text: "选择日期", click: () => { // let calendarApi = this.$refs.myCalendar.getApi(); // calendarApi.gotoDate('2020-01-11') this.timeVisible = true; }, }, Create: { text: "新建日程", // click:function(){ // this.visible=true; // console.log(this.visible) // } click: () => { this.visible = true; }, }, }, initialView: "dayGridMonth", //初始视图 locale: "zh-cn", //语言 themeSystem: "bootstrap", //主题 allDayText: "全天", //allDay时间 displayEventEnd: true, //所有视图显示结束时间 dateClick: this.handleDateClick, editable: true, selectable: true, selectMirror: true, dayMaxEvents: true, weekends: true, select: this.handleDateSelect, //选择日期事件,可响应一个新建日程的事件 eventDrop: this.handleEventDrop, //拖动事件 eventResize:this.handleEventResize, eventClick: this.handleEventClick, //点击事件对象事件 }, }; }, methods: { handleEventResize(info){ alert(info.event.title + " 结束时间变为 " + info.event.end.toISOString()); if (!confirm("确定更改吗?")) { info.revert(); } }, handleEventDrop(info) { alert( info.event.title + " 被拖动为 " + info.event.start.toISOString() ); if (!confirm("确定更改吗?")) { info.revert(); } }, // 添加事件 handleDateSelect(selectInfo) { let title = prompt("请输入一个事件的标题"); let calendarApi = selectInfo.view.calendar; calendarApi.unselect(); // clear date selection if (title) { calendarApi.addEvent({ // id: createEventId(), title, start: selectInfo.startStr, end: selectInfo.endStr, allDay: selectInfo.allDay, classNames: ["cal"], }); } }, // 删除事件 handleEventClick(clickInfo) { if (confirm(`你确定要删除'${clickInfo.event.title}'事件吗`)) { clickInfo.event.remove(); } }, // 重复类型 repeatChange(value) { this.repeat = value; console.log(this.repeat); }, // 选谁的日历 selectChange(value) { this.select = value; console.log(this.select); }, // 选谁 peopleChange(value) { this.selectP = value; console.log(this.selectP); }, // 是否全天 dayChange(e) { this.day = e.target.checked; console.log(`checked = ${e.target.checked}`); console.log(this.day); }, // 是否农历 calChange(e) { this.cal = e.target.checked; console.log(`checked = ${e.target.checked}`); console.log(this.cal); }, }, };

其中我用了boostrap主题,这个引入的方式我就不写了,官网可查。

这里其他的其实都是正常的代码,说说最关键的农历部分的渲染吧。

主要是这部分处理渲染获取农历对象后,再在下面html:``中渲染你想展示的东西,那个对象还包括很多东西可以根据需要去选择。这样就可以完美处理。

中间还有一个bug,按照我上面的代码没问题,但是原博客的代码没有timeGridDay,我加上这个后,初次渲染没有星期几显示undefined,切换上一天下一天后才能处理,找不到办法,后来加了那个博客的姐姐,帮我解决了这个问题。

 日视图部分也得自己定义渲染,不然就会有那个bug不知道为什么,到此,这个带农历的日程管理就完美解决了。对了,还有一个可能大家会找不到的就是这个calendar里面的方法使用,比如prev,next,gotoDate这些,带callback的方法直接定义一个方法就可以了,这些在vue中找不到明确的例子。后来找了很久我突然发现,人家官网写的明明白白,

 还是英文文档看得少,当时没仔细。然后这个问题也解决了,通过gotoDate实现了自定义按钮,选择日期功能。

7.vant的calendar  先上成功的效果

  实现完pc端的后感觉基本上是茅塞顿开,然后用看了下vant可以自定义日历中的内容。

  然后vant里面有一个formatter来调整数据。

 组件部分代码

    
    calendar
  :poppable="false"
  :show-confirm="false"
  :show-title="false"
  :show-subtitle="false"
  :min-date="minDate"
  :max-date="maxDate"
  :default-date="Today"
  :style="{ height: '500px' }"
  color="#4EB9CE"
  @select="select"
   :formatter="formatter" 
>
    

formatter代码

        formatter(day) {
            var tmp=formatDate(day.date).split('-');
            var lunar=calendar.solar2lunar(tmp[0],tmp[1],tmp[2])
            day.topInfo=lunar.IDayCn 
            day.className="tes"
            // console.log(day)
    //   const month = day.date.getMonth() + 1;
      const date = day.date.getDate();
      
      return day;
    },

这个思路跟实现其实跟pc端的是一模一样,然后通过vant自带的topInfo将农历添加上去,实现后比较美观。

再顺便插一个vant日历中我遇到的一个bug吧,找了很久找不到解决办法,不知道是不是官网的bug?

vant calendar中的subtitle属性,设置为false以后多了一个month-title,相当于没作用,

 

 仅仅是从图上变成了图下,完全不能符合我的预期,title属性false倒是消失了。

最后是问同学用样式穿透解决了,没想到可以直接写未渲染的元素,通过f12找到那个多出来的title,样式中通过样式穿透设置为display:none即可。

::v-deep .van-calendar__month-title{
    display: none;
}

 完美解决pc端移动端的所有问题,解决完后很开心,虽然每个问题写下来看起来也就那样,但是过程属实有点痛苦,尝试了各种办法,。

总结一下,还是得学会看文档,多看英文文档,看文档得仔细。还得多敲,多加强知识,中间还遇到了不少是es6那些的东西产生的错误。感觉我变强了,我也变秃了。

相关