preface

  • Date selection must be done by many people. Recently, we are working on a mobile terminal project. The product wants to create a date and time selection interface similar to this.

  • From this interface, the component library of our normal date and time selection certainly does not meet the requirements directly, but some parts do; The time on the right is ok, but what about the date and day on the left?
  • No year on the screen? So I’m going to do a little foreshadowing here, and we’ll see later.
  • Let’s implement this requirement step by step. I’m usingVue + Vant.Vant website.

start

Base Component selection

  • First we need to look at the correspondingUIComponent library to find the components that basically meet the needs, I mainly looked for two components one isDatetimePicker Time selectionAnd the other one isPicker selector.
  • Either should be possible, and I chosePicker selectorComponents,DatetimePicker Time selectionComponent how to implement can try yourself.

The data structure

  • We can see from the picture that this requires three columns. The first column shows the date and week, the second column shows the hour, and the last column shows the minute.

1, the HTML

<van-picker title=" title "show-toolbar: Columns ="dateColumns" @confirm="onConfirm" @cancel="onCancel" @change="onChange" /> <van-picker title=" title" show-toolbar: Columns ="dateColumns" @confirm="onConfirm" @change="onChange" />Copy the code

2, the vue

  • We initialize one after we enter the pagedateColumns, including the default first column and the next two column times. At the back of theonChangegetNewDateArrayThe method is where we implement the logic later.
export default { ... Data () {dateColumns: [], defaultYear: new Date().getFullYear(), // Storage year, default current year dateArray: [], // locate () {this.datecolumns = [this.getNewDatearray (),// locate () {this.datecolumns = [this.getNewDatearray (),// locate () {this.datecolumns = [this.getNewDatearray (),// Locate (); Array.from({length: 24}, (v, k) => {if (k < 10) {k = '0' ${k} '} return k;}), defaultIndex: 1,}, {// Array.from({ length: 60 }, (v, k) => { if (k < 10) { k = `0${k}` } return k; }), defaultIndex: 1, }]; Method :{onConfirm(picker, values) {... }, onChange(picker, values) { console.log('piker', picker, values) // ... }, onCancel() { ... }, getNewDateArray(date, flag, picker, type) { ... }Copy the code

Logical analysis

  • The first XX is the month, the second XX is the day, and the third XX is the day of the week. All we need to use is the month (onChange getDatearray). This will be explained later in the ‘questions’ directory), we need to use this to determine whether it is the first month or the last month at the end of each scroll, and reset the month and year.

    1. When scrolling to the first column of the dateColumns values array, the last month’s data needs to be loaded; 2. When scrolling to the end of the values array in the first column of dateColumns, you need to load the data of the next month; When scrolling to the first value of the dateColumns column, check whether month is the first month. If it is the first month, reset month to the 12th month and set the default year to -1. When scrolling to the end of the values array in the first column of dateColumns, we need to determine whether month is the last month. If it is the last month, we need to reset the month to the first month and set the default year to +1.

  • The relevant codes are as follows

onChange(picker, values) { console.log('piker', picker, values) const dateArr = values[0].text.split(' ')[0].slice(0, 1). The split (" month "); const month = dateArr[0]; // If (values[0] === this.dateArray[0]) {// Select 'dateColumns' first column' values' first let newMonth; If (month === '1') {// Check whether 'month' is' the first month 'newMonth = 12; this.defaultYear -= 1; } else { newMonth = Number(month) - 1; } this.getNewDateArray(`${this.defaultYear}-${newMonth}-1`, false, picker, } else if (values[0] === this.datearray [this.datearray.length-1]) {// Determine the last let of 'dateColumns' array newMonth; If (month === '12') {// Check whether 'month' is' last month 'newMonth = 1; this.defaultYear += 1; } else { newMonth = Number(month) + 1 } this.getNewDateArray(`${this.defaultYear}-${newMonth}-1`, false, picker, false) } },Copy the code

Initialize the

  • The first time we enter the page we initialize the data for the current month.

1. We need to get the total number of days in the current month; 2. Generate the corresponding data format on UI, each time need to get the day of the week by the current date; 3, define an array from Monday to Sunday to facilitate the corresponding value. Note that Sunday returns 0. 4. Check whether flag is true to return the object.

The code is as follows:

Const weekArray = [' Sunday ', 'on Monday, Tuesday, Wednesday, Thursday, Friday,' on Saturday]. /** * date: * flag: whether it is the first time to enter * picker: instance of picker * type: */ getNewDateArray(date, flag, picker, type) {date = date? new Date(date) : new Date(); let month = date.getMonth() + 1; // Store monthDays const monthDays = new Date(date.getFullYear(), month, 0).getDate(); // Store monthDays const monthDays = new Date(date.getFullYear(), month, 0).getDate(); // Get the total number of days in the current month let arr = []; let index = 0; for (let i = 1; i <= monthDays; I + +) {let STR = ` ${month} month ${I}, ${weekArray [new Date (` ${Date. GetFullYear ()} - ${month} - ${I} `). GetDay ()]} ` arr. Push (STR);  }... // code if(flag) {return {values: arr, defaultIndex: date.getDate() -1}}... // code }Copy the code

Then scroll up and down to load

  • The code of what is only the data initialized when entering for the first time. Sliding up and down to the start or end does not add content, so the code of what is supplemented and modified.

1. Determine whether scroll down to one or scroll up to the last by type; 2. Scroll down to a total number of days in the previous month that you added by default and add the new month to the dateArray array; 3. Scroll up to the last array and set the default value to the length -1 for adding the new month, since the index for column starts at 0. And appends the new month to the dateArray array; 4. If flag is false, call Picker instance to modify existing values and default selected indexes.

The code is as follows:

. // code + if(type) { + this.dateArray = arr.concat(this.dateArray); // Add to dateArray array + index = monthDays; + } else { + index = this.dateArray.length - 1; + this.datearray = this.datearray.concat (arr); If (flag) {return {+ values: this.dateArray, -values: arr, defaultIndex: date.getDate() - 1 } + } else { + picker.setColumnValues(0, this.dateArray) + picker.setColumnIndex(0, Index) // Set default}Copy the code

Done?

  • We just saw that it’s just a matter of choiceUIInterface, then after the selection is completed, how to display it? Let’s take a look at the ones that have been chosenUIInterface?

  • Hahahahaha,wtfAnd the year? They don’t show the year? Just to be safe, we’re going to add the year, and of course we’re going to use that on the back end.

  • Looking at the documentation, I see that the value of the array can be an object, which is displayed astextField, then we put the generated datastrLet’s change that structure.

  • The code is as follows:
onChange() { - if(values[0] === this.dateArray[0]) { + if(values[0].text === this.dateArray[0].text) { ... - } else if (values[0] === this.dateArray[this.dateArray.length - 1]) { + } else if (values[0].text === this.dateArray[this.dateArray.length - 1].text) { ... } }, getNewDateArray(date, flag, picker, Type) {- let STR = ` ${month} month ${I}, ${weekArray [new Date (` ${Date. GetFullYear ()} - ${month} - ${I} `). GetDay ()]} ` + let STR = { + year: date.getFullYear(),, + text: ` ${month} month ${I}, ${weekArray [new Date (` ${newYear} - ${month} - ${I} `). GetDay ()]} ` +}; }Copy the code
  • If the data structure is added, does it take effect? Let’s look at the interface;

  • There is nothing wrong with the interface, so can we get the data? We are inonChangePrint the currently selectedvalues;

  • Ok, perfect! Year’s problem solved, don’t you thinkso easy.

Try again?

  • Try the current date on the first or last day of the current month.Did you find something? If it’s day 1, you’re not going to load the previous month’s data because there’s no triggeronChangeEvent, you can pull up to the new date, and then pull down to the first one, so it will refresh, of course, if your product can accept that is also ok; Same thing on the last day.
The solution
  • We can add the previous month or the next month by determining whether today is the first or last day of the month at the beginning;

Add an identifier variable getMoreMonth, false: no more needs to be fetched. 1: the previous month; 2: the next month; 2. Add a length identifier variable moreLen initialized to the current date to store the length that needs to be added to the default selection; 3. Obtain the current year and month, determine the month before or after the acquisition, and reset the year and month; 4. Generate a new array of a month, determine whether it is the month before or after, generate the month before all data into the new array; Subsequent months just push into the previous ARR array; 5. After the loop is complete, set the index length to be increased. If it is the previous month (getMoreMonth === 1), add moreLen to the total number of days in the previous month. And concatenate the new array with the previous array;

  • Add the following code:
getNewDateArray(date, flag, picker, type) { + let getMoreMonth = false; // Do not need to fetch more 1. + if(flag) {// const newDate = newDate (); + const monthDays = new Date(newDate.getFullYear(), newDate.getMonth() + 1, 0).getDate() + if(new Date().getDate() === 1) {// first day of the month + getMoreMonth = 1; +} else if (new Date().getDate() === monthDays) {// getMoreMonth = 2; + } + } date = date ? new Date(date) : new Date(); . for (let i = 1; i <= monthDays; i ++) { let str = { year: date.getFullYear(), text: ` ${month} month ${I}, ${weekArray [new Date (` ${Date. GetFullYear ()} - ${month} - ${I} `). GetDay ()]} `}; arr.push(str); } + let moreLen = date.getDate() - 1; + if(getMoreMonth) { + let newYear = date.getFullYear(); + if (month === 1) {if (month === 1) {+ month = 12; + newYear -= 1; + } else { + month -= 1; +} +} else {+ if (month === 12) {// Last day of month + month = 1; + newYear += 1; + } else { + month += 1; + } + } + const moreMonthDays = new Date(newYear, month, 0).getDate(); + let beforeArray = []; + for (let i = 1; i <= moreMonthDays; i ++) { + let str = { + year: newYear, + text: ` ${month} month ${I}, ${weekArray [new Date (` ${newYear} - ${month} - ${I} `). GetDay ()]} ` +}; + if (getMoreMonth === 2) {push + arr.push(STR); + } else { + beforeArray.push(str); +} +} + moreLen = getMoreMonth === 1? (moreLen + beforeArray.length) : moreLen; // Get index of previous month Total days of previous month + arr = beforeArray.concat(arr); + } if(flag) { return { values: this.dateArray, - defaultIndex: date.getDate() - 1 + defaultIndex: moreLen } } else { ...Copy the code

Test the

  • Modify themountedIs the first day of the current month and is modifiedgetNewDateArrayThe first logic that determines whether to enter for the first time.
  • The code is modified as follows:
Mounted () {this.datecolumns = [- this.getNewDatearray ('2020-11-1'),// first column + this.getNewDatearray ('2020-11-1'), }, getNewDateArray(date, flag, picker, type) {let getMoreMonth = false; If (flag) {-const newDate = newDate (); + const newDate = newDate (Date); if(flag) {-const newDate = newDate (Date);  const monthDays = new Date(newDate.getFullYear(), newDate.getMonth() + 1, 0).getDate() - if(new Date().getDate() === 1) { + if(new Date(date).getDate() === 1) { + console.log('first day') getMoreMonth = 1; } else if (new Date().getDate() === monthDays) { getMoreMonth = 2; } } ... }Copy the code
  • Take a look at the effect of the test, get the previous month no problem, and then take a look at the next month.

  • Get the next month, modify the code as follows:
Mounted () {this.datecolumns = [- this.getNewDatearray ('2020-11-1', True),// first column + this.getNewDatearray ('2020-11-1',) }, getNewDateArray(date, flag, picker, type) {let getMoreMonth = false; If (flag) {-const newDate = newDate (Date); + const newDate = newDate (); if(flag) {-const newDate = newDate (Date);  const monthDays = new Date(newDate.getFullYear(), newDate.getMonth() + 1, 0).getDate() + if(new Date().getDate() === 1) { - if(new Date(date).getDate() === 1) { - console.log('first day') getMoreMonth = 1;  - } else if (new Date().getDate() === monthDays) { + } else if (new Date(date).getDate() === monthDays) { + console.log('last day') getMoreMonth = 2; } } ... }Copy the code
  • Ok, no problem, basically solved for the current problem.

doubt

${this.defaultYear}-${newMonth}-1, false, picker, true); ${this.defaultYear}-${newMonth}-1, false, picker, true) There’s a pit, and I thought I needed to use it, so I sent it; However, when I used it later, I found that when I scroll to the end of January, I loaded March instead of February.

  • Preload all comments of the code for two months on the first or last day of the month (for testing purposes) and modify the code test as follows to get the above results:
Mounted () {this.datecolumns = [- this.getNewDatearray ('2020-11-30', true),// True),// first column...]  }, onChange(picker, values) { ... const month = dateArr[0]; // Current month const day = dateArr[1]; // Day of the month if(...) {... this.getNewDateArray(`${this.defaultYear}-${newMonth}-${day}`, false, picker, true) } else if (...) {... this.getNewDateArray(`${this.defaultYear}-${newMonth}-${day}`, false, picker, false) } }Copy the code
  • And the reason for that is because I get it here2020Years of1The last day of the month is31No, but2Month only29Days, sogetMonth()And what you get is2Again,+ 1Becomes a3.

Point can be optimized

1, each time the first or the last to load, can be optimized to scroll to load? 2. Because the code is written in a hurry, some points can be optimized.

Complete code and use

Component code

// customSelectDate.vue <template> <van-popup class="time-custom-picker" v-model="showDatepicker" position="bottom" :style="{ height: '300px'}"> <van-picker title=" select time "show-toolbar :visible-item-count="5" :columns="dateColumns" @confirm="onConfirm" @cancel="onCancel" @change="onChange" /> </van-popup> </template> <script> import { weekArray } from '@/assets/js/constant'; const timeArray = [ { values: Array.from({ length: 24}, (n, k) = > {the if (k < 10) {k = ${k} ` ` 0} return k;}), defaultIndex: 24,}, {values: [' : ']}, {/ / the second column values: Array.from({ length: 60 }, (v, k) => { if (k < 10) { k = `0${k}` } return k; }), defaultIndex: Export default {name: 'publishSelectDate', props: {}, components: {}, data() {return {showDatepicker: false, dateColumns: [], defaultYear: new Date().getFullYear(), dateArray: [], } }, created() {}, computed: {}, mounted() {this.datecolumns = [this.getNewDatearray (undefined, true),// first...timeArray]; }, methods: { onConfirm(values, indexs) { this.showDatepicker = ! This. ShowDatepicker this.$emit('select-date', values)}, Values) {const dateArr = values [0]. The text. The split (' ') [0]. Slice (0, 1). The split (" month "); const month = dateArr[0]; If (values[0].text === this.datearRay [0].text) {let newMonth; if(month === '1') { newMonth = 12; this.defaultYear -= 1; } else { newMonth = Number(month) - 1; } this.getNewDateArray(' ${this.defaultYear}/${newMonth}/1 ', false, picker, true)} Elseif (values[0].text === this.datearray [this.datearray.length-1].text) {let newMonth; if(month === '12') { newMonth = 1; this.defaultYear += 1; } else { newMonth = Number(month) + 1 } this.getNewDateArray(`${this.defaultYear}/${newMonth}/1`, false, picker, false) } }, onCancel() { this.showDatepicker = ! this.showDatepicker }, getNewDateArray(date, flag, picker, type) { let getMoreMonth = false; // Do not need to fetch more 1. If (flag) {// whether the first const newDate = newDate (); const monthDays = new Date(newDate.getFullYear(), newDate.getMonth() + 1, 0).getDate() if(new Date().getDate() === 1) {// getMoreMonth = 1; } else if (new Date().getDate() === monthDays) {// getMoreMonth = 2; } } date = date ? new Date(date) : new Date(); let month = date.getMonth() + 1; const monthDays = new Date(date.getFullYear(), month, 0).getDate(); let arr = []; let index = 0; For (let I = 1; i <= monthDays; i ++) { let str = { year: date.getFullYear(), text: ` ${month} month ${I}, ${weekArray [new Date (` ${Date. GetFullYear ()} / ${month} / ${I} `). GetDay ()]} `}; arr.push(str); } let moreLen = 0; If (getMoreMonth) {let newYear = date.getFullYear(); If (getMoreMonth === 1) {if (month === 1) {month = 12; newYear -= 1; } else { month -= 1; If (month === 12) {month = 1; newYear += 1; } else { month += 1; } } const moreMonthDays = new Date(newYear, month, 0).getDate(); let beforeArray = []; For (let I = 1; i <= moreMonthDays; i ++) { let str = { year: newYear, text: ` ${month} month ${I}, ${weekArray [new Date (` ${newYear} / ${month} / ${I} `). GetDay ()]} `}; If (getMoreMonth === 2) {// Push arr. Push (STR); } else {// Add last month to last month array beforeArray.push(STR); MoreLen = getMoreMonth === 1? beforeArray.length : 0; arr = beforeArray.concat(arr); If (type) {this.datearray = arr. Concat (this.datearray); index = monthDays; } else { index = this.dateArray.length - 1; this.dateArray = this.dateArray.concat(arr); } if(flag) {return {values: this.dateArray, defaultIndex: getMoreMonth === 1? moreLen + date.getDate() - 1 : date.getDate() - 1 } } else { picker.setColumnValues(0, this.dateArray) picker.setColumnIndex(0, index) } }, } } </script> <style lang="scss" scope> .time-custom-picker { font-family: PingFangSC, PingFangSC-Regular; color: #000000; .van-picker { .van-picker__toolbar { .van-picker__confirm { color: #1795ff; } } .van-picker__columns { .van-picker-column { flex: none; } .van-picker-column:first-child { width: 200px; } .van-picker-column:nth-child(2), .van-picker-column:nth-child(4) { width: 60px; } } } } </style>Copy the code

use

<custom-select-date ref="selectDate" @select-date="onConfirm"></custom-select-date> onConfirm(values) { this.checkedYear  = values[0].year; this.form.submitDeadline = `${values[0].text} ${values[1]}:${values[3]}`; },Copy the code

conclusion

  • Through the product of the prototype andUIIf the existing UI library cannot fully meet the requirements, we can find a high similarity to modify.
  • After the functionality is done, you need to do some testing against some critical points.
  • I got a thumbs up here. Handsome guy and beautiful girl. Thank you.