@hzab/form-render-mobile 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hzab/form-render-mobile",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "formily-form-render-mobile",
5
5
  "main": "lib",
6
6
  "scripts": {
@@ -34,7 +34,7 @@ function Loader(opt) {
34
34
 
35
35
  window._AMapLoaderTemp.plugins = plugins ? [...defaultPlugins, ...(plugins || [])] : defaultPlugins;
36
36
 
37
- // 不用改组件时可以不安装 "@amap/amap-jsapi-loader"
37
+ // 不用地图组件时可以不安装 "@amap/amap-jsapi-loader"
38
38
  const AMapLoader = require("@amap/amap-jsapi-loader");
39
39
 
40
40
  window._AMapLoaderTemp.loading = true;
@@ -0,0 +1,129 @@
1
+ import AMapLoader from "./loader";
2
+
3
+ if (!window._AMapLoaderTemp) {
4
+ window._AMapLoaderTemp = {};
5
+ }
6
+
7
+ export function setKey(key, securityJsCode) {
8
+ window._AMapLoaderTemp.key = key;
9
+ window._AMapLoaderTemp.securityJsCode = securityJsCode;
10
+ }
11
+
12
+ export function setServerKey(key, securityJsCode) {
13
+ window._AMapLoaderTemp.serverKey = key;
14
+ window._AMapLoaderTemp.serverSecurityJsCode = securityJsCode;
15
+ }
16
+
17
+ export function setSecurityJsCode(securityJsCode) {
18
+ window._AMapLoaderTemp.securityJsCode = securityJsCode;
19
+ window._AMapSecurityConfig = {
20
+ securityJsCode: securityJsCode,
21
+ };
22
+ }
23
+
24
+ export type setMarkerOptT = {
25
+ id?: number | string;
26
+ marker?: Object;
27
+ map?: Object;
28
+ AMap?: Object;
29
+ };
30
+
31
+ export type markersT = {
32
+ [k: number | string]: Object;
33
+ };
34
+
35
+ export class MapUtils {
36
+ public map;
37
+ public AMap;
38
+ public markers: markersT = {};
39
+ public pickerMarker;
40
+
41
+ constructor({ map, AMap }) {
42
+ if (!map) {
43
+ throw new Error("请传入地图实例");
44
+ }
45
+ this.map = map;
46
+ this.AMap = AMap;
47
+ }
48
+
49
+ /**
50
+ * 获取地图中心点
51
+ * @param map
52
+ * @returns { lng: Number, lat: Number }
53
+ */
54
+ getCenter(map = this.map) {
55
+ if (!map) {
56
+ throw new Error("请传入地图实例和 AMap");
57
+ }
58
+ return map && map.getCenter && map.getCenter();
59
+ }
60
+
61
+ /**
62
+ * 设置地图中心点
63
+ * @param lon
64
+ * @param lat
65
+ * @param opt
66
+ */
67
+ setCenter(lon, lat, opt = { map: this.map, AMap: this.AMap || window.AMap }) {
68
+ const { map = this.map, AMap = this.AMap || window.AMap } = opt || {};
69
+ if (!(AMap && map)) {
70
+ throw new Error("请传入地图实例和 AMap");
71
+ }
72
+ const position = new window.AMap.LngLat(lon, lat);
73
+ // 设置中心点
74
+ map.setCenter(position);
75
+ }
76
+
77
+ /**
78
+ * 创建或修改 Marker
79
+ * @param lon
80
+ * @param lat
81
+ * @param opt
82
+ * @returns
83
+ */
84
+ setMarker(lon, lat, opt?: setMarkerOptT) {
85
+ const { id = Date.now(), marker, map = this.map, AMap = this.AMap } = opt || {};
86
+ if (!(AMap && map)) {
87
+ throw new Error("请传入地图实例和 AMap");
88
+ }
89
+ const position = new AMap.LngLat(lon, lat);
90
+ let _marker = marker || this.markers[id];
91
+ // 创建 Marker 或修改位置
92
+ if (_marker) {
93
+ // @ts-ignore
94
+ _marker.setPosition(position);
95
+ return _marker;
96
+ }
97
+ const _m = new AMap.Marker({
98
+ position,
99
+ });
100
+ this.markers[id] = _m;
101
+ map?.add(_m);
102
+ return _m;
103
+ }
104
+
105
+ /**
106
+ * 创建或修改地图选点的 Marker
107
+ * @param lon
108
+ * @param lat
109
+ * @param opt
110
+ * @returns
111
+ */
112
+ setPickerMarker(lon, lat, opt?: any) {
113
+ const { marker = this.pickerMarker } = opt || {};
114
+ this.pickerMarker = this.setMarker(lon, lat, {
115
+ ...opt,
116
+ marker,
117
+ });
118
+ }
119
+
120
+ on(...args) {
121
+ return this.map?.on(...args);
122
+ }
123
+
124
+ off(...args) {
125
+ return this.map?.off(...args);
126
+ }
127
+ }
128
+
129
+ export { AMapLoader };
@@ -1,23 +1,20 @@
1
1
  import { useEffect, useRef } from "react";
2
2
 
3
+ import { MapUtils } from "./common/utils";
3
4
  import AMapLoader from "./common/loader";
4
5
 
5
6
  import "./index.less";
6
7
 
7
- let map = null;
8
- let AMap = null;
9
-
10
8
  if (window._AMapLoaderTemp && window._AMapLoaderTemp.key) {
11
9
  AMapLoader({
12
10
  key: window._AMapLoaderTemp.key,
13
11
  securityJsCode: window._AMapLoaderTemp.securityJsCode,
14
- }).then((_AMap) => {
15
- AMap = _AMap;
16
12
  });
17
13
  }
18
14
 
19
15
  function AMapCom(props) {
20
16
  const mapRef = useRef();
17
+ const mapDomRef = useRef();
21
18
 
22
19
  useEffect(() => {
23
20
  const { key, securityJsCode, plugins } = props;
@@ -27,21 +24,21 @@ function AMapCom(props) {
27
24
  plugins,
28
25
  }).then((AMap) => {
29
26
  const { zoom, center, init } = props;
30
- map = new AMap.Map(mapRef.current, {
27
+ mapRef.current = new AMap.Map(mapDomRef.current, {
31
28
  zoom: zoom || 11,
32
29
  center: center || [120.160217, 30.243861],
33
30
  });
34
- init && init(map, AMap);
31
+ init && init({ map: mapRef.current, mapUtils: new MapUtils({ map: mapRef.current, AMap }), AMap });
35
32
  });
36
33
  }, []);
37
34
 
38
35
  useEffect(() => {
39
36
  if (Array.isArray(props.center) && props.center.length > 1) {
40
- map?.setCenter(props.center);
37
+ mapRef.current?.setCenter(props.center);
41
38
  }
42
39
  }, [props.center]);
43
40
 
44
- return <div ref={mapRef} className="a-map-container" style={props.style}></div>;
41
+ return <div ref={mapDomRef} className="a-map-container" style={props.style}></div>;
45
42
  }
46
43
 
47
44
  export default AMapCom;
@@ -26,11 +26,7 @@ export function getPropsValue(value = {}, field: any, opt) {
26
26
  lon,
27
27
  lat,
28
28
  addr,
29
- center: null,
30
29
  };
31
- if (lon && lat) {
32
- res.center = [lon, lat];
33
- }
34
30
  return res;
35
31
  }
36
32
 
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useState } from "react";
2
- import { SearchBar, CheckList, Button } from "antd-mobile";
2
+ import { SearchBar, CheckList, Button, Toast } from "antd-mobile";
3
3
  import { debounce } from "lodash";
4
4
 
5
5
  import { getSearchTips } from "../../servers";
@@ -37,6 +37,7 @@ function Search(props) {
37
37
  setLoading(false);
38
38
  })
39
39
  .catch((err) => {
40
+ Toast.show({ icon: "fail", content: err });
40
41
  setLoading(false);
41
42
  });
42
43
  }, 500),
@@ -0,0 +1,48 @@
1
+ .location-picker-modal-content {
2
+ position: relative;
3
+
4
+ .location-picker-map {
5
+ position: relative;
6
+ width: 100%;
7
+ flex: 1;
8
+ .a-map-container {
9
+ height: inherit;
10
+ .amap-logo,
11
+ .amap-copyright {
12
+ z-index: 0;
13
+ }
14
+ }
15
+
16
+ .location-picker-center-icon {
17
+ position: absolute;
18
+ top: 50%;
19
+ left: 50%;
20
+ transform: translate(-50%, -100%);
21
+ font-size: 32px;
22
+ color: #1890ff;
23
+ }
24
+ }
25
+
26
+ .spin-loading-wrap {
27
+ position: absolute;
28
+ top: 0;
29
+ left: 0;
30
+ right: 0;
31
+ bottom: 0;
32
+ z-index: 9;
33
+ display: flex;
34
+ align-items: center;
35
+ justify-content: center;
36
+ background-color: rgba(0, 0, 0, 0.5);
37
+ .spin-loading {
38
+ }
39
+ }
40
+ /* 水平样式 */
41
+ &.location-modal-content-layout-hor {
42
+ display: flex;
43
+
44
+ .location-picker-info {
45
+ width: 50vw;
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,202 @@
1
+ import { useEffect, useMemo, useRef, useState } from "react";
2
+ import { SpinLoading, Toast } from "antd-mobile";
3
+ import { LocationFill } from "antd-mobile-icons";
4
+ import { useField } from "@formily/react";
5
+ import { debounce } from "lodash";
6
+
7
+ import PickerInfo from "../PickerInfo";
8
+ import AMapCom from "../../Map/AMap";
9
+ import Notice from "../Notice";
10
+ import MapSearch from "../MapSearch";
11
+
12
+ import { MapUtils } from "../../Map/AMap/common/utils";
13
+ import { getAddress } from "../../servers";
14
+ import { getPropsValue } from "../../common/utils";
15
+
16
+ import "./index.less";
17
+
18
+ export const ModalContent = (props) => {
19
+ const {
20
+ /**
21
+ * 选择器和地图的布局
22
+ * 水平 hor
23
+ * 垂直 ver
24
+ */
25
+ layout = "ver",
26
+ // 是否允许搜索
27
+ hasSearch = true,
28
+ /**
29
+ *
30
+ isObjectRes
31
+ true: 把所有数据存放在一个对象中(该对象和其他表单项平级),对象 key 为当前项的 name
32
+ false: 所有数据打平放到当前的 data 中,和其他表单项平级
33
+ */
34
+ isObjectRes = true,
35
+ /**
36
+ * 打开地图时是否根据经纬度自动修正已填的地址
37
+ */
38
+ isAutoFixAddr = false,
39
+ /**
40
+ * 改变经纬度等数据的触发模式
41
+ * 移动地图 move
42
+ * 点击地图 click
43
+ */
44
+ changeMode = "move",
45
+ lonKey = "longitude",
46
+ latKey = "latitude",
47
+ addrKey = "address",
48
+ value,
49
+ // 搜索框是否自动搜索
50
+ isAutoSearch = true,
51
+ //
52
+ visible,
53
+ defaultLocation,
54
+ } = props;
55
+
56
+ const field: any = useField();
57
+
58
+ const mapUtilsRef = useRef<MapUtils>();
59
+
60
+ // 数据格式转为内部格式,方便存取
61
+ const formatVal = useMemo(
62
+ () =>
63
+ getPropsValue(value, field, {
64
+ isObjectRes,
65
+ lonKey,
66
+ latKey,
67
+ addrKey,
68
+ }),
69
+ [isObjectRes, lonKey, latKey, addrKey, value, field.data],
70
+ );
71
+ const [loading, setLoading] = useState(false);
72
+ const [addrLoading, setAddrLoading] = useState(false);
73
+ // 地图选点组件选中的值
74
+ const [pickInfo, setPickInfo] = useState(formatVal);
75
+
76
+ useEffect(() => {
77
+ if (visible) {
78
+ let _lon = defaultLocation.lon;
79
+ let _lat = defaultLocation.lat;
80
+ if (formatVal.lon && formatVal.lat) {
81
+ _lon = formatVal.lon;
82
+ _lat = formatVal.lat;
83
+ }
84
+ // 解决关闭弹窗之后点位不居中的问题
85
+ if (window.AMap && window.AMap.LngLat) {
86
+ setMapCenter(_lon, _lat);
87
+ }
88
+ setPickPoint(_lon, _lat, { isAutoFixAddr: false });
89
+ }
90
+ }, [visible, formatVal, changeMode]);
91
+
92
+ function mapInit({ map, mapUtils }) {
93
+ mapUtilsRef.current = mapUtils;
94
+ let _lon = defaultLocation.lon;
95
+ let _lat = defaultLocation.lat;
96
+ if (pickInfo.lon && pickInfo.lat) {
97
+ _lon = pickInfo.lon;
98
+ _lat = pickInfo.lat;
99
+ }
100
+ setPickPoint(_lon, _lat, { isAutoFixAddr });
101
+
102
+ setMapCenter(_lon, _lat);
103
+ setLoading(false);
104
+
105
+ if (changeMode === "click") {
106
+ mapUtilsRef.current?.setPickerMarker(_lon, _lat);
107
+ // 点击选中点位的情况
108
+ mapUtilsRef.current.on("click", function (ev) {
109
+ const { lng: evLon, lat: evLat } = ev.lnglat || {};
110
+ setPickPoint(evLon, evLat, { changeCenter: true });
111
+ });
112
+ } else {
113
+ // 移动地图
114
+ mapUtilsRef.current.on(
115
+ "touchend",
116
+ debounce(function (ev) {
117
+ let currentCenter = mapUtilsRef.current?.getCenter();
118
+ const { lng: centerLon, lat: centerLat } = currentCenter || {};
119
+ // 移动选中点位的情况
120
+ let _lon = centerLon;
121
+ let _lat = centerLat;
122
+ setPickPoint(_lon, _lat);
123
+ }, 1000),
124
+ );
125
+ }
126
+ }
127
+
128
+ /**
129
+ * 设置地图中心点
130
+ * @param lon
131
+ * @param lat
132
+ */
133
+ function setMapCenter(lon, lat) {
134
+ mapUtilsRef.current?.setCenter(lon, lat);
135
+ }
136
+
137
+ async function setPickPoint(_lon, _lat, opt?: any) {
138
+ const { isAutoFixAddr: _isAutoFixAddr = isAutoFixAddr || true, changeCenter } = opt || {};
139
+ const lon = _lon || pickInfo.lon;
140
+ const lat = _lat || pickInfo.lat;
141
+ if (changeMode === "click") {
142
+ mapUtilsRef.current?.setPickerMarker(_lon, _lat);
143
+ }
144
+ if (changeCenter) {
145
+ setMapCenter(_lon, _lat);
146
+ }
147
+ const addr = _isAutoFixAddr || !pickInfo.addr ? await getAddr(lon, lat) : pickInfo.addr;
148
+ const res = { ...pickInfo, lon, lat, addr };
149
+ setPickInfo(res);
150
+ props.setPickInfo && props.setPickInfo(res);
151
+ }
152
+
153
+ function getAddr(_lng, _lat) {
154
+ return new Promise((resolve, reject) => {
155
+ if (!window.AMap) {
156
+ reject();
157
+ return new Error("window.AMap is not defined");
158
+ }
159
+
160
+ setAddrLoading(true);
161
+ getAddress(_lng, _lat)
162
+ .then((addr) => {
163
+ setAddrLoading(false);
164
+ setPickInfo((_p) => ({ ..._p, addr }));
165
+ resolve(addr);
166
+ })
167
+ .catch((err) => {
168
+ reject(err);
169
+ Toast.show({ icon: "fail", content: err });
170
+ setAddrLoading(false);
171
+ });
172
+ });
173
+ }
174
+
175
+ return (
176
+ <div className={`location-picker-modal-content location-modal-content-layout-${layout}`}>
177
+ {/* 点击/移动 地图选中的数据 */}
178
+ <PickerInfo
179
+ pickInfo={pickInfo}
180
+ setPickInfo={setPickInfo}
181
+ setPoint={setPickPoint}
182
+ addrLoading={addrLoading}
183
+ defaultLocation={defaultLocation}
184
+ />
185
+
186
+ <div className="location-picker-map">
187
+ {/* 关闭弹窗之后清除搜索内容 */}
188
+ {hasSearch && visible ? <MapSearch setPoint={setPickPoint} isAutoSearch={isAutoSearch} /> : null}
189
+ <AMapCom init={mapInit} loading={loading} style={{ height: "66vh" }} />
190
+ {!loading && changeMode !== "click" && <LocationFill className="location-picker-center-icon" />}
191
+ <Notice changeMode={changeMode} />
192
+ </div>
193
+ {loading && (
194
+ <div className="spin-loading-wrap">
195
+ <SpinLoading className="spin-loading" />
196
+ </div>
197
+ )}
198
+ </div>
199
+ );
200
+ };
201
+
202
+ export default ModalContent;
@@ -0,0 +1,15 @@
1
+ import { NoticeBar } from "antd-mobile";
2
+ import { ExclamationCircleOutline } from "antd-mobile-icons";
3
+
4
+ export const Notice = (props) => {
5
+ return (
6
+ <NoticeBar
7
+ className="location-picker-notice-bar"
8
+ content={props.changeMode === "click" ? "点击地图上的位置,选择目标地址" : "拖拽移动地图,中心标点为目标地址"}
9
+ color="default"
10
+ icon={<ExclamationCircleOutline />}
11
+ />
12
+ );
13
+ };
14
+
15
+ export default Notice;
@@ -0,0 +1,17 @@
1
+ .location-picker-info {
2
+ padding: 2vw 4vw;
3
+ .picker-info-lon,
4
+ .picker-info-lat,
5
+ .picker-info-addr {
6
+ display: flex;
7
+ .picker-info-label {
8
+ white-space: nowrap;
9
+ }
10
+ }
11
+ .picker-info-lon {
12
+ }
13
+ .picker-info-lat {
14
+ }
15
+ .picker-info-addr {
16
+ }
17
+ }
@@ -0,0 +1,78 @@
1
+ import { Input, DotLoading } from "antd-mobile";
2
+
3
+ import "./index.less";
4
+
5
+ export const PickerInfo = (props) => {
6
+ const { pickInfo, setPickInfo, setPoint, addrLoading, defaultLocation } = props;
7
+
8
+ function onPILonChange(e) {
9
+ const val = e?.target?.value ?? e;
10
+ const res: { lon; lat; addr } = { ...pickInfo };
11
+ res.lon = val;
12
+ setPickInfo(res);
13
+ return res;
14
+ }
15
+
16
+ function onPILatChange(e) {
17
+ const val = e?.target?.value ?? e;
18
+ const res: { lon; lat; addr } = { ...pickInfo };
19
+ res.lat = val;
20
+ setPickInfo(res);
21
+ return res;
22
+ }
23
+
24
+ /**
25
+ * 失去焦点之后,地图居中
26
+ * @param e
27
+ */
28
+ function onPILonBlur(e) {
29
+ const res = onPILonChange(e);
30
+ setPoint && setPoint(res?.lon || defaultLocation.lon, res?.lat || defaultLocation.lat, { changeCenter: true });
31
+ }
32
+
33
+ /**
34
+ * 失去焦点之后,地图居中
35
+ * @param e
36
+ */
37
+ function onPILatBlur(e) {
38
+ const res = onPILatChange(e);
39
+ setPoint && setPoint(res?.lon || defaultLocation.lon, res?.lat || defaultLocation.lat, { changeCenter: true });
40
+ }
41
+
42
+ return (
43
+ <div className="location-picker-info">
44
+ <div className="picker-info-lon">
45
+ <div className="picker-info-label">经度:</div>
46
+ <Input
47
+ className="picker-info-input"
48
+ disabled={addrLoading}
49
+ max={180}
50
+ min={-180}
51
+ value={pickInfo.lon}
52
+ onChange={onPILonChange}
53
+ onBlur={onPILonBlur}
54
+ onEnterPress={onPILonBlur}
55
+ />
56
+ </div>
57
+ <div className="picker-info-lat">
58
+ <div className="picker-info-label">纬度:</div>
59
+ <Input
60
+ className="picker-info-input"
61
+ disabled={addrLoading}
62
+ max={90}
63
+ min={-90}
64
+ value={pickInfo.lat}
65
+ onChange={onPILatChange}
66
+ onBlur={onPILatBlur}
67
+ onEnterPress={onPILatBlur}
68
+ />
69
+ </div>
70
+ <div className="picker-info-addr">
71
+ <div className="picker-info-label">地址:</div>
72
+ {addrLoading ? <DotLoading /> : pickInfo.addr}
73
+ </div>
74
+ </div>
75
+ );
76
+ };
77
+
78
+ export default PickerInfo;
@@ -0,0 +1,13 @@
1
+ .location-value-box {
2
+ .location-value-head-box {
3
+ display: flex;
4
+ .location-lon-lat {
5
+ flex: 1;
6
+ }
7
+ }
8
+
9
+ .location-btn {
10
+ padding: 4px 0;
11
+ padding-left: 5px;
12
+ }
13
+ }
@@ -0,0 +1,48 @@
1
+ import { useMemo } from "react";
2
+ import { Button } from "antd-mobile";
3
+ import { LocationFill } from "antd-mobile-icons";
4
+ import { useField } from "@formily/react";
5
+
6
+ import { getPropsValue } from "../../common/utils";
7
+
8
+ import "./index.less";
9
+
10
+ /**
11
+ * 展示 form 表单当前项的数据
12
+ * @param props
13
+ * @returns
14
+ */
15
+ export const ResInfo = (props) => {
16
+ const { onShow, isObjectRes, lonKey, latKey, addrKey, value, pickerText = "选点" } = props || {};
17
+ const field: any = useField();
18
+
19
+ // 数据格式转为内部格式,方便存取
20
+ const formatVal = useMemo(
21
+ () =>
22
+ getPropsValue(value, field, {
23
+ isObjectRes,
24
+ lonKey,
25
+ latKey,
26
+ addrKey,
27
+ }),
28
+ [isObjectRes, lonKey, latKey, addrKey, value, field.data],
29
+ );
30
+
31
+ return (
32
+ <div className="location-value-box">
33
+ <div className="location-value-head-box">
34
+ <div className="location-lon-lat">
35
+ <div>经度:{formatVal.lon} </div>
36
+ <div>纬度:{formatVal.lat}</div>
37
+ </div>
38
+ <Button className="location-btn" color="primary" fill="none" onClick={onShow}>
39
+ <LocationFill />
40
+ {pickerText}
41
+ </Button>
42
+ </div>
43
+ <div>地址:{formatVal.addr}</div>
44
+ </div>
45
+ );
46
+ };
47
+
48
+ export default ResInfo;