zy-react-library 1.0.0

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/README.md ADDED
@@ -0,0 +1,14 @@
1
+ # zy-react-library
2
+
3
+ ## 📦 安装
4
+
5
+ ```bash
6
+ # yarn
7
+ yarn add zy-react-library
8
+ ```
9
+
10
+ ## 📄 更新日志
11
+
12
+ ### v1.0.0 (2025-10-22)
13
+
14
+ - 🎉 初始版本发布
@@ -0,0 +1,31 @@
1
+ import type { FormInstance, FormProps } from "antd/es/form";
2
+ import type { Gutter } from "antd/es/grid/row";
3
+ import type { FC } from "react";
4
+ import type { FormOption, FormValues } from "./FormItemsRenderer";
5
+
6
+ /**
7
+ * FormBuilder 组件属性
8
+ */
9
+ export interface FormBuilderProps extends Omit<FormProps, "form"> {
10
+ /** 表单初始值 */
11
+ values?: FormValues;
12
+ /** 表单配置项数组 */
13
+ options: FormOption[];
14
+ /** 栅格间距,默认 24 */
15
+ gutter?: Gutter | [Gutter, Gutter];
16
+ /** 占据栅格列数,默认 12 */
17
+ span?: number | string;
18
+ /** 表单实例(通过 Form.useForm() 创建) */
19
+ form: FormInstance;
20
+ /** 自动生成必填规则,默认 true */
21
+ useAutoGenerateRequired?: boolean;
22
+ /** 表单提交回调 */
23
+ onFinish?: (values: any) => void;
24
+ }
25
+
26
+ /**
27
+ * 表单构建器组件
28
+ */
29
+ declare const FormBuilder: FC<FormBuilderProps>;
30
+
31
+ export default FormBuilder;
@@ -0,0 +1,41 @@
1
+ import { Form, Row } from "antd";
2
+ import FormItemsRenderer from "./FormItemsRenderer";
3
+
4
+ /**
5
+ * 表单构建器组件
6
+ */
7
+ const FormBuilder = (props) => {
8
+ const {
9
+ values,
10
+ options,
11
+ gutter = 24,
12
+ span = 12,
13
+ labelCol = { span: 4 },
14
+ onFinish,
15
+ useAutoGenerateRequired = true,
16
+ ...restProps
17
+ } = props;
18
+
19
+ return (
20
+ <Form
21
+ labelCol={labelCol}
22
+ scrollToFirstError
23
+ wrapperCol={{ span: 24 - labelCol.span }}
24
+ onFinish={onFinish}
25
+ initialValues={values}
26
+ {...restProps}
27
+ >
28
+ <Row gutter={gutter}>
29
+ <FormItemsRenderer
30
+ options={options}
31
+ span={span}
32
+ useAutoGenerateRequired={useAutoGenerateRequired}
33
+ />
34
+ </Row>
35
+ </Form>
36
+ );
37
+ };
38
+
39
+ FormBuilder.displayName = "FormBuilder";
40
+
41
+ export default FormBuilder;
@@ -0,0 +1,107 @@
1
+ import type { ColProps } from "antd/es/col";
2
+ import type { FormItemProps, Rule } from "antd/es/form";
3
+ import type { NamePath } from "rc-field-form/lib/interface";
4
+ import type { FC, ReactNode } from "react";
5
+ import type { FORM_ITEM_RENDER_ENUM } from "../../enum/formItemRender";
6
+
7
+ /**
8
+ * 表单项渲染类型
9
+ */
10
+ export type FormItemRenderType
11
+ = | (typeof FORM_ITEM_RENDER_ENUM)[keyof typeof FORM_ITEM_RENDER_ENUM]
12
+ | ((props: any) => ReactNode);
13
+
14
+ /**
15
+ * 选项项数据类型
16
+ */
17
+ export interface OptionItem {
18
+ /** 值字段 */
19
+ value?: any;
20
+ /** 标签字段 */
21
+ label?: string;
22
+ /** 字典ID */
23
+ dictionariesId?: any;
24
+ /** ID字段 */
25
+ id?: any;
26
+ /** 名称字段 */
27
+ name?: string;
28
+ [key: string]: any;
29
+ }
30
+
31
+ /**
32
+ * 字段键配置
33
+ */
34
+ export interface itemsFieldConfig {
35
+ /** 值字段的键名,默认为 'value' */
36
+ valueKey?: string;
37
+ /** 标签字段的键名,默认为 'label' */
38
+ labelKey?: string;
39
+ }
40
+
41
+ /**
42
+ * 表单值类型
43
+ */
44
+ export type FormValues = Record<string, any>;
45
+
46
+ /**
47
+ * 表单配置项
48
+ */
49
+ export interface FormOption {
50
+ /** 表单项字段名 */
51
+ name?: string | string[];
52
+ /** 表单项标签 */
53
+ label?: ReactNode;
54
+ /** 渲染类型 */
55
+ render?: FormItemRenderType;
56
+ /** 占据栅格列数,默认 12 */
57
+ span?: number | string;
58
+ /** 是否必填,默认 true,支持函数动态计算 */
59
+ required?: boolean | ((formValues: FormValues) => boolean);
60
+ /** 验证规则 */
61
+ rules?: Rule | Rule[];
62
+ /** 占位符文本,默认会根据传入的 render 类型自动判断(请选择、请输入)和 label 组合 */
63
+ placeholder?: ReactNode;
64
+ /** 提示信息,传入将在 label 右侧生成图标展示 tooltip */
65
+ tip?: ReactNode;
66
+ /** 是否隐藏,默认 false,支持函数动态计算 */
67
+ hidden?: boolean | ((formValues: FormValues) => boolean);
68
+ /** 是否自定义渲染,默认 false,将不生成 Col 和 Form.Item( 仅生效 render、items、itemsField、componentProps ) */
69
+ customizeRender?: boolean;
70
+ /** 选项数据(用于 select、radio、checkbox) */
71
+ items?: OptionItem[];
72
+ /** 字段键配置 */
73
+ itemsField?: itemsFieldConfig;
74
+ /** 传递给表单控件的属性,支持函数动态计算 */
75
+ componentProps?: Record<string, any> | ((formValues: FormValues) => Record<string, any>);
76
+ /** 传递给 Form.Item 的属性,支持函数动态计算 */
77
+ formItemProps?: FormItemProps | ((formValues: FormValues) => FormItemProps);
78
+ /** label 栅格配置 */
79
+ labelCol?: ColProps;
80
+ /** wrapper 栅格配置 */
81
+ wrapperCol?: ColProps;
82
+ /** 是否应该更新(用于表单联动) */
83
+ shouldUpdate?: boolean | ((prevValues: FormValues, nextValues: FormValues, info: { source?: string }) => boolean);
84
+ /** 依赖字段(用于表单联动) */
85
+ dependencies?: NamePath[];
86
+ }
87
+
88
+ /**
89
+ * FormItemsRenderer 组件属性
90
+ */
91
+ export interface FormItemsRendererProps {
92
+ /** 表单配置项数组 */
93
+ options: FormOption[];
94
+ /** 默认栅格占据列数,默认 12 */
95
+ span?: number;
96
+ /** 是否折叠(仅显示前3项),默认 false */
97
+ collapse?: boolean;
98
+ /** 自动生成必填规则,默认 true */
99
+ useAutoGenerateRequired?: boolean;
100
+ }
101
+
102
+ /**
103
+ * 表单项渲染器组件
104
+ */
105
+ declare const FormItemsRenderer: FC<FormItemsRendererProps>;
106
+
107
+ export default FormItemsRenderer;
@@ -0,0 +1,345 @@
1
+ import { InfoCircleOutlined } from "@ant-design/icons";
2
+ import {
3
+ Checkbox,
4
+ Col,
5
+ DatePicker,
6
+ Divider,
7
+ Form,
8
+ Input,
9
+ InputNumber,
10
+ Radio,
11
+ Select,
12
+ Tooltip,
13
+ } from "antd";
14
+ import dayjs from "dayjs";
15
+ import { FORM_ITEM_RENDER_ENUM } from "../../enum/formItemRender";
16
+
17
+ const { TextArea } = Input;
18
+ const { RangePicker } = DatePicker;
19
+
20
+ /**
21
+ * 表单项渲染器组件
22
+ */
23
+ const FormItemsRenderer = ({
24
+ options,
25
+ span = 12,
26
+ collapse = false,
27
+ useAutoGenerateRequired = true,
28
+ }) => {
29
+ const form = Form.useFormInstance();
30
+
31
+ // 获取传给组件的属性
32
+ const getComponentProps = (option) => {
33
+ return typeof option.componentProps === "function"
34
+ ? option.componentProps(form.getFieldsValue())
35
+ : (option.componentProps || {});
36
+ };
37
+
38
+ // 获取传给formItem的属性
39
+ const getFormItemProps = (option) => {
40
+ const formItemProps = typeof option.formItemProps === "function"
41
+ ? option.formItemProps(form.getFieldsValue())
42
+ : (option.formItemProps || {});
43
+
44
+ // 为日期组件添加特殊处理
45
+ if ([
46
+ FORM_ITEM_RENDER_ENUM.DATE,
47
+ FORM_ITEM_RENDER_ENUM.DATE_MONTH,
48
+ FORM_ITEM_RENDER_ENUM.DATE_YEAR,
49
+ FORM_ITEM_RENDER_ENUM.DATETIME,
50
+ ].includes(option.render)) {
51
+ formItemProps.getValueFromEvent = (_, dateString) => dateString;
52
+ formItemProps.getValueProps = value => ({ value: value ? dayjs(value) : undefined });
53
+ }
54
+
55
+ // 为日期周组件添加特殊处理
56
+ if ([
57
+ FORM_ITEM_RENDER_ENUM.DATE_WEEK,
58
+ ].includes(option.render)) {
59
+ formItemProps.getValueFromEvent = (_, dateString) => dateString;
60
+ formItemProps.getValueProps = value => ({ value: value ? dayjs(value, "YYYY-wo") : undefined });
61
+ }
62
+
63
+ // 为日期范围组件添加特殊处理
64
+ if ([
65
+ FORM_ITEM_RENDER_ENUM.DATE_RANGE,
66
+ FORM_ITEM_RENDER_ENUM.DATETIME_RANGE,
67
+ ].includes(option.render)) {
68
+ formItemProps.getValueFromEvent = (_, dateString) => dateString;
69
+ formItemProps.getValueProps = value => ({ value: Array.isArray(value) ? value.map(v => v ? dayjs(v) : undefined) : undefined });
70
+ }
71
+
72
+ return formItemProps;
73
+ };
74
+
75
+ // 获取items里的value和label字段key
76
+ const getItemsFieldKey = (option) => {
77
+ return {
78
+ valueKey: option?.itemsField?.valueKey || "value",
79
+ labelKey: option?.itemsField?.labelKey || "label",
80
+ };
81
+ };
82
+
83
+ // 获取验证规则
84
+ const getRules = (option) => {
85
+ if (!useAutoGenerateRequired)
86
+ return option.rules ? (Array.isArray(option.rules) ? option.rules : [option.rules]) : [];
87
+ if (option.render === FORM_ITEM_RENDER_ENUM.DIVIDER)
88
+ return [];
89
+
90
+ // 支持动态计算 required
91
+ const required = typeof option.required === "function"
92
+ ? option.required(form.getFieldsValue())
93
+ : (option.required || true);
94
+
95
+ if (required) {
96
+ const isBlurTrigger = !option.render || [
97
+ FORM_ITEM_RENDER_ENUM.INPUT,
98
+ FORM_ITEM_RENDER_ENUM.TEXTAREA,
99
+ FORM_ITEM_RENDER_ENUM.INPUT_NUMBER,
100
+ FORM_ITEM_RENDER_ENUM.NUMBER,
101
+ ].includes(option.render);
102
+
103
+ const rules = [
104
+ {
105
+ required: true,
106
+ message: `${isBlurTrigger ? "请输入" : "请选择"}${option.label}`,
107
+ },
108
+ ];
109
+
110
+ if (option.rules) {
111
+ if (Array.isArray(option.rules)) {
112
+ rules.push(...option.rules);
113
+ }
114
+ else {
115
+ rules.push(option.rules);
116
+ }
117
+ }
118
+ return rules;
119
+ }
120
+
121
+ return option.rules ? (Array.isArray(option.rules) ? option.rules : [option.rules]) : [];
122
+ };
123
+
124
+ // 渲染表单控件
125
+ const renderFormControl = (option) => {
126
+ const componentProps = getComponentProps(option);
127
+ const itemsFieldKey = getItemsFieldKey(option);
128
+ /** @type {string | Function} */
129
+ const render = option.render || FORM_ITEM_RENDER_ENUM.INPUT;
130
+ const placeholder = option.placeholder || `请${render === FORM_ITEM_RENDER_ENUM.SELECT || render === FORM_ITEM_RENDER_ENUM.RADIO || render === FORM_ITEM_RENDER_ENUM.CHECKBOX ? "选择" : "输入"}${option.label}`;
131
+
132
+ switch (render) {
133
+ case FORM_ITEM_RENDER_ENUM.INPUT:
134
+ return <Input placeholder={placeholder} {...componentProps} />;
135
+
136
+ case FORM_ITEM_RENDER_ENUM.TEXTAREA:
137
+ return <TextArea placeholder={placeholder} rows={3} {...componentProps} />;
138
+
139
+ case FORM_ITEM_RENDER_ENUM.INPUT_NUMBER:
140
+ case FORM_ITEM_RENDER_ENUM.NUMBER:
141
+ return <InputNumber placeholder={placeholder} style={{ width: "100%" }} {...componentProps} />;
142
+
143
+ case FORM_ITEM_RENDER_ENUM.SELECT:
144
+ return (
145
+ <Select placeholder={placeholder} {...componentProps}>
146
+ {(option.items || []).map((item) => {
147
+ const value = item[itemsFieldKey.valueKey] ?? item.dictionariesId ?? item.id;
148
+ const label = item[itemsFieldKey.labelKey] ?? item.name;
149
+ return (
150
+ <Select.Option key={value} value={value}>
151
+ {label}
152
+ </Select.Option>
153
+ );
154
+ })}
155
+ </Select>
156
+ );
157
+
158
+ case FORM_ITEM_RENDER_ENUM.RADIO:
159
+ return (
160
+ <Radio.Group {...componentProps}>
161
+ {(option.items || []).map((item) => {
162
+ const value = item[itemsFieldKey.valueKey] ?? item.dictionariesId ?? item.id;
163
+ const label = item[itemsFieldKey.labelKey] ?? item.name;
164
+ return (
165
+ <Radio key={value} value={value}>
166
+ {label}
167
+ </Radio>
168
+ );
169
+ })}
170
+ </Radio.Group>
171
+ );
172
+
173
+ case FORM_ITEM_RENDER_ENUM.CHECKBOX:
174
+ return (
175
+ <Checkbox.Group {...componentProps}>
176
+ {(option.items || []).map((item) => {
177
+ const value = item[itemsFieldKey.valueKey] ?? item.dictionariesId ?? item.id;
178
+ const label = item[itemsFieldKey.labelKey] ?? item.name;
179
+ return (
180
+ <Checkbox key={value} value={value}>
181
+ {label}
182
+ </Checkbox>
183
+ );
184
+ })}
185
+ </Checkbox.Group>
186
+ );
187
+
188
+ case FORM_ITEM_RENDER_ENUM.DATE:
189
+ return <DatePicker placeholder={placeholder} format="YYYY-MM-DD" style={{ width: "100%" }} {...componentProps} />;
190
+
191
+ case FORM_ITEM_RENDER_ENUM.DATE_MONTH:
192
+ return <DatePicker picker="month" placeholder={placeholder} format="YYYY-MM" style={{ width: "100%" }} {...componentProps} />;
193
+
194
+ case FORM_ITEM_RENDER_ENUM.DATE_YEAR:
195
+ return <DatePicker picker="year" placeholder={placeholder} format="YYYY" style={{ width: "100%" }} {...componentProps} />;
196
+
197
+ case FORM_ITEM_RENDER_ENUM.DATE_WEEK:
198
+ return <DatePicker picker="week" placeholder={placeholder} format="YYYY-wo" style={{ width: "100%" }} {...componentProps} />;
199
+
200
+ case FORM_ITEM_RENDER_ENUM.DATE_RANGE:
201
+ return (
202
+ <RangePicker
203
+ placeholder={[`请选择开始${option.label}`, `请选择结束${option.label}`]}
204
+ format="YYYY-MM-DD"
205
+ style={{ width: "100%" }}
206
+ {...componentProps}
207
+ />
208
+ );
209
+
210
+ case FORM_ITEM_RENDER_ENUM.DATETIME:
211
+ return (
212
+ <DatePicker
213
+ showTime
214
+ placeholder={placeholder}
215
+ format="YYYY-MM-DD HH:mm:ss"
216
+ style={{ width: "100%" }}
217
+ {...componentProps}
218
+ />
219
+ );
220
+
221
+ case FORM_ITEM_RENDER_ENUM.DATETIME_RANGE:
222
+ return (
223
+ <RangePicker
224
+ showTime
225
+ placeholder={[`请选择开始${option.label}`, `请选择结束${option.label}`]}
226
+ format="YYYY-MM-DD HH:mm:ss"
227
+ style={{ width: "100%" }}
228
+ {...componentProps}
229
+ />
230
+ );
231
+
232
+ case FORM_ITEM_RENDER_ENUM.DIVIDER:
233
+ return null;
234
+
235
+ default:
236
+ // 支持传入自定义组件
237
+ if (typeof render === "function") {
238
+ const CustomComponent = render;
239
+ return <CustomComponent {...componentProps} />;
240
+ }
241
+ return <Input placeholder={placeholder} {...componentProps} />;
242
+ }
243
+ };
244
+
245
+ // 渲染 label(带提示)
246
+ const renderLabel = (option) => {
247
+ if (!option.tip)
248
+ return option.label;
249
+
250
+ return (
251
+ <>
252
+ {option.label}
253
+ <Tooltip title={option.tip}>
254
+ <InfoCircleOutlined style={{ marginLeft: 4, fontSize: 12 }} />
255
+ </Tooltip>
256
+ </>
257
+ );
258
+ };
259
+
260
+ return (
261
+ <>
262
+ {options.map((option, index) => {
263
+ const itemSpan = option.render === FORM_ITEM_RENDER_ENUM.DIVIDER ? 24 : option.span ?? span;
264
+ // 使用 style 控制显示/隐藏
265
+ const style = collapse && index >= 3 ? { display: "none" } : undefined;
266
+
267
+ // 如果是分割线
268
+ if (option.render === FORM_ITEM_RENDER_ENUM.DIVIDER) {
269
+ return (
270
+ <Col key={option.name || index} span={itemSpan} style={style}>
271
+ <Divider orientation="left">{option.label}</Divider>
272
+ </Col>
273
+ );
274
+ }
275
+
276
+ // 如果配置了 shouldUpdate 或 dependencies,使用 Form.Item 的联动机制
277
+ if (option.shouldUpdate || option.dependencies || option?.componentProps?.shouldUpdate || option?.componentProps?.dependencies) {
278
+ return (
279
+ option.customizeRender
280
+ ? (renderFormControl(option))
281
+ : (
282
+ <Col key={option.name || index} span={itemSpan} style={style}>
283
+ <Form.Item
284
+ noStyle
285
+ shouldUpdate={option.shouldUpdate || option?.componentProps?.shouldUpdate}
286
+ dependencies={option.dependencies || option?.componentProps?.dependencies}
287
+ >
288
+ {(form) => {
289
+ // 支持动态计算 hidden
290
+ const hidden = typeof option.hidden === "function"
291
+ ? option.hidden(form.getFieldsValue())
292
+ : (option.hidden || false);
293
+
294
+ if (hidden)
295
+ return null;
296
+
297
+ return (
298
+ <Form.Item
299
+ name={option.name}
300
+ label={renderLabel(option)}
301
+ rules={getRules(option)}
302
+ labelCol={option.labelCol}
303
+ wrapperCol={option.wrapperCol}
304
+ {...getFormItemProps(option)}
305
+ >
306
+ {renderFormControl(option)}
307
+ </Form.Item>
308
+ );
309
+ }}
310
+ </Form.Item>
311
+ </Col>
312
+ )
313
+ );
314
+ }
315
+
316
+ // 普通表单项(静态配置)
317
+ if (option.hidden)
318
+ return null;
319
+
320
+ return (
321
+ option.customizeRender
322
+ ? (renderFormControl(option))
323
+ : (
324
+ <Col key={option.name || index} span={itemSpan} style={style}>
325
+ <Form.Item
326
+ name={option.name}
327
+ label={renderLabel(option)}
328
+ rules={getRules(option)}
329
+ labelCol={option.labelCol}
330
+ wrapperCol={option.wrapperCol}
331
+ {...getFormItemProps(option)}
332
+ >
333
+ {renderFormControl(option)}
334
+ </Form.Item>
335
+ </Col>
336
+ )
337
+ );
338
+ })}
339
+ </>
340
+ );
341
+ };
342
+
343
+ FormItemsRenderer.displayName = "FormItemsRenderer";
344
+
345
+ export default FormItemsRenderer;
@@ -0,0 +1,3 @@
1
+ import FormBuilder from "./FormBuilder";
2
+
3
+ export default FormBuilder;
@@ -0,0 +1,3 @@
1
+ import FormBuilder from "./FormBuilder";
2
+
3
+ export default FormBuilder;
@@ -0,0 +1,12 @@
1
+ import type { FC } from "react";
2
+
3
+ export interface PreviewImgProps {
4
+ /** 图片列表 */
5
+ files: any[];
6
+ /** 图片地址的key,默认 filePath */
7
+ fileUrlKey?: string;
8
+ }
9
+
10
+ declare const PreviewImg: FC<PreviewImgProps>;
11
+
12
+ export default PreviewImg;
@@ -0,0 +1,26 @@
1
+ import { Image } from "antd";
2
+ import { getFileUrl } from "../../utils/index";
3
+
4
+ const PreviewImg = (props) => {
5
+ const { files = [], fileUrlKey = "filePath" } = props;
6
+ const fileUrl = getFileUrl();
7
+
8
+ return (
9
+ <Image.PreviewGroup>
10
+ {
11
+ files.filter(Boolean).map((item, index) => (
12
+ <Image
13
+ key={item[fileUrlKey] || item}
14
+ src={item[fileUrlKey] ? fileUrl + item[fileUrlKey] : fileUrl + item}
15
+ style={{ marginLeft: index > 0 ? 10 : 0 }}
16
+ width={100}
17
+ height={100}
18
+ alt=""
19
+ />
20
+ ))
21
+ }
22
+ </Image.PreviewGroup>
23
+ );
24
+ };
25
+
26
+ export default PreviewImg;
@@ -0,0 +1,41 @@
1
+ import type { FormInstance, FormProps } from "antd/es/form";
2
+ import type { FC, ReactNode } from "react";
3
+ import type { FormOption } from "../FormBuilder/FormItemsRenderer";
4
+
5
+ type FormValues = Record<string, any>;
6
+
7
+ /**
8
+ * Search 组件属性
9
+ */
10
+ export interface SearchProps extends Omit<FormProps, "form" | "onFinish"> {
11
+ /** 表单配置项数组 */
12
+ options: FormOption[];
13
+ /** 表单值 */
14
+ values?: FormValues;
15
+ /** 搜索和重置都会触发的回调 */
16
+ onFinish?: (values: FormValues, type: "submit" | "reset") => void;
17
+ /** 搜索回调 */
18
+ onSubmit?: (values: FormValues) => void;
19
+ /** 重置回调 */
20
+ onReset?: (values: FormValues) => void;
21
+ /** 搜索按钮文本,默认"搜索" */
22
+ searchText?: string;
23
+ /** 重置按钮文本,默认"重置" */
24
+ resetText?: string;
25
+ /** 是否显示搜索按钮,默认 true */
26
+ showSearchButton?: boolean;
27
+ /** 是否显示重置按钮,默认 true */
28
+ showResetButton?: boolean;
29
+ /** 额外的底部按钮组 */
30
+ extraButtons?: ReactNode;
31
+ /** 表单实例(通过 Form.useForm() 创建) */
32
+ form: FormInstance;
33
+ }
34
+
35
+ /**
36
+ * 搜索表单组件
37
+ * 支持自动展开/收起功能,当表单项超过4个时显示展开/收起按钮
38
+ */
39
+ declare const Search: FC<SearchProps>;
40
+
41
+ export default Search;