常用前端组件date-picker(一)

496 阅读4分钟

前言

在工作过程中date-picker作为常用的时间选择器经常被用到,观察了公司对date-picker的写法后,发现大多是都是使用Jquery去写的,通过传入一个配置来指定生成Swiper的页面元素ID、swiper的列表,以及默认选中项等等。

之所以这样写可能是由于项目时间较久,前辈们在开发时还不是react、vue的时代,也可能是Jq写的兼容性更好、通用性更强。在使用过程中,经常有一些附加的需求是组件内部没有封装的,因此逐渐有了自己写一个这样组件的想法。

因为自己比较喜欢react的编码方式以及JSX,所以自己写用到的是最新的react18以及最新的swiper8版本。现在的大多数项目所安装的依赖肯定是没有这么新,所以只当是锻炼自己的一个小demo。而且不管框架和版本是什么,其中的内在逻辑掌握即可,在针对不同环境写不同的代码。

dependencies

    "lodash": "^4.17.21",
    "moment": "^2.29.3",
    "react": "^18.1.0",
    "react-dom": "^18.1.0",
    "react-scripts": "5.0.1",
    "sass": "^1.52.1",
    "swiper": "^8.1.5",

需求

工作中针对date-picker常用的需求就体现在对于日期选择范围的把控上,比如在选择某些订单的日期、或者是选择某些人的生日方面,都要求只能选择当前日期之前的日期。也会出现只支持几月前,半年前,一年前的日期(例如查询订单的范围、券商查询交割单)。

虽然校验一般都是通过后端来校验,但是如果前端在选择时就能限定了范围,也是极好的!(在工作中遇到选择生日时还可以选择当天之后的日期,给我的体验是相当不好。)

所以刚开始定义的需求如下:

/**
  * 1. 什么都不传,默认日期为当前日期,以当前日期前后推50年
  * 2. 只传yearLength, 默认日期为当前日期,以当前日期前后推yearLength年
  * 3. 传了date 和 yearLength,默认日期为date,以当前日期前后推yearLength年
  * 4. before 默认日期传了为默认日期,没传为当前日期, 从当前日期往前推
  * 5. after 默认日期传了为默认日期,没传为当前日期, 从当前日期往后推
  * 6. duration  传入一个日期前后多少年   或者是给定前后日期
  */
  
  // 之前多少年或之前多少月
interface Before {
    type: "year" | "month"; // 之前的类型
    value: number; // int 或 0.5的倍数, 0.5表示半月
    initialDate: string; // 'yyyy-mm-dd' 初始日期,一打开swiper的初始日期
}
// 之后多少年或之后多少月
interface After {
    type: "year" | "month"; // 之后的类型
    value: number; // int 或 0.5的倍数, 0.5表示半月
    initialDate: string; // 'yyyy-mm-dd' 初始日期,一打开swiper的初始日期
}
// 期间
interface Duration {
    startTime: string | "now"; // 开始时间  "yyyy-mm-dd"
    endTime: string | "now"; // 结束时间 ”yyyy-mm-dd"
    initialDate: string; // 'yyyy-mm-dd' 初始日期,一打开swiper的初始日期
}

// 组件参数
 interface DateProps {
    date?: string;
    before?: Before;
    after?: After;
    duration?: Duration;
 }

后来在开发before模式过程中,发现无论是哪一种模式本质是都是一个duration的范围,由三个主要的时间来把控,最大时间、最小时间、初始时间。因此即使是不同的模式,在实现起来有些细微差别,但主要逻辑都是相同的,所以在做完before后其余模式就没再开发。

构建页面

页面的主要结构就是中间的Swiper,可以通过弹性盒的方式自己扩展swiper的个数,然后通过原生的方式插入swiper。我这里为了方便是直接写死的三个swiper,本来打算做时、分两个swiper但是发现逻辑大同小异就没做。

JSX

// app.js
import DateEight from './dateEight';
import './App.scss';
import "swiper/scss";
function App() {
  return (
    <DateEight />
  );
}
export default App;

//src/dateEight/index.js
function DateEight(props) {
return (
        <div className="App">
          <div className="date-box" onClick={openPicker}>
            <span className="date-box-content">
              { selectedValue }
            </span>
            <p  className="date-arrow">
              <span className="icon icon-down"></span>
            </p >
          </div>
    
          {
            showPicker ? (
              <section className="date-wrapper">
                <div className="date-wrapper-blank" onClick={ closePicker }></div>
                <div className="date-picker">
                    <div className="date-picker-button">
                        <div onClick={ cancelHandler }>取消</div>
                        <div onClick={ confirmHandler }>确定</div>
                    </div>
                    <div className="date-picker-container">
                        <Swiper
                            direction='vertical'
                            spaceBetween={0}
                            slidesPerView={5}
                            initialSlide={yearIndex}
                            centeredSlides={true}
                            onSwiper={setYearSwiper}
                            onSlideChange={yearSlideChange}
                        >
                            {
                                yearSlide.map(year => {
                                    const { key, value } = year;
                                    return (
                                        <SwiperSlide key={key}>{ value }年</SwiperSlide>
                                    )
                                })
                            }
                        </Swiper>
                        <Swiper
                            direction='vertical'
                            spaceBetween={0}
                            slidesPerView={5}
                            initialSlide={monthIndex}
                            centeredSlides={true}
                            onSwiper={setMonthSwiper}
                            onSlideChange={monthSlideChange}
                        >
                            {
                                monthSlide.map(month => {
                                    const { key, value } = month;
                                    return (
                                        <SwiperSlide key={key}>{ value }月</SwiperSlide>
                                    )
                                })
                            }
                        </Swiper>
                        <Swiper
                            direction='vertical'
                            spaceBetween={0}
                            slidesPerView={5}
                            initialSlide={dayIndex}
                            centeredSlides={true}
                            onSwiper={setDaySwiper}
                            // onSlideChange={() => console.log('month change')}
                        >
                            {
                                daySlide.map(day => {
                                    const { key, value } = day;
                                    return (
                                        <SwiperSlide key={key}>{ value }日</SwiperSlide>
                                    )
                                })
                            }
                        </Swiper>
                        
                        {
                          hourShow ? (
                            <>
                              <div className="swiper-container" id="date-swiper-hour">
                                <div className="swiper-wrapper">
                                  {
                                    hourSlide.map(hour => {
                                        const { key, value } = hour;
                                        return (
                                            <SwiperSlide key={key}>{ value }时</SwiperSlide>
                                        )
                                    })
                                  }
                                </div>
                            </div>
                              <div className="swiper-container" id="date-swiper-minute">
                                <div className="swiper-wrapper">
                                  {
                                    minuteSlide.map(minute => {
                                        const { key, value } = minute;
                                      return (
                                        <SwiperSlide key={key}>{ value }分</SwiperSlide>
                                      )
                                    })
                                  }
                                </div>
                              </div>
                            </>) : null
                        }
    
                    </div>
                    <div className="date-picker-focus">
                        <div></div>
                    </div>
                </div>
            </section>
            ) : null
          }
        </div>
      );
}

app.scss

 .date-box {
    color: #5f606d;
    height: 50px;
    display: flex;
    align-items: center;
    &-content {
      color: #2C2C3C;
    }
    .date-arrow {
      width: 25px;
      height: 25px;
      display: flex;
      justify-content: center;
      align-items: center;
      margin: 0;
      .icon {
        position: static;
        width: 0.75rem;
        height: 0.75rem;
        cursor: pointer;
        &.icon-down {
          content: url('./down.jpg')
        }
      }
    }
  }
  @mixin flexx {
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .date-wrapper {
    width: 100%;
    height: 100%;
    position: fixed;
    top: 0;
    left: 0;
    color: #5f606d;
    z-index: 999;
    .date-picker {
      width: 100%;
      height: 15rem;
      background-color: #e5e8eb;
      position: relative;
      &-button {
        width: 100%;
        height: 2.5rem;
        display: flex;
        justify-content: space-between;
        align-items: center;
        background-color: #f9f9f9;
        border-top: solid 1px #c5c5c5;
        > div {
          width: 4rem;
          height: 100%;
          @include flexx;
          font-size: 1rem;
          color: #00abf3;
        }
      }
      &-container {
        width: 90%;
        height: 12.5rem;
        display: flex;
        margin: 0 auto;
        > div { 
          height: 100%;
          flex: 1;
          overflow: hidden;
          .swiper-slide {
            color: #c3c3c3;
            line-height: 2rem;
            font-size: 1rem;
            @include flexx;
            &-active { 
              color: #202020;
              font-size: 1.5rem;
              hr {
                width: 100%;
                height: 1px;
                border: none;
                border-top: 1px solid #c5c5c5;
                margin-top: 0.5rem !important;
                margin-bottom: 0.5rem !important;
              }
            }
          }
        }
      }
      &-focus {
        width: 100%;
        height: 3rem;
        border-top: 1px solid #c5c5c5;
        border-bottom: 1px solid #c5c5c5;
        position: absolute;
        left: 0;;
        top: 7.25rem;
      }
    }
    &-blank { width: 100%; height: calc(100vh - 15rem);}
  }