記新專案中遇到的有關農曆,日曆元件的所有問題。

FreshChick發表於2020-07-29

最近專案涉及到日程管理,所以需要用到日曆元件,重點是需要帶有農曆的日曆元件。歷時一個多星期,基本上市面上可見的所有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那些的東西產生的錯誤。感覺我變強了,我也變禿了。

相關文章