page.js 36 KB

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