index.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977
  1. import React, { Component } from 'react';
  2. import ReactDOM from 'react-dom';
  3. import { Carousel, Tooltip } from 'antd';
  4. import { Link } from 'react-router-dom';
  5. import Fullscreen from 'react-fullscreen-crossbrowser';
  6. import './index.less';
  7. import { formatSeconds, formatPercent, formatDate } from '@src/services/Tools';
  8. import Assets from '@src/components/Assets';
  9. import Navigation from '../../../../components/Navigation';
  10. import Tabs from '../../../../components/Tabs';
  11. import Icon from '../../../../components/Icon';
  12. import Switch from '../../../../components/Switch';
  13. import Select from '../../../../components/Select';
  14. import { Button } from '../../../../components/Button';
  15. import AnswerSelect from '../../../../components/AnswerSelect';
  16. import AnswerList from '../../../../components/AnswerList';
  17. import AnswerButton from '../../../../components/AnswerButton';
  18. import AnswerTable from '../../../../components/AnswerTable';
  19. import OtherAnswer from '../../../../components/OtherAnswer';
  20. import { Textarea } from '../../../../components/Input';
  21. import { QuestionNoteModal } from '../../../../components/OtherModal';
  22. import { AskTarget, QrCodeAssets } from '../../../../../Constant';
  23. import { Question } from '../../../../stores/question';
  24. import { My } from '../../../../stores/my';
  25. import { User } from '../../../../stores/user';
  26. import Sentence from '../../process/sentence';
  27. export default class extends Component {
  28. constructor(props) {
  29. super(props);
  30. this.state = {
  31. step: 0,
  32. hideAnalysis: true,
  33. analysisTab: 'official',
  34. showAnswer: false,
  35. noteField: AskTarget[0].key,
  36. showIds: false,
  37. };
  38. }
  39. note() {
  40. const { userQuestion } = this.props;
  41. const { questionNo } = userQuestion;
  42. const { note } = this.state;
  43. if (note) {
  44. this.setState({ noteModal: true });
  45. return;
  46. }
  47. My.getQuestionNote(questionNo.id)
  48. .then(result => {
  49. this.setState({ note: result || {}, noteModal: true });
  50. });
  51. }
  52. prevQuestion() {
  53. const { userQuestion, report } = this.props;
  54. if (userQuestion.no === 1) return;
  55. Question.getDetailByNo(report.id, userQuestion.no - 1).then(r => {
  56. linkTo(`/paper/question/${r.id}`);
  57. });
  58. }
  59. nextQuestion() {
  60. const { userQuestion, report } = this.props;
  61. if (userQuestion.questionNumber === userQuestion.no) return;
  62. Question.getDetailByNo(report.id, userQuestion.no + 1).then(r => {
  63. linkTo(`/paper/question/${r.id}`);
  64. });
  65. }
  66. changeData(type, field, value) {
  67. let { data, empty } = this.state;
  68. data = data || {};
  69. empty = empty || {};
  70. data[type] = data[type] || {};
  71. data[type][field] = value;
  72. empty[type] = empty[type] || {};
  73. if (value) empty[type][field] = !value;
  74. this.setState({ data, empty });
  75. }
  76. submitAsk() {
  77. const { userQuestion, questionNo = {} } = this.props;
  78. const { data = {} } = this.state;
  79. const { ask = {} } = data;
  80. if (!ask.originContent || !ask.content) {
  81. this.setState({ empty: { ask: { originContent: !ask.originContent, content: !ask.content } } });
  82. return Promise.reject();
  83. }
  84. if (!ask.target) {
  85. ask.target = AskTarget[0].value;
  86. }
  87. data.ask = {};
  88. return My.addQuestionAsk(userQuestion.id, ask.target, questionNo.id, ask.originContent, ask.content).then(() => {
  89. this.setState({ askModal: false, askOkModal: true, data });
  90. }).catch(err => {
  91. this.setState({ askError: err.message, data });
  92. });
  93. }
  94. submitFeedbackError() {
  95. const { questionNo = {} } = this.props;
  96. const { data = {} } = this.state;
  97. const { feedback = {} } = data;
  98. if (!feedback.originContent || !feedback.content || !feedback.target) {
  99. this.setState({ empty: { feedback: { originContent: !feedback.originContent, content: !feedback.content, target: !feedback.target } } });
  100. return Promise.reject();
  101. }
  102. data.feedback = {};
  103. return My.addFeedbackErrorQuestion(
  104. questionNo.id,
  105. questionNo.title,
  106. feedback.target,
  107. feedback.originContent,
  108. feedback.content,
  109. )
  110. .then(() => {
  111. this.setState({ feedbackModal: false, feedbackOkModal: true, data });
  112. })
  113. .catch(err => {
  114. this.setState({ feedbackError: err.message, data });
  115. });
  116. }
  117. submitNote(close) {
  118. const { questionNo = {} } = this.props;
  119. const { note = {} } = this.state;
  120. My.updateQuestionNote(questionNo.id, note)
  121. .then(() => {
  122. if (close) this.setState({ noteModal: false });
  123. })
  124. .catch(err => {
  125. this.setState({ noteError: err.message });
  126. });
  127. }
  128. toggleFullscreen() {
  129. const { isFullscreenEnabled } = this.state;
  130. this.setState({ isFullscreenEnabled: !isFullscreenEnabled });
  131. }
  132. toggleCollect() {
  133. const { userQuestion = {}, questionNo = {}, flow } = this.props;
  134. if (!userQuestion.collect) {
  135. My.addQuestionCollect(questionNo.id).then(() => {
  136. userQuestion.collect = true;
  137. flow.setState({ userQuestion });
  138. });
  139. } else {
  140. My.delQuestionCollect(questionNo.id).then(() => {
  141. userQuestion.collect = false;
  142. flow.setState({ userQuestion });
  143. });
  144. }
  145. }
  146. switchNo(no) {
  147. linkTo(`/question/detail/${no.id}`);
  148. }
  149. formatStem(text) {
  150. if (!text) return '';
  151. const { question = { content: {} }, userQuestion } = this.props;
  152. const { table = {}, questions = [] } = question.content;
  153. const { showAnswer } = this.state;
  154. text = text.replace(/#select#/g, "<span class='#select#' />");
  155. text = text.replace(/#table#/g, "<span class='#table#' />");
  156. setTimeout(() => {
  157. const selectList = document.getElementsByClassName('#select#');
  158. const tableList = document.getElementsByClassName('#table#');
  159. for (let i = 0; i < selectList.length; i += 1) {
  160. if (!questions[i]) break;
  161. ReactDOM.render(
  162. <AnswerSelect
  163. list={questions[i].select}
  164. type={'single'}
  165. selected={(userQuestion.userAnswer || { questions: [] }).questions[i]}
  166. answer={(question.answer || { questions: [] }).questions[i]}
  167. fix
  168. show={showAnswer}
  169. />,
  170. selectList[i],
  171. );
  172. }
  173. if (table.row && table.col && table.header) {
  174. const columns = table.header.map((title, index) => {
  175. return { title, key: index };
  176. });
  177. for (let i = 0; i < tableList.length; i += 1) {
  178. ReactDOM.render(<AnswerTable list={columns} columns={columns} data={table.data} />, tableList[i]);
  179. }
  180. }
  181. }, 1);
  182. return text;
  183. }
  184. formatOtherStem(question) {
  185. if (!question.stem) return '';
  186. const { content = {}, stem } = question;
  187. const { table = {}, questions = [] } = content;
  188. let text = stem.replace(/#select#/g, `<span class='#select#${question.id}' />`);
  189. text = text.replace(/#table#/g, `<span class='#table#${question.id}' />`);
  190. setTimeout(() => {
  191. const selectList = document.getElementsByClassName(`#select#${question.id}`);
  192. const tableList = document.getElementsByClassName(`#table#${question.id}`);
  193. for (let i = 0; i < selectList.length; i += 1) {
  194. if (!questions[i]) break;
  195. ReactDOM.render(
  196. <AnswerSelect
  197. list={questions[i].select}
  198. type={'single'}
  199. // selected={(userQuestion.userAnswer || { questions: [] }).questions[i]}
  200. // answer={(question.answer || { questions: [] }).questions[i]}
  201. fix
  202. // show={showAnswer}
  203. />,
  204. selectList[i],
  205. );
  206. }
  207. if (table.row && table.col && table.header) {
  208. const columns = table.header.map((title, index) => {
  209. return { title, key: index };
  210. });
  211. for (let i = 0; i < tableList.length; i += 1) {
  212. ReactDOM.render(<AnswerTable list={columns} columns={columns} data={table.data} />, tableList[i]);
  213. }
  214. }
  215. }, 1);
  216. return text;
  217. }
  218. render() {
  219. return (
  220. <Fullscreen
  221. enabled={this.state.isFullscreenEnabled}
  222. onChange={isFullscreenEnabled => this.setState({ isFullscreenEnabled })}
  223. >
  224. {this.renderDetail()}
  225. </Fullscreen>
  226. );
  227. }
  228. renderDetail() {
  229. const { question = {} } = this.props;
  230. switch (question.questionType) {
  231. case 'sentence':
  232. return <Sentence {...this.props} {...this.state} flow={this} scene="answer" mode="question" />;
  233. default:
  234. return <div className="base">{this.renderBase()}</div>;
  235. }
  236. }
  237. renderHeader() {
  238. const {
  239. userQuestion = {},
  240. questionNo = {},
  241. paper = {},
  242. report = {},
  243. questionNos = [],
  244. question = {},
  245. info,
  246. detail,
  247. } = this.props;
  248. const { showIds } = this.state;
  249. return (
  250. <div className={'layout-header'}>
  251. {detail && (
  252. <div className="left">
  253. {paper.paperModule && paper.paperModule !== 'examination' && (
  254. <div className="btn">
  255. <Button
  256. radius
  257. onClick={() => {
  258. linkTo(`/paper/report/${report.id}`);
  259. }}
  260. >
  261. 返回练习报告
  262. </Button>
  263. </div>
  264. )}
  265. {paper.paperModule && paper.paperModule === 'examination' && (
  266. <div className="btn">
  267. <Button
  268. radius
  269. onClick={() => {
  270. linkTo(`/paper/report/${report.id}`);
  271. }}
  272. >
  273. 返回成绩单
  274. </Button>
  275. </div>
  276. )}
  277. <div className="no">No.{userQuestion.stageNo || userQuestion.no}</div>
  278. <div className="title">
  279. <Assets name="book" />
  280. {paper.title}
  281. </div>
  282. </div>
  283. )}
  284. <div className="center">
  285. <div className="menu-wrap">
  286. ID:{questionNo.title}
  287. {questionNos && questionNos.length > 0 && (
  288. <Icon
  289. name="other"
  290. onClick={() => {
  291. this.setState({ showIds: true });
  292. }}
  293. />
  294. )}
  295. {showIds && (
  296. <div className="menu-content">
  297. <p>题源汇总</p>
  298. {(questionNos || []).map(row => (
  299. <p onClick={() => info && this.switchNo(row)}>ID:{row.title}</p>
  300. ))}
  301. </div>
  302. )}
  303. </div>
  304. </div>
  305. <div className="right" hidden={question.questionType === 'awa'}>
  306. {detail && (
  307. <span className="b" hidden={!userQuestion.id}>
  308. 用时:
  309. <span
  310. dangerouslySetInnerHTML={{
  311. __html: formatSeconds(userQuestion.userTime).replace(
  312. /([0-9]+)(min|m|hour|h|s)/g,
  313. '<span class="s">$1</span>$2',
  314. ),
  315. }}
  316. />
  317. {/* 用时:<span className="s">1</span>m<span className="s">39</span>s */}
  318. </span>
  319. )}
  320. <span className="b">
  321. 全站:
  322. <span
  323. dangerouslySetInnerHTML={{
  324. __html: formatSeconds(questionNo.totalTime / questionNo.totalNumber).replace(
  325. /([0-9]+)(min|m|hour|h|s)/g,
  326. '<span class="s">$1</span>$2',
  327. ),
  328. }}
  329. />
  330. {/* 全站:<span className="s">1</span>m<span className="s">39</span>s */}
  331. </span>
  332. <span className="b">
  333. <span className="s">{formatPercent(questionNo.totalCorrect, questionNo.totalNumber)}</span>%
  334. </span>
  335. <Tooltip title={'全站平均正确率'} trigger='click'>
  336. <Icon name="question c-p" />
  337. </Tooltip>
  338. <Icon name="star" active={userQuestion.collect} onClick={() => this.toggleCollect()} />
  339. </div>
  340. </div>
  341. );
  342. }
  343. renderBase() {
  344. const { questionStatus, userQuestion = {}, questionNo = {}, paper = {}, detail } = this.props;
  345. const { showIds } = this.state;
  346. return (
  347. <div
  348. className={`layout ${paper.paperModule}`}
  349. onClick={() => {
  350. if (showIds) this.setState({ showIds: false });
  351. }}
  352. >
  353. {this.renderHeader()}
  354. <div className="layout-body">{this.renderBody()}</div>
  355. <div className="layout-footer">
  356. <div className="left">
  357. <Tooltip overlayClassName="gray" placement="top" title="全屏">
  358. <a>
  359. <Icon
  360. name={this.state.isFullscreenEnabled ? 'sceen-restore' : 'sceen-full'}
  361. onClick={() => this.toggleFullscreen()}
  362. />
  363. </a>
  364. </Tooltip>
  365. </div>
  366. <div className="center">
  367. <AnswerButton className="item" onClick={() => User.needLogin().then(() => this.note())}>
  368. 笔记
  369. </AnswerButton>
  370. {questionStatus >= 0 && (
  371. <AnswerButton
  372. className="item"
  373. onClick={() => {
  374. if (questionStatus > 0) {
  375. User.needLogin().then(() => {
  376. this.setState({ askModal: true, ask: { target: AskTarget[0].value } });
  377. });
  378. } else {
  379. this.setState({ askFailModal: true });
  380. }
  381. }}
  382. >
  383. 提问
  384. </AnswerButton>
  385. )}
  386. <AnswerButton className="item" onClick={() => User.needLogin().then(() => this.setState({ feedbackModal: true, feedback: { position: AskTarget[0].value } }))}>
  387. 纠错
  388. </AnswerButton>
  389. </div>
  390. {detail && (
  391. <div className="right">
  392. {userQuestion.no !== 1 && <Icon name="prev" onClick={() => this.prevQuestion()} />}
  393. {userQuestion.questionNumber !== userQuestion.no && (
  394. <Icon name="next" onClick={() => this.nextQuestion()} />
  395. )}
  396. </div>
  397. )}
  398. </div>
  399. {this.state.askModal && this.renderAsk()}
  400. {this.state.askOkModal && this.renderAskOk()}
  401. {this.state.askFailModal && this.renderAskFail()}
  402. {this.state.feedbackModal && this.renderFeedbackError()}
  403. {this.state.feedbackOkModal && this.renderFeedbackErrorOk()}
  404. {/* {this.state.noteModal && this.renderNote()} */}
  405. <QuestionNoteModal show={this.state.noteModal} defaultData={this.state.note} questionNo={questionNo} onConfirm={(data) => this.setState({ noteModal: false, note: data })} onCancel={() => this.setState({ noteModal: false })} />
  406. </div>
  407. );
  408. }
  409. renderBody() {
  410. const { question = { content: {} } } = this.props;
  411. const { typeset = 'one' } = question.content;
  412. const { hideAnalysis, showAnswer } = this.state;
  413. const show = typeset === 'one' ? true : !hideAnalysis;
  414. return (
  415. <div className="layout-content">
  416. <div className={question.questionType === 'awa' ? 'two' : typeset}>
  417. {this.renderContent()}
  418. {typeset === 'two' && (
  419. <div className="block">
  420. <div className="block-answer">
  421. {(
  422. <Switch
  423. checked={showAnswer}
  424. onChange={value => {
  425. this.setState({ showAnswer: value });
  426. }}
  427. >
  428. {showAnswer ? '显示答案' : '显示答案'}
  429. </Switch>
  430. )}
  431. {this.renderAnswer()}
  432. </div>
  433. </div>
  434. )}
  435. {question.questionType === 'awa' && this.renderAWA()}
  436. </div>
  437. {question.questionType !== 'awa' && this.renderAnalysis()}
  438. {typeset === 'two' && question.questionType !== 'awa' && (
  439. <div className="fixed-analysis" onClick={() => this.setState({ hideAnalysis: !hideAnalysis })}>
  440. {show ? '收起解析 >' : '查看解析 <'}
  441. </div>
  442. )}
  443. </div>
  444. );
  445. }
  446. renderAnalysis() {
  447. const { question = { content: {} } } = this.props;
  448. const { typeset = 'one' } = question.content;
  449. const { hideAnalysis, analysisTab } = this.state;
  450. const show = typeset === 'one' ? true : !hideAnalysis;
  451. const { showAnswer } = this.state;
  452. return (<div className="block">
  453. <div className={`block-analysis two-analysis ${show ? 'show' : ''}`}>
  454. <Tabs
  455. type="division"
  456. active={analysisTab}
  457. space={2}
  458. tabs={[
  459. { key: 'official', name: '官方解析' },
  460. { key: 'qx', name: '千行解析' },
  461. { key: 'association', name: '题源联想' },
  462. { key: 'qa', name: '相关回答' },
  463. ]}
  464. onChange={key => {
  465. this.setState({ analysisTab: key });
  466. }}
  467. />
  468. <div className="detail">
  469. {typeset === 'two' && (
  470. <div className="block">
  471. <div className="block-answer">
  472. {<Switch
  473. checked={showAnswer}
  474. onChange={value => {
  475. this.setState({ showAnswer: value });
  476. }}
  477. >
  478. {showAnswer ? '显示答案' : '显示答案'}
  479. </Switch>
  480. }
  481. {this.renderAnswer()}
  482. </div>
  483. </div>
  484. )}
  485. {this.renderText()}
  486. </div>
  487. </div></div>
  488. );
  489. }
  490. renderText() {
  491. const { question = {}, userQuestion = {} } = this.props;
  492. const { asks = [], associations = [] } = userQuestion;
  493. const { analysisTab } = this.state;
  494. let content;
  495. switch (analysisTab) {
  496. case 'official':
  497. content = (
  498. <div className="detail-block "><div className="block-text u-s-n" dangerouslySetInnerHTML={{ __html: question.officialContent }} /></div>
  499. );
  500. break;
  501. case 'qx':
  502. content = <div className="detail-block "><div className="block-text u-s-n" dangerouslySetInnerHTML={{ __html: question.qxContent }} /></div>;
  503. break;
  504. case 'association':
  505. content = (
  506. <div className="detail-block">
  507. <Carousel>
  508. {associations.filter(row => row).map(association => {
  509. const { questions = [], type } = association.content || {};
  510. return <div className="block-text u-s-n">
  511. <div dangerouslySetInnerHTML={{ __html: this.formatOtherStem(association) }} />
  512. {questions.map((item, index) => {
  513. return (
  514. <div>
  515. <div className="text m-b-2 u-s-n" dangerouslySetInnerHTML={{ __html: item.description }} />
  516. <AnswerList
  517. answer={(question.answer || { questions: [] }).questions[index]}
  518. list={item.select}
  519. type={type}
  520. first={item.first}
  521. second={item.second}
  522. direction={item.direction}
  523. />
  524. </div>
  525. );
  526. })}
  527. </div>;
  528. })}
  529. </Carousel>
  530. </div>
  531. );
  532. break;
  533. case 'qa':
  534. content = (
  535. <div className="detail-block ">
  536. <div className="block-answer">
  537. {asks.map((ask, index) => {
  538. return <OtherAnswer key={index} data={ask} />;
  539. })}
  540. </div>
  541. </div>
  542. );
  543. break;
  544. default:
  545. break;
  546. }
  547. return content;
  548. }
  549. renderAnswer() {
  550. const { question = { content: {} }, userQuestion = {} } = this.props;
  551. const { questions = [], type } = question.content;
  552. const { showAnswer } = this.state;
  553. return questions.map((item, index) => {
  554. return (
  555. <div>
  556. <div className="text m-b-2 u-s-n" dangerouslySetInnerHTML={{ __html: item.description }} />
  557. <AnswerList
  558. show={showAnswer}
  559. selected={(userQuestion.userAnswer || { questions: [] }).questions[index]}
  560. answer={(question.answer || { questions: [] }).questions[index]}
  561. distributed={(question.answerDistributed || { questions: [] }).questions[index]}
  562. list={item.select}
  563. type={type}
  564. first={item.first}
  565. second={item.second}
  566. direction={item.direction}
  567. />
  568. </div>
  569. );
  570. });
  571. }
  572. renderContent() {
  573. const { question = { content: {} } } = this.props;
  574. const { typeset = 'one' } = question.content;
  575. const { steps = [] } = question.content;
  576. const { showAnswer, step } = this.state;
  577. return (<div className="block">
  578. <div className="block-content"
  579. style={{ paddingBottom: '100px' }}>
  580. {typeset === 'one' && question.questionType !== 'awa' && (
  581. <Switch
  582. checked={showAnswer}
  583. onChange={value => {
  584. this.setState({ showAnswer: value });
  585. }}
  586. >
  587. {showAnswer ? '显示答案' : '显示答案'}
  588. </Switch>
  589. )}
  590. {question.questionType === 'awa' && <h2>Analytical Writing Assessment</h2>}
  591. {steps.length > 0 && (
  592. <Navigation
  593. theme="detail"
  594. list={question.content.steps}
  595. active={step}
  596. onChange={v => this.setState({ step: v })}
  597. />
  598. )}
  599. <div
  600. className="text u-s-n"
  601. dangerouslySetInnerHTML={{ __html: this.formatStem(steps.length > 0 ? steps[step].stem : question.stem) }}
  602. />
  603. {typeset === 'one' && question.questionType !== 'awa' && this.renderAnswer()}
  604. </div></div>
  605. );
  606. }
  607. renderAWA() {
  608. const { userQuestion } = this.props;
  609. const { showAnswer } = this.state;
  610. return (<div className="block">
  611. <div className="block-awa">
  612. <Switch
  613. checked={showAnswer}
  614. onChange={value => {
  615. this.setState({ showAnswer: value });
  616. }}
  617. >
  618. {showAnswer ? '显示答案' : '显示答案'}
  619. </Switch>
  620. <div className="body">
  621. <h2>Your Response</h2>
  622. {showAnswer && (
  623. <div className="detail">
  624. <div className="info">
  625. <span className="b">
  626. 用时:
  627. <span
  628. dangerouslySetInnerHTML={{
  629. __html: formatSeconds(userQuestion.userTime).replace(
  630. /([0-9]+)(min|m|hour|h|s)/g,
  631. '<span class="s">$1</span>$2',
  632. ),
  633. }}
  634. />
  635. {/* 用时:<span className="s">1</span>m<span className="s">39</span>s */}
  636. </span>
  637. <span className="b">
  638. 单词数:<span className="s">{Number((userQuestion.detail || {}).words || 0)}</span>词
  639. </span>
  640. </div>
  641. <div className="content-awa" dangerouslySetInnerHTML={{ __html: (userQuestion.userAnswer || {}).awa || '' }} />
  642. </div>
  643. )}
  644. {!showAnswer && <div className="show-awa"><div>打开「显示答案」查看作文</div></div>}
  645. </div>
  646. </div></div>
  647. );
  648. }
  649. renderAsk() {
  650. const { data = {}, empty = {} } = this.state;
  651. const { ask = {} } = data;
  652. const emptyAsk = empty.ask || {};
  653. return (
  654. <div className="question-modal ask">
  655. <div className="mask" />
  656. <div className="modal-body">
  657. <div className="modal-title">提问</div>
  658. <div className="modal-desc">
  659. <div className="select-inline">
  660. 我想对
  661. <Select
  662. excludeSelf
  663. size="small"
  664. theme="white"
  665. value={ask.target}
  666. list={AskTarget}
  667. onChange={item => {
  668. this.changeData('ask', 'target', item.value);
  669. }}
  670. />
  671. 进行提问
  672. </div>
  673. <div className="label">有疑问的具体内容是:</div>
  674. <Textarea
  675. className="textarea"
  676. value={ask.originContent}
  677. placeholder="请复制粘贴有疑问的内容。"
  678. empty={emptyAsk.originContent}
  679. onChange={e => {
  680. this.changeData('ask', 'originContent', e.target.value);
  681. }}
  682. />
  683. <div className="label">针对以上内容的问题是:</div>
  684. <Textarea
  685. className="textarea"
  686. value={ask.content}
  687. placeholder="提问频率高的问题会被优先回答哦。"
  688. empty={emptyAsk.content}
  689. onChange={e => {
  690. this.changeData('ask', 'content', e.target.value);
  691. }}
  692. />
  693. </div>
  694. <div className="bottom">
  695. <AnswerButton theme="cancel" size="lager" onClick={() => this.setState({ askModal: false })}>
  696. 取消
  697. </AnswerButton>
  698. <AnswerButton size="lager" onClick={() => this.submitAsk()}>
  699. 提交
  700. </AnswerButton>
  701. </div>
  702. </div>
  703. </div>
  704. );
  705. }
  706. renderAskOk() {
  707. return (
  708. <div className="question-modal ask-ok">
  709. <div className="mask" />
  710. <div className="modal-body">
  711. <div className="modal-title">提问</div>
  712. <div className="modal-content">
  713. <div className="left">
  714. <div className="text">已提交成功!</div>
  715. <div className="text">关注公众号,老师回答后会立即收到通知。</div>
  716. <div className="text">我们也会通过站内信的方式通知你。</div>
  717. <div className="small">
  718. 成为学员享受极速答疑特权。<Link to="">了解更多</Link>
  719. </div>
  720. </div>
  721. <div className="right">
  722. <Assets name={QrCodeAssets} width={150} height={150} />
  723. <div className="text">扫码关注公众号</div>
  724. <div className="text">千行GMAT</div>
  725. </div>
  726. </div>
  727. <div className="confirm">
  728. <AnswerButton
  729. size="lager"
  730. theme="confirm"
  731. onClick={() => {
  732. this.setState({ askOkModal: false });
  733. }}
  734. >
  735. 好的,知道了
  736. </AnswerButton>
  737. </div>
  738. </div>
  739. </div>
  740. );
  741. }
  742. renderAskFail() {
  743. return (
  744. <div className="question-modal ask-ok">
  745. <div className="mask" />
  746. <div className="modal-body">
  747. <div className="modal-title">提问</div>
  748. <div className="modal-content">
  749. <div className="left">
  750. <div className="text">提问功能正在维护中。</div>
  751. <div className="text">可先查阅“相关问答” 或 成为学员享受极速 答疑特权。</div>
  752. <Link to="/">了解更多></Link>
  753. </div>
  754. <div className="right">
  755. <Assets name={QrCodeAssets} width={150} height={150} />
  756. <div className="text">扫码关注公众号</div>
  757. <div className="text">千行GMAT</div>
  758. </div>
  759. </div>
  760. <div className="confirm">
  761. <AnswerButton
  762. size="lager"
  763. theme="confirm"
  764. onClick={() => {
  765. this.setState({ askFailModal: false });
  766. }}
  767. >
  768. 好的,知道了
  769. </AnswerButton>
  770. </div>
  771. </div>
  772. </div>
  773. );
  774. }
  775. renderFeedbackError() {
  776. const { data = {}, empty = {} } = this.state;
  777. const { feedback = {} } = data;
  778. const emptyFeedback = empty.feedback || {};
  779. if (!feedback.target) {
  780. feedback.target = AskTarget[0].value;
  781. }
  782. return (
  783. <div className="question-modal error">
  784. <div className="mask" />
  785. <div className="modal-body">
  786. <div className="modal-title">纠错</div>
  787. <div className="modal-desc">
  788. <div className="select-inline">
  789. 我想对
  790. <Select
  791. excludeSelf
  792. size="small"
  793. theme="white"
  794. value={feedback.target}
  795. list={AskTarget}
  796. onChange={item => {
  797. this.changeData('feedback', 'target', item.value);
  798. }}
  799. />
  800. 进行纠错
  801. </div>
  802. <div className="label">错误内容是:</div>
  803. <Textarea
  804. className="textarea"
  805. value={feedback.originContent}
  806. placeholder="你可以适当扩大复制范围以使我们准确定位,感谢。"
  807. empty={emptyFeedback.originContent}
  808. onChange={(e) => {
  809. this.changeData('feedback', 'originContent', e.target.value);
  810. }}
  811. />
  812. <div className="label">应该改为:</div>
  813. <Textarea
  814. className="textarea"
  815. value={feedback.content}
  816. placeholder="只需提供正确内容即可"
  817. empty={emptyFeedback.content}
  818. onChange={(e) => {
  819. this.changeData('feedback', 'content', e.target.value);
  820. }} />
  821. </div>
  822. <div className="bottom">
  823. <AnswerButton
  824. theme="cancel"
  825. size="lager"
  826. onClick={() => {
  827. this.setState({ feedbackModal: false });
  828. }}
  829. >
  830. 取消
  831. </AnswerButton>
  832. <AnswerButton
  833. size="lager"
  834. onClick={() => {
  835. this.submitFeedbackError();
  836. }}
  837. >
  838. 提交
  839. </AnswerButton>
  840. </div>
  841. </div>
  842. </div>
  843. );
  844. }
  845. renderFeedbackErrorOk() {
  846. return (
  847. <div className="question-modal error-ok">
  848. <div className="mask" />
  849. <div className="modal-body">
  850. <div className="modal-title">纠错</div>
  851. <div className="modal-content">
  852. <div className="left">
  853. <div className="text">
  854. <Assets name="right" svg />
  855. 已提交成功!
  856. </div>
  857. <div className="text">感谢您的耐心反馈,我们会尽快核实并以站内信的方式告知结果。</div>
  858. <div className="text">您也可以关注公众号及时获取结果。</div>
  859. </div>
  860. <div className="right">
  861. <Assets name={QrCodeAssets} width={150} height={150} />
  862. <div className="text">扫码关注公众号</div>
  863. <div className="text">千行GMAT</div>
  864. </div>
  865. </div>
  866. <div className="confirm">
  867. <AnswerButton
  868. size="lager"
  869. theme="confirm"
  870. onClick={() => {
  871. this.setState({ feedbackOkModal: false });
  872. }}
  873. >
  874. 好的,知道了
  875. </AnswerButton>
  876. </div>
  877. </div>
  878. </div>
  879. );
  880. }
  881. renderNote() {
  882. const { noteField, note = {} } = this.state;
  883. return (
  884. <div className="question-modal note">
  885. <div className="mask" />
  886. <div className="modal-body">
  887. <div className="modal-title">笔记</div>
  888. <div className="modal-content">
  889. <div className="tabs">
  890. {AskTarget.map(item => {
  891. return (
  892. <div
  893. className={`tab ${noteField === item.key ? 'active' : ''}`}
  894. onClick={() => {
  895. this.setState({ noteField: item.key });
  896. }}
  897. >
  898. <div className="text">{item.label}</div>
  899. <div className="date">{note[`${item.key}Time`] ? formatDate(note[`${item.key}Time`]) : ''}</div>
  900. </div>
  901. );
  902. })}
  903. </div>
  904. <div className="input">
  905. <Textarea
  906. className="textarea"
  907. value={note[`${noteField}Content`] || ''}
  908. placeholder="记下笔记,方便以后复习"
  909. onChange={e => {
  910. note[`${noteField}Time`] = new Date();
  911. note[`${noteField}Content`] = e.target.value;
  912. this.setState({ note });
  913. }}
  914. />
  915. <div className="bottom">
  916. <AnswerButton
  917. theme="cancel"
  918. size="lager"
  919. onClick={() => {
  920. this.setState({ noteModal: false });
  921. }}
  922. >
  923. 取消
  924. </AnswerButton>
  925. {/* <AnswerButton
  926. size="lager"
  927. onClick={() => {
  928. this.submitNote();
  929. }}
  930. >
  931. 编辑
  932. </AnswerButton> */}
  933. <AnswerButton
  934. size="lager"
  935. onClick={() => {
  936. this.submitNote();
  937. }}
  938. >
  939. 保存
  940. </AnswerButton>
  941. </div>
  942. </div>
  943. </div>
  944. </div>
  945. </div>
  946. );
  947. }
  948. }