react——日历组件封装(移动端)

1,183 阅读2分钟

利用antd组件库的Modal组件进行的封装,通过点击事件控制弹出或隐藏

功能上如果有需要可以自行增加业务逻辑,这只是一个基础版日历组件,根据自己业务需求可增加逻辑代码,代码注释基本上写的很详细了,欢迎大佬指正!

效果图

rili.jpg

// Calendar 组件
import React, { useState, useEffect, useRef, memo } from 'react';
import { Modal, Icon } from 'antd-mobile';
import './style.less';

const weekDay = [
    {
        label: '日',
        value: 0,
    },
    {
        label: '一',
        value: 1,
    },
    {
        label: '二',
        value: 2,
    },
    {
        label: '三',
        value: 3,
    },
    {
        label: '四',
        value: 4,
    },
    {
        label: '五',
        value: 5,
    },
    {
        label: '六',
        value: 6,
    },
];

const basic = {
    year: new Date().getFullYear(),
    month: new Date().getMonth() + 1,
    day: new Date().getDate(),
};

const Calendar = memo((props) => {
    let { show, onClose, defaultDate } = props;
    // 日期展示
    const [showDate, setShowDate] = useState([]);
    // 选择的日期
    const [select, setSelect] = useState({});

    const date = useRef({ year: basic.year, month: basic.month, day: basic.day });

    useEffect(() => {
        setSelect({ ...basic });
        Day(basic.year, basic.month, basic.day);
    }, []);

    useEffect(() => {
        // 保存确定后的日期,取消则显示上次日期
        date.current = { ...defaultDate };
        Day(date.current.year, date.current.month, date.current.day);
    }, [show]);

    // 上月
    const handlePrev = () => {
        let month;
        let year;
        let day = select.day;
        if (Number(select.month) > 1) {
            month = select.month - 1;
            year = select.year;
        }
        if (Number(select.month) === 1) {
            month = 12;
            year = select.year - 1;
        }
        setSelect({ year: year, month: month, day: day });
        Day(year, month, day);
    };

    // 下月
    const handleNext = () => {
        let month;
        let year;
        let day = select.day;
        if (select.month < 12) {
            month = Number(select.month) + 1;
            year = select.year;
        }
        if (select.month == 12) {
            month = 1;
            year = Number(select.year) + 1;
        }
        setSelect({ year: year, month: month, day: day });
        Day(year, month, day);
    };

    // 日期选择
    const handleChangeDate = (val) => {
        // 使用 useRef 存贮点击的日期,可以同步更新
        date.current = { year: select.year, month: select.month, day: val };
        setSelect({ year: select.year, month: select.month, day: val });
        Day(select.year, select.month, val);
    };

    // 用来计算生成日历面板
    const Day = (year, month, day) => {
        let total;
        //判断月分是大月还是小月
        //就可以得出这个月除了2月外是30天还是31天
        if (month !== 2) {
            if (month === 4 || month === 6 || month === 9 || month === 11) {
                total = 30;
            } else {
                total = 31;
            }
        } else {
            //判断是否是闰年
            if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
                total = 29;
            } else {
                total = 28;
            }
        }
        let firstday = new Date(year, month - 1, 1);
        let week = firstday.getDay();
        let dom = [];

        //显示星期
        if (week !== 0) {
            for (let i = 0; i < week; i++) {
                dom.push('');
            }
        }
        //显示每一天的号数
        for (let j = 1; j <= total; j++) {
            //选中的日期使用 check
            let check = false;
            // 这里没有使用 select 的日期做比较,是因为 useState 更新是异步的,切换上月或者下月的时候,月份就会匹配不上,就会出现选中一个日期,切换一下月份再回来选择的日期就会丢失,所以使用 useRef 存贮(同步更新)
            if (year == date.current.year && month == date.current.month && j == date.current.day) {
                check = true;
            }
            dom.push({ label: j, check: check });
        }
        setShowDate(dom);
    };

    return (
        // 这里使用的Modal是从下往上弹出,可参照 antd-mobile 文档API自行修改
        <Modal popup visible={show} className="calendar-modal" animationType="slide-up">
            <div className="calendar-content">
                <div className="calendar-bar">
                    <div
                        className="close"
                        onClick={() => {
                            onClose(false, date.current);
                        }}
                    >
                        取消
                    </div>
                    <div className="bar-left"> {select.year}年</div>
                    <div className="bar-right">
                        <Icon type="left" onClick={handlePrev} />
                        <span className="showtext">{select.month}月</span>
                        <Icon type="right" onClick={handleNext} />
                    </div>
                    <div
                        className="close"
                        onClick={() => {
                            onClose(true, date.current);
                        }}
                    >
                        确认
                    </div>
                </div>
                <div className="calendar-data-content">
                    {weekDay.map((item) => {
                        return (
                            <li className="everyday" key={item.value}>
                                {item.label}
                            </li>
                        );
                    })}
                    {showDate &&
                        showDate.map((item, index) => {
                            return (
                                <div key={index} className="everyday" onClick={() => handleChangeDate(item.label)}>
                                    <span className={`everyday-text ${item.check ? 'check' : ''}`}>{item.label}</span>
                                </div>
                            );
                        })}
                </div>
            </div>
        </Modal>
    );
});

调用方式

const Index = () => {
    const [show, setShow] = useState(false);
    const [defaultDate, setDefaultDate] = useState({ year: basic.year, month: basic.month, day: basic.day });
    const onClose = (type, date) => {
        if (type) {
            // 确定
            setDefaultDate(date);
            console.log('%c 🍏嘿嘿5🍏:', 'color: CornflowerBlue; background: DeepPink; font-size: 20px;', 'todo');
            // 这是彩色的console.log打印,喜欢的可以去vscode自行下载插件(插件名:张大伟--同事自己开发的(大佬))
        } else {
            // 取消
            console.log('%c 🐭嘿嘿77🐭:', 'color: LightPink; background: Yellow; font-size: 20px;', 'todo');
        }
        setShow(false);
    };
    const onOk = () => {
        setShow(true);
    };
    return (
        <div>
            <div onClick={onOk}>呵呵</div>
            <Calendar show={show} onClose={onClose} defaultDate={defaultDate} />
        </div>
    );
};
export default Index;

样式

.calendar-content {
    margin: 0 20px;
    height: 350px;

    .calendar-bar {
        display        : flex;
        align-items    : center;
        justify-content: space-between;
        margin-bottom  : 30px;
        padding        : 10px 15px;

        .bar-left {
            font-weight: bold;
            color      : #999999;
            font-size  : 18px;
        }

        .bar-right {
            display    : flex;
            align-items: center;

            .showtext {
                color      : #999999;
                font-weight: bold;
                font-size  : 18px;
                display    : inline-block;
                width      : 60px;
            }
        }

        .close {
            font-weight: bold;
            color      : #333333;
            font-size  : 18px;
        }
    }

    .calendar-data-content {
        overflow: hidden;

        .everyday {
            font-size  : 16px;
            list-style : none;
            width      : 14%;
            height     : 38px;
            float      : left;
            text-align : center;
            position   : relative;
            font-weight: 700;

            .everyday-text {
                display    : inline-block;
                height     : 34px;
                width      : 34px;
                text-align : center;
                line-height: 34px;
            }

        }

        .active {
            color: red;
        }

        .check {
            background-color: skyblue;
            color           : #ffffff;
            border-radius   : 4px;
        }
    }

}

.calendar-modal {
    .am-modal-content {
        border-top-left-radius : 24px;
        border-top-right-radius: 24px;
    }
}