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 = { '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()