page.js 35 KB

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