page.js 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174
  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.finishTimes : 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.report) {
  75. time = formatSeconds(record.report.userTime / record.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. {(record.report) && <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.finishTimes : 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 && (
  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.title = `${row.titleZh}`;
  495. row.info = [
  496. {
  497. title: '已做',
  498. number: row.userNumber || '-',
  499. unit: '题',
  500. },
  501. {
  502. title: '剩余',
  503. number: row.userNumber ? row.questionNumber - row.userNumber : '-',
  504. unit: '题',
  505. },
  506. {
  507. title: '总计',
  508. number: row.questionNumber || 0,
  509. unit: '题',
  510. },
  511. ];
  512. if (row.userStat) {
  513. row.correct = formatPercent(row.userStat.userCorrect, row.userStat.userNumber, false);
  514. } else {
  515. row.correct = '--';
  516. }
  517. row.progress = formatPercent(row.questionNumber - row.userNumber || 0, row.questionNumber);
  518. row.totalCorrect = formatPercent(row.stat.totalCorrect, row.stat.totalNumber, false);
  519. row.pieValue = formatPercent(row.userNumber, row.questionNumber);
  520. row.pieText = formatPercent(row.userNumber, row.questionNumber, false);
  521. row.pieSubText = `共${row.questionNumber}题`;
  522. row.children = row.children.map(r => {
  523. r.title = r.title || r.titleZh;
  524. r.progress = formatPercent(r.userNumber, r.questionNumber);
  525. return r;
  526. });
  527. return row;
  528. });
  529. this.setState({ exerciseProgress: result });
  530. });
  531. }
  532. onChangeTab(level, tab) {
  533. const { tab1 } = this.state;
  534. const data = {};
  535. if (level > 1) {
  536. data.tab1 = tab1;
  537. data.tab2 = tab;
  538. } else {
  539. data.tab1 = tab;
  540. }
  541. // this.refreshData(tab);
  542. this.refreshQuery(data);
  543. }
  544. onChangeCourse(struct) {
  545. const { tab1, tab2 } = this.state;
  546. const data = {
  547. tab1, tab2, struct,
  548. };
  549. this.refreshQuery(data);
  550. }
  551. onPreviewCourse() {
  552. const { tab1, tab2, struct } = this.state;
  553. const data = {
  554. tab1, tab2, struct,
  555. };
  556. this.refreshQuery(data);
  557. }
  558. onPreviewList(recordId) {
  559. const { tab1, tab2, struct } = this.state;
  560. const data = {
  561. tab1, tab2, struct, recordId,
  562. };
  563. this.refreshQuery(data);
  564. }
  565. previewAction(type, item) {
  566. switch (type) {
  567. case 'start':
  568. Question.startLink('preview', item);
  569. break;
  570. case 'restart':
  571. this.restart(item);
  572. break;
  573. case 'continue':
  574. Question.continueLink('preview', item);
  575. break;
  576. default:
  577. break;
  578. }
  579. }
  580. // 开通课程
  581. open(recordId) {
  582. Order.useRecord(recordId).then(() => {
  583. this.refresh();
  584. });
  585. }
  586. restart(item) {
  587. asyncConfirm('提示', '你打算重做本套练习,过往做题记录可至「个人中心-报告」查看。', () => {
  588. Question.restart(item.paper.id).then(() => {
  589. this.refresh();
  590. });
  591. });
  592. }
  593. exerciseList(item) {
  594. User.needLogin().then(() => {
  595. linkTo(`/exercise/list/${item.id}`);
  596. });
  597. }
  598. activeSentence() {
  599. User.needLogin().then(() => {
  600. Sentence.active(this.code)
  601. .then(() => {
  602. // 重新获取长难句信息
  603. User.clearSentenceTrail();
  604. this.setState({ sentence: null, articleMap: null, paperList: null });
  605. this.refresh();
  606. })
  607. .catch(err => {
  608. this.setState({ sentenceError: err.message });
  609. });
  610. });
  611. }
  612. trailSentence() {
  613. User.needLogin().then(() => {
  614. User.sentenceTrail();
  615. this.setState({ sentenceError: null });
  616. });
  617. }
  618. sentenceRead(article) {
  619. if (article) {
  620. linkTo(`/sentence/read?chapter=${article.chapter}&part=${article.part}`);
  621. } else {
  622. linkTo('/sentence/read');
  623. }
  624. }
  625. sentenceFilter(value) {
  626. const { paperChecked, paperList } = this.state;
  627. value = paperChecked === value ? null : value;
  628. const list = paperList.filter(row => {
  629. const finish = row.paper ? row.paper.finishTimes > 0 : false;
  630. if (value === 0) {
  631. return !finish;
  632. } if (value === 1) {
  633. return finish;
  634. }
  635. return true;
  636. });
  637. this.setState({ paperFilterList: list, paperChecked: value });
  638. }
  639. clearExercise() {
  640. My.clearLatestExercise()
  641. .then(() => {
  642. const { info } = this.props.user;
  643. info.latestExercise = 0;
  644. User.infoHandle(info);
  645. });
  646. this.setState({ latest: null });
  647. }
  648. renderView() {
  649. const { tab1, tab2, tabs, latest, sentenceModel, previewType, courseTabs = [] } = this.state;
  650. const [subject] = tabs.filter(row => row.key === tab1);
  651. const children = (subject && subject.children) ? subject.children : (tab1 === 'preview' && previewType === PREVIEW_COURSE ? courseTabs : []);
  652. return (
  653. <div>
  654. {latest && (
  655. <Continue
  656. data={latest}
  657. onClose={() => {
  658. this.clearExercise();
  659. }}
  660. onContinue={() => {
  661. Question.continueLink('exercise', { id: latest.originId, report: latest });
  662. }}
  663. onRestart={() => {
  664. this.restart(latest);
  665. Question.startLink('exercise', { id: latest.originId, report: latest });
  666. }}
  667. // onNext={() => {
  668. // Question.continueLink('exercise', { id: latest.originId, report: latest });
  669. // }}
  670. />
  671. )}
  672. <div className="content">
  673. <Module className="m-t-2">
  674. <Tabs
  675. type="card"
  676. active={tab1}
  677. tabs={tabs}
  678. onChange={key => {
  679. this.onChangeTab(1, key);
  680. }}
  681. />
  682. {children && children.length > 1 && (
  683. <Tabs active={tab2} tabs={children} onChange={key => this.onChangeTab(2, key)} />
  684. )}
  685. </Module>
  686. {tab1 !== SENTENCE && tab1 !== PREVIEW && this.renderExercise()}
  687. {tab1 === SENTENCE && this.renderSentence()}
  688. {tab1 === PREVIEW && this.renderPreview()}
  689. {tab1 !== SENTENCE && this.state.faqs && <QAList data={this.state.faqs} active={'faq'} tabs={[{ key: 'faq', name: 'FAQs' }]} />}
  690. </div>
  691. {tab1 === SENTENCE && this.renderSentenceInfo()}
  692. {sentenceModel && this.renderInputCodeModel()}
  693. </div>
  694. );
  695. }
  696. renderPreview() {
  697. const { previewType } = this.state;
  698. switch (previewType) {
  699. case PREVIEW_COURSE:
  700. return this.renderPreviewCourse();
  701. case PREVIEW_LIST:
  702. return this.renderPreviewList();
  703. default:
  704. return <div />;
  705. }
  706. }
  707. renderPreviewCourse() {
  708. const { courseStructs, struct, tab2, courseTabs, courseMap = {}, contract = {} } = this.state;
  709. return (
  710. <div className="work-body">
  711. <div className="work-nav" hidden={courseTabs && courseTabs.length > 0 && tab2 !== courseTabs[0].key}>
  712. <Tabs
  713. type="tag"
  714. active={struct || ''}
  715. space={5}
  716. tabs={courseStructs}
  717. onChange={key => {
  718. this.onChangeCourse(key);
  719. }}
  720. />
  721. </div>
  722. <div className="work-nav">
  723. <div className="left">学习中</div>
  724. </div>
  725. <Division col="3">
  726. {(courseMap.process || []).map(row => {
  727. return <Card1
  728. title={`${row.course.title}${row.vsNo > 0 ? `V${row.vsNo}` : ''}${row.number > 0 ? `(${row.number}课时)` : ''}`}
  729. tag={CourseModuleMap[row.course.courseModule]}
  730. status={row.isSuspend && !row.restoreTime ? 'stop' : 'ing'}
  731. list={(row.papers || []).map(r => {
  732. let progress = 0;
  733. if (r.report) {
  734. progress = formatPercent(r.report.userNumber, r.report.questionNumber);
  735. }
  736. r.progress = progress;
  737. return r;
  738. })}
  739. data={row}
  740. onPreview={() => {
  741. this.onPreviewList(row.id);
  742. }}
  743. previewAction={(type, item) => {
  744. this.previewAction(type, item);
  745. }}
  746. />;
  747. })}
  748. </Division>
  749. <div className="work-nav">
  750. <div className="left">待开通</div>
  751. </div>
  752. <Division col="3">
  753. {(courseMap.open || []).map(row => {
  754. return <Card1
  755. title={`${row.course.title}${row.vsNo > 0 ? `V${row.vsNo}` : ''}${row.number > 0 ? `(${row.number}课时)` : ''}`}
  756. tag={CourseModuleMap[row.course.courseModule]}
  757. contract={contract}
  758. status='open'
  759. data={row}
  760. onOpen={() => {
  761. this.open(row.id);
  762. }}
  763. />;
  764. })}
  765. </Division>
  766. <div className="work-nav">
  767. <div className="left">已结束</div>
  768. </div>
  769. <Division col="3">
  770. {(courseMap.end || []).map(row => {
  771. return <Card1
  772. title={`${row.course.title}${row.vsNo > 0 ? `V${row.vsNo}` : ''}${row.number > 0 ? `(${row.number}课时)` : ''}`}
  773. tag={CourseModuleMap[row.course.courseModule]}
  774. status='end'
  775. data={row}
  776. onPreview={() => {
  777. this.onPreviewList(row.id);
  778. }}
  779. />;
  780. })}
  781. </Division>
  782. </div>
  783. );
  784. }
  785. renderPreviewList() {
  786. const { previews = [], record = {}, search = {} } = this.state;
  787. const { finish, endTime } = search;
  788. let finishTime = '';
  789. if (endTime) {
  790. const endTimeD = new Date(endTime);
  791. const now = new Date();
  792. if ((now.getTime() + 86400000) > endTimeD.getTime()) {
  793. finishTime = 'today';
  794. } else {
  795. finishTime = 'tomorrow';
  796. }
  797. }
  798. return (
  799. <div className="work-body">
  800. <div className="work-nav">
  801. <div className="left">{`${(record.course || {}).title || ''}${record.vsNo > 0 ? `V${record.vsNo}` : ''}${record.number > 0 ? `(${record.number}课时)` : ''}`}全部作业</div>
  802. <div className="right theme c-p" onClick={() => this.onPreviewCourse()}>
  803. 我的课程 >
  804. </div>
  805. </div>
  806. <ListTable
  807. filters={[
  808. {
  809. type: 'radio',
  810. checked: finishTime,
  811. list: [{ key: 'today', title: '今日需完成' }, { key: 'tomorrow', title: '明日需完成' }],
  812. onChange: (item) => {
  813. if (item.key === finishTime) {
  814. this.search({ endTime: null });
  815. } else if (item.key === 'today') {
  816. const a = new Date();
  817. a.setDate(a.getDate() + 1);
  818. a.setHours(0);
  819. a.setMinutes(0);
  820. a.setMilliseconds(0);
  821. a.setSeconds(0);
  822. this.search({ endTime: formatDate(a, 'YYYY-MM-DD') });
  823. } else if (item.key === 'tomorrow') {
  824. const a = new Date();
  825. a.setDate(a.getDate() + 2);
  826. a.setHours(0);
  827. a.setMinutes(0);
  828. a.setMilliseconds(0);
  829. a.setSeconds(0);
  830. this.search({ endTime: formatDate(a, 'YYYY-MM-DD') });
  831. } else {
  832. this.search({ endTime: null });
  833. }
  834. },
  835. },
  836. {
  837. type: 'radio',
  838. checked: finish,
  839. list: [{ key: '0', title: '未完成' }, { key: '1', title: '已完成' }],
  840. onChange: (item) => {
  841. if (item.key === finish) {
  842. this.search({ finish: null });
  843. } else if (item.key === '0') {
  844. this.search({ finish: '0' });
  845. } else if (item.key === '1') {
  846. this.search({ finish: '1' });
  847. } else {
  848. this.search({ finish: null });
  849. }
  850. },
  851. },
  852. ]}
  853. data={previews}
  854. columns={this.columns}
  855. />
  856. </div>
  857. );
  858. }
  859. renderSentence() {
  860. const { sentence = {} } = this.state;
  861. const { sentenceTrail } = this.props.user;
  862. if (sentence.code || sentenceTrail) {
  863. return this.renderSentenceArticle();
  864. }
  865. return this.renderInputCode();
  866. }
  867. renderSentenceInfo() {
  868. const { sentence = {}, tab3, sentenceInfo = {}, faqs = [], comments = [] } = this.state;
  869. const { sentenceTrail } = this.props.user;
  870. if (sentence.code || sentenceTrail) {
  871. return null;
  872. }
  873. return <div className='bottom-info'>
  874. <div className='content'>
  875. <Tabs active={tab3} space={7.5} type='tag' theme='white' tabs={[{ key: 'faq', title: '千行长难句' }, { key: 'code', title: '关于CODE' }, { key: 'comment', title: '考生评价' }]} onChange={(key) => {
  876. this.setState({ tab3: key });
  877. }} />
  878. {tab3 === 'faq' && <AnswerCarousel
  879. hideBtn
  880. list={faqs}
  881. onFaq={() => User.needLogin().then(() => this.setState({ showFaq: true, faq: { channel: 'exercise-sentence' } }))}
  882. />}
  883. {tab3 === 'code' && <div dangerouslySetInnerHTML={{ __html: sentenceInfo.detail }} />}
  884. {tab3 === 'comment' && (comments || []).map((item) => {
  885. return <Comment data={item} />;
  886. })}
  887. </div>
  888. </div>;
  889. }
  890. renderSentenceArticle() {
  891. const {
  892. sentence = {},
  893. introduction,
  894. chapterSteps,
  895. chapterStep = 1,
  896. exerciseChapter = {},
  897. chapterMap = {},
  898. articleMap = {},
  899. trailArticles = [],
  900. paperFilterList = [],
  901. paperList = [],
  902. paperChecked,
  903. sentenceInfo = {},
  904. } = this.state;
  905. const { sentenceTrail } = this.props.user;
  906. let maxStep = 0;
  907. if (sentenceTrail) {
  908. // 试用只能访问第一step
  909. maxStep = 1;
  910. // 查找练习章节
  911. }
  912. // 减去前言计算chapter
  913. const chapter = introduction ? chapterStep - 1 : chapterStep;
  914. const chapterInfo = chapterMap[chapter] || {};
  915. let isExercise = false;
  916. if (chapterInfo && chapterInfo.exercise) {
  917. isExercise = true;
  918. }
  919. return (
  920. <div>
  921. {sentence.code && <div className="sentence-code">CODE: {sentence.code}</div>}
  922. {sentenceTrail && (
  923. <div className="sentence-code">
  924. CODE: <a href={sentenceInfo.link} target="_blank">去获取</a>
  925. <a
  926. onClick={() => {
  927. this.setState({ sentenceModel: true });
  928. }}
  929. >
  930. 输入
  931. </a>
  932. </div>
  933. )}
  934. <Module>
  935. <Step
  936. list={chapterSteps}
  937. step={chapterStep}
  938. onClick={step => {
  939. this.setState({ chapterStep: step });
  940. }}
  941. message="请购买后访问"
  942. maxStep={maxStep}
  943. />
  944. </Module>
  945. {/* 正常前言 */}
  946. {sentence.code && chapter === 0 && (
  947. <List
  948. // title={chapterInfo.title}
  949. list={[introduction]}
  950. onClick={() => {
  951. this.sentenceRead();
  952. }}
  953. />
  954. )}
  955. {/* 正常文章 */}
  956. {sentence.code && chapter > 0 && !isExercise && (
  957. <List
  958. position={`Chapter${chapter}`}
  959. title={chapterInfo.title}
  960. list={articleMap[chapter]}
  961. onClick={part => {
  962. this.sentenceRead(part);
  963. }}
  964. />
  965. )}
  966. {/* 正常练习 */}
  967. {sentence.code && isExercise && (
  968. <ListTable
  969. position={`Chapter${chapter}`}
  970. title={chapterInfo.title}
  971. filters={[
  972. {
  973. type: 'radio',
  974. checked: paperChecked,
  975. list: [{ key: 0, title: '未完成' }, { key: 1, title: '已完成' }],
  976. onChange: item => {
  977. this.sentenceFilter(item.key);
  978. },
  979. },
  980. ]}
  981. data={paperFilterList}
  982. columns={this.sentenceColums}
  983. />
  984. )}
  985. {/* 试读文章 */}
  986. {sentenceTrail &&
  987. trailArticles.map(info => {
  988. return (
  989. <List
  990. position={info.value ? `Chapter${info.value}` : null}
  991. title={info.title}
  992. list={info.articles}
  993. onClick={part => {
  994. this.sentenceRead(part);
  995. }}
  996. />
  997. );
  998. })}
  999. {/* 试练 */}
  1000. {sentenceTrail && (
  1001. <ListTable
  1002. position={`Chapter${exerciseChapter.value}`}
  1003. title={exerciseChapter.title}
  1004. data={paperList}
  1005. columns={this.sentenceColums}
  1006. />
  1007. )}
  1008. </div>
  1009. );
  1010. }
  1011. renderInputCode() {
  1012. const { sentenceError, sentenceInfo = {} } = this.state;
  1013. return (
  1014. <Module className="code-module">
  1015. <div className="title">输入《千行GMAT长难句》专属 Code,解锁在线练习功能。</div>
  1016. <div className="input-block">
  1017. <InputItem
  1018. size="lager"
  1019. placeholder="请输入CODE"
  1020. onChange={value => {
  1021. this.code = value;
  1022. }}
  1023. />
  1024. <Button
  1025. size="lager"
  1026. onClick={() => {
  1027. this.activeSentence();
  1028. }}
  1029. >
  1030. 解锁
  1031. </Button>
  1032. {sentenceError && <div className="error">{sentenceError}</div>}
  1033. </div>
  1034. <div className="tip">
  1035. {/* <Link to="/" className="left link">
  1036. 什么是CODE?
  1037. </Link> */}
  1038. <span>没有 CODE?</span>
  1039. <a href={sentenceInfo.link} target="_blank" className="theme">
  1040. 去获取 >
  1041. </a>
  1042. <span> 或 </span>
  1043. <a
  1044. onClick={() => {
  1045. this.trailSentence();
  1046. }}
  1047. className="theme"
  1048. >
  1049. 试用 >
  1050. </a>
  1051. </div>
  1052. </Module>
  1053. );
  1054. }
  1055. renderInputCodeModel() {
  1056. const { sentenceError } = this.state;
  1057. return (
  1058. <Modal visible closable={false} footer={false} title={false}>
  1059. <div className="code-module-modal">
  1060. <div className="title">请输入CODE</div>
  1061. <div className="desc">
  1062. <InputItem
  1063. onChange={value => {
  1064. this.code = value;
  1065. }}
  1066. />
  1067. {sentenceError && <div className="error">{sentenceError}</div>}
  1068. {/* <div className="tip">
  1069. <Link to="/" className="right link">
  1070. 什么是CODE?
  1071. </Link>
  1072. </div> */}
  1073. </div>
  1074. <div className="btn-list">
  1075. <AnswerButton size="lager" theme="confirm" width={150} onClick={() => this.activeSentence()}>
  1076. 确认
  1077. </AnswerButton>
  1078. <AnswerButton
  1079. size="lager"
  1080. theme="cancel"
  1081. width={150}
  1082. onClick={() => this.setState({ sentenceModel: false })}
  1083. >
  1084. 取消
  1085. </AnswerButton>
  1086. </div>
  1087. </div>
  1088. </Modal>
  1089. );
  1090. }
  1091. renderExercise() {
  1092. const { exerciseProgress = [] } = this.state;
  1093. return (
  1094. <div>
  1095. <Division col={2}>
  1096. {(exerciseProgress || []).map(struct => {
  1097. const [first] = struct.children;
  1098. let col = 3;
  1099. if (first && first.type === 'paper') {
  1100. col = 5;
  1101. }
  1102. return (
  1103. <Panel
  1104. title={struct.title}
  1105. message={struct.description}
  1106. data={struct}
  1107. col={col}
  1108. onClick={item => {
  1109. User.needLogin()
  1110. .then(() => {
  1111. if (item.type === 'paper') {
  1112. if (item.progress === 0) {
  1113. Question.startLink('exercise', item);
  1114. } else if (item.progress === 100) {
  1115. Question.startLink('exercise', item);
  1116. } else {
  1117. Question.continueLink('exercise', item);
  1118. }
  1119. } else {
  1120. this.exerciseList(item);
  1121. }
  1122. });
  1123. }}
  1124. />
  1125. );
  1126. })}
  1127. </Division>
  1128. </div>
  1129. );
  1130. }
  1131. }