page.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. import React from 'react';
  2. import moment from 'moment';
  3. import { DatePicker, Checkbox, Modal, Form, Input, Row, Col, Button, Upload, List, Comment } from 'antd';
  4. import './index.less';
  5. import Page from '@src/containers/Page';
  6. import Block from '@src/components/Block';
  7. // import FilterLayout from '@src/layouts/FilterLayout';
  8. import ActionLayout from '@src/layouts/ActionLayout';
  9. import TableLayout from '@src/layouts/TableLayout';
  10. import { formatDate, formatSecond, formatPercent } from '@src/services/Tools';
  11. import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
  12. import { PcUrl } from '../../../../Constant';
  13. import { Course } from '../../../stores/course';
  14. import { User } from '../../../stores/user';
  15. import { System } from '../../../stores/system';
  16. export default class extends Page {
  17. init() {
  18. this.videoColumns = [{
  19. title: '课时',
  20. dataIndex: 'courseNo.no',
  21. }, {
  22. title: '课时名称',
  23. dataIndex: 'courseNo.title',
  24. }, {
  25. title: '学习记录',
  26. dataIndex: 'records',
  27. render: (text) => {
  28. return (text || []).map(row => {
  29. return <p>{formatDate(row.createTime, 'YYYY-MM-DD HH:mm:ss')} {formatSecond(row.userTime)}</p>;
  30. });
  31. },
  32. }, {
  33. title: '预习作业进度',
  34. dataIndex: 'userPaper',
  35. render: (text, record) => {
  36. const { userPaper, userReport } = record;
  37. return `${userPaper ? `${userPaper.times}遍` : ''}${userReport ? formatPercent(userReport.userNumber, userReport.questionNumber, false) : '0%'}`;
  38. },
  39. }, {
  40. title: '操作',
  41. dataIndex: 'handler',
  42. render: (text, record) => {
  43. return <div className="table-button">
  44. {<a onClick={() => {
  45. User.locationUser(record.userId, `${PcUrl}/my/report`);
  46. }}>查看</a>}
  47. </div>;
  48. },
  49. }];
  50. this.vsAction = [{
  51. key: 'addAppointment',
  52. name: '添加预约',
  53. }];
  54. this.vsList = [{
  55. key: 'id',
  56. type: 'hidden',
  57. }, {
  58. key: 'title',
  59. type: 'input',
  60. name: '课程名称',
  61. }, {
  62. key: 'timerange',
  63. type: 'daterange',
  64. showTime: true,
  65. name: '上课时间',
  66. }, {
  67. key: 'cctalkChannel',
  68. type: 'input',
  69. name: '频道号',
  70. }];
  71. this.vsColumns = [{
  72. title: '课时序号',
  73. dataIndex: 'no',
  74. render: (text) => {
  75. const { data } = this.state;
  76. return `${text}/${data.number}`;
  77. },
  78. }, {
  79. title: '上课时间',
  80. dataIndex: 'time',
  81. render: (text, record) => {
  82. return <DatePicker.RangePicker showTime value={[record.startTime, record.endTime]} onChange={(value) => {
  83. this.changeAppointment(record.id, { startTime: value[0], endTime: value[1] });
  84. }} />;
  85. },
  86. }, {
  87. title: '课程名称',
  88. dataIndex: 'title',
  89. }, {
  90. title: '频道号',
  91. dataIndex: 'cctalkChannel',
  92. }, {
  93. title: '学习进度',
  94. dataIndex: 'isFinish',
  95. render: (text, record) => {
  96. return <Checkbox checked={text > 0} onChange={(e) => {
  97. this.changeAppointment(record.id, { isFinish: e.target.checked ? 1 : 0 });
  98. }} />;
  99. },
  100. }, {
  101. title: '预习作业',
  102. dataIndex: 'previewAssign',
  103. render: (text, record) => {
  104. const { userPaper } = record;
  105. return userPaper ? <a onClick={() => {
  106. User.locationUser(record.userId, `${PcUrl}/paper/report/${record.reportId}`);
  107. }} target="_blank">查看{userPaper.times > 0 ? '(已完成)' : ''}</a> : (text ? text.title : '');
  108. },
  109. }, {
  110. title: '笔记批阅',
  111. dataIndex: 'noteList',
  112. render: (text, record) => {
  113. return <a onClick={() => {
  114. this.noteAction(record);
  115. }}>查看</a>;
  116. },
  117. }, {
  118. title: '课后补充',
  119. dataIndex: 'supplyList',
  120. render: (text, record) => {
  121. return <a onClick={() => {
  122. this.supplyAction(record);
  123. }}>查看</a>;
  124. },
  125. }];
  126. }
  127. initData() {
  128. const { id } = this.params;
  129. let handler;
  130. if (id) {
  131. handler = Course.listStudy({ ids: [id] });
  132. }
  133. handler
  134. .then(result => {
  135. const [row] = result.list;
  136. if (!row) {
  137. asyncSMessage('记录不存在');
  138. return;
  139. }
  140. const { course } = row;
  141. this.setState({ data: row, module: course.courseModule });
  142. this.refresh();
  143. });
  144. }
  145. refresh() {
  146. // const { id } = this.params;
  147. const { module } = this.state;
  148. switch (module) {
  149. case 'video':
  150. this.refreshVideo();
  151. break;
  152. case 'online':
  153. break;
  154. case 'vs':
  155. this.refreshVs();
  156. break;
  157. default:
  158. }
  159. }
  160. refreshVideo() {
  161. const { id } = this.params;
  162. User.allCourseRecord(Object.assign({ recordId: id }, this.state.search)).then(result => {
  163. this.setTableData(result, result ? result.length : 0);
  164. });
  165. }
  166. refreshVs() {
  167. const { id } = this.params;
  168. User.listCourseAppointment(Object.assign({ recordId: id }, this.state.search)).then(result => {
  169. result.list = result.list.map(row => {
  170. row.startTime = moment(row.startTime);
  171. row.endTime = moment(row.endTime);
  172. return row;
  173. });
  174. this.setTableData(result.list, result.total);
  175. });
  176. }
  177. refreshComment(appointmentId, type) {
  178. this.setState({ comment: {} });
  179. User.allCourseAppointmentComment({ appointmentId, type })
  180. .then(result => {
  181. const commentMap = {};
  182. result.forEach((row) => {
  183. commentMap[row.id] = row;
  184. });
  185. this.setState({ commentList: result, commentMap });
  186. });
  187. }
  188. changeAppointment(id, data) {
  189. data.id = id;
  190. return User.editCourseAppointment(data).then(() => {
  191. this.refreshVs();
  192. this.cleanInfo();
  193. });
  194. }
  195. addAppointmentAction() {
  196. const { id } = this.params;
  197. asyncForm('添加', this.vsList, {}, info => {
  198. const { data } = this.state;
  199. ([info.startTime, info.endTime] = info.timerange);
  200. return User.addCourseAppointment(Object.assign({ recordId: id, userId: data.userId, courseId: data.courseId, noteList: [], supplyList: [] }, info)).then(() => {
  201. asyncSMessage('添加成功!');
  202. this.refreshVs();
  203. });
  204. }).catch(err => {
  205. console.log(err);
  206. });
  207. }
  208. noteAction(record) {
  209. this.open(record, 'note');
  210. this.refreshComment(record.id, 'note');
  211. }
  212. supplyAction(record) {
  213. this.open(record, 'supply');
  214. this.refreshComment(record.id, 'supply');
  215. }
  216. submitComment(comment) {
  217. if (comment.id) {
  218. User.editCourseAppointmentComment(comment)
  219. .then(() => {
  220. this.refreshComment(comment.appointmentId, comment.type);
  221. this.setState({ comment: {} });
  222. });
  223. } else {
  224. User.addCourseAppointmentComment(comment)
  225. .then(() => {
  226. this.refreshComment(comment.appointmentId, comment.type);
  227. this.setState({ comment: {} });
  228. });
  229. }
  230. }
  231. deleteComment(comment) {
  232. User.delCourseAppointmentComment({ id: comment.id }).then(() => {
  233. this.refreshComment(comment.appointmentId, comment.type);
  234. });
  235. }
  236. deleteAppointment(row) {
  237. User.delCourseAppointment({ id: row.id }).then(() => {
  238. asyncSMessage('删除成功!');
  239. this.refresh();
  240. });
  241. }
  242. changeInfo(key, data) {
  243. const info = this.state[key] || {};
  244. this.setState({ [key]: Object.assign(info, data) });
  245. }
  246. renderVs() {
  247. const { data, page, commentMap } = this.state;
  248. return <div flex>
  249. {data.number > page.total && <ActionLayout
  250. itemList={this.vsAction}
  251. selectedKeys={this.state.selectedKeys}
  252. onAction={key => this.onAction(key)}
  253. />}
  254. <TableLayout
  255. columns={this.vsColumns}
  256. list={this.state.list}
  257. pagination={this.state.page}
  258. loading={this.props.core.loading}
  259. onChange={(pagination, filters, sorter) => this.tableChange(pagination, filters, sorter)}
  260. onSelect={(keys, rows) => this.tableSelect(keys, rows)}
  261. selectedKeys={this.state.selectedKeys}
  262. />
  263. {this.state.note && <Modal visible closable title='课后笔记' footer={null} onCancel={() => {
  264. this.close(false, 'note');
  265. }}>
  266. <List
  267. className="comment-list"
  268. itemLayout="horizontal"
  269. dataSource={this.state.commentList}
  270. renderItem={item => (
  271. <Comment
  272. actions={[!!item.userId && <span onClick={() => this.changeInfo('comment', { id: null, parentId: item.id, file: null, content: null })}>回复</span>, !item.userId && <span onClick={() => this.changeInfo('comment', item)}>编辑</span>, !item.userId && <span onClick={() => this.deleteComment(item)}>删除</span>]}
  273. author={item.userId ? '学生' : '老师'}
  274. // avatar={item.userId ? '学' : '师'}
  275. content={<p>{item.reply && <span>{item.reply}</span>}{item.content}<br /> {item.file && <a href={item.file} target="_blank">{item.name || '文件'}</a>}</p>}
  276. datetime={formatDate(item.createTime, 'YYYY-MM-DD')}
  277. />
  278. )}
  279. />
  280. <Form>
  281. <Row>
  282. {this.state.comment.parentId && <Col span={24}><span>回复:{(commentMap[this.state.comment.parentId] || {}).content}</span></Col>}
  283. <Col span={12}>
  284. <Input value={(this.state.comment || {}).content || ''} onChange={e => {
  285. this.changeInfo('comment', { content: e.target.value });
  286. }} />
  287. </Col>
  288. <Col span={1}><Upload
  289. showUploadList={false}
  290. beforeUpload={(file) => System.uploadImage(file).then((result) => {
  291. this.changeInfo('comment', { file: result.url, name: file.name });
  292. return Promise.reject();
  293. })}
  294. >
  295. <Button>上传文档{(this.state.comment || {}).file ? '(已上传)' : ''}</Button>
  296. </Upload></Col>
  297. <Col span={1} offset={8}><Button type='primary' onClick={() => {
  298. if (!this.state.comment || !this.state.comment.content) {
  299. asyncSMessage('请输入内容', 'error');
  300. return;
  301. }
  302. this.state.comment.appointmentId = this.state.note.id;
  303. this.state.comment.type = 'note';
  304. this.state.comment.recordId = this.state.note.recordId;
  305. this.submitComment(this.state.comment);
  306. }}>{(this.state.comment || {}).id ? '修改' : '发布'}</Button></Col>
  307. </Row>
  308. </Form>
  309. </Modal>}
  310. {this.state.supply && <Modal visible closable title='课后补充' footer={null} onCancel={() => {
  311. this.close(false, 'supply');
  312. }}>
  313. <List
  314. className="comment-list"
  315. itemLayout="horizontal"
  316. dataSource={this.state.commentList}
  317. renderItem={item => (
  318. <Comment
  319. actions={[!!item.userId && <span onClick={() => this.changeInfo('comment', { id: null, parentId: item.id, file: null, content: null })}>回复</span>, !item.userId && <span onClick={() => this.changeInfo('comment', item)}>编辑</span>, !item.userId && <span onClick={() => this.deleteComment(item)}>删除</span>]}
  320. author={item.userId ? '学生' : '老师'}
  321. // avatar={item.userId ? '学' : '师'}
  322. content={<p>{item.reply && <span>{item.reply}</span>}{item.content}<br /> {item.file && <a href={item.file} target="_blank">{item.name || '文件'}</a>}</p>}
  323. datetime={formatDate(item.createTime, 'YYYY-MM-DD')}
  324. />
  325. )}
  326. />
  327. <Form>
  328. <Row>
  329. {this.state.comment.parentId && <Col span={24}><span>回复:{(commentMap[this.state.comment.parentId] || {}).content}</span></Col>}
  330. <Col span={12}>
  331. <Input value={(this.state.comment || {}).content || ''} onChange={e => {
  332. this.changeInfo('comment', { content: e.target.value });
  333. }} />
  334. </Col>
  335. <Col span={1}><Upload
  336. showUploadList={false}
  337. beforeUpload={(file) => System.uploadImage(file).then((result) => {
  338. this.changeInfo('comment', { file: result.url, name: file.name });
  339. return Promise.reject();
  340. })}
  341. >
  342. <Button>上传文档{(this.state.comment || {}).file ? '(已上传)' : ''}</Button>
  343. </Upload></Col>
  344. <Col span={1} offset={8}><Button type='primary' onClick={() => {
  345. if (!this.state.comment || !this.state.comment.content) {
  346. asyncSMessage('请输入内容', 'error');
  347. return;
  348. }
  349. this.state.comment.appointmentId = this.state.supply.id;
  350. this.state.comment.type = 'supply';
  351. this.state.comment.recordId = this.state.supply.recordId;
  352. this.submitComment(this.state.comment);
  353. }}>{(this.state.comment || {}).id ? '修改' : '发布'}</Button></Col>
  354. </Row>
  355. </Form>
  356. </Modal>}
  357. </div>;
  358. }
  359. renderVideo() {
  360. return <div flex>
  361. <TableLayout
  362. columns={this.videoColumns}
  363. list={this.state.list}
  364. pagination={this.state.page}
  365. loading={this.props.core.loading}
  366. onChange={(pagination, filters, sorter) => this.tableChange(pagination, filters, sorter)}
  367. onSelect={(keys, rows) => this.tableSelect(keys, rows)}
  368. selectedKeys={this.state.selectedKeys}
  369. />
  370. </div>;
  371. }
  372. renderDetail() {
  373. switch (this.state.module) {
  374. case 'online':
  375. return [];
  376. case 'vs':
  377. return [this.renderVs()];
  378. case 'video':
  379. return [this.renderVideo()];
  380. default:
  381. return <div />;
  382. }
  383. }
  384. renderView() {
  385. const { data = {} } = this.state;
  386. const { course = {} } = data;
  387. return <Block flex>
  388. <h1>{course.title}{data.vsNo > 0 ? `V${data.vsNo}` : ''}{data.number > 0 ? `(${data.number}课时)` : ''}</h1>
  389. {this.renderDetail()}
  390. </Block>;
  391. }
  392. }