image.png
最近工作中經(jīng)常用到日歷這個組件, 以前大多是用moment.js或者dayjs去做的, 其實(shí)使用原生js也并不復(fù)雜.
1.首先我們來認(rèn)識幾個這個組件中使用的new Date常用的方法
this.lastDay = new Date(y, m + 1, 0).getDate() // 獲取指定月的最后一天也即每月多少天
this.lastMonthDay = new Date(y, m, 0).getDate() // 獲取上個月多少天
this.firstDayisWhat = new Date(y, m, 1).getDay() // 第一天星期幾0-6(星期日到星期六)
this.lastDayisWhat = new Date(y, m + 1, 0).getDay() // 最后一天星期幾0-6(星期日到星期六)
date.getFullYear() // 指定日期的年
date.getMonth() // 指定日期的月
date.getDate() // 指定日期的天
new Date().getFullYear() + 1 // 指定年加1年
這幾個方法就可以寫出我們需要的組件. 其中需要注意的是 星期是 0 - 6 星期日到星期六, 月份是 0 - 11 一月到十二月.
2.需求整理
依照ElementUI的日歷, 我們需要可動態(tài)渲染的slot, 默認(rèn)的選中, 上月下月翻頁, 除此之外,我又加了動態(tài)的起始周
3.開始代碼
props: {
activeDay: {
type: Array,
default: () => []
}, // 首先我們接收activeDay 默認(rèn)選中的日期 后面我們將點(diǎn)擊事件暴露給父節(jié)點(diǎn), 在父節(jié)點(diǎn)中動態(tài)修改選中的日期
startingweek: {
type: [Number, String],
default: () => 0
}, // 起始周 默認(rèn)為 0 即周日開始 參數(shù) 0 - 6 , 星期日到星期六
filtWeekTab: {
type: Function
}, // 渲染week的方法, 這個方法暴露出三個參數(shù) 當(dāng)前的星期, 當(dāng)前年, 當(dāng)前月
activeCurrentDay: {
type: Boolean,
default: () => true
}, // 是否默認(rèn)選中當(dāng)前天
width: {
type: [Number, String],
default: () => 400
} // 寬度
},
核心方法,因?yàn)閯討B(tài)起始周的關(guān)系,所以前后填充的邏輯比較繞
(7 - this.startingweek + this.firstDayisWhat) % 7
(6 - this.lastDayisWhat + this.startingweek) % 7
能看懂這倆為什么這么寫,基本上算是無壓力寫這個組件了
此處拿到所有需要展示的日期
dateInit (year = new Date().getFullYear(), month = new Date().getMonth()) {
const y = year // 獲取傳入的年 默認(rèn) new Date().getFullYear()
const m = month // 獲取傳入的月 默認(rèn) new Date().getMonth()
this.panelYear = year // 全局展示的年
this.panelMonth = month // 全局展示的月
this.lastDay = new Date(y, m + 1, 0).getDate() // 每月最后一天也即每月多少天
this.lastMonthDay = new Date(y, m, 0).getDate() // 獲取上個月多少天
this.firstDayisWhat = new Date(y, m, 1).getDay() // 第一天星期幾0-6(星期日到星期六)
this.lastDayisWhat = new Date(y, m + 1, 0).getDay() // 最后一天星期幾0-6(星期日到星期六)
let beginTmp = [] // 初始化頭部填充 非當(dāng)前月的數(shù)據(jù)都是day開頭
// 頭部拿到 firstDayisWhat 當(dāng)月第一天是周幾 去循環(huán) 用lastMonthDay上個月的總天數(shù)
for (let i = 0; i < (7 - this.startingweek + this.firstDayisWhat) % 7; i++) {
beginTmp.push(`upday-${this.lastMonthDay - ((7 - this.startingweek + this.firstDayisWhat) % 7 - i - 1)}`)
}
// 初始化完整的月的每天格式為年月日
const lastFullTmp = []
// 拿到lastDay本月的天數(shù)循環(huán)拼接完整年月日 panelYear panelMonth i addPreZero方法補(bǔ)零
for (let i = 1; i <= this.lastDay; i++) {
lastFullTmp.push(`${this.panelYear}-${this.addPreZero(this.panelMonth + 1)}-${this.addPreZero(i)}`)
}
// 尾部填充
let lastinTmp = []
// 獲取最后一天是周幾
for (let i = 0; i < (6 - this.lastDayisWhat + this.startingweek) % 7; i++) {
lastinTmp.push(`downday-0${i + 1}`)
}
// 拼接數(shù)組拿到當(dāng)前月完整的數(shù)據(jù)
this.dayFullList = [...beginTmp, ...lastFullTmp, ...lastinTmp]
},
此處做選中, 如果activeCurrentDay為false則不選中當(dāng)前天, 很簡單的邏輯控制
// 設(shè)置選中的日期
getActiveDay (day) {
if (this.activeCurrentDay && this.formatDate().includes(day)) {
return true
}
return this.activeDay.includes(day)
},
此處對應(yīng)的 filtWeekTab以及起始周的控制,filtWeekTab接收一個方法, 暴露出三個參數(shù), 如下圖
Math.abs(value - 1 + this.startingweek) % 7 // 這個是關(guān)鍵
getStartingDay (value) {
let week
let key
key = Math.abs(value - 1 + this.startingweek) % 7
switch (key) {
case 0:
week = '日'
break;
case 1:
week = '一'
break;
case 2:
week = '二'
break;
case 3:
week = '三'
break;
case 4:
week = '四'
break;
case 5:
week = '五'
break;
case 6:
week = '六'
break;
}
if (typeof this.filtWeekTab === 'function') {
return this.filtWeekTab(week, this.panelYear, this.panelMonth)
}
return week
},
使用方法
filtWeekTab (value, year, month) {
console.log(value, year, month);
return value
},
image.png
最關(guān)鍵的翻頁邏輯及slot插槽處理 // 屬于內(nèi)置方法,
updateDays (year = this.panelYear, month = this.panelMonth) {
this.dateInit(year, month)
this.panelYear = year
this.panelMonth = month
}
<div class="date-tools" :style="`width: ${width}px`">
<div class="date-years">
<span>{{ panelYear }}年{{ panelMonth + 1 }}月</span>
<slot name="buttons"></slot>
</div>
<div class="date-weeks">
<span v-for="i in 7" :key="i">{{ getStartingDay(i) }}</span>
</div>
<div class="date-days">
<template v-for="(day, index) in dayFullList">
<div @click="getClickDay(day)" v-if="day.includes('day-')" :key="index" class="dayButton">
<slot :days="day">
<span class="othermonths">{{ filterDay(day) }}</span>
</slot>
</div>
<div @click="getClickDay(day)" v-else :key="index" :class="{ active: getActiveDay(day) }" class="dayButton">
<slot :days="day">
<span class="currentmonth">{{ filterDay(day) }}</span>
</slot>
</div>
</template>
</div>
</div>
這個組件總共設(shè)置了兩個插槽一個具名插槽buttons默認(rèn)為空, 因?yàn)橛X的翻頁按鈕每個人放的位置都不一樣,而且翻頁按鈕有些定制化的需求如翻到指定年月日選中日期等,所以通過具名插槽及updateDays方法可以動態(tài)設(shè)置翻頁.使用案列如下
<demo ref="demos" :activeDay="activeDay" :activeCurrentDay="true" :startingweek="0" :filtWeekTab="filtWeekTab" @getClickDay="getClickDay">
<template slot="buttons">
<div>
<el-button @click="down">上個月</el-button>
<el-button @click="up">下個月</el-button>
</div>
</template>
<template v-slot="{ days }">
<span v-if="days.includes('day-')" class="othermonths">{{ getDays(days) }}</span>
<span v-if="!days.includes('day-')" class="currentmonth">{{ getDays(days) }} {{ activeDay.includes(days) ? '??' : '' }}</span>
</template>
</demo>
up () {
let t = this.$refs.demos.getCurrentYearMonth()
let year = t[0]
let month
if (Number(t[1]) >= 11) {
month = 0
year = new Date(`${t[0]}-01-01`).getFullYear() + 1
} else {
month = Number(t[1]) + 1
}
this.$refs.demos.updateDays(year, month)
},
down () {
let t = this.$refs.demos.getCurrentYearMonth()
let year = t[0]
let month
if (Number(t[1]) <= 0) {
month = 11
year = new Date(`${t[0]}-01-01`).getFullYear() - 1
} else {
month = Number(t[1]) - 1
}
this.$refs.demos.updateDays(year, month)
}
說一下第二個匿名插槽, 匿名插槽暴露出來的參數(shù)是days及當(dāng)前月的日期 可以通過判斷條件渲染不同的樣式及頁面, 和element的日歷是一致的
最后補(bǔ)充上完整代碼和使用案列, 使用上有什么問題歡迎和我說
<template>
<div class="date-tools" :style="`width: ${width}px`">
<div class="date-years">
<span>{{ panelYear }}年{{ panelMonth + 1 }}月</span>
<slot name="buttons"></slot>
</div>
<div class="date-weeks">
<span v-for="i in 7" :key="i">{{ getStartingDay(i) }}</span>
</div>
<div class="date-days">
<template v-for="(day, index) in dayFullList">
<div @click="getClickDay(day)" v-if="day.includes('day-')" :key="index" class="dayButton">
<slot :days="day">
<span class="othermonths">{{ filterDay(day) }}</span>
</slot>
</div>
<div @click="getClickDay(day)" v-else :key="index" :class="{ active: getActiveDay(day) }" class="dayButton">
<slot :days="day">
<span class="currentmonth">{{ filterDay(day) }}</span>
</slot>
</div>
</template>
</div>
</div>
</template>
<script>
export default {
props: {
activeDay: {
type: Array,
default: () => []
},
startingweek: {
type: [Number, String],
default: () => 0
},
filtWeekTab: {
type: Function
},
activeCurrentDay: {
type: Boolean,
default: () => true
},
width: {
type: [Number, String],
default: () => 400
}
},
data () {
return {
dayFullList: [], // 所有的天數(shù)列表珍坊,前面空位補(bǔ)0
panelYear: '', // 全局展示的年
panelMonth: '', // 全局展示的月(從0開始)
lastDay: '', // 每月最后一天也即每月多少天
lastMonthDay: '', // 獲取上個月多少天
firstDayisWhat: '', // 第一天星期幾0-6(星期日到星期六)
lastDayisWhat: '' // 最后一天星期幾0-6(星期日到星期六)
}
},
methods: {
dateInit (year = new Date().getFullYear(), month = new Date().getMonth()) {
const y = year // 獲取傳入的年 默認(rèn) new Date().getFullYear()
const m = month // 獲取傳入的月 默認(rèn) new Date().getMonth()
this.panelYear = year // 全局展示的年
this.panelMonth = month // 全局展示的月
this.lastDay = new Date(y, m + 1, 0).getDate() // 每月最后一天也即每月多少天
this.lastMonthDay = new Date(y, m, 0).getDate() // 獲取上個月多少天
this.firstDayisWhat = new Date(y, m, 1).getDay() // 第一天星期幾0-6(星期日到星期六)
this.lastDayisWhat = new Date(y, m + 1, 0).getDay() // 最后一天星期幾0-6(星期日到星期六)
let beginTmp = [] // 初始化頭部填充 非當(dāng)前月的數(shù)據(jù)都是day開頭
// 頭部拿到 firstDayisWhat 當(dāng)月第一天是周幾 去循環(huán) 用lastMonthDay上個月的總天數(shù)
for (let i = 0; i < (7 - this.startingweek + this.firstDayisWhat) % 7; i++) {
beginTmp.push(`upday-${this.lastMonthDay - ((7 - this.startingweek + this.firstDayisWhat) % 7 - i - 1)}`)
}
// 初始化完整的月的每天格式為年月日
const lastFullTmp = []
// 拿到lastDay本月的天數(shù)循環(huán)拼接完整年月日 panelYear panelMonth i addPreZero方法補(bǔ)零
for (let i = 1; i <= this.lastDay; i++) {
lastFullTmp.push(`${this.panelYear}-${this.addPreZero(this.panelMonth + 1)}-${this.addPreZero(i)}`)
}
// 尾部填充
let lastinTmp = []
// 獲取最后一天是周幾
for (let i = 0; i < (6 - this.lastDayisWhat + this.startingweek) % 7; i++) {
lastinTmp.push(`downday-0${i + 1}`)
}
// 拼接數(shù)組拿到當(dāng)前月完整的數(shù)據(jù)
this.dayFullList = [...beginTmp, ...lastFullTmp, ...lastinTmp]
},
// 小于9的需要添加0前綴
addPreZero (num) {
return num > 9 ? num : '0' + num
},
// 設(shè)置選中的日期
getActiveDay (day) {
if (this.activeCurrentDay && this.formatDate().includes(day)) {
return true
}
return this.activeDay.includes(day)
},
getStartingDay (value) {
let week
let key
key = Math.abs(value - 1 + this.startingweek) % 7
switch (key) {
case 0:
week = '日'
break;
case 1:
week = '一'
break;
case 2:
week = '二'
break;
case 3:
week = '三'
break;
case 4:
week = '四'
break;
case 5:
week = '五'
break;
case 6:
week = '六'
break;
}
if (typeof this.filtWeekTab === 'function') {
return this.filtWeekTab(week, this.panelYear, this.panelMonth)
}
return week
},
filterDay (value) {
return parseInt(value.slice(-2))
},
getClickDay (day) {
this.$emit('getClickDay', day)
},
updateDays (year = this.panelYear, month = this.panelMonth) {
this.dateInit(year, month)
this.panelYear = year
this.panelMonth = month
},
getCurrentYearMonth () {
return [this.panelYear, this.panelMonth]
},
formatDate (date = new Date()) {
let y = date.getFullYear()
let m = date.getMonth() + 1
m = m < 10 ? '0' + m : m
let d = date.getDate()
d = d < 10 ? ('0' + d) : d
return y + '-' + m + '-' + d
}
},
mounted () {
this.dateInit()
}
}
</script>
<style lang="scss" scoped>
.date-tools {
min-width: 400px;
.date-years {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
font-size: 13px;
}
.date-weeks {
display: flex;
border-bottom: 1px solid #d7dce5;
padding-bottom: 10px;
span {
width: calc(100% / 7);
text-align: center;
color: #929aac;
font-size: 13px;
}
}
.date-days {
display: flex;
flex-wrap: wrap;
.dayButton {
width: calc(100% / 7);
min-height: 50px;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
&:hover {
background-color: #f2f8fe;
span:not(.othermonths) {
color: #1989fa;
}
}
span {
width: 20px;
height: 20px;
line-height: 20px;
border-radius: 50%;
font-size: 12px;
text-align: center;
cursor: pointer;
}
.othermonths {
color: #e3e3e3;
}
.currentmonth {
color: #000;
}
}
.active {
background-color: #f2f8fe;
cursor: pointer;
span.currentmonth {
color: #1989fa;
}
}
}
}
</style>
<template>
<div>
<demo ref="demos" :activeDay="activeDay" :activeCurrentDay="true" :startingweek="0" :filtWeekTab="filtWeekTab" @getClickDay="getClickDay">
<template slot="buttons">
<div>
<el-button @click="down">上個月</el-button>
<el-button @click="up">下個月</el-button>
</div>
</template>
<template v-slot="{ days }">
<span v-if="days.includes('day-')" class="othermonths">{{ getDays(days) }}</span>
<span v-if="!days.includes('day-')" class="currentmonth">{{ getDays(days) }} {{ activeDay.includes(days) ? '??' : '' }}</span>
</template>
</demo>
</div>
</template>
<script>
import demo from './demo'
export default {
components: {
demo
},
data () {
return {
activeDay: ['2020-11-20']
}
},
mounted () {
setTimeout(() => {
this.activeDay = ['2020-11-20', '2020-11-10']
}, 3000);
},
methods: {
filtWeekTab (value, year, month) {
console.log(value, year, month);
return value
},
getDays (days) {
// console.log(days);
return parseInt(days.slice(-2))
},
getClickDay (day) {
console.log(day)
},
up () {
let t = this.$refs.demos.getCurrentYearMonth()
let year = t[0]
let month
if (Number(t[1]) >= 11) {
month = 0
year = new Date(`${t[0]}-01-01`).getFullYear() + 1
} else {
month = Number(t[1]) + 1
}
this.$refs.demos.updateDays(year, month)
},
down () {
let t = this.$refs.demos.getCurrentYearMonth()
let year = t[0]
let month
if (Number(t[1]) <= 0) {
month = 11
year = new Date(`${t[0]}-01-01`).getFullYear() - 1
} else {
month = Number(t[1]) - 1
}
this.$refs.demos.updateDays(year, month)
}
},
}
</script>