Procházet zdrojové kódy

添加订单管理状态修改功能

zhanghe před 6 roky
rodič
revize
04768c02f8

+ 9 - 0
src/common/router.js

@@ -298,6 +298,15 @@ export const getRouterData = (app) => {
     '/trade/order/list': {
       component: dynamicWrapper(app, ['trade'], () => import('../routes/Trade/Order/OrderList')),
     },
+    '/trade/order/create': {
+      component: dynamicWrapper(app, ['trade', 'terminal', 'shelves'], () => import('../routes/Trade/Order/OrderCreate')),
+    },
+    '/trade/order/view/:id': {
+      component: dynamicWrapper(app, ['trade'], () => import('../routes/Trade/Order/OrderDetail')),
+    },
+    '/trade/order/sub/:id': {
+      component: dynamicWrapper(app, ['trade'], () => import('../routes/Trade/Order/SubOrderDetail')),
+    },
     // 统计概览相关路由注册
     '/dashboard/analysis': {
       component: dynamicWrapper(app, ['chart'], () => import('../routes/Dashboard/Analysis')),

+ 7 - 2
src/components/RBList/StandardTableList.js

@@ -19,6 +19,7 @@ export default class StandardTableList extends PureComponent {
     header: false,
     footer: false,
     showStatusSelect: true,
+    rowSelectable: true,
   }
   static propTypes = {
     loading: PropTypes.bool,
@@ -33,6 +34,7 @@ export default class StandardTableList extends PureComponent {
       PropTypes.bool,
     ]),
     showStatusSelect: PropTypes.bool,
+    rowSelectable: PropTypes.bool,
     keepUIState: PropTypes.object,
   }
   constructor(props) {
@@ -320,17 +322,19 @@ export default class StandardTableList extends PureComponent {
       dataSource,
       header,
       footer,
+      rowSelectable,
+      ...restProps
     } = this.props;
     const listFooter = footer ? this.getListFooter : false;
     const listHeader = header ? this.getListHeader : false;
     const { selectedKeys } = this.state;
-    const rowSelection = {
+    const rowSelection = rowSelectable ? {
       selectedKeys,
       onChange: this.handleRowSelectChange,
       getCheckboxProps: record => ({
         disabled: record.disabled,
       }),
-    };
+    } : null;
     return (
       <Table
         bordered={false}
@@ -343,6 +347,7 @@ export default class StandardTableList extends PureComponent {
         dataSource={dataSource}
         pagination={false}
         className={styles.table}
+        {...restProps}
       />
     );
   }

+ 5 - 5
src/components/RBTableSelector/Selector.js

@@ -175,7 +175,7 @@ export default class Selector extends PureComponent {
           </div>
         </div>
       );
-    }
+    };
     const renderSingleListScope = () => {
       return (
         <div className={styles.content}>
@@ -189,7 +189,7 @@ export default class Selector extends PureComponent {
           />
         </div>
       );
-    }
+    };
 
     return (
       <div className={styles.wrapper}>
@@ -204,12 +204,12 @@ export default class Selector extends PureComponent {
           <div className={styles.input}>
             <Input
               suffix={
-                searchValue ?
+                searchValue ? (
                   <Icon
                     type="close-circle"
                     onClick={this.handleInputClearOperation}
                   />
-                : null
+                ) : null
               }
               value={searchValue}
               onChange={this.handleInputChange}
@@ -234,7 +234,7 @@ export default class Selector extends PureComponent {
           </Button>
           <Button
             type="primary"
-            style={{marginLeft: 20}}
+            style={{ marginLeft: 20 }}
             onClick={this.handleFinishOperation}
           >完成
           </Button>

+ 4 - 6
src/components/RBTableSelector/SingleSelectTable.js

@@ -21,13 +21,11 @@ export default class SingleSelectTable extends Component {
   };
   state = {
     selectedRowKey: null,
-    selectedRow: null,
   };
 
   handleRowClick = (record) => {
     this.setState({
-      selectedRow: record,
-      selectedRowKey: record.key
+      selectedRowKey: record.key,
     });
     this.props.onSingleTransfer(record);
   }
@@ -73,10 +71,10 @@ export default class SingleSelectTable extends Component {
       } else {
         return false;
       }
-    }
+    };
     return (
       <Table
-        bordered={true}
+        bordered
         loading={loading}
         footer={() => renderTableFooter(pagination)}
         columns={addColumnOnFirst(columns)}
@@ -86,7 +84,7 @@ export default class SingleSelectTable extends Component {
         onRow={onRowClick}
         onChange={this.handleTableChange}
         className={styles.table}
-        scroll={{y:400}}
+        scroll={{ y: 400 }}
       />
     );
   }

+ 101 - 3
src/models/trade.js

@@ -27,7 +27,6 @@ export default {
     *fetchTerminalBuyMsg({ payload }, { call, put }) {
       const response = yield call(queryTerminalBuyMsg, payload);
       if (response.success) {
-        message.success('加载终端购物车列表成功');
         yield put({
           type: 'querySuccess',
           payload: {
@@ -39,7 +38,6 @@ export default {
     *fetchOrderList({ payload }, { call, put }) {
       const response = yield call(queryOrderList, payload);
       if (response.success) {
-        message.success('加载订单列表成功');
         yield put({
           type: 'querySuccess',
           payload: {
@@ -51,10 +49,110 @@ export default {
         });
       }
     },
-    *createOrderItem({ payload }, { call }) {
+    *fetchOrderItem({ payload }, { call, put }) {
+      const response = yield call(queryOrderItem, payload);
+      if (response.success) {
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            currentItem: response.data,
+          },
+        });
+      }
+    },
+    *createOrderItem({ payload }, { call, put }) {
       const response = yield call(createOrderItem, payload);
       if (response.success) {
         message.success('创建订单成功');
+        const { id } = response.data;
+        yield put(routerRedux.push({
+          pathname: `/trade/order/view/${id}`,
+        }));
+      }
+    },
+    *invalidOrderItem({ payload, states }, { call, put }) {
+      const response = yield call(invalidOrderItem, payload);
+      if (response.success) {
+        message.success('作废订单成功');
+        const { orderId } = states;
+        // 详情页作废成功刷新详情页,列表页支付成功刷新列表页
+        if (orderId) {
+          yield put({
+            type: 'fetchOrderItem',
+            payload: { id: orderId },
+          });
+        } else {
+          yield put({
+            type: 'fetchOrderList',
+            payload: states.Queryers,
+          });
+        }
+      }
+    },
+    *payOrderItem({ payload, states }, { call, put }) {
+      const response = yield call(payOrderItem, payload);
+      if (response.success) {
+        message.success('支付订单成功');
+        const { orderId } = states;
+        // 详情页支付成功刷新详情页,列表页支付成功刷新列表页
+        if (orderId) {
+          yield put({
+            type: 'fetchOrderItem',
+            payload: { id: orderId },
+          });
+        } else {
+          yield put({
+            type: 'fetchOrderList',
+            payload: states.Queryers,
+          });
+        }
+      }
+    },
+    *fetchSubOrderItem({ payload }, { call, put }) {
+      const response = yield call(querySubOrderItem, payload);
+      if (response.success) {
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            currentItem: response.data,
+          },
+        });
+      }
+    },
+    *sendOrderItem({ payload, states }, { call, put }) {
+      const response = yield call(sendOrderItem, payload);
+      if (response.success) {
+        message.success('发货成功');
+        const { orderId } = states;
+        if (orderId) {
+          yield put({
+            type: 'fetchSubOrderItem',
+            payload: { id: orderId },
+          });
+        } else {
+          yield put({
+            type: 'fetchOrderList',
+            payload: states.Queryers,
+          });
+        }
+      }
+    },
+    *receiveOrderItem({ payload, states }, { call, put }) {
+      const response = yield call(receiveOrderItem, payload);
+      if (response.success) {
+        message.success('确认收货成功');
+        const { orderId } = states;
+        if (orderId) {
+          yield put({
+            type: 'fetchSubOrderItem',
+            payload: { id: orderId },
+          });
+        } else {
+          yield put({
+            type: 'fetchOrderList',
+            payload: states.Queryers,
+          });
+        }
       }
     },
   },

+ 676 - 0
src/routes/Trade/Order/OrderCreate.js

@@ -0,0 +1,676 @@
+/* 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 Selector from '../../../components/RBTableSelector/Selector';
+import FooterToolbar from '../../../components/FooterToolbar';
+import { checkProductType, toDecimal2 } from '../../../utils/utils';
+import { ORDER_UNPAID } from '../../../utils/config';
+import styles from './OrderCreate.less';
+
+const formItemLayout = {
+  labelCol: {
+    xs: { span: 24 },
+    sm: { span: 7 },
+  },
+  wrapperCol: {
+    xs: { span: 24 },
+    sm: { span: 14 },
+    md: { span: 10 },
+  },
+};
+
+@connect(({ terminal, shelves, trade, loading }) => ({
+  trade,
+  shelves,
+  terminal,
+  sLoading: loading.models.shelves,
+  tLoading: loading.models.terminal,
+  submitting: loading.models.trade,
+}))
+@Form.create()
+export default class OrderCreatePage extends Component {
+  state = {
+    currentStep: 0,
+    selectedGoods: [],
+    adjustPrice: 0,
+    deliveryInfo: {},
+    terminalSelectorDestroy: true,
+    goodsSelectorDestroy: true,
+    width: '100%',
+  };
+  componentDidMount() {
+    // 监听视图窗口变化
+    window.addEventListener('resize', this.resizeFooterToolbar);
+    this.resizeFooterToolbar();
+  }
+  componentWillUnmount() {
+    window.removeEventListener('resize', this.resizeFooterToolbar);
+  }
+  resizeFooterToolbar = () => {
+    const sider = document.querySelectorAll('.ant-layout-sider')[0];
+    const width = `calc(100% - ${sider.style.width})`;
+    if (this.state.width !== width) {
+      this.setState({ width });
+    }
+  }
+  // 终端模态选择器显示
+  handleTerminalSelectorModalShow = () => {
+    this.setState({
+      terminalSelectorDestroy: false,
+    });
+    this.props.dispatch({
+      type: 'terminal/fetchTerminalList',
+      payload: {},
+    });
+  }
+  // 终端模态选择器完成
+  handleTerminalSelectorFinish = (rows) => {
+    this.setState({
+      terminalSelectorDestroy: true,
+    });
+    if (!rows || !rows.length) {
+      return;
+    }
+    const {
+      id, code, name, campusName, merchantId, merchantName, contactName, mobile, address,
+    } = rows[0];
+    this.setState({
+      deliveryInfo: {
+        id, code, name, campusName, merchantId, merchantName, contactName, mobile, address,
+      },
+    });
+  };
+  // 终端模态选择器取消
+  handleTerminalSelectorCancel = () => {
+    this.setState({
+      terminalSelectorDestroy: true,
+    });
+  };
+  // 终端模态选择器变化
+  handleTerminalSelectorChange = (params) => {
+    this.props.dispatch({
+      type: 'terminal/fetchTerminalList',
+      payload: params,
+    });
+  }
+  // 商品模态选择器展现
+  handleGoodsSelectorModalShow = () => {
+    const { deliveryInfo } = this.state;
+    const { merchantId } = deliveryInfo;
+    this.setState({
+      goodsSelectorDestroy: false,
+    });
+    this.props.dispatch({
+      type: 'shelves/fetchItemList',
+      payload: { merchantId },
+    });
+  };
+  // 商品模态选择器变化
+  handleGoodsSelectorChange = (params) => {
+    const { deliveryInfo } = this.state;
+    const { merchantId } = deliveryInfo;
+    this.props.dispatch({
+      type: 'shelves/fetchItemList',
+      payload: { merchantId, ...params },
+    });
+  };
+  // 商品模态选择器完成
+  handleGoodsSelectorFinish = (rows) => {
+    // 去掉没有定价的商品
+    const newRows = rows.filter((row) => {
+      if (!row.goods || !row.goods.length) {
+        return false;
+      }
+      return true;
+    });
+    this.setState({
+      goodsSelectorDestroy: true,
+      selectedGoods: newRows,
+    });
+  };
+  // 商品模态选择器取消
+  handleGoodsSelectorCancel = () => {
+    this.setState({
+      goodsSelectorDestroy: true,
+    });
+  };
+  // 响应价格变化
+  handleGoodsSelectChange = (rowIndex, goodsId) => {
+    const { selectedGoods } = this.state;
+    const targetRow = selectedGoods[rowIndex];
+    const { goods } = targetRow;
+    const targetGoods = goods.find(goodsItem => goodsItem.id === goodsId);
+    if (targetGoods) {
+      targetRow.selectedGoodsId = targetGoods.id;
+      targetRow.selectedChargeUnit = targetGoods.chargeUnit;
+      targetRow.selectedPrice = targetGoods.merchantPrice;
+      selectedGoods[rowIndex] = targetRow;
+      this.setState({ selectedGoods });
+    }
+  }
+  // 响应数量变化 - 填写
+  handleQuantityInputChange = (rowIndex, e) => {
+    const { selectedGoods } = this.state;
+    const targetRow = selectedGoods[rowIndex];
+    targetRow.quantity = e.target.value;
+    selectedGoods[rowIndex] = targetRow;
+    this.setState({ selectedGoods });
+  }
+  // 响应数量变化 - 加减
+  handleQuantityStepChange = (rowIndex, type) => {
+    const { selectedGoods } = this.state;
+    const targetRow = selectedGoods[rowIndex];
+    const { quantity } = targetRow;
+    if (type === 'plus') {
+      targetRow.quantity = quantity + 1;
+    } else if (type === 'minus' && quantity > 1) {
+      targetRow.quantity = quantity - 1;
+    }
+    selectedGoods[rowIndex] = targetRow;
+    this.setState({ selectedGoods });
+  }
+  // 优惠价格调整
+  handleAdjustPriceChange = (value) => {
+    this.setState({ adjustPrice: value });
+  }
+  // 下一步
+  nextStep = () => {
+    const { currentStep } = this.state;
+    if (currentStep === 0) {
+      this.props.form.validateFieldsAndScroll((error, values) => {
+        if (!error) {
+          this.setState({
+            currentStep: this.state.currentStep + 1,
+            deliveryInfo: {
+              ...this.state.deliveryInfo,
+              ...values,
+            },
+          });
+        }
+      });
+      return;
+    }
+    this.setState({ currentStep: this.state.currentStep + 1 });
+  }
+  // 上一步
+  prevStep = () => {
+    this.setState({ currentStep: this.state.currentStep - 1 });
+  }
+  // 提交订单
+  handleSubmitOrder = (goodsList) => {
+    const { deliveryInfo, adjustPrice } = this.state;
+    const { id, contactName, mobile, address } = deliveryInfo;
+    const goods = goodsList.map(item => ({
+      goodsId: item.selectedGoodsId,
+      quantity: item.quantity,
+    }));
+    const jsonBody = {
+      mobile,
+      address,
+      goods,
+      adjustPrice,
+      uid: id,
+      name: contactName,
+      status: ORDER_UNPAID,
+    };
+    this.props.dispatch({
+      type: 'trade/createOrderItem',
+      payload: jsonBody,
+    });
+  }
+
+  render() {
+    const {
+      currentStep, selectedGoods, adjustPrice = 0, deliveryInfo,
+      terminalSelectorDestroy, goodsSelectorDestroy,
+    } = this.state;
+    const {
+      code, name, campusName, merchantName, contactName, mobile, address,
+    } = deliveryInfo;
+    const { terminal, tLoading, shelves, sLoading, form } = this.props;
+    const { getFieldDecorator } = form;
+
+    // ##### Card1: 终端选择及收货信息填写 #####
+    const terminalInfoCard = () => {
+      const getTerminalModal = () => {
+        return (
+          <Modal
+            visible
+            width={1100}
+            footer={null}
+            title="终端列表"
+            maskClosable={false}
+            onCancel={this.handleTerminalSelectorCancel}
+          >
+            <Selector
+              multiple={false}
+              loading={tLoading}
+              selectorName="Terminal"
+              list={terminal.list}
+              pageNo={terminal.pageNo}
+              pageSize={terminal.pageSize}
+              totalSize={terminal.totalSize}
+              onCancel={this.handleTerminalSelectorCancel}
+              onChange={this.handleTerminalSelectorChange}
+              onFinish={this.handleTerminalSelectorFinish}
+            />
+          </Modal>
+        );
+      };
+      return (
+        <Card title="终端信息" style={{ marginTop: 10, marginBottom: 70 }}>
+          <Form>
+            <Form.Item
+              {...formItemLayout}
+              label={<a onClick={this.handleTerminalSelectorModalShow}>选择终端</a>}
+            >
+              <List
+                bordered
+                size="small"
+                dataSource={[
+                  `终端编号: ${code || ''}`,
+                  `终端名称: ${name || ''}`,
+                  `所属校区: ${campusName || ''}`,
+                  `所属渠道: ${merchantName || ''}`,
+                ]}
+                renderItem={item => <List.Item>{item}</List.Item>}
+              />
+            </Form.Item>
+            <Form.Item label="收货人" {...formItemLayout}>
+              {getFieldDecorator('contactName', {
+                rules: [{ required: true, message: '请填写收货人' }],
+                initialValue: contactName,
+              })(
+                <Input placeholder="请填写" />
+              )}
+            </Form.Item>
+            <Form.Item label="收货地址" {...formItemLayout}>
+              {getFieldDecorator('address', {
+                rules: [{ required: true, message: '请填写收货地址' }],
+                initialValue: address,
+              })(
+                <Input placeholder="请填写" />
+              )}
+            </Form.Item>
+            <Form.Item label="手机号码" {...formItemLayout}>
+              {getFieldDecorator('mobile', {
+                rules: [
+                  {
+                    required: true, message: '请填写联系电话',
+                  }, {
+                    pattern: /^[1][34578][0-9]{9}$/g, message: '请输入11位有效手机号!',
+                  },
+                ],
+                initialValue: mobile,
+              })(
+                <Input placeholder="请填写" />
+              )}
+            </Form.Item>
+          </Form>
+          {!terminalSelectorDestroy && getTerminalModal()}
+        </Card>
+      );
+    };
+
+    // ##### Card2: 商品选择 #####
+    const rowDataFormatter = (rows) => {
+      // 默认选定第一个价格类型
+      const findSelectedPrice = (goodsArr) => {
+        if (!goodsArr) { return; }
+        let selectedGoodsId = null;
+        let selectedChargeUnit = null;
+        let selectedPrice = null;
+        goodsArr.forEach((item, index) => {
+          if (index === 0) {
+            selectedGoodsId = item.id;
+            selectedChargeUnit = item.chargeUnit;
+            selectedPrice = parseInt(item.merchantPrice, 10);
+          }
+        });
+        return { selectedGoodsId, selectedChargeUnit, selectedPrice };
+      };
+      const newRows = [];
+      for (let row of rows) {
+        // 默认数量
+        if (!row.quantity) {
+          row.quantity = 1;
+        }
+        // 默认选中价格
+        if (!row.selectedGoodsId) {
+          const price = findSelectedPrice(row.goods);
+          row = { ...row, ...price };
+        }
+        // 小计
+        row.subTotal = row.quantity * row.selectedPrice;
+        newRows.push(row);
+      }
+      return newRows;
+    };
+    // 按需求处理后的数据
+    const formattedDataSource = rowDataFormatter(selectedGoods);
+    // 计算总价
+    const computeTotalPrice = (rows) => {
+      let sum = 0;
+      rows.forEach((row) => sum += row.subTotal);
+      return sum;
+    };
+    const totalPrice = computeTotalPrice(formattedDataSource);
+    const goodsSelectCard = () => {
+      const getGoodsModal = () => {
+        return (
+          <Modal
+            visible
+            width={1100}
+            footer={null}
+            title="商品列表"
+            maskClosable={false}
+            onCancel={this.handleGoodsSelectorCancel}
+          >
+            <Selector
+              multiple
+              loading={sLoading}
+              selectorName="Product"
+              list={shelves.list}
+              pageNo={shelves.pageNo}
+              pageSize={shelves.pageSize}
+              totalSize={shelves.totalSize}
+              selectedRows={selectedGoods}
+              onCancel={this.handleGoodsSelectorCancel}
+              onChange={this.handleGoodsSelectorChange}
+              onFinish={this.handleGoodsSelectorFinish}
+            />
+          </Modal>
+        );
+      };
+      const goodsColumns = [{
+        title: '商品编号',
+        dataIndex: 'code',
+        key: 0,
+        render: text => (
+          <a>{text}</a>
+        ),
+        width: '16%',
+      }, {
+        title: '商品名称',
+        dataIndex: 'name',
+        key: 1,
+        render: text => (
+          <a>{text}</a>
+        ),
+        width: '24%',
+      }, {
+        title: '商品类型',
+        key: 2,
+        dataIndex: 'type',
+        render: text => checkProductType(text),
+        width: '15%',
+        align: 'center',
+      }, {
+        title: '单价',
+        key: 3,
+        dataIndex: 'goods',
+        render: (goodsArr, record, index) => {
+          if (!goodsArr) {
+            return null;
+          }
+          return (
+            <Select
+              style={{ width: 150 }}
+              value={record.selectedGoodsId}
+              onChange={goodsId => this.handleGoodsSelectChange(index, goodsId)}
+            >
+              {
+                goodsArr.map(item => (
+                  <Select.Option key={item.id} value={item.id}>
+                    {`¥${item.merchantPrice}/${item.chargeUnit}`}
+                  </Select.Option>
+                ))
+              }
+            </Select>
+          );
+        },
+        width: '12%',
+        align: 'center',
+      }, {
+        title: '数量',
+        key: 4,
+        dataIndex: 'quantity',
+        width: '15%',
+        render: (text, _, index) => {
+          return (
+            <Input
+              style={{ width: 150 }}
+              addonBefore={
+                <Icon
+                  type="minus"
+                  className={styles.icon}
+                  onClick={() => this.handleQuantityStepChange(index, 'minus')}
+                />
+              }
+              addonAfter={
+                <Icon
+                  type="plus"
+                  className={styles.icon}
+                  onClick={() => this.handleQuantityStepChange(index, 'plus')}
+                />
+              }
+              value={text}
+              onChange={e => this.handleQuantityInputChange(index, e)}
+            />
+          );
+        },
+        align: 'center',
+      }, {
+        title: '小计',
+        key: 5,
+        dataIndex: 'subTotal',
+        width: '13%',
+        render: text => (
+          <span style={{ fontWeight: 500 }}>{`¥${text}`}</span>
+        ),
+        align: 'center',
+      }];
+      return (
+        <Card
+          title={<a onClick={this.handleGoodsSelectorModalShow}>选择商品</a>}
+          style={{ marginTop: 10, marginBottom: 70 }}
+        >
+          <Table
+            border={false}
+            pagination={false}
+            columns={goodsColumns}
+            dataSource={formattedDataSource}
+            rowKey={record => record.id}
+          />
+          {!goodsSelectorDestroy && getGoodsModal()}
+        </Card>
+      );
+    };
+
+    // ##### Card3: 确认订单 #####
+    const confirmInfoCard = () => {
+      const columns = [{
+        title: '商品名称',
+        dataIndex: 'name',
+        key: 1,
+        width: '20%',
+      }, {
+        title: '商品编号',
+        dataIndex: 'code',
+        key: 2,
+        width: '20%',
+      }, {
+        title: '商品类型',
+        dataIndex: 'type',
+        key: 3,
+        align: 'center',
+        render: text => checkProductType(text),
+        width: '12%',
+      }, {
+        title: '单价',
+        dataIndex: 'selectedPrice',
+        key: 4,
+        align: 'center',
+        render: text => `¥${text}`,
+        width: '12%',
+      }, {
+        title: '数量',
+        dataIndex: 'quantity',
+        key: 5,
+        align: 'center',
+        render: text => `×${text}`,
+        width: '12%',
+      }, {
+        title: '单位',
+        dataIndex: 'selectedChargeUnit',
+        key: 6,
+        align: 'center',
+        width: '12%',
+      }, {
+        title: '小计',
+        dataIndex: 'subTotal',
+        key: 7,
+        align: 'center',
+        render: text => `¥${text}`,
+        width: '12%',
+      }];
+      return (
+        <div>
+          <Card title="商品清单" style={{ marginTop: 10 }}>
+            <Table
+              bordered
+              columns={columns}
+              pagination={false}
+              rowKey={record => record.id}
+              dataSource={formattedDataSource}
+            />
+          </Card>
+          <Card title="收货信息" style={{ marginTop: 10, marginBottom: 70 }}>
+            <List
+              bordered
+              size="small"
+              dataSource={[
+                `终端账号: ${code || ''}`,
+                `收货人: ${contactName || ''}`,
+                `收货地址: ${address || ''}`,
+                `手机号码: ${mobile || ''}`,
+              ]}
+              renderItem={item => <List.Item>{item}</List.Item>}
+            />
+          </Card>
+        </div>
+      );
+    };
+
+    // 步骤条配置
+    const steps = [{
+      title: '选择购买终端',
+      stepKey: 0,
+      icon: 'shop',
+      content: terminalInfoCard(),
+    }, {
+      title: '选择商品',
+      stepKey: 1,
+      icon: 'environment',
+      content: goodsSelectCard(),
+    }, {
+      title: '确认订单',
+      stepKey: 2,
+      icon: 'profile',
+      content: confirmInfoCard(),
+    }];
+
+    return (
+      <div>
+        <Steps current={currentStep}>
+          {steps.map((item, index) =>
+            (
+              <Steps.Step
+                key={item.stepKey}
+                title={item.title}
+                icon={<Icon type={steps[index].icon} />}
+              />
+            )
+          )}
+        </Steps>
+        {steps[currentStep].content}
+        <FooterToolbar
+          extra={
+            <div>
+              <span className={styles.quantity}>
+                已选择&nbsp;
+                <span style={{ color: '#f60', fontWeight: 500 }}>
+                  {selectedGoods.length}
+                </span>
+                &nbsp;件商品
+              </span>
+              <span className={styles.adjustPrice}>
+                <span style={{ color: '#2f7d0d' }}>
+                  优惠:&nbsp;&nbsp;¥&nbsp;
+                </span>
+                <InputNumber
+                  min={0}
+                  max={totalPrice}
+                  size="small"
+                  value={adjustPrice}
+                  onChange={this.handleAdjustPriceChange}
+                />
+              </span>
+              <span className={styles.totalPrice}>
+                总价:&nbsp;&nbsp;&nbsp;
+                <span style={{ color: '#f60', fontSize: 24 }}>
+                  {`¥ ${toDecimal2(totalPrice - adjustPrice)}`}
+                </span>
+              </span>
+            </div>
+          }
+          style={{ width: this.state.width }}
+        >
+          {
+            (currentStep > 0) && (
+              <Button onClick={this.prevStep}>
+                {`上一步: ${steps[currentStep - 1].title}`}
+              </Button>
+            )
+          }
+          {/* 步骤一:选择终端 */}
+          {
+            (currentStep === 0) && (
+              <Button
+                type="primary"
+                disabled={Object.keys(deliveryInfo).length === 0}
+                onClick={this.nextStep}
+              >
+                {`下一步: ${steps[currentStep + 1].title}`}
+              </Button>
+            )
+          }
+          {/* 步骤二:选择产品 */}
+          {
+            (currentStep === 1) && (
+              <Button
+                type="primary"
+                disabled={Object.keys(selectedGoods).length === 0}
+                onClick={this.nextStep}
+              >
+                {`下一步: ${steps[currentStep + 1].title}`}
+              </Button>
+            )
+          }
+          {/* 步骤三:确认订单 */}
+          {
+            (currentStep === 2) && (
+              <Button
+                type="primary"
+                onClick={() => this.handleSubmitOrder(formattedDataSource)}
+              >
+                确认下单
+              </Button>
+            )
+          }
+        </FooterToolbar>
+      </div>
+    );
+  }
+}

+ 22 - 0
src/routes/Trade/Order/OrderCreate.less

@@ -0,0 +1,22 @@
+@import "~antd/lib/style/themes/default.less";
+
+.icon {
+  &:hover {
+    cursor: pointer;
+  }
+}
+
+.quantity {
+  display: inline-block;
+  width: 180px;
+}
+
+.totalPrice {
+  display: inline-block;
+  width: 180px;
+}
+
+.adjustPrice {
+  display: inline-block;
+  width: 180px;
+}

+ 251 - 0
src/routes/Trade/Order/OrderDetail.js

@@ -0,0 +1,251 @@
+/* eslint-disable no-trailing-spaces */
+import React, { Component } from 'react';
+import moment from 'moment';
+import pathToRegexp from 'path-to-regexp';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { Card, Modal, Table, Button } from 'antd';
+import FooterToolbar from '../../../components/FooterToolbar';
+import { ORDER_UNPAID, ORDER_SPLITED } from '../../../utils/config';
+import { renderOrderSplitStatus, renderOrderStatus, renderProductType, provinceCodeToName } from '../../../utils/utils';
+import styles from './OrderDetail.less';
+
+@connect(({ loading, trade }) => ({
+  trade,
+  loading: loading.models.trade,
+}))
+export default class OrderDetail extends Component {
+  componentDidMount() {
+    const match = pathToRegexp('/trade/order/view/:id')
+      .exec(this.props.location.pathname);
+    if (match) {
+      this.props.dispatch({
+        type: 'trade/fetchOrderItem',
+        payload: { id: match[1] },
+      });
+    }
+  }
+  /**
+   * 作废订单
+   */
+  invalidOrders = ({ id, serialNo }) => {
+    Modal.confirm({
+      okText: '确定',
+      cancelText: '取消',
+      title: `你确定要作废编号: ${serialNo}订单?`,
+      onOk: () => {
+        this.props.dispatch({
+          type: 'trade/invalidOrderItem',
+          payload: { id },
+          states: { orderId: id },
+        });
+      },
+    });
+  }
+
+  /**
+   * 支付订单
+   */
+  payOrders = ({ id, serialNo, finalPrice }) => {
+    Modal.confirm({
+      okText: '支付',
+      cancelText: '取消',
+      title: `你确定要支付编号: ${serialNo}订单?`,
+      content: <span style={{ color: '#f00', fontWeight: 'bold' }}>{`金额: ¥${finalPrice}`}</span>,
+      onOk: () => {
+        this.props.dispatch({
+          type: 'trade/payOrderItem',
+          payload: { id },
+          states: { orderId: id },
+        });
+      },
+    });
+  }
+  handlePageBack = () => {
+    this.props.dispatch(routerRedux.push({
+      pathname: '/trade/order/list',
+      state: this.props.location.state,
+    }));
+  }
+
+  render() {
+    const { trade } = this.props;
+    const { currentItem } = trade;
+    const {
+      id,
+      note,
+      goods,
+      serialNo,
+      name,
+      mobile,
+      address,
+      userCode,
+      provinceCode,
+      cityName,
+      zoneName,
+      classroomName,
+      merchantName,
+      splitStatus,
+      gmtCreated,
+      gmtModified,
+      orderStatus,
+      originPrice,
+      adjustPrice,
+      finalPrice,
+    } = currentItem;
+
+    const orderPreviewColumns = [{
+      dataIndex: 'col-1',
+      key: 1,
+      width: '50%',
+    }, {
+      dataIndex: 'col-2',
+      key: 2,
+      width: '50%',
+    }];
+    const orderPreviewData = [{
+      key: 'row-1',
+      'col-1': `订单编号: ${serialNo}`,
+      'col-2': `订单备注: ${note || '无'}`,
+    }, {
+      key: 'row-2',
+      'col-1': `拆分状态: ${renderOrderSplitStatus(splitStatus)}`,
+      'col-2': `拆分原因: ${splitStatus === ORDER_SPLITED ? '商品属性不同或属于不同商家,订单被拆分为多个子订单分开处理或配送' : ''}`,
+    }, {
+      key: 'row-3',
+      'col-1': `创建时间: ${moment(gmtCreated).format('YYYY-MM-DD HH:mm:ss')}`,
+      'col-2': `更新时间: ${moment(gmtModified).format('YYYY-MM-DD HH:mm:ss')}`,
+    }];
+    const userInfoData = [{
+      key: 'row-1',
+      'col-1': `购买用户: ${provinceCodeToName(provinceCode)}${cityName}${zoneName}${classroomName}【${userCode}】`,
+      'col-2': `所属渠道: ${merchantName}`,
+    }, {
+      key: 'row-2',
+      'col-1': `收货人: ${name}`,
+      'col-2': `手机号码: ${mobile}`,
+    }, {
+      key: 'row-3',
+      'col-1': `收货地址: ${address}`,
+    }];
+    const orderDetailColumns = [{
+      title: '产品名称',
+      key: 1,
+      dataIndex: 'name',
+      width: '20%',
+    }, {
+      title: '产品编号',
+      key: 2,
+      dataIndex: 'code',
+      width: '18%',
+    }, {
+      title: '产品类型',
+      key: 3,
+      dataIndex: 'type',
+      render: text => renderProductType(text),
+      width: '16%',
+      align: 'center',
+    }, {
+      title: '数量',
+      key: 4,
+      dataIndex: 'quantity',
+      width: '15%',
+      align: 'center',
+    }, {
+      title: '付款方式',
+      key: 5,
+      dataIndex: 'chargeUnit',
+      width: '16%',
+      align: 'center',
+    }, {
+      title: '金额',
+      key: 6,
+      dataIndex: 'merchantPrice',
+      render: text => `¥ ${text}`,
+      width: '16%',
+      align: 'right',
+    }];
+    return (
+      <div>
+        {/* 该订单的一些基础信息 */}
+        <Card style={{ marginBottom: 70 }}>
+          <Table
+            bordered
+            size="small"
+            pagination={false}
+            showHeader={false}
+            className={styles.orderTable}
+            columns={orderPreviewColumns}
+            dataSource={orderPreviewData}
+            title={() => '订单概要'}
+            footer={() => {
+              return (
+                <div className={styles.previewTableFooter}>
+                  <span>
+                    订单状态:&nbsp;
+                    <a style={{ color: '#f00' }}>
+                      {renderOrderStatus(orderStatus, true)}
+                    </a>
+                  </span>
+                  <span>
+                    {orderStatus === ORDER_UNPAID && (
+                      <span>
+                        <Button
+                          size="small"
+                          type="primary"
+                          onClick={() => this.payOrders({ id, serialNo, finalPrice })}
+                        >立即支付
+                        </Button>
+                        <Button
+                          size="small"
+                          style={{ marginLeft: 10 }}
+                          onClick={() => this.invalidOrders({ id, serialNo })}
+                        >作废订单
+                        </Button>
+                      </span>
+                    )}
+                  </span>
+                </div>
+              );
+            }}
+          />
+          {/* 该订单中包含的商品清单 */}
+          <Table
+            bordered
+            size="small"
+            pagination={false}
+            className={styles.orderTable}
+            columns={orderDetailColumns}
+            dataSource={goods}
+            rowKey={record => record.id}
+            title={() => '订单详情'}
+            footer={() => {
+              return (
+                <div className={styles.detailTableFooter}>
+                  <p>订单总价:&nbsp;<span>{`¥${originPrice}`}</span></p>
+                  <p>优惠金额:&nbsp;<span>{`¥${adjustPrice}`}</span></p>
+                  <p>实付金额:&nbsp;<span>{`¥${finalPrice}`}</span></p>
+                </div>
+              );
+            }}
+          />
+          {/* 该订单的收货信息等内容 */}
+          <Table
+            bordered
+            size="small"
+            pagination={false}
+            showHeader={false}
+            className={styles.orderTable}
+            columns={orderPreviewColumns}
+            dataSource={userInfoData}
+            title={() => '收货信息'}
+          />
+        </Card>
+        {/* 底部工具条 */}
+        <FooterToolbar style={{ width: '100%' }}>
+          <Button type="primary" onClick={this.handlePageBack}>返回订单列表</Button>
+        </FooterToolbar>
+      </div>
+    );
+  }
+}

+ 30 - 0
src/routes/Trade/Order/OrderDetail.less

@@ -0,0 +1,30 @@
+@import "~antd/lib/style/themes/default.less";
+
+.orderTable {
+  margin-bottom: 20px;
+  :global(.ant-table-footer) {
+    background: #f4f5f9;
+    padding: 8px 16px;
+  }
+  :global(.ant-table-title) {
+    background: #f4f5f9;
+    border-left: 3px solid #778 !important;
+  }
+}
+.previewTableFooter {
+  text-align: justify;
+  span {
+    &:last-child {
+      float: right;
+    }
+  }
+}
+.detailTableFooter {
+  text-align: right;
+  & > p > span {
+    color: #f90;
+    font-weight: 400;
+    width: 60px;
+    display: inline-block;
+  }
+}

+ 275 - 46
src/routes/Trade/Order/OrderList.js

@@ -1,67 +1,186 @@
+/* eslint-disable no-trailing-spaces */
 import React, { Component } from 'react';
 import moment from 'moment';
 import { connect } from 'dva';
 import { routerRedux } from 'dva/router';
-import { Card, Button, Popover, message } from 'antd';
+import { Card, Table, Modal, Form, Popover, Divider, Input } from 'antd';
 import { StandardTableList } from '../../../components/RBList/index';
-import { addRowKey, renderOrderStatus, renderOrderSplitStatus, provinceCodeToName } from '../../../utils/utils';
+import { addRowKey, renderOrderStatus, renderGoodsType, provinceCodeToName } from '../../../utils/utils';
+import {
+  ORDER_FORSEND,
+  ORDER_SENT,
+  ORDER_UNPAID,
+  ORDER_PAYOK,
+  ORDER_COMPLETE,
+  ORDER_CANCEL,
+} from '../../../utils/config';
+import styles from './OrderList.less';
 
-const Message = message;
 @connect(({ loading, trade }) => ({
   trade,
   loading: loading.models.trade,
 }))
+@Form.create()
 export default class OrderListPage extends Component {
   constructor(props) {
     super(props);
     const { state } = props.location;
     this.state = {
+      currentTab: 'all',
+      logisticsModalDestroy: true,
       UIParams: (state || {}).UIParams, // 组件的状态参数
       Queryers: (state || {}).Queryers, // 查询的条件参数
     };
   }
   componentDidMount() {
+    const { currentTab } = this.state;
     this.props.dispatch({
       type: 'trade/fetchOrderList',
-      payload: { ...this.state.Queryers },
+      payload: {
+        orderStatus: currentTab,
+        ...this.state.Queryers,
+      },
+    });
+  }
+  handleCardTabChange = (key) => {
+    this.setState({
+      currentTab: key,
+    });
+    this.props.dispatch({
+      type: 'trade/fetchOrderList',
+      payload: { orderStatus: key },
     });
   }
   handleCreateOperation = () => {
+    this.props.dispatch(routerRedux.push({
+      pathname: '/trade/order/create',
+      state: this.state,
+    }));
+  }
+  handleViewOperation = (id) => {
+    this.props.dispatch(routerRedux.push({
+      pathname: `/trade/order/view/${id}`,
+      state: this.state,
+    }));
+  }
+  handleViewSubOrderOperation = (id) => {
+    this.props.dispatch(routerRedux.push({
+      pathname: `/trade/order/sub/${id}`,
+      state: this.state,
+    }));
   }
   handleFilterOperation = (params, states) => {
+    const { currentTab } = this.state;
     this.props.dispatch({
       type: 'trade/fetchOrderList',
-      payload: params,
+      payload: { orderStatus: currentTab, ...params },
     });
     this.setState({
       UIParams: states,
       Queryers: params,
     });
   }
-  handleBatchOperation = () => {
-    Message.info('暂不支持批量操作!');
+  handleLogisticsModalShow = () => {
+    this.setState({ logisticsModalDestroy: false });
+  }
+  handleLogisticsModalHide = () => {
+    this.setState({ logisticsModalDestroy: true });
+  }
+  /**
+   * 立即发货
+   */
+  orderDelivery = ({ id }) => {
+    this.props.form.validateFields((errors, values) => {
+      if (!errors) {
+        this.props.dispatch({
+          type: 'trade/sendOrderItem',
+          payload: { id, ...values },
+          states: { orderId: id},
+        });
+        this.handleLogisticsModalHide();
+      }
+    });
+  }
+  /**
+   * 确认收货
+   */
+  orderReceipt = ({ id, serialNo }) => {
+    Modal.confirm({
+      okText: '确定',
+      cancelText: '取消',
+      title: '你确定用户已收到该订单内的货物?',
+      content: `订单号: ${serialNo}`,
+      onOk: () => {
+        this.props.dispatch({
+          type: 'trade/receiveOrderItem',
+          payload: { id },
+          states: { orderId: id },
+        });
+      },
+    });
+  }
+  /**
+   * 作废订单
+   */
+  invalidOrders = ({ id, serialNo }) => {
+    Modal.confirm({
+      okText: '确定',
+      cancelText: '取消',
+      title: `你确定要作废编号: ${serialNo}订单?`,
+      onOk: () => {
+        this.props.dispatch({
+          type: 'trade/invalidOrderItem',
+          payload: { id },
+          states: this.state,
+        });
+      },
+    });
+  }
+
+  /**
+   * 支付订单
+   */
+  payOrders = ({ id, serialNo, finalPrice }) => {
+    Modal.confirm({
+      okText: '支付',
+      cancelText: '取消',
+      title: `你确定要支付编号: ${serialNo}订单?`,
+      content: <span style={{ color: '#f00', fontWeight: 'bold' }}>{`金额: ¥${finalPrice}`}</span>,
+      onOk: () => {
+        this.props.dispatch({
+          type: 'trade/payOrderItem',
+          payload: { id },
+          states: this.state,
+        });
+      },
+    });
   }
 
   render() {
-    const { loading, trade } = this.props;
+    const { logisticsModalDestroy } = this.state;
+    const { loading, trade, form } = this.props;
+    const { getFieldDecorator } = form;
     const { list, totalSize, pageSize, pageNo } = trade;
 
-    const renderOperation = () => {
+    const renderOperation = ({ orderStatus, id, serialNo, finalPrice }) => {
       return (
-        <div>
-          <Button
-            size="small"
-            type="primary"
-          >详情
-          </Button>
+        <div className={styles.operation}>
+          <a onClick={() => this.handleViewOperation(id)}>详情</a>
+          {orderStatus === ORDER_UNPAID && (
+            <span>
+              <Divider type="vertical" />
+              <a onClick={() => this.payOrders({ id, serialNo, finalPrice })}>支付</a>
+            </span>
+          )}
+          {orderStatus === ORDER_UNPAID && (
+            <span>
+              <Divider type="vertical" />
+              <a onClick={() => this.invalidOrders({ id, serialNo })}>作废</a>
+            </span>
+          )}
         </div>
       );
     };
-    const batchActions = [{
-      key: 'delete',
-      name: '批量清空',
-    }, {
-    }];
     const basicSearch = {
       keys: [{
         name: '订单编号',
@@ -84,11 +203,11 @@ export default class OrderListPage extends Component {
     }, {
       title: '用户信息',
       key: 2,
-      width: '27%',
+      width: '25%',
       render: (_, record) => {
         const { userCode, provinceCode, cityName, zoneName, merchantName } = record;
         return (
-          <div>
+          <div className={styles.user}>
             <p>
               <span>终端编号:&nbsp;</span>
               {userCode}
@@ -111,18 +230,18 @@ export default class OrderListPage extends Component {
       render: (_, record) => {
         const { originPrice, adjustPrice, finalPrice } = record;
         return (
-          <div>
+          <div className={styles.price}>
             <p>
-              <span>初始价格:&nbsp;</span>
-              {originPrice}
+              <span>原价:&nbsp;</span>
+              <a>{`¥${originPrice}`}</a>
             </p>
             <p>
-              <span>优惠价格:&nbsp;</span>
-              {adjustPrice}
+              <span>优惠:&nbsp;</span>
+              <a>{`¥${adjustPrice}`}</a>
             </p>
             <p>
-              <span>实际售价:&nbsp;</span>
-              {finalPrice}
+              <span>实:&nbsp;</span>
+              <a>{`¥${finalPrice}`}</a>
             </p>
           </div>
         );
@@ -143,12 +262,12 @@ export default class OrderListPage extends Component {
                   {name}
                 </p>
                 <p>
-                <span>手机号码:&nbsp;</span>
-                {mobile}
+                  <span>手机号码:&nbsp;</span>
+                  {mobile}
                 </p>
                 <p>
-                <span>收货地址:&nbsp;</span>
-                {address}
+                  <span>收货地址:&nbsp;</span>
+                  {address}
                 </p>
               </div>
             }
@@ -158,37 +277,149 @@ export default class OrderListPage extends Component {
         );
       },
     }, {
-      title: '拆单状态',
-      key: 5,
-      dataIndex: 'splitStatus',
-      render: text => renderOrderSplitStatus(text),
-      width: '8%',
-    }, {
       title: '订单状态',
-      key: 6,
+      key: 5,
       dataIndex: 'orderStatus',
       render: text => renderOrderStatus(text),
       width: '8%',
     }, {
       title: '下单时间',
-      key: 7,
+      key: 6,
       dataIndex: 'gmtCreated',
       render: text => moment(text).format('YYYY-MM-DD HH:mm:ss'),
       width: '16%',
     }, {
       title: '操作',
-      key: 8,
+      key: 7,
       dataIndex: 'operation',
       render: (_, record) => renderOperation(record),
-      width: '5%',
+      width: '15%',
+      align: 'right',
+    }];
+
+    const expandedRowRender = ({ detailList }) => {
+      const renderSubOrderOperation = ({ orderStatus, id, serialNo }) => {
+        return (
+          <div className={styles.operation}>
+            <a onClick={() => this.handleViewSubOrderOperation(id)}>详情</a>
+            {orderStatus === ORDER_FORSEND && (
+              <span>
+                <Divider type="vertical" />
+                <a onClick={this.handleLogisticsModalShow}>发货</a>
+              </span>
+            )}
+            {orderStatus === ORDER_SENT && (
+              <span>
+                <Divider type="vertical" />
+                <a onClick={() => this.orderReceipt({ id, serialNo })}>收货</a>
+              </span>
+            )}
+            {!logisticsModalDestroy && (
+              <Modal
+                visible
+                maskClosable={false}
+                title={`订单号: ${serialNo}`}
+                okText="发货"
+                cancelText="取消"
+                onCancel={this.handleLogisticsModalHide}
+                onOk={() => this.orderDelivery({ id })}
+              >
+                <Form>
+                  <Form.Item
+                    hasFeedback
+                    label="物流单号"
+                    labelCol={{ xs: { span: 24 }, md: { span: 7 } }}
+                    wrapperCol={{ xs: { span: 24 }, md: { span: 14 } }}
+                  >
+                    {getFieldDecorator('trackNo', {
+                      rules: [{ required: true, message: '请填写物流单号!' }],
+                    })(
+                      <Input placeholder="请填写物流单号" />
+                    )}
+                  </Form.Item>
+                </Form>
+              </Modal>
+            )}
+          </div>
+        );
+      };
+      const subOrderColumns = [{
+        title: '子订单编号',
+        key: 1,
+        dataIndex: 'serialNo',
+        width: '20%',
+        align: 'center',
+      }, {
+        title: '商品类型',
+        key: 2,
+        dataIndex: 'type',
+        render: text => renderGoodsType(text),
+        width: '20%',
+        align: 'center',
+      }, {
+        title: '子订单价格',
+        key: 3,
+        dataIndex: 'originPrice',
+        render: text => `¥${text}`,
+        width: '20%',
+        align: 'center',
+      }, {
+        title: '子订单状态',
+        key: 4,
+        dataIndex: 'orderStatus',
+        render: text => renderOrderStatus(text),
+        width: '20%',
+        align: 'center',
+      }, {
+        title: '操作',
+        key: 5,
+        width: '20%',
+        render: (_, record) => renderSubOrderOperation(record),
+      }];
+      return (
+        <Table
+          bordered
+          size="small"
+          pagination={false}
+          rowKey={record => record.id}
+          columns={subOrderColumns}
+          dataSource={detailList}
+        />
+      );
+    };
+
+    const cardTabList = [{
+      key: 'all',
+      tab: '全部状态',
+    }, {
+      key: ORDER_UNPAID,
+      tab: '未支付',
+    }, {
+      key: ORDER_PAYOK,
+      tab: '已支付',
+    }, {
+      key: ORDER_CANCEL,
+      tab: '已作废',
+    }, {
+      key: ORDER_FORSEND,
+      tab: '待发货',
+    }, {
+      key: ORDER_COMPLETE,
+      tab: '已完成',
     }];
+
     return (
-      <Card>
+      <Card
+        tabList={cardTabList}
+        onTabChange={this.handleCardTabChange}
+      >
         <StandardTableList
           columns={columns}
           loading={loading}
+          rowSelectable={false}
           showStatusSelect={false}
           dataSource={addRowKey(list)}
+          expandedRowRender={expandedRowRender}
           header={{
             basicSearch,
             onFilterClick: this.handleFilterOperation,
@@ -196,8 +427,6 @@ export default class OrderListPage extends Component {
           }}
           footer={{
             pagination,
-            batchActions,
-            onBatchClick: this.handleBatchOperation,
           }}
           keepUIState={{ ...this.state.UIParams }}
         />

+ 26 - 0
src/routes/Trade/Order/OrderList.less

@@ -0,0 +1,26 @@
+@import "~antd/lib/style/themes/default.less";
+
+.price > p > span {
+  font-weight: bold;
+}
+
+.price > p > a {
+  color: #f90;
+  &:hover {
+    cursor: unset;
+  };
+}
+
+.user > p > span {
+  font-weight: bold;
+}
+
+.operation {
+  a {
+    font-weight: bold;
+  }
+}
+
+:global(.ant-tabs-tab) {
+  font-size: 14px;
+}

+ 278 - 0
src/routes/Trade/Order/SubOrderDetail.js

@@ -0,0 +1,278 @@
+/* eslint-disable no-trailing-spaces */
+import React, { Component } from 'react';
+import moment from 'moment';
+import pathToRegexp from 'path-to-regexp';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { Card, Modal, Form, Input, Table, Button } from 'antd';
+import FooterToolbar from '../../../components/FooterToolbar';
+import { ORDER_FORSEND, ORDER_SENT } from '../../../utils/config';
+import { renderOrderStatus, renderProductType, provinceCodeToName } from '../../../utils/utils';
+import styles from './OrderDetail.less';
+
+@connect(({ loading, trade }) => ({
+  trade,
+  loading: loading.models.trade,
+}))
+@Form.create()
+export default class SubOrderDetailPage extends Component {
+  state = {
+    logisticsModalDestroy: true,
+  };
+  componentDidMount() {
+    const match = pathToRegexp('/trade/order/sub/:id')
+      .exec(this.props.location.pathname);
+    if (match) {
+      this.props.dispatch({
+        type: 'trade/fetchSubOrderItem',
+        payload: { id: match[1] },
+      });
+    }
+  }
+  handleLogisticsModalShow = () => {
+    this.setState({ logisticsModalDestroy: false });
+  }
+  handleLogisticsModalHide = () => {
+    this.setState({ logisticsModalDestroy: true });
+  }
+  /**
+   * 立即发货
+   */
+  orderDelivery = ({ id }) => {
+    this.props.form.validateFields((errors, values) => {
+      if (!errors) {
+        this.props.dispatch({
+          type: 'trade/sendOrderItem',
+          payload: { id, ...values },
+          states: { orderId: id},
+        });
+        this.handleLogisticsModalHide();
+      }
+    });
+  }
+  /**
+   * 确认收货
+   */
+  orderReceipt = ({ id, serialNo }) => {
+    Modal.confirm({
+      okText: '确定',
+      cancelText: '取消',
+      title: '你确定用户已收到该订单内的货物?',
+      content: `订单号: ${serialNo}`,
+      onOk: () => {
+        this.props.dispatch({
+          type: 'trade/receiveOrderItem',
+          payload: { id },
+          states: { orderId: id },
+        });
+      },
+    });
+  }
+  handlePageBack = () => {
+    this.props.dispatch(routerRedux.push({
+      pathname: '/trade/order/list',
+      state: this.props.location.state,
+    }));
+  }
+
+  render() {
+    const { logisticsModalDestroy } = this.state;
+    const { trade, form } = this.props;
+    const { currentItem } = trade;
+    const { getFieldDecorator } = form;
+    const {
+      id,
+      note,
+      goods,
+      serialNo,
+      name,
+      mobile,
+      address,
+      userCode,
+      provinceCode,
+      cityName,
+      zoneName,
+      classroomName,
+      merchantName,
+      gmtCreated,
+      gmtModified,
+      orderStatus,
+    } = currentItem;
+
+    const orderPreviewColumns = [{
+      dataIndex: 'col-1',
+      key: 1,
+      width: '50%',
+    }, {
+      dataIndex: 'col-2',
+      key: 2,
+      width: '50%',
+    }];
+    const orderPreviewData = [{
+      key: 'row-1',
+      'col-1': `订单编号: ${serialNo}`,
+      'col-2': `订单备注: ${note || '无'}`,
+    }, {
+      key: 'row-2',
+      'col-1': `创建时间: ${moment(gmtCreated).format('YYYY-MM-DD HH:mm:ss')}`,
+      'col-2': `更新时间: ${moment(gmtModified).format('YYYY-MM-DD HH:mm:ss')}`,
+    }];
+    const userInfoData = [{
+      key: 'row-1',
+      'col-1': `购买用户: ${provinceCodeToName(provinceCode)}${cityName}${zoneName}${classroomName}【${userCode}】`,
+      'col-2': `所属渠道: ${merchantName}`,
+    }, {
+      key: 'row-2',
+      'col-1': `收货人: ${name}`,
+      'col-2': `手机号码: ${mobile}`,
+    }, {
+      key: 'row-3',
+      'col-1': `收货地址: ${address}`,
+    }];
+    const orderDetailColumns = [{
+      title: '产品名称',
+      key: 1,
+      dataIndex: 'name',
+      width: '20%',
+    }, {
+      title: '产品编号',
+      key: 2,
+      dataIndex: 'code',
+      width: '18%',
+    }, {
+      title: '产品类型',
+      key: 3,
+      dataIndex: 'type',
+      render: text => renderProductType(text),
+      width: '16%',
+      align: 'center',
+    }, {
+      title: '数量',
+      key: 4,
+      dataIndex: 'quantity',
+      width: '15%',
+      align: 'center',
+    }, {
+      title: '付款方式',
+      key: 5,
+      dataIndex: 'chargeUnit',
+      width: '16%',
+      align: 'center',
+    }, {
+      title: '单价',
+      key: 6,
+      dataIndex: 'merchantPrice',
+      render: text => `¥ ${text}`,
+      width: '16%',
+      align: 'center',
+    }];
+    return (
+      <div>
+        {/* 该订单的一些基础信息 */}
+        <Card>
+          <Table
+            bordered
+            size="small"
+            pagination={false}
+            showHeader={false}
+            className={styles.orderTable}
+            columns={orderPreviewColumns}
+            dataSource={orderPreviewData}
+            title={() => '子订单概要'}
+            footer={() => {
+              return (
+                <div className={styles.previewTableFooter}>
+                  <span>
+                    订单状态:&nbsp;
+                    <a style={{ color: '#f00' }}>
+                      {renderOrderStatus(orderStatus, true)}
+                    </a>
+                  </span>
+                  <span>
+                    {orderStatus === ORDER_FORSEND && (
+                      <span>
+                        <Button
+                          size="small"
+                          type="primary"
+                          onClick={this.handleLogisticsModalShow}
+                        >立即发货
+                        </Button>
+                      </span>
+                    )}
+                  </span>
+                  <span>
+                    {orderStatus === ORDER_SENT && (
+                      <span>
+                        <Button
+                          size="small"
+                          type="primary"
+                          onClick={() => this.orderReceipt({ id, serialNo })}
+                        >确认收货
+                        </Button>
+                      </span>
+                    )}
+                  </span>
+                </div>
+              );
+            }}
+          />
+          {/* 该订单中包含的商品清单 */}
+          <Table
+            bordered
+            size="small"
+            pagination={false}
+            className={styles.orderTable}
+            columns={orderDetailColumns}
+            dataSource={goods}
+            rowKey={record => record.id}
+            title={() => '子订单详情'}
+          />
+          {/* 该订单的收货信息等内容 */}
+          <Table
+            bordered
+            size="small"
+            pagination={false}
+            showHeader={false}
+            className={styles.orderTable}
+            columns={orderPreviewColumns}
+            dataSource={userInfoData}
+            title={() => '收货信息'}
+          />
+          {/* 确认发货弹框-填写物流单号 */}
+          {
+            !logisticsModalDestroy && (
+              <Modal
+                visible
+                maskClosable={false}
+                title={`订单号: ${serialNo}`}
+                okText="发货"
+                cancelText="取消"
+                onCancel={this.handleLogisticsModalHide}
+                onOk={() => this.orderDelivery({ id })}
+              >
+                <Form>
+                  <Form.Item
+                    hasFeedback
+                    label="物流单号"
+                    labelCol={{ xs: { span: 24 }, md: { span: 7 } }}
+                    wrapperCol={{ xs: { span: 24 }, md: { span: 14 } }}
+                  >
+                    {getFieldDecorator('trackNo', {
+                      rules: [{ required: true, message: '请填写物流单号!' }],
+                    })(
+                      <Input placeholder="请填写物流单号" />
+                    )}
+                  </Form.Item>
+                </Form>
+              </Modal>
+            )
+          }
+        </Card>
+        {/* 底部工具条 */}
+        <FooterToolbar style={{ width: '100%' }}>
+          <Button type="primary" onClick={this.handlePageBack}>返回订单列表</Button>
+        </FooterToolbar>
+      </div>
+    );
+  }
+}

+ 3 - 0
src/routes/Trade/ShopCart/ShopCartDetail.js

@@ -233,6 +233,9 @@ export default class ShopCartDetail extends Component {
       key: 3,
       dataIndex: 'goods',
       render: (goodsArr, record, index) => {
+        if (!goodsArr) {
+          return null;
+        }
         return (
           <Select
             style={{ width: 150 }}

+ 6 - 2
src/services/trade.js

@@ -1,13 +1,17 @@
 import { stringify } from 'qs';
 import request from '../utils/request';
-import { api } from '../utils/config';
+import { api, PAGE_SIZE } from '../utils/config';
 
 export async function queryTerminalBuyMsg(params) {
   return request(`${api.shopCart}?${stringify(params)}`);
 }
 
 export async function queryOrderList(params) {
-  return request(`${api.order}?${stringify(params)}`);
+  const newParams = {
+    pageSize: PAGE_SIZE,
+    ...params,
+  };
+  return request(`${api.order}?${stringify(newParams)}`);
 }
 
 export async function queryOrderItem({ id }) {

+ 5 - 0
src/utils/config.js

@@ -21,6 +21,9 @@ const ORDER_SPLITED = 'YES';
 const ORDER_UNSPLIT = 'UN';
 const ORDER_NOSPLIT = 'NO';
 
+const GOODS_VIRTUAL = 'VIRTUAL';
+const GOODS_ENTITY = 'ENTITY';
+
 const DOMAIN_CP = 1010;
 const DOMAIN_LJ = 2010;
 const DOMAIN_PJ = 3010;
@@ -108,6 +111,8 @@ export {
   ORDER_NOSPLIT,
   ORDER_SPLITED,
   ORDER_UNSPLIT,
+  GOODS_ENTITY,
+  GOODS_VIRTUAL,
   PAGE_SIZE,
   DOMAIN_CP,
   DOMAIN_LJ,

+ 53 - 20
src/utils/utils.js

@@ -14,6 +14,8 @@ import {
   ORDER_UNSPLIT,
   ORDER_SPLITED,
   ORDER_NOSPLIT,
+  GOODS_VIRTUAL,
+  GOODS_ENTITY,
   STATUS_NORMAL,
   STATUS_DELETE,
   PRODUCT_COURSE,
@@ -380,26 +382,46 @@ export function checkProductType(typeStr) {
   }
 }
 
-export function renderOrderStatus(status) {
-  switch (status) {
-    case ORDER_UNPAID:
-      return <Badge status="default" text="未支付" />;
-    case ORDER_CANCEL:
-      return <Badge status="error" text="已作废" />;
-    case ORDER_PAYOK:
-      return <Badge status="processing" text="已支付" />;
-    case ORDER_FORSEND:
-      return <Badge status="processing" text="待发货" />;
-    case ORDER_SENT:
-      return <Badge status="processing" text="已发货" />;
-    case ORDER_RECEIVED:
-      return <Badge status="processing" text="已收货" />;
-    case ORDER_COMPLETE:
-      return <Badge status="success" text="已完成" />;
-    case ORDER_REFUND:
-      return <Badge status="warning" text="已退款" />;
-    default:
-      return '未定义';
+export function renderOrderStatus(status, isPlainText) {
+  const map = {
+    [ORDER_UNPAID]: {
+      name: '未支付',
+      type: 'default',
+    },
+    [ORDER_CANCEL]: {
+      name: '已作废',
+      type: 'error',
+    },
+    [ORDER_PAYOK]: {
+      name: '已支付',
+      type: 'processing',
+    },
+    [ORDER_COMPLETE]: {
+      name: '已完成',
+      type: 'success',
+    },
+    [ORDER_FORSEND]: {
+      name: '待发货',
+      type: 'processing',
+    },
+    [ORDER_SENT]: {
+      name: '已发货',
+      type: 'processing',
+    },
+    [ORDER_RECEIVED]: {
+      name: '已收货',
+      type: 'warning',
+    },
+    [ORDER_REFUND]: {
+      name: '退款中',
+      type: 'warning',
+    },
+  };
+  const obj = map[status] || { name: '未定义', type: 'error' };
+  if (isPlainText) {
+    return obj.name;
+  } else {
+    return <Badge status={obj.type} text={obj.name} />;
   }
 }
 
@@ -415,3 +437,14 @@ export function renderOrderSplitStatus(status) {
       return '';
   }
 }
+
+export function renderGoodsType(status) {
+  switch (status) {
+    case GOODS_VIRTUAL:
+      return '虚拟';
+    case GOODS_ENTITY:
+      return '实体';
+    default:
+      return '';
+  }
+}