page.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116
  1. import React from 'react';
  2. import './index.less';
  3. import { Modal } from 'antd';
  4. import { Link } from 'react-router-dom';
  5. import Page from '@src/containers/Page';
  6. import { asyncConfirm, asyncSMessage } from '@src/services/AsyncTools';
  7. import { formatTreeData, formatSeconds, formatDate, formatPercent, getMap } from '@src/services/Tools';
  8. import Continue from '../../../components/Continue';
  9. import Step from '../../../components/Step';
  10. import Panel from '../../../components/Panel';
  11. import List from '../../../components/List';
  12. import Tabs from '../../../components/Tabs';
  13. import Module from '../../../components/Module';
  14. import Input from '../../../components/Input';
  15. import Button from '../../../components/Button';
  16. import AnswerButton from '../../../components/AnswerButton';
  17. import Division from '../../../components/Division';
  18. import { Card1 } from '../../../components/Card';
  19. import ListTable from '../../../components/ListTable';
  20. import ProgressText from '../../../components/ProgressText';
  21. import IconButton from '../../../components/IconButton';
  22. import QAList from '../../../components/QAList';
  23. import { Main } from '../../../stores/main';
  24. import { My } from '../../../stores/my';
  25. import { Sentence } from '../../../stores/sentence';
  26. import { Question } from '../../../stores/question';
  27. import { Course } from '../../../stores/course';
  28. import { User } from '../../../stores/user';
  29. import { CourseModuleShow, CourseModule } from '../../../../Constant';
  30. import { Order } from '../../../stores/order';
  31. const SENTENCE = 'sentence';
  32. const PREVIEW = 'preview';
  33. const PREVIEW_COURSE = 'PREVIEW_COURSE';
  34. const PREVIEW_LIST = 'PREVIEW_LIST';
  35. const CourseModuleMap = getMap(CourseModule, 'value', 'label');
  36. const CourseSorted = getMap([{ value: 'sc', sort: 1 }, { value: 'rc', sort: 2 }, { value: 'cr', sort: 3 }], 'value', 'sort');
  37. const exerciseColumns = [{
  38. title: '练习册',
  39. width: 250,
  40. align: 'left',
  41. render: record => {
  42. let progress = 0;
  43. if (record.report) {
  44. progress = formatPercent(record.report.userNumber, record.report.questionNumber);
  45. }
  46. return (
  47. <div className="table-row">
  48. <div className="night f-s-16">{record.title}</div>
  49. <div>
  50. <ProgressText progress={progress} times={record.paper ? record.paper.times : 0} size="small" />
  51. </div>
  52. </div>
  53. );
  54. },
  55. }, {
  56. title: '正确率',
  57. width: 150,
  58. align: 'left',
  59. render: item => {
  60. return (
  61. <div className="table-row">
  62. <div className="night f-s-16 f-w-b">--</div>
  63. <div className="f-s-12">{formatPercent(item.stat.totalCorrect, item.stat.totalNumber, false)}</div>
  64. </div>
  65. );
  66. },
  67. }, {
  68. title: '全站用时',
  69. width: 150,
  70. align: 'left',
  71. render: record => {
  72. let time = '--';
  73. if (record.paper) {
  74. time = formatSeconds(record.paper.report.userTime / record.paper.report.userNumber);
  75. }
  76. return (
  77. <div className="table-row">
  78. <div className="night f-s-16 f-w-b">{time}</div>
  79. <div className="f-s-12">全站{formatSeconds(record.stat.totalTime / record.stat.totalNumber)}</div>
  80. </div>
  81. );
  82. },
  83. }, {
  84. title: '最近做题',
  85. width: 150,
  86. align: 'left',
  87. render: (record) => {
  88. const time = record.report ? record.report.updateTime : record.paper ? record.paper.latestTime : null;
  89. return (
  90. <div className="table-row">
  91. <div>{time && formatDate(time, 'YYYY-MM-DD')}</div>
  92. <div>{time && formatDate(time, 'HH:mm')}</div>
  93. </div>
  94. );
  95. },
  96. }, {
  97. title: '操作',
  98. width: 180,
  99. align: 'left',
  100. render: record => {
  101. return (
  102. <div className="table-row p-t-1">
  103. {!record.report && <IconButton type="start" tip="Start" onClick={() => {
  104. Question.startLink('exercise', record);
  105. }} />}
  106. {(record.report && !record.report.isFinish) && <IconButton className="m-r-2" type="continue" tip="Continue" onClick={() => {
  107. Question.continueLink('exercise', record);
  108. }} />}
  109. <IconButton type="restart" tip="Restart" onClick={() => {
  110. this.restart(record);
  111. }} />
  112. </div>
  113. );
  114. },
  115. }, {
  116. title: '报告',
  117. width: 30,
  118. align: 'right',
  119. render: record => {
  120. return (
  121. <div className="table-row p-t-1">
  122. {record.report && record.report.isFinish && <IconButton type="report" tip="Report" onClick={() => {
  123. Question.reportLink(record);
  124. }} />}
  125. </div>
  126. );
  127. },
  128. }];
  129. export default class extends Page {
  130. constructor(props) {
  131. super(props);
  132. this.sentenceColums = [{
  133. title: '练习册',
  134. width: 250,
  135. align: 'left',
  136. render: record => {
  137. let progress = 0;
  138. if (record.report) {
  139. progress = formatPercent(record.report.userNumber, record.report.questionNumber);
  140. }
  141. return (
  142. <div className="table-row">
  143. <div className="night f-s-16">{record.title}</div>
  144. <div>
  145. <ProgressText progress={progress} times={record.paper ? record.paper.times : 0} size="small" />
  146. </div>
  147. </div>
  148. );
  149. },
  150. }, {
  151. title: '正确率',
  152. width: 150,
  153. align: 'left',
  154. render: record => {
  155. let correct = '--';
  156. if (record.report) {
  157. correct = formatPercent(record.report.userCorrect, record.report.userNumber, false);
  158. }
  159. return (
  160. <div className="table-row">
  161. <div className="night f-s-16 f-w-b">{correct}</div>
  162. <div className="f-s-12">
  163. 全站{formatPercent(record.stat.totalCorrect, record.stat.totalNumber, false)}
  164. </div>
  165. </div>
  166. );
  167. },
  168. }, {
  169. title: '全站用时',
  170. width: 150,
  171. align: 'left',
  172. render: record => {
  173. let time = '--';
  174. if (record.report) {
  175. time = formatSeconds(record.report.userTime / record.report.userNumber);
  176. }
  177. return (
  178. <div className="table-row">
  179. <div className="night f-s-16 f-w-b">{time}</div>
  180. <div className="f-s-12">全站{formatSeconds(record.stat.totalTime / record.stat.totalNumber)}</div>
  181. </div>
  182. );
  183. },
  184. }, {
  185. title: '最近做题',
  186. width: 150,
  187. align: 'left',
  188. render: record => {
  189. const time = record.report ? record.report.updateTime : record.paper ? record.paper.latestTime : null;
  190. return (
  191. <div className="table-row">
  192. <div>{time && formatDate(time, 'YYYY-MM-DD')}</div>
  193. <div>{time && formatDate(time, 'HH:mm')}</div>
  194. </div>
  195. );
  196. },
  197. }, {
  198. title: '操作',
  199. width: 180,
  200. align: 'left',
  201. render: record => {
  202. return (
  203. <div className="table-row p-t-1">
  204. {!record.report && (
  205. <IconButton
  206. type="start"
  207. tip="Start"
  208. onClick={() => {
  209. Question.startLink('sentence', record);
  210. }}
  211. />
  212. )}
  213. {record.report && !record.report.isFinish && (
  214. <IconButton
  215. className="m-r-2"
  216. type="continue"
  217. tip="Continue"
  218. onClick={() => {
  219. Question.continueLink('sentence', record);
  220. }}
  221. />
  222. )}
  223. {record.report && !!record.report.isFinish && (
  224. <IconButton
  225. type="restart"
  226. tip="Restart"
  227. onClick={() => {
  228. this.restart(record);
  229. }}
  230. />
  231. )}
  232. </div>
  233. );
  234. },
  235. }, {
  236. title: '报告',
  237. width: 30,
  238. align: 'right',
  239. render: record => {
  240. return (
  241. <div className="table-row p-t-1">
  242. {record.report && record.report.isFinish && <IconButton type="report" tip="Report" onClick={() => {
  243. Question.reportLink(record);
  244. }} />}
  245. </div>
  246. );
  247. },
  248. }];
  249. }
  250. initState() {
  251. this.code = null;
  252. this.columns = exerciseColumns;
  253. this.exerciseProgress = {};
  254. this.courseProgress = {};
  255. this.inited = false;
  256. return {
  257. tab1: SENTENCE,
  258. tab2: '',
  259. previewType: PREVIEW_COURSE,
  260. tabs: [],
  261. allCourse: [],
  262. courseProgress: {},
  263. };
  264. }
  265. init() {
  266. Main.getExercise().then(result => {
  267. const list = result.map(row => {
  268. row.title = `${row.titleZh}${row.titleEn}`;
  269. row.key = row.extend;
  270. return row;
  271. });
  272. const tabs = formatTreeData(list, 'id', 'title', 'parentId');
  273. // 课程顶级分类
  274. const courseStructs = result.filter(row => row.isCourse && row.level === 1);
  275. courseStructs.unshift({ key: '', name: '全部' });
  276. tabs.push({ key: PREVIEW, name: '预习作业' });
  277. this.courseStructMap = getMap(courseStructs, 'id', 'title');
  278. this.setState({
  279. tabs,
  280. courseStructs,
  281. courseTabs: CourseModuleShow.map(row => {
  282. row.title = row.label;
  283. row.key = row.value;
  284. return row;
  285. }),
  286. });
  287. this.inited = true;
  288. this.refreshData();
  289. });
  290. }
  291. initData() {
  292. const { info = {} } = this.props.user;
  293. if (info.latestExercise) {
  294. // 获取最后一次做题记录
  295. Question.baseReport(info.latestExercise).then(result => {
  296. this.setState({ latest: result });
  297. });
  298. }
  299. const data = Object.assign(this.state, this.state.search);
  300. if (!data.tab1) {
  301. data.tab1 = SENTENCE;
  302. }
  303. if (data.recordId) {
  304. // 作业列表
  305. data.previewType = PREVIEW_LIST;
  306. }
  307. this.setState(data);
  308. if (this.inited) this.refreshData();
  309. }
  310. refreshData(tab) {
  311. const { tab1 } = this.state;
  312. switch (tab || tab1) {
  313. case SENTENCE:
  314. this.refreshSentence();
  315. break;
  316. case PREVIEW:
  317. this.refreshPreview();
  318. break;
  319. default:
  320. this.refreshExercise(tab || tab1);
  321. }
  322. }
  323. refreshSentence() {
  324. const { sentence } = this.state;
  325. if (!sentence) {
  326. User.clearSentenceTrail();
  327. Sentence.getInfo()
  328. .then(result => {
  329. // result.code = '123123';
  330. // result.trailPages = 20;
  331. this.setState({ sentence: result });
  332. return result;
  333. })
  334. .then(({ code, trailPages, chapters }) => {
  335. return Sentence.listArticle().then(result => {
  336. const chapterSteps = [];
  337. const chapterMap = {};
  338. const map = {};
  339. const trailArticles = [];
  340. let totalPage = 0;
  341. let introduction = null;
  342. let exerciseChapter = null;
  343. let index = 0;
  344. let lastChapter = -1;
  345. chapters.forEach(row => {
  346. chapterMap[row.value] = row;
  347. if (row.exercise) exerciseChapter = row;
  348. });
  349. result.forEach(article => {
  350. if (article.chapter === 0) introduction = article;
  351. if (!map[article.chapter]) {
  352. map[article.chapter] = [];
  353. }
  354. article.startPage = totalPage + 1;
  355. article.endPage = totalPage + article.pages;
  356. if (article.chapter) {
  357. article.position = `Part ${article.part}`;
  358. } else {
  359. // 设置list中的样式
  360. article.style = 'introduction';
  361. }
  362. totalPage += article.pages;
  363. if (article.startPage < trailPages) {
  364. if (lastChapter !== article.chapter) {
  365. lastChapter = article.chapter;
  366. trailArticles.push(Object.assign({ articles: [] }, chapterMap[article.chapter] || {}));
  367. }
  368. trailArticles[trailArticles.length - 1].articles.push(article);
  369. }
  370. map[article.chapter].push(article);
  371. });
  372. if (!code) {
  373. chapterSteps.push('试用');
  374. }
  375. // 添加前言
  376. if (introduction) {
  377. chapterSteps.push(`${introduction.title}`);
  378. chapterMap[0] = {
  379. title: introduction.title,
  380. value: 0,
  381. };
  382. }
  383. index += 1;
  384. chapters.forEach(row => {
  385. chapterSteps.push(`「${index}」${row.short}`);
  386. index += 1;
  387. });
  388. this.setState({ articleMap: map, trailArticles, chapterSteps, introduction, chapterMap, exerciseChapter });
  389. });
  390. })
  391. .then(() => {
  392. return Sentence.listPaper().then(result => {
  393. this.setState({ paperList: result, paperFilterList: result });
  394. });
  395. });
  396. }
  397. }
  398. refreshPreview() {
  399. const { previewType } = this.state;
  400. switch (previewType) {
  401. case PREVIEW_LIST:
  402. this.refreshListPreview();
  403. break;
  404. case PREVIEW_COURSE:
  405. default:
  406. this.refreshCourseProcess();
  407. break;
  408. }
  409. }
  410. refreshCourseProcess() {
  411. const { courseTabs, courseStructs, struct } = this.state;
  412. let { tab2 } = this.state;
  413. let tab;
  414. if (tab2 === '') {
  415. tab2 = courseTabs[0].key;
  416. this.setState({ tab2 });
  417. ([tab] = courseTabs);
  418. } else {
  419. ([tab] = courseTabs.filter(row => row.key === tab2));
  420. }
  421. const [courseStruct] = courseStructs.filter(row => row.key === struct);
  422. Course.progress(tab.value, courseStruct ? courseStruct.id : null).then(result => {
  423. const courseMap = {};
  424. // 排序:sc,rc,cr
  425. result = result.map(row => {
  426. row.sort = CourseSorted[row.course.extend] || 10;
  427. return row;
  428. });
  429. result.sort((a, b) => {
  430. return a.sort < b.sort ? -1 : (a.sort > b.sort ? 1 : 0);
  431. });
  432. const now = new Date().getTime();
  433. courseMap.open = result.filter(row => !row.isUsed);
  434. courseMap.end = result.filter(row => row.isUsed && (!row.isStop || (row.isStop && row.restoreTime)) && new Date(row.useEndTime).getTime() < now);
  435. courseMap.process = result.filter(row => row.isUsed && (row.isStop || row.restoreTime) && new Date(row.useEndTime).getTime() >= now);
  436. this.setState({ courseMap });
  437. });
  438. Main.listFaq({ page: 1, size: 100, channel: 'preview' }).then(result => {
  439. this.setState({ faqs: result.list });
  440. });
  441. }
  442. refreshListPreview() {
  443. const { recordId, endTime, finish } = this.state;
  444. Course.listPreview({ recordId, endTime, finish }).then(result => {
  445. this.setState({ previews: result.list });
  446. });
  447. Course.record(recordId).then(result => {
  448. this.setState({ record: result });
  449. });
  450. }
  451. refreshExercise(tab) {
  452. const { tabs, tab1 } = this.state;
  453. let { tab2 } = this.state;
  454. if (!tabs) {
  455. // 等待数据加载
  456. return;
  457. }
  458. const [subject] = tabs.filter(row => row.key === tab);
  459. // 切换tab1的情况
  460. if (tab2 === '' || tab1 !== tab) {
  461. tab2 = subject.children[0].key;
  462. this.setState({ tab2 });
  463. }
  464. const [type] = subject.children.filter(row => row.key === tab2);
  465. Question.getExerciseProgress(type.id).then(result => {
  466. // const exerciseProgress = getMap(r, 'id');
  467. result = result.map(row => {
  468. row.info = [
  469. {
  470. title: '已做',
  471. number: row.userNumber || '-',
  472. unit: '题',
  473. },
  474. {
  475. title: '剩余',
  476. number: row.userNumber ? row.questionNumber - row.userNumber : '-',
  477. unit: '题',
  478. },
  479. {
  480. title: '总计',
  481. number: row.questionNumber || 0,
  482. unit: '题',
  483. },
  484. ];
  485. if (row.userStat) {
  486. row.correct = formatPercent(row.userStat.userCorrect, row.userStat.userNumber, false);
  487. } else {
  488. row.correct = '--';
  489. }
  490. row.progress = formatPercent(row.questionNumber - row.userNumber || 0, row.questionNumber);
  491. row.totalCorrect = formatPercent(row.stat.totalCorrect, row.stat.totalNumber, false);
  492. row.pieValue = formatPercent(row.userNumber, row.questionNumber);
  493. row.pieText = formatPercent(row.userNumber, row.questionNumber, false);
  494. row.pieSubText = `共${row.questionNumber}题`;
  495. row.children = row.children.map(r => {
  496. r.title = r.title || r.titleZh;
  497. r.progress = formatPercent(r.userNumber, r.questionNumber);
  498. return r;
  499. });
  500. return row;
  501. });
  502. this.setState({ exerciseProgress: result });
  503. });
  504. Main.listFaq({ page: 1, size: 100, channel: 'exercise' }).then(result => {
  505. this.setState({ faqs: result.list });
  506. });
  507. }
  508. onChangeTab(level, tab) {
  509. const { tab1 } = this.state;
  510. const data = {};
  511. if (level > 1) {
  512. data.tab1 = tab1;
  513. data.tab2 = tab;
  514. } else {
  515. data.tab1 = tab;
  516. }
  517. // this.refreshData(tab);
  518. this.refreshQuery(data);
  519. }
  520. onChangeCourse(struct) {
  521. const { tab1, tab2 } = this.state;
  522. const data = {
  523. tab1, tab2, struct,
  524. };
  525. this.refreshQuery(data);
  526. }
  527. onPreviewCourse() {
  528. const { tab1, tab2, struct } = this.state;
  529. const data = {
  530. tab1, tab2, struct,
  531. };
  532. this.refreshQuery(data);
  533. }
  534. onPreviewList(recordId) {
  535. const { tab1, tab2, struct } = this.state;
  536. const data = {
  537. tab1, tab2, struct, recordId,
  538. };
  539. this.refreshQuery(data);
  540. }
  541. previewAction(type, item) {
  542. switch (type) {
  543. case 'start':
  544. Question.startLink('preview', item);
  545. break;
  546. case 'restart':
  547. this.restart(item);
  548. break;
  549. case 'continue':
  550. Question.continueLink('preview', item);
  551. break;
  552. default:
  553. break;
  554. }
  555. }
  556. // 开通课程
  557. open(recordId) {
  558. Order.useRecord(recordId).then(() => {
  559. asyncSMessage('开通成功');
  560. this.refresh();
  561. });
  562. }
  563. restart(item) {
  564. asyncConfirm('提示', '是否重置', () => {
  565. Question.restart(item.paper.id).then(() => {
  566. this.refresh();
  567. });
  568. });
  569. }
  570. exerciseList(item) {
  571. User.needLogin().then(() => {
  572. linkTo(`/exercise/list/${item.id}`);
  573. });
  574. }
  575. activeSentence() {
  576. User.needLogin().then(() => {
  577. Sentence.active(this.code)
  578. .then(() => {
  579. // 重新获取长难句信息
  580. User.clearSentenceTrail();
  581. this.setState({ sentence: null, articleMap: null, paperList: null });
  582. this.refresh();
  583. })
  584. .catch(err => {
  585. this.setState({ sentenceError: err.message });
  586. });
  587. });
  588. }
  589. trailSentence() {
  590. User.needLogin().then(() => {
  591. User.sentenceTrail();
  592. this.setState({ sentenceError: null });
  593. });
  594. }
  595. sentenceRead(article) {
  596. if (article) {
  597. linkTo(`/sentence/read?chapter=${article.chapter}&part=${article.part}`);
  598. } else {
  599. linkTo('/sentence/read');
  600. }
  601. }
  602. sentenceFilter(value) {
  603. const { paperChecked, paperList } = this.state;
  604. value = paperChecked === value ? null : value;
  605. const list = paperList.filter(row => {
  606. const finish = row.paper ? row.paper.times > 0 : false;
  607. if (value === 0) {
  608. return !finish;
  609. } if (value === 1) {
  610. return finish;
  611. }
  612. return true;
  613. });
  614. this.setState({ paperFilterList: list, paperChecked: value });
  615. }
  616. clearExercise() {
  617. My.clearLatestExercise();
  618. this.setState({ latest: null });
  619. }
  620. renderView() {
  621. const { tab1, tab2, tabs, latest, sentenceModel, previewType, courseTabs = [] } = this.state;
  622. const [subject] = tabs.filter(row => row.key === tab1);
  623. const children = (subject && subject.children) ? subject.children : (tab1 === 'preview' && previewType === PREVIEW_COURSE ? courseTabs : []);
  624. return (
  625. <div>
  626. {latest && (
  627. <Continue
  628. data={latest}
  629. onClose={() => {
  630. this.clearExercise();
  631. }}
  632. onContinue={() => {
  633. Question.continueLink('exercise', latest);
  634. }}
  635. onRestart={() => {
  636. this.restart(latest);
  637. }}
  638. onNext={() => {
  639. Question.continueLink('exercise', latest);
  640. }}
  641. />
  642. )}
  643. <div className="content">
  644. <Module className="m-t-2">
  645. <Tabs
  646. type="card"
  647. active={tab1}
  648. tabs={tabs}
  649. onChange={key => {
  650. this.onChangeTab(1, key);
  651. }}
  652. />
  653. {children && children.length > 1 && (
  654. <Tabs active={tab2} tabs={children} onChange={key => this.onChangeTab(2, key)} />
  655. )}
  656. </Module>
  657. {tab1 !== SENTENCE && tab1 !== PREVIEW && this.renderExercise()}
  658. {tab1 === SENTENCE && this.renderSentence()}
  659. {tab1 === PREVIEW && this.renderPreview()}
  660. {this.state.faqs && <QAList data={this.state.faqs} active={'faq'} tabs={[{ key: 'faq', name: 'FAQs' }]} />}
  661. </div>
  662. {sentenceModel && this.renderInputCodeModel()}
  663. </div>
  664. );
  665. }
  666. renderPreview() {
  667. const { previewType } = this.state;
  668. switch (previewType) {
  669. case PREVIEW_COURSE:
  670. return this.renderPreviewCourse();
  671. case PREVIEW_LIST:
  672. return this.renderPreviewList();
  673. default:
  674. return <div />;
  675. }
  676. }
  677. renderPreviewCourse() {
  678. const { courseStructs, struct, tab2, courseTabs, courseMap = {} } = this.state;
  679. return (
  680. <div className="work-body">
  681. <div className="work-nav" hidden={courseTabs && courseTabs.length > 0 && tab2 !== courseTabs[0].key}>
  682. <Tabs
  683. type="tag"
  684. active={struct || ''}
  685. space={5}
  686. tabs={courseStructs}
  687. onChange={key => {
  688. this.onChangeCourse(key);
  689. }}
  690. />
  691. </div>
  692. <div className="work-nav">
  693. <div className="left">学习中</div>
  694. </div>
  695. <Division col="3">
  696. {(courseMap.process || []).map(row => {
  697. return <Card1
  698. title={`${row.course.title}${row.vsNo > 0 ? `V${row.vsNo}` : ''}${row.number > 0 ? `(${row.number}课时)` : ''}`}
  699. tag={CourseModuleMap[row.course.courseModule]}
  700. status={row.isStop && !row.isSuspend ? 'stop' : 'ing'}
  701. list={row.papers.map(r => {
  702. let progress = 0;
  703. if (r.report) {
  704. progress = formatPercent(r.report.userNumber, r.report.questionNumber);
  705. }
  706. r.progress = progress;
  707. return r;
  708. })}
  709. data={row}
  710. onPreview={() => {
  711. this.onPreviewList(row.id);
  712. }}
  713. previewAction={(type, item) => {
  714. this.previewAction(type, item);
  715. }}
  716. />;
  717. })}
  718. </Division>
  719. <div className="work-nav">
  720. <div className="left">待开通</div>
  721. </div>
  722. <Division col="3">
  723. {(courseMap.wait || []).map(row => {
  724. return <Card1
  725. title={`${row.course.title}${row.vsNo > 0 ? `V${row.vsNo}` : ''}${row.number > 0 ? `(${row.number}课时)` : ''}`}
  726. tag={CourseModuleMap[row.course.courseModule]}
  727. status='open'
  728. data={row}
  729. onOpen={() => {
  730. this.open(row.id);
  731. }}
  732. />;
  733. })}
  734. </Division>
  735. <div className="work-nav">
  736. <div className="left">已结束</div>
  737. </div>
  738. <Division col="3">
  739. {(courseMap.end || []).map(row => {
  740. return <Card1
  741. title={`${row.course.title}${row.vsNo > 0 ? `V${row.vsNo}` : ''}${row.number > 0 ? `(${row.number}课时)` : ''}`}
  742. tag={CourseModuleMap[row.course.courseModule]}
  743. status='end'
  744. data={row}
  745. onPreview={() => {
  746. this.onPreviewList(row.id);
  747. }}
  748. />;
  749. })}
  750. </Division>
  751. </div>
  752. );
  753. }
  754. renderPreviewList() {
  755. const { previews = [], record = {}, search = {} } = this.state;
  756. const { finish, endTime } = search;
  757. let finishTime = '';
  758. if (endTime) {
  759. const endTimeD = new Date(endTime);
  760. const now = new Date();
  761. if (now.getTime() + 86400000 > endTimeD.getTime()) {
  762. finishTime = 'today';
  763. } else {
  764. finishTime = 'tomorrow';
  765. }
  766. }
  767. return (
  768. <div className="work-body">
  769. <div className="work-nav">
  770. <div className="left">{`${(record.course || {}).title || ''}${record.vsNo > 0 ? `V${record.vsNo}` : ''}${record.number > 0 ? `(${record.number}课时)` : ''}`}全部作业</div>
  771. <div className="right theme c-p" onClick={() => this.onPreviewCourse()}>
  772. 我的课程 >
  773. </div>
  774. </div>
  775. <ListTable
  776. filters={[
  777. {
  778. type: 'radio',
  779. checked: finishTime,
  780. list: [{ key: 'today', title: '今日需完成' }, { key: 'tomorrow', title: '明日需完成' }],
  781. onChange: (item) => {
  782. if (item.key === finishTime) {
  783. this.search({ endTime: null });
  784. } else if (item.key === 'today') {
  785. const a = new Date();
  786. a.setDate(a.getDate() + 1);
  787. a.setHours(0);
  788. a.setMinutes(0);
  789. a.setMilliseconds(0);
  790. a.setSeconds(0);
  791. this.search({ endTime: formatDate(a, 'YYYY-MM-DD') });
  792. } else if (item.key === 'tomorrow') {
  793. const a = new Date();
  794. a.setDate(a.getDate() + 2);
  795. a.setHours(0);
  796. a.setMinutes(0);
  797. a.setMilliseconds(0);
  798. a.setSeconds(0);
  799. this.search({ endTime: formatDate(a, 'YYYY-MM-DD') });
  800. } else {
  801. this.search({ endTime: null });
  802. }
  803. },
  804. },
  805. {
  806. type: 'radio',
  807. checked: finish,
  808. list: [{ key: '0', title: '未完成' }, { key: '1', title: '已完成' }],
  809. onChange: (item) => {
  810. if (item.key === finish) {
  811. this.search({ finish: null });
  812. } else if (item.key === '0') {
  813. this.search({ finish: '0' });
  814. } else if (item.key === '1') {
  815. this.search({ finish: '1' });
  816. } else {
  817. this.search({ finish: null });
  818. }
  819. },
  820. },
  821. ]}
  822. data={previews}
  823. columns={this.columns}
  824. />
  825. </div>
  826. );
  827. }
  828. renderSentence() {
  829. const { sentence = {} } = this.state;
  830. const { sentenceTrail } = this.props.user;
  831. if (sentence.code || sentenceTrail) {
  832. return this.renderSentenceArticle();
  833. }
  834. return this.renderInputCode();
  835. }
  836. renderSentenceArticle() {
  837. const {
  838. sentence = {},
  839. introduction,
  840. chapterSteps,
  841. chapterStep = 1,
  842. exerciseChapter = {},
  843. chapterMap = {},
  844. articleMap = {},
  845. trailArticles = [],
  846. paperFilterList = [],
  847. paperList = [],
  848. paperChecked,
  849. sentenceInfo = {},
  850. } = this.state;
  851. const { sentenceTrail } = this.props.user;
  852. let maxStep = 0;
  853. if (sentenceTrail) {
  854. // 试用只能访问第一step
  855. maxStep = 1;
  856. // 查找练习章节
  857. }
  858. // 减去前言计算chapter
  859. const chapter = introduction ? chapterStep - 1 : chapterStep;
  860. const chapterInfo = chapterMap[chapter] || {};
  861. let isExercise = false;
  862. if (chapterInfo && chapterInfo.exercise) {
  863. isExercise = true;
  864. }
  865. return (
  866. <div>
  867. {sentence.code && <div className="sentence-code">CODE: {sentence.code}</div>}
  868. {sentenceTrail && (
  869. <div className="sentence-code">
  870. CODE: <a href={sentenceInfo.link} target="_blank">去获取</a>
  871. <a
  872. onClick={() => {
  873. this.setState({ sentenceModel: true });
  874. }}
  875. >
  876. 输入
  877. </a>
  878. </div>
  879. )}
  880. <Module>
  881. <Step
  882. list={chapterSteps}
  883. step={chapterStep}
  884. onClick={step => {
  885. this.setState({ chapterStep: step });
  886. }}
  887. message="请购买后访问"
  888. maxStep={maxStep}
  889. />
  890. </Module>
  891. {/* 正常前言 */}
  892. {sentence.code && chapter === 0 && (
  893. <List
  894. // title={chapterInfo.title}
  895. list={[introduction]}
  896. onClick={() => {
  897. this.sentenceRead();
  898. }}
  899. />
  900. )}
  901. {/* 正常文章 */}
  902. {sentence.code && chapter > 0 && !isExercise && (
  903. <List
  904. position={`Chapter${chapter}`}
  905. title={chapterInfo.title}
  906. list={articleMap[chapter]}
  907. onClick={part => {
  908. this.sentenceRead(part);
  909. }}
  910. />
  911. )}
  912. {/* 正常练习 */}
  913. {sentence.code && isExercise && (
  914. <ListTable
  915. position={`Chapter${chapter}`}
  916. title={chapterInfo.title}
  917. filters={[
  918. {
  919. type: 'radio',
  920. checked: paperChecked,
  921. list: [{ key: 0, title: '未完成' }, { key: 1, title: '已完成' }],
  922. onChange: item => {
  923. this.sentenceFilter(item.key);
  924. },
  925. },
  926. ]}
  927. data={paperFilterList}
  928. columns={this.sentenceColums}
  929. />
  930. )}
  931. {/* 试读文章 */}
  932. {sentenceTrail &&
  933. trailArticles.map(info => {
  934. return (
  935. <List
  936. position={info.value ? `Chapter${info.value}` : null}
  937. title={info.title}
  938. list={info.articles}
  939. onClick={part => {
  940. this.sentenceRead(part);
  941. }}
  942. />
  943. );
  944. })}
  945. {/* 试练 */}
  946. {sentenceTrail && (
  947. <ListTable
  948. position={`Chapter${exerciseChapter.value}`}
  949. title={exerciseChapter.title}
  950. data={paperList}
  951. columns={this.sentenceColums}
  952. />
  953. )}
  954. </div>
  955. );
  956. }
  957. renderInputCode() {
  958. const { sentenceError, sentenceInfo = {} } = this.state;
  959. return (
  960. <Module className="code-module">
  961. <div className="title">输入《千行GMAT长难句》专属 Code,解锁在线练习功能。</div>
  962. <div className="input-block">
  963. <Input
  964. size="lager"
  965. placeholder="请输入CODE"
  966. onChange={value => {
  967. this.code = value;
  968. }}
  969. />
  970. <Button
  971. size="lager"
  972. onClick={() => {
  973. this.activeSentence();
  974. }}
  975. >
  976. 解锁
  977. </Button>
  978. {sentenceError && <div className="error">{sentenceError}</div>}
  979. </div>
  980. <div className="tip">
  981. <Link to="/" className="left link">
  982. 什么是CODE?
  983. </Link>
  984. <span>没有 CODE?</span>
  985. <a href={sentenceInfo.link} target="_blank" className="link">
  986. 去获取 >>
  987. </a>
  988. <a
  989. onClick={() => {
  990. this.trailSentence();
  991. }}
  992. className="right link"
  993. >
  994. 试用 >>
  995. </a>
  996. </div>
  997. </Module>
  998. );
  999. }
  1000. renderInputCodeModel() {
  1001. const { sentenceError } = this.state;
  1002. return (
  1003. <Modal visible closable={false} footer={false} title={false}>
  1004. <div className="code-module-modal">
  1005. <div className="title">请输入CODE</div>
  1006. <div className="desc">
  1007. <Input
  1008. onChange={value => {
  1009. this.code = value;
  1010. }}
  1011. />
  1012. {sentenceError && <div className="error">{sentenceError}</div>}
  1013. <div className="tip">
  1014. <Link to="/" className="right link">
  1015. 什么是CODE?
  1016. </Link>
  1017. </div>
  1018. </div>
  1019. <div className="btn-list">
  1020. <AnswerButton size="lager" theme="confirm" width={150} onClick={() => this.activeSentence()}>
  1021. 确认
  1022. </AnswerButton>
  1023. <AnswerButton
  1024. size="lager"
  1025. theme="cancel"
  1026. width={150}
  1027. onClick={() => this.setState({ sentenceModel: false })}
  1028. >
  1029. 取消
  1030. </AnswerButton>
  1031. </div>
  1032. </div>
  1033. </Modal>
  1034. );
  1035. }
  1036. renderExercise() {
  1037. const { exerciseProgress = [] } = this.state;
  1038. return (
  1039. <div>
  1040. <Division col={2}>
  1041. {(exerciseProgress || []).map(struct => {
  1042. const [first] = struct.children;
  1043. let col = 3;
  1044. if (first && first.type === 'paper') {
  1045. col = 5;
  1046. }
  1047. return (
  1048. <Panel
  1049. title={struct.titleEn}
  1050. message={struct.description}
  1051. data={struct}
  1052. col={col}
  1053. onClick={item => {
  1054. if (item.type === 'paper') {
  1055. if (item.progress === 0) {
  1056. Question.startLink('exercise', item);
  1057. } else if (item.progress === 100) {
  1058. Question.startLink('exercise', item);
  1059. } else {
  1060. Question.continueLink('exercise', item);
  1061. }
  1062. } else {
  1063. this.exerciseList(item);
  1064. }
  1065. }}
  1066. />
  1067. );
  1068. })}
  1069. </Division>
  1070. </div>
  1071. );
  1072. }
  1073. }