page.js 34 KB

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