react實(shí)現(xiàn)自定義日歷選擇器Calendar

寫(xiě)在前面的話,那天和自己說(shuō)要一個(gè)星期發(fā)布一篇文章脓鹃,不知不覺(jué)就又到了該寫(xiě)文章的日期了逸尖,我卻一直沒(méi)有準(zhǔn)備好要寫(xiě)什么,想到之前項(xiàng)目中自己寫(xiě)的Calendar,總結(jié)還沒(méi)有寫(xiě)娇跟。寫(xiě)這個(gè)項(xiàng)目要感謝"ciqulover"寫(xiě)的文章岩齿,我在寫(xiě)項(xiàng)目的時(shí)候參考了他寫(xiě)的Calendar,地址在這里React.js入門(mén)實(shí)踐:一個(gè)酷酷的日歷選擇器組件,啰嗦完畢苞俘,我們開(kāi)始吧盹沈!

首先看一下效果:


image

我們可以看到效果就是這樣的,它主要實(shí)現(xiàn)了以下的一些功能:

  1. 可以選擇日期
  2. 可以選擇月份
  3. 會(huì)根據(jù)不同的服務(wù)器返回的日期狀態(tài)顯示不同的圖標(biāo)
  4. 超出時(shí)間外不可以切換日期
  5. 有一個(gè)隱藏顯示動(dòng)畫(huà)

實(shí)現(xiàn)這個(gè)日歷選擇器最最重要的是想清楚它的數(shù)據(jù)是怎么來(lái)的吃谣,把這個(gè)想清楚后乞封,我們就知道了應(yīng)該怎么去表現(xiàn)它,因?yàn)槟侵皇撬澈髷?shù)據(jù)的延伸和變現(xiàn)岗憋。

大家都知道一年有12月:

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

一個(gè)月有N多天:

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

一天有N多數(shù)據(jù)肃晚,這些數(shù)據(jù)都是當(dāng)天的一些狀態(tài),比如是否可以點(diǎn)擊仔戈,是否是過(guò)去的日期关串,等等:

/**
 * 日對(duì)象
 * @param day 日
 * @param month 月 已加1
 * @param year 年
 * @param timestamp 時(shí)間戳
 * @param isCurrentMonth 是否是當(dāng)前月,只有當(dāng)前月份的日期可以點(diǎn)擊
 * @param isPost 是否是生日之前的日期
 * @parm flag -1 默認(rèn)值
 * @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};
}

因?yàn)闀?huì)設(shè)計(jì)到非常多的日期當(dāng)天狀態(tài)的判斷监徘,比如是否是過(guò)去的晋修,是否是未來(lái)的,等等凰盔,所以這些狀態(tài)都保存在了日的數(shù)據(jù)里墓卦,我們?cè)谂袛嗟臅r(shí)候直接從這個(gè)日數(shù)據(jù)里判斷就可以了,不用去在外部判斷廊蜒。

下一步是整合所有數(shù)據(jù)弄成年的數(shù)據(jù):

//獲取一年的數(shù)據(jù)
export const displayDaysPerMonth = (year)=>{
  //定義每個(gè)月的天數(shù)趴拧,如果是閏年第二月改為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());

  //獲取每一個(gè)月顯示數(shù)據(jù)中需要補(bǔ)足上個(gè)月的天數(shù)
  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 = [];
    //定義當(dāng)前月的對(duì)象,以保存當(dāng)前月中日的數(shù)據(jù)
    let month_bean = getMonthInstance(monthIndex+1,year);

    //添加上個(gè)月補(bǔ)齊的數(shù)據(jù)
    for (; addDays > 0; addDays--) {
      let day_bean = getDayInstance(daysCountPrevious--,monthIndex,year,false);
      monthData.unshift(day_bean)
    }
    //添加當(dāng)月的數(shù)據(jù)
    for (let i = 1; i <= daysCount;i++) {
      let day_bean = getDayInstance(i,monthIndex+1,year,true);
      monthData.push(day_bean)
    }
    //補(bǔ)足下一個(gè)月
    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天山叮,這其中需要補(bǔ)足上個(gè)月下個(gè)月的天數(shù)著榴,因?yàn)槭巧蟼€(gè)月和下個(gè)月的數(shù)據(jù),所以他們的可點(diǎn)擊是false屁倔。
獲取完年數(shù)據(jù)以后脑又,我們就可以整合數(shù)據(jù)來(lái)顯示界面了,這里我分開(kāi)了兩個(gè)View來(lái)實(shí)現(xiàn)锐借,Calendar是總控问麸,以及顯示上面的切換月份那個(gè)tabCalendarMain則是顯示下面具體的日歷選擇器钞翔。

下面是Calendar的代碼严卖,具體的邏輯我會(huì)在代碼中寫(xiě)注釋。

/**
 * Created by jfy on 2018/1/22.
 * 千里之行布轿,始于足下哮笆!
 * 1.建立數(shù)據(jù)
 * 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',
        }
    }
  //當(dāng)調(diào)用者的state發(fā)生改變時(shí)
  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(),
          });
      }
      //判斷是否有購(gòu)買(mǎi)日期来颤,如果有,則獲取購(gòu)買(mǎi)日期的年和月稠肘,在購(gòu)買(mǎi)日期之前的按鈕是不可以切換的
      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'});
        }
      }

  }
  //初始化獲取當(dāng)前數(shù)據(jù)
  componentDidMount(){
    let data = getCurrentData(this.state.year,this.state.month,this.state.day);
    this.onChangeDateListener(data);
  }

  //獲取日歷狀態(tài)
  getReportDailyStatus(data){
    this.props.getReportDailyStatus(data,this.state.viewData);
  }

  //當(dāng)時(shí)間發(fā)生改變時(shí)
  onChangeDateListener(data){
    this.getReportDailyStatus(data);
    this.props.onChangeDateListener(data,this.state.viewData);

  }
  //切換到下一個(gè)月
  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);
      })
  }
  //切換到上一個(gè)月
  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>
      );
  }
}

我們?cè)谇袚Q日期之后福铅,調(diào)用接口來(lái)進(jìn)行相應(yīng)的改變,或者是重新獲取數(shù)據(jù)來(lái)刷新頁(yè)面项阴。
比如上面的:

 //切換到下一個(gè)月
  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);
      })
  }

切換到上個(gè)月因?yàn)樾枰袛嗍欠窨梢郧袚Q日期滑黔,所以多了很多代碼。

  //切換到上一個(gè)月
  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);
      })
  }

這里最主要的是時(shí)間的改變的跟新:當(dāng)外部設(shè)置時(shí)間時(shí)环揽,我們需要更隨Props來(lái)更新時(shí)間略荡。

 //當(dāng)調(diào)用者的state發(fā)生改變時(shí)
  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的調(diào)用:

<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}
                />

我們給它傳入了兩個(gè)接口,當(dāng)時(shí)間選中和當(dāng)時(shí)間切換的時(shí)候薯演,還有當(dāng)前的年月日撞芍。

我們的日期顯示是先日后六的,所以定義了

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

日歷顯示是橫向的7天跨扮,豎向的6行序无,所以我們的數(shù)據(jù)顯示這里還需要進(jìn)行一次切割。讓它能夠豎向的遍歷六次衡创,橫向的遍歷7次帝嗡。

//獲取當(dāng)前月顯示數(shù)據(jù)
  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>
    );
  }

具體的日期狀態(tài)顯示在this._renderViewCurrentDay(data);

隱藏動(dòng)畫(huà)的實(shí)現(xiàn)需要判斷今天是哪天璃氢,今天是在哪一行哟玷,我們根據(jù)在哪一行來(lái)獲取需要進(jìn)行動(dòng)畫(huà)的位置。

//獲取當(dāng)前日期在第幾行
  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;
         }
       }
    }
  }

我們調(diào)用動(dòng)畫(huà)

  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'

    }
  }
  //當(dāng)調(diào)用者的state發(fā)生改變時(shí)
  componentWillReceiveProps(nextProps) {
    if(nextProps.year||nextProps.month||nextProps.day) {
      this.setState({
        current_day: nextProps.year+''+nextProps.month+''+nextProps.day
      });
    }
  }

  //獲取當(dāng)前月顯示數(shù)據(jù)
  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;
  }

  //獲取當(dāng)前日期在第幾行
  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);
      }
  }
  //星期標(biāo)題
  _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>
          )
        }

      //顯示補(bǔ)
      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>
        )
      //顯示對(duì)勾
      case 5:
      case 8:
        return (
          <div style={{ backgroundImage: `url('${getImg('hava_data')}')`, backgroundSize: 'contain'}} className="calendar_box_hava_data">
          </div>
        )
      //顯示未來(lái)
      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>未填寫(xiě)</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>出報(bào)告日</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
}

補(bǔ)充一份文件,就是上面的scss文件繼承的base_layout.scss椰苟;

//基礎(chǔ)布局文件
@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;
}

//豎向居中對(duì)齊
.base_column_justify_center {
  @extend .base_column;
  @include justify-content(center);
}

//橫向居中對(duì)齊
.base_row_justify_center {
  @extend .base_row;
  @include justify-content(center);
}

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

//橫向垂直居中對(duì)齊
.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);
}

//頁(yè)面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文件是一份顏色文件。

/**
 * 字體和顏色舆蝴,基礎(chǔ)布局的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;

補(bǔ)充答應(yīng)大家的函數(shù):

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

/**
 * 年的對(duì)象
 * @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:[]};
}

/**
 * 日對(duì)象
 * @param day 日
 * @param month 月 已加1
 * @param year 年
 * @param timestamp 時(shí)間戳
 * @param isCurrentMonth 是否是當(dāng)前月洁仗,只有當(dāng)前月份的日期可以點(diǎn)擊
 * @parm flag -1 默認(rèn)值
 * @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;
}

//獲取一年的數(shù)據(jù)
export const displayDaysPerMonth = (year)=>{
  //定義每個(gè)月的天數(shù)层皱,如果是閏年第二月改為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());

  //獲取每一個(gè)月顯示數(shù)據(jù)中需要補(bǔ)足上個(gè)月的天數(shù)
  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 = [];
    //定義當(dāng)前月的對(duì)象,以保存當(dāng)前月中日的數(shù)據(jù)
    let month_bean = getMonthInstance(monthIndex+1,year);

    //添加上個(gè)月補(bǔ)齊的數(shù)據(jù)
    for (; addDays > 0; addDays--) {
      let day_bean = getDayInstance(daysCountPrevious--,monthIndex,year,false);
      monthData.unshift(day_bean)
    }
    //添加當(dāng)月的數(shù)據(jù)
    for (let i = 1; i <= daysCount;i++) {
      let day_bean = getDayInstance(i,monthIndex+1,year,true);
      monthData.push(day_bean)
    }
    //補(bǔ)足下一個(gè)月
    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 下一個(gè)月 false 上一個(gè)月
 */
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);
}
/**
 * 獲取當(dāng)前數(shù)據(jù)
 * @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;
}


//判斷時(shí)間是否是未來(lái)
export const isFutureTime=(timestamp)=>{
  return timestamp>Commons.getTimestamp();
}

寫(xiě)到這里Over...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末赠潦,一起剝皮案震驚了整個(gè)濱河市叫胖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌她奥,老刑警劉巖臭家,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疲陕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡钉赁,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)携茂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)你踩,“玉大人,你說(shuō)我怎么就攤上這事讳苦〈ぃ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵鸳谜,是天一觀的道長(zhǎng)膝藕。 經(jīng)常有香客問(wèn)我,道長(zhǎng)咐扭,這世上最難降的妖魔是什么芭挽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮蝗肪,結(jié)果婚禮上袜爪,老公的妹妹穿的比我還像新娘。我一直安慰自己薛闪,他們只是感情好辛馆,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著豁延,像睡著了一般昙篙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上诱咏,一...
    開(kāi)封第一講書(shū)人閱讀 49,816評(píng)論 1 290
  • 那天苔可,我揣著相機(jī)與錄音,去河邊找鬼胰苏。 笑死硕蛹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的硕并。 我是一名探鬼主播法焰,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼倔毙!你這毒婦竟也來(lái)了埃仪?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤陕赃,失蹤者是張志新(化名)和其女友劉穎卵蛉,沒(méi)想到半個(gè)月后颁股,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡傻丝,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年甘有,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片葡缰。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡亏掀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泛释,到底是詐尸還是另有隱情滤愕,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布怜校,位于F島的核電站间影,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏茄茁。R本人自食惡果不足惜魂贬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胰丁。 院中可真熱鬧随橘,春花似錦、人聲如沸锦庸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)甘萧。三九已至萝嘁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扬卷,已是汗流浹背牙言。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怪得,地道東北人咱枉。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像徒恋,于是被迫代替她去往敵國(guó)和親蚕断。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容