Przeglądaj źródła

:bug: fix some bugs;
:memo: write usage document;

zhanghe 6 lat temu
rodzic
commit
7d7b3262ba

+ 50 - 60
README.md

@@ -1,22 +1,22 @@
-[English](./README.md) | 简体中文
+简体中文
 
-# Ant Design Pro
+# Hotax
 
-[![](https://img.shields.io/travis/ant-design/ant-design-pro.svg?style=flat-square)](https://travis-ci.org/ant-design/ant-design-pro) [![Build status](https://ci.appveyor.com/api/projects/status/67fxu2by3ibvqtat/branch/master?svg=true)](https://ci.appveyor.com/project/afc163/ant-design-pro/branch/master)  [![Gitter](https://badges.gitter.im/ant-design/ant-design-pro.svg)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
+[![](https://img.shields.io/travis/ant-design/ant-design-pro.svg?style=flat-square)](https://travis-ci.org/ant-design/ant-design-pro) [![Build status](https://ci.appveyor.com/api/projects/status/67fxu2by3ibvqtat/branch/master?svg=true)](https://ci.appveyor.com/project/afc163/ant-design-pro/branch/master)
 
-开箱即用的中台前端/设计解决方案。
+领教双师云课堂内容管理平台 - CMS3.0
 
-![](https://gw.alipayobjects.com/zos/rmsportal/xEdBqwSzvoSapmnSnYjU.png)
+![](http://efunimgs.oss-cn-beijing.aliyuncs.com/resources/readme/image/1/15280205018177511.png)
 
-- 预览:http://preview.pro.ant.design
-- 首页:http://pro.ant.design/index-cn
-- 使用文档:http://pro.ant.design/docs/getting-started-cn
-- 更新日志: http://pro.ant.design/docs/changelog-cn
-- 常见问题:http://pro.ant.design/docs/faq-cn
+- 预览:http://cms.lingjiao.cn
+- 首页:http://cms.lingjiao.cn/dashboard/sold
+- AntdPro使用文档:http://pro.ant.design/docs/getting-started-cn
+- AntdPro更新日志: http://pro.ant.design/docs/changelog-cn
+- AntdPro常见问题:http://pro.ant.design/docs/faq-cn
 
 ## 特性
 
-- :gem: **优雅美观**:基于 Ant Design 体系精心设计
+- :gem: **优雅美观**:基于 Ant Design Pro体系精心设计
 - :triangular_ruler: **常见设计模式**:提炼自中后台应用的典型页面和场景
 - :rocket: **最新技术栈**:使用 React/dva/antd 等前端前沿技术开发
 - :iphone: **响应式**:针对不同屏幕大小设计
@@ -26,65 +26,55 @@
 - :1234: **Mock 数据**:实用的本地数据调试方案
 - :white_check_mark: **UI 测试**:自动化测试保障前端产品质量
 
-## 模
+## 模
 
 ```
-- Dashboard
-  - 分析页
-  - 监控页
-  - 工作台
-- 表单页
-  - 基础表单页
-  - 分步表单页
-  - 高级表单页
-- 列表页
-  - 查询表格
-  - 标准列表
-  - 卡片列表
-  - 搜索列表(项目/应用/文章)
-- 详情页
-  - 基础详情页
-  - 高级详情页
-- 结果
-  - 成功页
-  - 失败页
-- 异常
-  - 403 无权限
-  - 404 找不到
-  - 500 服务器出错
-- 帐户
-  - 登录
-  - 注册
-  - 注册成功
+- 统计概览
+  - 销售详情
+- 基础资源
+  - 图库管理
+  - 视频管理
+- 产品加工
+  - 制作课件
+  - 制作课
+  - 制作课程
+  - 制作配套
+  - 制作师训
+  - 制作套餐包
+- 产品出售
+  - 上架课程
+  - 上架配套
+  - 上架师训
+  - 上架套餐包
+- 前端配置
+  - 首页入口
+  - 栏目类型
+  - 侧边栏目
+  - 推荐内容
+- 交易管理
+  - 购物车
+  - 订单列表
+- 厂商管理
+  - 商户列表
+- 校区管理
+  - 校区列表
+- 终端管理
+  - 终端用户
+  - 白名单
+- 系统管理
+  - 系统用户
 ```
 
 ## 使用
 
 ```bash
-$ git clone https://github.com/ant-design/ant-design-pro.git --depth=1
-$ cd ant-design-pro
+$ git clone http://gogs.efunbox.cn:3000/Rankin/Hotax.git
+$ cd Hotax
 $ npm install
-$ npm start         # 访问 http://localhost:8000
+$ npm start:no-proxy         # 访问 http://tt-cms.ai160.com
+$ npm run build  # 生产环境构建
 ```
 
-也可以使用集成化的 [ant-design-pro-cli](https://github.com/ant-design/ant-design-pro-cli) 工具。
-
-```bash
-$ npm install ant-design-pro-cli -g
-$ mkdir pro-demo && cd pro-demo
-$ pro new
-```
-
-更多信息请参考 [使用文档](http://pro.ant.design/docs/getting-started)。
-
 ## 兼容性
 
 现代浏览器及 IE11。
-
-## 参与贡献
-
-我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 :smiley::
-
-- 在你的公司或个人项目中使用 Ant Design Pro。
-- 通过 [Issue](http://github.com/ant-design/ant-design-pro/issues) 报告 bug 或进行咨询。
-- 提交 [Pull Request](http://github.com/ant-design/ant-design-pro/pulls) 改进 Pro 的代码。

BIN
src/assets/shopqr.png


+ 4 - 4
src/common/router.js

@@ -255,7 +255,7 @@ export const getRouterData = (app) => {
       component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/ShelvesCreate')),
     },
     '/shelves/course/edit': {
-      component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/ShelvesEdit')),
+      component: dynamicWrapper(app, ['shelves', 'resource'], () => import('../routes/Shelves/ShelvesEdit')),
     },
     '/shelves/support': {
       component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves')),
@@ -267,7 +267,7 @@ export const getRouterData = (app) => {
       component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/ShelvesCreate')),
     },
     '/shelves/support/edit': {
-      component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/ShelvesEdit')),
+      component: dynamicWrapper(app, ['shelves', 'resource'], () => import('../routes/Shelves/ShelvesEdit')),
     },
     '/shelves/training': {
       component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves')),
@@ -279,7 +279,7 @@ export const getRouterData = (app) => {
       component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/ShelvesCreate')),
     },
     '/shelves/training/edit': {
-      component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/ShelvesEdit')),
+      component: dynamicWrapper(app, ['shelves', 'resource'], () => import('../routes/Shelves/ShelvesEdit')),
     },
     '/shelves/package': {
       component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves')),
@@ -291,7 +291,7 @@ export const getRouterData = (app) => {
       component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/ShelvesCreate')),
     },
     '/shelves/package/edit': {
-      component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/ShelvesEdit')),
+      component: dynamicWrapper(app, ['shelves', 'resource'], () => import('../routes/Shelves/ShelvesEdit')),
     },
     // 前端管理相关路由注册
     '/frontend/tagGroup': {

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

@@ -18,7 +18,7 @@
   :global(.ant-card) {
     margin-left: 10px;
     width: 70px;
-    height: 70px;
+    height: 80px;
     float: left;
     &:hover {
       background: #f5f5f5;

+ 100 - 110
src/routes/Document/DocumentPlatform.js

@@ -6,120 +6,110 @@ import styles from './document.less';
 
 const { Description } = DescriptionList;
 
+const resourceQAList = [{
+  question: '图片的创建与编辑',
+  answer: '操作:"资源管理 > 图片管理 > 新建/编辑",正确填写图片的编号和名称后方可选择待上传图片;',
+  note: '注意:图片编号应满足格式限制,不填写或者格式错误禁止上传图片',
+  imgList: [
+    'http://efunimgs.oss-cn-beijing.aliyuncs.com/resources/document/image/1/15279977811093052.png',
+    'http://efunimgs.oss-cn-beijing.aliyuncs.com/resources/document/image/2/15280040674381093.png',
+  ],
+}, {
+  question: '视频的创建与编辑',
+  answer: '操作:"资源管理 > 视频管理 > 新建/编辑",正确填写视频相关信息',
+  note: '注意:视频编号、名称、路径、类型、清晰度、格式为必填项',
+  imgList: [
+    'http://efunimgs.oss-cn-beijing.aliyuncs.com/resources/document/video/1/15280080455214642.png',
+  ],
+}];
+const productQAList = [{
+  question: '课件的创建及编辑',
+  answer: '操作:"产品加工 > 制作课件 > 新建/编辑",正确填写课件编号及名称等基础信息后,选择课件内容后排序',
+  note: '注意:图片课件可包含多张图片资源,视频课件仅能包含一个视频资源,切勿图片与视频混选',
+  imgList: [
+    'http://efunimgs.oss-cn-beijing.aliyuncs.com/resources/document/ware/1/1528009377007865.png',
+    'http://efunimgs.oss-cn-beijing.aliyuncs.com/resources/document/ware/2/15280107071076247.png',
+  ],
+}, {
+  question: '课/课程/配套/师训/套餐包等创建及编辑',
+  answer: '此种产品类型的加工过程请参照课件的创建过程',
+}];
+const shelvesQAList = [{
+  question: '产品的上架流程',
+  answer: '操作:"产品出售 > 上架XX > 新建",正确选择产品及对应渠道后,提交即可将产品上架到对应渠道',
+  note: '注意:新建过程仅仅是将一个产品关联到某渠道,此时前端并未展现,需要再次编辑该产品挂载到相应栏目下,才可展现',
+  imgList: [
+    'http://efunimgs.oss-cn-beijing.aliyuncs.com/resources/document/shelves/1/15280127770612598.png',
+    'http://efunimgs.oss-cn-beijing.aliyuncs.com/resources/document/shelves/2/15280127974934585.png',
+  ],
+}];
+const otherQAList = [{
+  question: '如何调整某入口下侧边栏目顺序',
+  answer: '操作:"前端配置 > 首页入口 > 编辑标签组",在编辑页面找到该入口下所有栏目表格,拖动或者点击进行排序',
+}, {
+  question: '如何调整某栏目下的课程排序',
+  answer: '操作:"前端配置 > 栏目管理 > 编辑标签", 在编辑页面找到已关联的产品后拖动或者上移/下移进行课程排序',
+  note: '注意:此处点击关联产品可批量进行课程与栏目关联操作',
+}];
+
+function FAQComponent({ sort, bigTitle, qaList }) {
+  return (
+    <DescriptionList
+      col={1}
+      size="large"
+      title={`${sort}.${bigTitle}`}
+      className={styles.descList}
+    >
+      {/* eslint-disable react/no-array-index-key */}
+      {
+        qaList.map((qa, index) => {
+          const { question, answer, note, imgList } = qa;
+          return (
+            <div key={index}>
+              <Description>
+                <Icon type="question-circle-o" className={styles.question} />&nbsp;{question}
+              </Description>
+              <Description>
+                <Icon type="check-circle-o" className={styles.answer} />&nbsp;{answer}
+                {note && (<div>&nbsp;&nbsp;&nbsp;&nbsp;{note}</div>)}
+              </Description>
+              {(imgList && imgList.length) && imgList.map((img, imgIndex) => (
+                <Description key={imgIndex}>
+                  <img src={img} alt="" style={{ width: '100%', height: 550 }} />
+                </Description>
+              ))}
+            </div>
+          );
+        })
+      }
+    </DescriptionList>
+  );
+}
+
 export default class PlatformDocument extends PureComponent {
   render() {
     return (
       <Card title="平台使用文档" className={styles.card}>
-        <DescriptionList
-          col={1}
-          size="large"
-          title="1.资源管理"
-          className={styles.descList}
-        >
-          <Description>
-            <Icon type="question-circle-o" className={styles.question} />&nbsp;
-            如何上传图片?
-          </Description>
-          <Description>
-            <Icon type="check-circle-o" className={styles.answer} />&nbsp;
-            操作:"资源管理 > 图片管理 > 新建 > 提交",正确填写图片的编号和名称后方可选择待上传图片;
-            <br />
-            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-            注意:图片编号应满足格式限制,不填写或者格式错误禁止上传图片,图片一旦创建完成,图片编号不可修改;
-          </Description>
-          <Description>
-            <Icon type="question-circle-o" className={styles.question} />&nbsp;
-            如何查看视频内容?
-          </Description>
-          <Description>
-            <Icon type="check-circle-o" className={styles.answer} />&nbsp;
-            操作:"资源管理 > 视频管理 > 检索",可根据视频编号或者名称查看具体视频内容;
-          </Description>
-        </DescriptionList>
-        <DescriptionList
-          col={1}
-          size="large"
-          title="2.产品加工"
-          className={styles.descList}
-        >
-          <Description>
-            <Icon type="question-circle-o" className={styles.question} />&nbsp;
-            如何制作课件?
-          </Description>
-          <Description>
-            <Icon type="check-circle-o" className={styles.answer} />&nbsp;
-            操作:"产品加工 > 制作课件 > 新建 > 提交",正确填写课件的编号及名称后点击`资源列表`选择一个视频或者多个图片;
-            <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-            注意:环节名称与前端课件的环节名对应,不填写则前端展现为空;
-          </Description>
-          <Description>
-            <Icon type="question-circle-o" className={styles.question} />&nbsp;
-            如何制作课?
-          </Description>
-          <Description>
-            <Icon type="check-circle-o" className={styles.answer} />&nbsp;
-            操作:"产品加工 > 制作课 > 新建 > 提交",正确填写课的编号及名称后点击`课件列表`选择一个或者多个课件;
-            <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-            注意:拖动表格或者点击"上移/下移"来调整课件顺序;
-          </Description>
-          <Description>
-            <Icon type="question-circle-o" className={styles.question} />&nbsp;
-            如何制作课程?
-          </Description>
-          <Description>
-            <Icon type="check-circle-o" className={styles.answer} />&nbsp;
-            操作:"产品加工 > 制作课程 > 新建 > 提交",正确填写课程的编号及名称后选择封面、背景、课及相关配套等信息;
-            <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-            注意:拖动表格或者点击"上移/下移"来调整课或者相关配套顺序;
-          </Description>
-          <Description>
-            <Icon type="question-circle-o" className={styles.question} />&nbsp;
-            如何制作配套?
-          </Description>
-          <Description>
-            <Icon type="check-circle-o" className={styles.answer} />&nbsp;
-            操作:"产品加工 > 制作配套 > 新建 > 提交",正确填写配套的编号及名称后选择封面、详情图片列表及相关配套等信息;
-            <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-            注意:拖动表格或者点击"上移/下移"来调整相关配套顺序;
-          </Description>
-          <Description>
-            <Icon type="question-circle-o" className={styles.question} />&nbsp;
-            如何制作师训内容?
-          </Description>
-          <Description>
-            <Icon type="check-circle-o" className={styles.answer} />&nbsp;
-            操作:"产品加工 > 制作师训 > 新建 > 提交",正确填写师训的编号、主题、活动时间后选择封面、详情大图列表等信息;
-            <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-            注意:活动起始日期和截止日期可不选;
-          </Description>
-          <Description>
-            <Icon type="question-circle-o" className={styles.question} />&nbsp;
-            如何创建套餐包?
-          </Description>
-          <Description>
-            <Icon type="check-circle-o" className={styles.answer} />&nbsp;
-            操作:"产品加工 > 制作套餐包 > 新建 > 提交",正确填写师训的编号、名称后选择套餐包含的课程或者配套等内容;
-            <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-            注意:选择完课程及配套内容后须填写该课程或配套在套餐包里的价格;
-          </Description>
-        </DescriptionList>
-        <DescriptionList
-          col={1}
-          size="large"
-          title="3.产品上架"
-          className={styles.descList}
-        >
-          <Description>
-            <Icon type="question-circle-o" className={styles.question} />&nbsp;
-            如何将产品上架到不同的渠道平台?
-          </Description>
-          <Description>
-            <Icon type="check-circle-o" className={styles.answer} />&nbsp;
-            操作:"产品出售 > 上架XX > 新建 > 提交",选择需要上架的产品,然后选择需要上架的渠道平台;
-            <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-            注意:改步操作后该产品仅属于可售卖状态,需要再次编辑填写相关价格,并选择需上架的栏目;
-          </Description>
-        </DescriptionList>
+        <FAQComponent
+          sort={1}
+          bigTitle="资源管理"
+          qaList={resourceQAList}
+        />
+        <FAQComponent
+          sort={2}
+          bigTitle="产品加工"
+          qaList={productQAList}
+        />
+        <FAQComponent
+          sort={3}
+          bigTitle="产品上架"
+          qaList={shelvesQAList}
+        />
+        <FAQComponent
+          sort={4}
+          bigTitle="其他问题"
+          qaList={otherQAList}
+        />
       </Card>
     );
   }

+ 6 - 0
src/routes/Document/document.less

@@ -21,3 +21,9 @@
   color: #52c41a;
   font-weight: bold;
 }
+
+@media (min-width: 1600px) {
+  .card {
+    margin: 16px 25% !important;
+  }
+}

+ 2 - 0
src/routes/Product/Package/PackageCreate.js

@@ -332,6 +332,7 @@ export default class PackageCreatePage extends Component {
             multiple
             loading={pLoading}
             selectorName={productType}
+            selectedRows={products}
             fixedName="Product"
             list={product.list}
             pageNo={product.pageNo}
@@ -384,6 +385,7 @@ export default class PackageCreatePage extends Component {
             </Form.Item>
           </Form>
         </Card>
+        {/* 已选产品列表Card 支持排序 */}
         <Card title={renderCardName()} style={{ marginBottom: 70 }}>
           <Table
             bordered

+ 31 - 5
src/routes/Shelves/ShelvesEdit.js

@@ -9,9 +9,11 @@ import Selector from '../../components/AXTableSelector/Selector';
 import { addRowKey } from '../../utils/utils';
 import { Hotax } from '../../utils/config';
 
-@connect(({ loading, shelves, tag }) => ({
+@connect(({ loading, shelves, resource, tag }) => ({
   tag,
   shelves,
+  resource,
+  rLoading: loading.models.resource,
   tLoading: loading.models.tag,
   submitting: loading.models.shelves,
 }))
@@ -30,6 +32,7 @@ export default class ShelvesEdit extends Component {
       productType,
       scene: type,
       tagSelectorDestroy: true,
+      resourceSelectorDestroy: true,
     };
   }
   componentDidMount() {
@@ -47,8 +50,9 @@ export default class ShelvesEdit extends Component {
     this.props.dispatch({
       type: 'shelves/createItemPrice',
       payload: {
+        pid,
+        merchantId,
         ...data,
-        ...this.state,
         productType: scene.toUpperCase(),
         status: Hotax.STATUS_NORMAL,
       },
@@ -63,8 +67,9 @@ export default class ShelvesEdit extends Component {
     this.props.dispatch({
       type: 'shelves/updateItemPrice',
       payload: {
+        pid,
+        merchantId,
         ...data,
-        ...this.state,
       },
       states: {
         pid,
@@ -144,6 +149,21 @@ export default class ShelvesEdit extends Component {
       tagSelectorDestroy: true,
     });
   };
+  handleResourceSelectorModalShow = () => {
+    this.setState({ resourceSelectorDestroy: false });
+    this.props.dispatch({
+      type: 'resource/fetchImageList',
+    });
+  };
+  handleResourceSelectorModalCancel = () => {
+    this.setState({ resourceSelectorDestroy: true });
+  };
+  handleResourceSelectorChange = (params) => {
+    this.props.dispatch({
+      type: 'resource/fetchImageList',
+      payload: params,
+    });
+  };
   handlePageBack = () => {
     const { scene } = this.state;
     this.props.dispatch(routerRedux.push({
@@ -152,8 +172,8 @@ export default class ShelvesEdit extends Component {
     }));
   };
   render() {
-    const { tagSelectorDestroy } = this.state;
-    const { submitting, tLoading, shelves, tag, form } = this.props;
+    const { tagSelectorDestroy, resourceSelectorDestroy } = this.state;
+    const { submitting, tLoading, rLoading, resource, shelves, tag, form } = this.props;
     const { getFieldDecorator } = form;
     const { currentItem } = shelves;
     const { goods, tags } = currentItem;
@@ -169,6 +189,12 @@ export default class ShelvesEdit extends Component {
               onCreate={this.handleGoodsCreate}
               onUpdate={this.handleGoodsUpdate}
               onDelete={this.handleGoodsDelete}
+              qrDestroy={resourceSelectorDestroy}
+              qrLoading={rLoading}
+              qrData={resource}
+              onQRShow={this.handleResourceSelectorModalShow}
+              onQRHide={this.handleResourceSelectorModalCancel}
+              onQRChange={this.handleResourceSelectorChange}
             />
           )}
         </Card>

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

@@ -63,7 +63,7 @@ export default class ShelvesListPage extends Component {
       onOk: () => {
         this.props.dispatch({
           type: 'shelves/deleteItem',
-          payload: { id: item.id },
+          payload: { pid: item.pid, merchantId: item.merchantId },
           states: this.state,
         });
       },

+ 79 - 11
src/routes/Shelves/TableForm.js

@@ -6,9 +6,12 @@ import {
   InputNumber,
   Popconfirm,
   Divider,
+  Modal,
   message,
 } from 'antd';
-import { chargeUnitMap, durationMap, sortMap } from '../../utils/utils';
+import Selector from '../../components/AXTableSelector/Selector';
+import { chargeUnitMap, durationMap, genAbsolutePicUrl, sortMap } from '../../utils/utils';
+import shopqr from '../../assets/shopqr.png';
 import styles from './TableForm.less';
 
 export default class TableForm extends PureComponent {
@@ -18,10 +21,11 @@ export default class TableForm extends PureComponent {
     this.state = {
       data: props.value,
       loading: props.loading,
+      currentKey: null,
     };
   }
   componentWillReceiveProps(nextProps) {
-    if ('value' in nextProps) {
+    if ('value' in nextProps && (!this.props.value || !this.props.value.length) ) {
       this.setState({
         data: nextProps.value,
       });
@@ -87,6 +91,20 @@ export default class TableForm extends PureComponent {
       this.setState({ data: newData });
     }
   }
+  handleQRSelectShow(key) {
+    this.setState({
+      currentKey: key,
+    }, () => this.props.onQRShow());
+  }
+  handleQRSelectFinish(rows) {
+    const newData = this.state.data.map(item => ({ ...item }));
+    const target = this.getRowByKey(this.state.currentKey, newData);
+    if (target) {
+      target.shopQR = rows[0].path;
+      this.setState({ data: newData });
+    }
+    this.props.onQRHide();
+  }
   saveRow(e, key) {
     e.persist();
     if (this.clickedCancel) {
@@ -99,12 +117,13 @@ export default class TableForm extends PureComponent {
       e.target.focus();
       return;
     }
-    const { id, chargeUnit, cpPrice, merchantPrice, terminalPrice, duration } = target;
+    const { id, chargeUnit, cpPrice, merchantPrice, terminalPrice, duration, shopQR } = target;
     const sort = sortMap[chargeUnit];
     if (target.isNew) {
       this.props.onCreate({
         sort,
         cpPrice,
+        shopQR,
         duration,
         chargeUnit,
         merchantPrice,
@@ -114,6 +133,7 @@ export default class TableForm extends PureComponent {
       this.props.onUpdate({
         id,
         sort,
+        shopQR,
         cpPrice,
         duration,
         chargeUnit,
@@ -142,7 +162,7 @@ export default class TableForm extends PureComponent {
       title: '计价单位',
       dataIndex: 'chargeUnit',
       key: 'chargeUnit',
-      width: '15%',
+      width: '13%',
       align: 'center',
       render: (text, record) => {
         if (record.editable) {
@@ -174,13 +194,34 @@ export default class TableForm extends PureComponent {
       title: '时长(天)',
       dataIndex: 'duration',
       key: 'duration',
-      width: '15%',
+      width: '10%',
       align: 'center',
     }, {
+      title: '二维码',
+      dataIndex: 'shopQR',
+      key: 'shopQR',
+      width: '10%',
+      align: 'center',
+      render: (text, record) => {
+        const { editable, key } = record;
+        return (
+          <div className={styles.qrWrapper}>
+            {
+              editable && (
+                <div className={styles.qrBtn}>
+                  <a onClick={() => this.handleQRSelectShow(key)}>{!text ? '选择' : '更换'}</a>
+                </div>
+              )
+            }
+            <img src={!text ? shopqr : genAbsolutePicUrl(text)} alt="二维码" />
+          </div>
+        );
+      },
+    }, {
       title: '供应商售价(¥)',
       dataIndex: 'cpPrice',
       key: 'cpPrice',
-      width: '20%',
+      width: '18%',
       align: 'center',
       render: (text, record) => {
         if (record.editable) {
@@ -189,7 +230,7 @@ export default class TableForm extends PureComponent {
               min={1}
               value={text}
               onChange={e => this.handleFieldChange(e, 'cpPrice', record.key)}
-              style={{ width: '100%', border: 'unset' }}
+              style={{ width: '100%' }}
               placeholder="请输入"
             />
           );
@@ -200,7 +241,7 @@ export default class TableForm extends PureComponent {
       title: '平台方售价(¥)',
       dataIndex: 'merchantPrice',
       key: 'merchantPrice',
-      width: '20%',
+      width: '18%',
       align: 'center',
       render: (text, record) => {
         if (record.editable) {
@@ -209,7 +250,7 @@ export default class TableForm extends PureComponent {
               min={1}
               value={text}
               onChange={value => this.handleFieldChange(value, 'merchantPrice', record.key)}
-              style={{ width: '100%', border: 'unset' }}
+              style={{ width: '100%' }}
               placeholder="请输入"
             />
           );
@@ -220,7 +261,7 @@ export default class TableForm extends PureComponent {
       title: '终端显示价格(¥)',
       dataIndex: 'terminalPrice',
       key: 'terminalPrice',
-      width: '20%',
+      width: '18%',
       align: 'center',
       render: (text, record) => {
         if (record.editable) {
@@ -229,7 +270,7 @@ export default class TableForm extends PureComponent {
               min={1}
               value={text}
               onChange={value => this.handleFieldChange(value, 'terminalPrice', record.key)}
-              style={{ width: '100%', border: 'unset' }}
+              style={{ width: '100%' }}
               placeholder="请输入"
             />
           );
@@ -275,6 +316,32 @@ export default class TableForm extends PureComponent {
         );
       },
     }];
+    /* 二维码选择模态框 */
+    const getResourceModal = () => {
+      return (
+        <Modal
+          width={1100}
+          footer={null}
+          visible
+          title="图片资源"
+          maskClosable={false}
+          onCancel={this.props.onQRHide}
+        >
+          <Selector
+            multiple={false}
+            loading={this.props.qrLoading}
+            selectorName="PictureSingle"
+            list={this.props.qrData.list}
+            pageNo={this.props.qrData.pageNo}
+            pageSize={this.props.qrData.pageSize}
+            totalSize={this.props.qrData.totalSize}
+            onCancel={this.props.onQRHide}
+            onChange={this.props.onQRChange}
+            onFinish={rows => this.handleQRSelectFinish(rows)}
+          />
+        </Modal>
+      );
+    };
 
     return (
       <Fragment>
@@ -288,6 +355,7 @@ export default class TableForm extends PureComponent {
             return styles.editable;
           }}
         />
+        {!this.props.qrDestroy && getResourceModal()}
         <Button
           style={{ width: '100%', marginTop: 16, marginBottom: 8 }}
           type="dashed"

+ 37 - 1
src/routes/Shelves/TableForm.less

@@ -3,11 +3,47 @@
 .editable {
   td {
     height: 32px;
-    padding: 0 !important;
+    padding: 10px !important;
   }
 }
+/*
 .select {
   :global(.ant-select-selection) {
     border: unset;
   }
 }
+*/
+
+.qrWrapper {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  .qrBtn {
+    z-index: 10;
+    display: none;
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    a {
+      position: relative;
+      font-size: 10px;
+      top: 30%;
+      transform: translateY(-30%);
+      padding: 5px 10px;
+      background: #fff;
+      color: #73777a;
+    }
+  }
+  img {
+    width: 50px;
+    height: 50px;
+  }
+  &:hover {
+    .qrBtn {
+      display: block;
+      background: rgba(0,193,222,.8);
+    }
+  }
+}

+ 123 - 13
src/routes/Trade/Order/OrderCreate.js

@@ -1,7 +1,8 @@
 /* eslint-disable no-trailing-spaces */
 import React, { Component } from 'react';
 import { connect } from 'dva';
-import { Card, Modal, Form, Table, List, Steps, Select, Button, Input, InputNumber, Icon } from 'antd';
+import { Tooltip, Card, Modal, Form, Table, List, Steps, Select, Button, Input, InputNumber, Icon } from 'antd';
+import Ellipsis from '../../../components/Ellipsis';
 import Selector from '../../../components/AXTableSelector/Selector';
 import FooterToolbar from '../../../components/FooterToolbar';
 import { checkProductType, toDecimal2 } from '../../../utils/utils';
@@ -37,6 +38,8 @@ export default class OrderCreatePage extends Component {
     deliveryInfo: {},
     terminalSelectorDestroy: true,
     goodsSelectorDestroy: true,
+    packageFixModalDestroy: true,
+    packageContent: {},
     width: '100%',
   };
   componentDidMount() {
@@ -132,6 +135,19 @@ export default class OrderCreatePage extends Component {
       goodsSelectorDestroy: true,
     });
   };
+  // 套餐包详情模态框展现
+  handlePackageFixModalShow = (contents) => {
+    this.setState({
+      packageFixModalDestroy: false,
+      packageContent: contents,
+    });
+  };
+  // 套餐包详情模态框取消
+  handlePackageFixCancel = () => {
+    this.setState({
+      packageFixModalDestroy: true,
+    });
+  };
   // 响应价格变化
   handleGoodsSelectChange = (rowIndex, goodsId) => {
     const { selectedGoods } = this.state;
@@ -219,8 +235,8 @@ export default class OrderCreatePage extends Component {
 
   render() {
     const {
-      currentStep, selectedGoods, adjustPrice = 0, deliveryInfo,
-      terminalSelectorDestroy, goodsSelectorDestroy,
+      currentStep, selectedGoods, adjustPrice = 0, deliveryInfo, packageContent,
+      terminalSelectorDestroy, goodsSelectorDestroy, packageFixModalDestroy,
     } = this.state;
     const {
       code, name, campusName, merchantName, contactName, mobile, address,
@@ -329,11 +345,11 @@ export default class OrderCreatePage extends Component {
       };
       const newRows = [];
       for (let row of rows) {
-        // 默认数量
+        // 设置默认数量
         if (!row.quantity) {
           row.quantity = 1;
         }
-        // 默认选中价格
+        // 设置默认选中价格
         if (!row.selectedGoodsId) {
           const price = findSelectedPrice(row.goods);
           row = { ...row, ...price };
@@ -382,28 +398,121 @@ export default class OrderCreatePage extends Component {
           </Modal>
         );
       };
+      // 修正课程包内配套数量
+      const getPackageFixModal = () => {
+        const { products } = packageContent;
+        const packageColumns = [{
+          title: '产品编号',
+          dataIndex: 'code',
+          key: 'code',
+          width: '20%',
+        }, {
+          title: '产品名称',
+          dataIndex: 'name',
+          key: 'name',
+          width: '40%',
+        }, {
+          title: '产品类型',
+          dataIndex: 'type',
+          key: 'type',
+          render: text => checkProductType(text),
+          width: '20%',
+          align: 'center',
+        }, {
+          title: '单价',
+          dataIndex: 'merchantPrice',
+          key: 'merchantPrice',
+          width: '20%',
+          render: (text, record) => {
+            if (record.type !== Hotax.PRODUCT_SUPPORT) {
+              return '-';
+            }
+            return `¥${text}/件`;
+          },
+          align: 'center',
+        /* TODO: 套餐包内配套数量修改
+        }, {
+          title: '数量',
+          dataIndex: 'quantity',
+          key: 'quantity',
+          width: '20%',
+          render: (text, record, index) => {
+            if (record.type !== Hotax.PRODUCT_SUPPORT) {
+              return '-';
+            }
+            return (
+              <Input
+                style={{ width: 150 }}
+                addonBefore={
+                  <Icon
+                    type="minus"
+                    className={styles.icon}
+                    onClick={() => this.handleQuantityStepChange(index, 'minus', record.id)}
+                  />
+                }
+                addonAfter={
+                  <Icon
+                    type="plus"
+                    className={styles.icon}
+                    onClick={() => this.handleQuantityStepChange(index, 'plus', record.id)}
+                  />
+                }
+                value={text}
+                onChange={e => this.handleQuantityInputChange(index, e, record.id)}
+              />
+            );
+          },
+          align: 'center',
+        */
+        }];
+        return (
+          <Modal
+            visible
+            width={1100}
+            title="套餐包详情"
+            maskClosable={false}
+            onCancel={this.handlePackageFixCancel}
+          >
+            <Table
+              bordered
+              pagination={false}
+              columns={packageColumns}
+              dataSource={products}
+              rowKey={record => record.id}
+              scroll={{ y: 500 }}
+            />
+          </Modal>
+        );
+      };
       const goodsColumns = [{
         title: '商品编号',
         dataIndex: 'code',
         key: 0,
-        render: text => (
-          <a>{text}</a>
-        ),
-        width: '16%',
+        render: (text, record) => {
+          if (record.type === Hotax.PRODUCT_PACKAGE) {
+            return (
+              <Tooltip placement="top" title="点击查看套餐包内容">
+                <a className="a-link" onClick={() => this.handlePackageFixModalShow(record)}>{text}</a>
+              </Tooltip>
+            );
+          }
+          return text;
+        },
+        width: '17%',
       }, {
         title: '商品名称',
         dataIndex: 'name',
         key: 1,
+        width: '28%',
         render: text => (
-          <a>{text}</a>
+          <Ellipsis tooltip lines={1}>{text}</Ellipsis>
         ),
-        width: '24%',
       }, {
         title: '商品类型',
         key: 2,
         dataIndex: 'type',
         render: text => checkProductType(text),
-        width: '15%',
+        width: '12%',
         align: 'center',
       }, {
         title: '单价',
@@ -435,7 +544,7 @@ export default class OrderCreatePage extends Component {
         title: '数量',
         key: 4,
         dataIndex: 'quantity',
-        width: '15%',
+        width: '13%',
         render: (text, _, index) => {
           return (
             <Input
@@ -483,6 +592,7 @@ export default class OrderCreatePage extends Component {
             rowKey={record => record.id}
           />
           {!goodsSelectorDestroy && getGoodsModal()}
+          {!packageFixModalDestroy && getPackageFixModal()}
         </Card>
       );
     };

+ 13 - 13
src/routes/Trade/ShopCart/ShopCartList.js

@@ -3,9 +3,9 @@ import moment from 'moment';
 import { connect } from 'dva';
 import { routerRedux } from 'dva/router';
 import { Card, Modal, Form, Button, message } from 'antd';
-import { StandardTableList } from '../../../components/AXList/index';
-import AXRemoteSelect from '../../../components/AXRemoteSelect/index';
-import Ellipsis from '../../../components/Ellipsis/index';
+import { StandardTableList } from '../../../components/AXList';
+import AXRemoteSelect from '../../../components/AXRemoteSelect';
+import Ellipsis from '../../../components/Ellipsis';
 import { addRowKey, renderStatus } from '../../../utils/utils';
 
 const Message = message;
@@ -64,7 +64,7 @@ export default class ShopCartListPage extends Component {
       UIParams: states,
       Queryers: params,
     });
-  }
+  };
   handleModalFilterOperation = () => {
     const { getFieldsValue } = this.props.form;
     const { merchantIds, campusIds } = getFieldsValue();
@@ -77,16 +77,16 @@ export default class ShopCartListPage extends Component {
       },
     });
     this.handleFilterModalDestroy();
-  }
+  };
   handleBatchOperation = () => {
     Message.info('暂不支持批量操作!');
-  }
+  };
   handleFilterModalShow = () => {
     this.setState({ filterModalDestroy: false });
-  }
+  };
   handleFilterModalDestroy = () => {
     this.setState({ filterModalDestroy: true });
-  }
+  };
   handleMerchantRemoteSelectSearch = (value) => {
     this.props.dispatch({
       type: 'merchant/fetchMerchantList',
@@ -95,7 +95,7 @@ export default class ShopCartListPage extends Component {
         name: value,
       },
     });
-  }
+  };
   handleCampusRemoteSelectSearch = (value) => {
     this.props.dispatch({
       type: 'campus/fetchCampusList',
@@ -104,7 +104,7 @@ export default class ShopCartListPage extends Component {
         name: value,
       },
     });
-  }
+  };
   gotoShopCart = ({ id, mobile, contactName, address }) => {
     this.props.dispatch(routerRedux.push({
       pathname: `/trade/shopcart/detail/${id}`,
@@ -113,7 +113,7 @@ export default class ShopCartListPage extends Component {
         deliveryInfo: { mobile, contactName, address },
       },
     }));
-  }
+  };
 
   render() {
     const { loading, fetching1, fetching2, form, campus, merchant, terminal } = this.props;
@@ -124,7 +124,7 @@ export default class ShopCartListPage extends Component {
       return (
         <Ellipsis tooltip lines={1}>{name}</Ellipsis>
       );
-    }
+    };
     const renderOperation = (item) => {
       return (
         <div>
@@ -170,7 +170,7 @@ export default class ShopCartListPage extends Component {
       title: '所属校区',
       key: 3,
       dataIndex: 'campusName',
-      render: (text) => renderCampusName(text),
+      render: text => renderCampusName(text),
       width: '25%',
     }, {
       title: '所属渠道',