@hzab/list-render 1.10.0-beta1 → 1.10.0-beta2
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
CHANGED
package/src/common/constant.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import _ from "lodash";
|
|
2
2
|
import { getFieldMap } from "./utils";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
isRunStr,
|
|
5
|
+
handleSchemaStrVal,
|
|
6
|
+
isScopeKey as formIsScopeKey,
|
|
7
|
+
getScopeKeyVal,
|
|
8
|
+
} from "@hzab/form-render/src/common/schema-handler";
|
|
4
9
|
import { URL_PARAM_NAME } from "./constant";
|
|
5
10
|
|
|
6
11
|
// ===================== 类型定义 =====================
|
|
7
12
|
|
|
8
|
-
type AntdDateComponent =
|
|
13
|
+
type AntdDateComponent = "DatePicker" | "DatePicker.RangePicker";
|
|
9
14
|
|
|
10
15
|
interface XComponentProps {
|
|
11
16
|
isSplitTimes?: boolean;
|
|
@@ -17,13 +22,13 @@ interface XComponentProps {
|
|
|
17
22
|
interface FormilyField {
|
|
18
23
|
type?: string;
|
|
19
24
|
title?: string;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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;
|
|
27
32
|
[k: string]: unknown;
|
|
28
33
|
}
|
|
29
34
|
|
|
@@ -269,7 +274,6 @@ export const handleHoc = (source, cb, opt) => {
|
|
|
269
274
|
|
|
270
275
|
let res = null;
|
|
271
276
|
if (isRunStr(source)) {
|
|
272
|
-
|
|
273
277
|
_source = handleSchemaStrVal(source, schemaScope);
|
|
274
278
|
}
|
|
275
279
|
if (formIsScopeKey(source, schemaScope)) {
|
|
@@ -354,6 +358,22 @@ export const getValByScope = (strKey, schemaScope = {}) => {
|
|
|
354
358
|
}
|
|
355
359
|
};
|
|
356
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
|
+
|
|
357
377
|
/**
|
|
358
378
|
* 将对象序列化后写入 URL query 中
|
|
359
379
|
* @param obj 要存储的对象(保持原始类型)
|
|
@@ -367,7 +387,10 @@ export function setURLObjectQueryParam<T extends object>(obj: T, paramName = URL
|
|
|
367
387
|
const [origin, hash = ""] = href.split("#");
|
|
368
388
|
const [path, queryString = ""] = hash.split("?");
|
|
369
389
|
const searchParams = new URLSearchParams(queryString);
|
|
370
|
-
|
|
390
|
+
// 指定的空值不设置
|
|
391
|
+
const copyObj = _.cloneDeep(removeEmptyValues(obj));
|
|
392
|
+
|
|
393
|
+
const encoded = encodeURIComponent(JSON.stringify(copyObj));
|
|
371
394
|
|
|
372
395
|
searchParams.set(paramName, encoded);
|
|
373
396
|
|
|
@@ -380,11 +403,11 @@ export function setURLObjectQueryParam<T extends object>(obj: T, paramName = URL
|
|
|
380
403
|
}
|
|
381
404
|
|
|
382
405
|
/**
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
* @param
|
|
406
|
+
* 从哈希路由的查询参数中,删除某个“对象型参数”下的指定属性(不刷新页面)
|
|
407
|
+
* @param parentKey 父级对象参数名
|
|
408
|
+
* @param childKey 要删除的对象属性名
|
|
386
409
|
*/
|
|
387
|
-
|
|
410
|
+
export function removeURLObjectQueryParam(parentKey: string, childKey: string, reset?: boolean): void {
|
|
388
411
|
if (typeof window === "undefined") return;
|
|
389
412
|
|
|
390
413
|
try {
|
|
@@ -393,21 +416,52 @@ export function setURLObjectQueryParam<T extends object>(obj: T, paramName = URL
|
|
|
393
416
|
const [hashPath, queryString = ""] = hash.split("?");
|
|
394
417
|
const searchParams = new URLSearchParams(queryString);
|
|
395
418
|
|
|
396
|
-
searchParams.
|
|
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
|
+
}
|
|
397
431
|
|
|
398
|
-
//
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
+
}
|
|
402
451
|
}
|
|
403
452
|
|
|
404
|
-
|
|
453
|
+
// 重建哈希部分
|
|
454
|
+
let newHash = hashPath;
|
|
455
|
+
const qs = searchParams.toString();
|
|
456
|
+
if (qs) newHash += `?${qs}`;
|
|
405
457
|
|
|
458
|
+
const newUrl = `${origin}${newHash ? `#${newHash}` : ""}`;
|
|
406
459
|
window.history.replaceState({}, "", newUrl);
|
|
407
460
|
} catch (e) {
|
|
408
461
|
console.error(`[removeURLObjectQueryParam] 删除失败:`, e);
|
|
409
462
|
}
|
|
410
463
|
}
|
|
464
|
+
|
|
411
465
|
/**
|
|
412
466
|
* 从 URL 中解析序列化的对象
|
|
413
467
|
* @param paramName query 参数名
|
|
@@ -433,14 +487,13 @@ export function getURLObjectQueryParam<T = any>(paramName = URL_PARAM_NAME): T |
|
|
|
433
487
|
}
|
|
434
488
|
}
|
|
435
489
|
|
|
436
|
-
|
|
437
490
|
// ===================== 辅助函数 =====================
|
|
438
491
|
|
|
439
492
|
const pickFormilyProperties = (schema: FormilyV1Schema): Record<string, FormilyField> =>
|
|
440
|
-
|
|
493
|
+
schema?.schema?.properties ?? {};
|
|
441
494
|
|
|
442
495
|
const isAntdDateComponent = (comp: unknown, whitelist: readonly string[]) =>
|
|
443
|
-
typeof comp ===
|
|
496
|
+
typeof comp === "string" && whitelist.includes(comp);
|
|
444
497
|
|
|
445
498
|
const filterKeys = (props: Record<string, FormilyField>, filters: readonly string[]) => {
|
|
446
499
|
const filterSet = new Set(filters);
|
|
@@ -448,14 +501,10 @@ const filterKeys = (props: Record<string, FormilyField>, filters: readonly strin
|
|
|
448
501
|
};
|
|
449
502
|
|
|
450
503
|
const matchSplitFlag = (field: FormilyField, splitFlag: string, expected: unknown) =>
|
|
451
|
-
field?.[
|
|
504
|
+
field?.["x-component-props"]?.[splitFlag] === expected;
|
|
452
505
|
|
|
453
506
|
/** 两个值都缺失则返回 undefined;否则返回二元组 */
|
|
454
|
-
const getRangeTupleOrUndefined = (
|
|
455
|
-
urlObj: URLQueryObject,
|
|
456
|
-
startKey: string,
|
|
457
|
-
endKey: string
|
|
458
|
-
): [any, any] | undefined => {
|
|
507
|
+
const getRangeTupleOrUndefined = (urlObj: URLQueryObject, startKey: string, endKey: string): [any, any] | undefined => {
|
|
459
508
|
const obj = (urlObj ?? {}) as Record<string, any>;
|
|
460
509
|
const hasStart = Object.prototype.hasOwnProperty.call(obj, startKey);
|
|
461
510
|
const hasEnd = Object.prototype.hasOwnProperty.call(obj, endKey);
|
|
@@ -472,14 +521,14 @@ export function extractSplitDateRanges(
|
|
|
472
521
|
filters: string[],
|
|
473
522
|
formilySchema: FormilyV1Schema,
|
|
474
523
|
urlObj: URLQueryObject,
|
|
475
|
-
options: ExtractOptions = {}
|
|
524
|
+
options: ExtractOptions = {},
|
|
476
525
|
): Record<string, any> {
|
|
477
526
|
const {
|
|
478
|
-
splitFlag =
|
|
527
|
+
splitFlag = "isSplitTimes",
|
|
479
528
|
splitFlagExpectedValue = true,
|
|
480
|
-
defaultStartKey =
|
|
481
|
-
defaultEndKey =
|
|
482
|
-
antdDateComponents = [
|
|
529
|
+
defaultStartKey = "startTime",
|
|
530
|
+
defaultEndKey = "endTime",
|
|
531
|
+
antdDateComponents = ["DatePicker", "DatePicker.RangePicker"],
|
|
483
532
|
removeMatchedKeys = true,
|
|
484
533
|
} = options;
|
|
485
534
|
|
|
@@ -494,10 +543,10 @@ export function extractSplitDateRanges(
|
|
|
494
543
|
|
|
495
544
|
for (const [fieldKey, field] of candidates) {
|
|
496
545
|
// 未命中则直接跳过本次循环
|
|
497
|
-
if (!isAntdDateComponent(field?.[
|
|
546
|
+
if (!isAntdDateComponent(field?.["x-component"], antdDateComponents)) continue;
|
|
498
547
|
if (!matchSplitFlag(field, splitFlag, splitFlagExpectedValue)) continue;
|
|
499
548
|
|
|
500
|
-
const props = field?.[
|
|
549
|
+
const props = field?.["x-component-props"] ?? {};
|
|
501
550
|
const startKey = (props?.startKey as string) || defaultStartKey;
|
|
502
551
|
const endKey = (props?.endKey as string) || defaultEndKey;
|
|
503
552
|
|
package/src/list-render.jsx
CHANGED
|
@@ -15,8 +15,13 @@ import FormModal from "./FormModal";
|
|
|
15
15
|
import DetailModal from "./DetailModal";
|
|
16
16
|
|
|
17
17
|
import { objToFormData } from "./common/utils";
|
|
18
|
-
import {
|
|
19
|
-
|
|
18
|
+
import {
|
|
19
|
+
setURLObjectQueryParam,
|
|
20
|
+
getURLObjectQueryParam,
|
|
21
|
+
removeURLObjectQueryParam,
|
|
22
|
+
extractSplitDateRanges,
|
|
23
|
+
} from "./common/handleQuerySchema";
|
|
24
|
+
import { CLEAR_LIST, URL_PARAM_NAME } from "./common/constant";
|
|
20
25
|
|
|
21
26
|
import "./index.less";
|
|
22
27
|
|
|
@@ -54,7 +59,7 @@ const ListRender = forwardRef(function (props, parentRef) {
|
|
|
54
59
|
/**
|
|
55
60
|
* 自定义设置url query对象参数的key
|
|
56
61
|
* */
|
|
57
|
-
appendUrlQueryKey = URL_PARAM_NAME
|
|
62
|
+
appendUrlQueryKey = URL_PARAM_NAME,
|
|
58
63
|
} = props;
|
|
59
64
|
const pageSizeOptions = props.paginationConf?.pageSizeOptions || pageSizeOptionMap[props.layout] || [10, 20, 50, 100];
|
|
60
65
|
// const [pageSizeOptions, setPageSizeOptions] = useState(
|
|
@@ -119,11 +124,11 @@ const ListRender = forwardRef(function (props, parentRef) {
|
|
|
119
124
|
if (queryRef.current && queryRef.current?.formRef?.current.formRender) {
|
|
120
125
|
const extractValues = extractSplitDateRanges(filters, schema, getUrlQuery);
|
|
121
126
|
|
|
122
|
-
queryRef.current.formRef.current.formRender?.setValues(extractValues)
|
|
127
|
+
queryRef.current.formRef.current.formRender?.setValues(extractValues);
|
|
123
128
|
|
|
124
|
-
formQueryRef.current = _.cloneDeep(getUrlQuery)
|
|
129
|
+
formQueryRef.current = _.cloneDeep(getUrlQuery);
|
|
125
130
|
}
|
|
126
|
-
})
|
|
131
|
+
});
|
|
127
132
|
|
|
128
133
|
!props.closeAutoRequest && getList({ ...(modelQueryRef.current || {}), ...getUrlQuery });
|
|
129
134
|
}, []);
|
|
@@ -224,7 +229,7 @@ const ListRender = forwardRef(function (props, parentRef) {
|
|
|
224
229
|
|
|
225
230
|
if (appendUrlQuery) {
|
|
226
231
|
const getUrlQuery = getURLObjectQueryParam(appendUrlQueryKey);
|
|
227
|
-
setURLObjectQueryParam({ ...getUrlQuery, ...paginationQueryRef.current }, appendUrlQueryKey)
|
|
232
|
+
setURLObjectQueryParam({ ...getUrlQuery, ...paginationQueryRef.current }, appendUrlQueryKey);
|
|
228
233
|
}
|
|
229
234
|
getList();
|
|
230
235
|
}
|
|
@@ -249,7 +254,25 @@ const ListRender = forwardRef(function (props, parentRef) {
|
|
|
249
254
|
}
|
|
250
255
|
|
|
251
256
|
function handleFormReset() {
|
|
252
|
-
appendUrlQuery && removeURLObjectQueryParam(appendUrlQueryKey)
|
|
257
|
+
appendUrlQuery && removeURLObjectQueryParam(appendUrlQueryKey, "", true);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function handleFieldValueChange(filed, form) {
|
|
261
|
+
// props?.onFieldValueChange?.(filed, form);
|
|
262
|
+
|
|
263
|
+
if (!appendUrlQuery) return;
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
const { componentType, value, path } = filed;
|
|
267
|
+
if (CLEAR_LIST.includes(componentType)) {
|
|
268
|
+
if (value === "" || value?.length === 0 || value === undefined) {
|
|
269
|
+
const key = path.entire;
|
|
270
|
+
key && removeURLObjectQueryParam(appendUrlQueryKey, key);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
} catch (e) {
|
|
274
|
+
console.error("url参数清除失败", e);
|
|
275
|
+
}
|
|
253
276
|
}
|
|
254
277
|
|
|
255
278
|
function forceUpdate() {
|
|
@@ -409,6 +432,7 @@ const ListRender = forwardRef(function (props, parentRef) {
|
|
|
409
432
|
onReset={handleFormReset}
|
|
410
433
|
schemaScope={props.schemaScope}
|
|
411
434
|
components={props.components}
|
|
435
|
+
onFieldValueChange={handleFieldValueChange}
|
|
412
436
|
i18n={i18n}
|
|
413
437
|
/>
|
|
414
438
|
) : (
|
|
@@ -7,6 +7,7 @@ import FormRender from "@hzab/form-render";
|
|
|
7
7
|
import { handleQuerySchema } from "../common/handleQuerySchema";
|
|
8
8
|
|
|
9
9
|
import "./index.less";
|
|
10
|
+
import { onFieldValueChange } from "@formily/core";
|
|
10
11
|
|
|
11
12
|
function QueryRender(props, parentRef) {
|
|
12
13
|
const [schema, setSchema] = useState({});
|
|
@@ -58,7 +59,6 @@ function QueryRender(props, parentRef) {
|
|
|
58
59
|
props.onSearch && props.onSearch(_.cloneDeep(formRef?.current?.formRender?.values), "queryRender", true);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
|
|
62
62
|
return (
|
|
63
63
|
<div className="query-render">
|
|
64
64
|
{Object.keys(schema?.schema?.properties || {}).length > 0 ? (
|
|
@@ -69,6 +69,7 @@ function QueryRender(props, parentRef) {
|
|
|
69
69
|
schema={schema}
|
|
70
70
|
schemaScope={{ scenario: "query", ...(props.schemaScope || {}) }}
|
|
71
71
|
initialValues={props.queryFormInitialValues}
|
|
72
|
+
onFieldValueChange={props.onFieldValueChange}
|
|
72
73
|
Slots={() => {
|
|
73
74
|
return (
|
|
74
75
|
<div className="query-operation">
|