whistle.mock-plugins 1.2.2-beta.13 → 1.2.2-beta.18
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/COLUMN_RESIZE_IMPLEMENTATION.md +162 -0
- package/TEST_GUIDE.md +156 -0
- package/VERSION.md +34 -0
- package/app/public/js/index.js +1 -1
- package/app/public/js/index.js.LICENSE.txt +0 -2
- package/lib/models.js +9 -0
- package/lib/routes/interfaces.js +4 -2
- package/lib/ruleManager.js +30 -47
- package/package.json +2 -1
- package/ui/src/components/interface-management/DataManagementDrawer.js +115 -0
- package/ui/src/components/interface-management/ResponseContentEditor.js +118 -132
- package/ui/src/components/interface-management/index.js +1 -0
- package/ui/src/pages/InterfaceManagementV2.js +500 -142
- package/ui/src/pages/Settings.js +75 -6
- package/ui/src/styles/interface-management.css +37 -1
- package/ui/src/styles/settings.css +29 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
2
|
import { ReactSortable } from 'react-sortablejs';
|
|
3
3
|
import ReactDOM from 'react-dom';
|
|
4
|
-
import { Card, Button, Space, Badge, Row, Col, Input, Select, Tooltip, message,
|
|
4
|
+
import { Card, Button, Space, Badge, Row, Col, Input, Select, Tooltip, message, Tag, Divider } from 'antd';
|
|
5
5
|
import {
|
|
6
6
|
CodeOutlined,
|
|
7
7
|
EyeOutlined,
|
|
@@ -15,13 +15,20 @@ import {
|
|
|
15
15
|
FullscreenExitOutlined,
|
|
16
16
|
MenuOutlined,
|
|
17
17
|
FilterOutlined,
|
|
18
|
-
|
|
18
|
+
PlusOutlined,
|
|
19
|
+
QuestionCircleOutlined
|
|
19
20
|
} from '@ant-design/icons';
|
|
20
21
|
import { generateResponseId } from './utils';
|
|
21
22
|
|
|
22
23
|
const { TextArea } = Input;
|
|
23
24
|
const { Option } = Select;
|
|
24
25
|
|
|
26
|
+
const MATCH_TYPE_LABELS = {
|
|
27
|
+
exact: '精确',
|
|
28
|
+
contains: '包含',
|
|
29
|
+
regex: '正则',
|
|
30
|
+
};
|
|
31
|
+
|
|
25
32
|
const ResponseContentEditor = ({
|
|
26
33
|
form,
|
|
27
34
|
responses,
|
|
@@ -48,7 +55,6 @@ const ResponseContentEditor = ({
|
|
|
48
55
|
}, [responses]);
|
|
49
56
|
|
|
50
57
|
const handleAfterSort = (newList) => {
|
|
51
|
-
// 将排序后的列表写回表单 responses 字段;activeResponseId 不变
|
|
52
58
|
setFieldsValue({ responses: newList });
|
|
53
59
|
};
|
|
54
60
|
|
|
@@ -72,7 +78,6 @@ const ResponseContentEditor = ({
|
|
|
72
78
|
const newResponseId = generateResponseId();
|
|
73
79
|
let currentResponses = getFieldValue('responses') || [];
|
|
74
80
|
|
|
75
|
-
// 确保数组格式
|
|
76
81
|
if (!Array.isArray(currentResponses)) {
|
|
77
82
|
try {
|
|
78
83
|
if (typeof currentResponses === 'string') {
|
|
@@ -97,7 +102,6 @@ const ResponseContentEditor = ({
|
|
|
97
102
|
|
|
98
103
|
const updatedResponses = [...currentResponses, newResponse];
|
|
99
104
|
|
|
100
|
-
// 更新表单字段
|
|
101
105
|
setFieldsValue({
|
|
102
106
|
responses: updatedResponses,
|
|
103
107
|
activeResponseId: newResponseId
|
|
@@ -115,7 +119,6 @@ const ResponseContentEditor = ({
|
|
|
115
119
|
|
|
116
120
|
const updatedResponses = currentResponses.filter(resp => resp.id !== responseId);
|
|
117
121
|
|
|
118
|
-
// 如果删除的是当前选中的响应,切换到第一个响应
|
|
119
122
|
let newActiveId = activeResponseId;
|
|
120
123
|
if (responseId === activeResponseId) {
|
|
121
124
|
newActiveId = updatedResponses[0]?.id;
|
|
@@ -186,27 +189,29 @@ const ResponseContentEditor = ({
|
|
|
186
189
|
}
|
|
187
190
|
};
|
|
188
191
|
|
|
189
|
-
//
|
|
190
|
-
const
|
|
191
|
-
const currentMatchers = activeResponse
|
|
192
|
+
// 添加入参匹配规则
|
|
193
|
+
const handleAddParamMatcher = () => {
|
|
194
|
+
const currentMatchers = (activeResponse && activeResponse.paramMatchers) || [];
|
|
192
195
|
updateActiveResponseParamMatchers([
|
|
193
196
|
...currentMatchers,
|
|
194
197
|
{ paramPath: '', paramValue: '', matchType: 'exact' }
|
|
195
198
|
]);
|
|
196
199
|
};
|
|
197
200
|
|
|
198
|
-
//
|
|
199
|
-
const
|
|
200
|
-
const currentMatchers =
|
|
201
|
-
currentMatchers.
|
|
202
|
-
updateActiveResponseParamMatchers(
|
|
201
|
+
// 删除入参匹配规则
|
|
202
|
+
const handleRemoveParamMatcher = (index) => {
|
|
203
|
+
const currentMatchers = (activeResponse && activeResponse.paramMatchers) || [];
|
|
204
|
+
const newMatchers = currentMatchers.filter((_, i) => i !== index);
|
|
205
|
+
updateActiveResponseParamMatchers(newMatchers);
|
|
203
206
|
};
|
|
204
207
|
|
|
205
|
-
//
|
|
206
|
-
const
|
|
207
|
-
const currentMatchers =
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
// 更新入参匹配规则某个字段
|
|
209
|
+
const handleParamMatcherChange = (index, field, value) => {
|
|
210
|
+
const currentMatchers = (activeResponse && activeResponse.paramMatchers) || [];
|
|
211
|
+
const newMatchers = currentMatchers.map((m, i) =>
|
|
212
|
+
i === index ? { ...m, [field]: value } : m
|
|
213
|
+
);
|
|
214
|
+
updateActiveResponseParamMatchers(newMatchers);
|
|
210
215
|
};
|
|
211
216
|
|
|
212
217
|
// 格式化JSON内容
|
|
@@ -272,6 +277,83 @@ const ResponseContentEditor = ({
|
|
|
272
277
|
);
|
|
273
278
|
};
|
|
274
279
|
|
|
280
|
+
// 渲染响应级入参匹配规则区域
|
|
281
|
+
const renderParamMatchers = () => {
|
|
282
|
+
if (!activeResponse) return null;
|
|
283
|
+
|
|
284
|
+
const matchers = activeResponse.paramMatchers || [];
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<div style={{ marginBottom: '12px' }}>
|
|
288
|
+
<Divider orientation="left" orientationMargin={0} style={{ margin: '0 0 10px 0', fontSize: '12px' }}>
|
|
289
|
+
<Space size={6}>
|
|
290
|
+
<FilterOutlined style={{ color: '#1890ff' }} />
|
|
291
|
+
<span style={{ fontWeight: 'bold', color: '#333' }}>入参匹配规则</span>
|
|
292
|
+
{matchers.length > 0 ? (
|
|
293
|
+
<Tag color="blue" style={{ fontSize: '11px', padding: '0 4px' }}>{matchers.length}条</Tag>
|
|
294
|
+
) : (
|
|
295
|
+
<Tag color="default" style={{ fontSize: '11px', padding: '0 4px' }}>无规则(默认响应)</Tag>
|
|
296
|
+
)}
|
|
297
|
+
<Tooltip title="满足所有规则时返回此响应;无规则的响应作为默认兜底。响应按列表顺序依次检查,第一个匹配的响应被返回。">
|
|
298
|
+
<QuestionCircleOutlined style={{ color: '#bbb', cursor: 'help', fontSize: '12px' }} />
|
|
299
|
+
</Tooltip>
|
|
300
|
+
</Space>
|
|
301
|
+
</Divider>
|
|
302
|
+
<div>
|
|
303
|
+
{matchers.map((matcher, index) => (
|
|
304
|
+
<div key={index} style={{ display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '8px' }}>
|
|
305
|
+
<Input
|
|
306
|
+
style={{ flex: 1 }}
|
|
307
|
+
placeholder="参数路径(如:userId 或 data.user.id)"
|
|
308
|
+
value={matcher.paramPath || ''}
|
|
309
|
+
onChange={(e) => handleParamMatcherChange(index, 'paramPath', e.target.value)}
|
|
310
|
+
size="small"
|
|
311
|
+
/>
|
|
312
|
+
<Input
|
|
313
|
+
style={{ flex: 1 }}
|
|
314
|
+
placeholder="期望值(如:123 或 admin)"
|
|
315
|
+
value={matcher.paramValue || ''}
|
|
316
|
+
onChange={(e) => handleParamMatcherChange(index, 'paramValue', e.target.value)}
|
|
317
|
+
size="small"
|
|
318
|
+
/>
|
|
319
|
+
<Select
|
|
320
|
+
style={{ width: 90 }}
|
|
321
|
+
value={matcher.matchType || 'exact'}
|
|
322
|
+
onChange={(val) => handleParamMatcherChange(index, 'matchType', val)}
|
|
323
|
+
size="small"
|
|
324
|
+
>
|
|
325
|
+
<Option value="exact">精确匹配</Option>
|
|
326
|
+
<Option value="contains">包含</Option>
|
|
327
|
+
<Option value="regex">正则</Option>
|
|
328
|
+
</Select>
|
|
329
|
+
<Button
|
|
330
|
+
type="text"
|
|
331
|
+
danger
|
|
332
|
+
icon={<DeleteOutlined />}
|
|
333
|
+
size="small"
|
|
334
|
+
onClick={() => handleRemoveParamMatcher(index)}
|
|
335
|
+
/>
|
|
336
|
+
</div>
|
|
337
|
+
))}
|
|
338
|
+
<Button
|
|
339
|
+
type="dashed"
|
|
340
|
+
size="small"
|
|
341
|
+
icon={<PlusOutlined />}
|
|
342
|
+
onClick={handleAddParamMatcher}
|
|
343
|
+
style={{ width: '100%' }}
|
|
344
|
+
>
|
|
345
|
+
添加匹配规则
|
|
346
|
+
</Button>
|
|
347
|
+
{matchers.length === 0 && (
|
|
348
|
+
<div style={{ color: '#bbb', fontSize: '12px', marginTop: '6px', textAlign: 'center' }}>
|
|
349
|
+
无规则时此响应作为默认兜底响应
|
|
350
|
+
</div>
|
|
351
|
+
)}
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
);
|
|
355
|
+
};
|
|
356
|
+
|
|
275
357
|
// 如果没有响应数据,显示创建按钮
|
|
276
358
|
if (!Array.isArray(responses) || responses.length === 0) {
|
|
277
359
|
return (
|
|
@@ -422,7 +504,6 @@ const ResponseContentEditor = ({
|
|
|
422
504
|
|
|
423
505
|
<div style={{ position: 'relative' }}>
|
|
424
506
|
{isEditing ? (
|
|
425
|
-
// 编辑模式:显示输入框
|
|
426
507
|
<Input.Group compact>
|
|
427
508
|
<Input
|
|
428
509
|
style={{ width: 'calc(100% - 80px)' }}
|
|
@@ -446,7 +527,6 @@ const ResponseContentEditor = ({
|
|
|
446
527
|
/>
|
|
447
528
|
</Input.Group>
|
|
448
529
|
) : (
|
|
449
|
-
// 选择模式:显示下拉选择器
|
|
450
530
|
<Input.Group compact>
|
|
451
531
|
<Select
|
|
452
532
|
style={{ width: 'calc(100% - 40px)' }}
|
|
@@ -456,17 +536,21 @@ const ResponseContentEditor = ({
|
|
|
456
536
|
optionLabelProp="label"
|
|
457
537
|
>
|
|
458
538
|
{responses.map((resp, index) => {
|
|
459
|
-
const matcherCount = resp.paramMatchers
|
|
539
|
+
const matcherCount = (resp.paramMatchers || []).filter(m => m.paramPath).length;
|
|
540
|
+
const isDefault = resp.id === getFieldValue('activeResponseId');
|
|
541
|
+
const labelContent = (
|
|
542
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
|
543
|
+
<CheckCircleOutlined style={{ color: '#52c41a' }} />
|
|
544
|
+
<span>{resp.name || `响应 ${index + 1}`}</span>
|
|
545
|
+
{isDefault && <Tag color="green" style={{ marginLeft: 2, fontSize: '11px', padding: '0 4px' }}>默认</Tag>}
|
|
546
|
+
{matcherCount > 0 && <Tag color="blue" style={{ marginLeft: 2, fontSize: '11px', padding: '0 4px' }}>{matcherCount}条规则</Tag>}
|
|
547
|
+
</div>
|
|
548
|
+
);
|
|
460
549
|
return (
|
|
461
550
|
<Option
|
|
462
551
|
key={resp.id}
|
|
463
552
|
value={resp.id}
|
|
464
|
-
label={
|
|
465
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
466
|
-
<CheckCircleOutlined style={{ color: '#52c41a' }} />
|
|
467
|
-
<span>{resp.name || `响应 ${index + 1}`}</span>
|
|
468
|
-
</div>
|
|
469
|
-
}
|
|
553
|
+
label={labelContent}
|
|
470
554
|
>
|
|
471
555
|
<div style={{ padding: '4px 0' }}>
|
|
472
556
|
<div style={{
|
|
@@ -479,17 +563,11 @@ const ResponseContentEditor = ({
|
|
|
479
563
|
{resp.name || `响应 ${index + 1}`}
|
|
480
564
|
</span>
|
|
481
565
|
<Space size={4}>
|
|
482
|
-
{
|
|
483
|
-
|
|
566
|
+
{isDefault && <Badge status="processing" text="默认" />}
|
|
567
|
+
{matcherCount > 0 && (
|
|
568
|
+
<Tag color="blue" style={{ fontSize: '11px', padding: '0 4px' }}>
|
|
484
569
|
{matcherCount}条规则
|
|
485
570
|
</Tag>
|
|
486
|
-
) : (
|
|
487
|
-
<Tag color="default" style={{ fontSize: '11px', margin: 0 }}>
|
|
488
|
-
兜底
|
|
489
|
-
</Tag>
|
|
490
|
-
)}
|
|
491
|
-
{resp.id === activeResponseId && (
|
|
492
|
-
<Badge status="processing" text="当前选中" />
|
|
493
571
|
)}
|
|
494
572
|
</Space>
|
|
495
573
|
</div>
|
|
@@ -537,100 +615,8 @@ const ResponseContentEditor = ({
|
|
|
537
615
|
</Row>
|
|
538
616
|
</div>
|
|
539
617
|
|
|
540
|
-
{/*
|
|
541
|
-
{activeResponse && (
|
|
542
|
-
<div style={{
|
|
543
|
-
marginBottom: '16px',
|
|
544
|
-
border: '1px solid #e8e8e8',
|
|
545
|
-
borderRadius: '6px',
|
|
546
|
-
padding: '12px',
|
|
547
|
-
background: '#fafafa'
|
|
548
|
-
}}>
|
|
549
|
-
<div style={{
|
|
550
|
-
display: 'flex',
|
|
551
|
-
alignItems: 'center',
|
|
552
|
-
justifyContent: 'space-between',
|
|
553
|
-
marginBottom: '10px'
|
|
554
|
-
}}>
|
|
555
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
|
556
|
-
<FilterOutlined style={{ color: '#1890ff' }} />
|
|
557
|
-
<label style={{ fontSize: '12px', color: '#333', fontWeight: 'bold' }}>
|
|
558
|
-
入参匹配规则
|
|
559
|
-
</label>
|
|
560
|
-
{activeResponse.paramMatchers && activeResponse.paramMatchers.length > 0 ? (
|
|
561
|
-
<Tag color="blue" style={{ fontSize: '11px', lineHeight: '18px', padding: '0 6px' }}>
|
|
562
|
-
{activeResponse.paramMatchers.length}条
|
|
563
|
-
</Tag>
|
|
564
|
-
) : (
|
|
565
|
-
<Tag color="default" style={{ fontSize: '11px', lineHeight: '18px', padding: '0 6px' }}>
|
|
566
|
-
兜底响应
|
|
567
|
-
</Tag>
|
|
568
|
-
)}
|
|
569
|
-
</div>
|
|
570
|
-
<Button
|
|
571
|
-
type="dashed"
|
|
572
|
-
size="small"
|
|
573
|
-
icon={<PlusCircleOutlined />}
|
|
574
|
-
onClick={handleAddMatcher}
|
|
575
|
-
style={{ fontSize: '12px' }}
|
|
576
|
-
>
|
|
577
|
-
添加规则
|
|
578
|
-
</Button>
|
|
579
|
-
</div>
|
|
580
|
-
|
|
581
|
-
{(!activeResponse.paramMatchers || activeResponse.paramMatchers.length === 0) && (
|
|
582
|
-
<div style={{
|
|
583
|
-
color: '#999',
|
|
584
|
-
fontSize: '12px',
|
|
585
|
-
textAlign: 'center',
|
|
586
|
-
padding: '8px 0'
|
|
587
|
-
}}>
|
|
588
|
-
无匹配规则,当其他响应均不匹配时作为兜底返回
|
|
589
|
-
</div>
|
|
590
|
-
)}
|
|
591
|
-
|
|
592
|
-
{activeResponse.paramMatchers && activeResponse.paramMatchers.map((matcher, index) => (
|
|
593
|
-
<Row key={index} gutter={8} style={{ marginBottom: '8px' }} align="middle">
|
|
594
|
-
<Col span={8}>
|
|
595
|
-
<Input
|
|
596
|
-
size="small"
|
|
597
|
-
placeholder="参数路径,如 data.type"
|
|
598
|
-
value={matcher.paramPath}
|
|
599
|
-
onChange={(e) => handleMatcherChange(index, 'paramPath', e.target.value)}
|
|
600
|
-
/>
|
|
601
|
-
</Col>
|
|
602
|
-
<Col span={5}>
|
|
603
|
-
<Select
|
|
604
|
-
size="small"
|
|
605
|
-
style={{ width: '100%' }}
|
|
606
|
-
value={matcher.matchType || 'exact'}
|
|
607
|
-
onChange={(val) => handleMatcherChange(index, 'matchType', val)}
|
|
608
|
-
>
|
|
609
|
-
<Option value="exact">精确匹配</Option>
|
|
610
|
-
<Option value="contains">包含</Option>
|
|
611
|
-
<Option value="regex">正则</Option>
|
|
612
|
-
</Select>
|
|
613
|
-
</Col>
|
|
614
|
-
<Col span={9}>
|
|
615
|
-
<Input
|
|
616
|
-
size="small"
|
|
617
|
-
placeholder="期望值"
|
|
618
|
-
value={matcher.paramValue}
|
|
619
|
-
onChange={(e) => handleMatcherChange(index, 'paramValue', e.target.value)}
|
|
620
|
-
/>
|
|
621
|
-
</Col>
|
|
622
|
-
<Col span={2} style={{ textAlign: 'center' }}>
|
|
623
|
-
<Tooltip title="删除此规则">
|
|
624
|
-
<MinusCircleOutlined
|
|
625
|
-
style={{ color: '#ff4d4f', cursor: 'pointer', fontSize: '16px' }}
|
|
626
|
-
onClick={() => handleDeleteMatcher(index)}
|
|
627
|
-
/>
|
|
628
|
-
</Tooltip>
|
|
629
|
-
</Col>
|
|
630
|
-
</Row>
|
|
631
|
-
))}
|
|
632
|
-
</div>
|
|
633
|
-
)}
|
|
618
|
+
{/* 响应级入参匹配规则区域 */}
|
|
619
|
+
{activeResponse && renderParamMatchers()}
|
|
634
620
|
|
|
635
621
|
{/* 响应内容编辑区域 */}
|
|
636
622
|
{activeResponse && (
|
|
@@ -683,4 +669,4 @@ const ResponseContentEditor = ({
|
|
|
683
669
|
);
|
|
684
670
|
};
|
|
685
671
|
|
|
686
|
-
export default ResponseContentEditor;
|
|
672
|
+
export default ResponseContentEditor;
|
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
export { default as ResponseContentEditor } from './ResponseContentEditor';
|
|
3
3
|
export { default as InterfaceTestModal } from './InterfaceTestModal';
|
|
4
4
|
export { default as PreviewModal } from './PreviewModal';
|
|
5
|
+
export { default as DataManagementDrawer } from './DataManagementDrawer';
|
|
5
6
|
export * from './constants';
|
|
6
7
|
export * from './utils';
|