whistle.mock-plugins 1.3.1 → 1.4.0-beta.2
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/VERSION.md +29 -0
- package/app/cgi-bin/interfaces.js +2 -0
- package/app/cgi-bin/settings.js +1 -0
- package/app/cgi-bin/stats.js +22 -12
- package/app/cgi-bin/webdav.js +56 -0
- package/app/public/index.html +1 -1
- package/app/public/js/11.index.js +2 -0
- package/app/public/js/11.index.js.LICENSE.txt +5 -0
- package/app/public/js/704.index.js +2 -0
- package/app/public/js/704.index.js.LICENSE.txt +1 -0
- package/app/public/js/798.index.js +2 -0
- package/app/public/js/798.index.js.LICENSE.txt +53 -0
- package/app/public/js/807.index.js +2 -0
- package/app/public/js/807.index.js.LICENSE.txt +1 -0
- package/app/public/js/968.index.js +2 -0
- package/app/public/js/968.index.js.LICENSE.txt +12 -0
- package/app/public/js/index.js +1 -1
- package/app/public/js/index.js.LICENSE.txt +0 -173
- package/lib/routes/settings.js +1 -0
- package/lib/webdav-sync.js +806 -0
- package/package.json +9 -2
- package/API.md +0 -247
- package/COLUMN_RESIZE_IMPLEMENTATION.md +0 -162
- package/FEATURE_ENHANCEMENT.md +0 -157
- package/TEST_GUIDE.md +0 -156
- package/data/features.json +0 -1
- package/data/interfaces.json +0 -1
- package/index.html +0 -52
- package/initialize.js +0 -32
- package/public/bundle.js +0 -2
- package/public/bundle.js.LICENSE.txt +0 -38
- package/public/index.html +0 -1
- package/public/plugin.mock-plugin.html +0 -14
- package/rules.txt +0 -22
- package/ui/src/App.css +0 -173
- package/ui/src/components/AppLayout.js +0 -407
- package/ui/src/components/FeatureCard.js +0 -58
- package/ui/src/components/KeyboardShortcutsHelp.js +0 -58
- package/ui/src/components/Layout.js +0 -94
- package/ui/src/components/MainNav.js +0 -42
- package/ui/src/components/PageContainer.js +0 -16
- package/ui/src/components/VersionModal.js +0 -99
- package/ui/src/components/interface-management/DataManagementDrawer.js +0 -115
- package/ui/src/components/interface-management/DataManagementModal.js +0 -110
- package/ui/src/components/interface-management/InterfaceTestModal.js +0 -313
- package/ui/src/components/interface-management/PreviewModal.js +0 -48
- package/ui/src/components/interface-management/README.md +0 -184
- package/ui/src/components/interface-management/RealResponseRecorder.js +0 -165
- package/ui/src/components/interface-management/ResponseContentEditor.js +0 -645
- package/ui/src/components/interface-management/constants.js +0 -36
- package/ui/src/components/interface-management/index.js +0 -7
- package/ui/src/components/interface-management/utils.js +0 -103
- package/ui/src/hooks/useKeyboardShortcuts.js +0 -99
- package/ui/src/index.html +0 -14
- package/ui/src/index.js +0 -50
- package/ui/src/pages/InterfaceManagementV2.js +0 -2351
- package/ui/src/pages/MockDataV2.js +0 -1089
- package/ui/src/pages/Settings.js +0 -299
- package/ui/src/pages/Sniffer.js +0 -521
- package/ui/src/pages/Stats.js +0 -288
- package/ui/src/services/del_api.js +0 -192
- package/ui/src/styles/app-layout.css +0 -373
- package/ui/src/styles/cyber-theme.css +0 -1234
- package/ui/src/styles/earth-theme.css +0 -618
- package/ui/src/styles/global.css +0 -224
- package/ui/src/styles/interface-management.css +0 -1321
- package/ui/src/styles/mock-data.css +0 -1185
- package/ui/src/styles/settings.css +0 -252
- package/ui/src/styles/version-modal.css +0 -116
- package/webpack.config.js +0 -54
|
@@ -1,645 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { ReactSortable } from 'react-sortablejs';
|
|
3
|
-
import ReactDOM from 'react-dom';
|
|
4
|
-
import { Card, Button, Space, Badge, Row, Col, Input, Select, Tooltip, message, Tag, Divider } from 'antd';
|
|
5
|
-
import {
|
|
6
|
-
CodeOutlined,
|
|
7
|
-
EyeOutlined,
|
|
8
|
-
FormatPainterOutlined,
|
|
9
|
-
FileTextOutlined,
|
|
10
|
-
PlusCircleOutlined,
|
|
11
|
-
DeleteOutlined,
|
|
12
|
-
CheckCircleOutlined,
|
|
13
|
-
EditOutlined,
|
|
14
|
-
FullscreenOutlined,
|
|
15
|
-
FullscreenExitOutlined,
|
|
16
|
-
MenuOutlined,
|
|
17
|
-
FilterOutlined,
|
|
18
|
-
PlusOutlined,
|
|
19
|
-
QuestionCircleOutlined
|
|
20
|
-
} from '@ant-design/icons';
|
|
21
|
-
import { generateResponseId } from './utils';
|
|
22
|
-
|
|
23
|
-
const { TextArea } = Input;
|
|
24
|
-
const { Option } = Select;
|
|
25
|
-
|
|
26
|
-
const MATCH_TYPE_LABELS = {
|
|
27
|
-
exact: '精确',
|
|
28
|
-
contains: '包含',
|
|
29
|
-
regex: '正则',
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const ResponseContentEditor = ({
|
|
33
|
-
form,
|
|
34
|
-
responses,
|
|
35
|
-
activeResponseId,
|
|
36
|
-
onPreview,
|
|
37
|
-
enableSorting = false
|
|
38
|
-
}) => {
|
|
39
|
-
const { getFieldValue, setFieldsValue } = form;
|
|
40
|
-
const [isEditing, setIsEditing] = useState(false);
|
|
41
|
-
const [editingName, setEditingName] = useState('');
|
|
42
|
-
const [isTextareaFullscreen, setIsTextareaFullscreen] = useState(false);
|
|
43
|
-
const [showSorting, setShowSorting] = useState(false);
|
|
44
|
-
|
|
45
|
-
const [responseParamMatcherEnabled, setResponseParamMatcherEnabled] = useState(() => {
|
|
46
|
-
const v = localStorage.getItem('responseParamMatcherEnabled');
|
|
47
|
-
return v === null ? true : v !== 'false';
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
const handleStorage = () => {
|
|
52
|
-
const v = localStorage.getItem('responseParamMatcherEnabled');
|
|
53
|
-
setResponseParamMatcherEnabled(v === null ? true : v !== 'false');
|
|
54
|
-
};
|
|
55
|
-
window.addEventListener('storage', handleStorage);
|
|
56
|
-
return () => window.removeEventListener('storage', handleStorage);
|
|
57
|
-
}, []);
|
|
58
|
-
|
|
59
|
-
// 查找激活的响应
|
|
60
|
-
const activeResponseIndex = responses.findIndex(r => r.id === activeResponseId);
|
|
61
|
-
const activeResponse = activeResponseIndex >= 0 ? responses[activeResponseIndex] : null;
|
|
62
|
-
// 拖拽排序:基于 ReactSortable 管理一个本地列表镜像
|
|
63
|
-
const [sortableList, setSortableList] = useState(() => {
|
|
64
|
-
return Array.isArray(responses) ? responses.map(r => ({ ...r })) : [];
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
setSortableList(Array.isArray(responses) ? responses.map(r => ({ ...r })) : []);
|
|
69
|
-
}, [responses]);
|
|
70
|
-
|
|
71
|
-
const handleAfterSort = (newList) => {
|
|
72
|
-
setFieldsValue({ responses: newList });
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// 全屏切换函数
|
|
77
|
-
const toggleTextareaFullscreen = () => {
|
|
78
|
-
setIsTextareaFullscreen(!isTextareaFullscreen);
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
// ESC键退出全屏 - 移到全屏覆盖层内部处理
|
|
82
|
-
const handleFullscreenEscKey = (event) => {
|
|
83
|
-
if (event.key === 'Escape') {
|
|
84
|
-
event.preventDefault();
|
|
85
|
-
event.stopPropagation();
|
|
86
|
-
setIsTextareaFullscreen(false);
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
// 添加新响应
|
|
91
|
-
const handleAddResponse = () => {
|
|
92
|
-
const newResponseId = generateResponseId();
|
|
93
|
-
let currentResponses = getFieldValue('responses') || [];
|
|
94
|
-
|
|
95
|
-
if (!Array.isArray(currentResponses)) {
|
|
96
|
-
try {
|
|
97
|
-
if (typeof currentResponses === 'string') {
|
|
98
|
-
currentResponses = JSON.parse(currentResponses);
|
|
99
|
-
}
|
|
100
|
-
} catch (e) {
|
|
101
|
-
currentResponses = [];
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (!Array.isArray(currentResponses)) {
|
|
106
|
-
currentResponses = [];
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const newResponse = {
|
|
110
|
-
id: newResponseId,
|
|
111
|
-
name: `响应 ${currentResponses.length + 1}`,
|
|
112
|
-
description: '',
|
|
113
|
-
content: '{\n "code": 0,\n "message": "success",\n "data": {}\n}',
|
|
114
|
-
paramMatchers: []
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const updatedResponses = [...currentResponses, newResponse];
|
|
118
|
-
|
|
119
|
-
setFieldsValue({
|
|
120
|
-
responses: updatedResponses,
|
|
121
|
-
activeResponseId: newResponseId
|
|
122
|
-
});
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// 删除响应
|
|
126
|
-
const handleDeleteResponse = (responseId) => {
|
|
127
|
-
let currentResponses = getFieldValue('responses') || [];
|
|
128
|
-
|
|
129
|
-
if (currentResponses.length <= 1) {
|
|
130
|
-
message.warning('至少需要保留一个响应');
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const updatedResponses = currentResponses.filter(resp => resp.id !== responseId);
|
|
135
|
-
|
|
136
|
-
let newActiveId = activeResponseId;
|
|
137
|
-
if (responseId === activeResponseId) {
|
|
138
|
-
newActiveId = updatedResponses[0]?.id;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
setFieldsValue({
|
|
142
|
-
responses: updatedResponses,
|
|
143
|
-
activeResponseId: newActiveId
|
|
144
|
-
});
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
// 切换响应
|
|
148
|
-
const handleResponseSelect = (responseId) => {
|
|
149
|
-
setFieldsValue({ activeResponseId: responseId });
|
|
150
|
-
setIsEditing(false);
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
// 开始编辑响应名称
|
|
154
|
-
const handleStartEdit = () => {
|
|
155
|
-
if (activeResponse) {
|
|
156
|
-
setEditingName(activeResponse.name || '');
|
|
157
|
-
setIsEditing(true);
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
// 完成编辑响应名称
|
|
162
|
-
const handleFinishEdit = () => {
|
|
163
|
-
if (activeResponseId && editingName.trim()) {
|
|
164
|
-
const currentResponses = getFieldValue('responses') || [];
|
|
165
|
-
const updatedResponses = currentResponses.map(resp =>
|
|
166
|
-
resp.id === activeResponseId ? { ...resp, name: editingName.trim() } : resp
|
|
167
|
-
);
|
|
168
|
-
setFieldsValue({ responses: updatedResponses });
|
|
169
|
-
}
|
|
170
|
-
setIsEditing(false);
|
|
171
|
-
setEditingName('');
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
// 取消编辑
|
|
175
|
-
const handleCancelEdit = () => {
|
|
176
|
-
setIsEditing(false);
|
|
177
|
-
setEditingName('');
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
// 更新响应内容
|
|
181
|
-
const updateActiveResponseContent = (content) => {
|
|
182
|
-
if (activeResponseIndex >= 0) {
|
|
183
|
-
const currentResponses = getFieldValue('responses') || [];
|
|
184
|
-
const newResponses = [...currentResponses];
|
|
185
|
-
newResponses[activeResponseIndex] = {
|
|
186
|
-
...newResponses[activeResponseIndex],
|
|
187
|
-
content
|
|
188
|
-
};
|
|
189
|
-
setFieldsValue({ responses: newResponses });
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
// 更新当前响应的 paramMatchers
|
|
194
|
-
const updateActiveResponseParamMatchers = (paramMatchers) => {
|
|
195
|
-
if (activeResponseIndex >= 0) {
|
|
196
|
-
const currentResponses = getFieldValue('responses') || [];
|
|
197
|
-
const newResponses = [...currentResponses];
|
|
198
|
-
newResponses[activeResponseIndex] = {
|
|
199
|
-
...newResponses[activeResponseIndex],
|
|
200
|
-
paramMatchers
|
|
201
|
-
};
|
|
202
|
-
setFieldsValue({ responses: newResponses });
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
// 添加入参匹配规则
|
|
207
|
-
const handleAddParamMatcher = () => {
|
|
208
|
-
const currentMatchers = (activeResponse && activeResponse.paramMatchers) || [];
|
|
209
|
-
updateActiveResponseParamMatchers([
|
|
210
|
-
...currentMatchers,
|
|
211
|
-
{ paramPath: '', paramValue: '', matchType: 'exact' }
|
|
212
|
-
]);
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
// 删除入参匹配规则
|
|
216
|
-
const handleRemoveParamMatcher = (index) => {
|
|
217
|
-
const currentMatchers = (activeResponse && activeResponse.paramMatchers) || [];
|
|
218
|
-
const newMatchers = currentMatchers.filter((_, i) => i !== index);
|
|
219
|
-
updateActiveResponseParamMatchers(newMatchers);
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
// 更新入参匹配规则某个字段
|
|
223
|
-
const handleParamMatcherChange = (index, field, value) => {
|
|
224
|
-
const currentMatchers = (activeResponse && activeResponse.paramMatchers) || [];
|
|
225
|
-
const newMatchers = currentMatchers.map((m, i) =>
|
|
226
|
-
i === index ? { ...m, [field]: value } : m
|
|
227
|
-
);
|
|
228
|
-
updateActiveResponseParamMatchers(newMatchers);
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
// 格式化JSON内容
|
|
232
|
-
const formatJsonContent = () => {
|
|
233
|
-
if (!activeResponse || !activeResponse.content) {
|
|
234
|
-
message.warning('没有可格式化的JSON内容');
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
try {
|
|
239
|
-
const parsedJson = JSON.parse(activeResponse.content);
|
|
240
|
-
const formattedJson = JSON.stringify(parsedJson, null, 2);
|
|
241
|
-
|
|
242
|
-
updateActiveResponseContent(formattedJson);
|
|
243
|
-
message.success('JSON已格式化');
|
|
244
|
-
} catch (error) {
|
|
245
|
-
message.error('无效的JSON格式,无法格式化');
|
|
246
|
-
}
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
// 预览响应内容
|
|
250
|
-
const handlePreview = () => {
|
|
251
|
-
if (!activeResponse) {
|
|
252
|
-
message.error('未找到有效的响应内容');
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (onPreview) {
|
|
257
|
-
onPreview();
|
|
258
|
-
}
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
// 渲染全屏TextArea
|
|
262
|
-
const renderFullscreenTextarea = () => {
|
|
263
|
-
if (!isTextareaFullscreen || !activeResponse) return null;
|
|
264
|
-
|
|
265
|
-
return ReactDOM.createPortal(
|
|
266
|
-
<div
|
|
267
|
-
className="textarea-fullscreen-overlay"
|
|
268
|
-
onKeyDown={handleFullscreenEscKey}
|
|
269
|
-
tabIndex={-1}
|
|
270
|
-
>
|
|
271
|
-
<div className="textarea-fullscreen-header">
|
|
272
|
-
<Button
|
|
273
|
-
icon={<FullscreenExitOutlined />}
|
|
274
|
-
onClick={toggleTextareaFullscreen}
|
|
275
|
-
size="small"
|
|
276
|
-
type="text"
|
|
277
|
-
style={{ color: '#595959' }}
|
|
278
|
-
>
|
|
279
|
-
退出全屏 (ESC)
|
|
280
|
-
</Button>
|
|
281
|
-
</div>
|
|
282
|
-
<TextArea
|
|
283
|
-
className="textarea-fullscreen-editor"
|
|
284
|
-
autoFocus
|
|
285
|
-
placeholder="请输入响应内容..."
|
|
286
|
-
value={activeResponse.content || ''}
|
|
287
|
-
onChange={(e) => updateActiveResponseContent(e.target.value)}
|
|
288
|
-
/>
|
|
289
|
-
</div>,
|
|
290
|
-
document.body
|
|
291
|
-
);
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
// 渲染响应级入参匹配规则区域
|
|
295
|
-
const renderParamMatchers = () => {
|
|
296
|
-
if (!activeResponse) return null;
|
|
297
|
-
|
|
298
|
-
const matchers = activeResponse.paramMatchers || [];
|
|
299
|
-
|
|
300
|
-
return (
|
|
301
|
-
<div style={{ marginBottom: '12px' }}>
|
|
302
|
-
<Divider orientation="left" orientationMargin={0} style={{ margin: '0 0 10px 0', fontSize: '12px' }}>
|
|
303
|
-
<Space size={6}>
|
|
304
|
-
<FilterOutlined style={{ color: '#1677ff' }} />
|
|
305
|
-
<span style={{ fontWeight: 'bold', color: '#333' }}>入参匹配规则</span>
|
|
306
|
-
{matchers.length > 0 && (
|
|
307
|
-
<Tag color="blue" style={{ fontSize: '11px', padding: '0 4px' }}>{matchers.length}条</Tag>
|
|
308
|
-
)}
|
|
309
|
-
<Tooltip title="满足所有规则时返回此响应;无规则的响应作为默认兜底。响应按列表顺序依次检查,第一个匹配的响应被返回。">
|
|
310
|
-
<QuestionCircleOutlined style={{ color: '#bbb', cursor: 'help', fontSize: '12px' }} />
|
|
311
|
-
</Tooltip>
|
|
312
|
-
</Space>
|
|
313
|
-
</Divider>
|
|
314
|
-
<div>
|
|
315
|
-
{matchers.map((matcher, index) => (
|
|
316
|
-
<div key={index} style={{ display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '8px' }}>
|
|
317
|
-
<Input
|
|
318
|
-
style={{ flex: 1 }}
|
|
319
|
-
placeholder="参数路径(如:userId 或 data.user.id)"
|
|
320
|
-
value={matcher.paramPath || ''}
|
|
321
|
-
onChange={(e) => handleParamMatcherChange(index, 'paramPath', e.target.value)}
|
|
322
|
-
size="small"
|
|
323
|
-
/>
|
|
324
|
-
<Input
|
|
325
|
-
style={{ flex: 1 }}
|
|
326
|
-
placeholder="期望值(如:123 或 admin)"
|
|
327
|
-
value={matcher.paramValue || ''}
|
|
328
|
-
onChange={(e) => handleParamMatcherChange(index, 'paramValue', e.target.value)}
|
|
329
|
-
size="small"
|
|
330
|
-
/>
|
|
331
|
-
<Select
|
|
332
|
-
style={{ width: 90 }}
|
|
333
|
-
value={matcher.matchType || 'exact'}
|
|
334
|
-
onChange={(val) => handleParamMatcherChange(index, 'matchType', val)}
|
|
335
|
-
size="small"
|
|
336
|
-
>
|
|
337
|
-
<Option value="exact">精确匹配</Option>
|
|
338
|
-
<Option value="contains">包含</Option>
|
|
339
|
-
<Option value="regex">正则</Option>
|
|
340
|
-
</Select>
|
|
341
|
-
<Button
|
|
342
|
-
type="text"
|
|
343
|
-
danger
|
|
344
|
-
icon={<DeleteOutlined />}
|
|
345
|
-
size="small"
|
|
346
|
-
onClick={() => handleRemoveParamMatcher(index)}
|
|
347
|
-
/>
|
|
348
|
-
</div>
|
|
349
|
-
))}
|
|
350
|
-
<Button
|
|
351
|
-
type="dashed"
|
|
352
|
-
size="small"
|
|
353
|
-
icon={<PlusOutlined />}
|
|
354
|
-
onClick={handleAddParamMatcher}
|
|
355
|
-
style={{ width: '100%' }}
|
|
356
|
-
>
|
|
357
|
-
添加匹配规则
|
|
358
|
-
</Button>
|
|
359
|
-
{matchers.length === 0 && (
|
|
360
|
-
<div style={{ color: '#bbb', fontSize: '12px', marginTop: '6px', textAlign: 'center' }}>
|
|
361
|
-
无规则时此响应作为默认兜底响应
|
|
362
|
-
</div>
|
|
363
|
-
)}
|
|
364
|
-
</div>
|
|
365
|
-
</div>
|
|
366
|
-
);
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
// 如果没有响应数据,显示创建按钮
|
|
370
|
-
if (!Array.isArray(responses) || responses.length === 0) {
|
|
371
|
-
return (
|
|
372
|
-
<Card
|
|
373
|
-
title={
|
|
374
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
375
|
-
<CodeOutlined />
|
|
376
|
-
<span>响应内容编辑</span>
|
|
377
|
-
</div>
|
|
378
|
-
}
|
|
379
|
-
style={{ marginBottom: 16 }}
|
|
380
|
-
bodyStyle={{ padding: '16px' }}
|
|
381
|
-
>
|
|
382
|
-
<div style={{
|
|
383
|
-
textAlign: 'center',
|
|
384
|
-
padding: '40px 20px',
|
|
385
|
-
borderRadius: '6px',
|
|
386
|
-
border: '1px dashed #d9d9d9'
|
|
387
|
-
}}>
|
|
388
|
-
<FileTextOutlined style={{ fontSize: '32px', color: '#bfbfbf', marginBottom: '12px' }} />
|
|
389
|
-
<div style={{ color: '#999', marginBottom: '16px' }}>暂无响应数据</div>
|
|
390
|
-
<Button
|
|
391
|
-
type="primary"
|
|
392
|
-
icon={<PlusCircleOutlined />}
|
|
393
|
-
onClick={handleAddResponse}
|
|
394
|
-
>
|
|
395
|
-
创建第一个响应
|
|
396
|
-
</Button>
|
|
397
|
-
</div>
|
|
398
|
-
</Card>
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
return (
|
|
403
|
-
<Card
|
|
404
|
-
title={
|
|
405
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
406
|
-
<CodeOutlined />
|
|
407
|
-
<span>响应内容编辑</span>
|
|
408
|
-
<Badge
|
|
409
|
-
count={responses.length}
|
|
410
|
-
style={{ backgroundColor: '#52c41a' }}
|
|
411
|
-
/>
|
|
412
|
-
</div>
|
|
413
|
-
}
|
|
414
|
-
extra={
|
|
415
|
-
<Space size="small">
|
|
416
|
-
{enableSorting && (
|
|
417
|
-
<Button
|
|
418
|
-
type="default"
|
|
419
|
-
icon={<MenuOutlined />}
|
|
420
|
-
onClick={() => setShowSorting(!showSorting)}
|
|
421
|
-
size="small"
|
|
422
|
-
style={{ display: 'flex', alignItems: 'center', gap: '4px' }}
|
|
423
|
-
>
|
|
424
|
-
排序
|
|
425
|
-
</Button>
|
|
426
|
-
)}
|
|
427
|
-
<Button
|
|
428
|
-
type="default"
|
|
429
|
-
icon={<PlusCircleOutlined />}
|
|
430
|
-
onClick={handleAddResponse}
|
|
431
|
-
size="small"
|
|
432
|
-
style={{
|
|
433
|
-
display: 'flex',
|
|
434
|
-
alignItems: 'center',
|
|
435
|
-
gap: '4px'
|
|
436
|
-
}}
|
|
437
|
-
>
|
|
438
|
-
添加响应
|
|
439
|
-
</Button>
|
|
440
|
-
<Button
|
|
441
|
-
type="default"
|
|
442
|
-
icon={<EyeOutlined />}
|
|
443
|
-
onClick={handlePreview}
|
|
444
|
-
size="small"
|
|
445
|
-
style={{
|
|
446
|
-
display: 'flex',
|
|
447
|
-
alignItems: 'center',
|
|
448
|
-
gap: '4px',
|
|
449
|
-
borderColor: '#52c41a',
|
|
450
|
-
color: '#52c41a'
|
|
451
|
-
}}
|
|
452
|
-
>
|
|
453
|
-
预览
|
|
454
|
-
</Button>
|
|
455
|
-
<Button
|
|
456
|
-
type="default"
|
|
457
|
-
icon={<FormatPainterOutlined />}
|
|
458
|
-
onClick={formatJsonContent}
|
|
459
|
-
size="small"
|
|
460
|
-
style={{
|
|
461
|
-
display: 'flex',
|
|
462
|
-
alignItems: 'center',
|
|
463
|
-
gap: '4px',
|
|
464
|
-
borderColor: '#fa8c16',
|
|
465
|
-
color: '#fa8c16'
|
|
466
|
-
}}
|
|
467
|
-
>
|
|
468
|
-
格式化JSON
|
|
469
|
-
</Button>
|
|
470
|
-
</Space>
|
|
471
|
-
}
|
|
472
|
-
style={{ marginBottom: 16 }}
|
|
473
|
-
bodyStyle={{ padding: '16px' }}
|
|
474
|
-
>
|
|
475
|
-
{enableSorting && showSorting && Array.isArray(sortableList) && sortableList.length > 1 && (
|
|
476
|
-
<div style={{ marginBottom: 12 }}>
|
|
477
|
-
<ReactSortable
|
|
478
|
-
list={sortableList}
|
|
479
|
-
setList={(newState) => {
|
|
480
|
-
setSortableList(newState);
|
|
481
|
-
handleAfterSort(newState);
|
|
482
|
-
}}
|
|
483
|
-
animation={150}
|
|
484
|
-
ghostClass="sortable-ghost"
|
|
485
|
-
style={{ display: 'flex', gap: 8, overflowX: 'auto', paddingBottom: 4 }}
|
|
486
|
-
>
|
|
487
|
-
{sortableList.map((item) => (
|
|
488
|
-
<div
|
|
489
|
-
key={item.id}
|
|
490
|
-
className={`sortable-chip${item.id === activeResponseId ? ' sortable-chip-active' : ''}`}
|
|
491
|
-
onClick={() => handleResponseSelect(item.id)}
|
|
492
|
-
title={item.name || '未命名响应'}
|
|
493
|
-
>
|
|
494
|
-
{item.name || '未命名响应'}
|
|
495
|
-
</div>
|
|
496
|
-
))}
|
|
497
|
-
</ReactSortable>
|
|
498
|
-
</div>
|
|
499
|
-
)}
|
|
500
|
-
{/* 响应选择和名称编辑合并区域 */}
|
|
501
|
-
<div style={{ marginBottom: '16px' }}>
|
|
502
|
-
<Row gutter={12} align="middle">
|
|
503
|
-
<Col span={20}>
|
|
504
|
-
<div style={{ marginBottom: '8px' }}>
|
|
505
|
-
<label style={{
|
|
506
|
-
fontSize: '12px',
|
|
507
|
-
color: '#666',
|
|
508
|
-
fontWeight: 'bold'
|
|
509
|
-
}}>
|
|
510
|
-
选择响应 / 编辑名称
|
|
511
|
-
</label>
|
|
512
|
-
</div>
|
|
513
|
-
|
|
514
|
-
<div style={{ position: 'relative' }}>
|
|
515
|
-
{isEditing ? (
|
|
516
|
-
<Input.Group compact>
|
|
517
|
-
<Input
|
|
518
|
-
style={{ width: 'calc(100% - 80px)' }}
|
|
519
|
-
value={editingName}
|
|
520
|
-
onChange={(e) => setEditingName(e.target.value)}
|
|
521
|
-
onPressEnter={handleFinishEdit}
|
|
522
|
-
onBlur={handleFinishEdit}
|
|
523
|
-
placeholder="输入响应名称"
|
|
524
|
-
autoFocus
|
|
525
|
-
/>
|
|
526
|
-
<Button
|
|
527
|
-
style={{ width: '40px' }}
|
|
528
|
-
type="primary"
|
|
529
|
-
icon={<CheckCircleOutlined />}
|
|
530
|
-
onClick={handleFinishEdit}
|
|
531
|
-
/>
|
|
532
|
-
<Button
|
|
533
|
-
style={{ width: '40px' }}
|
|
534
|
-
icon={<DeleteOutlined />}
|
|
535
|
-
onClick={handleCancelEdit}
|
|
536
|
-
/>
|
|
537
|
-
</Input.Group>
|
|
538
|
-
) : (
|
|
539
|
-
<Input.Group compact>
|
|
540
|
-
<Select
|
|
541
|
-
style={{ width: 'calc(100% - 40px)' }}
|
|
542
|
-
placeholder="选择要编辑的响应"
|
|
543
|
-
value={activeResponseId}
|
|
544
|
-
onChange={handleResponseSelect}
|
|
545
|
-
>
|
|
546
|
-
{responses.map((resp, index) => (
|
|
547
|
-
<Option key={resp.id} value={resp.id}>
|
|
548
|
-
{resp.name || `响应 ${index + 1}`}
|
|
549
|
-
</Option>
|
|
550
|
-
))}
|
|
551
|
-
</Select>
|
|
552
|
-
<Tooltip title="编辑响应名称">
|
|
553
|
-
<Button
|
|
554
|
-
style={{ width: '40px' }}
|
|
555
|
-
icon={<EditOutlined />}
|
|
556
|
-
onClick={handleStartEdit}
|
|
557
|
-
disabled={!activeResponse}
|
|
558
|
-
/>
|
|
559
|
-
</Tooltip>
|
|
560
|
-
</Input.Group>
|
|
561
|
-
)}
|
|
562
|
-
</div>
|
|
563
|
-
</Col>
|
|
564
|
-
|
|
565
|
-
<Col span={4}>
|
|
566
|
-
<div style={{ marginBottom: '8px' }}>
|
|
567
|
-
<label style={{
|
|
568
|
-
fontSize: '12px',
|
|
569
|
-
color: '#666',
|
|
570
|
-
fontWeight: 'bold'
|
|
571
|
-
}}>
|
|
572
|
-
操作
|
|
573
|
-
</label>
|
|
574
|
-
</div>
|
|
575
|
-
<Tooltip title="删除当前响应">
|
|
576
|
-
<Button
|
|
577
|
-
type="text"
|
|
578
|
-
danger
|
|
579
|
-
icon={<DeleteOutlined />}
|
|
580
|
-
onClick={() => activeResponseId && handleDeleteResponse(activeResponseId)}
|
|
581
|
-
disabled={responses.length <= 1 || !activeResponse}
|
|
582
|
-
style={{ width: '100%' }}
|
|
583
|
-
>
|
|
584
|
-
删除
|
|
585
|
-
</Button>
|
|
586
|
-
</Tooltip>
|
|
587
|
-
</Col>
|
|
588
|
-
</Row>
|
|
589
|
-
</div>
|
|
590
|
-
|
|
591
|
-
{/* 响应级入参匹配规则区域 */}
|
|
592
|
-
{activeResponse && responseParamMatcherEnabled && renderParamMatchers()}
|
|
593
|
-
|
|
594
|
-
{/* 响应内容编辑区域 */}
|
|
595
|
-
{activeResponse && (
|
|
596
|
-
<>
|
|
597
|
-
<div style={{ marginBottom: '8px' }}>
|
|
598
|
-
<label style={{
|
|
599
|
-
fontSize: '12px',
|
|
600
|
-
color: '#666',
|
|
601
|
-
fontWeight: 'bold'
|
|
602
|
-
}}>
|
|
603
|
-
响应内容
|
|
604
|
-
</label>
|
|
605
|
-
</div>
|
|
606
|
-
|
|
607
|
-
<div style={{ position: 'relative' }}>
|
|
608
|
-
<TextArea
|
|
609
|
-
rows={26}
|
|
610
|
-
placeholder="请输入响应内容..."
|
|
611
|
-
style={{
|
|
612
|
-
fontFamily: 'Monaco, Menlo, "Ubuntu Mono", monospace',
|
|
613
|
-
fontSize: '13px',
|
|
614
|
-
lineHeight: '1.5',
|
|
615
|
-
minHeight: '620px'
|
|
616
|
-
}}
|
|
617
|
-
value={activeResponse.content || ''}
|
|
618
|
-
onChange={(e) => updateActiveResponseContent(e.target.value)}
|
|
619
|
-
/>
|
|
620
|
-
<Button
|
|
621
|
-
className="textarea-fullscreen-btn"
|
|
622
|
-
icon={<FullscreenOutlined />}
|
|
623
|
-
onClick={toggleTextareaFullscreen}
|
|
624
|
-
size="small"
|
|
625
|
-
style={{
|
|
626
|
-
position: 'absolute',
|
|
627
|
-
top: '8px',
|
|
628
|
-
right: '12px',
|
|
629
|
-
background: 'rgba(255, 255, 255, 0.9)',
|
|
630
|
-
border: '1px solid #d9d9d9',
|
|
631
|
-
zIndex: 10
|
|
632
|
-
}}
|
|
633
|
-
title="全屏编辑"
|
|
634
|
-
/>
|
|
635
|
-
</div>
|
|
636
|
-
</>
|
|
637
|
-
)}
|
|
638
|
-
|
|
639
|
-
{/* 渲染全屏编辑器 */}
|
|
640
|
-
{renderFullscreenTextarea()}
|
|
641
|
-
</Card>
|
|
642
|
-
);
|
|
643
|
-
};
|
|
644
|
-
|
|
645
|
-
export default ResponseContentEditor;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// 常量定义文件
|
|
2
|
-
export const contentTypes = [
|
|
3
|
-
{ value: 'application/json; charset=utf-8', label: 'JSON' },
|
|
4
|
-
{ value: 'text/plain; charset=utf-8', label: '纯文本' },
|
|
5
|
-
{ value: 'text/html; charset=utf-8', label: 'HTML' },
|
|
6
|
-
{ value: 'application/xml; charset=utf-8', label: 'XML' },
|
|
7
|
-
{ value: 'application/javascript; charset=utf-8', label: 'JavaScript' },
|
|
8
|
-
];
|
|
9
|
-
|
|
10
|
-
export const proxyTypes = [
|
|
11
|
-
{ value: 'response', label: '模拟响应' },
|
|
12
|
-
{ value: 'redirect', label: '重定向' },
|
|
13
|
-
{ value: 'url_redirect', label: 'URL重定向' },
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
export const statusCodes = [
|
|
17
|
-
{ value: '200', label: '200 OK' },
|
|
18
|
-
{ value: '201', label: '201 Created' },
|
|
19
|
-
{ value: '204', label: '204 No Content' },
|
|
20
|
-
{ value: '400', label: '400 Bad Request' },
|
|
21
|
-
{ value: '401', label: '401 Unauthorized' },
|
|
22
|
-
{ value: '403', label: '403 Forbidden' },
|
|
23
|
-
{ value: '404', label: '404 Not Found' },
|
|
24
|
-
{ value: '500', label: '500 Internal Server Error' },
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
export const httpMethods = [
|
|
28
|
-
{ value: 'ALL', label: '所有方法' },
|
|
29
|
-
{ value: 'GET', label: 'GET' },
|
|
30
|
-
{ value: 'POST', label: 'POST' },
|
|
31
|
-
{ value: 'PUT', label: 'PUT' },
|
|
32
|
-
{ value: 'DELETE', label: 'DELETE' },
|
|
33
|
-
{ value: 'PATCH', label: 'PATCH' },
|
|
34
|
-
{ value: 'HEAD', label: 'HEAD' },
|
|
35
|
-
{ value: 'OPTIONS', label: 'OPTIONS' },
|
|
36
|
-
];
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
// 统一导出接口管理相关的组件
|
|
2
|
-
export { default as ResponseContentEditor } from './ResponseContentEditor';
|
|
3
|
-
export { default as InterfaceTestModal } from './InterfaceTestModal';
|
|
4
|
-
export { default as PreviewModal } from './PreviewModal';
|
|
5
|
-
export { default as DataManagementDrawer } from './DataManagementDrawer';
|
|
6
|
-
export * from './constants';
|
|
7
|
-
export * from './utils';
|