react实现自定义日历选择器Calendar

写在前面的话,那天和自己说要一个星期发布一篇文章,不知不觉就又到了该写文章的日期了,我却一直没有准备好要写什么,想到之前项目中自己写的Calendar,总结还没有写。写这个项目要感谢"ciqulover"写的文章,我在写项目的时候参考了他写的Calendar,地址在这里React.js入门实践:一个酷酷的日历选择器组件,啰嗦完毕,我们开始吧!

首先看一下效果:


image

我们可以看到效果就是这样的,它主要实现了以下的一些功能:

  1. 可以选择日期
  2. 可以选择月份
  3. 会根据不同的服务器返回的日期状态显示不同的图标
  4. 超出时间外不可以切换日期
  5. 有一个隐藏显示动画

实现这个日历选择器最最重要的是想清楚它的数据是怎么来的,把这个想清楚后,我们就知道了应该怎么去表现它,因为那只是它背后数据的延伸和变现。

大家都知道一年有12月:

/**
 * 年的对象
 * @param year
 * @returns {{year: *, months: Array}}
 */
const getYearInstance = (year)=>{
  return {year, months:[]}
}

一个月有N多天:

/**
 *
 * @param month 月
 * @param year 年
 * @returns {{month: *, days: Array}}
 */
const getMonthInstance = (month,year)=>{
  return { month,year,days:[]};
}

一天有N多数据,这些数据都是当天的一些状态,比如是否可以点击,是否是过去的日期,等等:

/**
 * 日对象
 * @param day 日
 * @param month 月 已加1
 * @param year 年
 * @param timestamp 时间戳
 * @param isCurrentMonth 是否是当前月,只有当前月份的日期可以点击
 * @param isPost 是否是生日之前的日期
 * @parm flag -1 默认值
 * @returns {{day: *, month: *, year: *, week: number, isHaveData: boolean, isOnClick: boolean}}
 */
const getDayInstance = (day,month,year,isCurrentMonth=false,isPost = false)=>{

  month = getMonth(month);
  year = getYear(month,year);
  let timestamp = parseInt(getTimestamp(year+'-'+month+'-'+day));
  let week = new Date(year, month, day).getDay();
  let months= month;
  let days = day;
  let flag = -1;
  let isGetReport = false;
  let _isFutureTime = isFutureTime(timestamp);
  let  isTodays = isToday(year,month,day);
  if(month<10){
    months = '0'+month;
  }
  if(day<10){
    days = '0'+day;
  }
  let All_date = year+'-'+months+'-'+days;
  return {day,month,year,week,timestamp,isCurrentMonth,All_date,isPost,flag,isTodays,isGetReport,_isFutureTime};
}

因为会设计到非常多的日期当天状态的判断,比如是否是过去的,是否是未来的,等等,所以这些状态都保存在了日的数据里,我们在判断的时候直接从这个日数据里判断就可以了,不用去在外部判断。

下一步是整合所有数据弄成年的数据:

//获取一年的数据
export const displayDaysPerMonth = (year)=>{
  //定义每个月的天数,如果是闰年第二月改为29天
  let daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
    daysInMonth[1] = 29
  }

  let daysInPreviousMonth = [].concat(daysInMonth);
  daysInPreviousMonth.unshift(daysInPreviousMonth.pop());

  //获取每一个月显示数据中需要补足上个月的天数
  let addDaysFromPreMonth = new Array(12)
  .fill(null)
  .map((item, index) => {
    let day = new Date(year, index, 1).getDay();
    if (day === 0) {
      return 7
    } else {
      return day
    }
  });

  let year_bean = getYearInstance(year);
  for(let monthIndex = 0; monthIndex < 12;monthIndex++) {
    let addDays = addDaysFromPreMonth[monthIndex],
      daysCount = daysInMonth[monthIndex],
      daysCountPrevious = daysInPreviousMonth[monthIndex],
      monthData = [];
    //定义当前月的对象,以保存当前月中日的数据
    let month_bean = getMonthInstance(monthIndex+1,year);

    //添加上个月补齐的数据
    for (; addDays > 0; addDays--) {
      let day_bean = getDayInstance(daysCountPrevious--,monthIndex,year,false);
      monthData.unshift(day_bean)
    }
    //添加当月的数据
    for (let i = 1; i <= daysCount;i++) {
      let day_bean = getDayInstance(i,monthIndex+1,year,true);
      monthData.push(day_bean)
    }
    //补足下一个月
    for (let i = 42 - monthData.length, j = 0; j < i;) {
      let day_bean = getDayInstance(++j,monthIndex+2,year,false);
      monthData.push(day_bean)
    }

    month_bean.days.push(...monthData);
    year_bean.months.push(month_bean);
  }
  // logger(year_bean);
  return year_bean;
}

我们的日历显示的日期是42天,这其中需要补足上个月下个月的天数,因为是上个月和下个月的数据,所以他们的可点击是false
获取完年数据以后,我们就可以整合数据来显示界面了,这里我分开了两个View来实现,Calendar是总控,以及显示上面的切换月份那个tabCalendarMain则是显示下面具体的日历选择器。

下面是Calendar的代码,具体的逻辑我会在代码中写注释。

/**
 * Created by jfy on 2018/1/22.
 * 千里之行,始于足下!
 * 1.建立数据
 * 2.render
 * 3.提供接口
 */
import React from 'react';


import {getTimestamp, getDate} from '../../../../utils/common';
import './style/Calendar.scss';
import CalendarMain from './CalendarMain';
function getImg(name, path='zgl/calendar') {
  return `${window.global.config.static_file_host}/static/images/zrk_user_web/${path}/${name}.png`;
}
import {displayDaysPerMonth,changeMonthToDate,getCurrentData} from './DayUtils';

export default  class Calendar extends React.Component{
  constructor(props){
        super(props)
        const default_date = getDate(this.props.timestamp);
        this.state = {
          year: default_date.getFullYear(),
          month: default_date.getMonth()+1,
          day: default_date.getDate(),
          viewData:this.props.viewData?this.props.viewData:displayDaysPerMonth(default_date.getFullYear()),
          up_img_url:'up_month',
        }
    }
  //当调用者的state发生改变时
  componentWillReceiveProps(nextProps) {
      if(nextProps.default_date) {
          const default_date = getDate(getTimestamp(nextProps.default_date));
          this.setState({
              year: default_date.getFullYear(),
              month: default_date.getMonth() + 1,
              day: default_date.getDate(),
          });
      }
      //判断是否有购买日期,如果有,则获取购买日期的年和月,在购买日期之前的按钮是不可以切换的
      if(nextProps.purchasing_date){
        let purchasing_date = nextProps.purchasing_date;
        purchasing_date = getDate(getTimestamp(purchasing_date));
        let month = purchasing_date.getMonth()+1;
        let year = purchasing_date.getFullYear();
        if(year === this.state.year && month === (this.state.month)){
          this.setState({up_img_url:'up_month_gray'});
        }else{
          this.setState({up_img_url:'up_month'});
        }
      }

  }
  //初始化获取当前数据
  componentDidMount(){
    let data = getCurrentData(this.state.year,this.state.month,this.state.day);
    this.onChangeDateListener(data);
  }

  //获取日历状态
  getReportDailyStatus(data){
    this.props.getReportDailyStatus(data,this.state.viewData);
  }

  //当时间发生改变时
  onChangeDateListener(data){
    this.getReportDailyStatus(data);
    this.props.onChangeDateListener(data,this.state.viewData);

  }
  //切换到下一个月
  nextMonth(){
      this.setState({up_img_url:'up_month'});
      let data = changeMonthToDate(this.state.year,this.state.month,this.state.day,true);
      let viewData = displayDaysPerMonth(data.year);
      this.setState({
          year: data.year,
          month: data.month,
          day:data.day,
          viewData:viewData
      }, ()=>{
          this.onChangeDateListener(data);
      })
  }
  //切换到上一个月
  prevMonth() {
    let purchasing_date = this.props.purchasing_date;
    purchasing_date = getDate(getTimestamp(purchasing_date));
    let month = purchasing_date.getMonth()+1;
    let year = purchasing_date.getFullYear();
    if(year === this.state.year && (month === (this.state.month-1) || month === (this.state.month))){
      this.setState({up_img_url:'up_month_gray'});
    }else{
      this.setState({up_img_url:'up_month'});
    }
    if(year === this.state.year && month === this.state.month ){
      return ;
    }
    let data = changeMonthToDate(this.state.year,this.state.month,this.state.day,false);
    let viewData = displayDaysPerMonth(data.year);
      this.setState({
          year: data.year,
          month: data.month,
          day:data.day,
          viewData:viewData
      }, ()=>{
          this.onChangeDateListener(data);
      })
  }
  //选择日期
  onDatePickListener(day) {
    this.setState({day})
  }

  render(){
      let props = {
          viewData: this.state.viewData
      };
      return (
            <div className="base_column">
                <div ref = 'Head' className="calendar_title_layout" onClick={this.datePickerToggle.bind(this)}>
                  <img id="prevMonth" className="calendar_img" src={getImg(this.state.up_img_url)} onClick={this.prevMonth.bind(this)} />
                  <div className="calendar_title">{this.state.year +'年'+ (this.state.month<10?'0'+this.state.month:this.state.month) +'月'+ (this.state.day<10?'0'+this.state.day:this.state.day) +'日'}</div>
                  <img id="nextMonth" className="calendar_img" src={getImg('down_month')} onClick={this.nextMonth.bind(this)}/>
                </div>

                <CalendarMain {...props}
                              ref = 'CalendarMain'
                              onDatePickListener = {this.onDatePickListener.bind(this)}
                              onChangeDateListener = {this.onChangeDateListener.bind(this)}
                              year={this.state.year}
                              month={this.state.month}
                              day={this.state.day}
                />

            </div>
      );
  }
}

我们在切换日期之后,调用接口来进行相应的改变,或者是重新获取数据来刷新页面。
比如上面的:

 //切换到下一个月
  nextMonth(){
      this.setState({up_img_url:'up_month'});
      let data = changeMonthToDate(this.state.year,this.state.month,this.state.day,true);
      let viewData = displayDaysPerMonth(data.year);
      this.setState({
          year: data.year,
          month: data.month,
          day:data.day,
          viewData:viewData
      }, ()=>{
          this.onChangeDateListener(data);
      })
  }

切换到上个月因为需要判断是否可以切换日期,所以多了很多代码。

  //切换到上一个月
  prevMonth() {
    let purchasing_date = this.props.purchasing_date;
    purchasing_date = getDate(getTimestamp(purchasing_date));
    let month = purchasing_date.getMonth()+1;
    let year = purchasing_date.getFullYear();
    if(year === this.state.year && (month === (this.state.month-1) || month === (this.state.month))){
      this.setState({up_img_url:'up_month_gray'});
    }else{
      this.setState({up_img_url:'up_month'});
    }
    if(year === this.state.year && month === this.state.month ){
      return ;
    }
    let data = changeMonthToDate(this.state.year,this.state.month,this.state.day,false);
    let viewData = displayDaysPerMonth(data.year);
      this.setState({
          year: data.year,
          month: data.month,
          day:data.day,
          viewData:viewData
      }, ()=>{
          this.onChangeDateListener(data);
      })
  }

这里最主要的是时间的改变的跟新:当外部设置时间时,我们需要更随Props来更新时间。

 //当调用者的state发生改变时
  componentWillReceiveProps(nextProps) {
      if(nextProps.default_date) {
          const default_date = getDate(getTimestamp(nextProps.default_date));
          this.setState({
              year: default_date.getFullYear(),
              month: default_date.getMonth() + 1,
              day: default_date.getDate(),
          });
      }
      if(nextProps.purchasing_date){
        let purchasing_date = nextProps.purchasing_date;
        purchasing_date = getDate(getTimestamp(purchasing_date));
        let month = purchasing_date.getMonth()+1;
        let year = purchasing_date.getFullYear();
        if(year === this.state.year && month === (this.state.month)){
          this.setState({up_img_url:'up_month_gray'});
        }else{
          this.setState({up_img_url:'up_month'});
        }
      }

  }

最后我们看CalendarMain的调用:

<CalendarMain {...props}
                              ref = 'CalendarMain'
                              onDatePickListener = {this.onDatePickListener.bind(this)}
                              onChangeDateListener = {this.onChangeDateListener.bind(this)}
                              year={this.state.year}
                              month={this.state.month}
                              day={this.state.day}
                />

我们给它传入了两个接口,当时间选中和当时间切换的时候,还有当前的年月日。

我们的日期显示是先日后六的,所以定义了

week_names: ['日', '一', '二', '三', '四', '五', '六'],

日历显示是横向的7天,竖向的6行,所以我们的数据显示这里还需要进行一次切割。让它能够竖向的遍历六次,横向的遍历7次。

//获取当前月显示数据
  getCurrentMonth() {
    let view_data = this.props.viewData;
    let current_month_data = view_data.months[this.props.month-1].days;
    let rowsInMonth = [];
    current_month_data.forEach((day, index) => {
      if (index % 7 === 0) {
        rowsInMonth.push(current_month_data.slice(index, index + 7))
      }
    });
    return rowsInMonth;
  }

在代码里我们看到的遍历是这样的。


  _renderMain() {
    let current_month_data = this.getCurrentMonth();
    return (
      <div className="main" ref='main' >
        {
          current_month_data.map((row, rowindex) => {
            return (
              <div key={rowindex} className="calendar_box_row" ref={'row_'+rowindex} style={{height:45}}>
                {
                  row.map((data, index) => {
                    return (
                      <div className={data.isCurrentMonth && !data._isFutureTime ? 'calendar_box_current_month' : 'calendar_box_other_month'}
                           key={data.All_date}
                           onClick={this.onDatePickListener.bind(this, data,'row_'+rowindex)}
                      >
                        {
                          this._renderViewCurrentDay(data)
                        }
                      </div>
                    );
                  })
                }
              </div>
            )
          })
        }

      </div>
    );
  }

具体的日期状态显示在this._renderViewCurrentDay(data);

隐藏动画的实现需要判断今天是哪天,今天是在哪一行,我们根据在哪一行来获取需要进行动画的位置。

//获取当前日期在第几行
  getCurrentRowIndex(){
    let current_month_data = this.getCurrentMonth();
    for(let [index,data] of current_month_data.entries()){
       for(let value of data){
         if(value.day===this.props.day && value.month === this.props.month){
           return index;
         }
       }
    }
  }

我们调用动画

  datePickerToggle() {
    let row_data = ['row_0','row_1','row_2','row_3','row_4','row_5'];
    //确定是在哪一行
    for(let row of row_data){
      if(row!==this.state.current_rowIndex){
        this.refs[row].style.height = this.refs[row].style.height === '45px'? '0px':'45px';
      }
    }
    if(this.state.current_status==='bottom_arrow'){
      this.setState({current_status:'top_arrow'});
    }else{
      this.setState({current_status:'bottom_arrow'});
    }
  }  

CalendarMain的代码:

/**
 * Created by jfy on 2018/1/22.
 * 千里之行,始于足下!
 */
import React from 'react';

import './style/Calendar.scss';
function getImg(name, path='zgl/calendar') {
  return `${window.global.config.static_file_host}/static/images/zrk_user_web/${path}/${name}.png`;
}
export default class CalendarMain extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      current_day:this.props.year+''+this.props.month+''+ this.props.day,
      week_names: ['日', '一', '二', '三', '四', '五', '六'],
      current_rowIndex:'row_'+this.getCurrentRowIndex(),
      current_status:'top_arrow'

    }
  }
  //当调用者的state发生改变时
  componentWillReceiveProps(nextProps) {
    if(nextProps.year||nextProps.month||nextProps.day) {
      this.setState({
        current_day: nextProps.year+''+nextProps.month+''+nextProps.day
      });
    }
  }

  //获取当前月显示数据
  getCurrentMonth() {
    let view_data = this.props.viewData;
    let current_month_data = view_data.months[this.props.month-1].days;
    let rowsInMonth = [];
    current_month_data.forEach((day, index) => {
      if (index % 7 === 0) {
        rowsInMonth.push(current_month_data.slice(index, index + 7))
      }
    });
    return rowsInMonth;
  }

  //获取当前日期在第几行
  getCurrentRowIndex(){
    let current_month_data = this.getCurrentMonth();
    for(let [index,data] of current_month_data.entries()){
       for(let value of data){
         if(value.day===this.props.day && value.month === this.props.month){
           return index;
         }
       }
    }
  }

  //选择日期
  onDatePickListener(data,rowindex,evn) {
    evn.preventDefault();
      let days = data.year+''+data.month+''+data.day;
      if (days !== this.state.current_day && data.isCurrentMonth) {
          this.props.onDatePickListener(data.day);
          if (data.isHaveData) {
              this.props.onChangeDateListener(data);
          }
          this.setState({
              current_day:days,
              current_rowIndex:rowindex
          });
          this.props.onChangeDateListener(data);
      }
  }
  //星期标题
  _renderWeekHeader() {
    return (
      <div className="calendar_header_row" ref="header" style={{height:45}}>
        {
          this.state.week_names.map((name, index) => {
            return (
              <div className="calendar_box" key={index}>
                {name}
              </div>
            )
          })
        }
      </div>
    );
  }

  datePickerToggle() {
    let row_data = ['row_0','row_1','row_2','row_3','row_4','row_5'];
    //确定是在哪一行
    for(let row of row_data){
      if(row!==this.state.current_rowIndex){
        this.refs[row].style.height = this.refs[row].style.height === '45px'? '0px':'45px';
      }
    }
    if(this.state.current_status==='bottom_arrow'){
      this.setState({current_status:'top_arrow'});
    }else{
      this.setState({current_status:'bottom_arrow'});
    }
  }

  componentDidMount(){
    setTimeout(()=>{
      this.datePickerToggle()
    },500)
  }

  //其他日期的效果
  _renderViewItem(data){
    if(data.isGetReport){
      return (
        <div style={{ backgroundImage: `url('${getImg('birthday_icon')}')`, backgroundSize: 'contain'}} className="calendar_box_hava_data">
          <span style={{color:'#fff'}}>{data.day}</span>
        </div>
      )
    }
    switch (data.flag){
      //不可以修改
      case 1:
        if(data.isTodays){
          return (
            <div className="calendar_box_click_red">
              {data.day}
            </div>
          )
        }else{
          return (
            <div style={{ backgroundImage: `url('${getImg('hava_data')}')`, backgroundSize: 'contain'}} className="calendar_box_hava_data">
            </div>
          )
        }
      case 2:
        if(data.isTodays){
          return (
            <div className="calendar_box_click_red">
              {data.day}
            </div>
          )
        }else{
          return (
            <div className="calendar_box_other_gray">
              {data.day}
            </div>
          )
        }

      //显示补
      case 3:
      case 4:
      case 6:
      case 7:
        return (
          <div style={{ backgroundImage: `url('${getImg('no_hava_data')}')`, backgroundSize: 'contain'}} className="calendar_box_hava_data">
          </div>
        )
      //显示对勾
      case 5:
      case 8:
        return (
          <div style={{ backgroundImage: `url('${getImg('hava_data')}')`, backgroundSize: 'contain'}} className="calendar_box_hava_data">
          </div>
        )
      //显示未来
      case 9:
        return (
          <div className="calendar_box_furture">
            {data.day}
          </div>
        )
      default:
        if(data.isTodays){
          return (
            <div className="calendar_box_click_red">
              {data.day}
            </div>
          )
        }else{
          return (
            <div className="calendar_box_other_gray">
              {data.day}
            </div>
          )
        }

    }
  }

  _renderViewCurrentDay(data){
    if(this.state.current_day===(data.year+''+data.month+''+data.day)){
        if(data.isTodays){
          return (
            <div className="calendar_box_click_red">
              {data.day}
            </div>
          )
        }
        switch (data.flag){
          case 3:
          case 4:
          case 6:
          case 7:
            return (
              <div className="calendar_box_click_red">
                {data.day}
              </div>
            )
          default:
            return (
              <div className="calendar_box_click_blue">
                {data.day}
              </div>
            )
        }
    }else{
      return this._renderViewItem(data);
    }
  }

  _renderMain() {
    let current_month_data = this.getCurrentMonth();
    return (
      <div className="main" ref='main' >
        {
          current_month_data.map((row, rowindex) => {
            return (
              <div key={rowindex} className="calendar_box_row" ref={'row_'+rowindex} style={{height:45}}>
                {
                  row.map((data, index) => {
                    return (
                      <div className={data.isCurrentMonth && !data._isFutureTime ? 'calendar_box_current_month' : 'calendar_box_other_month'}
                           key={data.All_date}
                           onClick={this.onDatePickListener.bind(this, data,'row_'+rowindex)}
                      >
                        {
                          this._renderViewCurrentDay(data)
                        }
                      </div>
                    );
                  })
                }
              </div>
            )
          })
        }

      </div>
    );
  }

  render() {
    let view_data = this.props.viewData;
    return (
      <div className="calendar_layout" >
        {this._renderWeekHeader()}
        {this._renderMain()}
        <div className="calendar_status_layout" onClick={this.datePickerToggle.bind(this)}>
            <div className="base_row_align_center">
              <img src={getImg('bu_icon')} className="calendar_box_icon"/>
              <p>未填写</p>
            </div>
            <img className="calendar_status_icon" src={getImg(this.state.current_status)}/>
            <div className="base_row_align_center">
              <div style={{ backgroundImage: `url('${getImg('report_icon')}')`, backgroundSize: 'contain'}} className="calendar_box_icon">
                <span style={{color:'#fff'}}>{view_data.birthday_day}</span>
              </div>
              <p>出报告日</p>
            </div>
        </div>
      </div>
    );
  }
}

最后是Scss的代码:

@import "../../../static/css/commons/base_layout";

.additional_layout{
  @extend .base_column;
}

.additional_layout_row{
  @extend .base_row;
}

.additional_item_layout_v1{
  @extend .base_center;
  @extend %flex;
  background-color: rgba(249,184,91,0.1);
  height: 120px;
  margin: 5px;
  position: relative;
}

.additional_item_layout_v2{
  @extend .additional_item_layout_v1;
  background-color: rgba(236,129,126,0.1);
}

.additional_item_layout_v3{
  @extend .additional_item_layout_v1;
  background-color: rgba(147,208,133,0.1);
}

.additional_item_layout_v4{
  @extend .additional_item_layout_v1;
  background-color: rgba(122,106,241,0.1);
}

.additional_item_layout_v5{
  @extend .additional_item_layout_v1;
  background-color: rgba(255,227,76,0.1);
}

.additional_item_layout_v6{
  @extend .additional_item_layout_v1;
  background-color: rgba(125,158,240,0.1);
}

.additional_icon{
  width: 41px;
  height: 41px;
}

.additional_count_p{
  position: absolute;
  bottom: 5px;
  right: 5px;
}

.additional_icon_last{
  width: 75px;
  height: 75px;
}

.additional_item_last_layout{
  @extend .base_center;
  width: 75px;
  height: 75px;
}
.additional_item_last_layout p{
  font-size: 13px;
  color: #8e8e8e;
}
.additional_future_content{
  @extend .base_column_align_center;
}

.additional_future_icon{
  height: 110px;
  width: 118px;
  margin-top: 25px;
}

.additional_future_content p {
  font-size: 15px;
  color: #5e5e5e;
  margin-top: 20px;
}

.additional_modal_content{
  @extend .base_column_align_center;
  position: relative;
}

.additional_modal_close_img{
  position: absolute;
  width: 15px;
  height: 15px;
  top:0px;
  right: 0px;
}

.additional_modal_icon{
  height: 150px;
  width: 85.5px;
  margin-top: 16px;
}

.additional_modal_p{
  font-size: 15px;
  color: #5e5e5e;
  margin-top: 15px;
}

.button_layout{
  @extend .base_row_justify_space-between;
  padding: 15px 0px;
}

.button {
  @extend .base_center;
  height: 35px;
  background-color: #ffb544;
  border-radius: 50px;
  padding: 5px 15px;
}

.button2 {
  @extend .base_center;
  height: 35px;
  background-color: $buttonColor;
  border-radius: 50px;
  padding: 2px 10px;
  margin-left: 10px;
}

.button p{
  font-size: 11px;
  color: white;
}

.button2 p{
  font-size: 11px;
  color: white;
}

.additional_show_layout {
  width: auto !important;
  height: auto
}

补充一份文件,就是上面的scss文件继承的base_layout.scss

//基础布局文件
@import "base";

%flex{
  flex: 1;
  -webkit-flex: 1;
}

.base {
  display: flex;
  display: -webkit-flex;
}

//横向布局
.base_row {
  @extend .base;
  flex-direction: row; -webkit-flex-direction: row;
}

//竖向布局
.base_column{
  @extend .base;
  flex-direction: column; -webkit-flex-direction: column;
}

@mixin justify-content($layout:center){
  justify-content: $layout;
  -webkit-justify-content: $layout;
}

@mixin align-items($layout:center){
  align-items: $layout;
  -webkit-align-items: $layout;
}

@mixin get_font_size($fontSize:14px){
  font-size: $fontSize;
}

//竖向居中对齐
.base_column_justify_center {
  @extend .base_column;
  @include justify-content(center);
}

//横向居中对齐
.base_row_justify_center {
  @extend .base_row;
  @include justify-content(center);
}

//竖向垂直居中齐
.base_column_align_center {
  @extend .base_column;
  @include align-items(center);
}

//横向垂直居中对齐
.base_row_align_center {
  @extend .base_row;
  @include align-items(center);
}

//居中
.base_center {
  @extend .base;
  @include justify-content(center);
  @include align-items(center);
}

.base_row_justify_space-between{
  @extend .base_row;
  @include justify-content(space-between);
}

.base_row_title_layout{
  @extend .base_row_align_center;
  @include justify-content(space-between);
}

//页面container根布局
.page_container {
  flex: 1;
  -webkit-flex: 1;
  flex-direction: column; -webkit-flex-direction: column;
  display: flex;
  display: -webkit-flex;
  background: $backgroundColor;
  font-size: 15px;
}

.page_context {
  @extend .base_column;
  @extend %flex;
  overflow: scroll;
  overflow-scrolling: touch;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 10px;
  overflow-x: hidden;
}

//如果有需要,自己添加...

base.scss文件是一份颜色文件。

/**
 * 字体和颜色,基础布局的base文件
 */
//字体大小
$remindTitleSize:20px;
$navigationSize:18px;
$contentSize:17px;
$buttonSize:16px;
$tabFontSize:15px;
$minorSize:14px;
$smalltitleSize:13px;
$smallerSize:12px;
$assistSize:11px;

//颜色
$white:#fff;
$assistColor:#c1c1c1;
$minorColor:#8e8e8e;
$contentColor:#5e5e5e;
$remindColor:#ee5765;
$buttonColor:#7d9ef0;
$backgroundColor:#f0f0f0;
$lineColor:#e8e8e8;
$headerTextColor:#5c5c5c;
$titleColor:#333333;
$circleColor:#FFF9c6;

补充答应大家的函数:

/**
 * Created by jfy on 2018/1/23.
 * 千里之行,始于足下!
 */

/**
 * 年的对象
 * @param year
 * @returns {{year: *, months: Array}}
 */
const getYearInstance = (year)=>{
  return {year, months:[]}
}

/**
 *
 * @param month 月
 * @param year 年
 * @returns {{month: *, days: Array}}
 */
const getMonthInstance = (month,year)=>{
  return { month,year,days:[]};
}

/**
 * 日对象
 * @param day 日
 * @param month 月 已加1
 * @param year 年
 * @param timestamp 时间戳
 * @param isCurrentMonth 是否是当前月,只有当前月份的日期可以点击
 * @parm flag -1 默认值
 * @returns {{day: *, month: *, year: *, week: number, isOnClick: boolean}}
 */
const getDayInstance = (day,month,year,isCurrentMonth=false,isPost = false)=>{

  year = getYear(month,year);
  month = getMonth(month);

  let date = new Date();
  let hour = date.getHours() < 10 ? "0" + date.getHours() : date.getHours();
  let minute = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes();
  let second  =date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
  let time = " "+hour+":"+minute+":"+second;
  let current_date = year+'-'+month+'-'+day;
  let current_timestamp = parseInt(Commons.getTimestamp(current_date+time,1));

  let timestamp = parseInt(Commons.getTimestamp(current_date,1));
  let start_timestamp = parseInt(Commons.getTimestamp(current_date+" 00:00:00",1));
  let end_timestamp = parseInt(Commons.getTimestamp(current_date+" 23:59:59",1));

  let week = new Date(year, month, day).getDay();
  let months= month;
  let days = day;
  let flag = -1;
  let _isFutureTime = isFutureTime(timestamp);
  let  isTodays = isToday(year,month,day);
  if(month<10){
    months = '0'+month;
  }
  if(day<10){
    days = '0'+day;
  }
  let All_date = year+'-'+months+'-'+days;
  return {day,month,year,week,current_timestamp,timestamp,start_timestamp,end_timestamp,isCurrentMonth,All_date,isPost,flag,isTodays,_isFutureTime};
}

//获取month
const getMonth = (month) =>{
  if(month==0){
    return 12;
  }else if(month ==13){
    return 1;
  }
  return month;
}
//获取year
const getYear = (month,year)=>{
  if(month==0){
    return --year;
  }else if(month ==13){
    return ++year;
  }
  return year;
}

//获取一年的数据
export const displayDaysPerMonth = (year)=>{
  //定义每个月的天数,如果是闰年第二月改为29天
  let daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
    daysInMonth[1] = 29
  }

  let daysInPreviousMonth = [].concat(daysInMonth);
  daysInPreviousMonth.unshift(daysInPreviousMonth.pop());

  //获取每一个月显示数据中需要补足上个月的天数
  let addDaysFromPreMonth = new Array(12)
  .fill(null)
  .map((item, index) => {
    let day = new Date(year, index, 1).getDay();
    if (day === 0) {
      return 7
    } else {
      return day
    }
  });

  let year_bean = getYearInstance(year);
  for(let monthIndex = 0; monthIndex < 12;monthIndex++) {
    let addDays = addDaysFromPreMonth[monthIndex],
      daysCount = daysInMonth[monthIndex],
      daysCountPrevious = daysInPreviousMonth[monthIndex],
      monthData = [];
    //定义当前月的对象,以保存当前月中日的数据
    let month_bean = getMonthInstance(monthIndex+1,year);

    //添加上个月补齐的数据
    for (; addDays > 0; addDays--) {
      let day_bean = getDayInstance(daysCountPrevious--,monthIndex,year,false);
      monthData.unshift(day_bean)
    }
    //添加当月的数据
    for (let i = 1; i <= daysCount;i++) {
      let day_bean = getDayInstance(i,monthIndex+1,year,true);
      monthData.push(day_bean)
    }
    //补足下一个月
    for (let i = 42 - monthData.length, j = 0; j < i;) {
      let day_bean = getDayInstance(++j,monthIndex+2,year,false);
      monthData.push(day_bean)
    }

    month_bean.days.push(...monthData);
    year_bean.months.push(month_bean);
  }
  return year_bean;
}

/**
 * 切换月份的日期切换,month和year是切换前的
 * @param month
 * @param year
 * @param isNext true 下一个月 false 上一个月
 */
export const changeMonthToDate = (year,month,day,isNext=false)=>{

    if(isNext){
      if (month === 12) {
          month =1;
          year++;
      } else {
          month++;
      }
    }else {
       if(month === 1){
         month = 12;
         year--;
       }else{
         month--;
       }
    }
    day = handleDay(day,month,year);
    return getDayInstance(day,month,year);
}
/**
 * 获取当前数据
 * @param year
 * @param month
 * @param day
 * @returns {{year: *, month: *, day: *, timestamp: Number}}
 */
export const getCurrentData = (year,month,day)=>{
  return getDayInstance(day,month,year);
}

/**
 *
 * @param day
 * @param oldMonth 切换前的月
 * @param newMonth 切换后的月
 * @param year 切换后的年
 * @returns {*}
 */
export const handleDay = (day,newMonth,year)=>{
  let daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
    daysInMonth[1] = 29
  }
  if(daysInMonth.indexOf(day)!==-1){
      day = daysInMonth[--newMonth];
  }
  return day;
}

//是否是今天
export const isToday=(year,month,day)=>{
  let all_date = year+'-'+month+'-'+day;
  let todaysDate = new Date();
  let years = todaysDate.getFullYear();
  let months = todaysDate.getMonth()+1;
  let days = todaysDate.getDate();
  let all_dates = years+'-'+months+'-'+days;
  return all_dates === all_date;
}

//是否是同一天
export const isToday_V2 = (all_date,date)=>{
    let _date = date.toString().replace(/-/g, "/");
     _date = new Date(_date);
    let years = _date.getFullYear();
    let months = _date.getMonth()+1;
    let days = _date.getDate();
    let all_dates = years+''+months+''+days;
    return all_date === all_dates;
}


//判断时间是否是未来
export const isFutureTime=(timestamp)=>{
  return timestamp>Commons.getTimestamp();
}

写到这里Over...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容