page.js 33 KB

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