andrew преди 4 години
родител
ревизия
5cca594cdd

+ 115
- 0
estateagents-admin-manager/src/components/EditIcon/index.jsx Целия файл

@@ -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
estateagents-admin-manager/src/components/GlobalHeader/AvatarDropdown.jsx Целия файл

@@ -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
estateagents-admin-manager/src/components/GlobalHeader/NoticeIconView.jsx Целия файл

@@ -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
estateagents-admin-manager/src/components/GlobalHeader/RightContent.jsx Целия файл

@@ -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
estateagents-admin-manager/src/components/GlobalHeader/ShowPassword.jsx Целия файл

@@ -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
estateagents-admin-manager/src/components/GlobalHeader/index.less Целия файл

@@ -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
estateagents-admin-manager/src/components/HeaderDropdown/index.jsx Целия файл

@@ -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
estateagents-admin-manager/src/components/HeaderDropdown/index.less Целия файл

@@ -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
+}