page.js 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662
  1. import React, { Component } from 'react';
  2. import { Link } from 'react-router-dom';
  3. import './index.less';
  4. import { Icon, Dropdown } from 'antd';
  5. import FileUpload from '@src/components/FileUpload';
  6. import Page from '@src/containers/Page';
  7. import Assets from '@src/components/Assets';
  8. import { asyncSMessage } from '@src/services/AsyncTools';
  9. import { formatDate, formatSeconds, formatPercent } from '@src/services/Tools';
  10. import UserLayout from '../../../layouts/User';
  11. import Button from '../../../components/Button';
  12. import ProgressText from '../../../components/ProgressText';
  13. import IconButton from '../../../components/IconButton';
  14. import { Icon as GIcon } from '../../../components/Icon';
  15. import menu from '../index';
  16. import Tabs from '../../../components/Tabs';
  17. import UserTable from '../../../components/UserTable';
  18. import More from '../../../components/More';
  19. import Modal from '../../../components/Modal';
  20. import DatePlane from '../../../components/Date';
  21. import Note from '../../../components/Note';
  22. import { My } from '../../../stores/my';
  23. import { Order } from '../../../stores/order';
  24. import { Common } from '../../../stores/common';
  25. export default class extends Page {
  26. initState() {
  27. return {
  28. tab: 'online',
  29. status: 'all',
  30. };
  31. }
  32. formatRecord(row) {
  33. if (row.number > 1 && row.number === row.appointments.length) {
  34. row.status = 'end';
  35. } else if (row.useEndTime) {
  36. if (new Date(row.useEndTime).getTime() > new Date().getTime()) {
  37. row.status = 'end';
  38. } else if (row.isSuspend && !row.restoreTime) {
  39. row.status = 'suspend';
  40. } else {
  41. row.status = 'ing';
  42. }
  43. } else if (new Date(row.useEndTime).getTime() < new Date().getTime()) {
  44. row.status = 'end';
  45. } else {
  46. row.status = 'no';
  47. }
  48. row.paperMap = {};
  49. row.appointmentPaperMap = {};
  50. if (row.papers) {
  51. row.papers.forEach(paper => {
  52. if (paper.courseNo) row.paperMap[paper.courseNo] = paper;
  53. if (paper.appointment) row.appointmentPaperMap[paper.appointment] = paper;
  54. });
  55. }
  56. row.progressMap = {};
  57. if (row.progress) {
  58. row.progress.forEach(progress => {
  59. row.progressMap[progress.courseNoId] = progress;
  60. });
  61. }
  62. row.courseNoMap = {};
  63. row.courseTime = 0;
  64. if (row.courseNos) {
  65. row.courseNos.forEach(no => {
  66. row.courseNoMap[no.id] = no;
  67. row.courseTime += no.time;
  68. no.paper = row.paperMap[no.id];
  69. no.progress = row.progressMap[no.id];
  70. });
  71. }
  72. if (row.currentNo) {
  73. row.currentCourseNo = row.courseNoMap[row.currentNo];
  74. } else {
  75. row.currentNo = 0;
  76. }
  77. // 如果已经最新预约结束,则添加一个空记录作为最新预约
  78. if (!row.appointments || row.appointments.length === 0) {
  79. row.appointments = [{}];
  80. } else {
  81. row.appointments.forEach(r => {
  82. r.paper = row.appointmentPaperMap[r.id];
  83. r.noteList = (row.comments || []).map(c => c.type === 'note' && c.appointmentId === r.id);
  84. r.supplyList = (row.comments || []).map(c => c.type === 'supply' && c.appointmentId === r.id);
  85. });
  86. // 是否是最后一课时,是否过预约时间
  87. }
  88. row.days = new Date(row.userEndTime);
  89. return row;
  90. }
  91. initData() {
  92. const data = Object.assign(this.state, this.state.search);
  93. data.filterMap = this.state.search;
  94. if (data.order) {
  95. data.sortMap = { [data.order]: data.direction };
  96. }
  97. this.setState(data);
  98. let isUsed = null;
  99. let isEnd = null;
  100. switch (data.status) {
  101. case 'learning':
  102. isUsed = true;
  103. isEnd = false;
  104. break;
  105. case 'end':
  106. isUsed = true;
  107. isEnd = true;
  108. break;
  109. case 'nouse':
  110. isUsed = false;
  111. isEnd = false;
  112. break;
  113. case 'all':
  114. default:
  115. }
  116. My.listCourse(Object.assign({ courseModule: data.tab }, this.state.search, { isUsed, isEnd })).then(result => {
  117. result.list = result.list.map(row => {
  118. return this.formatRecord(row);
  119. });
  120. this.setState({ list: result.list, total: result.total, page: data.page });
  121. });
  122. }
  123. onAction() {}
  124. onTabChange(tab) {
  125. const data = { tab };
  126. this.refreshQuery(data);
  127. }
  128. onStatusChange(status) {
  129. this.search({ status });
  130. }
  131. openTime(record) {
  132. if (record.courseTimeMap) {
  133. this.setState({ data: record, showTime: true });
  134. return;
  135. }
  136. My.timeCourse(record.id).then(result => {
  137. const courseMap = {};
  138. const previewMap = {};
  139. const stopMap = {};
  140. result.forEach(row => {
  141. const date = formatDate(row.day, 'YYYY-MM-DD');
  142. if (row.type === 'stop') {
  143. stopMap[date] = true;
  144. } else if (row.type === 'preview') {
  145. previewMap[date] = true;
  146. } else if (row.type === 'course') {
  147. courseMap[date] = true;
  148. }
  149. });
  150. record.courseTimeMap = courseMap;
  151. record.previewTimeMap = previewMap;
  152. record.stopTimeMap = stopMap;
  153. this.setState({ data: record, showTime: true });
  154. });
  155. }
  156. refreshDetail(recordId) {
  157. My.detailCourse(recordId).then(result => {
  158. let { list } = this.state;
  159. list = list.map(row => {
  160. if (row.id === recordId) return this.formatRecord(result);
  161. return row;
  162. });
  163. this.setState({ list });
  164. });
  165. }
  166. suspend() {
  167. const { suspend } = this.state;
  168. My.suspendCourse(suspend.id)
  169. .then(() => {
  170. asyncSMessage('停课成功');
  171. this.setState({ showSuspend: false });
  172. this.refreshDetail(suspend.id);
  173. })
  174. .catch(err => {
  175. asyncSMessage(err.message, 'error');
  176. });
  177. }
  178. restore() {
  179. const { restore } = this.state;
  180. My.restoreCourse(restore.id)
  181. .then(() => {
  182. asyncSMessage('恢复成功');
  183. this.setState({ showRestore: false });
  184. this.refreshDetail(restore.id);
  185. })
  186. .catch(err => {
  187. asyncSMessage(err.message, 'error');
  188. });
  189. }
  190. submitAppointmentComment(data) {
  191. if (data.id) {
  192. My.editAppointmentComment(data).then(() => {
  193. this.refreshDetail(data.recordId);
  194. });
  195. } else {
  196. My.addAppointmentComment(data).then(() => {
  197. this.refreshDetail(data.recordId);
  198. });
  199. }
  200. }
  201. deleteAppointmentComment(row) {
  202. My.delAppointmentComment(row.id).then(() => {
  203. this.refreshDetail(row.recordId);
  204. });
  205. }
  206. submitQuestionFile(data) {
  207. My.uploadAppointmentQuestion(data).then(() => {
  208. this.refreshDetail(data.recordId);
  209. });
  210. }
  211. setCCTalkName(recordId, cctalkname) {
  212. My.setCCTalkName(recordId, cctalkname).then(() => {
  213. this.refreshDetail(recordId);
  214. });
  215. }
  216. submitComment() {
  217. const { comment } = this.state;
  218. My.addComment(comment.channel, comment.position, comment.content).then(() => {
  219. this.setState({ showComment: false, showFinish: true, comment: {} });
  220. });
  221. }
  222. open(recordId) {
  223. Order.useRecord(recordId).then(() => {
  224. asyncSMessage('开通成功');
  225. this.refreshDetail(recordId);
  226. });
  227. }
  228. closeCommentTips(recordId) {
  229. My.courseCommentTips(recordId);
  230. }
  231. uploadNote(file) {
  232. const { note = {} } = this.state;
  233. Common.uploadImage(file).then(result => {
  234. note.file = result.url;
  235. note.name = file.name;
  236. this.setState({ note });
  237. });
  238. }
  239. uploadSupply(file) {
  240. const { supply = {} } = this.state;
  241. Common.uploadImage(file).then(result => {
  242. supply.file = result.url;
  243. supply.name = file.name;
  244. this.setState({ supply });
  245. });
  246. }
  247. renderView() {
  248. const { config } = this.props;
  249. return <UserLayout active={config.key} menu={menu} center={this.renderDetail()} />;
  250. }
  251. renderDetail() {
  252. const {
  253. tab,
  254. status,
  255. showTips,
  256. showTime,
  257. showSuspend,
  258. showRestore,
  259. showUploadNote,
  260. showUploadSupply,
  261. showSupply,
  262. showNote,
  263. showComment,
  264. showFinish,
  265. comment = {},
  266. data = {},
  267. note = {},
  268. supply = {},
  269. appointment = {},
  270. } = this.state;
  271. return (
  272. <div className="detail-layout">
  273. <div hidden={!showTips} className="tip">
  274. <div className="text">理清备考思路,避开常见误区,让学习的每一分钟发光发热!</div>
  275. <a href="" target="_blank">
  276. 去填写 >
  277. </a>
  278. <Icon type="close-circle" theme="filled" onClick={() => this.setState({ showTips: false })} />
  279. </div>
  280. <Tabs
  281. type="division"
  282. theme="theme"
  283. size="small"
  284. space={2.5}
  285. width={100}
  286. active={tab}
  287. tabs={[{ key: 'online', title: '在线课程' }, { key: 'vs', title: '1V1私教' }]}
  288. onChange={key => this.onTabChange(key)}
  289. />
  290. <Tabs
  291. type="tag"
  292. theme="white"
  293. size="small"
  294. space={5}
  295. width={54}
  296. active={status}
  297. tabs={[
  298. { key: 'all', title: '全部' },
  299. { key: 'nouse', title: '未开通' },
  300. { key: 'learning', title: '学习中' },
  301. { key: 'end', title: '已结束' },
  302. ]}
  303. onChange={key => this.onStatusChange(key)}
  304. />
  305. {this[`renderTab${tab}`]()}
  306. <Modal show={showTime} className="clock-modal" title="打卡表" width={460} onClose={() => {}}>
  307. <div>
  308. <div className="d-i-b w-3">
  309. <div className="t-2 t-s-14">听课频率</div>
  310. <div className="t-4 t-s-18">{data.currentNo ? data.totalDays / data.currentNo : 0}天/课时</div>
  311. </div>
  312. <div className="d-i-b w-3">
  313. <div className="t-2 t-s-14">作业完成度</div>
  314. <div className="t-4 t-s-18">{data.previewProgress || 0}%</div>
  315. </div>
  316. </div>
  317. <div className="b-b m-t-1 m-b-1" />
  318. <div className="m-b-1">
  319. <div className="tag d-i-b t-s-16">
  320. <i style={{ background: '#6865FD' }} />
  321. 听课
  322. </div>
  323. <div className="tag d-i-b t-s-16">
  324. <i style={{ background: '#4299FF' }} />
  325. 预习
  326. </div>
  327. <div className="tag d-i-b t-s-16">
  328. <i style={{ background: '#E7E7E7' }} />
  329. 停课
  330. </div>
  331. </div>
  332. <DatePlane
  333. hideInput
  334. disabledDate={current => {
  335. const date = current.format('YYYY-MM-DD');
  336. return data.stopTimeMap[date];
  337. }}
  338. dateRender={current => {
  339. const date = current.format('YYYY-MM-DD');
  340. return (
  341. <div className="ant-calendar-date">
  342. {current.get('date')}
  343. {data.courseTimeMap[date] && <i className="s1" style={{ background: '#6865FD' }} />}
  344. {data.previewTimeMap[date] && <i className="s2" style={{ background: '#4299FF' }} />}
  345. </div>
  346. );
  347. }}
  348. checkRefresh={(date, refresh) => {
  349. setTimeout(() => {
  350. refresh();
  351. }, 2000);
  352. return true;
  353. }}
  354. />
  355. <div className="t-2 t-s-12">*听课频率≤2天/课时,作业完成度≥90%,课程有效期可延长7-10天。</div>
  356. </Modal>
  357. <Modal
  358. show={showComment}
  359. title="评价"
  360. onConfirm={() => comment.content && this.submitComment()}
  361. onCancel={() => this.setState({ showComment: false, comment: {} })}
  362. >
  363. <textarea
  364. value={comment.content}
  365. className="b-c-1 w-10 p-10"
  366. rows={6}
  367. placeholder="您的看法对我们来说很重要!"
  368. onChange={e => {
  369. comment.content = e.target.value;
  370. this.setState({ comment });
  371. }}
  372. />
  373. <div className="b-b m-t-2" />
  374. </Modal>
  375. <Modal
  376. show={showFinish}
  377. title="提交成功"
  378. confirmText="好的,知道了"
  379. btnAlign="center"
  380. onConfirm={() => this.setState({ showFinish: false })}
  381. >
  382. <div className="t-2 t-s-18">
  383. <Icon type="check" className="t-5 m-r-5" />
  384. 您的每一次反馈都是千行进步的动力。
  385. </div>
  386. </Modal>
  387. <Modal
  388. show={showSuspend}
  389. title="申请停课"
  390. width={630}
  391. confirmText="立即停课"
  392. onConfirm={() => this.suspend()}
  393. onCancel={() => this.setState({ showSuspend: false })}
  394. >
  395. <div className="t-2 t-s-18">最长停课周期为1个月,到期后系统自动恢复至“学习中”状态。</div>
  396. <div className="t-2 t-s-18">每门课程只有一次申请机会,是否需要停课?</div>
  397. <div style={{ bottom: 20, left: 0 }} className="p-a t-3 t-s-14">
  398. *停课不影响听课频率的计算;
  399. </div>
  400. <div style={{ bottom: 0, left: 0 }} className="p-a t-3 t-s-14">
  401. {' '}
  402. 停课期间可随时恢复学习。
  403. </div>
  404. </Modal>
  405. <Modal
  406. show={showRestore}
  407. title="恢复学习"
  408. width={630}
  409. confirmText="立即恢复"
  410. onConfirm={() => this.restore()}
  411. onCancel={() => this.setState({ showRestore: false })}
  412. >
  413. <div className="t-2 t-s-18">恢复学习后,本课程的停课功能将无法使用。是否恢复学习?</div>
  414. </Modal>
  415. <Modal
  416. show={showUploadNote}
  417. title="上传笔记"
  418. width={630}
  419. confirmText="提交"
  420. onConfirm={() => this.submitAppointmentComment(note)}
  421. onCancel={() => this.setState({ showUploadNote: false, note: {} })}
  422. >
  423. <textarea
  424. className="b-c-1 w-10 p-10 m-b-1"
  425. rows={6}
  426. value={note.content}
  427. placeholder="请输入留言内容。"
  428. onChange={e => {
  429. note.content = e.target.value;
  430. this.setState({ note });
  431. }}
  432. />
  433. <div className={`t-c drag-upload ${this.state.draging ? 'draging' : ''}`}>
  434. <Button theme="file">上传文件</Button>
  435. <span className="m-l-1 t-3 t-s-14">支持 docx, xls, PDF, rar, zip, PNG, JPG 等类型的文件</span>
  436. <div className="fixed">
  437. <span className="f-w-b t-s-18">放开文件立刻上传{supply.file && `上传文件:${supply.name} 成功`}</span>
  438. </div>
  439. <FileUpload
  440. type="none"
  441. onDragEnter={() => this.setState({ draging: true })}
  442. onDragLeave={() => this.setState({ draging: false })}
  443. onUpload={file => this.uploadNote(file)}
  444. />
  445. </div>
  446. <div className="b-b m-t-2" />
  447. </Modal>
  448. <Modal
  449. show={showNote}
  450. title="课后笔记"
  451. width={500}
  452. height={500}
  453. onClose={() => this.setState({ showNote: false })}
  454. >
  455. {appointment.noteList &&
  456. appointment.noteList.map(row => {
  457. return <Note user={this.props.user} teacher={data.teacher} data={row} />;
  458. })}
  459. </Modal>
  460. <Modal
  461. show={showSupply}
  462. title="课后留言"
  463. width={500}
  464. height={500}
  465. onClose={() => this.setState({ showSupply: false })}
  466. >
  467. {appointment.supplyList &&
  468. appointment.supplyList.map(row => {
  469. return <Note user={this.props.user} teacher={data.teacher} data={row} />;
  470. })}
  471. </Modal>
  472. <Modal
  473. show={showUploadSupply}
  474. title="上传留言"
  475. width={630}
  476. confirmText="提交"
  477. onConfirm={() => this.submitAppointmentComment(supply)}
  478. onCancel={() => this.setState({ showUploadSupply: false, supply: {} })}
  479. >
  480. <textarea
  481. className="b-c-1 w-10 p-10 m-b-1"
  482. rows={6}
  483. value={supply.content}
  484. placeholder="请输入留言内容。"
  485. onChange={e => {
  486. supply.content = e.target.value;
  487. this.setState({ supply });
  488. }}
  489. />
  490. <div className={`t-c drag-upload ${this.state.draging ? 'draging' : ''}`}>
  491. <Button theme="file">上传文件</Button>
  492. <span className="m-l-1 t-3 t-s-14">支持 docx, xls, PDF, rar, zip, PNG, JPG 等类型的文件</span>
  493. <div className="fixed">
  494. <span className="f-w-b t-s-18">放开文件立刻上传{supply.file && `上传文件:${supply.name} 成功`}</span>
  495. </div>
  496. <FileUpload
  497. type="none"
  498. onDragEnter={() => this.setState({ draging: true })}
  499. onDragLeave={() => this.setState({ draging: false })}
  500. onUpload={file => this.uploadSupply(file)}
  501. />
  502. </div>
  503. <div className="b-b m-t-2" />
  504. </Modal>
  505. </div>
  506. );
  507. }
  508. renderTabonline() {
  509. const { list = [] } = this.state;
  510. console.log(list);
  511. return list.map(item => {
  512. return (
  513. <CourseOnline
  514. data={item}
  515. user={this.props.user}
  516. refreshDetail={recordId => {
  517. this.refreshDetail(recordId);
  518. }}
  519. onRestore={() => {
  520. this.setState({ showRestore: true, restore: item });
  521. }}
  522. onSuspend={() => {
  523. this.setState({ showSuspend: true, suspend: item });
  524. }}
  525. onTime={() => {
  526. this.openTime(item);
  527. }}
  528. onComment={() => {
  529. this.setState({ showComment: true, comment: { channel: 'course-video', position: item.course.id } });
  530. }}
  531. closeCommentTips={() => this.closeCommentTips(item.id)}
  532. />
  533. );
  534. });
  535. }
  536. renderTabvs() {
  537. const { list = [] } = this.state;
  538. return list.map(item => {
  539. return (
  540. <CourseVs
  541. data={item}
  542. user={this.props.user}
  543. refreshDetail={recordId => {
  544. this.refreshDetail(recordId);
  545. }}
  546. onRestore={() => {
  547. this.setState({ showRestore: true, restore: item });
  548. }}
  549. onSuspend={() => {
  550. this.setState({ showSuspend: true, suspend: item });
  551. }}
  552. onComment={() => {
  553. this.setState({ showComment: true, comment: { channel: 'course-vs', position: item.course.id } });
  554. }}
  555. closeCommentTips={() => this.closeCommentTips(item.id)}
  556. onNote={appointment => this.setState({ showNote: true, appointment, data: item })}
  557. onUploadNote={(appointment, row) => {
  558. this.setState({ showUploadNote: true, appointment, data: item, note: row || {} });
  559. }}
  560. onUploadSupply={(appointment, row) => {
  561. this.setState({ showUploadSupply: true, appointment, data: item, supply: row || {} });
  562. }}
  563. onDeleteNote={(appointment, row) => this.deleteAppointmentComment(row)}
  564. onDeleteSupply={(appointment, row) => this.deleteAppointmentComment(row)}
  565. onSupply={appointment => this.setState({ whoSupply: true, appointment, data: item })}
  566. onUploadQuestion={(appointment, file, name) => {
  567. this.submitQuestionFile({
  568. id: appointment.id,
  569. recordId: appointment.recordId,
  570. questionFile: file,
  571. questionFileName: name,
  572. });
  573. }}
  574. setCCTalkName={(appointment, cctalkName) => this.setCCTalkName(appointment.recordId, cctalkName)}
  575. />
  576. );
  577. });
  578. }
  579. }
  580. class CourseOnline extends Component {
  581. constructor(props) {
  582. super(props);
  583. this.columns = [
  584. {
  585. title: '学习内容',
  586. key: 'title',
  587. render: (text, record) => {
  588. return `课时 ${record.no}: ${text}`;
  589. },
  590. },
  591. {
  592. title: '预习作业',
  593. key: 'paper',
  594. render: (text, record) => {
  595. const { paper = {} } = record;
  596. return `${paper.paper && paper.paper.times > 0 ? `${paper.paper.times}次+` : ''}${
  597. paper.report ? formatPercent(paper.report.userNumber, paper.report.questionNumber, false) : '0%'
  598. }`;
  599. },
  600. },
  601. {
  602. title: '进度',
  603. key: 'progress',
  604. render: (text, record) => {
  605. return record.progress ? (
  606. <div>
  607. <div className="v-a-m d-i-b">
  608. <ProgressText width={50} size="small" times={1} progress={20} unit="次" />
  609. </div>
  610. <IconButton className="m-l-2" type="start" />
  611. <IconButton className="m-l-5" type="report" />
  612. </div>
  613. ) : (
  614. '-'
  615. );
  616. },
  617. },
  618. {
  619. title: '最近学习',
  620. key: 'lastTime',
  621. render: (text, record) => {
  622. const { paper = {} } = record;
  623. return paper.report && formatDate(paper.report.updateTime, 'YYYY-MM-DD HH:mm:ss');
  624. },
  625. },
  626. {
  627. title: '笔记',
  628. key: 'note',
  629. render: () => {
  630. return <GIcon name="note" active />;
  631. },
  632. },
  633. {
  634. title: '问答',
  635. key: 'ask',
  636. render: (text, record) => {
  637. return (
  638. <Link to={`/course/ask/${record.courseId}?no=${record.id}`}>{`${record.answerNumber ||
  639. 0}/${record.askNumber || 0}`}</Link>
  640. );
  641. },
  642. },
  643. ];
  644. this.state = { open: false };
  645. }
  646. render() {
  647. const { data = {} } = this.props;
  648. switch (data.status) {
  649. case 'ing':
  650. return this.renderIng();
  651. case 'not':
  652. return this.renderNot();
  653. case 'end':
  654. return this.renderEnd();
  655. case 'suspend':
  656. return this.renderSuspend();
  657. default:
  658. return <div />;
  659. }
  660. }
  661. renderIng() {
  662. const { data, onTime, onComment, onSuspend } = this.props;
  663. const { open } = this.state;
  664. return (
  665. <div className="course-item ing">
  666. <div className="title">
  667. <div className="tag">学习中</div>
  668. <div className="text">{data.course.title}</div>
  669. <div className="right">
  670. <More
  671. menu={[{ label: '评价', key: 'comment' }, { label: '停课', key: 'suspend' }]}
  672. onClick={value => {
  673. const { key } = value;
  674. if (key === 'comment') {
  675. onComment();
  676. } else if (key === 'suspend') {
  677. onSuspend();
  678. }
  679. }}
  680. />
  681. </div>
  682. </div>
  683. {data.currentCourseNo && (
  684. <div
  685. className="continue"
  686. onClick={() => {
  687. linkTo(`/course/detail/${data.course.id}`);
  688. }}
  689. >
  690. <Assets name="notice" />
  691. 继续学习:课时 {data.currentNo}:{data.currentCourseNo.title}
  692. </div>
  693. )}
  694. <div className="detail">
  695. <div className="left">
  696. <Assets name="sun_blue" src={data.course.cover} />
  697. <div className="info">
  698. <div className="t1">授课老师</div>
  699. <div className="t2">{data.course.teacher}</div>
  700. <div className="t1">有效期</div>
  701. <div className="t2">{data.useExpireDays}Days</div>
  702. </div>
  703. </div>
  704. <div className="right">
  705. <div className="item">
  706. <GIcon name="speed-block" active noHover />
  707. <div className="text">
  708. <span>{data.currentNo}</span>/{data.courseNos.length}
  709. </div>
  710. </div>
  711. <div className="item">
  712. <GIcon name="question-block" active noHover />
  713. <div className="text">
  714. <span>{data.answerNumber}</span>/{data.askNumber}
  715. </div>
  716. </div>
  717. <div className="item" onClick={() => onTime()}>
  718. <GIcon name="clockin-block" active noHover />
  719. <div className="text">
  720. <span>{formatSeconds(data.totalTime)}</span> {data.previewProgress || 0}%
  721. </div>
  722. </div>
  723. <div className="item">
  724. <GIcon name="note-block" active noHover />
  725. <div className="text">{data.noteNumber}</div>
  726. </div>
  727. </div>
  728. <div className="open">
  729. <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} />
  730. </div>
  731. </div>
  732. {open && this.renderTable()}
  733. </div>
  734. );
  735. }
  736. renderNot() {
  737. const { data } = this.props;
  738. return (
  739. <div className="course-item not">
  740. <div className="title">
  741. <div className="tag">未开通</div>
  742. <div className="text">{data.course.title}</div>
  743. </div>
  744. <div className="detail">
  745. <div className="left">
  746. <Assets name="sun_blue" src={data.course.cover} />
  747. <div className="info">
  748. <div className="t1">授课老师</div>
  749. <div className="t2">{data.course.teacher}</div>
  750. <div>
  751. <div className="d-i-b m-r-2">
  752. <div className="t-2">课时</div>
  753. <div className="t-1 t-s-16">{data.courseNos.length}</div>
  754. </div>
  755. <div className="d-i-b">
  756. <div className="t-2">总时长</div>
  757. <div className="t-1 t-s-16">{formatSeconds(data.courseTime)}</div>
  758. </div>
  759. </div>
  760. </div>
  761. </div>
  762. <div className="right t-c">
  763. <div className="text">请于 {formatDate(data.endTime, 'YYYY-MM-DD')} 前开通</div>
  764. <Button
  765. size="lager"
  766. radius
  767. onClick={() => {
  768. this.open(data.id);
  769. }}
  770. >
  771. 立即开通
  772. </Button>
  773. </div>
  774. </div>
  775. </div>
  776. );
  777. }
  778. renderEnd() {
  779. const { data, onTime, onComment } = this.props;
  780. const { open } = this.state;
  781. return (
  782. <div className="course-item end">
  783. <div className="title">
  784. <div className="tag">已结束</div>
  785. <div className="text">{data.course.title}</div>
  786. <div className="right">
  787. <More
  788. menu={[{ label: '评价', key: 'comment' }]}
  789. onClick={value => {
  790. const { key } = value;
  791. if (key === 'comment') {
  792. onComment();
  793. }
  794. }}
  795. />
  796. </div>
  797. </div>
  798. <div className="detail">
  799. <div className="left">
  800. <Assets name="sun_blue" src={data.course.cover} />
  801. <div className="info">
  802. <div className="t1">授课老师</div>
  803. <div className="t2">{data.course.teacher}</div>
  804. <div className="t1">有效期</div>
  805. <div className="t2">
  806. {formatDate(data.useStartTime, 'YYYY-MM-DD')}至{formatDate(data.useEndTime, 'YYYY-MM-DD')}
  807. </div>
  808. </div>
  809. </div>
  810. <div className="right">
  811. <div className="item">
  812. <GIcon name="question-block" active noHover />
  813. <div className="text">
  814. <span>{data.answerNumber}</span>/{data.askNumber}
  815. </div>
  816. </div>
  817. <div className="item" onClick={() => onTime()}>
  818. <GIcon name="clockin-block" active noHover />
  819. <div className="text">
  820. <span>{formatSeconds(data.totalTime)}</span> {data.previewProgress || 0}%
  821. </div>
  822. </div>
  823. <div className="item">
  824. <GIcon name="note-block" active noHover />
  825. <div className="text">{data.noteNumber}</div>
  826. </div>
  827. {data.courseAward > 0 && (
  828. <div className="item">
  829. <GIcon name="gift-block" active />
  830. <div className="text">赠送{data.courseAward}天</div>
  831. </div>
  832. )}
  833. </div>
  834. <div className="open">
  835. <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} />
  836. </div>
  837. </div>
  838. {open && this.renderTable()}
  839. </div>
  840. );
  841. }
  842. renderSuspend() {
  843. const { data, onTime, onRestore, onComment } = this.props;
  844. const { open } = this.state;
  845. return (
  846. <div className="course-item end">
  847. <div className="title">
  848. <div className="tag">已停课</div>
  849. <div className="text">
  850. {data.course.title}
  851. <Button size="small" radius onClick={() => onRestore()}>
  852. 恢复上课
  853. </Button>
  854. </div>
  855. <div className="t-s-12">
  856. <span className="t-3">申请时间:</span>
  857. <span className="t-2 m-r-1">{formatDate(data.suspendTime, 'YYYY-MM-DD HH:mm:ss')}</span>
  858. <span className="t-3">已停课:</span>
  859. <span>{parseInt((new Date().getTime() - new Date(data.suspendTime).getTime()) / 86400000, 10)}Days/30</span>
  860. </div>
  861. <div className="right">
  862. <More
  863. menu={[{ label: '评价', key: 'comment' }]}
  864. onClick={value => {
  865. const { key } = value;
  866. if (key === 'comment') {
  867. onComment();
  868. }
  869. }}
  870. />
  871. </div>
  872. </div>
  873. <div className="detail">
  874. <div className="left">
  875. <Assets name="sun_blue" src={data.course.cover} />
  876. <div className="info">
  877. <div className="t1">授课老师</div>
  878. <div className="t2">{data.course.teacher}</div>
  879. <div className="t1">有效期</div>
  880. <div className="t2">{data.useExpireDays}Days</div>
  881. </div>
  882. </div>
  883. <div className="right">
  884. <div className="item">
  885. <GIcon name="speed-block" noHover />
  886. <div className="text">
  887. <span>{data.currentNo}</span>/{data.courseNos.length}
  888. </div>
  889. </div>
  890. <div className="item">
  891. <GIcon name="question-block" active noHover />
  892. <div className="text">
  893. <span>{data.answerNumber}</span>/{data.askNumber}
  894. </div>
  895. </div>
  896. <div className="item" onClick={() => onTime()}>
  897. <GIcon name="clockin-block" active noHover />
  898. <div className="text">
  899. <span>{formatSeconds(data.totalTime)}</span> {data.previewProgress || 0}%
  900. </div>
  901. </div>
  902. <div className="item">
  903. <GIcon name="note-block" active noHover />
  904. <div className="text">{data.noteNumber}</div>
  905. </div>
  906. </div>
  907. <div className="open">
  908. <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} />
  909. </div>
  910. </div>
  911. {open && this.renderTable()}
  912. </div>
  913. );
  914. }
  915. renderTable() {
  916. const { data = {} } = this.props;
  917. const { courseNos = [] } = data;
  918. return <UserTable size="small" columns={this.columns} data={courseNos} />;
  919. }
  920. }
  921. const titleMap = {
  922. 1: '预约时间',
  923. 2: '答疑文档',
  924. 3: '上课',
  925. 4: '课后笔记',
  926. 5: '课后补充',
  927. 6: '备考信息',
  928. 7: '完成作业',
  929. };
  930. const iconMap = {
  931. 1: 'time-icon',
  932. 2: 'QA-icon',
  933. 3: 'class-icon',
  934. 4: 'note-icon',
  935. 5: 'supplement-icon',
  936. 6: 'information-icon',
  937. 7: 'homework-icon',
  938. };
  939. const statusMap = {
  940. 1: appointment => {
  941. if (!appointment.id) return 'end';
  942. return '';
  943. },
  944. 2: appointment => {
  945. if (!appointment.questionFile) return 'not';
  946. return '';
  947. },
  948. 3: (appointment, data) => {
  949. if (!data.cctalkName) return 'not';
  950. return '';
  951. },
  952. 4: appointment => {
  953. if (!appointment.noteList && appointment.noteList.length === 0) return 'not';
  954. return '';
  955. },
  956. 5: appointment => {
  957. if (!appointment.supplyList && appointment.supplyList.length === 0) return 'not';
  958. return '';
  959. },
  960. 6: () => {
  961. return '';
  962. },
  963. 7: appointment => {
  964. const { paper } = appointment;
  965. if (!paper.report || formatPercent(paper.report.userNumber, paper.report.questionNumber) < 100) return 'not';
  966. return '';
  967. },
  968. };
  969. class CourseVs extends Component {
  970. constructor(props) {
  971. super(props);
  972. this.columns = {
  973. system: [
  974. { title: '学习内容', key: 'title' },
  975. {
  976. title: '预习作业',
  977. key: 'paper',
  978. render: (text, record) => {
  979. return record.progress ? (
  980. <div>
  981. <div className="v-a-m d-i-b">
  982. <ProgressText width={50} size="small" times={1} progress={20} unit="次" />
  983. </div>
  984. <IconButton className="m-l-2" type="start" />
  985. <IconButton className="m-l-5" type="report" />
  986. </div>
  987. ) : (
  988. '-'
  989. );
  990. },
  991. },
  992. {
  993. title: '授课时间',
  994. key: 'time',
  995. render: (text, record) => {
  996. return `${formatDate(record.startTime, 'YYYY-MM-DD HH:mm:ss')} ~ ${formatDate(record.endTime, 'HH:mm:ss')}`;
  997. },
  998. },
  999. {
  1000. title: '课后笔记',
  1001. key: 'note',
  1002. render: (text, record) => {
  1003. return <a onClick={() => this.props.onNote(record)}>查看</a>;
  1004. },
  1005. },
  1006. {
  1007. title: '课后补充',
  1008. key: 'supply',
  1009. render: (text, record) => {
  1010. return <a onClick={() => this.props.onSupply(record)}>查看</a>;
  1011. },
  1012. },
  1013. ],
  1014. answer: [
  1015. { title: '学习内容', key: 'title' },
  1016. {
  1017. title: '答疑文档',
  1018. key: 'questionFile',
  1019. render: (text, record) => {
  1020. return (
  1021. <a href={text} target="_blank">
  1022. {record.questionFileName}
  1023. </a>
  1024. );
  1025. },
  1026. },
  1027. {
  1028. title: '授课时间',
  1029. key: 'time',
  1030. render: (text, record) => {
  1031. return `${formatDate(record.startTime, 'YYYY-MM-DD HH:mm:ss')} ~ ${formatDate(record.endTime, 'HH:mm:ss')}`;
  1032. },
  1033. },
  1034. {
  1035. title: '课后补充',
  1036. key: 'supply',
  1037. render: (text, record) => {
  1038. this.props.onSupply(record);
  1039. },
  1040. },
  1041. ],
  1042. };
  1043. this.listMap = {
  1044. novice: [1, 3],
  1045. coach: [1, 6, 3],
  1046. system: [1, 7, 3, 4],
  1047. answer: [1, 2, 3],
  1048. };
  1049. const index = props.data.appointments.length - 1;
  1050. this.state = {
  1051. open: false,
  1052. tab: 'ing',
  1053. index,
  1054. list: this.listMap[this.props.data.course.vsType],
  1055. showTips:
  1056. props.data.commentTips === 0 &&
  1057. (props.data.appointments.length === parseInt(props.data.number / 2, 10) ||
  1058. props.data.appointments.length === props.data.number),
  1059. };
  1060. }
  1061. closeCommentTips() {
  1062. My.courseCommentTips(this.props.data.id).then(() => {
  1063. this.setState({ showTips: false });
  1064. });
  1065. }
  1066. render() {
  1067. const { data = {} } = this.props;
  1068. switch (data.status) {
  1069. case 'ing':
  1070. return this.renderIng();
  1071. case 'not':
  1072. return this.renderNot();
  1073. case 'end':
  1074. return this.renderEnd();
  1075. case 'suspend':
  1076. return this.renderSuspend();
  1077. default:
  1078. return <div />;
  1079. }
  1080. }
  1081. renderIng() {
  1082. const { data, onComment, closeCommentTips } = this.props;
  1083. const { tab, showTips } = this.state;
  1084. return (
  1085. <div className="education-item ing">
  1086. <div className="title">
  1087. <div className="tag">学习中</div>
  1088. <div className="text">
  1089. {data.course.title}
  1090. {data.vsNo > 0 ? `V${data.vsNo}` : ''}
  1091. {data.number > 0 ? `(${data.number}课时)` : ''}
  1092. </div>
  1093. <div className="right">
  1094. <More
  1095. menu={[{ label: '评价', key: 'comment' }, { label: '停课', key: 'suspend' }]}
  1096. onClick={value => {
  1097. const { key } = value;
  1098. if (key === 'comment') {
  1099. onComment();
  1100. }
  1101. }}
  1102. />
  1103. </div>
  1104. </div>
  1105. {showTips && (
  1106. <div className="continue" onClick={() => closeCommentTips()}>
  1107. <Assets name="notice" />
  1108. 课程已过半,可以来写写评价啦<a onClick={() => onComment()}>去写评价 ></a>
  1109. </div>
  1110. )}
  1111. <div className="detail">
  1112. <div className="left">
  1113. <Assets name="sun_blue" src={data.course.cover} />
  1114. <div className="info">
  1115. <div className="t1">授课老师</div>
  1116. <div className="t2">{data.teacher.realname}</div>
  1117. <div className="t1">有效期</div>
  1118. <div className="t2">{data.useExpireDays}Days</div>
  1119. </div>
  1120. </div>
  1121. <div className="right">
  1122. <div className="item">
  1123. <GIcon name="speed-block" active noHover />
  1124. <div className="text">
  1125. <span>{data.appointments.length}</span>/{data.number}
  1126. </div>
  1127. </div>
  1128. <div className="item">
  1129. <GIcon name="time-block" active noHover />
  1130. <div className="text">
  1131. <span>{data.appointments.length > 0 ? data.totalDays / data.appointments.length : 0}</span>/课时
  1132. </div>
  1133. </div>
  1134. </div>
  1135. </div>
  1136. {(data.course.vsType === 'system' || data.course.vsType === 'answer') && (
  1137. <Tabs
  1138. className="t-l"
  1139. type="line"
  1140. theme="theme"
  1141. size="small"
  1142. width={80}
  1143. active={tab}
  1144. tabs={[{ key: 'ing', title: '授课中' }, { key: 'end', title: '已结课' }]}
  1145. onChange={key => this.setState({ tab: key })}
  1146. />
  1147. )}
  1148. {tab === 'ing' ? this.renderTimeLine() : this.renderTable()}
  1149. </div>
  1150. );
  1151. }
  1152. renderNot() {
  1153. const { data } = this.props;
  1154. return (
  1155. <div className="education-item not">
  1156. <div className="title">
  1157. <div className="tag">未开通</div>
  1158. <div className="text">
  1159. {data.course.title}
  1160. {data.vsNo > 0 ? `V${data.vsNo}` : ''}
  1161. {data.number > 0 ? `(${data.number}课时)` : ''}
  1162. </div>
  1163. </div>
  1164. <div className="detail">
  1165. <div className="left">
  1166. <Assets name="sun_blue" src={data.course.cover} />
  1167. <div className="info">
  1168. <div className="t1">授课老师</div>
  1169. <div className="t2">{data.teacher.realname}</div>
  1170. <div>
  1171. <div className="d-i-b m-r-2">
  1172. <div className="t-2">课时</div>
  1173. <div className="t-1 t-s-16">{data.number}</div>
  1174. </div>
  1175. <div className="d-i-b">
  1176. <div className="t-2">总时长</div>
  1177. <div className="t-1 t-s-16">{data.number}Hours</div>
  1178. </div>
  1179. </div>
  1180. </div>
  1181. </div>
  1182. <div className="right">
  1183. <div className="qr-code">
  1184. <Assets name="qrcode" src={data.teacher.qr} />
  1185. <div className="i">
  1186. <div className="t1">请尽快与老师预约上课时间</div>
  1187. <div className="t2">请于 {formatDate(data.endTime, 'YYYY-MM-DD')} 前开通课程</div>
  1188. </div>
  1189. </div>
  1190. </div>
  1191. </div>
  1192. {/* {this.renderTimeLine()} */}
  1193. </div>
  1194. );
  1195. }
  1196. renderEnd() {
  1197. const { data, onComment, closeCommentTips } = this.props;
  1198. const { open, tab, showTips } = this.state;
  1199. return (
  1200. <div className="education-item end">
  1201. <div className="title">
  1202. <div className="tag">已结课</div>
  1203. <div className="text">
  1204. {data.course.title}
  1205. {data.vsNo > 0 ? `V${data.vsNo}` : ''}
  1206. {data.number > 0 ? `(${data.number}课时)` : ''}
  1207. </div>
  1208. <div className="right">
  1209. <More
  1210. menu={[{ label: '评价', key: 'comment' }]}
  1211. onClick={value => {
  1212. const { key } = value;
  1213. if (key === 'comment') {
  1214. onComment();
  1215. }
  1216. }}
  1217. />
  1218. </div>
  1219. </div>
  1220. {showTips && (
  1221. <div className="continue" onClick={() => closeCommentTips()}>
  1222. <Assets name="notice" />
  1223. 课程已结束,可以来写写评价啦<a onClick={() => onComment()}>去写评价 ></a>
  1224. </div>
  1225. )}
  1226. <div className="detail">
  1227. <div className="left">
  1228. <Assets name="sun_blue" src={data.course.cover} />
  1229. <div className="info">
  1230. <div className="t1">授课老师</div>
  1231. <div className="t2">{data.teacher.realname}</div>
  1232. <div className="t1">有效期</div>
  1233. <div className="t2">{data.useExpireDays}Days</div>
  1234. </div>
  1235. </div>
  1236. <div className="right">
  1237. <div className="item">
  1238. <GIcon name="speed-block" active noHover />
  1239. <div className="text">
  1240. <span>{data.appointments.length}</span>/{data.number}
  1241. </div>
  1242. </div>
  1243. <div className="item">
  1244. <GIcon name="time-block" active noHover />
  1245. <div className="text">
  1246. <span>{data.appointments.length > 0 ? data.totalDays / data.appointments.length : 0}</span>/课时
  1247. </div>
  1248. </div>
  1249. </div>
  1250. <div className="open">
  1251. <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} />
  1252. </div>
  1253. </div>
  1254. {(data.course.vsType === 'system' || data.course.vsType === 'answer') && (
  1255. <Tabs
  1256. className="t-l"
  1257. type="line"
  1258. theme="theme"
  1259. size="small"
  1260. width={80}
  1261. active={tab}
  1262. tabs={[{ key: 'ing', title: '授课中' }, { key: 'end', title: '已结课' }]}
  1263. onChange={key => this.setState({ tab: key })}
  1264. />
  1265. )}
  1266. {tab === 'ing' ? this.renderTimeLine() : this.renderTable()}
  1267. </div>
  1268. );
  1269. }
  1270. renderSuspend() {
  1271. const { data, onRestore, onComment } = this.props;
  1272. return (
  1273. <div className="education-item end">
  1274. <div className="title">
  1275. <div className="tag">已停课</div>
  1276. <div className="text">
  1277. {data.course.title}
  1278. {data.vsNo > 0 ? `V${data.vsNo}` : ''}
  1279. {data.number > 0 ? `(${data.number}课时)` : ''}
  1280. <Button size="small" onClick={() => onRestore()}>
  1281. 恢复上课
  1282. </Button>
  1283. </div>
  1284. <div className="t-s-12">
  1285. <span className="t-3">申请时间:</span>
  1286. <span className="t-2 m-r-1">{formatDate(data.suspendTime, 'YYYY-MM-DD HH:mm:ss')}</span>
  1287. <span className="t-3">已停课:</span>
  1288. <span>{parseInt((new Date().getTime() - new Date(data.suspendTime).getTime()) / 86400000, 10)}Days/30</span>
  1289. </div>
  1290. <div className="right">
  1291. <More
  1292. menu={[{ label: '评价', key: 'comment' }]}
  1293. onClick={value => {
  1294. const { key } = value;
  1295. if (key === 'comment') {
  1296. onComment();
  1297. }
  1298. }}
  1299. />
  1300. </div>
  1301. </div>
  1302. <div className="detail">
  1303. <div className="left">
  1304. <Assets name="sun_blue" src={data.course.cover} />
  1305. <div className="info">
  1306. <div className="t1">授课老师</div>
  1307. <div className="t2">{data.teacher.realname}</div>
  1308. <div className="t1">有效期</div>
  1309. <div className="t2">{data.useExpireDays}Days</div>
  1310. </div>
  1311. </div>
  1312. <div className="right">
  1313. <div className="item">
  1314. <GIcon name="speed-block" active noHover />
  1315. <div className="text">
  1316. <span>{data.appointments.length}</span>/{data.number}
  1317. </div>
  1318. </div>
  1319. <div className="item">
  1320. <GIcon name="time-block" active noHover />
  1321. <div className="text">
  1322. <span>{data.appointments.length > 0 ? data.totalDays / data.appointments.length : 0}</span>/课时
  1323. </div>
  1324. </div>
  1325. </div>
  1326. </div>
  1327. </div>
  1328. );
  1329. }
  1330. renderTimeLine() {
  1331. const { data = {}, onUploadNote, onUploadSupply, onUploadQuestion, setCCTalkName } = this.props;
  1332. const { list = [], index } = this.state;
  1333. const appointment = data.appointments[index] || {};
  1334. let status = '';
  1335. return [
  1336. <div className="class-hour">
  1337. <div className="text">
  1338. 课时 {appointment.no}:{appointment.title}
  1339. </div>
  1340. <div className="right">
  1341. <GIcon name="prev" onClick={() => this.setState({ index: index === 0 ? index : index - 1 })} />
  1342. <span>上一课时</span>
  1343. <span>下一课时</span>
  1344. <GIcon
  1345. name="next"
  1346. onClick={() => this.setState({ index: index >= data.appointments.length - 1 ? index : index + 1 })}
  1347. />
  1348. </div>
  1349. </div>,
  1350. <div className="time-line">
  1351. {list.map(item => {
  1352. if (status === '') {
  1353. // 上一阶段完成
  1354. status = statusMap(appointment, data);
  1355. } else {
  1356. // 上一阶段未完成
  1357. status = 'end';
  1358. }
  1359. return (
  1360. <TimeLineItem
  1361. type={item}
  1362. user={this.props.user}
  1363. appointment={appointment}
  1364. data={data}
  1365. status={status}
  1366. onUploadNote={onUploadNote}
  1367. onUploadSupply={onUploadSupply}
  1368. onUploadQuestion={onUploadQuestion}
  1369. setCCTalkName={setCCTalkName}
  1370. />
  1371. );
  1372. })}
  1373. </div>,
  1374. ];
  1375. }
  1376. renderTable() {
  1377. const { data = {} } = this.props;
  1378. const { appointments = [] } = data;
  1379. return <UserTable size="small" columns={this.columns[data.course.vsType]} data={appointments} />;
  1380. }
  1381. }
  1382. class TimeLineItem extends Component {
  1383. constructor(props) {
  1384. super(props);
  1385. this.state = {};
  1386. }
  1387. onClick(key) {
  1388. const { onClick } = this.props;
  1389. const { status } = this.props;
  1390. if (status === 'not') return;
  1391. if (onClick) onClick(key);
  1392. }
  1393. render() {
  1394. const { type } = this.props;
  1395. const { status } = this.props;
  1396. return (
  1397. <div className={`time-line-item ${status}`}>
  1398. <div className="icon-title">
  1399. <GIcon name={iconMap[type]} active={status !== 'not'} noHover />
  1400. <div className="title">{titleMap[type]}</div>
  1401. </div>
  1402. <div className="time-line-detail">{this.renderDetail()}</div>
  1403. </div>
  1404. );
  1405. }
  1406. renderDetail() {
  1407. const {
  1408. data = {},
  1409. appointment = {},
  1410. type,
  1411. onUploadNote,
  1412. onUploadSupply,
  1413. onDeleteNote,
  1414. onDeleteSupply,
  1415. onUploadQuestion,
  1416. setCCTalkName,
  1417. } = this.props;
  1418. const { status } = this.props;
  1419. const { paper } = appointment;
  1420. switch (type) {
  1421. case '1':
  1422. switch (status) {
  1423. case 'end':
  1424. case 'not':
  1425. return (
  1426. <span>
  1427. 请尽快与老师预约上课时间,老师微信:{data.teacher.wechat}扫码加微信{' '}
  1428. <Dropdown overlay={<Assets name="qrcode" />}>
  1429. <span>
  1430. <Assets className="m-l-1" name="erweima" />
  1431. </span>
  1432. </Dropdown>
  1433. </span>
  1434. );
  1435. default:
  1436. return (
  1437. <span>
  1438. {formatDate(appointment.startTime, 'YYYY-MM-DD HH:mm:ss')} ~{' '}
  1439. {formatDate(appointment.endTime, 'HH:mm:ss')}
  1440. </span>
  1441. );
  1442. }
  1443. case '2':
  1444. switch (status) {
  1445. case 'end':
  1446. return <span className="link">点此上传</span>;
  1447. case 'not':
  1448. return (
  1449. <FileUpload
  1450. onUpload={file => {
  1451. return Common.upload(file).then(result => onUploadQuestion(appointment, result.url, file.name));
  1452. }}
  1453. >
  1454. <span className="link">点此上传</span>
  1455. </FileUpload>
  1456. );
  1457. default:
  1458. return (
  1459. <a href={appointment.questionFile} target="_blank">
  1460. {appointment.questionFileName || '文件'}
  1461. </a>
  1462. );
  1463. }
  1464. case '3':
  1465. switch (status) {
  1466. case 'end':
  1467. return <input placeholder="请输入CCtalk用户名查看授课频道" />;
  1468. case 'not':
  1469. return (
  1470. <input
  1471. placeholder="请输入CCtalk用户名查看授课频道"
  1472. onChange={e => {
  1473. setCCTalkName(e.target.value);
  1474. }}
  1475. />
  1476. );
  1477. default:
  1478. return (
  1479. <span>
  1480. CCtalk 频道号 :{appointment.cctalkChannel}{' '}
  1481. <a className="link" href="" target="_black">
  1482. CC talk使用手册
  1483. </a>
  1484. </span>
  1485. );
  1486. }
  1487. case '4':
  1488. switch (status) {
  1489. case 'end':
  1490. return <span className="link">点此上传</span>;
  1491. case 'not':
  1492. return (
  1493. <span className="link" onClick={() => onUploadNote(appointment)}>
  1494. 点此上传
  1495. </span>
  1496. );
  1497. default:
  1498. return (
  1499. <div>
  1500. <div>
  1501. <span className="link" onClick={() => onUploadNote(appointment)}>
  1502. 点此上传
  1503. </span>
  1504. </div>
  1505. <div className="note-list">
  1506. {appointment.noteList.map(row => {
  1507. return (
  1508. <Note
  1509. user={this.props.user}
  1510. teacher={data.teacher}
  1511. data={row}
  1512. reply={!row.userId}
  1513. actionList={[
  1514. row.userId && { key: 'edit', title: '编辑' },
  1515. row.userId && { key: 'delete', title: '删除' },
  1516. ]}
  1517. onAction={key => {
  1518. switch (key) {
  1519. case 'edit':
  1520. onUploadNote(appointment, row);
  1521. break;
  1522. case 'delete':
  1523. onDeleteNote(appointment, row);
  1524. break;
  1525. case 'reply':
  1526. onUploadNote(appointment, { parentId: row.id });
  1527. break;
  1528. default:
  1529. }
  1530. }}
  1531. />
  1532. );
  1533. })}
  1534. </div>
  1535. </div>
  1536. );
  1537. }
  1538. case '5':
  1539. switch (status) {
  1540. case 'end':
  1541. return <span className="link">写留言</span>;
  1542. case 'not':
  1543. return (
  1544. <span className="link" onClick={() => onUploadSupply(appointment)}>
  1545. 写留言
  1546. </span>
  1547. );
  1548. default:
  1549. return (
  1550. <div>
  1551. <div>
  1552. <span className="link" onClick={() => onUploadSupply(appointment)}>
  1553. 写留言
  1554. </span>
  1555. </div>
  1556. <div className="note-list">
  1557. {appointment.supplyList.map(row => {
  1558. return (
  1559. <Note
  1560. user={this.props.user}
  1561. teacher={data.teacher}
  1562. data={row}
  1563. reply={!row.userId}
  1564. actionList={[
  1565. row.userId && { key: 'edit', title: '编辑' },
  1566. row.userId && { key: 'delete', title: '删除' },
  1567. ]}
  1568. onAction={key => {
  1569. switch (key) {
  1570. case 'edit':
  1571. onUploadSupply(appointment, row);
  1572. break;
  1573. case 'delete':
  1574. onDeleteSupply(appointment, row);
  1575. break;
  1576. case 'reply':
  1577. onUploadSupply(appointment, { parentId: row.id });
  1578. break;
  1579. default:
  1580. }
  1581. }}
  1582. />
  1583. );
  1584. })}
  1585. </div>
  1586. </div>
  1587. );
  1588. }
  1589. case '6':
  1590. return [
  1591. <div>
  1592. <span>基本情况</span>
  1593. <a href={status !== 'end' ? '' : ''} className="link">
  1594. 填写
  1595. </a>
  1596. </div>,
  1597. <div>
  1598. <span>备考细节</span>
  1599. <a href={status !== 'end' ? '' : ''} className="link">
  1600. 填写
  1601. </a>
  1602. </div>,
  1603. ];
  1604. case '7':
  1605. return (
  1606. <ProgressText
  1607. progress={paper ? formatPercent(paper.report.userNumber, paper.report.questionNumber) : 0}
  1608. size="small"
  1609. />
  1610. );
  1611. default:
  1612. return <div />;
  1613. }
  1614. }
  1615. }