@hzab/list-render 1.10.2 → 1.10.5
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/CHANGELOG.md +8 -0
- package/README.md +77 -59
- package/package.json +1 -1
- package/src/common/constant.ts +4 -0
- package/src/common/handleQuerySchema.ts +576 -302
- package/src/components/Formily/FormilyEditTable.tsx +33 -7
- package/src/components/Formily/FormilyField.tsx +68 -68
- package/src/list-render.jsx +74 -16
- package/src/query-render/index.jsx +95 -92
|
@@ -1,302 +1,576 @@
|
|
|
1
|
-
import _ from "lodash";
|
|
2
|
-
import { getFieldMap } from "./utils";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
};
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
"x-
|
|
149
|
-
"x-
|
|
150
|
-
"
|
|
151
|
-
"
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
"
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
conf["x-component-props"].
|
|
196
|
-
}
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
*
|
|
266
|
-
* @
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
*
|
|
292
|
-
* @param
|
|
293
|
-
* @param
|
|
294
|
-
* @returns
|
|
295
|
-
*/
|
|
296
|
-
export const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
import { getFieldMap } from "./utils";
|
|
3
|
+
import {
|
|
4
|
+
isRunStr,
|
|
5
|
+
handleSchemaStrVal,
|
|
6
|
+
isScopeKey as formIsScopeKey,
|
|
7
|
+
getScopeKeyVal,
|
|
8
|
+
} from "@hzab/form-render/src/common/schema-handler";
|
|
9
|
+
import { URL_PARAM_NAME } from "./constant";
|
|
10
|
+
|
|
11
|
+
// ===================== 类型定义 =====================
|
|
12
|
+
|
|
13
|
+
type AntdDateComponent = "DatePicker" | "DatePicker.RangePicker";
|
|
14
|
+
|
|
15
|
+
interface XComponentProps {
|
|
16
|
+
isSplitTimes?: boolean;
|
|
17
|
+
startKey?: string;
|
|
18
|
+
endKey?: string;
|
|
19
|
+
[k: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface FormilyField {
|
|
23
|
+
type?: string;
|
|
24
|
+
title?: string;
|
|
25
|
+
"x-decorator"?: string;
|
|
26
|
+
"x-component"?: string;
|
|
27
|
+
"x-validator"?: unknown[];
|
|
28
|
+
"x-component-props"?: XComponentProps;
|
|
29
|
+
"x-decorator-props"?: Record<string, unknown>;
|
|
30
|
+
"x-designable-id"?: string;
|
|
31
|
+
"x-index"?: number;
|
|
32
|
+
[k: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface FormilyV1SchemaNode {
|
|
36
|
+
type?: string;
|
|
37
|
+
properties?: Record<string, FormilyField>;
|
|
38
|
+
[k: string]: unknown;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface FormilyV1Schema {
|
|
42
|
+
form?: Record<string, unknown>;
|
|
43
|
+
schema: FormilyV1SchemaNode;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type URLQueryObject = Record<string, any> | null | undefined;
|
|
47
|
+
|
|
48
|
+
interface ExtractOptions {
|
|
49
|
+
/** 分拆开关字段名,位于 x-component-props;默认 'isSplitTimes' */
|
|
50
|
+
splitFlag?: string;
|
|
51
|
+
/** 分拆开关的期望值;默认 true */
|
|
52
|
+
splitFlagExpectedValue?: unknown;
|
|
53
|
+
/** 默认开始/结束键名(当 schema 未显式配置时),默认 'startTime' / 'endTime' */
|
|
54
|
+
defaultStartKey?: string;
|
|
55
|
+
defaultEndKey?: string;
|
|
56
|
+
/** 认为是 antd 日期组件的名单,默认 DatePicker / DatePicker.RangePicker */
|
|
57
|
+
antdDateComponents?: readonly AntdDateComponent[];
|
|
58
|
+
/**
|
|
59
|
+
* 命中并成功赋值后,是否从返回对象中删除对应的 startKey / endKey
|
|
60
|
+
* 默认 true(删除)
|
|
61
|
+
*/
|
|
62
|
+
removeMatchedKeys?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const handleQuerySchema = (opt) => {
|
|
66
|
+
const { schema, search, filters, onSearch, replaceComList, schemaScope } = opt || {};
|
|
67
|
+
const queryProperties = {};
|
|
68
|
+
let index = 0;
|
|
69
|
+
if (search) {
|
|
70
|
+
// @ts-ignore
|
|
71
|
+
queryProperties.search = handleSearch({ search, onSearch, index });
|
|
72
|
+
index += 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const fieldMap = getFieldMap(_.cloneDeep(schema));
|
|
76
|
+
filters?.forEach((key) => {
|
|
77
|
+
const item = fieldMap[key];
|
|
78
|
+
if (item) {
|
|
79
|
+
const xComponent = item["x-component"];
|
|
80
|
+
const replaceItem = replaceComList?.find((it) => it.name === key || (!it.name && it.component === xComponent));
|
|
81
|
+
|
|
82
|
+
const itemConf = {
|
|
83
|
+
...item,
|
|
84
|
+
...replaceItem,
|
|
85
|
+
required: false,
|
|
86
|
+
"x-index": index,
|
|
87
|
+
"x-display": "visible",
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// 处理 props 不存在、从 scope 中获取的情况
|
|
91
|
+
const comProps = itemConf["x-component-props"];
|
|
92
|
+
if (!comProps) {
|
|
93
|
+
itemConf["x-component-props"] = {};
|
|
94
|
+
} else if (isScopeKey(comProps)) {
|
|
95
|
+
itemConf["x-component-props"] = getValByScope(comProps, schemaScope) || {};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (typeof itemConf["x-component-props"].allowClear !== "boolean") {
|
|
99
|
+
itemConf["x-component-props"].allowClear = true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
handleInput(itemConf, opt);
|
|
103
|
+
handleSelect(itemConf, opt);
|
|
104
|
+
rmUnusedParams(itemConf, opt);
|
|
105
|
+
|
|
106
|
+
// 处理快捷提交
|
|
107
|
+
handleShortcutSubmit(itemConf, opt);
|
|
108
|
+
|
|
109
|
+
queryProperties[key] = itemConf;
|
|
110
|
+
index += 1;
|
|
111
|
+
} else if (key === "$timerange") {
|
|
112
|
+
queryProperties[key] = handleTimerange({ index, key });
|
|
113
|
+
index += 1;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
form: {
|
|
119
|
+
layout: "inline",
|
|
120
|
+
},
|
|
121
|
+
schema: {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties: queryProperties,
|
|
124
|
+
"x-designable-id": "querySchema",
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 处理 search
|
|
131
|
+
* @param param0
|
|
132
|
+
* @returns
|
|
133
|
+
*/
|
|
134
|
+
export const handleSearch = (opt) => {
|
|
135
|
+
const { search, onSearch, index } = opt || {};
|
|
136
|
+
const conf = {
|
|
137
|
+
type: "string",
|
|
138
|
+
title: "",
|
|
139
|
+
"x-decorator": "FormItem",
|
|
140
|
+
"x-component": "Input",
|
|
141
|
+
"x-validator": [],
|
|
142
|
+
"x-component-props": {
|
|
143
|
+
allowClear: true,
|
|
144
|
+
placeholder: search,
|
|
145
|
+
onPressEnter: onSearch,
|
|
146
|
+
},
|
|
147
|
+
"x-decorator-props": {},
|
|
148
|
+
"x-designable-id": "searchInput",
|
|
149
|
+
"x-index": index,
|
|
150
|
+
name: "search",
|
|
151
|
+
description: "",
|
|
152
|
+
};
|
|
153
|
+
// 清除事件
|
|
154
|
+
return handleInputClear(conf, opt);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 把选择相关组件切换成下拉菜单
|
|
159
|
+
*/
|
|
160
|
+
export const handleSelect = (conf, opt) => {
|
|
161
|
+
const { selectList = [], isSelectSearch = true } = opt || {};
|
|
162
|
+
const _selectList = ["Select", "Radio.Group", "Checkbox.Group", ...selectList];
|
|
163
|
+
const xComponent = conf["x-component"];
|
|
164
|
+
if (_selectList.includes(xComponent)) {
|
|
165
|
+
conf["x-component"] = "Select";
|
|
166
|
+
conf["x-component-props"].showSearch = isSelectSearch;
|
|
167
|
+
}
|
|
168
|
+
return conf;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 把输入相关组件切换成 Input
|
|
173
|
+
*/
|
|
174
|
+
export const handleInput = (conf, opt) => {
|
|
175
|
+
const { inputList = [] } = opt || {};
|
|
176
|
+
const _inputList = ["NumberPicker", ...inputList];
|
|
177
|
+
const xComponent = conf["x-component"];
|
|
178
|
+
if (_inputList.includes(xComponent)) {
|
|
179
|
+
conf["x-component"] = "Input";
|
|
180
|
+
}
|
|
181
|
+
return conf;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 去除无关参数
|
|
186
|
+
* @param conf
|
|
187
|
+
* @param opt
|
|
188
|
+
* @returns
|
|
189
|
+
*/
|
|
190
|
+
export const rmUnusedParams = (conf, opt) => {
|
|
191
|
+
const { isRmDefault, isRmValidator } = opt || {};
|
|
192
|
+
// 去除默认值
|
|
193
|
+
if (isRmDefault) {
|
|
194
|
+
conf.default = undefined;
|
|
195
|
+
conf["x-component-props"].defaultValue = undefined;
|
|
196
|
+
}
|
|
197
|
+
// 去除校验
|
|
198
|
+
if (isRmValidator) {
|
|
199
|
+
conf["x-validator"] = [];
|
|
200
|
+
}
|
|
201
|
+
return conf;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export const handleTimerange = ({ key, index }) => {
|
|
205
|
+
return {
|
|
206
|
+
type: "string[]",
|
|
207
|
+
title: "时间范围",
|
|
208
|
+
"x-decorator": "FormItem",
|
|
209
|
+
"x-component": "DatePicker.RangePicker",
|
|
210
|
+
"x-validator": [],
|
|
211
|
+
"x-component-props": {
|
|
212
|
+
picker: "date",
|
|
213
|
+
showTime: true,
|
|
214
|
+
allowClear: true,
|
|
215
|
+
},
|
|
216
|
+
"x-decorator-props": {},
|
|
217
|
+
name: key,
|
|
218
|
+
"x-designable-id": key,
|
|
219
|
+
"x-index": index,
|
|
220
|
+
};
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 处理快捷提交逻辑
|
|
225
|
+
* @param conf
|
|
226
|
+
* @param opt
|
|
227
|
+
*/
|
|
228
|
+
export const handleShortcutSubmit = (conf, opt) => {
|
|
229
|
+
const { isEnterSubmit = true, isChangeSubmit = true, onSearch, customSubmitList } = opt || {};
|
|
230
|
+
const xComponent = conf["x-component"];
|
|
231
|
+
const xComponentProps = conf["x-component-props"];
|
|
232
|
+
const onChangeList = [
|
|
233
|
+
"Select",
|
|
234
|
+
"Cascader",
|
|
235
|
+
"TreeSelect",
|
|
236
|
+
"DatePicker",
|
|
237
|
+
"DatePicker.RangePicker",
|
|
238
|
+
"TimePicker",
|
|
239
|
+
"TimePicker.RangePicker",
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
// 输入框
|
|
243
|
+
if (isEnterSubmit && xComponent === "Input") {
|
|
244
|
+
const onPressEnter = getSchemaVal(xComponentProps?.onPressEnter, opt);
|
|
245
|
+
conf["x-component-props"].onPressEnter = function (e, ...args) {
|
|
246
|
+
e?.preventDefault && e?.preventDefault();
|
|
247
|
+
// 阻止默认事件,解决单个 input 提交问题
|
|
248
|
+
const res = onPressEnter && onPressEnter(e, ...args);
|
|
249
|
+
onSearch && onSearch();
|
|
250
|
+
return res;
|
|
251
|
+
};
|
|
252
|
+
// 清除事件
|
|
253
|
+
handleInputClear(conf, opt);
|
|
254
|
+
} else if (isChangeSubmit && onChangeList.includes(xComponent)) {
|
|
255
|
+
conf["x-component-props"].onChange = handleHoc(xComponentProps?.onChange, onSearch, opt);
|
|
256
|
+
}
|
|
257
|
+
// 处理自定义组件快捷提交逻辑
|
|
258
|
+
const item = customSubmitList?.find((it) => it.component === xComponent);
|
|
259
|
+
if (item) {
|
|
260
|
+
conf["x-component-props"][item.event] = handleHoc(xComponentProps[item.event], onSearch, opt);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* 事件拦截,处理调用额外函数
|
|
266
|
+
* @param source
|
|
267
|
+
* @param cb
|
|
268
|
+
* @returns
|
|
269
|
+
*/
|
|
270
|
+
export const handleHoc = (source, cb, opt) => {
|
|
271
|
+
return async function (...args) {
|
|
272
|
+
const { schemaScope } = opt;
|
|
273
|
+
let _source = getSchemaVal(source, schemaScope);
|
|
274
|
+
|
|
275
|
+
let res = null;
|
|
276
|
+
if (isRunStr(source)) {
|
|
277
|
+
_source = handleSchemaStrVal(source, schemaScope);
|
|
278
|
+
}
|
|
279
|
+
if (formIsScopeKey(source, schemaScope)) {
|
|
280
|
+
_source = getScopeKeyVal(source, schemaScope);
|
|
281
|
+
}
|
|
282
|
+
if (_source) {
|
|
283
|
+
res = await _source(...args);
|
|
284
|
+
}
|
|
285
|
+
cb && cb(...args);
|
|
286
|
+
return res;
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* input 清除事件
|
|
292
|
+
* @param conf
|
|
293
|
+
* @param opt
|
|
294
|
+
* @returns
|
|
295
|
+
*/
|
|
296
|
+
export const handleInputClear = (conf, opt) => {
|
|
297
|
+
const { onSearch, schemaScope } = opt || {};
|
|
298
|
+
const onChange = getSchemaVal(conf["x-component-props"]?.onChange, schemaScope);
|
|
299
|
+
conf["x-component-props"].onChange = function (e, ...args) {
|
|
300
|
+
const res = onChange && onChange(e, ...args);
|
|
301
|
+
if (e.type === "click" && e.target?.value === "") {
|
|
302
|
+
onSearch();
|
|
303
|
+
}
|
|
304
|
+
return res;
|
|
305
|
+
};
|
|
306
|
+
return conf;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* 是否是 scope key
|
|
311
|
+
* @param strKey
|
|
312
|
+
* @returns
|
|
313
|
+
*/
|
|
314
|
+
export const isScopeKey = (strKey) => {
|
|
315
|
+
if (typeof strKey !== "string") {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
const key = strKey.trim();
|
|
319
|
+
return key.startsWith("{{") && key.endsWith("}}");
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* 获取 scope key
|
|
324
|
+
* @param strKey
|
|
325
|
+
* @returns
|
|
326
|
+
*/
|
|
327
|
+
export const getScopeKey = (strKey) => {
|
|
328
|
+
if (!isScopeKey(strKey)) {
|
|
329
|
+
return "";
|
|
330
|
+
}
|
|
331
|
+
const key = strKey.trim();
|
|
332
|
+
return key.replace(/^\{\{/, "").replace(/\}\}$/, "");
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* 通过字符串获取 schemaScope 中的数据
|
|
337
|
+
* @param strKey
|
|
338
|
+
* @param schemaScope
|
|
339
|
+
* @returns
|
|
340
|
+
*/
|
|
341
|
+
export const getSchemaVal = (strKey, schemaScope = {}) => {
|
|
342
|
+
const val = getValByScope(strKey, schemaScope);
|
|
343
|
+
if (!_.isNil(val) && val !== "") {
|
|
344
|
+
return val;
|
|
345
|
+
}
|
|
346
|
+
return strKey;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* 通过字符串获取 schemaScope 中的数据
|
|
351
|
+
* @param strKey
|
|
352
|
+
* @param schemaScope
|
|
353
|
+
* @returns
|
|
354
|
+
*/
|
|
355
|
+
export const getValByScope = (strKey, schemaScope = {}) => {
|
|
356
|
+
if (isScopeKey(strKey)) {
|
|
357
|
+
return schemaScope && schemaScope[getScopeKey(strKey)];
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
function removeEmptyValues(obj) {
|
|
362
|
+
return _.pickBy(obj, (value) => {
|
|
363
|
+
// 排除 undefined 和 null
|
|
364
|
+
if (value == null) return false;
|
|
365
|
+
|
|
366
|
+
// 排除空字符串
|
|
367
|
+
if (_.isString(value) && value.trim() === "") return false;
|
|
368
|
+
|
|
369
|
+
// 排除空数组
|
|
370
|
+
if (_.isArray(value) && value.length === 0) return false;
|
|
371
|
+
|
|
372
|
+
// 保留其他值(包括 0、false、非空对象等)
|
|
373
|
+
return true;
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* 将对象序列化后写入 URL query 中
|
|
379
|
+
* @param obj 要存储的对象(保持原始类型)
|
|
380
|
+
* @param paramName query 参数名
|
|
381
|
+
*/
|
|
382
|
+
export function setURLObjectQueryParam<T extends object>(obj: T, paramName = URL_PARAM_NAME): void {
|
|
383
|
+
try {
|
|
384
|
+
if (typeof window === "undefined") return;
|
|
385
|
+
|
|
386
|
+
const href = window.location.href;
|
|
387
|
+
const [origin, hash = ""] = href.split("#");
|
|
388
|
+
const [path, queryString = ""] = hash.split("?");
|
|
389
|
+
const searchParams = new URLSearchParams(queryString);
|
|
390
|
+
// 指定的空值不设置
|
|
391
|
+
const copyObj = _.cloneDeep(removeEmptyValues(obj));
|
|
392
|
+
|
|
393
|
+
const encoded = encodeURIComponent(JSON.stringify(copyObj));
|
|
394
|
+
|
|
395
|
+
searchParams.set(paramName, encoded);
|
|
396
|
+
|
|
397
|
+
const newHash = `${path}?${searchParams.toString()}`;
|
|
398
|
+
|
|
399
|
+
window.history.replaceState({}, "", `${origin}#${newHash}`);
|
|
400
|
+
} catch (e) {
|
|
401
|
+
console.error(`[setURLObjectQueryParam] 设置失败:`, e);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* 从哈希路由的查询参数中,删除某个“对象型参数”下的指定属性(不刷新页面)
|
|
407
|
+
* @param parentKey 父级对象参数名
|
|
408
|
+
* @param childKey 要删除的对象属性名
|
|
409
|
+
*/
|
|
410
|
+
export function removeURLObjectQueryParam(parentKey: string, childKey: string, reset?: boolean): void {
|
|
411
|
+
if (typeof window === "undefined") return;
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
const href = window.location.href;
|
|
415
|
+
const [origin, hash = ""] = href.split("#");
|
|
416
|
+
const [hashPath, queryString = ""] = hash.split("?");
|
|
417
|
+
const searchParams = new URLSearchParams(queryString);
|
|
418
|
+
|
|
419
|
+
const raw = searchParams.get(parentKey);
|
|
420
|
+
if (raw == null) return;
|
|
421
|
+
|
|
422
|
+
// URLSearchParams.get 已自动做了百分号解码,但是可能会遇到编码两次的情况,因此这里再尝试解码一次
|
|
423
|
+
let obj: unknown;
|
|
424
|
+
try {
|
|
425
|
+
obj = JSON.parse(decodeURIComponent(raw));
|
|
426
|
+
} catch {
|
|
427
|
+
// 若不是合法 JSON,就不处理该参数
|
|
428
|
+
console.error(`[removeURLObjectQueryParam] 序列化URL参数错误`);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// 如果是重置,直接清空整个parentKey
|
|
433
|
+
if (reset && parentKey) searchParams.delete(parentKey);
|
|
434
|
+
else {
|
|
435
|
+
if (obj && typeof obj === "object" && !Array.isArray(obj)) {
|
|
436
|
+
const record = obj as Record<string, unknown>;
|
|
437
|
+
// 删除空值key
|
|
438
|
+
if (childKey || record[childKey] === "" || record[childKey] === undefined || record[childKey] === null)
|
|
439
|
+
delete record[childKey];
|
|
440
|
+
|
|
441
|
+
// 如果对象被删空,则移除整个 parentKey;否则写回
|
|
442
|
+
if (Object.keys(record).length === 0) {
|
|
443
|
+
searchParams.delete(parentKey);
|
|
444
|
+
} else {
|
|
445
|
+
searchParams.set(parentKey, encodeURIComponent(JSON.stringify(record)));
|
|
446
|
+
}
|
|
447
|
+
} else {
|
|
448
|
+
// parentKey 的值不是对象,安全起见不处理
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// 重建哈希部分
|
|
454
|
+
let newHash = hashPath;
|
|
455
|
+
const qs = searchParams.toString();
|
|
456
|
+
if (qs) newHash += `?${qs}`;
|
|
457
|
+
|
|
458
|
+
const newUrl = `${origin}${newHash ? `#${newHash}` : ""}`;
|
|
459
|
+
window.history.replaceState({}, "", newUrl);
|
|
460
|
+
} catch (e) {
|
|
461
|
+
console.error(`[removeURLObjectQueryParam] 删除失败:`, e);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* 从 URL 中解析序列化的对象
|
|
467
|
+
* @param paramName query 参数名
|
|
468
|
+
* @returns 还原的对象(如果不存在返回 null)
|
|
469
|
+
*/
|
|
470
|
+
export function getURLObjectQueryParam<T = any>(paramName = URL_PARAM_NAME): T | Record<string, any> {
|
|
471
|
+
// 返回空对象是为了防止在合并对象时报错导致页面空白
|
|
472
|
+
if (typeof window === "undefined") return {};
|
|
473
|
+
|
|
474
|
+
const href = window.location.href;
|
|
475
|
+
const [, hash = ""] = href.split("#");
|
|
476
|
+
const [, queryString = ""] = hash.split("?");
|
|
477
|
+
const searchParams = new URLSearchParams(queryString);
|
|
478
|
+
const encoded = searchParams.get(paramName);
|
|
479
|
+
|
|
480
|
+
if (!encoded) return null;
|
|
481
|
+
|
|
482
|
+
try {
|
|
483
|
+
return JSON.parse(decodeURIComponent(encoded)) as T;
|
|
484
|
+
} catch (e) {
|
|
485
|
+
console.error(`[getURLObjectQueryParam] 解析失败:`, e);
|
|
486
|
+
return {};
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// ===================== 辅助函数 =====================
|
|
491
|
+
|
|
492
|
+
const pickFormilyProperties = (schema: FormilyV1Schema): Record<string, FormilyField> =>
|
|
493
|
+
schema?.schema?.properties ?? {};
|
|
494
|
+
|
|
495
|
+
const isAntdDateComponent = (comp: unknown, whitelist: readonly string[]) =>
|
|
496
|
+
typeof comp === "string" && whitelist.includes(comp);
|
|
497
|
+
|
|
498
|
+
const filterKeys = (props: Record<string, FormilyField>, filters: readonly string[]) => {
|
|
499
|
+
const filterSet = new Set(filters);
|
|
500
|
+
return Object.entries(props).filter(([k]) => filterSet.has(k));
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
const matchSplitFlag = (field: FormilyField, splitFlag: string, expected: unknown) =>
|
|
504
|
+
field?.["x-component-props"]?.[splitFlag] === expected;
|
|
505
|
+
|
|
506
|
+
/** 两个值都缺失则返回 undefined;否则返回二元组 */
|
|
507
|
+
const getRangeTupleOrUndefined = (urlObj: URLQueryObject, startKey: string, endKey: string): [any, any] | undefined => {
|
|
508
|
+
const obj = (urlObj ?? {}) as Record<string, any>;
|
|
509
|
+
const hasStart = Object.prototype.hasOwnProperty.call(obj, startKey);
|
|
510
|
+
const hasEnd = Object.prototype.hasOwnProperty.call(obj, endKey);
|
|
511
|
+
if (!hasStart && !hasEnd) return undefined;
|
|
512
|
+
return [obj[startKey], obj[endKey]];
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// ===================== 主函数 =====================
|
|
516
|
+
/**
|
|
517
|
+
* 根据formilySchema配置逆解析表单key并将url query赋值
|
|
518
|
+
* 三层筛选:根据filters + splitFlag + antdDateComponents 提取formilySchema配置项然后通过配置的key赋值并返回
|
|
519
|
+
* */
|
|
520
|
+
export function extractSplitDateRanges(
|
|
521
|
+
filters: string[],
|
|
522
|
+
formilySchema: FormilyV1Schema,
|
|
523
|
+
urlObj: URLQueryObject,
|
|
524
|
+
options: ExtractOptions = {},
|
|
525
|
+
): Record<string, any> {
|
|
526
|
+
const {
|
|
527
|
+
splitFlag = "isSplitTimes",
|
|
528
|
+
splitFlagExpectedValue = true,
|
|
529
|
+
defaultStartKey = "startTime",
|
|
530
|
+
defaultEndKey = "endTime",
|
|
531
|
+
antdDateComponents = ["DatePicker", "DatePicker.RangePicker"],
|
|
532
|
+
removeMatchedKeys = true,
|
|
533
|
+
} = options;
|
|
534
|
+
|
|
535
|
+
const properties = pickFormilyProperties(formilySchema);
|
|
536
|
+
const candidates = filterKeys(properties, filters);
|
|
537
|
+
|
|
538
|
+
// 保持不变性
|
|
539
|
+
const base = _.cloneDeep(urlObj) as Record<string, any>;
|
|
540
|
+
|
|
541
|
+
// 统一延迟删除,避免顺序影响
|
|
542
|
+
const keysToDelete = new Set<string>();
|
|
543
|
+
|
|
544
|
+
for (const [fieldKey, field] of candidates) {
|
|
545
|
+
// 未命中则直接跳过本次循环
|
|
546
|
+
if (!isAntdDateComponent(field?.["x-component"], antdDateComponents)) continue;
|
|
547
|
+
if (!matchSplitFlag(field, splitFlag, splitFlagExpectedValue)) continue;
|
|
548
|
+
|
|
549
|
+
const props = field?.["x-component-props"] ?? {};
|
|
550
|
+
const startKey = (props?.startKey as string) || defaultStartKey;
|
|
551
|
+
const endKey = (props?.endKey as string) || defaultEndKey;
|
|
552
|
+
|
|
553
|
+
const tuple = getRangeTupleOrUndefined(base, startKey, endKey);
|
|
554
|
+
if (!tuple) continue; // 两个值都没有则不赋值、不删除
|
|
555
|
+
|
|
556
|
+
// 命中,给 fieldKey 赋值
|
|
557
|
+
base[fieldKey] = tuple;
|
|
558
|
+
|
|
559
|
+
// 按需记录要删除的键(只删除“命中的”)
|
|
560
|
+
if (removeMatchedKeys) {
|
|
561
|
+
if (Object.prototype.hasOwnProperty.call(base, startKey)) keysToDelete.add(startKey);
|
|
562
|
+
if (Object.prototype.hasOwnProperty.call(base, endKey)) keysToDelete.add(endKey);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// 统一执行删除,保证无顺序副作用
|
|
567
|
+
if (removeMatchedKeys) {
|
|
568
|
+
for (const k of keysToDelete) {
|
|
569
|
+
delete base[k];
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return base;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
export default handleQuerySchema;
|