calendar(taro中開發小程序價格日曆)

前言

因項目需求,最近使用taro,開發了一款應用於票務商品購票的價格日曆,感受這個東西的應用仍是比較多的,所以,就分享出來,關公門前耍個大刀,路過的大佬們手下留情哦!css

  • 先預覽下效果

爲了響應國家號召,節能減排,小弟就將一些邊緣的邏輯和樣式省略,重點分享日曆這部分的邏輯,完整代碼請 猛戳 這裏。git

實現思路分析

ticketPriceList(設置有價格日期等信息的商品集合)經過props傳遞進calendar組件,calendar組件中initDate方法對日曆數據進行初始化(初始化日期顯示數據,該方法會在組件初始化時先調用一次),同時遍歷ticketPriceList,經過匹配二者日期是否相等來將有價格的日期同步到日曆上顯示。經過調用prevMonthnextMonth來切換顯示的月份,最終一樣調用initDate來更新日曆顯示的數據。github

實現代碼

<!--calendar組件-->

import Taro, {
    Component
} from '@tarojs/taro'
import { View, Image } from '@tarojs/components'

import './index.scss'

export default class SkuSelector extends Component {
    //props default
    static defaultProps = {
        ticketPriceList: []
    };
    constructor(props) {
        super(props);
        this.state = {
            date: new Date(), //獲取系統日期
            currentDate: '', //當前選擇的日期
            selectMonth: '', //選擇的月份
            weeks: ['日', '一', '二', '三', '四', '五', '六'],
            color: '#fff',//選中日期文字顏色
            bgColor: 'f5f6f7',//非今日的日期背景色
            dayArr: [], //月份天數
            year: '', //當前選擇的年
            month: '', //當前選擇的月
            status: 1, //1:屬於當月的天,0:屬於上月2:屬於下月
            prevMonthStatus: false,//返回上個月按鈕禁用狀態,false:禁用,true,不由用
        }
    }

    //時間格式 xxxx-xx-xx 轉時間戳
    fmtDate(date) {
        let dateStamp = new Date(date.replace(/-/g, '/'));
        return dateStamp.getTime()
    }

    //設置有效的日期範圍
    setValidDate(dateObj) {
        const currentDateStamp = this.fmtDate(dateObj.sellDate);//當前傳入日期時間戳
        const startDateStamp = this.fmtDate(this.props.ticketPriceList[0].sellDate);//設訂價格的票券起始日期時間戳
        const endDateStamp = this.fmtDate(this.props.ticketPriceList[this.props.ticketPriceList.length - 1].sellDate)///設訂價格的票券結束日期時間戳
        if (currentDateStamp >= startDateStamp && currentDateStamp <= endDateStamp) {
            if (dateObj.marketAmount && dateObj.dailyStock > 0) {//有效的日期(可點擊)
                dateObj.ban = 1;//可選狀態
                dateObj.color = '#666';
            } else if (dateObj.marketAmount && dateObj.dailyStock <= 0) {//已售罄
                dateObj.bgColor = '#999';
                dateObj.color = '#333';
                dateObj.ban = 3;//已售罄
            }

        }
    }

    //判斷傳入時間跟當日時間大小,返回值爲二者差值
    checkValidDate(date) {
        let localYear = this.state.date.getFullYear()
        let localMonth = this.state.date.getMonth() + 1
        let day = this.state.date.getDate()
        let localDate = this.formatNum(localYear) + '-' + this.formatNum(localMonth) + '-' + this.formatNum(day)//當日完整日期:'xxxx-xx-xx'
        const currentDateStamp = this.fmtDate(date);//當前傳入日期時間戳
        const localDateStamp = this.fmtDate(localDate)//當日時間戳
        return currentDateStamp - localDateStamp


    }

    //初始化日期
    initDate(year, month) {
        return new Promise(resolve => {
            let weekValue = '';
            let totalDay = new Date(year, month + 1, 0).getDate()//獲取當前所選擇月份總天數
            this.setState({
                selectMonth: month + 1,
                dayArr: []
            }, () => {
                //獲取並填充當前所查詢年份對應月份數據
                for (let i = 1; i <= totalDay; i++) {
                    let dayDate = this.formatNum(year) + '-' + this.formatNum(month + 1) + '-' + this.formatNum(i);
                    let obj = {
                        sellDate: dayDate,
                        day: i,
                        marketAmount: '',//零售價
                        storeAmount: '',//門市價
                        dailyStock: '',//庫存
                        bgColor: 'none',
                        color: '#ccc',
                        status: 1, //當月的天
                        ban: 2//1:正常,2:禁用,3:售罄
                    }

                    weekValue = (new Date(year, month, i)).getDay(); //獲取這天對應的是星期幾 0 - 6,0 表示星期天
                    if (i == 1 && weekValue != 0) {
                        this.addBeforeValue(weekValue)//填充日曆開始的空白,
                    }

                    let index = this.props.ticketPriceList.findIndex((item) => {
                        return item.sellDate == dayDate //匹配設置價格的天,並返回其索引
                    })
                    if (index >= 0) {
                        obj.marketAmount = this.props.ticketPriceList[index].marketAmount;//將價格賦值給日曆中匹配到的項
                        obj.dailyStock = this.props.ticketPriceList[index].dailyStock;
                        obj.storeAmount = this.props.ticketPriceList[index].storeAmount;
                    }
                    this.setValidDate(obj) //設置有效的日期範圍
                    let dayArr = this.state.dayArr;
                    dayArr.push(obj)//將當前月的天push進數組
                    this.setState({
                        dayArr
                    }, () => {
                        if (i == totalDay && weekValue != 6) {
                            this.addAfterValue(weekValue) //填充日曆結尾的空白
                        }
                        resolve(true);
                    })
                }
            })
            //判斷當前展現的月份跟本月關係,若是是當月則禁用返回上個月按鈕
            let dayDate = this.formatNum(year) + '-' + this.formatNum(month + 1) + '-' + this.formatNum(1);
            if (this.checkValidDate(dayDate) <= 0) {
                this.setState({
                    prevMonthStatus: false
                })
            } else {
                this.setState({
                    prevMonthStatus: true
                })
            }
        })
    }

    //補充前面空白日期
    addBeforeValue(weekValue) {
        let totalDay = new Date(this.state.year, this.state.month, 0).getDate();
        for (let i = 0; i < weekValue; i++) {
            let obj = {
                sellDate: '',
                day: '',
                marketAmount: '',//零售價
                storeAmount: '',//門市價
                bgColor: 'none',
                color: '#ccc',
                status: 0,
                ban: 2//禁用
            }
            // obj.day = totalDay - (weekValue - i) + 1;
            let dayArr = this.state.dayArr;
            dayArr.push(obj);
            this.setState({
                dayArr
            })
        }
    }

    //補充後空白日期
    addAfterValue(weekValue) {
        let totalDay = new Date(this.state.year, this.state.month, 0).getDate();
        for (let i = 0; i < (6 - weekValue); i++) {
            let obj = {
                sellDate: '',
                day: '',
                marketAmount: '',//零售價
                storeAmount: '',//門市價
                bgColor: 'none',
                color: '#ccc',
                status: 2,
                ban: 2//禁用
            }
            //  obj.day = i + 1;
            let dayArr = this.state.dayArr;
            dayArr.push(obj);
            this.setState({
                dayArr
            })
        }
    }
    //日期時間的格式化
    formatNum(num) {
        return num < 10 ? '0' + num : num + '';
    }

    //調用initDate方法
    async callInitDateFunc(year, month) {
        await this.initDate(year, month);
        this.initInfo();
    }
    //上一個月
    prevMonth() {
        if (!this.state.prevMonthStatus) return
        if (this.state.month == 0) {//1月
            let year = this.state.year - 1;//須要返回到上一年
            this.setState({
                year,
                month: 11//12月
            }, () => {
                this.callInitDateFunc(this.state.year, this.state.month);
            })
        } else {
            let month = this.state.month - 1;
            this.setState({
                month
            }, () => {
                this.callInitDateFunc(this.state.year, this.state.month);
            })
        }

    }
    //下一個月
    nextMonth() {
        if (this.state.month == 11) {//12月
            let year = this.state.year + 1;//到下一年
            this.setState({
                year,
                month: 0//1月
            }, () => {
                this.callInitDateFunc(this.state.year, this.state.month);
            })
        } else {
            let month = this.state.month + 1;
            this.setState({
                month
            }, () => {
                this.callInitDateFunc(this.state.year, this.state.month);
            })
        }

    }
    //輸出當前點擊項(選中的哪天)
    handleClick(obj) {
        //查詢當前選中日期下標
        let idx = this.state.dayArr.findIndex((item) => {
            return item.sellDate == obj.sellDate
        })
        if (this.state.dayArr[idx].status == 0) {//若是點擊日曆開始的填充日期
            this.setState({
                status: 0
            }, () => {
                // this.prevMonth();//自動跳轉上一個月
            })
            return;
        }
        if (this.state.dayArr[idx].status == 2) { //若是點擊日曆結尾的填充日期
            this.setState({
                status: 2
            }, () => {
                //  this.nextMonth();//自動跳轉下一個月
            })
            return;
        }
        if (this.state.dayArr[idx].status == 1 && this.state.dayArr[idx].ban == 1) {//點擊日曆日期,而且爲可點擊狀態
            let dayArr = this.state.dayArr;
            dayArr[idx].bgColor = '#FEB100';//設定選中日期的背景色
            dayArr[idx].color = '#fff';//選中日期的文字顏色

            //將日曆其餘可點擊狀態的日期文字重置爲默認色
            let count = 0;
            for (let i = 0; i < this.state.dayArr.length; i++) {
                if (this.state.dayArr[i].status == 1 && this.state.dayArr[i].ban == 1 && i != idx) {
                    dayArr[i].bgColor = 'none';
                    dayArr[i].color = '#666';
                    count++;
                }
                if (count >= this.props.ticketPriceList.length) break;//說明已經所有重置完成,直接結束循環
            }
            this.setState({
                dayArr
            })
            this.props.handleClick(obj)//返回當前點擊的日期給父組件
        }

    }
    //價格日曆映射表(當前查詢日期->價格日曆 ,返回查詢日期在價格日曆的索引位置)
    findTicketIndex(obj) {
        let findDateObj = obj;
        let index = this.state.dayArr.findIndex((item) => {
            return item.sellDate == findDateObj.sellDate //返回其索引
        })
        return index;

    }
    //初始化商品信息
    initInfo() {
        const findIndex = this.findTicketIndex(this.props.currentSelectTicket);
        if (findIndex !== -1) {
            this.handleClick(this.state.dayArr[findIndex])
        }
    }

    componentDidMount() {
        let year = this.state.date.getFullYear(); //獲取當前所在年份
        let month = this.state.date.getMonth(); //獲取當前所在月份 0-11
        this.setState({
            year,
            month
        }, async () => {
            await this.initDate(this.state.year, this.state.month); //初始化日曆數據 
            this.initInfo() //初始化當前商品價格等展現信息,該方法只在組件初始化的時候執行一次
        })


    }

    render() {
        const { year, selectMonth, dayArr, weeks, prevMonthStatus } = this.state
        return (
            <View className="calendar-selector-container">
                <View className="calendar-header">
                    <View className="header-left" onClick={() => this.prevMonth()}>
                        <Image className='left-arrow' src={prevMonthStatus ? 'http://static.ledouya.com/FkhXIKoqvceD_ieVbVlUWzM4X_PR' : 'http://static.ledouya.com/Fr3ECEFgaTuTbQPkPKLl-PA2eV8m'} />
                        <Text className={['left-text', !prevMonthStatus && 'ban-text']}>上月</Text>
                    </View>
                    <View className="header-center">{year + '年' + selectMonth}月</View>
                    <View className="header-right" onClick={() => this.nextMonth()}>
                        <Text className="right-text">下月</Text>
                        <Image className="right-arrow" src="http://static.ledouya.com/Fl7CcBEZszqiWTpfN0bKSB9NeZdX" />
                    </View>
                </View>
                <View className="calendar-week">
                    {
                        weeks.map((v) => (
                            <View className="list">{v}</View>
                        ))
                    }

                </View>
                <View className="calendar-content">
                    <View className="calendar-day">
                        {dayArr.map((v, i) => (
                            <View className="list" onClick={() => this.handleClick(v)}>
                                <View className={['day', v.ban == 3 && 'sell-out']} style={{ background: v.bgColor, color: v.color }}>{v.ban == 1 || v.ban == 2 ? v.day : '售罄'}</View>
                                {v.ban == 1 && <View className="price">¥{(parseFloat(v.marketAmount) / 100).toFixed(2)}</View>}
                            </View>
                        ))
                        }
                        <View className="local-month">{selectMonth}</View>
                    </View>
                </View>
            </View>
        )
    }
}

複製代碼

以上就是calendar組件實現的核心代碼,雖然功能比較簡單,但實現的過程當中細節邏輯感受仍是挺多的,也是費了些時間去思考細節。所以分享出來,共同進步。若有錯誤,望大佬們多多指正。數組

相關文章
相關標籤/搜索