page.js 34 KB

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