1. Demonstration of results
1.1 Date switching
1.2 Click on a multiple-choice
1.3 Sliding multi-select
2. Implement basic date rendering
2.1 train of thought
2.1.1 Date object methods to be used
new Date(2020, 4, 1).getDay()
Calculates the day of the week that was passed in, and returns the value0 means Sunday.1 means MondayAnd so on…new Date(2020, 4, 0).getDate()
Calculate the total number of days in that month for the incoming month,The third parameter is 0 to calculate the total number of days!!
2.1.2 Analyze the calendar date structure
- A date table with six rows and seven columns
42
This is fixed (important!).- In general, the dates of last month, this month and next month are displayed, but in the special case where the first day of the month falls on a Monday, only the dates of this month and next month are displayed.
- Also note that in January and December each year, the year before and the year after, respectively, should be
- 1
or+ 1
2.1.3 How to calculate the date of last month
When the first day of the month is not a Monday, the date of the previous month must be displayed
- Calculate the first day of the month (
n
.0 <= n <= 6
), then it will be shown last monthn-1
God, watch outn
To zero,n
It’s going to be 7- Count the number of days in a month
- Cycle add date – cycle start: days in the last month -n + 2, cycle end: <= days in the last month
2.1.4 How to calculate the date of next month
- The current date total 42 minusThe days of the monthsubtracting
n-1
, get the number of days to show in the next month- Loop add date: start of loop: 1, end of loop: <= result of first step
2.1.5 2d array rendering generates calendar
The above steps generate the last month’s date and the next month’s date and the current month’s date, and the combination of the three arrays is just a one-dimensional array of length 42. But our table is structured like TR -> TD, so we have to convert a one-dimensional array into a two-dimensional array in order to iterate through the basic calendar style.
2.2 Implementation Code
2.2.1 Calendar. Vue
<template>
<div class="calendar">
<table class="calendar-table">
<thead>
<tr>
<th v-for="(item, i) in weeks" :key="i">{{ item }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(dates, i) in res" :key="i">
<td
v-for="(item, index) in dates"
:key="index"
:class="{notCurMonth: ! item.isCurMonth, currentDay: item.date === curDate}"
>
<! -- <span>{{ item.date.split('-').slice(1).join('-') }}</span> -->
<span>{{ item.date }}</span>
<slot :data="item" />
</td>
</tr>
</tbody>
</table>
</div>
</template>
Copy the code
<script>
data() {
return {
weeks: ['一'.'二'.'三'.'four'.'five'.'六'.'day'].curYear: new Date().getFullYear(), / / the current year
curMonth: new Date().getMonth(), / / the current month
days: 0.// Total number of days in the current month
curDate: parseTime(new Date().getTime()), // Current date In yyyY-MM-DD format, which is used to match the current date
prevDays: [].// The date not displayed in the previous month of the current month
rearDays: [].// Display date not next month of the current month
curDays: [].// Date of the current month
showDays: [].// A total of 42 dates are displayed
res: [].// A two-dimensional array}},created() {
// Render the current month by default
this.handleGetDays(this.curYear, this.curMonth)
},
methods() {
handleGetDays(year, month) {
this.showDays = []
this.days = getDaysInMonth(year, month) // Get the number of days in the month
let firstDayOfWeek = new Date(year, month, 1).getDay() // Get the day of the month [0, 6]
if (firstDayOfWeek === 0) { // Sunday is 0 Monday is 1, and so on
firstDayOfWeek = 7
}
this.prevDays = handleCrateDate(year, month, 1, firstDayOfWeek, 'prev')
this.curDays = handleCrateDate(year, month, 1.this.days)
this.rearDays = handleCrateDate(year, month, 1.42 - this.days - (firstDayOfWeek - 1), 'rear')
this.showDays.unshift(... this.prevDays)this.showDays.push(... this.curDays)this.showDays.push(... this.rearDays)this.res = this.handleFormatDates(this.showDays)
},
handleFormatDates(arr, size = 7) { // Pass in the original array of length 42, and finally convert it to a two-dimensional array
const arr2 = []
for (let i = 0; i < size; i++) {
const temp = arr.slice(i * size, i * size + size)
arr2.push(temp)
}
console.log(arr2)
return arr2
},
}
</script>
Copy the code
2.2.2 index. Js
// Get the number of days in the month
export const getDaysInMonth = (year, month) = > {
const day = new Date(year, month + 1.0)
return day.getDate()
}
// Create an array of dates in YYYY-MM-DD format
export const handleCrateDate = (year, month, start, end, type) = > {
const arr = []
if (type === 'prev') { / / in January
if (start === end) return []
const daysInLastMonth = getDaysInMonth(year, month - 1) // Get the number of days in the previous month
for (let i = daysInLastMonth - end + 2; i <= daysInLastMonth; i++) {
arr.push({
date: parseTime(new Date(year, month - 1, i)),
isCurMonth: false}}})else if (type === 'rear') { / / next January
for (let i = start; i <= end; i++) {
arr.push({
date: parseTime(new Date(year, month + 1, i)),
isCurMonth: false}}})else { / / this month
for (let i = start; i <= end; i++) {
arr.push({
date: parseTime(new Date(year, month, i)),
isCurMonth: true}}})return arr
}
export function parseTime(time, cFormat) {
if (arguments.length === 0| |! time) {return null
}
const format = cFormat || '{y}-{m}-{d}'
let date
if (typeof time === 'object') {
date = time
} else {
if ((typeof time === 'string')) {
if ((/ ^ [0-9] + $/.test(time))) {
// support "1548221490638"
time = parseInt(time)
} else {
// support safari
// https://stackoverflow.com/questions/4310953/invalid-date-in-safari
time = time.replace(new RegExp(/-/gm), '/')}}if ((typeof time === 'number') && (time.toString().length === 10)) {
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1.d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{([ymdhisa])+}/g.(result, key) = > {
const value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') { return ['day'.'一'.'二'.'三'.'four'.'five'.'六'][value] }
return value.toString().padStart(2.'0')})return time_str
}
Copy the code
2.2.3 Generate the most basic calendar
ps: notCurMonth
andcurrentDay
These two class names write their own, the example is not posted, otherwise it will all be the same color.
3. Switch the date and year
3.1 train of thought
Listen for changes in the drop-down box. The handleGetDays method is then called. Finally post the complete code
4. Select the date (click, swipe)
4.1 Click Select
4.4.1 thinking
td
Bind the click event, passing in three parameters:item
– > value,i
-> Row,j
– > in the column- through
res[i][j]
You can get one of the items that you’re currently clicking onisSelected
I’m going to invert the property, and I’m going to determine itsisSelected
Whether it istrue
If so, push the current item intoselectedDates
Array to perform the de-redo operation (Array.from(new Set(this.selectedDates))
). If not, find the index for that item, and clickselectedDates
Delete it from array (this.selectedDates.splice(this.selectedDates.indexOf(item.date), 1)
)- Post the complete code at the end
4.2 Slide Selection
2 ideas
- In slide mode, the first click is the start, the second click is the end, and the index of the position of the two clicks exists
moveIndex
Array, and the array length can only be2
, when the array length is2
, the array is emptied and the index is restored.Array in ascending order!! . It is convenient to intercept later- To establish a
canMove
State, after the first click, you can slide, after the second click, you can’t slide. The defaultcanMove
fortrue
whenmoveIndex.length === 1
.canMove
forfalse
And whenmoveIndex.length === 2
.canMove
fortrue
- Sliding with
mouseover
Listening, because the trigger frequency is low, is also incomingitem
.i
.j
Three parameters, and the index calculation method of the element over which the mouse hover isi * 7 + j
- The total array selected is
this.selectedDates = this.showDays.slice(this.moveIndex[0], this.moveIndex[1] + 1)
- The traversal loop adds one of these elements between the first click index and the second click index
isRangeSelected
State, used to add color. The index of the first click and the last click ismoveIndex[0]
和i * 7 + j
They can be styled separately to distinguish them.
5. The change of the beginning of the week
5.1 Switching the Chinese characters of the table header
Initially defined header is fixed: weekes: [‘ a ‘, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘, ‘].
When we get the start date of the week passed in, we can reorder the array using splice and unshift.
This.weeks. Unshift (… this.weeks.splice(this.startOfWeek – 1))
5.2 Switching table contents
5.2.1 ideas
- Defines an object that enumerates Roman and Chinese numerals for dates
const obj = {
1: '一'.2: '二'.3: '三'.4: 'four'.5: 'five'.6: '六'.0: 'day'
}
Copy the code
- Get Chinese from the beginning of the month (‘ one ‘,…)
- Then use
indexOf
Method get the beginning date of the month in Chineseweeks
The index in the array- Get the index out even if the previous month to display the number of days, passed
handleGetDay()
functions
The code is attached at the back
6. Call from parent component
6.1 the Attributes
parameter | instructions | type | An optional value | The default value |
---|---|---|---|---|
can-select | Whether to enable date selection | Boolean | true/false | false |
selectMode | Select mode: click/swipe | String | click/move | click |
start-of-week | Week beginning day | Number | [1, 7] | 1 |
width | The width of the entire calendar | String | – | 70% |
tbodyHeight | Height of date | String | – | 60px |
6.2 Events
The event name | instructions | parameter |
---|---|---|
dateSelected | Select date trigger when the user is enabled | selection |
7. Complete code
7.1 Calendar. Vue
<template>
<div class="calendar">
<div class="select">
<el-form inline>
<el-form-item>
<el-select v-model="curYear" placeholder="Please select">
<el-option v-for="item in yearOptions" :key="item.key" :value="item.value" :label="item.label" />
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="curMonth" placeholder="Please select">
<el-option v-for="item in monthOptions" :key="item.key" :value="item.value" :label="item.label" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuickChange('prev')">On January</el-button>
<el-button type="primary" @click="handleQuickChange('next')">Next month</el-button>
</el-form-item>
</el-form>
</div>
<table class="calendar-table" :style="{width}">
<thead>
<tr>
<th v-for="(item, i) in weeks" :key="i">{{ item }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(dates, i) in res" :key="i" :style="{height: tbodyHeight}">
<td
v-for="(item, index) in dates"
:key="index"
:class="{notCurMonth: ! item.isCurMonth, currentDay: item.date === curDate, selectDay: item.isSelected, rangeSelectd: item.isRangeSelected, weekend: item.isWeekend}"
@click="handleItemClick(item, i, index)"
@mouseover="handleItemMove(item, i, index)"
>
<! -- <span>{{ item.date.split('-').slice(1).join('-') }}</span> -->
<span>{{ item.date }}</span>
<slot :data="item" />
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import { getDaysInMonth, handleCrateDate, handleCreateDatePicker, parseTime } from '.. /utils/index'
export default {
components: {},props: {
'selectMode': {
type: String.default: 'click'
},
'startOfWeek': {
type: Number.default: 1
},
canSelect: {
type: Boolean.default: false
},
width: {
type: String.default: '70%'
},
tbodyHeight: {
type: String.default: '60px'}},data() {
return {
monthOptions: [].yearOptions: [].weeks: ['一'.'二'.'三'.'four'.'five'.'六'.'day'].curYear: new Date().getFullYear(), / / the current year
curMonth: new Date().getMonth(), / / the current month
days: 0.// Total number of days in the current month
curDate: parseTime(new Date().getTime()), // Current date In yyyY-MM-DD format, which is used to match the current date
prevDays: [].// The date not displayed in the previous month of the current month
rearDays: [].// Display date not next month of the current month
curDays: [].// Date of the current month
showDays: [].// A total of 42 dates are displayed
res: [].// A two-dimensional array
selectedDates: [].// The selected date
selectedMode: false.// True means click, false means swipe
moveIndex: [].// Two, the first is the start, the second is the end
canMove: false // Can trigger a slide when the moveIndex array has a value}},computed: {},watch: {
curMonth: {
handler(val) {
this.handleGetDays(this.curYear, val, this.startOfWeek)
}
},
curYear: {
handler(val) {
this.handleGetDays(val, this.curMonth, this.startOfWeek)
}
}
},
created() {
this.weeks.unshift(... this.weeks.splice(this.startOfWeek - 1)) // Render the header
this.handleGetDays(this.curYear, this.curMonth, this.startOfWeek) // Render table contents
this.selectedMode = this.selectMode === 'click' // Determine the selection mode
},
mounted() {
this.monthOptions = handleCreateDatePicker().months
this.yearOptions = handleCreateDatePicker().years
if (localStorage.selectedDates) this.selectedDates = JSON.parse(localStorage.selectedDates)
},
methods: {
handleGetDays(year, month, startOfWeek) {
this.showDays = []
this.days = getDaysInMonth(year, month)
let firstDayOfWeek = new Date(`${year}-${month + 1}- 01 `).getDay()
// Start date of processing week
const obj = {
1: '一'.2: '二'.3: '三'.4: 'four'.5: 'five'.6: '六'.0: 'day'
}
const firstDayInCN = obj[firstDayOfWeek]
const index = this.weeks.indexOf(firstDayInCN)
this.prevDays = handleCrateDate(year, month, 1, index + 1.'prev')
this.rearDays = handleCrateDate(year, month, 1.42 - this.days - (index), 'rear')
this.curDays = handleCrateDate(year, month, 1.this.days)
this.showDays.unshift(... this.prevDays)this.showDays.push(... this.curDays)this.showDays.push(... this.rearDays)this.res = this.handleFormatDates(this.showDays)
},
handleFormatDates(arr, size = 7) { // Pass in the original array of length 42, and finally convert it to a two-dimensional array
const arr2 = []
for (let i = 0; i < size; i++) {
const temp = arr.slice(i * size, i * size + size)
arr2.push(temp)
}
return arr2
},
handleItemClick(item, i, j) {
if (!this.canSelect) return
if (this.selectedMode) {
this.$nextTick(() = > {
this.res[i][j].isSelected = !this.res[i][j].isSelected
if (this.res[i][j].isSelected) {
this.selectedDates.push(this.res[i][j].date)
this.selectedDates = Array.from(new Set(this.selectedDates))
} else {
this.selectedDates.splice(this.selectedDates.indexOf(item.date), 1)}this.$emit('dateSelected'.this.selectedDates)
})
} else {
// In slide mode, the first click is the start and the second click is the end
const index = i * 7 + j
this.canMove = true
if (this.moveIndex.length === 1) {
this.canMove = false
}
if (this.moveIndex.length === 2) {
this.showDays.forEach(item= > {
item.isSelected = false
item.isRangeSelected = false
})
this.canMove = true
this.moveIndex.length = 0
}
this.moveIndex.push(index)
this.moveIndex.sort((a, b) = > a - b)
this.selectedDates = this.showDays.slice(this.moveIndex[0].this.moveIndex[1] + 1)
this.selectedDates.length ! = =0 && this.$emit('dateSelected'.this.selectedDates)
}
},
handleItemMove(data, i, j) {
if (this.canMove && !this.selectedMode) {
const index = i * 7 + j
this.showDays.forEach(item= > {
item.isSelected = false
item.isRangeSelected = false
})
// Make the first and last dates highlighted in blue
this.showDays[index].isSelected = true
this.showDays[this.moveIndex[0]].isSelected = true
// In different cases, if the index of the user's mouse cursor is smaller than the index of the start date, do if else
if (this.moveIndex[0] < index) {
for (let i = this.moveIndex[0] + 1; i < index; i++) {
this.showDays[i].isRangeSelected = true}}else {
for (let i = index + 1; i < this.moveIndex[0]; i++) {
this.showDays[i].isRangeSelected = true}}}},handleQuickChange(type) {
if (type === 'prev') {
this.curMonth--
console.log(this.curMonth)
if (this.curMonth === -1) {
this.curMonth = 11
this.curYear -= 1}}else if (type === 'next') {
this.curMonth++
if (this.curMonth === 12) {
this.curMonth = 0
this.curYear += 1
}
}
}
}
}
</script>
<style scoped lang="scss">
.calendar{
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.calendar-table{
table-layout: fixed;
border-collapse: collapse;
transition:.3s;
thead tr{
height: 50px;
}
tbody tr{&:first-child td{
border-top: 1px solid #08a8a0;
}
td{
cursor: pointer;
border-right: 1px solid #08a8a0;
border-bottom: 1px solid #08a8a0;
&:first-child{
border-left: 1px solid #08a8a0; }}}}.notCurMonth{
color: #C0C4CC;
}
.currentDay{
color: #fff;
background-color: #08a8a0;
}
.selectDay{
color: #fff;
background-color: #409EFF;
}
.rangeSelectd{
color: # 606266;
background-color: #dee2e9;
}
.weekend{
color: #F73131;
}
</style>
Copy the code
The index of 7.2 utils/js
/* eslint-disable camelcase */
/* eslint-disable no-unused-vars */
// Get the number of days in the month
export const getDaysInMonth = (year, month) = > {
const day = new Date(year, month + 1.0)
return day.getDate()
}
// Create date A date other than the current month is created in yyyY-MM-DD format
export const handleCrateDate = (year, month, start, end, type) = > {
const arr = []
if (type === 'prev') { / / in January
if (start === end) return []
const daysInLastMonth = getDaysInMonth(year, month - 1) // Get the number of days in the previous month
console.log('The current month is${month + 1}Month, last Month${month}The days of the month are${daysInLastMonth}Day `)
for (let i = daysInLastMonth - end + 2; i <= daysInLastMonth; i++) {
arr.push({
// date: `${month === 0 ? year - 1 : year}-${(month + 1) < 10 ? month === 0 ? 12 : `0${month}` : month}-${i < 10 ? `0${i}` : i}`,
date: parseTime(new Date(year, month - 1, i)),
isCurMonth: false.isSelected: false.isRangeSelected: false}}})else if (type === 'rear') { / / next January
for (let i = start; i <= end; i++) {
arr.push({
// date: `${month === 11 ? year + 1 : year}-${(month + 1) < 9 ? `0${month + 2}` : month + 2 <= 12 ? month + 2 : (month + 2) % 12 < 10 ? `0${(month + 2) % 12}` : (month + 2) % 12}-${i < 10 ? `0${i}` : i}`,
date: parseTime(new Date(year, month + 1, i)),
isCurMonth: false.isSelected: false.isRangeSelected: false}}})else { / / this month
for (let i = start; i <= end; i++) {
arr.push({
// date: `${year}-${(month + 1) < 10 ? `0${month + 1}` : month + 1}-${i < 10 ? `0${i}` : i}`,
date: parseTime(new Date(year, month, i)),
isCurMonth: true.isSelected: false.isRangeSelected: false}}})// console.log(arr)
return arr
}
export const handleCreateDatePicker = () = > {
const years = []
const months = []
for (let i = 1970; i <= 2099; i++) {
years.push({
label: `${i}Years `.value: i
})
}
for (let i = 0; i <= 11; i++) {
months.push({
label: `${i + 1}Month `.value: i
})
}
return {
years,
months
}
}
/**
* Parse the time to string
* @param {(Object|string|number)} time
* @param {string} cFormat
* @returns {string | null}* /
export function parseTime(time, cFormat) {
if (arguments.length === 0| |! time) {return null
}
const format = cFormat || '{y}-{m}-{d}'
let date
if (typeof time === 'object') {
date = time
} else {
if ((typeof time === 'string')) {
if ((/ ^ [0-9] + $/.test(time))) {
// support "1548221490638"
time = parseInt(time)
} else {
// support safari
// https://stackoverflow.com/questions/4310953/invalid-date-in-safari
time = time.replace(new RegExp(/-/gm), '/')}}if ((typeof time === 'number') && (time.toString().length === 10)) {
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1.d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{([ymdhisa])+}/g.(result, key) = > {
const value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') { return ['day'.'一'.'二'.'三'.'four'.'five'.'六'][value] }
return value.toString().padStart(2.'0')})return time_str
}
Copy the code