page.js 18 KB

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