whistle.mock-plugins 1.0.6-beta.2 → 1.0.6-beta.21
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/app/cgi-bin/interfaces.js +35 -0
- package/app/public/js/index.js +1 -1
- package/app/public/js/index.js.LICENSE.txt +8 -2
- package/lib/models.js +12 -1
- package/lib/routes/interfaces.js +274 -56
- package/lib/ruleManager.js +41 -16
- package/lib/rules-server.js +473 -210
- package/lib/server.js +0 -28
- package/package.json +1 -1
- package/ui/src/components/interface-management/InterfaceTestModal.js +304 -0
- package/ui/src/components/interface-management/PreviewModal.js +50 -0
- package/ui/src/components/interface-management/README.md +143 -0
- package/ui/src/components/interface-management/ResponseContentEditor.js +406 -0
- package/ui/src/components/interface-management/constants.js +36 -0
- package/ui/src/components/interface-management/index.js +6 -0
- package/ui/src/components/interface-management/utils.js +103 -0
- package/ui/src/pages/InterfaceManagement.js +295 -700
package/lib/server.js
CHANGED
|
@@ -410,20 +410,6 @@ const handleLegacyRequest = (req, res, next) => {
|
|
|
410
410
|
const https = require('https');
|
|
411
411
|
const { Readable } = require('stream');
|
|
412
412
|
|
|
413
|
-
// 添加CORS支持 - 设置跨域相关的响应头
|
|
414
|
-
res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
|
|
415
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH');
|
|
416
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept');
|
|
417
|
-
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
418
|
-
res.setHeader('Access-Control-Max-Age', '86400'); // 24小时
|
|
419
|
-
|
|
420
|
-
// 处理预检请求
|
|
421
|
-
if (req.method === 'OPTIONS') {
|
|
422
|
-
res.statusCode = 204; // No Content
|
|
423
|
-
res.end();
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
413
|
// 获取原始完整URL的所有可能来源
|
|
428
414
|
let originalUrl = null;
|
|
429
415
|
|
|
@@ -519,20 +505,6 @@ const handleLegacyRequest = (req, res, next) => {
|
|
|
519
505
|
res.setHeader(key, proxyRes.headers[key]);
|
|
520
506
|
});
|
|
521
507
|
|
|
522
|
-
// 确保设置CORS头,即使原始响应没有
|
|
523
|
-
if (!res.getHeader('Access-Control-Allow-Origin')) {
|
|
524
|
-
res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
|
|
525
|
-
}
|
|
526
|
-
if (!res.getHeader('Access-Control-Allow-Methods')) {
|
|
527
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH');
|
|
528
|
-
}
|
|
529
|
-
if (!res.getHeader('Access-Control-Allow-Headers')) {
|
|
530
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept');
|
|
531
|
-
}
|
|
532
|
-
if (!res.getHeader('Access-Control-Allow-Credentials')) {
|
|
533
|
-
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
534
|
-
}
|
|
535
|
-
|
|
536
508
|
// 设置状态码
|
|
537
509
|
res.statusCode = proxyRes.statusCode;
|
|
538
510
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Modal, Button, Form, Input, Alert, Space } from 'antd';
|
|
3
|
+
import { ExclamationCircleOutlined } from '@ant-design/icons';
|
|
4
|
+
import { proxyTypes } from './constants';
|
|
5
|
+
import { isUrlMatchPattern, refreshCacheAfterUpdate } from './utils';
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
|
|
8
|
+
const InterfaceTestModal = ({
|
|
9
|
+
visible,
|
|
10
|
+
onCancel,
|
|
11
|
+
editingInterface
|
|
12
|
+
}) => {
|
|
13
|
+
const [testForm] = Form.useForm();
|
|
14
|
+
const [testResult, setTestResult] = useState(null);
|
|
15
|
+
const [testLoading, setTestLoading] = useState(false);
|
|
16
|
+
|
|
17
|
+
const handleTestSubmit = async () => {
|
|
18
|
+
try {
|
|
19
|
+
const values = await testForm.validateFields();
|
|
20
|
+
const { testUrl } = values;
|
|
21
|
+
|
|
22
|
+
if (!testUrl) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
setTestLoading(true);
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// 测试前刷新缓存,确保使用最新的接口定义
|
|
30
|
+
await refreshCacheAfterUpdate();
|
|
31
|
+
} catch (error) {
|
|
32
|
+
// 即使刷新缓存失败,也继续测试
|
|
33
|
+
console.warn('刷新缓存失败,将使用现有的缓存数据:', error);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
// 验证测试 URL 是否匹配当前接口的匹配规则
|
|
38
|
+
if (!isUrlMatchPattern(testUrl, editingInterface.urlPattern, editingInterface.proxyType)) {
|
|
39
|
+
let errorMessage = '测试URL与接口匹配规则不匹配';
|
|
40
|
+
|
|
41
|
+
// 根据不同的代理类型提供更具体的错误信息
|
|
42
|
+
if (editingInterface.proxyType === 'url_redirect') {
|
|
43
|
+
errorMessage = `测试URL必须完全匹配 ${editingInterface.urlPattern}`;
|
|
44
|
+
} else if (editingInterface.proxyType === 'redirect') {
|
|
45
|
+
errorMessage = `测试URL必须以 ${editingInterface.urlPattern} 开头`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setTestResult({
|
|
49
|
+
success: false,
|
|
50
|
+
error: errorMessage
|
|
51
|
+
});
|
|
52
|
+
setTestLoading(false);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 调用实际测试接口
|
|
57
|
+
const response = await axios.post(`/cgi-bin/test-interface`, {
|
|
58
|
+
url: testUrl,
|
|
59
|
+
interfaceId: editingInterface.id
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (response.data && response.data.code === 0) {
|
|
63
|
+
// 设置测试结果
|
|
64
|
+
setTestResult({
|
|
65
|
+
success: true,
|
|
66
|
+
...response.data.data,
|
|
67
|
+
matchedRule: editingInterface.urlPattern,
|
|
68
|
+
httpMethod: editingInterface.httpMethod,
|
|
69
|
+
requestUrl: testUrl,
|
|
70
|
+
mockInfo: response.data.data.mockInfo
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
throw new Error(response.data?.message || '测试响应格式不正确');
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
setTestResult({
|
|
77
|
+
success: false,
|
|
78
|
+
error: error.response?.data?.message || error.message || '测试失败'
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
setTestLoading(false);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error.errorFields) {
|
|
85
|
+
return; // 表单验证错误
|
|
86
|
+
}
|
|
87
|
+
console.error('测试失败:', error);
|
|
88
|
+
setTestLoading(false);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const formatResponseContent = (content, contentType) => {
|
|
93
|
+
if (contentType && contentType.includes('json')) {
|
|
94
|
+
try {
|
|
95
|
+
const parsed = JSON.parse(content);
|
|
96
|
+
return JSON.stringify(parsed, null, 2);
|
|
97
|
+
} catch (e) {
|
|
98
|
+
return content;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return content;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<Modal
|
|
106
|
+
title="测试接口"
|
|
107
|
+
open={visible}
|
|
108
|
+
onCancel={onCancel}
|
|
109
|
+
footer={[
|
|
110
|
+
<Button key="back" onClick={onCancel}>
|
|
111
|
+
关闭
|
|
112
|
+
</Button>,
|
|
113
|
+
<Button key="submit" type="primary" loading={testLoading} onClick={handleTestSubmit}>
|
|
114
|
+
测试
|
|
115
|
+
</Button>
|
|
116
|
+
]}
|
|
117
|
+
width={800}
|
|
118
|
+
>
|
|
119
|
+
{editingInterface && (
|
|
120
|
+
<div className="test-interface-container">
|
|
121
|
+
<Alert
|
|
122
|
+
message="URL匹配规则说明"
|
|
123
|
+
description={
|
|
124
|
+
<div>
|
|
125
|
+
<p>当前接口匹配规则:<code>{editingInterface.urlPattern}</code></p>
|
|
126
|
+
<p>代理类型:{
|
|
127
|
+
proxyTypes.find(item => item.value === editingInterface.proxyType)?.label ||
|
|
128
|
+
editingInterface.proxyType || '模拟响应'
|
|
129
|
+
}</p>
|
|
130
|
+
|
|
131
|
+
{editingInterface.proxyType === 'url_redirect' && (
|
|
132
|
+
<div>
|
|
133
|
+
<p><b>URL重定向模式规则:</b></p>
|
|
134
|
+
<ul>
|
|
135
|
+
<li>URL匹配规则必须是完整URL(包括http://或https://)</li>
|
|
136
|
+
<li>需要完全匹配URL路径,测试URL必须与匹配规则<b>完全一致</b></li>
|
|
137
|
+
<li>命中后将直接跳转到目标URL: {editingInterface.targetUrl}</li>
|
|
138
|
+
</ul>
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
|
|
142
|
+
{editingInterface.proxyType === 'redirect' && (
|
|
143
|
+
<div>
|
|
144
|
+
<p><b>重定向模式规则:</b></p>
|
|
145
|
+
<ul>
|
|
146
|
+
<li>URL匹配规则必须以http://或https://开头</li>
|
|
147
|
+
<li>测试URL必须以匹配规则开头(<b>前缀匹配</b>)</li>
|
|
148
|
+
<li>命中后将直接跳转到目标URL: {editingInterface.targetUrl}</li>
|
|
149
|
+
</ul>
|
|
150
|
+
</div>
|
|
151
|
+
)}
|
|
152
|
+
|
|
153
|
+
{editingInterface.proxyType === 'response' && (
|
|
154
|
+
<div>
|
|
155
|
+
<p>在Whistle规则中,您需要配置完整URL或域名指向whistle.mock-plugins://,然后插件会根据接口的匹配规则处理请求。</p>
|
|
156
|
+
<p>插件支持以下匹配方式:</p>
|
|
157
|
+
<ul>
|
|
158
|
+
<li>精确匹配:完全匹配URL路径部分</li>
|
|
159
|
+
<li>通配符匹配:使用*代表任意字符(例如:/api/users/*)</li>
|
|
160
|
+
<li>正则表达式:使用/pattern/格式(例如:/\/api\/users\/\d+/)</li>
|
|
161
|
+
</ul>
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
<p>请在下方输入完整测试URL进行测试</p>
|
|
166
|
+
</div>
|
|
167
|
+
}
|
|
168
|
+
type="info"
|
|
169
|
+
showIcon
|
|
170
|
+
style={{ marginBottom: 16 }}
|
|
171
|
+
/>
|
|
172
|
+
|
|
173
|
+
<Form
|
|
174
|
+
form={testForm}
|
|
175
|
+
layout="vertical"
|
|
176
|
+
>
|
|
177
|
+
<Form.Item
|
|
178
|
+
name="testUrl"
|
|
179
|
+
label="测试URL"
|
|
180
|
+
rules={[{ required: true, message: '请输入测试URL' }]}
|
|
181
|
+
>
|
|
182
|
+
<Input
|
|
183
|
+
placeholder="输入完整URL,例如:https://example.com/api/users"
|
|
184
|
+
/>
|
|
185
|
+
</Form.Item>
|
|
186
|
+
</Form>
|
|
187
|
+
|
|
188
|
+
{testResult && (
|
|
189
|
+
<div className="test-result">
|
|
190
|
+
<div className="result-header">
|
|
191
|
+
<h3>测试结果</h3>
|
|
192
|
+
{testResult.success ? (
|
|
193
|
+
<span className="status success">成功</span>
|
|
194
|
+
) : (
|
|
195
|
+
<span className="status error">失败</span>
|
|
196
|
+
)}
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
{testResult.success ? (
|
|
200
|
+
<div className="result-content">
|
|
201
|
+
<div className="result-item">
|
|
202
|
+
<span className="label">请求URL:</span>
|
|
203
|
+
<span className="value">{testResult.requestUrl}</span>
|
|
204
|
+
</div>
|
|
205
|
+
<div className="result-item">
|
|
206
|
+
<span className="label">匹配规则:</span>
|
|
207
|
+
<span className="value">{testResult.matchedRule}</span>
|
|
208
|
+
</div>
|
|
209
|
+
<div className="result-item">
|
|
210
|
+
<span className="label">请求方法:</span>
|
|
211
|
+
<span className="value">{testResult.httpMethod || 'ALL'}</span>
|
|
212
|
+
</div>
|
|
213
|
+
<div className="result-item">
|
|
214
|
+
<span className="label">处理方式:</span>
|
|
215
|
+
<span className="value">
|
|
216
|
+
{(() => {
|
|
217
|
+
const found = proxyTypes.find(item => item.value === editingInterface.proxyType);
|
|
218
|
+
return found ? found.label : editingInterface.proxyType || '模拟响应';
|
|
219
|
+
})()}
|
|
220
|
+
</span>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
{(editingInterface.proxyType === 'redirect' || editingInterface.proxyType === 'url_redirect') && (
|
|
224
|
+
<div className="result-item">
|
|
225
|
+
<span className="label">重定向目标:</span>
|
|
226
|
+
<span className="value">{testResult.targetUrl || editingInterface.targetUrl}</span>
|
|
227
|
+
</div>
|
|
228
|
+
)}
|
|
229
|
+
|
|
230
|
+
{(editingInterface.proxyType === 'redirect' || editingInterface.proxyType === 'url_redirect') &&
|
|
231
|
+
testResult.formattedHeaders && (
|
|
232
|
+
<div className="result-item">
|
|
233
|
+
<span className="label">自定义请求头:</span>
|
|
234
|
+
<div className="value" style={{ whiteSpace: 'pre-wrap', fontFamily: 'monospace' }}>
|
|
235
|
+
{
|
|
236
|
+
editingInterface.customHeaders &&
|
|
237
|
+
Object.entries(editingInterface.customHeaders)
|
|
238
|
+
.map(([key, value]) => {
|
|
239
|
+
const generatedValue = testResult.customHeaders[key];
|
|
240
|
+
return (
|
|
241
|
+
<div key={key}>
|
|
242
|
+
{key}: {value.startsWith('@') ? (
|
|
243
|
+
<span>
|
|
244
|
+
<span style={{ color: '#d9d9d9' }}>{value}</span>
|
|
245
|
+
{' '} → {' '}
|
|
246
|
+
<span style={{ color: '#52c41a', fontWeight: 'bold' }}>{generatedValue}</span>
|
|
247
|
+
<span style={{ color: '#8c8c8c', fontSize: '12px' }}> (随机生成)</span>
|
|
248
|
+
</span>
|
|
249
|
+
) : generatedValue}
|
|
250
|
+
</div>
|
|
251
|
+
);
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
)}
|
|
257
|
+
|
|
258
|
+
{editingInterface.proxyType === 'response' && (
|
|
259
|
+
<>
|
|
260
|
+
<div className="result-item">
|
|
261
|
+
<span className="label">状态码:</span>
|
|
262
|
+
<span className="value">{testResult.statusCode}</span>
|
|
263
|
+
</div>
|
|
264
|
+
<div className="result-item">
|
|
265
|
+
<span className="label">内容类型:</span>
|
|
266
|
+
<span className="value">{testResult.contentType}</span>
|
|
267
|
+
</div>
|
|
268
|
+
</>
|
|
269
|
+
)}
|
|
270
|
+
|
|
271
|
+
{testResult.mockInfo && (
|
|
272
|
+
<>
|
|
273
|
+
<div className="result-item">
|
|
274
|
+
<span className="label">模拟延迟:</span>
|
|
275
|
+
<span className="value">{testResult.mockInfo.delay}ms</span>
|
|
276
|
+
</div>
|
|
277
|
+
<div className="result-item">
|
|
278
|
+
<span className="label">响应时间:</span>
|
|
279
|
+
<span className="value">{new Date(testResult.mockInfo.timestamp).toLocaleString()}</span>
|
|
280
|
+
</div>
|
|
281
|
+
</>
|
|
282
|
+
)}
|
|
283
|
+
|
|
284
|
+
{editingInterface.proxyType === 'response' && (
|
|
285
|
+
<div className="result-body">
|
|
286
|
+
<div className="label">响应内容:</div>
|
|
287
|
+
<pre>{formatResponseContent(testResult.responseBody, testResult.contentType)}</pre>
|
|
288
|
+
</div>
|
|
289
|
+
)}
|
|
290
|
+
</div>
|
|
291
|
+
) : (
|
|
292
|
+
<div className="result-error">
|
|
293
|
+
<ExclamationCircleOutlined /> {testResult.error}
|
|
294
|
+
</div>
|
|
295
|
+
)}
|
|
296
|
+
</div>
|
|
297
|
+
)}
|
|
298
|
+
</div>
|
|
299
|
+
)}
|
|
300
|
+
</Modal>
|
|
301
|
+
);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
export default InterfaceTestModal;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Modal, Button } from 'antd';
|
|
3
|
+
|
|
4
|
+
const PreviewModal = ({
|
|
5
|
+
visible,
|
|
6
|
+
onCancel,
|
|
7
|
+
previewContent
|
|
8
|
+
}) => {
|
|
9
|
+
return (
|
|
10
|
+
<Modal
|
|
11
|
+
title={previewContent?.title || "预览响应内容"}
|
|
12
|
+
open={visible}
|
|
13
|
+
onCancel={onCancel}
|
|
14
|
+
footer={[
|
|
15
|
+
<Button key="close" onClick={onCancel}>
|
|
16
|
+
关闭
|
|
17
|
+
</Button>
|
|
18
|
+
]}
|
|
19
|
+
width={600}
|
|
20
|
+
>
|
|
21
|
+
{previewContent?.description && (
|
|
22
|
+
<div style={{
|
|
23
|
+
marginBottom: '16px',
|
|
24
|
+
padding: '8px',
|
|
25
|
+
background: '#f9f9f9',
|
|
26
|
+
borderRadius: '4px',
|
|
27
|
+
borderLeft: '4px solid #1890ff'
|
|
28
|
+
}}>
|
|
29
|
+
<div style={{ fontWeight: 'bold', marginBottom: '4px' }}>描述:</div>
|
|
30
|
+
<div>{previewContent.description}</div>
|
|
31
|
+
</div>
|
|
32
|
+
)}
|
|
33
|
+
|
|
34
|
+
<div style={{
|
|
35
|
+
maxHeight: '60vh',
|
|
36
|
+
overflow: 'auto',
|
|
37
|
+
backgroundColor: '#f5f5f5',
|
|
38
|
+
padding: '16px',
|
|
39
|
+
borderRadius: '4px',
|
|
40
|
+
fontFamily: 'monospace'
|
|
41
|
+
}}>
|
|
42
|
+
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordWrap: 'break-word' }}>
|
|
43
|
+
{previewContent?.content}
|
|
44
|
+
</pre>
|
|
45
|
+
</div>
|
|
46
|
+
</Modal>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default PreviewModal;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# 接口管理组件拆分与优化
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
将原本的 `InterfaceManagement.js` 文件(2100多行)拆分为多个更小、更易维护的组件文件,并优化响应管理UI,简化用户界面。
|
|
5
|
+
|
|
6
|
+
## 文件结构
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
src/components/interface-management/
|
|
10
|
+
├── index.js # 统一导出文件
|
|
11
|
+
├── constants.js # 常量定义(选项数组)
|
|
12
|
+
├── utils.js # 工具函数
|
|
13
|
+
├── ResponseContentEditor.js # 合并的响应内容编辑器(包含管理功能)
|
|
14
|
+
├── InterfaceTestModal.js # 接口测试模态框
|
|
15
|
+
├── PreviewModal.js # 预览模态框
|
|
16
|
+
└── README.md # 说明文档
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 组件优化说明
|
|
20
|
+
|
|
21
|
+
### 1. 响应管理UI优化
|
|
22
|
+
- **合并组件**:将原来的 `ResponseManager` 和 `ResponseEditor` 合并为一个 `ResponseContentEditor` 组件
|
|
23
|
+
- **简化界面**:移除响应描述字段和内容统计信息,只保留响应名称设置
|
|
24
|
+
- **一体化操作**:响应的添加、删除、切换和内容编辑都在同一个Card组件中完成
|
|
25
|
+
- **更简洁的UI**:减少界面复杂度,提升用户体验
|
|
26
|
+
|
|
27
|
+
### 2. 各组件职责
|
|
28
|
+
|
|
29
|
+
#### constants.js
|
|
30
|
+
- `contentTypes`: 内容类型选项
|
|
31
|
+
- `proxyTypes`: 代理类型选项
|
|
32
|
+
- `statusCodes`: 状态码选项
|
|
33
|
+
- `httpMethods`: HTTP方法选项
|
|
34
|
+
|
|
35
|
+
#### utils.js
|
|
36
|
+
- `cleanJsonResponse()`: 清理JSON响应
|
|
37
|
+
- `isValidUrl()`: 验证URL格式
|
|
38
|
+
- `flushCache()`: 刷新缓存
|
|
39
|
+
- `refreshCacheAfterUpdate()`: 更新后刷新缓存
|
|
40
|
+
- `isUrlMatchPattern()`: URL匹配检查
|
|
41
|
+
- `formatResponseContent()`: 格式化响应内容
|
|
42
|
+
- `generateResponseId()`: 生成唯一响应ID
|
|
43
|
+
|
|
44
|
+
#### ResponseContentEditor.js
|
|
45
|
+
**合并后的响应内容编辑器组件**,负责:
|
|
46
|
+
- 响应的增删改查操作
|
|
47
|
+
- 响应选择和切换
|
|
48
|
+
- 响应名称编辑(简化,只保留名称字段)
|
|
49
|
+
- 响应内容编辑和格式化
|
|
50
|
+
- 预览功能集成
|
|
51
|
+
|
|
52
|
+
Props:
|
|
53
|
+
- `form`: 表单实例
|
|
54
|
+
- `responses`: 响应数组
|
|
55
|
+
- `activeResponseId`: 当前激活的响应ID
|
|
56
|
+
- `onPreview`: 预览回调
|
|
57
|
+
|
|
58
|
+
#### InterfaceTestModal.js
|
|
59
|
+
接口测试模态框组件,负责:
|
|
60
|
+
- 接口测试表单
|
|
61
|
+
- 测试结果显示
|
|
62
|
+
- URL匹配规则说明
|
|
63
|
+
- 测试失败错误处理
|
|
64
|
+
|
|
65
|
+
Props:
|
|
66
|
+
- `visible`: 是否显示
|
|
67
|
+
- `onCancel`: 取消回调
|
|
68
|
+
- `editingInterface`: 当前编辑的接口
|
|
69
|
+
|
|
70
|
+
#### PreviewModal.js
|
|
71
|
+
预览模态框组件,负责:
|
|
72
|
+
- 响应内容预览
|
|
73
|
+
- 格式化内容显示
|
|
74
|
+
|
|
75
|
+
Props:
|
|
76
|
+
- `visible`: 是否显示
|
|
77
|
+
- `onCancel`: 取消回调
|
|
78
|
+
- `previewContent`: 预览内容对象
|
|
79
|
+
|
|
80
|
+
## 主文件变更
|
|
81
|
+
|
|
82
|
+
新的 `InterfaceManagement.js` 文件:
|
|
83
|
+
- 从 2126 行减少到约 800 行
|
|
84
|
+
- 导入优化后的 `ResponseContentEditor` 组件
|
|
85
|
+
- 简化响应相关的状态管理
|
|
86
|
+
- 保持完整的功能性
|
|
87
|
+
|
|
88
|
+
## 使用方式
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
// 导入优化后的组件
|
|
92
|
+
import {
|
|
93
|
+
ResponseContentEditor,
|
|
94
|
+
InterfaceTestModal,
|
|
95
|
+
PreviewModal,
|
|
96
|
+
contentTypes,
|
|
97
|
+
proxyTypes,
|
|
98
|
+
statusCodes,
|
|
99
|
+
httpMethods,
|
|
100
|
+
refreshCacheAfterUpdate,
|
|
101
|
+
formatResponseContent,
|
|
102
|
+
generateResponseId
|
|
103
|
+
} from '../components/interface-management';
|
|
104
|
+
|
|
105
|
+
// 使用合并后的响应编辑器
|
|
106
|
+
<ResponseContentEditor
|
|
107
|
+
form={form}
|
|
108
|
+
responses={responses}
|
|
109
|
+
activeResponseId={activeResponseId}
|
|
110
|
+
onPreview={handlePreview}
|
|
111
|
+
/>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 优化效果
|
|
115
|
+
|
|
116
|
+
### UI简化
|
|
117
|
+
1. **移除冗余字段**:去掉响应描述和内容统计,界面更简洁
|
|
118
|
+
2. **合并操作区域**:响应管理和内容编辑在同一界面完成
|
|
119
|
+
3. **流程简化**:减少用户操作步骤,提升效率
|
|
120
|
+
|
|
121
|
+
### 代码优化
|
|
122
|
+
1. **组件合并**:从2个组件合并为1个,减少组件复杂度
|
|
123
|
+
2. **逻辑集中**:相关功能集中在一个组件中,便于维护
|
|
124
|
+
3. **接口简化**:减少组件间的通信和状态传递
|
|
125
|
+
|
|
126
|
+
### 功能保持
|
|
127
|
+
1. **完整功能**:所有原有功能都得到保留
|
|
128
|
+
2. **兼容性**:保持与现有数据结构的完全兼容
|
|
129
|
+
3. **稳定性**:优化过程中不破坏任何现有功能
|
|
130
|
+
|
|
131
|
+
## 优势
|
|
132
|
+
|
|
133
|
+
1. **用户体验**:更简洁的界面,更流畅的操作体验
|
|
134
|
+
2. **代码维护**:更少的组件数量,更集中的业务逻辑
|
|
135
|
+
3. **性能提升**:减少组件渲染和状态更新的复杂度
|
|
136
|
+
4. **开发效率**:简化的代码结构,更容易理解和修改
|
|
137
|
+
|
|
138
|
+
## 兼容性
|
|
139
|
+
|
|
140
|
+
- 完全兼容原有功能和数据格式
|
|
141
|
+
- 保持所有API接口不变
|
|
142
|
+
- 用户界面和交互逻辑保持一致
|
|
143
|
+
- 构建和打包正常工作
|