whistle.mock-plugins 1.2.2-beta.7 → 1.2.2-beta.9

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.
@@ -15,7 +15,6 @@ import {
15
15
  FileTextOutlined, PlusCircleOutlined, SettingOutlined,
16
16
  SearchOutlined, FilterOutlined, ReloadOutlined, DownOutlined, QuestionCircleOutlined, CopyOutlined
17
17
  } from '@ant-design/icons';
18
- // 移除 react-resizable,改用原生实现
19
18
  import '../styles/interface-management.css';
20
19
  import axios from 'axios';
21
20
 
@@ -32,7 +31,6 @@ import {
32
31
  generateResponseId
33
32
  } from '../components/interface-management';
34
33
  import DataManagementModal from '../components/interface-management/DataManagementModal';
35
- import { DataManagementDrawer } from '../components/interface-management';
36
34
 
37
35
  const { Option } = Select;
38
36
  const { Search } = AntInput;
@@ -44,13 +42,13 @@ const COLUMN_CONFIG = [
44
42
  { key: 'group', title: '分组', required: false },
45
43
  { key: 'urlPattern', title: 'URL匹配规则', required: false },
46
44
  { key: 'proxyType', title: '处理方式', required: false },
47
- { key: 'currentResponse', title: '当前响应', required: false },
45
+ { key: 'currentResponse', title: '响应配置', required: false },
48
46
  { key: 'responseDelay', title: '延迟(毫秒)', required: false },
49
47
  { key: 'httpStatus', title: '状态码', required: false },
50
48
  { key: 'contentType', title: '内容类型', required: false },
51
49
  { key: 'targetUrl', title: '目标URL', required: false },
52
50
  { key: 'customHeaders', title: '自定义头', required: false },
53
- { key: 'paramMatchers', title: '参数匹配', required: false },
51
+ { key: 'paramMatchers', title: '匹配规则', required: false },
54
52
  { key: 'httpMethod', title: '请求方法', required: false },
55
53
  { key: 'action', title: '操作', required: true }
56
54
  ];
@@ -92,12 +90,6 @@ const InterfaceManagement = () => {
92
90
  const [selectedRowKeys, setSelectedRowKeys] = useState([]);
93
91
  const [drawerVisible, setDrawerVisible] = useState(false);
94
92
 
95
- // 数据管理显示模式
96
- const [displayMode, setDisplayMode] = useState('modal');
97
-
98
- // 接口表单显示模式
99
- const [interfaceFormDisplayMode, setInterfaceFormDisplayMode] = useState('modal');
100
-
101
93
  // 表单实例
102
94
  const [form] = Form.useForm();
103
95
 
@@ -105,27 +97,13 @@ const InterfaceManagement = () => {
105
97
  const [tableConfig, setTableConfig] = useState(() => {
106
98
  const cached = localStorage.getItem('interface-table-config');
107
99
  const defaultVisibleColumns = COLUMN_CONFIG.map(col => col.key);
108
- const defaultConfig = {
100
+ return cached ? JSON.parse(cached) : {
109
101
  sortOrder: null,
110
102
  sortField: null,
111
103
  pageSize: 10,
112
104
  current: 1,
113
- visibleColumns: defaultVisibleColumns,
114
- columnWidths: {
115
- currentResponse: 180
116
- }
105
+ visibleColumns: defaultVisibleColumns
117
106
  };
118
-
119
- if (cached) {
120
- const parsedConfig = JSON.parse(cached);
121
- // 确保 columnWidths 存在
122
- if (!parsedConfig.columnWidths) {
123
- parsedConfig.columnWidths = { currentResponse: 180 };
124
- }
125
- return parsedConfig;
126
- }
127
-
128
- return defaultConfig;
129
107
  });
130
108
 
131
109
  // 保存表格配置到localStorage
@@ -145,52 +123,6 @@ const InterfaceManagement = () => {
145
123
  }
146
124
  }, [selectedFeatureId]);
147
125
 
148
- // 监听数据管理显示模式变化
149
- useEffect(() => {
150
- // 初始化时从 localStorage 读取配置
151
- const savedMode = localStorage.getItem('dataManagementDisplayMode');
152
- if (savedMode) {
153
- setDisplayMode(savedMode);
154
- }
155
-
156
- // 监听 storage 事件,实现设置立即生效
157
- const handleStorageChange = () => {
158
- const newMode = localStorage.getItem('dataManagementDisplayMode');
159
- if (newMode) {
160
- setDisplayMode(newMode);
161
- }
162
- };
163
-
164
- window.addEventListener('storage', handleStorageChange);
165
-
166
- return () => {
167
- window.removeEventListener('storage', handleStorageChange);
168
- };
169
- }, []);
170
-
171
- // 监听接口表单显示模式变化
172
- useEffect(() => {
173
- // 初始化时从 localStorage 读取配置
174
- const savedMode = localStorage.getItem('interfaceFormDisplayMode');
175
- if (savedMode) {
176
- setInterfaceFormDisplayMode(savedMode);
177
- }
178
-
179
- // 监听 storage 事件,实现设置立即生效
180
- const handleStorageChange = () => {
181
- const newMode = localStorage.getItem('interfaceFormDisplayMode');
182
- if (newMode) {
183
- setInterfaceFormDisplayMode(newMode);
184
- }
185
- };
186
-
187
- window.addEventListener('storage', handleStorageChange);
188
-
189
- return () => {
190
- window.removeEventListener('storage', handleStorageChange);
191
- };
192
- }, []);
193
-
194
126
  // 获取当前选中的功能模块
195
127
  const selectedFeature = features.find(f => f.id === selectedFeatureId);
196
128
 
@@ -743,22 +675,7 @@ const InterfaceManagement = () => {
743
675
  // 重置列配置
744
676
  const handleResetColumns = () => {
745
677
  const defaultColumns = COLUMN_CONFIG.map(col => col.key);
746
- saveTableConfig({
747
- visibleColumns: defaultColumns,
748
- columnWidths: {
749
- active: 80,
750
- name: 150,
751
- group: 120,
752
- urlPattern: 250,
753
- proxyType: 120,
754
- currentResponse: 180,
755
- responseDelay: 100,
756
- httpStatus: 100,
757
- contentType: 120,
758
- targetUrl: 200,
759
- customHeaders: 100
760
- }
761
- });
678
+ saveTableConfig({ visibleColumns: defaultColumns });
762
679
  };
763
680
 
764
681
  // 切换列配置面板显示
@@ -789,74 +706,6 @@ const InterfaceManagement = () => {
789
706
  message.success('数据已刷新');
790
707
  };
791
708
 
792
- // 可调整大小的表头组件(使用原生实现)
793
- const ResizableTitle = (props) => {
794
- const { onResize, width, columnKey, ...restProps } = props;
795
-
796
- if (!width || !onResize) {
797
- return <th {...restProps} />;
798
- }
799
-
800
- const handleMouseDown = (e) => {
801
- e.preventDefault();
802
- e.stopPropagation();
803
-
804
- const startX = e.clientX;
805
- const startWidth = width;
806
- let currentWidth = startWidth;
807
-
808
- const handleMouseMove = (moveEvent) => {
809
- const deltaX = moveEvent.clientX - startX;
810
- currentWidth = Math.max(50, startWidth + deltaX); // 最小宽度 50px
811
-
812
- // 实时更新列宽(不保存到 localStorage)
813
- const newColumnWidths = {
814
- ...tableConfig.columnWidths,
815
- [columnKey]: currentWidth
816
- };
817
- setTableConfig(prev => ({
818
- ...prev,
819
- columnWidths: newColumnWidths
820
- }));
821
- };
822
-
823
- const handleMouseUp = () => {
824
- document.removeEventListener('mousemove', handleMouseMove);
825
- document.removeEventListener('mouseup', handleMouseUp);
826
- document.body.style.cursor = '';
827
- document.body.style.userSelect = '';
828
-
829
- // 拖动结束后保存到 localStorage
830
- onResize(columnKey, currentWidth);
831
- };
832
-
833
- document.addEventListener('mousemove', handleMouseMove);
834
- document.addEventListener('mouseup', handleMouseUp);
835
- document.body.style.cursor = 'col-resize';
836
- document.body.style.userSelect = 'none';
837
- };
838
-
839
- return (
840
- <th {...restProps} style={{ ...restProps.style, position: 'relative' }}>
841
- {restProps.children}
842
- <span
843
- className="column-resize-handle"
844
- onMouseDown={handleMouseDown}
845
- onClick={(e) => e.stopPropagation()}
846
- />
847
- </th>
848
- );
849
- };
850
-
851
- // 处理列宽调整
852
- const handleResize = (columnKey, newWidth) => {
853
- const newColumnWidths = {
854
- ...tableConfig.columnWidths,
855
- [columnKey]: newWidth
856
- };
857
- saveTableConfig({ columnWidths: newColumnWidths });
858
- };
859
-
860
709
  // 批量操作接口状态
861
710
  const handleBatchOperation = async (active) => {
862
711
  if (!selectedRowKeys || selectedRowKeys.length === 0) {
@@ -891,12 +740,7 @@ const InterfaceManagement = () => {
891
740
  title: '状态',
892
741
  dataIndex: 'active',
893
742
  key: 'active',
894
- width: tableConfig.columnWidths?.active || 80,
895
- onHeaderCell: (column) => ({
896
- width: column.width,
897
- columnKey: 'active',
898
- onResize: handleResize,
899
- }),
743
+ width: 80,
900
744
  render: (active, record) => (
901
745
  <Switch
902
746
  checked={active !== false}
@@ -909,12 +753,6 @@ const InterfaceManagement = () => {
909
753
  title: '名称',
910
754
  dataIndex: 'name',
911
755
  key: 'name',
912
- width: tableConfig.columnWidths?.name || 150,
913
- onHeaderCell: (column) => ({
914
- width: column.width,
915
- columnKey: 'name',
916
- onResize: handleResize,
917
- }),
918
756
  sorter: (a, b) => a.name.localeCompare(b.name),
919
757
  render: (text) => <span className="interface-name">{text}</span>,
920
758
  },
@@ -922,12 +760,7 @@ const InterfaceManagement = () => {
922
760
  title: '分组',
923
761
  dataIndex: 'group',
924
762
  key: 'group',
925
- width: tableConfig.columnWidths?.group || 120,
926
- onHeaderCell: (column) => ({
927
- width: column.width,
928
- columnKey: 'group',
929
- onResize: handleResize,
930
- }),
763
+ width: 120,
931
764
  sorter: (a, b) => {
932
765
  const groupA = a.group || '';
933
766
  const groupB = b.group || '';
@@ -948,12 +781,6 @@ const InterfaceManagement = () => {
948
781
  title: 'URL匹配规则',
949
782
  dataIndex: 'urlPattern',
950
783
  key: 'urlPattern',
951
- width: tableConfig.columnWidths?.urlPattern || 250,
952
- onHeaderCell: (column) => ({
953
- width: column.width,
954
- columnKey: 'urlPattern',
955
- onResize: handleResize,
956
- }),
957
784
  ellipsis: true,
958
785
  render: (text) => <span className="url-pattern">{text}</span>,
959
786
  },
@@ -961,12 +788,7 @@ const InterfaceManagement = () => {
961
788
  title: '处理方式',
962
789
  dataIndex: 'proxyType',
963
790
  key: 'proxyType',
964
- width: tableConfig.columnWidths?.proxyType || 120,
965
- onHeaderCell: (column) => ({
966
- width: column.width,
967
- columnKey: 'proxyType',
968
- onResize: handleResize,
969
- }),
791
+ width: 120,
970
792
  render: (text) => {
971
793
  const proxyType = proxyTypes.find(item => item.value === text);
972
794
  return (
@@ -978,45 +800,82 @@ const InterfaceManagement = () => {
978
800
  },
979
801
  },
980
802
  {
981
- title: '当前响应',
803
+ title: '响应配置',
982
804
  dataIndex: 'activeResponseId',
983
805
  key: 'currentResponse',
984
- width: tableConfig.columnWidths?.currentResponse || 180,
985
- className: 'current-response-column',
986
- onHeaderCell: (column) => ({
987
- width: column.width,
988
- columnKey: 'currentResponse',
989
- onResize: handleResize,
990
- }),
991
- onCell: () => ({
992
- className: 'current-response-cell',
993
- }),
806
+ width: 160,
994
807
  render: (activeResponseId, record) => {
995
- // 如果没有响应数据,返回空
808
+ if (record.proxyType !== 'response') {
809
+ return <span style={{ color: '#ccc' }}>-</span>;
810
+ }
996
811
  if (!record.responses || !Array.isArray(record.responses) || record.responses.length === 0) {
997
- return <span className="no-response">无响应数据</span>;
812
+ return <span style={{ color: '#ff4d4f', fontSize: '12px' }}>无响应数据</span>;
998
813
  }
999
-
1000
- // 找到当前激活的响应
1001
- const activeResponse = record.responses.find(resp => resp.id === activeResponseId);
1002
-
1003
- // 如果没有找到激活的响应,使用第一个
1004
- const currentResponse = activeResponse || record.responses[0];
1005
-
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
+
820
+ // 场景一:所有响应都没有规则 → 显示 Select 快捷切换
821
+ if (!hasSmartMatch) {
822
+ return (
823
+ <Select
824
+ value={activeResponseId || record.responses[0]?.id}
825
+ style={{ width: '100%' }}
826
+ size="small"
827
+ onChange={(value) => handleResponseSwitch(record.id, value)}
828
+ >
829
+ {record.responses.map(resp => (
830
+ <Option key={resp.id} value={resp.id}>
831
+ {resp.name}
832
+ </Option>
833
+ ))}
834
+ </Select>
835
+ );
836
+ }
837
+
838
+ // 场景二/三:有响应含规则 → 显示 Tag 摘要
839
+ const tooltipContent = (
840
+ <div style={{ maxWidth: 260 }}>
841
+ {record.responses.map((resp) => {
842
+ const matchers = resp.paramMatchers || [];
843
+ return (
844
+ <div key={resp.id} style={{ marginBottom: 6 }}>
845
+ <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
846
+ {matchers.length > 0 ? (
847
+ <Tag color="blue" style={{ fontSize: '11px', margin: 0 }}>{matchers.length}条规则</Tag>
848
+ ) : (
849
+ <Tag color="default" style={{ fontSize: '11px', margin: 0 }}>兜底</Tag>
850
+ )}
851
+ <span style={{ fontWeight: resp.id === activeResponseId ? 'bold' : 'normal' }}>
852
+ {resp.name}
853
+ </span>
854
+ </div>
855
+ {matchers.length > 0 && (
856
+ <div style={{ paddingLeft: 8, marginTop: 2 }}>
857
+ {matchers.map((m, i) => (
858
+ <div key={i} style={{ fontSize: '11px', color: '#666' }}>
859
+ {m.paramPath} = {m.paramValue}
860
+ <span style={{ color: '#52c41a', marginLeft: 4 }}>
861
+ ({m.matchType === 'exact' ? '精确' : m.matchType === 'contains' ? '包含' : '正则'})
862
+ </span>
863
+ </div>
864
+ ))}
865
+ </div>
866
+ )}
867
+ </div>
868
+ );
869
+ })}
870
+ </div>
871
+ );
872
+
1006
873
  return (
1007
- <Select
1008
- value={activeResponseId || record.responses[0]?.id}
1009
- style={{ width: '100%' }}
1010
- onChange={(value) => handleResponseSwitch(record.id, value)}
1011
- disabled={record.proxyType !== 'response'}
1012
- getPopupContainer={(trigger) => trigger.parentNode}
1013
- >
1014
- {record.responses.map(resp => (
1015
- <Option key={resp.id} value={resp.id}>
1016
- {resp.name}
1017
- </Option>
1018
- ))}
1019
- </Select>
874
+ <Tooltip title={tooltipContent} placement="left">
875
+ <Tag color="blue" style={{ cursor: 'default' }}>
876
+ 智能匹配 · {total}个响应
877
+ </Tag>
878
+ </Tooltip>
1020
879
  );
1021
880
  },
1022
881
  },
@@ -1024,12 +883,7 @@ const InterfaceManagement = () => {
1024
883
  title: '延迟(毫秒)',
1025
884
  dataIndex: 'responseDelay',
1026
885
  key: 'responseDelay',
1027
- width: tableConfig.columnWidths?.responseDelay || 100,
1028
- onHeaderCell: (column) => ({
1029
- width: column.width,
1030
- columnKey: 'responseDelay',
1031
- onResize: handleResize,
1032
- }),
886
+ width: 100,
1033
887
  sorter: (a, b) => (a.responseDelay || 0) - (b.responseDelay || 0),
1034
888
  render: (delay) => {
1035
889
  const delayValue = parseInt(delay, 10) || 0;
@@ -1044,12 +898,7 @@ const InterfaceManagement = () => {
1044
898
  title: '状态码',
1045
899
  dataIndex: 'httpStatus',
1046
900
  key: 'httpStatus',
1047
- width: tableConfig.columnWidths?.httpStatus || 100,
1048
- onHeaderCell: (column) => ({
1049
- width: column.width,
1050
- columnKey: 'httpStatus',
1051
- onResize: handleResize,
1052
- }),
901
+ width: 100,
1053
902
  render: (status) => {
1054
903
  const statusCode = status || 200;
1055
904
  let statusClass = 'status-success';
@@ -1065,12 +914,7 @@ const InterfaceManagement = () => {
1065
914
  title: '内容类型',
1066
915
  dataIndex: 'contentType',
1067
916
  key: 'contentType',
1068
- width: tableConfig.columnWidths?.contentType || 120,
1069
- onHeaderCell: (column) => ({
1070
- width: column.width,
1071
- columnKey: 'contentType',
1072
- onResize: handleResize,
1073
- }),
917
+ width: 120,
1074
918
  sorter: (a, b) => {
1075
919
  const aType = a.contentType || '';
1076
920
  const bType = b.contentType || '';
@@ -1089,12 +933,6 @@ const InterfaceManagement = () => {
1089
933
  title: '目标URL',
1090
934
  dataIndex: 'targetUrl',
1091
935
  key: 'targetUrl',
1092
- width: tableConfig.columnWidths?.targetUrl || 200,
1093
- onHeaderCell: (column) => ({
1094
- width: column.width,
1095
- columnKey: 'targetUrl',
1096
- onResize: handleResize,
1097
- }),
1098
936
  ellipsis: true,
1099
937
  sorter: (a, b) => {
1100
938
  const aUrl = a.targetUrl || '';
@@ -1110,12 +948,7 @@ const InterfaceManagement = () => {
1110
948
  title: '自定义头',
1111
949
  dataIndex: 'customHeaders',
1112
950
  key: 'customHeaders',
1113
- width: tableConfig.columnWidths?.customHeaders || 100,
1114
- onHeaderCell: (column) => ({
1115
- width: column.width,
1116
- columnKey: 'customHeaders',
1117
- onResize: handleResize,
1118
- }),
951
+ width: 100,
1119
952
  sorter: (a, b) => {
1120
953
  const aCount = Object.keys(a.customHeaders || {}).length;
1121
954
  const bCount = Object.keys(b.customHeaders || {}).length;
@@ -1159,38 +992,75 @@ const InterfaceManagement = () => {
1159
992
  }
1160
993
  },
1161
994
  {
1162
- title: '参数匹配',
1163
- dataIndex: 'paramMatchers',
995
+ title: '匹配规则',
996
+ dataIndex: 'responses',
1164
997
  key: 'paramMatchers',
1165
998
  width: 100,
1166
- render: (_, record) => {
999
+ render: (responses, record) => {
1167
1000
  if (record.proxyType !== 'response') {
1168
- return '-';
1001
+ return <span style={{ color: '#ccc' }}>-</span>;
1169
1002
  }
1170
-
1171
- const matchers = record.paramMatchers || [];
1172
- const count = matchers.length;
1173
-
1174
- if (count === 0) {
1175
- return '-';
1003
+
1004
+ // 汇总所有响应的 paramMatchers(响应级),兼容旧数据接口级 paramMatchers
1005
+ const responsesWithMatchers = Array.isArray(responses)
1006
+ ? responses.filter(r => r.paramMatchers && Array.isArray(r.paramMatchers) && r.paramMatchers.length > 0)
1007
+ : [];
1008
+ const totalRules = responsesWithMatchers.reduce((sum, r) => sum + r.paramMatchers.length, 0);
1009
+
1010
+ // 兼容旧数据:接口级 paramMatchers
1011
+ const legacyMatchers = record.paramMatchers || [];
1012
+
1013
+ if (totalRules === 0 && legacyMatchers.length === 0) {
1014
+ return <span style={{ color: '#ccc' }}>-</span>;
1176
1015
  }
1177
-
1178
- return (
1179
- <Tooltip title={
1180
- <div>
1181
- {matchers.map((matcher, index) => (
1182
- <div key={index}>
1183
- {matcher.paramPath}: {matcher.paramValue}
1184
- <span style={{ color: '#52c41a', marginLeft: 4 }}>
1185
- ({matcher.matchType === 'exact' ? '精确' : matcher.matchType === 'contains' ? '包含' : '正则'})
1186
- </span>
1016
+
1017
+ // 新数据:按响应分组展示
1018
+ if (totalRules > 0) {
1019
+ const tooltipContent = (
1020
+ <div style={{ maxWidth: 280 }}>
1021
+ {responsesWithMatchers.map(resp => (
1022
+ <div key={resp.id} style={{ marginBottom: 6 }}>
1023
+ <div style={{ fontWeight: 'bold', marginBottom: 2 }}>{resp.name}:</div>
1024
+ {resp.paramMatchers.map((m, i) => (
1025
+ <div key={i} style={{ paddingLeft: 8, fontSize: '12px' }}>
1026
+ {m.paramPath} = {m.paramValue}
1027
+ <span style={{ color: '#52c41a', marginLeft: 4 }}>
1028
+ ({m.matchType === 'exact' ? '精确' : m.matchType === 'contains' ? '包含' : '正则'})
1029
+ </span>
1030
+ </div>
1031
+ ))}
1187
1032
  </div>
1188
1033
  ))}
1034
+ {Array.isArray(responses) && responses.some(r => !r.paramMatchers || r.paramMatchers.length === 0) && (
1035
+ <div style={{ color: '#999', fontSize: '12px', marginTop: 4 }}>
1036
+ + 兜底响应(无规则)
1037
+ </div>
1038
+ )}
1189
1039
  </div>
1190
- }>
1191
- <span style={{ color: '#1890ff' }}>
1192
- {count}条规则
1193
- </span>
1040
+ );
1041
+ return (
1042
+ <Tooltip title={tooltipContent} placement="left">
1043
+ <span style={{ color: '#1890ff', cursor: 'default' }}>{totalRules}条规则</span>
1044
+ </Tooltip>
1045
+ );
1046
+ }
1047
+
1048
+ // 旧数据兼容展示
1049
+ const tooltipContent = (
1050
+ <div>
1051
+ {legacyMatchers.map((matcher, index) => (
1052
+ <div key={index}>
1053
+ {matcher.paramPath}: {matcher.paramValue}
1054
+ <span style={{ color: '#52c41a', marginLeft: 4 }}>
1055
+ ({matcher.matchType === 'exact' ? '精确' : matcher.matchType === 'contains' ? '包含' : '正则'})
1056
+ </span>
1057
+ </div>
1058
+ ))}
1059
+ </div>
1060
+ );
1061
+ return (
1062
+ <Tooltip title={tooltipContent} placement="left">
1063
+ <span style={{ color: '#fa8c16', cursor: 'default' }}>{legacyMatchers.length}条(旧)</span>
1194
1064
  </Tooltip>
1195
1065
  );
1196
1066
  }
@@ -1547,11 +1417,6 @@ const InterfaceManagement = () => {
1547
1417
  selectedRowKeys,
1548
1418
  onChange: onSelectChange,
1549
1419
  }}
1550
- components={{
1551
- header: {
1552
- cell: ResizableTitle,
1553
- },
1554
- }}
1555
1420
  size="middle"
1556
1421
  pagination={{
1557
1422
  current: tableConfig.current,
@@ -1569,70 +1434,63 @@ const InterfaceManagement = () => {
1569
1434
  </div>
1570
1435
  </Card>
1571
1436
 
1572
- {/* 接口编辑/创建模态框/抽屉 */}
1573
- {interfaceFormDisplayMode === 'drawer' ? (
1574
- <Drawer
1575
- title={
1576
- <div style={{
1577
- fontSize: '18px',
1578
- fontWeight: 600,
1579
- color: '#262626',
1580
- display: 'flex',
1581
- alignItems: 'center',
1582
- gap: '8px'
1583
- }}>
1584
- <span>{editingInterface ? '编辑接口' : (isDuplicateMode ? '复制新增接口' : '添加接口')}</span>
1585
- <Badge
1586
- count="配置向导"
1587
- style={{
1588
- backgroundColor: '#f0f9ff',
1589
- color: '#1677ff',
1590
- fontSize: '11px',
1591
- fontWeight: 'normal'
1592
- }}
1593
- />
1594
- </div>
1595
- }
1596
- placement="right"
1597
- width={1040}
1598
- open={modalVisible}
1599
- onClose={handleCancel}
1600
- destroyOnClose
1601
- bodyStyle={{
1602
- padding: '0',
1603
- background: '#fafafa'
1604
- }}
1605
- footer={
1606
- <div style={{ textAlign: 'right' }}>
1607
- <Space>
1608
- <Button onClick={handleCancel}>取消</Button>
1609
- <Button type="primary" onClick={handleSubmit}>
1610
- {editingInterface ? '保存' : '创建'}
1611
- </Button>
1612
- </Space>
1613
- </div>
1614
- }
1615
- className="interface-drawer"
1616
- >
1617
- <div style={{ padding: '24px' }}>
1618
- <Form
1619
- form={form}
1620
- layout="vertical"
1621
- initialValues={{
1622
- name: '',
1623
- group: '',
1624
- pattern: '',
1625
- proxyType: 'response',
1626
- statusCode: '200',
1627
- contentType: 'application/json; charset=utf-8',
1628
- responses: [],
1629
- httpMethod: 'ALL',
1630
- targetUrl: '',
1631
- headerItems: [],
1632
- paramMatchers: [],
1633
- responseDelay: '0'
1634
- }}
1635
- >
1437
+ {/* 接口编辑/创建模态框 */}
1438
+ <Modal
1439
+ title={
1440
+ <div style={{
1441
+ fontSize: '18px',
1442
+ fontWeight: 600,
1443
+ color: '#262626',
1444
+ display: 'flex',
1445
+ alignItems: 'center',
1446
+ gap: '8px'
1447
+ }}>
1448
+ <span>{editingInterface ? '编辑接口' : (isDuplicateMode ? '复制新增接口' : '添加接口')}</span>
1449
+ <Badge
1450
+ count="配置向导"
1451
+ style={{
1452
+ backgroundColor: '#f0f9ff',
1453
+ color: '#1677ff',
1454
+ fontSize: '11px',
1455
+ fontWeight: 'normal'
1456
+ }}
1457
+ />
1458
+ </div>
1459
+ }
1460
+ open={modalVisible}
1461
+ onOk={handleSubmit}
1462
+ onCancel={handleCancel}
1463
+ width={1200}
1464
+ destroyOnClose
1465
+ okText={editingInterface ? '保存' : '创建'}
1466
+ cancelText="取消"
1467
+ bodyStyle={{
1468
+ maxHeight: '75vh',
1469
+ overflow: 'auto',
1470
+ padding: '0',
1471
+ background: '#fafafa'
1472
+ }}
1473
+ className="interface-modal"
1474
+ >
1475
+ <div style={{ padding: '24px' }}>
1476
+ <Form
1477
+ form={form}
1478
+ layout="vertical"
1479
+ initialValues={{
1480
+ name: '',
1481
+ group: '',
1482
+ pattern: '',
1483
+ proxyType: 'response',
1484
+ statusCode: '200',
1485
+ contentType: 'application/json; charset=utf-8',
1486
+ responses: [],
1487
+ httpMethod: 'ALL',
1488
+ targetUrl: '',
1489
+ headerItems: [],
1490
+ paramMatchers: [],
1491
+ responseDelay: '0'
1492
+ }}
1493
+ >
1636
1494
  <Collapse
1637
1495
  defaultActiveKey={
1638
1496
  editingInterface && editingInterface.proxyType === 'response'
@@ -1924,97 +1782,9 @@ const InterfaceManagement = () => {
1924
1782
  </div>
1925
1783
  </div>
1926
1784
 
1927
- {/* 请求入参匹配设置 */}
1928
- <Form.Item
1929
- label="请求入参匹配"
1930
- tooltip="设置请求参数的匹配条件,只有当请求参数满足条件时才返回对应的响应。支持嵌套属性路径,如:a.b.c"
1931
- >
1932
- <Form.List name="paramMatchers">
1933
- {(fields, { add, remove }) => (
1934
- <>
1935
- {fields.map(({ key, name, ...restField }) => (
1936
- <div key={key} style={{ display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '8px' }}>
1937
- <Form.Item
1938
- {...restField}
1939
- name={[name, 'paramPath']}
1940
- rules={[
1941
- { required: true, message: '请输入参数路径' },
1942
- {
1943
- pattern: /^[a-zA-Z_$][a-zA-Z0-9_$.]*$/,
1944
- message: '请输入有效的参数路径,如:userId 或 data.user.id'
1945
- }
1946
- ]}
1947
- style={{ flex: 1, margin: 0 }}
1948
- >
1949
- <Input placeholder="参数路径(如:userId 或 data.user.id)" />
1950
- </Form.Item>
1951
- <Form.Item
1952
- {...restField}
1953
- name={[name, 'paramValue']}
1954
- rules={[{ required: true, message: '请输入期望值' }]}
1955
- style={{ flex: 1, margin: 0 }}
1956
- >
1957
- <Input placeholder="期望值(如:123 或 admin)" />
1958
- </Form.Item>
1959
- <Form.Item
1960
- {...restField}
1961
- name={[name, 'matchType']}
1962
- style={{ width: 100, margin: 0 }}
1963
- initialValue="exact"
1964
- >
1965
- <Select placeholder="匹配类型">
1966
- <Option value="exact">精确匹配</Option>
1967
- <Option value="contains">包含</Option>
1968
- <Option value="regex">正则</Option>
1969
- </Select>
1970
- </Form.Item>
1971
- <Button
1972
- type="text"
1973
- danger
1974
- icon={<DeleteOutlined />}
1975
- onClick={() => remove(name)}
1976
- />
1977
- </div>
1978
- ))}
1979
- <div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginTop: '8px' }}>
1980
- <Button
1981
- type="dashed"
1982
- onClick={() => add()}
1983
- icon={<PlusOutlined />}
1984
- style={{ flex: 1 }}
1985
- >
1986
- 添加参数匹配规则
1987
- </Button>
1988
- <Popover
1989
- content={
1990
- <div style={{ maxWidth: '300px' }}>
1991
- <div style={{ marginBottom: '8px', fontWeight: 'bold' }}>匹配规则说明:</div>
1992
- <div style={{ lineHeight: '1.6' }}>
1993
- <div>• <strong>精确匹配</strong>:参数值完全相等</div>
1994
- <div>• <strong>包含</strong>:参数值包含指定内容</div>
1995
- <div>• <strong>正则</strong>:参数值符合正则表达式</div>
1996
- <div>• <strong>嵌套路径</strong>:使用点号分隔,如 data.user.id</div>
1997
- </div>
1998
- </div>
1999
- }
2000
- title="参数匹配规则"
2001
- trigger="click"
2002
- placement="top"
2003
- >
2004
- <Button
2005
- type="text"
2006
- icon={<QuestionCircleOutlined />}
2007
- style={{
2008
- color: '#999',
2009
- padding: '4px'
2010
- }}
2011
- size="small"
2012
- />
2013
- </Popover>
2014
- </div>
2015
- </>
2016
- )}
2017
- </Form.List>
1785
+ {/* 入参匹配规则已移至各响应内部配置,此处保留隐藏字段向后兼容旧数据 */}
1786
+ <Form.Item name="paramMatchers" hidden initialValue={[]}>
1787
+ <Input />
2018
1788
  </Form.Item>
2019
1789
 
2020
1790
  {/* 隐藏的表单字段用于存储响应数据 */}
@@ -2139,538 +1909,7 @@ const InterfaceManagement = () => {
2139
1909
  </Collapse>
2140
1910
  </Form>
2141
1911
  </div>
2142
- </Drawer>
2143
- ) : (
2144
- <Modal
2145
- title={
2146
- <div style={{
2147
- fontSize: '18px',
2148
- fontWeight: 600,
2149
- color: '#262626',
2150
- display: 'flex',
2151
- alignItems: 'center',
2152
- gap: '8px'
2153
- }}>
2154
- <span>{editingInterface ? '编辑接口' : (isDuplicateMode ? '复制新增接口' : '添加接口')}</span>
2155
- <Badge
2156
- count="配置向导"
2157
- style={{
2158
- backgroundColor: '#f0f9ff',
2159
- color: '#1677ff',
2160
- fontSize: '11px',
2161
- fontWeight: 'normal'
2162
- }}
2163
- />
2164
- </div>
2165
- }
2166
- open={modalVisible}
2167
- onCancel={handleCancel}
2168
- onOk={handleSubmit}
2169
- width={1040}
2170
- destroyOnClose
2171
- okText={editingInterface ? '保存' : '创建'}
2172
- cancelText="取消"
2173
- bodyStyle={{
2174
- maxHeight: '70vh',
2175
- overflow: 'auto',
2176
- background: '#fafafa'
2177
- }}
2178
- >
2179
- <div style={{ padding: '0' }}>
2180
- <Form
2181
- form={form}
2182
- layout="vertical"
2183
- initialValues={{
2184
- name: '',
2185
- group: '',
2186
- pattern: '',
2187
- proxyType: 'response',
2188
- statusCode: '200',
2189
- contentType: 'application/json; charset=utf-8',
2190
- responses: [],
2191
- httpMethod: 'ALL',
2192
- targetUrl: '',
2193
- headerItems: [],
2194
- paramMatchers: [],
2195
- responseDelay: '0'
2196
- }}
2197
- >
2198
- {/* Modal 模式下使用相同的表单内容,但去掉 Collapse 包装 */}
2199
- <div style={{ padding: '24px' }}>
2200
- {/* 基础信息 */}
2201
- <div style={{ marginBottom: '24px' }}>
2202
- <div style={{ fontSize: '16px', fontWeight: 600, marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '8px' }}>
2203
- <Badge count="1" style={{ backgroundColor: '#1677ff' }} />
2204
- <span>基础信息</span>
2205
- </div>
2206
- <div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
2207
- <div style={{ flex: 1 }}>
2208
- <Form.Item
2209
- name="name"
2210
- label="接口名称"
2211
- rules={[{ required: true, message: '请输入接口名称' }]}
2212
- >
2213
- <Input placeholder="请输入接口名称" />
2214
- </Form.Item>
2215
- </div>
2216
- <div style={{ flex: 1 }}>
2217
- <Form.Item
2218
- name="group"
2219
- label="分组"
2220
- tooltip="为接口设置分组,便于管理和批量操作"
2221
- >
2222
- <Select
2223
- placeholder="选择或创建分组"
2224
- showSearch
2225
- allowClear
2226
- style={{ width: '100%' }}
2227
- mode="tags"
2228
- maxTagCount="responsive"
2229
- maxTagTextLength={10}
2230
- onChange={(value) => {
2231
- if (Array.isArray(value) && value.length > 0) {
2232
- const groupValue = value[0] === '' ? undefined : value[0];
2233
- form.setFieldsValue({ group: groupValue });
2234
- }
2235
- }}
2236
- onInputKeyDown={(e) => {
2237
- if (e.key === 'Enter') {
2238
- e.stopPropagation();
2239
- }
2240
- }}
2241
- >
2242
- {Array.from(new Set(interfaces.map(item => item.group).filter(Boolean))).map(group => (
2243
- <Option key={group} value={group}>{group}</Option>
2244
- ))}
2245
- </Select>
2246
- </Form.Item>
2247
- </div>
2248
- </div>
2249
- </div>
2250
-
2251
- {/* URL配置 */}
2252
- <div style={{ marginBottom: '24px' }}>
2253
- <div style={{ fontSize: '16px', fontWeight: 600, marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '8px' }}>
2254
- <Badge count="2" style={{ backgroundColor: '#1677ff' }} />
2255
- <span>URL配置</span>
2256
- </div>
2257
- <div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
2258
- <div style={{ flex: 1 }}>
2259
- <Form.Item
2260
- name="pattern"
2261
- label="URL匹配规则"
2262
- rules={[
2263
- { required: true, message: '请输入URL匹配规则' },
2264
- ({ getFieldValue }) => ({
2265
- validator(_, value) {
2266
- const proxyType = getFieldValue('proxyType');
2267
- if (!value) return Promise.resolve();
2268
-
2269
- if (proxyType === 'url_redirect') {
2270
- try {
2271
- new URL(value);
2272
- return Promise.resolve();
2273
- } catch (e) {
2274
- return Promise.reject(new Error('URL重定向模式下,URL匹配规则必须是完整的URL(包括http://或https://)'));
2275
- }
2276
- }
2277
-
2278
- if (proxyType === 'redirect') {
2279
- if (value.startsWith('http://') || value.startsWith('https://')) {
2280
- return Promise.resolve();
2281
- }
2282
- return Promise.reject(new Error('重定向模式下,URL匹配规则必须以http://或https://开头'));
2283
- }
2284
-
2285
- if (proxyType === 'response') {
2286
- if (value.startsWith('/') && value.endsWith('/')) {
2287
- try {
2288
- new RegExp(value.slice(1, -1));
2289
- return Promise.resolve();
2290
- } catch (e) {
2291
- return Promise.reject(new Error('无效的正则表达式格式'));
2292
- }
2293
- }
2294
-
2295
- if (value.includes('*')) {
2296
- if (/^[a-zA-Z0-9\-_/.*]+$/.test(value)) {
2297
- return Promise.resolve();
2298
- }
2299
- return Promise.reject(new Error('通配符URL格式不正确'));
2300
- }
2301
-
2302
- if (/^[a-zA-Z0-9\-_/]+$/.test(value)) {
2303
- return Promise.resolve();
2304
- }
2305
- return Promise.reject(new Error('URL路径格式不正确'));
2306
- }
2307
-
2308
- return Promise.resolve();
2309
- },
2310
- }),
2311
- ]}
2312
- tooltip={{
2313
- title: (
2314
- <>
2315
- <div>不同处理方式下URL匹配规则要求:</div>
2316
- <ul style={{margin: '5px 0 0 15px', padding: 0}}>
2317
- <li><b>模拟响应:</b> 支持路径格式如 /api/users,通配符如 /api/*,正则如 /\/api\/\d+/</li>
2318
- <li><b>重定向:</b> 必须以http://或https://开头,例如:https://example.com/api</li>
2319
- <li><b>URL重定向:</b> 必须是完整URL,包括http://或https://,例如:https://example.com/api/users</li>
2320
- </ul>
2321
- </>
2322
- ),
2323
- overlayStyle: { maxWidth: '450px' }
2324
- }}
2325
- >
2326
- <Input
2327
- placeholder="根据选择的处理方式输入相应格式的URL"
2328
- />
2329
- </Form.Item>
2330
- </div>
2331
- <div style={{ flex: 1 }}>
2332
- <Form.Item
2333
- name="proxyType"
2334
- label="处理方式"
2335
- rules={[{ required: true, message: '请选择处理方式' }]}
2336
- tooltip={{
2337
- title: (
2338
- <>
2339
- <div>不同处理方式的规则说明:</div>
2340
- <ul style={{margin: '5px 0 0 15px', padding: 0}}>
2341
- <li><b>模拟响应:</b> 返回您定义的响应内容</li>
2342
- <li><b>重定向:</b> 将请求重定向到其他URL,URL匹配规则必须以http://或https://开头</li>
2343
- <li><b>URL重定向:</b> 完全匹配URL时重定向,URL匹配规则必须是完整URL</li>
2344
- </ul>
2345
- </>
2346
- ),
2347
- overlayStyle: { maxWidth: '450px' }
2348
- }}
2349
- >
2350
- <Select onChange={(value) => {
2351
- form.setFieldsValue({ pattern: '' });
2352
- const patternInput = document.querySelector('input[placeholder="根据选择的处理方式输入相应格式的URL"]');
2353
- if (patternInput) {
2354
- if (value === 'response') {
2355
- patternInput.placeholder = "例如:/api/users,/api/users/*,/api\\/users\\/\\d+/";
2356
- } else if (value === 'redirect') {
2357
- patternInput.placeholder = "例如:https://example.com/api";
2358
- } else if (value === 'url_redirect') {
2359
- patternInput.placeholder = "例如:https://example.com/api/users";
2360
- }
2361
- }
2362
- }}>
2363
- {proxyTypes.map(item => (
2364
- <Option key={item.value} value={item.value}>{item.label}</Option>
2365
- ))}
2366
- </Select>
2367
- </Form.Item>
2368
- </div>
2369
- </div>
2370
-
2371
- <div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
2372
- <div style={{ flex: 1 }}>
2373
- <Form.Item
2374
- name="httpMethod"
2375
- label="请求方法"
2376
- rules={[{ required: true, message: '请选择请求方法' }]}
2377
- >
2378
- <Select>
2379
- {httpMethods.map(item => (
2380
- <Option key={item.value} value={item.value}>{item.label}</Option>
2381
- ))}
2382
- </Select>
2383
- </Form.Item>
2384
- </div>
2385
- <div style={{ flex: 1 }}>
2386
- {/* 这里可以留空或添加其他字段 */}
2387
- </div>
2388
- </div>
2389
- </div>
2390
-
2391
- {/* 高级配置 */}
2392
- <div style={{ marginBottom: '24px' }}>
2393
- <div style={{ fontSize: '16px', fontWeight: 600, marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '8px' }}>
2394
- <Badge count="3" style={{ backgroundColor: '#1677ff' }} />
2395
- <span>高级配置</span>
2396
- </div>
2397
-
2398
- {/* 根据proxyType显示不同的表单项 */}
2399
- <Form.Item noStyle shouldUpdate={(prevValues, currentValues) => prevValues.proxyType !== currentValues.proxyType}>
2400
- {({ getFieldValue }) => {
2401
- const proxyType = getFieldValue('proxyType');
2402
-
2403
- if (proxyType === 'response') {
2404
- return (
2405
- <>
2406
- <div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
2407
- <div style={{ flex: 1 }}>
2408
- <Form.Item
2409
- name="statusCode"
2410
- label="状态码"
2411
- rules={[{ required: true, message: '请选择状态码' }]}
2412
- >
2413
- <Select>
2414
- {statusCodes.map(item => (
2415
- <Option key={item.value} value={item.value}>{item.label}</Option>
2416
- ))}
2417
- </Select>
2418
- </Form.Item>
2419
- </div>
2420
- <div style={{ flex: 1 }}>
2421
- <Form.Item
2422
- name="contentType"
2423
- label="内容类型"
2424
- rules={[{ required: true, message: '请选择内容类型' }]}
2425
- >
2426
- <Select>
2427
- {contentTypes.map(item => (
2428
- <Option key={item.value} value={item.value}>{item.label}</Option>
2429
- ))}
2430
- </Select>
2431
- </Form.Item>
2432
- </div>
2433
- <div style={{ flex: 1 }}>
2434
- <Form.Item
2435
- name="responseDelay"
2436
- label="延迟响应(毫秒)"
2437
- tooltip="设置接口响应延迟时间,单位为毫秒"
2438
- rules={[
2439
- {
2440
- pattern: /^\d*$/,
2441
- message: '请输入有效的数字'
2442
- },
2443
- {
2444
- validator: (_, value) => {
2445
- if (!value || parseInt(value, 10) <= 60000) {
2446
- return Promise.resolve();
2447
- }
2448
- return Promise.reject(new Error('延迟时间不能超过60秒(60000毫秒)'));
2449
- }
2450
- }
2451
- ]}
2452
- >
2453
- <Input placeholder="例如:500" />
2454
- </Form.Item>
2455
- </div>
2456
- </div>
2457
-
2458
- {/* 请求入参匹配设置 */}
2459
- <Form.Item
2460
- label="请求入参匹配"
2461
- tooltip="设置请求参数的匹配条件,只有当请求参数满足条件时才返回对应的响应。支持嵌套属性路径,如:a.b.c"
2462
- >
2463
- <Form.List name="paramMatchers">
2464
- {(fields, { add, remove }) => (
2465
- <>
2466
- {fields.map(({ key, name, ...restField }) => (
2467
- <div key={key} style={{ display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '8px' }}>
2468
- <Form.Item
2469
- {...restField}
2470
- name={[name, 'paramPath']}
2471
- rules={[
2472
- { required: true, message: '请输入参数路径' },
2473
- {
2474
- pattern: /^[a-zA-Z_$][a-zA-Z0-9_$.]*$/,
2475
- message: '请输入有效的参数路径,如:userId 或 data.user.id'
2476
- }
2477
- ]}
2478
- style={{ flex: 1, margin: 0 }}
2479
- >
2480
- <Input placeholder="参数路径(如:userId 或 data.user.id)" />
2481
- </Form.Item>
2482
- <Form.Item
2483
- {...restField}
2484
- name={[name, 'paramValue']}
2485
- rules={[{ required: true, message: '请输入期望值' }]}
2486
- style={{ flex: 1, margin: 0 }}
2487
- >
2488
- <Input placeholder="期望值(如:123 或 admin)" />
2489
- </Form.Item>
2490
- <Form.Item
2491
- {...restField}
2492
- name={[name, 'matchType']}
2493
- style={{ width: 100, margin: 0 }}
2494
- initialValue="exact"
2495
- >
2496
- <Select placeholder="匹配类型">
2497
- <Option value="exact">精确匹配</Option>
2498
- <Option value="contains">包含</Option>
2499
- <Option value="regex">正则</Option>
2500
- </Select>
2501
- </Form.Item>
2502
- <Button
2503
- type="text"
2504
- danger
2505
- icon={<DeleteOutlined />}
2506
- onClick={() => remove(name)}
2507
- />
2508
- </div>
2509
- ))}
2510
- <div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginTop: '8px' }}>
2511
- <Button
2512
- type="dashed"
2513
- onClick={() => add()}
2514
- icon={<PlusOutlined />}
2515
- style={{ flex: 1 }}
2516
- >
2517
- 添加参数匹配规则
2518
- </Button>
2519
- <Popover
2520
- content={
2521
- <div style={{ maxWidth: '300px' }}>
2522
- <div style={{ marginBottom: '8px', fontWeight: 'bold' }}>匹配规则说明:</div>
2523
- <div style={{ lineHeight: '1.6' }}>
2524
- <div>• <strong>精确匹配</strong>:参数值完全相等</div>
2525
- <div>• <strong>包含</strong>:参数值包含指定内容</div>
2526
- <div>• <strong>正则</strong>:参数值符合正则表达式</div>
2527
- <div>• <strong>嵌套路径</strong>:使用点号分隔,如 data.user.id</div>
2528
- </div>
2529
- </div>
2530
- }
2531
- title="参数匹配规则"
2532
- trigger="click"
2533
- placement="top"
2534
- >
2535
- <Button
2536
- type="text"
2537
- icon={<QuestionCircleOutlined />}
2538
- style={{
2539
- color: '#999',
2540
- padding: '4px'
2541
- }}
2542
- size="small"
2543
- />
2544
- </Popover>
2545
- </div>
2546
- </>
2547
- )}
2548
- </Form.List>
2549
- </Form.Item>
2550
-
2551
- {/* 隐藏的表单字段用于存储响应数据 */}
2552
- <Form.Item
2553
- name="responses"
2554
- initialValue={[]}
2555
- hidden
2556
- >
2557
- <Input />
2558
- </Form.Item>
2559
- <Form.Item name="activeResponseId" hidden>
2560
- <Input />
2561
- </Form.Item>
2562
-
2563
- {/* 使用新的合并组件 */}
2564
- <Form.Item noStyle shouldUpdate>
2565
- {({ getFieldValue }) => {
2566
- let responses = getFieldValue('responses') || [];
2567
- const activeResponseId = getFieldValue('activeResponseId');
2568
-
2569
- if (!Array.isArray(responses)) {
2570
- try {
2571
- if (typeof responses === 'string') {
2572
- responses = JSON.parse(responses);
2573
- }
2574
- } catch (e) {
2575
- responses = [];
2576
- }
2577
- }
2578
-
2579
- return (
2580
- <ResponseContentEditor
2581
- form={form}
2582
- responses={responses}
2583
- activeResponseId={activeResponseId}
2584
- onPreview={handlePreview}
2585
- />
2586
- );
2587
- }}
2588
- </Form.Item>
2589
- </>
2590
- );
2591
- } else if (proxyType === 'redirect' || proxyType === 'url_redirect') {
2592
- return (
2593
- <>
2594
- <Form.Item
2595
- name="targetUrl"
2596
- label="重定向目标URL"
2597
- rules={[
2598
- { required: true, message: '请输入重定向目标URL' },
2599
- {
2600
- validator(_, value) {
2601
- if (!value) return Promise.resolve();
2602
- try {
2603
- new URL(value);
2604
- return Promise.resolve();
2605
- } catch (e) {
2606
- return Promise.reject(new Error('请输入有效的URL格式(包括http://或https://)'));
2607
- }
2608
- }
2609
- }
2610
- ]}
2611
- tooltip="重定向的目标URL,必须是完整的URL格式"
2612
- >
2613
- <Input
2614
- placeholder="例如:https://api.example.com/users"
2615
- />
2616
- </Form.Item>
2617
-
2618
- {/* 自定义请求头设置 */}
2619
- <Form.Item label="自定义请求头">
2620
- <Form.List name="headerItems">
2621
- {(fields, { add, remove }) => (
2622
- <>
2623
- {fields.map(({ key, name, ...restField }) => (
2624
- <div key={key} style={{ display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '8px' }}>
2625
- <Form.Item
2626
- {...restField}
2627
- name={[name, 'headerName']}
2628
- rules={[{ required: true, message: '请输入请求头名称' }]}
2629
- style={{ flex: 1, margin: 0 }}
2630
- >
2631
- <Input placeholder="请求头名称(如:Authorization)" />
2632
- </Form.Item>
2633
- <Form.Item
2634
- {...restField}
2635
- name={[name, 'headerValue']}
2636
- rules={[{ required: true, message: '请输入请求头值' }]}
2637
- style={{ flex: 1, margin: 0 }}
2638
- >
2639
- <Input placeholder="请求头值(如:Bearer token123)" />
2640
- </Form.Item>
2641
- <Button
2642
- type="text"
2643
- danger
2644
- icon={<DeleteOutlined />}
2645
- onClick={() => remove(name)}
2646
- />
2647
- </div>
2648
- ))}
2649
- <Button
2650
- type="dashed"
2651
- onClick={() => add()}
2652
- icon={<PlusOutlined />}
2653
- style={{ width: '100%', marginTop: '8px' }}
2654
- >
2655
- 添加请求头
2656
- </Button>
2657
- </>
2658
- )}
2659
- </Form.List>
2660
- </Form.Item>
2661
- </>
2662
- );
2663
- }
2664
-
2665
- return null;
2666
- }}
2667
- </Form.Item>
2668
- </div>
2669
- </div>
2670
- </Form>
2671
- </div>
2672
- </Modal>
2673
- )}
1912
+ </Modal>
2674
1913
 
2675
1914
  {/* 使用拆分后的预览模态框组件 */}
2676
1915
  <PreviewModal
@@ -2679,26 +1918,15 @@ const InterfaceManagement = () => {
2679
1918
  previewContent={previewContent}
2680
1919
  />
2681
1920
 
2682
- {/* 数据管理弹窗/抽屉 */}
2683
- {displayMode === 'drawer' ? (
2684
- <DataManagementDrawer
2685
- visible={dataModalVisible}
2686
- onCancel={() => { setDataModalVisible(false); setDataModalRecord(null); }}
2687
- interfaceItem={dataModalRecord}
2688
- onSaved={() => {
2689
- fetchInterfaces();
2690
- }}
2691
- />
2692
- ) : (
2693
- <DataManagementModal
2694
- visible={dataModalVisible}
2695
- onCancel={() => { setDataModalVisible(false); setDataModalRecord(null); }}
2696
- interfaceItem={dataModalRecord}
2697
- onSaved={() => {
2698
- fetchInterfaces();
2699
- }}
2700
- />
2701
- )}
1921
+ {/* 数据管理弹窗 */}
1922
+ <DataManagementModal
1923
+ visible={dataModalVisible}
1924
+ onCancel={() => { setDataModalVisible(false); setDataModalRecord(null); }}
1925
+ interfaceItem={dataModalRecord}
1926
+ onSaved={() => {
1927
+ fetchInterfaces();
1928
+ }}
1929
+ />
2702
1930
  </div>
2703
1931
  </AppLayout>
2704
1932
  );