whistle.mock-plugins 1.0.3 → 1.0.4

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.
@@ -38,6 +38,12 @@ const InterfaceManagement = () => {
38
38
  { value: 'application/javascript; charset=utf-8', label: 'JavaScript' },
39
39
  ];
40
40
 
41
+ const proxyTypes = [
42
+ { value: 'response', label: '模拟响应' },
43
+ { value: 'redirect', label: '重定向' },
44
+ { value: 'url_redirect', label: 'URL重定向' },
45
+ ];
46
+
41
47
  const statusCodes = [
42
48
  { value: '200', label: '200 OK' },
43
49
  { value: '201', label: '201 Created' },
@@ -134,10 +140,12 @@ const InterfaceManagement = () => {
134
140
  form.setFieldsValue({
135
141
  name: record.name,
136
142
  pattern: record.urlPattern,
143
+ proxyType: record.proxyType || 'response',
137
144
  statusCode: record.httpStatus?.toString() || '200',
138
145
  contentType: record.contentType || 'application/json; charset=utf-8',
139
146
  responseBody: record.responseContent || '',
140
147
  httpMethod: record.httpMethod || 'ALL',
148
+ targetUrl: record.targetUrl || '',
141
149
  });
142
150
  setModalVisible(true);
143
151
  };
@@ -174,7 +182,6 @@ const InterfaceManagement = () => {
174
182
  }
175
183
  };
176
184
 
177
- // 清理JSON响应内容中不需要的空白和格式化
178
185
  const cleanJsonResponse = (jsonStr) => {
179
186
  try {
180
187
  const parsed = JSON.parse(jsonStr);
@@ -184,18 +191,24 @@ const InterfaceManagement = () => {
184
191
  }
185
192
  };
186
193
 
194
+ // 验证URL是否合法
195
+ const isValidUrl = (url) => {
196
+ if (!url) return false;
197
+
198
+ try {
199
+ new URL(url);
200
+ return true;
201
+ } catch (e) {
202
+ return false;
203
+ }
204
+ };
205
+
187
206
  const handleSubmit = async () => {
188
207
  try {
189
208
  const values = await form.validateFields();
190
209
 
191
- // 验证 URL 匹配规则格式
192
- if (!isValidPattern(values.pattern)) {
193
- message.error('URL匹配规则格式不正确');
194
- return;
195
- }
196
-
197
210
  // 验证响应内容格式
198
- if (values.contentType.includes('json')) {
211
+ if (values.proxyType === 'response' && values.contentType.includes('json')) {
199
212
  try {
200
213
  // 清理并验证JSON
201
214
  const cleanedJson = cleanJsonResponse(values.responseBody);
@@ -206,13 +219,26 @@ const InterfaceManagement = () => {
206
219
  return;
207
220
  }
208
221
  }
222
+
223
+ // 验证重定向URL
224
+ if ((values.proxyType === 'redirect' || values.proxyType === 'url_redirect') && !values.targetUrl) {
225
+ message.error('重定向目标URL不能为空');
226
+ return;
227
+ }
228
+
229
+ // 验证重定向URL的格式
230
+ if ((values.proxyType === 'redirect' || values.proxyType === 'url_redirect') && !isValidUrl(values.targetUrl)) {
231
+ message.error('重定向目标URL格式不正确,请输入完整的URL,包括http://或https://');
232
+ return;
233
+ }
209
234
 
210
235
  const interfaceData = {
211
236
  name: values.name,
212
237
  featureId: selectedFeatureId,
213
238
  urlPattern: values.pattern,
214
- proxyType: 'response',
215
- responseContent: values.responseBody,
239
+ proxyType: values.proxyType,
240
+ responseContent: values.proxyType === 'response' ? values.responseBody : '',
241
+ targetUrl: (values.proxyType === 'redirect' || values.proxyType === 'url_redirect') ? values.targetUrl : '',
216
242
  httpStatus: parseInt(values.statusCode, 10), // 转换为数字
217
243
  contentType: values.contentType,
218
244
  responseDelay: 0,
@@ -247,29 +273,6 @@ const InterfaceManagement = () => {
247
273
  }
248
274
  };
249
275
 
250
- // 验证 URL 匹配规则格式
251
- const isValidPattern = (pattern) => {
252
- if (!pattern) return false;
253
-
254
- // 检查是否是有效的正则表达式
255
- if (pattern.startsWith('/') && pattern.endsWith('/')) {
256
- try {
257
- new RegExp(pattern.slice(1, -1));
258
- return true;
259
- } catch (e) {
260
- return false;
261
- }
262
- }
263
-
264
- // 检查通配符格式
265
- if (pattern.includes('*')) {
266
- return /^[a-zA-Z0-9\-_/.*]+$/.test(pattern);
267
- }
268
-
269
- // 检查普通路径格式
270
- return /^[a-zA-Z0-9\-_/]+$/.test(pattern);
271
- };
272
-
273
276
  const handleCancel = () => {
274
277
  setModalVisible(false);
275
278
  };
@@ -282,6 +285,45 @@ const InterfaceManagement = () => {
282
285
  testForm.resetFields();
283
286
  };
284
287
 
288
+ // 检查 URL 是否匹配模式
289
+ const isUrlMatchPattern = (url, pattern, proxyType) => {
290
+ if (!url || !pattern) return false;
291
+
292
+ try {
293
+ // 对于url_redirect类型,需要完全匹配
294
+ if (proxyType === 'url_redirect') {
295
+ return url === pattern;
296
+ }
297
+
298
+ // 对于redirect类型,只要url以pattern开头即可命中(前缀匹配)
299
+ if (proxyType === 'redirect') {
300
+ return url.indexOf(pattern) === 0;
301
+ }
302
+
303
+ // 以下是默认的匹配逻辑(用于response类型等)
304
+ // 如果是正则表达式
305
+ if (pattern.startsWith('/') && pattern.endsWith('/')) {
306
+ const regex = new RegExp(pattern.slice(1, -1));
307
+ return regex.test(url);
308
+ }
309
+
310
+ // 如果是通配符模式
311
+ if (pattern.includes('*')) {
312
+ const regexPattern = pattern
313
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
314
+ .replace(/\*/g, '.*');
315
+ const regex = new RegExp(`^${regexPattern}$`);
316
+ return regex.test(url);
317
+ }
318
+
319
+ // 精确匹配
320
+ return url === pattern;
321
+ } catch (e) {
322
+ console.error('URL匹配检查失败:', e);
323
+ return false;
324
+ }
325
+ };
326
+
285
327
  const handleTestSubmit = async () => {
286
328
  try {
287
329
  const values = await testForm.validateFields();
@@ -289,12 +331,21 @@ const InterfaceManagement = () => {
289
331
 
290
332
  try {
291
333
  // 验证测试 URL 是否匹配当前接口的匹配规则
292
- if (!isUrlMatchPattern(values.testUrl, editingInterface.urlPattern)) {
334
+ if (!isUrlMatchPattern(values.testUrl, editingInterface.urlPattern, editingInterface.proxyType)) {
335
+ let errorMessage = '测试URL与接口匹配规则不匹配';
336
+
337
+ // 根据不同的代理类型提供更具体的错误信息
338
+ if (editingInterface.proxyType === 'url_redirect') {
339
+ errorMessage = `测试URL必须完全匹配 ${editingInterface.urlPattern}`;
340
+ } else if (editingInterface.proxyType === 'redirect') {
341
+ errorMessage = `测试URL必须以 ${editingInterface.urlPattern} 开头`;
342
+ }
343
+
293
344
  setTestResult({
294
345
  success: false,
295
- error: '测试URL与接口匹配规则不匹配'
346
+ error: errorMessage
296
347
  });
297
- message.error('测试URL与接口匹配规则不匹配');
348
+ message.error(errorMessage);
298
349
  return;
299
350
  }
300
351
 
@@ -336,34 +387,6 @@ const InterfaceManagement = () => {
336
387
  }
337
388
  };
338
389
 
339
- // 检查 URL 是否匹配模式
340
- const isUrlMatchPattern = (url, pattern) => {
341
- if (!url || !pattern) return false;
342
-
343
- try {
344
- // 如果是正则表达式
345
- if (pattern.startsWith('/') && pattern.endsWith('/')) {
346
- const regex = new RegExp(pattern.slice(1, -1));
347
- return regex.test(url);
348
- }
349
-
350
- // 如果是通配符模式
351
- if (pattern.includes('*')) {
352
- const regexPattern = pattern
353
- .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
354
- .replace(/\*/g, '.*');
355
- const regex = new RegExp(`^${regexPattern}$`);
356
- return regex.test(url);
357
- }
358
-
359
- // 精确匹配
360
- return url === pattern;
361
- } catch (e) {
362
- console.error('URL匹配检查失败:', e);
363
- return false;
364
- }
365
- };
366
-
367
390
  const handleSelectFeature = (featureId) => {
368
391
  setSelectedFeatureId(featureId);
369
392
  };
@@ -381,6 +404,11 @@ const InterfaceManagement = () => {
381
404
  };
382
405
 
383
406
  const formatJsonContent = () => {
407
+ const proxyType = form.getFieldValue('proxyType');
408
+ if (proxyType !== 'response') {
409
+ return;
410
+ }
411
+
384
412
  const responseBody = form.getFieldValue('responseBody');
385
413
  if (responseBody) {
386
414
  try {
@@ -394,6 +422,11 @@ const InterfaceManagement = () => {
394
422
  };
395
423
 
396
424
  const handlePreview = () => {
425
+ const proxyType = form.getFieldValue('proxyType');
426
+ if (proxyType !== 'response') {
427
+ return;
428
+ }
429
+
397
430
  const responseBody = form.getFieldValue('responseBody');
398
431
  if (responseBody) {
399
432
  try {
@@ -442,22 +475,47 @@ const InterfaceManagement = () => {
442
475
  key: 'urlPattern',
443
476
  ellipsis: true,
444
477
  },
478
+ {
479
+ title: '处理方式',
480
+ dataIndex: 'proxyType',
481
+ key: 'proxyType',
482
+ width: 120,
483
+ render: (text) => {
484
+ const found = proxyTypes.find(item => item.value === text);
485
+ return found ? found.label : text || '模拟响应';
486
+ }
487
+ },
445
488
  {
446
489
  title: '状态码',
447
490
  dataIndex: 'httpStatus',
448
491
  key: 'httpStatus',
449
492
  width: 100,
493
+ render: (text, record) => {
494
+ return record.proxyType === 'response' ? text : '-';
495
+ }
450
496
  },
451
497
  {
452
498
  title: '内容类型',
453
499
  dataIndex: 'contentType',
454
500
  key: 'contentType',
455
501
  width: 120,
456
- render: (text) => {
502
+ render: (text, record) => {
503
+ if (record.proxyType !== 'response') {
504
+ return '-';
505
+ }
457
506
  const found = contentTypes.find(item => item.value === text);
458
507
  return found ? found.label : text;
459
508
  }
460
509
  },
510
+ {
511
+ title: '目标URL',
512
+ dataIndex: 'targetUrl',
513
+ key: 'targetUrl',
514
+ ellipsis: true,
515
+ render: (text, record) => {
516
+ return (record.proxyType === 'redirect' || record.proxyType === 'url_redirect') ? text : '-';
517
+ }
518
+ },
461
519
  {
462
520
  title: '请求方法',
463
521
  dataIndex: 'httpMethod',
@@ -577,10 +635,12 @@ const InterfaceManagement = () => {
577
635
  initialValues={{
578
636
  name: '',
579
637
  pattern: '',
638
+ proxyType: 'response',
580
639
  statusCode: '200',
581
640
  contentType: 'application/json; charset=utf-8',
582
641
  responseBody: '{\n "code": 0,\n "message": "success",\n "data": {}\n}',
583
- httpMethod: 'ALL'
642
+ httpMethod: 'ALL',
643
+ targetUrl: ''
584
644
  }}
585
645
  >
586
646
  <div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
@@ -597,36 +657,120 @@ const InterfaceManagement = () => {
597
657
  <Form.Item
598
658
  name="pattern"
599
659
  label="URL匹配规则"
600
- rules={[{ required: true, message: '请输入URL匹配规则' }]}
601
- tooltip="支持多种匹配方式:精确匹配 - /api/users,通配符 - /api/users/*,正则表达式 - /api\/users\/\d+/"
660
+ rules={[
661
+ { required: true, message: '请输入URL匹配规则' },
662
+ ({ getFieldValue }) => ({
663
+ validator(_, value) {
664
+ const proxyType = getFieldValue('proxyType');
665
+ if (!value) return Promise.resolve();
666
+
667
+ // URL重定向模式下,必须是完整URL
668
+ if (proxyType === 'url_redirect') {
669
+ try {
670
+ new URL(value);
671
+ return Promise.resolve();
672
+ } catch (e) {
673
+ return Promise.reject(new Error('URL重定向模式下,URL匹配规则必须是完整的URL(包括http://或https://)'));
674
+ }
675
+ }
676
+
677
+ // 重定向模式下,必须以http://或https://开头
678
+ if (proxyType === 'redirect') {
679
+ if (value.startsWith('http://') || value.startsWith('https://')) {
680
+ return Promise.resolve();
681
+ }
682
+ return Promise.reject(new Error('重定向模式下,URL匹配规则必须以http://或https://开头'));
683
+ }
684
+
685
+ // 响应模式下的验证
686
+ if (proxyType === 'response') {
687
+ // 正则表达式验证
688
+ if (value.startsWith('/') && value.endsWith('/')) {
689
+ try {
690
+ new RegExp(value.slice(1, -1));
691
+ return Promise.resolve();
692
+ } catch (e) {
693
+ return Promise.reject(new Error('无效的正则表达式格式'));
694
+ }
695
+ }
696
+
697
+ // 通配符验证
698
+ if (value.includes('*')) {
699
+ if (/^[a-zA-Z0-9\-_/.*]+$/.test(value)) {
700
+ return Promise.resolve();
701
+ }
702
+ return Promise.reject(new Error('通配符URL格式不正确'));
703
+ }
704
+
705
+ // 普通路径验证
706
+ if (/^[a-zA-Z0-9\-_/]+$/.test(value)) {
707
+ return Promise.resolve();
708
+ }
709
+ return Promise.reject(new Error('URL路径格式不正确'));
710
+ }
711
+
712
+ return Promise.resolve();
713
+ },
714
+ }),
715
+ ]}
716
+ tooltip={{
717
+ title: (
718
+ <>
719
+ <div>不同处理方式下URL匹配规则要求:</div>
720
+ <ul style={{margin: '5px 0 0 15px', padding: 0}}>
721
+ <li><b>模拟响应:</b> 支持路径格式如 /api/users,通配符如 /api/*,正则如 /\/api\/\d+/</li>
722
+ <li><b>重定向:</b> 必须以http://或https://开头,例如:https://example.com/api</li>
723
+ <li><b>URL重定向:</b> 必须是完整URL,包括http://或https://,例如:https://example.com/api/users</li>
724
+ </ul>
725
+ </>
726
+ ),
727
+ overlayStyle: { maxWidth: '450px' }
728
+ }}
602
729
  >
603
- <Input placeholder="例如:/api/users,/api/users/*,/api\/users\/\d+/" />
730
+ <Input
731
+ placeholder="根据选择的处理方式输入相应格式的URL"
732
+ />
604
733
  </Form.Item>
605
734
  </div>
606
735
  </div>
607
-
736
+
608
737
  <div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
609
738
  <div style={{ flex: 1 }}>
610
739
  <Form.Item
611
- name="statusCode"
612
- label="状态码"
613
- rules={[{ required: true, message: '请选择状态码' }]}
614
- >
615
- <Select>
616
- {statusCodes.map(item => (
617
- <Option key={item.value} value={item.value}>{item.label}</Option>
618
- ))}
619
- </Select>
620
- </Form.Item>
621
- </div>
622
- <div style={{ flex: 1 }}>
623
- <Form.Item
624
- name="contentType"
625
- label="内容类型"
626
- rules={[{ required: true, message: '请选择内容类型' }]}
740
+ name="proxyType"
741
+ label="处理方式"
742
+ rules={[{ required: true, message: '请选择处理方式' }]}
743
+ tooltip={{
744
+ title: (
745
+ <>
746
+ <div>不同处理方式的规则说明:</div>
747
+ <ul style={{margin: '5px 0 0 15px', padding: 0}}>
748
+ <li><b>模拟响应:</b> 返回您定义的响应内容</li>
749
+ <li><b>重定向:</b> 将请求重定向到其他URL,URL匹配规则必须以http://或https://开头</li>
750
+ <li><b>URL重定向:</b> 完全匹配URL时重定向,URL匹配规则必须是完整URL</li>
751
+ </ul>
752
+ </>
753
+ ),
754
+ overlayStyle: { maxWidth: '450px' }
755
+ }}
627
756
  >
628
- <Select>
629
- {contentTypes.map(item => (
757
+ <Select onChange={(value) => {
758
+ // 当切换代理类型时,清空pattern字段,并提供不同的placeholder
759
+ form.setFieldsValue({ pattern: '' });
760
+
761
+ // 为不同的代理类型提供不同的pattern占位符
762
+ const patternInput = document.querySelector('input[placeholder="根据选择的处理方式输入相应格式的URL"]');
763
+ if (patternInput) {
764
+ if (value === 'response') {
765
+ patternInput.placeholder = "例如:/api/users,/api/users/*,/api\/users\/\d+/";
766
+ } else if (value === 'redirect') {
767
+ patternInput.placeholder = "例如:https://example.com/api";
768
+ } else if (value === 'url_redirect') {
769
+ patternInput.placeholder = "例如:https://example.com/api/users";
770
+ }
771
+ }
772
+ }}>
773
+ {proxyTypes.map(item => (
630
774
  <Option key={item.value} value={item.value}>{item.label}</Option>
631
775
  ))}
632
776
  </Select>
@@ -647,37 +791,131 @@ const InterfaceManagement = () => {
647
791
  </div>
648
792
  </div>
649
793
 
650
- <Form.Item
651
- name="responseBody"
652
- labelCol={{
653
- span: 8, /* 宽度比例 */
654
- }}
655
- label={
656
- <div style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
657
- <span>响应内容</span>
658
- <Space>
659
- <Button
660
- type="link"
661
- icon={<EyeOutlined />}
662
- onClick={handlePreview}
663
- style={{ padding: 0 }}
664
- >
665
- 预览
666
- </Button>
667
- <Button
668
- type="link"
669
- icon={<FormatPainterOutlined />}
670
- onClick={formatJsonContent}
671
- style={{ padding: 0 }}
794
+ {/* 根据proxyType显示不同的表单项 */}
795
+ <Form.Item noStyle shouldUpdate={(prevValues, currentValues) => prevValues.proxyType !== currentValues.proxyType}>
796
+ {({ getFieldValue }) => {
797
+ const proxyType = getFieldValue('proxyType');
798
+
799
+ if (proxyType === 'response') {
800
+ return (
801
+ <>
802
+ <div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
803
+ <div style={{ flex: 1 }}>
804
+ <Form.Item
805
+ name="statusCode"
806
+ label="状态码"
807
+ rules={[{ required: true, message: '请选择状态码' }]}
808
+ >
809
+ <Select>
810
+ {statusCodes.map(item => (
811
+ <Option key={item.value} value={item.value}>{item.label}</Option>
812
+ ))}
813
+ </Select>
814
+ </Form.Item>
815
+ </div>
816
+ <div style={{ flex: 1 }}>
817
+ <Form.Item
818
+ name="contentType"
819
+ label="内容类型"
820
+ rules={[{ required: true, message: '请选择内容类型' }]}
821
+ >
822
+ <Select>
823
+ {contentTypes.map(item => (
824
+ <Option key={item.value} value={item.value}>{item.label}</Option>
825
+ ))}
826
+ </Select>
827
+ </Form.Item>
828
+ </div>
829
+ </div>
830
+
831
+ <Form.Item
832
+ name="responseBody"
833
+ labelCol={{
834
+ span: 8, /* 宽度比例 */
835
+ }}
836
+ label={
837
+ <div style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
838
+ <span>响应内容</span>
839
+ <Space>
840
+ <Button
841
+ type="link"
842
+ icon={<EyeOutlined />}
843
+ onClick={handlePreview}
844
+ style={{ padding: 0 }}
845
+ >
846
+ 预览
847
+ </Button>
848
+ <Button
849
+ type="link"
850
+ icon={<FormatPainterOutlined />}
851
+ onClick={formatJsonContent}
852
+ style={{ padding: 0 }}
853
+ >
854
+ 格式化JSON
855
+ </Button>
856
+ </Space>
857
+ </div>
858
+ }
859
+ rules={[{ required: true, message: '请输入响应内容' }]}
860
+ >
861
+ <TextArea rows={12} placeholder="请输入响应内容" style={{ fontFamily: 'monospace' }} />
862
+ </Form.Item>
863
+ </>
864
+ );
865
+ }
866
+
867
+ if (proxyType === 'redirect' || proxyType === 'url_redirect') {
868
+ return (
869
+ <Form.Item
870
+ name="targetUrl"
871
+ label="重定向目标URL"
872
+ rules={[
873
+ { required: true, message: '请输入重定向目标URL' },
874
+ {
875
+ validator: (_, value) => {
876
+ if (!value) return Promise.resolve();
877
+
878
+ try {
879
+ new URL(value);
880
+ return Promise.resolve();
881
+ } catch (e) {
882
+ return Promise.reject(new Error('请输入有效的URL,必须包含http://或https://'));
883
+ }
884
+ }
885
+ }
886
+ ]}
887
+ tooltip={proxyType === 'redirect' ?
888
+ "重定向模式:输入完整的目标URL。匹配时使用前缀匹配,只要请求URL以匹配规则开头即命中。例如:https://example.com/api" :
889
+ "URL重定向模式:输入完整的目标URL。匹配时要求完全匹配URL,必须与匹配规则完全一致才命中。例如:https://example.com/api/users"
890
+ }
672
891
  >
673
- 格式化JSON
674
- </Button>
675
- </Space>
676
- </div>
677
- }
678
- rules={[{ required: true, message: '请输入响应内容' }]}
679
- >
680
- <TextArea rows={12} placeholder="请输入响应内容" style={{ fontFamily: 'monospace' }} />
892
+ <Input
893
+ placeholder={proxyType === 'redirect' ?
894
+ "例如:https://example.com/api" :
895
+ "例如:https://example.com/api/users"
896
+ }
897
+ addonBefore={
898
+ <Select
899
+ defaultValue="https://"
900
+ className="select-before"
901
+ style={{ width: 100 }}
902
+ onChange={(value) => {
903
+ const currentUrl = form.getFieldValue('targetUrl') || '';
904
+ const urlWithoutProtocol = currentUrl.replace(/^https?:\/\//, '');
905
+ form.setFieldsValue({ targetUrl: value + urlWithoutProtocol });
906
+ }}
907
+ >
908
+ <Option value="http://">http://</Option>
909
+ <Option value="https://">https://</Option>
910
+ </Select>
911
+ }
912
+ />
913
+ </Form.Item>
914
+ );
915
+ }
916
+
917
+ return null;
918
+ }}
681
919
  </Form.Item>
682
920
  </Form>
683
921
  </Modal>
@@ -703,14 +941,45 @@ const InterfaceManagement = () => {
703
941
  description={
704
942
  <div>
705
943
  <p>当前接口匹配规则:<code>{editingInterface.urlPattern}</code></p>
706
- <p>在Whistle规则中,您需要配置完整URL或域名指向whistle.mock-plugins://,然后插件会根据接口的匹配规则处理请求。</p>
707
- <p>插件支持以下匹配方式:</p>
708
- <ul>
709
- <li>精确匹配:完全匹配URL路径部分</li>
710
- <li>通配符匹配:使用*代表任意字符(例如:/api/users/*)</li>
711
- <li>正则表达式:使用/pattern/格式(例如:/\/api\/users\/\d+/)</li>
712
- <li>部分匹配:URL包含指定路径即匹配</li>
713
- </ul>
944
+ <p>代理类型:{
945
+ proxyTypes.find(item => item.value === editingInterface.proxyType)?.label ||
946
+ editingInterface.proxyType || '模拟响应'
947
+ }</p>
948
+
949
+ {editingInterface.proxyType === 'url_redirect' && (
950
+ <div>
951
+ <p><b>URL重定向模式规则:</b></p>
952
+ <ul>
953
+ <li>URL匹配规则必须是完整URL(包括http://或https://)</li>
954
+ <li>需要完全匹配URL路径,测试URL必须与匹配规则<b>完全一致</b></li>
955
+ <li>命中后将直接跳转到目标URL: {editingInterface.targetUrl}</li>
956
+ </ul>
957
+ </div>
958
+ )}
959
+
960
+ {editingInterface.proxyType === 'redirect' && (
961
+ <div>
962
+ <p><b>重定向模式规则:</b></p>
963
+ <ul>
964
+ <li>URL匹配规则必须以http://或https://开头</li>
965
+ <li>测试URL必须以匹配规则开头(<b>前缀匹配</b>)</li>
966
+ <li>命中后将直接跳转到目标URL: {editingInterface.targetUrl}</li>
967
+ </ul>
968
+ </div>
969
+ )}
970
+
971
+ {editingInterface.proxyType === 'response' && (
972
+ <div>
973
+ <p>在Whistle规则中,您需要配置完整URL或域名指向whistle.mock-plugins://,然后插件会根据接口的匹配规则处理请求。</p>
974
+ <p>插件支持以下匹配方式:</p>
975
+ <ul>
976
+ <li>精确匹配:完全匹配URL路径部分</li>
977
+ <li>通配符匹配:使用*代表任意字符(例如:/api/users/*)</li>
978
+ <li>正则表达式:使用/pattern/格式(例如:/\/api\/users\/\d+/)</li>
979
+ </ul>
980
+ </div>
981
+ )}
982
+
714
983
  <p>请在下方输入完整测试URL进行测试</p>
715
984
  </div>
716
985
  }
@@ -761,13 +1030,35 @@ const InterfaceManagement = () => {
761
1030
  <span className="value">{testResult.httpMethod || 'ALL'}</span>
762
1031
  </div>
763
1032
  <div className="result-item">
764
- <span className="label">状态码:</span>
765
- <span className="value">{testResult.statusCode}</span>
766
- </div>
767
- <div className="result-item">
768
- <span className="label">内容类型:</span>
769
- <span className="value">{testResult.contentType}</span>
1033
+ <span className="label">处理方式:</span>
1034
+ <span className="value">
1035
+ {(() => {
1036
+ const found = proxyTypes.find(item => item.value === editingInterface.proxyType);
1037
+ return found ? found.label : editingInterface.proxyType || '模拟响应';
1038
+ })()}
1039
+ </span>
770
1040
  </div>
1041
+
1042
+ {(editingInterface.proxyType === 'redirect' || editingInterface.proxyType === 'url_redirect') && (
1043
+ <div className="result-item">
1044
+ <span className="label">重定向目标:</span>
1045
+ <span className="value">{testResult.targetUrl || editingInterface.targetUrl}</span>
1046
+ </div>
1047
+ )}
1048
+
1049
+ {editingInterface.proxyType === 'response' && (
1050
+ <>
1051
+ <div className="result-item">
1052
+ <span className="label">状态码:</span>
1053
+ <span className="value">{testResult.statusCode}</span>
1054
+ </div>
1055
+ <div className="result-item">
1056
+ <span className="label">内容类型:</span>
1057
+ <span className="value">{testResult.contentType}</span>
1058
+ </div>
1059
+ </>
1060
+ )}
1061
+
771
1062
  {testResult.mockInfo && (
772
1063
  <>
773
1064
  <div className="result-item">
@@ -780,10 +1071,13 @@ const InterfaceManagement = () => {
780
1071
  </div>
781
1072
  </>
782
1073
  )}
783
- <div className="result-body">
784
- <div className="label">响应内容:</div>
785
- <pre>{formatResponseContent(testResult.responseBody, testResult.contentType)}</pre>
786
- </div>
1074
+
1075
+ {editingInterface.proxyType === 'response' && (
1076
+ <div className="result-body">
1077
+ <div className="label">响应内容:</div>
1078
+ <pre>{formatResponseContent(testResult.responseBody, testResult.contentType)}</pre>
1079
+ </div>
1080
+ )}
787
1081
  </div>
788
1082
  ) : (
789
1083
  <div className="result-error">