page.js 54 KB

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