Vue plug-ins increased by 20 date picker

A collection of plug-ins before yourself realize Date Picker, recently nothing, to think ElementUI modeled on the DatePicker, and he wrote a simple date picker, the thought that not too much trouble, only to find there are a lot of hands-on problems to be solved. And after the finish, only to find scalability poor, from the level of ElementUI far worse, the next step is to learn about ElementUI source code, look at the gap between themselves.

structure

I selected this date will be made in the form of plug-in Vue, there are three files:

- index.js
- MyDate.js
- MyDatePicker.vue

index.jsVery simple, only a method intstall, in installwhich registered global components:

import MyDatePicker from './MyDatePicker'

export default {
  install(Vue) {
    Vue.component('MyDatePicker', MyDatePicker)
  }
}

MyDatePicker.vueIs part of the UI, where custom styles and interactive events, I will put the data alone MyDate.js, the export in the form of Class

Data section

I order is to complete MyDate.jsthe data portion of a date selector The basic structure is as follows:

Finally deriving a two-dimensional array, a two-dimensional array comprises six outer array, each row of data corresponding to the date selector, the inner layer and comprising an array of object elements 7, corresponding to Monday to Sunday, this is equivalent to a total of 42 elements corresponding to the panel 42 just dates.

When you select a date, first through the new Datefirst day of the month to get the current date where the constructor and the day of the week

// 当前选择日期所在月的第一天及这一天是星期几
const firstDayOfCurrentMonth = new Date(this.current.year, this.current.month - 1);
const firstDayOfWeek = firstDayOfCurrentMonth.getDay();

It should be noted that new Date().getMonth()the range [0, 11]and we use every day of the month is less 1of. And I currentkept inside date is used to display, has been added 1, so the need to reduce the above1

Next, a two-dimensional array is generated by traversing we need two layers, the outer layer is traversed line data corresponding to:

for (let row = 0; row < 6; row++) {
}

The key is the inner layer data, assuming that we have chosen is in June 2019, June 1st is a Saturday, the seven elements of internal array of two-dimensional array which should correspond to kiss ['日', '一', '二', '三', '四', '五', '六'], and now Saturday, June 1, 2009, it is located in an array the seventh, filled the array of results should be:

[5.26, 5.27, 5.28, 5.29, 5.30, 5.31, 6.1]

JS's Dateconstructor date will automatically be converted beyond the current month, meaning that, when we construct a date new Date(2019, 5, 0), it will automatically forward fall day, the resulting date is2019-05-31

So the above into an array corresponding to June, the number of days is:

[-5, -4, -3, -2, -1, 0, 1]

So the current range is traversed [-5, 1], starting point of the week of June 1 there is a relationship a few:

// 内层遍历起始点
let weekLoopStart = -firstDayOfWeek + 1; // -5

End point is7 + weekLoopStart - 1

So that when the inner end of a traverse, to weekLoopStartadd 7, can generate a new line of data:

[2, 3 4, 5, 6, 7, 8]

So basically in the form of two traversal it is:

// 行数据
for (let row = 0; row < 6; row++) {
  const rowDate = [];
  // 列数据
  for (let weekDay = weekLoopStart; weekDay <= 7 + weekLoopStart - 1; weekDay++) {
    // 生成需要的对象
  }
  weekLoopStart += 7;
  this.dates.push(rowDate)
}

Around a bit, but scalability is not very good, or too stupid.

Traversing the inner layer generated objects have several attributes:

const targetDate = new Date(this.current.year, this.current.month - 1, weekDay);
const day = targetDate.getDate();
const month = targetDate.getMonth() + 1;
rowDate.push({
  date: targetDate,
  value: format(targetDate),
  label: day,
  key: weekDay,
  isCurrentMonth: month === this.current.month,
  isToday: +targetDate === +this.today
})

dateStandard date is the object, valueis formatted for display dates selected, labelthe date selected in the calendar, keythe entire traversal of its actual numbers, isCurrentused to determine whether the date of the current month, or in -5such a format to the previous month's date (such date is on the panel have different styles), isTodayto identify today's date:

This creates a two-dimensional array put this.datesthis in the instance attributes

When changing selection of the month, the date will change with the panel, corresponding to the example method is changeDate, just because the data generated getDateArraymethods are dependent on the this.currentcarried out, it is only necessary to change the this.currentvalue, and repeat getDateArraythe method on the line

// 改变日期
changeDate(date = new Date()) {
  this.current = {
    year: date.getFullYear(),
    month: date.getMonth() + 1
  };
  this.getDateArray()
}

Such basic data is complete.

UI part

UI part in the .vuesingle-file finished assembly, the panel used <table>labels, the dateintroduction of which MyDateexamples are declared to calculate the remaining attributes, and MaDateassociated with an instance, so that the formation of such a change process:

There are three things to record what

(1) setting cell types

<tr v-for="(row, rowIndex) in tbody" :key="'row-'  + rowIndex">
  <td v-for="cell in row" :key="cell.key" @mousedown="selectDate(cell)">
    <span :class="tableCellClass(cell)">{{cell.label}}</span>
  </td>
</tr>

Because the original cells and data traversing cella relationship, if there is a lot of code template unloading, less intuitive, to produce an object with a computed attribute parameters passed there is no way, it is possible to use a methodreturn pass an object:class

// 设定日期单元格样式
tableCellClass(cell) {
  return {
    'not-current-month': !cell.isCurrentMonth,
    today: cell.isToday,
    selected: cell.value === this.selectedDate
  }
},

(2) animation

Animation ElementUI is slipping out upwards

It is through the Vue <transition>components implemented, and <transition>is achieved JS animation used requestAnimationFramein the API, very smooth, and easy to reuse. Find time to look good or better than the Vue source code, to learn about.

I use CSS animation to achieve, when the selection box, add a class container-visible, the original heightby the 0change 320px, while opacitya 0change 1, while adding will-changeand transform: translateZ(0)to improve performance:

.date-container {
  position: absolute;
  left: 0;
  top: 46px;
  color: #606266;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
  background: #fff;
  border-radius: 4px;
  line-height: 30px;
  margin: 5px 0;
  transition: all 0.5s ease;
  border: 1px solid #e4e7ed;
  height: 0;
  overflow: hidden;
  will-change: height;
  opacity: 0;
  transform: translateZ(0);
}
.container-visible {
  height: 320px;
  opacity: 1;
}

The effect can also be achieved, but there are two problems, one is not a good reuse, but need to change a fixed height, if the panel height changes, the effect is likely to be biased

(3) The third problem is the date selection box appears and hide its specific logic is as follows:

  1. Click on the input box, a selection box
  2. Click the input box and select the section outside the box, selection box disappears
  3. Click the input box and the inner part of the selection box, select the boxes do not disappear
  4. Click the selection box to quickly select the month (a few small arrow), select the box corresponding change occurs, do not go away
  5. Click on a specific date selection box, select the box disappears, choose success

I have chosen is the use <input>of focusand blurevents, when two events occur, changing the control variable selection box is displayedcontainerVisible

focusNo problem, but blurhas a big problem, when the problem first encountered when you click on the button functions and selection boxes period, did not trigger the corresponding function, selection box disappears (previously encountered a similar business in the development of problems), mainly because of blurthe timing of events:

click事件发生之前blur事件就发生了,导致click事件没有发生时,元素就隐藏了,click事件无效。

所以像以前一样,将选择框绑定的click事件改为了mousedown事件,这样做的效果是,点击日期能够选择了,并且选择事件执行了,并且之后选择框失效了,这时候上面的五条逻辑满足了1/2/5,但是3/4又出问题了,点击选择框的小按钮,选择框意外消失了。

之所以这样,是因为mousedown事件之后,blur事件执行,导致选择框小事,我们要做的是在mousedown之后,不触发blur事件,所以应该使用peventDefault方法(注意不是stopImmdeiation,因为不是冒泡导致的),Vue中提供的修饰符是prevent,所以在所有的mousedown事件后面添加上修饰符prevnet

<button type="button" class="btn next-month-btn" @mousedown="changeMonth(1)">
 <span class="iconfont icon-el-icon-arrow-right"></span>
</button>

这样条件4满足了,但是3不行,所以需要在整个选择框的容器上添加一个mousedonw事件,并且使用prevnet修饰符,里面的点击事件只需要使用mousedown就可以了

<div class="date-container" :class="{'container-visible': containerVisible}" @mousedown.prevent>
</div>

这样基本上就成功了,但是还是有一些小瑕疵,一个问题就是blur事件发生的过于容易,比如我点击浏览器之外的桌面部分,blur事件也会发生,选择框会消失,而ElementUI的并不会消失,还有就是绑定了没有必要的点击事件,不好复用,并且不知道如果同时有多个弹出框的时候还不会有其他的问题。

ElementUI是把这块单独提出了一个方法,位于element/src/utils/clickoutside.js,它对这种情况的点击事件做了统一的处理,主要的思路就是在document绑定了统一的点击事件,通过收集此刻的弹窗元素到一个队列中,隐藏这个队列中的元素,它没有使用blur事件,更可控,也更适合更多的元素。

优化

这个日期选择器插件的基本功能能够满足,但是如果作为ElementUI那样的轮子,还差的很多,扩展非常困难(快速选择月、年的面板我就没有做)

下一步的计划就是首先学习clickoutside的实现,然后学习ElementUI的源码,这个计划也好久了,要尽快执行啊~

最后,代码都在这里

Guess you like

Origin blog.csdn.net/duola8789/article/details/92796117