zoukankan      html  css  js  c++  java
  • react 仿 antd 风格的季度选择组件

    产品也真是够了,周选择、月份选择、年份选择都是 antd 直接支持的,然而他现在要求要季度选择和半年份的选择。

    那就来实现一个仿 antd 风格的季度选择组件吧,本文部分参照博客园-真的想不出来-模仿 Antd 写一个季度的时间选择器 V1.0

    我实现了复制可用的版本。

    效果

    功能:

    • 一个纯组件,并且是 ts 版本。
    • 可以切换年份
    • 点选某个季度,执行 props 传入的 onChange 函数,value 参数形如 "2019-Q2"
    • 点击外部收起下拉框
    • 支持 value 传入默认选择项并定位到此。

    调用

    <QuarterPicker value={selectStartMonth} onChange={this.startDataChange} style={{marginRight: 24}}></QuarterPicker>
    
    
    startDataChange(data: any) {
      dispatch.dataQueryDistributorDot.SET({
        selectStartMonth: data
      });
    }
    

    代码

    -- 第二次更新 --

    补充了 ts 的一些类型说明,并且将 componentWillReceiveProps(nextProps, prevState) 替换为 static getDerivedStateFromProps(nextProps, prevState),因为前者即将被 React 废弃。

    import React, { Component } from 'react';
    import moment from 'moment';
    import './index.less';
    
    type IProps = {
      className?: string;
      style?: React.CSSProperties;
      value?: string;
      defaultValue?: string;
      startValue?: string;
      endValue?: string;
      open?: boolean;
      disabled?: boolean;
      onOk?: Function;
      showOk?: boolean;
      onChange?: Function;
    };
    type IState = {
      stateOpen: boolean;
      year: string;
      selectTime: string;
      selectionTime: string;
      oneDisplay: string;
      twoDisplay: string;
    };
    
    const quarterData = [{
      value: 'Q1',
      label: '第一季度'
    }, {
      value: 'Q2',
      label: '第二季度'
    }, {
      value: 'Q3',
      label: '第三季度'
    }, {
      value: 'Q4',
      label: '第四季度'
    }];
    
    const _defaultProps = {
      showOk: false, // 是否使用确定按钮,默认不使用
      disabled: false, // 组件是否禁用,默认组件可以使用
      defaultValue: "请选择时间", // 默认日期 or 没有日期时的提示语
      value: "",
      startValue: "1970-1",
      endValue: `${moment().format("YYYY")}-${moment().quarter()}`,
      open: undefined,
      onOk: () => {},
      className: ""
    }
    
    class QuarterPicker extends Component<IProps, IState> {
      private static defaultProps = _defaultProps; //主要是用 static 关联当前的class Loading
      private toggleContainer: React.RefObject<HTMLDivElement>;
      constructor(props: IProps) {
        super(props)
        this.state = {
          stateOpen: false, // 是否展示弹窗
          year: "", // "2020"
          selectTime: `${moment().format("YYYY")}-${moment().quarter()}`, // 选中的时间, "2020-1", "-1" 代表第一季度
          selectionTime: "", // 点确定后需要返回的时间
          oneDisplay: "block",
          twoDisplay: "block"
        }
        this.toggleContainer = React.createRef()
    
      }
    
      componentDidMount() {
        const { value, open } = this.props;
        let { year, selectTime } = this.state;
        year = value ? value.split("-")[0] : selectTime.split("-")[0]
        this.setState({
          selectTime: value ? value : selectTime,
          selectionTime: value ? value : "",
          year
        })
        this.idBlock(year)
        if (open === undefined) {
          document.addEventListener('mousedown', this.handleClickOutside)
        }
      }
    
      componentWillUnmount() {
        document.removeEventListener('mousedown', this.handleClickOutside)
      }
    
      // componentWillReceiveProps 被废弃,使用 getDerivedStateFromProps 来取代
      static getDerivedStateFromProps(nextProps: IProps, prevState: IState) {
        // 该方法内禁止访问 this
        const { value } = nextProps;
        if (value !== prevState.selectionTime) {
          // 通过对比nextProps和prevState,返回一个用于更新状态的对象
          const year = value && value.split('-')[0];
          return {
            selectTime: value,
            selectionTime: value,
            year
          };
        }
        // 不需要更新状态,返回null
        return null;
      }
    
      onclick = (ev: any) => {
        // ...
        this.setState({
          stateOpen: !this.state.stateOpen,
        })
      }
    
      handleClickOutside = (ev: MouseEvent) => {
        if (!(this && this.toggleContainer && this.toggleContainer.current)) {
          return;
        }
        if (this.state.stateOpen && !this.toggleContainer.current.contains(ev.target as Node)) {
          this.setState({ stateOpen: false });
        }
      };
    
      ulliclick = (index: number) => {
        // ...
      }
    
      iconLeftClick = () => {
        // ...
        const year = parseInt(this.state.year);
        this.setState({
          year: (year - 1).toString()
        })
      }
    
      iconRightClick = () => {
        // ...
        const year = parseInt(this.state.year);
        this.setState({
          year: (year + 1).toString()
        })
      }
    
      idBlock = (year: string) => {
        // ...
      }
    
      okBut = (ev: any) => {
        // ...
      }
    
      textChange = () => {
        // ...
      }
    
      changeQuarter = (item: any) => {
        this.props.onChange && this.props.onChange(`${this.state.year}-${item.value}`);
        this.setState({
          stateOpen: false,
        })
      }
    
      render() {
        const { oneDisplay, twoDisplay, selectTime, year, selectionTime, stateOpen } = this.state;
        const { className, defaultValue, disabled, showOk, open } = this.props;
        let openOnOff = false;
        if (typeof (this.props.open) === "boolean") {
          openOnOff = !!open;
        } else {
          openOnOff = stateOpen;
        }
        return (
          <div
            className={`QuarterlyPicker ${className}`}
            id="QuarterlyPicker"
            style={this.props.style}
            ref={this.toggleContainer}>
            <div className="begin">
              <input className={selectionTime ? "zjl-input" : "zjl-input default_input"}
                value={selectionTime ? selectionTime : defaultValue}
                disabled={disabled}
                onClick={(ev) => { disabled ? null : this.onclick(ev) }}
                onChange={() => { this.textChange() }}
              />
              <i className="img" ></i>
            </div>
            <div className="child" style={{ display: openOnOff ? "block" : "none" }}>
              <header className="zjl-timehear">
                <span>{selectTime}</span>
              </header>
              <div className="con">
                <ul className="content-one">
                  <li className="lefticon" onClick={this.iconLeftClick} style={{ display: oneDisplay }}>{"<<"}</li>
                  <li className="righticon" onClick={this.iconRightClick} style={{ display: twoDisplay }}>{">>"}</li>
                  <li>{year}</li>
                </ul>
              </div>
              <div className="TimerXhlleft">
                <ul className="quaterleft">
                  {
                    quarterData && quarterData.map(item => {
                      return <li
                        key={item.value}
                        className={`quaterleftli ${this.props.value === item.value ? 'active' : ''}`}
                        onClick={this.changeQuarter.bind(this, item)}>
                        {item.label}
                      </li>
                    })
                  }
                </ul>
              </div>
              {
                showOk ?
                  <div className="zjl-but">
                    <span onClick={this.okBut}>确定</span>
                  </div> : null
              }
            </div>
          </div>
        )
      }
    }
    
    export default QuarterPicker;
    

    样式

    :global {
      .QuarterlyPicker{
        height: 100%;
        min-height: 22px;
        min- 90px;
        box-sizing: border-box;
        margin: 0;
        padding: 0;
        color: rgba(0, 0, 0, 0.65);
        font-size: 14px;
        font-variant: tabular-nums;
        line-height: 1.5;
        list-style: none;
        font-feature-settings: 'tnum';
        position: relative;
        display: inline-block;
        outline: none;
        cursor: text;
        transition: opacity 0.3s;
        .begin{
          position: relative;
          height: 100%;
          .zjl-input{
            text-overflow: ellipsis;
            touch-action: manipulation;
            box-sizing: border-box;
            margin: 0;
            padding: 0;
            font-variant: tabular-nums;
            list-style: none;
            font-feature-settings: 'tnum';
            position: relative;
            display: inline-block;
             100%;
            height: 100%;
            padding: 4px 11px;
            color: rgba(0, 0, 0, 0.65);
            font-size: 14px;
            line-height: 1.5;
            background-color: #fff;
            background-image: none;
            border: 1px solid #d9d9d9;
            border-radius: 4px;
            transition: all 0.3s;
            &:hover{
              border-color: #40a9ff;
              border-right- 1px !important;
            }
            &:focus {
              border-color: #40a9ff;
              border-right- 1px !important;
              outline: 0;
              box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
            }
          }
          .zjl-input[disabled] {
            color: rgba(0, 0, 0, 0.25);
            background-color: #f5f5f5;
            cursor: not-allowed;
            opacity: 1;
          }
          .default_input{
            color: rgba(0, 0, 0, 0.25);
          }
          .img{
            display: inline-block;
            position: absolute;
            top: 50%;
            right: 12px;
            height: 14px;
             14px;
            margin-top: -7px;
            // background: url("../../assets/imgs/日历1.png") no-repeat center;
            background-size: 100% 100%;
            color: rgba(0, 0, 0, 0.25);
            font-size: 14px;
            line-height: 1;
            z-index: 1;
            transition: all 0.3s;
            user-select: none;
          }
        }
        .child{
          box-sizing: border-box;
          margin: 0;
          padding: 0;
          color: rgba(0, 0, 0, 0.65);
          font-variant: tabular-nums;
          line-height: 1.5;
          list-style: none;
          font-feature-settings: 'tnum';
          position: absolute;
          z-index: 1050;
           280px;
          font-size: 14px;
          line-height: 1.5;
          text-align: left;
          list-style: none;
          background-color: #fff;
          background-clip: padding-box;
          border: 1px solid #fff;
          border-radius: 4px;
          outline: none;
          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
          .zjl-but {
            position: relative;
            height: auto;
            text-align: right;
            padding: 0 12px;
            line-height: 38px;
            border-top: 1px solid #e8e8e8;
            span{
              position: relative;
              display: inline-block;
              font-weight: 400;
              white-space: nowrap;
              text-align: center;
              background-image: none;
              border: 1px solid transparent;
              box-shadow: 0 2px 0 rgba(0, 0, 0, 0.015);
              cursor: pointer;
              transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
              user-select: none;
              touch-action: manipulation;
              height: 32px;
              padding: 0 15px;
              color: #fff;
              background-color: #1890ff;
              border-color: #1890ff;
              text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12);
              -webkit-box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
              box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
              height: 24px;
              padding: 0 7px;
              font-size: 14px;
              border-radius: 4px;
              line-height: 22px;
              &:hover{
                color: #fff;
                background-color: #40a9ff;
                border-color: #40a9ff;
              }
            }
          }
          .zjl-timehear{
            height: 34px;
            padding: 6px 10px;
            border-bottom: 1px solid #e8e8e8;
            span{
              display: inline-block;
               100%;
              margin: 0;
              cursor: default;
            }
          }
          .TimerXhlleft{
             100%;
            padding: 20px;
            .quaterleft{
              display: flex;
              flex-direction: row;
              flex-wrap: wrap;
              justify-content: space-between;
              padding: 0;
              .quaterleftli{
                 50%;
                text-align: center;
                line-height: 50px;
                height: 50px;
                color: #333;
                padding: 0;
                margin: 0;
                list-style: none;
    
                cursor: pointer;
                &:hover{
                  background: #e6f7ff;
                  cursor: pointer;
                }
                &.active{
                  background: #bae7ff;
                  border-radius: 1px;
                  // color: #fff;
                }
                &.warnnodata{
                  background: #F5f5f5;
                  color: rgba(0, 0, 0, 0.25);
                  cursor: not-allowed;
                }
              }
            }
          }
          .con{
            height: 40px;
            line-height: 40px;
            text-align: center;
            border-bottom: 1px solid #e8e8e8;
            user-select: none;
            .content-one{
              white-space: nowrap;
              overflow: hidden;
              position: relative;
              padding: 0;
              .lefticon{
                position: absolute;
                z-index: 100;
                top: 0;
                left: 0;
                font-size: 18px;
                cursor: pointer;
                 30px;
                margin-left: 20px;
                &:hover{
                  color: #40a9ff;
                }
              }
              .righticon{
                position: absolute;
                z-index: 100;
                top: 0;
                right: 0;
                font-size: 18px;
                cursor: pointer;
                 30px;
                margin-right: 20px;
                &:hover{
                  color: #40a9ff;
                }
              }
              li {
                display: inline-block;
                text-align: center;
                cursor: default;
              }
            }
          }
        }
      }
    
    }
    
    
  • 相关阅读:
    开源中最好的Web开发的资源
    数据结构慕课PTA 05-树9 Huffman Codes
    【读书笔记】C++ primer 5th 从入门到自闭(一)
    【PTA】浙江大学数据结构慕课 课后编程作业 03-树1 树的同构
    nuvoton980 generate yaffs2 format rootfs (九)
    nuvoton980 burn firmware to spi-nand (八)
    nuvoton980 kernel support bridge and nat(七)
    nuvoton980 kernel support tf card(六)
    nuvoton980 kernel support leds-gpio (五)
    nuvoton980 kernel support spi nand boot and rtc (四)
  • 原文地址:https://www.cnblogs.com/everlose/p/12516461.html
Copyright © 2011-2022 走看看