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

:sparkles: 增加用户权限编辑功能
:sparkles: 产品出售编辑显示产品详情
:sparkles: 用户标签交互优化

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

+ 1 - 1
src/common/router.js

@@ -272,7 +272,7 @@ export const getRouterData = (app) => {
       component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/ShelvesCreate')),
     },
     '/shelves/course/edit': {
-      component: dynamicWrapper(app, ['shelves', 'resource'], () => import('../routes/Shelves/ShelvesEdit')),
+      component: dynamicWrapper(app, ['shelves', 'product', 'resource'], () => import('../routes/Shelves/ShelvesEdit')),
     },
     '/shelves/support': {
       component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves')),

+ 95 - 0
src/components/AXItem/CourseItem.js

@@ -0,0 +1,95 @@
+import React, { Component } from 'react';
+import { Row, Col, Card, Table, Radio } from 'antd';
+import { genAbsolutePicUrl } from '../../utils/utils';
+import styles from './CourseItem.less';
+
+function CourseTpl({ title, subTitle, coverUrl }) {
+  return (
+    <div className={styles.content}>
+      <div className={styles.courseWrapper}>
+        <img src={genAbsolutePicUrl(coverUrl)} alt="" />
+        <div className={styles.desc}>
+          <div className={styles.title}>{title}</div>
+          <div className={styles.subTitle}>{subTitle}</div>
+        </div>
+      </div>
+    </div>
+  );
+}
+
+class CourseItem extends Component {
+  state = {
+    openKey: 'lesson',
+  };
+  handleRadioChange = (e) => {
+    this.setState({ openKey: e.target.value });
+  };
+  render() {
+    const { subItemList, supportList } = this.props;
+    const lessonColumns = [{
+      title: '课编号',
+      dataIndex: 'code',
+      key: 1,
+      width: '50%',
+    }, {
+      title: '课名称',
+      dataIndex: 'title',
+      key: 2,
+      width: '50%',
+    }];
+    const supportColumns = [{
+      title: '配套编号',
+      dataIndex: 'code',
+      key: 1,
+      width: '50%',
+    }, {
+      title: '配套名称',
+      dataIndex: 'name',
+      key: 2,
+      width: '50%',
+    }];
+    return (
+      <Row gutter={16} style={{ marginBottom: 16 }}>
+        <Col span={9}>
+          <Card title="课程信息" style={{ height: 540 }} className={styles.coverCard}>
+            <CourseTpl {...this.props} />
+          </Card>
+        </Col>
+        <Col span={15}>
+          <Card
+            title={
+              <Radio.Group
+                value={this.state.openKey}
+                onChange={this.handleRadioChange}
+              >
+                <Radio.Button value="lesson">课列表</Radio.Button>
+                <Radio.Button value="support">周边配套</Radio.Button>
+              </Radio.Group>
+            }
+            style={{ height: 540 }}
+          >{this.state.openKey === 'lesson' ? (
+            <Table
+              pagination={false}
+              rowKey={record => record.id}
+              dataSource={subItemList}
+              columns={lessonColumns}
+              scroll={{ y: 400 }}
+              className={styles.dataTable}
+            />
+          ) : (
+            <Table
+              pagination={false}
+              rowKey={record => record.id}
+              dataSource={supportList}
+              columns={supportColumns}
+              scroll={{ y: 400 }}
+              className={styles.dataTable}
+            />
+          )}
+          </Card>
+        </Col>
+      </Row>
+    );
+  }
+}
+export default CourseItem;

+ 78 - 0
src/components/AXItem/CourseItem.less

@@ -0,0 +1,78 @@
+@import "~antd/lib/style/themes/default.less";
+
+.coverCard {
+  background: #d9d9d9;
+  :global(.ant-card) {
+    height: 500px;
+  }
+  :global(.ant-card-body) {
+    position: relative;
+    text-align: center;
+    height: 480px;
+    overflow: hidden;
+    .content {
+      width: 320px;
+      height: 458px;
+      position: absolute;
+      top: 0;
+      bottom: 0;
+      left: 0;
+      right: 0;
+      margin: auto;
+    }
+  }
+}
+.dataTable {
+  :global {
+    .ant-table-title {
+      padding: 0 0 16px 0;
+    }
+    .ant-table-footer {
+      padding: 10px;
+    }
+    .ant-table-tbody > tr > td {
+      padding: 5px;
+    }
+    .ant-table-thead > tr > th {
+      padding: 10px 5px;
+    }
+  }
+}
+
+.courseWrapper {
+  width: 320px;
+  height: 458px;
+  border: solid 5px #36cfc9;
+  border-radius: 29px;
+  position: relative;
+  img {
+    position: inherit;
+    width: 100%;
+    height: 100%;
+    border-radius: 25px;
+  }
+  .desc {
+    position: absolute;
+    bottom: 0;
+    width: 100%;
+    height: 112px;
+    background: #fff;
+    border-radius: 0 0 20px 20px;
+    .title {
+      width: 100%;
+      margin-left: 6px;
+      font-size: 36px;
+      white-space: nowrap;
+      overflow: hidden;
+      text-align: center;
+    }
+    .subTitle {
+      width: 100%;
+      margin-left: 6px;
+      font-size: 30px;
+      white-space: nowrap;
+      overflow: hidden;
+      text-align: center;
+    }
+  }
+}

+ 40 - 0
src/components/AXItem/PackageItem.js

@@ -0,0 +1,40 @@
+import React, { Component } from 'react';
+import { Card, Table } from 'antd';
+import { renderProductType } from '../../utils/utils';
+import styles from './CourseItem.less';
+
+class PackageItem extends Component {
+  render() {
+    const { products } = this.props;
+    const packageColumns = [{
+      title: '产品编号',
+      dataIndex: 'code',
+      key: 1,
+      width: '40%',
+    }, {
+      title: '产品名称',
+      dataIndex: 'name',
+      key: 2,
+      width: '40%',
+    }, {
+      title: '产品类型',
+      dataIndex: 'type',
+      key: 3,
+      width: '20%',
+      render: text => renderProductType(text),
+    }];
+    return (
+      <Card title="套餐包内容" style={{ height: 450, marginBottom: 16 }}>
+        <Table
+          pagination={false}
+          rowKey={record => record.id}
+          dataSource={products}
+          columns={packageColumns}
+          scroll={{ y: 300 }}
+          className={styles.dataTable}
+        />
+      </Card>
+    );
+  }
+}
+export default PackageItem;

+ 75 - 0
src/components/AXItem/SupportItem.js

@@ -0,0 +1,75 @@
+import React, { Component } from 'react';
+import { Row, Col, Card, Table, Radio } from 'antd';
+import { genAbsolutePicUrl } from '../../utils/utils';
+import styles from './SupportItem.less';
+
+function SupportTpl({ title, subTitle, coverUrl }) {
+  return (
+    <div className={styles.content}>
+      <div className={styles.courseWrapper}>
+        <img src={genAbsolutePicUrl(coverUrl)} alt="" />
+        <div className={styles.desc}>
+          <div className={styles.title}>{title}</div>
+          <div className={styles.subTitle}>{subTitle}</div>
+        </div>
+      </div>
+    </div>
+  );
+}
+
+class SupportItem extends Component {
+  state = {
+    openKey: 'support',
+  };
+  handleRadioChange = (e) => {
+    this.setState({ openKey: e.target.value });
+  };
+  render() {
+    const { supportList } = this.props;
+    const supportColumns = [{
+      title: '配套编号',
+      dataIndex: 'code',
+      key: 1,
+      width: '50%',
+    }, {
+      title: '配套名称',
+      dataIndex: 'name',
+      key: 2,
+      width: '50%',
+    }];
+    return (
+      <Row gutter={16} style={{ marginBottom: 16 }}>
+        <Col span={9}>
+          <Card title="配套信息" style={{ height: 540 }} className={styles.coverCard}>
+            <SupportTpl {...this.props} />
+          </Card>
+        </Col>
+        <Col span={15}>
+          <Card
+            title={
+              <Radio.Group
+                value={this.state.openKey}
+                onChange={this.handleRadioChange}
+              >
+                <Radio.Button value="support">相关配套</Radio.Button>
+              </Radio.Group>
+            }
+            style={{ height: 540 }}
+          >
+            {this.state.openKey === 'support' ? (
+              <Table
+                pagination={false}
+                rowKey={record => record.id}
+                dataSource={supportList}
+                columns={supportColumns}
+                scroll={{ y: 400 }}
+                className={styles.dataTable}
+              />
+            ) : null}
+          </Card>
+        </Col>
+      </Row>
+    );
+  }
+}
+export default SupportItem;

+ 78 - 0
src/components/AXItem/SupportItem.less

@@ -0,0 +1,78 @@
+@import "~antd/lib/style/themes/default.less";
+
+.coverCard {
+  background: #d9d9d9;
+  :global(.ant-card) {
+    height: 500px;
+  }
+  :global(.ant-card-body) {
+    position: relative;
+    text-align: center;
+    height: 480px;
+    overflow: hidden;
+    .content {
+      width: 320px;
+      height: 458px;
+      position: absolute;
+      top: 0;
+      bottom: 0;
+      left: 0;
+      right: 0;
+      margin: auto;
+    }
+  }
+}
+.dataTable {
+  :global {
+    .ant-table-title {
+      padding: 0 0 16px 0;
+    }
+    .ant-table-footer {
+      padding: 10px;
+    }
+    .ant-table-tbody > tr > td {
+      padding: 5px;
+    }
+    .ant-table-thead > tr > th {
+      padding: 10px 5px;
+    }
+  }
+}
+
+.courseWrapper {
+  width: 320px;
+  height: 458px;
+  border: solid 5px #36cfc9;
+  border-radius: 29px;
+  position: relative;
+  img {
+    position: inherit;
+    width: 100%;
+    height: 100%;
+    border-radius: 25px;
+  }
+  .desc {
+    position: absolute;
+    bottom: 0;
+    width: 100%;
+    height: 112px;
+    background: #fff;
+    border-radius: 0 0 20px 20px;
+    .title {
+      width: 100%;
+      margin-left: 6px;
+      font-size: 36px;
+      white-space: nowrap;
+      overflow: hidden;
+      text-align: center;
+    }
+    .subTitle {
+      width: 100%;
+      margin-left: 6px;
+      font-size: 30px;
+      white-space: nowrap;
+      overflow: hidden;
+      text-align: center;
+    }
+  }
+}

+ 33 - 0
src/components/AXItem/TrainingItem.js

@@ -0,0 +1,33 @@
+import React, { Component } from 'react';
+import { Card } from 'antd';
+import { genAbsolutePicUrl } from '../../utils/utils';
+import styles from './TrainingItem.less';
+
+function TrainingTpl({ title, dateDesc, coverUrl }) {
+  return (
+    <div className={styles.content}>
+      <div className={styles.courseWrapper}>
+        <img src={genAbsolutePicUrl(coverUrl)} alt="" />
+        <div className={styles.desc}>
+          <div className={styles.title}>{title}</div>
+          <div className={styles.date}>{dateDesc}</div>
+        </div>
+      </div>
+    </div>
+  );
+}
+
+class TrainingItem extends Component {
+  render() {
+    return (
+      <Card
+        title="师训信息"
+        style={{ height: 540, marginBottom: 16 }}
+        className={styles.coverCard}
+      >
+        <TrainingTpl {...this.props} />
+      </Card>
+    );
+  }
+}
+export default TrainingItem;

+ 80 - 0
src/components/AXItem/TrainingItem.less

@@ -0,0 +1,80 @@
+@import "~antd/lib/style/themes/default.less";
+
+.coverCard {
+  background: #d9d9d9;
+  :global(.ant-card) {
+    height: 500px;
+  }
+  :global(.ant-card-body) {
+    position: relative;
+    text-align: center;
+    height: 480px;
+    overflow: hidden;
+    .content {
+      width: 650px;
+      height: 400px;
+      position: absolute;
+      top: 0;
+      bottom: 0;
+      left: 0;
+      right: 0;
+      margin: auto;
+    }
+  }
+}
+.dataTable {
+  :global {
+    .ant-table-title {
+      padding: 0 0 16px 0;
+    }
+    .ant-table-footer {
+      padding: 10px;
+    }
+    .ant-table-tbody > tr > td {
+      padding: 5px;
+    }
+    .ant-table-thead > tr > th {
+      padding: 10px 5px;
+    }
+  }
+}
+
+.courseWrapper {
+  width: 655px;
+  height: 400px;
+  border: solid 5px #36cfc9;
+  border-radius: 30px;
+  position: relative;
+  img {
+    position: inherit;
+    width: 100%;
+    height: 100%;
+    border-radius: 25px;
+  }
+  .desc {
+    position: absolute;
+    bottom: 0;
+    width: 100%;
+    height: 96px;
+    background: #fff;
+    border-radius: 0 0 20px 20px;
+    .title {
+      width: 100%;
+      margin-top: 5px;
+      margin-left: 32px;
+      font-size: 28px;
+      white-space: nowrap;
+      overflow: hidden;
+      text-align: left;
+    }
+    .date {
+      width: 100%;
+      margin-top: 5px;
+      margin-left: 32px;
+      font-size: 28px;
+      white-space: nowrap;
+      overflow: hidden;
+      text-align: left;
+    }
+  }
+}

+ 5 - 1
src/components/AXItem/index.js

@@ -1,3 +1,7 @@
 import PictureItem from './PictureItem';
+import CourseItem from './CourseItem';
+import SupportItem from './SupportItem';
+import TrainingItem from './TrainingItem';
+import PackageItem from './PackageItem';
 
-export { PictureItem };
+export { PictureItem, CourseItem, SupportItem, TrainingItem, PackageItem };

+ 6 - 4
src/components/AXTableSelector/Selector.js

@@ -237,10 +237,12 @@ export default class Selector extends PureComponent {
             onClick={this.handleFinishOperation}
           >完成
           </Button>
-          <Button
-            style={{ marginLeft: 20, background: '#5cdbd3' }}
-          >{`已选择【${(selectedRows || []).length}】项`}
-          </Button>
+          {multiple && (
+            <Button
+              style={{ marginLeft: 20, background: '#5cdbd3' }}
+            >{`已选择【${(selectedRows || []).length}】项`}
+            </Button>
+          )}
         </div>
       </div>
     );

+ 1 - 1
src/components/GlobalHeader/index.less

@@ -31,7 +31,7 @@
     border-right: 1px solid @hover-bg;
     font-size: 20px;
     margin: 0;
-    padding: 0 21px 0 21px;
+    padding: 0 17.5px 0 17.5px;
     font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif;
     font-weight: 600;
     &:hover {

+ 11 - 0
src/models/terminal.js

@@ -12,6 +12,7 @@ import {
   updateSpecialTerminalItem,
   deleteSpecialTerminalItem,
   queryTerminalAuthList,
+  updateTerminalAuth,
   queryTerminalTagList,
   queryTerminalTagItem,
   createTerminalTagItem,
@@ -170,6 +171,16 @@ export default {
         });
       }
     },
+    *updateTerminalAuth({ payload, states }, { call, put }) {
+      const response = yield call(updateTerminalAuth, payload);
+      if (response.success) {
+        message.success('修改权限时长成功');
+        yield put({
+          type: 'fetchTerminalAuthList',
+          payload: states,
+        });
+      }
+    },
     *fetchTerminalTagList({ payload }, { call, put }) {
       const response = yield call(queryTerminalTagList, payload);
       if (response.success) {

+ 41 - 12
src/routes/Frontend/Personalize/PersonalizeEdit.js

@@ -8,6 +8,7 @@ import Selector from '../../../components/AXTableSelector/Selector';
 import AXDragSortTable from '../../../components/AXDragSortTable';
 import FooterToolbar from '../../../components/FooterToolbar';
 import { renderStatus, statusToBool, boolToStatus, renderProductType } from '../../../utils/utils';
+import { Hotax } from '../../../utils/config';
 import styles from './PersonalizeEdit.less';
 
 const formItemLayout = {
@@ -29,6 +30,7 @@ const formItemLayout = {
   shelves,
   terminal,
   tagType,
+  loading,
   sLoading: loading.models.shelves,
   tLoading: loading.models.tagType,
   mtLoading: loading.models.tag,
@@ -62,6 +64,11 @@ export default class PersonalizeEditPage extends Component {
       type: 'terminal/fetchTerminalRecommendCourse',
       payload: { uid: this.state.uid },
     });
+    // 加载全部
+    this.props.dispatch({
+      type: 'tagType/fetchTagTypeList',
+      payload: { pageSize: 1000 },
+    });
   }
 
   /**
@@ -238,7 +245,7 @@ export default class PersonalizeEditPage extends Component {
   handleUserTagSubmit = () => {
     this.props.form.validateFieldsAndScroll((err, values) => {
       if (!err) {
-        const { name, status } = values;
+        const { sort, name, status } = values;
         const { uid } = this.state;
         const { terminal } = this.props;
         const { currentUserTagItem } = terminal;
@@ -248,12 +255,12 @@ export default class PersonalizeEditPage extends Component {
         if (!id) {
           this.props.dispatch({
             type: 'terminal/createTerminalTagItem',
-            payload: { uid, name, typeCode, status: newStatus, productList: pidList },
+            payload: { sort, uid, name, typeCode, status: newStatus, productList: pidList },
           });
         } else {
           this.props.dispatch({
             type: 'terminal/updateTerminalTagItem',
-            payload: { id, uid, name, typeCode, status: newStatus, productList: pidList },
+            payload: { id, sort, uid, name, typeCode, status: newStatus, productList: pidList },
           });
         }
         this.setState({ tagModalDestroy: true });
@@ -295,32 +302,44 @@ export default class PersonalizeEditPage extends Component {
       recModalDestroy,
       merchantTagModalDestroy,
     } = this.state;
-    const { sLoading, tLoading, mtLoading, terminal, shelves, tagType, tag, form } = this.props;
+    const {
+      sLoading, tLoading, mtLoading, loading, terminal, shelves, tagType, tag, form,
+    } = this.props;
     const { getFieldDecorator } = form;
     const { userTagList, userRecCourse, currentUserTagItem } = terminal;
-    const { name, status, productList, typeCode, typeName } = currentUserTagItem;
+    const { sort, name, status, productList, typeCode, typeName } = currentUserTagItem;
 
     // 用户标签列表表头
     const tagColumns = [{
+      title: '标签位置',
+      dataIndex: 'sort',
+      width: '10%',
+    }, {
       title: '标签名称',
       dataIndex: 'name',
-      key: 1,
-      width: '30%',
+      width: '25%',
     }, {
       title: '标签类型',
       dataIndex: 'typeCode',
-      key: 2,
-      width: '30%',
+      width: '25%',
+      filters: (tagType.list || []).map(item => ({ text: item.name, value: item.code })),
+      onFilter: (value, record) => record.typeCode.indexOf(value) === 0,
     }, {
       title: '标签状态',
       dataIndex: 'status',
-      key: 3,
       width: '15%',
       render: text => renderStatus(text),
+      filters: [{
+        text: '正常',
+        value: Hotax.STATUS_NORMAL,
+      }, {
+        text: '删除',
+        value: Hotax.STATUS_DELETE,
+      }],
+      onFilter: (value, record) => record.status === value,
       align: 'center',
     }, {
       title: '操作',
-      key: 4,
       width: '25%',
       align: 'right',
       render: (_, record) => (
@@ -411,6 +430,7 @@ export default class PersonalizeEditPage extends Component {
             dataSource={tagTypeData}
             columns={tagTypeColumns}
             className={styles.tagTable}
+            onChange={this.handleUserTagTableChange}
           />
         </Card>
       );
@@ -476,6 +496,13 @@ export default class PersonalizeEditPage extends Component {
                 <Input placeholder="请输入" />
               )}
             </Form.Item>
+            <Form.Item hasFeedback label="标签位置" {...formItemLayout}>
+              {getFieldDecorator('sort', {
+                initialValue: sort,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
             <Form.Item label="标签类型" {...formItemLayout}>
               <Button
                 disabled={tagTypeSelecting}
@@ -620,11 +647,12 @@ export default class PersonalizeEditPage extends Component {
               <Button onClick={() => this.handleUserTagModalShow()} style={{ float: 'right' }} type="primary">新建标签</Button>
             </div>
           }
+          loading={loading.effects['terminal/fetchTerminalTagList']}
           style={{ marginBottom: 16 }}
           className={styles.tagCard}
         >
           <Table
-            pagination={false}
+            pagination={{ pageSize: 15 }}
             dataSource={userTagList}
             columns={tagColumns}
             rowKey={record => record.id}
@@ -640,6 +668,7 @@ export default class PersonalizeEditPage extends Component {
               <Button onClick={this.handleUserRecModalShow} style={{ float: 'right' }} type="primary">更换课程</Button>
             </div>
           }
+          loading={loading.effects['terminal/fetchTerminalRecommendCourse']}
           style={{ marginBottom: 70 }}
           className={styles.tagCard}
         >

+ 11 - 5
src/routes/Frontend/Recommend/RecommendPoster.js

@@ -8,6 +8,7 @@ import FooterToolbar from '../../../components/FooterToolbar';
 import {
   boolToStatus, genAbsolutePicUrl, renderProductType, renderStatus, statusToBool,
 } from '../../../utils/utils';
+import { Hotax } from '../../../utils/config';
 import bitmap from '../../../assets/bitmap.png';
 import styles from './RecommendPoster.less';
 
@@ -255,7 +256,6 @@ export default class RecommendPosterEditPage extends Component {
     /* 海报列表格式设定 */
     const posterColumns = [{
       title: '位置',
-      key: 1,
       dataIndex: 'sort',
       width: '15%',
       render: (text, record) => {
@@ -276,7 +276,6 @@ export default class RecommendPosterEditPage extends Component {
       align: 'center',
     }, {
       title: '海报封面',
-      key: 2,
       dataIndex: 'img',
       width: '27%',
       render: (text, record) => {
@@ -296,7 +295,7 @@ export default class RecommendPosterEditPage extends Component {
       align: 'center',
     }, {
       title: '关联产品信息',
-      key: 3,
+      dataIndex: 'related',
       render: (_, record) => {
         // 将产品信息渲染成一个小表格
         const { id, code, name, type, product, isEdit, isNew } = record;
@@ -342,7 +341,6 @@ export default class RecommendPosterEditPage extends Component {
       align: 'center',
     }, {
       title: '删除状态',
-      key: 4,
       dataIndex: 'status',
       width: '13%',
       render: (text, record) => {
@@ -360,9 +358,17 @@ export default class RecommendPosterEditPage extends Component {
         return renderStatus(text);
       },
       align: 'center',
+      filters: [{
+        text: '正常',
+        value: Hotax.STATUS_NORMAL,
+      }, {
+        text: '删除',
+        value: Hotax.STATUS_DELETE,
+      }],
+      onFilter: (value, record) => record.status === value,
     }, {
       title: '相关操作',
-      key: 5,
+      dataIndex: 'operation',
       width: '15%',
       render: (_, record) => {
         const { id, isNew, isEdit } = record;

+ 37 - 2
src/routes/Shelves/ShelvesEdit.js

@@ -1,18 +1,22 @@
+/* eslint-disable no-trailing-spaces */
 import React, { Component } from 'react';
 import pathToRegexp from 'path-to-regexp';
 import { Card, Modal, Form, Button, Tag, Icon } from 'antd';
 import { connect } from 'dva';
 import { routerRedux } from 'dva/router';
 import TableForm from './TableForm';
+import { CourseItem, SupportItem, TrainingItem, PackageItem } from '../../components/AXItem';
 import FooterToolbar from '../../components/FooterToolbar';
 import Selector from '../../components/AXTableSelector/Selector';
 import { addRowKey } from '../../utils/utils';
 import { Hotax } from '../../utils/config';
+import Package from "../Product/Package";
 
-@connect(({ loading, shelves, resource, tag }) => ({
+@connect(({ loading, shelves, product, resource, tag }) => ({
   tag,
   shelves,
   resource,
+  product,
   rLoading: loading.models.resource,
   tLoading: loading.models.tag,
   submitting: loading.models.shelves,
@@ -48,6 +52,10 @@ export default class ShelvesEdit extends Component {
         merchantId,
       },
     });
+    this.props.dispatch({
+      type: 'product/fetchProductItem',
+      payload: { pid },
+    });
   }
   handleGoodsCreate = (data) => {
     const { pid, merchantId, scene } = this.state;
@@ -177,13 +185,40 @@ export default class ShelvesEdit extends Component {
   };
   render() {
     const { tagSelectorDestroy, resourceSelectorDestroy } = this.state;
-    const { submitting, tLoading, rLoading, resource, shelves, tag, form } = this.props;
+    const { submitting, tLoading, rLoading, resource, product, shelves, tag, form } = this.props;
     const { getFieldDecorator } = form;
     const { currentItem } = shelves;
     const { goods, tags } = currentItem;
 
+    // render product show card
+    const renderProductCard = (proInfo) => {
+      if (!proInfo) return;
+      const { type } = proInfo;
+      switch (type) {
+        case Hotax.PRODUCT_COURSE:
+          return (
+            <CourseItem {...proInfo} />
+          );
+        case Hotax.PRODUCT_SUPPORT:
+          return (
+            <SupportItem {...proInfo} />
+          );
+        case Hotax.PRODUCT_TRAINING:
+          return (
+            <TrainingItem {...proInfo} />
+          );
+        case Hotax.PRODUCT_PACKAGE:
+          return (
+            <PackageItem {...proInfo} />
+          );
+        default:
+          return null;
+      }
+    };
+
     return (
       <div>
+        {renderProductCard(product ? product.currentItem : {})}
         <Card title="价格管理" style={{ marginBottom: 16 }}>
           {getFieldDecorator('goods', {
             initialValue: (!goods || !goods.length) ? null : addRowKey(goods),

+ 95 - 6
src/routes/Terminal/Auth/AuthList.js

@@ -2,7 +2,7 @@
 import React, { Component } from 'react';
 import moment from 'moment';
 import { connect } from 'dva';
-import { Card, Badge, message } from 'antd';
+import { Modal, Form, Card, Badge, Button, DatePicker, message } from 'antd';
 import { StandardTableList } from '../../../components/AXList';
 import Ellipsis from '../../../components/Ellipsis';
 import { addRowKey } from '../../../utils/utils';
@@ -10,13 +10,21 @@ import styles from './AuthList.less';
 
 const Message = message;
 
+const formItemLayout = {
+  labelCol: {
+    md: { span: 6 },
+  },
+  wrapperCol: {
+    md: { span: 18 },
+  },
+};
 const timestamp2Str = ts => moment(ts).format('YYYY-MM-DD HH:mm:ss');
 
+@Form.create()
 @connect(({ terminal, loading }) => ({
   terminal,
   loading: loading.models.terminal,
 }))
-
 export default class TerminalAuthListPage extends Component {
   constructor(props) {
     super(props);
@@ -24,6 +32,8 @@ export default class TerminalAuthListPage extends Component {
     this.state = {
       UIParams: (state || {}).UIParams, // 组件的状态参数
       Queryers: (state || {}).Queryers, // 查询的条件参数
+      authEditModalDestroy: true,
+      targetRow: {},
     };
   }
   componentWillMount() {
@@ -50,8 +60,39 @@ export default class TerminalAuthListPage extends Component {
   handleBatchOperation = () => {
     Message.info('暂不支持批量操作!');
   };
+  handleEditAuthModalShow = (record) => {
+    this.setState({
+      authEditModalDestroy: false,
+      targetRow: record,
+    });
+  };
+  handleEditAuthModalHide = () => {
+    this.setState({
+      authEditModalDestroy: true,
+    });
+  };
+  handleEditAuthModalFinish = () => {
+    this.props.form.validateFieldsAndScroll((err, values) => {
+      if (!err) {
+        const { startTime, endTime } = values;
+        const stStr = moment(startTime).format('YYYY-MM-DD');
+        const etStr = moment(endTime).format('YYYY-MM-DD');
+        const { targetRow } = this.state;
+        this.props.dispatch({
+          type: 'terminal/updateTerminalAuth',
+          payload: { ...targetRow, startTime: stStr, endTime: etStr },
+          states: { ...this.state.Queryers },
+        });
+      }
+    });
+    this.setState({
+      authEditModalDestroy: true,
+    });
+  };
   render() {
-    const { terminal, loading } = this.props;
+    const { targetRow, authEditModalDestroy } = this.state;
+    const { terminal, loading, form } = this.props;
+    const { getFieldDecorator } = form;
     const { pageNo, pageSize, totalSize, list } = terminal;
     const basicSearch = {
       keys: [{
@@ -80,7 +121,7 @@ export default class TerminalAuthListPage extends Component {
       title: '产品编号',
       key: 2,
       dataIndex: 'pcode',
-      width: '15%',
+      width: '12%',
       render: text => (
         <Ellipsis tooltip lines={1}>{text}</Ellipsis>
       ),
@@ -88,7 +129,7 @@ export default class TerminalAuthListPage extends Component {
       title: '产品名称',
       key: 3,
       dataIndex: 'pname',
-      width: '18%',
+      width: '13%',
       render: text => (
         <Ellipsis tooltip lines={1}>{text}</Ellipsis>
       ),
@@ -96,7 +137,7 @@ export default class TerminalAuthListPage extends Component {
       title: '权限有效期',
       key: 4,
       render: (_, record) => {
-        const {startTime, endTime} = record;
+        const { startTime, endTime } = record;
         return (
           <div className={styles.authDesc}>
             <p><span>起始时间:&nbsp;&nbsp;</span>{`${timestamp2Str(startTime)}`}</p>
@@ -126,6 +167,14 @@ export default class TerminalAuthListPage extends Component {
       render: text => timestamp2Str(text),
       width: '15%',
       align: 'center',
+    }, {
+      title: '操作',
+      key: 7,
+      render: (_, record) => (
+        <Button onClick={() => this.handleEditAuthModalShow(record)} size="small" className="editBtn">修改权限</Button>
+      ),
+      width: '8%',
+      align: 'right',
     }];
     return (
       <Card>
@@ -145,6 +194,46 @@ export default class TerminalAuthListPage extends Component {
           keepUIState={{ ...this.state.UIParams }}
           showStatusSelect={false}
         />
+        {!authEditModalDestroy && (
+          <Modal
+            visible
+            title="修改权限时间"
+            cancelText="取消"
+            okText="确定"
+            maskClosable={false}
+            onCancel={this.handleEditAuthModalHide}
+            onOk={this.handleEditAuthModalFinish}
+          >
+            <Form>
+              <Form.Item label="起始日期" {...formItemLayout}>
+                {getFieldDecorator('startTime', {
+                  rules: [{ required: true, message: '请选择起始日期' }],
+                  initialValue: targetRow && targetRow.startTime && moment(targetRow.startTime),
+                })(
+                  <DatePicker
+                    showTime
+                    placeholder="选择日期时间"
+                    format="YYYY-MM-DD HH:mm:ss"
+                    style={{ width: 280 }}
+                  />
+                )}
+              </Form.Item>
+              <Form.Item label="截止日期" {...formItemLayout}>
+                {getFieldDecorator('endTime', {
+                  rules: [{ required: true, message: '请选择结束日期' }],
+                  initialValue: targetRow && targetRow.endTime && moment(targetRow.endTime),
+                })(
+                  <DatePicker
+                    showTime
+                    placeholder="选择日期时间"
+                    format="YYYY-MM-DD HH:mm:ss"
+                    style={{ width: 280 }}
+                  />
+                )}
+              </Form.Item>
+            </Form>
+          </Modal>
+        )}
       </Card>
     );
   }

+ 13 - 0
src/services/terminal.js

@@ -140,6 +140,19 @@ export async function queryTerminalAuthList(params) {
 }
 
 /**
+ * 修改用户权限时长
+ * @param params
+ * @returns {Promise<Object>}
+ */
+export async function updateTerminalAuth(params) {
+  const options = {
+    method: 'POST',
+    body: params,
+  };
+  return request(`${api.terminalAuthEdit}`, options);
+}
+
+/**
  * 查询终端标签列表
  * @param uid
  * @returns {Promise<Object>}

+ 1 - 0
src/utils/config.js

@@ -102,6 +102,7 @@ const apiObj = {
   terminalItem: '/user',
   terminalUnbound: '/device/unbind',
   terminalAuth: '/user/auth/list',
+  terminalAuthEdit: '/user/auth',
   specialTerminal: '/white/user/list',
   specialTerminalItem: '/white/user',
   cmsUser: '/cms/user/list',