page.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173
  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 InputItem from '../../../components/InputItem';
  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: 'faq',
  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. if (result.dataId) {
  351. Main.listComment({ page: 1, size: 100, channel: 'course_data', position: result.dataId }).then(r => {
  352. this.setState({ comments: r.list });
  353. });
  354. }
  355. return result;
  356. })
  357. .then(({ code, trailPages, chapters }) => {
  358. return Sentence.listArticle().then(result => {
  359. const chapterSteps = [];
  360. const chapterMap = {};
  361. const map = {};
  362. const trailArticles = [];
  363. let totalPage = 0;
  364. let introduction = null;
  365. let exerciseChapter = null;
  366. let index = 0;
  367. let lastChapter = -1;
  368. chapters.forEach(row => {
  369. chapterMap[row.value] = row;
  370. if (row.exercise) exerciseChapter = row;
  371. });
  372. result.forEach(article => {
  373. if (article.chapter === 0) introduction = article;
  374. if (!map[article.chapter]) {
  375. map[article.chapter] = [];
  376. }
  377. article.startPage = totalPage + 1;
  378. article.endPage = totalPage + article.pages;
  379. if (article.chapter) {
  380. article.position = `Part ${article.part}`;
  381. } else {
  382. // 设置list中的样式
  383. article.style = 'introduction';
  384. }
  385. totalPage += article.pages;
  386. if (article.startPage < trailPages) {
  387. if (lastChapter !== article.chapter) {
  388. lastChapter = article.chapter;
  389. trailArticles.push(Object.assign({ articles: [] }, chapterMap[article.chapter] || {}));
  390. }
  391. trailArticles[trailArticles.length - 1].articles.push(article);
  392. }
  393. map[article.chapter].push(article);
  394. });
  395. if (!code) {
  396. chapterSteps.push('试用');
  397. }
  398. // 添加前言
  399. if (introduction) {
  400. chapterSteps.push(`${introduction.title}`);
  401. chapterMap[0] = {
  402. title: introduction.title,
  403. value: 0,
  404. };
  405. }
  406. index += 1;
  407. chapters.forEach(row => {
  408. chapterSteps.push(`「${index}」${row.short}`);
  409. index += 1;
  410. });
  411. this.setState({ articleMap: map, trailArticles, chapterSteps, introduction, chapterMap, exerciseChapter });
  412. });
  413. })
  414. .then(() => {
  415. return Sentence.listPaper().then(result => {
  416. this.setState({ paperList: result, paperFilterList: result });
  417. });
  418. });
  419. Main.listFaq({ page: 1, size: 100, channel: 'exercise-sentence' }).then(result => {
  420. this.setState({ faqs: result.list });
  421. });
  422. }
  423. }
  424. refreshPreview() {
  425. const { previewType } = this.state;
  426. switch (previewType) {
  427. case PREVIEW_LIST:
  428. this.refreshListPreview();
  429. break;
  430. case PREVIEW_COURSE:
  431. default:
  432. this.refreshCourseProcess();
  433. break;
  434. }
  435. }
  436. refreshCourseProcess() {
  437. const { courseTabs, courseStructs, struct } = this.state;
  438. let { tab2 } = this.state;
  439. let tab;
  440. if (tab2 === '') {
  441. tab2 = courseTabs[0].key;
  442. this.setState({ tab2 });
  443. ([tab] = courseTabs);
  444. } else {
  445. ([tab] = courseTabs.filter(row => row.key === tab2));
  446. }
  447. const [courseStruct] = courseStructs.filter(row => row.key === struct);
  448. Course.progress(tab.value, courseStruct ? courseStruct.id : null).then(result => {
  449. const courseMap = {};
  450. // 排序:sc,rc,cr
  451. result = result.map(row => {
  452. row.sort = CourseSorted[row.course.extend] || 10;
  453. return row;
  454. });
  455. result.sort((a, b) => {
  456. return a.sort < b.sort ? -1 : (a.sort > b.sort ? 1 : 0);
  457. });
  458. const now = new Date().getTime();
  459. courseMap.open = result.filter(row => !row.isUsed && (!row.useEndTime || new Date(row.useEndTime).getTime() > now));
  460. courseMap.end = result.filter(row => (!row.isSuspend || (row.isSuspend && row.restoreTime)) && new Date(row.useEndTime).getTime() < now);
  461. courseMap.process = result.filter(row => row.isUsed && ((!row.isSuspend && new Date(row.useEndTime).getTime() >= now) || (row.isSuspend && !row.restoreTime)));
  462. this.setState({ courseMap });
  463. });
  464. Main.listFaq({ page: 1, size: 100, channel: 'exercise-preview' }).then(result => {
  465. this.setState({ faqs: result.list });
  466. });
  467. }
  468. refreshListPreview() {
  469. const { recordId, endTime, finish } = this.state;
  470. Course.listPreview({ recordId, endTime, finish }).then(result => {
  471. this.setState({ previews: result.list });
  472. });
  473. Course.record(recordId).then(result => {
  474. this.setState({ record: result });
  475. });
  476. }
  477. refreshExercise(tab) {
  478. const { tabs, tab1 } = this.state;
  479. let { tab2 } = this.state;
  480. if (!tabs) {
  481. // 等待数据加载
  482. return;
  483. }
  484. const [subject] = tabs.filter(row => row.key === tab);
  485. // 切换tab1的情况
  486. if (tab2 === '' || tab1 !== tab) {
  487. tab2 = subject.children[0].key;
  488. this.setState({ tab2 });
  489. }
  490. const [type] = subject.children.filter(row => row.key === tab2);
  491. Question.getExerciseProgress(type.id).then(result => {
  492. // const exerciseProgress = getMap(r, 'id');
  493. result = result.map(row => {
  494. row.info = [
  495. {
  496. title: '已做',
  497. number: row.userNumber || '-',
  498. unit: '题',
  499. },
  500. {
  501. title: '剩余',
  502. number: row.userNumber ? row.questionNumber - row.userNumber : '-',
  503. unit: '题',
  504. },
  505. {
  506. title: '总计',
  507. number: row.questionNumber || 0,
  508. unit: '题',
  509. },
  510. ];
  511. if (row.userStat) {
  512. row.correct = formatPercent(row.userStat.userCorrect, row.userStat.userNumber, false);
  513. } else {
  514. row.correct = '--';
  515. }
  516. row.progress = formatPercent(row.questionNumber - row.userNumber || 0, row.questionNumber);
  517. row.totalCorrect = formatPercent(row.stat.totalCorrect, row.stat.totalNumber, false);
  518. row.pieValue = formatPercent(row.userNumber, row.questionNumber);
  519. row.pieText = formatPercent(row.userNumber, row.questionNumber, false);
  520. row.pieSubText = `共${row.questionNumber}题`;
  521. row.children = row.children.map(r => {
  522. r.title = r.title || r.titleZh;
  523. r.progress = formatPercent(r.userNumber, r.questionNumber);
  524. return r;
  525. });
  526. return row;
  527. });
  528. this.setState({ exerciseProgress: result });
  529. });
  530. }
  531. onChangeTab(level, tab) {
  532. const { tab1 } = this.state;
  533. const data = {};
  534. if (level > 1) {
  535. data.tab1 = tab1;
  536. data.tab2 = tab;
  537. } else {
  538. data.tab1 = tab;
  539. }
  540. // this.refreshData(tab);
  541. this.refreshQuery(data);
  542. }
  543. onChangeCourse(struct) {
  544. const { tab1, tab2 } = this.state;
  545. const data = {
  546. tab1, tab2, struct,
  547. };
  548. this.refreshQuery(data);
  549. }
  550. onPreviewCourse() {
  551. const { tab1, tab2, struct } = this.state;
  552. const data = {
  553. tab1, tab2, struct,
  554. };
  555. this.refreshQuery(data);
  556. }
  557. onPreviewList(recordId) {
  558. const { tab1, tab2, struct } = this.state;
  559. const data = {
  560. tab1, tab2, struct, recordId,
  561. };
  562. this.refreshQuery(data);
  563. }
  564. previewAction(type, item) {
  565. switch (type) {
  566. case 'start':
  567. Question.startLink('preview', item);
  568. break;
  569. case 'restart':
  570. this.restart(item);
  571. break;
  572. case 'continue':
  573. Question.continueLink('preview', item);
  574. break;
  575. default:
  576. break;
  577. }
  578. }
  579. // 开通课程
  580. open(recordId) {
  581. Order.useRecord(recordId).then(() => {
  582. this.refresh();
  583. });
  584. }
  585. restart(item) {
  586. asyncConfirm('提示', '是否重置', () => {
  587. Question.restart(item.paper.id).then(() => {
  588. this.refresh();
  589. });
  590. });
  591. }
  592. exerciseList(item) {
  593. User.needLogin().then(() => {
  594. linkTo(`/exercise/list/${item.id}`);
  595. });
  596. }
  597. activeSentence() {
  598. User.needLogin().then(() => {
  599. Sentence.active(this.code)
  600. .then(() => {
  601. // 重新获取长难句信息
  602. User.clearSentenceTrail();
  603. this.setState({ sentence: null, articleMap: null, paperList: null });
  604. this.refresh();
  605. })
  606. .catch(err => {
  607. this.setState({ sentenceError: err.message });
  608. });
  609. });
  610. }
  611. trailSentence() {
  612. User.needLogin().then(() => {
  613. User.sentenceTrail();
  614. this.setState({ sentenceError: null });
  615. });
  616. }
  617. sentenceRead(article) {
  618. if (article) {
  619. linkTo(`/sentence/read?chapter=${article.chapter}&part=${article.part}`);
  620. } else {
  621. linkTo('/sentence/read');
  622. }
  623. }
  624. sentenceFilter(value) {
  625. const { paperChecked, paperList } = this.state;
  626. value = paperChecked === value ? null : value;
  627. const list = paperList.filter(row => {
  628. const finish = row.paper ? row.paper.times > 0 : false;
  629. if (value === 0) {
  630. return !finish;
  631. } if (value === 1) {
  632. return finish;
  633. }
  634. return true;
  635. });
  636. this.setState({ paperFilterList: list, paperChecked: value });
  637. }
  638. clearExercise() {
  639. My.clearLatestExercise()
  640. .then(() => {
  641. const { info } = this.props.user;
  642. info.latestExercise = 0;
  643. User.infoHandle(info);
  644. });
  645. this.setState({ latest: null });
  646. }
  647. renderView() {
  648. const { tab1, tab2, tabs, latest, sentenceModel, previewType, courseTabs = [] } = this.state;
  649. const [subject] = tabs.filter(row => row.key === tab1);
  650. const children = (subject && subject.children) ? subject.children : (tab1 === 'preview' && previewType === PREVIEW_COURSE ? courseTabs : []);
  651. return (
  652. <div>
  653. {latest && (
  654. <Continue
  655. data={latest}
  656. onClose={() => {
  657. this.clearExercise();
  658. }}
  659. onContinue={() => {
  660. Question.continueLink('exercise', { id: latest.originId, report: latest });
  661. }}
  662. onRestart={() => {
  663. this.restart(latest);
  664. Question.startLink('exercise', { id: latest.originId, report: latest });
  665. }}
  666. onNext={() => {
  667. Question.continueLink('exercise', { id: latest.originId, report: latest });
  668. }}
  669. />
  670. )}
  671. <div className="content">
  672. <Module className="m-t-2">
  673. <Tabs
  674. type="card"
  675. active={tab1}
  676. tabs={tabs}
  677. onChange={key => {
  678. this.onChangeTab(1, key);
  679. }}
  680. />
  681. {children && children.length > 1 && (
  682. <Tabs active={tab2} tabs={children} onChange={key => this.onChangeTab(2, key)} />
  683. )}
  684. </Module>
  685. {tab1 !== SENTENCE && tab1 !== PREVIEW && this.renderExercise()}
  686. {tab1 === SENTENCE && this.renderSentence()}
  687. {tab1 === PREVIEW && this.renderPreview()}
  688. {tab1 !== SENTENCE && this.state.faqs && <QAList data={this.state.faqs} active={'faq'} tabs={[{ key: 'faq', name: 'FAQs' }]} />}
  689. </div>
  690. {tab1 === SENTENCE && this.renderSentenceInfo()}
  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. renderSentenceInfo() {
  867. const { sentence = {}, tab3, sentenceInfo = {}, faqs = [], comments = [] } = this.state;
  868. const { sentenceTrail } = this.props.user;
  869. if (sentence.code || sentenceTrail) {
  870. return null;
  871. }
  872. return <div className='bottom-info'>
  873. <div className='content'>
  874. <Tabs active={tab3} space={7.5} type='tag' theme='white' tabs={[{ key: 'faq', title: '千行长难句' }, { key: 'code', title: '关于CODE' }, { key: 'comment', title: '考生评价' }]} onChange={(key) => {
  875. this.setState({ tab3: key });
  876. }} />
  877. {tab3 === 'faq' && <AnswerCarousel
  878. hideBtn
  879. list={faqs}
  880. onFaq={() => User.needLogin().then(() => this.setState({ showFaq: true, faq: { channel: 'exercise-sentence' } }))}
  881. />}
  882. {tab3 === 'code' && <div dangerouslySetInnerHTML={{ __html: sentenceInfo.detail }} />}
  883. {tab3 === 'comment' && (comments || []).map((item) => {
  884. return <Comment data={item} />;
  885. })}
  886. </div>
  887. </div>;
  888. }
  889. renderSentenceArticle() {
  890. const {
  891. sentence = {},
  892. introduction,
  893. chapterSteps,
  894. chapterStep = 1,
  895. exerciseChapter = {},
  896. chapterMap = {},
  897. articleMap = {},
  898. trailArticles = [],
  899. paperFilterList = [],
  900. paperList = [],
  901. paperChecked,
  902. sentenceInfo = {},
  903. } = this.state;
  904. const { sentenceTrail } = this.props.user;
  905. let maxStep = 0;
  906. if (sentenceTrail) {
  907. // 试用只能访问第一step
  908. maxStep = 1;
  909. // 查找练习章节
  910. }
  911. // 减去前言计算chapter
  912. const chapter = introduction ? chapterStep - 1 : chapterStep;
  913. const chapterInfo = chapterMap[chapter] || {};
  914. let isExercise = false;
  915. if (chapterInfo && chapterInfo.exercise) {
  916. isExercise = true;
  917. }
  918. return (
  919. <div>
  920. {sentence.code && <div className="sentence-code">CODE: {sentence.code}</div>}
  921. {sentenceTrail && (
  922. <div className="sentence-code">
  923. CODE: <a href={sentenceInfo.link} target="_blank">去获取</a>
  924. <a
  925. onClick={() => {
  926. this.setState({ sentenceModel: true });
  927. }}
  928. >
  929. 输入
  930. </a>
  931. </div>
  932. )}
  933. <Module>
  934. <Step
  935. list={chapterSteps}
  936. step={chapterStep}
  937. onClick={step => {
  938. this.setState({ chapterStep: step });
  939. }}
  940. message="请购买后访问"
  941. maxStep={maxStep}
  942. />
  943. </Module>
  944. {/* 正常前言 */}
  945. {sentence.code && chapter === 0 && (
  946. <List
  947. // title={chapterInfo.title}
  948. list={[introduction]}
  949. onClick={() => {
  950. this.sentenceRead();
  951. }}
  952. />
  953. )}
  954. {/* 正常文章 */}
  955. {sentence.code && chapter > 0 && !isExercise && (
  956. <List
  957. position={`Chapter${chapter}`}
  958. title={chapterInfo.title}
  959. list={articleMap[chapter]}
  960. onClick={part => {
  961. this.sentenceRead(part);
  962. }}
  963. />
  964. )}
  965. {/* 正常练习 */}
  966. {sentence.code && isExercise && (
  967. <ListTable
  968. position={`Chapter${chapter}`}
  969. title={chapterInfo.title}
  970. filters={[
  971. {
  972. type: 'radio',
  973. checked: paperChecked,
  974. list: [{ key: 0, title: '未完成' }, { key: 1, title: '已完成' }],
  975. onChange: item => {
  976. this.sentenceFilter(item.key);
  977. },
  978. },
  979. ]}
  980. data={paperFilterList}
  981. columns={this.sentenceColums}
  982. />
  983. )}
  984. {/* 试读文章 */}
  985. {sentenceTrail &&
  986. trailArticles.map(info => {
  987. return (
  988. <List
  989. position={info.value ? `Chapter${info.value}` : null}
  990. title={info.title}
  991. list={info.articles}
  992. onClick={part => {
  993. this.sentenceRead(part);
  994. }}
  995. />
  996. );
  997. })}
  998. {/* 试练 */}
  999. {sentenceTrail && (
  1000. <ListTable
  1001. position={`Chapter${exerciseChapter.value}`}
  1002. title={exerciseChapter.title}
  1003. data={paperList}
  1004. columns={this.sentenceColums}
  1005. />
  1006. )}
  1007. </div>
  1008. );
  1009. }
  1010. renderInputCode() {
  1011. const { sentenceError, sentenceInfo = {} } = this.state;
  1012. return (
  1013. <Module className="code-module">
  1014. <div className="title">输入《千行GMAT长难句》专属 Code,解锁在线练习功能。</div>
  1015. <div className="input-block">
  1016. <InputItem
  1017. size="lager"
  1018. placeholder="请输入CODE"
  1019. onChange={value => {
  1020. this.code = value;
  1021. }}
  1022. />
  1023. <Button
  1024. size="lager"
  1025. onClick={() => {
  1026. this.activeSentence();
  1027. }}
  1028. >
  1029. 解锁
  1030. </Button>
  1031. {sentenceError && <div className="error">{sentenceError}</div>}
  1032. </div>
  1033. <div className="tip">
  1034. {/* <Link to="/" className="left link">
  1035. 什么是CODE?
  1036. </Link> */}
  1037. <span>没有 CODE?</span>
  1038. <a href={sentenceInfo.link} target="_blank" className="theme">
  1039. 去获取 >
  1040. </a>
  1041. <span> 或 </span>
  1042. <a
  1043. onClick={() => {
  1044. this.trailSentence();
  1045. }}
  1046. className="theme"
  1047. >
  1048. 试用 >
  1049. </a>
  1050. </div>
  1051. </Module>
  1052. );
  1053. }
  1054. renderInputCodeModel() {
  1055. const { sentenceError } = this.state;
  1056. return (
  1057. <Modal visible closable={false} footer={false} title={false}>
  1058. <div className="code-module-modal">
  1059. <div className="title">请输入CODE</div>
  1060. <div className="desc">
  1061. <InputItem
  1062. onChange={value => {
  1063. this.code = value;
  1064. }}
  1065. />
  1066. {sentenceError && <div className="error">{sentenceError}</div>}
  1067. {/* <div className="tip">
  1068. <Link to="/" className="right link">
  1069. 什么是CODE?
  1070. </Link>
  1071. </div> */}
  1072. </div>
  1073. <div className="btn-list">
  1074. <AnswerButton size="lager" theme="confirm" width={150} onClick={() => this.activeSentence()}>
  1075. 确认
  1076. </AnswerButton>
  1077. <AnswerButton
  1078. size="lager"
  1079. theme="cancel"
  1080. width={150}
  1081. onClick={() => this.setState({ sentenceModel: false })}
  1082. >
  1083. 取消
  1084. </AnswerButton>
  1085. </div>
  1086. </div>
  1087. </Modal>
  1088. );
  1089. }
  1090. renderExercise() {
  1091. const { exerciseProgress = [] } = this.state;
  1092. return (
  1093. <div>
  1094. <Division col={2}>
  1095. {(exerciseProgress || []).map(struct => {
  1096. const [first] = struct.children;
  1097. let col = 3;
  1098. if (first && first.type === 'paper') {
  1099. col = 5;
  1100. }
  1101. return (
  1102. <Panel
  1103. title={struct.titleEn}
  1104. message={struct.description}
  1105. data={struct}
  1106. col={col}
  1107. onClick={item => {
  1108. User.needLogin()
  1109. .then(() => {
  1110. if (item.type === 'paper') {
  1111. if (item.progress === 0) {
  1112. Question.startLink('exercise', item);
  1113. } else if (item.progress === 100) {
  1114. Question.startLink('exercise', item);
  1115. } else {
  1116. Question.continueLink('exercise', item);
  1117. }
  1118. } else {
  1119. this.exerciseList(item);
  1120. }
  1121. });
  1122. }}
  1123. />
  1124. );
  1125. })}
  1126. </Division>
  1127. </div>
  1128. );
  1129. }
  1130. }