Browse Source

1. :sparkles: add feature - audiobook manage module;
2. :zap: improving performance - tag `a` change to `Button`;
3. :bug: fix some bugs - goods create failed bug;

zhanghe 7 years ago
parent
commit
1901128b8b
36 changed files with 619 additions and 117 deletions
  1. 4 0
      src/common/menu.js
  2. 11 0
      src/common/router.js
  3. 12 0
      src/components/AXTableSelector/columnsMap.js
  4. 1 1
      src/components/AXUpload/index.js
  5. 2 2
      src/components/SiderMenu/SiderMenu.js
  6. 1 1
      src/index.less
  7. 40 0
      src/models/resource.js
  8. 8 0
      src/models/shelves.js
  9. 1 1
      src/models/terminal.js
  10. 7 7
      src/routes/Campus/CampusCreate.js
  11. 1 1
      src/routes/Frontend/Recommend/RecommendCourse.js
  12. 8 6
      src/routes/Frontend/Tag/TagCreate.js
  13. 6 5
      src/routes/Frontend/TagGroup/TagGroupCreate.js
  14. 6 6
      src/routes/Merchant/MerchantList.js
  15. 11 12
      src/routes/Product/Course/CourseCreate.js
  16. 21 6
      src/routes/Product/Courseware/CoursewareCreate.js
  17. 0 14
      src/routes/Product/Courseware/CoursewareCreate.less
  18. 7 8
      src/routes/Product/Lesson/LessonCreate.js
  19. 0 15
      src/routes/Product/Lesson/LessonCreate.less
  20. 1 5
      src/routes/Product/Package/PackageCreate.js
  21. 6 6
      src/routes/Product/Support/SupportCreate.js
  22. 172 0
      src/routes/Resource/AudioBook/AudioBookCreate.js
  23. 221 0
      src/routes/Resource/AudioBook/AudioBookList.js
  24. 25 0
      src/routes/Resource/AudioBook/AudioBookList.less
  25. 2 2
      src/routes/Resource/Video/VideoTableList.js
  26. 2 10
      src/routes/Shelves/ShelvesCreate.js
  27. 1 1
      src/routes/Shelves/ShelvesEdit.js
  28. 5 0
      src/routes/Shelves/ShelvesList.js
  29. 2 2
      src/routes/Shelves/TableForm.js
  30. 1 1
      src/routes/System/CmsUser/CmsUserCreate.js
  31. 1 1
      src/routes/Terminal/User/TerminalCreate.js
  32. 1 1
      src/routes/Terminal/User/TerminalList.js
  33. 2 2
      src/routes/Trade/Order/OrderCreate.js
  34. 25 0
      src/services/resource.js
  35. 3 1
      src/utils/config.js
  36. 2 0
      src/utils/utils.js

+ 4 - 0
src/common/menu.js

@@ -29,6 +29,10 @@ const menuData = () => {
       name: '视频管理',
       path: 'video',
       icon: 'video-camera',
+    }, {
+      name: '有声读物',
+      path: 'audiobook',
+      icon: 'notification',
     }],
     authority: ['admin', 'platform'],
   }, {

+ 11 - 0
src/common/router.js

@@ -133,6 +133,17 @@ export const getRouterData = (app) => {
       component: dynamicWrapper(app, ['resource'], () => import('../routes/Resource/Video/VideoCreate')),
       name: '编辑视频',
     },
+    '/resource/audiobook': {
+      component: dynamicWrapper(app, ['resource'], () => import('../routes/Resource/AudioBook/AudioBookList')),
+      name: '创建有声读物',
+    },
+    '/resource/audiobook-create': {
+      component: dynamicWrapper(app, ['resource'], () => import('../routes/Resource/AudioBook/AudioBookCreate')),
+      name: '编辑有声读物',
+    },
+    '/resource/audiobook-edit/:id': {
+      component: dynamicWrapper(app, ['resource'], () => import('../routes/Resource/AudioBook/AudioBookCreate')),
+    },
     // 系统管理相关路由注册
     '/system/cms-user': {
       component: dynamicWrapper(app, ['cmsUser'], () => import('../routes/System/CmsUser')),

+ 12 - 0
src/components/AXTableSelector/columnsMap.js

@@ -90,6 +90,18 @@ const clMap = {
       render: text => renderVideoQuality(text),
     }],
   },
+  AudioBook: {
+    columns: [{
+      title: '有声读物编号',
+      key: 1,
+      dataIndex: 'code',
+      width: '40%',
+    }, {
+      title: '有声读物名称',
+      key: 2,
+      dataIndex: 'name',
+    }],
+  },
   Courseware: {
     columns: [{
       title: '课件编号',

+ 1 - 1
src/components/AXUpload/index.js

@@ -188,7 +188,7 @@ class Uploader extends Component {
           visible={previewVisible}
           footer={null}
           onCancel={this.handleCancelPreview}
-          width={650}
+          width={1100}
         >
           <img src={previewImg} alt="" style={{ width: '100%', marginTop: 20 }} />
         </Modal>

+ 2 - 2
src/components/SiderMenu/SiderMenu.js

@@ -168,7 +168,7 @@ export default class SiderMenu extends PureComponent {
     const { collapsed, onCollapse } = this.props;
     onCollapse(!collapsed);
     this.triggerResizeEvent();
-  }
+  };
   @Debounce(600)
   triggerResizeEvent() { // eslint-disable-line
     const event = document.createEvent('HTMLEvents');
@@ -197,7 +197,7 @@ export default class SiderMenu extends PureComponent {
       item =>
         key && (item.key === key || item.path === key),
     );
-  }
+  };
   handleOpenChange = (openKeys) => {
     const lastOpenKey = openKeys[openKeys.length - 1];
     const moreThanOne = openKeys.filter(openKey => this.isMainMenu(openKey)).length > 1;

+ 1 - 1
src/index.less

@@ -60,7 +60,7 @@ body {
   .ant-tag {
     width: 137px;
     text-align: center;
-    margin-top: 5px;
+    margin-top: 8px !important;
     height: 25px;
     line-height: 25px;
   }

+ 40 - 0
src/models/resource.js

@@ -3,8 +3,11 @@ import { message } from 'antd';
 import {
   createResource,
   updateResource,
+  createAudioBook,
+  updateAudioBook,
   queryImageResource,
   queryVideoResource,
+  queryAudioBookResource,
 } from '../services/resource';
 
 export default {
@@ -46,6 +49,20 @@ export default {
         });
       }
     },
+    *fetchAudioBookList({ payload }, { call, put }) {
+      const response = yield call(queryAudioBookResource, payload);
+      if (response.success) {
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: response.data.list || [],
+            pageSize: response.data.pageSize,
+            totalSize: response.data.totalSize,
+            pageNo: response.data.pageNo,
+          },
+        });
+      }
+    },
     *createImage({ payload }, { call, put }) {
       const response = yield call(createResource, payload);
       if (response && response.success) {
@@ -60,6 +77,7 @@ export default {
     *createVideo({ payload, states }, { call, put }) {
       const response = yield call(createResource, payload);
       if (response && response.success) {
+        message.success('创建视频成功');
         yield put(
           routerRedux.push({
             pathname: '/resource/video',
@@ -68,6 +86,18 @@ export default {
         );
       }
     },
+    *createAudioBook({ payload, states }, { call, put }) {
+      const response = yield call(createAudioBook, payload);
+      if (response && response.success) {
+        message.success('创建有声读物成功');
+        yield put(
+          routerRedux.push({
+            pathname: '/resource/audiobook',
+            state: states,
+          })
+        );
+      }
+    },
     *updateImage({ payload, states }, { call, put }) {
       const response = yield call(updateResource, payload);
       if (response && response.success) {
@@ -88,6 +118,16 @@ export default {
         }));
       }
     },
+    *updateAudioBook({ payload, states }, { call, put }) {
+      const response = yield call(updateAudioBook, payload);
+      if (response && response.success) {
+        message.success('修改有声读物成功');
+        yield put(routerRedux.push({
+          pathname: '/resource/audiobook',
+          state: states,
+        }));
+      }
+    },
   },
 
   reducers: {

+ 8 - 0
src/models/shelves.js

@@ -188,6 +188,14 @@ export default {
         ...action.payload,
       };
     },
+    cleanListState(state) {
+      return {
+        ...state,
+        list: [],
+        pageSize: 15,
+        pageNo: 1,
+      };
+    },
     cleanItemState(state) {
       return {
         ...state,

+ 1 - 1
src/models/terminal.js

@@ -139,7 +139,7 @@ export default {
       if (response.success) {
         message.success('账号已成功解绑');
         yield put({
-          type: 'fetchSpecialTerminalList',
+          type: 'fetchTerminalList',
           payload: states.Queryers,
         });
       }

+ 7 - 7
src/routes/Campus/CampusCreate.js

@@ -73,7 +73,7 @@ export default class CampusCreatePage extends PureComponent {
       return match[1];
     }
     return false;
-  }
+  };
   handleMerchantSelectorModalShow = () => {
     this.setState({
       merchantSelectorDestroy: false,
@@ -82,7 +82,7 @@ export default class CampusCreatePage extends PureComponent {
       type: 'merchant/fetchMerchantList',
       payload: {},
     });
-  }
+  };
   handleMerchantSelectorFinish = (rows) => {
     this.setState({
       merchantSelectorDestroy: true,
@@ -109,7 +109,7 @@ export default class CampusCreatePage extends PureComponent {
       type: 'merchant/fetchMerchantList',
       payload: params,
     });
-  }
+  };
   handlePageSubmit = () => {
     this.props.form.validateFieldsAndScroll((error, values) => {
       if (!error) {
@@ -143,13 +143,13 @@ export default class CampusCreatePage extends PureComponent {
         }
       }
     });
-  }
+  };
   handlePageBack = () => {
     this.props.dispatch(routerRedux.push({
       pathname: '/campus/list',
       state: this.props.location.state,
     }));
-  }
+  };
 
   render() {
     const { merchantSelectorDestroy } = this.state;
@@ -161,7 +161,7 @@ export default class CampusCreatePage extends PureComponent {
     const renderCityName = () => {
       const { provinceCode, cityName } = currentItem;
       if (!provinceCode && !cityName) {
-
+        return null;
       } else {
         return [
           provinceCodeToName(provinceCode),
@@ -242,7 +242,7 @@ export default class CampusCreatePage extends PureComponent {
           <Form>
             <Form.Item
               {...formItemLayout}
-              label={!this.isEdit() ? <a onClick={this.handleMerchantSelectorModalShow}>所属厂商</a> : '所属厂商'}
+              label={!this.isEdit() ? <Button size="small" type="primary" onClick={this.handleMerchantSelectorModalShow}>所属厂商</Button> : '所属厂商'}
             >
               <List
                 bordered

+ 1 - 1
src/routes/Frontend/Recommend/RecommendCourse.js

@@ -119,7 +119,7 @@ export default class RecommendCourseEditPage extends Component {
     return (
       <div>
         <Card
-          title={<a onClick={this.handleSelectorModalShow}>选择课程</a>}
+          title={<Button type="primary" onClick={this.handleSelectorModalShow}>选择课程</Button>}
           style={{ marginBottom: 70 }}
         >
           <AXDragSortTable

+ 8 - 6
src/routes/Frontend/Tag/TagCreate.js

@@ -267,7 +267,13 @@ export default class TagCreatePage extends Component {
       width: '10%',
       align: 'left',
       render: (text, record) => (
-        <a onClick={() => this.handleTagMetaSelectorModalShow(record.key)}>{text}</a>
+        <Button
+          size="small"
+          type="primary"
+          style={{ width: 90 }}
+          onClick={() => this.handleTagMetaSelectorModalShow(record.key)}
+        >{text}
+        </Button>
       ),
     }, {
       dataIndex: 'value',
@@ -398,11 +404,7 @@ export default class TagCreatePage extends Component {
     };
     const renderProductCardName = () => {
       return (
-        <div className={styles.cardName}>
-          <span>
-            <a onClick={this.handleShelvesSelectorModalShow}>关联产品</a>
-          </span>
-        </div>
+        <Button type="primary" onClick={this.handleShelvesSelectorModalShow}>关联产品</Button>
       );
     };
     return (

+ 6 - 5
src/routes/Frontend/TagGroup/TagGroupCreate.js

@@ -220,11 +220,12 @@ export default class TagCreatePage extends Component {
     };
     const renderMerchantCardName = () => {
       return (
-        <div className={styles.cardName}>
-          <span>
-            <a disabled={!!this.isEdit()} onClick={this.handleMerchantSelectorModalShow}>所属渠道</a>
-          </span>
-        </div>
+        <Button
+          type="primary"
+          disabled={!!this.isEdit()}
+          onClick={this.handleMerchantSelectorModalShow}
+        >所属渠道
+        </Button>
       );
     };
     return (

+ 6 - 6
src/routes/Merchant/MerchantList.js

@@ -34,7 +34,7 @@ export default class MerchantListPage extends Component {
       pathname: '/merchant/create',
       state: this.state,
     }));
-  }
+  };
   handleDeleteOperation = (item) => {
     Modal.confirm({
       okText: '确定',
@@ -48,13 +48,13 @@ export default class MerchantListPage extends Component {
         });
       },
     });
-  }
+  };
   handleEditOperation = (item) => {
     this.props.dispatch(routerRedux.push({
       pathname: `/merchant/edit/${item.id}`,
       state: this.state,
     }));
-  }
+  };
   handleFilterOperation = (params, states) => {
     this.props.dispatch({
       type: 'merchant/fetchMerchantList',
@@ -64,16 +64,16 @@ export default class MerchantListPage extends Component {
       UIParams: states,
       Queryers: params,
     });
-  }
+  };
   handleDepositOperation = (item) => {
     this.props.dispatch(routerRedux.push({
       pathname: `/merchant/deposit/${item.id}`,
       state: { ...this.state, currentItem: item },
     }));
-  }
+  };
   handleBatchOperation = () => {
     Message.info('暂不支持批量操作!');
-  }
+  };
 
   render() {
     const { loading, merchant } = this.props;

+ 11 - 12
src/routes/Product/Course/CourseCreate.js

@@ -407,23 +407,22 @@ export default class CourseItemCreatePage extends Component {
         </Modal>
       );
     };
-
     const renderLessonCardName = () => {
       return (
-        <div className={styles.cardName}>
-          <span>
-            <a onClick={() => this.handleSelectorModalShow('lesson')}>课列表</a>
-          </span>
-        </div>
+        <Button
+          type="primary"
+          onClick={() => this.handleSelectorModalShow('lesson')}
+        >课列表
+        </Button>
       );
     };
     const renderSupportCardName = () => {
       return (
-        <div className={styles.cardName}>
-          <span>
-            <a onClick={() => this.handleSelectorModalShow('support')}>配套列表</a>
-          </span>
-        </div>
+        <Button
+          type="primary"
+          onClick={() => this.handleSelectorModalShow('support')}
+        >配套列表
+        </Button>
       );
     };
     const renderCoverCardName = () => {
@@ -540,7 +539,7 @@ export default class CourseItemCreatePage extends Component {
           </Form>
         </Card>
         {/* 封面及背景图选择Card */}
-        <Card title="封面 | 背景" style={{ marginBottom: 16 }}>
+        <Card title="封面|背景" style={{ marginBottom: 16 }}>
           <Row gutter={16}>
             <Col
               md={{ span: 9, offset: 1 }}

+ 21 - 6
src/routes/Product/Courseware/CoursewareCreate.js

@@ -91,6 +91,12 @@ export default class CoursewareCreatePage extends Component {
         payload: params,
       });
     }
+    if (resourceType === 'AudioBook') {
+      this.props.dispatch({
+        type: 'resource/fetchAudioBookList',
+        payload: params,
+      });
+    }
   };
   handleSelectorModalShow = () => {
     this.setState({
@@ -197,6 +203,14 @@ export default class CoursewareCreatePage extends Component {
               <img src={genAbsolutePicUrl(text)} alt="" />
             </div>
           );
+        } else if (type === Hotax.RESOURCE_AUDIOBOOK) {
+          const { img } = record;
+          const { path } = img || {};
+          return (
+            <div className={styles.picture}>
+              <img src={genAbsolutePicUrl(path)} alt="" />
+            </div>
+          );
         } else {
           return (
             <div className={styles.video}>
@@ -244,11 +258,11 @@ export default class CoursewareCreatePage extends Component {
 
     const renderCardName = () => {
       return (
-        <div className={styles.cardName}>
-          <span>
-            <a onClick={this.handleSelectorModalShow}>资源列表</a>
-          </span>
-        </div>
+        <Button
+          type="primary"
+          onClick={this.handleSelectorModalShow}
+        >资源列表
+        </Button>
       );
     };
     const renderModalTitle = () => {
@@ -259,6 +273,7 @@ export default class CoursewareCreatePage extends Component {
         >
           <Radio.Button value="Picture">图片</Radio.Button>
           <Radio.Button value="Video">视频</Radio.Button>
+          <Radio.Button value="AudioBook">有声读物</Radio.Button>
         </Radio.Group>
       );
     };
@@ -345,7 +360,7 @@ export default class CoursewareCreatePage extends Component {
               onCancel={this.handleSelectorCancel}
             >
               <Selector
-                multiple={resourceType === 'Picture'}
+                multiple={resourceType === 'Picture' || resourceType === 'AudioBook'}
                 loading={loading}
                 selectorName={resourceType}
                 list={resource.list}

+ 0 - 14
src/routes/Product/Courseware/CoursewareCreate.less

@@ -1,19 +1,5 @@
 @import "../../../../node_modules/antd/lib/style/themes/default.less";
 
-.cardName {
-  & > span {
-    display: inline-block;
-    height: 24px;
-    padding: 0 7px;
-    vertical-align: bottom;
-  }
-  :global {
-    .ant-btn-primary {
-      margin-left: 10px;
-    }
-  }
-}
-
 .picture {
   position: relative;
   vertical-align: middle;

+ 7 - 8
src/routes/Product/Lesson/LessonCreate.js

@@ -3,11 +3,10 @@ import pathToRegexp from 'path-to-regexp';
 import { connect } from 'dva';
 import { routerRedux } from 'dva/router';
 import { Form, Modal, Card, Button, Input, Switch } from 'antd';
-import AXDragSortTable from '../../../components/AXDragSortTable/index';
+import AXDragSortTable from '../../../components/AXDragSortTable';
 import Selector from '../../../components/AXTableSelector/Selector';
-import FooterToolbar from '../../../components/FooterToolbar/index';
+import FooterToolbar from '../../../components/FooterToolbar';
 import { renderStatus, statusToBool, boolToStatus } from '../../../utils/utils';
-import styles from './LessonCreate.less';
 
 const fieldLabels = {
   code: '课编号',
@@ -199,11 +198,11 @@ export default class LessonCreatePage extends Component {
 
     const renderCardName = () => {
       return (
-        <div className={styles.cardName}>
-          <span>
-            <a onClick={this.handleSelectorModalShow}>课件列表</a>
-          </span>
-        </div>
+        <Button
+          type="primary"
+          onClick={this.handleSelectorModalShow}
+        >课件列表
+        </Button>
       );
     };
     return (

+ 0 - 15
src/routes/Product/Lesson/LessonCreate.less

@@ -1,15 +0,0 @@
-@import "../../../../node_modules/antd/lib/style/themes/default.less";
-
-.cardName {
-  & > span {
-    display: inline-block;
-    height: 24px;
-    padding: 0 7px;
-    vertical-align: bottom;
-  }
-  :global {
-    .ant-btn-primary {
-      margin-left: 10px;
-    }
-  }
-}

+ 1 - 5
src/routes/Product/Package/PackageCreate.js

@@ -298,11 +298,7 @@ export default class PackageCreatePage extends Component {
 
     const renderCardName = () => {
       return (
-        <div className={styles.cardName}>
-          <span>
-            <a onClick={this.handleSelectorModalShow}>课程配套</a>
-          </span>
-        </div>
+        <Button type="primary" onClick={this.handleSelectorModalShow}>课程配套</Button>
       );
     };
     const renderModalTitle = () => {

+ 6 - 6
src/routes/Product/Support/SupportCreate.js

@@ -327,11 +327,11 @@ export default class SupportCreatePage extends Component {
 
     const renderSupportCardName = () => {
       return (
-        <div className={styles.cardName}>
-          <span>
-            <a onClick={() => this.handleSelectorModalShow('support')}>相关配套</a>
-          </span>
-        </div>
+        <Button
+          type="primary"
+          onClick={() => this.handleSelectorModalShow('support')}
+        >相关配套
+        </Button>
       );
     };
     const renderCoverCardName = () => {
@@ -440,7 +440,7 @@ export default class SupportCreatePage extends Component {
           </Form>
         </Card>
         {/* 封面及走马灯选择Card */}
-        <Card title="封面 | 图册" style={{ marginBottom: 16 }}>
+        <Card title="封面|图册" style={{ marginBottom: 16 }}>
           <Row gutter={16}>
             <Col
               md={{ span: 10, offset: 1 }}

+ 172 - 0
src/routes/Resource/AudioBook/AudioBookCreate.js

@@ -0,0 +1,172 @@
+import React, { PureComponent } from 'react';
+import { Card, Form, Input, Button } from 'antd';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
+import Uploader from '../../../components/AXUpload';
+import { Hotax } from '../../../utils/config';
+
+const formItemLayout = {
+  labelCol: {
+    xs: { span: 24 },
+    sm: { span: 6 },
+  },
+  wrapperCol: {
+    xs: { span: 24 },
+    sm: { span: 14 },
+    md: { span: 12 },
+  },
+};
+const submitFormLayout = {
+  wrapperCol: {
+    xs: { span: 24, offset: 0 },
+    sm: { span: 12, offset: 6 },
+  },
+};
+
+function getFileObject(params) {
+  if (!params) { return; }
+  const { imgFormat, imgPath, imgUrl } = params;
+  return {
+    url: imgUrl,
+    path: imgPath,
+    name: '',
+    size: 0,
+    type: `image/${imgFormat}`,
+  };
+}
+
+@Form.create()
+@connect(({ loading }) => ({
+  submitting: loading.models.resource,
+}))
+export default class AudioBookCreatePage extends PureComponent {
+  constructor(props) {
+    super(props);
+    const { state } = props.location;
+    const { audioBookItem } = state || {};
+    this.state = {
+      fileList: audioBookItem ? [getFileObject(audioBookItem)] : [],
+      ...(audioBookItem || {}),
+    };
+  }
+  handleOnChangeEvent = (fileList) => {
+    this.setState({ fileList });
+    return fileList;
+  };
+  handlePageBack = () => {
+    const { UIParams, Queryers } = this.props.location.state || {};
+    this.props.dispatch(
+      routerRedux.push({
+        pathname: '/resource/audiobook',
+        state: {
+          UIParams,
+          Queryers,
+        },
+      })
+    );
+  };
+  handlePageSubmit = (e) => {
+    e.preventDefault();
+    this.props.form.validateFieldsAndScroll((err, values) => {
+      if (!err) {
+        const { fileList } = this.state;
+        const { ...params } = values;
+        if (Array.isArray(fileList) && fileList.length) {
+          const { path, type } = fileList[0];
+          params.imgPath = path;
+          params.imgFormat = type ? type.split('/')[1] : '';
+        }
+        params.type = Hotax.RESOURCE_AUDIOBOOK;
+        params.status = Hotax.STATUS_NORMAL;
+        const { id } = this.state;
+        const { UIParams, Queryers } = this.props.location.state || {};
+        if (id) {
+          this.props.dispatch({
+            type: 'resource/updateAudioBook',
+            payload: { id, ...params },
+            states: { UIParams, Queryers },
+          });
+        } else {
+          this.props.dispatch({
+            type: 'resource/createAudioBook',
+            payload: params,
+            states: { UIParams, Queryers },
+          });
+        }
+      }
+    });
+  };
+  render() {
+    const { form, submitting } = this.props;
+    const { getFieldDecorator } = form;
+    const { fileList, code, name, audioPath, audioFormat } = this.state;
+
+    return (
+      <PageHeaderLayout>
+        <Card>
+          <Form onSubmit={this.handlePageSubmit}>
+            <Form.Item label="有声读物编号" hasFeedback {...formItemLayout}>
+              {getFieldDecorator('code', {
+                rules: [{
+                  required: true, message: '编号不能为空!',
+                }, {
+                  pattern: /^[a-zA-Z0-9|-]+$/ig, message: '编号格式错误!',
+                }],
+                initialValue: code,
+              })(
+                <Input />
+              )}
+            </Form.Item>
+            <Form.Item label="有声读物名称" hasFeedback {...formItemLayout}>
+              {getFieldDecorator('name', {
+                rules: [{ required: true, message: '名称不能为空!' }],
+                initialValue: name,
+              })(
+                <Input />
+              )}
+            </Form.Item>
+            <Form.Item label="上传配图" {...formItemLayout}>
+              {getFieldDecorator('fileList', {
+                getValueFromEvent: this.handleOnChangeEvent,
+              })(
+                <Uploader
+                  fileList={fileList}
+                  accept="image"
+                  multiple={false}
+                  totalLimit={1}
+                />
+              )}
+            </Form.Item>
+            <Form.Item label="音频路径" {...formItemLayout}>
+              {getFieldDecorator('audioPath', {
+                rules: [{ required: true, message: '音频路径不能为空!' }],
+                initialValue: audioPath,
+              })(
+                <Input />
+              )}
+            </Form.Item>
+            <Form.Item label="音频格式" {...formItemLayout}>
+              {getFieldDecorator('audioFormat', {
+                rules: [{ required: true, message: '音频格式不能为空!' }],
+                initialValue: audioFormat || 'mp4',
+              })(
+                <Input disabled />
+              )}
+            </Form.Item>
+            <Form.Item {...submitFormLayout} style={{ marginTop: 32 }}>
+              <Button onClick={this.handlePageBack}>取消</Button>
+              <Button
+                type="primary"
+                htmlType="submit"
+                loading={submitting}
+                style={{ marginLeft: 8 }}
+              >提交
+              </Button>
+            </Form.Item>
+          </Form>
+        </Card>
+      </PageHeaderLayout>
+    );
+  }
+}

+ 221 - 0
src/routes/Resource/AudioBook/AudioBookList.js

@@ -0,0 +1,221 @@
+/* eslint-disable no-prototype-builtins,jsx-a11y/media-has-caption */
+import React, { Component } from 'react';
+import moment from 'moment';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { Card, Button, message } from 'antd';
+import Ellipsis from '../../../components/Ellipsis';
+import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
+import { StandardTableList } from '../../../components/AXList';
+import { addRowKey, genAbsolutePicUrl } from '../../../utils/utils';
+import styles from './AudioBookList.less';
+
+const Message = message;
+
+function deleteBlankKey(obj) {
+  if (!(typeof obj === 'object')) {
+    return;
+  }
+  const newObj = { ...obj };
+  for (const key in newObj) {
+    if (newObj.hasOwnProperty(key) && !newObj[key]) {
+      delete newObj[key];
+    }
+  }
+  return newObj;
+}
+function genAudioBook(obj = {}) {
+  const { id, code, name, status } = obj;
+  let { img, audio } = obj;
+  img = img || {};
+  audio = audio || {};
+  return {
+    id,
+    code,
+    name,
+    status,
+    imgUrl: img.url,
+    imgPath: img.path,
+    imgFormat: img.format,
+    imgQuality: img.quality,
+    audioUrl: audio.url,
+    audioPath: audio.path,
+    audioFormat: audio.format,
+    audioQuality: audio.quality,
+  };
+}
+
+@connect(({ loading, resource }) => ({
+  resource,
+  loading: loading.models.resource,
+}))
+export default class AudioBookListPage extends Component {
+  constructor(props) {
+    super(props);
+    const { state } = props.location;
+    this.state = {
+      UIParams: (state || {}).UIParams, // 组件的状态参数
+      Queryers: (state || {}).Queryers, // 查询的条件参数
+    };
+  }
+  componentWillMount() {
+    this.props.dispatch({
+      type: 'resource/clearState',
+    });
+  }
+  componentDidMount() {
+    this.props.dispatch({
+      type: 'resource/fetchAudioBookList',
+      payload: { ...this.state.Queryers },
+    });
+  }
+  // 创建有声读物(增)
+  handleCreateOperation = () => {
+    const { UIParams, Queryers } = this.state;
+    this.props.dispatch(
+      routerRedux.push({
+        pathname: '/resource/audiobook-create',
+        state: { UIParams, Queryers },
+      })
+    );
+  };
+  // 修改有声读物(改)
+  handleUpdateOperation = (item) => {
+    const { UIParams, Queryers } = this.state;
+    this.props.dispatch(
+      routerRedux.push({
+        pathname: `/resource/audiobook-edit/${item.id}`,
+        state: { UIParams, Queryers, audioBookItem: genAudioBook(item) },
+      })
+    );
+  };
+  // 查询有声读物(查)
+  handleFilterOperation = (params, states) => {
+    const newParams = deleteBlankKey(params);
+    this.props.dispatch({
+      type: 'resource/fetchAudioBookList',
+      payload: newParams,
+    });
+    this.setState({
+      UIParams: states,
+      Queryers: newParams,
+    });
+  };
+  // TODO: 批量操作
+  handleBatchOperation = () => {
+    Message.info('暂不支持批量操作!');
+  };
+
+  render() {
+    const { loading, resource } = this.props;
+    const { list, totalSize, pageSize, pageNo } = resource;
+    const pagination = {
+      pageNo,
+      pageSize,
+      totalSize,
+    };
+    const batchActions = [{
+      key: 'delete',
+      name: '批量删除',
+    }, {
+      key: 'edit',
+      name: '批量编辑',
+    }];
+    const basicSearch = {
+      keys: [{
+        name: '有声读物名称',
+        field: 'name',
+      }, {
+        name: '有声读物编号',
+        field: 'code',
+      }],
+    };
+    const columns = [{
+      title: '有声读物编号',
+      key: 1,
+      dataIndex: 'code',
+      width: '18%',
+      render: text => (
+        <Ellipsis tooltip lines={1}>{text}</Ellipsis>
+      ),
+    }, {
+      title: '有声读物名称',
+      key: 2,
+      dataIndex: 'name',
+      width: '20%',
+      render: text => (
+        <Ellipsis tooltip lines={1}>{text}</Ellipsis>
+      ),
+    }, {
+      title: '有声读物配图',
+      key: 3,
+      dataIndex: 'img',
+      width: '20%',
+      render: (text) => {
+        const { path } = text || {};
+        return (
+          <div className={styles.picWrapper}>
+            <img src={genAbsolutePicUrl(path)} alt="thumb" />
+          </div>
+        );
+      },
+    }, {
+      title: '有声读物音频',
+      key: 4,
+      dataIndex: 'audio',
+      width: '20%',
+      render: (text) => {
+        const { url } = text || {};
+        return (
+          <div className={styles.audioWrapper}>
+            <audio controls src={url}>浏览器暂不支持播放</audio>
+          </div>
+        );
+      },
+    }, {
+      title: '修改时间',
+      key: 5,
+      dataIndex: 'gmtModified',
+      render: text => moment(text).format('YYYY-MM-DD HH:mm:ss'),
+      width: '17%',
+    }, {
+      title: '操作',
+      key: 6,
+      dataIndex: 'operation',
+      render: (_, record) => (
+        <Button
+          size="small"
+          type="primary"
+          className="editBtn"
+          onClick={() => this.handleUpdateOperation(record)}
+        >编辑
+        </Button>
+      ),
+      width: '5%',
+      align: 'right',
+    }];
+    return (
+      <PageHeaderLayout>
+        <Card bordered={false}>
+          <StandardTableList
+            columns={columns}
+            loading={loading}
+            dataSource={addRowKey(list)}
+            header={{
+              basicSearch,
+              onFilterClick: this.handleFilterOperation,
+              onCreateClick: this.handleCreateOperation,
+            }}
+            footer={{
+              pagination,
+              batchActions,
+              onBatchClick: this.handleBatchOperation,
+            }}
+            keepUIState={{ ...this.state.UIParams }}
+            showStatusSelect={false}
+          />
+        </Card>
+      </PageHeaderLayout>
+    );
+  }
+}

+ 25 - 0
src/routes/Resource/AudioBook/AudioBookList.less

@@ -0,0 +1,25 @@
+@import "../../../../node_modules/antd/lib/style/themes/default.less";
+
+.picWrapper {
+  position: relative;
+  vertical-align: middle;
+  text-align: center;
+  width: 90px;
+  height: 128px;
+  line-height: 128px;
+  img {
+    max-height: 100%;
+    max-width: 100%;
+    vertical-align: middle;
+    height: auto;
+  }
+}
+
+.audioWrapper {
+  width: 180px;
+  height: 35px;
+  audio {
+    width: 100%;
+    height: 100%;
+  }
+}

+ 2 - 2
src/routes/Resource/Video/VideoTableList.js

@@ -4,7 +4,7 @@ import { Modal, Button } from 'antd';
 import { StandardTableList } from '../../../components/AXList';
 import AXVideoPlayer from '../../../components/AXVideoPlayer';
 import Ellipsis from '../../../components/Ellipsis';
-import { renderVideoQuality } from '../../../utils/utils';
+import { resourceQuality } from '../../../utils/utils';
 
 function VideoTableList({
   UIParams, dataSource, loading, totalSize, pageSize, pageNo, modalDestroy, currentItem,
@@ -57,7 +57,7 @@ function VideoTableList({
     title: '质量',
     key: 4,
     dataIndex: 'quality',
-    render: text => renderVideoQuality(text),
+    render: text => resourceQuality[text],
     width: '10%',
   }, {
     title: '修改时间',

+ 2 - 10
src/routes/Shelves/ShelvesCreate.js

@@ -242,20 +242,12 @@ export default class ShelvesCreatePage extends Component {
     // render card title
     const renderProductCardName = () => {
       return (
-        <div className={styles.cardName}>
-          <span>
-            <a onClick={this.handleProductSelectorModalShow}>{`选择${productNameMap[scene]}`}</a>
-          </span>
-        </div>
+        <Button type="primary" onClick={this.handleProductSelectorModalShow}>{`选择${productNameMap[scene]}`}</Button>
       );
     };
     const renderMerchantCardName = () => {
       return (
-        <div className={styles.cardName}>
-          <span>
-            <a onClick={this.handleMerchantSelectorModalShow}>选择渠道</a>
-          </span>
-        </div>
+        <Button type="primary" onClick={this.handleMerchantSelectorModalShow}>选择渠道</Button>
       );
     };
 

+ 1 - 1
src/routes/Shelves/ShelvesEdit.js

@@ -182,7 +182,7 @@ export default class ShelvesEdit extends Component {
       <div>
         <Card title="价格管理" style={{ marginBottom: 16 }}>
           {getFieldDecorator('goods', {
-            initialValue: addRowKey(goods),
+            initialValue: (!goods || !goods.length) ? null : addRowKey(goods),
           })(
             <TableForm
               loading={submitting}

+ 5 - 0
src/routes/Shelves/ShelvesList.js

@@ -34,6 +34,11 @@ export default class ShelvesListPage extends Component {
       Queryers: (state || {}).Queryers, // 查询的条件参数
     };
   }
+  componentWillMount() {
+    this.props.dispatch({
+      type: 'shelves/cleanListState',
+    });
+  }
   componentDidMount() {
     const { scene } = this.state;
     this.props.dispatch({

+ 2 - 2
src/routes/Shelves/TableForm.js

@@ -19,13 +19,13 @@ export default class TableForm extends PureComponent {
     super(props);
 
     this.state = {
-      data: props.value,
+      data: null,
       loading: props.loading,
       currentKey: null,
     };
   }
   componentWillReceiveProps(nextProps) {
-    if ('value' in nextProps && (!this.props.value || !this.props.value.length) ) {
+    if ('value' in nextProps && !this.state.data) {
       this.setState({
         data: nextProps.value,
       });

+ 1 - 1
src/routes/System/CmsUser/CmsUserCreate.js

@@ -209,7 +209,7 @@ export default class CmsUserCreatePage extends PureComponent {
           <Form>
             <Form.Item
               {...formItemLayout}
-              label={<a onClick={this.handleMerchantSelectorModalShow}>选择厂商</a>}
+              label={<Button size="small" type="primary" onClick={this.handleMerchantSelectorModalShow}>选择厂商</Button>}
             >
               <List
                 bordered

+ 1 - 1
src/routes/Terminal/User/TerminalCreate.js

@@ -154,7 +154,7 @@ export default class TerminalCreatePage extends PureComponent {
           <Form>
             <Form.Item
               {...formItemLayout}
-              label={<a onClick={this.handleCampusSelectorModalShow}>选择校区</a>}
+              label={<Button size="small" type="primary" onClick={this.handleCampusSelectorModalShow}>选择校区</Button>}
             >
               <List
                 bordered

+ 1 - 1
src/routes/Terminal/User/TerminalList.js

@@ -206,7 +206,7 @@ export default class TerminalListPage extends Component {
             ) : (
               <Button
                 size="small"
-                className="delBtn"
+                className="recBtn"
                 onClick={() => this.handleRecoverOperation(item)}
               >解禁
               </Button>

+ 2 - 2
src/routes/Trade/Order/OrderCreate.js

@@ -276,7 +276,7 @@ export default class OrderCreatePage extends Component {
           <Form>
             <Form.Item
               {...formItemLayout}
-              label={<a onClick={this.handleTerminalSelectorModalShow}>选择终端</a>}
+              label={<Button size="small" type="primary" onClick={this.handleTerminalSelectorModalShow}>选择终端</Button>}
             >
               <List
                 bordered
@@ -581,7 +581,7 @@ export default class OrderCreatePage extends Component {
       }];
       return (
         <Card
-          title={<a onClick={this.handleGoodsSelectorModalShow}>选择商品</a>}
+          title={<Button type="primary" onClick={this.handleGoodsSelectorModalShow}>选择商品</Button>}
           style={{ marginTop: 10, marginBottom: 70 }}
         >
           <Table

+ 25 - 0
src/services/resource.js

@@ -21,6 +21,15 @@ export async function queryVideoResource(params) {
   return request(`${api.resource}?${stringify(newParams)}`);
 }
 
+export async function queryAudioBookResource(params) {
+  const newParams = {
+    pageSize: Hotax.PAGE_SIZE,
+    ...params,
+    type: Hotax.RESOURCE_AUDIOBOOK,
+  };
+  return request(`${api.resource}?${stringify(newParams)}`);
+}
+
 export async function queryOssSignature(params) {
   const signature = getSignature();
   const expireTime = Math.floor(((new Date()).getTime() / 1000) + 5).toString();
@@ -48,3 +57,19 @@ export async function updateResource(params) {
   };
   return request(`${api.resourceItem}`, options);
 }
+
+export async function createAudioBook(params) {
+  const options = {
+    method: 'POST',
+    body: params,
+  };
+  return request(`${api.audiobookItem}`, options);
+}
+
+export async function updateAudioBook(params) {
+  const options = {
+    method: 'PUT',
+    body: params,
+  };
+  return request(`${api.audiobookItem}`, options);
+}

+ 3 - 1
src/utils/config.js

@@ -7,6 +7,7 @@ Hotax.RESOURCE_VIDEO = 0;
 Hotax.RESOURCE_AUDIO = 1;
 Hotax.RESOURCE_LIVE = 2;
 Hotax.RESOURCE_IMAGE = 3;
+Hotax.RESOURCE_AUDIOBOOK = 4;
 
 // 清晰度类型 <流畅|标清|高清|超清>
 Hotax.QUALITY_FLUENT = 'fluent';
@@ -78,7 +79,7 @@ Hotax.API_HOST_LOC = 'http://192.168.1.40:8500';
 // 接口地址(测试)
 Hotax.API_HOST_DEV = 'http://tt-cms.api.ai160.com';
 // 接口地址(线上)
-Hotax.API_HOST_PRO = 'http://cms.lingjiao.cn/api/v1';
+Hotax.API_HOST_PRO = '/api';
 // oss存储地址
 Hotax.OSS_HOST = 'http://efunimgs.oss-cn-beijing.aliyuncs.com';
 /********************* 常量定义结束 **********************/
@@ -89,6 +90,7 @@ const apiObj = {
   userLogout: '/logout',
   resource: '/resource/list',
   resourceItem: '/resource',
+  audiobookItem: '/resource/audioImg',
   signature: '/oss/signature',
   merchant: '/merchant/list',
   merchantItem: '/merchant',

+ 2 - 0
src/utils/utils.js

@@ -447,6 +447,8 @@ export function getResourceTypeName(type) {
       return '音频';
     case Hotax.RESOURCE_IMAGE:
       return '图片';
+    case Hotax.RESOURCE_AUDIOBOOK:
+      return '有声读物';
     default:
       return '';
   }