行程日历组件:选择开始和结束日期+滑动选择时间

前一篇文章刚给大家分享了滑动组件(http://blog.csdn.net/sq_zhuyi/article/details/79103683),本篇文章就正好利用到这个滑动组件,在日历控件中实现选择时间的模块。同样,为了便于入题,我们先看效果图:


这是一个在租车项目中选择取车时间和还车时间的日历,其实同样的场景被用于很多旅游项目,用来选择出发、返程时间。

上代码,CSS模块:

.acal {
    position: absolute;
    left: 0;
    right: 0;
    top: 44px;
    z-index: 100;
}

.acal .head {
    position: fixed;
    left: 0;
    right: 0;
    top: 44px;
    z-index: 12;
    height: 44px;
    padding: 0 25px;
    border-top: 1px solid #ddd;
    border-bottom: 1px solid #ddd;
    background-color: #fff;
    text-align: center;
}
.acal .head .dt {
    line-height: 44px;
    font-size: 18px;
}
.acal .head .day {
    width: 3.5em;
    line-height: 26px;
    border: 1px solid #f00;
    border-radius: 8px;
    margin-top: 8px;
}

.acal .week {
    position: fixed;
    left: 0;
    right: 0;
    top: 88px;
    z-index: 11;
    line-height: 32px;
    text-align: center;
    background-color: #fff;
    border-bottom: 1px solid #eee;
    box-shadow: 0 2px 20px rgba(0,0,0,0.2);
}
.acal .week>i {
    width: 14.28%;
}

.acal .calbody {
    padding: 80px 0 200px 0;
    background-color: #f9f9f9;
}
.acal .month {
    position: relative;
}
.acal .month>h2 {
    text-align: center;
    line-height: 295px;
    font-size: 80px;
    color: #dedede;
    font-weight: normal;
}
.acal .month .days {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
}
.acal .month .days>i {
    float: left;
    width: 59px;
    height: 39px;
    margin: 10px 0;
    text-align: center;
    font-size: 20px;
    position: relative;
}
.acal .month .days em {
    width: 39px;
    line-height: 39px;
    border-radius: 50%;
    position: absolute;
    left: 10px;
    top: 0;
    z-index: 2;
}
.acal .month .days>i.disabled {
    color: #999;
}
.acal .month .days>i.hover {
    color: #fff;
}
.acal .month .days>i.hover>em {
    background-color: #f00;
}
.acal .month .days>i.hover::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    z-index: 1;
    background-color: #FFD2CF;
}
.acal .month .days>i.start::before {
    left: 30px;
    right: 0;
}
.acal .month .days>i.end::before {
    left: 0;
    right: 30px;
}
.acal .month .days>i.active {
    background-color: #FFD2CF;
}

.acal .time {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 60px;
    z-index: 10;
    border-top: 1px solid #eee;
    background-color: #fff;
    box-shadow: 0 -2px 20px rgba(0,0,0,0.2);
}
.acal .time .tit {
    float: left;
    font-size: 16px;
    width: 100px;
    text-indent: 14px;
    line-height: 56px;
}
.acal .time .slide {
    float: left;
    width: 300px;
    height: 2px;
    margin-top: 26px;
    background-color: #ddd;
}
.acal .time .value {
    float: left;
    height: 2px;
    background-color: #f00;
    position: relative;
}
.acal .time .value>em {
    position: absolute;
    right: -30px;
    top: -10px;
    line-height: 22px;
    padding: 0 6px;
    border: 1px solid #f00;
    background-color: #fff;
    border-radius: 20px;
}

日期模块:

/**
 * 日历
 * @param {object} options 
 * @param {number} options.minday 最少提前几天预定
 * @param {number} options.months 显示几个月
 * @param {date} options.start 默认开始时间
 * @param {date} options.end 默认结束时间
 * @param {function} options.callback 选择时间后的回调函数
 */
function Calendar(options) {
    this.options = options;
    this.options.daysel = '.acal .days>i';

    this.init();
}

Calendar.prototype.init = function () {
    var ins = this;
    if($('.acal').length==0){
        ins._createDOM();
        ins._bindEvent();
    }
    // 初始化日期-
    var start = ins.options.start;
    var m1 = $('.acal [data-m="{0}-{1}"]'.format(start.getFullYear(), start.getMonth()));
    var d1 = m1.next().find('[data-d="{0}"]'.format(start.getDate()));
    d1.trigger('click');
    
    var end = ins.options.end;
    var m2 = $('.acal [data-m="{0}-{1}"]'.format(end.getFullYear(), end.getMonth()));
    var d2 = m2.next().find('[data-d="{0}"]'.format(end.getDate()));
    d2.trigger('click');


    // 初始化时间-
    var arr = $('.acal .time em');
    var t1 = start.date(5).split(' ')[1];
    arr.eq(0).html(t1).parent().data('value', t1);
    var t2 = end.date(5).split(' ')[1];
    arr.eq(1).html(t1).parent().data('value', t2);

    var tt = [];
    for (var i = 0; i < 24; i++) {
        tt.push(i + ':00');
        tt.push(i + ':30');
    }
    new Slider({
        bar: $('.acal .slide').eq(0),
        btn: $('.acal .slide em').eq(0),
        data: tt,
        width: 300,
        callback: ins.setValue.bind(ins)
    }).init();
    new Slider({
        bar: $('.acal .slide').eq(1),
        btn: $('.acal .slide em').eq(1),
        data: tt,
        width: 300,
        callback: ins.setValue.bind(ins)
    }).init();
    
    ins.setValue();
};
Calendar.prototype.hide = function () {
    $('.acal').css('display', 'none');
}
Calendar.prototype.show = function () {
    $('.acal').css('display', 'block');
}

Calendar.prototype._bindEvent = function () {
    var ins = this;
    var options = this.options;
    $(options.daysel).on('click', function () {
        var $t = $(this);
        if ($t.hasClass('disabled')) return;

        var len = $(options.daysel + '.hover').length;
        $t.addClass('hover');
        if (len == 1) {
            ins._resetDate();
        } else if (len == 2) {
            $(options.daysel).removeClass('hover active start end');
            $t.addClass('hover');
        }
    });
    $('.acal .btn-red').on('click', function(){
        ins.hide();
        if(ins.options.callback){
            ins.options.callback(options.start, options.end);
        }
    });
}

Calendar.prototype._createDOM = function () {
    var ss = '<div class="acal" style="display:none;">' +
        '<div class="head"><i class="left dt"></i><i class="day"></i><i class="right dt"></i></div>' +
        '<div class="week"><i>日</i><i>一</i><i>二</i><i>三</i><i>四</i><i>五</i><i>六</i></div>' +
        '<div class="calbody"></div>' +
        // time start
        '<div class="time">' +
        '<div class="row clearfix">' +
        '<i class="tit">取车时间</i>' +
        '<div class="slide"><div class="value" data-value="10:00"><em>10:00</em></div></div>' +
        '</div>' +
        '<div class="row clearfix">' +
        '<i class="tit">还车时间</i>' +
        '<div class="slide"><div class="value" data-value="10:00"><em>10:00</em></div></div>' +
        '</div>' +
        '</div>' +
        // time end
        '<div class="footbar"><a class="btn-red">确认</a></div>' +
        '</div>';
    $('body').append(ss);
    this._createMonth();
}

Calendar.prototype._createMonth = function () {
    var options = this.options;
    var s0 = '<div class="month"><h2 data-m="{2}-{3}">{0}月</h2><div class="days">{1}</div></div>';
    var s1 = '<i data-d="{0}"><em>{0}</em></i>';
    var s2 = '<i class="disabled"><em>{0}</em></i>';
    var ss = '';
    var now = new Date();
    var arr = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    for (var i = 0; i < options.months; i++) {
        var dt = new Date(now.getFullYear(), now.getMonth() + i, 1);
        let max = arr[dt.getMonth()];
        if (dt.getFullYear() % 4 == 0 && dt.getMonth() == 1) max += 1;
        var s = '',
            d0 = 0;
        // 从正确的周N开始-
        while (d0 < dt.getDay()) {
            s += s2.format('');
            d0++;
        }
        for (var d = 1; d <= max; d++) {
            var disabled = (i == 0) && (d < now.getDate() + options.minday);
            s += (disabled ? s2 : s1).format(d);
        }
        // 不足一个月补全-
        var d1 = 7 - (d0 + max) % 7;
        while (d1 > 0) {
            s += s2.format('');
            d1--;
        }
        ss += s0.format(dt.getMonth() + 1, s, dt.getFullYear(), dt.getMonth());
    }
    $('.calbody').html(ss);
}

Calendar.prototype._resetDate = function () {
    var arr = $(this.options.daysel + '.hover');
    var d1 = arr.eq(0).addClass('start');
    var d2 = arr.eq(1).addClass('end');

    var next = d1.next();
    while (!next.hasClass('end')) {
        next.addClass('active');
        next = next.next();
        if (next.length == 0) {
            next = d1.parent().parent().next().find('.days>i').eq(0);
        }
    }
    this.setValue();
}
// 将最终的日期set到头部-
Calendar.prototype.setValue = function(){
    var options = this.options;
    var d1 = $(options.daysel + '.start');
    var d2 = $(options.daysel + '.end');

    var arr = $('.acal .time em');
    var t1 = arr.eq(0).html() || '10:00';
    var t2 = arr.eq(1).html() || '10:00';

    function getDT(d, t){
        var ym = d.parent().prev().data('m').split('-');
        var s = '{0}-{1}-{2} {3}'.format(ym[0], ym[1]-0+1, d.data('d'), t);
        return s.date(1);
    }
    options.start = getDT(d1, t1);
    options.end = getDT(d2, t2);

    $('.acal .head .dt').eq(0).html(options.start.date(4).replace('-','月').replace(' ','日 '));
    $('.acal .head .dt').eq(1).html(options.end.date(4).replace('-','月').replace(' ','日 '));
    
    $('.acal .head .day').html(av.days(options.start, options.end)+'天');
};

时间模块:

/**
 * 滑动模块
 * @param {object} options 
 * @param {jquery} options.bar 进度条
 * @param {jquery} options.btn 按钮
 * @param {Array} options.data 进度条上的数据
 * @param {number} options.width 进度条宽度(可选)
 * @param {function} options.callback 选择后的回调
 */
function Slider(options) {
    this.options = options;
    this.init();
}

Slider.prototype.init = function () {
    var ins = this;
    if (ins.options.steps) {
        return; // 只init一次-
    }
    // 将整个bar分隔成N个宽度-
    var steps = [];
    var ww = ins.options.width || ins.options.bar[0].offsetWidth;
    var w = (ww - 20) / (ins.options.data.length - 1);
    $.each(ins.options.data, function (i, v) {
        steps[i] = w * i;
    });
    ins.options.steps = steps;

    var $v = ins.options.btn.parent();
    var val = $v.data('value');
    if (!val) {
        val = ins.options.data[0];
    }
    ins.setValue(val);
    $.each(ins.options.data, function (i, v) {
        if (val == v) {
            $v.width(ins.options.steps[i]);
        }
    });

    // 绑定btn事件-
    ins.bindEvent();
};
Slider.prototype.setValue = function (value) {
    this.options.btn.html(value).parent().data('value', value);
};
Slider.prototype.bindEvent = function () {
    var ins = this;
    var $b = ins.options.btn;
    var $v = $b.parent();
    var x0 = 0, // 鼠标按下位置-
        x1 = 0, // 鼠标移动过程的位置-
        w0 = 0; // 滑动前的value大小-

    $b.on('mousedown touchstart', function (event) {
        x0 = event.clientX || event.touches[0].clientX;
        w0 = $v.width();
        w2 = ins.options.width || ins.options.bar[0].offsetWidth;
    }).on('mousemove touchmove', function (event) {
        if (x0 == 0) return;
        x1 = event.clientX || event.touches[0].clientX;
        var w1 = w0 + x1 - x0;
        if (w1 > w2) return;
        $v.width(w1);
        var val = getNewValue(w1);
        if (val != $v.data('value')) {
            ins.setValue(val);
        }
    }).on('mouseup touchend', function (event) {
        x0 = x1 = 0;
        if(ins.options.callback){
            ins.options.callback();
        }
    });

    // 根据当前value宽度获取对应的data-
    function getNewValue(w) {
        var max = 0;
        $.each(ins.options.steps, function (i, v) {
            if (w > v) max = i;
        });
        return ins.options.data[max];
    }
};

大家已经发现,该日历组件主要用于移动端项目,但JavaScript代码其实不区分客户端的,稍微修改CSS代码,一样用于PC端设备。


作者:朱会震


  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值