wdnmd

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

最近项目涉及到日程管理,所以需要用到日历组件,重点是需要带有农历的日历组件。历时一个多星期,基本上市面上可见的所有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 

引入组件

    <!-- 日历组件 -->
    <FullCalendar ref="myCalendar" :options="calendarOptions" />

下面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]);
              //   <p><label>${_dateF.cDay}</label><span>${_dateF.IDayCn}</span></p>
              return {
                html: `<div style="width:100%;"><div style="float:right;padding-top:13px;font-size:20px">${_dateF.cDay}</div><div style="float:left;padding-top:12px;font-size:13px;">${_dateF.IDayCn}</div></div>`,
              };
            },
          },
          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: `<h3>${_dateF.ncWeek.slice(
                  2
                )}</h3><p><label style="padding-left:20px">${
                  _dateF.cDay
                }</label><span style="padding-left:5px">${
                  _dateF.IDayCn
                }</span></p>`,
              };
            },
          },
          timeGridDay: {
            slotMinTime: "09:00", //日视图开始时间
            slotMaxTime: "20:00", //日视图结束时间
            displayEventTime: true, //是否显示时间
            dayHeaderContent(item){
              return {html:`<h3>${item.text}</h3>`}
            }
          },
        },
        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来调整数据。

 组件部分代码

    <!-- 日历 -->
    <van-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" 
>
    </van-calendar>

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那些的东西产生的错误。感觉我变强了,我也变秃了。

posted @ 2020-07-29 16:17  FreshChick  阅读(1772)  评论(1编辑  收藏  举报