import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import { Carousel, Tooltip } from 'antd'; import { Link } from 'react-router-dom'; import Fullscreen from 'react-fullscreen-crossbrowser'; import './index.less'; import { formatSeconds, formatPercent, formatDate } from '@src/services/Tools'; import Assets from '@src/components/Assets'; import Navigation from '../../../../components/Navigation'; import Tabs from '../../../../components/Tabs'; import Icon from '../../../../components/Icon'; import Switch from '../../../../components/Switch'; import Select from '../../../../components/Select'; import { Button } from '../../../../components/Button'; import AnswerSelect from '../../../../components/AnswerSelect'; import AnswerList from '../../../../components/AnswerList'; import AnswerButton from '../../../../components/AnswerButton'; import AnswerTable from '../../../../components/AnswerTable'; import OtherAnswer from '../../../../components/OtherAnswer'; import { Textarea } from '../../../../components/Input'; import { QuestionNoteModal } from '../../../../components/OtherModal'; import { AskTarget } from '../../../../../Constant'; import { Question } from '../../../../stores/question'; import { My } from '../../../../stores/my'; import { User } from '../../../../stores/user'; import Sentence from '../../process/sentence'; export default class extends Component { constructor(props) { super(props); this.state = { step: 0, hideAnalysis: true, analysisTab: 'official', showAnswer: false, noteField: AskTarget[0].key, showIds: false, }; } prevQuestion() { const { userQuestion, report } = this.props; if (userQuestion.no === 1) return; Question.getDetailByNo(report.id, userQuestion.no - 1).then(r => { linkTo(`/paper/question/${r.id}`); }); } nextQuestion() { const { userQuestion, report } = this.props; if (userQuestion.questionNumber === userQuestion.no) return; Question.getDetailByNo(report.id, userQuestion.no + 1).then(r => { linkTo(`/paper/question/${r.id}`); }); } changeData(type, field, value) { let { data, empty } = this.state; data = data || {}; empty = empty || {}; data[type] = data[type] || {}; data[type][field] = value; empty[type] = empty[type] || {}; if (value) empty[type][field] = !value; this.setState({ data, empty }); } submitAsk() { const { userQuestion, questionNo = {} } = this.props; const { ask = {} } = this.state; if (!ask.originContent || !ask.content || !ask.target) { this.setState({ empty: { ask: { originContent: !ask.originContent, content: !ask.content, target: !ask.target } } }); return Promise.reject(); } return My.addQuestionAsk(userQuestion.id, ask.target, questionNo.id, ask.originContent, ask.content).then(() => { this.setState({ askModal: false, askOkModal: true, ask: {} }); }).catch(err => { this.setState({ askError: err.message, ask: {} }); }); } submitFeedbackError() { const { questionNo = {} } = this.props; const { feedback = {} } = this.state; if (!feedback.originContent || !feedback.content || !feedback.target) { this.setState({ empty: { feedback: { originContent: !feedback.originContent, content: !feedback.content, target: !feedback.target } } }); return Promise.reject(); } return My.addFeedbackErrorQuestion( questionNo.id, questionNo.title, feedback.target, feedback.originContent, feedback.content, ) .then(() => { this.setState({ feedbackModal: false, feedbackOkModal: true, feedback: {} }); }) .catch(err => { this.setState({ feedbackError: err.message, feedback: {} }); }); } submitNote(close) { const { questionNo = {} } = this.props; const { note = {} } = this.state; My.updateQuestionNote(questionNo.id, note) .then(() => { if (close) this.setState({ noteModal: false }); }) .catch(err => { this.setState({ noteError: err.message }); }); } toggleFullscreen() { const { isFullscreenEnabled } = this.state; this.setState({ isFullscreenEnabled: !isFullscreenEnabled }); } toggleCollect() { const { userQuestion = {}, questionNo = {}, flow } = this.props; if (!userQuestion.collect) { My.addQuestionCollect(questionNo.id).then(() => { userQuestion.collect = true; flow.setState({ userQuestion }); }); } else { My.delQuestionCollect(questionNo.id).then(() => { userQuestion.collect = false; flow.setState({ userQuestion }); }); } } switchNo(no) { linkTo(`/question/detail/${no.id}`); } formatStem(text) { if (!text) return ''; const { question = { content: {} }, userQuestion } = this.props; const { table = {}, questions = [] } = question.content; const { showAnswer } = this.state; text = text.replace(/#select#/g, "<span class='#select#' />"); text = text.replace(/#table#/g, "<span class='#table#' />"); setTimeout(() => { const selectList = document.getElementsByClassName('#select#'); const tableList = document.getElementsByClassName('#table#'); for (let i = 0; i < selectList.length; i += 1) { if (!questions[i]) break; ReactDOM.render( <AnswerSelect list={questions[i].select} type={'single'} selected={(userQuestion.userAnswer || { questions: [] }).questions[i]} answer={(question.answer || { questions: [] }).questions[i]} fix show={showAnswer} />, selectList[i], ); } if (table.row && table.col && table.header) { const columns = table.header.map((title, index) => { return { title, key: index }; }); for (let i = 0; i < tableList.length; i += 1) { ReactDOM.render(<AnswerTable list={columns} columns={columns} data={table.data} />, tableList[i]); } } }, 1); return text; } formatOtherStem(question) { if (!question.stem) return ''; const { content = {}, stem } = question; const { table = {}, questions = [] } = content; let text = stem.replace(/#select#/g, `<span class='#select#${question.id}' />`); text = text.replace(/#table#/g, `<span class='#table#${question.id}' />`); setTimeout(() => { const selectList = document.getElementsByClassName(`#select#${question.id}`); const tableList = document.getElementsByClassName(`#table#${question.id}`); for (let i = 0; i < selectList.length; i += 1) { if (!questions[i]) break; ReactDOM.render( <AnswerSelect list={questions[i].select} type={'single'} // selected={(userQuestion.userAnswer || { questions: [] }).questions[i]} // answer={(question.answer || { questions: [] }).questions[i]} fix // show={showAnswer} />, selectList[i], ); } if (table.row && table.col && table.header) { const columns = table.header.map((title, index) => { return { title, key: index }; }); for (let i = 0; i < tableList.length; i += 1) { ReactDOM.render(<AnswerTable list={columns} columns={columns} data={table.data} />, tableList[i]); } } }, 1); return text; } render() { return ( <Fullscreen enabled={this.state.isFullscreenEnabled} onChange={isFullscreenEnabled => this.setState({ isFullscreenEnabled })} > {this.renderDetail()} </Fullscreen> ); } renderDetail() { const { paper = {} } = this.props; switch (paper.paperModule) { case 'sentence': return <Sentence {...this.props} {...this.state} flow={this} scene="answer" mode="question" />; default: return <div className="base">{this.renderBase()}</div>; } } renderHeader() { const { userQuestion = {}, questionNo = {}, paper = {}, report = {}, questionNos = [], question = {}, info, detail, } = this.props; const { showIds } = this.state; return ( <div className={'layout-header'}> {detail && ( <div className="left"> {paper.paperModule && paper.paperModule !== 'examination' && ( <div className="btn"> <Button radius onClick={() => { linkTo(`/paper/report/${report.id}`); }} > 返回练习报告 </Button> </div> )} {paper.paperModule && paper.paperModule === 'examination' && ( <div className="btn"> <Button radius onClick={() => { linkTo(`/paper/report/${report.id}`); }} > 返回成绩单 </Button> </div> )} <div className="no">No.{userQuestion.stageNo || userQuestion.no}</div> <div className="title"> <Assets name="book" /> {paper.title} </div> </div> )} <div className="center"> <div className="menu-wrap"> ID:{questionNo.title} {questionNos && questionNos.length > 0 && ( <Icon name="other" onClick={() => { this.setState({ showIds: true }); }} /> )} {showIds && ( <div className="menu-content"> <p>题源汇总</p> {(questionNos || []).map(row => ( <p onClick={() => info && this.switchNo(row)}>ID:{row.title}</p> ))} </div> )} </div> </div> <div className="right" hidden={question.questionType === 'awa'}> {detail && ( <span className="b" hidden={!userQuestion.id}> 用时: <span dangerouslySetInnerHTML={{ __html: formatSeconds(userQuestion.userTime).replace( /([0-9]+)(min|m|hour|h|s)/g, '<span class="s">$1</span>$2', ), }} /> {/* 用时:<span className="s">1</span>m<span className="s">39</span>s */} </span> )} <span className="b"> 全站: <span dangerouslySetInnerHTML={{ __html: formatSeconds(questionNo.totalTime / questionNo.totalNumber).replace( /([0-9]+)(min|m|hour|h|s)/g, '<span class="s">$1</span>$2', ), }} /> {/* 全站:<span className="s">1</span>m<span className="s">39</span>s */} </span> <span className="b"> <span className="s">{formatPercent(questionNo.totalCorrect, questionNo.totalNumber)}</span>% </span> <Icon name="question c-p" /> <Icon name="star" active={userQuestion.collect} onClick={() => this.toggleCollect()} /> </div> </div> ); } renderBase() { const { questionStatus, userQuestion = {}, questionNo = {}, paper = {}, detail } = this.props; const { showIds } = this.state; return ( <div className={`layout ${paper.paperModule}`} onClick={() => { if (showIds) this.setState({ showIds: false }); }} > {this.renderHeader()} <div className="layout-body">{this.renderBody()}</div> <div className="layout-footer"> <div className="left"> <Tooltip overlayClassName="gray" placement="top" title="全屏"> <a> <Icon name={this.state.isFullscreenEnabled ? 'sceen-restore' : 'sceen-full'} onClick={() => this.toggleFullscreen()} /> </a> </Tooltip> </div> <div className="center"> <AnswerButton className="item" onClick={() => User.needLogin().then(() => this.setState({ noteModal: true }))}> 笔记 </AnswerButton> {questionStatus >= 0 && ( <AnswerButton className="item" onClick={() => { if (questionStatus > 0) { User.needLogin().then(() => { this.setState({ askModal: true, ask: { target: AskTarget[0].value } }); }); } else { this.setState({ askFailModal: true }); } }} > 提问 </AnswerButton> )} <AnswerButton className="item" onClick={() => User.needLogin().then(() => this.setState({ feedbackModal: true, feedback: { position: AskTarget[0].value } }))}> 纠错 </AnswerButton> </div> {detail && ( <div className="right"> {userQuestion.no !== 1 && <Icon name="prev" onClick={() => this.prevQuestion()} />} {userQuestion.questionNumber !== userQuestion.no && ( <Icon name="next" onClick={() => this.nextQuestion()} /> )} </div> )} </div> {this.state.askModal && this.renderAsk()} {this.state.askOkModal && this.renderAskOk()} {this.state.askFailModal && this.renderAskFail()} {this.state.feedbackModal && this.renderFeedbackError()} {this.state.feedbackOkModal && this.renderFeedbackErrorOk()} {/* {this.state.noteModal && this.renderNote()} */} <QuestionNoteModal show={this.state.noteModal} defaultData={this.state.note} questionNo={questionNo} onConfirm={() => this.setState({ noteModal: false })} onCancel={() => this.setState({ noteModal: false })} /> </div> ); } renderBody() { const { question = { content: {} } } = this.props; const { typeset = 'one' } = question.content; const { hideAnalysis, showAnswer } = this.state; const show = typeset === 'one' ? true : !hideAnalysis; return ( <div className="layout-content"> <div className={typeset}> {this.renderContent()} {typeset === 'two' && ( <div className="block"> <div className="block-answer"> {( <Switch checked={showAnswer} onChange={value => { this.setState({ showAnswer: value }); }} > {showAnswer ? '显示答案' : '显示答案'} </Switch> )} {this.renderAnswer()} </div> </div> )} {question.questionType === 'awa' && this.renderAWA()} </div> {question.questionType !== 'awa' && this.renderAnalysis()} {typeset === 'two' && question.questionType !== 'awa' && ( <div className="fixed-analysis" onClick={() => this.setState({ hideAnalysis: !hideAnalysis })}> {show ? '收起解析 >' : '查看解析 <'} </div> )} </div> ); } renderAnalysis() { const { question = { content: {} } } = this.props; const { typeset = 'one' } = question.content; const { hideAnalysis, analysisTab } = this.state; const show = typeset === 'one' ? true : !hideAnalysis; const { showAnswer } = this.state; return (<div className="block"> <div className={`block-analysis two-analysis ${show ? 'show' : ''}`}> <Tabs type="division" active={analysisTab} space={2} tabs={[ { key: 'official', name: '官方解析' }, { key: 'qx', name: '千行解析' }, { key: 'association', name: '题源联想' }, { key: 'qa', name: '相关回答' }, ]} onChange={key => { this.setState({ analysisTab: key }); }} /> <div className="detail"> {typeset === 'two' && ( <div className="block"> <div className="block-answer"> {<Switch checked={showAnswer} onChange={value => { this.setState({ showAnswer: value }); }} > {showAnswer ? '显示答案' : '显示答案'} </Switch> } {this.renderAnswer()} </div> </div> )} {this.renderText()} </div> </div></div> ); } renderText() { const { question = {}, userQuestion = {} } = this.props; const { asks = [], associations = [] } = userQuestion; const { analysisTab } = this.state; let content; switch (analysisTab) { case 'official': content = ( <div className="detail-block "><div className="block-text" dangerouslySetInnerHTML={{ __html: question.officialContent }} /></div> ); break; case 'qx': content = <div className="detail-block "><div className="block-text u-s-n" dangerouslySetInnerHTML={{ __html: question.qxContent }} /></div>; break; case 'association': content = ( <div className="detail-block"> <Carousel> {associations.filter(row => row).map(association => { const { questions = [], type } = association.content || {}; return <div className="block-text"> <div dangerouslySetInnerHTML={{ __html: this.formatOtherStem(association) }} /> {questions.map((item, index) => { return ( <div> <div className="text m-b-2" dangerouslySetInnerHTML={{ __html: item.description }} /> <AnswerList answer={(question.answer || { questions: [] }).questions[index]} list={item.select} type={type} first={item.first} second={item.second} direction={item.direction} /> </div> ); })} </div>; })} </Carousel> </div> ); break; case 'qa': content = ( <div className="detail-block "> <div className="block-answer"> {asks.map((ask, index) => { return <OtherAnswer key={index} data={ask} />; })} </div> </div> ); break; default: break; } return content; } renderAnswer() { const { question = { content: {} }, userQuestion = {} } = this.props; const { questions = [], type } = question.content; const { showAnswer } = this.state; return questions.map((item, index) => { return ( <div> <div className="text m-b-2" dangerouslySetInnerHTML={{ __html: item.description }} /> <AnswerList show={showAnswer} selected={(userQuestion.userAnswer || { questions: [] }).questions[index]} answer={(question.answer || { questions: [] }).questions[index]} distributed={(question.answerDistributed || { questions: [] }).questions[index]} list={item.select} type={type} first={item.first} second={item.second} direction={item.direction} /> </div> ); }); } renderContent() { const { question = { content: {} } } = this.props; const { typeset = 'one' } = question.content; const { steps = [] } = question.content; const { showAnswer, step } = this.state; return (<div className="block"> <div className="block-content"> {typeset === 'one' && question.questionType !== 'awa' && ( <Switch checked={showAnswer} onChange={value => { this.setState({ showAnswer: value }); }} > {showAnswer ? '显示答案' : '显示答案'} </Switch> )} {question.questionType === 'awa' && <h2>Analytical Writing Assessment</h2>} {steps.length > 0 && ( <Navigation theme="detail" list={question.content.steps} active={step} onChange={v => this.setState({ step: v })} /> )} <div className="text" dangerouslySetInnerHTML={{ __html: this.formatStem(steps.length > 0 ? steps[step].stem : question.stem) }} /> {typeset === 'one' && question.questionType !== 'awa' && this.renderAnswer()} </div></div> ); } renderAWA() { const { userQuestion = { detail: {}, userAnswer: {} } } = this.state; const { showAnswer } = this.state; return (<div className="block"> <div className="block-awa"> <Switch checked={showAnswer} onChange={value => { this.setState({ showAnswer: value }); }} > {showAnswer ? '显示答案' : '显示答案'} </Switch> <div className="body"> <h2>Your Response</h2> {showAnswer && ( <div className="detail"> <div className="info"> <span className="b"> 用时: <span dangerouslySetInnerHTML={{ __html: formatSeconds(userQuestion.userTime).replace( /([0-9]+)(min|m|hour|h|s)/g, '<span class="s">$1</span>$2', ), }} /> {/* 用时:<span className="s">1</span>m<span className="s">39</span>s */} </span> <span className="b"> 单词数:<span className="s">{Number((userQuestion.detail || {}).words || 0)}</span>词 </span> </div> <div className="content-awa" dangerouslySetInnerHTML={{ __html: userQuestion.userAnswer.awa || '' }} /> </div> )} {!showAnswer && <div className="show-awa">选择「显示答案」查看自己的作文</div>} </div> </div></div> ); } renderAsk() { const { ask = {}, empty = {} } = this.state; const emptyAsk = empty.ask || {}; return ( <div className="question-modal ask"> <div className="mask" /> <div className="modal-body"> <div className="modal-title">提问</div> <div className="modal-desc"> <div className="select-inline"> 我想对 <Select excludeSelf size="small" theme="white" value={ask.target} list={AskTarget} onChange={item => { this.changeData('ask', 'target', item.value); }} /> 进行提问 </div> <div className="label">有疑问的具体内容是:</div> <Textarea className="textarea" value={ask.originContent} placeholder="请复制粘贴有疑问的内容。" empty={emptyAsk.originContent} onChange={e => { this.changeData('ask', 'originContent', e.target.value); }} /> <div className="label">针对以上内容的问题是:</div> <Textarea className="textarea" value={ask.content} placeholder="提问频率高的问题会被优先回答哦。" empty={emptyAsk.content} onChange={e => { this.changeData('ask', 'content', e.target.value); }} /> </div> <div className="bottom"> <AnswerButton theme="cancel" size="lager" onClick={() => this.setState({ askModal: false })}> 取消 </AnswerButton> <AnswerButton size="lager" onClick={() => this.submitAsk()}> 提交 </AnswerButton> </div> </div> </div> ); } renderAskOk() { return ( <div className="question-modal ask-ok"> <div className="mask" /> <div className="modal-body"> <div className="modal-title">提问</div> <div className="modal-content"> <div className="left"> <div className="text">已提交成功!</div> <div className="text">关注公众号,老师回答后会立即收到通知。</div> <div className="text">我们也会通过站内信的方式通知你。</div> <div className="small"> 成为学员享受极速答疑特权。<Link to="">了解更多</Link> </div> </div> <div className="right"> <Assets name="qrcode" /> <div className="text">扫码关注公众号</div> <div className="text">千行GMAT</div> </div> </div> <div className="confirm"> <AnswerButton size="lager" theme="confirm" onClick={() => { this.setState({ askOkModal: false }); }} > 好的,知道了 </AnswerButton> </div> </div> </div> ); } renderAskFail() { return ( <div className="question-modal ask-ok"> <div className="mask" /> <div className="modal-body"> <div className="modal-title">提问</div> <div className="modal-content"> <div className="left"> <div className="text">提问功能正在维护中。</div> <div className="text">可先查阅“相关问答” 或 成为学员享受极速 答疑特权。</div> <Link to="/">了解更多></Link> </div> <div className="right"> <Assets name="qrcode" /> <div className="text">扫码关注公众号</div> <div className="text">千行GMAT</div> </div> </div> <div className="confirm"> <AnswerButton size="lager" theme="confirm" onClick={() => { this.setState({ askFailModal: false }); }} > 好的,知道了 </AnswerButton> </div> </div> </div> ); } renderFeedbackError() { const { feedback = {}, empty = {} } = this.state; const emptyFeedback = empty.feedback || {}; return ( <div className="question-modal error"> <div className="mask" /> <div className="modal-body"> <div className="modal-title">纠错</div> <div className="modal-desc"> <div className="select-inline"> 我想对 <Select excludeSelf size="small" theme="white" value={feedback.target} list={AskTarget} onChange={item => { this.changeData('feedback', 'target', item.value); }} /> 进行提问 </div> <div className="label">错误内容是:</div> <Textarea className="textarea" value={feedback.originContent} placeholder="你可以适当扩大复制范围以使我们准确定位,感谢。" empty={emptyFeedback.originContent} onChange={(e) => { this.changeData('feedback', 'originContent', e.target.value); }} /> <div className="label">应该改为:</div> <Textarea className="textarea" value={feedback.content} placeholder="只需提供正确内容即可" empty={emptyFeedback.content} onChange={(e) => { this.changeData('feedback', 'content', e.target.value); }} /> </div> <div className="bottom"> <AnswerButton theme="cancel" size="lager" onClick={() => { this.setState({ feedbackModal: false }); }} > 取消 </AnswerButton> <AnswerButton size="lager" onClick={() => { this.submitFeedbackError(); }} > 提交 </AnswerButton> </div> </div> </div> ); } renderFeedbackErrorOk() { return ( <div className="question-modal error-ok"> <div className="mask" /> <div className="modal-body"> <div className="modal-title">纠错</div> <div className="modal-content"> <div className="left"> <div className="text"> <Assets name="right" svg /> 已提交成功! </div> <div className="text">感谢您的耐心反馈,我们会尽快核实并以站内信的方式告知结果。</div> <div className="text">您也可以关注公众号及时获取结果。</div> </div> <div className="right"> <Assets name="qrcode" /> <div className="text">扫码关注公众号</div> <div className="text">千行GMAT</div> </div> </div> <div className="confirm"> <AnswerButton size="lager" theme="confirm" onClick={() => { this.setState({ feedbackOkModal: false }); }} > 好的,知道了 </AnswerButton> </div> </div> </div> ); } renderNote() { const { noteField, note = {} } = this.state; return ( <div className="question-modal note"> <div className="mask" /> <div className="modal-body"> <div className="modal-title">笔记</div> <div className="modal-content"> <div className="tabs"> {AskTarget.map(item => { return ( <div className={`tab ${noteField === item.key ? 'active' : ''}`} onClick={() => { this.setState({ noteField: item.key }); }} > <div className="text">{item.label}</div> <div className="date">{note[`${item.key}Time`] ? formatDate(note[`${item.key}Time`]) : ''}</div> </div> ); })} </div> <div className="input"> <Textarea className="textarea" value={note[`${noteField}Content`] || ''} placeholder="记下笔记,方便以后复习" onChange={e => { note[`${noteField}Time`] = new Date(); note[`${noteField}Content`] = e.target.value; this.setState({ note }); }} /> <div className="bottom"> <AnswerButton theme="cancel" size="lager" onClick={() => { this.setState({ noteModal: false }); }} > 取消 </AnswerButton> <AnswerButton size="lager" onClick={() => { this.submitNote(); }} > 编辑 </AnswerButton> <AnswerButton size="lager" onClick={() => { this.submitNote(true); }} > 保存 </AnswerButton> </div> </div> </div> </div> </div> ); } }