index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. import React, { Component } from 'react';
  2. import './index.less';
  3. import Assets from '@src/components/Assets';
  4. import { formatSecond, formatPercent, formatSeconds } from '@src/services/Tools';
  5. import Icon from '../../../../components/Icon';
  6. import Button from '../../../../components/Button';
  7. import Switch from '../../../../components/Switch';
  8. import Tabs from '../../../../components/Tabs';
  9. import Progress from '../../../../components/Progress';
  10. import HardInput from '../../../../components/HardInput';
  11. import AnswerCheckbox from '../../../../components/AnswerCheckbox';
  12. import { SentenceOption } from '../../../../../Constant';
  13. import { Question } from '../../../../stores/question';
  14. export default class extends Component {
  15. constructor(props) {
  16. super(props);
  17. // 确保可以自身进行答案显示,外部也可以直接显示答案
  18. // 将props转入state
  19. this.state = {
  20. analysisTab: 'qx',
  21. focusKey: 'subject',
  22. scene: this.props.scene || 'answer',
  23. userQuestion: this.props.userQuestion,
  24. question: this.props.question,
  25. };
  26. const { question, userQuestion } = this.props;
  27. if (this.state.scene === 'answer') {
  28. this.state.stem = this.formatStem(question.stem, userQuestion.userAnswer, question.answer);
  29. } else {
  30. this.state.stem = question.stem;
  31. }
  32. }
  33. showAnswer() {
  34. const { userQuestion } = this.state;
  35. Question.getDetailById(userQuestion.id).then(result => {
  36. const { question } = result;
  37. this.setState({
  38. userQuestion: result,
  39. question: result.question,
  40. scene: 'answer',
  41. stem: this.formatStem(question.stem, result.userAnswer, question.answer),
  42. });
  43. });
  44. }
  45. addTarget(target) {
  46. const uuid = target.getAttribute('uuid');
  47. if (!uuid) return;
  48. const text = target.innerText;
  49. const { focusKey, answer = {}, question } = this.state;
  50. if (!answer[focusKey]) answer[focusKey] = [];
  51. if (answer[focusKey].filter(row => row.uuid === uuid).length > 0) return;
  52. answer[focusKey].push({
  53. text,
  54. uuid,
  55. });
  56. this.setState({
  57. answer,
  58. stem: this.formatStem(question.stem, answer),
  59. });
  60. }
  61. removeTarget(key, target) {
  62. const { answer = {}, question } = this.state;
  63. if (!answer[key]) return;
  64. answer[key] = answer[key].filter(row => row.uuid !== target.uuid);
  65. this.setState({
  66. answer,
  67. stem: this.formatStem(question.stem, answer),
  68. });
  69. }
  70. next() {
  71. const { flow } = this.props;
  72. const { scene } = this.state;
  73. if (scene === 'question') {
  74. const { answer } = this.state;
  75. flow.submit(answer).then(() => {
  76. this.showAnswer();
  77. });
  78. } else if (scene === 'answer') {
  79. flow.next();
  80. }
  81. }
  82. formatStem(stem, userAnswer, answer) {
  83. // userAnswer 添加蓝色字, 错误的添加红色背景
  84. // answer 添加绿色背景
  85. const uuidMap = {};
  86. const show = !!answer;
  87. answer = answer || {};
  88. userAnswer = userAnswer || {};
  89. Object.keys(userAnswer).forEach(key => {
  90. if (key === 'options') return;
  91. const u = userAnswer[key];
  92. const a = answer[key] && answer[key].length > 0 ? answer[key][0] : [];
  93. const map = {};
  94. a.forEach(row => {
  95. if (!uuidMap[row.uuid]) uuidMap[row.uuid] = [];
  96. uuidMap[row.uuid].push('true');
  97. map[row.uuid] = row;
  98. });
  99. u.forEach(row => {
  100. if (!uuidMap[row.uuid]) uuidMap[row.uuid] = [];
  101. uuidMap[row.uuid].push('user');
  102. if (show && !map[row.uuid]) uuidMap[row.uuid].push('false');
  103. });
  104. });
  105. Object.keys(uuidMap).forEach(uuid => {
  106. stem = stem.replace(`uuid='${uuid}'`, `uuid='${uuid}' class='${uuidMap[uuid].join(' ')}'`);
  107. });
  108. return stem;
  109. }
  110. renderHeader() {
  111. const { mode } = this.props;
  112. switch (mode) {
  113. case 'process':
  114. return this.renderProcessHeader();
  115. default:
  116. return this.renderQuestionHeader();
  117. }
  118. }
  119. renderProcessHeader() {
  120. const { userQuestion, singleTime, paper, flow } = this.props;
  121. return (
  122. <div className="layout-header">
  123. <div className="left">
  124. <div className="title">{paper.title}</div>
  125. </div>
  126. <div className="right">
  127. <div className="text">
  128. <Assets name="timecost_icon" />
  129. Time cost {formatSecond(userQuestion.userTime || singleTime)}
  130. </div>
  131. <Icon name="star" active={userQuestion.collect} onClick={() => flow.toggleCollect()} />
  132. </div>
  133. </div>
  134. );
  135. }
  136. renderQuestionHeader() {
  137. const { userQuestion, questionNo, paper, flow } = this.props;
  138. return (
  139. <div className="layout-header">
  140. <div className="left">
  141. <div className="title">{paper.title}</div>
  142. </div>
  143. <div className="right">
  144. <span className="b">
  145. 用时:
  146. <span
  147. dangerouslySetInnerHTML={{
  148. __html: formatSeconds(userQuestion.userTime).replace(/([0-9]+)(m|min|h|hour|s)/g, '<span class="s">$1</span>$2'),
  149. }}
  150. />
  151. {/* 用时:<span className="s">1</span>m<span className="s">39</span>s */}
  152. </span>
  153. <span className="b">
  154. 全站:
  155. <span
  156. dangerouslySetInnerHTML={{
  157. __html: formatSeconds(questionNo.totalTime / questionNo.totalNumber).replace(
  158. /([0-9]+)(m|min|h|hour|s)/g,
  159. '<span class="s">$1</span>$2',
  160. ),
  161. }}
  162. />
  163. {/* 全站:<span className="s">1</span>m<span className="s">39</span>s */}
  164. </span>
  165. <span className="b">
  166. <span className="s">{formatPercent(questionNo.totalCorrect, questionNo.totalNumber)}</span>%
  167. </span>
  168. <Icon name="star" active={userQuestion.collect} onClick={() => flow.toggleCollect()} />
  169. </div>
  170. </div>
  171. );
  172. }
  173. render() {
  174. const { flow, paper, userQuestion } = this.props;
  175. return (
  176. <div id="paper-process-sentence">
  177. <div className="layout">
  178. {this.renderHeader()}
  179. {this.renderBody()}
  180. <div className="layout-footer">
  181. <div className="left">
  182. <Icon
  183. name={this.props.isFullscreenEnabled ? 'sceen-restore' : 'sceen-full'}
  184. onClick={() => flow.toggleFullscreen()}
  185. />
  186. </div>
  187. <div className="center">
  188. <div className="p">
  189. <Progress theme="theme" progress={formatPercent(userQuestion.no, paper.questionNumber)} />
  190. </div>
  191. <div className="t">
  192. {userQuestion.no}/{paper.questionNumber}
  193. </div>
  194. </div>
  195. <div className="right">
  196. <Button
  197. size="lager"
  198. radius
  199. onClick={() => {
  200. this.next();
  201. }}
  202. >
  203. Next <Assets name="next_icon" />
  204. </Button>
  205. </div>
  206. </div>
  207. </div>
  208. </div>
  209. );
  210. }
  211. renderBody() {
  212. const { scene } = this.state;
  213. switch (scene) {
  214. case 'question':
  215. return this.renderQuestion();
  216. case 'answer':
  217. return this.renderAnswer();
  218. default:
  219. return null;
  220. }
  221. }
  222. renderQuestion() {
  223. const { question } = this.props;
  224. const { focusKey, answer = {} } = this.state;
  225. const { stem } = question;
  226. return (
  227. <div className="layout-body">
  228. <div className="title">
  229. <Icon name="question" />
  230. 请分别找出句子中的主语,谓语和宾语,并做出逻辑关系判断。
  231. </div>
  232. <div
  233. className="desc"
  234. dangerouslySetInnerHTML={{ __html: stem }}
  235. onClick={e => {
  236. this.addTarget(e.target);
  237. }}
  238. />
  239. <div className="label">主语</div>
  240. <div className="input">
  241. <HardInput
  242. focus={focusKey === 'subject'}
  243. list={answer.subject || []}
  244. onClick={() => {
  245. this.setState({ focusKey: 'subject' });
  246. }}
  247. onDelete={item => {
  248. this.removeTarget('subject', item);
  249. }}
  250. />
  251. </div>
  252. <div className="label">谓语</div>
  253. <div className="input">
  254. <HardInput
  255. focus={focusKey === 'predicate'}
  256. list={answer.predicate || []}
  257. onClick={() => {
  258. this.setState({ focusKey: 'predicate' });
  259. }}
  260. onDelete={item => {
  261. this.removeTarget('predicate', item);
  262. }}
  263. />
  264. </div>
  265. <div className="label">宾语</div>
  266. <div className="input">
  267. <HardInput
  268. focus={focusKey === 'object'}
  269. list={answer.object || []}
  270. onClick={() => {
  271. this.setState({ focusKey: 'object' });
  272. }}
  273. onDelete={item => {
  274. this.removeTarget('object', item);
  275. }}
  276. />
  277. </div>
  278. <div className="select">
  279. <div className="select-title">本句存在以下哪种逻辑关系?(可多选)</div>
  280. <AnswerCheckbox
  281. list={SentenceOption}
  282. selected={answer.options}
  283. onChange={values => {
  284. answer.options = values;
  285. this.setState({ answer });
  286. }}
  287. />
  288. </div>
  289. </div>
  290. );
  291. }
  292. renderAnswer() {
  293. const { mode, question, userQuestion } = this.props;
  294. const { analysisTab, showAnswer } = this.state;
  295. const { userAnswer = {} } = userQuestion;
  296. const { answer } = question;
  297. const { stem } = question;
  298. return (
  299. <div className="layout-body">
  300. {mode === 'question' ? (
  301. <Switch
  302. checked={showAnswer}
  303. onChange={value => {
  304. this.setState({ showAnswer: value });
  305. }}
  306. >
  307. {showAnswer ? '显示答案' : '关闭答案'}
  308. </Switch>
  309. ) : (<div className="title">
  310. <Icon name="question" />
  311. 请分别找出句子中的主语,谓语和宾语,并做出逻辑关系判断。
  312. </div>)}
  313. <div className="desc" dangerouslySetInnerHTML={{ __html: stem }} />
  314. <div className="label">主语</div>
  315. <div className="input">
  316. <HardInput show={showAnswer} list={userAnswer.subject || []} answer={answer.subject} />
  317. </div>
  318. <div className="label">谓语</div>
  319. <div className="input">
  320. <HardInput show={showAnswer} list={userAnswer.predicate || []} answer={answer.predicate} />
  321. </div>
  322. <div className="label">宾语</div>
  323. <div className="input">
  324. <HardInput show={showAnswer} list={userAnswer.object || []} answer={answer.object} />
  325. </div>
  326. <div className="select">
  327. <div className="select-title">本句存在以下哪种逻辑关系?(可多选)</div>
  328. <AnswerCheckbox
  329. show={showAnswer}
  330. list={SentenceOption}
  331. selected={userAnswer.options}
  332. answer={answer.options}
  333. />
  334. </div>
  335. {showAnswer && (
  336. <div className="analysis">
  337. <Tabs
  338. type="division"
  339. active={analysisTab}
  340. tabs={[{ key: 'qx', name: '解析详情' }, { key: 'chinese', name: '中文语意' }]}
  341. onChange={key => {
  342. this.setState({ analysisTab: key });
  343. }}
  344. />
  345. {this.renderText()}
  346. </div>
  347. )}
  348. </div>
  349. );
  350. }
  351. renderText() {
  352. const { analysisTab, question = {} } = this.state;
  353. let content;
  354. switch (analysisTab) {
  355. case 'chinese':
  356. content = (
  357. <div className="detail-block text-block" dangerouslySetInnerHTML={{ __html: question.chineseContent }} />
  358. );
  359. break;
  360. case 'qx':
  361. content = <div className="detail-block text-block u-s-n" dangerouslySetInnerHTML={{ __html: question.qxContent }} />;
  362. break;
  363. default:
  364. break;
  365. }
  366. return content;
  367. }
  368. }