微信小程序日历组件开发案例

今天我们一起写一个微信小程序日历组件

微信小程序日历组件
https://github.com/749264345/wx-calendar

好,我们先看一下要实现的模样,如下图


52魔都

52魔都

由以上截图我们可以看到

1.日历可以通过按钮【切换展示效果】改变日历的呈现效果,上图是平铺模式,下图是收起滚动模式。
2.通过点击具体的日期可以在页面上显示当前选中的具体日期。
3.点击【今天】快速回到当日视图。
4.点击【◀】和【▶】切换月份。
上面的四点也是基本的交互需求,我们马上开始。
首先,我们先结构后样式,做出最基本的界面结构
这边我们把整体结构分成上中下,操作显示区,星期显示区,日期显示区。

<view class='calendar'>
    <!--显示当前年月日-->
    <view class='calendar-title'>
        <view class='item ctrl' bindtap='lastMonth'>{{lastMonth}}</view>
        <view class='item title'>{{title}}</view>
        <view class='item ctrl' bindtap='nextMonth'>{{nextMonth}}</view>
        <view class='item ctrl today' bindtap='today'>今天</view>
    </view>

    <!--星期-->
    <view class='calendar-week'>
        <view class='item'>{{item}}</view>
    </view>

    <!--日期-->
    <view class='calendar-container'>
        <!--上个月占位格子-->
        <view class='grid gray'>{{item}}</view>

        <!--当月格子-->
        <view class='grid'>
            <view class="wrap">{{item.date}}</view>
        </view>

        <!--下个月占位格子-->
        <view class='grid gray'>{{item}}</view>
    </view>
</view>

这是我们基本的日历结构,机智的小伙伴已经从布局中知道我们实现的大致逻辑了,是的,我们先获取当月有多少天,上月和下月有多少天,这样我们的日历就出来了。好,慢慢来,下面我们详细说,我们先写上基本的样式。

.calendar {
    width: 100%;
    text-align: center;
    font-size: 30rpx;
    box-sizing: border-box;
}

/* 标题 */
.calendar-title {
    line-height: 70rpx;
    font-size: 30rpx;
    text-align: left;
    padding: 0 20rpx;
    box-sizing: border-box;
}

.calendar-title .ctrl {
    display: inline-block;
    padding: 0 20rpx;
    background: #f5f5f5;
    border-radius: 10rpx;
}

.calendar-title .item {
    display: inline-block;
    vertical-align: middle;
    line-height: 50rpx;
}

.calendar-title .title {
    min-width: 300rpx;
    text-align: center;
}

.calendar-title .today {
    float: right;
    margin-top: 10rpx;
}

/* 星期 */
.calendar-week {
    display: flex;
    text-align: center;
    padding: 20rpx 10rpx;
    box-sizing: border-box;
    border-top: 1rpx solid #e0e0e0;
    border-bottom: 1rpx solid #e0e0e0;
    background: #f5f5f5;
}

.calendar-week .item {
    flex: 1;
}

/* 日期 */
.calendar-container {
    display: flex;
    flex-wrap: wrap;
    padding: 20rpx 10rpx;
    box-sizing: border-box;
}

.calendar-container .grid {
    display: inline-block;
    width: 14.28571428571429%;
    line-height: 70rpx;
    position: relative;
    z-index: 1;
}

.calendar-container .grid.gray {
    color: #ccc;
}

.calendar-container .grid .wrap.select {
    background: rgb(49, 120, 228);
    border-radius: 10rpx;
    color: #fff;
    width: 80%;
    margin: 0 auto;
}

以上我们基本试下了日历的界面,下面我们来实现星期和日期的展示。
好,我们先显示星期,我们先在组件中定义一个数组,用来遍历显示星期的标题;

Component({
    properties: {
        //星期数组
        weekText: {
            type: Array,
            value: ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
        }
    },
    ...
})

我们将星期的标题定义为可配置的模式,默认显示如上的文字,之后我们可以在组件外自定义,个性化显示。于是我们调整下wxml的代码。

    <!--遍历星期-->
    <view class='calendar-week'>
        <view wx:for='{{weekText}}' class='item' wx:key='{{item}}'>{{item}}</view>
    </view>

这样我们就能看到我们想要的效果。


52魔都

下面我们开始日期的显示,我们先获取当月有几天,这里的核心代码是

new Date(year, month, date).getDate();

http://www.w3school.com.cn

由此我们做如下尝试


52魔都

我们如期获得了返回值,而当我们传入日期为0时返回了31为当月的全部天数。

由于JavaScript中day的范围为1~31中的值,所以当设为0时,会向前 一天,也即表示上个月的最后一天,通过这种方式可以得到每个月份的天数。

知道了获取当月天数的原理,我们还需要知道当月1号是星期几。
我们使用如下的方法:

new Date(Date.UTC(year, month-1, date)).getDay();

http://www.w3school.com.cn

我们同样在控制台做出调试;


52魔都

需要注意的是,上面的month是实际的月份,而下面这个方法需要在实际的月份上减去1。
于是我们获取0-6之间的值,分别对应周日~周六。
值为6是周六,值为0是周日。
由于日历的第一天是周日,周日对应的是0,于是传入每月1日,返回值为多少,就是星期几,也就说明当月1日前面空几格。
知道了当月就几天,当月前面有几天,我们做一下算法就可以得出,当月后面有几天,于是我们建立如下函数:

    // 组件的初始数据
    data: {
        //当月格子
        thisMonthDays: [],
        //上月格子
        empytGridsBefore: [],
        //下月格子
        empytGridsAfter: [],
},

methods: {
        //获取当月天数
        getThisMonthDays: function (year, month) {
            return new Date(year, month, 0).getDate();
        },
        // 绘制当月天数占的格子
        createDays: function (year, month) {
            let thisMonthDays = [],
                days = this.getThisMonthDays(year, month);
            for (let i = 1; i <= days; i++) {
                thisMonthDays.push({
                    date: i,
                    dateFormat: this.zero(i),
                    monthFormat: this.zero(month),
                    week: this.data.weekText[new Date(Date.UTC(year, month - 1, i)).getDay()]
                });
            }
            this.setData({
                thisMonthDays
            })
        },
        //获取当月空出的天数
        createEmptyGrids: function (year, month) {
            let week = new Date(Date.UTC(year, month - 1, 1)).getDay(),
                empytGridsBefore = [],
                empytGridsAfter = [],
                emptyDays = (week == 0 ? 7 : week);
            //当月天数
            var thisMonthDays = this.getThisMonthDays(year, month);
            //上月天数
            var preMonthDays = month - 1 < 0 
                ? this.getThisMonthDays(year - 1, 12) 
                : this.getThisMonthDays(year, month - 1);

            //空出日期
            for (let i = 1; i <= emptyDays; i++) {
                empytGridsBefore.push(preMonthDays - (emptyDays - i));
            }

            var after = (42 - thisMonthDays - emptyDays) - 7 >= 0 
                        ? (42 - thisMonthDays - emptyDays) - 7 
                        : (42 - thisMonthDays - emptyDays);
            for (let i = 1; i <= after; i++) {
                empytGridsAfter.push(i);
            }
            this.setData({
                empytGridsAfter,
                empytGridsBefore
            })
        },

        //补全0
        zero: function (i) {
            return i >= 10 ? i : '0' + i;
        },
}

我们同样修改下wxml代码,同时我们为上月,下月,今天,三个按钮添加相关事件监听。

    <!--显示当前年月日-->
    <view class='calendar-title'>
        <view class='item ctrl' bindtap='lastMonth'>{{lastMonth}}</view>
        <view class='item title'>{{title}}</view>
        <view class='item ctrl' bindtap='nextMonth'>{{nextMonth}}</view>
        <view class='item ctrl today' bindtap='today'>今天</view>
    </view>
<!--上个月占位格子-->
<view class='grid gray' wx:for='{{empytGridsBefore}}' wx:key='{{item}}'>{{item}}</view>

<!--当月格子-->
<view class='grid' wx:for='{{thisMonthDays}}' wx:key='{{indx}}'>
   <view class='self' wx:if="{{ format === year+'-'+item.monthFormat+'-'+item.dateFormat }}"></view>
   <view class="wrap {{ select === year+'-'+item.monthFormat+'-'+item.dateFormat ? 'select' :''}}" bindtap='select' data-date='{{item.date}}'>{{item.date}}</view>
</view>

<!--下个月占位格子-->
<view class='grid gray' wx:for='{{empytGridsAfter}}' wx:key='{{item}}'>{{item}}</view>

相关的事件监听:

//默认选中当天 并初始化组件
today: function () {
    let DATE = this.data.defaultValue ? new Date(this.data.defaultValue) : new Date(),
        year = DATE.getFullYear(),
        month = DATE.getMonth() + 1,
        date = DATE.getDate(),
        select = year + '-' + this.zero(month) + '-' + this.zero(date);

    this.setData({
        format: select,
        select: select,
        year: year,
        month: month,
        date: date,
        YEAR: year,
        MONTH: month,
        DATE: date,
    })

    //初始化日历组件UI
    this.display(year, month, date);

    //发送事件监听
    this.triggerEvent('select', select);
},
//上个月
lastMonth: function () {
    let month = this.data.month == 1 ? 12 : this.data.month - 1;
    let year = this.data.month == 1 ? this.data.year - 1 : this.data.year;
    //初始化日历组件UI
    this.display(year, month, 0);
},
//下个月
nextMonth: function () {
    let month = this.data.month == 12 ? 1 : this.data.month + 1;
    let year = this.data.month == 12 ? this.data.year + 1 : this.data.year;
    //初始化日历组件UI
    this.display(year, month, 0);
},

代码中我们使用this.display(year, month, 0)为组件统一初始化;
同时在today函数中我们添加事件监听函数,将选中的日期发送到页面,我们通过事件订阅来获取的相关值。

<Calendar id="Calendar" bind:select="select"></Calendar>
//组件监听事件
select(e) {
    this.setData({
        selectVal:e.detail
    })
},

最后我们为切换显示效果的按钮添加事件:

    toggleType(){
        this.selectComponent('#Calendar').toggleType();
    }

组件中对应的方法,每当切换展示效果,组件都需要初始化

        //切换展示
        toggleType(){
            this.setData({
                toggleType: this.data.toggleType == 'mini' ? 'large' :'mini'
            })
            //初始化日历组件UI
            this.display(this.data.year, this.data.month, this.data.date);
        },

以上基本上是小程序日历组件实现的基本逻辑,介于篇幅太长还有很多实现上的细节不在此一一细说,大家可以移步我的github

微信小程序日历组件
https://github.com/749264345/wx-calendar

上文中有不足之处,请给出建议或更优的实现方案,谢谢~
最后祝大家五一快乐~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容