page.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. import React from 'react';
  2. import './index.less';
  3. import { Link } from 'react-router-dom';
  4. import { Tooltip } from 'antd';
  5. import Page from '@src/containers/Page';
  6. import { asyncConfirm } from '@src/services/AsyncTools';
  7. import { formatTreeData, getMap } from '@src/services/Tools';
  8. import Continue from '../../../components/Continue';
  9. import Step from '../../../components/Step';
  10. import List from '../../../components/List';
  11. import Tabs from '../../../components/Tabs';
  12. import Module from '../../../components/Module';
  13. import Input from '../../../components/Input';
  14. import Button from '../../../components/Button';
  15. import Division from '../../../components/Division';
  16. import Card from '../../../components/Card';
  17. import ListTable from '../../../components/ListTable';
  18. import ProgressText from '../../../components/ProgressText';
  19. import IconButton from '../../../components/IconButton';
  20. import { Main } from '../../../stores/main';
  21. import { My } from '../../../stores/my';
  22. import { Sentence } from '../../../stores/sentence';
  23. import { Question } from '../../../stores/question';
  24. import { Course } from '../../../stores/course';
  25. import { User } from '../../../stores/user';
  26. const SENTENCE = 'sentence';
  27. const PREVIEW = 'preview';
  28. const PREVIEW_CLASS = 'PREVIEW_CLASS';
  29. const PREVIEW_LIST = 'PREVIEW_LIST';
  30. const exerciseColumns = [
  31. {
  32. title: '练习册',
  33. width: 250,
  34. align: 'left',
  35. render: item => {
  36. return (
  37. <div className="table-row">
  38. <div className="night f-s-16">{item.title}</div>
  39. <div>
  40. <ProgressText
  41. progress={item.report.id ? item.repport.userNumber / item.report.questionNumber : 0}
  42. size="small"
  43. />
  44. </div>
  45. </div>
  46. );
  47. },
  48. },
  49. {
  50. title: '正确率',
  51. width: 150,
  52. align: 'left',
  53. render: item => {
  54. return (
  55. <div className="table-row">
  56. <div className="night f-s-16 f-w-b">--</div>
  57. <div className="f-s-12">{item.stat.totalCorrect / item.stat.totalNumber}</div>
  58. </div>
  59. );
  60. },
  61. },
  62. {
  63. title: '全站用时',
  64. width: 150,
  65. align: 'left',
  66. render: item => {
  67. return (
  68. <div className="table-row">
  69. <div className="night f-s-16 f-w-b">--</div>
  70. <div className="f-s-12">全站{item.stat.totalTime / item.stat.totalNumber}s</div>
  71. </div>
  72. );
  73. },
  74. },
  75. {
  76. title: '最近做题',
  77. width: 150,
  78. align: 'left',
  79. render: () => {
  80. return (
  81. <div className="table-row">
  82. <div>2019-04-28</div>
  83. <div>07:30</div>
  84. </div>
  85. );
  86. },
  87. },
  88. {
  89. title: '操作',
  90. width: 180,
  91. align: 'left',
  92. render: item => {
  93. return (
  94. <div className="table-row p-t-1">
  95. {!item.repport.id && (
  96. <IconButton type="start" tip="Start" onClick={() => this.previewAction('start', item)} />
  97. )}
  98. {item.repport.id && (
  99. <IconButton
  100. className="m-r-2"
  101. type="continue"
  102. tip="Continue"
  103. onClick={() => this.previewAction('continue', item)}
  104. />
  105. )}
  106. {item.repport.id && (
  107. <IconButton type="restart" tip="Restart" onClick={() => this.previewAction('restart', item)} />
  108. )}
  109. </div>
  110. );
  111. },
  112. },
  113. {
  114. title: '报告',
  115. width: 30,
  116. align: 'right',
  117. render: item => {
  118. return (
  119. <div className="table-row p-t-1">
  120. {item.report.userNumber === item.report.questionNumber && <IconButton type="report" tip="Report" />}
  121. </div>
  122. );
  123. },
  124. },
  125. ];
  126. export default class extends Page {
  127. constructor(props) {
  128. super(props);
  129. this.sentenceColums = [
  130. {
  131. title: '练习册',
  132. width: 250,
  133. align: 'left',
  134. render: row => {
  135. return (
  136. <div className="table-row">
  137. <div className="night f-s-16">{row.title}</div>
  138. <div>
  139. <ProgressText progress={row.process} size="small" />
  140. </div>
  141. </div>
  142. );
  143. },
  144. },
  145. {
  146. title: '正确率',
  147. width: 150,
  148. align: 'left',
  149. render: () => {
  150. return (
  151. <div className="table-row">
  152. <div className="night f-s-16 f-w-b">--</div>
  153. <div className="f-s-12">全站55%</div>
  154. </div>
  155. );
  156. },
  157. },
  158. {
  159. title: '全站用时',
  160. width: 150,
  161. align: 'left',
  162. render: () => {
  163. return (
  164. <div className="table-row">
  165. <div className="night f-s-16 f-w-b">55%</div>
  166. <div className="f-s-12">全站56s</div>
  167. </div>
  168. );
  169. },
  170. },
  171. {
  172. title: '最近做题',
  173. width: 150,
  174. align: 'left',
  175. render: () => {
  176. return (
  177. <div className="table-row">
  178. <div>2019-04-28</div>
  179. <div>07:30</div>
  180. </div>
  181. );
  182. },
  183. },
  184. {
  185. title: '操作',
  186. width: 180,
  187. align: 'left',
  188. render: () => {
  189. return (
  190. <div className="table-row p-t-1">
  191. <IconButton className="m-r-2" type="continue" tip="Continue" />
  192. <IconButton type="restart" tip="Restart" />
  193. </div>
  194. );
  195. },
  196. },
  197. {
  198. title: '报告',
  199. width: 30,
  200. align: 'right',
  201. render: () => {
  202. return (
  203. <div className="table-row p-t-1">
  204. <IconButton type="report" tip="Report" />
  205. </div>
  206. );
  207. },
  208. },
  209. ];
  210. }
  211. initState() {
  212. this.code = null;
  213. this.columns = exerciseColumns;
  214. this.exerciseProcess = {};
  215. this.inited = false;
  216. return {
  217. tab1: SENTENCE,
  218. tab2: '',
  219. previewType: PREVIEW_CLASS,
  220. tabs: [],
  221. allClass: [],
  222. classProcess: {},
  223. };
  224. }
  225. init() {
  226. Main.getExercise().then(result => {
  227. const list = result.map(row => {
  228. row.title = `${row.titleZh}${row.titleEn}`;
  229. row.key = row.extend;
  230. return row;
  231. });
  232. const tabs = formatTreeData(list, 'id', 'title', 'parentId');
  233. tabs.push({ key: PREVIEW, name: '预习作业' });
  234. const map = getMap(tabs, 'key');
  235. this.setState({ tabs, map });
  236. this.inited = true;
  237. this.refreshData();
  238. });
  239. }
  240. initData() {
  241. const { info = {} } = this.props.user;
  242. if (info.latestExercise) {
  243. // 获取最后一次做题记录
  244. Question.baseReport(info.latestExercise).then(result => {
  245. this.setState({ latest: result });
  246. });
  247. }
  248. if (this.inited) this.refreshData();
  249. }
  250. refreshData() {
  251. const { tab1 } = this.state;
  252. switch (tab1) {
  253. case SENTENCE:
  254. this.refreshSentence();
  255. break;
  256. case PREVIEW:
  257. this.refreshPreview();
  258. break;
  259. default:
  260. this.refreshExercise();
  261. }
  262. }
  263. refreshSentence() {
  264. const { sentence, articleMap, paperList } = this.state;
  265. if (!sentence) {
  266. Sentence.getInfo().then(result => {
  267. const chapters = [];
  268. const map = {};
  269. let index = 0;
  270. let exerciseChapter = null;
  271. if (!result.code) {
  272. chapters.push(`${index}」试用`);
  273. }
  274. index += 1;
  275. result.chapters.forEach(row => {
  276. map[row.value] = row;
  277. chapters.push(`「${index}」${row.short}`);
  278. index += 1;
  279. if (row.exercise) exerciseChapter = row;
  280. });
  281. this.setState({ sentence: result, chapters, chapterMap: map, exerciseChapter });
  282. });
  283. }
  284. if (!articleMap) {
  285. Sentence.listArticle().then(result => {
  286. const map = {};
  287. result.forEach(article => {
  288. if (!map[article.chapter]) {
  289. map[article.chapter] = [];
  290. }
  291. map[article.chapter].push(article);
  292. });
  293. this.setState({ articleMap: map });
  294. });
  295. }
  296. if (!paperList) {
  297. Sentence.listPaper().then(result => {
  298. this.setState({ paperList: result, paperFilterList: result });
  299. });
  300. }
  301. }
  302. refreshPreview() {
  303. const { previewType } = this.state;
  304. switch (previewType) {
  305. case PREVIEW_LIST:
  306. this.refreshListPreview();
  307. break;
  308. case PREVIEW_CLASS:
  309. default:
  310. this.refreshClassProcess();
  311. break;
  312. }
  313. }
  314. refreshClassProcess() {
  315. Course.classProcess().then(result => {
  316. const classProcess = {};
  317. for (let i = 0; i < result.length; i += 1) {
  318. const item = result[i];
  319. classProcess[item.category].push(item);
  320. }
  321. this.setState({ classProcess });
  322. });
  323. }
  324. refreshListPreview() {
  325. Question.listPreview().then(result => {
  326. this.setState({ previews: result });
  327. });
  328. }
  329. refreshExercise() {
  330. const { map, tab1 } = this.state;
  331. let { tab2 } = this.state;
  332. if (!map) {
  333. // 等待数据加载
  334. return;
  335. }
  336. if (tab1 === '') {
  337. return;
  338. }
  339. const subject = map[tab1];
  340. if (tab2 === '') {
  341. tab2 = subject.children[0].key;
  342. this.onChangeTab(2, tab2);
  343. return;
  344. }
  345. const type = map[tab2];
  346. Main.getExerciseChildren(type.id, true).then(result => {
  347. const exerciseChild = result;
  348. this.setState({ exerciseChild });
  349. });
  350. Question.getExerciseProcess(type.id).then(r => {
  351. const exerciseProcess = getMap(r, 'id');
  352. this.setState({ exerciseProcess });
  353. });
  354. }
  355. onChangePreviewType(type) {
  356. this.setState({ previewType: type });
  357. this.refreshPreview();
  358. }
  359. onChangeTab(level, tab) {
  360. const state = {};
  361. state[`tab${level}`] = tab;
  362. this.setState(state);
  363. this.refresh();
  364. }
  365. previewAction(type, item) {
  366. switch (type) {
  367. case 'start':
  368. this.start('preview', item);
  369. break;
  370. case 'restart':
  371. this.restart(item);
  372. break;
  373. case 'continue':
  374. this.continue('preview', item);
  375. break;
  376. default:
  377. break;
  378. }
  379. }
  380. restart(item) {
  381. asyncConfirm('提示', '是否重置', () => {
  382. Question.restart(item.report.id).then(() => {
  383. this.refresh();
  384. });
  385. });
  386. }
  387. start(type, item) {
  388. linkTo(`/paper/process/${type}/${item.id}`);
  389. }
  390. continue(type, item) {
  391. linkTo(`/paper/process/${type}/${item.id}?r=${item.report.id}`);
  392. }
  393. activeSentence() {
  394. Sentence.active(this.code).then(() => {
  395. // 重新获取长难句信息
  396. this.clearSentenceTrail();
  397. this.setState({ sentence: null, articleMap: null, paperList: null });
  398. this.refresh();
  399. });
  400. }
  401. trailSentence() {
  402. this.setState({ sentenceInput: false });
  403. User.sentenceTrail();
  404. }
  405. sentenceRead(article) {
  406. linkTo(`/sentence/read?chapter=${article.chapter}&part=${article.part}`);
  407. }
  408. sentenceFilter() {
  409. const { paperList } = this.state;
  410. const list = paperList.filter(row => {
  411. return !!row;
  412. });
  413. this.setState({ paperFilterList: list });
  414. }
  415. clearExercise() {
  416. My.clearLatestExercise();
  417. this.setState({ latest: null });
  418. }
  419. renderView() {
  420. const { tab1 = {}, tab2 = {}, tabs, map = {}, latest } = this.state;
  421. const children = (map[tab1] || {}).children || [];
  422. return (
  423. <div>
  424. {latest && (
  425. <Continue
  426. data={latest}
  427. onClose={() => {
  428. this.clearExercise();
  429. }}
  430. onContinue={() => { }}
  431. onRestart={() => { }}
  432. onNext={() => { }}
  433. />
  434. )}
  435. <div className="content">
  436. <Module className="m-t-2">
  437. <Tabs
  438. type="card"
  439. active={tab1}
  440. tabs={tabs}
  441. onChange={key => {
  442. this.onChangeTab(1, key);
  443. }}
  444. />
  445. {children.length > 1 && <Tabs active={tab2} tabs={children} onChange={key => this.onChangeTab(2, key)} />}
  446. </Module>
  447. {tab1 !== SENTENCE && tab1 !== PREVIEW && this.renderExercise()}
  448. {tab1 === SENTENCE && this.renderSentence()}
  449. {tab1 === PREVIEW && this.renderPreview()}
  450. </div>
  451. </div>
  452. );
  453. }
  454. renderPreview() {
  455. const { previewType } = this.state;
  456. switch (previewType) {
  457. case PREVIEW_CLASS:
  458. return this.renderPreviewClass();
  459. case PREVIEW_LIST:
  460. return this.renderPreviewList();
  461. default:
  462. return <div />;
  463. }
  464. }
  465. renderPreviewClass() {
  466. const { allClass, classProcess } = this.state;
  467. return (
  468. <div className="work-body">
  469. <div className="work-nav">
  470. <div className="left">完成情况</div>
  471. <div className="right theme c-p" onClick={() => this.onChangePreviewType(PREVIEW_LIST)}>
  472. 全部作业 >
  473. </div>
  474. </div>
  475. <Division col="3">
  476. {allClass.map(item => {
  477. return <Card data={item} process={classProcess[item.id]} previewAction={this.previewAction} />;
  478. })}
  479. </Division>
  480. </div>
  481. );
  482. }
  483. renderPreviewList() {
  484. const { previews } = this.state;
  485. return (
  486. <div className="work-body">
  487. <div className="work-nav">
  488. <div className="left">全部作业</div>
  489. <div className="right theme c-p" onClick={() => this.onChangePreviewType(PREVIEW_CLASS)}>
  490. 我的课程 >
  491. </div>
  492. </div>
  493. <ListTable
  494. filters={[
  495. {
  496. type: 'radio',
  497. checked: 'today',
  498. list: [{ key: 'today', title: '今日需完成' }, { key: 'tomorrow', title: '明日需完成' }],
  499. },
  500. {
  501. type: 'radio',
  502. checked: 'unfinish',
  503. list: [{ key: 'unfinish', title: '未完成' }, { key: 'finish', title: '已完成' }],
  504. },
  505. { type: 'select', checked: 'all', list: [{ key: 'all', title: '全部' }] },
  506. ]}
  507. rightAction={
  508. <div>
  509. 有效期至:2019-11-13{' '}
  510. <Tooltip overlayClassName="gray" placement="top" title="全部模考做完才可重置">
  511. <a>
  512. <Button size="small" disabled radius>
  513. Reset
  514. </Button>
  515. </a>
  516. </Tooltip>
  517. </div>
  518. }
  519. data={previews}
  520. columns={this.columns}
  521. />
  522. </div>
  523. );
  524. }
  525. renderSentence() {
  526. const { sentence = {}, sentenceInput } = this.state;
  527. const { sentenceTrail } = this.props.user;
  528. if (sentenceInput !== true && (sentence.code || sentenceTrail)) {
  529. return this.renderSentenceArticle();
  530. }
  531. return this.renderInputCode();
  532. }
  533. renderSentenceArticle() {
  534. const {
  535. sentence = {},
  536. chapters,
  537. chapter,
  538. exerciseChapter = {},
  539. chapterMap = {},
  540. articleMap = {},
  541. paperFilterList = [],
  542. paperList = [],
  543. paperChecked,
  544. } = this.state;
  545. const { sentenceTrail } = this.props.user;
  546. let maxStep = 0;
  547. if (sentenceTrail) {
  548. // 试用只能访问第一step
  549. maxStep = 1;
  550. // 查找练习章节
  551. }
  552. const chapterInfo = chapterMap[chapter] || {};
  553. let isExercise = false;
  554. if (chapterInfo && chapterInfo.exercise) {
  555. isExercise = true;
  556. }
  557. return (
  558. <div>
  559. {sentence.code && <div className="sentence-code">CODE: {sentence.code}</div>}
  560. {sentenceTrail && (
  561. <div className="sentence-code">
  562. CODE: <Link to="">去获取</Link>
  563. <a
  564. onClick={() => {
  565. this.setState({ sentenceInput: true });
  566. }}
  567. >
  568. 输入
  569. </a>
  570. </div>
  571. )}
  572. <Module>
  573. <Step
  574. list={chapters}
  575. step={chapter}
  576. onClick={step => {
  577. this.setState({ chapter: step });
  578. }}
  579. message="请购买后访问"
  580. maxStep={maxStep}
  581. />
  582. </Module>
  583. {/* 正常文章 */}
  584. {sentence.code && !isExercise && (
  585. <List
  586. title={`Chapter${chapter}`}
  587. subTitle={chapterInfo.title}
  588. list={articleMap[chapter]}
  589. onClick={part => {
  590. this.sentenceRead(part);
  591. }}
  592. />
  593. )}
  594. {/* 正常练习 */}
  595. {sentence.code && isExercise && (
  596. <ListTable
  597. title={`Chapter${chapter}`}
  598. subTitle={chapterInfo.title}
  599. filters={[
  600. {
  601. type: 'radio',
  602. checked: paperChecked,
  603. list: [{ key: 0, title: '未完成' }, { key: 1, title: '已完成' }],
  604. onChange: item => {
  605. console.log(item);
  606. this.sentenceFilter(item);
  607. },
  608. },
  609. ]}
  610. data={paperFilterList}
  611. columns={this.sentenceColums}
  612. />
  613. )}
  614. {/* 试读文章 */}
  615. {sentenceTrail && (
  616. <List
  617. list={[]}
  618. onClick={part => {
  619. this.sentenceRead(part);
  620. }}
  621. />
  622. )}
  623. {/* 试练 */}
  624. {sentenceTrail && (
  625. <ListTable
  626. title={`Chapter${exerciseChapter.value}`}
  627. subTitle={exerciseChapter.title}
  628. data={paperList}
  629. columns={this.sentenceColums}
  630. />
  631. )}
  632. </div>
  633. );
  634. }
  635. renderInputCode() {
  636. return (
  637. <Module className="code-module">
  638. <div className="title">输入《千行GMAT长难句》专属 Code,解锁在线练习功能。</div>
  639. <div className="input-block">
  640. <Input
  641. size="lager"
  642. placeholder="请输入CODE"
  643. onChange={value => {
  644. this.code = value;
  645. }}
  646. />
  647. <Button
  648. size="lager"
  649. onClick={() => {
  650. this.activeSentence();
  651. }}
  652. >
  653. 解锁
  654. </Button>
  655. </div>
  656. <div className="tip">
  657. <Link to="/" className="left link">
  658. 什么是CODE?
  659. </Link>
  660. <span>没有 CODE?</span>
  661. <Link to="/" className="link">
  662. 去获取 >>
  663. </Link>
  664. <a
  665. onClick={() => {
  666. this.trailSentence();
  667. }}
  668. className="right link"
  669. >
  670. 试用 >>
  671. </a>
  672. </div>
  673. </Module>
  674. );
  675. }
  676. renderExercise() {
  677. return <div />;
  678. }
  679. }