@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.
- package/lib/index.js +1 -1
- package/package.json +1 -1
- package/src/components/LocationPicker/Map/AMap/common/loader.ts +2 -2
- package/src/components/LocationPicker/Map/AMap/common/utils.ts +15 -4
- package/src/components/LocationPicker/Map/AMap/index.jsx +3 -2
- package/src/components/LocationPicker/README.md +31 -22
- package/src/components/LocationPicker/assets/svg-icon.js +86 -0
- package/src/components/LocationPicker/common/utils.ts +2 -9
- package/src/components/LocationPicker/components/MapSearch/index.jsx +0 -3
- package/src/components/LocationPicker/components/ModalContent/index.less +18 -4
- package/src/components/LocationPicker/components/ModalContent/index.tsx +45 -62
- package/src/components/LocationPicker/components/Notice/index.tsx +1 -1
- package/src/components/LocationPicker/components/ResInfo/index.less +9 -10
- package/src/components/LocationPicker/components/ResInfo/index.tsx +20 -18
- package/src/components/LocationPicker/index.tsx +24 -72
- package/src/components/LocationPicker/servers/index.ts +119 -63
- package/src/components/TreeSelect/SelectList/index.tsx +58 -25
- package/src/components/TreeSelect/common/data-handler.ts +158 -3
- package/src/components/TreeSelect/index.less +1 -0
- package/src/components/TreeSelect/index.tsx +8 -1
- package/src/index.less +5 -2
- package/lib/static/imgs/marker-icon_ab8bbcc8cb.svg +0 -4
- package/lib/static/imgs/picker-icon_24d725ef02.svg +0 -5
- package/lib/static/imgs/position-icon_5bcb8a742e.svg +0 -6
- package/lib/static/imgs/reset-icon_9edad62306.svg +0 -5
- package/src/components/LocationPicker/assets/marker-icon.svg +0 -4
- package/src/components/LocationPicker/assets/picker-icon.svg +0 -5
- package/src/components/LocationPicker/assets/position-icon.svg +0 -6
- 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 {
|
|
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 {
|
|
10
|
+
import { getPropsValue } from "./common/utils";
|
|
11
|
+
import { getAddress } from "./servers";
|
|
11
12
|
|
|
12
13
|
import "./index.less";
|
|
13
14
|
|
|
14
|
-
export
|
|
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,
|
|
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
|
|
90
|
-
lat
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
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 {
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}>
|