@hzab/form-render-mobile 0.3.1 → 0.4.0
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 +1 -1
- 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/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 +54 -26
- package/src/components/TreeSelect/SelectList/index.tsx +59 -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> {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
getAddressByAMap(lon, lat)
|
|
14
|
+
.then(resolve)
|
|
15
|
+
.catch((err) => {
|
|
16
|
+
if (err.result === "USER_DAILY_QUERY_OVER_LIMIT" || err.result === "A_MAP_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> {
|
|
8
26
|
return new Promise((resolve, reject) => {
|
|
9
27
|
if (!window.AMap) {
|
|
10
|
-
|
|
28
|
+
const error: addressErrorI = Error("Error getAddress window.AMap is not defined.");
|
|
29
|
+
error.result = "A_MAP_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 进行请求
|
|
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
|
* 获取搜索提示
|
|
@@ -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,16 +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}
|
|
139
|
+
disabled={it.disabled}
|
|
106
140
|
onClick={onStopPropagation}
|
|
107
141
|
onChange={(v) => onCheckboxChange(itKey, v)}
|
|
108
142
|
>
|
|
109
|
-
{it[labelKey]}
|
|
143
|
+
{it[labelKey]}-{itKey}
|
|
110
144
|
</Checkbox>
|
|
111
145
|
{it.hasChildren && (active ? <UpOutline className="arrow-icon" /> : <DownOutline className="arrow-icon" />)}
|
|
112
146
|
</div>
|
|
113
147
|
);
|
|
114
|
-
}
|
|
148
|
+
};
|
|
115
149
|
|
|
116
150
|
return (
|
|
117
151
|
<div className="tree-select-list" ref={boxRef}>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* 获取列表枚举数据
|
|
3
5
|
* @param list
|
|
@@ -14,14 +16,24 @@ export const getDataEnum = function (list, opt?: any) {
|
|
|
14
16
|
level,
|
|
15
17
|
fullPathLabels: [...(parent.fullPathLabels || []), it[label]],
|
|
16
18
|
fullPathValues: [...(parent.fullPathValues || []), it[value]],
|
|
19
|
+
parentEnum: { ...parent.parentEnum },
|
|
17
20
|
};
|
|
21
|
+
if (parent[value]) {
|
|
22
|
+
item.parentEnum[parent[value]] = 1;
|
|
23
|
+
}
|
|
18
24
|
item.fullPathItems = [...(parent.fullPathItems || []), item];
|
|
19
25
|
delete item[children];
|
|
20
26
|
|
|
21
27
|
dataEnum[it[value]] = item;
|
|
22
28
|
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
const childrenList = it[children];
|
|
30
|
+
if (childrenList) {
|
|
31
|
+
const childrenEnum = {};
|
|
32
|
+
childrenList.forEach((it) => {
|
|
33
|
+
childrenEnum[it[value]] = 1;
|
|
34
|
+
});
|
|
35
|
+
item.childrenEnum = childrenEnum;
|
|
36
|
+
getDataEnum(childrenList, { ...opt, dataEnum, parent: item });
|
|
25
37
|
}
|
|
26
38
|
});
|
|
27
39
|
return dataEnum;
|
|
@@ -89,21 +101,32 @@ export const flatActiveTreeData = function (treeData, activeKeys = [], opt?: any
|
|
|
89
101
|
const { list = [], fieldNames, parent = {}, level = 1 } = opt || {};
|
|
90
102
|
const { value = "value", children = "children", label = "label" } = fieldNames || {};
|
|
91
103
|
treeData?.forEach((it) => {
|
|
104
|
+
const childrenList = it?.[children];
|
|
92
105
|
const item = {
|
|
93
106
|
...it,
|
|
94
107
|
parent,
|
|
95
108
|
level,
|
|
96
109
|
fullPathLabel: [...(parent.fullPathLabel || []), it[label]],
|
|
110
|
+
fullPathValues: [...(parent.fullPathValues || []), it[value]],
|
|
111
|
+
parentEnum: { ...parent.parentEnum },
|
|
112
|
+
childrenLen: childrenList?.length || 0,
|
|
97
113
|
};
|
|
114
|
+
if (parent[value]) {
|
|
115
|
+
item.parentEnum[parent[value]] = 1;
|
|
116
|
+
}
|
|
98
117
|
item.fullPathItems = [...(parent.fullPathItems || []), item];
|
|
99
118
|
|
|
100
119
|
if (item.parentId === null || item.parentId === undefined || item.parentId === "") {
|
|
101
120
|
item.parentId = parent[value];
|
|
102
121
|
}
|
|
103
|
-
const childrenList = it?.[children];
|
|
104
122
|
delete item[children];
|
|
105
123
|
list.push(item);
|
|
106
124
|
if (Array.isArray(childrenList) && childrenList.length > 0) {
|
|
125
|
+
const childrenEnum = {};
|
|
126
|
+
childrenList.forEach((it) => {
|
|
127
|
+
childrenEnum[it[value]] = 1;
|
|
128
|
+
});
|
|
129
|
+
item.childrenEnum = childrenEnum;
|
|
107
130
|
item.hasChildren = true;
|
|
108
131
|
activeKeys.includes(it[value]) &&
|
|
109
132
|
flatActiveTreeData(childrenList, activeKeys, {
|
|
@@ -133,3 +156,135 @@ export const getTreeAllExpandKeys = function (treeData, opt?: any) {
|
|
|
133
156
|
});
|
|
134
157
|
return expandKeys;
|
|
135
158
|
};
|
|
159
|
+
|
|
160
|
+
export const removeAllSelected = function (treeData, selected = [], opt?: any) {
|
|
161
|
+
const { fieldNames } = opt || {};
|
|
162
|
+
const { value = "value", children = "children" } = fieldNames || {};
|
|
163
|
+
treeData?.forEach((it) => {
|
|
164
|
+
const key = it[value];
|
|
165
|
+
const idx = selected.findIndex((k) => k === key);
|
|
166
|
+
idx >= 0 && selected.splice(idx, 1);
|
|
167
|
+
const childList = it[children];
|
|
168
|
+
if (Array.isArray(childList) && childList.length > 0) {
|
|
169
|
+
removeAllSelected(childList, selected, opt);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
return selected;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 处理选中状态
|
|
177
|
+
* @param treeData
|
|
178
|
+
* @param key
|
|
179
|
+
* @param val
|
|
180
|
+
* @param opt
|
|
181
|
+
* @returns
|
|
182
|
+
* _checked 选中状态
|
|
183
|
+
* _indeterminate 半选状态(只在父子联动中使用)
|
|
184
|
+
*/
|
|
185
|
+
export const handleChecked = function (treeData, targetKey, targetVal, opt) {
|
|
186
|
+
const {
|
|
187
|
+
fieldNames,
|
|
188
|
+
/**
|
|
189
|
+
* 是否是父子联动
|
|
190
|
+
*/
|
|
191
|
+
treeCheckStrictly,
|
|
192
|
+
selected = [],
|
|
193
|
+
} = opt || {};
|
|
194
|
+
const { value = "value", children = "children" } = fieldNames || {};
|
|
195
|
+
const dataLen = treeData?.length;
|
|
196
|
+
let checkedCount = 0;
|
|
197
|
+
let indeterminate = false;
|
|
198
|
+
for (let i = 0; i < dataLen; i++) {
|
|
199
|
+
const it = treeData[i];
|
|
200
|
+
const key = it[value];
|
|
201
|
+
// 处理禁用、只读状态?
|
|
202
|
+
if (key === targetKey) {
|
|
203
|
+
it._checked = targetVal;
|
|
204
|
+
it._indeterminate = false;
|
|
205
|
+
if (treeCheckStrictly) {
|
|
206
|
+
// 父子联动,处理当前项子项的选中状态,所有子项跟随当前项
|
|
207
|
+
setAllChecked(it[children], targetVal, opt);
|
|
208
|
+
// 结束当前项的处理,当前项及父级的状态由上层遍历处理
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
const childList = it[children];
|
|
212
|
+
if (Array.isArray(childList) && childList.length > 0) {
|
|
213
|
+
const childRes = handleChecked(childList, targetKey, targetVal, { ...opt, selected });
|
|
214
|
+
if (treeCheckStrictly) {
|
|
215
|
+
// 父子联动,处理父级的选中、半选状态
|
|
216
|
+
it._checked = childRes._checked;
|
|
217
|
+
it._indeterminate = childRes._indeterminate;
|
|
218
|
+
// 子项有半选,则父级也是半选
|
|
219
|
+
if (it._indeterminate && !it._checked) {
|
|
220
|
+
indeterminate = true;
|
|
221
|
+
}
|
|
222
|
+
if (it._checked) {
|
|
223
|
+
it._indeterminate = false;
|
|
224
|
+
removeAllSelected(childList, selected, opt);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (it._checked) {
|
|
231
|
+
// 当前项处理完处理选中状态,checkedCount + 1, key 添加到 selected 数组中
|
|
232
|
+
checkedCount += 1;
|
|
233
|
+
selected.push(key);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
treeData,
|
|
239
|
+
checkedCount,
|
|
240
|
+
_checked: dataLen > 0 && checkedCount === dataLen,
|
|
241
|
+
_indeterminate: indeterminate || (checkedCount > 0 && checkedCount < dataLen),
|
|
242
|
+
selected,
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* 初始化选中状态
|
|
248
|
+
* @param _treeData
|
|
249
|
+
* @param opt
|
|
250
|
+
* _checked 选中状态
|
|
251
|
+
* _indeterminate 半选状态(只在父子联动中使用)
|
|
252
|
+
*/
|
|
253
|
+
export const initChecked = function (_treeData, selected, opt) {
|
|
254
|
+
const treeData = _.cloneDeep(_treeData);
|
|
255
|
+
_.uniq(selected || [])?.forEach((it) => {
|
|
256
|
+
handleChecked(treeData, it, true, opt);
|
|
257
|
+
});
|
|
258
|
+
return treeData;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 设置所有项的选中状态
|
|
263
|
+
* @param treeData
|
|
264
|
+
* @param targetVal
|
|
265
|
+
* @param opt
|
|
266
|
+
*/
|
|
267
|
+
export const setAllChecked = function (treeData, targetVal, opt) {
|
|
268
|
+
const { fieldNames } = opt || {};
|
|
269
|
+
const { children = "children" } = fieldNames || {};
|
|
270
|
+
treeData?.forEach((it) => {
|
|
271
|
+
it._checked = targetVal;
|
|
272
|
+
it._indeterminate = undefined;
|
|
273
|
+
const childList = it[children];
|
|
274
|
+
if (Array.isArray(childList) && childList.length > 0) {
|
|
275
|
+
setAllChecked(childList, targetVal, opt);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
return treeData;
|
|
279
|
+
};
|
|
280
|
+
/**
|
|
281
|
+
* 清除所有的选中状态
|
|
282
|
+
* @param treeData
|
|
283
|
+
* @param opt
|
|
284
|
+
* @returns
|
|
285
|
+
* _checked 选中状态
|
|
286
|
+
* _indeterminate 半选状态
|
|
287
|
+
*/
|
|
288
|
+
export const cleanChecked = function (treeData, opt) {
|
|
289
|
+
return setAllChecked(treeData, undefined, opt);
|
|
290
|
+
};
|
|
@@ -122,7 +122,14 @@ export const TreeSelect = observer((props: any) => {
|
|
|
122
122
|
{showSearch && (
|
|
123
123
|
<TreeSearch visible={visible} dataSource={dataSource} setTreeData={setTreeData} fieldNames={fieldNames} />
|
|
124
124
|
)}
|
|
125
|
-
<SelectList
|
|
125
|
+
<SelectList
|
|
126
|
+
{...props}
|
|
127
|
+
visible={visible}
|
|
128
|
+
dataSource={dataSource}
|
|
129
|
+
treeData={treeData}
|
|
130
|
+
selected={selected}
|
|
131
|
+
setSelected={setSelected}
|
|
132
|
+
/>
|
|
126
133
|
</div>
|
|
127
134
|
</Popup>
|
|
128
135
|
</PickerRightOutline>
|
package/src/index.less
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
.adm-formily-item {
|
|
4
4
|
font-size: 4vw;
|
|
5
5
|
.adm-formily-item-label {
|
|
6
|
-
line-height: 1.
|
|
6
|
+
line-height: 1.5;
|
|
7
7
|
}
|
|
8
8
|
.adm-formily-item-error-help {
|
|
9
|
-
line-height: 1.
|
|
9
|
+
line-height: 1.5;
|
|
10
|
+
}
|
|
11
|
+
.adm-formily-item-control .adm-formily-item-control-content .adm-formily-item-control-content-component {
|
|
12
|
+
line-height: 1.5;
|
|
10
13
|
}
|
|
11
14
|
}
|
|
12
15
|
.adm-list-body {
|