page.js 32 KB

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