import React , { useState , useEffect , useRef , useCallback } from 'react' ; import { AppHead , AppFooter } from 'components' ; import { useNavigate } from 'react-router-dom' ; import { Button , Row , Divider , Breadcrumb , Form , Radio , RadioGroup , Input , Toggle , IconButton , Message , toaster , Modal , } from 'rsuite' ; import TrashIcon from '@rsuite/icons/Trash' ; import ExpandOutlineIcon from '@rsuite/icons/ExpandOutline' ; import { axios } from 'api' ; const getSerialStr = ( i ) => { return String . fromCharCode ( 65 + i ) ; } ; const OptionsItem = ( props ) => { const { data , index , onValueChange = ( id , item ) => { } , onDeleteValue = ( id , item ) => { } , } = props ; const [ inputCotent , setinputCotent ] = useState ( data ?. content ) ; return data ? ( < div style = { { display : 'flex' , alignItems : 'center' , marginTop : 25 } } > < IconButton icon = { < TrashIcon />} onClick={() => onDeleteValue(index, data)}/ > < p style = { { marginLeft : 15 } } > { getSerialStr ( index ) } . < /p> < Input style = { { flex : 1 , margin : '0 10px' } } defaultValue = { inputCotent } onBlur = { ( ) => { onValueChange ( index , { ... data , content : inputCotent , } ) ; } } onChange = { ( value ) => setinputCotent ( value ) } /> < Toggle checked = { data ?. isanswer } checkedChildren = "正确答案" unCheckedChildren = "错误答案" onChange = { ( value ) => { onValueChange ( index , { ... data , isanswer : value , } ) ; } } /> < /div>) : null; } ; interface OptionViewProps { type : number ; onChange ? : ( value : string ) => void ; } const OptionView = ( props ) => { const { type , onChange = ( value ) => { } } = props ; const [ callBackData , setcallBackData ] = useState ( '' ) ; const [ radioValue , setradioValue ] = useState < any > ( 1 ) ; const [ optionsValue , setoptionsValue ] = useState < Array < any > > ( [ { name : 'A' , content : '' , isanswer : false } , { name : 'B' , content : '' , isanswer : false } , { name : 'C' , content : '' , isanswer : false } , { name : 'D' , content : '' , isanswer : false } , ] ) ; const getJudgmentData = useCallback ( ( value ) => { const data = { answer : value === 1 ? true : false , } ; return JSON . stringify ( data ) ; } , [ ] ) ; const getSelectData = useCallback ( ( data ) => { const newData = [ ] ; data . map ( ( item , index ) => { if ( ! item || ! item ?. content ) { return null ; } newData . push ( item ) ; return item ; } ) ; return JSON . stringify ( newData ) ; } , [ ] ) ; useEffect ( ( ) => { if ( optionsValue ) { const data = getSelectData ( optionsValue ) ; setcallBackData ( data ) ; } } , [ optionsValue , getSelectData ] ) ; useEffect ( ( ) => { if ( callBackData ) { onChange ( callBackData ) ; } } , [ callBackData , onChange ] ) ; useEffect ( ( ) => { if ( type == null || ( ! radioValue && ! optionsValue ) ) { return ; } if ( type === 3 ) { setcallBackData ( getJudgmentData ( radioValue ) ) ; } if ( type === 0 || type === 1 ) { setcallBackData ( getSelectData ( optionsValue ) ) ; } } , [ type , setcallBackData , radioValue , optionsValue , getJudgmentData , getSelectData ] ) ; if ( type === 3 ) { return ( < Form . Group > < RadioGroup onChange = { ( value ) => { setradioValue ( value ) ; const data = getJudgmentData ( value ) ; setcallBackData ( data ) ; } } inline value = { radioValue } > < Radio value = { 1 } > 正确 < /Radio> < Radio value = { 0 } > 错误 < /Radio> < /RadioGroup> < /Form.Group>); } return ( < div > { optionsValue ?. map ( ( item , index ) => item ? ( < OptionsItem key = { JSON . stringify ( item ) + '_' + index } data = { item } index = { index } onValueChange = { ( id , data ) => { optionsValue [ id ] = { ... optionsValue [ id ] , ... data , } ; setoptionsValue ( [ ... optionsValue ] ) ; } } onDeleteValue = { ( id , item ) => { let data = optionsValue ; const start = data . slice ( 0 , id ) ; const end = data . slice ( id + 1 , data . length ) ; data = [ ... start , ... end ] ; data = data . map ( ( item , index ) => { return { ... item , name : getSerialStr ( index ) , } ; } ) ; setoptionsValue ( [ ... data ] ) ; } } />) : null)} < Button style = { { width : '100%' , marginTop : 25 } } onClick = { ( ) => { setoptionsValue ( [ ... optionsValue , { name : getSerialStr ( optionsValue ?. length ) , content : '' , isanswer : false } , ] ) ; } } > < ExpandOutlineIcon style = { { marginRight : 10 } } /> 增加选项 < /Button> < /div>); } ; export default function QuestionCreate ( ) { const navigate = useNavigate ( ) ; const [ createConfig , setcreateConfig ] = useState < any > ( { content : '' , type : 0 , netLoading : false , } ) ; const refOptionsData = useRef < any > ( '' ) ; const __APIcreateQuestion = ( type , content , options ) => { if ( createConfig ?. netLoading ) { return ; } if ( ! content || ! options ) { toaster . push ( < Message > 题目内容或选项数据不完整 。 < /Message>); return ; } setcreateConfig ( { ... createConfig , netLoading : true , } ) ; const msgTitle = '提交题目' ; const params = { type , content , options , } ; axios . post ( '/api/question/add' , params , { } ) . then ( ( res ) => { setcreateConfig ( { ... createConfig , netLoading : false , } ) ; const { data } = res ; if ( data ?. code !== 200 || ! data ?. data ) { toaster . push ( < Message > { data ?. msg || msgTitle + '失败，请稍后重试！' } < /Message>); } else { toaster . push ( < Message > { msgTitle + '成功。' } < /Message>); setcreateConfig ( { content : '' , type : createConfig ?. type , netLoading : false , } ) ; __emptyCreateConfig ( ) ; } } ) . catch ( ( error ) => { setcreateConfig ( { ... createConfig , netLoading : false , } ) ; toaster . push ( < Message > { error + '' || msgTitle + '失败，请稍后重试！' } < /Message>); } ) ; } ; description param { * } return { * } const __emptyCreateConfig = ( ) => { const typeOld = createConfig ?. type ; setcreateConfig ( { type : null , } ) ; refOptionsData . current = null ; setTimeout ( ( ) => { setcreateConfig ( { content : '' , type : typeOld , netLoading : false , } ) ; } , 100 ) ; } ; const [ emptyModalConfig , setemptyModalConfig ] = useState ( { showModal : false , } ) ; description param { number } type param { string } options return return { null | string } const __checkOptionsCompliance = ( type , options ) => { if ( ! options ) { return '选项数据为空' ; } if ( type === 3 ) { return null ; } try { const optionsObj = JSON . parse ( options ) ; if ( typeof optionsObj !== 'object' ) { return '选项数据异常' ; } let warningStr = null ; let existCorrectOption = false ; optionsObj . map ( ( item , index ) => { if ( item ?. isanswer ) { existCorrectOption = true ; } return item ; } ) ; if ( optionsObj ?. length < 2 ) { return '选项不得少于两个' ; } if ( ! existCorrectOption ) { return '必须有至少一个正确选项' ; } if ( warningStr ) { return warningStr ; } return null ; } catch ( error ) { return '选项数据可能不是 JSON 格式' ; } } ; return ( < div className = "container" > < AppHead /> < div className = "context control" > < div className = "layout content" > < div className = "content_app" > < div > < Row style = { { display : 'flex' , padding : 10 } } > < h4 style = { { fontWeight : 300 } } > 添加题目 < /h4> < div style = { { flex : 1 } } /> < Button style = { { marginRight : 10 } } disabled = { refOptionsData . current && createConfig ?. content ? false : true } onClick = { ( ) => { setemptyModalConfig ( { showModal : true , } ) ; } } > 清空数据 < /Button> < Button appearance = "primary" disabled = { refOptionsData . current && createConfig ?. content ? false : true } onClick = { ( ) => { const warningMsg = __checkOptionsCompliance ( createConfig ?. type , refOptionsData . current ) ; if ( warningMsg ) { toaster . push ( < Message > { warningMsg } < /Message>); return ; } __APIcreateQuestion ( createConfig ?. type , createConfig ?. content , refOptionsData . current ) ; } } > 确认提交 < /Button> < /Row> < Divider style = { { margin : 0 } } /> < /div> < div style = { { padding : '10px 0' } } > < Breadcrumb > < Breadcrumb . Item onClick = { ( ) => { navigate ( '/control/' , { replace : true , } ) ; } } > 控制中心 < /Breadcrumb.Item> < Breadcrumb . Item active > 添加题目 < /Breadcrumb.Item> < /Breadcrumb> < /div> < div style = { { flex : 1 , width : '100%' , margin : '0 auto' , paddingTop : 30 , maxWidth : 700 } } > < Form fluid onChange = { ( data ) => { setcreateConfig ( { ... setcreateConfig , ... data , } ) ; } } formValue = { createConfig } > < Form . Group > < Form . ControlLabel > < b > 题目类型 < /b> < /Form.ControlLabel> < Form . Control name = "type" accepter = { RadioGroup } inline > < Radio value = { 3 } > 判断题 < /Radio> < Radio value = { 0 } > 选择题 < /Radio> < Radio value = { 1 } > 多选题 < /Radio> < /Form.Control> < /Form.Group> < Form . Group > < Form . ControlLabel > < b > 题目内容 < /b> < /Form.ControlLabel> < Form . Control name = "content" type = "content" style = { { width : '100%' } } /> < /Form.Group> < Form . Group > < Form . ControlLabel > < b > 题目选项 < /b> < /Form.ControlLabel> { createConfig ?. type !== null ? ( < div > < OptionView type = { createConfig ?. type } onChange = { ( value ) => { refOptionsData . current = value ; } } /> < /div>) : null} < /Form.Group> < /Form> < /div> < /div> < Modal backdrop = "static" role = "alertdialog" open = { emptyModalConfig . showModal } onClose = { ( ) => setemptyModalConfig ( { showModal : false , } ) } size = "xs" > < Modal . Body > 你确定要清空输入的题目数据吗 ？ 清空之后需要重新填写全部内容 ， 不可恢复 。 < /Modal.Body> < Modal . Footer > < Button onClick = { ( ) => setemptyModalConfig ( { showModal : false , } ) } appearance = "subtle" > 取消 < /Button> < Button onClick = { ( ) => { __emptyCreateConfig ( ) ; setemptyModalConfig ( { showModal : false , } ) ; } } appearance = "primary" > 确定 < /Button> < /Modal.Footer> < /Modal> < /div> < /div> < AppFooter / > < /div>); }