Sfoglia il codice sorgente

[Add] 增加标签类型管理

zhanghe 6 anni fa
parent
commit
161ca45421

+ 3 - 0
src/common/menu.js

@@ -8,6 +8,9 @@ const menuData = [
     icon: 'tags',
     path: 'tag',
     children: [{
+      name: '标签类型',
+      path: 'tagType',
+    },{
       name: '标签组',
       path: 'tagGroup',
     },{

+ 13 - 2
src/common/router.js

@@ -84,6 +84,17 @@ export const getRouterData = (app) => {
       component: dynamicWrapper(app, ['merchant/detail', 'mproduct/mproduct'], () => import('../routes/Merchant/Edit')),
       name: '编辑厂商',
     },
+    '/tag/tagType': {
+      component: dynamicWrapper(app, ['tagType/tagType'], () => import('../routes/TagType/List')),
+    },
+    '/tag/tagType/add': {
+      component: dynamicWrapper(app, ['tagType/detail'], () => import('../routes/TagType/Edit')),
+      name: '创建标签类型',
+    },
+    '/tag/tagType/edit/:id': {
+      component: dynamicWrapper(app, ['tagType/detail'], () => import('../routes/TagType/Edit')),
+      name: '修改标签类型',
+    },
     '/tag/tagGroup': {
       component: dynamicWrapper(app, ['group/group', 'merchant/merchant'], () => import('../routes/TagGroup/List')),
     },
@@ -99,11 +110,11 @@ export const getRouterData = (app) => {
       component: dynamicWrapper(app, ['tag/tag', 'merchant/merchant'], () => import('../routes/Tag/List')),
     },
     '/tag/tagItem/add': {
-      component: dynamicWrapper(app, ['tag/detail', 'group/group'], () => import('../routes/Tag/Edit')),
+      component: dynamicWrapper(app, ['tag/detail', 'group/group', 'tagType/tagType'], () => import('../routes/Tag/Edit')),
       name: '创建标签',
     },
     '/tag/tagItem/edit/:id': {
-      component: dynamicWrapper(app, ['tag/detail', 'group/group'], () => import('../routes/Tag/Edit')),
+      component: dynamicWrapper(app, ['tag/detail', 'group/group', 'tagType/tagType'], () => import('../routes/Tag/Edit')),
       name: '修改标签',
     },
     '/basic-product/ware': {

+ 89 - 0
src/models/tagType/detail.js

@@ -0,0 +1,89 @@
+import { queryOne, create, update } from '../../services/tagType';
+import pathToRegexp from 'path-to-regexp';
+import { message } from 'antd';
+import { Codes } from '../../utils/config';
+
+export default {
+  namespace: 'tagTypeDetail',
+
+  state: {
+    filters: {},
+    operType: 'create',
+    currentItem: {},
+    modalVisible: false,
+    itemLoading: false,
+  },
+
+  subscriptions: {
+    setup({ dispatch, history }) {
+      history.listen(({ pathname, state }) => {
+        const match = pathToRegexp('/tag/tagType/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 == '/tag/tagType/add') {
+          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 }) {
+      const { data, success } = yield call(create, { ...payload, status: Codes.CODE_NORMAL });
+      if (success) {
+        message.success('创建成功!');
+        if (callback) callback();
+      }
+    },
+    * update ({ payload, callback }, { call, put }) {
+      const { data, success } = yield call(update, payload);
+      if (success) {
+        message.success('修改成功!');
+        if (callback) callback();
+      }
+    }
+  },
+
+  reducers: {
+    changeLoading(state, action) {
+      return { ...state, ...action.payload };
+    },
+    querySuccess(state, action) {
+      const currentItem = action.payload;
+      return { ...state, currentItem };
+    },
+    saveFilters(state, action) {
+      const filters = action.payload;
+      return { ...state, filters };
+    },
+    showModal(state, action) {
+      return { ...state, ...action.payload, modalVisible: true };
+    },
+    hideModal(state, action) {
+      return { ...state, ...action.payload, modalVisible: false };
+    },
+    saveOperType(state, action) {
+      return { ...state, ...action.payload };
+    },
+    saveSortResult(state, action) {
+      const tagList = action.payload.tagList;
+      const currentItem = { ...state.currentItem, tagList };
+      return { ...state, modalVisible: false, currentItem };
+    },
+    initState(state) {
+      return { ...state, currentItem: {}, itemLoading: false };
+    }
+  }
+}

+ 61 - 0
src/models/tagType/tagType.js

@@ -0,0 +1,61 @@
+import { query, create, update, remove } from '../../services/tagType';
+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: 'tagType',
+
+  state: { listLoading: false },
+
+  subscriptions: {
+    setup({ dispatch, history }) {
+      history.listen((location) => {
+        if (location.pathname == '/tag/tagType') {
+          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 }});
+    },
+    * delete ({ payload, callback }, { call, put }) {
+      const { data, success } = yield call(remove, payload);
+      if (success) {
+        message.success('删除成功!');
+        if (callback) callback();
+      }
+    },
+  },
+
+  reducers: {
+    changeLoading(state, { payload }) {
+      return { ...state, ...payload };
+    },
+  }
+})

+ 1 - 0
src/routes/Course/Edit/index.js

@@ -369,6 +369,7 @@ export default class CourseDetail extends PureComponent {
               </Form.Item>
               <Form.Item label="所属供应商:" {...formItemLayout}>
                 {getFieldDecorator('cpId', {
+                  rules: [{ required: true, type: 'string', message: '供应商为必选项!' }],
                   initialValue: cpId,
                 })(
                   <Select placeholder="请选择">{merchant.list.map(item => <Select.Option value={item.id} key={item.id}>{item.name}</Select.Option>)}</Select>

+ 1 - 0
src/routes/Support/Edit/index.js

@@ -346,6 +346,7 @@ export default class SupportDetail extends PureComponent {
               </Form.Item>
               <Form.Item label="所属供应商:" {...formItemLayout}>
                 {getFieldDecorator('cpId', {
+                  rules: [{ required: true, message: '供应商为必选项!' }],
                   initialValue: cpId,
                 })(
                   <Select placeholder="请选择">{merchant.list.map(item => <Select.Option value={item.id} key={item.id}>{item.name}</Select.Option>)}</Select>

+ 25 - 14
src/routes/Tag/Edit/index.js

@@ -11,6 +11,7 @@ import { Codes, itemStatuses, productType } from '../../../utils/config';
 @connect(state => ({
   tagDetail: state.tagDetail,
   group: state.group,
+  tagType: state.tagType,
 }))
 export default class TagDetail extends PureComponent {
   componentDidMount() {
@@ -22,6 +23,13 @@ export default class TagDetail extends PureComponent {
         pageSize: 1000,
       }
     });
+    dispatch({
+      type: 'tagType/query',
+      payload: {
+        pageNo: 1,
+        pageSize: 1000,
+      },
+    });
   }
 
   handleModalShow = () => {
@@ -102,11 +110,10 @@ export default class TagDetail extends PureComponent {
   }
 
   render() {
-    const { dispatch, form, tagDetail, group } = this.props;
+    const { dispatch, form, tagDetail, group, tagType } = this.props;
     const { itemLoading, currentItem, filters, modalVisible } = tagDetail;
     const { getFieldDecorator } = form;
-    const { list } = group;
-    const { productList, name, code, type, groupId } = currentItem;
+    const { productList, name, code, type, groupId, typeCode } = currentItem;
 
     const itemTableColumns = [{
       title: '位置',
@@ -158,21 +165,25 @@ export default class TagDetail extends PureComponent {
                   initialValue: name,
                 })(<Input placeholder="请输入" />)}
               </Form.Item>
-              {/* 标签类型已抽象为标签组
-              <Form.Item label="标签类型:" hasFeedback {...formItemLayout}>
-                {getFieldDecorator('type', {
-                  rules: [{ required: true, type: 'string', message: "编号为必填项!" }],
-                  initialValue: type || Codes.CODE_COURSE,
+              <Form.Item label="标签类型" hasFeedback {...formItemLayout}>
+                {getFieldDecorator('typeCode', {
+                  rules: [{ required: true, type: 'string', message: "标签类型为必选项!" }],
+                  initialValue: typeCode,
                 })(
-                  <Radio.Group>
-                    {Object.keys(tagType).map(key => <Radio value={key} key={key}>{tagType[key]}</Radio>)}
-                  </Radio.Group>
+                  <Select
+                    showSearch
+                    allowClear
+                    placeholder="请输入标签类型编号或者名称进行筛选"
+                    optionFilterProp="children"
+                    filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
+                  >
+                    {tagType.list.map(item => <Select.Option value={item.code} key={item.code}>{`${item.name}`}</Select.Option>)}
+                  </Select>
                 )}
               </Form.Item>
-              */}
               <Form.Item label="所属标签组" hasFeedback {...formItemLayout}>
                 {getFieldDecorator('groupId', {
-                  rules: [{ required: true, type: 'string', message: "标签组为必选项!" }],
+                  rules: [{ required: true, type: 'string', message: "标签类型为必选项!" }],
                   initialValue: groupId,
                 })(
                   <Select
@@ -182,7 +193,7 @@ export default class TagDetail extends PureComponent {
                     optionFilterProp="children"
                     filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                   >
-                    {list.map(item => <Select.Option value={item.id} key={item.id}>{`${item.merchantName}/${item.name}/${item.code}`}</Select.Option>)}
+                    {group.list.map(item => <Select.Option value={item.id} key={item.id}>{`${item.merchantName}/${item.name}/${item.code}`}</Select.Option>)}
                   </Select>
                 )}
               </Form.Item>

+ 8 - 3
src/routes/Tag/List/table.js

@@ -31,10 +31,15 @@ export default class TableList extends PureComponent {
       key: 'name',
       width: '20%',
     },{
+      title: '标签类型',
+      dataIndex: 'typeCode',
+      key: 'typeCode',
+      width: '13%'
+    },{
       title: '所属标签组',
       dataIndex: 'groupName',
       key: 'groupName',
-      width: '20%',
+      width: '13%',
     },{
       title: '渠道名称',
       dataIndex: 'merchantId',
@@ -43,7 +48,7 @@ export default class TableList extends PureComponent {
       filters: merchantList.map(item => ({ text: item.name, value: item.id })),
       filterMultiple: false,
       filteredValue: [curMerchant],
-      width: '17%',
+      width: '15%',
     },{
       title: '状态',
       dataIndex: 'status',
@@ -60,7 +65,7 @@ export default class TableList extends PureComponent {
       render: (text, record) => (
         <div>{moment(text).format('YYYY-MM-DD HH:mm:ss')}</div>
       ),
-      width: '19%',
+      width: '15%',
     },{
       title: '操作',
       dataIndex: 'operation',

+ 184 - 0
src/routes/TagType/Edit/index.js

@@ -0,0 +1,184 @@
+import React, { PureComponent } from 'react';
+import { routerRedux } from 'dva/router';
+import queryString from 'query-string';
+import { connect } from 'dva';
+import { Spin, Badge, Table, Card, Form, Input, Icon, Button, Select } from 'antd';
+import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
+import TagSortModal from './modal';
+import { Codes, statuses, tagType } from '../../../utils/config';
+
+@Form.create()
+@connect(state => ({ tagTypeDetail: state.tagTypeDetail }))
+export default class TagTypeDetail extends PureComponent {
+  handleModalShow = () => {
+    const { dispatch } = this.props;
+    dispatch({ type: 'tagTypeDetail/showModal' });
+  }
+
+  handleModalCancel = () => {
+    const { dispatch } = this.props;
+    dispatch({ type: 'tagTypeDetail/hideModal' });
+  }
+
+  handleModalOk = (data) => {
+    const { dispatch } = this.props;
+    dispatch({
+      type: 'tagTypeDetail/saveSortResult',
+      payload: { tagList: data }
+    });
+  }
+
+  handlePageSubmit = (e) => {
+    e.preventDefault()
+    const {
+      dispatch,
+      form: {
+        validateFields,
+        getFieldsValue,
+      },
+      tagTypeDetail: {
+        operType,
+        currentItem,
+        filters,
+      }
+    } = this.props;
+    validateFields((errors) => {
+      if (errors) return;
+
+      let data = {};
+      if (operType == 'create') {
+        data = {
+          ...getFieldsValue(),
+          status: Codes.CODE_NORMAL
+        };
+      } else if (operType == 'update') {
+        const { id, status, name, code, tagList } = currentItem;
+        data = {
+          id,
+          status,
+          name,
+          code,
+          tagList: (tagList || []).map(item => item.id),
+          ...getFieldsValue(),
+        };
+      }
+
+      dispatch({
+        type: `tagTypeDetail/${operType}`,
+        payload: data,
+        callback: () => {
+          dispatch({ type: 'tagTypeDetail/initState' });
+          dispatch(
+            routerRedux.push({
+              pathname: '/tag/tagType',
+              search: queryString.stringify(filters),
+            })
+          );
+        }
+      })
+    });
+  }
+
+  handlePageCancel = () => {
+    const { dispatch, tagTypeDetail: { filters } } = this.props;
+    dispatch({ type: 'tagTypeDetail/initState' });
+    dispatch(
+      routerRedux.push({
+        pathname: '/tag/tagType',
+        search: queryString.stringify(filters),
+      })
+    );
+  }
+
+  render() {
+    const { dispatch, form, tagTypeDetail } = this.props;
+    const { itemLoading, currentItem, filters, modalVisible, operType } = tagTypeDetail;
+    const { tagList, name, code } = currentItem;
+    const { getFieldDecorator } = form;
+
+    const tagTableColumns = [{
+      title: '位置',
+      dataIndex: 'sort',
+      key: 'sort',
+      render: (text, record, index) => index + 1,
+    },{
+      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]} />);
+      },
+    }];
+
+    const formItemLayout = {
+      labelCol: {
+        span: 7,
+      },
+      wrapperCol: {
+        span: 12,
+      },
+    };
+    const submitFormLayout = {
+      wrapperCol: {
+        xs: { span: 24, offset: 0 },
+        sm: { span: 10, offset: 7 },
+      },
+    };
+
+    return (
+      <PageHeaderLayout>
+        <Spin spinning={itemLoading}>
+          <Card>
+            <Form layout="horizontal" onSubmit={this.handlePageSubmit}>
+              <Form.Item label="标签类型编号:" hasFeedback {...formItemLayout}>
+                {getFieldDecorator('code', {
+                  rules: [{ required: true, type: 'string', message: "编号为必填项!" }],
+                  initialValue: code,
+                })(
+                  <Input disabled={operType === 'update' ? true : false} placeholder="请输入" />
+                )}
+              </Form.Item>
+              <Form.Item label="标签类型名称:" hasFeedback {...formItemLayout}>
+                {getFieldDecorator('name', {
+                  rules: [{ required: true, type: 'string', message: "名称为必填项!" }],
+                  initialValue: name,
+                })(<Input placeholder="请输入" />)}
+              </Form.Item>
+              <Form.Item label="标签排序" {...formItemLayout}>
+                <Button onClick={this.handleModalShow} disabled={!tagList || !tagList.length} type="primary" size="small" icon="edit">排序</Button>
+              </Form.Item>
+              <Form.Item wrapperCol={{ offset: 7, span: 12 }}>
+                <Table
+                  locale={{
+                    emptyText: <span style={{ color: "#C6D0D6" }}>&nbsp;&nbsp;<Icon type="frown-o"/>该标签类型下不包含任何标签,无法排序,请先去关联标签吧!</span>
+                  }}
+                  dataSource={tagList || []}
+                  columns={tagTableColumns}
+                  rowKey={record => record.id}
+                  bordered
+                  pagination={false}
+                />
+              </Form.Item>
+              <Form.Item {...submitFormLayout} style={{ marginTop: 32 }}>
+                <Button onClick={this.handlePageCancel}>取消</Button>
+                <Button type="primary" style={{ marginLeft: 35 }} htmlType="submit">提交</Button>
+              </Form.Item>
+            </Form>
+            <TagSortModal
+              tagList={tagList || []}
+              rowKeyName="id"
+              modalVisible={modalVisible}
+              onCancel={this.handleModalCancel}
+              onOk={this.handleModalOk}
+            />
+          </Card>
+        </Spin>
+      </PageHeaderLayout>
+    );
+  }
+}

+ 50 - 0
src/routes/TagType/Edit/modal.js

@@ -0,0 +1,50 @@
+import React, { PureComponent } from 'react';
+import { Badge } from 'antd';
+import SelectModal from '../../../components/SelectModal';
+import { Codes, tagType, statuses } from '../../../utils/config';
+
+export default class TagSortModal extends PureComponent {
+  render() {
+    const { tagList, modalVisible, rowKeyName, onCancel, onOk } = this.props;
+    const modalProps = {
+      title: '标签排序',
+      maskClosable: false,
+      visible: modalVisible,
+      destroyOnClose: true,
+      onCancel,
+      onOk,
+    };
+    const selTableProps = {
+      operSort: true,
+      tablePagination: false,
+      tableDataSource: tagList,
+      rowKeyName: rowKeyName,
+      tableColumns: [{
+        title: '位置',
+        dataIndex: 'sort',
+        key: 'sort',
+        render: (text, record, index) => index + 1,
+      },{
+        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]} />);
+        },
+      }],
+    };
+
+    return (
+      <SelectModal
+        mode="sort"
+        { ...modalProps }
+        { ...selTableProps }
+      />
+    );
+  }
+}

+ 111 - 0
src/routes/TagType/List/index.js

@@ -0,0 +1,111 @@
+import React, { PureComponent } from 'react';
+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';
+
+@connect(state => ({ tagType: state.tagType }))
+export default class TagType extends PureComponent {
+  render() {
+    const { location, dispatch, tagType } = 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 } = tagType;
+
+    // 把携带的参数中空值项删除
+    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: '/tag/tagType/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: `/tag/tagType/edit/${item.id}`,
+            state: filters,
+          })
+        );
+      },
+      onDeleteItem: (id) => {
+        dispatch({
+          type: 'tagType/delete',
+          payload: id,
+          callback: () => {
+            dispatch(
+              routerRedux.push({
+                pathname,
+                search: queryString.stringify(filters),
+              })
+            );
+          }
+        });
+      },
+    };
+
+    return (
+      <PageHeaderLayout>
+        <Card>
+          <Search { ...searchProps } />
+          <TableList { ...listProps } />
+        </Card>
+      </PageHeaderLayout>
+    );
+  }
+}

+ 36 - 0
src/routes/TagType/List/search.js

@@ -0,0 +1,36 @@
+import react, { PureComponent } from 'react';
+import { Button, Row, Col, Icon } from 'antd';
+import DataSearch from '../../../components/DataSearch';
+
+export default class Search extends PureComponent {
+  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 || 'code',
+      },
+      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>
+    );
+  }
+}

+ 75 - 0
src/routes/TagType/List/table.js

@@ -0,0 +1,75 @@
+import React, { PureComponent } from 'react';
+import moment from 'moment';
+import { Divider, Modal, Table, Menu, Icon, Badge } from 'antd';
+import { statuses, Codes } from '../../../utils/config';
+
+export default class TableList extends PureComponent {
+
+  handleDeleteItem = (record) => {
+    const { onDeleteItem } = this.props;
+    Modal.confirm({
+      title: `您确定要删除该标签类型?`,
+      okText: '确定',
+      cancelText: '取消',
+      onOk: () => onDeleteItem({id: record.id}),
+    });
+  }
+
+  render() {
+    const { curMerchant, merchantList, onDeleteItem, onEditItem, pagination, ...tableProps } = this.props;
+
+    const columns = [{
+      title: '标签类型编号',
+      dataIndex: 'code',
+      key: 'code',
+      width: '25%',
+    },{
+      title: '标签类型名称',
+      dataIndex: 'name',
+      key: 'name',
+      width: '25%',
+    },{
+      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]} />);
+      },
+      width: '10%',
+    },{
+      title: '修改时间',
+      dataIndex: 'gmtModified',
+      key: 'gmtModified',
+      render: (text, record) => (
+        <div>{moment(text).format('YYYY-MM-DD HH:mm:ss')}</div>
+      ),
+      width: '20%',
+    },{
+      title: '操作',
+      dataIndex: 'operation',
+      key: 'operation',
+      render: (text, record) => (
+        <div>
+          <a onClick={() => onEditItem(record)}>编辑</a>
+          <Divider type="vertical" />
+          <a onClick={() => this.handleDeleteItem(record)}>删除</a>
+        </div>
+      ),
+      width: '20%',
+    }];
+
+    // 配置分页
+    tableProps.pagination = !!pagination && { ...pagination, showSizeChanger: true, showQuickJumper: true, showTotal: total => `共 ${total} 条`};
+
+    return (
+      <Table
+        simple
+        bordered
+        { ...tableProps }
+        columns={columns}
+        rowKey={record => record.id}
+      />
+    );
+  }
+}

+ 1 - 0
src/routes/Training/Edit/index.js

@@ -315,6 +315,7 @@ export default class TrainingDetail extends PureComponent {
               </Form.Item>
               <Form.Item label="所属供应商:" {...formItemLayout}>
                 {getFieldDecorator('cpId', {
+                  rules: [{ required: true, message: '供应商为必填项!' }],
                   initialValue: cpId,
                 })(
                   <Select placeholder="请选择">{merchant.list.map(item => <Select.Option value={item.id} key={item.id}>{item.name}</Select.Option>)}</Select>

+ 32 - 0
src/services/tagType.js

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

+ 2 - 0
src/utils/api.js

@@ -17,6 +17,8 @@ module.exports = {
   recommend: `${config.apiHost}/merchant/recommend`,
   groups: `${config.apiHost}/group/list`,
   group: `${config.apiHost}/group`,
+  tagTypes: `${config.apiHost}/tagType/list`,
+  tagType: `${config.apiHost}/tagType`,
   tags: `${config.apiHost}/tag/list`,
   tag: `${config.apiHost}/tag`,
   wares: `${config.apiHost}/ware/list`,