andrew 4 jaren geleden
bovenliggende
commit
bb6424f4c6

BIN
src/assets/statistics.png Bestand weergeven


BIN
src/assets/touxiang.jpg Bestand weergeven


BIN
src/assets/xiaochengxu.png Bestand weergeven


BIN
src/assets/yinhao.png Bestand weergeven


+ 128
- 0
src/components/AMap/AMapSearch.jsx Bestand weergeven

@@ -0,0 +1,128 @@
1
+import React from 'react';
2
+import ReactDOM from 'react-dom';
3
+import { Map, Marker } from 'react-amap'
4
+import debounce from 'lodash.debounce'
5
+import styles from './style.less'
6
+
7
+const getPosFromProps = props => {
8
+  if (props.value) {
9
+    const temp = props.value.split(',')
10
+    return { longitude: temp[0], latitude: temp[1] }
11
+  } else {
12
+    return null
13
+  }
14
+}
15
+
16
+class AMapSearch extends React.PureComponent {
17
+
18
+  constructor(props) {
19
+    super(props);
20
+    this.state = {
21
+      markerPosition: getPosFromProps(props),
22
+    }
23
+
24
+    // 高德地图 Marker 实例
25
+    this.markerInstance = undefined
26
+    // 高德地图 Map 实例
27
+    this.mapInstance = undefined
28
+
29
+    this.amapEvents = {
30
+      created: mapInstance => {
31
+        this.mapInstance = mapInstance
32
+        
33
+        AMap.plugin(['AMap.Autocomplete', 'AMap.PlaceSearch', 'AMap.CitySearch'], () => {
34
+          // 实例化Autocomplete
35
+          const autoOptions = { input: 'amapInput' }
36
+          const autoComplete = new AMap.Autocomplete(autoOptions)
37
+          const placeSearch = new AMap.PlaceSearch({ map: mapInstance })
38
+
39
+          // 监听下拉框选中事件
40
+          AMap.event.addListener(autoComplete, 'select', e => {
41
+            // TODO 针对选中的poi实现自己的功能
42
+            placeSearch.setCity(e.poi.adcode)
43
+            placeSearch.search(e.poi.name)
44
+          })
45
+
46
+          const citySearch = new AMap.CitySearch()
47
+          citySearch.getLocalCity((status, result) => {
48
+            if (status === 'complete' && result.info === 'OK') {
49
+              // 查询成功,result即为当前所在城市信息
50
+              console.log('当前所在城市:', result)
51
+              if (result && result.city && result.bounds) {
52
+                // 当前城市位置信息
53
+                const citybounds = result.bounds;
54
+                // 地图显示当前城市
55
+                mapInstance.setBounds(citybounds);
56
+                // 需要在设置坐标成功后,重新设置 缩放级别
57
+                // mapInstance.setZoom(15)
58
+              }
59
+            }
60
+          })
61
+
62
+          // 搜索结果点击
63
+          placeSearch.on('markerClick', debounce(e => {
64
+            const { location: loc } = e.data
65
+            const lngLat = `${loc.lng},${loc.lat}`
66
+            this.props.onChange(lngLat, e)
67
+          }, 300, { leading: true }))
68
+        })
69
+
70
+        // 实例点击事件
71
+        mapInstance.on('click', debounce(e => {
72
+          const lngLat = `${e.lnglat.getLng()},${e.lnglat.getLat()}`
73
+          this.props.onChange(lngLat, e)
74
+        }, 300, { leading: true }));
75
+      },
76
+    };
77
+    
78
+    this.markerEvents = {
79
+      created: markerInstance => {
80
+        this.markerInstance = markerInstance
81
+        console.log('--->',markerInstance)
82
+      }
83
+    }
84
+    // this.markerPosition = { longitude: 120, latitude: 30 };
85
+  }
86
+
87
+  static getDerivedStateFromProps(props, state) {
88
+    if (props.value && !state.markerPosition) {
89
+      return { markerPosition: getPosFromProps(props) }
90
+    }
91
+
92
+    return null
93
+  }
94
+
95
+  componentDidUpdate(prevProps) {
96
+    if (this.props.value !== prevProps.value) {
97
+      if (this.props.value) {
98
+        this.setState({ markerPosition: getPosFromProps(this.props) }, () => {
99
+          if (this.mapInstance) {
100
+            // 需要在设置坐标成功后,重新设置 缩放级别
101
+            // this.mapInstance.setZoom(15)
102
+          }
103
+        })
104
+      }
105
+    }
106
+  }
107
+
108
+  render() {
109
+    return (
110
+      <>
111
+        <div style={{ width: '100%', height: '100%', minHeight: '400px', position: 'relative' }}>
112
+          {/* zoom={15} 设置后,无效,不知道什么原因,必须手动设置 */}
113
+          <Map plugins={['ToolBar']} events={this.amapEvents} amapkey={this.props.amapkey} center={this.state.markerPosition}>
114
+            <Marker position={this.state.markerPosition} events={this.markerEvents} clickable />
115
+          </Map>
116
+          {
117
+          <div className={styles.infoBox}>
118
+            <span className={styles.inputText}>请输入关键字</span>
119
+            <input id="amapInput" className={styles.input} type="text" />
120
+          </div>
121
+          }
122
+        </div>
123
+      </>
124
+    )
125
+  }
126
+}
127
+
128
+export default AMapSearch

+ 7
- 0
src/components/AMap/index.js Bestand weergeven

@@ -0,0 +1,7 @@
1
+import AMapSearch from './AMapSearch'
2
+import { regeo as getRegeo } from './utils'
3
+
4
+export {
5
+  AMapSearch,
6
+  getRegeo
7
+}

+ 53
- 0
src/components/AMap/style.less Bestand weergeven

@@ -0,0 +1,53 @@
1
+
2
+.infoBox {
3
+  padding: 10px;
4
+  position: absolute;
5
+  top: 20px;
6
+  left: 20px;
7
+  background-color: white;
8
+  width: 290px;
9
+  box-shadow: 0 2px 6px 0 rgba(114, 124, 245, .5);
10
+  z-index: 100;
11
+  .inputText{
12
+    margin-right: 10px;
13
+  }
14
+  .input{
15
+    width: 150px;
16
+    height: 32px;
17
+    font-size: 16px;
18
+  }
19
+ 
20
+}
21
+
22
+
23
+/* types */
24
+
25
+.native-toast-error {
26
+  background-color: #d92727;
27
+  color: white;
28
+}
29
+
30
+.native-toast-success {
31
+  background-color: #62a465;
32
+  color: white;
33
+}
34
+
35
+.native-toast-warning {
36
+  background-color: #fdaf17;
37
+  color: white;
38
+}
39
+
40
+.native-toast-info {
41
+  background-color: #5060ba;
42
+  color: white;
43
+}
44
+
45
+[class^="native-toast-icon-"] {
46
+  vertical-align: middle;
47
+  margin-right: 8px
48
+}
49
+
50
+[class^="native-toast-icon-"] svg {
51
+  width: 16px;
52
+  height: 16px;
53
+}

+ 26
- 0
src/components/AMap/utils.js Bestand weergeven

@@ -0,0 +1,26 @@
1
+import { fetch, apis } from '@/utils/request'
2
+
3
+export function regeo(key, params) {
4
+  return new Promise((resolve, reject) => {
5
+    fetch(apis.amap.regeo)({
6
+      params: {
7
+        key,
8
+        output: 'json',
9
+        extensions: 'base',
10
+        batch: 'false',
11
+        roadlevel: 0,
12
+        ...params,
13
+      }
14
+    }).then(res => {
15
+      if (res.status === '1' && res.infocode === '10000') {
16
+        resolve(res.regeocode)
17
+      } else {
18
+        console.error(res)
19
+        resolve(res.info)
20
+      }
21
+    }).catch(err => {
22
+      console.error(err)
23
+      resolve(err.message)
24
+    })
25
+  })
26
+}

+ 42
- 0
src/components/ActionList/index.jsx Bestand weergeven

@@ -0,0 +1,42 @@
1
+import React from 'react';
2
+import { Menu, Dropdown, Icon } from 'antd';
3
+
4
+import './style.less';
5
+
6
+export default function withActions(actionsRender) {
7
+  return function ActionList(text, record) {
8
+    const actions = actionsRender(text, record);
9
+    const [Act1, Act2, ...leftActs] = (actions || []).filter(Boolean);
10
+    const showMore = leftActs && leftActs.length > 0;
11
+
12
+    const menus = (
13
+      <Menu className="actlist-menu">
14
+        {[Act2].concat(leftActs).filter(Boolean).map((Act, index) => (<Menu.Item key={`dm-${index}`}>{Act}</Menu.Item>))}
15
+      </Menu>
16
+    );
17
+    console.log('---------->', record, menus)
18
+
19
+    return (
20
+      <div className="action-list">
21
+        {
22
+          Act1 && <span>{Act1}</span>
23
+        }
24
+        {
25
+          !showMore && Act2 && <span>{Act2}</span>
26
+        }
27
+        {
28
+          showMore ?
29
+          (
30
+            <span>
31
+              <Dropdown overlay={menus} trigger={['click']}>
32
+                <a onClick={e => e.preventDefault()}>
33
+                  <span>更多</span> <Icon type="down" />
34
+                </a>
35
+              </Dropdown>
36
+            </span>
37
+          ) : null
38
+        }
39
+      </div>
40
+    );
41
+  }
42
+}

+ 29
- 0
src/components/ActionList/style.less Bestand weergeven

@@ -0,0 +1,29 @@
1
+
2
+:global{
3
+  .action-list {  
4
+    span {
5
+      display: inline-block;
6
+      & + span {
7
+        margin-left: 16px;
8
+      }
9
+  
10
+      a {
11
+        color: #666;
12
+  
13
+        span {
14
+          color: #FF4A4A;
15
+        }
16
+      }
17
+    }
18
+  }
19
+  
20
+  .actlist-menu {
21
+    .ant-dropdown-menu-item {
22
+      color: #666 !important;
23
+
24
+      &:hover {
25
+        background-color: rgba(0,0,0, .1);
26
+      }
27
+    }
28
+  }
29
+}

+ 32
- 0
src/components/AuthButton/index.jsx Bestand weergeven

@@ -0,0 +1,32 @@
1
+import React from 'react';
2
+
3
+let allBtns = [];
4
+let current = [];
5
+
6
+const AuthButton = ({ children, name, noRight }) => {
7
+  const btn = allBtns.filter(x => x.code === name)[0]
8
+
9
+  // 没维护的按钮, 或者不需要权限的按钮直接通过
10
+  // if (!btn || !btn.roles || !btn.roles.length) {
11
+  //   return <>{children}</>
12
+  // }
13
+
14
+  // const hasRight = btn.roles.some(x => current.some(y => x === y))
15
+
16
+  if (!btn) {
17
+    return <>{children}</>
18
+  }
19
+
20
+  const hasRight = current.filter(x => x.code === name)[0]
21
+  
22
+  return hasRight ? <>{children}</> : <>{noRight}</>
23
+}
24
+
25
+const setAllBtnAuth = x => allBtns = x;
26
+const setUserBtnAuth = x => current = x;
27
+
28
+export default AuthButton;
29
+export {
30
+  setAllBtnAuth,
31
+  setUserBtnAuth,
32
+};

+ 10
- 0
src/components/Authorized/Authorized.jsx Bestand weergeven

@@ -0,0 +1,10 @@
1
+import React from 'react';
2
+import check from './CheckPermissions';
3
+
4
+const Authorized = ({ children, authority, noMatch = null }) => {
5
+  const childrenRender = typeof children === 'undefined' ? null : children;
6
+  const dom = check(authority, childrenRender, noMatch);
7
+  return <>{dom}</>;
8
+};
9
+
10
+export default Authorized;

+ 25
- 0
src/components/Authorized/AuthorizedRoute.jsx Bestand weergeven

@@ -0,0 +1,25 @@
1
+import { Redirect, Route } from 'umi';
2
+import React from 'react';
3
+import Authorized from './Authorized';
4
+
5
+const AuthorizedRoute = ({ component: Component, render, authority, redirectPath, ...rest }) => (
6
+  <Authorized
7
+    authority={authority}
8
+    noMatch={
9
+      <Route
10
+        {...rest}
11
+        render={() => (
12
+          <Redirect
13
+            to={{
14
+              pathname: redirectPath,
15
+            }}
16
+          />
17
+        )}
18
+      />
19
+    }
20
+  >
21
+    <Route {...rest} render={props => (Component ? <Component {...props} /> : render(props))} />
22
+  </Authorized>
23
+);
24
+
25
+export default AuthorizedRoute;

+ 76
- 0
src/components/Authorized/CheckPermissions.jsx Bestand weergeven

@@ -0,0 +1,76 @@
1
+import React from 'react';
2
+import { CURRENT } from './renderAuthorize'; // eslint-disable-next-line import/no-cycle
3
+
4
+import PromiseRender from './PromiseRender';
5
+
6
+/**
7
+ * 通用权限检查方法
8
+ * Common check permissions method
9
+ * @param { 权限判定 | Permission judgment } authority
10
+ * @param { 你的权限 | Your permission description } currentAuthority
11
+ * @param { 通过的组件 | Passing components } target
12
+ * @param { 未通过的组件 | no pass components } Exception
13
+ */
14
+const checkPermissions = (authority, currentAuthority, target, Exception) => {
15
+  // 没有判定权限.默认查看所有
16
+  // Retirement authority, return target;
17
+  if (!authority) {
18
+    return target;
19
+  } // 数组处理
20
+
21
+  if (Array.isArray(authority)) {
22
+    if (Array.isArray(currentAuthority)) {
23
+      if (currentAuthority.some(item => authority.includes(item))) {
24
+        return target;
25
+      }
26
+    } else if (authority.includes(currentAuthority)) {
27
+      return target;
28
+    }
29
+
30
+    return Exception;
31
+  } // string 处理
32
+
33
+  if (typeof authority === 'string') {
34
+    if (Array.isArray(currentAuthority)) {
35
+      if (currentAuthority.some(item => authority === item)) {
36
+        return target;
37
+      }
38
+    } else if (authority === currentAuthority) {
39
+      return target;
40
+    }
41
+
42
+    return Exception;
43
+  } // Promise 处理
44
+
45
+  if (authority instanceof Promise) {
46
+    return <PromiseRender ok={target} error={Exception} promise={authority} />;
47
+  } // Function 处理
48
+
49
+  if (typeof authority === 'function') {
50
+    try {
51
+      const bool = authority(currentAuthority); // 函数执行后返回值是 Promise
52
+
53
+      if (bool instanceof Promise) {
54
+        return <PromiseRender ok={target} error={Exception} promise={bool} />;
55
+      }
56
+
57
+      if (bool) {
58
+        return target;
59
+      }
60
+
61
+      return Exception;
62
+    } catch (error) {
63
+      throw error;
64
+    }
65
+  }
66
+
67
+  throw new Error('unsupported parameters');
68
+};
69
+
70
+export { checkPermissions };
71
+
72
+function check(authority, target, Exception) {
73
+  return checkPermissions(authority, CURRENT, target, Exception);
74
+}
75
+
76
+export default check;

+ 78
- 0
src/components/Authorized/PromiseRender.jsx Bestand weergeven

@@ -0,0 +1,78 @@
1
+import React from 'react';
2
+import { Spin } from 'antd';
3
+import isEqual from 'lodash/isEqual';
4
+import { isComponentClass } from './Secured'; // eslint-disable-next-line import/no-cycle
5
+
6
+export default class PromiseRender extends React.Component {
7
+  state = {
8
+    component: () => null,
9
+  };
10
+
11
+  componentDidMount() {
12
+    this.setRenderComponent(this.props);
13
+  }
14
+
15
+  shouldComponentUpdate = (nextProps, nextState) => {
16
+    const { component } = this.state;
17
+
18
+    if (!isEqual(nextProps, this.props)) {
19
+      this.setRenderComponent(nextProps);
20
+    }
21
+
22
+    if (nextState.component !== component) return true;
23
+    return false;
24
+  }; // set render Component : ok or error
25
+
26
+  setRenderComponent(props) {
27
+    const ok = this.checkIsInstantiation(props.ok);
28
+    const error = this.checkIsInstantiation(props.error);
29
+    props.promise
30
+      .then(() => {
31
+        this.setState({
32
+          component: ok,
33
+        });
34
+        return true;
35
+      })
36
+      .catch(() => {
37
+        this.setState({
38
+          component: error,
39
+        });
40
+      });
41
+  } // Determine whether the incoming component has been instantiated
42
+  // AuthorizedRoute is already instantiated
43
+  // Authorized  render is already instantiated, children is no instantiated
44
+  // Secured is not instantiated
45
+
46
+  checkIsInstantiation = target => {
47
+    if (isComponentClass(target)) {
48
+      const Target = target;
49
+      return props => <Target {...props} />;
50
+    }
51
+
52
+    if (React.isValidElement(target)) {
53
+      return props => React.cloneElement(target, props);
54
+    }
55
+
56
+    return () => target;
57
+  };
58
+
59
+  render() {
60
+    const { component: Component } = this.state;
61
+    const { ok, error, promise, ...rest } = this.props;
62
+    return Component ? (
63
+      <Component {...rest} />
64
+    ) : (
65
+      <div
66
+        style={{
67
+          width: '100%',
68
+          height: '100%',
69
+          margin: 'auto',
70
+          paddingTop: 50,
71
+          textAlign: 'center',
72
+        }}
73
+      >
74
+        <Spin size="large" />
75
+      </div>
76
+    );
77
+  }
78
+}

+ 70
- 0
src/components/Authorized/Secured.jsx Bestand weergeven

@@ -0,0 +1,70 @@
1
+import React from 'react';
2
+import CheckPermissions from './CheckPermissions';
3
+/**
4
+ * 默认不能访问任何页面
5
+ * default is "NULL"
6
+ */
7
+
8
+const Exception403 = () => 403;
9
+
10
+export const isComponentClass = component => {
11
+  if (!component) return false;
12
+  const proto = Object.getPrototypeOf(component);
13
+  if (proto === React.Component || proto === Function.prototype) return true;
14
+  return isComponentClass(proto);
15
+}; // Determine whether the incoming component has been instantiated
16
+// AuthorizedRoute is already instantiated
17
+// Authorized  render is already instantiated, children is no instantiated
18
+// Secured is not instantiated
19
+
20
+const checkIsInstantiation = target => {
21
+  if (isComponentClass(target)) {
22
+    const Target = target;
23
+    return props => <Target {...props} />;
24
+  }
25
+
26
+  if (React.isValidElement(target)) {
27
+    return props => React.cloneElement(target, props);
28
+  }
29
+
30
+  return () => target;
31
+};
32
+/**
33
+ * 用于判断是否拥有权限访问此 view 权限
34
+ * authority 支持传入 string, () => boolean | Promise
35
+ * e.g. 'user' 只有 user 用户能访问
36
+ * e.g. 'user,admin' user 和 admin 都能访问
37
+ * e.g. ()=>boolean 返回true能访问,返回false不能访问
38
+ * e.g. Promise  then 能访问   catch不能访问
39
+ * e.g. authority support incoming string, () => boolean | Promise
40
+ * e.g. 'user' only user user can access
41
+ * e.g. 'user, admin' user and admin can access
42
+ * e.g. () => boolean true to be able to visit, return false can not be accessed
43
+ * e.g. Promise then can not access the visit to catch
44
+ * @param {string | function | Promise} authority
45
+ * @param {ReactNode} error 非必需参数
46
+ */
47
+
48
+const authorize = (authority, error) => {
49
+  /**
50
+   * conversion into a class
51
+   * 防止传入字符串时找不到staticContext造成报错
52
+   * String parameters can cause staticContext not found error
53
+   */
54
+  let classError = false;
55
+
56
+  if (error) {
57
+    classError = () => error;
58
+  }
59
+
60
+  if (!authority) {
61
+    throw new Error('authority is required');
62
+  }
63
+
64
+  return function decideAuthority(target) {
65
+    const component = CheckPermissions(authority, target, classError || Exception403);
66
+    return checkIsInstantiation(component);
67
+  };
68
+};
69
+
70
+export default authorize;

+ 10
- 0
src/components/Authorized/index.jsx Bestand weergeven

@@ -0,0 +1,10 @@
1
+import Authorized from './Authorized';
2
+import AuthorizedRoute from './AuthorizedRoute';
3
+import Secured from './Secured';
4
+import check from './CheckPermissions';
5
+import renderAuthorize from './renderAuthorize';
6
+Authorized.Secured = Secured;
7
+Authorized.AuthorizedRoute = AuthorizedRoute;
8
+Authorized.check = check;
9
+const RenderAuthorize = renderAuthorize(Authorized);
10
+export default RenderAuthorize;

+ 30
- 0
src/components/Authorized/renderAuthorize.js Bestand weergeven

@@ -0,0 +1,30 @@
1
+/* eslint-disable eslint-comments/disable-enable-pair */
2
+
3
+/* eslint-disable import/no-mutable-exports */
4
+let CURRENT = 'NULL';
5
+
6
+/**
7
+ * use  authority or getAuthority
8
+ * @param {string|()=>String} currentAuthority
9
+ */
10
+const renderAuthorize = Authorized => currentAuthority => {
11
+  if (currentAuthority) {
12
+    if (typeof currentAuthority === 'function') {
13
+      CURRENT = currentAuthority();
14
+    }
15
+
16
+    if (
17
+      Object.prototype.toString.call(currentAuthority) === '[object String]' ||
18
+      Array.isArray(currentAuthority)
19
+    ) {
20
+      CURRENT = currentAuthority;
21
+    }
22
+  } else {
23
+    CURRENT = 'NULL';
24
+  }
25
+
26
+  return Authorized;
27
+};
28
+
29
+export { CURRENT };
30
+export default Authorized => renderAuthorize(Authorized);

+ 134
- 0
src/components/Cards/PosterCard.jsx Bestand weergeven

@@ -0,0 +1,134 @@
1
+import React, { useState, useEffect } from 'react';
2
+import { Card, Checkbox, Button, Upload } from 'antd';
3
+import apis from '../../services/apis';
4
+import request from '../../utils/request'
5
+import { uploaderProps } from '../../utils/upload';
6
+import head1 from '../../assets/head1.png';
7
+import head2 from '../../assets/head2.png';
8
+import head3 from '../../assets/head3.png';
9
+import link from '../../assets/link.png';
10
+
11
+const { Meta } = Card;
12
+
13
+/**
14
+ *
15
+ *
16
+ * @param {*} props
17
+ * @returns
18
+ */
19
+const PosterCard = (props) => {
20
+  const [data, setData] = useState([])
21
+  const [indexOneUrl, setIndexOneUrl] = useState('')
22
+  const [indexTwoUrl, setIndexTwoUrl] = useState('')
23
+  const [indexThreeUrl, setIndexThreeUrl] = useState('')
24
+  let resultData = []
25
+
26
+  useEffect(() => {
27
+  }, [])
28
+
29
+  const handleUploadIndexOneSucess = url => {
30
+    console.log(url, "第一张图上传回调");
31
+    setIndexOneUrl(url)
32
+    resultData[0] = url
33
+    resultData[1] = indexTwoUrl
34
+    resultData[2] = indexThreeUrl
35
+    props.onChange(resultData)
36
+  }
37
+
38
+  const handleUploadIndexTwoSucess = url => {
39
+    console.log(url, "第二张图上传回调");
40
+    setIndexTwoUrl(url)
41
+    resultData[0] = indexOneUrl
42
+    resultData[1] = url
43
+    resultData[2] = indexThreeUrl
44
+    props.onChange(resultData)
45
+  }
46
+
47
+  const handleUploadIndexThreeSucess = url => {
48
+    console.log(url, "第三张图上传回调");
49
+    setIndexThreeUrl(url)
50
+    resultData[0] = indexOneUrl
51
+    resultData[1] = indexTwoUrl
52
+    resultData[2] = url
53
+    props.onChange(resultData)
54
+  }
55
+
56
+  return (
57
+
58
+    <div style={{ display: 'flex' }}>
59
+
60
+      <div style={{ width: '420px', height: '940px', display: 'inline-block', marginTop: '30px' }}>
61
+        <div style={{ width: '375px', height: '780px', backgroundColor: '#CFCFCF', boxShadow: '0px 0px 16px 6px rgba(0,0,0,0.15)', position: 'relative', margin: '0 auto' }}>
62
+          <img style={{ width: '100%', height: '100%' }} src={indexOneUrl} alt="" />
63
+          <img src={head1} style={{ width: '66px', height: '66px', position: 'absolute', left: '20px', bottom: '38px' }} alt="" />
64
+          <p style={{ margin: '0', fontSize: '18px', position: 'absolute', left: '96px', bottom: '79px', color: '#333' }}>小五子</p>
65
+          <p style={{ margin: '0', fontSize: '15px', position: 'absolute', left: '96px', bottom: '58px', color: '#333' }}>175 1256 0225</p>
66
+          <p style={{ margin: '0', fontSize: '14px', position: 'absolute', left: '96px', bottom: '39px', color: '#666' }}>邀请您参与活动</p>
67
+          <img src={link} style={{ margin: '0', width: '80px', height: '80px', backgroundColor: '#fff', padding: '4px', borderRadius: '6px', position: 'absolute', right: '30px', bottom: '45px' }} alt="" />
68
+          <p style={{ margin: '0', fontSize: '13px', position: 'absolute', right: '30px', bottom: '20px', color: '#666' }}>长按识别更多</p>
69
+
70
+        </div>
71
+        <p style={{ textAlign: 'center', fontSize: '19px', color: '#666', marginTop: '30px' }}>生成海报分享给朋友</p>
72
+        <p style={{ textAlign: 'center' }}>
73
+          <Upload showUploadList={false}
74
+            {...uploaderProps}
75
+            onSuccess={handleUploadIndexOneSucess}>
76
+            <Button>
77
+              上传图片
78
+          </Button>
79
+          </Upload>
80
+        </p>
81
+      </div>
82
+
83
+      <div style={{ width: '420px', height: '940px', display: 'inline-block', marginTop: '30px' }}>
84
+        <div style={{ width: '375px', height: '780px', backgroundColor: '#CFCFCF', boxShadow: '0px 0px 16px 6px rgba(0,0,0,0.15)', position: 'relative', margin: '0 auto' }}>
85
+          <img style={{ width: '100%', height: '100%' }} src={indexTwoUrl} alt="" />
86
+          <img src={head1} style={{ width: '66px', height: '66px', position: 'absolute', left: '20px', bottom: '122px' }} alt="" />
87
+          <p style={{ margin: '0', fontSize: '18px', position: 'absolute', left: '96px', bottom: '163px', color: '#333' }}>小五子</p>
88
+          <p style={{ margin: '0', fontSize: '15px', position: 'absolute', left: '96px', bottom: '142px', color: '#333' }}>175 1256 0225</p>
89
+          <p style={{ margin: '0', fontSize: '14px', position: 'absolute', left: '96px', bottom: '121px', color: '#666' }}>邀请您参与活动</p>
90
+          <div style={{ margin: '0', width: '100%', height: '112px', backgroundColor: '#fff', position: 'absolute', left: '0px', bottom: '0.5px', }}>
91
+            <p style={{ margin: '0', fontSize: '16px', position: 'absolute', left: '30px', bottom: '58px', color: '#666' }}>长按识别小程序码</p>
92
+            <p style={{ margin: '0', fontSize: '16px', position: 'absolute', left: '30px', bottom: '31px', color: '#666' }}>进入<span style={{ margin: '0 10px', fontSize: '17px', color: '#000' }}>致云</span>查看详情</p>
93
+            <img src={link} style={{ margin: '0', width: '100px', height: '100px', position: 'absolute', right: '20px', bottom: '6px' }} alt="" />
94
+          </div>
95
+        </div>
96
+        <p style={{ textAlign: 'center', fontSize: '19px', color: '#666', marginTop: '30px' }}>生成海报分享给朋友</p>
97
+        <p style={{ textAlign: 'center' }}>
98
+          <Upload showUploadList={false}
99
+            {...uploaderProps}
100
+            onSuccess={handleUploadIndexTwoSucess}>
101
+            <Button>
102
+              上传图片
103
+          </Button>
104
+          </Upload></p>
105
+      </div>
106
+
107
+      <div style={{ width: '420px', height: '940px', display: 'inline-block', marginTop: '30px' }}>
108
+        <div style={{ width: '375px', height: '780px', backgroundColor: '#CFCFCF', boxShadow: '0px 0px 16px 6px rgba(0,0,0,0.15)', position: 'relative', margin: '0 auto' }}>
109
+          <img style={{ width: '100%', height: '100%' }} src={indexThreeUrl} alt="" />
110
+          <img src={head1} style={{ width: '66px', height: '66px', position: 'absolute', left: '20px', bottom: '142px' }} alt="" />
111
+          <p style={{ margin: '0', fontSize: '18px', position: 'absolute', left: '96px', bottom: '183px', color: '#333' }}>小五子</p>
112
+          <p style={{ margin: '0', fontSize: '15px', position: 'absolute', left: '96px', bottom: '162px', color: '#333' }}>175 1256 0225</p>
113
+          <p style={{ margin: '0', fontSize: '14px', position: 'absolute', left: '96px', bottom: '141px', color: '#666' }}>邀请您参与活动</p>
114
+          <div style={{ margin: '0', width: '335px', borderRadius: '6px', height: '112px', backgroundColor: '#fff', position: 'absolute', left: '20px', bottom: '20px', }}></div>
115
+          <p style={{ margin: '0', fontSize: '16px', position: 'absolute', left: '50px', bottom: '78px', color: '#666' }}>长按识别小程序码</p>
116
+          <p style={{ margin: '0', fontSize: '16px', position: 'absolute', left: '50px', bottom: '51px', color: '#666' }}>进入<span style={{ margin: '0 10px', fontSize: '17px', color: '#000' }}>致云</span>查看详情</p>
117
+          <img src={link} style={{ margin: '0', width: '100px', height: '100px', position: 'absolute', right: '50px', bottom: '26px' }} alt="" />
118
+        </div>
119
+        <p style={{ textAlign: 'center', fontSize: '19px', color: '#666', marginTop: '30px' }}>生成海报分享给朋友</p>
120
+        <p style={{ textAlign: 'center' }}>
121
+          <Upload showUploadList={false}
122
+            {...uploaderProps}
123
+            onSuccess={handleUploadIndexThreeSucess}>
124
+            <Button>
125
+              上传图片
126
+          </Button>
127
+          </Upload>
128
+        </p>
129
+      </div>
130
+    </div>
131
+  )
132
+}
133
+export default PosterCard
134
+

+ 166
- 0
src/components/CommEditor/index.jsx Bestand weergeven

@@ -0,0 +1,166 @@
1
+import React, { useEffect, useState } from 'react'
2
+import { PageHeader, Form, Button, Spin, Modal, notification } from 'antd'
3
+
4
+const formItemLayout = {
5
+  labelCol: {
6
+    xs: { span: 24 },
7
+    sm: { span: 4 },
8
+  },
9
+  wrapperCol: {
10
+    xs: { span: 24 },
11
+    sm: { span: 12 },
12
+  },
13
+}
14
+
15
+const tailFormItemLayout = {
16
+  wrapperCol: {
17
+    xs: {
18
+      span: 24,
19
+      offset: 0,
20
+    },
21
+    sm: {
22
+      span: 16,
23
+      offset: 6,
24
+    },
25
+  },
26
+}
27
+
28
+export default Form.create({})(props => {
29
+  const [title, updateTitle] = useState('详情' || props.title)
30
+  const [loading, updateLoading] = useState(false)
31
+  const [pageData, updatePageData] = useState(null)
32
+
33
+  const id = props.dataId
34
+  const { getFieldDecorator, setFieldsValue, validateFields, resetFields } = props.form
35
+
36
+  const handleCancel = () => {
37
+    if (props.onCancel) {
38
+      props.onCancel()
39
+    }
40
+  }
41
+
42
+  const { fetchAPI, saveAPI, updateAPI } = props
43
+
44
+  const handleSubmit = (e) => {
45
+    e.preventDefault();
46
+    validateFields((err, values) => {
47
+      if (!err) {
48
+        updateLoading(true)
49
+        if (!id && !props.record) {
50
+          saveAPI({data: values}).then(res => {
51
+            updateLoading(false)
52
+            // 保存成功之后直接跳转到详情
53
+            Modal.success({
54
+              content: '保存信息成功',
55
+              onOk: () => {
56
+                if (props.onSuccess) {
57
+                  props.onSuccess(res)
58
+                }
59
+              }
60
+            })
61
+          }).catch(err => {
62
+            updateLoading(false)
63
+            if (props.onError) {
64
+              props.onError(err)
65
+            }
66
+          })
67
+        } else {
68
+          const pKey = id || props.dateKey(props.record)
69
+
70
+          const data = props.record ? { ...props.record, ...values } : values
71
+          updateAPI({ data, urlData: { id: pKey } }).then(res => {
72
+            updateLoading(false)
73
+            updatePageData(res)
74
+            notification.success({ message: '更新信息成功' })
75
+            if (props.onSuccess) {
76
+              props.onSuccess(res)
77
+            }
78
+          }).catch(err => {
79
+            updateLoading(false)
80
+            if (props.onError) {
81
+              props.onError(err)
82
+            }
83
+          })
84
+        }
85
+      }
86
+    });
87
+  }
88
+
89
+  useEffect(() => {
90
+    if (props.mode !== 'inner') {
91
+      if (!pageData) {
92
+        resetFields()
93
+      } else {
94
+        setFieldsValue(pageData)
95
+      }
96
+    }
97
+  }, [props.mode, pageData])
98
+
99
+  useEffect(() => {
100
+    if (props.mode === 'inner') {
101
+      if (!props.record) {
102
+        resetFields()
103
+      } else {
104
+        setFieldsValue(props.record)
105
+      }
106
+      updatePageData(props.record)
107
+    }
108
+  }, [props.mode, props.record])
109
+
110
+  useEffect(() => {
111
+    if (props.mode !== 'inner' && id) {
112
+      updateLoading(true)
113
+      fetchAPI({ urlData: {id} }).then((res) => {
114
+        updatePageData(res)
115
+        updateLoading(false)
116
+        if (!res) {
117
+          notification.info({ message: '没有查询到数据' })
118
+        } else {
119
+          if (props.getPageTitle) {
120
+            updateTitle(props.getPageTitle(res.typeName))
121
+          }
122
+        }
123
+      }).catch(err => {
124
+        updateLoading(false)
125
+      })
126
+    }
127
+  }, [])
128
+
129
+  const childProps = {
130
+    form: props.form,
131
+    dataSource: pageData
132
+  }
133
+
134
+  const showHeader = props.showHeader !== false
135
+
136
+  return (
137
+    <div>
138
+      {
139
+        showHeader && (
140
+          <PageHeader
141
+            ghost={false}
142
+            backIcon={false}
143
+            title={title}
144
+          ></PageHeader>
145
+        )
146
+      }
147
+      <div style={{marginTop: '16px'}}>
148
+        <Spin spinning={loading} tip="请稍候...">
149
+          <Form {...formItemLayout} onSubmit={handleSubmit} >
150
+            {
151
+              props.render(childProps)
152
+            }
153
+            <Form.Item {...tailFormItemLayout}>
154
+              <Button type="primary" htmlType="submit">
155
+                保存
156
+              </Button>
157
+              <Button onClick={handleCancel} style={{ marginLeft: '100px' }}>
158
+                取消
159
+              </Button>
160
+            </Form.Item>
161
+          </Form>
162
+        </Spin>
163
+      </div>
164
+    </div>
165
+  )
166
+})

+ 22
- 0
src/components/CommList/DataTable.jsx Bestand weergeven

@@ -0,0 +1,22 @@
1
+import React, { useState } from 'react'
2
+import { Spin, Table } from 'antd'
3
+
4
+export default props => {
5
+  const [loading, setLoading] = useState(false)
6
+
7
+  const pagination = {
8
+    ...props.pagination,
9
+    onChange: (page, pageSize) => {
10
+      setLoading(true)
11
+      props.onPageChange(page, pageSize).then(() => setLoading(false)).catch(() => setLoading(false))
12
+    }
13
+  }
14
+
15
+  const { onPageChange, ...tableProps } = props
16
+
17
+  return (
18
+    <Spin spinning={loading} delay={300} tip="请稍候...">
19
+      <Table pagination={pagination} {...tableProps} />
20
+    </Spin>
21
+  )
22
+}

+ 40
- 0
src/components/CommList/FilterForm.jsx Bestand weergeven

@@ -0,0 +1,40 @@
1
+import React from 'react'
2
+import { Form } from 'antd'
3
+
4
+export default Form.create({})((props) => {
5
+  const handleSubmit = (e) => {
6
+    e.preventDefault();
7
+    props.form.validateFields((err, values) => {
8
+      if (!err) {
9
+        props.onSearch(values)
10
+      }
11
+    });
12
+  }
13
+
14
+  const handleReset = () => {
15
+    props.form.resetFields();
16
+    props.onSearch(undefined)
17
+  }
18
+
19
+  const { getFieldDecorator } = props.form
20
+
21
+  const childProps = {
22
+    form: props.form
23
+  }
24
+
25
+  return (
26
+    <Form layout="inline" onSubmit={e => handleSubmit(e)}>
27
+      {
28
+        props.render(childProps)
29
+      }
30
+      <Form.Item>
31
+        <Button type="primary" htmlType="submit">
32
+          搜索
33
+        </Button>
34
+        <Button style={{ marginLeft: 8 }} onClick={handleReset}>
35
+          重置
36
+          </Button>
37
+      </Form.Item>
38
+    </Form>
39
+  )
40
+})

+ 93
- 0
src/components/CommList/index.jsx Bestand weergeven

@@ -0,0 +1,93 @@
1
+import React, { useEffect, useState, useCallback, useRef } from 'react'
2
+import { PageHeader, Form, Input, Button, Icon, Table, Rate, Tag, Spin, notification, Tooltip } from 'antd'
3
+
4
+export default (props) => {
5
+  const defaultPageInfo = { pageNum: 1, pageSize: 10 }
6
+  const [loading, updateLoading] = useState(false)
7
+  const [filterParams, updateFilterParams] = useState({})
8
+  const [pageData, updatePageData] = useState([])
9
+  const [pageNavData, updatePageNavData] = useState({})
10
+
11
+  const getList = (params) => {
12
+    updateLoading(true)
13
+    props.fetchList(params).then(res => {
14
+      updateLoading(false)
15
+      updatePageData(res[0])
16
+      updatePageNavData(res[1])
17
+
18
+      if (!res[1].total) {
19
+        notification.info({ message: '没有查询到数据' })
20
+      }
21
+    }).catch(err => {
22
+      updateLoading(false)
23
+      // do nothing
24
+    })
25
+  }
26
+
27
+  const handleSearch = (params) => {
28
+    updateFilterParams(params || {})
29
+    
30
+    getList({
31
+      ...defaultPageInfo,
32
+      ...(params || {})
33
+    })
34
+  }
35
+
36
+  const handlePageChange = (pageNum, pageSize) => {
37
+    getList({
38
+      pageNum,
39
+      pageSize,
40
+      ...filterParams
41
+    })
42
+  }
43
+
44
+  const dataRef = useRef()
45
+  dataRef.current = pageData
46
+
47
+  useEffect(() => {
48
+    getList(defaultPageInfo)
49
+
50
+    if (props.refreshData) {
51
+      props.refreshData((callback) => {
52
+        const newList = callback(dataRef.current)
53
+        updatePageData(newList)
54
+      })
55
+    }
56
+  }, [])
57
+
58
+  const pagination = {
59
+    ...pageNavData,
60
+    onChange: handlePageChange
61
+  }
62
+
63
+  const showFilterForm = props.showFilterForm !== false
64
+  const tableProp = {
65
+    ...(props.table || {}),
66
+    dataSource: pageData
67
+  }
68
+
69
+  return (
70
+    <div>
71
+      <PageHeader
72
+        ghost={false}
73
+        backIcon={false}
74
+        title={props.title || "列表"}
75
+        extra={[<Button key={1} type="primary" onClick={props.onAdd}>新增</Button>]}
76
+      ></PageHeader>
77
+
78
+      {
79
+        showFilterForm && (
80
+          <FilterForm onSearch={handleSearch} render={props.renderFilter} />
81
+        )
82
+      }
83
+      
84
+      <div style={{marginTop: '16px'}}>
85
+        <Spin spinning={loading} delay={300} tip="请稍候...">
86
+          <Table
87
+            {...tableProp}
88
+            pagination={pagination} />
89
+        </Spin>
90
+      </div>
91
+    </div>
92
+  )
93
+}

+ 71
- 0
src/components/CopyBlock/index.jsx Bestand weergeven

@@ -0,0 +1,71 @@
1
+import { Icon, Popover, Typography } from 'antd';
2
+import React, { useRef } from 'react';
3
+import { FormattedMessage } from 'umi-plugin-react/locale';
4
+import { connect } from 'dva';
5
+import { isAntDesignPro } from '@/utils/utils';
6
+import styles from './index.less';
7
+
8
+const firstUpperCase = pathString =>
9
+  pathString
10
+    .replace('.', '')
11
+    .split(/\/|-/)
12
+    .map(s => s.toLowerCase().replace(/( |^)[a-z]/g, L => L.toUpperCase()))
13
+    .filter(s => !!s)
14
+    .join(''); // when  click block copy, send block url to  ga
15
+
16
+const onBlockCopy = label => {
17
+  if (!isAntDesignPro()) {
18
+    return;
19
+  }
20
+
21
+  const ga = window && window.ga;
22
+
23
+  if (ga) {
24
+    ga('send', 'event', {
25
+      eventCategory: 'block',
26
+      eventAction: 'copy',
27
+      eventLabel: label,
28
+    });
29
+  }
30
+};
31
+
32
+const BlockCodeView = ({ url }) => {
33
+  const blockUrl = `npx umi block add ${firstUpperCase(url)} --path=${url}`;
34
+  return (
35
+    <div className={styles['copy-block-view']}>
36
+      <Typography.Paragraph
37
+        copyable={{
38
+          text: blockUrl,
39
+          onCopy: () => onBlockCopy(url),
40
+        }}
41
+        style={{
42
+          display: 'flex',
43
+        }}
44
+      >
45
+        <pre>
46
+          <code className={styles['copy-block-code']}>{blockUrl}</code>
47
+        </pre>
48
+      </Typography.Paragraph>
49
+    </div>
50
+  );
51
+};
52
+
53
+export default connect(({ routing }) => ({
54
+  location: routing.location,
55
+}))(({ location }) => {
56
+  const url = location.pathname;
57
+  const divDom = useRef(null);
58
+  return (
59
+    <Popover
60
+      title={<FormattedMessage id="app.preview.down.block" defaultMessage="下载此页面到本地项目" />}
61
+      placement="topLeft"
62
+      content={<BlockCodeView url={url} />}
63
+      trigger="click"
64
+      getPopupContainer={dom => (divDom.current ? divDom.current : dom)}
65
+    >
66
+      <div className={styles['copy-block']} ref={divDom}>
67
+        <Icon type="download" />
68
+      </div>
69
+    </Popover>
70
+  );
71
+});

+ 29
- 0
src/components/CopyBlock/index.less Bestand weergeven

@@ -0,0 +1,29 @@
1
+.copy-block {
2
+  position: fixed;
3
+  right: 80px;
4
+  bottom: 40px;
5
+  z-index: 99;
6
+  display: flex;
7
+  flex-direction: column;
8
+  align-items: center;
9
+  justify-content: center;
10
+  width: 40px;
11
+  height: 40px;
12
+  font-size: 20px;
13
+  background: #fff;
14
+  border-radius: 40px;
15
+  box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14),
16
+    0 1px 10px 0 rgba(0, 0, 0, 0.12);
17
+  cursor: pointer;
18
+}
19
+
20
+.copy-block-view {
21
+  position: relative;
22
+  .copy-block-code {
23
+    display: inline-block;
24
+    margin: 0 0.2em;
25
+    padding: 0.2em 0.4em 0.1em;
26
+    font-size: 85%;
27
+    border-radius: 3px;
28
+  }
29
+}

+ 32
- 0
src/components/CustomUpload/index.jsx Bestand weergeven

@@ -0,0 +1,32 @@
1
+import React, { PureComponent } from 'react'
2
+import { Upload, Spin } from 'antd';
3
+import { uploaderProps } from '@/utils/upload'
4
+
5
+export default class extends PureComponent {
6
+  state = {
7
+    loading: false,
8
+  }
9
+  
10
+  handleChange = ({ file, fileList }) => {
11
+    const { status, response } = file
12
+    this.setState({ fileList, loading: status === "uploading" })
13
+
14
+    if (typeof this.props.onChange === 'function' && status != "uploading") {
15
+      const image = status === 'done' ? response : undefined
16
+      this.props.onChange(image);
17
+    }
18
+  };
19
+
20
+  render() {
21
+    return (
22
+      <Upload
23
+        {...uploaderProps}
24
+        onChange={this.handleChange}
25
+      >
26
+        <Spin spinning={this.state.loading}>
27
+          {this.props.children}
28
+        </Spin>
29
+      </Upload>
30
+    )
31
+  }
32
+}