最新消息:20210816 当前crifan.com域名已被污染,为防止失联,请关注(页面右下角的)公众号

【记录】ReactJS中参考spring-picker自己去实现弹框选择

ReactJS crifan 2602浏览 0评论

之前:

【未解决】Preact中用spring-picker去实现弹框选择

但是

(1)虽然手动解决了css中样式(calc无效)的问题

(2)但是还是无法解决,什么原因导致了第二次进入页面后,无法消失掉背景的modal页面

所以只能放弃。

所以只能自己去尝试。

然后就是拷贝粘贴代码,一点点调试。

期间,解决了:

【已解决】[eslint] Using this.refs is deprecated. (react/no-string-refs)

和:

【已解决】Preact中布局样式style.less中的calc不生效

然后再去:

【已解决】ReactJS中阻止点击事件不让Modal窗口消失

期间遇到了:

【未解决】ReactJS中css中div中span的水平居中问题

【总结】

然后最后用代码:

ucowsapp/src/components/base-modal/index.js

import { h, Component } from ‘preact’;
import ReactCSSTransitionGroup from ‘react-addons-css-transition-group’;
import style from ‘./style.less’;
export default class BaseModal extends Component {
  // state = {
  //   isVisible : false
  // }
  constructor(props) {
    super(props);
    this.modalOverlayOnClick = this.modalOverlayOnClick.bind(this);
    this.modalOnClick = this.modalOnClick.bind(this);
  }
  // componentWillReceiveProps(nextProps) {
  //   this.setState({
  //     isVisible: nextProps.visible
  //   });
  //   console.log(`BaseModal componentWillReceiveProps: nextProps.visible=${nextProps.visible}, this.state.isVisible=${this.state.isVisible}`);
  // }
  componentDidUpdate() {
    // console.log(`BaseModal componentDidUpdate: this.modalOverlay=${this.modalOverlay}`);
    // if (this.modalOverlay && !this.modalOverlay.onclick) {
    //   // 点击阴影背景时cancel() popup
    //   this.modalOverlay.onclick = (e) => {
    //     e.stopPropagation();
    //     this.props.onCancel && this.props.onCancel();
    //   };
    //   // 点击modal阻止默认行为
    //   // 原理:react event listener中无法阻止原生事件,所以用原生事件来替代react事件
    //   this.modal.onclick = (e) => e.stopPropagation();
    //   console.log(`modal onclick stopPropagation`);
    // }
  }
  modalOverlayOnClick(e){
    // e.preventDefault();
    e.stopPropagation();
    e.nativeEvent.stopImmediatePropagation();
    // console.log(`BaseModal modalOverlayOnClick:`);
    // console.log(e);
    // console.log(this.props.onCancel);
    if (this.props.onCancel) {
      this.props.onCancel();
    }
  }
  modalOnClick(e){
    // e.preventDefault();
    e.stopPropagation();
    e.nativeEvent.stopImmediatePropagation();
    // console.log(`BaseModal modalOnClick:`);
    // console.log(e);
  }
        // <div class={style.modal_overlay} ref={(mol) => {this.modalOverlay = mol;}}>
        //   <div class={style.modal} ref={(m) => {this.modal = m;}}>
        // <div class={style.modal_overlay} onClick={this.props.onCancel}>
        //   <div class={style.modal} onClick={this.props.onCancel}>
          // <div class={style.modal} onClick={this.omitModalClick}>
  // omitModalClick(e){
  //   console.log(`omitModalClick: e=${e}`);
  // }
  render () {
    let modal = null;
    // if (this.state.isVisible) {
    if (this.props.visible) {
      modal = (
        <div class={style.modal_overlay} onClick={this.modalOverlayOnClick}>
          <div class={style.modal} onClick={this.modalOnClick}>
            {this.props.children}
          </div>
        </div>
      );
    }
    return (
      <ReactCSSTransitionGroup
        transitionName="modal-transition"
        transitionEnterTimeout={120}
        transitionLeaveTimeout={120}>
        { modal }
      </ReactCSSTransitionGroup>
    );
  }
}

/src/components/base-modal/style.less

.modal_overlay {
  position: fixed;
  top: 0;
  left: 0;
  bottom:0;
  z-index: 1000;
  background-color:rgba(11,11,11,0.4);
  width: 100%;
  .modal {
    position: fixed;
    top: calc(~"100% – 260px");
    left: 0;
    bottom: 0;
    right: 0;
    z-index: 1001;
    background-color: #fff;
    width: 100%;
    height: 260px;
  }
}
// .modal-transition-enter {
//   @include fadeIn;
// }
// .modal-transition-leave {
//   @include fadeOut;
// }
// .modal-transition-enter .modal {
//   @include transitionBottomToUpFn(‘260’,calc(100% – 260px));
// }
// .modal-transition-leave .modal {
//   @include transitionUpToBottomFn(‘260’,calc(100% – 260px));
// }

src/components/picker/index.js

import { h, Component } from ‘preact’;
import _ from ‘lodash’;
import style from ‘./style.less’;
import cx from "classnames";
const getIndex = (list, item) => {
  if (list && list.length < 1) {
    return 0;
  }
  let index1 = _.findIndex(list, item);
  let index2 = list.indexOf(item);
  let index = Math.max(index1, index2);
  if (index < 0) {
    throw new Error(‘list数组中不存在defaultValue’);
  }
  return index;
};
export default class Picker extends Component {
  state = {
    isModalOpen: false,
    dataList: [
      {
        "name" : "",
        "value" : 0
      }
    ]
  };
  constructor(props) {
    super(props);
    this.startY = 0;
    this.endY   = 0;
    //当前拖动的Y坐标
    this.currentY = 0;
    this.itemHeight = 36;
    this.selectedIndex = this.getInitialIndex();
    this.state = {style: {}};
    this._defaultValue = null;
  }
  componentWillReceiveProps(nextProps) {
    const isEqual = _.isEqual(
      nextProps.data.defaultValue,
      this._defaultValue
    );
    if (!isEqual) {
      this._defaultValue = nextProps.data.defaultValue;
      this.selectedIndex = this.getReceivePropsIndex(nextProps.data);
      if (this.selectedIndex === 0) {
        this.state = {
          style: {
            transform: `translate3d(0px, ${this.itemHeight * 2}px, 0px)`
          }
        };
      }
    }
  }
  componentDidMount () {
    this.setSelectedValue(this.selectedIndex);
  }
  handleWrapperStart (e) {
    e.preventDefault();
  }
  // 初始化获得selectedIndex
  getInitialIndex() {
    let index = getIndex(
      this.props.data.list,
      this.props.data.defaultValue
    );
    if (!this.props.data.defaultValue && this.props.data.list.length > 3) {
      index = Math.floor(this.props.data.list.length / 2);
    }
    return index;
  }
  getReceivePropsIndex (data) {
    if (this._defaultValue) {
      this.selectedIndex = getIndex(
        data.list,
        data.defaultValue
      );
    }
    return this.selectedIndex;
  }
  getInitialStyle () {
    this.currentY = 0;
    if (this.selectedIndex > 2) {
      this.currentY = – (this.selectedIndex – 2) * this.itemHeight;
    } else {
      this.currentY = (2 – this.selectedIndex) * this.itemHeight;
    }
    return `translate3d(0px, ${ this.currentY }px, 0px)`;
  }
  handleTouchStart (e) {
    e.preventDefault();
    if (this.props.data.list.length <= 1) {
      return;
    }
    this.startY = e.nativeEvent.changedTouches[0].pageY;
  }
  handleTouchEnd (e) {
    e.preventDefault();
    if (this.props.data.list.length <= 1) {
      return;
    }
    this.endY = e.nativeEvent.changedTouches[0].pageY;
    // 实际滚动距离
    let v = parseInt(this.endY – this.startY);
    let value = v % this.itemHeight;
    // 计算出每次拖动的36px整倍数
    this.currentY += (v – value);
    // 正数y最大值
    const max1 = 2 * this.itemHeight;
    // 负数y最小值
    const max2 = (this.props.data.list.length – 3) * this.itemHeight;
    if (this.currentY > max1) {
      this.currentY = max1;
    }
    else if (this.currentY > 0 && this.currentY < max1) {
      this.currentY = this.currentY;
    }
    else if (this.currentY === max1) {
      this.currentY = this.currentY;
    }
    else if (Math.abs(this.currentY) > max2) {
      this.currentY = – max2;
    }
    this.countListIndex(this.currentY);
    this.setState({
      style: {
        transform: `translate3d(0px, ${ this.currentY }px, 0px)`
      }
    });
  }
  handleTouchMove (e) {
    e.preventDefault();
    if (this.props.data.list.length <= 1) {
      return;
    }
    const pageY = e.nativeEvent.changedTouches[0].pageY;
    let value = parseInt(pageY – this.startY);
    const y = this.currentY + value;
    let style = `translate3d(0px, ${ y }px, 0px)`;
    this.setState({
      style: {
        transform: style
      }
    });
  }
  // 计算list数组索引
  countListIndex (pageY) {
    let n = pageY / this.itemHeight;
    n = n > 0 ? 2 – n : Math.abs(n) + 2;
    this.setSelectedValue(n);
  }
  // set选中值
  setSelectedValue (index) {
    const length = this.props.data.list.length;
    if (length === 0) {
      this.callback(null);
      return;
    }
    if (index < 0 || index > length -1) {
      throw new Error(‘滑动取值索引数值出现错误’+ index);
    }
    const value = this.props.data.list[index];
    this.selectedIndex = index;
    this.callback(value);
  }
  // 回调
  callback (value) {
    this.props.onChange(value);
  }
  getSelectedClass (index) {
    if (this.selectedIndex === index) {
      return ‘ui_picker_item_selected’;
    }
    return ”;
  }
  // showCurrentItem(index, displayValue){
  //   let selectedItemStyles = cx(style.ui_picker_item, style.ui_picker_item_selected);
  //   console.log(selectedItemStyles);
  //   if (this.selectedIndex === index) {
  //     return <div key={index}
  //                 class={selectedItemStyles}>
  //                 {displayValue}
  //               </div>;
  //   }
  //   return <div key={index}
  //               class={style.ui_picker_item}>
  //               {displayValue}
  //             </div>;
  // }
  render () {
    const curStyle = {
      transform: this.getInitialStyle()
    };
    let selectedItemStyles = cx(style.ui_picker_item, style.ui_picker_item_selected);
    return (
      <div class={style.ui_picker_wrapper} onTouchStart={this.handleWrapperStart.bind(this)}>
          <div class={style.ui_picker}
            style = {this.state.style.transform ? this.state.style : curStyle}
            onTouchStart={this.handleTouchStart.bind(this)}
            onTouchMove={this.handleTouchMove.bind(this)}
            onTouchEnd = {this.handleTouchEnd.bind(this)}>
            {
              this.props.data.list.map((data, index) => {
                const displayValue = this.props.data.displayValue(data);
                if (this.selectedIndex === index) {
                  return <div key={index}
                              class={selectedItemStyles}>
                              {displayValue}
                            </div>;
                }
                return <div key={index}
                            class={style.ui_picker_item}>
                            {displayValue}
                          </div>;
              })
            }
          </div>
          <div class={style.ui_picker_center} />
      </div>
    );
  }
}

/src/components/picker/style.less

@itemHeight: 36px;
.ui_picker_wrapper {
  width: 100%;
  height: 180px;
  overflow: hidden;
  position: relative;
  pointer-events: auto;
  margin-top: 18px;
  touch-action: none;
  .ui_picker {
    width: 100%;
    transition: transform;
    transition-duration: .3s;
    transition-timing-function: ease-out;
  }
  .ui_picker_item {
    height: @itemHeight;
    line-height: @itemHeight;
    text-align: center;
    color: #999;
    font-size: 18px;
    white-space:nowrap;
    text-overflow:ellipsis;
    overflow:hidden;
  }
  .ui_picker_item_selected {
    color: #000;
  }
  .ui_picker_center {
    height: @itemHeight;
    box-sizing: border-box;
    position: absolute;
    left: 0;
    width: 100%;
    top: 50%;
    z-index: 100;
    margin-top: -18px;
    pointer-events: none;
    border-top: 1px solid #d7d7d7;
    border-bottom: 1px solid #d7d7d7;
  }
}

/src/components/popup/index.js

import { h, Component } from ‘preact’;
import style from ‘./style.less’;
import BaseModal from ‘../base-modal’;
export default class Popup extends Component {
  state = {
    // modalVisible : false,
    centerTitle : ""
  }
  constructor(props) {
    super(props);
    this.state.centerTitle = this.props.centerTitle;
    this.handleCancel = this.handleCancel.bind(this);
    this.handleConfirm = this.handleConfirm.bind(this);
  }
  // componentWillReceiveProps(nextProps) {
  //   this.setState({
  //     modalVisible: nextProps.visible
  //   });
  //   console.log(`Popup componentWillReceiveProps: nextProps.visible=${nextProps.visible}, this.state.modalVisible=${this.state.modalVisible}`);
  // }
  handleCancel () {
    console.log(`handleCancel: this.props.onCancel=${this.props.onCancel}`);
    if (this.props.onCancel) {
      this.props.onCancel();
    }
    // this.setState({modalVisible : false});
  }
  handleConfirm() {
    console.log(`handleConfirm: this.props.onConfirm=${this.props.onConfirm}`);
    if (this.props.onConfirm) {
      this.props.onConfirm();
    }
  }
  // /**
  //  * 使用原生事件替代react事件
  //  * 当BaseModal modal使用原生事件来阻止冒泡时
  //  * 完成与取消按钮的react onClick会失效,所以使用原生事件而不使用react事件
  //  */
  // componentDidUpdate () {
  //   console.log(`Popup componentDidUpdate: this.confirmButton=${this.confirmButton},this.cancelButton=${this.cancelButton}`);
  //   if (this.confirmButton && !this.confirmButton.onclick) {
  //     this.confirmButton.onclick = (e) => {
  //       e.stopPropagation();
  //       this.handleConfirm();
  //     };
  //     this.cancelButton.onclick =(e) => {
  //       e.stopPropagation();
  //       this.handleCancel();
  //     };
  //   }
  // }
            // <span ref={(cancelBtn) => {this.cancelButton = cancelBtn;}}>{cancelTitle}</span>
            // <span ref={(confirmBtn) => {this.confirmButton = confirmBtn;}}>{confirmTitle}</span>
        // visible={this.state.modalVisible}>
  render () {
    let cancelTitle = ‘取消’;
    let confirmTitle = ‘完成’;
    return (
      <BaseModal
        onCancel={this.handleCancel}
        visible={this.props.visible}>
          <div class={style.ui_popup_title}>
            <span onClick={this.handleCancel}>{cancelTitle}</span>
            <p>{this.state.centerTitle}</p>
            <span onClick={this.handleConfirm}>{confirmTitle}</span>
          </div>
          <div class={style.ui_popup_content}>
            {this.props.children}
          </div>
      </BaseModal>
    );
  }
}

src/components/popup/style.less

.ui_popup_title {
  height: 44px;
  font-size: 17px;
  background: #ccc;
  padding: 0 10px;
  line-height: 44px;
  > span:first-child {
    display: inline-block;
    float: left;
    color: #007aff;
    // width: 60px;
    // text-align: center;
    // position: absolute;
    // left: 0;
    // top: 0;
  }
  > span:last-child {
    display: inline-block;
    float: right;
    color: #007aff;
    // width: 60px;
    // text-align: center;
    // position: absolute;
    // right: 0;
    // top: 0;
  }
  > p {
    margin-left: 80px;
    top: 0px;
    bottom: 0px;
    // left: 60px;
    // right: 60px;
    height: 44px;
    display: inline-block;
    // height: 0.84rem;
    text-align: center;
    // align: center;
    // line-height: 0.84rem;
    // font-size: 0.32rem;
    color: #272727;
    font-weight: bold;
    // position: relative;
  }
}
.ui_popup_content {
  height: 216px;
  width: 100%;
  overflow: hidden;
  -webkit-overflow-scrolling : touch;
}

src/container/estrus-management/index.js

import style from ‘./style.less’;
import Popup from ‘../../components/popup’;
import Picker from ‘../../components/picker’;
export default class EstrusManagement extends Component {
  state = {
    pickerVisible: false,
    selectedValue: {name: ‘确认发情’, value: 2},
  };
  userData =  {
    list: [
      {name: ‘确认发情’, value: 2},
      {name: ‘未发情’, value: 3}
    ],
    defaultValue: this.state.selectedValue,
    displayValue(item) {
      return item.name;
    }
  };
  constructor(props) {
    super(props);
    autoBind(this);
    this.cancelPick = this.cancelPick.bind(this);
    this.confirmPick = this.confirmPick.bind(this);
    this.onPickChange = this.onPickChange.bind(this);
    this.fetchPending();
  }
  confirmPick() {
    console.log("confirmPick");
    this.setState({pickerVisible: false});
    console.log(this.state.selectedValue);
    const url = `/faqing/faqing/updateStatus`;
    console.log(url);
    let postJson = {
      "id": this.state.activeItem.current_id,
      "status": this.state.selectedValue.value
    };
    console.log(postJson);
    alert(JSON.stringify(postJson));
    this.props.fetch(
      url,
      {
        method : "POST",
        headers : {
          ‘Content-Type’: ‘application/json’,
          ‘Accept’: ‘application/json’
        },
        body: JSON.stringify(postJson)
      },
      (data) => {
        console.log(data);
        this.setState({ activeSegment: 1});
        this.fetchDisposed();
      }
    );
  }
  cancelPick() {
    console.log("cancelPick");
    this.userData.defaultValue = {};
    this.setState({
      pickerVisible: false,
      selectedValue: {}
    });
  }
  onPickChange(data) {
    console.log("onPickChange");
    data = data || {};
    this.userData.defaultValue = data;
    this.setState({selectedValue: data});
    console.log(this.state.selectedValue);
  }
  render() {
    const {
      totalPending,
      activeSegment
    } = this.state;
    return (
      。。。
        <div>
          <Popup
            onCancel={this.cancelPick.bind(this)}
            onConfirm={this.confirmPick.bind(this)}
            centerTitle={"请选择处理状态"}
            visible={this.state.pickerVisible}>
            <Picker
              onChange={this.onPickChange}
              data={this.userData}
            />
          </Popup>
        </div>

实现了对应的效果:

注:

现在有个小问题:

对于modal弹出框中间的标题来说,对其不是绝对的居中

只是此处left的距离是写死的。

后期再想办法优化。

转载请注明:在路上 » 【记录】ReactJS中参考spring-picker自己去实现弹框选择

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
89 queries in 0.204 seconds, using 22.16MB memory