whistle.mock-plugins 1.2.1 → 1.2.2-beta.11

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.
@@ -42,13 +42,13 @@ const COLUMN_CONFIG = [
42
42
  { key: 'group', title: '分组', required: false },
43
43
  { key: 'urlPattern', title: 'URL匹配规则', required: false },
44
44
  { key: 'proxyType', title: '处理方式', required: false },
45
- { key: 'currentResponse', title: '当前响应', required: false },
45
+ { key: 'currentResponse', title: '响应配置', required: false },
46
46
  { key: 'responseDelay', title: '延迟(毫秒)', required: false },
47
47
  { key: 'httpStatus', title: '状态码', required: false },
48
48
  { key: 'contentType', title: '内容类型', required: false },
49
49
  { key: 'targetUrl', title: '目标URL', required: false },
50
50
  { key: 'customHeaders', title: '自定义头', required: false },
51
- { key: 'paramMatchers', title: '参数匹配', required: false },
51
+ { key: 'paramMatchers', title: '匹配规则', required: false },
52
52
  { key: 'httpMethod', title: '请求方法', required: false },
53
53
  { key: 'action', title: '操作', required: true }
54
54
  ];
@@ -800,35 +800,74 @@ const InterfaceManagement = () => {
800
800
  },
801
801
  },
802
802
  {
803
- title: '当前响应',
803
+ title: '响应配置',
804
804
  dataIndex: 'activeResponseId',
805
805
  key: 'currentResponse',
806
- width: 180,
806
+ width: 160,
807
807
  render: (activeResponseId, record) => {
808
- // 如果没有响应数据,返回空
808
+ if (record.proxyType !== 'response') {
809
+ return <span style={{ color: '#ccc' }}>-</span>;
810
+ }
809
811
  if (!record.responses || !Array.isArray(record.responses) || record.responses.length === 0) {
810
- return <span className="no-response">无响应数据</span>;
812
+ return <span style={{ color: '#ff4d4f', fontSize: '12px' }}>无响应数据</span>;
811
813
  }
812
-
813
- // 找到当前激活的响应
814
- const activeResponse = record.responses.find(resp => resp.id === activeResponseId);
815
-
816
- // 如果没有找到激活的响应,使用第一个
817
- const currentResponse = activeResponse || record.responses[0];
818
-
814
+
815
+ const total = record.responses.length;
816
+ const hasSmartMatch = record.responses.some(
817
+ r => r.paramMatchers && Array.isArray(r.paramMatchers) && r.paramMatchers.length > 0
818
+ );
819
+ const fallbackResp = record.responses.find(r => r.id === activeResponseId) || record.responses[0];
820
+
821
+ const tooltipContent = (
822
+ <div style={{ maxWidth: 260 }}>
823
+ {record.responses.map((resp) => {
824
+ const matchers = resp.paramMatchers || [];
825
+ return (
826
+ <div key={resp.id} style={{ marginBottom: 6 }}>
827
+ <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
828
+ {matchers.length > 0 ? (
829
+ <Tag color="blue" style={{ fontSize: '11px', margin: 0 }}>{matchers.length}条规则</Tag>
830
+ ) : (
831
+ <Tag color="default" style={{ fontSize: '11px', margin: 0 }}>兜底</Tag>
832
+ )}
833
+ <span style={{ fontWeight: resp.id === activeResponseId ? 'bold' : 'normal' }}>
834
+ {resp.name}
835
+ </span>
836
+ </div>
837
+ {matchers.length > 0 && (
838
+ <div style={{ paddingLeft: 8, marginTop: 2 }}>
839
+ {matchers.map((m, i) => (
840
+ <div key={i} style={{ fontSize: '11px', color: '#666' }}>
841
+ {m.paramPath} = {m.paramValue}
842
+ <span style={{ color: '#52c41a', marginLeft: 4 }}>
843
+ ({m.matchType === 'exact' ? '精确' : m.matchType === 'contains' ? '包含' : '正则'})
844
+ </span>
845
+ </div>
846
+ ))}
847
+ </div>
848
+ )}
849
+ </div>
850
+ );
851
+ })}
852
+ </div>
853
+ );
854
+
819
855
  return (
820
- <Select
821
- value={activeResponseId || record.responses[0]?.id}
822
- style={{ width: '100%' }}
823
- onChange={(value) => handleResponseSwitch(record.id, value)}
824
- disabled={record.proxyType !== 'response'}
825
- >
826
- {record.responses.map(resp => (
827
- <Option key={resp.id} value={resp.id}>
828
- {resp.name}
829
- </Option>
830
- ))}
831
- </Select>
856
+ <Tooltip title={tooltipContent} placement="left">
857
+ {hasSmartMatch ? (
858
+ <Tag color="blue" style={{ cursor: 'default' }}>
859
+ 智能匹配 · {total}个响应
860
+ </Tag>
861
+ ) : total > 1 ? (
862
+ <Tag color="orange" style={{ cursor: 'default' }}>
863
+ 手动切换 · {total}个响应
864
+ </Tag>
865
+ ) : (
866
+ <span style={{ color: '#999', fontSize: '12px' }}>
867
+ {fallbackResp?.name || '1个响应'}
868
+ </span>
869
+ )}
870
+ </Tooltip>
832
871
  );
833
872
  },
834
873
  },
@@ -945,38 +984,75 @@ const InterfaceManagement = () => {
945
984
  }
946
985
  },
947
986
  {
948
- title: '参数匹配',
949
- dataIndex: 'paramMatchers',
987
+ title: '匹配规则',
988
+ dataIndex: 'responses',
950
989
  key: 'paramMatchers',
951
990
  width: 100,
952
- render: (_, record) => {
991
+ render: (responses, record) => {
953
992
  if (record.proxyType !== 'response') {
954
- return '-';
993
+ return <span style={{ color: '#ccc' }}>-</span>;
955
994
  }
956
-
957
- const matchers = record.paramMatchers || [];
958
- const count = matchers.length;
959
-
960
- if (count === 0) {
961
- return '-';
995
+
996
+ // 汇总所有响应的 paramMatchers(响应级),兼容旧数据接口级 paramMatchers
997
+ const responsesWithMatchers = Array.isArray(responses)
998
+ ? responses.filter(r => r.paramMatchers && Array.isArray(r.paramMatchers) && r.paramMatchers.length > 0)
999
+ : [];
1000
+ const totalRules = responsesWithMatchers.reduce((sum, r) => sum + r.paramMatchers.length, 0);
1001
+
1002
+ // 兼容旧数据:接口级 paramMatchers
1003
+ const legacyMatchers = record.paramMatchers || [];
1004
+
1005
+ if (totalRules === 0 && legacyMatchers.length === 0) {
1006
+ return <span style={{ color: '#ccc' }}>-</span>;
962
1007
  }
963
-
964
- return (
965
- <Tooltip title={
966
- <div>
967
- {matchers.map((matcher, index) => (
968
- <div key={index}>
969
- {matcher.paramPath}: {matcher.paramValue}
970
- <span style={{ color: '#52c41a', marginLeft: 4 }}>
971
- ({matcher.matchType === 'exact' ? '精确' : matcher.matchType === 'contains' ? '包含' : '正则'})
972
- </span>
1008
+
1009
+ // 新数据:按响应分组展示
1010
+ if (totalRules > 0) {
1011
+ const tooltipContent = (
1012
+ <div style={{ maxWidth: 280 }}>
1013
+ {responsesWithMatchers.map(resp => (
1014
+ <div key={resp.id} style={{ marginBottom: 6 }}>
1015
+ <div style={{ fontWeight: 'bold', marginBottom: 2 }}>{resp.name}:</div>
1016
+ {resp.paramMatchers.map((m, i) => (
1017
+ <div key={i} style={{ paddingLeft: 8, fontSize: '12px' }}>
1018
+ {m.paramPath} = {m.paramValue}
1019
+ <span style={{ color: '#52c41a', marginLeft: 4 }}>
1020
+ ({m.matchType === 'exact' ? '精确' : m.matchType === 'contains' ? '包含' : '正则'})
1021
+ </span>
1022
+ </div>
1023
+ ))}
973
1024
  </div>
974
1025
  ))}
1026
+ {Array.isArray(responses) && responses.some(r => !r.paramMatchers || r.paramMatchers.length === 0) && (
1027
+ <div style={{ color: '#999', fontSize: '12px', marginTop: 4 }}>
1028
+ + 兜底响应(无规则)
1029
+ </div>
1030
+ )}
975
1031
  </div>
976
- }>
977
- <span style={{ color: '#1890ff' }}>
978
- {count}条规则
979
- </span>
1032
+ );
1033
+ return (
1034
+ <Tooltip title={tooltipContent} placement="left">
1035
+ <span style={{ color: '#1890ff', cursor: 'default' }}>{totalRules}条规则</span>
1036
+ </Tooltip>
1037
+ );
1038
+ }
1039
+
1040
+ // 旧数据兼容展示
1041
+ const tooltipContent = (
1042
+ <div>
1043
+ {legacyMatchers.map((matcher, index) => (
1044
+ <div key={index}>
1045
+ {matcher.paramPath}: {matcher.paramValue}
1046
+ <span style={{ color: '#52c41a', marginLeft: 4 }}>
1047
+ ({matcher.matchType === 'exact' ? '精确' : matcher.matchType === 'contains' ? '包含' : '正则'})
1048
+ </span>
1049
+ </div>
1050
+ ))}
1051
+ </div>
1052
+ );
1053
+ return (
1054
+ <Tooltip title={tooltipContent} placement="left">
1055
+ <span style={{ color: '#fa8c16', cursor: 'default' }}>{legacyMatchers.length}条(旧)</span>
980
1056
  </Tooltip>
981
1057
  );
982
1058
  }
@@ -1698,97 +1774,9 @@ const InterfaceManagement = () => {
1698
1774
  </div>
1699
1775
  </div>
1700
1776
 
1701
- {/* 请求入参匹配设置 */}
1702
- <Form.Item
1703
- label="请求入参匹配"
1704
- tooltip="设置请求参数的匹配条件,只有当请求参数满足条件时才返回对应的响应。支持嵌套属性路径,如:a.b.c"
1705
- >
1706
- <Form.List name="paramMatchers">
1707
- {(fields, { add, remove }) => (
1708
- <>
1709
- {fields.map(({ key, name, ...restField }) => (
1710
- <div key={key} style={{ display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '8px' }}>
1711
- <Form.Item
1712
- {...restField}
1713
- name={[name, 'paramPath']}
1714
- rules={[
1715
- { required: true, message: '请输入参数路径' },
1716
- {
1717
- pattern: /^[a-zA-Z_$][a-zA-Z0-9_$.]*$/,
1718
- message: '请输入有效的参数路径,如:userId 或 data.user.id'
1719
- }
1720
- ]}
1721
- style={{ flex: 1, margin: 0 }}
1722
- >
1723
- <Input placeholder="参数路径(如:userId 或 data.user.id)" />
1724
- </Form.Item>
1725
- <Form.Item
1726
- {...restField}
1727
- name={[name, 'paramValue']}
1728
- rules={[{ required: true, message: '请输入期望值' }]}
1729
- style={{ flex: 1, margin: 0 }}
1730
- >
1731
- <Input placeholder="期望值(如:123 或 admin)" />
1732
- </Form.Item>
1733
- <Form.Item
1734
- {...restField}
1735
- name={[name, 'matchType']}
1736
- style={{ width: 100, margin: 0 }}
1737
- initialValue="exact"
1738
- >
1739
- <Select placeholder="匹配类型">
1740
- <Option value="exact">精确匹配</Option>
1741
- <Option value="contains">包含</Option>
1742
- <Option value="regex">正则</Option>
1743
- </Select>
1744
- </Form.Item>
1745
- <Button
1746
- type="text"
1747
- danger
1748
- icon={<DeleteOutlined />}
1749
- onClick={() => remove(name)}
1750
- />
1751
- </div>
1752
- ))}
1753
- <div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginTop: '8px' }}>
1754
- <Button
1755
- type="dashed"
1756
- onClick={() => add()}
1757
- icon={<PlusOutlined />}
1758
- style={{ flex: 1 }}
1759
- >
1760
- 添加参数匹配规则
1761
- </Button>
1762
- <Popover
1763
- content={
1764
- <div style={{ maxWidth: '300px' }}>
1765
- <div style={{ marginBottom: '8px', fontWeight: 'bold' }}>匹配规则说明:</div>
1766
- <div style={{ lineHeight: '1.6' }}>
1767
- <div>• <strong>精确匹配</strong>:参数值完全相等</div>
1768
- <div>• <strong>包含</strong>:参数值包含指定内容</div>
1769
- <div>• <strong>正则</strong>:参数值符合正则表达式</div>
1770
- <div>• <strong>嵌套路径</strong>:使用点号分隔,如 data.user.id</div>
1771
- </div>
1772
- </div>
1773
- }
1774
- title="参数匹配规则"
1775
- trigger="click"
1776
- placement="top"
1777
- >
1778
- <Button
1779
- type="text"
1780
- icon={<QuestionCircleOutlined />}
1781
- style={{
1782
- color: '#999',
1783
- padding: '4px'
1784
- }}
1785
- size="small"
1786
- />
1787
- </Popover>
1788
- </div>
1789
- </>
1790
- )}
1791
- </Form.List>
1777
+ {/* 入参匹配规则已移至各响应内部配置,此处保留隐藏字段向后兼容旧数据 */}
1778
+ <Form.Item name="paramMatchers" hidden initialValue={[]}>
1779
+ <Input />
1792
1780
  </Form.Item>
1793
1781
 
1794
1782
  {/* 隐藏的表单字段用于存储响应数据 */}