@alicloud/appflow-chat 0.0.1-beta.1
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-ZH.md +188 -0
- package/README.md +190 -0
- package/dist/appflow-chat.cjs.js +1903 -0
- package/dist/appflow-chat.esm.js +36965 -0
- package/dist/types/index.d.ts +862 -0
- package/package.json +87 -0
- package/src/components/DocReferences.tsx +64 -0
- package/src/components/HumanVerify/CustomParamsRenderer/ArrayField.tsx +394 -0
- package/src/components/HumanVerify/CustomParamsRenderer/FieldRenderer.tsx +202 -0
- package/src/components/HumanVerify/CustomParamsRenderer/ObjectField.tsx +126 -0
- package/src/components/HumanVerify/CustomParamsRenderer/index.tsx +166 -0
- package/src/components/HumanVerify/CustomParamsRenderer/types.ts +203 -0
- package/src/components/HumanVerify/HistoryCard.tsx +156 -0
- package/src/components/HumanVerify/HumanVerify.tsx +184 -0
- package/src/components/HumanVerify/index.ts +11 -0
- package/src/components/MarkdownRenderer.tsx +195 -0
- package/src/components/MessageBubble.tsx +400 -0
- package/src/components/RichMessageBubble.tsx +283 -0
- package/src/components/WebSearchPanel.tsx +68 -0
- package/src/context/RichBubble.tsx +21 -0
- package/src/core/BubbleContent.tsx +75 -0
- package/src/core/RichBubbleContent.tsx +324 -0
- package/src/core/SourceContent.tsx +285 -0
- package/src/core/WebSearchContent.tsx +219 -0
- package/src/core/index.ts +16 -0
- package/src/hooks/usePreSignUpload.ts +36 -0
- package/src/index.ts +80 -0
- package/src/markdown/components/Chart.tsx +120 -0
- package/src/markdown/components/Error.tsx +39 -0
- package/src/markdown/components/FileDisplay.tsx +246 -0
- package/src/markdown/components/Loading.tsx +41 -0
- package/src/markdown/components/SyntaxHighlight.tsx +182 -0
- package/src/markdown/index.tsx +250 -0
- package/src/markdown/styled.ts +234 -0
- package/src/markdown/utils/dataProcessor.ts +89 -0
- package/src/services/ChatService.ts +926 -0
- package/src/utils/fetchEventSource.ts +65 -0
- package/src/utils/loadEcharts.ts +32 -0
- package/src/utils/loadPrism.ts +156 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import React, { useCallback, useContext } from 'react';
|
|
2
|
+
import { Input, InputNumber, Switch, ConfigProvider } from 'antd';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import { FieldRendererProps } from './types';
|
|
5
|
+
import { ArrayField } from './ArrayField';
|
|
6
|
+
import { ObjectField } from './ObjectField';
|
|
7
|
+
|
|
8
|
+
// ==================== Styled Components ====================
|
|
9
|
+
|
|
10
|
+
const FieldItem = styled.div`
|
|
11
|
+
margin-bottom: 16px;
|
|
12
|
+
|
|
13
|
+
&:last-child {
|
|
14
|
+
margin-bottom: 0;
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const FieldLabel = styled.div`
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
margin-bottom: 6px;
|
|
22
|
+
font-size: 14px;
|
|
23
|
+
color: #1f2329;
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
const Required = styled.span`
|
|
27
|
+
color: #f54a45;
|
|
28
|
+
margin-right: 4px;
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
const LabelText = styled.span`
|
|
32
|
+
font-weight: 500;
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
const FieldDescription = styled.div`
|
|
36
|
+
font-size: 12px;
|
|
37
|
+
color: #8f959e;
|
|
38
|
+
margin-top: 4px;
|
|
39
|
+
word-break: break-word;
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
const FieldError = styled.div`
|
|
43
|
+
font-size: 12px;
|
|
44
|
+
color: #f54a45;
|
|
45
|
+
margin-top: 4px;
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* InputWrapper 组件
|
|
50
|
+
* 支持动态 prefixCls,自动继承用户项目的 ConfigProvider 配置
|
|
51
|
+
*/
|
|
52
|
+
const InputWrapper = styled.div<{ $prefixCls: string }>`
|
|
53
|
+
.${props => props.$prefixCls}-input,
|
|
54
|
+
.${props => props.$prefixCls}-input-number,
|
|
55
|
+
.${props => props.$prefixCls}-select {
|
|
56
|
+
width: 100%;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.${props => props.$prefixCls}-input-number {
|
|
60
|
+
width: 100%;
|
|
61
|
+
}
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 单个字段渲染器
|
|
66
|
+
* 根据字段类型渲染对应的输入组件
|
|
67
|
+
*/
|
|
68
|
+
export const FieldRenderer: React.FC<FieldRendererProps> = ({
|
|
69
|
+
name,
|
|
70
|
+
schema,
|
|
71
|
+
value,
|
|
72
|
+
onChange,
|
|
73
|
+
required = false,
|
|
74
|
+
disabled = false,
|
|
75
|
+
level = 0,
|
|
76
|
+
errors = {},
|
|
77
|
+
fieldPath = '',
|
|
78
|
+
}) => {
|
|
79
|
+
const { Type, Title, Description } = schema;
|
|
80
|
+
const displayTitle = Title || name;
|
|
81
|
+
|
|
82
|
+
// 获取 Ant Design 的 prefixCls 配置,自动继承用户项目的 ConfigProvider 设置
|
|
83
|
+
// 使用 ConfigProvider.ConfigContext 获取完整配置
|
|
84
|
+
const configContext = useContext(ConfigProvider.ConfigContext);
|
|
85
|
+
const prefixCls = configContext.getPrefixCls?.() || 'ant';
|
|
86
|
+
|
|
87
|
+
// 计算当前字段的完整路径
|
|
88
|
+
const currentPath = fieldPath ? `${fieldPath}.${name}` : name;
|
|
89
|
+
// 获取当前字段的错误信息
|
|
90
|
+
const errorMessage = errors[currentPath];
|
|
91
|
+
|
|
92
|
+
// 处理值变化
|
|
93
|
+
const handleChange = useCallback(
|
|
94
|
+
(newValue: any) => {
|
|
95
|
+
onChange?.(newValue);
|
|
96
|
+
},
|
|
97
|
+
[onChange]
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// 渲染基础类型输入框
|
|
101
|
+
const renderInput = () => {
|
|
102
|
+
switch (Type) {
|
|
103
|
+
case 'string':
|
|
104
|
+
return (
|
|
105
|
+
<InputWrapper $prefixCls={prefixCls}>
|
|
106
|
+
<Input
|
|
107
|
+
value={value}
|
|
108
|
+
onChange={(e) => handleChange(e.target.value)}
|
|
109
|
+
disabled={disabled}
|
|
110
|
+
/>
|
|
111
|
+
</InputWrapper>
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
case 'number':
|
|
115
|
+
return (
|
|
116
|
+
<InputWrapper $prefixCls={prefixCls}>
|
|
117
|
+
<InputNumber
|
|
118
|
+
value={value}
|
|
119
|
+
onChange={handleChange}
|
|
120
|
+
disabled={disabled}
|
|
121
|
+
style={{ width: '100%' }}
|
|
122
|
+
/>
|
|
123
|
+
</InputWrapper>
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
case 'boolean':
|
|
127
|
+
return (
|
|
128
|
+
<Switch
|
|
129
|
+
style={{borderRadius: 16}}
|
|
130
|
+
checked={value}
|
|
131
|
+
onChange={handleChange}
|
|
132
|
+
disabled={disabled}
|
|
133
|
+
/>
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
case 'array':
|
|
137
|
+
return (
|
|
138
|
+
<ArrayField
|
|
139
|
+
name={name}
|
|
140
|
+
schema={schema}
|
|
141
|
+
value={value}
|
|
142
|
+
onChange={handleChange}
|
|
143
|
+
required={required}
|
|
144
|
+
disabled={disabled}
|
|
145
|
+
level={level}
|
|
146
|
+
errors={errors}
|
|
147
|
+
fieldPath={fieldPath}
|
|
148
|
+
/>
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
case 'object':
|
|
152
|
+
return (
|
|
153
|
+
<ObjectField
|
|
154
|
+
name={name}
|
|
155
|
+
schema={schema}
|
|
156
|
+
value={value}
|
|
157
|
+
onChange={handleChange}
|
|
158
|
+
required={required}
|
|
159
|
+
disabled={disabled}
|
|
160
|
+
level={level}
|
|
161
|
+
errors={errors}
|
|
162
|
+
fieldPath={fieldPath}
|
|
163
|
+
/>
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
default:
|
|
167
|
+
return (
|
|
168
|
+
<InputWrapper $prefixCls={prefixCls}>
|
|
169
|
+
<Input
|
|
170
|
+
value={value}
|
|
171
|
+
onChange={(e) => handleChange(e.target.value)}
|
|
172
|
+
disabled={disabled}
|
|
173
|
+
/>
|
|
174
|
+
</InputWrapper>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// 对于 object 和 array 类型,直接渲染,不需要外层包装
|
|
180
|
+
if (Type === 'object' || Type === 'array') {
|
|
181
|
+
return renderInput();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 基础类型渲染带标签的表单项
|
|
185
|
+
return (
|
|
186
|
+
<FieldItem>
|
|
187
|
+
<FieldLabel>
|
|
188
|
+
{required && <Required>*</Required>}
|
|
189
|
+
<LabelText>{displayTitle}</LabelText>
|
|
190
|
+
</FieldLabel>
|
|
191
|
+
{renderInput()}
|
|
192
|
+
{errorMessage && (
|
|
193
|
+
<FieldError>{errorMessage}</FieldError>
|
|
194
|
+
)}
|
|
195
|
+
{Description && !errorMessage && (
|
|
196
|
+
<FieldDescription>{Description}</FieldDescription>
|
|
197
|
+
)}
|
|
198
|
+
</FieldItem>
|
|
199
|
+
);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export default FieldRenderer;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import { ObjectFieldProps } from './types';
|
|
4
|
+
|
|
5
|
+
// 前向声明,避免循环依赖
|
|
6
|
+
const FieldRenderer = React.lazy(() => import('./FieldRenderer'));
|
|
7
|
+
|
|
8
|
+
// ==================== Styled Components ====================
|
|
9
|
+
|
|
10
|
+
const ObjectFieldContainer = styled.div`
|
|
11
|
+
border: 1px solid #dbdbdb;
|
|
12
|
+
border-radius: 10px;
|
|
13
|
+
padding: 24px 16px;
|
|
14
|
+
position: relative;
|
|
15
|
+
margin-top: 20px;
|
|
16
|
+
margin-bottom: 20px;
|
|
17
|
+
background-color: #fff;
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
const ObjectTitle = styled.div`
|
|
21
|
+
position: absolute;
|
|
22
|
+
top: -12px;
|
|
23
|
+
left: 15px;
|
|
24
|
+
background: #fff;
|
|
25
|
+
padding: 2px 10px;
|
|
26
|
+
white-space: nowrap;
|
|
27
|
+
text-overflow: ellipsis;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
word-break: break-all;
|
|
30
|
+
max-width: calc(100% - 30px);
|
|
31
|
+
font-size: 14px;
|
|
32
|
+
font-weight: 500;
|
|
33
|
+
color: #1f2329;
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
const Required = styled.span`
|
|
37
|
+
display: inline-block;
|
|
38
|
+
margin-right: 4px;
|
|
39
|
+
color: #E00000;
|
|
40
|
+
font-size: 14px;
|
|
41
|
+
font-family: SimSun, sans-serif;
|
|
42
|
+
line-height: 1;
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const ObjectDescription = styled.div`
|
|
46
|
+
font-size: 12px;
|
|
47
|
+
color: #8f959e;
|
|
48
|
+
margin-bottom: 12px;
|
|
49
|
+
word-break: break-word;
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
const ObjectContent = styled.div`
|
|
53
|
+
/* 对象内容区域 */
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 对象类型字段渲染器
|
|
58
|
+
*/
|
|
59
|
+
export const ObjectField: React.FC<ObjectFieldProps> = ({
|
|
60
|
+
name,
|
|
61
|
+
schema,
|
|
62
|
+
value = {},
|
|
63
|
+
onChange,
|
|
64
|
+
required = false,
|
|
65
|
+
disabled = false,
|
|
66
|
+
level = 0,
|
|
67
|
+
errors = {},
|
|
68
|
+
fieldPath = '',
|
|
69
|
+
}) => {
|
|
70
|
+
const { Title, Description, Properties, Required: RequiredFields = [] } = schema;
|
|
71
|
+
const displayTitle = Title || name;
|
|
72
|
+
|
|
73
|
+
// 计算当前对象的完整路径
|
|
74
|
+
const currentPath = fieldPath ? `${fieldPath}.${name}` : name;
|
|
75
|
+
|
|
76
|
+
// 更新对象属性
|
|
77
|
+
const handlePropertyChange = useCallback(
|
|
78
|
+
(propertyName: string, propertyValue: any) => {
|
|
79
|
+
onChange?.({
|
|
80
|
+
...value,
|
|
81
|
+
[propertyName]: propertyValue,
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
[value, onChange]
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if (!Properties) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<ObjectFieldContainer>
|
|
93
|
+
<ObjectTitle>
|
|
94
|
+
{required && <Required>*</Required>}
|
|
95
|
+
<span>{displayTitle}</span>
|
|
96
|
+
</ObjectTitle>
|
|
97
|
+
|
|
98
|
+
{Description && (
|
|
99
|
+
<ObjectDescription>{Description}</ObjectDescription>
|
|
100
|
+
)}
|
|
101
|
+
|
|
102
|
+
<ObjectContent>
|
|
103
|
+
{Object.entries(Properties).map(([propertyName, propertySchema]) => {
|
|
104
|
+
const isRequired = RequiredFields.includes(propertyName);
|
|
105
|
+
return (
|
|
106
|
+
<React.Suspense key={propertyName} fallback={<div>加载中...</div>}>
|
|
107
|
+
<FieldRenderer
|
|
108
|
+
name={propertyName}
|
|
109
|
+
schema={propertySchema}
|
|
110
|
+
value={value[propertyName]}
|
|
111
|
+
onChange={(newValue) => handlePropertyChange(propertyName, newValue)}
|
|
112
|
+
required={isRequired}
|
|
113
|
+
disabled={disabled}
|
|
114
|
+
level={level + 1}
|
|
115
|
+
errors={errors}
|
|
116
|
+
fieldPath={currentPath}
|
|
117
|
+
/>
|
|
118
|
+
</React.Suspense>
|
|
119
|
+
);
|
|
120
|
+
})}
|
|
121
|
+
</ObjectContent>
|
|
122
|
+
</ObjectFieldContainer>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export default ObjectField;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import { FieldRenderer } from './FieldRenderer';
|
|
4
|
+
import {
|
|
5
|
+
CustomParamsRendererProps,
|
|
6
|
+
CustomParamSchema,
|
|
7
|
+
ValidationResult,
|
|
8
|
+
validateCustomParams,
|
|
9
|
+
} from './types';
|
|
10
|
+
|
|
11
|
+
export * from './types';
|
|
12
|
+
export { FieldRenderer } from './FieldRenderer';
|
|
13
|
+
export { ArrayField } from './ArrayField';
|
|
14
|
+
export { ObjectField } from './ObjectField';
|
|
15
|
+
|
|
16
|
+
// ==================== Styled Components ====================
|
|
17
|
+
|
|
18
|
+
const CustomParamsRendererContainer = styled.div`
|
|
19
|
+
width: 100%;
|
|
20
|
+
padding-bottom: 12px;
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
const EmptyState = styled.div`
|
|
24
|
+
padding: 24px;
|
|
25
|
+
text-align: center;
|
|
26
|
+
color: #8f959e;
|
|
27
|
+
font-size: 12px;
|
|
28
|
+
background-color: #fafafa;
|
|
29
|
+
border: 1px dashed #e5e6eb;
|
|
30
|
+
border-radius: 6px;
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* CustomParamsRenderer 组件
|
|
35
|
+
* 根据 CustomParam Schema 渲染表单
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* const schema: CustomParamSchema = {
|
|
40
|
+
* Type: 'object',
|
|
41
|
+
* Properties: {
|
|
42
|
+
* name: { Type: 'string', Title: '名称', Description: '请输入名称' },
|
|
43
|
+
* age: { Type: 'number', Title: '年龄' },
|
|
44
|
+
* enabled: { Type: 'boolean', Title: '是否启用' },
|
|
45
|
+
* tags: {
|
|
46
|
+
* Type: 'array',
|
|
47
|
+
* Title: '标签',
|
|
48
|
+
* Items: { Type: 'string' }
|
|
49
|
+
* },
|
|
50
|
+
* config: {
|
|
51
|
+
* Type: 'object',
|
|
52
|
+
* Title: '配置',
|
|
53
|
+
* Properties: {
|
|
54
|
+
* key: { Type: 'string', Title: '键' },
|
|
55
|
+
* value: { Type: 'string', Title: '值' }
|
|
56
|
+
* }
|
|
57
|
+
* }
|
|
58
|
+
* },
|
|
59
|
+
* Required: ['name']
|
|
60
|
+
* };
|
|
61
|
+
*
|
|
62
|
+
* <CustomParamsRenderer
|
|
63
|
+
* schema={schema}
|
|
64
|
+
* value={formValue}
|
|
65
|
+
* onChange={setFormValue}
|
|
66
|
+
* />
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export const CustomParamsRenderer: React.FC<CustomParamsRendererProps> = ({
|
|
70
|
+
schema,
|
|
71
|
+
value = {},
|
|
72
|
+
onChange,
|
|
73
|
+
disabled = false,
|
|
74
|
+
errors = {},
|
|
75
|
+
}) => {
|
|
76
|
+
const Properties = schema?.Properties;
|
|
77
|
+
const Required = schema?.Required || [];
|
|
78
|
+
|
|
79
|
+
// 处理属性值变化
|
|
80
|
+
const handlePropertyChange = useCallback(
|
|
81
|
+
(propertyName: string, propertyValue: any) => {
|
|
82
|
+
onChange?.({
|
|
83
|
+
...value,
|
|
84
|
+
[propertyName]: propertyValue,
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
[value, onChange]
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// 如果没有 Properties,返回空
|
|
91
|
+
if (!Properties || Object.keys(Properties).length === 0) {
|
|
92
|
+
return (
|
|
93
|
+
<CustomParamsRendererContainer>
|
|
94
|
+
<EmptyState>暂无参数配置</EmptyState>
|
|
95
|
+
</CustomParamsRendererContainer>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<CustomParamsRendererContainer>
|
|
101
|
+
{Object.entries(Properties).map(([propertyName, propertySchema]) => {
|
|
102
|
+
const isRequired = Required.includes(propertyName);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<FieldRenderer
|
|
106
|
+
key={propertyName}
|
|
107
|
+
name={propertyName}
|
|
108
|
+
schema={propertySchema}
|
|
109
|
+
value={value[propertyName]}
|
|
110
|
+
onChange={(newValue) => handlePropertyChange(propertyName, newValue)}
|
|
111
|
+
required={isRequired}
|
|
112
|
+
disabled={disabled}
|
|
113
|
+
level={0}
|
|
114
|
+
errors={errors}
|
|
115
|
+
fieldPath=""
|
|
116
|
+
/>
|
|
117
|
+
);
|
|
118
|
+
})}
|
|
119
|
+
</CustomParamsRendererContainer>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 使用 CustomParamsRenderer 的 Hook
|
|
125
|
+
* 提供表单值管理和校验功能
|
|
126
|
+
*/
|
|
127
|
+
export const useCustomParamsRenderer = (schema: CustomParamSchema) => {
|
|
128
|
+
const [value, setValue] = React.useState<Record<string, any>>({});
|
|
129
|
+
|
|
130
|
+
// 校验表单
|
|
131
|
+
const validate = useCallback((): ValidationResult => {
|
|
132
|
+
return validateCustomParams(schema, value);
|
|
133
|
+
}, [schema, value]);
|
|
134
|
+
|
|
135
|
+
// 重置表单
|
|
136
|
+
const reset = useCallback(() => {
|
|
137
|
+
setValue({});
|
|
138
|
+
}, []);
|
|
139
|
+
|
|
140
|
+
// 设置单个字段值
|
|
141
|
+
const setFieldValue = useCallback((fieldName: string, fieldValue: any) => {
|
|
142
|
+
setValue((prev) => ({
|
|
143
|
+
...prev,
|
|
144
|
+
[fieldName]: fieldValue,
|
|
145
|
+
}));
|
|
146
|
+
}, []);
|
|
147
|
+
|
|
148
|
+
// 获取单个字段值
|
|
149
|
+
const getFieldValue = useCallback(
|
|
150
|
+
(fieldName: string) => {
|
|
151
|
+
return value[fieldName];
|
|
152
|
+
},
|
|
153
|
+
[value]
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
value,
|
|
158
|
+
setValue,
|
|
159
|
+
validate,
|
|
160
|
+
reset,
|
|
161
|
+
setFieldValue,
|
|
162
|
+
getFieldValue,
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export default CustomParamsRenderer;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
export type ParamType = 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CustomParam 的 Schema 定义
|
|
5
|
+
*/
|
|
6
|
+
export interface CustomParamSchema {
|
|
7
|
+
Type: ParamType;
|
|
8
|
+
Title?: string;
|
|
9
|
+
Description?: string;
|
|
10
|
+
Required?: string[];
|
|
11
|
+
Properties?: Record<string, CustomParamSchema>;
|
|
12
|
+
Items?: CustomParamSchema;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* CustomParamsRenderer 组件的 Props
|
|
17
|
+
*/
|
|
18
|
+
export interface CustomParamsRendererProps {
|
|
19
|
+
/** CustomParams 的 schema */
|
|
20
|
+
schema: CustomParamSchema;
|
|
21
|
+
/** 表单值 */
|
|
22
|
+
value?: Record<string, any>;
|
|
23
|
+
/** 值变化回调 */
|
|
24
|
+
onChange?: (value: Record<string, any>) => void;
|
|
25
|
+
/** 是否禁用 */
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
/** 字段名前缀(用于嵌套场景) */
|
|
28
|
+
namePrefix?: string;
|
|
29
|
+
/** 验证错误信息 */
|
|
30
|
+
errors?: Record<string, string>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 单个字段渲染器的 Props
|
|
35
|
+
*/
|
|
36
|
+
export interface FieldRendererProps {
|
|
37
|
+
/** 字段名 */
|
|
38
|
+
name: string;
|
|
39
|
+
/** 字段 Schema */
|
|
40
|
+
schema: CustomParamSchema;
|
|
41
|
+
/** 字段值 */
|
|
42
|
+
value?: any;
|
|
43
|
+
/** 值变化回调 */
|
|
44
|
+
onChange?: (value: any) => void;
|
|
45
|
+
/** 是否必填 */
|
|
46
|
+
required?: boolean;
|
|
47
|
+
/** 是否禁用 */
|
|
48
|
+
disabled?: boolean;
|
|
49
|
+
/** 嵌套层级 */
|
|
50
|
+
level?: number;
|
|
51
|
+
/** 验证错误信息 */
|
|
52
|
+
errors?: Record<string, string>;
|
|
53
|
+
/** 字段路径(用于匹配错误信息) */
|
|
54
|
+
fieldPath?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 数组字段的 Props
|
|
59
|
+
*/
|
|
60
|
+
export interface ArrayFieldProps {
|
|
61
|
+
/** 字段名 */
|
|
62
|
+
name: string;
|
|
63
|
+
/** 字段 Schema */
|
|
64
|
+
schema: CustomParamSchema;
|
|
65
|
+
/** 字段值 */
|
|
66
|
+
value?: any[];
|
|
67
|
+
/** 值变化回调 */
|
|
68
|
+
onChange?: (value: any[]) => void;
|
|
69
|
+
/** 是否必填 */
|
|
70
|
+
required?: boolean;
|
|
71
|
+
/** 是否禁用 */
|
|
72
|
+
disabled?: boolean;
|
|
73
|
+
/** 嵌套层级 */
|
|
74
|
+
level?: number;
|
|
75
|
+
/** 验证错误信息 */
|
|
76
|
+
errors?: Record<string, string>;
|
|
77
|
+
/** 字段路径(用于匹配错误信息) */
|
|
78
|
+
fieldPath?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 对象字段的 Props
|
|
83
|
+
*/
|
|
84
|
+
export interface ObjectFieldProps {
|
|
85
|
+
/** 字段名 */
|
|
86
|
+
name: string;
|
|
87
|
+
/** 字段 Schema */
|
|
88
|
+
schema: CustomParamSchema;
|
|
89
|
+
/** 字段值 */
|
|
90
|
+
value?: Record<string, any>;
|
|
91
|
+
/** 值变化回调 */
|
|
92
|
+
onChange?: (value: Record<string, any>) => void;
|
|
93
|
+
/** 是否必填 */
|
|
94
|
+
required?: boolean;
|
|
95
|
+
/** 是否禁用 */
|
|
96
|
+
disabled?: boolean;
|
|
97
|
+
/** 嵌套层级 */
|
|
98
|
+
level?: number;
|
|
99
|
+
/** 验证错误信息 */
|
|
100
|
+
errors?: Record<string, string>;
|
|
101
|
+
/** 字段路径(用于匹配错误信息) */
|
|
102
|
+
fieldPath?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 校验错误信息
|
|
107
|
+
*/
|
|
108
|
+
export interface ValidationError {
|
|
109
|
+
field: string;
|
|
110
|
+
message: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 校验结果
|
|
115
|
+
*/
|
|
116
|
+
export interface ValidationResult {
|
|
117
|
+
valid: boolean;
|
|
118
|
+
errors: ValidationError[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 校验 CustomParams 的值
|
|
123
|
+
* @param schema Schema 定义
|
|
124
|
+
* @param value 表单值
|
|
125
|
+
* @param prefix 字段名前缀
|
|
126
|
+
* @returns 校验结果
|
|
127
|
+
*/
|
|
128
|
+
export const validateCustomParams = (
|
|
129
|
+
schema: CustomParamSchema,
|
|
130
|
+
value: Record<string, any>,
|
|
131
|
+
prefix: string = ''
|
|
132
|
+
): ValidationResult => {
|
|
133
|
+
const errors: ValidationError[] = [];
|
|
134
|
+
|
|
135
|
+
if (!schema?.Properties) {
|
|
136
|
+
return { valid: true, errors: [] };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const requiredFields = schema?.Required || [];
|
|
140
|
+
|
|
141
|
+
Object.entries(schema?.Properties).forEach(([key, fieldSchema]) => {
|
|
142
|
+
const fieldName = prefix ? `${prefix}.${key}` : key;
|
|
143
|
+
const fieldValue = value?.[key];
|
|
144
|
+
const isRequired = requiredFields.includes(key);
|
|
145
|
+
|
|
146
|
+
// 检查必填字段
|
|
147
|
+
if (isRequired) {
|
|
148
|
+
if (fieldValue === undefined || fieldValue === null || fieldValue === '') {
|
|
149
|
+
errors.push({
|
|
150
|
+
field: fieldName,
|
|
151
|
+
message: `${fieldSchema?.Title || key} 是必填项`,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 递归校验对象类型
|
|
157
|
+
if (fieldSchema?.Type === 'object' && fieldSchema?.Properties && fieldValue) {
|
|
158
|
+
const nestedResult = validateCustomParams(fieldSchema, fieldValue, fieldName);
|
|
159
|
+
errors.push(...nestedResult.errors);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 校验数组类型
|
|
163
|
+
if (fieldSchema?.Type === 'array' && fieldSchema?.Items && Array.isArray(fieldValue)) {
|
|
164
|
+
fieldValue.forEach((item, index) => {
|
|
165
|
+
const itemPath = `${fieldName}[${index}]`;
|
|
166
|
+
|
|
167
|
+
// 验证对象类型的数组项
|
|
168
|
+
if (fieldSchema?.Items?.Type === 'object' && fieldSchema?.Items?.Properties) {
|
|
169
|
+
const itemResult = validateCustomParams(
|
|
170
|
+
fieldSchema.Items,
|
|
171
|
+
item,
|
|
172
|
+
itemPath
|
|
173
|
+
);
|
|
174
|
+
errors.push(...itemResult.errors);
|
|
175
|
+
}
|
|
176
|
+
// 验证基础类型的数组项 - 只有当数组字段是必填时才验证
|
|
177
|
+
else if (isRequired) {
|
|
178
|
+
if (fieldSchema?.Items?.Type === 'string') {
|
|
179
|
+
if (item === undefined || item === null || item === '') {
|
|
180
|
+
errors.push({
|
|
181
|
+
field: itemPath,
|
|
182
|
+
message: `此项 是必填项`,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else if (fieldSchema?.Items?.Type === 'number') {
|
|
187
|
+
if (item === undefined || item === null) {
|
|
188
|
+
errors.push({
|
|
189
|
+
field: itemPath,
|
|
190
|
+
message: `此项 是必填项`,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
valid: errors.length === 0,
|
|
201
|
+
errors,
|
|
202
|
+
};
|
|
203
|
+
};
|