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