z-memo / interview

我们缺的从来都不是前端/后端工程师,而是工程师(或者那些会系统思考,并总是想着解决问题的人)
27 stars 3 forks source link

实现一个 Dialog 类,Dialog可以创建 dialog 对话框,对话框支持可拖拽 #64

Open MrSeaWave opened 3 years ago

MrSeaWave commented 3 years ago
class Dialog {
  constructor(text) {
    this.text = text;
    this.dialog;
    this.isMoving = false;
    this.x;
    this.y;
    this.lastX = 0;
    this.lastY = 0;
  }

  open() {
    const model = document.createElement('div');
    model.id = 'model';
    model.style = `
    position: absolute;
    top:0;
    left:0;
    bottom:0;
    right:0;
    background: rgba(0,0,0,0.3);
    display: flex;
    justify-content: center;
    align-items: center;
    `;

    model.addEventListener('click', this.close.bind(this));

    document.body.appendChild(model);

    this.dialog = document.createElement('div');
    this.dialog.style = `
    padding: 20px;
    background: white;
    `;
    this.dialog.innerText = this.text;

    this.dialog.addEventListener('click', (e) => e.stopPropagation());
    this.dialog.addEventListener('mousedown', this.handleMouseDown.bind(this));
    model.addEventListener('mousemove', this.handleMouseMove.bind(this));
    model.addEventListener('mouseup', this.handleMouseUp.bind(this));

    model.appendChild(this.dialog);
  }

  close() {
    this.dialog.removeEventListener('mousedown', this.handleMouseDown);
    document. removeEventListener('mousemove', this.handleMouseMove);
    document. removeEventListener('mouseup', this.handleMouseUp);
    document.body.removeChild(document.querySelector('#model'));
  }

  handleMouseDown(e) {
    this.isMoving = true;
    this.x = e.clientX;
    this.y = e.clientY;
  }

  handleMouseMove(e) {
    if (this.isMoving) {
      this.dialog.style.transform = `translate(${
        e.clientX - this.x + this.lastX
      }px,${e.clientY - this.y + this.lastY}px)`;
    }
  }

  handleMouseUp(e) {
    this.lastX = e.clientX - this.x + this.lastX;
    this.lastY = e.clientY - this.y + this.lastY;
    this.isMoving = false;
  }
}

let dialog = new Dialog('Hello World');
dialog.open();
MrSeaWave commented 2 years ago

react版本:https://segmentfault.com/a/1190000019296556

MrSeaWave commented 2 years ago

源码:https://github.com/qq449245884/frank-test-6/blob/master/lib/dialog/dialog.tsx

import React, {Fragment, ReactElement, ReactNode} from 'react'
import ReactDOM from 'react-dom'
import './dialog.scss';
import {Icon} from '../index'
import {scopedClassMaker} from '../helpers/classes'

interface Props {
  visible: boolean,
  buttons?: Array<ReactElement>,
  onClose: React.MouseEventHandler,
  closeOnClickMask?: boolean
}

const scopedClass = scopedClassMaker('fui-dialog')
const sc = scopedClass

const Dialog: React.FunctionComponent<Props> = (props) => {

  const onClickClose: React.MouseEventHandler = (e) => {
    props.onClose(e)
  }
  const onClickMask: React.MouseEventHandler = (e) => {
    if (props.closeOnClickMask) {
      props.onClose(e)
    }
  }
  const result = props.visible ? 
  <Fragment>
      <div className={sc('mask')} onClick={onClickMask}>
      </div>
      <div className={sc('')}>
        <div className={sc('close')} onClick={onClickClose}>
          <Icon name='close'/>
        </div>
        <header className={sc('header')}>提示</header>
        <main className={sc('main')}>
          {props.children}
        </main>
        {
          props.buttons && props.buttons.length &&
          <footer className={sc('footer')}>
            {
              props.buttons && props.buttons.map((button, index) => 
                React.cloneElement(button, {key: index})
              )
            }
          </footer>
        }

      </div>
  </Fragment>
  : 
  null
  return (
    ReactDOM.createPortal(result, document.body)
  )
}

Dialog.defaultProps = {
  closeOnClickMask: false
}

const modal = (content: ReactNode, buttons ?:Array<ReactElement>, afterClose?: () => void) => {
  const close = () => {
// 因为对话框的 visible 是由外部传入的,且 React 是单向数据流的,在组件内并不能直接修改 visible
// 所以在 onClose 方法我们需要再次渲染一个新的组件,并设置新组件 visible 为 false,覆盖原来的组件:
    ReactDOM.render(React.cloneElement(component, {visible: false}), div)
// 从 DOM 中卸载组件
    ReactDOM.unmountComponentAtNode(div)
    div.remove()
    afterClose && afterClose()
  }
  const component = 
  <Dialog visible={true} 
    onClose={() => {
// 直接在页面中点击关闭时,触发
      close(); 
      afterClose && afterClose()
    }}
    buttons={buttons}
  >
    {content}
  </Dialog>
  const div = document.createElement('div')
  document.body.append(div)
  ReactDOM.render(component, div)

// 提供手动关闭的能力,close =  modal(content)
  return close
}

const alert = (content: string) => {
  const button = <button onClick={() => close()}>ok</button>
  const close = modal(content, [button])
}

const confirm = (content: string, yes?: () => void, no?: () => void) => {

  const onYes = () => {
    close()
    yes && yes()
  }
  const onNo = () => {
    close()
    no && no()
  }
  const buttons = [
    <button onClick={onYes}>yes</button>, 
    <button onClick={onNo}>no</button>
  ]
  const close =  modal(content, buttons, no)
}

export {alert, confirm, modal}

export default Dialog