@hzab/form-render-mobile 0.3.2 → 0.4.1

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.
Files changed (29) hide show
  1. package/lib/index.js +1 -1
  2. package/package.json +1 -1
  3. package/src/components/LocationPicker/Map/AMap/common/loader.ts +2 -2
  4. package/src/components/LocationPicker/Map/AMap/common/utils.ts +15 -4
  5. package/src/components/LocationPicker/Map/AMap/index.jsx +3 -2
  6. package/src/components/LocationPicker/README.md +31 -22
  7. package/src/components/LocationPicker/assets/svg-icon.js +86 -0
  8. package/src/components/LocationPicker/common/utils.ts +2 -9
  9. package/src/components/LocationPicker/components/MapSearch/index.jsx +0 -3
  10. package/src/components/LocationPicker/components/ModalContent/index.less +18 -4
  11. package/src/components/LocationPicker/components/ModalContent/index.tsx +45 -62
  12. package/src/components/LocationPicker/components/Notice/index.tsx +1 -1
  13. package/src/components/LocationPicker/components/ResInfo/index.less +9 -10
  14. package/src/components/LocationPicker/components/ResInfo/index.tsx +20 -18
  15. package/src/components/LocationPicker/index.tsx +24 -72
  16. package/src/components/LocationPicker/servers/index.ts +119 -63
  17. package/src/components/TreeSelect/SelectList/index.tsx +58 -25
  18. package/src/components/TreeSelect/common/data-handler.ts +158 -3
  19. package/src/components/TreeSelect/index.less +1 -0
  20. package/src/components/TreeSelect/index.tsx +8 -1
  21. package/src/index.less +5 -2
  22. package/lib/static/imgs/marker-icon_ab8bbcc8cb.svg +0 -4
  23. package/lib/static/imgs/picker-icon_24d725ef02.svg +0 -5
  24. package/lib/static/imgs/position-icon_5bcb8a742e.svg +0 -6
  25. package/lib/static/imgs/reset-icon_9edad62306.svg +0 -5
  26. package/src/components/LocationPicker/assets/marker-icon.svg +0 -4
  27. package/src/components/LocationPicker/assets/picker-icon.svg +0 -5
  28. package/src/components/LocationPicker/assets/position-icon.svg +0 -6
  29. package/src/components/LocationPicker/assets/reset-icon.svg +0 -5
@@ -1,17 +1,18 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
2
  import { Popup, Button } from "antd-mobile";
3
- import { useField, useFieldSchema } from "@formily/react";
3
+ import { observer } from "@formily/react";
4
4
 
5
5
  import ResInfo from "./components/ResInfo";
6
6
  import ModalContent from "./components/ModalContent";
7
7
 
8
8
  import { MapUtils } from "./Map/AMap/common/utils";
9
9
  import { getCurrentPosition } from "../../common/location-utils";
10
- import { getParentValue, getPropsValue } from "./common/utils";
10
+ import { getPropsValue } from "./common/utils";
11
+ import { getAddress } from "./servers";
11
12
 
12
13
  import "./index.less";
13
14
 
14
- export function LocationPicker(props) {
15
+ export const LocationPicker = observer(function (props: any) {
15
16
  const {
16
17
  /**
17
18
  * 地图显示类型:
@@ -33,47 +34,32 @@ export function LocationPicker(props) {
33
34
  * 展示获取经纬度的按钮
34
35
  */
35
36
  showGetPositionBtn = true,
36
- /**
37
- *
38
- isObjectRes
39
- true: 把所有数据存放在一个对象中(该对象和其他表单项平级),对象 key 为当前项的 name
40
- false: 所有数据打平放到当前的 data 中,和其他表单项平级
41
- */
42
- isObjectRes = true,
43
37
  /**
44
38
  * 打开地图时是否根据经纬度自动修正已填的地址
45
39
  */
46
40
  isAutoFixAddr = false,
47
- /**
48
- * 改变经纬度等数据的触发模式
49
- * 移动地图 move
50
- * 点击地图 click
51
- */
52
- changeMode = "move",
53
41
  lonKey = "longitude",
54
42
  latKey = "latitude",
55
43
  addrKey = "address",
56
44
  value,
57
45
  // 搜索框是否自动搜索
58
46
  isAutoSearch = true,
47
+ icons = {},
59
48
  } = props;
60
- const field: any = useField();
61
- const fieldSchema = useFieldSchema();
62
49
 
63
- const [loading, setLoading] = useState(true);
64
50
  const [defaultLocation, setDefaultLocation] = useState({
65
51
  lon: 120.168893,
66
52
  lat: 30.225404,
67
53
  addr: "浙江省杭州市上城区南星街道杭州西湖风景名胜区",
68
54
  });
55
+ const [loading, setLoading] = useState(false);
69
56
  const [visible, setVisible] = useState(false);
70
57
 
71
58
  const mapUtilsRef = useRef<MapUtils>();
72
59
  const pickInfoRef = useRef(defaultLocation);
73
60
 
74
61
  useEffect(() => {
75
- const propsVal = getPropsValue(value, field, {
76
- isObjectRes,
62
+ const propsVal = getPropsValue(value, {
77
63
  lonKey,
78
64
  latKey,
79
65
  addrKey,
@@ -83,12 +69,17 @@ export function LocationPicker(props) {
83
69
  // 如果没有传入的经纬度,获取当前实际的经纬度数据
84
70
  // 获取当前经纬度
85
71
  getCurrentPosition()
86
- .then((res) => {
72
+ .then(async (res) => {
87
73
  const { coords = {} } = res;
74
+ const lon = coords.longitude;
75
+ const lat = coords.latitude;
76
+ const addr: string = await getAddress(lon, lat).then((addr) => {
77
+ return addr;
78
+ });
88
79
  pickInfoRef.current = {
89
- lon: coords.longitude,
90
- lat: coords.latitude,
91
- addr: "",
80
+ lon,
81
+ lat,
82
+ addr,
92
83
  };
93
84
  setDefaultLocation(pickInfoRef.current);
94
85
  setLoading(false);
@@ -120,18 +111,6 @@ export function LocationPicker(props) {
120
111
  [addrKey]: info.addr,
121
112
  };
122
113
  props.onChange && props.onChange(_res);
123
- if (isObjectRes === false) {
124
- // 只有一层的情况
125
- if (!field.parent) {
126
- field.form.setValues(_res);
127
- } else if (field.parent && fieldSchema.parent) {
128
- // 嵌套在 ArrayBase: ArrayTable ArrayCard 等 里面的情况
129
- const parentVal = getParentValue(field);
130
- Object.keys(_res).forEach((key) => {
131
- parentVal[key] = _res[key];
132
- });
133
- }
134
- }
135
114
  }
136
115
 
137
116
  function onOk() {
@@ -149,8 +128,7 @@ export function LocationPicker(props) {
149
128
  }
150
129
 
151
130
  const _props = {
152
- disabled: props.disabled,
153
- readOnly: props.readOnly,
131
+ ...props,
154
132
  /**
155
133
  * 地图显示类型:
156
134
  * 弹窗 dialog
@@ -165,23 +143,10 @@ export function LocationPicker(props) {
165
143
  layout: layout ?? "ver",
166
144
  // 是否允许搜索
167
145
  hasSearch: hasSearch ?? true,
168
- /**
169
- *
170
- isObjectRes
171
- true: 把所有数据存放在一个对象中(该对象和其他表单项平级),对象 key 为当前项的 name
172
- false: 所有数据打平放到当前的 data 中,和其他表单项平级
173
- */
174
- isObjectRes: isObjectRes ?? true,
175
146
  /**
176
147
  * 打开地图时是否根据经纬度自动修正已填的地址
177
148
  */
178
149
  isAutoFixAddr: isAutoFixAddr ?? false,
179
- /**
180
- * 改变经纬度等数据的触发模式
181
- * 移动地图:move
182
- * 点击地图:click
183
- */
184
- changeMode: changeMode ?? "move",
185
150
  lonKey: lonKey ?? "longitude",
186
151
  latKey: latKey ?? "latitude",
187
152
  addrKey: addrKey ?? "address",
@@ -189,27 +154,21 @@ export function LocationPicker(props) {
189
154
  // 搜索框是否自动搜索
190
155
  isAutoSearch: isAutoSearch ?? true,
191
156
  defaultLocation,
192
- // 地图选点按钮的 icon 元素
193
- pickerIcon: props.pickerIcon,
194
157
  showGetPositionBtn,
158
+ visible,
159
+ loading,
160
+ setLoading,
195
161
  };
196
162
 
197
163
  return (
198
164
  <div className="location-picker">
199
165
  {mode === "show" ? (
200
- <ModalContent
201
- {..._props}
202
- visible={visible}
203
- setPickInfo={setResInfo}
204
- layout={layout}
205
- setLoading={setLoading}
206
- loading={loading}
207
- />
166
+ <ModalContent {..._props} setPickInfo={setResInfo} />
208
167
  ) : (
209
168
  <>
210
169
  {/* 弹窗 */}
211
170
  {/* 有弹窗时,显示表单选中的结果 */}
212
- <ResInfo {..._props} onShow={onShow} />
171
+ <ResInfo {..._props} onShow={onShow} pickerIcon={icons.pickerIcon} />
213
172
  <Popup
214
173
  maskClassName="location-picker-popup-mask"
215
174
  className="location-picker-popup"
@@ -229,19 +188,12 @@ export function LocationPicker(props) {
229
188
  确认
230
189
  </Button>
231
190
  </div>
232
- <ModalContent
233
- {..._props}
234
- visible={visible}
235
- setPickInfo={setPickInfo}
236
- layout={layout}
237
- setLoading={setLoading}
238
- loading={loading}
239
- />
191
+ <ModalContent {..._props} setPickInfo={setPickInfo} />
240
192
  </Popup>
241
193
  </>
242
194
  )}
243
195
  </div>
244
196
  );
245
- }
197
+ });
246
198
 
247
199
  export default LocationPicker;
@@ -1,13 +1,33 @@
1
+ export interface addressErrorI extends Error {
2
+ result?: string;
3
+ }
4
+
1
5
  /**
2
6
  * 根据经纬度获取定义的地址
3
7
  * @param lon
4
8
  * @param lat
5
9
  * @returns
6
10
  */
7
- export function getAddress(lon, lat) {
11
+ export function getAddress(lon, lat): Promise<string> {
8
12
  return new Promise((resolve, reject) => {
9
- if (!window.AMap) {
10
- reject(Error("Error getAddress window.AMap is not defined."));
13
+ getAddressByAMap(lon, lat)
14
+ .then(resolve)
15
+ .catch((err) => {
16
+ if (err.result === "USER_DAILY_QUERY_OVER_LIMIT" || err.result === "A_MAP_GEOCODER_IS_NOT_DEFINED") {
17
+ getAddressByFetch(lon, lat).then(resolve).catch(reject);
18
+ } else {
19
+ reject(err);
20
+ }
21
+ });
22
+ });
23
+ }
24
+
25
+ export const getAddressByAMap = function (lon, lat): Promise<string> {
26
+ return new Promise((resolve, reject) => {
27
+ if (!window.AMap?.Geocoder) {
28
+ const error: addressErrorI = Error("Error getAddress window.AMap.Geocoder is not defined.");
29
+ error.result = "A_MAP_GEOCODER_IS_NOT_DEFINED";
30
+ reject(error);
11
31
  return;
12
32
  }
13
33
  const geocoder = new window.AMap.Geocoder({});
@@ -17,35 +37,43 @@ export function getAddress(lon, lat) {
17
37
  resolve(formattedAddress);
18
38
  return formattedAddress;
19
39
  } else if (result === "USER_DAILY_QUERY_OVER_LIMIT") {
20
- // 超出限制,使用 webServer 进行请求
21
- if (!window._AMapLoaderTemp.serverKey) {
22
- reject(new Error("超出使用限制,请联系管理员"));
23
- return;
24
- }
25
- fetch(
26
- `https://restapi.amap.com/v3/geocode/regeo?location=${lon?.toFixed(6)},${lat?.toFixed(6)}&key=${
27
- window._AMapLoaderTemp.serverKey
28
- }`,
29
- {
30
- mode: "cors",
31
- },
32
- )
33
- .then((res) => res.json())
34
- .then((res) => {
35
- if (res.status === "1") {
36
- resolve(res.regeocode?.formatted_address);
37
- } else {
38
- console.warn("Warn getAddress fetch: ", res);
39
- reject(new Error(res.info));
40
- }
41
- })
42
- .catch(reject);
40
+ const error: addressErrorI = new Error("超出使用限制,请联系管理员");
41
+ error.result = result;
42
+ reject(error);
43
43
  } else {
44
44
  reject(result);
45
45
  }
46
46
  });
47
47
  });
48
- }
48
+ };
49
+
50
+ export const getAddressByFetch = function (lon, lat): Promise<string> {
51
+ return new Promise((resolve, reject) => {
52
+ // 使用 webServer 进行请求,需要配置对应的 serverKey
53
+ if (!window._AMapLoaderTemp?.serverKey) {
54
+ reject(new Error("请配置 serverKey"));
55
+ return;
56
+ }
57
+ fetch(
58
+ `https://restapi.amap.com/v3/geocode/regeo?location=${lon?.toFixed(6)},${lat?.toFixed(6)}&key=${
59
+ window._AMapLoaderTemp.serverKey
60
+ }`,
61
+ {
62
+ mode: "cors",
63
+ },
64
+ )
65
+ .then((res) => res.json())
66
+ .then((res) => {
67
+ if (res.status === "1") {
68
+ resolve(res.regeocode?.formatted_address);
69
+ } else {
70
+ console.warn("Warn getAddressByFetch: ", res);
71
+ reject(new Error(res.info));
72
+ }
73
+ })
74
+ .catch(reject);
75
+ });
76
+ };
49
77
 
50
78
  /**
51
79
  * 获取搜索提示
@@ -56,7 +84,31 @@ export function getAddress(lon, lat) {
56
84
  export function getSearchTips(search, cityCode = "0571") {
57
85
  return new Promise((resolve, reject) => {
58
86
  if (!window.AMap) {
59
- reject(Error("Error getAddress window.AMap is not defined."));
87
+ reject(Error("Error getSearchTips window.AMap is not defined."));
88
+ return;
89
+ }
90
+ if (window.AMap.AutoComplete) {
91
+ getSearchTipsByAMap(search, cityCode)
92
+ .then(resolve)
93
+ .catch((err) => {
94
+ if (err.result === "USER_DAILY_QUERY_OVER_LIMIT" || err.result === "A_MAP_AUTO_COMPLETE_IS_NOT_DEFINED") {
95
+ getSearchTipsByFetch(search, cityCode).then(resolve).catch(reject);
96
+ } else {
97
+ reject(err);
98
+ }
99
+ });
100
+ } else {
101
+ getSearchTipsByFetch(search, cityCode).then(resolve).catch(reject);
102
+ }
103
+ });
104
+ }
105
+
106
+ export function getSearchTipsByAMap(search, cityCode = "0571") {
107
+ return new Promise((resolve, reject) => {
108
+ if (!window.AMap?.Autocomplete) {
109
+ const error: addressErrorI = Error("Error getSearchTips window.AMap.Autocomplete is not defined.");
110
+ error.result = "A_MAP_AUTO_COMPLETE_IS_NOT_DEFINED";
111
+ reject(error);
60
112
  return;
61
113
  }
62
114
  const autoComplete = new window.AMap.Autocomplete({
@@ -78,44 +130,48 @@ export function getSearchTips(search, cityCode = "0571") {
78
130
  };
79
131
  }),
80
132
  );
81
- } else if (result === "USER_DAILY_QUERY_OVER_LIMIT") {
82
- // 超出限制,使用 webServer 进行请求
83
- if (!window._AMapLoaderTemp.serverKey) {
84
- reject(new Error("超出使用限制,请联系管理员"));
85
- return;
86
- }
87
- fetch(
88
- `https://restapi.amap.com/v3/assistant/inputtips?key=${window._AMapLoaderTemp.serverKey}&keywords=${search}&city=${cityCode}`,
89
- {
90
- mode: "cors",
91
- },
92
- )
93
- .then((res) => res.json())
94
- .then((res) => {
95
- if (res.status === "1") {
96
- resolve(
97
- res.tips?.map((it) => {
98
- const [lng, lat] = it.location?.split(",");
99
-
100
- return {
101
- ...it,
102
- label: it.name,
103
- value: it.id,
104
- lng: +lng,
105
- lon: +lng,
106
- lat: +lat,
107
- };
108
- }),
109
- );
110
- } else {
111
- console.warn("Warn getSearchTips fetch: ", res);
112
- reject(new Error(res.info));
113
- }
114
- })
115
- .catch(reject);
116
133
  } else {
117
134
  reject(result);
118
135
  }
119
136
  });
120
137
  });
121
138
  }
139
+
140
+ export function getSearchTipsByFetch(search, cityCode = "0571") {
141
+ return new Promise((resolve, reject) => {
142
+ // 使用 webServer 进行请求,需要配置对应的 serverKey
143
+ if (!window._AMapLoaderTemp?.serverKey) {
144
+ reject(new Error("请配置 serverKey"));
145
+ return;
146
+ }
147
+ fetch(
148
+ `https://restapi.amap.com/v3/assistant/inputtips?key=${window._AMapLoaderTemp.serverKey}&keywords=${search}&city=${cityCode}`,
149
+ {
150
+ mode: "cors",
151
+ },
152
+ )
153
+ .then((res) => res.json())
154
+ .then((res) => {
155
+ if (res.status === "1") {
156
+ resolve(
157
+ res.tips?.map((it) => {
158
+ const [lng, lat] = it.location?.split(",");
159
+
160
+ return {
161
+ ...it,
162
+ label: it.name,
163
+ value: it.id,
164
+ lng: +lng,
165
+ lon: +lng,
166
+ lat: +lat,
167
+ };
168
+ }),
169
+ );
170
+ } else {
171
+ console.warn("Warn getSearchTips fetch: ", res);
172
+ reject(new Error(res.info));
173
+ }
174
+ })
175
+ .catch(reject);
176
+ });
177
+ }
@@ -1,29 +1,59 @@
1
- import { useEffect, useRef, useState } from "react";
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import { Checkbox } from "antd-mobile";
3
3
  import { DownOutline, UpOutline } from "antd-mobile-icons";
4
4
  import { List as VirtualizedList, AutoSizer } from "react-virtualized";
5
5
  import _ from "lodash";
6
6
 
7
- import { getTreeAllExpandKeys, flatActiveTreeData } from "../common/data-handler";
7
+ import { getTreeAllExpandKeys, flatActiveTreeData, handleChecked, initChecked } from "../common/data-handler";
8
8
 
9
9
  import "./index.less";
10
10
 
11
11
  export const SelectList = (props) => {
12
- const { multiple, selected, treeData, fieldNames, setSelected, treeDefaultExpandAll } = props;
12
+ const {
13
+ multiple,
14
+ selected,
15
+ // 处理过的数据(搜索等)
16
+ treeData,
17
+ // 源数据
18
+ dataSource,
19
+ fieldNames,
20
+ setSelected = () => {},
21
+ treeDefaultExpandAll,
22
+ treeCheckStrictly,
23
+ } = props;
13
24
  const { label: labelKey = "label", value: valueKey = "value", children: childrenKey = "children" } = fieldNames || {};
14
25
 
15
26
  const [activeKeys, setActiveKeys] = useState(
16
27
  treeDefaultExpandAll ? getTreeAllExpandKeys(treeData, { fieldNames }) : [],
17
28
  );
18
29
 
30
+ const checkedOpt = {
31
+ fieldNames,
32
+ treeCheckStrictly,
33
+ };
34
+
19
35
  const boxRef = useRef();
20
36
  const [boxH, setBoxH] = useState(0);
37
+ const [treeDataChecked, setTreeDataChecked] = useState(
38
+ initChecked(treeData, multiple ? selected : [selected], checkedOpt),
39
+ );
21
40
  const [list, setList] = useState(flatActiveTreeData(treeData, activeKeys, { fieldNames }));
41
+ const selectedRef = useRef(selected);
42
+
43
+ useEffect(() => {
44
+ selectedRef.current = selected;
45
+ }, [selected]);
22
46
 
23
47
  useEffect(() => {
24
- const _activeKeys = treeDefaultExpandAll ? getTreeAllExpandKeys(treeData, { fieldNames }) : [];
48
+ const _treeDataChecked = initChecked(
49
+ treeData,
50
+ Array.isArray(selectedRef.current) ? selectedRef.current : [selectedRef.current],
51
+ checkedOpt,
52
+ );
53
+ const _activeKeys = treeDefaultExpandAll ? getTreeAllExpandKeys(_treeDataChecked, { fieldNames }) : [];
25
54
  setActiveKeys(_activeKeys);
26
- setList(flatActiveTreeData(treeData, _activeKeys, { fieldNames }));
55
+ setList(flatActiveTreeData(_treeDataChecked, _activeKeys, { fieldNames }));
56
+ setTreeDataChecked(_treeDataChecked);
27
57
  }, [treeData]);
28
58
 
29
59
  useEffect(() => {
@@ -50,42 +80,44 @@ export const SelectList = (props) => {
50
80
  } else {
51
81
  keys.push(val);
52
82
  }
53
- setList(flatActiveTreeData(treeData, keys, { fieldNames }));
83
+ setList(flatActiveTreeData(treeDataChecked, keys, { fieldNames }));
54
84
  return [...keys];
55
85
  });
56
86
  }
57
87
 
58
88
  function onCheckboxChange(key, val) {
89
+ // 处理选中状态
59
90
  if (multiple) {
60
91
  // 多选
61
- setSelected &&
62
- setSelected((v = []) => {
63
- if (val) {
64
- // 选中则往选中数组中添加
65
- return _.uniq([...v, key]);
66
- }
67
- // 非选中,从选中数组中剔除
68
- const idx = v.findIndex((it) => it === key);
69
- if (idx >= 0) {
70
- v.splice(idx, 1);
71
- return [...v];
72
- }
73
- return v;
74
- });
92
+ const checkedRes = handleChecked(treeDataChecked, key, val, checkedOpt);
93
+ const treeDataRes = _.cloneDeep(checkedRes.treeData);
94
+ setTreeDataChecked(treeDataRes);
95
+ setList(flatActiveTreeData(checkedRes.treeData, activeKeys, { fieldNames }));
96
+ setSelected(checkedRes.selected);
75
97
  } else {
76
98
  setSelected(key);
77
99
  }
78
100
  }
79
101
 
80
- function rowRender({ index, key: listKey, style }: { index: number; key: string; style: ElementCSSInlineStyle }) {
102
+ // TODO: useCallback 优化
103
+ const rowRender = function ({
104
+ index,
105
+ key: listKey,
106
+ style,
107
+ }: {
108
+ index: number;
109
+ key: string;
110
+ style: ElementCSSInlineStyle;
111
+ }) {
81
112
  const it = list[index];
82
113
  if (!it) {
83
114
  return null;
84
115
  }
85
116
 
86
117
  const itKey = it[valueKey];
87
- const checked = multiple ? selected?.includes(itKey) : selected === itKey;
118
+
88
119
  const active = activeKeys.includes(itKey);
120
+
89
121
  return (
90
122
  <div
91
123
  className="virtualized-list-item"
@@ -102,17 +134,18 @@ export const SelectList = (props) => {
102
134
  <Checkbox
103
135
  key={itKey}
104
136
  value={itKey}
105
- checked={checked}
137
+ indeterminate={it._indeterminate}
138
+ checked={it._checked}
106
139
  disabled={it.disabled}
107
140
  onClick={onStopPropagation}
108
141
  onChange={(v) => onCheckboxChange(itKey, v)}
109
142
  >
110
- {it[labelKey]}
143
+ {it[labelKey]}-{itKey}
111
144
  </Checkbox>
112
145
  {it.hasChildren && (active ? <UpOutline className="arrow-icon" /> : <DownOutline className="arrow-icon" />)}
113
146
  </div>
114
147
  );
115
- }
148
+ };
116
149
 
117
150
  return (
118
151
  <div className="tree-select-list" ref={boxRef}>