@hzab/list-render 1.10.2 → 1.10.4

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.
@@ -1,302 +1,576 @@
1
- import _ from "lodash";
2
- import { getFieldMap } from "./utils";
3
- import { isRunStr, handleSchemaStrVal, isScopeKey as formIsScopeKey, getScopeKeyVal } from '@hzab/form-render/src/common/schema-handler';
4
-
5
- export const handleQuerySchema = (opt) => {
6
- const { schema, search, filters, onSearch, replaceComList, schemaScope } = opt || {};
7
- const queryProperties = {};
8
- let index = 0;
9
- if (search) {
10
- // @ts-ignore
11
- queryProperties.search = handleSearch({ search, onSearch, index });
12
- index += 1;
13
- }
14
-
15
- const fieldMap = getFieldMap(_.cloneDeep(schema));
16
- filters?.forEach((key) => {
17
- const item = fieldMap[key];
18
- if (item) {
19
- const xComponent = item["x-component"];
20
- const replaceItem = replaceComList?.find((it) => it.name === key || (!it.name && it.component === xComponent));
21
-
22
- const itemConf = {
23
- ...item,
24
- ...replaceItem,
25
- required: false,
26
- "x-index": index,
27
- "x-display": "visible",
28
- };
29
-
30
- // 处理 props 不存在、从 scope 中获取的情况
31
- const comProps = itemConf["x-component-props"];
32
- if (!comProps) {
33
- itemConf["x-component-props"] = {};
34
- } else if (isScopeKey(comProps)) {
35
- itemConf["x-component-props"] = getValByScope(comProps, schemaScope) || {};
36
- }
37
-
38
- if (typeof itemConf["x-component-props"].allowClear !== "boolean") {
39
- itemConf["x-component-props"].allowClear = true;
40
- }
41
-
42
- handleInput(itemConf, opt);
43
- handleSelect(itemConf, opt);
44
- rmUnusedParams(itemConf, opt);
45
-
46
- // 处理快捷提交
47
- handleShortcutSubmit(itemConf, opt);
48
-
49
- queryProperties[key] = itemConf;
50
- index += 1;
51
- } else if (key === "$timerange") {
52
- queryProperties[key] = handleTimerange({ index, key });
53
- index += 1;
54
- }
55
- });
56
-
57
- return {
58
- form: {
59
- layout: "inline",
60
- },
61
- schema: {
62
- type: "object",
63
- properties: queryProperties,
64
- "x-designable-id": "querySchema",
65
- },
66
- };
67
- };
68
-
69
- /**
70
- * 处理 search
71
- * @param param0
72
- * @returns
73
- */
74
- export const handleSearch = (opt) => {
75
- const { search, onSearch, index } = opt || {};
76
- const conf = {
77
- type: "string",
78
- title: "",
79
- "x-decorator": "FormItem",
80
- "x-component": "Input",
81
- "x-validator": [],
82
- "x-component-props": {
83
- allowClear: true,
84
- placeholder: search,
85
- onPressEnter: onSearch,
86
- },
87
- "x-decorator-props": {},
88
- "x-designable-id": "searchInput",
89
- "x-index": index,
90
- name: "search",
91
- description: "",
92
- };
93
- // 清除事件
94
- return handleInputClear(conf, opt);
95
- };
96
-
97
- /**
98
- * 把选择相关组件切换成下拉菜单
99
- */
100
- export const handleSelect = (conf, opt) => {
101
- const { selectList = [], isSelectSearch = true } = opt || {};
102
- const _selectList = ["Select", "Radio.Group", "Checkbox.Group", ...selectList];
103
- const xComponent = conf["x-component"];
104
- if (_selectList.includes(xComponent)) {
105
- conf["x-component"] = "Select";
106
- conf["x-component-props"].showSearch = isSelectSearch;
107
- }
108
- return conf;
109
- };
110
-
111
- /**
112
- * 把输入相关组件切换成 Input
113
- */
114
- export const handleInput = (conf, opt) => {
115
- const { inputList = [] } = opt || {};
116
- const _inputList = ["NumberPicker", ...inputList];
117
- const xComponent = conf["x-component"];
118
- if (_inputList.includes(xComponent)) {
119
- conf["x-component"] = "Input";
120
- }
121
- return conf;
122
- };
123
-
124
- /**
125
- * 去除无关参数
126
- * @param conf
127
- * @param opt
128
- * @returns
129
- */
130
- export const rmUnusedParams = (conf, opt) => {
131
- const { isRmDefault, isRmValidator } = opt || {};
132
- // 去除默认值
133
- if (isRmDefault) {
134
- conf.default = undefined;
135
- conf["x-component-props"].defaultValue = undefined;
136
- }
137
- // 去除校验
138
- if (isRmValidator) {
139
- conf["x-validator"] = [];
140
- }
141
- return conf;
142
- };
143
-
144
- export const handleTimerange = ({ key, index }) => {
145
- return {
146
- type: "string[]",
147
- title: "时间范围",
148
- "x-decorator": "FormItem",
149
- "x-component": "DatePicker.RangePicker",
150
- "x-validator": [],
151
- "x-component-props": {
152
- picker: "date",
153
- showTime: true,
154
- allowClear: true,
155
- },
156
- "x-decorator-props": {},
157
- name: key,
158
- "x-designable-id": key,
159
- "x-index": index,
160
- };
161
- };
162
-
163
- /**
164
- * 处理快捷提交逻辑
165
- * @param conf
166
- * @param opt
167
- */
168
- export const handleShortcutSubmit = (conf, opt) => {
169
- const { isEnterSubmit = true, isChangeSubmit = true, onSearch, customSubmitList } = opt || {};
170
- const xComponent = conf["x-component"];
171
- const xComponentProps = conf["x-component-props"];
172
- const onChangeList = [
173
- "Select",
174
- "Cascader",
175
- "TreeSelect",
176
- "DatePicker",
177
- "DatePicker.RangePicker",
178
- "TimePicker",
179
- "TimePicker.RangePicker",
180
- ];
181
-
182
- // 输入框
183
- if (isEnterSubmit && xComponent === "Input") {
184
- const onPressEnter = getSchemaVal(xComponentProps?.onPressEnter, opt);
185
- conf["x-component-props"].onPressEnter = function (e, ...args) {
186
- e?.preventDefault && e?.preventDefault();
187
- // 阻止默认事件,解决单个 input 提交问题
188
- const res = onPressEnter && onPressEnter(e, ...args);
189
- onSearch && onSearch();
190
- return res;
191
- };
192
- // 清除事件
193
- handleInputClear(conf, opt);
194
- } else if (isChangeSubmit && onChangeList.includes(xComponent)) {
195
- conf["x-component-props"].onChange = handleHoc(xComponentProps?.onChange, onSearch, opt);
196
- }
197
- // 处理自定义组件快捷提交逻辑
198
- const item = customSubmitList?.find((it) => it.component === xComponent);
199
- if (item) {
200
- conf["x-component-props"][item.event] = handleHoc(xComponentProps[item.event], onSearch, opt);
201
- }
202
- };
203
-
204
- /**
205
- * 事件拦截,处理调用额外函数
206
- * @param source
207
- * @param cb
208
- * @returns
209
- */
210
- export const handleHoc = (source, cb, opt) => {
211
- return async function (...args) {
212
- const { schemaScope } = opt;
213
- let _source = getSchemaVal(source, schemaScope);
214
-
215
- let res = null;
216
- if (isRunStr(source)) {
217
-
218
- _source = handleSchemaStrVal(source, schemaScope);
219
- }
220
- if (formIsScopeKey(source, schemaScope)) {
221
- _source = getScopeKeyVal(source, schemaScope);
222
- }
223
- if (_source) {
224
- res = await _source(...args);
225
- }
226
- cb && cb(...args);
227
- return res;
228
- };
229
- };
230
-
231
- /**
232
- * input 清除事件
233
- * @param conf
234
- * @param opt
235
- * @returns
236
- */
237
- export const handleInputClear = (conf, opt) => {
238
- const { onSearch, schemaScope } = opt || {};
239
- const onChange = getSchemaVal(conf["x-component-props"]?.onChange, schemaScope);
240
- conf["x-component-props"].onChange = function (e, ...args) {
241
- const res = onChange && onChange(e, ...args);
242
- if (e.type === "click" && e.target?.value === "") {
243
- onSearch();
244
- }
245
- return res;
246
- };
247
- return conf;
248
- };
249
-
250
- /**
251
- * 是否是 scope key
252
- * @param strKey
253
- * @returns
254
- */
255
- export const isScopeKey = (strKey) => {
256
- if (typeof strKey !== "string") {
257
- return false;
258
- }
259
- const key = strKey.trim();
260
- return key.startsWith("{{") && key.endsWith("}}");
261
- };
262
-
263
- /**
264
- * 获取 scope key
265
- * @param strKey
266
- * @returns
267
- */
268
- export const getScopeKey = (strKey) => {
269
- if (!isScopeKey(strKey)) {
270
- return "";
271
- }
272
- const key = strKey.trim();
273
- return key.replace(/^\{\{/, "").replace(/\}\}$/, "");
274
- };
275
-
276
- /**
277
- * 通过字符串获取 schemaScope 中的数据
278
- * @param strKey
279
- * @param schemaScope
280
- * @returns
281
- */
282
- export const getSchemaVal = (strKey, schemaScope = {}) => {
283
- const val = getValByScope(strKey, schemaScope);
284
- if (!_.isNil(val) && val !== "") {
285
- return val;
286
- }
287
- return strKey;
288
- };
289
-
290
- /**
291
- * 通过字符串获取 schemaScope 中的数据
292
- * @param strKey
293
- * @param schemaScope
294
- * @returns
295
- */
296
- export const getValByScope = (strKey, schemaScope = {}) => {
297
- if (isScopeKey(strKey)) {
298
- return schemaScope && schemaScope[getScopeKey(strKey)];
299
- }
300
- };
301
-
302
- export default handleQuerySchema;
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;