Vue 封装一个日历组件
先放上效果图,样式比较随意,大家看懂文章思路,可以自己去改。
这是用在公司内部sass系统里面的
这是element-ui
的组件
写日历,首先要发现他的数据排版规律,如同小学生找规律一样。
- 为了整体日历高度不随着月份的变化,高度发生变化,一般都是设计为 6 行 ,包含了上个月和下个月的天数(一般都是设置为灰色)
- 找到每个月的第一天是周几,如上图所示,
2020-11-01
是周日,getDay = 0
, 前面就会展示上个月一共7 - 0 = 7
的天数,尾部数据就是42 - 前面一共的天数
,一般你不需要可以算尾部天数的
自己封装的一些简易的时间函数,为了防止在主程序中写入过多代码
function getYearMonthDay (value) {
const date = new Date(value)
const year = date.getFullYear()
const month = date.getMonth()
const day = date.getDate()
return { year, month, day }
}
function getDate (year, month, day = 1) {
return new Date(year, month, day)
}
export default {
getYearMonthDay,
getDate
}
日历中展示的可见天数, 模板中只要for
循环遍历 visibleDays
就ok
了
computed: {
visibleDays() {
// 获取当月的第一天
const currentFirstDay = utils.getDate(this.timer.year, this.timer.month, 1)
// 获取是周几 就往前移动几天
let week = currentFirstDay.getDay()
// 日历当前月份的开始天数 如果是周日,就往前移动 7 天
week = week === 0 ? 7 : week
const startDay = currentFirstDay - week * 60 * 60 * 1000 * 24
// 循环42天 6 行 7 列
const arr = []
for (let i = 0; i < 42; i++) {
arr.push(new Date(startDay + i * 60 * 60 * 1000 * 24))
}
return arr
}
}
模板中怎么展示这visibleDays
数据,这也是一个找规律的问题
<div v-for="i in 6" :key="i">
<span
v-for="j in 7"
:key="j"
class="cell"
:class="[
{ notCurrentMonth: !isCurrentMonth(visibleDays[(i -1) * 7 + (j - 1)]) },
{ today: isToday(visibleDays[(i -1) * 7 + (j - 1)]) }
]"
@click="selectDay(visibleDays[(i -1) * 7 + (j - 1)])">
<span :class="{ selected: isSelect(visibleDays[(i -1) * 7 + (j - 1)]) }">{{ visibleDays[(i -1) * 7 + (j - 1)].getDate() }}</span>
</span>
</div>
完整组件代码
<template>
<div v-click-outsize>
<input :value="formDate" type="text" />
<div v-if="isVisible" class="panel">
<div class="panel-nav">
<span @click="pervMonth"> << </span>
<span>{{ timer.year }} 年 {{ timer.month + 1 }} 月 </span>
<span @click="nextMonth"> >> </span>
</div>
<div class="panel-content">
<p class="panel-week">
<span v-for="w in week" :key="'-' + w">{{ w }}</span>
</p>
<!-- 类似 9 * 9 乘法表 -->
<div v-for="i in 6" :key="i">
<span
v-for="j in 7"
:key="j"
class="cell"
:class="[
{ notCurrentMonth: !isCurrentMonth(visibleDays[(i -1) * 7 + (j - 1)]) },
{ today: isToday(visibleDays[(i -1) * 7 + (j - 1)]) }
]"
@click="selectDay(visibleDays[(i -1) * 7 + (j - 1)])">
<span :class="{ selected: isSelect(visibleDays[(i -1) * 7 + (j - 1)]) }">{{ visibleDays[(i -1) * 7 + (j - 1)].getDate() }}</span>
</span>
</div>
</div>
<div class="panel-footer"></div>
</div>
<div>努力学习,天天向上</div>
</div>
</template>
<script>
import utils from '../utils/index.js'
export default {
name: 'DatePicker',
directives: {
clickOutsize: {
bind(el, binding, vnode) {
const handler = (e) => {
if (el.contains(e.target)) {
if (!vnode.context.isVisible) {
vnode.context.focus()
}
} else {
if (vnode.context.isVisible) {
vnode.context.blur()
}
}
}
el.handler = handler
document.addEventListener('click', handler)
},
unbind(el) {
document.removeEventListener('ckick', el.handler)
}
}
},
props: {
value: {
type: [Date, String]
}
},
computed: {
formDate() {
const { year, month, day } = utils.getYearMonthDay(this.value)
return `${year}-${month + 1}-${day}`
},
visibleDays() {
// 获取当月的第一天
const currentFirstDay = utils.getDate(this.timer.year, this.timer.month, 1)
// 获取是周几 就往前移动几天
let week = currentFirstDay.getDay()
// 日历当前月份的开始天数 如果是周日,就往前移动 7 天
week = week === 0 ? 7 : week
const startDay = currentFirstDay - week * 60 * 60 * 1000 * 24
// 循环42天 6 行 7 列
const arr = []
for (let i = 0; i < 42; i++) {
arr.push(new Date(startDay + i * 60 * 60 * 1000 * 24))
}
return arr
}
},
data() {
return {
isVisible: false,
timer: {
year: '',
month: ''
},
week: '日一二三四五六'
}
},
mounted() {
const { year, month, day } = utils.getYearMonthDay(this.value)
this.timer.year = year
this.timer.month = month
},
methods: {
focus() {
this.isVisible = true
},
blur() {
this.isVisible = false
},
isCurrentMonth(date) {
const { year, month } = utils.getYearMonthDay(utils.getDate(this.timer.year, this.timer.month, 1))
const { year: y, month: m } = utils.getYearMonthDay(date)
return year === y && month === m
},
isToday(date) {
const { year, month, day } = utils.getYearMonthDay(new Date)
const { year: y, month: m, day: d } = utils.getYearMonthDay(date)
return year === y && month === m && day === d
},
selectDay(date) {
this.timer = utils.getYearMonthDay(date)
this.$emit('input', date)
this.blur()
},
isSelect(date) {
const { year, month, day } = utils.getYearMonthDay(this.value)
const { year: y, month: m, day: d } = utils.getYearMonthDay(date)
return year === y && month === m && day === d
},
pervMonth() {
const d = utils.getDate(this.timer.year, this.timer.month, 1)
d.setMonth(--this.timer.month)
this.timer = utils.getYearMonthDay(d)
},
nextMonth() {
const d = utils.getDate(this.timer.year, this.timer.month, 1)
d.setMonth(++this.timer.month)
this.timer = utils.getYearMonthDay(d)
}
}
}
</script>
<style lang="scss" scoped>
* {
padding: 0;
margin: 0;
}
.panel {
position: absolute;
background-color: #fff;
box-shadow: 0px 0px 10px 0px rgba(153, 153, 153, 0.5);
.panel-nav {
text-align: center;
span:first-child, span:last-child {
cursor: pointer;
}
}
.panel-content {
.panel-week {
span {
display: inline-block;
width: 32px;
height: 32px;
text-align: center;
line-height: 32px;
font-weight: bold;
font-size: 14px;
}
}
.cell {
display: inline-flex;
justify-content: center;
align-items: center;
width: 32px;
height: 32px;
text-align: center;
box-sizing: border-box;
font-weight: bold;
font-size: 14px;
box-sizing: border-box;
cursor: pointer;
span {
display: inline-block;
width: 20px;
height: 20px;
}
&:hover {
opacity: .3;
}
}
.notCurrentMonth {
color: gray;
}
.today {
color: #1890FF;
}
.selected {
background-color: #1890FF;
color: #fff;
border-radius: 50%;
}
}
}
</style>
贴一个GitHub地址:有需要的可以直接粘贴复制,MySimpleCalendar.vue
这个组件做成了响应式了。
github.com/HuDaDa0/cal…