月日组件
文章版权声明: 基于 http://t.csdn.cn/LcgIT 文章进行修改
主要基于原文作者的插件基础上增加了可设置disable月份和日期;
第一步:安装moment 插件
npm install --save moment
第二步:复制month-day-picker.vue组件见下方代码
// month-day-picker.vue
<template>
<div>
<el-popover
placement="bottom"
width="280"
v-model="visible">
<div class="date-month-day">
// header 内主要是 左右箭头(上个月和下个月),以及展示当前月份
<div class="header">
// @click="+monthVal !== 1 ? dirClick('left') : ''" 是我设置了月份为1的时候不展示上个月箭头,此处根据各自需求修改,不需要隐藏 click事件把三目事件去掉即可,包括i标签内的v-show="+monthVal !== 1" 箭头改为一直显示;
<div class="left-arrow" @click="+monthVal !== 1 ? dirClick('left') : ''">
<i v-show="+monthVal !== 1" class="el-icon-arrow-left" />
</div>
<div v-text="getMonthFormat" @click="monthTile" style="cursor: pointer"></div>
// @click="+monthVal !== 12 ? dirClick('right') : ''" 是我设置了所选月份等于12时不展示下个月箭头,此处根据各自需求修改,不需要隐藏 click事件把三目事件去掉即可,包括i标签内的v-show="+monthVal !== 12" 箭头改为一直显示;
<div class="right-arrow" @click="+monthVal !== 12 ? dirClick('right') : ''">
<i v-show="+monthVal !== 12" class="el-icon-arrow-right" />
</div>
</div>
// 主要是月份列表
<div class="content" v-if="monthShow">
<div class="month" v-for="(item) in getMonths"
:class="{'disable': item.disabled}" :key="item.key"
@click="!item.disabled ? monthClick(item) : ''">
<span :class="activeMonth(item.key)">{{item.value}}</span></div>
</div>
// 主要是所选月份内日期列表
<div class="content" v-else>
<div class="day"
v-for="(item) in getDays" :key="item"
:class="{'disable': getMonths.length>0&&monthVal ? getMonths[monthVal-1].disabled : ''}"
@click="!getMonths[monthVal-1].disabled ? dayClick(item) : ''">
<span :class="activeDay(item)">{{item}}</span>
</div>
</div>
</div>
<el-input
slot="reference"
:placeholder="placeholder"
prefix-icon="el-icon-date"
:style="`cursor: pointer;width: ${width} `"
:clearable="true"
:readonly="true"
v-model="dateVal">
</el-input>
</el-popover>
</div>
</template>
<script>
import moment from 'moment'; // 导入日期插件
export default {
name: 'index',
props: {
// 默认值
dateDefault: {
type: String,
},
// 居中排列
placeholder: {
type: String,
default: '选择日期',
},
// 默认年份,闰年
year: {
type: String,
default: '2020',
},
disabledStart: {
type: String,
},
disabledEnd: {
type: String,
},
// 宽度
width: {
type: String,
default: '100%',
},
},
data() {
return {
visible: false,
monthShow: false,
monthFormat: {
1: '1月',
2: '2月',
3: '3月',
4: '4月',
5: '5月',
6: '6月',
7: '7月',
8: '8月',
9: '9月',
10: '10月',
11: '11月',
12: '12月',
},
dateVal: '', // 展示在输入框内的值M年D月
monthVal: '', //当前选中的月份
dayVal: '', // 当前选中的日期
};
},
computed: {
getMonthFormat() {
return this.monthVal ? this.monthFormat[Number(this.monthVal)] : '';
},
// 默认选中天
activeDay() {
return (item)=> {
return Number(this.dayVal) === item ? 'active' : '';
};
},
// 默认选中月
activeMonth() {
// eslint-disable-next-line func-names
return function (item) {
return this.monthVal === item ? 'active' : '';
};
},
// 获取当前月的天数
getDays() {
let days = 30;
const bigMonth = [1, 3, 5, 7, 8, 10, 12];
if (this.monthVal && bigMonth.includes(Number(this.monthVal))) {
days = 31;
} else if (this.monthVal && Number(this.monthVal) === 2) {
days = 28;
if (Number(this.year) % 4 === 0) {
days = 29;
}
}
return days;
},
// 获取月份
getMonths() {
const mon = [];
// 注意
Object.keys(this.monthFormat).forEach(m => {
let monthObj = {
key: `${m}`,
value: this.monthFormat[m],
disabled: false
}
// 1、开始和结束限制同时存在 2、仅开始限制; 3、仅结束限制 4、没有限制
if(!!this.disabledStart){
if(m < +this.disabledStart) monthObj.disabled = true
}
if(!!this.disabledEnd){
if(m > +this.disabledEnd) monthObj.disabled = true
}
mon.push(monthObj);
});
return mon;
},
},
watch: {
dateDefault: {
handler(newVal) {
if(!newVal){
return;
}
if (newVal) {
let defaultDate = ""
if(newVal.split("-").length>2){
defaultDate = `${this.year}-${newVal.substring(0,5)}`
}else{
defaultDate = `${this.year}-${this.dateDefault}`
}
this.dateVal = moment(defaultDate).format('M月D日');
this.monthVal = moment(defaultDate).format('M');
this.dayVal = moment(defaultDate).format('D');
}
},
immediate: true, // immediate选项可以开启首次赋值监听
},
visible: {
handler(newVal) {
if (newVal) {
if (this.dateDefault === this.dateVal) {
// 按照闰年来算,防止出现29号,算到1号
const defaultDate = `${this.year}-${this.dateDefault}`;
this.dateVal = moment(defaultDate).format('M月D日');
this.monthVal = moment(defaultDate).format('M');
this.dayVal = moment(defaultDate).format('D');
}
} else {
this.monthShow = false;
this.$emit('sureValid');
}
},
immediate: true, // immediate选项可以开启首次赋值监听
},
},
methods: {
dirClick(type) {
if (type === 'left') {
if (Number(this.monthVal) === 1) {
this.monthVal = '12';
} else {
this.monthVal = moment(this.monthVal).subtract(1, 'M').format('M');
}
}
if (type === 'right') {
if (Number(this.monthVal) === 12) {
this.monthVal = '1';
} else {
this.monthVal = moment(this.monthVal).add(1, 'M').format('M');
}
}
// 默认选中
let month = moment().format('M');
let day = moment().format('D');
if (this.dateDefault) {
month = moment(this.dateDefault).format('M');
day = moment(this.dateDefault).format('D');
}
if (month === this.monthVal) {
this.dayVal = Number(day);
} else {
this.dayVal = '';
}
},
monthTile() {
this.monthShow = true;
},
monthClick(month) {
this.monthVal = month.key;
this.dirClick();
this.monthShow = false;
},
dayClick(item) {
this.dayVal = item;
const day = `${this.dayVal}`;
const val = {
day,
month: this.monthVal,
date: `${this.monthVal < 10 ? '0' : ''}${this.monthVal}-${day < 10 ? '0' : ''}${day}`,//不足10补0
dateStr: `${this.monthVal}月${day}日`,
};
this.dateVal = val.dateStr;
this.$emit('update:date', val.date);
this.$emit('changeDay', val);
this.visible = false;
this.$emit('sureValid');
},
}
};
</script>
<style lang="less" scoped>
.date-month-day{
.header{
display: -webkit-flex; /* Safari */
display: flex;
text-align: center;
flex-direction: row;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #ebeef5;
.left-arrow,.right-arrow{
cursor: pointer;
width: 30px;
height: 36px;
line-height: 36px;
font-size: 14px;
color: #42424D;
z-index: 9;
background: #fff;
}
}
.content{
display: -webkit-flex; /* Safari */
display: flex;
text-align: center;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
margin-top: 10px;
.day{
width: calc(100% / 7);
height: 36px;
padding: 4px 0;
box-sizing: border-box;
text-align: center;
cursor: pointer;
position: relative;
span{
width: 24px;
height: 24px;
display: block;
margin: 0 auto;
line-height: 24px;
position: absolute;
left: 50%;
transform: translateX(-50%);
border-radius: 50%;
}
.active{
color: #fff;
background-color: #409eff;
}
}
.month{
width: calc(100% / 4);
height: 48px;
padding: 6px 0;
box-sizing: border-box;
cursor: pointer;
span{
width: 60px;
height: 36px;
display: block;
line-height: 36px;
color: #606266;
margin: 0 auto;
border-radius: 18px;
}
.active{
color: #fff;
background-color: #409eff;
}
}
.disable{
cursor: not-allowed;
span{
color: #C0C4CC;
background-color: #F5F7FA;
pointer-events: none;
cursor: not-allowed;
}
}
}
::v-deep{
.el-input{
width: 120px;
z-index: 9;
}
.el-input__inner{
cursor: pointer;
border: none;
text-align: center;
padding: 0px;
color: #42424D;
}
}
}
</style>
第三步: 使用月日组件
// 先引入上述月日组件
import DateMonthDay from "../components/month-day-picker" // 具体组件地址根据自己的路径修改
// 注册组件
components:{ DateMonthDay },
// template中组件
// 比如我写在了el-form组件内
<el-form-item prop="endDate">
<DateMonthDay
@sureValid="endSure" // 组件校验事件,因为我这边是一个开始日期和一个结束日期,需要校验结束日期需大于开始日期,不需要可删除此行
@changeDay="(day) => changeDay(day,'endDate')"
:year="form.year"
:disabledStart="enabledMonth"
:disabledEnd="currentMonth"
v-model="form.endDate"
:dateDefault="dateDefault">
</DateMonthDay>
</el-form-item>
// data中预制参数
data(){
return{
form: {
year: '',// 年份,根据年份才能判断2
startDate: "",
endDate: "",
},
dateDefault: "03-01",// 组件默认日期,此处填写1月1日,可根据情况自行修改,不跟form.endDate用同一个字段是因为form.endDate会随时变更
// disabledStartMonth代表这个月份之前的月份禁用,disabledEndMonth代表这个月份以后的月份禁用,禁用日期为空代表没有禁用日期,也可以只填其中一个
disabledStartMonth: "03",// 3月份之前的日期不可选
disabledEndMonth: "06", // 6月份之后的日期不可选 ,按照我这个默认设置也就是3月到6月才是可以选择的
}
}
// script中的事件
// (开始日期和结束日期日期校验) 结尾返回true和false便于在提示后用户仍然不按正确提示修改日期,在保存提交表单时可以再次执行此事件。
endSure() {
if (new Date(this.form.startDate) > new Date(this.form.endDate)) {
this.$message({
type: 'warning',
message: '结束时间必须在开始时间之后!',
showClose: true,
})
return false;
}
return true
},
// 切换 月日
changeDay(day,type){
let form = this.form;
if(type === "startDate"){
this.form.startDate = form.year+'-'+day.date
}else{
this.form.endDate = form.year+'-'+day.date
}
},