andrew il y a 4 ans
Parent
révision
bc0033cd8a
26 fichiers modifiés avec 1321 ajouts et 0 suppressions
  1. 53
    0
      estateagents-admin-manager/src/components/SelectLang/index.jsx
  2. 24
    0
      estateagents-admin-manager/src/components/SelectLang/index.less
  3. 33
    0
      estateagents-admin-manager/src/components/SettingDrawer/themeColorClient.js
  4. 109
    0
      estateagents-admin-manager/src/components/Wangedit/Wangedit.jsx
  5. 84
    0
      estateagents-admin-manager/src/components/XForm/FileUpload.jsx
  6. 98
    0
      estateagents-admin-manager/src/components/XForm/ImageListUpload.jsx
  7. 77
    0
      estateagents-admin-manager/src/components/XForm/ImageUpload.jsx
  8. 31
    0
      estateagents-admin-manager/src/components/XForm/README.md
  9. 81
    0
      estateagents-admin-manager/src/components/XForm/WrapperForm.jsx
  10. 151
    0
      estateagents-admin-manager/src/components/XForm/WrapperItem.jsx
  11. 9
    0
      estateagents-admin-manager/src/components/XForm/index.jsx
  12. 13
    0
      estateagents-admin-manager/src/components/XForm/style.css
  13. 20
    0
      estateagents-admin-manager/src/components/XForm/style.less
  14. 13
    0
      estateagents-admin-manager/src/components/XForm/style.wxss
  15. 36
    0
      estateagents-admin-manager/src/components/ZmageImg/ZmageImg.jsx
  16. 81
    0
      estateagents-admin-manager/src/components/uploadImage/ImageUpload.jsx
  17. 8
    0
      estateagents-admin-manager/src/components/uploadImage/style.less
  18. 1
    0
      estateagents-admin-manager/src/e2e/__mocks__/antd-pro-merge-less.js
  19. 39
    0
      estateagents-admin-manager/src/e2e/baseLayout.e2e.js
  20. 15
    0
      estateagents-admin-manager/src/e2e/topMenu.e2e.js
  21. 155
    0
      estateagents-admin-manager/src/layouts/BasicLayout.jsx
  22. 20
    0
      estateagents-admin-manager/src/layouts/BlankLayout.jsx
  23. 17
    0
      estateagents-admin-manager/src/layouts/SearchList/BodyHeader.jsx
  24. 47
    0
      estateagents-admin-manager/src/layouts/SecurityLayout.jsx
  25. 42
    0
      estateagents-admin-manager/src/layouts/UserLayout.jsx
  26. 64
    0
      estateagents-admin-manager/src/layouts/UserLayout.less

+ 53
- 0
estateagents-admin-manager/src/components/SelectLang/index.jsx Voir le fichier

@@ -0,0 +1,53 @@
1
+import { Icon, Menu } from 'antd';
2
+import { formatMessage, getLocale, setLocale } from 'umi-plugin-react/locale';
3
+import React from 'react';
4
+import classNames from 'classnames';
5
+import HeaderDropdown from '../HeaderDropdown';
6
+import styles from './index.less';
7
+
8
+const SelectLang = props => {
9
+  const { className } = props;
10
+  const selectedLang = getLocale();
11
+
12
+  const changeLang = ({ key }) => setLocale(key, false);
13
+
14
+  const locales = ['zh-CN', 'zh-TW', 'en-US', 'pt-BR'];
15
+  const languageLabels = {
16
+    'zh-CN': '简体中文',
17
+    'zh-TW': '繁体中文',
18
+    'en-US': 'English',
19
+    'pt-BR': 'Português',
20
+  };
21
+  const languageIcons = {
22
+    'zh-CN': '🇨🇳',
23
+    'zh-TW': '🇭🇰',
24
+    'en-US': '🇺🇸',
25
+    'pt-BR': '🇧🇷',
26
+  };
27
+  const langMenu = (
28
+    <Menu className={styles.menu} selectedKeys={[selectedLang]} onClick={changeLang}>
29
+      {locales.map(locale => (
30
+        <Menu.Item key={locale}>
31
+          <span role="img" aria-label={languageLabels[locale]}>
32
+            {languageIcons[locale]}
33
+          </span>{' '}
34
+          {languageLabels[locale]}
35
+        </Menu.Item>
36
+      ))}
37
+    </Menu>
38
+  );
39
+  return (
40
+    <HeaderDropdown overlay={langMenu} placement="bottomRight">
41
+      <span className={classNames(styles.dropDown, className)}>
42
+        <Icon
43
+          type="global"
44
+          title={formatMessage({
45
+            id: 'navBar.lang',
46
+          })}
47
+        />
48
+      </span>
49
+    </HeaderDropdown>
50
+  );
51
+};
52
+
53
+export default SelectLang;

+ 24
- 0
estateagents-admin-manager/src/components/SelectLang/index.less Voir le fichier

@@ -0,0 +1,24 @@
1
+@import '~antd/es/style/themes/default.less';
2
+
3
+.menu {
4
+  :global(.anticon) {
5
+    margin-right: 8px;
6
+  }
7
+  :global(.ant-dropdown-menu-item) {
8
+    min-width: 160px;
9
+  }
10
+}
11
+
12
+.dropDown {
13
+  line-height: @layout-header-height;
14
+  vertical-align: top;
15
+  cursor: pointer;
16
+  > i {
17
+    font-size: 16px !important;
18
+    transform: none !important;
19
+    svg {
20
+      position: relative;
21
+      top: -1px;
22
+    }
23
+  }
24
+}

+ 33
- 0
estateagents-admin-manager/src/components/SettingDrawer/themeColorClient.js Voir le fichier

@@ -0,0 +1,33 @@
1
+// eslint-disable-next-line eslint-comments/disable-enable-pair
2
+
3
+/* eslint-disable import/no-extraneous-dependencies */
4
+import client from 'webpack-theme-color-replacer/client';
5
+import generate from '@ant-design/colors/lib/generate';
6
+export default {
7
+  getAntdSerials(color) {
8
+    const lightCount = 9;
9
+    const divide = 10; // 淡化(即less的tint)
10
+
11
+    let lightens = new Array(lightCount).fill(0);
12
+    lightens = lightens.map((_, i) => client.varyColor.lighten(color, i / divide));
13
+    const colorPalettes = generate(color);
14
+    return lightens.concat(colorPalettes);
15
+  },
16
+
17
+  changeColor(color) {
18
+    if (!color) {
19
+      return Promise.resolve();
20
+    }
21
+
22
+    const options = {
23
+      // new colors array, one-to-one corresponde with `matchColors`
24
+      newColors: this.getAntdSerials(color),
25
+
26
+      changeUrl(cssUrl) {
27
+        // while router is not `hash` mode, it needs absolute path
28
+        return `/${cssUrl}`;
29
+      },
30
+    };
31
+    return client.changer.changeColor(options, Promise);
32
+  },
33
+};

+ 109
- 0
estateagents-admin-manager/src/components/Wangedit/Wangedit.jsx Voir le fichier

@@ -0,0 +1,109 @@
1
+import React from 'react';
2
+import E from 'wangeditor';
3
+import { fetch, apis } from '../../utils/request';
4
+
5
+/**
6
+ * @param {*} props
7
+ * @returns
8
+ */
9
+class Wangedit extends React.Component {
10
+  constructor(props, context) {
11
+    super(props, context);
12
+    this.state = {
13
+      html: undefined,
14
+      contenteditable: props.contenteditable == false ? false : true
15
+    }
16
+    this.editor = undefined;
17
+  }
18
+
19
+  render() {
20
+    return (
21
+      <div ref="editorElem" style={{ textAlign: 'left' }}>
22
+      </div>
23
+    );
24
+  }
25
+
26
+  componentDidMount() {
27
+    const elem = this.refs.editorElem
28
+    this.editor = new E(elem)
29
+    // 使用 onchange 函数监听内容的变化
30
+    this.editor.customConfig.onchange = html => {
31
+      this.setState({ html })
32
+
33
+      if (typeof this.props.onChange === 'function') {
34
+        this.props.onChange(html)
35
+      }
36
+    }
37
+    this.editor.customConfig.zIndex = 100
38
+    this.editor.customConfig.uploadImgMaxLength = 1
39
+    this.editor.customConfig.customUploadImg = function (files, insert) {
40
+      if (!files.length) return
41
+      
42
+      const data = new FormData()
43
+      data.append('file', files[0])
44
+
45
+      fetch(apis.image.upload)({data}).then(insert)
46
+    }
47
+    this.editor.customConfig.menus = [
48
+      'head',  // 标题
49
+      'bold',  // 粗体
50
+      'fontSize',  // 字号
51
+      'fontName',  // 字体
52
+      'italic',  // 斜体
53
+      'underline',  // 下划线
54
+      'strikeThrough',  // 删除线
55
+      'foreColor',  // 文字颜色
56
+      'backColor',  // 背景颜色
57
+      'list',  // 列表
58
+      'justify',  // 对齐方式
59
+      'quote',  // 引用
60
+      'image',  // 插入图片
61
+      'undo',  // 撤销
62
+      'redo'  // 重复
63
+    ]
64
+    
65
+    // 过滤 word 字符
66
+    this.editor.customConfig.pasteFilterStyle = false
67
+    this.editor.customConfig.pasteTextHandle = function(content) {
68
+      const regs = [
69
+        /<!--\[if [\s\S]*?endif\]-->/ig,
70
+        /<[a-zA-Z0-9]+\:[^>]+>[^>]*<\/[a-zA-Z0-9]+\:[^>]+>/ig,
71
+        /<[a-zA-Z0-9]+\:[^>]+\/>/ig,
72
+        /<style>[\s\S]*?<\/style>/ig,
73
+        new RegExp('\u2029', 'ig'),     // 替换word分隔符 序号 8233
74
+      ]
75
+
76
+      return regs.reduce((acc, reg) => {
77
+        return acc.replace(reg, '')
78
+      }, content)
79
+    }
80
+
81
+    this.editor.create()
82
+    this.editor.$textElem.attr('contenteditable',this.state.contenteditable);
83
+    this.editor.customConfig.uploadImgShowBase64 = true
84
+    this.editor.txt.html(this.props.value)
85
+  }
86
+
87
+  componentDidUpdate(props, state) {
88
+    if (this.props.value && !state.html) {
89
+      if (this.editor) {
90
+        this.editor.txt.html(this.props.value)
91
+      }
92
+    }
93
+  }
94
+
95
+  /**
96
+   *增加这个 shouldComponentUpdate 生命函数
97
+    处理自动聚焦到富文本上
98
+   *
99
+   * @param {*} nextProps
100
+   * @returns
101
+   * @memberof Wangedit
102
+   */
103
+  shouldComponentUpdate(nextProps) {
104
+    return nextProps.value !== this.editor.txt.html()
105
+  }
106
+}
107
+
108
+export default Wangedit
109
+

+ 84
- 0
estateagents-admin-manager/src/components/XForm/FileUpload.jsx Voir le fichier

@@ -0,0 +1,84 @@
1
+import React, { useState, useEffect } from 'react'
2
+import { Upload, Button, Icon } from 'antd';
3
+import { uploaderProps } from '../../utils/upload';
4
+
5
+/**
6
+ * value 数据的接收格式 [{ url: xxxxx.mp4 }]
7
+ * size  参数限制可以上传多少个文件
8
+ * @param { value, size } props
9
+ */
10
+function fileUpload(props) {
11
+
12
+  const { value } = props
13
+  // console.log('fileUploadProps: ', props)
14
+  // eslint-disable-next-line react-hooks/rules-of-hooks
15
+  const [defaultFileList, setDefaultFileList] = useState([])
16
+
17
+  // eslint-disable-next-line react-hooks/rules-of-hooks
18
+  useEffect(() => {
19
+    setDefaultValue()
20
+  }, [props.value]);
21
+
22
+
23
+  function getFileList() {
24
+    console.log('fileUpload: ', value)
25
+    // value 数据的接收格式 [{ url: xxxxx.mp4 }, { url: xxxxx.jpg }]
26
+    return (value || []).filter(f => f !== undefined).map((img, inx) => ({ uid: inx, url: img, name: img.substring(img.lastIndexOf('/') + 1, img.length), status: 'done' }))
27
+  }
28
+
29
+  function setDefaultValue() {
30
+    if (!value) {
31
+      return;
32
+    }
33
+
34
+    setDefaultFileList(getFileList())
35
+  }
36
+
37
+  function onFileChange({ file, fileList }) {
38
+    console.log(file, fileList)
39
+    setDefaultFileList(fileList)
40
+    if (file.status === 'uploading') {
41
+      return
42
+    }
43
+
44
+    if (file.status === 'done' || file.status === 'removed') {
45
+
46
+      // 因为这个控件本身返回的数据格式 [{ ..., response: '服务器返回的数据' }]
47
+      // 但是 我这里自己用的时候 传入的数据是 [ 'xxxx.mp4', 'xxxx.jpg' ], 然后通过 getFileList() 转换成了 [{ url: 'xxx.mp4' }] 这样的格式
48
+
49
+      // 原因是因为 控件返回的数据 和 fileList 展示已经上传的数据 的格式字段不一样
50
+
51
+      const resFileList = fileList.filter(f => f.response !== undefined).map(i => i.response)
52
+      const tempFileList = fileList.filter(f => f.url !== undefined).map(i => i.url)
53
+      const resultList = tempFileList.concat(resFileList || [])
54
+      props.onChange(resultList)
55
+    }
56
+  }
57
+
58
+  return (
59
+    <>
60
+      <Upload
61
+        { ...uploaderProps }
62
+        {...props}
63
+        onChange={onFileChange}
64
+        fileList={defaultFileList}
65
+      >
66
+        {
67
+          props.size ?
68
+          (props.size > defaultFileList.length
69
+          && (
70
+            <Button>
71
+                <Icon type="upload" /> {props.label}
72
+              </Button>
73
+          )) : (
74
+            <Button>
75
+              <Icon type="upload" /> {props.label}
76
+            </Button>
77
+          )
78
+        }
79
+      </Upload>
80
+    </>
81
+  )
82
+}
83
+
84
+export default fileUpload

+ 98
- 0
estateagents-admin-manager/src/components/XForm/ImageListUpload.jsx Voir le fichier

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

+ 77
- 0
estateagents-admin-manager/src/components/XForm/ImageUpload.jsx Voir le fichier

@@ -0,0 +1,77 @@
1
+import React from 'react';
2
+import { Upload, Icon, message } from 'antd';
3
+import './style.less';
4
+import { uploaderProps } from '../../utils/upload';
5
+
6
+
7
+
8
+class ImageUpload extends React.Component {
9
+  state = {
10
+    loading: false,
11
+    imageUrl: undefined,
12
+  };
13
+
14
+  handleChange = info => {
15
+    if (info.file.status === "uploading") {
16
+      this.setState({ loading: true });
17
+      return;
18
+    }
19
+    
20
+    if (info.file.status === 'removed') {
21
+      this.props.onChange();
22
+    }
23
+
24
+    // if (info.file.status === "done") {
25
+    //   this.setState({
26
+    //     loading: false,
27
+    //   })
28
+
29
+    //   if (info.file.response && info.file.response.url) {
30
+    //     this.setState({
31
+    //       imageUrl: info.file.response.thumbUrl,
32
+    //     });
33
+
34
+    //     if (typeof this.props.onChange === 'function') {
35
+    //       this.props.onChange(info.file.response.url);
36
+    //     }
37
+    //   }
38
+    // }
39
+  };
40
+
41
+  handleUploadSucess = (url) => {
42
+    this.setState({ loading: false });
43
+    if (typeof this.props.onChange === 'function') {
44
+      this.props.onChange(url);
45
+    }
46
+  }
47
+
48
+  render() {
49
+    const uploadButton = (
50
+      <div>
51
+        <Icon style={{ fontSize: '2em', color: '#aaa' }} type={this.state.loading ? "loading" : "plus"} />
52
+      </div>
53
+    );
54
+
55
+    const value = this.props.value;
56
+    return (
57
+      <Upload
58
+        listType="picture-card"
59
+        className="avatar-uploader"
60
+        showUploadList={false}
61
+        beforeUpload={this.props.beforeUpload}
62
+        onChange={this.handleChange}
63
+        {...uploaderProps}
64
+        disabled={this.props.disabled}
65
+        onSuccess={this.handleUploadSucess}
66
+      >
67
+        {(this.state.imageUrl || value) ? (
68
+          <img src={this.state.imageUrl || value} alt="avatar" style={{ width: "100%" }} />
69
+        ) : (
70
+            uploadButton
71
+          )}
72
+      </Upload>
73
+    );
74
+  }
75
+}
76
+
77
+export default ImageUpload;

+ 31
- 0
estateagents-admin-manager/src/components/XForm/README.md Voir le fichier

@@ -0,0 +1,31 @@
1
+# XForm
2
+Antd Form 扩展
3
+
4
+## 使用说明
5
+
6
+```jsx
7
+import XForm, { FieldTypes } from '/path/to/XForm';
8
+
9
+const tplJSON = [
10
+  {},
11
+  {}
12
+]
13
+
14
+```
15
+
16
+通过 `json` 对象来实现自动构造 form 表单
17
+
18
+计划支持的属性
19
+
20
+[] label      显示的文字
21
+
22
+[] name       form name, 用于表单提交
23
+
24
+[] type       组件类型, 参见 FieldTypes
25
+
26
+[] rules      校验规则
27
+
28
+[] element    函数, 用于生成组件. (props) => <></> . 其中 props 包含 ...
29
+
30
+[] value      绑定的数据
31
+

+ 81
- 0
estateagents-admin-manager/src/components/XForm/WrapperForm.jsx Voir le fichier

@@ -0,0 +1,81 @@
1
+import React from 'react';
2
+import PropTypes from 'prop-types';
3
+import { Form, Button } from 'antd';
4
+import WrapperItem from './WrapperItem';
5
+
6
+const formItemLayout = {
7
+  labelCol: {
8
+    xs: { span: 24 },
9
+    sm: { span: 8 },
10
+  },
11
+  wrapperCol: {
12
+    xs: { span: 24 },
13
+    sm: { span: 16 },
14
+  },
15
+};
16
+
17
+class WrapperForm extends React.Component {
18
+  constructor(props) {
19
+    super(props);
20
+
21
+    this.state = {}
22
+  }
23
+
24
+  handleSubmit = e => {
25
+    e.preventDefault();
26
+    this.props.form.validateFieldsAndScroll((err, values) => {
27
+      if (err) {
28
+        if (typeof this.props.onError === 'function') {
29
+          typeof this.props.onError(err)
30
+        } else {
31
+          window.console.error(err)
32
+        }
33
+      } else {
34
+        if (typeof this.props.onSubmit === 'function') {
35
+          typeof this.props.onSubmit(values)
36
+        }
37
+      }
38
+    });
39
+  }
40
+
41
+  handleCancel = e => {
42
+    e.preventDefault();
43
+
44
+    if (typeof this.props.onCancel === 'function') {
45
+      typeof this.props.onCancel()
46
+    }
47
+  }
48
+
49
+  renderDefaultAction = (submitBtn, cancelBtn) => {
50
+    let FieldSubmit = null
51
+    let FieldCancel = null
52
+    if (submitBtn !== false) {
53
+      FieldSubmit = <Button htmlType="submit" type="primary">提交</Button>
54
+    }
55
+    if (cancelBtn !== false) {
56
+      FieldCancel = <Button htmlType="button" onClick={this.handleCancel} style={{ marginLeft: '48px' }}>取消</Button>
57
+    }
58
+
59
+    return FieldSubmit || FieldCancel ? <WrapperItem action render={<>{FieldSubmit}{FieldCancel}</>} /> : null
60
+  }
61
+
62
+  render () {
63
+    const {fields, form, children, submitBtn, cancelBtn, ...formProps} = this.props;
64
+    console.log('fields:', fields)
65
+    const FeildItems = (fields || []).filter(x => x).map((props, inx) => (<WrapperItem key={inx} {...props} form={form} />))
66
+    
67
+    return (
68
+      <Form {...formItemLayout} {...formProps} onSubmit={this.handleSubmit}>
69
+        {FeildItems}
70
+        {this.renderDefaultAction(submitBtn, cancelBtn)}
71
+        {children}
72
+      </Form>
73
+    );
74
+  }
75
+}
76
+
77
+WrapperForm.propTypes = {
78
+  fields: PropTypes.array.isRequired
79
+}
80
+
81
+export default WrapperForm;

+ 151
- 0
estateagents-admin-manager/src/components/XForm/WrapperItem.jsx Voir le fichier

@@ -0,0 +1,151 @@
1
+import React from 'react';
2
+import {
3
+  Form,
4
+  Input,
5
+  InputNumber,
6
+  Checkbox,
7
+  Radio,
8
+  DatePicker,
9
+  TimePicker,
10
+  Select,
11
+  Switch,
12
+} from 'antd';
13
+import ImageUploader from './ImageUpload';
14
+
15
+const {Item} = Form
16
+const { Option } = Select;
17
+const { MonthPicker, RangePicker } = DatePicker
18
+
19
+const FieldTypes = {
20
+  Text: 'Text',
21
+  Password: 'Password',
22
+  Number: 'Number',
23
+  Switch: 'Switch',
24
+  TimePicker: 'TimePicker',
25
+  DatePicker: 'DatePicker',
26
+  RangePicker: 'RangePicker',
27
+  MonthPicker: 'MonthPicker',
28
+  Select: 'Select',
29
+  ImageUploader: 'ImageUploader',
30
+}
31
+
32
+const tailFormItemLayout = {
33
+  wrapperCol: {
34
+    xs: {
35
+      span: 24,
36
+      offset: 0,
37
+    },
38
+    sm: {
39
+      span: 16,
40
+      offset: 8,
41
+    },
42
+  },
43
+};
44
+
45
+const WrapperItem = props => {
46
+  const {
47
+    form,
48
+    label,
49
+    name,
50
+    type,
51
+    placeholder,
52
+    render,
53
+    rules,
54
+    value,
55
+    dict,
56
+    action,
57
+    props: fieldProps,
58
+    hidden = false,
59
+    help,
60
+    ...restProps
61
+  } = props;
62
+
63
+  const { getFieldDecorator } = form || {};
64
+
65
+  let config = {
66
+    rules,
67
+    initialValue: value,
68
+  }
69
+
70
+  if (type === FieldTypes.Switch) {
71
+    config = {
72
+      ...config,
73
+      valuePropName: 'checked',
74
+    };
75
+  }
76
+
77
+  if (type === FieldTypes.ImageUploader) {
78
+    config = {
79
+      ...config,
80
+      getValueFromEvent: x => x,
81
+    };
82
+  }
83
+
84
+  // 没有类型与组件, 生成隐藏字段
85
+  if (!type && !render) {
86
+    getFieldDecorator(name, config);
87
+    return null;
88
+  }
89
+
90
+  const SelectOpts = (dict || []).map((item, index) => (<Option value={item.value}>{item.label}</Option>))
91
+
92
+  let Field = <></>;
93
+  if (render) {
94
+    Field = typeof render === 'function' ? render(props) : render
95
+  } else {
96
+    switch (type) {
97
+      case FieldTypes.Text:
98
+        Field = <Input placeholder={placeholder} style={{ width: '100%' }} {...fieldProps}/>;
99
+        break;
100
+      case FieldTypes.Password:
101
+        Field = <Input.Password placeholder={placeholder} style={{ width: '100%' }} {...fieldProps}/>;
102
+        break;
103
+      case FieldTypes.Number:
104
+        Field = <InputNumber placeholder={placeholder} style={{ width: '100%' }} {...fieldProps}/>;
105
+        break;
106
+      case FieldTypes.Switch:
107
+        Field = <Switch {...fieldProps} />;
108
+        break;
109
+      case FieldTypes.TimePicker:
110
+        Field = <TimePicker {...fieldProps} style={{ width: '100%' }}/>;
111
+        break;
112
+      case FieldTypes.DatePicker:
113
+        Field = <DatePicker {...fieldProps} style={{ width: '100%' }}/>;
114
+        break;
115
+      case FieldTypes.RangePicker:
116
+        Field = <RangePicker {...fieldProps} style={{ width: '100%' }}/>;
117
+        break;
118
+      case FieldTypes.MonthPicker:
119
+        Field = <MonthPicker {...fieldProps} style={{ width: '100%' }}/>;
120
+        break;
121
+      case FieldTypes.Select:
122
+        Field = <Select placeholder={placeholder} style={{ width: '100%' }} {...fieldProps}>{SelectOpts}</Select>
123
+        break;
124
+      case FieldTypes.ImageUploader:
125
+        const beforeUpload = { beforeUpload: props.beforeUpload }
126
+        Field = <ImageUploader {...fieldProps} { ...beforeUpload}/>
127
+        break;
128
+      default:
129
+        throw new Error(`暂时不支持的组件类型: ${type}`)
130
+    }
131
+  }
132
+
133
+  const visible = typeof hidden === 'function' ? !hidden() : !hidden;
134
+
135
+  if (!label && !name && !action && !help) return visible ? Field : null;
136
+
137
+  const itemProps = action ? { ...restProps, ...tailFormItemLayout } : restProps
138
+  const labelNode = typeof label === 'function' ? label() : label;
139
+  const helpNode = typeof help === 'function' ? help() : help;
140
+
141
+  const newItemProps = { ...itemProps, help: helpNode }
142
+
143
+  return visible && (
144
+    <Item label={labelNode} { ...newItemProps }>
145
+      {action ? Field : getFieldDecorator(name, config)(Field)}
146
+    </Item>
147
+  )
148
+};
149
+
150
+export default WrapperItem;
151
+export { FieldTypes };

+ 9
- 0
estateagents-admin-manager/src/components/XForm/index.jsx Voir le fichier

@@ -0,0 +1,9 @@
1
+import React from 'react';
2
+import { Form } from 'antd';
3
+import WrapperForm from './WrapperForm';
4
+import { FieldTypes } from './WrapperItem';
5
+
6
+const createForm = option => Form.create(option)(WrapperForm);
7
+
8
+export default Form.create()(WrapperForm);
9
+export { FieldTypes, createForm };

+ 13
- 0
estateagents-admin-manager/src/components/XForm/style.css Voir le fichier

@@ -0,0 +1,13 @@
1
+:global .avatar-uploader > .ant-upload {
2
+  width: 128px;
3
+  height: 128px;
4
+}
5
+/* you can make up upload button and sample style by using stylesheets */
6
+.ant-upload-select-picture-card i {
7
+  font-size: 32px;
8
+  color: #999;
9
+}
10
+.ant-upload-select-picture-card .ant-upload-text {
11
+  margin-top: 8px;
12
+  color: #666;
13
+}

+ 20
- 0
estateagents-admin-manager/src/components/XForm/style.less Voir le fichier

@@ -0,0 +1,20 @@
1
+:global {
2
+  .avatar-uploader {
3
+    & > .ant-upload {
4
+      width: 128px;
5
+      height: 128px;
6
+    }
7
+  }
8
+}
9
+
10
+
11
+/* you can make up upload button and sample style by using stylesheets */
12
+.ant-upload-select-picture-card i {
13
+  font-size: 32px;
14
+  color: #999;
15
+}
16
+
17
+.ant-upload-select-picture-card .ant-upload-text {
18
+  margin-top: 8px;
19
+  color: #666;
20
+}

+ 13
- 0
estateagents-admin-manager/src/components/XForm/style.wxss Voir le fichier

@@ -0,0 +1,13 @@
1
+:global .avatar-uploader > .ant-upload {
2
+  width: 128px;
3
+  height: 128px;
4
+}
5
+/* you can make up upload button and sample style by using stylesheets */
6
+.ant-upload-select-picture-card i {
7
+  font-size: 32px;
8
+  color: #999;
9
+}
10
+.ant-upload-select-picture-card .ant-upload-text {
11
+  margin-top: 8px;
12
+  color: #666;
13
+}

+ 36
- 0
estateagents-admin-manager/src/components/ZmageImg/ZmageImg.jsx Voir le fichier

@@ -0,0 +1,36 @@
1
+import React from 'react'
2
+import Zmage from 'react-zmage'
3
+
4
+function body(props) {
5
+  return (
6
+    <>
7
+      <Zmage
8
+        style={props.style}
9
+        src={props.src}
10
+        // alt="0"
11
+        backdrop="linear-gradient(90deg, rgba(86,81,81,0.7) 0%, rgba(86,81,81, 0.7) 100%)"
12
+        radius={5}
13
+        edge={20}
14
+        animate={{
15
+          flip: 'fade',
16
+        }}
17
+        controller={{
18
+          // 关闭按钮
19
+          close: false,
20
+          // 缩放按钮
21
+          zoom: false,
22
+          // 下载按钮
23
+          download: false,
24
+          // 旋转按钮
25
+          rotate: false,
26
+          // 翻页按钮
27
+          flip: true,
28
+          // 多页指示
29
+          pagination: true,
30
+        }}
31
+      />
32
+    </>
33
+  )
34
+}
35
+
36
+export default body

+ 81
- 0
estateagents-admin-manager/src/components/uploadImage/ImageUpload.jsx Voir le fichier

@@ -0,0 +1,81 @@
1
+import React from 'react';
2
+import { Upload, Icon, message, Modal } from 'antd';
3
+import './style.less';
4
+import { uploaderProps } from '../../utils/upload';
5
+
6
+
7
+
8
+class ImageUpload extends React.Component {
9
+
10
+  state = {
11
+    loading: false,
12
+    previewVisible: false,
13
+    previewImage: '',
14
+    fileList: this.getPropsFileList(),
15
+  };
16
+
17
+  componentDidUpdate(prevProps) {
18
+    // 直接 setState 会死循环
19
+    if (prevProps.value !== this.props.value) {
20
+      this.setState({ fileList: this.getPropsFileList() })
21
+    }
22
+  }
23
+
24
+  getPropsFileList() {
25
+    return !this.props.value ? [] : [{
26
+      uid: '-1',
27
+      name: 'image.png',
28
+      status: 'done',
29
+      url: this.props.value,
30
+    }]
31
+  }
32
+
33
+
34
+  handleChange = ({ file, fileList }) => {
35
+    const { status, response } = file
36
+    this.setState({ fileList, loading: status === "uploading" })
37
+
38
+    if (typeof this.props.onChange === 'function' && status != "uploading") {
39
+      const image = status === 'done' ? response : undefined
40
+      this.props.onChange(image);
41
+    }
42
+  };
43
+
44
+  handleCancel = () => this.setState({ previewVisible: false });
45
+
46
+  handlePreview = async file => {
47
+    console.log(file)
48
+    this.setState({
49
+      previewImage: file.url,
50
+      previewVisible: true,
51
+    });
52
+  };
53
+
54
+  render() {
55
+    const uploadButton = (
56
+      <div>
57
+        <Icon style={{ fontSize: '2em', color: '#aaa' }} type={this.state.loading ? "loading" : "plus"} />
58
+      </div>
59
+    );
60
+
61
+    return (
62
+      <>
63
+      <Upload
64
+        {...uploaderProps}
65
+        listType="picture-card"
66
+        className="avatar-uploader"
67
+        onChange={this.handleChange}
68
+        fileList={this.state.fileList}
69
+        onPreview={this.handlePreview}
70
+      >
71
+        {this.state.fileList.length >= 1 ? null : uploadButton}  
72
+      </Upload>
73
+      <Modal visible={this.state.previewVisible} footer={null} onCancel={this.handleCancel}>
74
+      <img alt="example" style={{ width: '100%' }} src={this.state.previewImage} />
75
+      </Modal>
76
+      </>
77
+    );
78
+  }
79
+}
80
+
81
+export default ImageUpload;

+ 8
- 0
estateagents-admin-manager/src/components/uploadImage/style.less Voir le fichier

@@ -0,0 +1,8 @@
1
+:global {
2
+  .avatar-uploader {
3
+    & > .ant-upload {
4
+      width: 128px;
5
+      height: 128px;
6
+    }
7
+  }
8
+}

+ 1
- 0
estateagents-admin-manager/src/e2e/__mocks__/antd-pro-merge-less.js Voir le fichier

@@ -0,0 +1 @@
1
+export default undefined;

+ 39
- 0
estateagents-admin-manager/src/e2e/baseLayout.e2e.js Voir le fichier

@@ -0,0 +1,39 @@
1
+const RouterConfig = require('../../config/config').default.routes;
2
+const { uniq } = require('lodash');
3
+
4
+const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
5
+
6
+function formatter(routes, parentPath = '') {
7
+  const fixedParentPath = parentPath.replace(/\/{1,}/g, '/');
8
+  let result = [];
9
+  routes.forEach(item => {
10
+    if (item.path) {
11
+      result.push(`${fixedParentPath}/${item.path}`.replace(/\/{1,}/g, '/'));
12
+    }
13
+    if (item.routes) {
14
+      result = result.concat(
15
+        formatter(item.routes, item.path ? `${fixedParentPath}/${item.path}` : parentPath),
16
+      );
17
+    }
18
+  });
19
+  return uniq(result.filter(item => !!item));
20
+}
21
+
22
+describe('Ant Design Pro E2E test', () => {
23
+  const testPage = path => async () => {
24
+    await page.goto(`${BASE_URL}${path}`);
25
+    await page.waitForSelector('footer', {
26
+      timeout: 2000,
27
+    });
28
+    const haveFooter = await page.evaluate(
29
+      () => document.getElementsByTagName('footer').length > 0,
30
+    );
31
+    expect(haveFooter).toBeTruthy();
32
+  };
33
+
34
+  const routers = formatter(RouterConfig);
35
+  console.log('routers', routers);
36
+  routers.forEach(route => {
37
+    it(`test pages ${route}`, testPage(route));
38
+  });
39
+});

+ 15
- 0
estateagents-admin-manager/src/e2e/topMenu.e2e.js Voir le fichier

@@ -0,0 +1,15 @@
1
+const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
2
+
3
+describe('Homepage', () => {
4
+  it('topmenu should have footer', async () => {
5
+    const params = '/form/basic-form?navTheme=light&layout=topmenu';
6
+    await page.goto(`${BASE_URL}${params}`);
7
+    await page.waitForSelector('footer', {
8
+      timeout: 2000,
9
+    });
10
+    const haveFooter = await page.evaluate(
11
+      () => document.getElementsByTagName('footer').length > 0,
12
+    );
13
+    expect(haveFooter).toBeTruthy();
14
+  });
15
+});

+ 155
- 0
estateagents-admin-manager/src/layouts/BasicLayout.jsx Voir le fichier

@@ -0,0 +1,155 @@
1
+/**
2
+ * Ant Design Pro v4 use `@ant-design/pro-layout` to handle Layout.
3
+ * You can view component api by:
4
+ * https://github.com/ant-design/ant-design-pro-layout
5
+ */
6
+import ProLayout from '@ant-design/pro-layout';
7
+import React, { useEffect } from 'react';
8
+import Link from 'umi/link';
9
+import Redirect from 'umi/redirect';
10
+import { connect } from 'dva';
11
+import { formatMessage } from 'umi-plugin-react/locale';
12
+// import Authorized from '@/utils/Authorized';
13
+import RightContent from '@/components/GlobalHeader/RightContent';
14
+import RenderAuthorize from '@/components/Authorized';
15
+import { isAntDesignPro } from '@/utils/utils';
16
+import logo from '../assets/logo.png';
17
+
18
+const footerRender = () => {
19
+  return (<></>
20
+    // <div
21
+    //   style={{
22
+    //     padding: ' 44px 0',
23
+    //     textAlign: 'center',
24
+    //     fontSize: '30px',
25
+    //     fontFamily: 'monospace',
26
+    //     fontWeight: '200',
27
+    //     // height:'200px',
28
+    //     color: 'rgba(102, 102, 102, 1)',
29
+    //     // position: 'fixed',
30
+    //     // bottom: '0',
31
+    //     // right: '0',
32
+    //     // marginTop: '20px',
33
+    //     // width:'100%'
34
+    //   }}
35
+    // >
36
+    //   Copy Right @ 南京云致
37
+    // </div>
38
+  )
39
+}
40
+const menuHeaderRender = (user) => (logo, title) => {
41
+  if (user && user.orgId) {
42
+    return (
43
+      <Link to="/">
44
+        <img src={user.orgLogo} alt="logo"/>
45
+        <h1>{user.miniAppName}</h1>
46
+      </Link>
47
+    );
48
+  } else {
49
+    return (
50
+      <Link to="/">
51
+        {logo}
52
+        {title}
53
+      </Link>
54
+    );
55
+  }
56
+}
57
+
58
+const BasicLayout = props => {
59
+  const { dispatch, children, settings } = props;
60
+
61
+  useEffect(() => {
62
+    if (dispatch && !props.user.currentUser.userId) {
63
+      dispatch({
64
+        type: 'user/fetchCurrent',
65
+      });
66
+      dispatch({
67
+        type: 'settings/getSetting',
68
+      });
69
+    }
70
+  }, []);
71
+
72
+  useEffect(() => {
73
+    const link = document.querySelector("link[rel*='icon']")
74
+    if (link) {
75
+      link.setAttribute('rel', 'shortcut icon')
76
+      link.setAttribute('href', props.user.currentUser.orgLogo)
77
+    }
78
+  }, [])
79
+
80
+  const Authorized = RenderAuthorize(props.user.currentUser.roles)
81
+
82
+  const handleMenuCollapse = payload => {
83
+    if (dispatch) {
84
+      dispatch({
85
+        type: 'global/changeLayoutCollapsed',
86
+        payload,
87
+      });
88
+    }
89
+  };
90
+
91
+  const findAuthority = path => ((props.user.menuList || []).filter(x => x.code === path)[0] || {}).roles
92
+
93
+  /**
94
+   * use Authorized check all menu item
95
+   */
96
+  const menuDataRender = menuList =>
97
+    menuList.map(item => {
98
+      const localItem = { ...item, children: item.children ? menuDataRender(item.children) : [] };
99
+      const authority = findAuthority(item.path);
100
+      // return Authorized.check(item.authority, localItem, null);
101
+      return Authorized.check(authority, localItem, null);
102
+    });
103
+
104
+  const checkRights = children => {
105
+    const authority = findAuthority(props.location.pathname);
106
+    return Authorized.check(authority, children, <Redirect to="/403" />);
107
+  }
108
+
109
+  return (
110
+    <ProLayout
111
+      logo={logo}
112
+      onCollapse={handleMenuCollapse}
113
+      menuItemRender={(menuItemProps, defaultDom) => {
114
+        if (menuItemProps.isUrl) {
115
+          return defaultDom;
116
+        }
117
+        return <Link to={menuItemProps.path}>{defaultDom}</Link>;
118
+      }}
119
+      breadcrumbRender={(routers = []) => [
120
+        {
121
+          path: '/',
122
+          breadcrumbName: formatMessage({
123
+            id: 'menu.home',
124
+            defaultMessage: 'Home',
125
+          }),
126
+        },
127
+        ...routers,
128
+      ]}
129
+      itemRender={(route, params, routes, paths) => {
130
+        const first = routes.indexOf(route) === 0;
131
+        return first ? (
132
+          <Link to={paths.join('/')}>{route.breadcrumbName}</Link>
133
+        ) : (
134
+            <span>{route.breadcrumbName}</span>
135
+          );
136
+      }}
137
+      footerRender={footerRender}
138
+      menuDataRender={menuDataRender}
139
+      menuHeaderRender={menuHeaderRender(props.user.currentUser)}
140
+      // formatMessage={formatMessage}
141
+      rightContentRender={rightProps => <RightContent {...rightProps} />}
142
+      {...props}
143
+      {...settings}
144
+    // pageTitleRender={()=><></>}
145
+    >
146
+      {checkRights(children)}
147
+    </ProLayout>
148
+  );
149
+};
150
+
151
+export default connect(({ global, settings, user }) => ({
152
+  collapsed: global.collapsed,
153
+  settings,
154
+  user
155
+}))(BasicLayout);

+ 20
- 0
estateagents-admin-manager/src/layouts/BlankLayout.jsx Voir le fichier

@@ -0,0 +1,20 @@
1
+import React from 'react';
2
+import { PageHeaderWrapper } from '@ant-design/pro-layout';
3
+
4
+const Layout = ({ children }) => (
5
+  <PageHeaderWrapper style={{height:'auto',background:'rgba(240,240,240,1)',paddingTop:'15px'}}>
6
+    <div
7
+      style={{
8
+        backgroundColor: '#fff',
9
+        padding: '32PX 28px',
10
+        boxShadow: '0px 0px 16px 2px rgba(0,0,0,0.12)',
11
+        borderRadius: '12px',
12
+        minHeight:'60vh'
13
+      }}
14
+    >
15
+      {children}
16
+    </div>
17
+  </PageHeaderWrapper>
18
+);
19
+
20
+export default Layout;

+ 17
- 0
estateagents-admin-manager/src/layouts/SearchList/BodyHeader.jsx Voir le fichier

@@ -0,0 +1,17 @@
1
+import React, { PureComponent } from 'react'
2
+import Style from './style.less';
3
+
4
+export default class BodyHeader extends PureComponent {
5
+  render() {
6
+    return (
7
+      <div className={Style['body-header']}>
8
+        <div className={Style['body-header-title']}>
9
+          {this.props.title}
10
+        </div>
11
+        <div className={Style['body-header-action']}>
12
+          {this.props.actions}
13
+        </div>
14
+      </div>
15
+    )
16
+  }
17
+}

+ 47
- 0
estateagents-admin-manager/src/layouts/SecurityLayout.jsx Voir le fichier

@@ -0,0 +1,47 @@
1
+import React from 'react';
2
+import { connect } from 'dva';
3
+import { Redirect } from 'umi';
4
+import PageLoading from '@/components/PageLoading';
5
+import Swiper from '../pages/swiper/index';
6
+
7
+class SecurityLayout extends React.Component {
8
+  state = {
9
+    isReady: false,
10
+  };
11
+
12
+  componentDidMount() {
13
+    this.setState({
14
+      isReady: true,
15
+    });
16
+    const { dispatch } = this.props;
17
+
18
+    if (dispatch) {
19
+      dispatch({
20
+        type: 'user/fetchCurrent',
21
+      });
22
+    }
23
+  }
24
+
25
+  render() {
26
+    const { isReady } = this.state;
27
+    const { children, loading, currentUser } = this.props;
28
+
29
+    if ((!currentUser.userId && loading) || !isReady) {
30
+      return <PageLoading />;
31
+    }
32
+
33
+    if (!currentUser.userId) {
34
+      return <Redirect to="/user/login"></Redirect>;
35
+    }
36
+    console.log(window.location, "window.locationwindow.locationwindow.location")
37
+    return <>
38
+      {children}
39
+      {window.location.hash == "#/index" && <Swiper />}
40
+    </>;
41
+  }
42
+}
43
+
44
+export default connect(({ user, loading }) => ({
45
+  currentUser: user.currentUser,
46
+  loading: loading.models.user,
47
+}))(SecurityLayout);

+ 42
- 0
estateagents-admin-manager/src/layouts/UserLayout.jsx Voir le fichier

@@ -0,0 +1,42 @@
1
+import { DefaultFooter, getMenuData, getPageTitle } from '@ant-design/pro-layout';
2
+import DocumentTitle from 'react-document-title';
3
+import Link from 'umi/link';
4
+import React from 'react';
5
+import { connect } from 'dva';
6
+import { formatMessage } from 'umi-plugin-react/locale';
7
+import logo from '../assets/logo.svg';
8
+import styles from './UserLayout.less';
9
+
10
+const UserLayout = props => {
11
+  const {
12
+    route = {
13
+      routes: [],
14
+    },
15
+  } = props;
16
+  const { routes = [] } = route;
17
+  const {
18
+    children,
19
+    location = {
20
+      pathname: '',
21
+    },
22
+  } = props;
23
+  const { breadcrumb } = getMenuData(routes);
24
+  return (
25
+    <DocumentTitle
26
+      title={getPageTitle({
27
+        pathname: location.pathname,
28
+        breadcrumb,
29
+        formatMessage,
30
+        ...props,
31
+      })}
32
+    >
33
+      <div className={styles.container}>
34
+        <div className={styles.content}>
35
+          {children}
36
+        </div>
37
+      </div>
38
+    </DocumentTitle>
39
+  );
40
+};
41
+
42
+export default connect(({ settings }) => ({ ...settings }))(UserLayout);

+ 64
- 0
estateagents-admin-manager/src/layouts/UserLayout.less Voir le fichier

@@ -0,0 +1,64 @@
1
+@import '~antd/es/style/themes/default.less';
2
+
3
+.container {
4
+  display: flex;
5
+  flex-direction: column;
6
+  height: 100vh;
7
+  overflow: auto;
8
+  background: @layout-body-background;
9
+}
10
+
11
+.lang {
12
+  width: 100%;
13
+  height: 40px;
14
+  line-height: 44px;
15
+  text-align: right;
16
+  :global(.ant-dropdown-trigger) {
17
+    margin-right: 24px;
18
+  }
19
+}
20
+
21
+.content {
22
+  flex: 1;
23
+}
24
+
25
+@media (min-width: @screen-md-min) {
26
+  .container {
27
+    background-color: #fff;
28
+  }
29
+
30
+}
31
+
32
+.top {
33
+  text-align: center;
34
+}
35
+
36
+.header {
37
+  height: 44px;
38
+  line-height: 44px;
39
+  a {
40
+    text-decoration: none;
41
+  }
42
+}
43
+
44
+.logo {
45
+  height: 44px;
46
+  margin-right: 16px;
47
+  vertical-align: top;
48
+}
49
+
50
+.title {
51
+  position: relative;
52
+  top: 2px;
53
+  color: @heading-color;
54
+  font-weight: 600;
55
+  font-size: 33px;
56
+  font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
57
+}
58
+
59
+.desc {
60
+  margin-top: 12px;
61
+  margin-bottom: 40px;
62
+  color: @text-color-secondary;
63
+  font-size: @font-size-base;
64
+}