page.js 32 KB

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