andrew 4 years ago
parent
commit
6b7643b884

+ 46
- 0
src/components/DragableUploadImageList/Item.jsx View File

@@ -0,0 +1,46 @@
1
+import React from 'react'
2
+import { Icon } from 'antd'
3
+
4
+export default (props) => {
5
+    const { dataset = {}, onPreview, onDelete, onClick, onSticky } = props
6
+    const { url } = dataset
7
+
8
+    const handlePreview = () => {
9
+        onPreview && onPreview(dataset)
10
+    }
11
+
12
+    const handleDelete = () => {
13
+        onDelete && onDelete(dataset)
14
+    }
15
+
16
+    const handleClick = () => {
17
+        return onClick ? onClick(dataset) : null
18
+    }
19
+
20
+    const handleSticky = () => {
21
+        onSticky && onSticky(dataset)
22
+    }
23
+
24
+    return (
25
+        <div className="ant-upload-list-picture-card-container">
26
+            <span>
27
+                <div className="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture-card">
28
+                    <div className="ant-upload-list-item-info">
29
+                        <span>
30
+                            <a className="ant-upload-list-item-thumbnail" href={url} target="_blank" rel="noopener noreferrer" onClick={handleClick}>
31
+                                <img className="ant-upload-list-item-image" src={url} alt="image" />
32
+                            </a>
33
+                        </span>
34
+                    </div>
35
+
36
+                    <span className="ant-upload-list-item-actions">
37
+                        <Icon type="vertical-align-top" className="anticon anticon-eye-o" onClick={handleSticky} />
38
+                        <Icon type="eye" className="anticon anticon-eye-o" onClick={handlePreview} />
39
+                        <Icon type="delete" onClick={handleDelete} />
40
+                    </span>
41
+                </div>
42
+            </span>
43
+        </div>
44
+    )
45
+}
46
+

+ 115
- 0
src/components/DragableUploadImageList/index.jsx View File

@@ -0,0 +1,115 @@
1
+import React from 'react';
2
+import { Upload, Icon, Modal } from 'antd';
3
+import './style.less';
4
+import { uploaderProps } from '../../utils/upload';
5
+import Item from './Item'
6
+
7
+/**
8
+ * unlimited  属性表示上传图片无限制
9
+ * 例子: <ImageListUpload unlimited/>
10
+ */
11
+class ImageListUpload extends React.Component {
12
+  state = {
13
+    previewVisible: false,
14
+    previewImage: '',
15
+    loadding: false,
16
+  };
17
+
18
+  getFileList = () => {
19
+    return (this.props.value || []).map((img, inx) => ({ uid: inx, url: img, status: 'done' }))
20
+  }
21
+
22
+  handleCancel = () => this.setState({ previewVisible: false });
23
+
24
+  handlePreview = async file => {
25
+    this.setState({
26
+      previewImage: file.url ,
27
+      previewVisible: true,
28
+    });
29
+  };
30
+
31
+  handleSticky = ({ url }) => {
32
+    const fileList = this.props.value || [];
33
+    this.props.onChange([url].concat(fileList.filter(x => x != url)))
34
+  }
35
+
36
+  handleDelete = ({ url }) => {
37
+    const fileList = this.props.value || [];
38
+    this.props.onChange(fileList.filter(x => x != url))
39
+  }
40
+
41
+  handleChange = (e) => {
42
+    if (e.file.status === "uploading") {
43
+      this.setState({ loading: true });
44
+      return;
45
+    }
46
+
47
+    const fileList = (this.props.value || []).filter(x => x != e.file.url);
48
+    this.props.onChange(fileList);
49
+
50
+    // console.log('删除图片触发了', e.file)
51
+    // if (e.file.state === 'removed') {
52
+    //   const fileList = (this.props.value || []).filter(x => x != e.file.url);
53
+    //   this.props.onChange(fileList);
54
+    // }
55
+
56
+    // if (e.file.status === "done") {
57
+    //   this.setState({
58
+    //     loading: false,
59
+    //   })
60
+
61
+    //   if (e.file.response && e.file.response.url) {
62
+    //     if (typeof this.props.onChange === 'function') {
63
+    //       const fileList = this.getFileList()
64
+    //       this.props.onChange([...fileList || [], e.file.response.url]);
65
+    //     }
66
+    //   }
67
+    // }
68
+  };
69
+
70
+  handleUploadSucess = (url) => {
71
+    this.setState({ loading: false });
72
+    if (typeof this.props.onChange === 'function') {
73
+      const fileList = this.props.value || [];
74
+      this.props.onChange([...fileList, url]);
75
+    }
76
+  }
77
+
78
+  render() {
79
+    const { previewVisible, previewImage } = this.state;
80
+    const { multiple = false } = this.props;
81
+    const fileList = this.getFileList();
82
+
83
+    const uploadButton = (
84
+      <div>
85
+        <Icon style={{ fontSize: '2em', color: '#aaa' }} type={this.state.loading ? "loading" : "plus"}  />
86
+      </div>
87
+    );
88
+    return (
89
+      <div className="clearfix x-dargable-image-upload-list">
90
+        <div className="ant-upload-list ant-upload-list-picture-card">
91
+            {fileList.map(img => <Item key={img.url} dataset={img} onPreview={this.handlePreview} onSticky={this.handleSticky} onDelete={this.handleDelete}/>)}
92
+        </div>
93
+          
94
+        <Upload
95
+          listType="picture-card"
96
+          showUploadList={false}
97
+          multiple={multiple}
98
+          onPreview={this.handlePreview}
99
+          onChange={this.handleChange}
100
+          { ...uploaderProps }
101
+          onSuccess={this.handleUploadSucess}
102
+        >
103
+        
104
+          {/* unlimited 表示上传无限制数量 */}
105
+          {(this.props.unlimited && uploadButton) || ((fileList || images).length >= 8 ? null : uploadButton)}
106
+        </Upload>
107
+        <Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
108
+          <img alt="example" style={{ width: '100%' }} src={previewImage} />
109
+        </Modal>
110
+      </div>
111
+    );
112
+  }
113
+}
114
+
115
+export default ImageListUpload;

+ 23
- 0
src/components/DragableUploadImageList/style.less View File

@@ -0,0 +1,23 @@
1
+:global {
2
+  .x-dargable-image-upload-list {
3
+    display: flex;
4
+  }
5
+
6
+  
7
+  .x-dargable-image-upload-list  .ant-upload-picture-card-wrapper {
8
+    width: auto !important;
9
+  }
10
+
11
+}
12
+
13
+
14
+/* you can make up upload button and sample style by using stylesheets */
15
+.ant-upload-select-picture-card i {
16
+  font-size: 32px;
17
+  color: #999;
18
+}
19
+
20
+.ant-upload-select-picture-card .ant-upload-text {
21
+  margin-top: 8px;
22
+  color: #666;
23
+}

+ 64
- 0
src/components/EchartsTest/EChart.jsx View File

@@ -0,0 +1,64 @@
1
+import React, { Component } from 'react';
2
+
3
+// 引入 ECharts 主模块
4
+import echarts from 'echarts/lib/echarts';
5
+// 引入柱状图
6
+import 'echarts/lib/chart/bar';
7
+import 'echarts/lib/chart/pie';
8
+import 'echarts/lib/chart/scatter';
9
+import 'echarts/lib/chart/effectScatter';
10
+import 'echarts/lib/chart/map';
11
+// 引入提示框和标题组件
12
+import 'echarts/lib/component/tooltip';
13
+import 'echarts/lib/component/legend';
14
+import 'echarts/lib/component/title';
15
+import 'echarts/lib/chart/line';
16
+import 'echarts/lib/component/dataZoom';
17
+import 'echarts/lib/component/geo';
18
+import 'echarts/lib/component/visualMap';
19
+
20
+import { ChinaData } from './china';
21
+
22
+echarts.registerMap('china', ChinaData);
23
+
24
+class EchartsTest extends Component {
25
+    chartRef = React.createRef();
26
+    chart = undefined
27
+    defaultChartOption = {}
28
+
29
+    componentDidMount () {
30
+        this.chart = echarts.init(this.chartRef.current)
31
+
32
+        // 绘制图表
33
+        this.chart.setOption({
34
+            ...this.defaultChartOption,
35
+            ...this.props.options || {},
36
+        });
37
+    }
38
+
39
+    componentDidUpdate (preProps) {
40
+        if (preProps.options != this.props.options) {
41
+            const options = {
42
+                ...this.defaultChartOption,
43
+                ...this.props.options || {},
44
+            }
45
+
46
+
47
+            this.chart.setOption(options);
48
+        }
49
+    }
50
+
51
+    render () {
52
+        const style = {
53
+            width: '600px',
54
+            height: '400px',
55
+            ...this.props.style || {}
56
+        }
57
+
58
+        return (
59
+            <div onClick={this.props.onClick} ref={this.chartRef} style={style}></div>
60
+        );
61
+    }
62
+}
63
+
64
+export default EchartsTest;

+ 37
- 0
src/components/EchartsTest/Line.jsx View File

@@ -0,0 +1,37 @@
1
+import React, { useEffect } from 'react';
2
+import EChart from './EChart';
3
+
4
+
5
+const Line = (props) => {
6
+
7
+  const {person_realty_consultant, person_estate_agent, person_null} = xxx
8
+
9
+  const options = {
10
+    xAxis: {},
11
+    legend: {
12
+      left: '70%',
13
+      data: ['来源置业顾问', '来源全民经纪人', '自主进入'],
14
+    },
15
+    series: [
16
+      {
17
+        type: 'pie',
18
+        datasetIndex: 1,
19
+        center: ['75%', '50%'],
20
+        radius: [0, '50%'],
21
+      },
22
+    ],
23
+    dataset: {
24
+      id: 'pie',
25
+      source: [
26
+        { form: '来源置业顾问', value: person_realty_consultant },
27
+        { form: '来源全民经纪人', value: person_estate_agent },
28
+        { form: '自主进入', value: person_null },
29
+      ]
30
+    },
31
+  }
32
+
33
+  return (
34
+    <EChart options={options} />
35
+  )
36
+
37
+}

+ 1
- 0
src/components/EchartsTest/china.js
File diff suppressed because it is too large
View File


+ 19
- 0
src/components/EchartsTest/index.jsx View File

@@ -0,0 +1,19 @@
1
+import React, { Component } from 'react';
2
+
3
+// 引入 ECharts 主模块
4
+import echarts from 'echarts/lib/echarts';
5
+// 引入柱状图
6
+import 'echarts/lib/chart/bar';
7
+// 引入提示框和标题组件
8
+import 'echarts/lib/component/tooltip';
9
+import 'echarts/lib/component/title';
10
+import 'echarts/lib/chart/line'
11
+
12
+class Chart extends Component {
13
+
14
+  render() {
15
+    return (<div></div>);
16
+  }
17
+}
18
+
19
+export default Chart;

+ 115
- 0
src/components/EditIcon/index.jsx View File

@@ -0,0 +1,115 @@
1
+import React, { useMemo } from 'react';
2
+import spriteImg from '../../assets/sprite.png';
3
+
4
+const spriteInfo = [
5
+    {
6
+        name: '启用',
7
+        type: 'start',
8
+        pos: 0,
9
+        color: '#ff925c',
10
+    },
11
+    {
12
+        name: '发布',
13
+        type: 'publish',
14
+        pos: -126,
15
+        color: '#ff925c',
16
+    },
17
+    {
18
+        name: '上架',
19
+        type: 'up',
20
+        pos: -18,
21
+        color: '#ff925c',
22
+    },
23
+    {
24
+        name: '编辑',
25
+        type: 'edit',
26
+        pos: -144,
27
+        color: '#ff925c',
28
+    },
29
+    {
30
+        name: '取消',
31
+        type: 'cancel',
32
+        pos: -36,
33
+        color: '#FF4A4A',
34
+    },
35
+    {
36
+        name: '停用',
37
+        type: 'stop',
38
+        pos: -162,
39
+        color: '#FF4A4A',
40
+    },
41
+    {
42
+        name: '结束',
43
+        type: 'end',
44
+        pos: -54,
45
+        color: '#FF4A4A',
46
+    },
47
+    {
48
+        name: '删除',
49
+        type: 'delete',
50
+        pos: -180,
51
+        color: '#FF4A4A',
52
+    },
53
+    {
54
+        name: '查看',
55
+        type: 'look',
56
+        pos: -72,
57
+        color: '#FF4A4A',
58
+    },
59
+    {
60
+        name: '添加',
61
+        type: 'add',
62
+        pos: -198,
63
+        color: '#FF4A4A',
64
+    },
65
+    {
66
+        name: '前置',
67
+        type: 'top',
68
+        pos: -90,
69
+        color: '#FF4A4A',
70
+    },
71
+    {
72
+        name: '下架',
73
+        type: 'down',
74
+        pos: -216,
75
+        color: '#FF4A4A',
76
+    },
77
+    {
78
+        name: '记录',
79
+        type: 'record',
80
+        pos: -108,
81
+        color: '#FF4A4A',
82
+    },
83
+    {
84
+        name: '数据',
85
+        type: 'data',
86
+        pos: -234,
87
+        color: '#FF4A4A',
88
+    },
89
+    {
90
+        name: '下载二维码',
91
+        type: 'download',
92
+        pos: -252,
93
+        color: '#ff925c',
94
+    },
95
+]
96
+
97
+function noop() { }
98
+
99
+const EditIcon = ({ text, type, color, position, onClick }) => {
100
+    const icon = spriteInfo.filter(x => x.type === type)[0] || {};
101
+    const color2 = color || icon.color || '#ff925c';
102
+    const position2 = position || icon.pos;
103
+
104
+    const wrappedStyle = useMemo(() => ({ color: `${color2}`, display: 'flex', alignItems: 'center', cursor: 'pointer' }), [type, color]);
105
+    const iconStyle = useMemo(() => ({ display: 'inline-block', marginLeft: '6px', background: `url(${spriteImg}) 0 ${position2}px / 100% 1500% no-repeat`, width: '18px', height: '18px' }), [type, position])
106
+
107
+    return (
108
+        <span style={wrappedStyle} onClick={onClick || noop} >
109
+            {text}
110
+            {type && <span style={iconStyle}></span>}
111
+        </span>
112
+    )
113
+};
114
+
115
+export default EditIcon;

+ 111
- 0
src/components/GlobalHeader/AvatarDropdown.jsx View File

@@ -0,0 +1,111 @@
1
+import { Avatar, Icon, Menu, Spin } from 'antd';
2
+import { FormattedMessage } from 'umi-plugin-react/locale';
3
+import React from 'react';
4
+import { connect } from 'dva';
5
+import router from 'umi/router';
6
+import HeaderDropdown from '../HeaderDropdown';
7
+import styles from './index.less';
8
+import ShowPassword from './ShowPassword'
9
+
10
+class AvatarDropdown extends React.Component {
11
+
12
+  constructor(props) {
13
+    super()
14
+    this.state = { visible: false }
15
+  }
16
+
17
+  onMenuClick = event => {
18
+    const { key } = event;
19
+
20
+    if (key === 'logout') {
21
+      const { dispatch } = this.props;
22
+
23
+      if (dispatch) {
24
+        dispatch({
25
+          type: 'login/logout',
26
+        });
27
+      }
28
+
29
+      return;
30
+    }
31
+
32
+    if (key === 'updatePassword') {
33
+      this.setState({ visible: true })
34
+      return;
35
+    }
36
+
37
+    router.push(`/account/${key}`);
38
+  };
39
+
40
+  onCancel = (e) => {
41
+    this.setState({ visible: false })
42
+  }
43
+
44
+  render() {
45
+    console.log(this.props, 'props')
46
+    const {
47
+      currentUser = {
48
+        avatar: '',
49
+        userName: '',
50
+        miniAppName: '',
51
+      },
52
+      menu,
53
+    } = this.props;
54
+    const menuHeaderDropdown = (
55
+      <Menu className={styles.menu} selectedKeys={[]} onClick={this.onMenuClick}>
56
+        {menu && (
57
+          <Menu.Item key="center">
58
+            <Icon type="user" />
59
+            <FormattedMessage id="menu.account.center" defaultMessage="account center" />
60
+          </Menu.Item>
61
+        )}
62
+        {menu && (
63
+          <Menu.Item key="settings">
64
+            <Icon type="setting" />
65
+            <FormattedMessage id="menu.account.settings" defaultMessage="account settings" />
66
+          </Menu.Item>
67
+        )}
68
+        {menu && <Menu.Divider />}
69
+
70
+        <Menu.Item key="updatePassword">
71
+          <Icon type="key" />
72
+          <FormattedMessage id="menu.account.updatePassword" defaultMessage="updatePassword" />
73
+        </Menu.Item>
74
+        <Menu.Item key="logout">
75
+          <Icon type="logout" />
76
+          <FormattedMessage id="menu.account.logout" defaultMessage="logout" />
77
+        </Menu.Item>
78
+      </Menu>
79
+    );
80
+    return currentUser && currentUser.userName ? (
81
+      <>
82
+        <HeaderDropdown overlay={menuHeaderDropdown}>
83
+            <div className={`${styles.action} ${styles.account}`} style={{display:'flex'}}>
84
+              
85
+         <div style={{margin:'10px 0' }}>
86
+              <p align="right" className={styles.name}>{currentUser.loginName}</p>
87
+     
88
+              <p align="right" className={styles.name}>欢迎登录{currentUser.miniAppName}</p>
89
+          </div>
90
+          <Avatar size="small" className={styles.avatar} src={currentUser.avatar === null ? currentUser.photo:currentUser.avatar} alt="avatar" />
91
+            </div>
92
+        </HeaderDropdown>
93
+
94
+        {/* 修改密码 */}
95
+       <ShowPassword visible={this.state.visible} onCancel={(e) => this.onCancel(e)} />
96
+      </>
97
+    ) : (
98
+      <Spin
99
+        size="small"
100
+        style={{
101
+          marginLeft: 8,
102
+          marginRight: 8,
103
+        }}
104
+      />
105
+    );
106
+  }
107
+}
108
+
109
+export default connect(({ user }) => ({
110
+  currentUser: user.currentUser,
111
+}))(AvatarDropdown);

+ 174
- 0
src/components/GlobalHeader/NoticeIconView.jsx View File

@@ -0,0 +1,174 @@
1
+import React, { Component } from 'react';
2
+import { Tag, message } from 'antd';
3
+import { connect } from 'dva';
4
+import { formatMessage } from 'umi-plugin-react/locale';
5
+import groupBy from 'lodash/groupBy';
6
+import moment from 'moment';
7
+import NoticeIcon from '../NoticeIcon';
8
+import styles from './index.less';
9
+
10
+class GlobalHeaderRight extends Component {
11
+  componentDidMount() {
12
+    const { dispatch } = this.props;
13
+
14
+    if (dispatch) {
15
+      dispatch({
16
+        type: 'global/fetchNotices',
17
+      });
18
+    }
19
+  }
20
+
21
+  changeReadState = clickedItem => {
22
+    const { id } = clickedItem;
23
+    const { dispatch } = this.props;
24
+
25
+    if (dispatch) {
26
+      dispatch({
27
+        type: 'global/changeNoticeReadState',
28
+        payload: id,
29
+      });
30
+    }
31
+  };
32
+  handleNoticeClear = (title, key) => {
33
+    const { dispatch } = this.props;
34
+    message.success(
35
+      `${formatMessage({
36
+        id: 'component.noticeIcon.cleared',
37
+      })} ${title}`,
38
+    );
39
+
40
+    if (dispatch) {
41
+      dispatch({
42
+        type: 'global/clearNotices',
43
+        payload: key,
44
+      });
45
+    }
46
+  };
47
+  getNoticeData = () => {
48
+    const { notices = [] } = this.props;
49
+
50
+    if (notices.length === 0) {
51
+      return {};
52
+    }
53
+
54
+    const newNotices = notices.map(notice => {
55
+      const newNotice = { ...notice };
56
+
57
+      if (newNotice.datetime) {
58
+        newNotice.datetime = moment(notice.datetime).fromNow();
59
+      }
60
+
61
+      if (newNotice.id) {
62
+        newNotice.key = newNotice.id;
63
+      }
64
+
65
+      if (newNotice.extra && newNotice.status) {
66
+        const color = {
67
+          todo: '',
68
+          processing: 'blue',
69
+          urgent: 'red',
70
+          doing: 'gold',
71
+        }[newNotice.status];
72
+        newNotice.extra = (
73
+          <Tag
74
+            color={color}
75
+            style={{
76
+              marginRight: 0,
77
+            }}
78
+          >
79
+            {newNotice.extra}
80
+          </Tag>
81
+        );
82
+      }
83
+
84
+      return newNotice;
85
+    });
86
+    return groupBy(newNotices, 'type');
87
+  };
88
+  getUnreadData = noticeData => {
89
+    const unreadMsg = {};    
90
+    Object.keys(noticeData).forEach(key => {
91
+      const value = noticeData[key];
92
+
93
+      if (!unreadMsg[key]) {
94
+        unreadMsg[key] = 0;
95
+      }
96
+
97
+      if (Array.isArray(value)) {
98
+        unreadMsg[key] = value.filter(item => !item.read).length;
99
+      }
100
+    });
101
+    return unreadMsg;
102
+  };
103
+
104
+  render() {
105
+    const { currentUser, fetchingNotices, onNoticeVisibleChange } = this.props;
106
+    const noticeData = this.getNoticeData();
107
+    const unreadMsg = this.getUnreadData(noticeData);
108
+    return (
109
+      <NoticeIcon
110
+        className={styles.action}
111
+        count={currentUser && currentUser.unreadCount}
112
+        onItemClick={item => {
113
+          this.changeReadState(item);
114
+        }}
115
+        loading={fetchingNotices}
116
+        clearText={formatMessage({
117
+          id: 'component.noticeIcon.clear',
118
+        })}
119
+        viewMoreText={formatMessage({
120
+          id: 'component.noticeIcon.view-more',
121
+        })}
122
+        onClear={this.handleNoticeClear}
123
+        onPopupVisibleChange={onNoticeVisibleChange}
124
+        onViewMore={() => message.info('Click on view more')}
125
+        clearClose
126
+      >
127
+        <NoticeIcon.Tab
128
+          tabKey="notification"
129
+          count={unreadMsg.notification}
130
+          list={noticeData.notification}
131
+          title={formatMessage({
132
+            id: 'component.globalHeader.notification',
133
+          })}
134
+          emptyText={formatMessage({
135
+            id: 'component.globalHeader.notification.empty',
136
+          })}
137
+          showViewMore
138
+        />
139
+        <NoticeIcon.Tab
140
+          tabKey="message"
141
+          count={unreadMsg.message}
142
+          list={noticeData.message}
143
+          title={formatMessage({
144
+            id: 'component.globalHeader.message',
145
+          })}
146
+          emptyText={formatMessage({
147
+            id: 'component.globalHeader.message.empty',
148
+          })}
149
+          showViewMore
150
+        />
151
+        <NoticeIcon.Tab
152
+          tabKey="event"
153
+          title={formatMessage({
154
+            id: 'component.globalHeader.event',
155
+          })}
156
+          emptyText={formatMessage({
157
+            id: 'component.globalHeader.event.empty',
158
+          })}
159
+          count={unreadMsg.event}
160
+          list={noticeData.event}
161
+          showViewMore
162
+        />
163
+      </NoticeIcon>
164
+    );
165
+  }
166
+}
167
+
168
+export default connect(({ user, global, loading }) => ({
169
+  currentUser: user.currentUser,
170
+  collapsed: global.collapsed,
171
+  fetchingMoreNotices: loading.effects['global/fetchMoreNotices'],
172
+  fetchingNotices: loading.effects['global/fetchNotices'],
173
+  notices: global.notices,
174
+}))(GlobalHeaderRight);

+ 29
- 0
src/components/GlobalHeader/RightContent.jsx View File

@@ -0,0 +1,29 @@
1
+import { Icon, Tooltip } from 'antd';
2
+import React from 'react';
3
+import { connect } from 'dva';
4
+import { formatMessage } from 'umi-plugin-react/locale';
5
+import Avatar from './AvatarDropdown';
6
+import HeaderSearch from '../HeaderSearch';
7
+import SelectLang from '../SelectLang';
8
+import styles from './index.less';
9
+
10
+const GlobalHeaderRight = props => {
11
+  const { theme, layout } = props;
12
+  let className = styles.right;
13
+
14
+  if (theme === 'dark' && layout === 'topmenu') {
15
+    className = `${styles.right}  ${styles.dark}`;
16
+  }
17
+
18
+  return (
19
+    <div className={className}>
20
+
21
+      <Avatar />
22
+    </div>
23
+  );
24
+};
25
+
26
+export default connect(({ settings }) => ({
27
+  theme: settings.navTheme,
28
+  layout: settings.layout,
29
+}))(GlobalHeaderRight);

+ 126
- 0
src/components/GlobalHeader/ShowPassword.jsx View File

@@ -0,0 +1,126 @@
1
+import React, { Component } from 'react'
2
+import { Modal, Button, Form, Input, Icon, notification } from 'antd'
3
+import request from '../../utils/request'
4
+import apis from '../../services/apis'
5
+
6
+
7
+function passwodForm(props) {
8
+
9
+
10
+  const openNotificationWithIcon = (type, message) => {
11
+    notification[type]({
12
+      message,
13
+      description:
14
+        '',
15
+    });
16
+  }
17
+
18
+  function handleSubmit(e) {
19
+    e.preventDefault();
20
+    props.form.validateFieldsAndScroll((err, values) => {
21
+      // 两次密码比较
22
+      if (values.newPassword !== values.newPasswordToo) {
23
+        openNotificationWithIcon('error', '两次密码输入不一样,请重新输入')
24
+        return
25
+      }
26
+      if (!err) {
27
+        request({ ...apis.user.updatePassword, params: { ...values } }).then(() => {
28
+          openNotificationWithIcon('success', '操作成功!')
29
+          props.form.resetFields()
30
+          props.onSuccess()
31
+        }).catch(error => {
32
+          // openNotificationWithIcon('error', error.message)
33
+        })
34
+      }
35
+    });
36
+  }
37
+
38
+  const { getFieldDecorator } = props.form
39
+  return (
40
+    <>
41
+      <Form labelCol={{ span: 7 }} wrapperCol={{ span: 12 }} onSubmit={(e) => handleSubmit(e)}>
42
+      <Form.Item label="请输入旧密码">
43
+            {getFieldDecorator('originalPassword', {
44
+              rules: [{ required: true, message: '请输入旧密码' }],
45
+            })(
46
+              <Input
47
+                prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
48
+                type="password"
49
+                placeholder="Password"
50
+              />,
51
+            )}
52
+          </Form.Item>
53
+        <Form.Item label="新密码">
54
+            {getFieldDecorator('newPassword', {
55
+              rules: [{ required: true, message: '请输入新密码' }],
56
+            })(
57
+              <Input
58
+                prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
59
+                type="password"
60
+                placeholder="Password"
61
+              />,
62
+            )}
63
+          </Form.Item>
64
+          <Form.Item label="确认新密码">
65
+            {getFieldDecorator('newPasswordToo', {
66
+              rules: [{ required: true, message: '请确认新密码' }],
67
+            })(
68
+              <Input
69
+                prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
70
+                type="password"
71
+                placeholder="Password"
72
+              />,
73
+            )}
74
+          </Form.Item>
75
+          <Form.Item>
76
+            <Button type="primary" htmlType="submit">
77
+                确定
78
+            </Button>
79
+          </Form.Item>
80
+      </Form>
81
+    </>
82
+  )
83
+}
84
+
85
+const WrappedShowPasswordForm = Form.create({ name: 'passwodForm' })(passwodForm)
86
+
87
+export default class ShowPassword extends Component {
88
+
89
+  constructor(props) {
90
+    super(props)
91
+    this.state = { visible: false }
92
+  }
93
+
94
+  componentDidUpdate(prevProps) {
95
+    // 典型用法(不要忘记比较 props):
96
+    console.log('接收值: ', this.props.visible, prevProps.visible)
97
+    if (this.props.visible !== prevProps.visible) {
98
+      // eslint-disable-next-line react/no-did-update-set-state
99
+      this.setState({ visible: this.props.visible })
100
+    }
101
+  }
102
+
103
+  handleOk = e => {
104
+    console.log('关闭了')
105
+    this.props.onCancel()
106
+  };
107
+
108
+  handleCancel = e => {
109
+    console.log('关闭了')
110
+    this.props.onCancel()
111
+  };
112
+
113
+  render() {
114
+    return (
115
+      <>
116
+        <Modal
117
+          title="修改密码"
118
+          visible={this.state.visible}
119
+          onCancel={this.handleCancel}
120
+          footer={null}>
121
+          <WrappedShowPasswordForm onSuccess={(e) => this.handleCancel(e)} />
122
+        </Modal>
123
+      </>
124
+    );
125
+  }
126
+}

+ 147
- 0
src/components/GlobalHeader/index.less View File

@@ -0,0 +1,147 @@
1
+@import '~antd/es/style/themes/default.less';
2
+
3
+@pro-header-hover-bg: rgba(0, 0, 0, 0.025);
4
+.name {
5
+
6
+  color: #fff;
7
+  padding: 0;
8
+  margin: 0;
9
+  height: 20px;
10
+  line-height: 20px;
11
+    
12
+}
13
+.logo {
14
+  display: inline-block;
15
+  height: @layout-header-height;
16
+  padding: 0 0 0 24px;
17
+  font-size: 20px;
18
+  line-height: @layout-header-height;
19
+  vertical-align: top;
20
+  cursor: pointer;
21
+  img {
22
+    display: inline-block;
23
+    vertical-align: middle;
24
+  }
25
+}
26
+
27
+.menu {
28
+  :global(.anticon) {
29
+    margin-right: 8px;
30
+  }
31
+  :global(.ant-dropdown-menu-item) {
32
+    min-width: 160px;
33
+  }
34
+}
35
+
36
+.trigger {
37
+  height: @layout-header-height;
38
+  padding: ~'calc((@{layout-header-height} - 20px) / 2)' 24px;
39
+  font-size: 20px;
40
+  cursor: pointer;
41
+  transition: all 0.3s, padding 0s;
42
+  &:hover {
43
+    background: @pro-header-hover-bg;
44
+  }
45
+}
46
+
47
+.right {
48
+  float: right;
49
+  height: 100%;
50
+  overflow: hidden;
51
+  .action {
52
+    display: inline-block;
53
+    height: 100%;
54
+    padding: 0 40px;
55
+    cursor: pointer;
56
+    transition: all 0.3s;
57
+    > i {
58
+      color: @text-color;
59
+      vertical-align: middle;
60
+    }
61
+    &:hover {
62
+      background: @pro-header-hover-bg;
63
+    }
64
+    &:global(.opened) {
65
+      background: @pro-header-hover-bg;
66
+    }
67
+  }
68
+  .search {
69
+    padding: 0 12px;
70
+    &:hover {
71
+      background: transparent;
72
+    }
73
+  }
74
+  .account {
75
+    .avatar {
76
+      width: 44px;
77
+      height: 44px;
78
+      margin: ~'calc((@{layout-header-height} - 44px) / 2)' 0;
79
+      margin-left: 20px;
80
+      color: @primary-color;
81
+      vertical-align: top;
82
+      background: rgba(255, 255, 255, 0.85);
83
+    }
84
+  }
85
+}
86
+
87
+.dark {
88
+  height: @layout-header-height;
89
+  .action {
90
+    color: rgba(255, 255, 255, 0.85);
91
+    > i {
92
+      color: rgba(255, 255, 255, 0.85);
93
+    }
94
+    &:hover,
95
+    &:global(.opened) {
96
+      background: @primary-color;
97
+    }
98
+    :global(.ant-badge) {
99
+      color: rgba(255, 255, 255, 0.85);
100
+    }
101
+  }
102
+}
103
+
104
+:global(.ant-pro-global-header) {
105
+  .dark {
106
+    .action {
107
+      color: @text-color;
108
+      > i {
109
+        color: @text-color;
110
+      }
111
+      &:hover {
112
+        color: rgba(255, 255, 255, 0.85);
113
+        > i {
114
+          color: rgba(255, 255, 255, 0.85);
115
+        }
116
+      }
117
+    }
118
+  }
119
+}
120
+
121
+@media only screen and (max-width: @screen-md) {
122
+  :global(.ant-divider-vertical) {
123
+    vertical-align: unset;
124
+  }
125
+  .name {
126
+    display: none;
127
+    color: #fff;
128
+  }
129
+  i.trigger {
130
+    padding: 22px 12px;
131
+  }
132
+  .logo {
133
+    position: relative;
134
+    padding-right: 12px;
135
+    padding-left: 12px;
136
+  }
137
+  .right {
138
+    position: absolute;
139
+    top: 0;
140
+    right: 12px;
141
+    .account {
142
+      .avatar {
143
+        margin-right: 0;
144
+      }
145
+    }
146
+  }
147
+}

+ 10
- 0
src/components/HeaderDropdown/index.jsx View File

@@ -0,0 +1,10 @@
1
+import { Dropdown } from 'antd';
2
+import React from 'react';
3
+import classNames from 'classnames';
4
+import styles from './index.less';
5
+
6
+const HeaderDropdown = ({ overlayClassName: cls, ...restProps }) => (
7
+  <Dropdown overlayClassName={classNames(styles.container, cls)} {...restProps} />
8
+);
9
+
10
+export default HeaderDropdown;

+ 16
- 0
src/components/HeaderDropdown/index.less View File

@@ -0,0 +1,16 @@
1
+@import '~antd/es/style/themes/default.less';
2
+
3
+.container > * {
4
+  background-color: #fff;
5
+  border-radius: 4px;
6
+  box-shadow: @shadow-1-down;
7
+}
8
+
9
+@media screen and (max-width: @screen-xs) {
10
+  .container {
11
+    width: 100% !important;
12
+  }
13
+  .container > * {
14
+    border-radius: 0 !important;
15
+  }
16
+}

+ 132
- 0
src/components/HeaderSearch/index.jsx View File

@@ -0,0 +1,132 @@
1
+import { AutoComplete, Icon, Input } from 'antd';
2
+import React, { Component } from 'react';
3
+import classNames from 'classnames';
4
+import debounce from 'lodash/debounce';
5
+import styles from './index.less';
6
+export default class HeaderSearch extends Component {
7
+  static defaultProps = {
8
+    defaultActiveFirstOption: false,
9
+    onPressEnter: () => {},
10
+    onSearch: () => {},
11
+    onChange: () => {},
12
+    className: '',
13
+    placeholder: '',
14
+    dataSource: [],
15
+    defaultOpen: false,
16
+    onVisibleChange: () => {},
17
+  };
18
+
19
+  static getDerivedStateFromProps(props) {
20
+    if ('open' in props) {
21
+      return {
22
+        searchMode: props.open,
23
+      };
24
+    }
25
+
26
+    return null;
27
+  }
28
+
29
+  inputRef = null;
30
+
31
+  constructor(props) {
32
+    super(props);
33
+    this.state = {
34
+      searchMode: props.defaultOpen,
35
+      value: '',
36
+    };
37
+    this.debouncePressEnter = debounce(this.debouncePressEnter, 500, {
38
+      leading: true,
39
+      trailing: false,
40
+    });
41
+  }
42
+
43
+  onKeyDown = e => {
44
+    if (e.key === 'Enter') {
45
+      this.debouncePressEnter();
46
+    }
47
+  };
48
+  onChange = value => {
49
+    if (typeof value === 'string') {
50
+      const { onSearch, onChange } = this.props;
51
+      this.setState({
52
+        value,
53
+      });
54
+
55
+      if (onSearch) {
56
+        onSearch(value);
57
+      }
58
+
59
+      if (onChange) {
60
+        onChange(value);
61
+      }
62
+    }
63
+  };
64
+  enterSearchMode = () => {
65
+    const { onVisibleChange } = this.props;
66
+    onVisibleChange(true);
67
+    this.setState(
68
+      {
69
+        searchMode: true,
70
+      },
71
+      () => {
72
+        const { searchMode } = this.state;
73
+
74
+        if (searchMode && this.inputRef) {
75
+          this.inputRef.focus();
76
+        }
77
+      },
78
+    );
79
+  };
80
+  leaveSearchMode = () => {
81
+    this.setState({
82
+      searchMode: false,
83
+      value: '',
84
+    });
85
+  };
86
+  debouncePressEnter = () => {
87
+    const { onPressEnter } = this.props;
88
+    const { value } = this.state;
89
+    onPressEnter(value);
90
+  };
91
+
92
+  render() {
93
+    const { className, placeholder, open, ...restProps } = this.props;
94
+    const { searchMode, value } = this.state;
95
+    delete restProps.defaultOpen; // for rc-select not affected
96
+
97
+    const inputClass = classNames(styles.input, {
98
+      [styles.show]: searchMode,
99
+    });
100
+    return (
101
+      <span
102
+        className={classNames(className, styles.headerSearch)}
103
+        onClick={this.enterSearchMode}
104
+        onTransitionEnd={({ propertyName }) => {
105
+          if (propertyName === 'width' && !searchMode) {
106
+            const { onVisibleChange } = this.props;
107
+            onVisibleChange(searchMode);
108
+          }
109
+        }}
110
+      >
111
+        <Icon type="search" key="Icon" />
112
+        <AutoComplete
113
+          key="AutoComplete"
114
+          {...restProps}
115
+          className={inputClass}
116
+          value={value}
117
+          onChange={this.onChange}
118
+        >
119
+          <Input
120
+            ref={node => {
121
+              this.inputRef = node;
122
+            }}
123
+            aria-label={placeholder}
124
+            placeholder={placeholder}
125
+            onKeyDown={this.onKeyDown}
126
+            onBlur={this.leaveSearchMode}
127
+          />
128
+        </AutoComplete>
129
+      </span>
130
+    );
131
+  }
132
+}

+ 32
- 0
src/components/HeaderSearch/index.less View File

@@ -0,0 +1,32 @@
1
+@import '~antd/es/style/themes/default.less';
2
+
3
+.headerSearch {
4
+  :global(.anticon-search) {
5
+    font-size: 16px;
6
+    cursor: pointer;
7
+  }
8
+  .input {
9
+    width: 0;
10
+    background: transparent;
11
+    border-radius: 0;
12
+    transition: width 0.3s, margin-left 0.3s;
13
+    :global(.ant-select-selection) {
14
+      background: transparent;
15
+    }
16
+    input {
17
+      padding-right: 0;
18
+      padding-left: 0;
19
+      border: 0;
20
+      box-shadow: none !important;
21
+    }
22
+    &,
23
+    &:hover,
24
+    &:focus {
25
+      border-bottom: 1px solid @border-color-base;
26
+    }
27
+    &.show {
28
+      width: 210px;
29
+      margin-left: 8px;
30
+    }
31
+  }
32
+}

+ 64
- 0
src/components/HouseSelect/ApartmentSelect.jsx View File

@@ -0,0 +1,64 @@
1
+import React, { useState, useEffect, useRef } from 'react';
2
+import { Select } from 'antd';
3
+import apis from '../../services/apis';
4
+import request from '../../utils/request'
5
+
6
+const { Option } = Select;
7
+
8
+function usePrevious(props) {
9
+  const ref = useRef();
10
+  useEffect(() => {
11
+    ref.current = props;
12
+  });
13
+  return ref.current;
14
+}
15
+
16
+/**
17
+ *
18
+ *
19
+ * @param {*} props
20
+ * @returns
21
+ */
22
+const ApartmentSelect = props => {
23
+  const [data, setData] = useState([])
24
+  const [value, setValue] = useState([])
25
+  useEffect(() => {
26
+    getList();
27
+  }, [props.value])
28
+
29
+
30
+  const getList = e => {
31
+    request({ ...apis.house.apartmentList, params: { pageNum: 1, pageSize: 999, buildingId: props.buildingId} }).then(data => {
32
+        setData(data.records)
33
+        checkValue(data.records)
34
+        // 默认选中第一个
35
+    })
36
+  }
37
+
38
+
39
+  const checkValue = (data) => {
40
+    if (props.value) {
41
+      const tempData = data.filter(f => f.apartmentId == props.value)
42
+      const va = (tempData.length > 0) ? props.value : '已删除,请重新选择'
43
+      props.onChange(va)
44
+
45
+    }
46
+  }
47
+
48
+  return (
49
+      <Select
50
+      showSearch
51
+      value={props.value}
52
+      style={{ width: '300px' }}
53
+      placeholder="请选择户型"
54
+      onChange={props.onChange}
55
+      filterOption={(input, option) =>
56
+        option.props.children && option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
57
+      }>
58
+          {data.map(apartment => (
59
+            <Option key={apartment.apartmentId} value={apartment.apartmentId}>{apartment.apartmentName}</Option>
60
+          ))}
61
+      </Select>
62
+  )
63
+}
64
+export default ApartmentSelect

+ 64
- 0
src/components/HouseSelect/BlockSelect.jsx View File

@@ -0,0 +1,64 @@
1
+import React, { useState, useEffect, useRef } from 'react';
2
+import { Select } from 'antd';
3
+import apis from '../../services/apis';
4
+import request from '../../utils/request'
5
+
6
+const { Option } = Select;
7
+
8
+function usePrevious(props) {
9
+  const ref = useRef();
10
+  useEffect(() => {
11
+    ref.current = props;
12
+  });
13
+  return ref.current;
14
+}
15
+
16
+/**
17
+ *
18
+ *
19
+ * @param {*} props
20
+ * @returns
21
+ */
22
+const BlockSelect = props => {
23
+  const [data, setData] = useState([])
24
+  const [value, setValue] = useState([])
25
+  useEffect(() => {
26
+    getList();
27
+  }, [props.value])
28
+
29
+
30
+  const getList = e => {
31
+    request({ ...apis.house.block, params: { pageNum: 1, pageSize: 999, buildingId: props.buildingId} }).then(data => {
32
+        setData(data.records)
33
+        checkValue(data.records)
34
+        // 默认选中第一个
35
+    })
36
+  }
37
+
38
+
39
+  const checkValue = (data) => {
40
+    if (props.value) {
41
+      const tempData = data.filter(f => f.buildingId == props.value)
42
+      const va = (tempData.length > 0) ? props.value : '已删除,请重新选择'
43
+      props.onChange(va)
44
+
45
+    }
46
+  }
47
+
48
+  return (
49
+      <Select
50
+      showSearch
51
+      value={props.value}
52
+      style={{ width: '300px' }}
53
+      placeholder="请选择楼栋"
54
+      onChange={props.onChange}
55
+      filterOption={(input, option) =>
56
+        option.props.children && option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
57
+      }>
58
+          {data.map(block => (
59
+            <Option key={block.blockId} value={block.blockId}>{block.blockName}</Option>
60
+          ))}
61
+      </Select>
62
+  )
63
+}
64
+export default BlockSelect

+ 64
- 0
src/components/HouseSelect/FloorSelect.jsx View File

@@ -0,0 +1,64 @@
1
+import React, { useState, useEffect, useRef } from 'react';
2
+import { Select } from 'antd';
3
+import apis from '../../services/apis';
4
+import request from '../../utils/request'
5
+
6
+const { Option } = Select;
7
+
8
+function usePrevious(props) {
9
+  const ref = useRef();
10
+  useEffect(() => {
11
+    ref.current = props;
12
+  });
13
+  return ref.current;
14
+}
15
+
16
+/**
17
+ *
18
+ *
19
+ * @param {*} props
20
+ * @returns
21
+ */
22
+const FloorSelect = props => {
23
+  const [data, setData] = useState([])
24
+  const [value, setValue] = useState([])
25
+  useEffect(() => {
26
+    getList();
27
+  }, [props.value])
28
+
29
+
30
+  const getList = e => {
31
+    request({ ...apis.house.floor, params: { pageNum: 1, pageSize: 999, buildingId: props.buildingId, blockId: props.blockId, unitId: props.unitId} }).then(data => {
32
+        setData(data.records)
33
+        checkValue(data.records)
34
+        // 默认选中第一个
35
+    })
36
+  }
37
+
38
+
39
+  const checkValue = (data) => {
40
+    if (props.value) {
41
+      const tempData = data.filter(f => f.buildingId == props.value)
42
+      const va = (tempData.length > 0) ? props.value : '已删除,请重新选择'
43
+      props.onChange(va)
44
+
45
+    }
46
+  }
47
+
48
+  return (
49
+      <Select
50
+      showSearch
51
+      value={props.value}
52
+      style={{ width: '300px' }}
53
+      placeholder="请选择楼层"
54
+      onChange={props.onChange}
55
+      filterOption={(input, option) =>
56
+        option.props.children && option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
57
+      }>
58
+          {data.map(floor => (
59
+            <Option key={floor.floorId} value={floor.floorId}>{floor.floorName}</Option>
60
+          ))}
61
+      </Select>
62
+  )
63
+}
64
+export default FloorSelect

+ 64
- 0
src/components/HouseSelect/RoomSelect.jsx View File

@@ -0,0 +1,64 @@
1
+import React, { useState, useEffect, useRef } from 'react';
2
+import { Select } from 'antd';
3
+import apis from '../../services/apis';
4
+import request from '../../utils/request'
5
+
6
+const { Option } = Select;
7
+
8
+function usePrevious(props) {
9
+  const ref = useRef();
10
+  useEffect(() => {
11
+    ref.current = props;
12
+  });
13
+  return ref.current;
14
+}
15
+
16
+/**
17
+ *
18
+ *
19
+ * @param {*} props
20
+ * @returns
21
+ */
22
+const RoomSelect = props => {
23
+  const [data, setData] = useState([])
24
+  const [value, setValue] = useState([])
25
+  useEffect(() => {
26
+    getList();
27
+  }, [props.value])
28
+
29
+
30
+  const getList = e => {
31
+    request({ ...apis.house.room, params: { pageNum: 1, pageSize: 999, buildingId: props.buildingId, blockId: props.blockId, unitId: props.unitId, floorId: props.floorId} }).then(data => {
32
+        setData(data.records)
33
+        checkValue(data.records)
34
+        // 默认选中第一个
35
+    })
36
+  }
37
+
38
+
39
+  const checkValue = (data) => {
40
+    if (props.value) {
41
+      const tempData = data.filter(f => f.buildingId == props.value)
42
+      const va = (tempData.length > 0) ? props.value : '已删除,请重新选择'
43
+      props.onChange(va)
44
+
45
+    }
46
+  }
47
+
48
+  return (
49
+      <Select
50
+      showSearch
51
+      value={props.value}
52
+      style={{ width: '300px' }}
53
+      placeholder="请选择房号"
54
+      onChange={props.onChange}
55
+      filterOption={(input, option) =>
56
+        option.props.children && option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
57
+      }>
58
+          {data.map(room => (
59
+            <Option key={room.roomId} value={room.roomId}>{room.roomName}</Option>
60
+          ))}
61
+      </Select>
62
+  )
63
+}
64
+export default RoomSelect

+ 64
- 0
src/components/HouseSelect/SalesBatchSelect.jsx View File

@@ -0,0 +1,64 @@
1
+import React, { useState, useEffect, useRef } from 'react';
2
+import { Select } from 'antd';
3
+import apis from '../../services/apis';
4
+import request from '../../utils/request'
5
+
6
+const { Option } = Select;
7
+
8
+function usePrevious(props) {
9
+  const ref = useRef();
10
+  useEffect(() => {
11
+    ref.current = props;
12
+  });
13
+  return ref.current;
14
+}
15
+
16
+/**
17
+ *
18
+ *
19
+ * @param {*} props
20
+ * @returns
21
+ */
22
+const SalesBatchSelect = props => {
23
+  const [data, setData] = useState([])
24
+  const [value, setValue] = useState([])
25
+  useEffect(() => {
26
+    getList();
27
+  }, [props.value])
28
+
29
+
30
+  const getList = e => {
31
+    request({ ...apis.house.apartmentList, params: { pageNum: 1, pageSize: 999, buildingId: props.buildingId} }).then(data => {
32
+        setData(data.records)
33
+        checkValue(data.records)
34
+        // 默认选中第一个
35
+    })
36
+  }
37
+
38
+
39
+  const checkValue = (data) => {
40
+    if (props.value) {
41
+      const tempData = data.filter(f => f.apartmentId == props.value)
42
+      const va = (tempData.length > 0) ? props.value : '已删除,请重新选择'
43
+      props.onChange(va)
44
+
45
+    }
46
+  }
47
+
48
+  return (
49
+      <Select
50
+      showSearch
51
+      value={props.value}
52
+      style={{ width: '300px' }}
53
+      placeholder="请选择批次"
54
+      onChange={props.onChange}
55
+      filterOption={(input, option) =>
56
+        option.props.children && option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
57
+      }>
58
+          {data.map(apartment => (
59
+            <Option key={apartment.apartmentId} value={apartment.apartmentId}>{apartment.apartmentName}</Option>
60
+          ))}
61
+      </Select>
62
+  )
63
+}
64
+export default SalesBatchSelect

+ 64
- 0
src/components/HouseSelect/UnitSelect.jsx View File

@@ -0,0 +1,64 @@
1
+import React, { useState, useEffect, useRef } from 'react';
2
+import { Select } from 'antd';
3
+import apis from '../../services/apis';
4
+import request from '../../utils/request'
5
+
6
+const { Option } = Select;
7
+
8
+function usePrevious(props) {
9
+  const ref = useRef();
10
+  useEffect(() => {
11
+    ref.current = props;
12
+  });
13
+  return ref.current;
14
+}
15
+
16
+/**
17
+ *
18
+ *
19
+ * @param {*} props
20
+ * @returns
21
+ */
22
+const UnitSelect = props => {
23
+  const [data, setData] = useState([])
24
+  const [value, setValue] = useState([])
25
+  useEffect(() => {
26
+    getList();
27
+  }, [props.value])
28
+
29
+
30
+  const getList = e => {
31
+    request({ ...apis.house.unit, params: { pageNum: 1, pageSize: 999, buildingId: props.buildingId, blockId: props.blockId} }).then(data => {
32
+        setData(data.records)
33
+        checkValue(data.records)
34
+        // 默认选中第一个
35
+    })
36
+  }
37
+
38
+
39
+  const checkValue = (data) => {
40
+    if (props.value) {
41
+      const tempData = data.filter(f => f.buildingId == props.value)
42
+      const va = (tempData.length > 0) ? props.value : '已删除,请重新选择'
43
+      props.onChange(va)
44
+
45
+    }
46
+  }
47
+
48
+  return (
49
+      <Select
50
+      showSearch
51
+      value={props.value}
52
+      style={{ width: '300px' }}
53
+      placeholder="请选择单元"
54
+      onChange={props.onChange}
55
+      filterOption={(input, option) =>
56
+        option.props.children && option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
57
+      }>
58
+          {data.map(unit => (
59
+            <Option key={unit.unitId} value={unit.unitId}>{unit.unitName}</Option>
60
+          ))}
61
+      </Select>
62
+  )
63
+}
64
+export default UnitSelect

+ 166
- 0
src/components/MiniQRCode/index.jsx View File

@@ -0,0 +1,166 @@
1
+import React, { PureComponent } from 'react'
2
+import PropTypes from 'prop-types'
3
+import classNames from 'classnames'
4
+import { Button, Modal, Tooltip } from 'antd'
5
+import { fetch, apis } from '../../utils/request'
6
+
7
+import Style from './style.less'
8
+
9
+const fetchQRCode = fetch(apis.image.qrcode)
10
+
11
+function encodeParam (o) {
12
+  return Object.keys(o).map((k) => {
13
+    const v = encodeURIComponent(o[k] || '')
14
+    return `${k}=${v}`
15
+  }).join('&')
16
+}
17
+
18
+class MiniQRCode extends PureComponent {
19
+  constructor(props) {
20
+    super(props)
21
+
22
+    this.state = {
23
+      previewVisible: false,
24
+      qrcode: undefined,
25
+      loading: false,
26
+    }
27
+  }
28
+
29
+  componentDidMount() {
30
+    if (this.props.targetId && this.props.value) {
31
+      const params = this.getQRCodeParams(this.props)
32
+
33
+      if (!params) return;
34
+
35
+      this.setState({
36
+        qrcode: {
37
+          img: this.props.value,
38
+          params,
39
+        }
40
+      })
41
+    }
42
+  }
43
+
44
+  componentDidUpdate(prevProps) {
45
+    const preParams = this.getQRCodeParams(prevProps)
46
+    const curParams = this.getQRCodeParams(this.props)
47
+
48
+    // 需要重新生成二维码
49
+    if (preParams !== curParams) {
50
+      this.createQRCode()
51
+      return
52
+    }
53
+
54
+    // 如果 value 有了更新
55
+    const { img } = this.state.qrcode || {}
56
+    if (prevProps.value !== this.props.value && this.props.value !== img) {
57
+      this.setState({
58
+        qrcode: {
59
+          img: this.props.value,
60
+          params: curParams,
61
+        }
62
+      })
63
+    }
64
+  }
65
+
66
+  getQRCodeParams = (props) => {
67
+    const { targetId, sceneParams = {}, page, params = {} } = props
68
+
69
+    if (!targetId || !page) {
70
+      return;
71
+    }
72
+
73
+    const sceneObj = {
74
+      id: targetId,
75
+      ...sceneParams,
76
+    }
77
+
78
+    return JSON.stringify({
79
+      scene: encodeParam(sceneObj),
80
+      page,
81
+      ...params,
82
+    })
83
+  }
84
+
85
+  createQRCode = () => {
86
+    const params = this.getQRCodeParams(this.props)
87
+
88
+    if (!params) {
89
+      return;
90
+    }
91
+
92
+    this.setState({ loading: true }, () => {
93
+      fetchQRCode({ data: { params } }).then((img) => {
94
+        this.setState({ qrcode: { img, params }, loading: false }, () => {
95
+          if (this.props.onChange) {
96
+            this.props.onChange(img)
97
+          }
98
+        })
99
+      })
100
+    })
101
+  }
102
+
103
+  handleThumbClick = () => {
104
+    this.setState({ previewVisible: true })
105
+  }
106
+
107
+  handleCancel = () => {
108
+    this.setState({ previewVisible: false })
109
+  }
110
+
111
+  handleBtnClick = () => {
112
+    this.createQRCode()
113
+  }
114
+
115
+  render() {
116
+    const { loading, qrcode = {}, previewVisible } = this.state
117
+    const { className, style, size } = this.props
118
+    const icon = !qrcode.img ? 'plus' : 'redo'
119
+    const btnText = !qrcode.img ? '生成' : '重新生成'
120
+    const clsList = classNames(Style.miniQRCode, className)
121
+    const styleUsed = style || {}
122
+    const thumbSize = size || 120
123
+    const thumbStyle = {
124
+      width: `${thumbSize}px`,
125
+      height: `${thumbSize}px`,
126
+    }
127
+    const btnSize = thumbSize > 96 ? 'default' : 'small'
128
+
129
+    return (
130
+      <div className={clsList} style={styleUsed}>
131
+        {
132
+          qrcode.img && (
133
+            <Tooltip title={qrcode.params}>
134
+              <div className={Style.thumb} onClick={this.handleThumbClick} style={thumbStyle}>
135
+                <img src={qrcode.img} alt=""/>
136
+              </div>
137
+            </Tooltip>
138
+          )
139
+        }
140
+        <div className={Style.body}>
141
+          <Button loading={loading} size={btnSize} icon={icon} onClick={this.handleBtnClick}>{btnText}</Button>
142
+        </div>
143
+        <Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
144
+          <img alt="preview" style={{ width: '100%' }} src={qrcode.img} />
145
+        </Modal>
146
+      </div>
147
+    );
148
+  }
149
+}
150
+
151
+MiniQRCode.propTypes = {
152
+  value: PropTypes.string,
153
+  onChange: PropTypes.func,
154
+  targetId: PropTypes.oneOfType([
155
+    PropTypes.string,
156
+    PropTypes.number,
157
+  ]),
158
+  page: PropTypes.string,
159
+  sceneParams: PropTypes.object,
160
+  params: PropTypes.object,
161
+  className: PropTypes.string,
162
+  style: PropTypes.object,
163
+  size: PropTypes.number,
164
+}
165
+
166
+export default MiniQRCode

+ 20
- 0
src/components/MiniQRCode/style.less View File

@@ -0,0 +1,20 @@
1
+.miniQRCode {
2
+  background: transparent;
3
+  display: flex;
4
+  align-items: center;
5
+
6
+  .thumb {
7
+    flex: none;
8
+    padding: 8px;
9
+    margin-right: 12px;
10
+
11
+    img {
12
+      width: 100%;
13
+      height: 100%;
14
+    }
15
+  }
16
+
17
+  .body {
18
+    flex: auto;
19
+  }
20
+}