react实现窗口悬浮框,可拖拽、折叠、滚动

1、效果如下

2、如下两个文件不需要修改

drag.js

javascript 复制代码
import React from "react";
import PropTypes from "prop-types";

export default class DragM extends React.Component {
  static propTypes = {
    children: PropTypes.element.isRequired
  };
  static defaultProps = {
    //默认是移动children dom,覆盖该方法,可以把tranform行为同步给外部
    updateTransform: (transformStr, tx, ty, tdom) => {
      tdom.style.transform = transformStr;
    }
  };
  position = {
    startX: 0,
    startY: 0,
    dx: 0,
    dy: 0,
    tx: 0,
    ty: 0
  };
  start = event => {
    if (event.button != 0) {
      //只允许左键,右键问题在于不选择conextmenu就不会触发mouseup事件
      return;
    }
    document.addEventListener("mousemove", this.docMove);
    this.position.startX = event.pageX - this.position.dx;
    this.position.startY = event.pageY - this.position.dy;
  };
  docMove = event => {
    const tx = event.pageX - this.position.startX;
    const ty = event.pageY - this.position.startY;
    const transformStr = `translate(${tx}px,${ty}px)`;
    this.props.updateTransform(transformStr, tx, ty, this.tdom);
    this.position.dx = tx;
    this.position.dy = ty;
  };
  docMouseUp = event => {
    document.removeEventListener("mousemove", this.docMove);
  };

  componentDidMount() {
    this.tdom.addEventListener("mousedown", this.start);
    //用document移除对mousemove事件的监听
    document.addEventListener("mouseup", this.docMouseUp);
  }

  componentWillUnmount() {
    this.tdom.removeEventListener("mousedown", this.start);
    document.removeEventListener("mouseup", this.docMouseUp);
    document.removeEventListener("mousemove", this.docMove);
  }

  render() {
    const {children} = this.props;
    const newStyle = {
      ...children.props.style,
      cursor: "move",
      userSelect: "none"
    };
    return React.cloneElement(React.Children.only(children), {
      ref: tdom => {
        return (this.tdom = tdom);
      },
      style: newStyle
    });
  }
}

index.js

javascript 复制代码
import React from "react";
import PropTypes from "prop-types";
import DragM from "./drag";
import {Modal} from "antd";

class BuildTitle extends React.Component {
  updateTransform = transformStr => {
    this.modalDom.style.transform = transformStr;
  };

  componentDidMount() {
    const modalList = document.getElementsByClassName("ant-modal"); //modal的class是ant-modal
    this.modalDom = modalList[modalList.length - 1];
  }

  render() {
    const {title} = this.props;
    return (
        <DragM updateTransform={this.updateTransform}>
          <div>{title}</div>
        </DragM>
    );
  }
}

export default class DragModal extends React.Component {
  static propTypes = {
    drag: PropTypes.bool,
    destroyOnClose: PropTypes.bool
  };

  static defaultProps = {
    drag: true,
    destroyOnClose: true
  };

  render() {
    const {
      drag,
      visible,
      title,
      destroyOnClose,
      children,
      ...restProps
    } = this.props;

    //是否可拖拽
    const _title =
        title && drag ? (
            <BuildTitle visible={visible} title={title}/>
        ) : (
            title
        );

    return (
        <Modal
            visible={visible}
            title={_title}
            destroyOnClose={destroyOnClose}
            {...restProps}
        >
          {children}
        </Modal>
    );
  }
}

3、如下两个文件,自己适当修改接口数据来源即可

index.js

javascript 复制代码
import React from "react";
import styles from "./index.less";
import {Badge, Icon, Tabs} from "antd";
import DragModal from "../DragModal";
import classNames from 'classnames';
import NHFetch from "../../../../utils/NHFetch";

const TabPane = Tabs.TabPane;

export default class ModalContent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: undefined,
      open: true
    };
  }

  componentDidMount() {
    this.getMessage();
  }

  componentDidUpdate(prevProps) {
    // 当props.params变化时获取新数据
    if (this.props.params !== prevProps.params) {
      console.log('----------------------------------------')
      this.getMessage();
    }
  }
  getMessage = () => {
    let xsid = this.props.params
    if (xsid) {
      NHFetch('api/zhxg-yxwz/shxd/getZzbdInfoList', 'get', {xsid: xsid}).then((res) => {
        if (res && res.code === 200) {
          this.setState({data: res.data});
        }
      })

    }
  }
  // 切换状态
  onChangeOpen = () => {
    this.setState({
      open: !this.state.open
    });
  };


  render() {
    const {data, open} = this.state;
    const {studentName} = this.props
    return (
        <DragModal
            className={classNames({
              [styles.modalClese]: !open
            })}
            wrapClassName={styles.dragWrap}
            closable={false}
            width={300}
            title={
              <div>
                {studentName + ":"}自助报道办理情况
                <div
                    className={styles.modalBtn}
                    onClick={this.onChangeOpen}

                >
                  {open ? <Icon type="down"/> : <Icon type="up"/>}
                </div>
              </div>
            }
            mask={false}
            visible={this.props.params}
            footer={false}
        >
          <div className={styles.modalContent}>
            {
                data && data.map((item, i) => {
                  return (
                      <div key={i} className={styles.modalListHeader}>
                        <Badge status={item.BLZT === '1' ? "success" : "processing"}/>
                        {item.HJMC + "  "}
                        <span style={item.BLZT === '1' ? {color: "green"} : {color: 'red'}}>
                        {item.BLZT === '1' ? '已办理' : "未办理"}
                        </span>
                      </div>
                  );
                })
            }
          </div>
        </DragModal>
    );
  }
}

css文件,index.less

css 复制代码
.box {
  background-color: #fff;

  .danger {
    color: #f5222d
  }

  .primary {
    color: #1990ff
  }
}

/* 页签 */
.tabs {
  :global {
    .ant-tabs-bar {
      margin-bottom: 0;
    }

    .ant-tabs-tab {
      margin-right: 0;
    }

    .ant-badge {
      margin-left: 4px;
      margin-top: -4px;
    }

    .ant-badge-count {
      height: 16px;
      line-height: 16px;
    }

    .ant-badge-multiple-words {
      padding: 0 4px;
    }
  }

  .tabsExtra {
    :global {
      .ant-input {
        width: 270px;
      }

      .ant-btn {
        width: 80px;
        margin-left: 10px;
      }

      .ant-select-selection {
        border: none;
        box-shadow: none;
      }
    }
  }
}

/* 菜单 */
.menuLayout {
  &:global(.ant-layout) {
    background: #fff;
  }

  :global {
    .ant-layout-sider {
      box-shadow: 2px 0 4px 0 rgba(217, 217, 217, 0.5);
    }

    .ant-layout-content {
      padding: 10px;
    }
  }
}

.menu {
  padding: 10px 0;

  :global {
    .ant-menu-item {
      width: 100%;
      margin-top: 0;
      margin-bottom: 0 !important;

      &:after {
        right: inherit;
        left: 0;
      }
    }
  }
}

/* 表格 */
.tableTop {
  overflow: hidden;

  .tableTopLeft {
    float: left;

    button {
      margin-right: 10px;
    }
  }

  .tableTopRight {
    float: right;
  }
}

.table {
  margin-top: 10px;

  .audit {
    position: absolute;
    left: -70px;
    top: 50%;
    width: 80px;
    height: 56px;
    margin-top: -28px;
    pointer-events: none
  }

  :global {
    .ant-table-thead {
      position: relative;
      height: 50px;

      &:after {
        content: "";
        display: block;
        position: absolute;
        left: 0;
        right: 0;
        top: 42px;
        background-color: #fff;
        height: 10px;
      }

      tr {
        background: rgba(25, 144, 255, 0.2);

        &:first-child > th:first-child {
          border-top-left-radius: 0;
        }

        &:first-child > th:last-child {
          border-top-right-radius: 0;
        }
      }

      th {
        padding: 10px;
        height: 50px;
        padding-bottom: 20px;
        white-space: nowrap;
      }
    }

    .ant-table-tbody {
      border-left: 1px solid #e8e8e8;
      border-right: 1px solid #e8e8e8;

      tr:first-child td {
        border-top: 1px solid #e8e8e8;
      }

      tr > td {
        position: relative;

        &:after {
          content: "";
          position: absolute;
          right: 0;
          top: 16px;
          bottom: 16px;
          border-right: 1px solid #e8e8e8;
        }

        &:last-child,
        &:first-child {
          &:after {
            display: none;
          }
        }
      }
    }
  }
}

.tablePage {
  overflow: hidden;
  margin-top: 10px;
  padding: 0 10px;

  .tablePageLeft {
    float: left;
    color: #999;
    line-height: 32px;
  }

  .tablePageRight {
    float: right;
  }
}

/* 弹窗 */
.dragWrap {
  overflow: hidden;
  pointer-events: none;

  .modalBtn {
    position: absolute;
    top: 0;
    right: 0;
    height: 50px;
    width: 50px;
    text-align: center;
    line-height: 50px;
    cursor: pointer;
    user-select: none;
  }

  .modalClese {
    :global {
      .ant-modal-body {
        height: 0;
      }
    }
  }

  .modalContent {
    width: 300px;
    padding: 0 20px 10px 20px;
  }

  .modalListHeader {
    padding: 10px;
  }

  .modalListItem {
    position: relative;
    padding: 5px 30px 5px 23px;
    background-color: rgba(25, 144, 255, 0.05);
    border-radius: 8px;
    margin-bottom: 6px;
  }

  .modalItemBadge {
    position: absolute;
    left: 10px;
    top: 5px;
  }

  .itemInfo {
    font-size: 12px;
    color: #999;

    span {
      color: #1990ff;
    }
  }

  .modalItemIcon {
    position: absolute;
    right: 10px;
    top: 50%;
    margin-top: -15px;
    color: #1990ff;
    font-size: 20px;
  }

  :global {
    .ant-modal {
      position: absolute;
      top: auto;
      bottom: 0;
      right: 0;
      margin: 0;
      padding-bottom: 0;
      pointer-events: auto;
    }

    .ant-modal-header {
      padding: 14px 10px;
      background-color: #1990ff;

      .ant-modal-title {
        padding-right: 50px;
        color: #fff;
        font-size: 14px;
      }
    }

    .ant-modal-content {
      overflow: hidden;
    }

    .ant-modal-close {
      color: #fff;
    }

    .ant-modal-close-x {
      height: 50px;
      line-height: 50px;
    }

    .ant-modal-body {
      max-height: 350px;
      padding: 0;
      overflow: hidden;
      overflow-y: scroll;
      margin-right: -20px;
      transition: all 0.3s;
    }
  }
}

4、最后适当位置使用组件即可

相关推荐
一个处女座的程序猿O(∩_∩)O1 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink4 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者6 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-6 小时前
验证码机制
前端·后端
燃先生._.7 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖8 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235248 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240259 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar9 小时前
纯前端实现更新检测
开发语言·前端·javascript