page.js 37 KB

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