page.js 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491
  1. import React from 'react';
  2. // import { Link } from 'react-router-dom';
  3. import './index.less';
  4. import LineChart from '@src/components/LineChart';
  5. import BarChart from '@src/components/BarChart';
  6. import PieChart from '@src/components/PieChart';
  7. import Assets from '@src/components/Assets';
  8. import Page from '@src/containers/Page';
  9. import { formatDate, formatPercent, formatSeconds, formatMinute, formatSecond, formatMinuteSecond, getMap } from '@src/services/Tools';
  10. import { Icon, Tooltip, Typography } from 'antd';
  11. import { Question } from '../../../stores/question';
  12. import { Button } from '../../../components/Button';
  13. import Tabs from '../../../components/Tabs';
  14. import { Icon as GIcon } from '../../../components/Icon';
  15. import { QuestionNoteModal } from '../../../components/OtherModal';
  16. import { My } from '../../../stores/my';
  17. import {
  18. QuestionDifficult,
  19. ExaminationQuestionType,
  20. ExaminationSubject,
  21. } from '../../../../Constant';
  22. const QuestionDifficultMap = getMap(QuestionDifficult, 'value', 'label');
  23. const QuestionDifficultSort = getMap(QuestionDifficult, 'value', 'sort');
  24. function BarOption3(titles, source, data1, data2, color1, color2) {
  25. return {
  26. title: [
  27. {
  28. text: titles[0],
  29. textStyle: { fontSize: 24, fontWeight: 'bold', color: '#686872' },
  30. left: 100,
  31. top: 15,
  32. },
  33. {
  34. text: titles[1],
  35. textStyle: { fontSize: 24, fontWeight: 'bold', color: '#686872' },
  36. left: 195,
  37. top: 15,
  38. },
  39. {
  40. text: titles[2],
  41. textStyle: { fontSize: 24, fontWeight: 'bold', color: '#686872' },
  42. left: 695,
  43. top: 15,
  44. },
  45. ],
  46. grid: [{ width: 250, x: 200, bottom: 30 }, { width: 250, x: 700, bottom: 30 }],
  47. xAxis: [
  48. {
  49. gridIndex: 0,
  50. show: false,
  51. axisTick: { show: false },
  52. axisLine: { show: false },
  53. splitLine: { show: false },
  54. },
  55. {
  56. gridIndex: 1,
  57. show: false,
  58. axisTick: { show: false },
  59. axisLine: { show: false },
  60. splitLine: { show: false },
  61. },
  62. ],
  63. yAxis: [
  64. {
  65. gridIndex: 0,
  66. type: 'category',
  67. axisTick: { show: false },
  68. axisLine: { show: false },
  69. splitLine: { show: false },
  70. offset: 15,
  71. data: source,
  72. axisLabel: { color: '#686872', fontSize: 16 },
  73. },
  74. {
  75. gridIndex: 1,
  76. type: 'category',
  77. axisTick: { show: false },
  78. axisLine: { show: false },
  79. splitLine: { show: false },
  80. axisLabel: { show: false },
  81. },
  82. ],
  83. series: [
  84. {
  85. type: 'bar',
  86. xAxisIndex: 0,
  87. yAxisIndex: 0,
  88. barWidth: 30,
  89. data: data1.map((item, index) => ({
  90. value: formatPercent(item[0], item[1], true),
  91. itemStyle: { color: index % 2 ? color1[0] : color1[1] },
  92. label: {
  93. show: true,
  94. color: '#303036',
  95. align: 'right',
  96. position: [360, 5],
  97. fontSize: 16,
  98. formatter: `${item[0]}/${item[1]} ${formatPercent(item[0], item[1], false)}`,
  99. },
  100. })),
  101. },
  102. {
  103. type: 'bar',
  104. xAxisIndex: 1,
  105. yAxisIndex: 1,
  106. barWidth: 30,
  107. data: data2.map((item, index) => ({
  108. value: parseInt(item, 10),
  109. itemStyle: { color: index % 2 ? color2[0] : color2[1] },
  110. label: {
  111. show: true,
  112. color: '#303036',
  113. align: 'right',
  114. fontSize: 16,
  115. position: [360, 5],
  116. formatter: formatMinuteSecond(parseInt(item, 10)),
  117. },
  118. })),
  119. },
  120. ],
  121. };
  122. }
  123. function BarOption2(title, data, legend, color) {
  124. return {
  125. title: {
  126. text: title,
  127. textStyle: { fontSize: 24, fontWeight: 'bold', color: '#686872' },
  128. },
  129. tooltip: {
  130. trigger: 'item',
  131. formatter: (params) => {
  132. return `${params.value[params.seriesIndex + 1]}%`;
  133. },
  134. },
  135. legend: {
  136. show: legend.length > 1,
  137. data: legend,
  138. right: 20,
  139. },
  140. color,
  141. dataset: {
  142. source: [['type', ...legend], ...data],
  143. },
  144. grid: { left: 30, right: 30, height: 300 },
  145. xAxis: {
  146. type: 'category',
  147. axisLabel: { color: '#686872' },
  148. axisLine: { lineStyle: { color: '#D1D6DF' } },
  149. },
  150. yAxis: {
  151. type: 'value',
  152. min: 0,
  153. max: 100,
  154. axisLabel: { color: '#686872' },
  155. axisLine: { lineStyle: { color: '#D1D6DF' } },
  156. },
  157. series: legend.map(() => ({
  158. type: 'bar',
  159. barWidth: 40,
  160. })),
  161. };
  162. }
  163. function lineOption1(title, data, legend, color, yMax = 300) {
  164. return {
  165. title: {
  166. text: title,
  167. textStyle: { fontSize: 24, fontWeight: 'bold', color: '#686872' },
  168. left: '0',
  169. },
  170. tooltip: {
  171. trigger: 'item',
  172. formatter: (params) => {
  173. return formatSeconds(params.value[params.seriesIndex + 1]);
  174. },
  175. position: (point) => {
  176. return [point[0] + 5, point[1] + 5];
  177. },
  178. },
  179. legend: {
  180. show: legend.length > 1,
  181. data: legend,
  182. right: 20,
  183. },
  184. color,
  185. grid: { left: 30, right: 30, height: 300 },
  186. xAxis: {
  187. type: 'category',
  188. axisLabel: { color: '#686872' },
  189. axisLine: { lineStyle: { color: '#D1D6DF' } },
  190. },
  191. yAxis: {
  192. type: 'value',
  193. min: 0,
  194. max: yMax,
  195. axisLabel: { color: '#686872' },
  196. axisLine: { lineStyle: { color: '#D1D6DF' } },
  197. },
  198. dataset: {
  199. source: [['type', ...legend], ...data],
  200. },
  201. series: legend.map(() => ({
  202. type: 'line',
  203. smooth: true,
  204. symbol: 'circle',
  205. })),
  206. };
  207. }
  208. function barOption1(title, value, allValue, avgCorrect, avgIncorrent) {
  209. value = parseInt(value, 10);
  210. allValue = parseInt(allValue, 10);
  211. avgCorrect = parseInt(avgCorrect, 10);
  212. avgIncorrent = parseInt(avgIncorrent, 10);
  213. const xAxis1 = [
  214. {
  215. gridIndex: 0,
  216. type: 'category',
  217. axisTick: { show: false },
  218. axisLine: { lineStyle: { color: '#D1D6DF' } },
  219. splitLine: { show: false },
  220. },
  221. {
  222. gridIndex: 1,
  223. type: 'category',
  224. axisTick: { show: false },
  225. axisLine: { lineStyle: { color: '#D1D6DF' } },
  226. splitLine: { show: false },
  227. data: [
  228. {
  229. value: 'Avg Time\nCorrect',
  230. textStyle: { color: '#686872', fontWeight: '500', fontSize: 14, lineHeight: 20 },
  231. },
  232. {
  233. value: 'Avg Time\nIncorrect',
  234. textStyle: { color: '#686872', fontWeight: '500', fontSize: 14, lineHeight: 20 },
  235. },
  236. ],
  237. },
  238. ];
  239. // const xAxis2 = {
  240. // type: 'category',
  241. // axisTick: { show: false },
  242. // axisLine: { lineStyle: { color: '#D1D6DF' } },
  243. // splitLine: { show: false },
  244. // };
  245. const yAxis1 = [
  246. {
  247. gridIndex: 0,
  248. show: false,
  249. min: 0,
  250. max: 300,
  251. axisTick: { show: false },
  252. axisLine: { show: false },
  253. splitLine: { show: false },
  254. },
  255. {
  256. gridIndex: 1,
  257. show: false,
  258. min: 0,
  259. max: 300,
  260. axisTick: { show: false },
  261. axisLine: { show: false },
  262. splitLine: { show: false },
  263. },
  264. ];
  265. // const yAxis2 = {
  266. // show: false,
  267. // min: 0,
  268. // max: 300,
  269. // axisTick: { show: false },
  270. // axisLine: { show: false },
  271. // splitLine: { show: false },
  272. // };
  273. const data1 = {
  274. type: 'bar',
  275. barWidth: 50,
  276. xAxisIndex: 0,
  277. yAxisIndex: 0,
  278. data: [
  279. {
  280. value,
  281. itemStyle: { color: '#7AA7DC' },
  282. label: {
  283. show: true,
  284. position: 'top',
  285. formatter: `{a|${formatSeconds(value)}}`,
  286. rich: { a: { fontSize: 16, fontWeight: 'bold', color: '#686872' } },
  287. },
  288. },
  289. {
  290. value: allValue,
  291. itemStyle: { color: '#598FCF' },
  292. label: {
  293. show: true,
  294. position: 'top',
  295. formatter: `{a|全站${formatSeconds(allValue)}}`,
  296. rich: { a: { fontSize: 12, color: '#686872' } },
  297. },
  298. },
  299. ],
  300. };
  301. const data2 = {
  302. type: 'bar',
  303. barWidth: 50,
  304. xAxisIndex: 1,
  305. yAxisIndex: 1,
  306. data: [
  307. {
  308. value: avgCorrect,
  309. name: 'Avg Time\nCorrect',
  310. itemStyle: { color: '#7775CA' },
  311. label: {
  312. show: true,
  313. position: 'top',
  314. formatter: `{a|${formatSeconds(avgCorrect)}}`,
  315. rich: { a: { fontSize: 16, fontWeight: 'bold', color: '#686872' } },
  316. },
  317. },
  318. {
  319. value: avgIncorrent,
  320. name: 'Avg Time\nIncorrect',
  321. itemStyle: { color: '#9396C9' },
  322. label: {
  323. show: true,
  324. position: 'top',
  325. formatter: `{a|${formatSeconds(avgIncorrent)}}`,
  326. rich: { a: { fontSize: 16, fontWeight: 'bold', color: '#686872' } },
  327. },
  328. },
  329. ],
  330. };
  331. return {
  332. title: [
  333. {
  334. text: title,
  335. textStyle: { fontSize: 24, fontWeight: 'bold', color: '#686872' },
  336. left: '0',
  337. },
  338. {
  339. text: 'Avg Time\nTotal',
  340. textAlign: 'center',
  341. textVerticalAlign: 'middle',
  342. textStyle: { color: '#686872', fontWeight: '500', fontSize: 14, lineHeight: 20 },
  343. bottom: '2%',
  344. left: '28%',
  345. },
  346. ],
  347. xAxis: xAxis1, // : xAxis2,
  348. yAxis: yAxis1, // : yAxis2,
  349. grid: [{ width: 200, x: 72 }, { width: 350, x: 272 }], // : { width: 200, left: '30%' },
  350. series: [data1, data2], // : [data1],
  351. };
  352. }
  353. function pieOption1(title, userCorrect, userNumber, totalCorrect, totalNumber) {
  354. const value = formatPercent(userCorrect, userNumber);
  355. const allValue = formatPercent(totalCorrect, totalNumber);
  356. return {
  357. title: [
  358. {
  359. text: title,
  360. textStyle: { fontSize: 24, fontWeight: 'bold', color: '#686872' },
  361. left: '0',
  362. },
  363. {
  364. text: `${value}%`,
  365. textAlign: 'center',
  366. textVerticalAlign: 'middle',
  367. textStyle: { color: value < 50 ? '#f19057' : '#7AA7DC', fontSize: 45 },
  368. subtext: `${userCorrect}/${userNumber}`,
  369. subtextStyle: { color: '#686872', fontSize: 16 },
  370. top: '35%',
  371. left: '49%',
  372. },
  373. ],
  374. series: [
  375. {
  376. type: 'pie',
  377. radius: ['64%', '70%'],
  378. label: {
  379. show: false,
  380. },
  381. hoverAnimation: false,
  382. animation: false,
  383. data: [
  384. {
  385. value: allValue,
  386. itemStyle: { color: '#7775CA' },
  387. label: {
  388. show: true,
  389. position: 'outside',
  390. formatter: `{a|全站用户}:{b|${allValue}%}`,
  391. rich: {
  392. a: {
  393. color: '#686872',
  394. fontSize: 16,
  395. },
  396. b: {
  397. color: '#6865FD',
  398. fontSize: 16,
  399. },
  400. },
  401. },
  402. },
  403. {
  404. value: 100 - allValue,
  405. itemStyle: { color: '#e1e1e1' },
  406. emphasis: { itemStyle: { color: '#e1e1e1' } },
  407. },
  408. ],
  409. },
  410. {
  411. type: 'pie',
  412. radius: ['45%', '61%'],
  413. label: {
  414. show: false,
  415. },
  416. hoverAnimation: false,
  417. animation: false,
  418. data: [
  419. { value, itemStyle: { color: value < 50 ? '#f19057' : '#7AA7DC' } },
  420. { value: 100 - value, itemStyle: { color: '#e1e1e1' }, emphasis: { itemStyle: { color: '#e1e1e1' } } },
  421. ],
  422. },
  423. ],
  424. };
  425. }
  426. function pieOption2(title, value1, value2, value3, total) {
  427. return {
  428. title: [
  429. {
  430. text: title,
  431. textStyle: { fontSize: 24, fontWeight: 'bold', color: '#686872' },
  432. left: '0',
  433. },
  434. {
  435. text: `${total}`,
  436. textAlign: 'center',
  437. textVerticalAlign: 'middle',
  438. textStyle: { color: '#686872', fontSize: 60 },
  439. subtext: '综合实力',
  440. subtextStyle: { color: '#686872', fontSize: 14 },
  441. top: '35%',
  442. left: '49.5%',
  443. },
  444. ],
  445. series: [
  446. {
  447. type: 'pie',
  448. radius: ['50%', '80%'],
  449. startAngle: 30,
  450. hoverAnimation: false,
  451. animation: false,
  452. data: [
  453. {
  454. value: value1,
  455. itemStyle: { color: '#6865FD' },
  456. label: {
  457. position: 'outside',
  458. formatter: `{a|逻辑关系} {b|${value1}}`,
  459. rich: {
  460. a: {
  461. color: '#686872',
  462. fontSize: 18,
  463. },
  464. b: {
  465. color: '#6865FD',
  466. fontSize: 18,
  467. },
  468. },
  469. },
  470. },
  471. {
  472. value: value2,
  473. itemStyle: { color: '#6EC28D' },
  474. label: {
  475. position: 'outside',
  476. formatter: `{a|句子结构} {b|${value2}}`,
  477. rich: {
  478. a: {
  479. color: '#686872',
  480. fontSize: 18,
  481. },
  482. b: {
  483. color: '#6EC28D',
  484. fontSize: 18,
  485. },
  486. },
  487. },
  488. },
  489. {
  490. value: value3,
  491. itemStyle: { color: '#598FCF' },
  492. label: {
  493. position: 'outside',
  494. formatter: `{a|阅读速度} {b|${value3}}`,
  495. rich: {
  496. a: {
  497. color: '#686872',
  498. fontSize: 18,
  499. },
  500. b: {
  501. color: '#598FCF',
  502. fontSize: 18,
  503. },
  504. },
  505. },
  506. },
  507. ],
  508. },
  509. ],
  510. };
  511. }
  512. export default class extends Page {
  513. initState() {
  514. return { tab: 'main', report: { paperModule: '' } };
  515. }
  516. initData() {
  517. const { id } = this.params;
  518. const { info = '' } = this.state.search;
  519. Question.detailReport(id).then(result => {
  520. switch (result.paperModule) {
  521. case 'sentence':
  522. this.refreshSentence(result);
  523. break;
  524. case 'textbook':
  525. this.refreshTextbook(result);
  526. break;
  527. case 'exercise':
  528. this.refreshExercise(result);
  529. break;
  530. case 'examination':
  531. this.refreshExamination(result);
  532. break;
  533. default:
  534. }
  535. this.setState({ report: result, paper: result.paper });
  536. return result;
  537. })
  538. .then(report => {
  539. switch (info) {
  540. case 'question':
  541. // 题目回顾列表
  542. Question.questionReport(id).then(result => {
  543. switch (report.paperModule) {
  544. case 'sentence':
  545. result = result.map((row) => {
  546. row.struct = row.detail.subject && row.detail.predicate && row.detail.object ? 0 : 1;
  547. row.logic = row.detail.options ? 0 : 1;
  548. row.note = row.note ? 1 : 0;
  549. row.collect = row.collect ? 1 : 0;
  550. return row;
  551. });
  552. break;
  553. case 'textbook':
  554. case 'exercise':
  555. result = result.map((row) => {
  556. row.correct = row.isCorrect ? 0 : 1;
  557. row.difficult = QuestionDifficultSort[row.question.difficult];
  558. row.place = row.question.place;
  559. row.note = row.note ? 1 : 0;
  560. row.collect = row.collect ? 1 : 0;
  561. return row;
  562. });
  563. this.refreshExercise(result);
  564. break;
  565. default:
  566. }
  567. this.setState({ list: result });
  568. });
  569. break;
  570. default:
  571. break;
  572. }
  573. });
  574. }
  575. refreshSentence() {
  576. const { info = '' } = this.state.search;
  577. switch (info) {
  578. case 'question':
  579. break;
  580. default:
  581. }
  582. }
  583. refreshTextbook() {
  584. this.refreshExercise();
  585. }
  586. refreshExamination() {
  587. const { info = '' } = this.state.search;
  588. switch (info) {
  589. case 'score':
  590. break;
  591. default:
  592. }
  593. }
  594. refreshExercise() {
  595. const { info = '' } = this.state.search;
  596. switch (info) {
  597. case 'question':
  598. break;
  599. default:
  600. }
  601. }
  602. questionSort(field) {
  603. let { order } = this.state;
  604. const { list = [] } = this.state;
  605. if (order === field) {
  606. order = 'no';
  607. // direction = 'asc';
  608. } else {
  609. order = field;
  610. // direction = 'desc';
  611. }
  612. list.sort((b, a) => {
  613. const aValue = a[order];
  614. const bValue = b[order];
  615. if (aValue === bValue) {
  616. return b.no - a.no;
  617. }
  618. if (order === 'no') {
  619. return b.no - a.no;
  620. }
  621. return bValue > aValue ? -1 : 1;
  622. });
  623. // if (direction === 'desc') {
  624. // list.reverse();
  625. // }
  626. this.setState({ order, list });
  627. }
  628. toggleCollect(index) {
  629. const { list } = this.state;
  630. const userQuestion = list[index];
  631. if (!userQuestion.collect) {
  632. My.addQuestionCollect(userQuestion.questionNo.id).then(() => {
  633. userQuestion.collect = true;
  634. this.setState({ list });
  635. });
  636. } else {
  637. My.delQuestionCollect(userQuestion.questionNo.id).then(() => {
  638. userQuestion.collect = false;
  639. this.setState({ list });
  640. });
  641. }
  642. }
  643. note(index) {
  644. const { list } = this.state;
  645. const userQuestion = list[index];
  646. const { questionNo } = userQuestion;
  647. My.getQuestionNote(questionNo.id)
  648. .then(result => {
  649. this.setState({ questionNo, note: result || {}, showNote: true, index });
  650. });
  651. }
  652. renderView() {
  653. const { report = {}, search = {} } = this.state;
  654. const { info } = search;
  655. switch (report.paperModule) {
  656. case 'sentence':
  657. if (info === 'question') {
  658. return this.renderSentenceQuestion();
  659. }
  660. return this.renderSentence();
  661. case 'textbook':
  662. if (info === 'question') {
  663. return this.renderExerciseQuestion();
  664. }
  665. return this.renderTextbook();
  666. case 'exercise':
  667. if (info === 'question') {
  668. return this.renderExerciseQuestion();
  669. }
  670. return this.renderExercise();
  671. case 'examination':
  672. return this.renderExamination();
  673. default:
  674. return <div />;
  675. }
  676. }
  677. renderSentence() {
  678. const { paper = {}, report = {}, search = {} } = this.state;
  679. const { info } = search;
  680. const { user } = this.props;
  681. return (
  682. <div className="sentence">
  683. <div className="header">
  684. <div className="content">
  685. <div className="title">Report for 「{report.paperModule === 'examination' ? '模考' : '练习'}」{paper.title}</div>
  686. <div className="btns">
  687. <Button size="small" radius onClick={() => {
  688. linkTo('/');
  689. }}>返回首页</Button>
  690. {!info && <Button size="small" radius onClick={() => {
  691. linkTo(`/paper/report/${report.id}?info=question`);
  692. }}>题目回顾</Button>}
  693. {info === 'question' && <Button size="small" radius onClick={() => {
  694. linkTo(`/paper/report/${report.id}`);
  695. }}>详细报告</Button>}
  696. </div>
  697. <div className="right">
  698. <div className="text">{user.info.nickname}</div>
  699. <div className="desc">练习次数{report.times}</div>
  700. <div className="desc">{formatDate(report.updateTime, 'YYYY-MM-DD HH:mm:ss')}</div>
  701. </div>
  702. </div>
  703. </div>
  704. {info === 'question' && this.renderSentenceQuestion()}
  705. {!info && this.renderSentenceDetail()}
  706. </div>
  707. );
  708. }
  709. renderTextbook() {
  710. return this.renderExercise();
  711. }
  712. renderExercise() {
  713. const { paper = {}, report = {}, search = {} } = this.state;
  714. const { info } = search;
  715. const { user } = this.props;
  716. return (
  717. <div className="exercise">
  718. <div className="header">
  719. <div className="content">
  720. <div className="title">Report for「练习」{paper.title}</div>
  721. <div className="btns">
  722. <Button size="small" radius onClick={() => {
  723. linkTo('/');
  724. }}>返回首页</Button>
  725. {!info && <Button size="small" radius onClick={() => {
  726. linkTo(`/paper/report/${report.id}?info=question`);
  727. }}>题目回顾</Button>}
  728. {info === 'question' && <Button size="small" radius onClick={() => {
  729. linkTo(`/paper/report/${report.id}`);
  730. }}>详细报告</Button>}
  731. </div>
  732. <div className="right">
  733. <div className="text">{user.info.nickname}</div>
  734. <div className="desc">练习次数{report.times}</div>
  735. <div className="desc">{formatDate(report.updateTime, 'YYYY-MM-DD HH:mm:ss')}</div>
  736. </div>
  737. </div>
  738. </div>
  739. {info === 'question' && this.renderExerciseQuestion()}
  740. {!info && this.renderExerciseDetail()}
  741. </div>
  742. );
  743. }
  744. renderExamination() {
  745. const { paper = {}, report = {}, search = {} } = this.state;
  746. const { info } = search;
  747. const { user } = this.props;
  748. return (
  749. <div className="examination">
  750. <div className="header">
  751. <div className="content">
  752. <div className="title">Report for 「{report.paperModule === 'examination' ? '模考' : '练习'}」{paper.title}</div>
  753. <div className="btns">
  754. <Button size="small" radius onClick={() => {
  755. linkTo('/');
  756. }}>返回首页</Button>
  757. {!info && <Button size="small" radius onClick={() => {
  758. linkTo(`/paper/report/${report.id}?info=score`);
  759. }}>成绩单</Button>}
  760. {info === 'score' && <Button size="small" radius onClick={() => {
  761. linkTo(`/paper/report/${report.id}`);
  762. }}>详细报告</Button>}
  763. </div>
  764. <div className="right">
  765. <div className="text">{user.info.nickname}</div>
  766. <div className="desc">练习次数{report.times}</div>
  767. <div className="desc">{formatDate(report.updateTime, 'YYYY-MM-DD HH:mm:ss')}</div>
  768. </div>
  769. </div>
  770. </div>
  771. {info === 'score' && this.renderExaminationScore()}
  772. {!info && this.renderExaminationDetail()}
  773. </div>
  774. );
  775. }
  776. renderSentenceQuestion() {
  777. const { report, list, order, showNote, note = {}, questionNo = {} } = this.state;
  778. return <div className='sentence question'>
  779. <div className='header'>
  780. <div className='content'>
  781. <div className='title'>题目回顾</div>
  782. <Button className='back' radius onClick={() => {
  783. linkTo(`/paper/report/${report.id}`);
  784. }}>返回长难句报告</Button>
  785. </div>
  786. </div>
  787. <div className='body'>
  788. <div className='content'>
  789. <div className='tip'><Assets name='notice' />点击题目查看详情</div>
  790. <table>
  791. <thead>
  792. <tr>
  793. <th>序号</th>
  794. <th width='420'>题目</th>
  795. <th className="point" onClick={() => {
  796. this.questionSort('struct');
  797. }}>句子结构<GIcon name={order === 'struct' ? 'arrow-down' : 'arrow-up'} active={order === 'struct'} /></th>
  798. <th className="point" onClick={() => {
  799. this.questionSort('logic');
  800. }}>逻辑关系<GIcon name={order === 'logic' ? 'arrow-down' : 'arrow-up'} active={order === 'logic'} /></th>
  801. <th className="point" onClick={() => {
  802. this.questionSort('userTime');
  803. }}>用时<GIcon name={order === 'userTime' ? 'arrow-down' : 'arrow-up'} active={order === 'userTime'} /></th>
  804. <th className="point" onClick={() => {
  805. this.questionSort('collect');
  806. }}>收藏<GIcon name={order === 'collect' ? 'arrow-down' : 'arrow-up'} active={order === 'collect'} /></th>
  807. <th className="point" onClick={() => {
  808. this.questionSort('note');
  809. }}>笔记<GIcon name={order === 'note' ? 'arrow-down' : 'arrow-up'} active={order === 'note'} /></th>
  810. </tr>
  811. </thead>
  812. <tbody>
  813. {(list || []).map((row, index) => {
  814. return <tr>
  815. <td>{row.no}</td>
  816. <td>
  817. <div className='n'><a href={`/paper/question/${row.id}`} target="_blank">{(row.questionNo || {}).title}</a></div>
  818. <div className='desc'>{row.question.description}</div>
  819. </td>
  820. <td><GIcon name={row.detail.subject && row.detail.predicate && row.detail.object ? 'right' : 'error'} noHover /></td>
  821. <td><GIcon name={row.detail.options ? 'right' : 'error'} noHover /></td>
  822. <td>{formatMinuteSecond(row.userTime)}</td>
  823. <td><GIcon name='star' active={row.collect} onClick={() => this.toggleCollect(index)} /></td>
  824. <td><GIcon name='note' active={row.note} onClick={() => this.note(index)} /></td>
  825. </tr>;
  826. })}
  827. </tbody>
  828. </table>
  829. </div>
  830. </div>
  831. <QuestionNoteModal show={showNote} defaultData={note} questionNo={questionNo} onConfirm={() => {
  832. list[this.state.index].note = true;
  833. this.setState({ showNote: false, list });
  834. }} onCancel={() => this.setState({ showNote: false })} />
  835. </div>;
  836. }
  837. renderSentenceDetail() {
  838. const { report = {} } = this.state;
  839. const { detail = {} } = report;
  840. return <div>
  841. <div className="body">
  842. <div className="content">
  843. <div className="title">完成情况</div>
  844. <div className="detail">
  845. <div className="block">
  846. <div className="t1">总耗时</div>
  847. <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(detail.info.userTime).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
  848. </div>
  849. {detail.info.userTime > detail.info.time && <div className="block">
  850. <div className="t1">超出建议用时</div>
  851. <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(detail.info.userTime - detail.info.time).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
  852. </div>}
  853. <div className="line" />
  854. <div className="block">
  855. <div className="t1">完成题目</div>
  856. <div className="t2">{detail.info.userNumber}</div>
  857. <div className="t3">题</div>
  858. </div>
  859. {detail.info.userNumber !== detail.info.questionNumber && <div className="block">
  860. <div className="t1">剩余未做</div>
  861. <div className="t2">{detail.info.questionNumber || 0 - detail.info.userNumber}</div>
  862. <div className="t3">题</div>
  863. </div>}
  864. </div>
  865. </div>
  866. </div>
  867. <div className="body gray">
  868. <div className="content">
  869. <div className="title">基本情况</div>
  870. <div className="block-wrapper">
  871. <div className="block">
  872. <PieChart option={pieOption1('正确率', detail.info.userCorrect, detail.info.userNumber, detail.info.totalCorrect, detail.info.totalNumber)} />
  873. </div>
  874. <div className="block">
  875. <BarChart option={barOption1('用时', Math.round(detail.info.userTime / detail.info.userNumber), Math.round(detail.info.totalTime / detail.info.totalNumber), Math.round(detail.info.correctTime / detail.info.userCorrect), Math.round(detail.info.incorrectTime / (detail.info.userNumber - detail.info.userCorrect)))} />
  876. </div>
  877. </div>
  878. </div>
  879. </div>
  880. <div className="body">
  881. <div className="content">
  882. <div className="title">能力评估</div>
  883. <PieChart option={pieOption2('综合得分', detail.ability.logic, detail.ability.struct, detail.ability.speed, detail.ability.score)} />
  884. </div>
  885. </div>
  886. <div className="body gray">
  887. <div className="content t-c">
  888. <Button size="lager" width={200} radius onClick={() => linkTo('/exercise?tab=sentence')}>
  889. 继续做题
  890. </Button>
  891. </div>
  892. </div>
  893. </div>;
  894. }
  895. renderExerciseQuestion() {
  896. const { report, list, order, showNote, note = {}, questionNo = {} } = this.state;
  897. return <div className='exercise question'>
  898. <div className='header'>
  899. <div className='content'>
  900. <div className='title'>题目回顾</div>
  901. <Button className='back' radius onClick={() => {
  902. linkTo(`/paper/report/${report.id}`);
  903. }}>返回练习报告</Button>
  904. </div>
  905. </div>
  906. <div className='body'>
  907. <div className='content'>
  908. <div className='tip'><Assets name='notice' />点击题目查看详情</div>
  909. <table>
  910. <thead>
  911. <tr>
  912. <th>序号</th>
  913. <th width='340'>题目</th>
  914. <th className="point" onClick={() => {
  915. this.questionSort('correct');
  916. }}>正误<GIcon name={order === 'correct' ? 'arrow-down' : 'arrow-up'} active={order === 'correct'} /></th>
  917. <th className="point" onClick={() => {
  918. this.questionSort('difficult');
  919. }}>难度<GIcon name={order === 'difficult' ? 'arrow-down' : 'arrow-up'} active={order === 'difficult'} /></th>
  920. <th className="point" onClick={() => {
  921. this.questionSort('userTime');
  922. }}>用时<GIcon name={order === 'userTime' ? 'arrow-down' : 'arrow-up'} active={order === 'userTime'} /></th>
  923. <th className="point" onClick={() => {
  924. this.questionSort('place');
  925. }}>主要考点<GIcon name={order === 'place' ? 'arrow-down' : 'arrow-up'} active={order === 'place'} /></th>
  926. <th className="point" onClick={() => {
  927. this.questionSort('collect');
  928. }}>收藏<GIcon name={order === 'collect' ? 'arrow-down' : 'arrow-up'} active={order === 'collect'} /></th>
  929. <th className="point" onClick={() => {
  930. this.questionSort('note');
  931. }}>笔记<GIcon name={order === 'note' ? 'arrow-down' : 'arrow-up'} active={order === 'note'} /></th>
  932. </tr>
  933. </thead>
  934. <tbody>
  935. {(list || []).map((row, index) => {
  936. return <tr>
  937. <td>{row.no}</td>
  938. <td>
  939. <div className='n'><a href={`/paper/question/${row.id}`} target="_blank">{(row.questionNo || {}).title}</a></div>
  940. <div className='desc'><Typography.Paragraph ellipsis={{ rows: 3, expandable: false }}>{row.question.description}</Typography.Paragraph></div>
  941. </td>
  942. <td>{row.question.questionType === 'awa' ? '-' : <GIcon name={row.isCorrect ? 'right' : 'error'} noHover />}</td>
  943. <td>{row.question.difficult}</td>
  944. <td>{formatMinuteSecond(row.userTime)}</td>
  945. <td>{row.question.place}</td>
  946. <td><GIcon name='star' active={row.collect} onClick={() => this.toggleCollect(index)} /></td>
  947. <td><GIcon name='note' active={row.note} onClick={() => this.note(index)} /></td>
  948. </tr>;
  949. })}
  950. </tbody>
  951. </table>
  952. </div>
  953. </div>
  954. <QuestionNoteModal show={showNote} defaultData={note} questionNo={questionNo} onConfirm={() => {
  955. list[this.state.index].note = true;
  956. this.setState({ showNote: false, list });
  957. }} onCancel={() => this.setState({ showNote: false })} />
  958. </div>;
  959. }
  960. renderExerciseDetail() {
  961. const { report = {} } = this.state;
  962. let { detail = {} } = report;
  963. detail = detail || {};
  964. const { pace = [], info = {}, difficult = [], place = [], limit = {} } = detail;
  965. return <div>
  966. <div className="body">
  967. <div className="content">
  968. <div className="title">完成情况</div>
  969. <div className="detail">
  970. <div className="block">
  971. <div className="t1">总耗时</div>
  972. <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(info.userTime).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
  973. </div>
  974. {info.userTime > info.time && <div className="block">
  975. <div className="t1">超出建议用时</div>
  976. <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(info.userTime - info.time).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
  977. </div>}
  978. <div className="line" />
  979. <div className="block">
  980. <div className="t1">完成题目</div>
  981. <div className="t2">{info.userNumber}</div>
  982. <div className="t3">题</div>
  983. </div>
  984. {info.userNumber !== info.questionNumber && <div className="block">
  985. <div className="t1">剩余未做</div>
  986. <div className="t2">{info.questionNumber - info.userNumber}</div>
  987. <div className="t3">题</div>
  988. </div>}
  989. </div>
  990. </div>
  991. </div>
  992. <div className="body gray">
  993. <div className="content">
  994. <div className="title">基本情况</div>
  995. <div className="block-wrapper">
  996. <div className="block">
  997. <PieChart option={pieOption1('正确率', info.userCorrect, info.userNumber, info.totalCorrect, info.totalNumber)} />
  998. </div>
  999. <div className="block">
  1000. <BarChart option={barOption1('用时', Math.round(info.userTime / info.userNumber), Math.round(info.totalTime / info.totalNumber), Math.round(detail.info.correctTime / detail.info.userCorrect), Math.round(detail.info.incorrectTime / (detail.info.userNumber - detail.info.userCorrect)))} />
  1001. </div>
  1002. </div>
  1003. </div>
  1004. </div>
  1005. <div className="body">
  1006. <div className="content">
  1007. <div className="title">PACE</div>
  1008. <div className="detail-1">
  1009. <div className="block">
  1010. <div className="t1">平均用时</div>
  1011. <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(info.userTime / info.userNumber).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
  1012. </div>
  1013. <div className="block all">
  1014. <div className="t1">全站用户</div>
  1015. <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(info.totalTime / info.totalNumber).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
  1016. </div>
  1017. </div>
  1018. <LineChart
  1019. height={400}
  1020. option={lineOption1(
  1021. '每题用时情况',
  1022. pace.map(row => {
  1023. return [`${row.no}`, row.userTime, row.time];
  1024. }),
  1025. ['我的', '全站'],
  1026. ['#7AA7DC', '#8684df'],
  1027. )}
  1028. />
  1029. </div>
  1030. </div>
  1031. {report.paperModule !== 'textbook' && <div className="body gray">
  1032. <div className="content">
  1033. <div className="title">难度分析</div>
  1034. <div className="detail-1">
  1035. <div className="block">
  1036. <div className="t1">正确率</div>
  1037. <div className="t2">{formatPercent(info.userCorrect, info.userNumber, false)}</div>
  1038. </div>
  1039. <div className="block all">
  1040. <div className="t1">全站用户</div>
  1041. <div className="t2">{formatPercent(info.totalCorrect, info.totalNumber, false)}</div>
  1042. </div>
  1043. </div>
  1044. <BarChart
  1045. height={400}
  1046. option={BarOption2(
  1047. '正确率',
  1048. difficult.map(row => {
  1049. return [QuestionDifficultMap[row.key], formatPercent(row.userCorrect, row.userNumber), formatPercent(row.totalCorrect, row.totalNumber)];
  1050. }),
  1051. ['我的', '全站'],
  1052. ['#8684df', '#5195e5'],
  1053. )}
  1054. />
  1055. </div>
  1056. </div>}
  1057. <div className="body">
  1058. <div className="content">
  1059. <div className="title">知识体系分析</div>
  1060. <div className="detail-1">
  1061. <div className="block">
  1062. <div className="t1">平均用时</div>
  1063. <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(info.userTime / info.userNumber).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
  1064. </div>
  1065. <div className="block all">
  1066. <div className="t1">正确率</div>
  1067. <div className="t2">{formatPercent(info.userCorrect, info.userNumber, false)}</div>
  1068. </div>
  1069. </div>
  1070. <BarChart
  1071. height={90 + 30 * place.length}
  1072. option={BarOption3(
  1073. ['知识点', '正确率分析', '用时分析'],
  1074. place.map(row => {
  1075. return row.key;
  1076. }),
  1077. place.map(row => {
  1078. return [row.userCorrect, row.userNumber];
  1079. }),
  1080. place.map(row => {
  1081. return row.userTime / row.userNumber;
  1082. }),
  1083. ['#7AA7DC', '#BFD4EE'],
  1084. ['#8684df', '#C3C3E5'],
  1085. )}
  1086. />
  1087. </div>
  1088. </div>
  1089. <div className="body gray">
  1090. <div className="content">
  1091. <div className="title">实战提醒</div>
  1092. {info.userTime > info.time && <div className="tip">
  1093. <div className="t1">在实战限时情况下,本套题正确率为 </div>
  1094. <div className="t2">{formatPercent(limit.userCorrect, limit.userNumber)}%</div>
  1095. <Tooltip title="仅统计在建议时间内完成题目正确率情况,更接近实战表现"><Icon type="question-circle" theme="filled" /></Tooltip>
  1096. </div>}
  1097. {info.userTime <= info.time && <div className="tip">
  1098. <div className="t1">目前的做题速度已达到实战标准!很棒!请继续保持!</div>
  1099. </div>}
  1100. </div>
  1101. </div>
  1102. <div className="body">
  1103. <div className="content t-c">
  1104. <Button size="lager" width={200} radius onClick={() => linkTo('/exercise')}>
  1105. 继续做题
  1106. </Button>
  1107. </div>
  1108. </div>
  1109. </div>;
  1110. }
  1111. renderTextbookQuestion() {
  1112. return this.renderExerciseQuestion();
  1113. }
  1114. renderExaminationDetail() {
  1115. const { report = {}, tab } = this.state;
  1116. const { detail = {} } = report;
  1117. const { subject = {} } = detail;
  1118. const subjectDetail = tab === 'main' ? null : (subject || {})[tab] || { info: {}, defficlt: [], place: [], pace: [] };
  1119. const tabs = [{ key: 'main', name: '总览 Overview' }];
  1120. if (subject.verbal) {
  1121. tabs.push({ key: 'verbal', name: '语文 Verbal' });
  1122. }
  1123. if (subject.quant) {
  1124. tabs.push({ key: 'quant', name: '数学 Quant' });
  1125. }
  1126. if (subject.ir) {
  1127. tabs.push({ key: 'ir', name: '综合推理 IR' });
  1128. }
  1129. return <div>
  1130. <div className="body">
  1131. <div className="content">
  1132. <Tabs
  1133. type="division"
  1134. theme="gray"
  1135. active={tab}
  1136. space={7}
  1137. tabs={tabs}
  1138. onChange={(value) => {
  1139. this.setState({ tab: value });
  1140. }}
  1141. />
  1142. {!subjectDetail && <div className="title">完成情况</div>}
  1143. {!subjectDetail && <div className="list">
  1144. <div className="detail">
  1145. <div className="block">
  1146. <div className="t1" />
  1147. {subject.verbal && <div className="t4">Verbal</div>}
  1148. {subject.quant && <div className="t4">Quant</div>}
  1149. {subject.ir && <div className="t4">IR</div>}
  1150. </div>
  1151. <div className="block">
  1152. <div className="t1">总耗时</div>
  1153. {subject.verbal && <div className="t1">
  1154. <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(Math.min(subject.verbal.info.userTime, subject.verbal.info.time)).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
  1155. </div>}
  1156. {subject.quant && <div className="t1">
  1157. <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(Math.min(subject.quant.info.userTime, subject.quant.info.time)).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
  1158. </div>}
  1159. {subject.ir && <div className="t1">
  1160. <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(Math.min(subject.ir.info.userTime, subject.ir.info.time)).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
  1161. </div>}
  1162. </div>
  1163. <div className="block">
  1164. <div className="t1">剩余时间</div>
  1165. {subject.verbal && <div className="t1">
  1166. <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(subject.verbal.info.questionNumber === subject.verbal.info.userNumber && subject.verbal.info.time > subject.verbal.info.userTime ? subject.verbal.info.time - subject.verbal.info.userTime : 0).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
  1167. </div>}
  1168. {subject.quant && <div className="t1">
  1169. <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(subject.quant.info.questionNumber === subject.quant.info.userNumber && subject.quant.info.time > subject.quant.info.userTime ? subject.quant.info.time - subject.quant.info.userTime : 0).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
  1170. </div>}
  1171. {subject.ir && <div className="t1">
  1172. <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(subject.ir.info.questionNumber === subject.ir.info.userNumber && subject.ir.info.time > subject.ir.info.userTime ? subject.ir.info.time - subject.ir.info.userTime : 0).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
  1173. </div>}
  1174. </div>
  1175. <div className="block">
  1176. <div className="t1" />
  1177. {subject.verbal && <div className="t1">
  1178. <div className="line" />
  1179. </div>}
  1180. {subject.quant && <div className="t1">
  1181. <div className="line" />
  1182. </div>}
  1183. {subject.ir && <div className="t1">
  1184. <div className="line" />
  1185. </div>}
  1186. </div>
  1187. <div className="block">
  1188. <div className="t1">完成题目</div>
  1189. {subject.verbal && <div className="t1">
  1190. <div className="t2">{subject.verbal.info.userNumber}</div>
  1191. <div className="t3">题</div>
  1192. </div>}
  1193. {subject.quant && <div className="t1">
  1194. <div className="t2">{subject.quant.info.userNumber}</div>
  1195. <div className="t3">题</div>
  1196. </div>}
  1197. {subject.ir && <div className="t1">
  1198. <div className="t2">{subject.ir.info.userNumber}</div>
  1199. <div className="t3">题</div>
  1200. </div>}
  1201. </div>
  1202. <div className="block">
  1203. <div className="t1">剩余未做</div>
  1204. {subject.verbal && <div className="t1">
  1205. <div className="t2">{subject.verbal.info.questionNumber - subject.verbal.info.userNumber}</div>
  1206. <div className="t3">题</div>
  1207. </div>}
  1208. {subject.quant && <div className="t1">
  1209. <div className="t2">{subject.quant.info.questionNumber - subject.quant.info.userNumber}</div>
  1210. <div className="t3">题</div>
  1211. </div>}
  1212. {subject.ir && <div className="t1">
  1213. <div className="t2">{subject.ir.info.questionNumber - subject.ir.info.userNumber}</div>
  1214. <div className="t3">题</div>
  1215. </div>}
  1216. </div>
  1217. </div></div>}
  1218. {subjectDetail && <div>
  1219. <div className="title">PACE</div>
  1220. <div className="detail-1">
  1221. <div className="block">
  1222. <div className="t1">平均用时</div>
  1223. <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(subjectDetail.info.userTime / subjectDetail.info.userNumber).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
  1224. </div>
  1225. </div>
  1226. <LineChart
  1227. height={400}
  1228. option={lineOption1(
  1229. '每题用时情况',
  1230. subjectDetail.pace.map(row => {
  1231. return [`${row.no}`, row.userTime];
  1232. }),
  1233. ['我的'],
  1234. ['#A3A8BF'],
  1235. )}
  1236. />
  1237. <div className="m-b-4" />
  1238. <LineChart
  1239. height={400}
  1240. option={lineOption1(
  1241. '累计用时情况',
  1242. (function (sd) {
  1243. let userTime = 0;
  1244. let time = 0;
  1245. return sd.pace.map(row => {
  1246. userTime += row.userTime;
  1247. time += row.time;
  1248. return [`${row.no}`, userTime, time];
  1249. });
  1250. }(subjectDetail)),
  1251. ['我的', '建议'],
  1252. ['#A3A8BF', '#7AA7DC'],
  1253. subjectDetail.info.time,
  1254. )}
  1255. />
  1256. </div>}
  1257. </div>
  1258. </div>
  1259. {!subjectDetail && <div className="body gray">
  1260. <div className="content">
  1261. <div className="title">整体表现</div>
  1262. <table>
  1263. <thead>
  1264. <tr>
  1265. <th>Question format</th>
  1266. <th>Total</th>
  1267. <th>Correct</th>
  1268. <th>%Correct</th>
  1269. <th>
  1270. Avg Time
  1271. </th>
  1272. <th>
  1273. Avg Time
  1274. <br />
  1275. Correct
  1276. </th>
  1277. <th>
  1278. Avg Time
  1279. <br />
  1280. Incorrect
  1281. </th>
  1282. <th>
  1283. Avg Diff
  1284. <br />
  1285. Correct
  1286. </th>
  1287. <th>
  1288. Avg Diff
  1289. <br />
  1290. Incorrect
  1291. </th>
  1292. </tr>
  1293. </thead>
  1294. <tbody>
  1295. {ExaminationQuestionType.map(row => {
  1296. const typeDetail = detail.type[row.value];
  1297. if (!typeDetail) return null;
  1298. return <tr>
  1299. <td>{row.long}</td>
  1300. <td>{typeDetail.info.userNumber}</td>
  1301. <td>{typeDetail.info.userCorrect}</td>
  1302. <td>
  1303. {formatPercent(typeDetail.info.userCorrect, typeDetail.info.userNumber)}
  1304. </td>
  1305. <td>
  1306. {formatSecond(typeDetail.info.userTime / typeDetail.info.userNumber)}
  1307. </td>
  1308. <td>
  1309. {formatSecond(typeDetail.info.correctTime / typeDetail.info.userCorrect)}
  1310. </td>
  1311. <td>
  1312. {formatSecond(typeDetail.info.incorrectTime / (typeDetail.info.userNumber - typeDetail.info.userCorrect))}
  1313. </td>
  1314. <td>{typeDetail.info.avgDiffCorrect}</td>
  1315. <td>{typeDetail.info.avgDiffIncorrect}</td>
  1316. </tr>;
  1317. })}
  1318. </tbody>
  1319. </table>
  1320. </div>
  1321. </div>}
  1322. {subjectDetail && <div className="body gray">
  1323. <div className="content">
  1324. <div className="title">基本情况</div>
  1325. <table>
  1326. <thead>
  1327. <tr>
  1328. <th>%Correct</th>
  1329. <th>Avg Time</th>
  1330. <th>
  1331. Avg Time
  1332. <br />
  1333. Correct
  1334. </th>
  1335. <th>
  1336. Avg Time
  1337. <br />
  1338. Incorrect
  1339. </th>
  1340. <th>
  1341. Avg Diff
  1342. <br />
  1343. Correct
  1344. </th>
  1345. <th>
  1346. Avg Diff
  1347. <br />
  1348. Incorrect
  1349. </th>
  1350. </tr>
  1351. </thead>
  1352. <tbody>
  1353. <tr>
  1354. <td>
  1355. {formatPercent(subjectDetail.info.userCorrect, subjectDetail.info.userNumber)}
  1356. <br />
  1357. <span>{subjectDetail.info.userCorrect}题/{subjectDetail.info.userNumber}题</span>
  1358. </td>
  1359. <td>
  1360. {formatSecond(subjectDetail.info.userTime / subjectDetail.info.userNumber)}
  1361. <br />
  1362. <span>{formatMinute(subjectDetail.info.userTime)}min/{subjectDetail.info.userNumber}题</span>
  1363. </td>
  1364. <td>
  1365. {formatSecond(subjectDetail.info.correctTime / subjectDetail.info.userCorrect)}
  1366. <br />
  1367. <span>{formatMinute(subjectDetail.info.correctTime)}min/{subjectDetail.info.userCorrect}题</span>
  1368. </td>
  1369. <td>
  1370. {formatSecond(subjectDetail.info.incorrectTime / (subjectDetail.info.userNumber - subjectDetail.info.userCorrect))}
  1371. <br />
  1372. <span>{formatMinute(subjectDetail.info.incorrectTime)}min/{subjectDetail.info.userNumber}题</span>
  1373. </td>
  1374. <td>{subjectDetail.info.avgDiffCorrect}</td>
  1375. <td>{subjectDetail.info.avgDiffIncorrect}</td>
  1376. </tr>
  1377. </tbody>
  1378. </table>
  1379. </div>
  1380. </div>}
  1381. {subjectDetail && <div className="body">
  1382. <div className="content">
  1383. <div className="title">难度分析</div>
  1384. <BarChart
  1385. height={400}
  1386. option={BarOption2(
  1387. '正确率',
  1388. Object.values(subjectDetail.difficult).map(row => {
  1389. return [QuestionDifficultMap[row.key], formatPercent(row.userCorrect, row.userNumber)];
  1390. }),
  1391. ['我的'],
  1392. ['#989FC1'],
  1393. )}
  1394. />
  1395. </div>
  1396. </div>}
  1397. {subjectDetail && <div className="body gray">
  1398. <div className="content">
  1399. <div className="title">知识体系分析</div>
  1400. <BarChart
  1401. height={90 + 30 * Object.keys(subjectDetail.place).length}
  1402. option={BarOption3(
  1403. ['知识点', '正确率分析', '用时分析'],
  1404. Object.values(subjectDetail.place).map(row => {
  1405. return row.key;
  1406. }),
  1407. Object.values(subjectDetail.place).map(row => {
  1408. return [row.userCorrect, row.userNumber];
  1409. }),
  1410. Object.values(subjectDetail.place).map(row => {
  1411. return row.userTime / row.userNumber;
  1412. }),
  1413. ['#92AFD2', '#BFD4EE'],
  1414. ['#989FC1', '#CCCCDC'],
  1415. )}
  1416. />
  1417. </div>
  1418. </div>}
  1419. </div >;
  1420. }
  1421. renderExaminationScore() {
  1422. const { report = {} } = this.state;
  1423. const { score, detail } = report;
  1424. const { subject, info } = detail;
  1425. return <div className="body">
  1426. <div className="content">
  1427. <div className="title">成绩单</div>
  1428. <table>
  1429. <thead>
  1430. <tr>
  1431. <th>学科</th>
  1432. <th>分数</th>
  1433. <th>排名</th>
  1434. <th>题目</th>
  1435. </tr>
  1436. </thead>
  1437. <tbody>
  1438. {ExaminationSubject.map(row => {
  1439. if (!subject[row.value]) return null;
  1440. return <tr>
  1441. <td>{row.long}</td>
  1442. <td>{row.ignore || !info.cat ? '--' : score[`${row.value}Score`]}</td>
  1443. <td>{row.ignore || !info.cat ? '--' : score[`${row.value}Rank`]}</td>
  1444. <td><Button size="small" radius onClick={() => {
  1445. Question.getDetailByNo(report.id, 1, row.value).then((r) => {
  1446. openLink(`/paper/question/${r.id}`);
  1447. });
  1448. }}>回顾</Button></td></tr>;
  1449. })}
  1450. <tr>
  1451. <td>Total</td>
  1452. <td>{!info.cat ? '仅CAT模考提供分数' : score.totalScore}</td>
  1453. <td>{!info.cat ? '--' : score.totalRank}</td>
  1454. <td /></tr>
  1455. </tbody>
  1456. </table>
  1457. </div>
  1458. </div>;
  1459. }
  1460. }