Index.js 11.3 KB
import React, { PureComponent } from 'react';
import { 
  StyleSheet, 
  View, 
  Text, 
  Image, 
  TouchableOpacity, 
  TouchableWithoutFeedback, 
  ScrollView 
} from 'react-native';
import _ from 'lodash';
import moment from 'moment';
import solarLunar from 'solarlunar';
import { width, height, zoomW, zoomH } from '../../utils/getSize';

import IMG_CBO1 from '../../assets/chevron-back-outline1.png';
import IMG_CFO1 from '../../assets/chevron-forward-outline1.png';

export default class Calendar extends PureComponent {
  constructor(props) {
    super(props);

    const currentDate = moment().format('YYYY-MM-DD');
    this.state = {
      displayMode: 'month',
      currentDate: currentDate,
    };
  }

  handleLeftActionPress = () => {
    const { displayMode, currentDate } = this.state;
    if (displayMode == 'month') {
      const temp = moment(currentDate).subtract(1, 'months');
      this.setState({ currentDate: temp, });
    } else {
      const temp = moment(currentDate).subtract(1, 'week');
      this.setState({ currentDate: temp, });
    }
  }

  handleRightActionPress = () => {
    const { displayMode, currentDate } = this.state;
    if (displayMode == 'month') {
      const temp = moment(currentDate).add(1, 'months');
      this.setState({ currentDate: temp, });
    } else {
      const temp = moment(currentDate).add(1, 'week');
      this.setState({ currentDate: temp, });
    }
  }

  handleHeaderPress = () => {
    const { displayMode } = this.state;
    if (displayMode == 'month') {
      this.setState({ displayMode: 'week', });
    } else {
      this.setState({ displayMode: 'month', });
    }
  }

  renderHeader = () => {
    return (
      <View style={[styles.calendarHeaderWrap]}>
        <TouchableWithoutFeedback onPress={this.handleLeftActionPress}>
          <View style={[styles.calendarHeaderAction]}>
            {/* <Text style={[styles.actionText]}></Text> */}
            <Image source={IMG_CBO1} style={[styles.actionIcon]} />
          </View>
        </TouchableWithoutFeedback>
        <TouchableWithoutFeedback onPress={this.handleHeaderPress}>
          <View style={[styles.calendarHeaderTitle]}>
            <Text style={[styles.headerTitle]}>{moment(this.state.currentDate).format('YYYY年 MM月')}</Text>
          </View>
        </TouchableWithoutFeedback>
        <TouchableWithoutFeedback onPress={this.handleRightActionPress}>
          <View style={[styles.calendarHeaderAction]}>
            <Image source={IMG_CFO1} style={[styles.actionIcon]} />
          </View>
        </TouchableWithoutFeedback>
    </View>
    );
  }

  getMonthRange = function (startDate, endDate) {
    startDate.subtract(startDate.day(), 'd'); // 第一行的第一列
    endDate.add(6 - endDate.day(), 'd');      // 最后一行的最后一列

    // 计算一共有几周
    const weekLength = Math.ceil(endDate.diff(startDate, 'd') / 7);
    const monthRange = _.map(_.range(weekLength), () => []);

    let index = 0;
    while (endDate.isAfter(startDate)) {
      const length = monthRange[index].push(startDate.format('YYYY-MM-DD'));
      if (!(length % 7)) {
        index++;
      }

      startDate.add(1, 'd');
    }

    return monthRange;
  }

  getWeekRange = function (startDate, endDate) {
    const weekRange = [];
    
    while (endDate.isAfter(startDate)) {
      weekRange.push(startDate.format('YYYY-MM-DD'));
      startDate.add(1, 'd');
    }

    return weekRange;
  }

  getRangeByMode = () => {
    const { currentDate, displayMode } = this.state;
    let startDate, endDate;
    if (displayMode == 'month') {
      startDate = moment(currentDate).startOf('month'); // 当前月第一天
      endDate = moment(currentDate).endOf('month');     // 当前月最后一天
      return this.getMonthRange(startDate, endDate);
    } else {
      startDate = moment(currentDate).startOf('week'); // 当前周第一天
      endDate = moment(currentDate).endOf('week');     // 当前周最后一天
      return this.getWeekRange(startDate, endDate);
    }
  }

  handleDaySelect = (date) => {
    const { onDaySelected } = this.props;

    this.setState({ currentDate: date, });
    if (onDaySelected) {
      onDaySelected(date);
    }
  }

  getLunar = (date) => {
    const temp = moment(date);
    const year = temp.format('YYYY');
    const month = temp.format('MM');
    const day = temp.format('DD');
    const lunarDate = solarLunar.solar2lunar(year, month, day);
    return lunarDate.term || lunarDate.dayCn;
  }

  renderDay = (date) => {
    const { currentDate, displayMode } = this.state;
    const temp = moment(currentDate).isSame(date, 'day'); // 当前选中日期
    if (temp == true) {
      return (
        <TouchableWithoutFeedback key={date} onPress={() => this.handleDaySelect(date)}>
          <View style={[styles.weekItem]}>
            <View style={[styles.dayItem, styles.daySelected2]}>
              <Text style={[styles.dayText, styles.dayTextSelected2]}>
                {moment(date).format('D')}
              </Text>
            </View>
            <Text style={[styles.lunarText]}>{this.getLunar(date)}</Text>
          </View>
        </TouchableWithoutFeedback>
      );
    }

    const { markedDates } = this.props;
    if (markedDates && markedDates.length > 0) {
      for (let i = 0; i < markedDates.length; i++) {
        const item = markedDates[i];
        const temp1 = moment(item);
        const temp2 = temp1.isSame(date, 'year') && temp1.isSame(date, 'month') && temp1.isSame(date, 'day');
        if (temp2 == true) {
          return (
            <TouchableWithoutFeedback key={date} onPress={() => this.handleDaySelect(date)}>
              <View style={[styles.weekItem]}>
                <View style={[styles.dayItem, styles.daySelected]}>
                  <Text style={[styles.dayText, styles.dayTextSelected]}>
                    {moment(date).format('D')}
                  </Text>
                </View>
                <Text style={[styles.lunarText]}>{this.getLunar(date)}</Text>
              </View>
            </TouchableWithoutFeedback>
          );
        }
      }
    }

    const currentMonth = moment(currentDate).month();
    const currentWeek = moment(currentDate).week();
    const tempMonth = moment(date).month();
    const tempWeek = moment(date).week();
    if ((displayMode == 'month' && currentMonth == tempMonth) 
      || (displayMode == 'week' && currentWeek == tempWeek)) {
      return (
        <TouchableWithoutFeedback key={date} onPress={() => this.handleDaySelect(date)}>
          <View style={[styles.weekItem]}>
            <View style={[styles.dayItem]}>
              <Text style={[styles.dayText]}>
                {moment(date).format('D')}
              </Text>
            </View>
            <Text style={[styles.lunarText]}>{this.getLunar(date)}</Text>
          </View>
        </TouchableWithoutFeedback>
      );
    }

    return (
      <TouchableWithoutFeedback key={date} onPress={() => this.handleDaySelect(date)}>
        <View style={[styles.weekItem]}>
          <View style={[styles.dayItem]}>
            <Text style={[styles.dayText, styles.dayText2]}>
              {moment(date).format('D')}
            </Text>
          </View>
          <Text style={[styles.lunarText]}>{this.getLunar(date)}</Text>
        </View>
      </TouchableWithoutFeedback>
    );
  }

  renderRow = (weekRange, weekIndex) => {
    let elements;
    if (weekRange && weekRange.length > 0) {
      elements = weekRange.map((date) => this.renderDay(date));
    }

    return (
      <View key={weekIndex} style={[styles.weekRow]}>
        {elements}
      </View>
    );
  }

  renderRange = () => {
    const { displayMode } = this.state;

    let elements;
    const range = this.getRangeByMode();

    if (displayMode == 'month') {
      if (range && range.length > 0) {
        elements = range.map((weekRange, weekIndex) => this.renderRow(weekRange, weekIndex));
      }
    } else {
      if (range && range.length > 0) {
        const dayItems = range.map((date) => this.renderDay(date));
        elements = (
          <View key={0} style={[styles.weekRow]}>
            {dayItems}
          </View>
        );
      }
    }

    return elements;
  }

  render() {
    const { displayMode } = this.state;
    return (
      <View style={[styles.calendarWrap]}>
        {this.renderHeader()}
        { displayMode == 'month' ?
          <View style={[styles.weekTitleWrap]}>
            <Text style={[styles.weekTitleText]}>周日</Text>
            <Text style={[styles.weekTitleText]}>周一</Text>
            <Text style={[styles.weekTitleText]}>周二</Text>
            <Text style={[styles.weekTitleText]}>周三</Text>
            <Text style={[styles.weekTitleText]}>周四</Text>
            <Text style={[styles.weekTitleText]}>周五</Text>
            <Text style={[styles.weekTitleText]}>周六</Text>
          </View> : 
          <View style={[styles.weekTitleWrap]}>
            <Text style={[styles.weekTitleText]}>周一</Text>
            <Text style={[styles.weekTitleText]}>周二</Text>
            <Text style={[styles.weekTitleText]}>周三</Text>
            <Text style={[styles.weekTitleText]}>周四</Text>
            <Text style={[styles.weekTitleText]}>周五</Text>
            <Text style={[styles.weekTitleText]}>周六</Text>
            <Text style={[styles.weekTitleText]}>周日</Text>
          </View> }
        <View style={[styles.range]}>
          {this.renderRange()}
        </View>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  calendarWrap: {
    display: 'flex',
    flexDirection: 'column',
    paddingTop: 10 / zoomH,
    paddingBottom: 10 / zoomH,
    // borderColor: '#f3f3f3',
    // borderWidth: 1,
  },
  calendarHeaderWrap: {
    display: 'flex',
    flexDirection: 'row',
    height: 40 / zoomH,
    paddingBottom: 4 / zoomH,
    borderBottomColor: '#f3f3f3',
    borderBottomWidth: 1,
  },
  calendarHeaderTitle: {
    display: 'flex',
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    height: '100%',
  },
  headerTitle: {
    color: '#333',
    fontSize: 18,
    fontWeight: '500',
  },
  calendarHeaderAction: {
    justifyContent: 'center',
    alignItems: 'center',
    width: 50 / zoomH,
    height: '100%',
  },
  actionText: {
    color: '#666',
    fontSize: 15,
  },
  actionIcon: {
    width: 22 / zoomW,
    height: 22 / zoomH,
  },
  weekTitleWrap: {
    display: 'flex',
    flexDirection: 'row',
    height: 36 / zoomH,
  },
  weekTitleText: {
    flex: 1,
    color: '#666',
    fontSize: 12,
    fontWeight: '500',
    textAlign: 'center',
    textAlignVertical: 'center',
  },
  weekWrap: {
    display: 'flex',
    flexDirection: 'column',
  },
  weekRow: {
    display: 'flex',
    flexDirection: 'row',
    height: 52 / zoomH,
  },
  weekItem: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    height: '100%',
  },
  dayItem: {
    justifyContent: 'center',
    alignItems: 'center',
    width: 30,
    height: 30,
  },
  daySelected: {
    borderWidth: 1,
    borderColor: '#e02021',
    borderRadius: 15,
  },
  daySelected2: {
    backgroundColor: '#404040',
    borderRadius: 15,
  },
  dayText: {
    color: '#666',
    fontSize: 15,
  },
  dayText2: {
    color: '#b3b3b3',
    fontSize: 14,
  },
  dayTextSelected: {
    color: '#e02021',
    // fontWeight: '500',
  },
  dayTextSelected2: {
    color: '#ffffff',
    fontWeight: '500',
  },
  lunarText: {
    marginTop: 2 / zoomH,
    color: '#666',
    fontSize: 11,
  },
});