张延森 4 yıl önce
ebeveyn
işleme
0fa4249e60

+ 84
- 0
src/components/XForm/FileUpload.jsx Dosyayı Görüntüle

@@ -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, ref) {
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 React.forwardRef(fileUpload)

+ 98
- 0
src/components/XForm/ImageListUpload.jsx Dosyayı Görüntüle

@@ -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
src/components/XForm/ImageUpload.jsx Dosyayı Görüntüle

@@ -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
src/components/XForm/README.md Dosyayı Görüntüle

@@ -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
src/components/XForm/WrapperForm.jsx Dosyayı Görüntüle

@@ -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
src/components/XForm/WrapperItem.jsx Dosyayı Görüntüle

@@ -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
src/components/XForm/index.jsx Dosyayı Görüntüle

@@ -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
src/components/XForm/style.css Dosyayı Görüntüle

@@ -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
src/components/XForm/style.less Dosyayı Görüntüle

@@ -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
src/components/XForm/style.wxss Dosyayı Görüntüle

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

+ 61
- 2
src/pages/building/Edit/Basic.jsx Dosyayı Görüntüle

@@ -1,5 +1,6 @@
1 1
 import React from 'react'
2
-import { Form, Input } from 'antd'
2
+import { Form, Input, notification, Select } from 'antd'
3
+import FileUpload from '@/components/XForm/FileUpload';
3 4
 import BuildingType from './components/BuildingTypeSelect'
4 5
 import { formItemLayout } from './utils'
5 6
 
@@ -9,6 +10,20 @@ const BuildingBasic = (props) => {
9 10
   const { form } = props;
10 11
   const { getFieldDecorator } = form;
11 12
 
13
+  
14
+  // 视频文件上传前 回调
15
+  const fileUploadBeforeUpload = (file, fileList) => {
16
+    return new Promise((resolve, reject) => {
17
+      if (file.type === 'video/mp4' || file.type === '.mp4') {
18
+          // setVideoImage(true)
19
+          resolve(file)
20
+      } else {
21
+        notification.error({ message: '项目视频,仅支持MP4格式' })
22
+        reject()
23
+      }
24
+    })
25
+  }
26
+
12 27
   return (
13 28
     <Form {...formItemLayout}>
14 29
       <Item label="项目Id" style={{ display: 'none' }}>
@@ -29,9 +44,53 @@ const BuildingBasic = (props) => {
29 44
           rules: [{ required: true, message: '请选择项目类型' }],
30 45
         })(<BuildingType />)}
31 46
       </Form.Item>
47
+      <Form.Item label="列表均价" help="项目列表展示价格,示例:约10000元/㎡、约1000万元/套起">
48
+        {getFieldDecorator('price')(<Input />)}
49
+      </Form.Item>
50
+      <Form.Item label="开盘时间" >
51
+        {getFieldDecorator('openingDate')(<Input placeholder="预计xxxx年xx月开盘" />)}
52
+      </Form.Item>
53
+      <Form.Item label="电话" >
54
+        {getFieldDecorator('tel', {
55
+          rules: [
56
+            {
57
+              pattern: new RegExp('^[0-9]*$'),
58
+              message: '请输入正确的电话号码',
59
+            },
60
+          ],
61
+        })(<Input placeholder="手机或者座机号码" />)}
62
+      </Form.Item>
63
+      <Form.Item label="项目说明" >
64
+        {getFieldDecorator('dynamic')(<Input placeholder="项目动态等,不超过30个字" maxLength={30}/>)}
65
+      </Form.Item>
66
+      <Form.Item label="物业类型" >
67
+        {getFieldDecorator('propertyType')(<Input />)}
68
+      </Form.Item>
69
+      <Form.Item label="销售状态" >
70
+        {getFieldDecorator('marketStatus', {
71
+          rules: [{ required: true, message: '请选择销售状态' }],
72
+        })(
73
+          <Select placeholder="销售状态" style={{ width: '100%' }}>
74
+            <Select.Option value="待售">待售</Select.Option>
75
+            <Select.Option value="在售">在售</Select.Option>
76
+            <Select.Option value="售罄">售罄</Select.Option>
77
+            <Select.Option value="在租">在租</Select.Option>
78
+          </Select>,
79
+        )}
80
+      </Form.Item>
81
+      <Form.Item label="项目标签" >
82
+        {getFieldDecorator('tag')(
83
+          <Select mode="tags" placeholder="输入后选中" style={{ width: '100%' }} />
84
+        )}
85
+      </Form.Item>
86
+      <Form.Item label="项目视频" help="视频仅支持mp4格式,建议尺寸:750*600,比例5:4,用于楼盘详情">
87
+        {getFieldDecorator('videoUrl')(
88
+          <FileUpload accept=".mp4" beforeUpload={fileUploadBeforeUpload} label="上传视频" size={1} />,
89
+        )}
90
+      </Form.Item>
32 91
 
33 92
     </Form>
34 93
   )
35 94
 }
36 95
 
37
-export default Form.create()(BuildingBasic)
96
+export default Form.create({ onValuesChange: console.log })(BuildingBasic)

+ 62
- 16
src/pages/building/Edit/components/BuildingTypeDetail.jsx Dosyayı Görüntüle

@@ -1,19 +1,56 @@
1
-import React from 'react'
2
-import { Button, Card, Form, Radio, Input, Select } from 'antd'
1
+import React, { useState } from 'react'
2
+import { Button, Drawer, Form, Radio, Input, Select } from 'antd'
3 3
 import { formItemLayout } from '../utils'
4
+import styles from '../style.less'
4 5
 
5 6
 const Item = Form.Item
6 7
 
8
+const priceUnit = {
9
+  average: '元/㎡',
10
+  total: '万元/套',
11
+  lease: '元/㎡/天',
12
+}
13
+
14
+const noBorderInput = {
15
+  width: '30px',
16
+  borderLeft: 0,
17
+  pointerEvents: 'none',
18
+  backgroundColor: '#fff',
19
+}
20
+
7 21
 const BuildingTypeDetail = (props) => {
8
-  const { buildingType, form } = props
9
-  const { getFieldDecorator } = form
22
+  const { title, dataset, form, visible, onClose, onSubmit } = props
23
+  const { getFieldDecorator, validateFieldsAndScroll } = form
24
+  const [priceType, setPriceType] = useState()
25
+
26
+  const handleSubmit = () => {
27
+    validateFieldsAndScroll((err, values) => {
28
+      if (err) {
29
+        console.error(err);
30
+        return;
31
+      };
32
+
33
+      const data = {
34
+        ...values,
35
+        ...dataset || {},
36
+      }
37
+
38
+      onSubmit(data)
39
+    })
40
+  }
10 41
 
11 42
   return (
12
-    <Card size="small" title={buildingType?.buildingTypeName} extra={<Button type="link" icon="close" />}>
43
+    <Drawer
44
+      width={600}
45
+      title={title} 
46
+      visible={visible}
47
+      onClose={onClose}
48
+      bodyStyle={{ paddingBottom: 80 }}
49
+      >
13 50
       <Form>
14 51
         <Item label="价格类型" {...formItemLayout}>
15 52
           {getFieldDecorator('priceType')(
16
-            <Radio.Group>
53
+            <Radio.Group onChange={({target}) => setPriceType(target.value)}>
17 54
               <Radio value="average">均价</Radio>
18 55
               <Radio value="total">总价</Radio>
19 56
               <Radio value="lease">租赁价</Radio>
@@ -21,18 +58,22 @@ const BuildingTypeDetail = (props) => {
21 58
           )}
22 59
         </Item>
23 60
         <Item label="价格区间" help='最高价与最低价一致时,只展示一个' {...formItemLayout}>
24
-          <span>{getFieldDecorator('startPrice')(<Input type="number" placeholder="最低价" style={{width: '120px'}} maxLength="9" />)}</span>
25
-          <span>-</span>
26
-          <span>{getFieldDecorator('endPrice')(<Input type="number" placeholder="最高价" style={{width: '120px'}} maxLength="9" />)}</span>
27
-          {/* <span>{this.props.type.priceType === "average" ? "元/㎡" : this.props.type.priceType === "lease" ? "元/㎡/天" : "万元/套"}</span> */}
61
+          <Input.Group compact>
62
+            {getFieldDecorator('startPrice')(<Input placeholder="最低价" style={{width: 'calc(50% - 50px)'}} />)}
63
+            {<Input style={{...noBorderInput, borderRight: 0}} placeholder="~" disabled />}
64
+            {getFieldDecorator('endPrice')(
65
+              <Input placeholder="最高价" style={{width: 'calc(50% - 60px)', borderLeft: 0, borderRight: 0}} />
66
+              )}
67
+            {<Input style={{...noBorderInput, width: '80px', textAlign: 'right'}} placeholder={priceUnit[priceType]} disabled />}
68
+          </Input.Group>
28 69
         </Item>
29 70
         <Item label="销售状态" {...formItemLayout}>
30 71
           {getFieldDecorator('marketStatus')(
31 72
             <Select placeholder="销售状态">
32
-              <Option value="待售">待售</Option>
33
-              <Option value="在售">在售</Option>
34
-              <Option value="售罄">售罄</Option>
35
-              <Option value="在租">在租</Option>
73
+              <Select.Option value="待售">待售</Select.Option>
74
+              <Select.Option value="在售">在售</Select.Option>
75
+              <Select.Option value="售罄">售罄</Select.Option>
76
+              <Select.Option value="在租">在租</Select.Option>
36 77
             </Select>,
37 78
           )}
38 79
         </Item>
@@ -40,10 +81,15 @@ const BuildingTypeDetail = (props) => {
40 81
           {getFieldDecorator('decoration')(<Input />)}
41 82
         </Item>
42 83
         <Item label="产权年限" {...formItemLayout}>
43
-          {getFieldDecorator('rightsYear')(<Input />)}
84
+          {getFieldDecorator('rightsYear', {
85
+            initialValue: 70,
86
+          })(<Input />)}
44 87
         </Item>
45 88
       </Form>
46
-    </Card>
89
+      <div className={styles['drawer-form-action']}>
90
+        <Button type="primary" onClick={handleSubmit}>确定</Button>
91
+      </div>
92
+    </Drawer>
47 93
   )
48 94
 }
49 95
 

+ 95
- 10
src/pages/building/Edit/components/BuildingTypeSelect.jsx Dosyayı Görüntüle

@@ -1,19 +1,104 @@
1 1
 import React, { useState, useEffect } from 'react'
2
-import { Checkbox, Card, notification } from 'antd'
3
-import request, { apis } from '@/utils/request'
4
-import CheckBuildingType from './CheckBuildingType'
2
+import { Button, Icon, Checkbox, Card, notification } from 'antd'
5 3
 import BuildingTypeDetail from './BuildingTypeDetail'
4
+import request, { apis } from '@/utils/request'
5
+
6
+const gridStyle = {
7
+  width: '25%',
8
+  // textAlign: 'center',
9
+};
10
+
11
+export default React.forwardRef((props, ref) => {
12
+  const {value, onChange} = props;
13
+  const [showDrawer, setShowDrawer] = useState(false)
14
+  const [curRow, setCurRow] = useState()
15
+  const [curBuildingType, setBuildingType] = useState()
16
+  const [typeList, setTypeList] = useState([])
17
+
18
+  const handleChange = (buildingType) => (e) => {
19
+    const current = (value || []).filter((it) => it.buildingTypeId === buildingType.buildingTypeId)[0] || {buildingTypeId: buildingType.buildingTypeId}
20
+    const checked = e.target.checked
21
+
22
+    // 打开 form 表单
23
+    if (checked) {
24
+      setCurRow(current)
25
+      setBuildingType(buildingType)
26
+      setShowDrawer(true)
27
+    } else {
28
+      // 取消选择
29
+      const data = (value || []).filter((it) => it.buildingTypeId !== buildingType.buildingTypeId)
30
+      onChange(data)
31
+    }
32
+  }
33
+
34
+  const handleClick = (buildingType) => () => {
35
+    const current = (value || []).filter((it) => it.buildingTypeId === buildingType.buildingTypeId)[0] || {buildingTypeId: buildingType.buildingTypeId}
36
+    
37
+    setCurRow(current)
38
+    setBuildingType(buildingType)
39
+    setShowDrawer(true)
40
+  }
41
+
42
+  const handleClose = () => {
43
+    setCurRow()
44
+    setBuildingType()
45
+    setShowDrawer(false)
46
+  }
47
+
48
+  const handleSubmit = (val) => {
49
+    const data = []
50
+    let found = false
51
+    for (let it of (value || [])) {
52
+      if (it.buildingTypeId === val.buildingTypeId) {
53
+        found = true
54
+        data.push(val)
55
+      } else {
56
+        data.push(it)
57
+      }
58
+    }
59
+
60
+    if (!found) {
61
+      data.push(val)
62
+    }
6 63
 
7
-export default (props) => {
8
-  const [checkedType, setCheckedType] = useState([])
64
+    onChange(data)
65
+    setShowDrawer(false)
66
+  }
9 67
 
10 68
   useEffect(() => {
69
+    // 请求类型列表
70
+    request({ ...apis.buildingType.getList, params: { pageNum: 1, pageSize: 999 } }).then(res => {
71
+      setTypeList(res.records)
72
+    }).catch((err) => {
73
+      notification.error({ message: err.message })
74
+    })
11 75
   }, [])
12 76
 
13 77
   return (
14
-    <div>
15
-      <CheckBuildingType />
16
-      <BuildingTypeDetail />
17
-    </div>
78
+    <Card bordered={false}>
79
+      {
80
+        typeList.map((it) => {
81
+          const checked = !!(value || []).filter((x) => it.buildingTypeId === x.buildingTypeId)[0]
82
+          return (
83
+            <Card.Grid style={gridStyle} key={it.buildingTypeId} hoverable={false}>
84
+              <Checkbox
85
+                checked={checked}
86
+                onChange={handleChange(it)}
87
+              >
88
+                {it.buildingTypeName}
89
+              </Checkbox>
90
+              {checked && <a href="#" onClick={handleClick(it)}><Icon type="edit" /></a>}              
91
+            </Card.Grid>
92
+          )
93
+        })
94
+      }
95
+      <BuildingTypeDetail
96
+        title={curBuildingType?.buildingTypeName}
97
+        dataset={curRow}
98
+        visible={showDrawer}
99
+        onClose={handleClose}
100
+        onSubmit={handleSubmit}
101
+      />
102
+    </Card>
18 103
   )
19
-}
104
+})

+ 0
- 36
src/pages/building/Edit/components/CheckBuildingType.jsx Dosyayı Görüntüle

@@ -1,36 +0,0 @@
1
-import React, { useState, useEffect } from 'react'
2
-import { Checkbox, Card, notification } from 'antd'
3
-import request, { apis } from '@/utils/request'
4
-
5
-const gridStyle = {
6
-  width: '25%',
7
-  // textAlign: 'center',
8
-};
9
-
10
-export default (props) => {
11
-  const [checkedType, setCheckedType] = useState([])
12
-  const [typeList, setTypeList] = useState([])
13
-
14
-  useEffect(() => {
15
-    // 请求类型列表
16
-    request({ ...apis.buildingType.getList, params: { pageNum: 1, pageSize: 999 } }).then(res => {
17
-      setTypeList(res.records)
18
-    }).catch((err) => {
19
-      notification.error({ message: err.message })
20
-    })
21
-  }, [])
22
-
23
-  return (
24
-    <Checkbox.Group style={{ width: '100%' }}>
25
-      <Card bordered={false}>
26
-        {
27
-          typeList.map((it) => (
28
-            <Card.Grid style={gridStyle} key={it.buildingTypeId} hoverable={false}>
29
-              <Checkbox value={it.buildingTypeId}>{it.buildingTypeName}</Checkbox>
30
-            </Card.Grid>
31
-          ))
32
-        }
33
-      </Card>
34
-    </Checkbox.Group>
35
-  )
36
-}

+ 1
- 1
src/pages/building/Edit/index.jsx Dosyayı Görüntüle

@@ -10,7 +10,7 @@ export default (props) => {
10 10
     <Card>
11 11
       <Tabs defaultActiveKey="1">
12 12
         <TabPane tab="基础信息" key="1">
13
-          <div className={styles['tab-wrapper']} style={{maxWidth: 900}}>
13
+          <div className={styles['tab-wrapper']} style={{maxWidth: 1000}}>
14 14
             <Basic />
15 15
           </div>
16 16
         </TabPane>

+ 11
- 0
src/pages/building/Edit/style.less Dosyayı Görüntüle

@@ -2,3 +2,14 @@
2 2
 .tab-wrapper {
3 3
   padding-top: 1em;
4 4
 }
5
+
6
+.drawer-form-action {
7
+  position: absolute;
8
+  right: 0;
9
+  bottom: 0;
10
+  width: 100%;
11
+  border-top: 1px solid #e9e9e9;
12
+  padding: 10px 16px;
13
+  background: #fff;
14
+  text-align: right;
15
+}

+ 2
- 2
src/pages/building/Edit/utils.js Dosyayı Görüntüle

@@ -2,10 +2,10 @@
2 2
 export const formItemLayout = {
3 3
   labelCol: {
4 4
     xs: { span: 24 },
5
-    sm: { span: 4 },
5
+    sm: { span: 3 },
6 6
   },
7 7
   wrapperCol: {
8 8
     xs: { span: 24 },
9
-    sm: { span: 16 },
9
+    sm: { span: 19 },
10 10
   },
11 11
 }

+ 39
- 0
src/utils/upload.js Dosyayı Görüntüle

@@ -0,0 +1,39 @@
1
+import { fetch, apis } from './request';
2
+import mixStr from './mixStr';
3
+
4
+const getToken = () => mixStr(window.localStorage.getItem('test-foobar'))
5
+
6
+const uploadImage = fetch(apis.image.upload)
7
+
8
+const uploaderProps = {
9
+  name: 'file',
10
+  // action: apis.image.uploadForAnt.url,
11
+  accept: '.png, .jpg, .jpeg, .gif',
12
+  // headers: {
13
+  //   Authorization: `Bearer ${getToken()}`
14
+  // },
15
+  customRequest({
16
+    action,
17
+    file,
18
+    headers,
19
+    onError,
20
+    onProgress,
21
+    onSuccess,
22
+    withCredentials,
23
+  }) {
24
+    const data = new FormData()
25
+    data.append('file', file)
26
+
27
+    uploadImage({ data }).then((img) => {
28
+      onSuccess(img, file);
29
+    }).catch(onError);
30
+
31
+    return {
32
+      abort() {
33
+        console.log('upload progress is aborted.');
34
+      },
35
+    };
36
+  },
37
+}
38
+
39
+export { uploaderProps }