张延森 il y a 5 ans
Parent
révision
2b849b2923

BIN
src/components/XForm.rar Voir le fichier


+ 19
- 9
src/components/XForm/ImageUpload.jsx Voir le fichier

@@ -1,10 +1,14 @@
1 1
 import React from 'react';
2 2
 import { Upload, Icon, message } from 'antd';
3 3
 import './style.less';
4
+import { uploaderProps } from '../../utils/upload';
5
+
6
+
4 7
 
5 8
 class ImageUpload extends React.Component {
6 9
   state = {
7
-    loading: false
10
+    loading: false,
11
+    imageUrl: undefined,
8 12
   };
9 13
 
10 14
   handleChange = info => {
@@ -14,13 +18,19 @@ class ImageUpload extends React.Component {
14 18
     }
15 19
 
16 20
     if (info.file.status === "done") {
17
-      const imageUrl = info.response.url
18
-
19 21
       this.setState({
20
-        loading: false
22
+        loading: false,
21 23
       })
22 24
 
23
-      this.props.onChange(imageUrl)
25
+      if (info.file.response && info.file.response.url) {
26
+        this.setState({
27
+          imageUrl: info.file.response.thumbUrl,
28
+        });
29
+
30
+        if (typeof this.props.onChange === 'function') {
31
+          this.props.onChange(info.file.response.url);
32
+        }
33
+      }
24 34
     }
25 35
   };
26 36
 
@@ -35,16 +45,16 @@ class ImageUpload extends React.Component {
35 45
 
36 46
     return (
37 47
       <Upload
38
-        name="avatar"
39 48
         listType="picture-card"
40 49
         className="avatar-uploader"
41 50
         showUploadList={false}
42
-        action={this.props.action}
43 51
         beforeUpload={this.props.beforeUpload}
44 52
         onChange={this.handleChange}
53
+
54
+        {...uploaderProps}
45 55
       >
46
-        {value ? (
47
-          <img src={value} alt="avatar" style={{ width: "100%" }} />
56
+        {(this.state.imageUrl || value) ? (
57
+          <img src={this.state.imageUrl || value} alt="avatar" style={{ width: "100%" }} />
48 58
         ) : (
49 59
           uploadButton
50 60
         )}

+ 4
- 2
src/components/XForm/WrapperForm.jsx Voir le fichier

@@ -50,7 +50,8 @@ class WrapperForm extends React.Component {
50 50
     let FieldSubmit = null
51 51
     let FieldCancel = null
52 52
     if (submitBtn !== false) {
53
-      FieldSubmit = <Button htmlType="submit" type="primary">提交</Button>
53
+      const submitProps = typeof this.props.submitting === undefined ? {} : { loading: this.props.submitting }
54
+      FieldSubmit = <Button htmlType="submit" type="primary" {...submitBtn}>提交</Button>
54 55
     }
55 56
     if (cancelBtn !== false) {
56 57
       FieldCancel = <Button htmlType="button" onClick={this.handleCancel} style={{ marginLeft: '48px' }}>取消</Button>
@@ -75,7 +76,8 @@ class WrapperForm extends React.Component {
75 76
 }
76 77
 
77 78
 WrapperForm.propTypes = {
78
-  fields: PropTypes.array.isRequired
79
+  fields: PropTypes.array.isRequired,
80
+  submitting: PropTypes.bool,
79 81
 }
80 82
 
81 83
 export default WrapperForm;

+ 7
- 7
src/components/XForm/WrapperItem.jsx Voir le fichier

@@ -1,4 +1,4 @@
1
-import React from 'react';
1
+import React, { forwardRef } from 'react';
2 2
 import {
3 3
   Form,
4 4
   Input,
@@ -41,7 +41,7 @@ const tailFormItemLayout = {
41 41
   },
42 42
 };
43 43
 
44
-const WrapperItem = (props) => {
44
+const WrapperItem = (props, ref) => {
45 45
   const {
46 46
     form,
47 47
     label,
@@ -81,12 +81,12 @@ const WrapperItem = (props) => {
81 81
   // 没有类型与组件, 生成隐藏字段
82 82
   if (!type && !render) {
83 83
     getFieldDecorator(name, config);
84
-    return <></>;
84
+    return null;
85 85
   }
86 86
 
87 87
   const SelectOpts = (dict || []).map((item, index) => (<Option value={item.value}>{item.name}</Option>))
88 88
 
89
-  let Field = <></>;
89
+  let Field = null;
90 90
   if (render) {
91 91
     Field = typeof render === 'function' ? render(props) : render
92 92
   } else {
@@ -126,16 +126,16 @@ const WrapperItem = (props) => {
126 126
     }
127 127
   }
128 128
 
129
-  if (!label && !name && !action) return Field;
129
+  if (!label && !name && !action) return <Field ref={ref} />;
130 130
   
131 131
   const itemProps = action ? { ...restProps, ...tailFormItemLayout } : restProps
132 132
 
133 133
   return (
134
-    <Item label={label} {...itemProps}>
134
+    <Item label={label} {...itemProps} ref={ref}>
135 135
       {action ? Field : getFieldDecorator(name, config)(Field)}
136 136
     </Item>
137 137
   )
138 138
 };
139 139
 
140
-export default WrapperItem;
140
+export default forwardRef(WrapperItem);
141 141
 export { FieldTypes };

+ 3
- 1
src/components/XForm/index.jsx Voir le fichier

@@ -3,5 +3,7 @@ import { Form } from 'antd';
3 3
 import WrapperForm from './WrapperForm';
4 4
 import { FieldTypes } from './WrapperItem';
5 5
 
6
-export default Form.create()(WrapperForm);
6
+const XForm = Form.create()(WrapperForm);
7
+
8
+export default XForm;
7 9
 export { FieldTypes };

+ 0
- 2
src/models/login.js Voir le fichier

@@ -38,8 +38,6 @@ const Model = {
38 38
         }
39 39
       }
40 40
 
41
-      console.log('----------> redirect ')
42
-
43 41
       yield put(routerRedux.replace(redirect || '/'));
44 42
     },
45 43
 

+ 109
- 0
src/pages/UserManage/Editor/Menus.jsx Voir le fichier

@@ -0,0 +1,109 @@
1
+import React, { useState, useEffect } from 'react';
2
+import { Checkbox, Card, Button, notification } from 'antd';
3
+import { fetch, apis } from '../../../utils/request';
4
+
5
+const getModules = fetch(apis.member.menus);
6
+const authorizeMenus = fetch(apis.member.authorize);
7
+
8
+const gridStyle = {
9
+  width: '25%',
10
+  textAlign: 'center',
11
+};
12
+
13
+const Menus = (props) => {
14
+  const [menus, setMenus] = useState([])
15
+  const [checkOpts, setCheckOptions] = useState({checkedList: [], indeterminate: true, checkAll: false})
16
+
17
+  const user = props.data || {}
18
+
19
+  useEffect(() => {
20
+    if (user && user.userId) {
21
+      getModules({ params: {userId: user.userId} }).then((list) => {
22
+        let checkedList = []
23
+        const modules = (list || []).map(item => {
24
+          if (item.hasRights) {
25
+            checkedList.push(item.menuId)
26
+          }
27
+  
28
+          return {
29
+            label: item.name,
30
+            value: item.menuId,
31
+          }
32
+        })
33
+  
34
+        setMenus(modules);
35
+        setCheckOptions({
36
+          checkedList,
37
+          indeterminate: !!checkedList.length && checkedList.length < modules.length,
38
+          checkAll: checkedList.length === modules.length
39
+        })
40
+      }).catch(x => x)
41
+    }
42
+  }, [user.userId])
43
+
44
+  const handleChange = (checkedList) => {
45
+    setCheckOptions({
46
+      checkedList,
47
+      indeterminate: !!checkedList.length && checkedList.length < menus.length,
48
+      checkAll: checkedList.length === menus.length
49
+    })
50
+  }
51
+
52
+  const checkAllChange = (e) => {
53
+    setCheckOptions({
54
+      checkedList: e.target.checked ? menus.map(x => x.value) : [],
55
+      indeterminate: false,
56
+      checkAll: e.target.checked,
57
+    })
58
+  }
59
+
60
+  const CheckAll = (
61
+    <Checkbox
62
+      indeterminate={checkOpts.indeterminate}
63
+      onChange={checkAllChange}
64
+      checked={checkOpts.checkAll}
65
+    > 全选 </Checkbox>
66
+  )
67
+
68
+  const handleSubmit = () => {
69
+    const data = {
70
+      userId: user.userId,
71
+      menus: checkOpts.checkedList
72
+    }
73
+
74
+    authorizeMenus({ data }).then(() => {
75
+      notification.success({ message: '更新授权成功' })
76
+    }).catch(x => x)
77
+  }
78
+
79
+  const handleCancel = () => {
80
+    if (typeof props.onCancel === 'function') {
81
+      props.onCancel()
82
+    }
83
+  }
84
+
85
+  return (
86
+    <div>
87
+      <Card title="开通权限" extra={CheckAll} bordered={false}>
88
+        <Checkbox.Group
89
+          value={checkOpts.checkedList}
90
+          onChange={handleChange}
91
+        >
92
+          {menus.map((menu, inx) => {
93
+            return (
94
+              <Card.Grid hoverable={false} style={gridStyle} key={inx}>
95
+                <Checkbox value={menu.value}>{menu.label}</Checkbox>
96
+              </Card.Grid>
97
+            )
98
+          })}
99
+        </Checkbox.Group>
100
+      </Card>
101
+      <div style={{ padding: '36px 100px' }}>
102
+        <Button type="primary" onClick={handleSubmit}>提交</Button>
103
+        <Button style={{ marginLeft: '36px' }} onClick={handleCancel}>取消</Button>
104
+      </div>
105
+    </div>
106
+  );
107
+}
108
+
109
+export default Menus;

+ 159
- 0
src/pages/UserManage/Editor/Miniapp.jsx Voir le fichier

@@ -0,0 +1,159 @@
1
+import React, { useState } from 'react';
2
+import { Button, Row, Col, Input, InputNumber, notification } from 'antd';
3
+import XForm, { FieldTypes } from '../../../components/XForm';
4
+import Style from '../style.less';
5
+import { fetch, apis } from '../../../utils/request';
6
+
7
+const saveMiniapp = fetch(apis.member.miniapp);
8
+
9
+const TplItem = React.forwardRef((props, ref) => {
10
+  const [val, setVal] = useState({
11
+    tplId: undefined,
12
+    fieldNum: undefined,
13
+    ...props.value || {}
14
+  })
15
+
16
+  const handleChange = field => e => {
17
+    const newVal = { ...val, [`${field}`]: (e.target ? e.target.value : e) }
18
+    setVal(newVal)
19
+
20
+    if (typeof props.onChange === 'function') {
21
+      props.onChange(newVal)
22
+    }
23
+  }
24
+
25
+  return (
26
+    <Input.Group compact ref={ref}>
27
+      <Input style={{ width: '70%' }} value={val.tplId} placeholder="消息模板ID" onChange={handleChange('tplId')}></Input>
28
+      <InputNumber style={{ width: '30%' }} value={val.fieldNum} placeholder="字段数" onChange={handleChange('fieldNum')}></InputNumber>
29
+    </Input.Group>
30
+  );
31
+})
32
+
33
+const Miniapp = (props) => {
34
+  const user = props.data || {}
35
+  const appdata = user.miniapp || {};
36
+  const noticeTPL = (appdata.tpls || []).filter(x => x.tplType === 'notice')[0] || {}
37
+
38
+  const checkMiniapp = () => {
39
+  }
40
+
41
+  const fields = [
42
+    {
43
+      label: '名称',
44
+      name: 'name',
45
+      type: FieldTypes.Text,
46
+      value: appdata.name,
47
+      rules: [{required: true, message: '请填写小程序名称'}]
48
+    },
49
+    {
50
+      label: 'APPID',
51
+      name: 'miniappId',
52
+      type: FieldTypes.Text,
53
+      value: appdata.miniappId,
54
+      rules: [
55
+        { required: true, message: '请填写 APPID' },
56
+        { pattern: /^[a-zA-Z0-9]+$/, message: 'APPID 只能由字母与数字组成' }
57
+      ]
58
+    },
59
+    {
60
+      label: 'Secret',
61
+      name: 'secret',
62
+      type: FieldTypes.Text,
63
+      value: appdata.secret,
64
+      rules: [
65
+        { required: true, message: '请填写 Secret' },
66
+        { pattern: /^[a-zA-Z0-9]+$/, message: 'APPID 只能由字母与数字组成' }
67
+      ]
68
+    },
69
+    {
70
+      label: 'Token',
71
+      name: 'token',
72
+      value: appdata.token,
73
+      type: FieldTypes.Text,
74
+      rules: [
75
+        { pattern: /^[a-zA-Z0-9]+$/, message: 'token 只能由字母与数字组成' }
76
+      ]
77
+    },
78
+    // {
79
+    //   label: '业务受理',
80
+    //   name: 'mainbiz',
81
+    //   render: <TplItem />
82
+    // },
83
+    // {
84
+    //   label: '新客户通知',
85
+    //   name: 'newCustomer',
86
+    //   render: <TplItem />
87
+    // },
88
+    {
89
+      label: '消息通知',
90
+      name: 'notice',
91
+      render: () => <TplItem />,
92
+      value: noticeTPL,
93
+    },
94
+    {
95
+      action: true,
96
+      render: () => {
97
+        return (
98
+          <div className={Style['flex-box']}>
99
+            <div className={Style['flex-item']}>
100
+              <img src="" alt="" style={{ width: '128px' }}/>
101
+            </div>
102
+            <div className={Style['flex-auto']}>
103
+              <Button type="primary" ghost onClick={checkMiniapp}>验证</Button>
104
+            </div>
105
+          </div>
106
+        )
107
+      }
108
+    },
109
+  ]
110
+
111
+  const handleSubmit = val => {
112
+    // 校验字段
113
+    const { mainbiz, newCustomer, notice, ...otherData} = val
114
+
115
+    // if (mainbiz.tplId && !mainbiz.fieldNum) {
116
+    //   notification.error({ message: '请填写业务受理消息模板字段数' })
117
+    //   return
118
+    // }
119
+    
120
+    // if (newCustomer.tplId && !newCustomer.fieldNum) {
121
+    //   notification.error({ message: '请填写新客户通知模板字段数' })
122
+    //   return
123
+    // }
124
+    
125
+    if (notice.tplId && !notice.fieldNum) {
126
+      notification.error({ message: '请填写消息通知模板字段数' })
127
+      return
128
+    }
129
+
130
+    const tpls = [
131
+      {
132
+        ...notice,
133
+        ...(appdata.tpls || []).filter(x => x.tplType === 'notice')[0] || {},
134
+        tplType: 'notice'
135
+      }
136
+    ]
137
+
138
+    const data = {
139
+      ...appdata,
140
+      ...otherData,
141
+      orgId: user.orgId,
142
+      tpls,
143
+    }
144
+
145
+    saveMiniapp({ data }).then(() => {
146
+      notification.success({ message: '保存小程序信息成功' })
147
+    }).catch(x => x)
148
+  }
149
+
150
+  const handleCancel = () => {
151
+    if (typeof props.onCancel === 'function') {
152
+      props.onCancel()
153
+    }
154
+  }
155
+
156
+  return (<XForm onSubmit={handleSubmit} onCancel={handleCancel} fields={fields}></XForm>)
157
+}
158
+
159
+export default Miniapp;

+ 70
- 14
src/pages/UserManage/Editor/User.jsx Voir le fichier

@@ -1,49 +1,105 @@
1
-import React from 'react';
2
-import { Button } from 'antd';
1
+import React, { useState, useEffect } from 'react';
2
+import { Button, notification } from 'antd';
3
+import md5 from 'md5';
4
+import moment from 'moment';
5
+import router from 'umi/router';
3 6
 import XForm, { FieldTypes } from '../../../components/XForm';
7
+import { fetch, apis } from '../../../utils/request';
8
+
9
+const defaultPass = '123456';
10
+const saveUser = fetch(apis.member.save)
11
+const updateUser = fetch(apis.member.update)
4 12
 
5 13
 export default (props) => {
14
+  const [data, setData] = useState({})
15
+  const [submitting, setSubmitting] = useState(false)
16
+  const user = props.data;
17
+
6 18
   const fields = [
7 19
     {
8 20
       label: '公司名称',
9 21
       name: 'orgName',
10
-      type: FieldTypes.Text
22
+      type: FieldTypes.Text,
23
+      value: user.orgName,
11 24
     },
12 25
     {
13
-      label: '用户名称',
14
-      name: 'name',
15
-      type: FieldTypes.Text
26
+      label: '公司水印',
27
+      name: 'waterMark',
28
+      type: FieldTypes.ImageUploader,
29
+      value: user.waterMark,
16 30
     },
17 31
     {
18
-      label: '登录名称',
19
-      name: 'loginName',
32
+      label: '用户名称',
33
+      name: 'userName',
20 34
       type: FieldTypes.Text,
21
-      placeholder: '不填, 默认使用手机号'
35
+      rules: [
36
+        {required: true, message: '请填写用户名称'}
37
+      ],
38
+      value: user.userName,
22 39
     },
23 40
     {
24 41
       label: '手机号',
25 42
       name: 'phone',
26 43
       type: FieldTypes.Text,
27 44
       rules: [
28
-        {required: true, message: '请填写手机号'}
29
-      ]
45
+        {required: true, message: '请填写手机号'},
46
+        {len: 11, message: '手机号格式不正确'}
47
+      ],
48
+      value: user.phone,
49
+    },
50
+    {
51
+      label: '登录名称',
52
+      name: 'loginName',
53
+      type: FieldTypes.Text,
54
+      placeholder: '不填, 默认使用手机号',
55
+      extra: `默认登录密码 ${defaultPass}`,
56
+      value: user.loginName,
30 57
     },
31 58
     {
32 59
       label: '头像',
33 60
       name: 'avatar',
34 61
       type: FieldTypes.ImageUploader,
35
-      extra: '建议图片大小 128 * 128'
62
+      extra: '建议图片大小 128 * 128',
63
+      value: user.avatar,
36 64
     },
37 65
     {
38 66
       label: '有效期',
39 67
       name: 'expDate',
40 68
       type: FieldTypes.DatePicker,
69
+      rules: [
70
+        {required: true, message: '请选择有效期'}
71
+      ],
72
+      value: moment(user.expDate),
41 73
     },
42 74
   ]
43 75
 
44 76
   const handleSubmit = val => {
45
-    window.console.log('submit data --->', val)
77
+    const data = {
78
+      ...val,
79
+      loginName: val.loginName || val.phone,
80
+      loginPassword: md5(defaultPass),
81
+    }
82
+
83
+    setSubmitting(true)
84
+    if (user && user.userId) {
85
+      updateUser({ urlData: { id: user.userId }, data: { ...user, ...data } }).then((user) => {
86
+        setSubmitting(false)
87
+        notification.success({message: '编辑信息成功'})
88
+      }).catch(() => setSubmitting(false))
89
+    } else {
90
+      saveUser({ data }).then((user) => {
91
+        setSubmitting(false)
92
+        notification.success({message: '新建信息成功'})
93
+      }).catch(() => setSubmitting(false))
94
+      router.push(`/member/edit/${user.userId}`)
95
+    }
96
+  }
97
+
98
+  const handleCancel = () => {
99
+    if (typeof props.onCancel === 'function') {
100
+      props.onCancel()
101
+    }
46 102
   }
47 103
 
48
-  return (<XForm onSubmit={handleSubmit} fields={fields}></XForm>)
104
+  return (<XForm onSubmit={handleSubmit} onCancel={handleCancel} fields={fields} submitting={submitting}></XForm>)
49 105
 }

+ 41
- 12
src/pages/UserManage/Editor/index.jsx Voir le fichier

@@ -1,28 +1,57 @@
1 1
 import React, { useState, useEffect } from 'react';
2 2
 import { PageHeaderWrapper } from '@ant-design/pro-layout';
3
-import { Radio } from 'antd';
3
+import { Radio, Button } from 'antd';
4
+import { router } from 'umi';
4 5
 import User from './User';
6
+import MiniApp from './Miniapp';
7
+import Menus from './Menus';
8
+import { fetch, apis } from '../../../utils/request';
5 9
 
6
-import request from '../../../utils/request';
7
-import apis from '../../../services/apis';
10
+const getUser = fetch(apis.member.get);
8 11
 
9 12
 export default (props) => {
10
-  const [ tab, changeTab ] = useState(1);
13
+  const [ tab, changeTab ] = useState(props.location.query.tab - 0 || 1);
14
+  const [ user, setUser ] = useState({});
15
+  const handleRadioChange = e => {
16
+    if (user.userId) {
17
+      changeTab(e.target.value)
18
+    }
19
+  }
11 20
 
12
-  // useEffect(() => {
13
-  //   request({ ...apis.user.current }).then()
14
-  // })
21
+  useEffect(() => {
22
+    if (props.match.params.id) {
23
+      getUser({ urlData: { id: props.match.params.id } }).then((user) => {
24
+        // user 中包含 miniapp 信息
25
+        setUser(user);
26
+      }).catch(() => {})
27
+    }
28
+  }, [props.match.params.id])
29
+
30
+  const handleCancel = (currentTab) => () => {
31
+    if (currentTab === 1) {
32
+      gotoList()
33
+    } else {
34
+      changeTab(1)
35
+    }
36
+  }
37
+
38
+  const gotoList = () => {
39
+    router.push('/member')
40
+  }
15 41
 
16 42
   return (
17
-    <PageHeaderWrapper>
18
-      <div>
19
-        <Radio.Group value={tab} buttonStyle="solid" onChange={e => changeTab(e.target.value)}>
43
+    <PageHeaderWrapper extra={<Button type="primary" ghost size="small" icon="close" onClick={gotoList}></Button> }>
44
+      <div style={{ marginBottom: '24px' }}>
45
+        <Radio.Group value={tab} buttonStyle="solid" onChange={handleRadioChange}>
20 46
           <Radio.Button value={1}>主信息</Radio.Button>
21 47
           <Radio.Button value={2}>小程序</Radio.Button>
48
+          <Radio.Button value={3}>开通权限</Radio.Button>
22 49
         </Radio.Group>
23 50
       </div>
24
-      <div>
25
-        { tab === 1 ? <User /> : null}
51
+      <div style={{ width: '680px', margin: '0 auto' }}>
52
+        { tab === 1 ? <User data={user} {...props} onCancel={handleCancel(1)}/> : null}
53
+        { tab === 2 ? <MiniApp data={user} {...props} onCancel={handleCancel(2)}/> : null}
54
+        { tab === 3 ? <Menus data={user} {...props} onCancel={handleCancel(3)}/> : null}
26 55
       </div>
27 56
     </PageHeaderWrapper>
28 57
   );

+ 118
- 29
src/pages/UserManage/index.jsx Voir le fichier

@@ -1,61 +1,150 @@
1
-import React from 'react';
1
+import React, { useState, useEffect } from 'react';
2 2
 import { PageHeaderWrapper } from '@ant-design/pro-layout';
3
-import { Card, Button, Icon, Tooltip } from 'antd';
3
+import { Pagination, Card, Button, Icon, Tooltip, notification, Modal } from 'antd';
4
+import router from 'umi/router';
5
+import moment from 'moment';
6
+import className from 'classnames';
4 7
 import Cell from '../../components/Cell';
5 8
 import Style from './style.less';
9
+import { fetch, apis } from '../../utils/request';
6 10
 
7 11
 const { Meta } = Card;
8 12
 
13
+const getMembers = fetch(apis.member.list);
14
+const turnMember = fetch(apis.member.turn);
15
+const resetMemberPassword = fetch(apis.member.resetPass);
16
+
9 17
 const lineTextStyle = {
10 18
   marginBottom: 0,
11 19
   lineHeight: '1.8em'
12 20
 }
13 21
 
22
+const colorDanger = '#ff7875';
23
+const colorSucces = '#449d44';
24
+
25
+const Warn = (props) => <span style={{ color: colorDanger }}>{props.children}</span>
26
+const ABCol = (props) => (
27
+  <div className={className(Style['flex-box'])} style={lineTextStyle}>
28
+    <div className={className(Style['flex-item'])} style={{ width: '30%' }}>{props.header}</div>
29
+    <div className={className(Style['flex-auto'])}>{props.children}</div>
30
+  </div>
31
+)
32
+
14 33
 export default (props) => {
34
+  const [members, setMembers] = useState([]);
35
+  const [pageNavi, setPageNavi] = useState({ current: 1, pageSize: 10, total: 0 })
36
+
37
+  useEffect(() => {
38
+    getMembers({ params: { pageNum: pageNavi.current, pageSize: pageNavi.pageSize } }).then((dt) => {
39
+      const { records, ...pagenavi } = dt || {};
40
+      setMembers(records);
41
+      setPageNavi({
42
+        ...pageNavi,
43
+        ...pagenavi
44
+      })
45
+    }).catch()
46
+  }, []);
15 47
 
16 48
   const CardBody = (cbProps) => {
49
+    const handleSwitch = (on) => () => {
50
+      if (!on) {
51
+        Modal.confirm({
52
+          title: '确定禁用当前用户?',
53
+          onOk() {
54
+            turnMember({ urlData: { type: 'off', id: cbProps.userId } }).then(() => {
55
+              notification.success({ message: '禁用成功'})
56
+            }).catch()
57
+          },
58
+        });
59
+      } else {
60
+        turnMember({ urlData: { type: 'on', id: cbProps.userId } }).then(() => {
61
+          notification.success({ message: '开启成功'})
62
+        }).catch()
63
+      }
64
+    }
65
+
17 66
     return (
18 67
       <>
19 68
         <Cell action={
20
-          cbProps.status ?
21
-            (<Button type="link">开启<Icon type="check-circle" /></Button>) :
22
-            (<Button type="link">关闭<Icon type="delete" /></Button>)
69
+          cbProps.status != 1 ?
70
+            (<Button type="link" onClick={handleSwitch(true)} style={{ color: colorSucces }}>开启<Icon type="check" /></Button>) :
71
+            (<Button type="link" onClick={handleSwitch(false)} style={{ color: colorDanger }}>关闭<Icon type="delete" /></Button>)
23 72
           }>
24 73
           <h3>南京楼市</h3>
25 74
         </Cell>
26 75
         <div>
27
-          <p style={lineTextStyle}>联系人 : 曹建芳</p>
28
-          <p style={lineTextStyle}>手机号 : 187 5565 6565</p>
29
-          <p style={lineTextStyle}>小程序 : 南京楼市</p>
30
-          <p style={lineTextStyle}>服务到期时间 : 2019-09-24</p>
76
+          <ABCol header={'联系人'}>{cbProps.userName}</ABCol>
77
+          <ABCol header={'手机号'}>{cbProps.phone}</ABCol>
78
+          <ABCol header={'小程序'}>{cbProps.miniappId ? cbProps.miniappName : <Warn>未设置</Warn>}</ABCol>
79
+          <ABCol header={'服务到期时间'}>{cbProps.expData ? moment(cbProps.expData).format('YYYY-MM-DD') : <Warn>未设置</Warn>}</ABCol>
31 80
         </div>
32 81
       </>
33 82
     )
34 83
   }
84
+  
85
+  const createUser = () => {
86
+    router.push('/member/new');
87
+  }
88
+
89
+  const handleSizeChange = e => {
90
+
91
+  }
92
+
93
+  const handlePageChange = e => {
94
+
95
+  }
96
+
97
+  const gotoDetail = (member, tab) => () => {
98
+    router.push(`/member/edit/${member.userId}?tab=${tab || 1}`)
99
+  }
100
+
101
+  const resetPassword = (member) => () => {
102
+    Modal.confirm({
103
+      title: '确定重置当前用户密码?',
104
+      onOk() {
105
+        resetMemberPassword({ urlData: {id: member.userId}}).then((message) => {
106
+          notification.success({ message })
107
+        }).catch()
108
+      },
109
+    });
110
+  }
35 111
 
36 112
   return (
37
-    <PageHeaderWrapper>
113
+    <PageHeaderWrapper extra={<Button type="primary" icon="plus" onClick={createUser}>新建</Button>}>
38 114
       <div className={Style['flex-box']}>
39
-        <div className={Style['flex-item']}>
40
-          <Card
41
-            style={{width: '560px', boxShadow:'3px 3px 5px #ccc', borderRadius: '12px', overflow: 'hidden'}}
42
-            cover={<img alt="example" src="https://os.alipayobjects.com/rmsportal/QBnOOoLaAfKPirc.png" />}
43
-            actions={[
44
-              <Tooltip title="编辑">
45
-                <Icon type="edit" key="edit" />
46
-              </Tooltip>,
47
-              <Tooltip title="权限分配">
48
-                <Icon type="setting" key="rights" />
49
-              </Tooltip>,
50
-              <Tooltip title="重置密码">
51
-                <Icon type="block" key="resetpass" />
52
-              </Tooltip>
53
-            ]}
54
-          >
55
-            <CardBody/>
56
-          </Card>
57
-        </div>
115
+        {
116
+          members.map((member, inx) => {
117
+            return (
118
+              <div className={className(Style['flex-item'], Style.member)} key={inx}>
119
+                <Card
120
+                  style={{width: '360px', boxShadow:'3px 3px 5px #ccc', borderRadius: '12px', overflow: 'hidden'}}
121
+                  cover={<div className={Style.square} style={{ width: '100%', background: `no-repeat center/80% url(${member.qrCode})` }} />}
122
+                  actions={[
123
+                    <Tooltip title="编辑">
124
+                      <Icon type="edit" key="edit" onClick={gotoDetail(member)}/>
125
+                    </Tooltip>,
126
+                    <Tooltip title="权限分配">
127
+                      <Icon type="setting" key="rights" onClick={gotoDetail(member, 3)}/>
128
+                    </Tooltip>,
129
+                    <Tooltip title="重置密码">
130
+                      <Icon type="block" key="resetpass" onClick={resetPassword(member)}/>
131
+                    </Tooltip>
132
+                  ]}
133
+                >
134
+                  <CardBody {...member}/>
135
+                </Card>
136
+              </div>
137
+            );
138
+          })
139
+        }
58 140
       </div>
141
+      <Pagination
142
+        showSizeChanger
143
+        current={pageNavi.current}
144
+        total={pageNavi.total}
145
+        onChange={handlePageChange}
146
+        onShowSizeChange={handleSizeChange}
147
+      />
59 148
     </PageHeaderWrapper>
60 149
   )
61 150
 }

+ 11
- 1
src/pages/UserManage/style.less Voir le fichier

@@ -9,4 +9,14 @@
9 9
   .flex-auto {
10 10
     flex: auto;
11 11
   }
12
-}
12
+}
13
+
14
+.member {
15
+  margin-bottom: 36px;
16
+  margin-right: 24px;
17
+}
18
+
19
+.square {
20
+  height: 0;
21
+  padding-bottom : 80%;
22
+}

+ 45
- 1
src/services/apis.js Voir le fichier

@@ -14,7 +14,51 @@ const apis = {
14 14
       url: `${prefix}/signout`,
15 15
       method: 'POST',
16 16
     },
17
-  }
17
+  },
18
+  member: {
19
+    list: {
20
+      url: `${prefix}/taUser`,
21
+      method: 'GET',
22
+    },
23
+    get: {
24
+      url: `${prefix}/taUser/:id`,
25
+      method: 'GET',
26
+    },
27
+    save: {
28
+      url: `${prefix}/taUser`,
29
+      method: 'POST',
30
+    },
31
+    update: {
32
+      url: `${prefix}/taUser/:id`,
33
+      method: 'PUT',
34
+    },
35
+    miniapp: {
36
+      url: `${prefix}/taMiniapp`,
37
+      method: 'POST',
38
+    },
39
+    menus: {
40
+      url: `${prefix}/sysmodules`,
41
+      method: 'GET',
42
+    },
43
+    authorize: {
44
+      url: `${prefix}/sysmodules`,
45
+      method: 'POST',
46
+    },
47
+    turn: {
48
+      url: `${prefix}/turn/taUser/:id/:type`,
49
+      method: 'PUT',
50
+    },
51
+    resetPass: {
52
+      url: `${prefix}/password/taUser/:id`,
53
+      method: 'PUT',
54
+    },
55
+  },
56
+  image: {
57
+    upload: {
58
+      url: `${prefix}/antd/image`,
59
+      method: 'POST',
60
+    }
61
+  },
18 62
 }
19 63
 
20 64
 export default apis;

+ 9
- 2
src/utils/request.js Voir le fichier

@@ -4,6 +4,8 @@
4 4
  */
5 5
 import request from 'umi-request';
6 6
 import { notification } from 'antd';
7
+import apis from '../services/apis';
8
+
7 9
 const codeMessage = {
8 10
   200: '服务器成功返回请求的数据。',
9 11
   201: '新建或修改数据成功。',
@@ -25,7 +27,7 @@ const codeMessage = {
25 27
 const replaceURLParams = (url, params = {}) => {
26 28
   return Object.keys(params).reduce((acc, k) => { // 此方法对每个元素进行处理
27 29
     const re = new RegExp(`:${k}(?!w)`, 'i')
28
-    return acc.replace(re, args[k])
30
+    return acc.replace(re, params[k])
29 31
   }, url)
30 32
 }
31 33
 
@@ -78,7 +80,7 @@ request.interceptors.response.use(async (response, options) => {
78 80
         throw new Error(message);
79 81
       }
80 82
 
81
-      if (data.token) {
83
+      if (data && data.token) {
82 84
         window.localStorage.setItem('x-token', data.token)
83 85
       }
84 86
 
@@ -92,6 +94,9 @@ request.interceptors.response.use(async (response, options) => {
92 94
   }
93 95
 });
94 96
 
97
+
98
+const fetch = api => options => request(api.url, {...api, ...options || {}})
99
+
95 100
 export default config => {
96 101
   if (typeof config === 'string') {
97 102
     return request(config);
@@ -100,3 +105,5 @@ export default config => {
100 105
     return request(url, options);
101 106
   }
102 107
 };
108
+
109
+export { fetch, apis }

+ 11
- 0
src/utils/upload.js Voir le fichier

@@ -0,0 +1,11 @@
1
+import apis from '../services/apis';
2
+
3
+const uploaderProps = {
4
+  name: 'file',
5
+  action: apis.image.upload.url,
6
+  headers: {
7
+    Authorization: `Bearer ${window.localStorage.getItem('x-token')}`
8
+  }
9
+}
10
+
11
+export { uploaderProps }