需求:
- 左边日历,选择日历中的日期,右边把选择的日期显示到表格中去
- 每个日期显示一行数据,左侧日期点击选中再次点击取消,取消右边表格数据也跟着取消
- 日历中的日期可以进行多个选则,这里不进行范围选则而是进行跨天和跨月进行选择
分析:
- 日历好像element-ui库中有el-calendar,赶紧看一下有没有这样的类似的事件,看了文档发现,只有单点选择,且选中后不能取消
- 想了想看看有没有类似的库,发现有,但是项目中已经够大了再加一些第三方库不太好,于是考虑自己封装一个日历组件,可单选取消,间隔多选,范围选择,这个通过配置就可以,方便以后使用
效果如下
1、范围选择
2、单点选择
3、插槽添加选择月份
4、禁止选择指定日期以前的时间 :restrictDate="{1:['2022-02-11']}"
5、禁止选择以后的日期 :restrictDate="{2:['2022-02-11']}"
6、 禁止选择某个范围的日期 :restrictDate="{3:['2022-02-11','2022-03-11']}"
7、给日期加上标记点 pointMap: {[new Date('20220-09-01').getTime()]: true},
由于时间充足,就把该组件打包发布到了npm上了,使用方式如下:
- npm i wqcalendar -S 包使用教程点击这里
- 源码已发布GitHub
html代码
<template>
<div class="w-calender-container">
<div class="w-top">
<div class="w-chooseMonth">
<slot name="left"></slot>
</div>
<div class="w-top-date">
{{ year }}年{{ `${month > 9 ? month : '0' + month}` }}月
</div>
<div class="w-btn-wrap">
<span @click="handleShowLastMonth">上一月</span>
<span @click="handleShowToday">今天</span>
<span @click="handleShowNextMonth">下一月</span>
<span v-if="isShowClear" @click="handleClear">清除</span>
</div>
</div>
<div class="w-date_wrap">
<ul class="w-week">
<li>日</li>
<li>一</li>
<li>二</li>
<li>三</li>
<li>四</li>
<li>五</li>
<li>六</li>
</ul>
<ul class="w-day">
<li v-for="(item,index) in days"
:class="{'c-isCurMonth':item.isNextMonth||item.isLastMonth,'c-isCurToday':item.isCurToday,'c-isActive':item.isActive,'c-is-previous-date':item.isPreviousDate}"
:key="index"
@click="handleChooseDay(item)">
<span>
{{ item.day }}
</span>
<span v-if="item.isShowPoint" class="w-point"></span>
</li>
</ul>
</div>
<div class="date-tip">
<div class="tip_row">
<span class="square"></span>
<span class="title">可选</span>
</div>
<div class="tip_row">
<span class="square chosen"></span>
<span class="title">已选中</span>
</div>
</div>
</div>
</template>
js代码
工具类点击这个 import MyTools from"../utils/MyTools";
<script>
import {getCurDay} from "../utils/MyTools";
export default {
name: 'calender',
props: {
//是否是多选
isMultipleChoice: {
type: Boolean,
default: true,
},
//是否显示清除按钮
isShowClear: {
type: Boolean,
default: true
},
//选择的月份
Month: {
type: String,
},
//需要加圆点的数据
//数据格式为map { new Date(2022-02-11).getTime():true}: 用年月日的时间戳为key
pointMap:{
type:Object,
default:()=>{
return {}
}
},
//是否禁止选择以前的日期 默认为空表示没有限制 注:如果只有数字则 默认时间是当天时间
//{1:['2022-02-11']}表示2022-02-11以前的日期不能选择;1:表示小于该时间的禁止选择,必填
//{2:['2022-02-11']}表示2022-02-11以后的日期不能选择;2:表示大于该时间的禁止选择,必填
//{3:['2022-02-11','2022-03-11']}表示2022-02-11到2022-03-11之间的日期可以选择;3:表示两个时间内的时间可以选择,必填
//{4:['2022-02-11','2022-03-11']}表示2022-02-11到2022-03-11之外的日期可以选择;4:表示两个时间之外的可以选择,必填
restrictDate: {
type: Object,
default: () => {
return null
}
}
},
watch: {
Month: {
handler(val) {
if (val) {
const date = val.split('-').map(Number);
this.year = date[0];
this.month = date[1]
this.days = [];
//选择月份后重新跟更新时间
this.dealDate();
}
},
immediate: true
},
pointMap: {
handler(val) {
if (val) {
this.dealDate();
}
},
immediate: true
}
},
data() {
return {
year: '',//年
month: '',//月
days: [],//日期
//是否已经选择了开始时间
isChooseOne: false,
endTime: null,
startTime: null,
monthValue: '',//选择的月份
chooseDateList: {},//如果是单选 则把选择的保存
}
},
methods: {
//得到当前年这个月分有多少天
getDays(Y, M) {
return new Date(Y, M, 0).getDate();
},
//得到当前年,这个月的一号是周几
getWeek(Y, M) {
let now = new Date()
now.setFullYear(this.year)
now.setMonth(this.month - 1)
now.setDate(1);
return now.getDay();
},
/**
* 获取本月日期
*/
pushDays() {
//将这个月多少天加入数组days
const m = `${this.month > 9 ? this.month : '0' + this.month}`;
for (let i = 1; i <= this.getDays(this.year, this.month); i++) {
const d = `${i > 9 ? i : '0' + i}`,
date = `${this.year}-${m}-${d}`;
this.days.push({
day: d,
isActive: false,
month: m,
year: `${this.year}`,
date,
timestamp: new Date(date).getTime(),//转换时间戳
})
}
//获取上个月的日期
this.getLastMonthDays()
//获取下个月的日期
this.getNextMonthDays()
},
/**
* 获取下个月的日期
*/
getNextMonthDays() {
const m = this.month < 12 ? this.month + 1 : 1,
y = this.month < 12 ? this.year : this.year + 1,
len = 42 - this.getDays(this.year, this.month) - this.getWeek(this.year, this.month),
_m = `${m > 9 ? m : '0' + m}`;
//将下个月要显示的天数加入days
for (let i = 1; i <= len; i++) {
const _d = `${i > 9 ? i : '0' + i}`,
date = `${y}-${_m}-${_d}`
this.days.push({
day: _d,
month: _m,
year: `${y}`,
isActive: false,
isNextMonth: true,
date,
timestamp: new Date(date).getTime()
})
}
},
/**
* 获取上个月的日期
*/
getLastMonthDays() {
const m = this.month > 1 ? this.month - 1 : this.year > 1970 ? 12 : 1,
y = this.month > 1 ? this.year : this.year > 1970 ? this.year - 1 : 1970,
len = this.getWeek(this.year, this.month),
lastMonthDays = this.getDays(this.year, this.month - 1)
//将上个月要显示的天数加入days
for (let i = 0; i < len; i++) {
const _m = `${m > 9 ? m : '0' + m}`,
date = `${y}-${_m}-${lastMonthDays - i}`
this.days.unshift({
day: `${lastMonthDays - i}`,
month: _m,
year: `${y}`,
isActive: false,
isLastMonth: true,
date,
timestamp: new Date(date).getTime(),
})
}
},
/**
* 上个月
*/
handleShowLastMonth() {
if (this.month > 1) {
this.month = this.month - 1;
} else if (this.year > 1970) {
this.month = 12;
this.year = this.year - 1;
}
this.dealDate();
},
/**
* 下个月
*/
handleShowNextMonth() {
this.days = [];
if (this.month < 12) {
this.month = this.month + 1;
} else {
this.month = this.month = 1;
this.year = this.year + 1;
}
this.dealDate();
},
/**
* 当天
*/
handleShowToday() {
let now = new Date();
this.year = now.getFullYear();
this.month = now.getMonth() + 1;
this.dealDate()
},
/**
* 处理时间
*/
dealDate() {
this.days = [];
const curDate = getCurDay()
this.pushDays();
// 判断 是否需要禁止选择某些时间段的时间
if (this.restrictDate) {
const keys = Object.keys(this.restrictDate);
let day, timestamp;
switch (keys[0]) {
case '1':
day = this.restrictDate[keys[0]] && this.restrictDate[keys[0]] || curDate;
timestamp = new Date(day).getTime();//转换时间戳
this.days.forEach(item => {
item.isCurToday = item.date === curDate
item.isPreviousDate = item.timestamp < timestamp
})
break;
case '2':
day = this.restrictDate[keys[0]] && this.restrictDate[keys[0]] || curDate;
timestamp = new Date(day).getTime();//转换时间戳
this.days.forEach(item => {
item.isCurToday = item.date === curDate
item.isPreviousDate = item.timestamp > timestamp
})
break;
case '3':
const s_d = this.restrictDate[keys[0]] && this.restrictDate[keys[0]][1] || curDate
const e_d = this.restrictDate[keys[0]] && this.restrictDate[keys[0]][2] || curDate
let s = new Date(s_d).getTime(),
e = new Date(e_d).getTime();
if (s > e) {
[s, e] = [e, s]
}
this.days.forEach(item => {
item.isCurToday = item.date === curDate
item.isPreviousDate = item.timestamp > s && item.timestamp < e
})
break
case '4':
const st_d = this.restrictDate[keys[0]] && this.restrictDate[keys[0]][1] || curDate
const en_d = this.restrictDate[keys[0]] && this.restrictDate[keys[0]][2] || curDate
let st = new Date(st_d).getTime(),
en = new Date(en_d).getTime();
if (st > en) {
[st, en] = [en, st]
}
this.days.forEach(item => {
item.isCurToday = item.date === curDate
item.isPreviousDate = item.timestamp < st || item.timestamp > en
})
break;
default:
this.days.forEach(item => {
item.isCurToday = item.date === curDate
})
}
} else {
this.days.forEach(item => {
item.isCurToday = item.date === curDate
})
}
this.getActiveDay()
},
/**
* 清空选择
*/
handleClear() {
this.isChooseOne = false;
this.startTime = null;
this.endTime = null;
this.chooseDateList = {};
this.days.forEach(item => {
item.isActive = false
})
},
/**
* 选择时间
* @param time
*/
handleChooseDay(time = {}) {
// 判断 是否是禁止选择的日期 是否是
if (this.restrictDate && time.isPreviousDate) {
return
}
//是否是多选
if (this.isMultipleChoice) {
this.chooseDateList = {}
//选择开始时间-结束时间
if (this.isChooseOne) {
this.endTime = time;
const {timestamp} = this.startTime || {};
//如果 选择的开始日期大于结束日期 则调换开始日期与结束日期
if (timestamp > time.timestamp) {
[this.startTime, this.endTime] = [this.endTime, this.startTime]
}
this.getActiveDay();
this.isChooseOne = false
this.$emit('chooseDays', this.chooseDateList)
} else {
this.isChooseOne = true
this.startTime = time;
//给选择的时间范围选中
this.days.forEach(item => {
item.isActive = item.timestamp === this.startTime.timestamp;
})
}
} else {
this.days.forEach(item => {
if (item.timestamp === time.timestamp) {
item.isActive = !time.isActive;
if (time.isActive) {
const {date, day, month, timestamp, year} = item
this.chooseDateList[time.timestamp] = {
date,
day,
month,
timestamp,
year
}
} else {
delete this.chooseDateList[time.timestamp]
}
}
})
this.$emit('chooseDays', this.chooseDateList)
}
},
/**
* 给选择的日期范围加上选中状态
*/
getActiveDay() {
if (this.isMultipleChoice) {
if (!this.startTime || !this.endTime) {
return
}
//给选择的时间范围选中
const {timestamp} = this.startTime || {};
const {timestamp: endTimestamp} = this.endTime || {};
this.days.forEach(item => {
item.isActive = item.timestamp >= timestamp && item.timestamp <= endTimestamp && !item.isPreviousDate;
//是否显示点
item.isShowPoint=this.pointMap[item.timestamp]
if (item.timestamp >= timestamp && item.timestamp <= endTimestamp && !item.isPreviousDate) {
const {date, day, month, timestamp, year} = item
this.chooseDateList[item.timestamp] = {
date,
day,
month,
timestamp,
year
}
} else {
delete this.chooseDateList[item.timestamp];
}
})
} else {
this.days.forEach(item => {
//是否显示点
item.isShowPoint=this.pointMap[item.timestamp]
//已选择的数据加上状态
item.isActive=!!this.chooseDateList[item.timestamp]
})
}
}
},
mounted() {
this.handleShowToday()
}
}
</script>
css 代码
<style>
ul {
list-style: none;
}
.w-calender-container {
width: 100%;
min-width: 400px;
border: 1px solid #ddd;
padding: 20px;
box-sizing: border-box;
}
.w-top {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 1% 0;
border-bottom: 1px solid #ddd;
box-sizing: border-box;
}
.w-top .w-top-date {
white-space: nowrap;
}
.w-top .w-btn-wrap {
min-width: 200px;
display: flex;
justify-content: space-around;
color: #409EFF;
}
.w-btn-wrap span {
flex: 1;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
font-size: 16px;
}
.w-btn-wrap span:last-child {
color: #FF6200;
}
.w-date_wrap {
width: 100%;
height: auto;
}
.w-date_wrap .w-week {
width: 100%;
display: flex;
flex-direction: row;
padding: 10px 0 10px 40px;
font-size: 16px;
box-sizing: border-box;
}
.w-date_wrap .w-week li {
width: 14.28%;
}
.w-date_wrap .w-day {
width: 100%;
display: flex;
flex-direction: row;
padding: 0 20px;
font-size: 16px;
flex-wrap: wrap;
box-sizing: border-box;
}
.w-day li {
position: relative;
cursor: pointer;
width: 14.28%;
padding: 4%;
border: 1px solid #ddd;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
box-sizing: border-box;
}
.w-day li .w-point {
position: absolute;
bottom: 26%;
background: #ff0000;
display: flex;
width: 4px;
height: 4px;
border-radius: 50%;
}
.w-day li:nth-child(n+8) {
border-top: none;
}
.w-day li:nth-child(n+1) {
border-right: none;
}
.w-day li:nth-child(7n) {
border-right: 1px solid #ddd
}
.date-tip {
height: 40px;
width: 100%;
display: flex;
}
.tip_row {
width: 100px;
height: 100%;
display: flex;
align-items: center;
}
.tip_row:first-child {
padding-left: 20px;
box-sizing: border-box;
}
.tip_row .square {
width: 20px;
height: 20px;
display: flex;
background: #fff;
border: 1px solid #c0c4cc;
}
.tip_row .chosen {
background: #409EFF;
border: 0;
}
.tip_row .title {
display: flex;
padding-left: 6px;
box-sizing: border-box;
white-space: nowrap;
}
.c-isCurMonth {
background: #fff;
color: #c0c4cc;
}
.c-isCurToday {
background: #fff;
color: #409EFF;
}
.c-isActive {
background: #409EFF;
color: #f2f8fe;
}
.chooseMonth {
width: 120px;
}
.isCurYearDay {
cursor: not-allowed !important;
opacity: 0.6;
}
.c-isCurMonth.c-isActive {
background: rgba(64, 158, 255, 0.56);
}
.c-is-previous-date {
background: rgba(192, 196, 204, 0.2);
color: #c0c4cc;
cursor: not-allowed !important;
}
</style>