page.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  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)
  394. .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 && <Continue
  425. data={latest}
  426. onClose={() => {
  427. this.clearExercise();
  428. }}
  429. onContinue={() => {
  430. }}
  431. onRestart={() => {
  432. }}
  433. onNext={() => {
  434. }} />}
  435. <div className="content">
  436. <Module className="m-t-2">
  437. <Tabs type="card" active={tab1} tabs={tabs} onChange={key => {
  438. this.onChangeTab(1, key);
  439. }} />
  440. {children.length > 1 && <Tabs active={tab2} tabs={children} onChange={key => this.onChangeTab(2, key)} />}
  441. </Module>
  442. {tab1 !== SENTENCE && tab1 !== PREVIEW && this.renderExercise()}
  443. {tab1 === SENTENCE && this.renderSentence()}
  444. {tab1 === PREVIEW && this.renderPreview()}
  445. </div>
  446. </div>
  447. );
  448. }
  449. renderPreview() {
  450. const { previewType } = this.state;
  451. switch (previewType) {
  452. case PREVIEW_CLASS:
  453. return this.renderPreviewClass();
  454. case PREVIEW_LIST:
  455. return this.renderPreviewList();
  456. default:
  457. return <div />;
  458. }
  459. }
  460. renderPreviewClass() {
  461. const { allClass, classProcess } = this.state;
  462. return (
  463. <div className="work-body">
  464. <div className="work-nav">
  465. <div className="left">完成情况</div>
  466. <div className="right theme c-p" onClick={() => this.onChangePreviewType(PREVIEW_LIST)}>
  467. 全部作业 >
  468. </div>
  469. </div>
  470. <Division col="3">
  471. {allClass.map(item => {
  472. return <Card data={item} process={classProcess[item.id]} previewAction={this.previewAction} />;
  473. })}
  474. </Division>
  475. </div>
  476. );
  477. }
  478. renderPreviewList() {
  479. const { previews } = this.state;
  480. return (
  481. <div className="work-body">
  482. <div className="work-nav">
  483. <div className="left">全部作业</div>
  484. <div className="right theme c-p" onClick={() => this.onChangePreviewType(PREVIEW_CLASS)}>
  485. 我的课程 >
  486. </div>
  487. </div>
  488. <ListTable
  489. filters={[
  490. {
  491. type: 'radio',
  492. checked: 'today',
  493. list: [{ key: 'today', title: '今日需完成' }, { key: 'tomorrow', title: '明日需完成' }],
  494. },
  495. {
  496. type: 'radio',
  497. checked: 'unfinish',
  498. list: [{ key: 'unfinish', title: '未完成' }, { key: 'finish', title: '已完成' }],
  499. },
  500. { type: 'select', checked: 'all', list: [{ key: 'all', title: '全部' }] },
  501. ]}
  502. data={previews}
  503. columns={this.columns}
  504. />
  505. </div>
  506. );
  507. }
  508. renderSentence() {
  509. const { sentence = {}, sentenceInput } = this.state;
  510. const { sentenceTrail } = this.props.user;
  511. if (sentenceInput !== true && (sentence.code || sentenceTrail)) {
  512. return this.renderSentenceArticle();
  513. }
  514. return this.renderInputCode();
  515. }
  516. renderSentenceArticle() {
  517. const { sentence = {}, chapters, chapter, exerciseChapter = {}, chapterMap = {}, articleMap = {}, paperFilterList = [], paperList = [], paperChecked } = this.state;
  518. const { sentenceTrail } = this.props.user;
  519. let maxStep = 0;
  520. if (sentenceTrail) {
  521. // 试用只能访问第一step
  522. maxStep = 1;
  523. // 查找练习章节
  524. }
  525. const chapterInfo = chapterMap[chapter] || {};
  526. let isExercise = false;
  527. if (chapterInfo && chapterInfo.exercise) {
  528. isExercise = true;
  529. }
  530. return <div>
  531. {sentence.code && <div className='sentence-code'>CODE: {sentence.code}</div>}
  532. {sentenceTrail && <div className='sentence-code'>CODE: <Link to=''>去获取</Link><a onClick={() => {
  533. this.setState({ sentenceInput: true });
  534. }}>输入</a></div>}
  535. <Module>
  536. <Step
  537. list={chapters}
  538. step={chapter}
  539. onClick={(step) => {
  540. this.setState({ chapter: step });
  541. }}
  542. message='请购买后访问'
  543. maxStep={maxStep}
  544. />
  545. </Module>
  546. {/* 正常文章 */}
  547. {sentence.code && !isExercise && <List
  548. title={`Chapter${chapter}`}
  549. subTitle={chapterInfo.title}
  550. list={articleMap[chapter]}
  551. onClick={(part) => {
  552. this.sentenceRead(part);
  553. }}
  554. />}
  555. {/* 正常练习 */}
  556. {sentence.code && isExercise && <ListTable
  557. title={`Chapter${chapter}`}
  558. subTitle={chapterInfo.title}
  559. filters={[{
  560. type: 'radio',
  561. checked: paperChecked,
  562. list: [{ key: 0, title: '未完成' }, { key: 1, title: '已完成' }],
  563. onChange: (item) => {
  564. console.log(item);
  565. this.sentenceFilter(item);
  566. },
  567. }]}
  568. data={paperFilterList}
  569. columns={this.sentenceColums}
  570. />}
  571. {/* 试读文章 */}
  572. {sentenceTrail && <List
  573. list={[]}
  574. onClick={(part) => {
  575. this.sentenceRead(part);
  576. }}
  577. />}
  578. {/* 试练 */}
  579. {sentenceTrail && <ListTable
  580. title={`Chapter${exerciseChapter.value}`}
  581. subTitle={exerciseChapter.title}
  582. data={paperList}
  583. columns={this.sentenceColums}
  584. />}
  585. </div>;
  586. }
  587. renderInputCode() {
  588. return (
  589. <Module className="code-module">
  590. <div className="title">输入《千行GMAT长难句》专属 Code,解锁在线练习功能。</div>
  591. <div className="input-block">
  592. <Input size="lager" placeholder="请输入CODE" onChange={(value) => {
  593. this.code = value;
  594. }} />
  595. <Button size="lager" onClick={() => {
  596. this.activeSentence();
  597. }}>解锁</Button>
  598. </div>
  599. <div className="tip">
  600. <Link to="/" className="left link">
  601. 什么是CODE?
  602. </Link>
  603. <span>没有 CODE?</span>
  604. <Link to="/" className="link">
  605. 去获取 >>
  606. </Link>
  607. <a onClick={() => {
  608. this.trailSentence();
  609. }} className="right link">
  610. 试用 >>
  611. </a>
  612. </div>
  613. </Module>
  614. );
  615. }
  616. renderExercise() {
  617. return <div />;
  618. }
  619. }