微信小程序手写时间间隔组件,可设置间隔时间一分钟,半小时,一小时的间隔

纯手写时间间隔组件

需求:小程序中可以根据时间段进行选择开始时间和结束时间,如:当前时间是09:00,
则我可以从9点开始选择时间,每半个小时为间隔,那么下一个时间就算9:30,10:00,依次类推
就像element-ui中有个时间选择器就可以根据自己设置的时间间隔去选择,可以0.5小时,1小时或者2.5小时的间隔进行选择
由于公司小程序用的是vant-ui来开发的,我去找vant的时间组件,发现没有这样的,所以决定自己开发一下这个组件

微信小程序手写时间间隔组件,可设置间隔时间一分钟,半小时,一小时的间隔_第1张图片

!!!注意:

由于公司用的是vant,所以弹窗按钮用的都是我们用vant自己封装的组件,
在这里就不展示弹窗组件还有按钮了,弹窗和按钮就得大家自己用自己的框架去显示了

看效果

微信小程序手写时间间隔组件,可设置间隔时间一分钟,半小时,一小时的间隔_第2张图片微信小程序手写时间间隔组件,可设置间隔时间一分钟,半小时,一小时的间隔_第3张图片
间隔一小时
微信小程序手写时间间隔组件,可设置间隔时间一分钟,半小时,一小时的间隔_第4张图片
提前30分钟和推迟30分钟 当前时间10:51
微信小程序手写时间间隔组件,可设置间隔时间一分钟,半小时,一小时的间隔_第5张图片微信小程序手写时间间隔组件,可设置间隔时间一分钟,半小时,一小时的间隔_第6张图片

创建 time-interval组件 如果你想全局使用则在app.json的usingComponents中添加你得组件地址

 "usingComponents": {"time-interval": "/components/time-interval/time-interval"}

创建好后就开始编码 HTML部分


<import src="../../static/templates/template.wxml">import>

<wxs src="./util.wxs" module="computed" />

<custom-popup
    customStyle="width: 100%;max-height: calc(100% - 148rpx); overflow: hidden; border-radius: 24rpx 24rpx 0 0;"
    hidden="{{!isShowTime}}" isShowPopup="{{isShowTime}}" title="{{title}}"
    isCloseOnClickOverlay="{{isCloseOnClickOverlay}}" bindhidePopup="hidePopup" bind:titleTap="titleTap">

    <view class="w-time_container">
        <view class="w-t_left" data-type="startOptions" bind:touchstart="onTouchStart" catch:touchmove="onTouchMove"
            bind:touchend="onTouchEnd" bind:touchcancel="onTouchEnd">
            <view
                style="{{ computed.wrapperStyle({ offset:startOptions.offset, itemHeight, visibleItemCount, duration:startOptions.duration }) }}">
                <view class="w-time_row {{index===startOptions.currentIndex?'w-check_box':''}}"
                    style="height: {{ itemHeight }}px" wx:for="{{timeList}}" wx:key="index" data-item="{{item}}"
                    data-index="{{ index }}" data-type="startOptions" bind:tap="onClickItem">
                    {{item.time}}
                view>
            view>
        view>
        <view class="w-t_right" data-type="endOptions" bind:touchstart="onTouchStart" catch:touchmove="onTouchMove"
            bind:touchend="onTouchEnd" bind:touchcancel="onTouchEnd">
            <view
                style="{{ computed.wrapperStyle({ offset:endOptions.offset, itemHeight, visibleItemCount, duration:endOptions.duration }) }}">
                <view
                    class="w-time_row {{index===endOptions.currentIndex?'w-check_box':''}} {{startTime.timeStamp>=item.timeStamp?'w-disabled':''}}"
                    wx:for="{{timeList}}" wx:key="index" data-item="{{item}}" data-index="{{ index }}"
                    data-type="endOptions" bind:tap="{{startTime.timeStamp>=item.timeStamp?'':'onClickItem'}}">
                    {{item.time}}
                view>
            view>

        view>
        <view class="w-pick_mask" style="background-size: 100% 40%;">view>
    view>
    
    <template is="iconBtn" data="{{btnText: '确定', btnTap: 'confirmTime'}}">template>
custom-popup>

js部分


/**
 * 获取当前天时间
 * @param param 【Y:年;M:月;D:日;h:小时;m:分钟;s:秒;】 默认精确到秒
 * @returns {*}
 */
function getCurrentDate(param = 's', target = '') {
    var now = target ? new Date(toIosDate(target)) : new Date();
    var year = now.getFullYear(); //得到年份
    var month = now.getMonth(); //得到月份
    var date = now.getDate(); //得到日期
    var day = now.getDay(); //得到周几
    var hour = now.getHours(); //得到小时
    var minu = now.getMinutes(); //得到分钟
    var sec = now.getSeconds(); //得到秒
    month = month + 1;
    if (month < 10) month = "0" + month;
    if (date < 10) date = "0" + date;
    if (hour < 10) hour = "0" + hour;
    if (minu < 10) minu = "0" + minu;
    if (sec < 10) sec = "0" + sec;

    const arr = {
        'Y': year,
        'M': year + "-" + month,
        'D': year + "-" + month + "-" + date,
        'h': year + "-" + month + "-" + date + " " + hour,
        'm': year + "-" + month + "-" + date + " " + hour + ":" + minu,
        's': year + "-" + month + "-" + date + " " + hour + ":" + minu + ":" + sec
    }
    return {
        year,
        month,
        day: date,
        hour,
        minu,
        sec,
        date: arr[param]
    };
}

function range(num, min, max) {
    return Math.min(Math.max(num, min), max);
}

function isObj(x) {
    const type = typeof x;
    return x !== null && (type === 'object' || type === 'function');
}
const DEFAULT_DURATION = 200;
let app = getApp();
let isIOS = /ios/ig.test(app.globalData.systemInfo.system);
/**
 * 判断是否是iso,ios的话时间格式会有问题得2023/09/13,以斜杠的格式传入
 * @param {时间} date 
 */
function toIosDate(date) {
    return isIOS ? date.replace(/-/g, '/') : date
}
Component({
    options: {
        addGlobalClass: true,
        styleIsolation: "apply-shared"
    },
    /**
     * 组件的属性列表
     */
    properties: {
        // 是否显示
        isShowTime: {
            type: Boolean,
            value: false,
            observer: 'initArr'
        },
        // 标题
        title: {
            type: String,
            value: '选择时间'
        },
        // 点击蒙层是否关闭弹窗
        isCloseOnClickOverlay: {
            type: Boolean,
            value: false
        },
        //间隔时间
        intervalTime: {
            type: Number,
            value: 1/60, //单位小时
        },
        //是否是推迟还是提前多少分钟 提前则大于0,推迟则小于0,且不能大于一天
        hasDelay: {
            type: Number,
            value: 0, //单位分钟
        },
        //选择的日期,默认当天的日期
        curDate: {
            type: String,
            valeu: getCurrentDate('D').date
        },
        //可见的选项个数
        visibleItemCount: {
            type: Number,
            value: 6
        },
        //选项高度
        itemHeight: {
            type: Number,
            value: 44
        }
    },

    /**
     * 组件的初始数据
     */
    data: {
        startTime: {},
        endTime: {},
        timeList: [],
        startOptions: {
            startY: 0,
            startOffset: 0,
            duration: 0,
            offset: 0,
            currentIndex: 0
        },
        endOptions: {
            startY: 0,
            startOffset: 0,
            duration: 0,
            offset: 0,
            currentIndex: 0
        }
    },


    /**
     * 组件的方法列表
     */
    methods: {
        initArr(nv, ov) {
            if (nv) {
                const {
                    date,
                    hour,
                    minu,
                } = getCurrentDate('D')
                const w_date = this.properties.curDate ? (date === this.properties.curDate ? '' : `${this.properties.curDate} 00:00`) : `${date} ${hour}:${minu}`

                this.setData({
                    timeList: this.createTime(getCurrentDate('D', w_date), this.properties.intervalTime)
                })
                this.setIndex(0, 'startOptions');
                this.setIndex(1, 'endOptions');
            }
        },
        createTime(target, h = 1) {
            const {
                hour,
                minu,
                date
            } = target
            const e_date = this.getRecentDate(1, this.properties.curDate)
            let arr = []
            let startStamp = new Date(toIosDate(date + ` ${hour}:${minu}`)).getTime()
            //是否是推迟还是提前多少分钟
            if (Math.abs(this.properties.hasDelay) > 0) {
                startStamp = startStamp + this.properties.hasDelay*60*1000
            } 
            const endStamp = new Date(toIosDate(e_date + ' 00:00')).getTime()

            for (let i = startStamp; i < endStamp; i += (h * 60 * 60 * 1000)) {
                const res = this.formatDate(i)
                arr.push({
                    time: res.time,
                    date: res.date,
                    dateM: res.dateM,
                    timeStamp: i,
                    disabled: false
                })

            }
            return arr
        },
        confirmTime() {
            if (!this.data.endTime.timeStamp) {
                return wx.showToast({
                    title: '请选择结束时间',
                    icon: 'none',
                })
            }

            this.triggerEvent('chooseInterver', {
                startTime: this.data.startTime,
                endTime: this.data.endTime
            })
        },

        //时间戳转换
        formatDate(target) {
            const date = new Date(target)
            let year = date.getFullYear();
            let months = date.getMonth() + 1;
            let month = (months < 10 ? '0' + months : months).toString();
            let day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate();
            let hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours();
            let min = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes();
            let sec = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds();
            return {
                year: year.toString(),
                month,
                day,
                hours,
                min,
                sec,
                time: hours + ":" + min,
                date: year + "-" + month + "-" + day + " " + hours + ":" + min,
                dateM: year + "-" + month + "-" + day
            }
        },
  //计算当前时间几天后的数据
        getRecentDate(day, target) {
            var date1 = target ? new Date(toIosDate(target)) : new Date(),
                time1 = date1.getFullYear() + "-" + (date1.getMonth() + 1) + "-" + date1.getDate(); //time1表示当前时间
            var date2 = new Date(date1);
            date2.setDate(date1.getDate() + day);
            const y = date2.getFullYear();
            const m = (date2.getMonth() + 1) > 9 ? (date2.getMonth() + 1) : '0' + (date2.getMonth() + 1)
            const d = date2.getDate() > 9 ? date2.getDate() : '0' + date2.getDate()
            let h = date2.getHours() < 10 ? '0' + date2.getHours() : date2.getHours();
            let n = date2.getMinutes() < 10 ? '0' + date2.getMinutes() : date2.getMinutes();
            let s = date2.getSeconds() < 10 ? '0' + date2.getSeconds() : date2.getSeconds();
            return y + "-" + m + "-" + d;
        },
        hidePopup() {
            this.setData({
                [`startOptions.startY`]: 0,
                [`startOptions.startOffset`]: 0,
                [`startOptions.duration`]: 0,
                [`startOptions.currentIndex`]: 0,
                [`endOptions.startY`]: 0,
                [`endOptions.startOffset`]: 0,
                [`endOptions.duration`]: 0,
                [`endOptions.currentIndex`]: 0,
            })
            this.triggerEvent('close')
        },
        titleTap() {
            this.triggerEvent('titleTap')
        },
        getCount() {
            return this.data.timeList.length;
        },
        onTouchStart(event) {
            const options = event.currentTarget.dataset.type
            this.setData({
                [`${options}.startY`]: event.touches[0].clientY,
                [`${options}.startOffset`]: this.data[options].offset,
                [`${options}.duration`]: 0,
            });
        },
        onTouchMove(event) {
            const options = event.currentTarget.dataset.type
            const deltaY = event.touches[0].clientY - this.data[options].startY;
            this.setData({
                [`${options}.offset`]: range(
                    this.data[options].startOffset + deltaY,
                    -(this.getCount() * this.properties.itemHeight),
                    this.properties.itemHeight
                ),
            });
        },

        onTouchEnd(event) {
            const options = event.currentTarget.dataset.type
            if (this.data[options].offset !== this.data[options].startOffset) {
                this.setData({
                    [`${options}.duration`]: DEFAULT_DURATION
                });

                const index = range(
                    Math.round(-this.data[options].offset / this.data.itemHeight),
                    0,
                    this.getCount() - 1
                );
                this.setIndex(index, options);
            }
        },

        onClickItem(event) {
            const options = event.currentTarget.dataset.type
            const {
                index
            } = event.currentTarget.dataset;
            this.setIndex(index, options);
        },

        /**
         * 设置位置
         * @param {*} index 数组下标 位置
         * @param {*} options 数据类型
         */
        setIndex(index, options) {
            const {
                data
            } = this;
            index = this.adjustIndex(index) || 0;
            const offset = -index * this.properties.itemHeight;
            if (index !== data[options].currentIndex) {
                this.setData({
                    [`${options}.offset`]: offset,
                    [`${options}.currentIndex`]: index,
                    [`${options==='startOptions'?'startTime':'endTime'}`]: this.data.timeList[index]
                })
                //判断结束时间是否大于开始时间,是则选择否则为空
                if (options === 'endOptions') {
                    if (this.data.startTime.timeStamp < this.data.timeList[index].timeStamp) {
                        this.setData({
                            endTime: this.data.timeList[index]
                        })
                    } else {
                        this.setData({
                            endTime: {}
                        })
                    }
                }else{
                    this.setData({
                        endTime: {}
                    })
                }
                
            } else {
                this.setData({
                    [`${options}.offset`]: offset,
                    [`${options==='startOptions'?'startTime':'endTime'}`]: this.data.timeList[index]
                });
            }
        },
        adjustIndex(index) {
            const count = this.getCount();

            index = range(index, 0, count);
            for (let i = index; i < count; i++) {
                if (!this.isDisabled(this.data.timeList[i])) return i;
            }
            for (let i = index - 1; i >= 0; i--) {
                if (!this.isDisabled(this.data.timeList[i])) return i;
            }
        },
        isDisabled(option) {
            return isObj(option) && option.disabled;
        },
    }
})

css 部分

.w-time_container {
    position: relative;
    height: 600rpx;
    width: 100%;
    padding: 8rpx;
    box-sizing: border-box;
    display: flex;
}

.w-t_left,
.w-t_right {
    width: 50%;
    height: 100%;
    display: flex;
    padding: 48rpx 0;
    flex-direction: column;
    align-items: center;
    box-sizing: border-box;
    overflow: hidden;
}

.w-pick_mask{
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    background-image: linear-gradient(180deg, hsla(0, 0%, 100%, 0.9), hsla(0, 0%, 100%, 0.4)), linear-gradient(0deg, hsla(0, 0%, 100%, 0.9), hsla(0, 0%, 100%, 0.4));
    background-repeat: no-repeat;
    background-position: top, bottom;
    transform: translateZ(0);
    pointer-events: none;
}


.w-time_row {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 32rpx;
    font-weight: 600;
    box-sizing: border-box;
}

.w-check_box {
    color: #264AFF;
}
.w-disabled {
    color: #0A1B33B3;
}

工具类util.wxs

function addUnit(value) {
    if (value == null) {
        return undefined;
    }
    var REGEXP1 = getRegExp('^-?\d+(\.\d+)?$');
    return REGEXP1.test('' + value) ? value + 'px' : value;
}
var REGEXP2 = getRegExp('{|}|"', 'g');

function keys(obj) {
    return JSON.stringify(obj)
        .replace(REGEXP2, '')
        .split(',')
        .map(function (item) {
            return item.split(':')[0];
        });
}

function kebabCase(word) {
    var newWord = word
        .replace(getRegExp("[A-Z]", 'g'), function (i) {
            return '-' + i;
        })
        .toLowerCase()

    return newWord;
}


function isArray(array) {
    return array && array.constructor === 'Array';
}

function style(styles) {
    if (isArray(styles)) {
        return styles
            .filter(function (item) {
                return item != null && item !== '';
            })
            .map(function (item) {
                return style(item);
            })
            .join(';');
    }

    if ('Object' === styles.constructor) {
        return keys(styles)
            .filter(function (key) {
                return styles[key] != null && styles[key] !== '';
            })
            .map(function (key) {
                return [kebabCase(key), [styles[key]]].join(':');
            })
            .join(';');
    }

    return styles;
}

function wrapperStyle(data) {
    var offset = addUnit(
        data.offset + (data.itemHeight * (data.visibleItemCount - 1)) / 2
    );
    return style({
        transition: 'transform ' + data.duration + 'ms',
        'line-height': addUnit(data.itemHeight),
        transform: 'translate3d(0, ' + offset + ', 0)',
    });
}

module.exports = {
    wrapperStyle: wrapperStyle,
};

Attributes

参数 说明 类型 可选值 默认值
intervalTime 间隔时间(单位小时) Number 1/60(一分钟);30/60(30分钟); 1(1小时);2.5(两个半小时) 1/60
hasDelay 是否是推迟还是提前多少分钟 提前则大于0,推迟则小于0,且不能大于一天 Number 10(提前10分钟);-10(推迟10分钟) 0
curDate 选择的日期,默认当天的日期 String 2023-02-11或 2023-02-11 09:00:00 当天时间

Events

参数 说明 类型 参数 返回值
chooseInterver 选择时间结果 Function - {starTime:resultTime,endTime:resultTime}

resultTime

prop 说明
date 日期+时分 2023-09-13 09:33
dateM 日期没有时分 2023-09-13
time 选择的时分 09:33
timeStamp 选择date的时间戳 1694568780000

看过vant源码的会发现 我这里会有一部分vant的代码,确实是,我用了一下vant的时间选择器的样式代码,没办法时间紧迫写的样式达不到那种丝滑,只能凑合用

你可能感兴趣的:(小程序,微信小程序,小程序)