فهرست منبع

add cms user manage module;

zhanghe 7 سال پیش
والد
کامیت
5aeac7307b

+ 45 - 0
.roadhogrc.mock.js

@@ -10,8 +10,10 @@ import { wareList } from './mock/ware';
 import { lessonList } from './mock/lesson';
 import { courseList } from './mock/course';
 import { supportList } from './mock/support';
+import { comboList } from './mock/combo';
 import { orderList } from './mock/order';
 import { signature } from './mock/signature';
+import { cmsUserList } from './mock/cmsUser';
 import * as api from './src/utils/api';
 
 // mock数据持久化
@@ -25,8 +27,10 @@ global.wareList = wareList;
 global.lessonList = lessonList;
 global.courseList = courseList;
 global.supportList = supportList;
+global.comboList = comboList;
 global.orderList = orderList;
 global.signature = signature;
+global.cmsUserList = cmsUserList;
 
 // 操作成功响应内容
 const SUCCESS = { code: 200, success: true, message: null };
@@ -318,6 +322,29 @@ const proxy = {
     const { id } = req.params;
     queryOne(global.supportList, id, res);
   },
+  // 课程包
+  [`POST ${api.combo.replace('/:id', '')}`]: (req, res) => {
+    console.log(`[POST][${api.combo}]`, req.body);
+    res.send(create(global.comboList, req.body));
+  },
+  [`DELETE ${api.combo}`]: (req, res) => {
+    console.log(`[DELETE][${api.combo}]`, req.params);
+    const { id } = req.params;
+    remove(global.comboList, id, res);
+  },
+  [`PUT ${api.combo.replace('/:id', '')}`]: (req, res) => {
+    console.log(`[PUT][${api.combo}]`, req.body);
+    res.send(update(global.comboList, req.body));
+  },
+  [`GET ${api.combos}`]: (req, res) => {
+    console.log(`[GET][${api.combos}]`, req.query);
+    res.send(query(global.comboList, req.query));
+  },
+  [`GET ${api.combo}`]: (req, res) => {
+    console.log(`[GET][${api.combo}]`, req.params);
+    const { id } = req.params;
+    queryOne(global.comboList, id, res);
+  },
   // 订单
   [`POST ${api.order.replace('/:id', '')}`]: (req, res) => {
     console.log(`[POST][${api.order}]`, req.body);
@@ -341,6 +368,24 @@ const proxy = {
     const { id } = req.params;
     queryOne(global.orderList, id, res);
   },
+  // cms用户
+  [`POST ${api.cmsUser.replace('/:id', '')}`]: (req, res) => {
+    console.log(`[POST][${api.cmsUser}]`, req.body);
+    res.send(create(global.cmsUserList, req.body));
+  },
+  [`DELETE ${api.cmsUser}`]: (req, res) => {
+    console.log(`[DELETE][${api.cmsUser}]`, req.params);
+    const { id } = req.params;
+    remove(global.cmsUserList, id, res);
+  },
+  [`PUT ${api.cmsUser.replace('/:id', '')}`]: (req, res) => {
+    console.log(`[PUT][${api.cmsUser}]`, req.body);
+    res.send(update(global.cmsUserList, req.body));
+  },
+  [`GET ${api.cmsUsers}`]: (req, res) => {
+    console.log(`[GET][${api.cmsUsers}]`, req.query);
+    res.send(query(global.cmsUserList, req.query));
+  },
 };
 
 // 是否禁用代理

+ 26 - 0
mock/cmsUser.js

@@ -0,0 +1,26 @@
+let cmsUserList = [];
+const merchants = ['贝尔安亲', '好托管', '昂乐'];
+for(let i = 1; i < 100; i++) {
+  cmsUserList.push({
+    id: String(i),
+    name: 'account-name-' + i,
+    nickname: '刘' + i,
+    gender: 0,
+    avatar: null,
+    birthday: '1990-08-20',
+    mobile: '18233167789',
+    mail: 'ceshi@163.com',
+    weChat: '77xx89llcz',
+    qq: '772358716',
+    merchantId: '1',
+    merchantName: merchants[i % 3],
+    domain: 2010,
+    isAdmin: null,
+    status: 'NORMAL',
+    resources: null,
+    gmtCreated: 1513576267000,
+    gmtModified: 1514952474000,
+  });
+}
+
+module.exports = { cmsUserList };

+ 15 - 0
mock/combo.js

@@ -0,0 +1,15 @@
+let comboList = [];
+const statuses = ['NORMAL', 'DEL'];
+for(let i = 1; i < 300; i++) {
+  comboList.push({
+    id: String(i),
+    code: 'combo-test-' + i,
+    name: '课程包-' + i,
+    productList: null,
+    status: statuses[i % 2],
+    gmtCreated: 1512981450000,
+    gmtModified: 1512981450000,
+  });
+}
+
+module.exports = { comboList };

+ 27 - 0
mock/goods.js

@@ -0,0 +1,27 @@
+let goodsList = [];
+let types = ['COURSE', 'SUPPORT', 'PACKAGE'];
+for(let i = 1; i < 500; i++) {
+  goodsList.push({
+    id: String(i),
+    code: 'Goods-test-' + i,
+    type: types[i % 3],
+    merchantId: '87',
+    goods: [{
+      id: '39489198994523',
+      duration: 12000,
+      chargeUnit: '年',
+      cpPrice: 1000.89,
+      merchantPrice: 2000.11,
+      terminalPrice: 3000.22,
+    },{
+      id: '43578294325898',
+      duration: 258998,
+      chargeUnit: '季',
+      cpPrice: 45928,
+      merchantPrice: 777665,
+      terminalPrice: 987343,
+    }],
+  })
+}
+
+module.exports = { goodsList };

+ 25 - 10
src/common/menu.js

@@ -46,11 +46,8 @@ const menuData = [
     icon: 'shop',
     path: 'goods',
     children: [{
-      name: '商品管理',
+      name: '商品列表',
       path: 'item',
-    },{
-      name: '商品包管理',
-      path: 'combo',
     }]
   },{
     name: '终端管理',
@@ -68,21 +65,39 @@ const menuData = [
     icon: 'trademark',
     path: 'order',
   },{
-    name: '销售统计',
-    icon: 'area-chart',
-    path: 'sales',
-  },{
     name: '厂商管理',
     icon: 'team',
     path: 'merchant',
   },{
     name: '账户管理',
     icon: 'user-add',
-    path: 'cms-user',
+    path: 'cms',
+    children: [{
+      name: 'CMS用户',
+      path: 'user',
+    }]
+  },{
+    name: '销售统计',
+    icon: 'area-chart',
+    path: 'sold',
+    children: [{
+      name: '概览',
+      path: 'overview'
+    },{
+      name: '销售详情',
+      path: 'detail',
+    }]
   },{
     name: '行为统计',
     icon: 'scan',
-    path: 'action',
+    path: 'behavior',
+    children: [{
+      name: '概览',
+      path: 'overview',
+    },{
+      name: '统计详情',
+      path: 'detail',
+    }]
   },
 ];
 

+ 14 - 0
src/common/router.js

@@ -125,6 +125,17 @@ export const getRouterData = (app) => {
       component: dynamicWrapper(app, ['support/detail', 'resource', 'support/support'], () => import('../routes/Support/detail')),
       name: '修改配套',
     },
+    '/product/package': {
+      component: dynamicWrapper(app, ['combo/combo'], () => import('../routes/Combo')),
+    },
+    // '/product/package/add': {
+    //   component: dynamicWrapper(app, ['combo/detail', 'support/support'], () => import('../routes/Combo/detail')),
+    //   name: '添加课程包',
+    // },
+    // '/product/package/edit/:id': {
+    //   component: dynamicWrapper(app, ['combo/detail', 'support/support'], () => import('../routes/Combo/detail')),
+    //   name: '修改课程包',
+    // },
     '/order': {
       component: dynamicWrapper(app, ['order/order'], () => import('../routes/Order')),
     },
@@ -146,6 +157,9 @@ export const getRouterData = (app) => {
     '/user/login': {
       component: dynamicWrapper(app, ['login'], () => import('../routes/Login')),
     },
+    '/cms/user': {
+      component: dynamicWrapper(app, ['cmsUser'], () => import('../routes/CmsUser')),
+    },
   };
   // Get name from ./menu.js or just set it in the router data.
   const menuData = getFlatMenuData(getMenuData());

+ 113 - 0
src/models/cmsUser.js

@@ -0,0 +1,113 @@
+import { query, create, update, remove } from '../services/cmsUser';
+import modelExtend from 'dva-model-extend';
+import queryString from 'query-string';
+import { message } from 'antd';
+import { pageModel } from './common';
+import config from '../utils/config';
+import { checkSearchParams } from '../utils/utils';
+
+export default modelExtend(pageModel, {
+  namespace: 'cmsUser',
+
+  state: {
+    currentItem: {},
+    itemLoading: false,
+    listLoading: false,
+    modalVisible: false,
+    modalType: 'create',
+  },
+
+  subscriptions: {
+    setup({ dispatch, history }) {
+      history.listen((location) => {
+        if (location.pathname === '/cms/user') {
+          const payload = checkSearchParams(queryString.parse(location.search));
+          dispatch({
+            type: 'query',
+            payload,
+          });
+        }
+      });
+    }
+  },
+
+  effects: {
+    * query ({ payload = {} }, { call, put }) {
+      yield put({ type: 'changeLoading', payload: { listLoading: true }});
+      const { data, success } = yield call(query, payload);
+      if (success) {
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: data.list,
+            pagination: {
+              current: Number(payload.pageNo) || 1,
+              pageSize: Number(payload.pageSize) || config.pageSize,
+              total: data.totalSize,
+            }
+          }
+        });
+      }
+      yield put({ type: 'changeLoading', payload: { listLoading: false }});
+    },
+    * create ({ payload, callback }, { call, put }) {
+      const { data, success } = yield call(create, payload);
+      if (success) {
+        message.success('创建成功!');
+        if (callback) {
+          callback();
+        }
+      } else {
+        message.error('创建失败!');
+      }
+    },
+    * update ({ payload, callback }, { call, put }) {
+      const { data, success } = yield call(update, payload);
+      if (success) {
+        yield put({ type: 'hideModal' });
+        message.success('更新成功!');
+        if (callback) {
+          callback();
+        }
+      } else {
+        message.error('更新失败!');
+      }
+    },
+    * delete ({ payload, callback }, { call, put }) {
+      const { data, success } = yield call(remove, payload);
+      if (success) {
+        message.success('删除成功!');
+        if (callback) {
+          callback();
+        }
+      } else {
+        message.error('删除失败!');
+      }
+    },
+    * recover ({ payload, callback }, { call, put }) {
+      const { data, success } = yield call(update, payload);
+      if (success) {
+        message.success('恢复成功!');
+        if (callback) {
+          callback();
+        }
+      } else {
+        message.error('恢复失败!');
+      }
+    },
+  },
+
+  reducers: {
+    changeLoading(state, { payload }) {
+      return { ...state, ...payload };
+    },
+
+    showModal(state, { payload }) {
+      return { ...state, ...payload, modalVisible: true };
+    },
+
+    hideModal(state) {
+      return { ...state, modalVisible: false };
+    },
+  }
+})

+ 113 - 0
src/models/combo/combo.js

@@ -0,0 +1,113 @@
+import { query, create, update, remove } from '../../services/combo';
+import modelExtend from 'dva-model-extend';
+import queryString from 'query-string';
+import { message } from 'antd';
+import { pageModel } from '../common';
+import { pageSize } from '../../utils/config';
+import { checkSearchParams } from '../../utils/utils';
+
+export default modelExtend(pageModel, {
+  namespace: 'combo',
+
+  state: {
+    currentItem: {},
+    itemLoading: false,
+    listLoading: false,
+    modalVisible: false,
+    modalType: 'create',
+  },
+
+  subscriptions: {
+    setup({ dispatch, history }) {
+      history.listen((location) => {
+        if (location.pathname === '/product/package') {
+          const payload = checkSearchParams(queryString.parse(location.search));
+          dispatch({
+            type: 'query',
+            payload,
+          });
+        }
+      });
+    }
+  },
+
+  effects: {
+    * query ({ payload = {} }, { call, put }) {
+      yield put({ type: 'changeLoading', payload: { listLoading: true }});
+      const { data, success } = yield call(query, payload);
+      if (success) {
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: data.list,
+            pagination: {
+              current: Number(payload.pageNo) || 1,
+              pageSize: Number(payload.pageSize) || pageSize,
+              total: data.totalSize,
+            }
+          }
+        });
+      }
+      yield put({ type: 'changeLoading', payload: { listLoading: false }});
+    },
+    * create ({ payload, callback }, { call, put }) {
+      const { data, success } = yield call(create, payload);
+      if (success) {
+        message.success('创建成功!');
+        if (callback) {
+          callback();
+        }
+      } else {
+        message.error('创建失败!');
+      }
+    },
+    * update ({ payload, callback }, { call, put }) {
+      const { data, success } = yield call(update, payload);
+      if (success) {
+        yield put({ type: 'hideModal' });
+        message.success('更新成功!');
+        if (callback) {
+          callback();
+        }
+      } else {
+        message.error('更新失败!');
+      }
+    },
+    * delete ({ payload, callback }, { call, put }) {
+      const { data, success } = yield call(remove, payload);
+      if (success) {
+        message.success('删除成功!');
+        if (callback) {
+          callback();
+        }
+      } else {
+        message.error('删除失败!');
+      }
+    },
+    * recover ({ payload, callback }, { call, put }) {
+      const { data, success } = yield call(update, payload);
+      if (success) {
+        message.success('恢复成功!');
+        if (callback) {
+          callback();
+        }
+      } else {
+        message.error('恢复失败!');
+      }
+    },
+  },
+
+  reducers: {
+    changeLoading(state, { payload }) {
+      return { ...state, ...payload };
+    },
+
+    showModal(state, { payload }) {
+      return { ...state, ...payload, modalVisible: true };
+    },
+
+    hideModal(state) {
+      return { ...state, modalVisible: false };
+    },
+  }
+})

+ 119 - 0
src/models/combo/detail.js

@@ -0,0 +1,119 @@
+import { queryOne, create, update } from '../../services/combo';
+import { message } from 'antd';
+import pathToRegexp from 'path-to-regexp';
+import { Codes } from '../../utils/config';
+
+export default {
+  namespace: 'comboDetail',
+
+  state: {
+    filters: {},
+    operType: 'create',
+    currentItem: {},
+    courseModalVisible: false,
+    supportModalVisible: false,
+    itemLoading: false,
+  },
+
+  subscriptions: {
+    setup({ dispatch, history }) {
+      history.listen(({ pathname, state, ...rest }) => {
+        const match = pathToRegexp('/product/package/edit/:id').exec(pathname);
+        if (match) {
+          dispatch({ type: 'query', payload: { id: match[1] } });
+          dispatch({ type: 'saveFilters', payload: state });
+          dispatch({ type: 'saveOperType', payload: { operType: 'update' } });
+        }
+        if (pathname === '/product/package/add') {
+          dispatch({ type: 'saveFilters', payload: state });
+          dispatch({ type: 'saveFilters', payload: state });
+          dispatch({ type: 'saveOperType', payload: { operType: 'create' } });
+        }
+      });
+    }
+  },
+
+  effects: {
+    * query ({ payload }, { call, put }) {
+      yield put({ type: 'changeLoading', payload: { itemLoading: true } });
+      const { data, success } = yield call(queryOne, payload);
+      if (success) {
+        yield put({ type: 'querySuccess', payload: { ...data } });
+      }
+      yield put({ type: 'changeLoading', payload: { itemLoading: false } });
+    },
+    * create ({ payload, callback }, { call, put }) {
+      // 创建课程包,默认状态为NORMAL
+      const { data, success } = yield call(create, { ...payload, status: Codes.CODE_NORMAL });
+      if (success) {
+        message.success('创建成功!');
+        yield put({ type: 'clearPage' });
+        if (callback) {
+          callback();
+        }
+      } else {
+        message.error('创建失败!');
+      }
+    },
+    * update ({ payload, callback }, { call, put }) {
+      const { data, success } = yield call(update, payload);
+      if (success) {
+        message.success('更新成功!');
+        yield put({ type: 'clearPage' });
+        if (callback) {
+          callback();
+        }
+      } else {
+        message.error('更新失败!');
+      }
+    }
+  },
+
+  reducers: {
+    changeLoading(state, { payload }) {
+      return { ...state, ...payload };
+    },
+
+    querySuccess(state, { payload }) {
+      return { ...state, currentItem: payload };
+    },
+
+    saveFilters(state, { payload: filters }) {
+      return { ...state, filters };
+    },
+
+    showCourseModal(state, { payload }) {
+      return { ...state, ...payload, courseModalVisible: true };
+    },
+
+    hideCourseModal(state) {
+      return { ...state, courseModalVisible: false };
+    },
+
+    showSupportModal(state, { payload }) {
+      return { ...state, ...payload, supportModalVisible: true };
+    },
+
+    hideSupportModal(state) {
+      return { ...state, supportModalVisible: false };
+    },
+
+    saveOperType(state, { payload }) {
+      return { ...state, ...payload };
+    },
+
+    saveCourseList(state, { payload: { courseList } }) {
+      const currentItem = { ...state.currentItem, courseList };
+      return { ...state, courseModalVisible: false, currentItem };
+    },
+
+    saveSupportList(state, { payload: { supportList } }) {
+      const currentItem = { ...state.currentItem, supportList };
+      return { ...state, supportModalVisible: false, currentItem };
+    },
+
+    clearPage(state) {
+      return { ...state, currentItem: {}, itemLoading: false };
+    }
+  }
+}

+ 1 - 1
src/models/user.js

@@ -1,4 +1,4 @@
-import { query as queryUsers, queryCurrent } from '../services/cmsuser';
+import { query as queryUsers, queryCurrent } from '../services/cmsUser';
 
 export default {
   namespace: 'user',

+ 158 - 0
src/routes/CmsUser/index.js

@@ -0,0 +1,158 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import queryString from 'query-string';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { Card } from 'antd';
+import TableList from './table';
+import ModalForm from './modal';
+import Search from './search';
+import PageHeaderLayout from '../../layouts/PageHeaderLayout';
+
+@connect(state => ({ cmsUser: state.cmsUser }))
+export default class CMSUser extends Component {
+  static propTypes = {
+    cmsUser: PropTypes.object,
+    location: PropTypes.object,
+    dispatch: PropTypes.func,
+  };
+
+  render() {
+    const { location, dispatch, cmsUser } = this.props;
+
+    location.query = queryString.parse(location.search);
+    const { query, pathname } = location;
+    const { field, keyword, ...filters } = query;
+    const { list, listLoading, pagination, currentItem, itemLoading, modalVisible, modalType } = cmsUser;
+
+    // 把携带的参数中空值项删除
+    Object.keys(filters).map(key => { filters[key] ? null : delete filters[key] });
+    // 如果搜索内容不为空则添加进filters中
+    if (field && keyword) {
+      filters.field = field;
+      filters.keyword = keyword;
+    }
+
+    const modalProps = {
+      item: modalType === 'create' ? {} : currentItem,
+      visible: modalVisible,
+      width: 550,
+      maskClosable: false,
+      title: `${modalType === 'create' ? '添加CMS用户' : '编辑CMS用户'}`,
+      wrapClassName: 'vertical-center-modal',
+      onOk (data) {
+        dispatch({
+          type: `cmsUser/${modalType}`,
+          payload: data,
+        });
+      },
+      onCancel () {
+        dispatch({
+          type: 'cmsUser/hideModal',
+        });
+      },
+    };
+
+    const searchProps = {
+      field,
+      keyword,
+      onSearch: (payload) => {
+        if (!payload.keyword.length) {
+          delete payload.field;
+          delete payload.keyword;
+        }
+        dispatch(routerRedux.push({
+          pathname,
+          search: queryString.stringify({
+            ...payload
+          })
+        }));
+      },
+      onAdd: () => {
+        dispatch({
+          type: 'cmsUser/showModal',
+          payload: {
+            modalType: 'create',
+          },
+        });
+      }
+    };
+
+    const listProps = {
+      location,
+      pagination,
+      dataSource: list,
+      loading: listLoading,
+      curStatus: filters.status,
+      onChange: (pagination, filterArgs) => {
+        const getValue = obj => Object.keys(obj).map(key => obj[key]).join(',');
+        const tableFilters = Object.keys(filterArgs).reduce((obj, key) => {
+          const newObj = { ...obj };
+          newObj[key] = getValue(filterArgs[key]);
+          return newObj;
+        }, {});
+
+        const data = { ...filters, ...tableFilters };
+        Object.keys(data).map(key => data[key] ? null : delete data[key]);
+        dispatch(routerRedux.push({
+          pathname,
+          search: queryString.stringify({
+            ...data,
+            pageNo: pagination.current,
+            pageSize: pagination.pageSize,
+          }),
+        }));
+      },
+      // 编辑
+      onEditItem: (item) => {
+        dispatch({
+          type: 'cmsUser/showModal',
+          payload: {
+            modalType: 'update',
+            currentItem: item,
+          },
+        })
+      },
+      // 封禁
+      onDeleteItem: (id) => {
+        dispatch({
+          type: 'cmsUser/delete',
+          payload: id,
+          callback: () => {
+            dispatch(
+              routerRedux.push({
+                pathname,
+                search: queryString.stringify(filters),
+              })
+            );
+          }
+        });
+      },
+      // 解禁
+      onRecoverItem: (payload) => {
+        dispatch({
+          type: 'cmsUser/recover',
+          payload,
+          callback: () => {
+            dispatch(
+              routerRedux.push({
+                pathname,
+                search: queryString.stringify(filters),
+              })
+            );
+          }
+        });
+      }
+    };
+
+    return (
+      <PageHeaderLayout>
+        <Card>
+          <Search { ...searchProps } />
+          <TableList { ...listProps } />
+          {modalVisible && <ModalForm { ...modalProps } />}
+        </Card>
+      </PageHeaderLayout>
+    );
+  }
+}

+ 120 - 0
src/routes/CmsUser/modal.js

@@ -0,0 +1,120 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Form, Cascader, Switch, Radio, Input, Modal, Icon } from 'antd';
+import { Codes, domains } from '../../utils/config';
+
+const RadioGroup = Radio.Group;
+const FormItem = Form.Item
+
+const formItemLayout = {
+  labelCol: {
+    span: 6,
+  },
+  wrapperCol: {
+    span: 15,
+  },
+}
+
+const ModalForm = ({
+  item = {},
+  onOk,
+  form: {
+    getFieldDecorator,
+    getFieldsValue,
+    validateFields,
+  },
+  ...modalProps,
+}) => {
+  const handleOk = () => {
+    validateFields((errors) => {
+      if (errors) return;
+      const data = {
+        ...getFieldsValue(),
+      };
+      item.id ? data.id = item.id : null;
+      onOk(data);
+    })
+  }
+
+  const modalOpts = {
+    ...modalProps,
+    onOk: handleOk,
+  }
+
+  return (
+    <Modal {...modalOpts}>
+      <Form layout="horizontal">
+        <FormItem label="所属平台:" {...formItemLayout}>
+          {getFieldDecorator('domain', {
+            rules: [{ required: true, type: 'number', message: '请选择平台!' }],
+            initialValue: item.domain || Codes.CODE_LJ,
+          })(
+            <RadioGroup>
+            {Object.keys(domains).map(key => <Radio value={Number(key)} key={key}>{domains[Number(key)]}</Radio>)}
+            </RadioGroup>
+          )}
+        </FormItem>
+        <FormItem label="用户名称:" hasFeedback {...formItemLayout}>
+          {getFieldDecorator('name', {
+            rules: [{ required: true, type: 'string', message: '用户名为必填项!' }],
+            initialValue: item.name,
+          })(<Input />)}
+        </FormItem>
+        <FormItem label="用户昵称:" hasFeedback {...formItemLayout}>
+          {getFieldDecorator('nickname', {
+            initialValue: item.nickname,
+          })(<Input />)}
+        </FormItem>
+        <FormItem label="用户性别:" {...formItemLayout}>
+          {getFieldDecorator('gender', {
+            initialValue: item.gender || 0,
+          })(
+            <RadioGroup>
+              <Radio value={0} key={0}>男</Radio>
+              <Radio value={1} key={1}>女</Radio>
+            </RadioGroup>
+          )}
+        </FormItem>
+        <FormItem label="出生日期:" hasFeedback {...formItemLayout}>
+          {getFieldDecorator('birthday', {
+            initialValue: item.birthday,
+          })(<Input />)}
+        </FormItem>
+        <FormItem label="电话:" hasFeedback {...formItemLayout}>
+          {getFieldDecorator('mobile', {
+            initialValue: item.mobile,
+          })(<Input />)}
+        </FormItem>
+        <FormItem label="邮箱:" hasFeedback {...formItemLayout}>
+          {getFieldDecorator('mail', {
+            initialValue: item.mail,
+          })(<Input />)}
+        </FormItem>
+        <FormItem label="QQ:" hasFeedback {...formItemLayout}>
+          {getFieldDecorator('qq', {
+            initialValue: item.qq,
+          })(<Input />)}
+        </FormItem>
+        <FormItem label="微信:" hasFeedback {...formItemLayout}>
+          {getFieldDecorator('weChat', {
+            initialValue: item.weChat,
+          })(<Input />)}
+        </FormItem>
+        <FormItem label="账号状态:" {...formItemLayout}>
+          {getFieldDecorator('status', {
+            valuePropsName: 'checked',
+        })(<Switch defaultChecked={item.status === Codes.CODE_NORMAL ? true : false} checkedChildren="使用中" unCheckedChildren="禁用中" />)}
+        </FormItem>
+      </Form>
+    </Modal>
+  )
+}
+
+ModalForm.propTypes = {
+  item: PropTypes.object,
+  form: PropTypes.object,
+  type: PropTypes.string,
+  onOk: PropTypes.func,
+}
+
+export default Form.create()(ModalForm);

+ 49 - 0
src/routes/CmsUser/search.js

@@ -0,0 +1,49 @@
+import react, { PureComponent } from 'react';
+import PropTypes from 'prop-types';
+import { Button, Form, Row, Col, Icon } from 'antd';
+import DataSearch from '../../components/DataSearch';
+
+const Search = ({
+  field,
+  keyword,
+  onSearch,
+  onAdd
+}) => {
+  const searchGroupProps = {
+    field,
+    keyword,
+    size: 'default',
+    select: true,
+    selectOptions: [{
+      value: 'name', name: '用户名称',
+    },{
+      value: 'nickname', name: '用户昵称',
+    }],
+    selectProps: {
+      defaultValue: field || 'name',
+    },
+    onSearch: (value) => {
+      onSearch(value);
+    },
+  }
+  return (
+    <Row gutter={24}>
+      <Col lg={10} md={12} sm={16} xs={24} style={{ marginBottom: 16 }}>
+        <DataSearch { ...searchGroupProps } />
+      </Col>
+      <Col lg={{ offset: 7, span: 7 }} md={12} sm={8} xs={24} style={{ marginBottom: 16, textAlign: 'right' }}>
+        <Button type="primary" onClick={onAdd}><Icon type="plus-circle" />添加用户</Button>
+      </Col>
+    </Row>
+  )
+}
+
+Search.propTypes = {
+  form: PropTypes.object.isRequired,
+  onSearch: PropTypes.func,
+  onAdd: PropTypes.func,
+  field: PropTypes.string,
+  keyword: PropTypes.string,
+}
+
+export default Form.create()(Search);

+ 117 - 0
src/routes/CmsUser/table.js

@@ -0,0 +1,117 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import moment from 'moment';
+import classnames from 'classnames';
+import queryString from 'query-string';
+import { Avatar, Modal, Badge, Table, Popconfirm, Menu, Icon } from 'antd';
+import AnimTableBody from '../../components/Animation/AnimTableBody';
+import { Codes, statuses, domains } from '../../utils/config';
+import styles from './table.less';
+
+const confirm = Modal.confirm;
+
+const colorList = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae'];
+
+const TableList = ({ onDeleteItem, onEditItem, onRecoverItem, location, curStatus, pagination, ...tableProps }) => {
+  // 从url中提取查询参数
+  location.query = queryString.parse(location.search);
+  const handleOperateItem = (record) => {
+    confirm({
+      title: `您确定要${record.status === Codes.CODE_NORMAL ? '禁用' : '解禁'}该账户?`,
+      onOk () {
+        if (record.status === Codes.CODE_NORMAL) {
+          onDeleteItem({id: record.id});
+        } else if (record.status === Codes.CODE_DELETE) {
+          onRecoverItem({ id: record.id, status: Codes.CODE_NORMAL });
+        }
+      },
+    });
+  }
+  const columns = [{
+    title: '头像',
+    dataIndex: 'avatar',
+    key: 'avatar',
+    render: (text, record) => (
+      <Avatar
+        src={text}
+        style={{ backgroundColor: colorList[Math.floor(Math.random()*(colorList.length -1))], verticalAlign: 'middle' }}
+      >
+        {record.nickname}
+      </Avatar>
+    )
+  },{
+    title: '用户名称',
+    dataIndex: 'name',
+    key: 'name',
+  },{
+    title: '用户昵称',
+    dataIndex: 'nickname',
+    key: 'nickname',
+  },{
+    title: '电话',
+    dataIndex: 'mobile',
+    key: 'mobile',
+  },{
+    title: '平台',
+    dataIndex: 'domain',
+    key: 'domain',
+    render: (text, record) => domains[record.domain]
+  },{
+    title: '状态',
+    dataIndex: 'status',
+    key: 'status',
+    render: (text, record) => {
+      const statusMap = {[Codes.CODE_NORMAL]: 'success', [Codes.CODE_DELETE]: 'error'};
+      return (<Badge status={statusMap[record.status]} text={statuses[record.status]} />);
+    },
+    filters: Object.keys(statuses).map(key => ({ text: statuses[key], value: key })),
+    filterMultiple: false,
+    filteredValue: curStatus,
+  },{
+    title: '添加时间',
+    dataIndex: 'gmtCreated',
+    key: 'gmtCreated',
+    render: (text, record) => (
+      <div>{moment(text).format('YYYY-MM-DD')}</div>
+    )
+  },{
+    title: '操作',
+    dataIndex: 'operation',
+    key: 'operation',
+    render: (text, record) => (
+      <div>
+        <a onClick={() => onEditItem(record)}>编辑</a>
+        <span className={styles.splitLine} />
+        <a onClick={() => handleOperateItem(record)}>{record.status === Codes.CODE_NORMAL ? '禁用' : '解禁'}</a>
+      </div>
+    )
+  }];
+
+  tableProps.pagination = !!pagination && { ...pagination, showSizeChanger: true, showQuickJumper: true, showTotal: total => `共 ${total} 条`};
+  const getBodyWrapperProps = {
+    page: location.query.page,
+    current: tableProps.pagination.current,
+  };
+  const getBodyWrapper = (body) => (<AnimTableBody {...getBodyWrapperProps} body={body} />);
+
+  return (
+    <Table
+      simple
+      bordered
+      { ...tableProps }
+      columns={columns}
+      className={classnames({ [styles.table]: true, [styles.motion]: true })}
+      rowKey={record => record.id}
+      getBodyWrapper={getBodyWrapper}
+    />
+  );
+}
+
+TableList.propTypes = {
+  location: PropTypes.object,
+  onChange: PropTypes.func.isRequired,
+  onDeleteItem: PropTypes.func.isRequired,
+  onEditItem: PropTypes.func.isRequired,
+}
+
+export default TableList;

+ 90 - 0
src/routes/CmsUser/table.less

@@ -0,0 +1,90 @@
+@import "~antd/lib/style/themes/default.less";
+@import "../../utils/utils.less";
+
+.table {
+  :global {
+    .ant-table-tbody > tr > td,
+    .ant-table-thead > tr > th {
+      height: 50px;
+    }
+  }
+
+  &.motion {
+    :global {
+      .ant-table-tbody > tr > td,
+      .ant-table-thead > tr > th {
+        &:nth-child(1) {
+          width: 10%;
+        }
+
+        &:nth-child(2) {
+          width: 16%;
+        }
+
+        &:nth-child(3) {
+          width: 14%;
+        }
+
+        &:nth-child(4) {
+          width: 15%;
+        }
+
+        &:nth-child(5) {
+          width: 10%;
+        }
+
+        &:nth-child(6) {
+          width: 10%;
+        }
+
+        &:nth-child(7) {
+          width: 12%;
+        }
+
+        &:nth-child(8) {
+          width: 13%;
+        }
+      }
+
+      .ant-table-thead {
+        & > tr {
+          transition: none;
+          display: block;
+
+          & > th {
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+          }
+        }
+      }
+
+      .ant-table-tbody {
+        & > tr {
+          transition: none;
+          display: block;
+          border-bottom: 1px solid #f5f5f5;
+
+          & > td {
+            border-bottom: none;
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+          }
+
+          &.ant-table-expanded-row-level-1 > td {
+            height: auto;
+          }
+        }
+      }
+    }
+  }
+}
+
+.splitLine {
+  background: @border-color-split;
+  display: inline-block;
+  margin: 0 8px;
+  width: 1px;
+  height: 12px;
+}

+ 135 - 0
src/routes/Combo/index.js

@@ -0,0 +1,135 @@
+import React, { PureComponent } from 'react';
+import PropTypes from 'prop-types';
+import queryString from 'query-string';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { Card } from 'antd';
+import TableList from './table';
+import Search from './search';
+import PageHeaderLayout from '../../layouts/PageHeaderLayout';
+import { Codes } from '../../utils/config';
+
+@connect(state => ({
+  combo: state.combo,
+}))
+export default class Combo extends PureComponent {
+  static propTypes = {
+    combo: PropTypes.object,
+    location: PropTypes.object,
+    dispatch: PropTypes.func,
+  };
+
+  render() {
+    const { location, dispatch, combo } = this.props;
+
+    location.query = queryString.parse(location.search);
+    const { query, pathname } = location;
+    const { field, keyword, ...filters } = query;
+    const { list, listLoading, pagination, currentItem, itemLoading, modalVisible, modalType } = combo;
+
+    // 把携带的参数中空值项删除
+    Object.keys(filters).map(key => { filters[key] ? null : delete filters[key] });
+    // 如果搜索内容不为空则添加进filters中
+    if (field && keyword) {
+      filters.field = field;
+      filters.keyword = keyword;
+    }
+
+    const searchProps = {
+      field,
+      keyword,
+      onSearch: (payload) => {
+        if (!payload.keyword.length) {
+          delete payload.field;
+          delete payload.keyword;
+        }
+        dispatch(routerRedux.push({
+          pathname,
+          search: queryString.stringify({
+            ...payload
+          })
+        }));
+      },
+      onAdd: () => {
+        dispatch(
+          routerRedux.push({
+            pathname: '/product/package/add',
+            state: filters,
+          })
+        );
+      }
+    };
+
+    const listProps = {
+      pagination,
+      location,
+      dataSource: list,
+      loading: listLoading,
+      curStatus: filters.status,
+      onChange: (pagination, filterArgs) => {
+        const getValue = obj => Object.keys(obj).map(key => obj[key]).join(',');
+        const tableFilters = Object.keys(filterArgs).reduce((obj, key) => {
+          const newObj = { ...obj };
+          newObj[key] = getValue(filterArgs[key]);
+          return newObj;
+        }, {});
+
+        const data = { ...filters, ...tableFilters };
+        Object.keys(data).map(key => data[key] ? null : delete data[key]);
+        dispatch(routerRedux.push({
+          pathname,
+          search: queryString.stringify({
+            ...data,
+            pageNo: pagination.current,
+            pageSize: pagination.pageSize,
+          }),
+        }));
+      },
+      onEditItem: (item) => {
+        dispatch(
+          routerRedux.push({
+            pathname: `/product/package/edit/${item.id}`,
+            state: filters,
+          })
+        );
+      },
+      onDeleteItem: (id) => {
+        dispatch({
+          type: 'combo/delete',
+          payload: id,
+          callback: () => {
+            dispatch(
+              routerRedux.push({
+                pathname,
+                search: queryString.stringify(filters),
+              })
+            );
+          }
+        });
+      },
+      onRecoverItem: (payload) => {
+        dispatch({
+          type: 'combo/recover',
+          payload,
+          callback: () => {
+            dispatch(
+              routerRedux.push({
+                pathname,
+                search: queryString.stringify(filters),
+              })
+            );
+          }
+        });
+      }
+    };
+
+    return (
+      <PageHeaderLayout>
+        <Card>
+          <Search { ...searchProps } />
+          <TableList { ...listProps } />
+        </Card>
+      </PageHeaderLayout>
+    );
+  }
+}

+ 48 - 0
src/routes/Combo/search.js

@@ -0,0 +1,48 @@
+import react, { PureComponent } from 'react';
+import PropTypes from 'prop-types';
+import { Button, Form, Row, Col, Icon } from 'antd';
+import DataSearch from '../../components/DataSearch';
+
+@Form.create()
+export default class Search extends PureComponent {
+  static propTypes = {
+    form: PropTypes.object.isRequired,
+    onSearch: PropTypes.func,
+    onAdd: PropTypes.func,
+    field: PropTypes.string,
+    keyword: PropTypes.string,
+  };
+
+  render() {
+    const { field, keyword, onSearch, onAdd } = this.props;
+
+    const searchGroupProps = {
+      field,
+      keyword,
+      size: 'default',
+      select: true,
+      selectOptions: [{
+        value: 'name', name: '课程包名称', mode: 'input',
+      },{
+        value: 'code', name: '课程包编号', mode: 'input',
+      }],
+      selectProps: {
+        defaultValue: field || 'name',
+      },
+      onSearch: (value) => {
+        onSearch(value);
+      },
+    };
+
+    return (
+      <Row gutter={24}>
+        <Col lg={10} md={12} sm={16} xs={24} style={{ marginBottom: 16 }}>
+          <DataSearch { ...searchGroupProps } />
+        </Col>
+        <Col lg={{ offset: 7, span: 7 }} md={12} sm={8} xs={24} style={{ marginBottom: 16, textAlign: 'right' }}>
+          <Button type="primary" onClick={onAdd}><Icon type="plus-circle" />新建课程包</Button>
+        </Col>
+      </Row>
+    );
+  }
+}

+ 101 - 0
src/routes/Combo/table.js

@@ -0,0 +1,101 @@
+import React, { PureComponent } from 'react';
+import PropTypes from 'prop-types';
+import moment from 'moment';
+import classnames from 'classnames';
+import queryString from 'query-string';
+import { Modal, Table, Menu, Icon, Badge } from 'antd';
+import AnimTableBody from '../../components/Animation/AnimTableBody';
+import styles from './table.less';
+import { statuses, Codes } from '../../utils/config';
+
+const confirm = Modal.confirm;
+
+export default class TableList extends PureComponent {
+  static propTypes = {
+    location: PropTypes.object,
+    onChange: PropTypes.func.isRequired,
+    onDeleteItem: PropTypes.func.isRequired,
+    onEditItem: PropTypes.func.isRequired,
+  };
+
+  handleOperateItem = (record) => {
+    const { onDeleteItem, onRecoverItem } = this.props;
+    confirm({
+      title: `您确定要${record.status === Codes.CODE_NORMAL ? '删除' : '恢复'}该课程包?`,
+      onOk () {
+        if (record.status === Codes.CODE_NORMAL) {
+          onDeleteItem({id: record.id});
+        } else if (record.status === Codes.CODE_DELETE) {
+          onRecoverItem({ id: record.id, status: Codes.CODE_NORMAL });
+        }
+      },
+    })
+  }
+
+  render() {
+    const { curStatus, onDeleteItem, onRecoverItem, onEditItem, location, pagination, ...tableProps } = this.props;
+
+    const columns = [{
+      title: '课程包编号',
+      dataIndex: 'code',
+      key: 'code',
+    },{
+      title: '课程包名称',
+      dataIndex: 'name',
+      key: 'name',
+    },{
+      title: '状态',
+      dataIndex: 'status',
+      key: 'status',
+      render: (text, record) => {
+        const statusMap = {[Codes.CODE_NORMAL]: 'success', [Codes.CODE_DELETE]: 'error'};
+        return (<Badge status={statusMap[record.status]} text={statuses[record.status]} />);
+      },
+      filters: Object.keys(statuses).map(key => ({ text: statuses[key], value: key })),
+      filterMultiple: false,
+      filteredValue: [curStatus],
+    },{
+      title: '添加时间',
+      dataIndex: 'gmtCreated',
+      key: 'gmtCreated',
+      render: (text, record) => (
+        <div>{moment(text).format('YYYY-MM-DD')}</div>
+      )
+    },{
+      title: '操作',
+      dataIndex: 'operation',
+      key: 'operation',
+      render: (text, record) => (
+        <div>
+          <a onClick={() => onEditItem(record)}>编辑</a>
+          <span className={styles.splitLine} />
+            <a onClick={() => this.handleOperateItem(record)}>{record.status === Codes.CODE_NORMAL ? '删除' : '恢复'}</a>
+        </div>
+      )
+    }];
+
+    // 数据table列表表头的筛选按钮点击重置后status值为空,此时删除该参数
+    columns.map(item => {
+      item.dataIndex === 'status' && !curStatus ? delete item.filteredValue : null;
+    });
+
+    tableProps.pagination = !!pagination && { ...pagination, showSizeChanger: true, showQuickJumper: true, showTotal: total => `共 ${total} 条`};
+    const getBodyWrapperProps = {
+      page: location.query.page,
+      current: tableProps.pagination.current,
+    };
+    const getBodyWrapper = (body) => (<AnimTableBody {...getBodyWrapperProps} body={body} />);
+
+    return (
+      <Table
+        simple
+        bordered
+        { ...tableProps }
+        columns={columns}
+        className={classnames({ [styles.table]: true, [styles.motion]: true })}
+        rowKey={record => record.id}
+        getBodyWrapper={getBodyWrapper}
+      />
+    );
+  }
+}

+ 78 - 0
src/routes/Combo/table.less

@@ -0,0 +1,78 @@
+@import "~antd/lib/style/themes/default.less";
+@import "../../utils/utils.less";
+
+.table {
+  :global {
+    .ant-table-tbody > tr > td,
+    .ant-table-thead > tr > th {
+      height: 50px;
+    }
+  }
+
+  &.motion {
+    :global {
+      .ant-table-tbody > tr > td,
+      .ant-table-thead > tr > th {
+        &:nth-child(1) {
+          width: 20%;
+        }
+
+        &:nth-child(2) {
+          width: 20%;
+        }
+
+        &:nth-child(3) {
+          width: 20%;
+        }
+
+        &:nth-child(4) {
+          width: 20%;
+        }
+
+        &:nth-child(5) {
+          width: 20%;
+        }
+      }
+
+      .ant-table-thead {
+        & > tr {
+          transition: none;
+          display: block;
+
+          & > th {
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+          }
+        }
+      }
+
+      .ant-table-tbody {
+        & > tr {
+          transition: none;
+          display: block;
+          border-bottom: 1px solid #f5f5f5;
+
+          & > td {
+            border-bottom: none;
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+          }
+
+          &.ant-table-expanded-row-level-1 > td {
+            height: auto;
+          }
+        }
+      }
+    }
+  }
+}
+
+.splitLine {
+  background: @border-color-split;
+  display: inline-block;
+  margin: 0 8px;
+  width: 1px;
+  height: 12px;
+}

+ 2 - 2
src/routes/Course/detail/index.js

@@ -282,7 +282,7 @@ export default class CourseDetail extends PureComponent {
                   initialValue: digest,
                 })(<TextArea />)}
               </FormItem>
-              <FormItem label="课程封面" {...formItemLayout}>
+              <FormItem label="封面图片" {...formItemLayout}>
                 <Button onClick={() => this.handleModalShow('cvImgBtn')} type="primary" icon="select" size="small">选择</Button>
                 {cvImg.url === undefined ? null :
                   <Card
@@ -293,7 +293,7 @@ export default class CourseDetail extends PureComponent {
                   >
                   </Card>}
               </FormItem>
-              <FormItem label="课程底图" {...formItemLayout}>
+              <FormItem label="背景图片" {...formItemLayout}>
                 <Button onClick={() => this.handleModalShow('bgImgBtn')} type="primary" icon="select" size="small">选择</Button>
                 {bgImg.url === undefined ? null :
                   <Card

+ 1 - 3
src/routes/Course/index.js

@@ -9,9 +9,7 @@ import Search from './search';
 import PageHeaderLayout from '../../layouts/PageHeaderLayout';
 import { Codes } from '../../utils/config';
 
-@connect(state => ({
-  course: state.course,
-}))
+@connect(state => ({ course: state.course }))
 export default class Course extends PureComponent {
   static propTypes = {
     course: PropTypes.object,

+ 1 - 1
src/routes/Course/search.js

@@ -40,7 +40,7 @@ export default class Search extends PureComponent {
           <DataSearch { ...searchGroupProps } />
         </Col>
         <Col lg={{ offset: 7, span: 7 }} md={12} sm={8} xs={24} style={{ marginBottom: 16, textAlign: 'right' }}>
-          <Button type="ghost" onClick={onAdd}><Icon type="plus-circle" />新建课程</Button>
+          <Button type="primary" onClick={onAdd}><Icon type="plus-circle" />新建课程</Button>
         </Col>
       </Row>
     );

+ 1 - 1
src/routes/Lesson/search.js

@@ -40,7 +40,7 @@ export default class Search extends PureComponent {
           <DataSearch { ...searchGroupProps } />
         </Col>
         <Col lg={{ offset: 7, span: 7 }} md={12} sm={8} xs={24} style={{ marginBottom: 16, textAlign: 'right' }}>
-          <Button type="ghost" onClick={onAdd}><Icon type="plus-circle" />添加</Button>
+          <Button type="primary" onClick={onAdd}><Icon type="plus-circle" />新建课</Button>
         </Col>
       </Row>
     );

+ 1 - 1
src/routes/Support/search.js

@@ -40,7 +40,7 @@ export default class Search extends PureComponent {
           <DataSearch { ...searchGroupProps } />
         </Col>
         <Col lg={{ offset: 7, span: 7 }} md={12} sm={8} xs={24} style={{ marginBottom: 16, textAlign: 'right' }}>
-          <Button type="ghost" onClick={onAdd}><Icon type="plus-circle" />新建配套</Button>
+          <Button type="primary" onClick={onAdd}><Icon type="plus-circle" />新建配套</Button>
         </Col>
       </Row>
     );

+ 1 - 1
src/routes/Ware/search.js

@@ -40,7 +40,7 @@ export default class Search extends PureComponent {
           <DataSearch { ...searchGroupProps } />
         </Col>
         <Col lg={{ offset: 7, span: 7 }} md={12} sm={8} xs={24} style={{ marginBottom: 16, textAlign: 'right' }}>
-          <Button type="ghost" onClick={onAdd}><Icon type="plus-circle" />添加</Button>
+          <Button type="primary" onClick={onAdd}><Icon type="plus-circle" />新建课件</Button>
         </Col>
       </Row>
     );

+ 33 - 4
src/services/cmsuser.js

@@ -1,11 +1,40 @@
+// import { stringify } from 'qs';
+// import request from '../utils/request';
+// import { users, currentUser } from '../utils/api'
+//
+// export async function query(params) {
+//   return request(`${users}?${stringify(params)}`);
+// }
+//
+// export async function queryCurrent(params) {
+//   return request(`${currentUser}?${stringify(params)}`);
+// }
+//
 import { stringify } from 'qs';
 import request from '../utils/request';
-import { users, currentUser } from '../utils/api'
+import { cmsUsers, cmsUser } from '../utils/api';
 
 export async function query(params) {
-  return request(`${users}?${stringify(params)}`);
+  return request(`${cmsUsers}?${stringify(params)}`);
 }
 
-export async function queryCurrent(params) {
-  return request(`${currentUser}?${stringify(params)}`);
+export async function create(params) {
+  const options = {
+    method: 'POST',
+    body: JSON.stringify(params),
+  };
+  return request(`${cmsUser.replace('/:id', '')}`, options);
+}
+
+export async function update(params) {
+  const options = {
+    method: 'PUT',
+    body: JSON.stringify(params),
+  };
+  return request(`${cmsUser.replace('/:id', '')}`, options);
+}
+
+export async function remove({ id }) {
+  const options = { method: 'DELETE' }
+  return request(`${cmsUser.replace('/:id', `/${id}`)}`, options);
 }

+ 32 - 0
src/services/combo.js

@@ -0,0 +1,32 @@
+import { stringify } from 'qs';
+import request from '../utils/request';
+import { combos, combo } from '../utils/api';
+
+export async function query(params) {
+  return request(`${combos}?${stringify(params)}`);
+}
+
+export async function queryOne({ id }) {
+  return request(`${combo.replace('/:id', `/${id}`)}`);
+}
+
+export async function create(params) {
+  const options = {
+    method: 'POST',
+    body: JSON.stringify(params),
+  };
+  return request(`${combo.replace('/:id', '')}`, options);
+}
+
+export async function update(params) {
+  const options = {
+    method: 'PUT',
+    body: JSON.stringify(params),
+  };
+  return request(`${combo.replace('/:id', '')}`, options);
+}
+
+export async function remove({ id }) {
+  const options = { method: 'DELETE' }
+  return request(`${combo.replace('/:id', `/${id}`)}`, options);
+}

+ 3 - 1
src/utils/api.js

@@ -5,7 +5,7 @@ module.exports = {
   resource: `${config.apiHost}/resource/:id`,
   signature: `${config.apiHost}/oss/signature`,
   cmsUsers: `${config.apiHost}/cms/user/list`,
-  currentCmsUser: `${config.apiHost}/cms/user`,
+  cmsUser: `${config.apiHost}/cms/user/:id`,
   userLogin: `${config.apiHost}/login`,
   userLogout: `${config.apiHost}/logout`,
   campuses: `${config.apiHost}/campus/list`,
@@ -26,6 +26,8 @@ module.exports = {
   course: `${config.apiHost}/course/:id`,
   supports: `${config.apiHost}/support/list`,
   support: `${config.apiHost}/support/:id`,
+  combos: `${config.apiHost}/combo/list`,
+  combo: `${config.apiHost}/combo/:id`,
   orders: `${config.apiHost}/orders`,
   order: `${config.apiHost}/order/:id`,
 };

+ 7 - 2
src/utils/config.js

@@ -13,6 +13,9 @@ Codes.CODE_SALE = 'SALE';
 Codes.CODE_UNPAID = 0;
 Codes.CODE_PAID = 1;
 Codes.CODE_CANCEL = 2;
+Codes.CODE_LJ = 1010;
+Codes.CODE_CP = 2010;
+Codes.CODE_PJ = 3010;
 
 module.exports = {
   // apiHost: 'http://lj.dev.cms.api.com:8500',
@@ -31,9 +34,11 @@ module.exports = {
     [Codes.CODE_SALE]: '出售中',
     [Codes.CODE_DELETE]: '已下架',
   },
+  // 平台代号
   domains: {
-    2010: '供应商',
-    3010: '渠道商',
+    [Codes.CODE_LJ]: '领教方',
+    [Codes.CODE_CP]: '供应商',
+    [Codes.CODE_PJ]: '渠道商',
   },
   // 资源类型
   resourceType: {