Преглед изворни кода

:sparkles: add feature `create video`;
:sparkles: add feature `update video`;
:sparkles: add feature `delete video`;

zhanghe пре 6 година
родитељ
комит
9ca3bcf031

+ 6 - 1
package.json

@@ -6,8 +6,13 @@
   "scripts": {
     "precommit": "npm run lint-staged",
     "start": "cross-env DISABLE_ESLINT=true roadhog dev",
-    "start:no-proxy": "cross-env NO_PROXY=true DISABLE_ESLINT=true roadhog dev",
+    "start:platform": "cross-env ROLE=PLATFORM API=DEV NO_PROXY=true DISABLE_ESLINT=true roadhog dev",
+    "start:channel": "cross-env ROLE=CHANNEL API=DEV NO_PROXY=true DISABLE_ESLINT=true roadhog dev",
+    "start:cp": "cross-env ROLE=CP API=DEV NO_PROXY=true DISABLE_ESLINT=true roadhog dev",
     "build": "cross-env DISABLE_ESLINT=true roadhog build",
+    "build:platform": "cross-env ROLE=PLATFORM API=PRO roadhog build",
+    "build:channel": "cross-env ROLE=CHANNEL API=PRO roadhog build",
+    "build:cp": "cross-env ROLE=CP API=PRO roadhog build",
     "site": "roadhog-api-doc static && gh-pages -d dist",
     "analyze": "cross-env ANALYZE=true roadhog build",
     "lint:style": "stylelint \"src/**/*.less\" --syntax less",

+ 8 - 0
src/common/router.js

@@ -125,6 +125,14 @@ export const getRouterData = (app) => {
     '/resource/video': {
       component: dynamicWrapper(app, ['resource'], () => import('../routes/Resource/Video/VideoList')),
     },
+    '/resource/video-create': {
+      component: dynamicWrapper(app, ['resource'], () => import('../routes/Resource/Video/VideoCreate')),
+      name: '创建视频',
+    },
+    '/resource/video-edit/:id': {
+      component: dynamicWrapper(app, ['resource'], () => import('../routes/Resource/Video/VideoCreate')),
+      name: '编辑视频',
+    },
     // 系统管理相关路由注册
     '/system/cms-user': {
       component: dynamicWrapper(app, ['cmsUser'], () => import('../routes/System/CmsUser')),

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

@@ -81,7 +81,6 @@ class Uploader extends Component {
     };
   }
   handleBeforeUpload = (file) => {
-    console.log('call beforeUpload func:', new Date().getTime())
     const { fileCode } = this.props;
     if (!checkFileSize(file.name, file.size)) {
       return false;
@@ -98,7 +97,6 @@ class Uploader extends Component {
       });
   }
   handleOnChange = ({ fileList }) => {
-    console.log('call onChange func:', new Date().getTime())
     const { ossSign, curFileName } = this.state;
     const { onUpload, totalLimit } = this.props;
     const { dir, host } = ossSign;
@@ -188,7 +186,7 @@ class Uploader extends Component {
             <Icon type="inbox" />
           </p>
           <p className={styles.dragText}>点击或者拖拽图片到此区域进行上传</p>
-          <p className={styles.dragHint}>{`支持jpg/png图片格式,大小需小于${FILE_MAX_SIZE}M`}</p>
+          <p className={styles.dragHint}>{`支持jpg/png图片格式,大小需小于${Hotax.FILE_MAX_SIZE}M`}</p>
         </Upload.Dragger>
         <Modal
           visible={previewVisible}

+ 9 - 3
src/index.less

@@ -57,6 +57,10 @@ body {
     height: 25px;
     line-height: 25px;
   }
+  .ant-radio-button-wrapper-checked {
+    background-color: @primary-color !important;
+    color: #fff !important;
+  }
 }
 
 // 自定义全局样式
@@ -64,16 +68,18 @@ body {
   margin-right: 10px;
   background: @primary-color !important;
   color: #fff !important;
-  font-weight: 500 !important;
 }
 :global(.delBtn) {
   background: #f5222d !important;
   color: #fff !important;
-  font-weight: 500 !important;
 }
 :global(.depositBtn) {
   margin-right: 10px;
   background: #a0d911 !important;
   color: #fff !important;
-  font-weight: 500 !important;
+}
+:global(.playBtn) {
+  margin-right: 10px;
+  background: #36cfc9 !important;
+  color: #fff !important;
 }

+ 10 - 2
src/models/resource.js

@@ -58,13 +58,13 @@ export default {
         );
       }
     },
-    *createVideo({ payload }, { call, put }) {
+    *createVideo({ payload, states }, { call, put }) {
       const response = yield call(createResource, payload);
       if (response && response.success) {
         yield put(
           routerRedux.push({
             pathname: '/resource/video',
-            state: response.data,
+            state: states,
           })
         );
       }
@@ -118,5 +118,13 @@ export default {
         ...action.payload,
       };
     },
+    clearState(state) {
+      return {
+        ...state,
+        list: [],
+        pageSize: 15,
+        pageNo: 1,
+      };
+    },
   },
 };

+ 4 - 4
src/routes/Product/Course/CourseCreate.js

@@ -425,14 +425,14 @@ export default class CourseItemCreatePage extends Component {
               )}
             </Form.Item>
             {this.isEdit() && (
-            <Form.Item label={fieldLabels.name} {...formItemLayout}>
-              {getFieldDecorator('name', {
+              <Form.Item label={fieldLabels.name} {...formItemLayout}>
+                {getFieldDecorator('name', {
                   initialValue: name,
                 })(
                   <Input disabled />
                 )}
-            </Form.Item>
-)}
+              </Form.Item>
+            )}
             <Form.Item hasFeedback label={fieldLabels.title} {...formItemLayout}>
               {getFieldDecorator('title', {
                 rules: [{ required: true, message: '请填写课程标题' }],

+ 5 - 0
src/routes/Resource/Picture/PictureList.js

@@ -22,6 +22,11 @@ export default class PictureListPage extends Component {
       isCard: false,
     };
   }
+  componentWillMount() {
+    this.props.dispatch({
+      type: 'resource/clearState',
+    });
+  }
   componentDidMount() {
     this.props.dispatch({
       type: 'resource/fetchImageList',

+ 86 - 31
src/routes/Resource/Video/VideoEdit.js

@@ -1,9 +1,13 @@
 import React, { PureComponent } from 'react';
-import { Card, Form, Select, Input, Button, Switch } from 'antd';
+import pathToRegexp from 'path-to-regexp';
+import { Card, Form, Radio, Input, Button, Switch } from 'antd';
 import { connect } from 'dva';
 import { routerRedux } from 'dva/router';
 import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
-import { boolToStatus, statusToBool } from '../../../utils/utils';
+import {
+  resourceTypes, resourceQuality, boolToStatus, statusToBool,
+} from '../../../utils/utils';
+import { Hotax } from '../../../utils/config';
 
 const formItemLayout = {
   labelCol: {
@@ -27,7 +31,15 @@ const submitFormLayout = {
 @connect(({ loading }) => ({
   submitting: loading.models.resource,
 }))
-export default class VideoEditPage extends PureComponent {
+export default class VideoCreatePage extends PureComponent {
+  isEdit = () => {
+    const { location } = this.props;
+    const match = pathToRegexp('/resource/video-edit/:id').exec(location.pathname);
+    if (match) {
+      return match[1];
+    }
+    return false;
+  }
   handlePageBack = () => {
     const { UIParams, Queryers } = this.props.location.state || {};
     this.props.dispatch(
@@ -41,17 +53,20 @@ export default class VideoEditPage extends PureComponent {
     e.preventDefault();
     this.props.form.validateFieldsAndScroll((err, values) => {
       if (!err) {
-        const { id, type, UIParams, Queryers } = this.props.location.state || {};
-        const { fileList, status, ...params } = values;
-        if (Array.isArray(fileList) && fileList.length) {
-          params.url = fileList[0].url;
+        const { UIParams, Queryers } = this.props.location.state || {};
+        const { status, ...params } = values;
+        const matchId = this.isEdit();
+        if (matchId) {
+          this.props.dispatch({
+            type: 'resource/updateVideo',
+            payload: { id: matchId, status: boolToStatus(status), ...params },
+            states: { UIParams, Queryers },
+          });
+          return;
         }
-        params.id = id;
-        params.type = type;
-        params.status = boolToStatus(status);
         this.props.dispatch({
-          type: 'resource/updateVideo',
-          payload: params,
+          type: 'resource/createVideo',
+          payload: { status: boolToStatus(status), ...params },
           states: { UIParams, Queryers },
         });
       }
@@ -61,7 +76,7 @@ export default class VideoEditPage extends PureComponent {
     const { form, submitting, location } = this.props;
     const { getFieldDecorator } = form;
     const { state = {} } = location;
-    const { code, name, size, type, path, rate, quality, status } = state;
+    const { code, name, size, type, path, rate, quality, status, format } = state;
 
     return (
       <PageHeaderLayout>
@@ -69,10 +84,16 @@ export default class VideoEditPage extends PureComponent {
           <Form onSubmit={this.handlePageSubmit}>
             <Form.Item label="视频编号" {...formItemLayout}>
               {getFieldDecorator('code', {
-                rules: [{ required: true, message: '视频编号不能为空' }],
+                rules: [
+                  {
+                    required: true, message: '视频编号不能为空',
+                  }, {
+                    pattern: /^[a-zA-Z0-9|-]+$/g, message: '编号包含非法字符',
+                  },
+                ],
                 initialValue: code,
               })(
-                <Input />
+                <Input placeholder="请填写" />
               )}
             </Form.Item>
             <Form.Item label="视频名称" {...formItemLayout}>
@@ -80,46 +101,80 @@ export default class VideoEditPage extends PureComponent {
                 rules: [{ required: true, message: '视频名称不能为空' }],
                 initialValue: name,
               })(
-                <Input />
+                <Input placeholder="请填写" />
+              )}
+            </Form.Item>
+            <Form.Item label="视频路径" {...formItemLayout}>
+              {getFieldDecorator('path', {
+                rules: [{ required: true, message: '资源路径为必填项' }],
+                initialValue: path,
+              })(
+                <Input placeholder="请填写" />
               )}
             </Form.Item>
             <Form.Item label="资源类型" {...formItemLayout}>
               {getFieldDecorator('type', {
                 rules: [{ required: true, message: '资源类型不能为空' }],
-                initialValue: type,
+                initialValue: type || Hotax.RESOURCE_VIDEO,
               })(
-                <Input />
+                <Radio.Group>
+                  {
+                    Object.keys(resourceTypes).map(key =>
+                      (
+                        <Radio.Button
+                          key={key}
+                          value={parseInt(key, 10)}
+                        >{resourceTypes[key]}
+                        </Radio.Button>
+                      )
+                    )
+                  }
+                </Radio.Group>
               )}
             </Form.Item>
-            <Form.Item label="资源路径" {...formItemLayout}>
-              {getFieldDecorator('path', {
-                initialValue: path,
+            <Form.Item label="清晰度" {...formItemLayout}>
+              {getFieldDecorator('quality', {
+                rules: [{ required: true, message: '清晰度为必选项' }],
+                initialValue: quality || Hotax.QUALITY_HIGH,
               })(
-                <Input />
+                <Radio.Group>
+                  {
+                    Object.keys(resourceQuality).map(key =>
+                      (
+                        <Radio.Button
+                          key={key}
+                          value={key}
+                        >{resourceQuality[key]}
+                        </Radio.Button>
+                      )
+                    )
+                  }
+                </Radio.Group>
               )}
             </Form.Item>
-            <Form.Item label="清晰度" {...formItemLayout}>
-              {getFieldDecorator('quality', {
-                initialValue: quality,
+            <Form.Item label="视频格式" {...formItemLayout}>
+              {getFieldDecorator('format', {
+                rules: [{ required: true, message: '视频格式不能为空' }],
+                initialValue: format || 'm3u8',
               })(
-                <Input />
+                <Input placeholder="请填写" />
               )}
             </Form.Item>
-            <Form.Item label="资源码流" {...formItemLayout}>
+            <Form.Item label="视频码流" {...formItemLayout}>
               {getFieldDecorator('rate', {
                 initialValue: rate,
               })(
-                <Input />
+                <Input placeholder="请填写" />
               )}
             </Form.Item>
-            <Form.Item label="资源大小" {...formItemLayout}>
+            <Form.Item label="视频大小" {...formItemLayout}>
               {getFieldDecorator('size', {
                 initialValue: size,
               })(
-                <Input />
+                <Input placeholder="请填写" addonAfter="字节" />
               )}
             </Form.Item>
-            <Form.Item label="资源状态" {...formItemLayout}>
+            <Form.Item label="视频状态" {...formItemLayout}>
               {getFieldDecorator('status', {
                 valuePropName: 'checked',
                 initialValue: statusToBool(status),

+ 47 - 6
src/routes/Resource/Video/VideoList.js

@@ -1,7 +1,8 @@
 /* eslint-disable no-prototype-builtins */
 import React, { Component } from 'react';
 import { connect } from 'dva';
-import { Card, Button, message } from 'antd';
+import { routerRedux } from 'dva/router';
+import { Card, Modal, Button, message } from 'antd';
 import VideoPlayList from './VideoPlayList';
 import VideoTableList from './VideoTableList';
 import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
@@ -29,16 +30,24 @@ function deleteBlankKey(obj) {
 export default class VideoListPage extends Component {
   constructor(props) {
     super(props);
+    const { state } = props.location;
     this.state = {
       isCard: false,
       destroy: true,
       current: {},
+      UIParams: (state || {}).UIParams, // 组件的状态参数
+      Queryers: (state || {}).Queryers, // 查询的条件参数
     };
   }
+  componentWillMount() {
+    this.props.dispatch({
+      type: 'resource/clearState',
+    });
+  }
   componentDidMount() {
     this.props.dispatch({
       type: 'resource/fetchVideoList',
-      payload: {},
+      payload: { ...this.state.Queryers },
     });
   }
   handleShowTypeChange = () => {
@@ -52,20 +61,49 @@ export default class VideoListPage extends Component {
   };
   // 创建视频(增)
   handleCreateOperation = () => {
+    const { UIParams, Queryers } = this.state;
+    this.props.dispatch(
+      routerRedux.push({
+        pathname: '/resource/video-create',
+        state: { UIParams, Queryers },
+      })
+    );
   };
   // 删除视频(删)
-  handleDeleteOperation = () => {
+  handleDeleteOperation = (item) => {
+    Modal.confirm({
+      okText: '确定',
+      cancelText: '取消',
+      title: '您确定要删除该视频吗?',
+      onOk: () => {
+        this.props.dispatch({
+          type: 'resource/deleteVideo',
+          payload: { id: item.id },
+        });
+      },
+    });
   };
   // 修改视频(改)
-  handleUpdateOperation = () => {
+  handleUpdateOperation = (item) => {
+    const { UIParams, Queryers } = this.state;
+    this.props.dispatch(
+      routerRedux.push({
+        pathname: `/resource/video-edit/${item.id}`,
+        state: { UIParams, Queryers, ...item },
+      })
+    );
   };
   // 查询视频(查)
-  handleFilterOperation = (params) => {
+  handleFilterOperation = (params, states) => {
     const newParams = deleteBlankKey(params);
     this.props.dispatch({
       type: 'resource/fetchVideoList',
       payload: newParams,
     });
+    this.setState({
+      UIParams: states,
+      Queryers: newParams,
+    });
   };
   // TODO: 批量操作
   handleBatchOperation = () => {
@@ -73,6 +111,7 @@ export default class VideoListPage extends Component {
   };
 
   render() {
+    const { UIParams } = this.state;
     const { loading, resource } = this.props;
     const { list, totalSize, pageSize, pageNo } = resource;
     const publicProps = {
@@ -80,6 +119,7 @@ export default class VideoListPage extends Component {
       pageNo,
       pageSize,
       totalSize,
+      UIParams,
       dataSource: addRowKey(list),
       onCreateClick: this.handleCreateOperation,
       onDeleteClick: this.handleDeleteOperation,
@@ -104,7 +144,8 @@ export default class VideoListPage extends Component {
             />
           </Button.Group>
           {this.state.isCard ?
-            <VideoPlayList {...publicProps} /> : (
+            (<VideoPlayList {...publicProps} />)
+            : (
               <VideoTableList
                 {...publicProps}
                 currentItem={this.state.current}

+ 45 - 15
src/routes/Resource/Video/VideoTableList.js

@@ -1,15 +1,15 @@
 import React from 'react';
 import moment from 'moment';
-import { Modal } from 'antd';
+import { Modal, Button } from 'antd';
 import { StandardTableList } from '../../../components/AXList';
 import AXVideoPlayer from '../../../components/AXVideoPlayer';
 import Ellipsis from '../../../components/Ellipsis';
 import { renderStatus, renderVideoQuality } from '../../../utils/utils';
 
 function VideoTableList({
-  dataSource, loading, totalSize, pageSize, pageNo, modalDestroy, currentItem,
-  onCreateClick, onDeleteClick, onUpdateClick, onFilterClick, onBatchClick,
-  onModalCreate, onModalDestroy,
+  UIParams, dataSource, loading, totalSize, pageSize, pageNo, modalDestroy, currentItem,
+  onCreateClick, onDeleteClick, onUpdateClick, onFilterClick, onBatchClick, onModalCreate,
+  onModalDestroy,
 }) {
   const pagination = {
     pageNo,
@@ -36,12 +36,15 @@ function VideoTableList({
     title: '视频编号',
     key: 1,
     dataIndex: 'code',
-    width: '17%',
+    width: '15%',
+    render: text => (
+      <Ellipsis tooltip lines={1}>{text}</Ellipsis>
+    ),
   }, {
     title: '视频名称',
     key: 2,
     dataIndex: 'name',
-    width: '30%',
+    width: '23%',
     render: text => (
       <Ellipsis tooltip lines={1}>{text}</Ellipsis>
     ),
@@ -51,37 +54,64 @@ function VideoTableList({
     dataIndex: 'format',
     width: '10%',
   }, {
-    title: '视频质量',
+    title: '质量',
     key: 4,
     dataIndex: 'quality',
     render: text => renderVideoQuality(text),
-    width: '10%',
+    width: '8%',
   }, {
-    title: '视频状态',
+    title: '状态',
     key: 5,
     dataIndex: 'status',
     render: text => renderStatus(text),
-    width: '10%',
+    width: '8%',
   }, {
-    title: '创建时间',
+    title: '修改时间',
     key: 6,
     dataIndex: 'gmtModified',
-    render: text => moment(text).format('YYYY-MM-DD'),
-    width: '15%',
+    render: text => moment(text).format('YYYY-MM-DD HH:mm:ss'),
+    width: '17%',
   }, {
     title: '操作',
     key: 8,
     dataIndex: 'operation',
-    render: (_, record) => <a onClick={() => onModalCreate(record)}>播放</a>,
-    width: '8%',
+    render: (_, record) => renderActions(record),
+    width: '19%',
     align: 'right',
   }];
+  const renderActions = (item) => {
+    return (
+      <div>
+        <Button
+          size="small"
+          type="primary"
+          className="playBtn"
+          onClick={() => onModalCreate(item)}
+        >播放
+        </Button>
+        <Button
+          size="small"
+          type="primary"
+          className="editBtn"
+          onClick={() => onUpdateClick(item)}
+        >编辑
+        </Button>
+        <Button
+          size="small"
+          className="delBtn"
+          onClick={() => onDeleteClick(item)}
+        >删除
+        </Button>
+      </div>
+    );
+  };
   return (
     <div>
       <StandardTableList
         columns={columns}
         loading={loading}
         dataSource={dataSource}
+        keepUIState={{ ...UIParams }}
         header={{ basicSearch, onFilterClick, onCreateClick }}
         footer={{ pagination, batchActions, onBatchClick }}
       />

+ 13 - 0
src/utils/utils.js

@@ -427,3 +427,16 @@ export function renderGoodsType(status) {
       return '';
   }
 }
+
+// 视频相关常量
+const resourceTypes = {
+  [Hotax.RESOURCE_AUDIO]: '音频',
+  [Hotax.RESOURCE_VIDEO]: '视频',
+};
+const resourceQuality = {
+  [Hotax.QUALITY_FLUENT]: '流畅',
+  [Hotax.QUALITY_STANDARD]: '标清',
+  [Hotax.QUALITY_HIGH]: '高清',
+  [Hotax.QUALITY_SUPERCLEAR]: '超清',
+};
+export { resourceTypes, resourceQuality };