用react擼個日曆的輪子

幾乎以前我經手的全部項目,都有用到日期選擇器或者日曆的控件,datetimepicker、calendar.js,都是基於JQ的控件。javascript

奈何產品爸爸總提一些UI和交互上的需求,改這些控件的源碼很差改啊!碰巧看了一篇用原生js擼日曆的文章,傳送連接,寫的很是棒啊,挺受啓迪的我,就在現有的react項目裏擼了個輪子,大佬們多多指點那,沒有太寫樣式,主要實現功能來着,效果以下。java


思路

  • dom:總體來看,日曆是一個7(天) * 6(周)組成的表格,那麼我就在state裏構建了這樣的一個數據結構,而後在render函數裏去遍歷每週和天天就能夠出來雛形了,子元素天天的數據結構是一個對象,方便擴展字段,增長顯示文字格式化,假期這些擴展。

this.state = {
    dates: [
        [{}, {}, {}, {}, {}, {}, {}],
        [...],
        [...],
        [...],
        [...],
        [...],
    ],
};複製代碼
  • 生成數據結構:

1.經過最上面form中的下拉菜單選擇當前月份,轉化成moment對象react

2.使用moment的startOf('month')方法,找到當月的第一天算法

3.使用moment的.isoWeekday()方法,找到當月的第一天是星期幾數組

4.經過這兩個值,便可計算出日曆上的當月顯示的第一天,算法以下antd

let now = moment();
//當月第一天
let firstDayOfMonth = now.clone().startOf('month');
//當月第一天是星期幾
let weekOfFirstDay = firstDayOfMonth.isoWeekday();
//日曆上的當月顯示的第一天
let firstDayOfMonthDisplay = firstDayOfMonth.clone().subtract(weekOfFirstDay - 1, 'days');複製代碼

5.有了第一天的moment對象,遍歷6周和每週7天,就能夠組裝出上面提到的數據結構了
數據結構

let dates = [];
//單個日曆,顯示6周
const weeksNum = 6;
//每週顯示7天
const weekDaysNum = 7;
for (let i=0; i<weeksNum; i++){
    let weekList = [];
    for (let j=0; j<weekDaysNum; j++){
        ...
        weekList.push(item);
        currentDate.add(1, 'days');
    }
    dates.push(weekList);
}
複製代碼

  • 假期/節日

1.假期的話,組裝這樣一個{日期: 假期}的映射,在上面步驟中遍歷天的時候,判斷一下是否存在於這個map中,若是有則把假期名稱置到item中。app

const holidayMap = {
    "2019-05-01": "勞動節1",
    "2019-05-02": "勞動節2",
    "2019-05-03": "勞動節3",
    "2019-05-04": "五四青年節",
    ...
};
複製代碼

2.相似的還有,非當前月日期的樣式,週末樣式,公休加班的樣式,都是在遍歷當天的時候去作判斷,把須要的樣式push到一個數組裏,而後.join(" ")組裝成當天完整的樣式便可。less

  • 完整代碼以下

import React, { Component } from 'react';
import { Menu, Icon, Form, Select, Row, Col, Button } from 'antd';
import moment from 'moment';
import '@/static/less/le_calendar.less';

const { Option } = Select;
const FormItem = Form.Item;
// console.log(moment);
const DATE_FORMAT = "YYYY-MM-DD";

const holidayMap = {
    "2019-05-01": "勞動節1",
    "2019-05-02": "勞動節2",
    "2019-05-03": "勞動節3",
    "2019-05-04": "五四青年節",
    "2019-06-07": "端午節1",
    "2019-06-08": "端午節2",
    "2019-06-09": "端午節3",
};

class queryForm extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            options: {
                years: [],
                months: [],
            },
        };
    };

    state = {

    };

    componentDidMount = function () {
        let mySelf = this;
        mySelf.init();
    };

    init = () => {
        let mySelf = this;
        mySelf.initOptions();
        mySelf.setDefaultOption();
    };

    initOptions = function(){
        let mySelf = this;
        let options = mySelf.state.options;
        let now = moment();
        const yearOfNow = now.year();
        let yearsOptions = [];
        let monthsOptions = [];
        const startYear = yearOfNow - 20;
        const endYear = yearOfNow + 20;
        const startMonth = 1;
        const endMonth = 12;

        for (let i=startYear; i<endYear; i++){
            let item = {
                key: i,
                label: i,
            }
            yearsOptions.push(item);
        }

        for (let i=startMonth; i<=endMonth; i++){
            let item = {
                key: i,
                label: i,
            }
            monthsOptions.push(item);
        }

        // console.log(platformOptions);
        options.years = yearsOptions;
        options.months = monthsOptions;
        mySelf.setState({
            options: options
        });
    };

    setDefaultOption = () => {
        let mySelf = this;
        let form = mySelf.props.form;
        // let options = mySelf.state.options;
        let now = moment();
        const yearOfNow = now.year();
        const monthOfNow = now.month() + 1;

        // console.log(options);
        let defaultYearOption = {key: yearOfNow, label: yearOfNow};
        let defaultMonthOption = {key: monthOfNow, label: monthOfNow};
        // console.log(defaultOption)
        let formData = {
            year: defaultYearOption,
            month: defaultMonthOption,
        };
        form.setFieldsValue(formData);
    };

    changeMonth = (pars) => {
        let mySelf = this;
        let form = mySelf.props.form;
        let res = form.getFieldsValue();
        let month = Number(res.month.key) + Number(pars);
        let monthOption = {key: month, label: month};
        // console.log(monthOption);
        let formData = {
            month: monthOption,
        };
        form.setFieldsValue(formData);
        mySelf.props.changeCalendar();
    };

    render() {
        let mySelf = this;
        // eslint-disable-next-line
        let { getFieldDecorator, getFieldValue } = mySelf.props.form;
        let { options } = mySelf.state;
        // console.log(options);
        return (
            <div> <Row className="margin-bottom-5"> <Col span={24}> <Form layout="inline" // wrappedComponentRef={(inst) => this.queryForm = inst} > <FormItem label="年" > {getFieldDecorator('year', { })( <Select labelInValue size={'default'} placeholder="year" style={{ width: 150 }} onChange={ () => setTimeout(() => { mySelf.props.changeCalendar() },0) } > {options.years.map(function(item,index){ return <Option key={item.key}>{item.label}</Option>; })} </Select> )} </FormItem> <FormItem label="月" > {getFieldDecorator('month', { })( <Select labelInValue size={'default'} placeholder="month" style={{ width: 150 }} onChange={ () => setTimeout(() => { mySelf.props.changeCalendar() },0) } > {options.months.map(function(item,index){ return <Option key={item.key}>{item.label}</Option>; })} </Select> )} </FormItem> <FormItem> <Button type="primary" size="small" onClick={() => mySelf.changeMonth(-1)}>pre</Button> <Button type="primary" size="small" onClick={() => mySelf.changeMonth(1)} className="margin-left-5">next</Button> </FormItem> </Form> </Col> </Row> </div>
        );
    }
};
let QueryFormComponent = Form.create()(queryForm);

class LeCalendar extends Component {
    constructor(props) {
        super(props);
        this.state = {
            //渲染日曆過程當中,當前顯示月的moment對象
            monthDisplay: "",
            dates: [
                [],
                [],
            ],
        };
    };

    componentDidMount = () => {
        let mySelf = this;
        mySelf.init();
        // mySelf.generateDates();
    };

    init = () => {
        let mySelf = this;
        mySelf.changeCalendar();
    };

    generateDates = () => {
        let mySelf = this;
        let dates = [];
        //單個日曆,顯示6周
        const weeksNum = 6;
        //每週顯示7天
        const weekDaysNum = 7;
        //日曆中的今天
        const todayDate = moment().format(DATE_FORMAT);

        //渲染日曆過程當中,當前顯示月的moment對象
        let now = mySelf.state.monthDisplay;
        //當前顯示的月份
        let nowMonth = now.month();
        // console.log(nowMonth);
        //當月第一天
        let firstDayOfMonth = now.clone().startOf('month');
        //當月第一天是星期幾
        let weekOfFirstDay = firstDayOfMonth.isoWeekday();
        //日曆上的當月顯示的第一天
        let firstDayOfMonthDisplay = firstDayOfMonth.clone().subtract(weekOfFirstDay - 1, 'days');

        //渲染日曆過程當中,使用的當前moment對象
        let currentDate = firstDayOfMonthDisplay.clone();
        // console.log(firstDayOfMonthDisplay.format(DATE_FORMAT));
        const otherMonthClass = "other-month";
        const dayOffClass = "day-off";
        const holidayClass = "holiday";
        const todayClass = "today";

        for (let i=0; i<weeksNum; i++){
            let weekList = [];
            for (let j=0; j<weekDaysNum; j++){
                let date = currentDate.format(DATE_FORMAT);
                let dateDisplay = currentDate.date();
                let currentMonth = currentDate.month();
                let currentWeekDay = currentDate.isoWeekday();
                let classNameList = [];
                let classNameStr = "";
                let content = "";
                // console.log(currentMonth);
                // console.log(currentDate.isoWeekday());

                //今天
                if (todayDate === date){
                    classNameList.push(todayClass);
                }

                //非當前月
                if (currentMonth !== nowMonth){
                    classNameList.push(otherMonthClass);
                }

                //週末
                if ([6, 7].includes(currentWeekDay)){
                    classNameList.push(dayOffClass);
                }

                //節假日
                if (holidayMap[date]){
                    classNameList.push(holidayClass);
                    content = holidayMap[date];
                }

                classNameStr = classNameList.join(" ");

                let item = {
                    momentObj: currentDate,
                    date: date,
                    dateDisplay: dateDisplay,
                    classNameStr: classNameStr,
                    content: content
                };
                weekList.push(item);
                currentDate.add(1, 'days');
            }
            dates.push(weekList);
        }

        mySelf.setState({
            dates
        });
        // console.log(dates);
    };

    changeCalendar = () => {
        let mySelf = this;
        let form = mySelf.queryForm.props.form;
        let res = form.getFieldsValue();
        let year = Number(res.year.key);
        let month = Number(res.month.key) -1;
        let now = moment();
        now.year(year).month(month);

        mySelf.state.monthDisplay = now;
        mySelf.generateDates();
        // console.log(firstDayOfMonthDisplay.format(DATE_FORMAT));
        console.log(now.format(DATE_FORMAT));
    };

    render() {
        // let { } = this.props;
        let mySelf = this;
        let { dates } = mySelf.state;
        let renderWeekDays = (days) => {
            let dom = null;
            // console.log(days);
            dom = (
                <React.Fragment>
                    {days.map((item,index) => {
                        let tdDom = (
                            <td key={index}>
                                <div className={item.classNameStr + ' day-cell'}>
                                    <div className="header">{ item.date } {item.dateDisplay}</div>
                                    <div>{ item.content }</div>
                                </div>
                            </td>
                        );
                        return tdDom;
                    })}
                </React.Fragment>
            );
            return dom
        };

        let renderWeeks = (dates) => {
            let dom = null;
            // console.log(dates);
            dom = (
                <React.Fragment>
                    {dates.map((item,index) => {
                        let trDom = (
                            <tr key={index}>
                                { renderWeekDays(item) }
                            </tr>
                        );
                        return trDom;
                    })}
                </React.Fragment>
            );
            return dom
        };

        return (
            <div>
                <QueryFormComponent
                    wrappedComponentRef={(inst) => mySelf.queryForm = inst}
                    changeCalendar={() => mySelf.changeCalendar()}
                />
                <table border="1" className="le-calendar-container">
                    <thead>
                        <tr>
                            <th>一</th>
                            <th>二</th>
                            <th>三</th>
                            <th>四</th>
                            <th>五</th>
                            <th>六</th>
                            <th>日</th>
                        </tr>
                    </thead>
                    <tbody>
                        { renderWeeks(dates) }
                    </tbody>
                    <tfoot>
                        <tr>
                            <td>foot1</td>
                            <td>foot2</td>
                        </tr>
                    </tfoot>
                </table>
            </div>
        );
    }
}

export default LeCalendar;
複製代碼
相關文章
相關標籤/搜索