zy-react-library 1.0.133 → 1.0.135

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.
@@ -45,6 +45,7 @@ const FormBuilder = (props) => {
45
45
  options={options}
46
46
  labelCol={labelCol}
47
47
  span={span}
48
+ gutter={gutter}
48
49
  useAutoGenerateRequired={useAutoGenerateRequired}
49
50
  initialValues={values}
50
51
  />
@@ -1,5 +1,7 @@
1
1
  import type { ColProps } from "antd/es/col";
2
2
  import type { FormItemProps, Rule } from "antd/es/form";
3
+ import type { FormListFieldData } from "antd/es/form/FormList";
4
+ import type { Gutter } from "antd/es/grid/row";
3
5
  import type { NamePath } from "rc-field-form/lib/interface";
4
6
  import type { FC, ReactNode } from "react";
5
7
  import type { FORM_ITEM_RENDER_ENUM } from "../../enum/formItemRender";
@@ -37,6 +39,26 @@ export interface itemsFieldConfig {
37
39
  */
38
40
  export type FormValues = Record<string, any>;
39
41
 
42
+ /**
43
+ * Form.List 独有的属性
44
+ */
45
+ export interface FormListUniqueProps {
46
+ /** 是否显示新增按钮,默认 true */
47
+ showAddButton?: boolean;
48
+ /** 是否显示删除按钮,默认 true */
49
+ showRemoveButton?: boolean;
50
+ /** 新增按钮的文本,默认 '添加' */
51
+ addButtonText?: string;
52
+ /** 删除按钮的文本,默认 '删除' */
53
+ removeButtonText?: string;
54
+ /** 表单配置项 */
55
+ options: FormOption[] | ((field: FormListFieldData) => FormOption[]);
56
+ /** 点击新增按钮时的默认值 */
57
+ addDefaultValue?: FormValues;
58
+ /** 点击新增按钮时插入的索引位置 */
59
+ addInsertIndex?: number;
60
+ }
61
+
40
62
  /**
41
63
  * 表单配置项
42
64
  */
@@ -81,6 +103,8 @@ export interface FormOption {
81
103
  dependencies?: NamePath[];
82
104
  /** 是否仅用于保存标签,不渲染到页面上,只在表单中保存数据,默认 false */
83
105
  onlyForLabel?: boolean;
106
+ /** Form.List 独有的属性 */
107
+ formListUniqueProps?: FormListUniqueProps;
84
108
  }
85
109
 
86
110
  /**
@@ -97,6 +121,10 @@ export interface FormItemsRendererProps {
97
121
  useAutoGenerateRequired?: boolean;
98
122
  /** 初始值,用于在表单未初始化时提供默认值 */
99
123
  initialValues?: FormValues;
124
+ /** 栅格间距,继承自 FormBuilder */
125
+ gutter?: Gutter | [Gutter, Gutter];
126
+ /** label 栅格配置,继承自 FormBuilder */
127
+ labelCol?: ColProps;
100
128
  }
101
129
 
102
130
  /**
@@ -1,5 +1,6 @@
1
1
  import { InfoCircleOutlined } from "@ant-design/icons";
2
2
  import {
3
+ Button,
3
4
  Checkbox,
4
5
  Col,
5
6
  DatePicker,
@@ -8,6 +9,7 @@ import {
8
9
  Input,
9
10
  InputNumber,
10
11
  Radio,
12
+ Row,
11
13
  Select,
12
14
  Tooltip,
13
15
  } from "antd";
@@ -19,10 +21,19 @@ const { RangePicker } = DatePicker;
19
21
 
20
22
  /**
21
23
  * 表单项渲染器组件
24
+ * @param {object} props - 组件属性
25
+ * @param {Array} props.options - 表单配置项数组
26
+ * @param {object} props.labelCol - label 栅格配置
27
+ * @param {number} props.gutter - 栅格间距
28
+ * @param {number} props.span - 默认栅格占据列数
29
+ * @param {boolean} props.collapse - 是否折叠(仅显示前3项)
30
+ * @param {boolean} props.useAutoGenerateRequired - 是否自动生成必填规则
31
+ * @param {object} props.initialValues - 初始值
22
32
  */
23
33
  const FormItemsRenderer = ({
24
34
  options,
25
35
  labelCol,
36
+ gutter = 24,
26
37
  span = 12,
27
38
  collapse = false,
28
39
  useAutoGenerateRequired = true,
@@ -47,6 +58,31 @@ const FormItemsRenderer = ({
47
58
  : (option.componentProps || {});
48
59
  };
49
60
 
61
+ // 获取 Form.List 独有的属性
62
+ const getFormListUniqueProps = (option) => {
63
+ const defaultProps = {
64
+ showAddButton: true,
65
+ showRemoveButton: true,
66
+ addButtonText: "添加",
67
+ removeButtonText: "删除",
68
+ options: [],
69
+ addDefaultValue: {},
70
+ addInsertIndex: undefined,
71
+ };
72
+
73
+ if (typeof option.formListUniqueProps === "function") {
74
+ return {
75
+ ...defaultProps,
76
+ ...option.formListUniqueProps(getFormValues()),
77
+ };
78
+ }
79
+
80
+ return {
81
+ ...defaultProps,
82
+ ...(option.formListUniqueProps || {}),
83
+ };
84
+ };
85
+
50
86
  // 获取传给formItem的属性
51
87
  const getFormItemProps = (option) => {
52
88
  const formItemProps = typeof option.formItemProps === "function"
@@ -92,6 +128,14 @@ const FormItemsRenderer = ({
92
128
  };
93
129
  };
94
130
 
131
+ // 获取 required
132
+ const getRequired = (required) => {
133
+ // 支持动态计算 required
134
+ return typeof required === "function"
135
+ ? required(getFormValues())
136
+ : (required ?? true);
137
+ };
138
+
95
139
  // 获取验证规则
96
140
  const getRules = (option) => {
97
141
  if (option.render === FORM_ITEM_RENDER_ENUM.DIVIDER)
@@ -115,11 +159,11 @@ const FormItemsRenderer = ({
115
159
  rules.push({ pattern: /^(\d+)(\.\d{1,2})?$/, message: "请输入正确的数字,最多保留两位小数" });
116
160
  rules.push({
117
161
  validator: (_, value) => {
118
- if (value && Math.abs(parseFloat(value)) > Number.MAX_SAFE_INTEGER) {
162
+ if (value && Math.abs(Number.parseFloat(value)) > Number.MAX_SAFE_INTEGER) {
119
163
  return Promise.reject("输入数值超出安全范围");
120
164
  }
121
165
  return Promise.resolve();
122
- }
166
+ },
123
167
  });
124
168
  break;
125
169
  }
@@ -127,12 +171,7 @@ const FormItemsRenderer = ({
127
171
  if (!useAutoGenerateRequired)
128
172
  return option.rules ? (Array.isArray(option.rules) ? [...option.rules, ...rules] : [option.rules, ...rules]) : [];
129
173
 
130
- // 支持动态计算 required
131
- const required = typeof option.required === "function"
132
- ? option.required(getFormValues())
133
- : (option.required ?? true);
134
-
135
- if (required) {
174
+ if (getRequired(option.required)) {
136
175
  const isBlurTrigger = !option.render || [
137
176
  FORM_ITEM_RENDER_ENUM.INPUT,
138
177
  FORM_ITEM_RENDER_ENUM.TEXTAREA,
@@ -162,6 +201,34 @@ const FormItemsRenderer = ({
162
201
  return option.key || option.name;
163
202
  };
164
203
 
204
+ // 使用 style 控制显示/隐藏
205
+ const getStyle = (index) => {
206
+ return collapse && index >= 3 ? { display: "none" } : undefined;
207
+ };
208
+
209
+ // 列数
210
+ const getCol = (option) => {
211
+ const itemSpan = option.render === FORM_ITEM_RENDER_ENUM.DIVIDER ? 24 : option.span ?? span;
212
+ const itemLabelCol = option.labelCol ?? (itemSpan === 24 ? { span: labelCol.span / 2 } : labelCol);
213
+ const itemWrapperCol = option.wrapperCol ?? { span: 24 - itemLabelCol.span };
214
+ return { span: itemSpan, labelCol: itemLabelCol, wrapperCol: itemWrapperCol };
215
+ };
216
+
217
+ // 获取 hidden
218
+ const getHidden = (hidden) => {
219
+ // 支持动态计算 hidden
220
+ return typeof hidden === "function"
221
+ ? hidden(getFormValues())
222
+ : (hidden ?? false);
223
+ };
224
+
225
+ // 获取 listOptions
226
+ const getListOptions = (listOptions, field) => {
227
+ return typeof listOptions === "function"
228
+ ? listOptions(field)
229
+ : (listOptions ?? []);
230
+ };
231
+
165
232
  // 渲染表单控件
166
233
  const renderFormControl = (option) => {
167
234
  const componentProps = getComponentProps(option);
@@ -230,13 +297,37 @@ const FormItemsRenderer = ({
230
297
  return <DatePicker placeholder={placeholder} format="YYYY-MM-DD" style={{ width: "100%" }} {...componentProps} />;
231
298
 
232
299
  case FORM_ITEM_RENDER_ENUM.DATE_MONTH:
233
- return <DatePicker picker="month" placeholder={placeholder} format="YYYY-MM" style={{ width: "100%" }} {...componentProps} />;
300
+ return (
301
+ <DatePicker
302
+ picker="month"
303
+ placeholder={placeholder}
304
+ format="YYYY-MM"
305
+ style={{ width: "100%" }}
306
+ {...componentProps}
307
+ />
308
+ );
234
309
 
235
310
  case FORM_ITEM_RENDER_ENUM.DATE_YEAR:
236
- return <DatePicker picker="year" placeholder={placeholder} format="YYYY" style={{ width: "100%" }} {...componentProps} />;
311
+ return (
312
+ <DatePicker
313
+ picker="year"
314
+ placeholder={placeholder}
315
+ format="YYYY"
316
+ style={{ width: "100%" }}
317
+ {...componentProps}
318
+ />
319
+ );
237
320
 
238
321
  case FORM_ITEM_RENDER_ENUM.DATE_WEEK:
239
- return <DatePicker picker="week" placeholder={placeholder} format="YYYY-wo" style={{ width: "100%" }} {...componentProps} />;
322
+ return (
323
+ <DatePicker
324
+ picker="week"
325
+ placeholder={placeholder}
326
+ format="YYYY-wo"
327
+ style={{ width: "100%" }}
328
+ {...componentProps}
329
+ />
330
+ );
240
331
 
241
332
  case FORM_ITEM_RENDER_ENUM.DATE_RANGE:
242
333
  return (
@@ -293,112 +384,198 @@ const FormItemsRenderer = ({
293
384
  );
294
385
  };
295
386
 
387
+ // 渲染普通表单项
388
+ const renderFormItem = ({ option, style, col, index, name }) => {
389
+ if (getHidden(option.hidden))
390
+ return null;
391
+
392
+ return (
393
+ <Col key={getKey(option) || index} span={col.span} style={style}>
394
+ <Form.Item
395
+ name={name || option.name}
396
+ label={renderLabel(option)}
397
+ rules={getRules(option)}
398
+ labelCol={col.labelCol}
399
+ wrapperCol={col.wrapperCol}
400
+ preserve={false}
401
+ {...getFormItemProps(option)}
402
+ >
403
+ {renderFormControl(option)}
404
+ </Form.Item>
405
+ </Col>
406
+ );
407
+ };
408
+
409
+ // 渲染特殊类型的表单项
410
+ const renderOtherTypeItem = ({ option, style, col, index, name }) => {
411
+ // 如果是 customizeRender 类型,完全交给外部控制渲染
412
+ if (option.customizeRender) {
413
+ return (
414
+ <Col key={getKey(option) || index} span={col.span} style={style}>
415
+ {option.render}
416
+ </Col>
417
+ );
418
+ }
419
+
420
+ // 如果是 onlyForLabel 类型,不渲染任何UI,只在表单中保存数据
421
+ if (option.onlyForLabel) {
422
+ return (
423
+ <Form.Item
424
+ key={getKey(option) || index}
425
+ name={name || option.name}
426
+ noStyle
427
+ preserve={false}
428
+ >
429
+ <input type="hidden" />
430
+ </Form.Item>
431
+ );
432
+ }
433
+
434
+ // 如果是分割线
435
+ if (option.render === FORM_ITEM_RENDER_ENUM.DIVIDER) {
436
+ return (
437
+ <Col key={getKey(option) || index} span={col.span} style={style}>
438
+ <Divider orientation="left">{option.label}</Divider>
439
+ </Col>
440
+ );
441
+ }
442
+
443
+ return null;
444
+ };
445
+
446
+ // 渲染 Form.List
447
+ const renderFormList = (option, index, col, style) => {
448
+ const formListUniqueProps = getFormListUniqueProps(option);
449
+ return (
450
+ <Col key={getKey(option) || index} span={col.span} style={style}>
451
+ <Form.List name={option.name}>
452
+ {(fields, { add, remove }) => (
453
+ <>
454
+ {fields.map((field, fieldIndex) => {
455
+ const listOptions = getListOptions(option.formListUniqueProps.options, field);
456
+ return (
457
+ <Row gutter={gutter} key={field.key}>
458
+ {listOptions.map((listOption, listIndex) => {
459
+ const col = getCol(listOption);
460
+
461
+ const params = {
462
+ option: listOption,
463
+ style,
464
+ col,
465
+ index: `${fieldIndex}_${listIndex}`,
466
+ name: [field.name, listOption.name],
467
+ };
468
+ const otherTypeItem = renderOtherTypeItem(params);
469
+ if (otherTypeItem)
470
+ return otherTypeItem;
471
+
472
+ // 如果是最后一个表单项,则在其后添加操作按钮
473
+ // 这样可以确保每个表单项组都有添加/删除按钮
474
+ if (listIndex === listOptions.length - 1) {
475
+ return (
476
+ <Col key={getKey(listOption) || listIndex} span={col.span} style={style}>
477
+ <Form.Item
478
+ label={renderLabel(listOption)}
479
+ labelCol={col.labelCol}
480
+ wrapperCol={col.wrapperCol}
481
+ preserve={false}
482
+ required={getRequired(listOption.required)}
483
+ {...getFormItemProps(listOption)}
484
+ >
485
+ <div style={{ display: "flex", gap: 10, alignItems: "center" }}>
486
+ <Form.Item
487
+ noStyle
488
+ rules={getRules(listOption)}
489
+ name={[field.name, listOption.name]}
490
+ >
491
+ {renderFormControl(listOption)}
492
+ </Form.Item>
493
+ {
494
+ // 只有当不是第一行时才显示删除按钮
495
+ fieldIndex >= 1
496
+ ? (
497
+ formListUniqueProps.showRemoveButton
498
+ && (
499
+ <Button
500
+ type="primary"
501
+ danger
502
+ onClick={() => remove(field.name)}
503
+ >
504
+ {formListUniqueProps.removeButtonText}
505
+ </Button>
506
+ )
507
+ )
508
+ : (
509
+ // 第一行显示添加按钮
510
+ formListUniqueProps.showAddButton
511
+ && (
512
+ <Button
513
+ type="primary"
514
+ onClick={() => add(formListUniqueProps.addDefaultValue, formListUniqueProps.addInsertIndex)}
515
+ >
516
+ {formListUniqueProps.addButtonText}
517
+ </Button>
518
+ )
519
+ )
520
+ }
521
+ </div>
522
+ </Form.Item>
523
+ </Col>
524
+ );
525
+ }
526
+
527
+ return renderFormItem(params);
528
+ })}
529
+ </Row>
530
+ );
531
+ })}
532
+ </>
533
+ )}
534
+ </Form.List>
535
+ </Col>
536
+ );
537
+ };
538
+
539
+ // 渲染需要动态更新的表单项
540
+ const renderDynamicFormItem = (option, index, style, col) => {
541
+ return (
542
+ <Form.Item
543
+ key={getKey(option) || index}
544
+ noStyle
545
+ preserve={false}
546
+ shouldUpdate={option.shouldUpdate ?? option?.componentProps?.shouldUpdate}
547
+ dependencies={option.dependencies ?? option?.componentProps?.dependencies}
548
+ >
549
+ {() => {
550
+ return renderFormItem({ option, style, col, index });
551
+ }}
552
+ </Form.Item>
553
+ );
554
+ };
555
+
296
556
  return (
297
557
  <>
298
558
  {options.map((option, index) => {
299
- // 列数
300
- const itemSpan = option.render === FORM_ITEM_RENDER_ENUM.DIVIDER ? 24 : option.span ?? span;
301
- const itemLabelCol = option.labelCol ?? (itemSpan === 24 ? { span: labelCol.span / 2 } : labelCol);
302
- const itemWrapperCol = option.wrapperCol ?? { span: 24 - itemLabelCol.span };
303
-
304
- // 使用 style 控制显示/隐藏
305
- const style = collapse && index >= 3 ? { display: "none" } : undefined;
306
-
307
- // 如果是 customizeRender 类型,完全交给外部控制渲染
308
- if (option.customizeRender) {
309
- return (
310
- <Col key={getKey(option) || index} span={itemSpan} style={style}>
311
- {option.render}
312
- </Col>
313
- );
314
- }
559
+ const col = getCol(option);
560
+ const style = getStyle(index);
315
561
 
316
- // 如果是 onlyForLabel 类型,不渲染任何UI,只在表单中保存数据
317
- if (option.onlyForLabel) {
318
- return (
319
- <Form.Item
320
- key={getKey(option) || index}
321
- name={option.name}
322
- noStyle
323
- preserve={false}
324
- >
325
- <input type="hidden" />
326
- </Form.Item>
327
- );
328
- }
562
+ // 处理特殊类型的表单项
563
+ const otherTypeItem = renderOtherTypeItem({ option, style, col, index });
564
+ if (otherTypeItem)
565
+ return otherTypeItem;
329
566
 
330
- // 如果是分割线
331
- if (option.render === FORM_ITEM_RENDER_ENUM.DIVIDER) {
332
- return (
333
- <Col key={getKey(option) || index} span={itemSpan} style={style}>
334
- <Divider orientation="left">{option.label}</Divider>
335
- </Col>
336
- );
567
+ // 如果是 Form.List
568
+ if (option.render === FORM_ITEM_RENDER_ENUM.FORM_LIST) {
569
+ return renderFormList(option, index, col, style);
337
570
  }
338
571
 
339
572
  // 如果配置了 shouldUpdate 或 dependencies,使用 Form.Item 的联动机制
340
573
  if ((option.shouldUpdate ?? option.dependencies) || (option?.componentProps?.shouldUpdate ?? option?.componentProps?.dependencies)) {
341
- return (
342
- <Form.Item
343
- key={getKey(option) || index}
344
- noStyle
345
- preserve={false}
346
- shouldUpdate={option.shouldUpdate ?? option?.componentProps?.shouldUpdate}
347
- dependencies={option.dependencies || option?.componentProps?.dependencies}
348
- >
349
- {() => {
350
- // 支持动态计算 hidden
351
- const hidden = typeof option.hidden === "function"
352
- ? option.hidden(getFormValues())
353
- : (option.hidden ?? false);
354
-
355
- if (hidden)
356
- return null;
357
-
358
- return (
359
- <Col key={getKey(option) || index} span={itemSpan} style={style}>
360
- <Form.Item
361
- name={option.name}
362
- label={renderLabel(option)}
363
- rules={getRules(option)}
364
- labelCol={itemLabelCol}
365
- wrapperCol={itemWrapperCol}
366
- preserve={false}
367
- {...getFormItemProps(option)}
368
- >
369
- {renderFormControl(option)}
370
- </Form.Item>
371
- </Col>
372
- );
373
- }}
374
- </Form.Item>
375
- );
574
+ return renderDynamicFormItem(option, index, style, col);
376
575
  }
377
576
 
378
577
  // 普通表单项(静态配置)
379
- // 支持动态计算 hidden
380
- const hidden = typeof option.hidden === "function"
381
- ? option.hidden(getFormValues())
382
- : (option.hidden ?? false);
383
-
384
- if (hidden)
385
- return null;
386
-
387
- return (
388
- <Col key={getKey(option) || index} span={itemSpan} style={style}>
389
- <Form.Item
390
- name={option.name}
391
- label={renderLabel(option)}
392
- rules={getRules(option)}
393
- labelCol={itemLabelCol}
394
- wrapperCol={itemWrapperCol}
395
- preserve={false}
396
- {...getFormItemProps(option)}
397
- >
398
- {renderFormControl(option)}
399
- </Form.Item>
400
- </Col>
401
- );
578
+ return renderFormItem({ option, style, col, index });
402
579
  })}
403
580
  </>
404
581
  );
@@ -1,4 +1,4 @@
1
- import { Modal } from "antd";
1
+ import { Button, Modal } from "antd";
2
2
  import { useEffect, useRef, useState } from "react";
3
3
  import { getFileUrl } from "../../utils";
4
4
  import AliPlayer from "./AliPlayer";
@@ -21,7 +21,6 @@ const Video = ({
21
21
  title = "视频",
22
22
  visible: externalVisible = false,
23
23
  onCancel,
24
- ...restProps
25
24
  }) => {
26
25
  const [internalVisible, setInternalVisible] = useState(false);
27
26
  const playerRef = useRef(null);
@@ -68,15 +67,15 @@ const Video = ({
68
67
  return (
69
68
  <Modal
70
69
  open={visible}
70
+ width={800}
71
71
  title={title}
72
72
  footer={[
73
- <button key="cancel" onClick={() => setVisible(false)}>
73
+ <Button key="cancel" onClick={() => setVisible(false)}>
74
74
  取消
75
- </button>,
75
+ </Button>,
76
76
  ]}
77
77
  maskClosable={false}
78
78
  onCancel={() => setVisible(false)}
79
- {...restProps}
80
79
  >
81
80
  {playerElement}
82
81
  </Modal>
@@ -32,4 +32,6 @@ export declare const FORM_ITEM_RENDER_ENUM: {
32
32
  DATETIME_RANGE: "datetimeRange";
33
33
  /** 映射为 antd Divider */
34
34
  DIVIDER: "divider";
35
+ /** 映射为 antd FormList */
36
+ FORM_LIST: "formList",
35
37
  };
@@ -32,4 +32,6 @@ export const FORM_ITEM_RENDER_ENUM = {
32
32
  DATETIME_RANGE: "datetimeRange",
33
33
  /** 映射为 antd Divider */
34
34
  DIVIDER: "divider",
35
+ /** 映射为 antd FormList */
36
+ FORM_LIST: "formList",
35
37
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zy-react-library",
3
3
  "private": false,
4
- "version": "1.0.133",
4
+ "version": "1.0.135",
5
5
  "type": "module",
6
6
  "description": "",
7
7
  "author": "LiuJiaNan",