1
0

page.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  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, formatSeconds, formatDate } 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.report && (
  95. <IconButton type="start" tip="Start" onClick={() => this.start('preview', item)} />
  96. )}
  97. {item.report.id && !item.report.isFinish && (
  98. <IconButton
  99. className="m-r-2"
  100. type="continue"
  101. tip="Continue"
  102. onClick={() => this.continue('preview', item)}
  103. />
  104. )}
  105. {item.report.id && (
  106. <IconButton type="restart" tip="Restart" onClick={() => this.restart('preview', 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.isFinish && <IconButton type="report" tip="Report" onClick={() => this.viewReport(item)} />}
  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: (record) => {
  134. let progress = 0;
  135. if (record.report) {
  136. progress = record.report.userNumber * 100 / record.report.questionNumber;
  137. }
  138. return (
  139. <div className="table-row">
  140. <div className="night f-s-16">{record.title}</div>
  141. <div>
  142. <ProgressText progress={progress} size="small" />
  143. </div>
  144. </div>
  145. );
  146. },
  147. },
  148. {
  149. title: '正确率',
  150. width: 150,
  151. align: 'left',
  152. render: (record) => {
  153. let correct = '--';
  154. if (record.report) {
  155. correct = `${record.report.userCorrect * 100 / record.report.userNumber}%`;
  156. }
  157. return (
  158. <div className="table-row">
  159. <div className="night f-s-16 f-w-b">{correct}</div>
  160. <div className="f-s-12">全站{record.stat.totalCorrect * 100 / record.stat.totalNumber}%</div>
  161. </div>
  162. );
  163. },
  164. },
  165. {
  166. title: '全站用时',
  167. width: 150,
  168. align: 'left',
  169. render: (record) => {
  170. let time = '--';
  171. if (record.paper) {
  172. time = record.paper.report.userTime / record.paper.report.userNumber;
  173. }
  174. return (
  175. <div className="table-row">
  176. <div className="night f-s-16 f-w-b">{formatSeconds(time)}</div>
  177. <div className="f-s-12">全站{formatSeconds(record.stat.totalTime / record.stat.totalNumber)}</div>
  178. </div>
  179. );
  180. },
  181. },
  182. {
  183. title: '最近做题',
  184. width: 150,
  185. align: 'left',
  186. render: (record) => {
  187. if (!record.report) return null;
  188. return (
  189. <div className="table-row">
  190. <div>{formatDate(record.report.updateTime, 'YYYY-MM-DD')}</div>
  191. <div>{formatDate(record.report.updateTime, 'HH:mm')}</div>
  192. </div>
  193. );
  194. },
  195. },
  196. {
  197. title: '操作',
  198. width: 180,
  199. align: 'left',
  200. render: (record) => {
  201. return (
  202. <div className="table-row p-t-1">
  203. {!record.report && <IconButton type="start" tip="Start" onClick={() => {
  204. this.start('sentence', record);
  205. }} />}
  206. {!record.report.isFinish && <IconButton className="m-r-2" type="continue" tip="Continue" onClick={() => {
  207. this.continue('sentence', record);
  208. }} />}
  209. <IconButton type="restart" tip="Restart" onClick={() => {
  210. this.restart(record);
  211. }} />
  212. </div>
  213. );
  214. },
  215. },
  216. {
  217. title: '报告',
  218. dataIndex: 'report',
  219. width: 30,
  220. align: 'right',
  221. render: (text, record) => {
  222. if (!record.report || !record.report.isFinish) return null;
  223. return (
  224. <div className="table-row p-t-1">
  225. <IconButton type="report" tip="Report" onClick={() => {
  226. this.viewReport(record);
  227. }} />
  228. </div>
  229. );
  230. },
  231. },
  232. ];
  233. }
  234. initState() {
  235. this.code = null;
  236. this.columns = exerciseColumns;
  237. this.exerciseProgress = {};
  238. this.inited = false;
  239. return {
  240. tab1: SENTENCE,
  241. tab2: '',
  242. pt: PREVIEW_CLASS,
  243. tabs: [],
  244. allClass: [],
  245. classProcess: {},
  246. };
  247. }
  248. init() {
  249. Main.getExercise().then(result => {
  250. const list = result.map((row) => {
  251. row.title = `${row.titleZh}${row.titleEn}`;
  252. row.key = row.extend;
  253. return row;
  254. });
  255. const map = getMap(list, 'key');
  256. const tabs = formatTreeData(list, 'id', 'title', 'parentId');
  257. tabs.push({ key: PREVIEW, name: '预习作业' });
  258. this.setState({ tabs, map });
  259. this.inited = true;
  260. this.refreshData();
  261. });
  262. }
  263. initData() {
  264. const { info = {} } = this.props.user;
  265. if (info.latestExercise) {
  266. // 获取最后一次做题记录
  267. Question.baseReport(info.latestExercise).then((result) => {
  268. this.setState({ latest: result });
  269. });
  270. }
  271. const data = Object.assign(this.state, this.state.search);
  272. this.setState(data);
  273. if (this.inited) this.refreshData();
  274. }
  275. refreshData(tab) {
  276. const { tab1 } = this.state;
  277. switch (tab || tab1) {
  278. case SENTENCE:
  279. this.refreshSentence();
  280. break;
  281. case PREVIEW:
  282. this.refreshPreview();
  283. break;
  284. default:
  285. this.refreshExercise(tab || tab1);
  286. }
  287. }
  288. refreshSentence() {
  289. const { sentence } = this.state;
  290. if (!sentence) {
  291. // User.clearSentenceTrail();
  292. Sentence.getInfo().then(result => {
  293. // result.code = '123123';
  294. result.trailPages = 20;
  295. this.setState({ sentence: result });
  296. return result;
  297. })
  298. .then(({ code, trailPages, chapters }) => {
  299. return Sentence.listArticle().then(result => {
  300. const chapterSteps = [];
  301. const chapterMap = {};
  302. const map = {};
  303. const trailArticles = [];
  304. let totalPage = 0;
  305. let introduction = null;
  306. let exerciseChapter = null;
  307. let index = 0;
  308. let lastChapter = -1;
  309. chapters.forEach(row => {
  310. chapterMap[row.value] = row;
  311. if (row.exercise) exerciseChapter = row;
  312. });
  313. result.forEach((article) => {
  314. if (article.chapter === 0) introduction = article;
  315. if (!map[article.chapter]) {
  316. map[article.chapter] = [];
  317. }
  318. article.startPage = totalPage + 1;
  319. article.endPage = totalPage + article.pages;
  320. if (article.chapter) {
  321. article.position = `${article.chapter}.${article.part}`;
  322. } else {
  323. // 设置list中的样式
  324. article.style = 'introduction';
  325. }
  326. totalPage += article.pages;
  327. if (article.startPage < trailPages) {
  328. if (lastChapter !== article.chapter) {
  329. lastChapter = article.chapter;
  330. trailArticles.push(Object.assign({ articles: [] }, chapterMap[article.chapter] || {}));
  331. }
  332. trailArticles[trailArticles.length - 1].articles.push(article);
  333. }
  334. map[article.chapter].push(article);
  335. });
  336. if (!code) {
  337. chapterSteps.push(`「${index}」试用`);
  338. }
  339. // 添加前言
  340. if (introduction) {
  341. index += 1;
  342. chapterSteps.push(`「${index}」${introduction.title}`);
  343. chapterMap[0] = {
  344. title: introduction.title,
  345. value: 0,
  346. };
  347. }
  348. index += 1;
  349. chapters.forEach(row => {
  350. chapterSteps.push(`「${index}」${row.short}`);
  351. index += 1;
  352. });
  353. this.setState({ articleMap: map, trailArticles, chapterSteps, introduction, chapterMap, exerciseChapter });
  354. });
  355. })
  356. .then(() => {
  357. return Sentence.listPaper().then(result => {
  358. this.setState({ paperList: result, paperFilterList: result });
  359. });
  360. });
  361. }
  362. }
  363. refreshPreview() {
  364. const { pt } = this.state;
  365. switch (pt) {
  366. case PREVIEW_LIST:
  367. this.refreshListPreview();
  368. break;
  369. case PREVIEW_CLASS:
  370. default:
  371. this.refreshClassProcess();
  372. break;
  373. }
  374. }
  375. refreshClassProcess() {
  376. Course.classProcess().then(result => {
  377. const classProcess = {};
  378. for (let i = 0; i < result.length; i += 1) {
  379. const item = result[i];
  380. classProcess[item.category].push(item);
  381. }
  382. this.setState({ classProcess });
  383. });
  384. }
  385. refreshListPreview() {
  386. Question.listPreview().then(result => {
  387. this.setState({ previews: result });
  388. });
  389. }
  390. refreshExercise(tab) {
  391. const { map, tab1 } = this.state;
  392. let { tab2 } = this.state;
  393. if (!map) {
  394. // 等待数据加载
  395. return;
  396. }
  397. const subject = map[tab];
  398. // 切换tab1的情况
  399. if (tab2 === '' || tab1 !== tab) {
  400. tab2 = subject.children[0].key;
  401. this.setState({ tab2 });
  402. }
  403. const type = map[tab2];
  404. Main.getExerciseChildren(type.id, true).then(result => {
  405. const exerciseChild = result;
  406. this.setState({ exerciseChild });
  407. });
  408. Question.getExerciseProgress(type.id).then((r => {
  409. const exerciseProgress = getMap(r, 'id');
  410. this.setState({ exerciseProgress });
  411. }));
  412. }
  413. onChangePreviewType(type) {
  414. this.setState({ pt: type });
  415. this.refreshPreview();
  416. }
  417. onChangeTab(level, tab) {
  418. const { tab1, tab2 } = this.state;
  419. // this.refreshData(tab);
  420. this.refreshQuery(Object.assign({ tab1, tab2 }, { [`tab${level}`]: tab }));
  421. }
  422. previewAction(type, item) {
  423. switch (type) {
  424. case 'start':
  425. this.start('preview', item);
  426. break;
  427. case 'restart':
  428. this.restart(item);
  429. break;
  430. case 'continue':
  431. this.continue('preview', item);
  432. break;
  433. default:
  434. break;
  435. }
  436. }
  437. restart(item) {
  438. asyncConfirm('提示', '是否重置', () => {
  439. Question.restart(item.report.id).then(() => {
  440. this.refresh();
  441. });
  442. });
  443. }
  444. start(type, item) {
  445. linkTo(`/paper/process/${type}/${item.id}`);
  446. }
  447. continue(type, item) {
  448. linkTo(`/paper/process/${type}/${item.id}?r=${item.report.id}`);
  449. }
  450. viewReport(item) {
  451. linkTo(`/paper/report/${item.report.id}`);
  452. }
  453. activeSentence() {
  454. Sentence.active(this.code)
  455. .then(() => {
  456. // 重新获取长难句信息
  457. User.clearSentenceTrail();
  458. this.setState({ sentence: null, articleMap: null, paperList: null });
  459. this.refresh();
  460. });
  461. }
  462. trailSentence() {
  463. this.setState({ sentenceInput: false });
  464. User.sentenceTrail();
  465. }
  466. sentenceRead(article) {
  467. if (article) {
  468. linkTo(`/sentence/read?chapter=${article.chapter}&part=${article.part}`);
  469. } else {
  470. linkTo('/sentence/read');
  471. }
  472. }
  473. sentenceFilter(value) {
  474. const { paperList } = this.state;
  475. const list = paperList.filter(row => {
  476. const finish = row.paper ? row.paper.times > 0 : false;
  477. if (value === 0) {
  478. return !finish;
  479. }
  480. return finish;
  481. });
  482. this.setState({ paperFilterList: list, paperChecked: value });
  483. }
  484. clearExercise() {
  485. My.clearLatestExercise();
  486. this.setState({ latest: null });
  487. }
  488. renderView() {
  489. const { tab1 = {}, tab2 = {}, tabs, map = {}, latest } = this.state;
  490. const children = (map[tab1] || {}).children || [];
  491. return (
  492. <div>
  493. {latest && <Continue
  494. data={latest}
  495. onClose={() => {
  496. this.clearExercise();
  497. }}
  498. onContinue={() => {
  499. }}
  500. onRestart={() => {
  501. }}
  502. onNext={() => {
  503. }} />}
  504. <div className="content">
  505. <Module className="m-t-2">
  506. <Tabs type="card" active={tab1} tabs={tabs} onChange={key => {
  507. this.onChangeTab(1, key);
  508. }} />
  509. {children.length > 1 && <Tabs active={tab2} tabs={children} onChange={key => this.onChangeTab(2, key)} />}
  510. </Module>
  511. {tab1 !== SENTENCE && tab1 !== PREVIEW && this.renderExercise()}
  512. {tab1 === SENTENCE && this.renderSentence()}
  513. {tab1 === PREVIEW && this.renderPreview()}
  514. </div>
  515. </div>
  516. );
  517. }
  518. renderPreview() {
  519. const { previewType } = this.state;
  520. switch (previewType) {
  521. case PREVIEW_CLASS:
  522. return this.renderPreviewClass();
  523. case PREVIEW_LIST:
  524. return this.renderPreviewList();
  525. default:
  526. return <div />;
  527. }
  528. }
  529. renderPreviewClass() {
  530. const { allClass, classProcess } = this.state;
  531. return (
  532. <div className="work-body">
  533. <div className="work-nav">
  534. <div className="left">完成情况</div>
  535. <div className="right theme c-p" onClick={() => this.onChangePreviewType(PREVIEW_LIST)}>
  536. 全部作业 >
  537. </div>
  538. </div>
  539. <Division col="3">
  540. {allClass.map(item => {
  541. return <Card data={item} process={classProcess[item.id]} previewAction={this.previewAction} />;
  542. })}
  543. </Division>
  544. </div>
  545. );
  546. }
  547. renderPreviewList() {
  548. const { previews } = this.state;
  549. return (
  550. <div className="work-body">
  551. <div className="work-nav">
  552. <div className="left">全部作业</div>
  553. <div className="right theme c-p" onClick={() => this.onChangePreviewType(PREVIEW_CLASS)}>
  554. 我的课程 >
  555. </div>
  556. </div>
  557. <ListTable
  558. filters={[
  559. {
  560. type: 'radio',
  561. checked: 'today',
  562. list: [{ key: 'today', title: '今日需完成' }, { key: 'tomorrow', title: '明日需完成' }],
  563. },
  564. {
  565. type: 'radio',
  566. checked: 'unfinish',
  567. list: [{ key: 'unfinish', title: '未完成' }, { key: 'finish', title: '已完成' }],
  568. },
  569. { type: 'select', checked: 'all', list: [{ key: 'all', title: '全部' }] },
  570. ]}
  571. data={previews}
  572. columns={this.columns}
  573. />
  574. </div>
  575. );
  576. }
  577. renderSentence() {
  578. const { sentence = {}, sentenceInput } = this.state;
  579. const { sentenceTrail } = this.props.user;
  580. if (sentenceInput !== true && (sentence.code || sentenceTrail)) {
  581. return this.renderSentenceArticle();
  582. }
  583. return this.renderInputCode();
  584. }
  585. renderSentenceArticle() {
  586. const { sentence = {}, introduction, chapterSteps, chapterStep = 1, exerciseChapter = {}, chapterMap = {}, articleMap = {}, trailArticles = [], paperFilterList = [], paperList = [], paperChecked } = this.state;
  587. const { sentenceTrail } = this.props.user;
  588. let maxStep = 0;
  589. if (sentenceTrail) {
  590. // 试用只能访问第一step
  591. maxStep = 1;
  592. // 查找练习章节
  593. }
  594. // 减去前言计算chapter
  595. const chapter = introduction ? chapterStep - 1 : chapterStep;
  596. const chapterInfo = chapterMap[chapter] || {};
  597. let isExercise = false;
  598. if (chapterInfo && chapterInfo.exercise) {
  599. isExercise = true;
  600. }
  601. return <div>
  602. {sentence.code && <div className='sentence-code'>CODE: {sentence.code}</div>}
  603. {sentenceTrail && <div className='sentence-code'>CODE: <Link to=''>去获取</Link><a onClick={() => {
  604. this.setState({ sentenceInput: true });
  605. }}>输入</a></div>}
  606. <Module>
  607. <Step
  608. list={chapterSteps}
  609. step={chapterStep}
  610. onClick={(step) => {
  611. this.setState({ chapterStep: step });
  612. }}
  613. message='请购买后访问'
  614. maxStep={maxStep}
  615. />
  616. </Module>
  617. {/* 正常前言 */}
  618. {sentence.code && chapter === 0 && <List
  619. // title={chapterInfo.title}
  620. list={[introduction]}
  621. onClick={() => {
  622. this.sentenceRead();
  623. }}
  624. />}
  625. {/* 正常文章 */}
  626. {sentence.code && chapter && !isExercise && <List
  627. position={`Chapter${chapter}`}
  628. title={chapterInfo.title}
  629. list={articleMap[chapter]}
  630. onClick={(part) => {
  631. this.sentenceRead(part);
  632. }}
  633. />}
  634. {/* 正常练习 */}
  635. {sentence.code && isExercise && <ListTable
  636. position={`Chapter${chapter}`}
  637. title={chapterInfo.title}
  638. filters={[{
  639. type: 'radio',
  640. checked: paperChecked,
  641. list: [{ key: 0, title: '未完成' }, { key: 1, title: '已完成' }],
  642. onChange: (item) => {
  643. this.sentenceFilter(item.key);
  644. },
  645. }]}
  646. data={paperFilterList}
  647. columns={this.sentenceColums}
  648. />}
  649. {/* 试读文章 */}
  650. {sentenceTrail && trailArticles.map((info) => {
  651. return <List
  652. position={info.value ? `Chapter${info.value}` : null}
  653. title={info.title}
  654. list={info.articles}
  655. onClick={(part) => {
  656. this.sentenceRead(part);
  657. }}
  658. />;
  659. })}
  660. {/* 试练 */}
  661. {sentenceTrail && <ListTable
  662. position={`Chapter${exerciseChapter.value}`}
  663. title={exerciseChapter.title}
  664. data={paperList}
  665. columns={this.sentenceColums}
  666. />}
  667. </div>;
  668. }
  669. renderInputCode() {
  670. return (
  671. <Module className="code-module">
  672. <div className="title">输入《千行GMAT长难句》专属 Code,解锁在线练习功能。</div>
  673. <div className="input-block">
  674. <Input size="lager" placeholder="请输入CODE" onChange={(value) => {
  675. this.code = value;
  676. }} />
  677. <Button size="lager" onClick={() => {
  678. this.activeSentence();
  679. }}>解锁</Button>
  680. </div>
  681. <div className="tip">
  682. <Link to="/" className="left link">
  683. 什么是CODE?
  684. </Link>
  685. <span>没有 CODE?</span>
  686. <Link to="/" className="link">
  687. 去获取 >>
  688. </Link>
  689. <a onClick={() => {
  690. this.trailSentence();
  691. }} className="right link">
  692. 试用 >>
  693. </a>
  694. </div>
  695. </Module>
  696. );
  697. }
  698. renderExercise() {
  699. return <div />;
  700. }
  701. }