zhanghe пре 7 година
родитељ
комит
745c4dac07

+ 3 - 0
src/common/menu.js

@@ -94,6 +94,9 @@ const menuData = [
     },{
       name: '终端用户',
       path: 'user',
+    }, {
+      name: '白名单用户',
+      path: 'whitelist',
     }],
   },{
     name: '账户管理',

+ 11 - 0
src/common/router.js

@@ -62,6 +62,17 @@ export const getRouterData = (app) => {
       component: dynamicWrapper(app, ['terminal/detail', 'campus'], () => import('../routes/Terminal/Edit')),
       name: '编辑终端',
     },
+    '/terminal/whitelist': {
+      component: dynamicWrapper(app, ['whitelist/whitelist'], () => import('../routes/WhiteList/List')),
+    },
+    '/terminal/whitelist/add': {
+      component: dynamicWrapper(app, ['whitelist/detail', 'terminal/terminal'], () => import('../routes/WhiteList/Edit')),
+      name: '添加白名单',
+    },
+    '/terminal/whitelist/edit/:id': {
+      component: dynamicWrapper(app, ['whitelist/detail', 'terminal/terminal'], () => import('../routes/WhiteList/Edit')),
+      name: '编辑白名单',
+    },
     '/merchant': {
       component: dynamicWrapper(app, ['merchant/merchant'], () => import('../routes/Merchant/List')),
     },

+ 1 - 1
src/models/terminal/terminal.js

@@ -64,7 +64,7 @@ export default modelExtend(pageModel, {
         message.success('解绑成功!');
         if (callback) callback();
       }
-    }
+    },
   },
 
   reducers: {

+ 94 - 0
src/models/whitelist/detail.js

@@ -0,0 +1,94 @@
+import { create, update, queryOne } from '../../services/whitelist';
+import pathToRegexp from 'path-to-regexp';
+import { message } from 'antd';
+
+export default {
+  namespace: 'whitelistDetail',
+
+  state: {
+    filters: {},
+    operType: 'create',
+    currentItem: {},
+    modalShow: false,
+  },
+
+  subscriptions: {
+    setup({ dispatch, history }) {
+      history.listen(({ pathname, state, record }) => {
+        const match = pathToRegexp('/terminal/whitelist/edit/:id').exec(pathname);
+        if (match) {
+          dispatch({
+            type: 'saveOuterData',
+            payload: { filters: state, operType: 'update'},
+          });
+          dispatch({
+            type: 'queryOne',
+            payload: {userId: match[1]},
+          });
+        } else if (pathname === '/terminal/whitelist/add') {
+          dispatch({
+            type: 'saveOuterData',
+            payload: { filters: state, operType: 'create', currentItem: {} },
+          });
+        }
+      });
+    }
+  },
+
+  effects: {
+    * queryOne({ payload, callback }, { call, put }) {
+      const { data, success } = yield call(queryOne, payload);
+      if (success) {
+        yield put({
+          type: 'saveCurrentItem',
+          payload: {...data},
+        });
+      }
+    },
+    * create ({ payload, callback }, { call, put }) {
+      const { data, success } = yield call(create, payload);
+      if (success) {
+        message.success('创建成功!');
+        yield put({ type: 'initState' });
+        if (callback) callback();
+      }
+    },
+    * update ({ payload, callback }, { call, put }) {
+      const { data, success } = yield call(update, payload);
+      if (success) {
+        message.success('修改成功!');
+        yield put({ type: 'initState' });
+        if (callback) callback();
+      }
+    },
+  },
+
+  reducers: {
+    saveOuterData(state, action) {
+      return { ...state, ...action.payload };
+    },
+    saveCurrentItem(state, action) {
+      return {
+        ...state,
+        currentItem: action.payload,
+      };
+    },
+    showModal(state) {
+      return { ...state, modalShow: true };
+    },
+    hideModal(state, action) {
+      const { currentItem } = state;
+      return {
+        ...state,
+        modalShow: false,
+        currentItem: {
+          ...currentItem,
+          ...action.payload,
+        }
+      };
+    },
+    initState(state) {
+      return { ...state, currentItem: {} };
+    }
+  }
+}

+ 68 - 0
src/models/whitelist/whitelist.js

@@ -0,0 +1,68 @@
+import { query, create, update, remove } from '../../services/whitelist';
+import modelExtend from 'dva-model-extend';
+import { message } from 'antd';
+import queryString from 'query-string';
+import { pageModel } from '../common';
+import config from '../../utils/config';
+import { checkSearchParams } from '../../utils/utils';
+
+export default modelExtend(pageModel, {
+  namespace: 'whitelist',
+
+  state: { listLoading: false },
+
+  subscriptions: {
+    setup({ dispatch, history }) {
+      history.listen((location) => {
+        if (location.pathname === '/terminal/whitelist') {
+          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 }});
+    },
+    * delete ({ payload, callback }, { call, put }) {
+      const { data, success } = yield call(remove, payload);
+      if (success) {
+        message.success('删除成功!');
+        if (callback) callback();
+      }
+    },
+    * recover ({ payload, callback }, { call, put }) {
+      const { data, success } = yield call(update, payload);
+      if (success) {
+        message.success('恢复成功!');
+        if (callback) callback();
+      }
+    },
+  },
+
+  reducers: {
+    changeLoading(state, { payload }) {
+      return { ...state, ...payload };
+    },
+  }
+})

+ 218 - 0
src/routes/WhiteList/Edit/index.js

@@ -0,0 +1,218 @@
+import React, { Component } from 'react';
+import moment from 'moment';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import queryString from 'query-string';
+import { Modal, Card, List, Form, Button, Input, DatePicker } from 'antd';
+import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
+import FooterToolbar from '../../../components/FooterToolbar';
+import TerminalSelectModal from './terminal';
+import { pageSize, Codes } from '../../../utils/config';
+
+@Form.create()
+@connect(state => ({
+  terminal: state.terminal,
+  whitelistDetail: state.whitelistDetail,
+}))
+export default class WhitelistUserCreate extends Component {
+  // 终端选择弹框,显示 -> 加载数据
+  handleTerminalSelectBtnClick = () => {
+    this.props.dispatch({ type: 'whitelistDetail/showModal' });
+    this.props.dispatch({
+      type: 'terminal/query',
+      payload: {
+        pageNo: 1,
+        pageSize,
+      },
+    });
+  }
+
+  // 选择终端
+  handleTerminalModalOk = (record) => {
+    this.props.dispatch({
+      type: 'whitelistDetail/hideModal',
+      payload: {
+        userId: record.id,
+        code: record.code,
+        merchantName: record.merchantName,
+      },
+    });
+  }
+
+  handleTerminalModalCancel = () => {
+    this.props.dispatch({ type: 'whitelistDetail/hideModal' });
+  }
+
+  handleTerminalModalSearch = (data) => {
+    const newData = { ...data };
+    if (newData.keyword) {
+      newData[newData.field] = newData.keyword;
+    }
+    delete newData.field;
+    delete newData.keyword;
+    this.props.dispatch({
+      type: 'terminal/query',
+      payload: { ...newData, pageNo: 1, pageSize },
+    });
+  }
+
+  handleTerminalModalTableChange = (pagination, filterArgs, filters) => {
+    const newFilters = { ...filters };
+    if (newFilters.keyword) {
+      newFilters[newFilters.field] = newFilters.keyword;
+    }
+    delete newFilters.field;
+    delete newFilters.keyword;
+    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 = { ...newFilters, ...tableFilters, pageNo: pagination.current, pageSize: pagination.pageSize };
+    Object.keys(data).map(key => (data[key] ? null : delete data[key]));
+    this.props.dispatch({ type: 'terminal/query', payload: data });
+  }
+
+  handlePageCancel = () => {
+    const { whitelistDetail, dispatch } = this.props;
+    const { filters } = whitelistDetail;
+    dispatch(routerRedux.push({
+      pathname: '/terminal/whitelist',
+      search: queryString.stringify(filters),
+    }));
+  }
+
+  handlePageSubmit = () => {
+    const { form, dispatch, whitelistDetail } = this.props;
+    const { getFieldsValue, validateFields } = form;
+    const { filters, currentItem, operType } = whitelistDetail;
+    const { id, userId, code, status } = currentItem;
+    validateFields((errors) => {
+      if (errors) return;
+      let formData = getFieldsValue();
+
+      if (operType === 'create') {
+        formData = {
+          code,
+          userId,
+          status: Codes.CODE_NORMAL,
+          ...formData,
+        };
+        dispatch({
+          type: 'whitelistDetail/create',
+          payload: formData,
+          callback: () => {
+            dispatch(routerRedux.push({
+              pathname: '/terminal/whitelist',
+              search: queryString.stringify(filters),
+            }));
+          },
+        });
+      } else {
+        formData = {
+          id,
+          code,
+          status,
+          userId,
+          ...formData,
+        };
+        dispatch({
+          type: 'whitelistDetail/update',
+          payload: formData,
+          callback: () => {
+            dispatch(routerRedux.push({
+              pathname: '/terminal/whitelist',
+              search: queryString.stringify(filters),
+            }));
+          },
+        });
+      }
+    });
+  }
+
+  render() {
+    const { whitelistDetail, terminal, form } = this.props;
+    const { getFieldDecorator } = form;
+    const { modalShow, currentItem } = whitelistDetail;
+    const { code, startTime, endTime, merchantName } = currentItem;
+
+    const formItemLayout = {
+      labelCol: {
+        xs: { span: 24 },
+        sm: { span: 7 },
+        md: { span: 8 },
+      },
+      wrapperCol: {
+        xs: { span: 24 },
+        sm: { span: 12 },
+        md: { span: 12 },
+      },
+    };
+
+    return (
+      <PageHeaderLayout>
+        <Card>
+          <Form>
+            <Form.Item label="选择终端" {...formItemLayout}>
+              <Button onClick={this.handleTerminalSelectBtnClick} type="primary" size="small" icon="plus-circle-o">选择</Button>
+                {code &&
+                  <List
+                    size="small"
+                    bordered
+                    style={{ width: '50%' }}
+                    dataSource={[
+                      `终端编号: ${code}`,
+                      `所属渠道: ${merchantName}`,
+                    ]}
+                    renderItem={item => <List.Item>{item}</List.Item>}
+                  />
+              }
+            </Form.Item>
+            <Form.Item label="选择起始时间" {...formItemLayout}>
+              {getFieldDecorator('startTime', {
+                rules: [{ required: true, message: '请选择起始时间' }],
+                initialValue: startTime && moment(startTime)
+              })(
+                <DatePicker
+                  showTime
+                  placeholder="起始时间"
+                  format="YYYY-MM-DD HH:mm:ss"
+                />
+              )}
+            </Form.Item>
+            <Form.Item label="选择截止时间" {...formItemLayout}>
+              {getFieldDecorator('endTime', {
+                initialValue: endTime && moment(endTime)
+              })(
+                <DatePicker
+                  showTime
+                  placeholder="截止时间"
+                  format="YYYY-MM-DD HH:mm:ss"
+                />
+              )}
+            </Form.Item>
+          </Form>
+          {/* 终端选择弹框 */}
+          <TerminalSelectModal
+            rowKeyName="id"
+            modalVisible={modalShow}
+            width={660}
+            onOk={this.handleTerminalModalOk}
+            onCancel={this.handleTerminalModalCancel}
+            onSearch={this.handleTerminalModalSearch}
+            fsTableDataSource={terminal.list || []}
+            fsTableLoading={terminal.listLoading}
+            fsTablePagination={terminal.pagination}
+            fsTableOnChange={this.handleTerminalModalTableChange}
+          />
+        </Card>
+        <FooterToolbar>
+          <Button onClick={this.handlePageCancel}>取消</Button>
+          <Button onClick={this.handlePageSubmit} type="primary">提交</Button>
+        </FooterToolbar>
+      </PageHeaderLayout>
+    );
+  }
+}

+ 74 - 0
src/routes/WhiteList/Edit/terminal.js

@@ -0,0 +1,74 @@
+import React, { PureComponent } from 'react';
+import SelectModal from '../../../components/SelectModal';
+import { Codes } from '../../../utils/config';
+
+export default class TerminalSelectModal extends PureComponent {
+
+  render() {
+    const {
+      modalVisible,
+      onCancel,
+      onOk,
+      onSearch,
+      ...fsTableOpts
+    } = this.props;
+
+    const modalProps = {
+      title: '选择终端',
+      maskClosable: false,
+      visible: modalVisible,
+      onCancel,
+      onOk,
+    };
+
+    const searchProps = {
+      searchField: 'code',
+      searchKeyWord: '',
+      searchSize: 'default',
+      searchSelect: true,
+      searchSelectOptions: [{
+        value: 'code', name: '终端编号', mode: 'input',
+      }],
+      searchSelectProps: {
+        defaultValue: 'code',
+      },
+      onSearch: (value) => {
+        onSearch(value);
+      },
+    };
+
+    const fsTableProps = {
+      fsTableColumns: [{
+        title: '终端编号',
+        dataIndex: 'code',
+        key: 1,
+        width: '25%',
+      },{
+        title: '终端名称',
+        dataIndex: 'name',
+        key: 2,
+        width: '15%',
+      },{
+        title: '校区名称',
+        dataIndex: 'campusName',
+        key: 3,
+        width: '30%',
+      },{
+        title: '渠道名称',
+        dataIndex: 'merchantName',
+        key: 4,
+        width: '15%',
+      }],
+      ...fsTableOpts,
+    }
+
+    return (
+      <SelectModal
+        mode="single"
+        { ...searchProps }
+        { ...fsTableProps }
+        { ...modalProps }
+      />
+    );
+  }
+}

+ 107 - 0
src/routes/WhiteList/List/index.js

@@ -0,0 +1,107 @@
+import React, { Component } from 'react';
+import queryString from 'query-string';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { Card } from 'antd';
+import WhitelistTableList from './table';
+import WhitelistSearch from './search';
+import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
+
+@connect(state => ({ whitelist: state.whitelist }))
+export default class WhitelistList extends Component {
+  render() {
+    const { dispatch, location, whitelist } = this.props;
+
+    location.query = queryString.parse(location.search);
+    const { query, pathname } = location;
+    const { field, keyword, ...filters } = query;
+
+    Object.keys(filters).map((key) => { filters[key] ? null : delete filters[key]; });
+    if (field && keyword) {
+      filters.field = field;
+      filters.keyword = keyword;
+    }
+
+    const { list, listLoading, pagination } = whitelist;
+
+    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: '/terminal/whitelist/add',
+          state: filters,
+        }));
+      },
+    };
+
+    const listProps = {
+      curStatus: filters.status,
+      dataSource: list,
+      loading: listLoading,
+      pagination,
+      location,
+      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: `/terminal/whitelist/edit/${item.userId}`,
+          state: filters,
+          record: item,
+        }));
+      },
+      onDeleteItem: (id) => {
+        dispatch({
+          type: 'whitelist/delete',
+          payload: { userId: id },
+          callback: () => {
+            dispatch(
+              routerRedux.push({
+                pathname,
+                search: queryString.stringify(filters),
+              })
+            );
+          },
+        });
+      },
+    };
+
+    return (
+      <PageHeaderLayout>
+        <Card>
+          <WhitelistSearch {...searchProps} />
+          <WhitelistTableList {...listProps} />
+        </Card>
+      </PageHeaderLayout>
+    );
+  }
+}

+ 43 - 0
src/routes/WhiteList/List/search.js

@@ -0,0 +1,43 @@
+import react, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { Row, Col, Button, Icon } from 'antd';
+import DataSearch from '../../../components/DataSearch';
+
+export default class WhitelistSearch extends Component {
+  static propTypes = {
+    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: 'code', name: '终端编号', type: '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>
+    );
+  }
+}

+ 106 - 0
src/routes/WhiteList/List/table.js

@@ -0,0 +1,106 @@
+import React, { Component } from 'react';
+import moment from 'moment';
+import queryString from 'query-string';
+import { Divider, Table, Modal, Badge } from 'antd';
+import { Codes, statuses } from '../../../utils/config';
+
+export default class WhitelistTable extends Component {
+  handleOperateItem = (record) => {
+    const { onDeleteItem } = this.props;
+    Modal.confirm({
+      title: `您确定要从白名单中移除该终端账号?`,
+      cancelText: '取消',
+      okText: '确定',
+      onOk() {
+        onDeleteItem(record.userId);
+      }
+    });
+  }
+
+  render() {
+    const { onDeleteItem, onEditItem, curStatus, location, pagination, ...tableProps } = this.props;
+
+    // 从url中提取查询参数
+    location.query = queryString.parse(location.search);
+
+    const columns = [{
+      title: '终端编号',
+      dataIndex: 'code',
+      key: 'code',
+      width: '14%',
+    }, {
+      title: '渠道名称',
+      dataIndex: 'merchantId',
+      key: 'merchantId',
+      render: (text, record) => record.merchantName,
+      width: '10%',
+    }, {
+      title: '起止日期',
+      dataIndex: 'startTime',
+      key: 'startTime',
+      render: text => (
+        <div>{moment(text).format('YYYY-MM-DD HH:mm:ss')}</div>
+      ),
+      width: '16%',
+    }, {
+      title: '截止日期',
+      dataIndex: 'endTime',
+      key: 'endTime',
+      render: text => (
+        <div>{text && moment(text).format('YYYY-MM-DD HH:mm:ss')}</div>
+      ),
+      width: '16%',
+    }, {
+      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],
+      width: '7%',
+    }, {
+      title: '修改时间',
+      dataIndex: 'gmtModified',
+      key: 'gmtModified',
+      render: text => (
+        <div>{moment(text).format('YYYY-MM-DD HH:mm:ss')}</div>
+      ),
+      width: '16%',
+    }, {
+      title: '操作',
+      dataIndex: 'operation',
+      key: 'operation',
+      render: (text, record) => (
+        <div>
+          <a onClick={() => onEditItem(record)}>编辑</a>
+          <Divider type="vertical" />
+          <a onClick={() => this.handleOperateItem(record)}>删除</a>
+        </div>
+      ),
+      width: '10%',
+    }];
+
+    columns.map((item) => {
+      item.dataIndex === 'status' && !curStatus ? delete item.filteredValue : null;
+    });
+
+    // 配置分页
+    tableProps.pagination = !!pagination && { ...pagination, showSizeChanger: true, showQuickJumper: true, showTotal: total => `共 ${total} 条` };
+
+    return (
+      <Table
+        simple
+        bordered
+        {...tableProps}
+        columns={columns}
+        rowKey={record => record.id}
+      />
+    );
+  }
+}

+ 35 - 0
src/services/whitelist.js

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

+ 2 - 0
src/utils/api.js

@@ -38,4 +38,6 @@ module.exports = {
   snapShot: `${config.apiHost}/order/snapshot`,
   recharge: `${config.apiHost}/money/charge`,
   unbind: `${config.apiHost}/device/unbind`,
+  whiteUsers: `${config.apiHost}/white/user/list`,
+  whiteUser: `${config.apiHost}/white/user`,
 };