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