CourseCreate.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. import React, { Component } from 'react';
  2. import pathToRegexp from 'path-to-regexp';
  3. import { connect } from 'dva';
  4. import { routerRedux } from 'dva/router';
  5. import {
  6. Form, Modal, Card, Button, Input, Switch, Select, Row, Col,
  7. } from 'antd';
  8. import {
  9. renderStatus, statusToBool, boolToStatus, genAbsolutePicUrl,
  10. } from '../../../utils/utils';
  11. import { Hotax } from '../../../utils/config';
  12. import AXDragSortTable from '../../../components/AXDragSortTable';
  13. import Selector from '../../../components/AXTableSelector/Selector';
  14. import FooterToolbar from '../../../components/FooterToolbar';
  15. import styles from './CourseCreate.less';
  16. const fieldLabels = {
  17. code: '课程编号',
  18. title: '课程标题',
  19. subTitle: '课程副标题',
  20. name: '课程全称',
  21. breadCrumb: '顶部导航名',
  22. merchant: '内容提供商',
  23. digest: '课程概要',
  24. detail: '课程详情',
  25. coverUrl: '课程封面图',
  26. bgUrl: '课程背景图',
  27. status: '课程状态',
  28. };
  29. const formItemLayout = {
  30. labelCol: {
  31. xs: { span: 24 },
  32. sm: { span: 3 },
  33. },
  34. wrapperCol: {
  35. xs: { span: 24 },
  36. sm: { span: 14 },
  37. md: { span: 12 },
  38. },
  39. };
  40. @connect(({ loading, product, lesson, resource, merchant }) => ({
  41. lesson,
  42. product,
  43. resource,
  44. merchant,
  45. lLoading: loading.models.lesson,
  46. pLoading: loading.models.product,
  47. rLoading: loading.models.resource,
  48. submitting: loading.models.product,
  49. }))
  50. @Form.create()
  51. export default class CourseItemCreatePage extends Component {
  52. state = {
  53. bgSelectorDestroy: true,
  54. coverSelectorDestroy: true,
  55. lessonSelectorDestroy: true,
  56. supportSelectorDestroy: true,
  57. };
  58. componentWillMount() {
  59. const match = pathToRegexp('/product/course/create').exec(this.props.location.pathname);
  60. if (match) {
  61. this.cleanPageState();
  62. }
  63. }
  64. componentDidMount() {
  65. const matchId = this.isEdit();
  66. if (matchId) {
  67. this.props.dispatch({
  68. type: 'product/fetchProductItem',
  69. payload: { pid: matchId },
  70. });
  71. }
  72. this.props.dispatch({
  73. type: 'merchant/fetchMerchantList',
  74. payload: { pageSize: 1000 }, // TODO 以后商户多了需要改写交互样式
  75. });
  76. }
  77. isEdit = () => {
  78. const { location } = this.props;
  79. const match = pathToRegexp('/product/course/edit/:id').exec(location.pathname);
  80. if (match) {
  81. return match[1];
  82. }
  83. return false;
  84. };
  85. cleanPageState = () => {
  86. this.props.dispatch({
  87. type: 'product/cleanItemState',
  88. payload: {},
  89. });
  90. };
  91. selectorDataFetcher = (name, params) => {
  92. switch (name) {
  93. case 'cover':
  94. this.props.dispatch({
  95. type: 'resource/fetchImageList',
  96. payload: params,
  97. });
  98. break;
  99. case 'bg':
  100. this.props.dispatch({
  101. type: 'resource/fetchImageList',
  102. payload: params,
  103. });
  104. break;
  105. case 'lesson':
  106. this.props.dispatch({
  107. type: 'lesson/fetchLessonList',
  108. payload: params,
  109. });
  110. break;
  111. case 'support':
  112. this.props.dispatch({
  113. type: 'product/fetchSupportList',
  114. payload: params,
  115. });
  116. break;
  117. default:
  118. break;
  119. }
  120. };
  121. currentItemFormatter = (name, rows) => {
  122. let payload;
  123. switch (name) {
  124. case 'cover':
  125. payload = { coverUrl: rows[0].path };
  126. break;
  127. case 'bg':
  128. payload = { bgUrl: rows[0].path };
  129. break;
  130. case 'lesson':
  131. payload = { subItemList: rows };
  132. break;
  133. case 'support':
  134. payload = { supportList: rows };
  135. break;
  136. default:
  137. break;
  138. }
  139. return payload;
  140. };
  141. handleSelectorModalShow = (name) => {
  142. this.setState({
  143. [`${name}SelectorDestroy`]: false,
  144. });
  145. this.selectorDataFetcher(name);
  146. };
  147. handleSelectorChange = (name, params) => {
  148. this.selectorDataFetcher(name, params);
  149. };
  150. handleSelectorFinish = (name, rows) => {
  151. this.setState({
  152. [`${name}SelectorDestroy`]: true,
  153. });
  154. const payload = this.currentItemFormatter(name, rows);
  155. this.props.dispatch({
  156. payload,
  157. type: 'product/fixCurrentItem',
  158. });
  159. };
  160. handleDragSortTableChange = (name, rows) => {
  161. const payload = this.currentItemFormatter(name, rows);
  162. this.props.dispatch({
  163. payload,
  164. type: 'product/fixCurrentItem',
  165. });
  166. };
  167. handleSelectorCancel = (name) => {
  168. this.setState({
  169. [`${name}SelectorDestroy`]: true,
  170. });
  171. };
  172. handlePageBack = () => {
  173. this.props.dispatch(routerRedux.push({
  174. pathname: '/product/course/list',
  175. state: this.props.location.state,
  176. }));
  177. };
  178. handlePageSubmit = (e) => {
  179. e.preventDefault();
  180. this.props.form.validateFieldsAndScroll((err, values) => {
  181. if (!err) {
  182. // 提取表单字段
  183. const { title, subTitle, name, status, ...rest } = values;
  184. const newName = `${title}_${subTitle}`;
  185. const newValues = { title, subTitle, name: newName, status: boolToStatus(status), ...rest };
  186. const { product } = this.props;
  187. const { currentItem } = product;
  188. const { bgUrl, coverUrl, subItemList, supportList } = currentItem;
  189. // 构造subList,supportIdList
  190. let subList;
  191. let supportIdList;
  192. if (subItemList) {
  193. subList = subItemList.map(
  194. item => ({ id: item.id, type: Hotax.PRODUCT_LESSON })
  195. );
  196. }
  197. if (supportList) {
  198. supportIdList = supportList.map(item => item.id);
  199. }
  200. const matchId = this.isEdit();
  201. if (matchId) {
  202. const params = {
  203. bgUrl,
  204. coverUrl,
  205. id: matchId,
  206. subItemList: subList,
  207. supportList: supportIdList,
  208. ...newValues,
  209. };
  210. this.props.dispatch({
  211. type: 'product/updateCourseItem',
  212. payload: params,
  213. states: this.props.location.state,
  214. });
  215. } else {
  216. const params = {
  217. bgUrl,
  218. coverUrl,
  219. subItemList: subList,
  220. supportList: supportIdList,
  221. ...newValues,
  222. };
  223. this.props.dispatch({
  224. type: 'product/createCourseItem',
  225. payload: params,
  226. states: this.props.location.state,
  227. });
  228. }
  229. }
  230. });
  231. };
  232. render() {
  233. const {
  234. form, submitting, rLoading, lLoading, pLoading, lesson, product, resource, merchant,
  235. } = this.props;
  236. const {
  237. lessonSelectorDestroy, supportSelectorDestroy, coverSelectorDestroy, bgSelectorDestroy,
  238. } = this.state;
  239. const { currentItem } = product;
  240. const {
  241. code, title, subTitle, name, digest, detail, status, coverUrl, bgUrl, cpId,
  242. breadCrumb, subItemList = [], supportList = [],
  243. } = currentItem;
  244. const { getFieldDecorator } = form;
  245. const lessonColumns = [{
  246. title: '课编号',
  247. dataIndex: 'code',
  248. key: 1,
  249. width: '20%',
  250. render: (text, record) => (
  251. <a
  252. className="a-link"
  253. target="_blank"
  254. rel="noopener noreferrer"
  255. href={`/product/lesson/edit/${record.id}`}
  256. >
  257. {text}
  258. </a>
  259. ),
  260. }, {
  261. title: '课名称',
  262. dataIndex: 'title',
  263. key: 2,
  264. width: '30%',
  265. render: (text, record) => (
  266. <a
  267. className="a-link"
  268. target="_blank"
  269. rel="noopener noreferrer"
  270. href={`/product/lesson/edit/${record.id}`}
  271. >
  272. {text}
  273. </a>
  274. ),
  275. }, {
  276. title: '状态',
  277. dataIndex: 'status',
  278. key: 3,
  279. render: text => renderStatus(text),
  280. }];
  281. const supportColumns = [{
  282. title: '配套编号',
  283. dataIndex: 'code',
  284. key: 1,
  285. width: '20%',
  286. render: (text, record) => (
  287. <a
  288. className="a-link"
  289. target="_blank"
  290. rel="noopener noreferrer"
  291. href={`/product/support/edit/${record.id}`}
  292. >
  293. {text}
  294. </a>
  295. ),
  296. }, {
  297. title: '配套名称',
  298. dataIndex: 'name',
  299. key: 2,
  300. width: '30%',
  301. render: (text, record) => (
  302. <a
  303. className="a-link"
  304. target="_blank"
  305. rel="noopener noreferrer"
  306. href={`/product/support/edit/${record.id}`}
  307. >
  308. {text}
  309. </a>
  310. ),
  311. }, {
  312. title: '配套状态',
  313. dataIndex: 'status',
  314. key: 3,
  315. render: text => renderStatus(text),
  316. }];
  317. const getMerchants = () => {
  318. const { list } = merchant;
  319. return list.map(item => ({
  320. text: item.name,
  321. key: item.id,
  322. }));
  323. };
  324. const getResourceModal = (isCover) => {
  325. return (
  326. <Modal
  327. width={1100}
  328. footer={null}
  329. visible
  330. title="图片资源"
  331. maskClosable={false}
  332. onCancel={() => this.handleSelectorCancel(isCover ? 'cover' : 'bg')}
  333. >
  334. <Selector
  335. multiple={false}
  336. loading={rLoading}
  337. selectorName="PictureSingle"
  338. list={resource.list}
  339. pageNo={resource.pageNo}
  340. pageSize={resource.pageSize}
  341. totalSize={resource.totalSize}
  342. onCancel={() => this.handleSelectorCancel(isCover ? 'cover' : 'bg')}
  343. onChange={data => this.handleSelectorChange(isCover ? 'cover' : 'bg', data)}
  344. onFinish={rows => this.handleSelectorFinish(isCover ? 'cover' : 'bg', rows)}
  345. />
  346. </Modal>
  347. );
  348. };
  349. const getLessonModal = () => {
  350. return (
  351. <Modal
  352. width={1100}
  353. footer={null}
  354. visible
  355. title="课资源"
  356. maskClosable={false}
  357. onCancel={() => this.handleSelectorCancel('lesson')}
  358. >
  359. <Selector
  360. multiple
  361. loading={lLoading}
  362. selectorName="Lesson"
  363. selectedRows={subItemList}
  364. list={lesson.list}
  365. pageNo={lesson.pageNo}
  366. pageSize={lesson.pageSize}
  367. totalSize={lesson.totalSize}
  368. onCancel={() => this.handleSelectorCancel('lesson')}
  369. onChange={data => this.handleSelectorChange('lesson', data)}
  370. onFinish={rows => this.handleSelectorFinish('lesson', rows)}
  371. />
  372. </Modal>
  373. );
  374. };
  375. const getSupportModal = () => {
  376. return (
  377. <Modal
  378. width={1100}
  379. footer={null}
  380. visible
  381. title="配套资源"
  382. maskClosable={false}
  383. onCancel={() => this.handleSelectorCancel('support')}
  384. >
  385. <Selector
  386. multiple
  387. loading={pLoading}
  388. selectorName="Support"
  389. selectedRows={supportList}
  390. list={product.list}
  391. pageNo={product.pageNo}
  392. pageSize={product.pageSize}
  393. totalSize={product.totalSize}
  394. onCancel={() => this.handleSelectorCancel('support')}
  395. onChange={data => this.handleSelectorChange('support', data)}
  396. onFinish={rows => this.handleSelectorFinish('support', rows)}
  397. />
  398. </Modal>
  399. );
  400. };
  401. const renderLessonCardName = () => {
  402. return (
  403. <Button
  404. type="primary"
  405. onClick={() => this.handleSelectorModalShow('lesson')}
  406. >课列表
  407. </Button>
  408. );
  409. };
  410. const renderSupportCardName = () => {
  411. return (
  412. <Button
  413. type="primary"
  414. onClick={() => this.handleSelectorModalShow('support')}
  415. >配套列表
  416. </Button>
  417. );
  418. };
  419. const renderCoverCardName = () => {
  420. return (
  421. <div className={styles.cardName}>
  422. <span>
  423. <a onClick={() => this.handleSelectorModalShow('cover')}>更换封面</a>
  424. </span>
  425. </div>
  426. );
  427. };
  428. const renderbgCardName = () => {
  429. return (
  430. <div className={styles.cardName}>
  431. <span>
  432. <a onClick={() => this.handleSelectorModalShow('bg')}>更换背景</a>
  433. </span>
  434. </div>
  435. );
  436. };
  437. return (
  438. <div>
  439. {/* 基础信息Card */}
  440. <Card title="基础信息" style={{ marginBottom: 16 }}>
  441. <Form>
  442. <Form.Item hasFeedback label={fieldLabels.code} {...formItemLayout}>
  443. {getFieldDecorator('code', {
  444. rules: [
  445. {
  446. required: true, message: '请填写课程编号',
  447. }, {
  448. pattern: /^[a-zA-Z0-9|-]+$/g, message: '编号包含非法字符',
  449. },
  450. ],
  451. initialValue: code,
  452. })(
  453. <Input placeholder="请输入" />
  454. )}
  455. </Form.Item>
  456. {this.isEdit() && (
  457. <Form.Item label={fieldLabels.name} {...formItemLayout}>
  458. {getFieldDecorator('name', {
  459. initialValue: name,
  460. })(
  461. <Input placeholder="根据标题及副标题自动生成完整名称,不必填写" disabled />
  462. )}
  463. </Form.Item>
  464. )}
  465. <Form.Item hasFeedback label={fieldLabels.title} {...formItemLayout}>
  466. {getFieldDecorator('title', {
  467. rules: [{ required: true, message: '请填写课程标题' }],
  468. initialValue: title,
  469. })(
  470. <Input placeholder="请输入" />
  471. )}
  472. </Form.Item>
  473. <Form.Item hasFeedback label={fieldLabels.subTitle} {...formItemLayout}>
  474. {getFieldDecorator('subTitle', {
  475. rules: [{ required: true, message: '请填写课程副标题' }],
  476. initialValue: subTitle,
  477. })(
  478. <Input placeholder="请输入" />
  479. )}
  480. </Form.Item>
  481. <Form.Item hasFeedback label={fieldLabels.breadCrumb} {...formItemLayout}>
  482. {getFieldDecorator('breadCrumb', {
  483. rules: [{ required: true, message: '请填写顶部导航名' }],
  484. initialValue: breadCrumb,
  485. })(
  486. <Input placeholder="请输入" />
  487. )}
  488. </Form.Item>
  489. <Form.Item hasFeedback label={fieldLabels.merchant} {...formItemLayout}>
  490. {getFieldDecorator('cpId', {
  491. rules: [{ required: true, message: '请选择供应商' }],
  492. initialValue: cpId,
  493. })(
  494. <Select placeholder="请选择">
  495. {
  496. getMerchants().map(item => (
  497. <Select.Option key={item.key} value={item.key}>
  498. {item.text}
  499. </Select.Option>
  500. ))
  501. }
  502. </Select>
  503. )}
  504. </Form.Item>
  505. <Form.Item label={fieldLabels.digest} {...formItemLayout}>
  506. {getFieldDecorator('digest', {
  507. initialValue: digest,
  508. })(
  509. <Input.TextArea rows={4} placeholder="请输入" />
  510. )}
  511. </Form.Item>
  512. <Form.Item label={fieldLabels.detail} {...formItemLayout}>
  513. {getFieldDecorator('detail', {
  514. initialValue: detail,
  515. })(
  516. <Input.TextArea rows={6} placeholder="请输入" />
  517. )}
  518. </Form.Item>
  519. <Form.Item label={fieldLabels.status} {...formItemLayout}>
  520. {getFieldDecorator('status', {
  521. valuePropName: 'checked',
  522. initialValue: statusToBool(status),
  523. })(
  524. <Switch
  525. checkedChildren="正常"
  526. unCheckedChildren="删除"
  527. />
  528. )}
  529. </Form.Item>
  530. </Form>
  531. </Card>
  532. {/* 封面及背景图选择Card */}
  533. <Card title="封面|背景" style={{ marginBottom: 16 }}>
  534. <Row gutter={16}>
  535. <Col
  536. md={{ span: 9, offset: 1 }}
  537. lg={{ span: 7, offset: 2 }}
  538. xl={{ span: 5, offset: 4 }}
  539. xxl={{ span: 3, offset: 6 }}
  540. >
  541. <Card
  542. hoverable
  543. title={renderCoverCardName()}
  544. >
  545. {
  546. coverUrl && (
  547. <div className={styles.cover}>
  548. <img src={genAbsolutePicUrl(coverUrl)} alt="" />
  549. </div>
  550. )
  551. }
  552. {!coverSelectorDestroy && getResourceModal(true)}
  553. </Card>
  554. </Col>
  555. <Col
  556. md={{ span: 9, offset: 2 }}
  557. lg={{ span: 7, offset: 4 }}
  558. xl={{ span: 5, offset: 4 }}
  559. xxl={{ span: 3, offset: 5 }}
  560. >
  561. <Card
  562. hoverable
  563. title={renderbgCardName()}
  564. >
  565. {
  566. bgUrl ? (
  567. <div className={styles.background}>
  568. <img src={genAbsolutePicUrl(bgUrl)} alt="" />
  569. </div>
  570. ) : null
  571. }
  572. {!bgSelectorDestroy && getResourceModal(false)}
  573. </Card>
  574. </Col>
  575. </Row>
  576. </Card>
  577. {/* 课列表选择Card */}
  578. <Card title={renderLessonCardName()} style={{ marginBottom: 16 }}>
  579. <AXDragSortTable
  580. columns={lessonColumns}
  581. data={subItemList}
  582. onChange={rows => this.handleDragSortTableChange('lesson', rows)}
  583. />
  584. {!lessonSelectorDestroy && getLessonModal()}
  585. </Card>
  586. {/* 周边配套选择Card */}
  587. <Card title={renderSupportCardName()} style={{ marginBottom: 70 }}>
  588. <AXDragSortTable
  589. columns={supportColumns}
  590. data={supportList}
  591. onChange={rows => this.handleDragSortTableChange('support', rows)}
  592. />
  593. {!supportSelectorDestroy && getSupportModal()}
  594. </Card>
  595. <FooterToolbar style={{ width: '100%' }}>
  596. <Button
  597. onClick={this.handlePageBack}
  598. style={{ marginRight: 10 }}
  599. >取消
  600. </Button>
  601. <Button
  602. type="primary"
  603. loading={submitting}
  604. onClick={this.handlePageSubmit}
  605. >提交
  606. </Button>
  607. </FooterToolbar>
  608. </div>
  609. );
  610. }
  611. }