react 提示消息队列 (支持动态添加,删除,多实例化)

import React from 'react'
import PropTypes from 'prop-types'

import AnimationOperateFeedbackInfo from '../AnimationOperateFeedbackInfo'
import OperateFeedbackInfo from '../OperateFeedbackInfo'

import './index.less'

const OPERATE_ARRAY_MAX_LENGTH = 5

export default function AssistantOperateFeedbackArea({
  processingOperateList, failedOperateList, onClickCleanFailedOperateBtn, animationEndCallback,
}) {
  const operateWrapStyle = {
    width: '200px',
    height: '28px',
    color: '#fff',
  }

  return (
    <div className="assistant-operate-feedback-area-wrap">
      {
        processingOperateList.length > 0 && (
          <div
            className="operate-feedback-area"
            style={{
              // queueMaxLength + 1 省略区域高度
              height: `${processingOperateList.length > OPERATE_ARRAY_MAX_LENGTH ? (OPERATE_ARRAY_MAX_LENGTH + 1) * 28 : processingOperateList.length * 28}px`,
            }}
          >
            {
              processingOperateList.slice(0, OPERATE_ARRAY_MAX_LENGTH).map((item) => {
                return (
                  <AnimationOperateFeedbackInfo
                    operateId={item.operateId}
                    operate={item.operate}
                    operateType={item.state}
                    animationEndCallback={animationEndCallback}
                    style={operateWrapStyle}
                    key={item.operateId}
                  />
                )
              })
            }
            {
              processingOperateList.length > OPERATE_ARRAY_MAX_LENGTH && (
                <div
                  style={operateWrapStyle}
                  className="ellipsis-operate-info"
                >
                  ... ...
                </div>
              )
            }
          </div>
        )
      }
      {
        failedOperateList.length > 0 && (
          <div
            className="operate-feedback-area"
            style={{
              // queueMaxLength + 1 省略区域高度
              height: `${failedOperateList.length > OPERATE_ARRAY_MAX_LENGTH ? (OPERATE_ARRAY_MAX_LENGTH + 1) * 28 : failedOperateList.length * 28}px`,
            }}
          >
            {
              failedOperateList.slice(0, OPERATE_ARRAY_MAX_LENGTH).map((item) => {
                return (
                  <div className="operate-feedback-info-wrap">
                    <OperateFeedbackInfo
                      operate={item.operate}
                      style={operateWrapStyle}
                      iconRotate={false}
                      iconPath={require('~/shared/assets/image/red-white-warn-icon-60-60.png')}
                    />
                  </div>
                )
              })
            }
            <div
              className="clean-failed-feedback-info-btn"
              onClick={onClickCleanFailedOperateBtn}
              tabIndex={0}
              role="button"
            >
              清除所有异常
            </div>
            {
              failedOperateList.length > OPERATE_ARRAY_MAX_LENGTH && (
                <div
                  style={operateWrapStyle}
                  className="ellipsis-operate-info"
                >
                  ... ...
                </div>
              )
            }
          </div>
        )
      }
      {
        processingOperateList.length === 0 && failedOperateList.length === 0 && (
          <div className="no-feedback-info-tip">
            暂无对教师端操作
          </div>
        )
      }
    </div>
  )
}

AssistantOperateFeedbackArea.propTypes = {
  processingOperateList: PropTypes.array,
  failedOperateList: PropTypes.array,
  onClickCleanFailedOperateBtn: PropTypes.func,
  animationEndCallback: PropTypes.func,
}

AssistantOperateFeedbackArea.defaultProps = {
  processingOperateList: [],
  failedOperateList: [],
  animationEndCallback: () => {},
  onClickCleanFailedOperateBtn: () => {},
}
import React, { useRef, useLayoutEffect } from 'react'
import PropTypes from 'prop-types'
import CX from 'classnames'

import './index.less'

export default function OperateFeedbackInfo({
  operate, iconPath, style, iconRotate, resetAnimation,
}) {
  const imgRef = useRef(null)

  useLayoutEffect(() => {
    if (resetAnimation === true) {
      const imgElem = imgRef.current
      imgElem.className = ''

      // 触发一次重绘 同步所有旋转的icon动画
      imgElem.height = imgElem.offsetHeight

      imgElem.className = 'operate-icon-rotate'
    }
  })

  return (
    <div
      className="operate-feedback-Info"
      style={style}
    >
      <div className="operate-feedback-content">{operate}</div>
      <div className="operate-feedback-state-icon">
        <img
          className={CX({
            'operate-icon-rotate': iconRotate,
          })}
          src={iconPath}
          alt=""
          ref={imgRef}
        />
      </div>
    </div>
  )
}

OperateFeedbackInfo.propTypes = {
  operate: PropTypes.string.isRequired,
  iconPath: PropTypes.string.isRequired,
  iconRotate: PropTypes.bool,
  resetAnimation: PropTypes.bool,
  style: PropTypes.object,
}
OperateFeedbackInfo.defaultProps = {
  style: {},
  resetAnimation: false,
  iconRotate: false,
}
import React from 'react'
import PropTypes from 'prop-types'

import CX from 'classnames'
import OperateFeedbackInfo from '../OperateFeedbackInfo'

import './index.less'

export default function AnimationOperateFeedbackInfo({
  operateId, operate, operateType, animationEndCallback, style,
}) {
  return (
    <div
      className={CX({
        'animation-operate-feedback-info-wrap': true,
        'animation-operate-feedback-processing-state': operateType === 'processing',
        'animation-operate-feedback-success-state': operateType === 'success',
      })}
      onAnimationEnd={() => {
        if (operateType === 'success') {
          animationEndCallback(operateId)
        }
      }}
    >
      <OperateFeedbackInfo
        resetAnimation={operateType !== 'success'}
        operate={operate}
        style={style}
        iconRotate={operateType !== 'success'}
        iconPath={operateType === 'success' ? require('~/shared/assets/image/icon-success-green-white-100-100.png') : require('~/shared/assets/image/processing-icon.svg')}
      />
    </div>
  )
}

AnimationOperateFeedbackInfo.propTypes = {
  operateId: PropTypes.string,
  operate: PropTypes.string,
  operateType: PropTypes.string,
  animationEndCallback: PropTypes.func,
  style: PropTypes.object,
}

AnimationOperateFeedbackInfo.defaultProps = {
  operateId: '',
  operate: '',
  operateType: '',
  animationEndCallback: () => {},
  style: {},
}

 

以上是所有UI部分(包括交互):效果如下:

 

下面是hoc逻辑部分:

import React, { Component } from 'react'
import {
  observable,
  action,
} from 'mobx'
import {
  observer,
} from 'mobx-react'

import uid from 'uuid'

import { AssistantOperateFeedbackArea } from '@dby-h5-clients/pc-1vn-components'
import { Rnd } from 'react-rnd'
import _ from 'lodash'

const operateListClump = observable.object({
  failedOperateList: [],
  processingOperateList: [],
})

class OperateState {
  @action
  constructor(operate = '') {
    this.operateId = uid()
    this.operate = operate
    operateListClump.processingOperateList.push({ operate, operateId: this.operateId, state: 'processing' })
  }

  operateId

  operate

  @action
  success(operate = '') {
    const operateIndex = _.findIndex(operateListClump.processingOperateList, { operateId: this.operateId })
    operateListClump.processingOperateList[operateIndex] = { operate: operate || this.operate, operateId: this.operateId, state: 'success' }
  }

  @action
  failed(operate = '') {
    operateListClump.failedOperateList.push({ operate: operate || this.operate, operateId: this.operateId, state: 'failed' })
    _.remove(operateListClump.processingOperateList, { operateId: this.operateId })
  }
}

@observer
class AssistantOperateList extends Component {
  static addOperate = action((operate) => {
    return new OperateState(operate)
  })

  @action
  removeSuccessOperate = (operateId) => {
    _.remove(operateListClump.processingOperateList, { operateId })
  }

  @action
  handleCleanAllFailedFeedbackInfo = () => {
    operateListClump.failedOperateList = []
  }

  render() {
    return (
      <Rnd
        bounds=".main-space-wrap"
        dragHandleClassName="assistant-operate-feedback-area-wrap"
        lockAspectRatio={16 / 9}
        enableResizing={{
          top: false,
          right: false,
          bottom: false,
          left: false,
          topRight: false,
          bottomRight: false,
          bottomLeft: false,
          topLeft: false,
        }}
        default={{
          x: 30,
          y: 30,
        }}
      >
        <AssistantOperateFeedbackArea
          failedOperateList={operateListClump.failedOperateList.toJSON()}
          processingOperateList={operateListClump.processingOperateList.toJSON()}
          animationEndCallback={this.removeSuccessOperate}
          onClickCleanFailedOperateBtn={this.handleCleanAllFailedFeedbackInfo}
        />
      </Rnd>
    )
  }
}

export default AssistantOperateList

 

 

使用说明:

在其它组件中导入:

import AssistantOperateList from '../AssistantOperateList'
const msg = AssistantOperateList.addOperate('协助开启答题器')
msg.success()
msg.failed()

 

posted @ 2019-09-09 11:29  贝子涵夕  阅读(948)  评论(0编辑  收藏  举报