conditional-selection 1.1.4 → 1.1.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/README.md +166 -132
- package/README.zh-CN.md +197 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,163 +1,197 @@
|
|
|
1
|
-
#
|
|
1
|
+
# conditional-selection
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
<p>
|
|
4
|
+
<a href="./README.md">English</a> | <a href="./README.zh-CN.md">中文</a>
|
|
5
|
+
</p>
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
- 条件树递归编辑
|
|
8
|
-
- 条件分组(AND/OR)切换
|
|
9
|
-
- 条件项动态增删
|
|
10
|
-
- 层级/数量/禁用等参数控制
|
|
11
|
-
- 插槽自定义条件行内容
|
|
12
|
-
- 状态管理采用 useImmer,数据流统一
|
|
7
|
+
A lightweight React component library for building conditional rule trees. Supports recursive nesting, AND/OR grouping, dynamic add/remove, and fully customizable condition rows via render props.
|
|
13
8
|
|
|
14
|
-
##
|
|
9
|
+
## Installation
|
|
15
10
|
|
|
16
|
-
|
|
11
|
+
```bash
|
|
12
|
+
npm install conditional-selection
|
|
13
|
+
```
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
import React, { useMemo, useRef, useState } from 'react';
|
|
20
|
-
import { ConditionalSelection } from '../packages/ConditionalSelection';
|
|
21
|
-
import type { TConditionalSelection } from '../packages/ConditionalSelection/types';
|
|
22
|
-
|
|
23
|
-
const fieldOptions = [
|
|
24
|
-
{ label: '测试字段1', value: 1 },
|
|
25
|
-
{ label: '测试字段2', value: 2 },
|
|
26
|
-
];
|
|
27
|
-
const functionOptions = [
|
|
28
|
-
{ label: '等于', value: 1 },
|
|
29
|
-
{ label: '不等于', value: 2 },
|
|
30
|
-
];
|
|
31
|
-
const valueOptions = [
|
|
32
|
-
{ label: '测试值1', value: 1 },
|
|
33
|
-
{ label: '测试值2', value: 2 },
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
const FlexSelect: React.FC<{ item: TConditionalSelection; onChange: (val: Record<string, any>) => void }> = ({ item, onChange }) => {
|
|
37
|
-
// field / function / value 存放在节点的 individual 里
|
|
38
|
-
const data = useMemo(() => item.individual ?? {}, [item.individual]);
|
|
39
|
-
|
|
40
|
-
const handleFieldChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
41
|
-
const val = e.target.value ? Number(e.target.value) : undefined;
|
|
42
|
-
onChange({ field: val, function: '', value: undefined });
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const handleFunctionChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
46
|
-
const val = e.target.value ? Number(e.target.value) : undefined;
|
|
47
|
-
onChange({ ...data, function: val, value: undefined });
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const handleValueChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
51
|
-
const val = e.target.value ? Number(e.target.value) : undefined;
|
|
52
|
-
onChange({ ...data, value: val });
|
|
53
|
-
};
|
|
15
|
+
> **Peer dependencies:** `react >= 16.8.0`, `react-dom >= 16.8.0`
|
|
54
16
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
61
|
-
))}
|
|
62
|
-
</select>
|
|
63
|
-
{data.field && (
|
|
64
|
-
<select style={{ width: '33%', marginRight: 16 }} value={data.function ?? ''} onChange={handleFunctionChange}>
|
|
65
|
-
<option value="">请选择函数</option>
|
|
66
|
-
{functionOptions.map(opt => (
|
|
67
|
-
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
68
|
-
))}
|
|
69
|
-
</select>
|
|
70
|
-
)}
|
|
71
|
-
{data.function && (
|
|
72
|
-
<select style={{ width: '33%', marginRight: 16 }} value={data.value ?? ''} onChange={handleValueChange}>
|
|
73
|
-
<option value="">请选择值</option>
|
|
74
|
-
{valueOptions.map(opt => (
|
|
75
|
-
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
76
|
-
))}
|
|
77
|
-
</select>
|
|
78
|
-
)}
|
|
79
|
-
</div>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { ConditionalSelection } from 'conditional-selection';
|
|
21
|
+
import type { TConditionalSelection } from 'conditional-selection';
|
|
82
22
|
|
|
83
23
|
export default function App() {
|
|
84
|
-
const refConditionalSelection = useRef<any>(null);
|
|
85
24
|
const [rules, setRules] = useState<TConditionalSelection | undefined>();
|
|
86
|
-
const [maxDeep, setMaxLevel] = useState(3);
|
|
87
|
-
const [disabled, setDisabled] = useState(false);
|
|
88
|
-
const [disabledConfig, setDisabledConfig] = useState({
|
|
89
|
-
addItem: false,
|
|
90
|
-
delItem: false,
|
|
91
|
-
linkChange: false,
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const getResult = async () => {
|
|
95
|
-
const data = await refConditionalSelection.current.getConditionalSelectionData()
|
|
96
|
-
console.log('当前数据:', data);
|
|
97
|
-
}
|
|
98
25
|
|
|
99
26
|
return (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
onChange={
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
item={item}
|
|
109
|
-
onChange={val => change(val)}
|
|
110
|
-
/>
|
|
111
|
-
)}
|
|
112
|
-
/>
|
|
27
|
+
<ConditionalSelection
|
|
28
|
+
conditionalRules={rules}
|
|
29
|
+
maxDeep={3}
|
|
30
|
+
onChange={setRules}
|
|
31
|
+
renderConditionRules={(item, change) => (
|
|
32
|
+
<MyConditionRow item={item} onChange={change} />
|
|
33
|
+
)}
|
|
34
|
+
/>
|
|
113
35
|
);
|
|
114
36
|
}
|
|
115
37
|
```
|
|
116
38
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
39
|
+
## Props
|
|
40
|
+
|
|
41
|
+
| Prop | Type | Default | Description |
|
|
42
|
+
|------|------|---------|-------------|
|
|
43
|
+
| `conditionalRules` | `TConditionalSelection \| null` | `undefined` | The condition tree data. Automatically initialized if `null` or `undefined`. |
|
|
44
|
+
| `maxDeep` | `number` | `1` | Maximum recursion depth. Must be greater than `0`. |
|
|
45
|
+
| `disabled` | `boolean \| TConditionalSelectionDisabledProps` | `false` | Disable all or specific operations. |
|
|
46
|
+
| `onChange` | `(value: TConditionalSelection \| undefined) => void` | — | Callback fired when the tree data changes. |
|
|
47
|
+
| `renderConditionRules` | `(item, change) => ReactNode` | — | Render prop for each condition row. Receives the current `INDIVIDUAL` node and an update handler. |
|
|
48
|
+
| `renderCreateCondition` | `(params) => ReactNode` | — | Render prop to customize the "Add condition" button. |
|
|
49
|
+
|
|
50
|
+
### `disabled` object shape
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
type TConditionalSelectionDisabledProps = {
|
|
54
|
+
addItem?: boolean; // disable adding new conditions
|
|
55
|
+
delItem?: boolean; // disable deleting conditions
|
|
56
|
+
linkChange?: boolean; // disable AND/OR toggle
|
|
57
|
+
};
|
|
58
|
+
```
|
|
125
59
|
|
|
126
|
-
|
|
60
|
+
## Data Structure
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
type TConditionalSelection<T = any> = {
|
|
64
|
+
_id: string;
|
|
65
|
+
framework: 'group' | 'individual';
|
|
66
|
+
link?: 'and' | 'or';
|
|
67
|
+
group?: TConditionalSelection<T>[];
|
|
68
|
+
individual?: Record<string, any>;
|
|
69
|
+
level: number;
|
|
70
|
+
};
|
|
71
|
+
```
|
|
127
72
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
73
|
+
- **`group`** node — contains child nodes and an AND/OR relationship toggle.
|
|
74
|
+
- **`individual`** node — a single condition row; its data lives in the `individual` field.
|
|
75
|
+
|
|
76
|
+
### Example Output
|
|
77
|
+
|
|
78
|
+
**Flat structure** — two `individual` conditions joined by AND:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"_id": "2dfd8d9d-4bbf-4bc3-aae6-6c747ccbdd10",
|
|
83
|
+
"framework": "group",
|
|
84
|
+
"level": 0,
|
|
85
|
+
"link": "and",
|
|
86
|
+
"group": [
|
|
87
|
+
{
|
|
88
|
+
"_id": "02399b6c-10a3-41d4-9722-cce2db9ae056",
|
|
89
|
+
"framework": "individual",
|
|
90
|
+
"individual": { "field": 1, "function": 1, "value": 1 },
|
|
91
|
+
"level": 1
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"_id": "6c627903-b970-43a8-a3f5-f5aba89f2200",
|
|
95
|
+
"framework": "individual",
|
|
96
|
+
"individual": { "field": 1, "function": 1, "value": 1 },
|
|
97
|
+
"level": 1
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
136
101
|
```
|
|
137
102
|
|
|
138
|
-
|
|
103
|
+
**Nested structure** — an `individual` AND a nested `group` (OR) at level 1:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"_id": "2dfd8d9d-4bbf-4bc3-aae6-6c747ccbdd10",
|
|
108
|
+
"framework": "group",
|
|
109
|
+
"level": 0,
|
|
110
|
+
"link": "and",
|
|
111
|
+
"group": [
|
|
112
|
+
{
|
|
113
|
+
"_id": "02399b6c-10a3-41d4-9722-cce2db9ae056",
|
|
114
|
+
"framework": "individual",
|
|
115
|
+
"individual": { "field": 1, "function": 1, "value": 1 },
|
|
116
|
+
"level": 1
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"_id": "2f808fe6-4dd8-488d-9b79-d2888915fddc",
|
|
120
|
+
"framework": "group",
|
|
121
|
+
"level": 1,
|
|
122
|
+
"link": "or",
|
|
123
|
+
"group": [
|
|
124
|
+
{
|
|
125
|
+
"_id": "17336bf2-2885-4350-8055-233b0058ec39",
|
|
126
|
+
"framework": "individual",
|
|
127
|
+
"individual": { "field": 1, "function": 1, "value": 1 },
|
|
128
|
+
"level": 2
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"_id": "6e21bd9a-f994-474a-bba5-d77ac9013a65",
|
|
132
|
+
"framework": "individual",
|
|
133
|
+
"individual": { "field": 1, "function": 1, "value": 1 },
|
|
134
|
+
"level": 2
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
```
|
|
139
141
|
|
|
140
|
-
|
|
142
|
+
## Accessing Data via Ref
|
|
141
143
|
|
|
142
144
|
```tsx
|
|
143
|
-
const ref = useRef<any>();
|
|
144
|
-
|
|
145
|
+
const ref = useRef<any>(null);
|
|
146
|
+
|
|
145
147
|
<ConditionalSelection ref={ref} ... />
|
|
146
|
-
|
|
148
|
+
|
|
149
|
+
// Returns a deep clone of the current tree
|
|
147
150
|
const data = await ref.current.getConditionalSelectionData();
|
|
148
151
|
```
|
|
149
152
|
|
|
150
|
-
##
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
## Custom Condition Row Example
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
const MyConditionRow = ({ item, onChange }) => {
|
|
157
|
+
const data = item.individual ?? {};
|
|
158
|
+
return (
|
|
159
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
160
|
+
<select value={data.field ?? ''} onChange={e => onChange({ field: e.target.value })}>
|
|
161
|
+
<option value="">Select field</option>
|
|
162
|
+
<option value="name">Name</option>
|
|
163
|
+
</select>
|
|
164
|
+
<select value={data.operator ?? ''} onChange={e => onChange({ ...data, operator: e.target.value })}>
|
|
165
|
+
<option value="">Select operator</option>
|
|
166
|
+
<option value="eq">Equals</option>
|
|
167
|
+
<option value="ne">Not equals</option>
|
|
168
|
+
</select>
|
|
169
|
+
<input
|
|
170
|
+
value={data.value ?? ''}
|
|
171
|
+
onChange={e => onChange({ ...data, value: e.target.value })}
|
|
172
|
+
placeholder="Value"
|
|
173
|
+
/>
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Performance Tips
|
|
154
180
|
|
|
155
|
-
|
|
181
|
+
- Wrap handlers and render props with `useCallback` to avoid unnecessary re-renders.
|
|
182
|
+
- Use [React Profiler](https://react.dev/reference/react/Profiler) for deeper performance analysis.
|
|
156
183
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
184
|
+
## Contributing
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
git clone https://github.com/1512-Yolo/conditional-selection.git
|
|
188
|
+
cd conditional-selection
|
|
189
|
+
npm install
|
|
190
|
+
npm run dev
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Components are exported from `packages/index.ts`. Styles are written in Less (`packages/ConditionalSelection/index.less`).
|
|
161
194
|
|
|
162
195
|
## License
|
|
196
|
+
|
|
163
197
|
MIT
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# conditional-selection
|
|
2
|
+
|
|
3
|
+
<p>
|
|
4
|
+
<a href="./README.md">English</a> | <a href="./README.zh-CN.md">中文</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
一个轻量级的 React 条件规则树组件库,支持递归嵌套、AND/OR 分组、动态增删,以及通过 render props 完全自定义条件行内容。
|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install conditional-selection
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
> **同级依赖(peerDependencies):** `react >= 16.8.0`,`react-dom >= 16.8.0`
|
|
16
|
+
|
|
17
|
+
## 快速开始
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { ConditionalSelection } from 'conditional-selection';
|
|
21
|
+
import type { TConditionalSelection } from 'conditional-selection';
|
|
22
|
+
|
|
23
|
+
export default function App() {
|
|
24
|
+
const [rules, setRules] = useState<TConditionalSelection | undefined>();
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<ConditionalSelection
|
|
28
|
+
conditionalRules={rules}
|
|
29
|
+
maxDeep={3}
|
|
30
|
+
onChange={setRules}
|
|
31
|
+
renderConditionRules={(item, change) => (
|
|
32
|
+
<MyConditionRow item={item} onChange={change} />
|
|
33
|
+
)}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Props 参数说明
|
|
40
|
+
|
|
41
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
42
|
+
|------|------|--------|------|
|
|
43
|
+
| `conditionalRules` | `TConditionalSelection \| null` | `undefined` | 条件树数据,传入 `null` 或 `undefined` 时自动初始化 |
|
|
44
|
+
| `maxDeep` | `number` | `1` | 最大递归层级,必须大于 `0` |
|
|
45
|
+
| `disabled` | `boolean \| TConditionalSelectionDisabledProps` | `false` | 禁用全部或部分操作 |
|
|
46
|
+
| `onChange` | `(value: TConditionalSelection \| undefined) => void` | — | 数据变更回调 |
|
|
47
|
+
| `renderConditionRules` | `(item, change) => ReactNode` | — | 条件行渲染插槽,参数为当前 `INDIVIDUAL` 节点和更新 handler |
|
|
48
|
+
| `renderCreateCondition` | `(params) => ReactNode` | — | 自定义"添加条件"按钮的渲染插槽 |
|
|
49
|
+
|
|
50
|
+
### `disabled` 对象格式
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
type TConditionalSelectionDisabledProps = {
|
|
54
|
+
addItem?: boolean; // 禁用添加条件
|
|
55
|
+
delItem?: boolean; // 禁用删除条件
|
|
56
|
+
linkChange?: boolean; // 禁用 AND/OR 切换
|
|
57
|
+
};
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 数据结构
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
type TConditionalSelection<T = any> = {
|
|
64
|
+
_id: string;
|
|
65
|
+
framework: 'group' | 'individual';
|
|
66
|
+
link?: 'and' | 'or';
|
|
67
|
+
group?: TConditionalSelection<T>[];
|
|
68
|
+
individual?: Record<string, any>;
|
|
69
|
+
level: number;
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- **`group`** 节点 — 包含子节点,支持 AND/OR 关系切换
|
|
74
|
+
- **`individual`** 节点 — 单个条件行,自定义数据存放在 `individual` 字段中
|
|
75
|
+
|
|
76
|
+
### 数据示例
|
|
77
|
+
|
|
78
|
+
**扁平结构** — 两个 `individual` 条件以 AND 连接:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"_id": "2dfd8d9d-4bbf-4bc3-aae6-6c747ccbdd10",
|
|
83
|
+
"framework": "group",
|
|
84
|
+
"level": 0,
|
|
85
|
+
"link": "and",
|
|
86
|
+
"group": [
|
|
87
|
+
{
|
|
88
|
+
"_id": "02399b6c-10a3-41d4-9722-cce2db9ae056",
|
|
89
|
+
"framework": "individual",
|
|
90
|
+
"individual": { "field": 1, "function": 1, "value": 1 },
|
|
91
|
+
"level": 1
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"_id": "6c627903-b970-43a8-a3f5-f5aba89f2200",
|
|
95
|
+
"framework": "individual",
|
|
96
|
+
"individual": { "field": 1, "function": 1, "value": 1 },
|
|
97
|
+
"level": 1
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**嵌套结构** — 一个 `individual` 与一个嵌套 `group`(OR)并列于第 1 层:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"_id": "2dfd8d9d-4bbf-4bc3-aae6-6c747ccbdd10",
|
|
108
|
+
"framework": "group",
|
|
109
|
+
"level": 0,
|
|
110
|
+
"link": "and",
|
|
111
|
+
"group": [
|
|
112
|
+
{
|
|
113
|
+
"_id": "02399b6c-10a3-41d4-9722-cce2db9ae056",
|
|
114
|
+
"framework": "individual",
|
|
115
|
+
"individual": { "field": 1, "function": 1, "value": 1 },
|
|
116
|
+
"level": 1
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"_id": "2f808fe6-4dd8-488d-9b79-d2888915fddc",
|
|
120
|
+
"framework": "group",
|
|
121
|
+
"level": 1,
|
|
122
|
+
"link": "or",
|
|
123
|
+
"group": [
|
|
124
|
+
{
|
|
125
|
+
"_id": "17336bf2-2885-4350-8055-233b0058ec39",
|
|
126
|
+
"framework": "individual",
|
|
127
|
+
"individual": { "field": 1, "function": 1, "value": 1 },
|
|
128
|
+
"level": 2
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"_id": "6e21bd9a-f994-474a-bba5-d77ac9013a65",
|
|
132
|
+
"framework": "individual",
|
|
133
|
+
"individual": { "field": 1, "function": 1, "value": 1 },
|
|
134
|
+
"level": 2
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## 通过 Ref 获取数据
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
const ref = useRef<any>(null);
|
|
146
|
+
|
|
147
|
+
<ConditionalSelection ref={ref} ... />
|
|
148
|
+
|
|
149
|
+
// 返回当前条件树的深拷贝
|
|
150
|
+
const data = await ref.current.getConditionalSelectionData();
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## 自定义条件行示例
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
const MyConditionRow = ({ item, onChange }) => {
|
|
157
|
+
const data = item.individual ?? {};
|
|
158
|
+
return (
|
|
159
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
160
|
+
<select value={data.field ?? ''} onChange={e => onChange({ field: e.target.value })}>
|
|
161
|
+
<option value="">选择字段</option>
|
|
162
|
+
<option value="name">名称</option>
|
|
163
|
+
</select>
|
|
164
|
+
<select value={data.operator ?? ''} onChange={e => onChange({ ...data, operator: e.target.value })}>
|
|
165
|
+
<option value="">选择操作符</option>
|
|
166
|
+
<option value="eq">等于</option>
|
|
167
|
+
<option value="ne">不等于</option>
|
|
168
|
+
</select>
|
|
169
|
+
<input
|
|
170
|
+
value={data.value ?? ''}
|
|
171
|
+
onChange={e => onChange({ ...data, value: e.target.value })}
|
|
172
|
+
placeholder="输入值"
|
|
173
|
+
/>
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## 性能优化建议
|
|
180
|
+
|
|
181
|
+
- 将 handler 和 render props 用 `useCallback` 包裹,避免不必要的重渲染
|
|
182
|
+
- 如需深度性能分析,可借助 [React Profiler](https://react.dev/reference/react/Profiler)
|
|
183
|
+
|
|
184
|
+
## 贡献与开发
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
git clone https://github.com/1512-Yolo/conditional-selection.git
|
|
188
|
+
cd conditional-selection
|
|
189
|
+
npm install
|
|
190
|
+
npm run dev
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
组件统一从 `packages/index.ts` 导出,样式使用 Less(`packages/ConditionalSelection/index.less`)编写。
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
MIT
|