@hzab/form-render 1.7.16-alpha.0 → 1.7.17
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 +1 -1
- package/src/components/LocationPicker/components/MapSearch/index.jsx +57 -57
- package/src/components/TreeCheckbox/common/utils.ts +221 -221
- package/src/components/Upload/README.md +14 -1
- package/src/components/Upload/common/handleIOFileList.ts +4 -40
- package/src/components/Upload/common/utils.js +3 -2
- package/src/components/Upload/uploader.jsx +57 -5
package/package.json
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { Input, AutoComplete } from "antd";
|
|
3
|
-
|
|
4
|
-
import { getSearchTips } from "../../servers";
|
|
5
|
-
|
|
6
|
-
import "./index.less";
|
|
7
|
-
|
|
8
|
-
const cityCode = "0571";
|
|
9
|
-
|
|
10
|
-
function Search(props) {
|
|
11
|
-
const [loading, setLoading] = useState(false);
|
|
12
|
-
const [tips, setTips] = useState([]);
|
|
13
|
-
|
|
14
|
-
function onSelect(value, option) {
|
|
15
|
-
const { lng: _lng, lat: _lat } = option || {};
|
|
16
|
-
props.setPoint && props.setPoint(_lng, _lat, { changeCenter: true });
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function onSearch(search) {
|
|
20
|
-
if (!search) {
|
|
21
|
-
setTips([]);
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
setLoading(true);
|
|
25
|
-
getSearchTips(search, props.cityCode || cityCode)
|
|
26
|
-
.then((res) => {
|
|
27
|
-
setTips(
|
|
28
|
-
res?.map((it) => ({
|
|
29
|
-
...it,
|
|
30
|
-
label: it.name,
|
|
31
|
-
// 解决回显为 id 的问题
|
|
32
|
-
value: it.name,
|
|
33
|
-
})),
|
|
34
|
-
);
|
|
35
|
-
setLoading(false);
|
|
36
|
-
})
|
|
37
|
-
.catch((err) => {
|
|
38
|
-
setLoading(false);
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<AutoComplete
|
|
44
|
-
className="location-map-search"
|
|
45
|
-
options={tips}
|
|
46
|
-
onSelect={onSelect}
|
|
47
|
-
onSearch={_.debounce(onSearch, 500)}
|
|
48
|
-
onClear={() => {
|
|
49
|
-
setTips([]);
|
|
50
|
-
}}
|
|
51
|
-
>
|
|
52
|
-
<Input.Search allowClear loading={loading} placeholder="请输入" />
|
|
53
|
-
</AutoComplete>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export default Search;
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { Input, AutoComplete } from "antd";
|
|
3
|
+
|
|
4
|
+
import { getSearchTips } from "../../servers";
|
|
5
|
+
|
|
6
|
+
import "./index.less";
|
|
7
|
+
|
|
8
|
+
const cityCode = "0571";
|
|
9
|
+
|
|
10
|
+
function Search(props) {
|
|
11
|
+
const [loading, setLoading] = useState(false);
|
|
12
|
+
const [tips, setTips] = useState([]);
|
|
13
|
+
|
|
14
|
+
function onSelect(value, option) {
|
|
15
|
+
const { lng: _lng, lat: _lat } = option || {};
|
|
16
|
+
props.setPoint && props.setPoint(_lng, _lat, { changeCenter: true });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function onSearch(search) {
|
|
20
|
+
if (!search) {
|
|
21
|
+
setTips([]);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
setLoading(true);
|
|
25
|
+
getSearchTips(search, props.cityCode || cityCode)
|
|
26
|
+
.then((res) => {
|
|
27
|
+
setTips(
|
|
28
|
+
res?.map((it) => ({
|
|
29
|
+
...it,
|
|
30
|
+
label: it.name,
|
|
31
|
+
// 解决回显为 id 的问题
|
|
32
|
+
value: it.name,
|
|
33
|
+
})),
|
|
34
|
+
);
|
|
35
|
+
setLoading(false);
|
|
36
|
+
})
|
|
37
|
+
.catch((err) => {
|
|
38
|
+
setLoading(false);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<AutoComplete
|
|
44
|
+
className="location-map-search"
|
|
45
|
+
options={tips}
|
|
46
|
+
onSelect={onSelect}
|
|
47
|
+
onSearch={_.debounce(onSearch, 500)}
|
|
48
|
+
onClear={() => {
|
|
49
|
+
setTips([]);
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
<Input.Search allowClear loading={loading} placeholder="请输入" />
|
|
53
|
+
</AutoComplete>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default Search;
|
|
@@ -1,221 +1,221 @@
|
|
|
1
|
-
import _ from "lodash";
|
|
2
|
-
|
|
3
|
-
export interface ITabTableDataOpt extends optT {
|
|
4
|
-
/**
|
|
5
|
-
* 展示 tab 的层级
|
|
6
|
-
*/
|
|
7
|
-
tabLevel?: number;
|
|
8
|
-
/**
|
|
9
|
-
* 当前处理的层级
|
|
10
|
-
*/
|
|
11
|
-
_level?: number;
|
|
12
|
-
fieldNames?: {
|
|
13
|
-
label?: string;
|
|
14
|
-
value?: string;
|
|
15
|
-
children?: string;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* 处理数据,按需设置 tab、table 数据
|
|
20
|
-
* @param treeData 树数据数组
|
|
21
|
-
* @param opt 配置项
|
|
22
|
-
* @returns
|
|
23
|
-
*/
|
|
24
|
-
export function getTabTableData(_treeData = [], opt: ITabTableDataOpt = {}) {
|
|
25
|
-
const treeData = _.cloneDeep(_treeData);
|
|
26
|
-
const { tabLevel = 0, _level = 0, fieldNames } = opt || {};
|
|
27
|
-
const { value, children } = fieldNames || {};
|
|
28
|
-
let resList = [];
|
|
29
|
-
// 当前层级小于 tabLevel,视为当前层级渲染为 tab
|
|
30
|
-
const isTab = _level < tabLevel;
|
|
31
|
-
|
|
32
|
-
// 当前层级渲染为 tab,直接 push 到 resList 中
|
|
33
|
-
if (isTab) {
|
|
34
|
-
resList.push({
|
|
35
|
-
level: _level,
|
|
36
|
-
defaultActiveKey: treeData[0]?.[value],
|
|
37
|
-
tabList: treeData,
|
|
38
|
-
});
|
|
39
|
-
// 继续处理子集数据
|
|
40
|
-
treeData?.forEach((it) => {
|
|
41
|
-
if (it[children]?.length > 0) {
|
|
42
|
-
it[children] = getTabTableData(it[children], {
|
|
43
|
-
...opt,
|
|
44
|
-
_level: _level + 1,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
} else {
|
|
49
|
-
// 非 tab 层级,处理 table 渲染数据(handleTreeToArray 自动处理子集)
|
|
50
|
-
resList.push({
|
|
51
|
-
level: _level,
|
|
52
|
-
tableData: handleTreeToArray(treeData, {
|
|
53
|
-
...opt,
|
|
54
|
-
level: undefined,
|
|
55
|
-
}),
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
return resList;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 处理树形数据,把树形数据方向转为多维数组,便于渲染
|
|
63
|
-
* @param params
|
|
64
|
-
* @returns
|
|
65
|
-
如:
|
|
66
|
-
[{key:1, children:[{key:11},{key:12}]}, {key:2, children:[{key:21, children:[{key:211}]},{key:22},{key:23}]}]
|
|
67
|
-
结果数组:
|
|
68
|
-
[1, 11, ]
|
|
69
|
-
[ , 12, ]
|
|
70
|
-
[2, 21, 211]
|
|
71
|
-
[ , 22, ]
|
|
72
|
-
[ , 23, ]
|
|
73
|
-
*/
|
|
74
|
-
export function handleTreeToArray(treeData = [], opt: any = {}) {
|
|
75
|
-
const { resList, maxCol } = transformTreeData(treeData, opt);
|
|
76
|
-
resList?.forEach((row) => {
|
|
77
|
-
fillArrByPush(maxCol, row);
|
|
78
|
-
});
|
|
79
|
-
return resList;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export type optT = {
|
|
83
|
-
resList?: Array<any>;
|
|
84
|
-
preCol?: number;
|
|
85
|
-
//是否合并叶子节点
|
|
86
|
-
mergeLeaf?: boolean;
|
|
87
|
-
// 最后一列是否合并到一个单元格
|
|
88
|
-
mergeFullLeaf?: boolean;
|
|
89
|
-
// 当前递归的层级
|
|
90
|
-
level?: number;
|
|
91
|
-
fieldNames?: {
|
|
92
|
-
value?: string;
|
|
93
|
-
label?: string;
|
|
94
|
-
children?: string;
|
|
95
|
-
};
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* 处理树形数据,把树形数据方向转为多维数组,便于渲染
|
|
100
|
-
* @param params
|
|
101
|
-
* @returns
|
|
102
|
-
如:
|
|
103
|
-
[{key:1, children:[{key:11},{key:12}]}, {key:2, children:[{key:21, children:[{key:211}]},{key:22},{key:23}]}]
|
|
104
|
-
结果数组:
|
|
105
|
-
[1, 11, ]
|
|
106
|
-
[ , 12, ]
|
|
107
|
-
[2, 21, 211]
|
|
108
|
-
[ , 22, ]
|
|
109
|
-
[ , 23, ]
|
|
110
|
-
*/
|
|
111
|
-
export function transformTreeData(treeData = [], opt: optT = {}) {
|
|
112
|
-
const { resList = [], preCol = 0, mergeLeaf, mergeFullLeaf, level = 0, fieldNames } = opt || {};
|
|
113
|
-
const { value = "value", children = "children", label = "label" } = fieldNames || {};
|
|
114
|
-
let maxCol = preCol + 1;
|
|
115
|
-
// 统计当前项包含的最大行数(当前项的行数等于所有子项行数之和),用于合并行的值
|
|
116
|
-
let maxRow = 0;
|
|
117
|
-
|
|
118
|
-
const leafIndexList = [];
|
|
119
|
-
const leafItemList = [];
|
|
120
|
-
|
|
121
|
-
// 是否有子项
|
|
122
|
-
let hasChildren = false;
|
|
123
|
-
treeData?.forEach((it, i) => {
|
|
124
|
-
if (it && Array.isArray(it[children]) && it[children].length > 0) {
|
|
125
|
-
hasChildren = true;
|
|
126
|
-
} else {
|
|
127
|
-
// 保存当前层级的叶子节点,用于叶子节点合并
|
|
128
|
-
leafIndexList.push(i);
|
|
129
|
-
leafItemList.push(it);
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
let lastRowIdx = resList.length > 0 ? resList.length - 1 : 0;
|
|
134
|
-
// 合并不存在 children 的层级,mergeFullLeaf === true && hasChildren === false && level > 0 时,合并当前数据
|
|
135
|
-
if (mergeFullLeaf === true && !hasChildren && level > 0) {
|
|
136
|
-
resList[lastRowIdx].push([...treeData?.map((it) => ({ ...it, label: it[label], value: it[value] }))]);
|
|
137
|
-
return {
|
|
138
|
-
resList,
|
|
139
|
-
maxCol,
|
|
140
|
-
// 合并之后视为 1 行
|
|
141
|
-
maxRow: 1,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// 合并当前层级的叶子节点,非叶子节点忽略
|
|
146
|
-
if (mergeLeaf === true && leafIndexList.length > 0) {
|
|
147
|
-
// treeData 中删除当前层级其他叶子节点
|
|
148
|
-
for (let i = leafIndexList.length - 1; i >= 0; i--) {
|
|
149
|
-
const idx = leafIndexList[i];
|
|
150
|
-
treeData.splice(idx, 1);
|
|
151
|
-
}
|
|
152
|
-
// 所有叶子节点放在尾部
|
|
153
|
-
treeData.push({
|
|
154
|
-
_type_: "leafList",
|
|
155
|
-
leafList: leafItemList?.map((it) => ({ ...it, label: it[label], value: it[value] })),
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
treeData?.forEach((it, i) => {
|
|
160
|
-
let item = null;
|
|
161
|
-
if (it._type_ === "leafList") {
|
|
162
|
-
// 合并当前层级的叶子节点
|
|
163
|
-
item = it.leafList;
|
|
164
|
-
} else {
|
|
165
|
-
item = { ...it, value: it[value], label: it[label] };
|
|
166
|
-
}
|
|
167
|
-
const childList = item[children] || [];
|
|
168
|
-
const childLen = childList?.length;
|
|
169
|
-
// 当 mergeFullLeaf !== true && children 长度大于1时,子项下标大于 0 的项需要另起一行,同时补充之前空的列。
|
|
170
|
-
if (i > 0) {
|
|
171
|
-
resList.push(fillArrByPush(preCol, [], { content: false }));
|
|
172
|
-
}
|
|
173
|
-
lastRowIdx = resList.length > 0 ? resList.length - 1 : 0;
|
|
174
|
-
// 解决首次进来第一行数组空的问题
|
|
175
|
-
if (!resList[lastRowIdx]) {
|
|
176
|
-
resList[lastRowIdx] = [];
|
|
177
|
-
}
|
|
178
|
-
resList[lastRowIdx].push(item);
|
|
179
|
-
if (childLen > 0) {
|
|
180
|
-
const res = transformTreeData(childList, {
|
|
181
|
-
...opt,
|
|
182
|
-
resList,
|
|
183
|
-
preCol: preCol + 1,
|
|
184
|
-
level: level + 1,
|
|
185
|
-
});
|
|
186
|
-
if (maxCol < res.maxCol) {
|
|
187
|
-
maxCol = res.maxCol;
|
|
188
|
-
}
|
|
189
|
-
// 当前的最大行数加上子项的行数
|
|
190
|
-
maxRow = maxRow + res.maxRow;
|
|
191
|
-
// 每项的最大行数为子项的最大行数之和
|
|
192
|
-
item._maxRow = res.maxRow;
|
|
193
|
-
} else {
|
|
194
|
-
item._maxRow = 1;
|
|
195
|
-
maxRow += 1;
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
return {
|
|
200
|
-
resList,
|
|
201
|
-
maxCol,
|
|
202
|
-
maxRow,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* 填充数组
|
|
208
|
-
* @param len
|
|
209
|
-
* @param arr
|
|
210
|
-
* @param opt
|
|
211
|
-
* @returns
|
|
212
|
-
*/
|
|
213
|
-
export function fillArrByPush(len, arr = [], opt: any = {}) {
|
|
214
|
-
const { content } = opt || {};
|
|
215
|
-
if (arr.length < len) {
|
|
216
|
-
for (let i = arr.length; i < len; i++) {
|
|
217
|
-
arr.push(content);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return arr;
|
|
221
|
-
}
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
|
|
3
|
+
export interface ITabTableDataOpt extends optT {
|
|
4
|
+
/**
|
|
5
|
+
* 展示 tab 的层级
|
|
6
|
+
*/
|
|
7
|
+
tabLevel?: number;
|
|
8
|
+
/**
|
|
9
|
+
* 当前处理的层级
|
|
10
|
+
*/
|
|
11
|
+
_level?: number;
|
|
12
|
+
fieldNames?: {
|
|
13
|
+
label?: string;
|
|
14
|
+
value?: string;
|
|
15
|
+
children?: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 处理数据,按需设置 tab、table 数据
|
|
20
|
+
* @param treeData 树数据数组
|
|
21
|
+
* @param opt 配置项
|
|
22
|
+
* @returns
|
|
23
|
+
*/
|
|
24
|
+
export function getTabTableData(_treeData = [], opt: ITabTableDataOpt = {}) {
|
|
25
|
+
const treeData = _.cloneDeep(_treeData);
|
|
26
|
+
const { tabLevel = 0, _level = 0, fieldNames } = opt || {};
|
|
27
|
+
const { value, children } = fieldNames || {};
|
|
28
|
+
let resList = [];
|
|
29
|
+
// 当前层级小于 tabLevel,视为当前层级渲染为 tab
|
|
30
|
+
const isTab = _level < tabLevel;
|
|
31
|
+
|
|
32
|
+
// 当前层级渲染为 tab,直接 push 到 resList 中
|
|
33
|
+
if (isTab) {
|
|
34
|
+
resList.push({
|
|
35
|
+
level: _level,
|
|
36
|
+
defaultActiveKey: treeData[0]?.[value],
|
|
37
|
+
tabList: treeData,
|
|
38
|
+
});
|
|
39
|
+
// 继续处理子集数据
|
|
40
|
+
treeData?.forEach((it) => {
|
|
41
|
+
if (it[children]?.length > 0) {
|
|
42
|
+
it[children] = getTabTableData(it[children], {
|
|
43
|
+
...opt,
|
|
44
|
+
_level: _level + 1,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
} else {
|
|
49
|
+
// 非 tab 层级,处理 table 渲染数据(handleTreeToArray 自动处理子集)
|
|
50
|
+
resList.push({
|
|
51
|
+
level: _level,
|
|
52
|
+
tableData: handleTreeToArray(treeData, {
|
|
53
|
+
...opt,
|
|
54
|
+
level: undefined,
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return resList;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 处理树形数据,把树形数据方向转为多维数组,便于渲染
|
|
63
|
+
* @param params
|
|
64
|
+
* @returns
|
|
65
|
+
如:
|
|
66
|
+
[{key:1, children:[{key:11},{key:12}]}, {key:2, children:[{key:21, children:[{key:211}]},{key:22},{key:23}]}]
|
|
67
|
+
结果数组:
|
|
68
|
+
[1, 11, ]
|
|
69
|
+
[ , 12, ]
|
|
70
|
+
[2, 21, 211]
|
|
71
|
+
[ , 22, ]
|
|
72
|
+
[ , 23, ]
|
|
73
|
+
*/
|
|
74
|
+
export function handleTreeToArray(treeData = [], opt: any = {}) {
|
|
75
|
+
const { resList, maxCol } = transformTreeData(treeData, opt);
|
|
76
|
+
resList?.forEach((row) => {
|
|
77
|
+
fillArrByPush(maxCol, row);
|
|
78
|
+
});
|
|
79
|
+
return resList;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type optT = {
|
|
83
|
+
resList?: Array<any>;
|
|
84
|
+
preCol?: number;
|
|
85
|
+
//是否合并叶子节点
|
|
86
|
+
mergeLeaf?: boolean;
|
|
87
|
+
// 最后一列是否合并到一个单元格
|
|
88
|
+
mergeFullLeaf?: boolean;
|
|
89
|
+
// 当前递归的层级
|
|
90
|
+
level?: number;
|
|
91
|
+
fieldNames?: {
|
|
92
|
+
value?: string;
|
|
93
|
+
label?: string;
|
|
94
|
+
children?: string;
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 处理树形数据,把树形数据方向转为多维数组,便于渲染
|
|
100
|
+
* @param params
|
|
101
|
+
* @returns
|
|
102
|
+
如:
|
|
103
|
+
[{key:1, children:[{key:11},{key:12}]}, {key:2, children:[{key:21, children:[{key:211}]},{key:22},{key:23}]}]
|
|
104
|
+
结果数组:
|
|
105
|
+
[1, 11, ]
|
|
106
|
+
[ , 12, ]
|
|
107
|
+
[2, 21, 211]
|
|
108
|
+
[ , 22, ]
|
|
109
|
+
[ , 23, ]
|
|
110
|
+
*/
|
|
111
|
+
export function transformTreeData(treeData = [], opt: optT = {}) {
|
|
112
|
+
const { resList = [], preCol = 0, mergeLeaf, mergeFullLeaf, level = 0, fieldNames } = opt || {};
|
|
113
|
+
const { value = "value", children = "children", label = "label" } = fieldNames || {};
|
|
114
|
+
let maxCol = preCol + 1;
|
|
115
|
+
// 统计当前项包含的最大行数(当前项的行数等于所有子项行数之和),用于合并行的值
|
|
116
|
+
let maxRow = 0;
|
|
117
|
+
|
|
118
|
+
const leafIndexList = [];
|
|
119
|
+
const leafItemList = [];
|
|
120
|
+
|
|
121
|
+
// 是否有子项
|
|
122
|
+
let hasChildren = false;
|
|
123
|
+
treeData?.forEach((it, i) => {
|
|
124
|
+
if (it && Array.isArray(it[children]) && it[children].length > 0) {
|
|
125
|
+
hasChildren = true;
|
|
126
|
+
} else {
|
|
127
|
+
// 保存当前层级的叶子节点,用于叶子节点合并
|
|
128
|
+
leafIndexList.push(i);
|
|
129
|
+
leafItemList.push(it);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
let lastRowIdx = resList.length > 0 ? resList.length - 1 : 0;
|
|
134
|
+
// 合并不存在 children 的层级,mergeFullLeaf === true && hasChildren === false && level > 0 时,合并当前数据
|
|
135
|
+
if (mergeFullLeaf === true && !hasChildren && level > 0) {
|
|
136
|
+
resList[lastRowIdx].push([...treeData?.map((it) => ({ ...it, label: it[label], value: it[value] }))]);
|
|
137
|
+
return {
|
|
138
|
+
resList,
|
|
139
|
+
maxCol,
|
|
140
|
+
// 合并之后视为 1 行
|
|
141
|
+
maxRow: 1,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 合并当前层级的叶子节点,非叶子节点忽略
|
|
146
|
+
if (mergeLeaf === true && leafIndexList.length > 0) {
|
|
147
|
+
// treeData 中删除当前层级其他叶子节点
|
|
148
|
+
for (let i = leafIndexList.length - 1; i >= 0; i--) {
|
|
149
|
+
const idx = leafIndexList[i];
|
|
150
|
+
treeData.splice(idx, 1);
|
|
151
|
+
}
|
|
152
|
+
// 所有叶子节点放在尾部
|
|
153
|
+
treeData.push({
|
|
154
|
+
_type_: "leafList",
|
|
155
|
+
leafList: leafItemList?.map((it) => ({ ...it, label: it[label], value: it[value] })),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
treeData?.forEach((it, i) => {
|
|
160
|
+
let item = null;
|
|
161
|
+
if (it._type_ === "leafList") {
|
|
162
|
+
// 合并当前层级的叶子节点
|
|
163
|
+
item = it.leafList;
|
|
164
|
+
} else {
|
|
165
|
+
item = { ...it, value: it[value], label: it[label] };
|
|
166
|
+
}
|
|
167
|
+
const childList = item[children] || [];
|
|
168
|
+
const childLen = childList?.length;
|
|
169
|
+
// 当 mergeFullLeaf !== true && children 长度大于1时,子项下标大于 0 的项需要另起一行,同时补充之前空的列。
|
|
170
|
+
if (i > 0) {
|
|
171
|
+
resList.push(fillArrByPush(preCol, [], { content: false }));
|
|
172
|
+
}
|
|
173
|
+
lastRowIdx = resList.length > 0 ? resList.length - 1 : 0;
|
|
174
|
+
// 解决首次进来第一行数组空的问题
|
|
175
|
+
if (!resList[lastRowIdx]) {
|
|
176
|
+
resList[lastRowIdx] = [];
|
|
177
|
+
}
|
|
178
|
+
resList[lastRowIdx].push(item);
|
|
179
|
+
if (childLen > 0) {
|
|
180
|
+
const res = transformTreeData(childList, {
|
|
181
|
+
...opt,
|
|
182
|
+
resList,
|
|
183
|
+
preCol: preCol + 1,
|
|
184
|
+
level: level + 1,
|
|
185
|
+
});
|
|
186
|
+
if (maxCol < res.maxCol) {
|
|
187
|
+
maxCol = res.maxCol;
|
|
188
|
+
}
|
|
189
|
+
// 当前的最大行数加上子项的行数
|
|
190
|
+
maxRow = maxRow + res.maxRow;
|
|
191
|
+
// 每项的最大行数为子项的最大行数之和
|
|
192
|
+
item._maxRow = res.maxRow;
|
|
193
|
+
} else {
|
|
194
|
+
item._maxRow = 1;
|
|
195
|
+
maxRow += 1;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
resList,
|
|
201
|
+
maxCol,
|
|
202
|
+
maxRow,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 填充数组
|
|
208
|
+
* @param len
|
|
209
|
+
* @param arr
|
|
210
|
+
* @param opt
|
|
211
|
+
* @returns
|
|
212
|
+
*/
|
|
213
|
+
export function fillArrByPush(len, arr = [], opt: any = {}) {
|
|
214
|
+
const { content } = opt || {};
|
|
215
|
+
if (arr.length < len) {
|
|
216
|
+
for (let i = arr.length; i < len; i++) {
|
|
217
|
+
arr.push(content);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return arr;
|
|
221
|
+
}
|
|
@@ -27,13 +27,26 @@
|
|
|
27
27
|
| ossServerUrl | string | 否 | - | 获取 oss 参数的接口,默认 /api/v1/user/oss/getWebOssConfig |
|
|
28
28
|
| ossOpt | Object | 否 | - | oss 上传的配置参数 |
|
|
29
29
|
| fileListConf | Object | 否 | {} | 结果数据配置 |
|
|
30
|
-
| previewConfig | Object | 否 | {} |
|
|
30
|
+
| previewConfig | Object | 否 | {} | 预览场景配置(见下表)。仅影响列表/弹窗预览,不进入 onChange / 表单提交 |
|
|
31
|
+
| previewBaseUrl | string | 否 | - | 仅预览使用的域名/路径前缀(糖衣)。与 `previewConfig.url` 并存时,以 `previewConfig.url` 为准;不进入表单提交 |
|
|
31
32
|
| onCountExceed | Function | 否 | - | 数量超出 maxCount 时的回调 |
|
|
32
33
|
| beforeUploadCheck | Function | 否 | - | 追加的 beforeUpload 检查,返回 Promise.reject() 进行拦截 |
|
|
33
34
|
| templateUrl | string | 否 | - | 模板下载的地址 |
|
|
34
35
|
| templateDownloadText | string | 否 | 模板下载 | 模板下载按钮的文案 |
|
|
35
36
|
| uploadParams | Object | 否 | {} | 文件上传接口入参 |
|
|
36
37
|
|
|
38
|
+
### previewConfig 预览配置
|
|
39
|
+
|
|
40
|
+
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
|
41
|
+
| ---- | ---- | ---- | ------ | ---- |
|
|
42
|
+
| url | string | 否 | - | 预览域名或路径前缀,如 `https://cdn.example.com`。相对路径会拼到此前缀后用于列表缩略图与弹窗预览 |
|
|
43
|
+
| query | object | 否 | - | 追加到预览地址的 query 对象,推荐写法,如 `{ loookType: 1 }` |
|
|
44
|
+
| search | string | 否 | - | `?` 后的原始字符串(可不含 `?`),会先解析再与 `query` 合并;同名键以 `query` 为准 |
|
|
45
|
+
|
|
46
|
+
兼容:把 `query` 写成字符串(如 `"loookType=1"`)时,组件会解析为 `search`,无需立刻改业务 schema。
|
|
47
|
+
|
|
48
|
+
> **边界**:`previewConfig.url` / `previewBaseUrl` 只用于上传列表视图与弹窗预览;`onChange` 与表单提交仍使用后端返回的相对/原始路径,不会提交拼接后的完整地址。
|
|
49
|
+
|
|
37
50
|
### fileListConf 结果数据配置
|
|
38
51
|
|
|
39
52
|
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { getFileURL } from "@hzab/utils/src/file/file";
|
|
2
2
|
import { getFileType } from "@hzab/utils/src/file/fileType";
|
|
3
|
-
import { isBase64Str } from "@hzab/utils/src/base64Str";
|
|
4
3
|
import { getUFileName, getFileName, getFullFileName, getFileExt } from "@hzab/utils/src/file/fileName";
|
|
5
|
-
import { getFileNameObj } from "@hzab/utils/src/upload/uploadUtils";
|
|
4
|
+
import { getFileNameObj, mergePreviewConfig } from "@hzab/utils/src/upload/uploadUtils";
|
|
6
5
|
import { nanoidNumALetters } from "@hzab/utils/src/nanoid";
|
|
7
6
|
import { getFormRenderPCGlobalServeConfig } from "../../../globalConfig";
|
|
8
|
-
import { addQuery } from "@hzab/utils/src/url"
|
|
7
|
+
import { addQuery } from "@hzab/utils/src/url";
|
|
8
|
+
|
|
9
|
+
export { mergePreviewConfig };
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -139,43 +140,6 @@ export const objStrToObj = function (objStr) {
|
|
|
139
140
|
return _objStr;
|
|
140
141
|
};
|
|
141
142
|
|
|
142
|
-
/**
|
|
143
|
-
* 预览 url 添加前缀
|
|
144
|
-
* @param {*} uri
|
|
145
|
-
* @param {*} previewConfig
|
|
146
|
-
* @returns
|
|
147
|
-
*/
|
|
148
|
-
export function mergePreviewConfig(uri, previewConfig) {
|
|
149
|
-
let _uri = uri;
|
|
150
|
-
let basePath = "";
|
|
151
|
-
if (typeof _uri === "object") {
|
|
152
|
-
basePath = _uri.basePath;
|
|
153
|
-
_uri = _uri.previewUrl || _uri.url;
|
|
154
|
-
}
|
|
155
|
-
if (typeof _uri !== "string") {
|
|
156
|
-
return _uri;
|
|
157
|
-
}
|
|
158
|
-
_uri = _uri?.trim();
|
|
159
|
-
// base64 直接返回
|
|
160
|
-
if (isBase64Str(_uri)) {
|
|
161
|
-
return _uri;
|
|
162
|
-
}
|
|
163
|
-
const isUriPattern = /http[s]?:\/\/[^\s]+/.test(_uri);
|
|
164
|
-
const query = previewConfig?.query?.trim() ? "?" + previewConfig?.query?.trim() : "";
|
|
165
|
-
|
|
166
|
-
if (isUriPattern) {
|
|
167
|
-
// 源地址为 http 地址,直接拼接 query 参数
|
|
168
|
-
return `${_uri}${query}`;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (previewConfig) {
|
|
172
|
-
// 拼接前缀
|
|
173
|
-
return `${previewConfig?.url?.trim()}/${_uri}${query}`?.trim();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return _uri;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
143
|
/**
|
|
180
144
|
* 子项数据归一化
|
|
181
145
|
* 字符串:url、base64
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { formatDirStr, mergeDirStr, handleMaxCount, mergePreviewConfig } from "@hzab/utils/upload/uploadUtils";
|
|
2
|
-
import { getFileURL } from "@hzab/utils/file/file";
|
|
1
|
+
import { formatDirStr, mergeDirStr, handleMaxCount, mergePreviewConfig } from "@hzab/utils/src/upload/uploadUtils";
|
|
2
|
+
import { getFileURL } from "@hzab/utils/src/file/file";
|
|
3
3
|
import { isBase64Str } from "@hzab/utils/src/base64Str";
|
|
4
4
|
import {
|
|
5
5
|
checkImageUrl,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
checkAudioUrl,
|
|
8
8
|
isValidMediaType,
|
|
9
9
|
checkUrlSuffix,
|
|
10
|
+
getFileType,
|
|
10
11
|
} from "@hzab/utils/src/file/fileType";
|
|
11
12
|
import { getArr } from "@hzab/utils/src/array";
|
|
12
13
|
|
|
@@ -10,6 +10,7 @@ import { Button, Upload as AUpload, message } from "antd";
|
|
|
10
10
|
import React, { useEffect, useState, useMemo } from "react";
|
|
11
11
|
|
|
12
12
|
import { handleInputFileList, handleOutputFileList } from "./common/handleIOFileList";
|
|
13
|
+
import { mergePreviewConfig } from "@hzab/utils/src/upload/uploadUtils";
|
|
13
14
|
|
|
14
15
|
import PreviewModal, { hasPreviewRender, hasPreviewMedium } from "./components/PreviewModal";
|
|
15
16
|
import { getOssUploadRequest, getOfflineUploadRequest } from "./common/customRequest";
|
|
@@ -57,6 +58,8 @@ export function Uploader({ onChange, ...props }) {
|
|
|
57
58
|
isResRemoveArr = true,
|
|
58
59
|
/** 预览配置参数 */
|
|
59
60
|
previewConfig = {},
|
|
61
|
+
/** 仅预览使用的域名/路径前缀,不参与 onChange / 表单提交;优先级低于 previewConfig.url */
|
|
62
|
+
previewBaseUrl = getFormRenderPCGlobalServeConfig()?.previewBaseUrl || "",
|
|
60
63
|
onCountExceed,
|
|
61
64
|
templateUrl,
|
|
62
65
|
beforeUploadCheck,
|
|
@@ -69,6 +72,26 @@ export function Uploader({ onChange, ...props }) {
|
|
|
69
72
|
const isPrivateStore =
|
|
70
73
|
props.uploadParams?.fileAcl === "private" || props.ossOpt?.signatureParams?.fileAcl === "private";
|
|
71
74
|
|
|
75
|
+
/** 预览前缀配置:previewConfig.url 优先,其次 previewBaseUrl;*/
|
|
76
|
+
const resolvedPreviewConfig = useMemo(() => {
|
|
77
|
+
const url = previewConfig?.url || previewBaseUrl;
|
|
78
|
+
const { query, search, ...rest } = previewConfig || {};
|
|
79
|
+
const isQueryObject = query && typeof query === "object";
|
|
80
|
+
const legacyQueryStr = typeof query === "string" ? query.trim() : "";
|
|
81
|
+
const resolvedSearch = (search || legacyQueryStr || "").replace(/^\?/, "") || undefined;
|
|
82
|
+
const resolvedQuery = isQueryObject ? query : undefined;
|
|
83
|
+
|
|
84
|
+
if (!url && !resolvedQuery && !resolvedSearch) {
|
|
85
|
+
return previewConfig || {};
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
...rest,
|
|
89
|
+
url,
|
|
90
|
+
...(resolvedQuery ? { query: resolvedQuery } : {}),
|
|
91
|
+
...(resolvedSearch ? { search: resolvedSearch } : {}),
|
|
92
|
+
};
|
|
93
|
+
}, [previewConfig.url, previewConfig.query, previewConfig.search, previewBaseUrl]);
|
|
94
|
+
|
|
72
95
|
/**
|
|
73
96
|
* 数据格式配置
|
|
74
97
|
*/
|
|
@@ -135,7 +158,12 @@ export function Uploader({ onChange, ...props }) {
|
|
|
135
158
|
};
|
|
136
159
|
|
|
137
160
|
useEffect(() => {
|
|
138
|
-
const list = handleInputFileList(value, {
|
|
161
|
+
const list = handleInputFileList(value, {
|
|
162
|
+
...fileListConf,
|
|
163
|
+
maxCount,
|
|
164
|
+
previewConfig: resolvedPreviewConfig,
|
|
165
|
+
thumbnailParams,
|
|
166
|
+
});
|
|
139
167
|
|
|
140
168
|
async function handlePreview() {
|
|
141
169
|
await handlePreviewUrls(list, {
|
|
@@ -150,7 +178,27 @@ export function Uploader({ onChange, ...props }) {
|
|
|
150
178
|
} else {
|
|
151
179
|
setFileList(list);
|
|
152
180
|
}
|
|
153
|
-
}, [value]);
|
|
181
|
+
}, [value, resolvedPreviewConfig]);
|
|
182
|
+
|
|
183
|
+
/** 仅用于列表展示:补齐 thumbUrl/previewUrl,不改动 url(提交仍用相对路径) */
|
|
184
|
+
const displayFileList = useMemo(() => {
|
|
185
|
+
if (!resolvedPreviewConfig?.url || !fileList?.length) {
|
|
186
|
+
return fileList;
|
|
187
|
+
}
|
|
188
|
+
return fileList.map((file) => {
|
|
189
|
+
if (!file || typeof file !== "object") {
|
|
190
|
+
return file;
|
|
191
|
+
}
|
|
192
|
+
// 用存储路径 url 拼前缀,避免已是完整 previewUrl 时语义混乱;无 url 时回退对象自身
|
|
193
|
+
const previewUrl = mergePreviewConfig(file.url || file, resolvedPreviewConfig);
|
|
194
|
+
const thumbUrl = file.thumbUrl ? mergePreviewConfig(file.thumbUrl, resolvedPreviewConfig) : previewUrl;
|
|
195
|
+
return {
|
|
196
|
+
...file,
|
|
197
|
+
previewUrl,
|
|
198
|
+
thumbUrl,
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
}, [fileList, resolvedPreviewConfig]);
|
|
154
202
|
|
|
155
203
|
const onRemove = (file) => {
|
|
156
204
|
const files = (fileList || []).filter((v) => v.url !== file.url);
|
|
@@ -206,7 +254,7 @@ export function Uploader({ onChange, ...props }) {
|
|
|
206
254
|
multiple: multiple,
|
|
207
255
|
listType: listType,
|
|
208
256
|
maxCount: maxCount,
|
|
209
|
-
fileList:
|
|
257
|
+
fileList: displayFileList,
|
|
210
258
|
onChange: handleChange,
|
|
211
259
|
disabled,
|
|
212
260
|
readOnly,
|
|
@@ -249,10 +297,14 @@ export function Uploader({ onChange, ...props }) {
|
|
|
249
297
|
setChecking(false);
|
|
250
298
|
},
|
|
251
299
|
customRequest,
|
|
300
|
+
...(props || {}),
|
|
301
|
+
// 保证展示列表与预览逻辑不被 props 覆盖丢失
|
|
302
|
+
fileList: displayFileList,
|
|
252
303
|
onPreview(file) {
|
|
253
304
|
// TODO: 逻辑合并到数据处理中
|
|
254
305
|
const isShowPreview = hasPreviewRender(file) || hasPreviewMedium(props.accept);
|
|
255
|
-
|
|
306
|
+
// 预览出口统一走 mergePreviewConfig;提交仍使用 file.url 相对路径
|
|
307
|
+
const fileUrl = mergePreviewConfig(file.url || file, resolvedPreviewConfig);
|
|
256
308
|
if (isShowPreview) {
|
|
257
309
|
setPreviewFile({
|
|
258
310
|
...file,
|
|
@@ -316,7 +368,7 @@ export function Uploader({ onChange, ...props }) {
|
|
|
316
368
|
accept={props?.accept}
|
|
317
369
|
setPreviewFile={setPreviewFile}
|
|
318
370
|
modalConf={props.modalConf}
|
|
319
|
-
previewConfig={
|
|
371
|
+
previewConfig={resolvedPreviewConfig}
|
|
320
372
|
/>
|
|
321
373
|
</>
|
|
322
374
|
);
|