前言
前面文章《如何将你封装的组件使用 npm 发布》,主要时讲如何封装组件,然后通过npm发布,今天就给大家造个轮子。另外一篇文章《小程序日历控件js日历数据组装》,是讲小程序日历控件的封装,但是代码都是用的vue写的,js代码可以复用,除了view部分,需要小小变动外,所以我们之间用来封装就好了。
Github地址:vue-c-calendar
快速应用
# 安装
npm install vue-c-calendar -S
引入模块
import cCalendar from 'vue-c-calendar'
Vue.use(cCalendar)
组件参数
labels: {// 头部显示的文字部分(出发/到达)
type: Array,
required: true
},
isSame: { // 是否可以选择相同日期(起始日期)
type: Boolean,
default: false
},
startDate: String, // 已选择的开始时间
endDate: String, // 已选择的结束时间
showAmount: { // 共显示月份 默认3个月
type: Number,
default: 3
},
disableBefore: { // 禁用什么日期之前的日期(默认今天)
type: String,
default: formatDate(TODAY)
},
disableAfter: String, // 禁用什么日期之后的日期
start: String, // 什么月份开始(可以为空)
sameEnable: { // 是否需要判断禁用日期(可以为true)
type: Boolean,
default: true
}
源码地址:vue-c-calendar
view
<template>
<div id="calerdar">
<div>
<div id="calendar-date" v-bind:class="{single: labels.length === 1}">
<div class="date fl">
<span class="year">{{startDateMomentYear}}</span>
<span class="month-date">{{startDateMomentMonth}}</span>
<span class="label">{{labels[0]}}</span>
</div>
<div class="date fr" v-if="endDateMoment">
<span class="year">{{endDateMomentYear}}</span>
<span class="month-date">{{endDateMomentMonth}}</span>
<span class="label">{{labels[1]}}</span>
</div>
<div class="date placeholder fr" v-else=""></div>
<div class="spliter"></div>
</div>
<ul id="week-label">
<li class="item" v-for="(week, windex) in weeks" :key="windex">{{week}}</li>
</ul>
</div>
<div>
<div id="month-list" v-bind:state="waiting ? 'waiting' : 'complete' " v-on:scroll="loadRepeat">
<section class="month-item" v-for="(month, mindex) in monthList" :key="mindex">
<p class="month-info">{{month.numStr}}月</p>
<ul class="month-main">
<li class="item null" v-for="(pmitem, pmindex) in month.prefix" :key="pmindex"></li>
<li class="item date" @click="checkDate(date)" v-for="(date, dindex) in month.dateList" :key="dindex" :class="{'disable': date.isDisable,'today': date.isToday,'start-date': date.isStartDate, 'end-date': date.isEndDate, 'progress': date.isProgress}">
<span class="num">{{date.dateData ? date.dateData.date : ''}}</span>
</li>
<li class="item null" v-for="(smitem, smindex) in month.surfix" :key="smindex"></li>
</ul>
</section>
</div>
</div>
</div>
</template>
js
<script>
import Vue from 'vue'
import moment from 'moment'
var TODAY = moment().startOf('date')
var SCROLL_LIMIT_PERCENT = 0.5
var MONTH_CH = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二']
var DateItem
var MonthItem
var makeDateList = function (data) {
var firstDateInMonth = data.curMonth.clone()
var lastDateInMonth = data.curMonth.clone().endOf('month')
var prefixAmount = firstDateInMonth.day()
var contentAmount = lastDateInMonth.date()
var surfixAmount = (6 - lastDateInMonth.day()) % 6
var result = []
var i, l
for (i = 0, l = prefixAmount + contentAmount + surfixAmount; i < l; i += 1) {
if (i < prefixAmount || i >= prefixAmount + contentAmount) {
result.push(new DateItem())
} else {
result.push(new DateItem(firstDateInMonth.clone(), data.disableBeforeMoment, data.disableAfterMoment))
firstDateInMonth.add(1, 'd')
}
}
return result
}
var formatDate = function (date, format) {
return moment(date).format(format || 'YYYY-MM-DD')
}
DateItem = function (date, disableBeforeMoment, disableAfterMoment) {
if (date !== undefined) {
this.dateData = {
year: date.year(),
month: date.month(),
date: date.date()
}
this.timeStamp = date.toDate().getTime()
this.isToday = date.isSame(TODAY)
this.isDisable = (disableBeforeMoment && date.isBefore(disableBeforeMoment)) || (disableAfterMoment && date.isAfter(disableAfterMoment))
} else {
this.isNull = true
}
this.isStartDate = false
this.isEndDate = false
this.isProgress = false
}
MonthItem = function (data) {
var targetMonth = data.curMonth.clone().startOf('month')
this.num = targetMonth.month()
this.numStr = MONTH_CH[this.num]
this.dateList = makeDateList(data)
}
export default {
name: "CCalendar",
props: {
labels: {
type: Array,
required: true
},
isSame: { // 是否能相同
type: Boolean,
default: false
},
startDate: String,
endDate: String,
showAmount: {
type: Number,
default: 3
},
disableBefore: {
type: String,
default: formatDate(TODAY)
},
disableAfter: String,
start: String,
sameEnable: {
type: Boolean,
default: true
}
},
onShow () {
this.animateFreeze = false
},
data () {
var startMonth = moment(this.start).startOf('month')
var singleMode = this.labels.length === 1
return {
weeks: ['日', '一', '二', '三', '四', '五', '六'],
disableBeforeMoment: moment(this.disableBefore),
disableAfterMoment: this.disableAfter && moment(this.disableAfter),
firstMonth: startMonth,
curMonth: startMonth.clone(),
curAmount: 0,
monthList: [],
startDateMoment: this.startDate && moment(this.startDate),
singleMode: singleMode,
endDateMoment: !singleMode && this.endDate && moment(this.endDate),
loadFreeze: false,
animateFreeze: false,
waiting: false
}
},
computed: {
startDateMomentYear () {
return this.startDateMoment.year()
},
startDateMomentMonth () {
return this.startDateMoment.format('MM-DD')
},
endDateMomentYear () {
return this.endDateMoment && this.endDateMoment.year()
},
endDateMomentMonth () {
return this.endDateMoment && this.endDateMoment.format('MM-DD')
}
},
watch: {
startDateMoment: 'refreshSelectRagne',
endDateMoment: 'refreshSelectRagne',
monthList: 'refreshSelectRagne'
},
methods: {
addMonth () {
var monthItem = new MonthItem({
curMonth: this.curMonth,
disableBeforeMoment: this.disableBeforeMoment,
disableAfterMoment: this.disableAfterMoment,
startDateMoment: this.startDateMoment,
endDateMoment: this.endDateMoment
})
this.monthList.push(monthItem)
this.curAmount += 1
this.curMonth.add(1, 'months')
},
loadRepeat () {
var self = this
if (!self.loadFreeze && self.showAmount > self.curAmount) {
self.loadFreeze = true
self.addMonth()
setTimeout(() => {
self.loadFreeze = false
self.loadRepeat()
}, 20)
}
},
refreshSelectRagne () {
var startTimeStamp = this.startDateMoment && this.startDateMoment.toDate().getTime()
var endTimeStamp = !this.singleMode && this.endDateMoment && this.endDateMoment.toDate().getTime()
var prefDate = {}
this.monthList.forEach((monthData) => {
monthData.dateList.forEach((dateData) => {
var dateTimeStamp = dateData.timeStamp
if (dateData.isNull) {
dateData.isStartDate = dateData.isEndDate = false
dateData.isProgress = (prefDate.isProgress || prefDate.isStartDate) && endTimeStamp
} else {
dateData.isStartDate = dateTimeStamp === startTimeStamp
dateData.isEndDate = dateTimeStamp === endTimeStamp
dateData.isProgress = dateTimeStamp > startTimeStamp && dateTimeStamp < endTimeStamp
}
prefDate = dateData
})
})
},
checkDate (date) {
if (date.isDisable || date.isNull || this.animateFreeze) {
return
}
if (this.startDateMoment && (this.singleMode || this.endDateMoment)) {
this.startDateMoment = this.endDateMoment = null
}
var dateData = date.dateData
var checkTargetMoment = moment([dateData.year, dateData.month, dateData.date])
if (!this.startDateMoment || checkTargetMoment[!this.sameEnable ? 'isSameOrBefore' : 'isBefore'](this.startDateMoment)) {
this.startDateMoment = checkTargetMoment
} else {
if (!this.isSame && '' + checkTargetMoment + '' === '' + this.startDateMoment + '') {
console.log('入离时间不能相同')
return
}
this.endDateMoment = checkTargetMoment
}
if (this.singleMode || this.endDateMoment) {
this.confirm()
this.waiting = false
} else {
this.waiting = true
}
},
confirm () {
var startDate = formatDate(this.startDateMoment)
var endDate = !this.singleMode ? formatDate(this.endDateMoment) : undefined
this.animateFreeze = true
this.$emit('complete', startDate, endDate)
}
},
mounted () {
Vue.nextTick(() => {
this.loadRepeat()
})
}
}
</script>
css(sass)
#calerdar{
background: #fff;
ul,li{
padding:0;
margin:0;
list-style:none
}
.layout-page-main{
border-top:px2rpx(1) solid #E2E2E2;
}
#calendar-date{
height: 4.2rem;
overflow: hidden;
.date{
padding: 0.31rem 0;
text-align: center;
width: 10rem;
.year{
display: block;
line-height: 1rem;
font-size: 1rem;
color: #9b9b9b;
}
.month-date{
display: block;
line-height: 1.5rem;
font-size: 1.5rem;
color: #3b4f62;
}
.label{
display: block;
line-height: 1rem;
font-size: 1rem;
color: #9b9b9b;
}
&.fl{
float: left;
}
&.fr{
float: right;
}
&.placeholder{
height: 100%;
position: relative;
&::before{
content: '';
display: block;
position: absolute;
left: 50%;
top: 50%;
height: 1px;
width: 0.9rem;
background-color: gray;
}
}
}
.spliter{
$block-height: 2rem;
height: $block-height;
margin: 0.7rem auto;
position: relative;
overflow: hidden;
&::before{
content: '';
position:absolute;
display: block;
height: $block-height;
width: 1px;
background-color: gray;
left: 45%;
transform: rotate(32deg);
}
}
}
#week-label{
$height: 2rem;
overflow: hidden;
border-bottom: 1px solid #dedede;
.item{
float: left;
width: (100%/7);
text-align: center;
$height: $height;
line-height: $height;
color: #b8b8b8;
font-size: 1.2rem;
&:first-child,
&:last-child{
color: #ff7362;
}
}
}
$item-width: (10rem / 7);
$num-width: 2rem;
$space-w: ($item-width - $num-width) * 0.5;
#month-list{
padding-top: 0.2rem;
height: 100%;
overflow: auto;
.month-item{
overflow: hidden;
padding-top: 0.35rem;
.month-info{
line-height: 0.86rem;
padding-left: 0.37rem;
color: #b7b7b7;
font-size: 1.5rem;
text-align: left;
}
.month-main{
overflow: hidden;
padding-top: 0.2rem;
.item{
float: left;
width: (100%/7);
color: #969696;
.num{
display:block;
position: relative;
// margin: auto;
height: $num-width;
line-height: $num-width;
text-align: center;
font-size: 1rem;
}
&.disable{
color: #e2e2e2;
}
&.today{
color: #0cc071;
}
&.start-date .num{
border-radius: .2rem 0 0 .2rem;
background-color: #0cc071;
color: #fff;
}
&.end-date .num{
border-radius: 0 .2rem .2rem 0;
background-color: #0cc071;
color: #fff;
}
&.start-date{
&.end-date .num{
border-radius: .2rem;
}
}
&.progress .num{
background-color: #0cc0717d;
color: #fff;
}
}
}
}
}
$space-w: 1rem;
&[calendar-type="multiple"]{
[state="complete"]{
.start-date .num{
margin-left: $space-w;
padding-right: $space-w;
border-top-left-radius: 99rem;
border-bottom-left-radius: 99rem;
}
.end-date .num{
margin-right: $space-w;
padding-left: $space-w;
border-top-right-radius: 99rem;
border-bottom-right-radius: 99rem;
}
}
[state="waiting"]{
.start-date .num{
margin: 0 $space-w;
border-radius: 100%;
}
}
}
&[calendar-type="single"]{
// set-header-height(1.3rem)
#week-label{
margin-top: 0.3rem;
}
.item.start-date .num{
margin: 0 $space-w;
padding: 0;
border-radius: 100%;
}
}
}