whistle.mock-plugins 1.2.2-beta.3 → 1.2.2-beta.5

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.
@@ -32,6 +32,7 @@ import {
32
32
  generateResponseId
33
33
  } from '../components/interface-management';
34
34
  import DataManagementModal from '../components/interface-management/DataManagementModal';
35
+ import { DataManagementDrawer } from '../components/interface-management';
35
36
 
36
37
  const { Option } = Select;
37
38
  const { Search } = AntInput;
@@ -91,6 +92,12 @@ const InterfaceManagement = () => {
91
92
  const [selectedRowKeys, setSelectedRowKeys] = useState([]);
92
93
  const [drawerVisible, setDrawerVisible] = useState(false);
93
94
 
95
+ // 数据管理显示模式
96
+ const [displayMode, setDisplayMode] = useState('modal');
97
+
98
+ // 接口表单显示模式
99
+ const [interfaceFormDisplayMode, setInterfaceFormDisplayMode] = useState('modal');
100
+
94
101
  // 表单实例
95
102
  const [form] = Form.useForm();
96
103
 
@@ -138,6 +145,52 @@ const InterfaceManagement = () => {
138
145
  }
139
146
  }, [selectedFeatureId]);
140
147
 
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
+
141
194
  // 获取当前选中的功能模块
142
195
  const selectedFeature = features.find(f => f.id === selectedFeatureId);
143
196
 
@@ -693,7 +746,17 @@ const InterfaceManagement = () => {
693
746
  saveTableConfig({
694
747
  visibleColumns: defaultColumns,
695
748
  columnWidths: {
696
- currentResponse: 180
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
697
760
  }
698
761
  });
699
762
  };
@@ -740,11 +803,21 @@ const InterfaceManagement = () => {
740
803
 
741
804
  const startX = e.clientX;
742
805
  const startWidth = width;
806
+ let currentWidth = startWidth;
743
807
 
744
808
  const handleMouseMove = (moveEvent) => {
745
809
  const deltaX = moveEvent.clientX - startX;
746
- const newWidth = Math.max(50, startWidth + deltaX); // 最小宽度 50px
747
- onResize(columnKey, newWidth);
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
+ }));
748
821
  };
749
822
 
750
823
  const handleMouseUp = () => {
@@ -752,6 +825,9 @@ const InterfaceManagement = () => {
752
825
  document.removeEventListener('mouseup', handleMouseUp);
753
826
  document.body.style.cursor = '';
754
827
  document.body.style.userSelect = '';
828
+
829
+ // 拖动结束后保存到 localStorage
830
+ onResize(columnKey, currentWidth);
755
831
  };
756
832
 
757
833
  document.addEventListener('mousemove', handleMouseMove);
@@ -815,7 +891,12 @@ const InterfaceManagement = () => {
815
891
  title: '状态',
816
892
  dataIndex: 'active',
817
893
  key: 'active',
818
- width: 80,
894
+ width: tableConfig.columnWidths?.active || 80,
895
+ onHeaderCell: (column) => ({
896
+ width: column.width,
897
+ columnKey: 'active',
898
+ onResize: handleResize,
899
+ }),
819
900
  render: (active, record) => (
820
901
  <Switch
821
902
  checked={active !== false}
@@ -828,6 +909,12 @@ const InterfaceManagement = () => {
828
909
  title: '名称',
829
910
  dataIndex: 'name',
830
911
  key: 'name',
912
+ width: tableConfig.columnWidths?.name || 150,
913
+ onHeaderCell: (column) => ({
914
+ width: column.width,
915
+ columnKey: 'name',
916
+ onResize: handleResize,
917
+ }),
831
918
  sorter: (a, b) => a.name.localeCompare(b.name),
832
919
  render: (text) => <span className="interface-name">{text}</span>,
833
920
  },
@@ -835,7 +922,12 @@ const InterfaceManagement = () => {
835
922
  title: '分组',
836
923
  dataIndex: 'group',
837
924
  key: 'group',
838
- width: 120,
925
+ width: tableConfig.columnWidths?.group || 120,
926
+ onHeaderCell: (column) => ({
927
+ width: column.width,
928
+ columnKey: 'group',
929
+ onResize: handleResize,
930
+ }),
839
931
  sorter: (a, b) => {
840
932
  const groupA = a.group || '';
841
933
  const groupB = b.group || '';
@@ -856,6 +948,12 @@ const InterfaceManagement = () => {
856
948
  title: 'URL匹配规则',
857
949
  dataIndex: 'urlPattern',
858
950
  key: 'urlPattern',
951
+ width: tableConfig.columnWidths?.urlPattern || 250,
952
+ onHeaderCell: (column) => ({
953
+ width: column.width,
954
+ columnKey: 'urlPattern',
955
+ onResize: handleResize,
956
+ }),
859
957
  ellipsis: true,
860
958
  render: (text) => <span className="url-pattern">{text}</span>,
861
959
  },
@@ -863,7 +961,12 @@ const InterfaceManagement = () => {
863
961
  title: '处理方式',
864
962
  dataIndex: 'proxyType',
865
963
  key: 'proxyType',
866
- width: 120,
964
+ width: tableConfig.columnWidths?.proxyType || 120,
965
+ onHeaderCell: (column) => ({
966
+ width: column.width,
967
+ columnKey: 'proxyType',
968
+ onResize: handleResize,
969
+ }),
867
970
  render: (text) => {
868
971
  const proxyType = proxyTypes.find(item => item.value === text);
869
972
  return (
@@ -921,7 +1024,12 @@ const InterfaceManagement = () => {
921
1024
  title: '延迟(毫秒)',
922
1025
  dataIndex: 'responseDelay',
923
1026
  key: 'responseDelay',
924
- width: 100,
1027
+ width: tableConfig.columnWidths?.responseDelay || 100,
1028
+ onHeaderCell: (column) => ({
1029
+ width: column.width,
1030
+ columnKey: 'responseDelay',
1031
+ onResize: handleResize,
1032
+ }),
925
1033
  sorter: (a, b) => (a.responseDelay || 0) - (b.responseDelay || 0),
926
1034
  render: (delay) => {
927
1035
  const delayValue = parseInt(delay, 10) || 0;
@@ -936,7 +1044,12 @@ const InterfaceManagement = () => {
936
1044
  title: '状态码',
937
1045
  dataIndex: 'httpStatus',
938
1046
  key: 'httpStatus',
939
- width: 100,
1047
+ width: tableConfig.columnWidths?.httpStatus || 100,
1048
+ onHeaderCell: (column) => ({
1049
+ width: column.width,
1050
+ columnKey: 'httpStatus',
1051
+ onResize: handleResize,
1052
+ }),
940
1053
  render: (status) => {
941
1054
  const statusCode = status || 200;
942
1055
  let statusClass = 'status-success';
@@ -952,7 +1065,12 @@ const InterfaceManagement = () => {
952
1065
  title: '内容类型',
953
1066
  dataIndex: 'contentType',
954
1067
  key: 'contentType',
955
- width: 120,
1068
+ width: tableConfig.columnWidths?.contentType || 120,
1069
+ onHeaderCell: (column) => ({
1070
+ width: column.width,
1071
+ columnKey: 'contentType',
1072
+ onResize: handleResize,
1073
+ }),
956
1074
  sorter: (a, b) => {
957
1075
  const aType = a.contentType || '';
958
1076
  const bType = b.contentType || '';
@@ -971,6 +1089,12 @@ const InterfaceManagement = () => {
971
1089
  title: '目标URL',
972
1090
  dataIndex: 'targetUrl',
973
1091
  key: 'targetUrl',
1092
+ width: tableConfig.columnWidths?.targetUrl || 200,
1093
+ onHeaderCell: (column) => ({
1094
+ width: column.width,
1095
+ columnKey: 'targetUrl',
1096
+ onResize: handleResize,
1097
+ }),
974
1098
  ellipsis: true,
975
1099
  sorter: (a, b) => {
976
1100
  const aUrl = a.targetUrl || '';
@@ -986,7 +1110,12 @@ const InterfaceManagement = () => {
986
1110
  title: '自定义头',
987
1111
  dataIndex: 'customHeaders',
988
1112
  key: 'customHeaders',
989
- width: 100,
1113
+ width: tableConfig.columnWidths?.customHeaders || 100,
1114
+ onHeaderCell: (column) => ({
1115
+ width: column.width,
1116
+ columnKey: 'customHeaders',
1117
+ onResize: handleResize,
1118
+ }),
990
1119
  sorter: (a, b) => {
991
1120
  const aCount = Object.keys(a.customHeaders || {}).length;
992
1121
  const bCount = Object.keys(b.customHeaders || {}).length;
@@ -1440,49 +1569,70 @@ const InterfaceManagement = () => {
1440
1569
  </div>
1441
1570
  </Card>
1442
1571
 
1443
- {/* 接口编辑/创建模态框 */}
1444
- <Modal
1445
- title={
1446
- <div style={{
1447
- fontSize: '18px',
1448
- fontWeight: 600,
1449
- color: '#262626',
1450
- display: 'flex',
1451
- alignItems: 'center',
1452
- gap: '8px'
1453
- }}>
1454
- <span>{editingInterface ? '编辑接口' : (isDuplicateMode ? '复制新增接口' : '添加接口')}</span>
1455
- <Badge
1456
- count="配置向导"
1457
- style={{
1458
- backgroundColor: '#f0f9ff',
1459
- color: '#1677ff',
1460
- fontSize: '11px',
1461
- fontWeight: 'normal'
1462
- }}
1463
- />
1464
- </div>
1465
- }
1466
- open={modalVisible}
1467
- onOk={handleSubmit}
1468
- onCancel={handleCancel}
1469
- width={1200}
1470
- destroyOnClose
1471
- okText={editingInterface ? '保存' : '创建'}
1472
- cancelText="取消"
1473
- bodyStyle={{
1474
- maxHeight: '75vh',
1475
- overflow: 'auto',
1476
- padding: '0',
1477
- background: '#fafafa'
1478
- }}
1479
- className="interface-modal"
1480
- >
1481
- <div style={{ padding: '24px' }}>
1482
- <Form
1483
- form={form}
1484
- layout="vertical"
1485
- initialValues={{
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
+ >
1486
1636
  name: '',
1487
1637
  group: '',
1488
1638
  pattern: '',
@@ -2003,7 +2153,538 @@ const InterfaceManagement = () => {
2003
2153
  </Collapse>
2004
2154
  </Form>
2005
2155
  </div>
2006
- </Modal>
2156
+ </Drawer>
2157
+ ) : (
2158
+ <Modal
2159
+ title={
2160
+ <div style={{
2161
+ fontSize: '18px',
2162
+ fontWeight: 600,
2163
+ color: '#262626',
2164
+ display: 'flex',
2165
+ alignItems: 'center',
2166
+ gap: '8px'
2167
+ }}>
2168
+ <span>{editingInterface ? '编辑接口' : (isDuplicateMode ? '复制新增接口' : '添加接口')}</span>
2169
+ <Badge
2170
+ count="配置向导"
2171
+ style={{
2172
+ backgroundColor: '#f0f9ff',
2173
+ color: '#1677ff',
2174
+ fontSize: '11px',
2175
+ fontWeight: 'normal'
2176
+ }}
2177
+ />
2178
+ </div>
2179
+ }
2180
+ open={modalVisible}
2181
+ onCancel={handleCancel}
2182
+ onOk={handleSubmit}
2183
+ width={1040}
2184
+ destroyOnClose
2185
+ okText={editingInterface ? '保存' : '创建'}
2186
+ cancelText="取消"
2187
+ bodyStyle={{
2188
+ maxHeight: '70vh',
2189
+ overflow: 'auto',
2190
+ background: '#fafafa'
2191
+ }}
2192
+ >
2193
+ <div style={{ padding: '0' }}>
2194
+ <Form
2195
+ form={form}
2196
+ layout="vertical"
2197
+ initialValues={{
2198
+ name: '',
2199
+ group: '',
2200
+ pattern: '',
2201
+ proxyType: 'response',
2202
+ statusCode: '200',
2203
+ contentType: 'application/json; charset=utf-8',
2204
+ responses: [],
2205
+ httpMethod: 'ALL',
2206
+ targetUrl: '',
2207
+ headerItems: [],
2208
+ paramMatchers: [],
2209
+ responseDelay: '0'
2210
+ }}
2211
+ >
2212
+ {/* Modal 模式下使用相同的表单内容,但去掉 Collapse 包装 */}
2213
+ <div style={{ padding: '24px' }}>
2214
+ {/* 基础信息 */}
2215
+ <div style={{ marginBottom: '24px' }}>
2216
+ <div style={{ fontSize: '16px', fontWeight: 600, marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '8px' }}>
2217
+ <Badge count="1" style={{ backgroundColor: '#1677ff' }} />
2218
+ <span>基础信息</span>
2219
+ </div>
2220
+ <div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
2221
+ <div style={{ flex: 1 }}>
2222
+ <Form.Item
2223
+ name="name"
2224
+ label="接口名称"
2225
+ rules={[{ required: true, message: '请输入接口名称' }]}
2226
+ >
2227
+ <Input placeholder="请输入接口名称" />
2228
+ </Form.Item>
2229
+ </div>
2230
+ <div style={{ flex: 1 }}>
2231
+ <Form.Item
2232
+ name="group"
2233
+ label="分组"
2234
+ tooltip="为接口设置分组,便于管理和批量操作"
2235
+ >
2236
+ <Select
2237
+ placeholder="选择或创建分组"
2238
+ showSearch
2239
+ allowClear
2240
+ style={{ width: '100%' }}
2241
+ mode="tags"
2242
+ maxTagCount="responsive"
2243
+ maxTagTextLength={10}
2244
+ onChange={(value) => {
2245
+ if (Array.isArray(value) && value.length > 0) {
2246
+ const groupValue = value[0] === '' ? undefined : value[0];
2247
+ form.setFieldsValue({ group: groupValue });
2248
+ }
2249
+ }}
2250
+ onInputKeyDown={(e) => {
2251
+ if (e.key === 'Enter') {
2252
+ e.stopPropagation();
2253
+ }
2254
+ }}
2255
+ >
2256
+ {Array.from(new Set(interfaces.map(item => item.group).filter(Boolean))).map(group => (
2257
+ <Option key={group} value={group}>{group}</Option>
2258
+ ))}
2259
+ </Select>
2260
+ </Form.Item>
2261
+ </div>
2262
+ </div>
2263
+ </div>
2264
+
2265
+ {/* URL配置 */}
2266
+ <div style={{ marginBottom: '24px' }}>
2267
+ <div style={{ fontSize: '16px', fontWeight: 600, marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '8px' }}>
2268
+ <Badge count="2" style={{ backgroundColor: '#1677ff' }} />
2269
+ <span>URL配置</span>
2270
+ </div>
2271
+ <div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
2272
+ <div style={{ flex: 1 }}>
2273
+ <Form.Item
2274
+ name="pattern"
2275
+ label="URL匹配规则"
2276
+ rules={[
2277
+ { required: true, message: '请输入URL匹配规则' },
2278
+ ({ getFieldValue }) => ({
2279
+ validator(_, value) {
2280
+ const proxyType = getFieldValue('proxyType');
2281
+ if (!value) return Promise.resolve();
2282
+
2283
+ if (proxyType === 'url_redirect') {
2284
+ try {
2285
+ new URL(value);
2286
+ return Promise.resolve();
2287
+ } catch (e) {
2288
+ return Promise.reject(new Error('URL重定向模式下,URL匹配规则必须是完整的URL(包括http://或https://)'));
2289
+ }
2290
+ }
2291
+
2292
+ if (proxyType === 'redirect') {
2293
+ if (value.startsWith('http://') || value.startsWith('https://')) {
2294
+ return Promise.resolve();
2295
+ }
2296
+ return Promise.reject(new Error('重定向模式下,URL匹配规则必须以http://或https://开头'));
2297
+ }
2298
+
2299
+ if (proxyType === 'response') {
2300
+ if (value.startsWith('/') && value.endsWith('/')) {
2301
+ try {
2302
+ new RegExp(value.slice(1, -1));
2303
+ return Promise.resolve();
2304
+ } catch (e) {
2305
+ return Promise.reject(new Error('无效的正则表达式格式'));
2306
+ }
2307
+ }
2308
+
2309
+ if (value.includes('*')) {
2310
+ if (/^[a-zA-Z0-9\-_/.*]+$/.test(value)) {
2311
+ return Promise.resolve();
2312
+ }
2313
+ return Promise.reject(new Error('通配符URL格式不正确'));
2314
+ }
2315
+
2316
+ if (/^[a-zA-Z0-9\-_/]+$/.test(value)) {
2317
+ return Promise.resolve();
2318
+ }
2319
+ return Promise.reject(new Error('URL路径格式不正确'));
2320
+ }
2321
+
2322
+ return Promise.resolve();
2323
+ },
2324
+ }),
2325
+ ]}
2326
+ tooltip={{
2327
+ title: (
2328
+ <>
2329
+ <div>不同处理方式下URL匹配规则要求:</div>
2330
+ <ul style={{margin: '5px 0 0 15px', padding: 0}}>
2331
+ <li><b>模拟响应:</b> 支持路径格式如 /api/users,通配符如 /api/*,正则如 /\/api\/\d+/</li>
2332
+ <li><b>重定向:</b> 必须以http://或https://开头,例如:https://example.com/api</li>
2333
+ <li><b>URL重定向:</b> 必须是完整URL,包括http://或https://,例如:https://example.com/api/users</li>
2334
+ </ul>
2335
+ </>
2336
+ ),
2337
+ overlayStyle: { maxWidth: '450px' }
2338
+ }}
2339
+ >
2340
+ <Input
2341
+ placeholder="根据选择的处理方式输入相应格式的URL"
2342
+ />
2343
+ </Form.Item>
2344
+ </div>
2345
+ <div style={{ flex: 1 }}>
2346
+ <Form.Item
2347
+ name="proxyType"
2348
+ label="处理方式"
2349
+ rules={[{ required: true, message: '请选择处理方式' }]}
2350
+ tooltip={{
2351
+ title: (
2352
+ <>
2353
+ <div>不同处理方式的规则说明:</div>
2354
+ <ul style={{margin: '5px 0 0 15px', padding: 0}}>
2355
+ <li><b>模拟响应:</b> 返回您定义的响应内容</li>
2356
+ <li><b>重定向:</b> 将请求重定向到其他URL,URL匹配规则必须以http://或https://开头</li>
2357
+ <li><b>URL重定向:</b> 完全匹配URL时重定向,URL匹配规则必须是完整URL</li>
2358
+ </ul>
2359
+ </>
2360
+ ),
2361
+ overlayStyle: { maxWidth: '450px' }
2362
+ }}
2363
+ >
2364
+ <Select onChange={(value) => {
2365
+ form.setFieldsValue({ pattern: '' });
2366
+ const patternInput = document.querySelector('input[placeholder="根据选择的处理方式输入相应格式的URL"]');
2367
+ if (patternInput) {
2368
+ if (value === 'response') {
2369
+ patternInput.placeholder = "例如:/api/users,/api/users/*,/api\\/users\\/\\d+/";
2370
+ } else if (value === 'redirect') {
2371
+ patternInput.placeholder = "例如:https://example.com/api";
2372
+ } else if (value === 'url_redirect') {
2373
+ patternInput.placeholder = "例如:https://example.com/api/users";
2374
+ }
2375
+ }
2376
+ }}>
2377
+ {proxyTypes.map(item => (
2378
+ <Option key={item.value} value={item.value}>{item.label}</Option>
2379
+ ))}
2380
+ </Select>
2381
+ </Form.Item>
2382
+ </div>
2383
+ </div>
2384
+
2385
+ <div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
2386
+ <div style={{ flex: 1 }}>
2387
+ <Form.Item
2388
+ name="httpMethod"
2389
+ label="请求方法"
2390
+ rules={[{ required: true, message: '请选择请求方法' }]}
2391
+ >
2392
+ <Select>
2393
+ {httpMethods.map(item => (
2394
+ <Option key={item.value} value={item.value}>{item.label}</Option>
2395
+ ))}
2396
+ </Select>
2397
+ </Form.Item>
2398
+ </div>
2399
+ <div style={{ flex: 1 }}>
2400
+ {/* 这里可以留空或添加其他字段 */}
2401
+ </div>
2402
+ </div>
2403
+ </div>
2404
+
2405
+ {/* 高级配置 */}
2406
+ <div style={{ marginBottom: '24px' }}>
2407
+ <div style={{ fontSize: '16px', fontWeight: 600, marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '8px' }}>
2408
+ <Badge count="3" style={{ backgroundColor: '#1677ff' }} />
2409
+ <span>高级配置</span>
2410
+ </div>
2411
+
2412
+ {/* 根据proxyType显示不同的表单项 */}
2413
+ <Form.Item noStyle shouldUpdate={(prevValues, currentValues) => prevValues.proxyType !== currentValues.proxyType}>
2414
+ {({ getFieldValue }) => {
2415
+ const proxyType = getFieldValue('proxyType');
2416
+
2417
+ if (proxyType === 'response') {
2418
+ return (
2419
+ <>
2420
+ <div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
2421
+ <div style={{ flex: 1 }}>
2422
+ <Form.Item
2423
+ name="statusCode"
2424
+ label="状态码"
2425
+ rules={[{ required: true, message: '请选择状态码' }]}
2426
+ >
2427
+ <Select>
2428
+ {statusCodes.map(item => (
2429
+ <Option key={item.value} value={item.value}>{item.label}</Option>
2430
+ ))}
2431
+ </Select>
2432
+ </Form.Item>
2433
+ </div>
2434
+ <div style={{ flex: 1 }}>
2435
+ <Form.Item
2436
+ name="contentType"
2437
+ label="内容类型"
2438
+ rules={[{ required: true, message: '请选择内容类型' }]}
2439
+ >
2440
+ <Select>
2441
+ {contentTypes.map(item => (
2442
+ <Option key={item.value} value={item.value}>{item.label}</Option>
2443
+ ))}
2444
+ </Select>
2445
+ </Form.Item>
2446
+ </div>
2447
+ <div style={{ flex: 1 }}>
2448
+ <Form.Item
2449
+ name="responseDelay"
2450
+ label="延迟响应(毫秒)"
2451
+ tooltip="设置接口响应延迟时间,单位为毫秒"
2452
+ rules={[
2453
+ {
2454
+ pattern: /^\d*$/,
2455
+ message: '请输入有效的数字'
2456
+ },
2457
+ {
2458
+ validator: (_, value) => {
2459
+ if (!value || parseInt(value, 10) <= 60000) {
2460
+ return Promise.resolve();
2461
+ }
2462
+ return Promise.reject(new Error('延迟时间不能超过60秒(60000毫秒)'));
2463
+ }
2464
+ }
2465
+ ]}
2466
+ >
2467
+ <Input placeholder="例如:500" />
2468
+ </Form.Item>
2469
+ </div>
2470
+ </div>
2471
+
2472
+ {/* 请求入参匹配设置 */}
2473
+ <Form.Item
2474
+ label="请求入参匹配"
2475
+ tooltip="设置请求参数的匹配条件,只有当请求参数满足条件时才返回对应的响应。支持嵌套属性路径,如:a.b.c"
2476
+ >
2477
+ <Form.List name="paramMatchers">
2478
+ {(fields, { add, remove }) => (
2479
+ <>
2480
+ {fields.map(({ key, name, ...restField }) => (
2481
+ <div key={key} style={{ display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '8px' }}>
2482
+ <Form.Item
2483
+ {...restField}
2484
+ name={[name, 'paramPath']}
2485
+ rules={[
2486
+ { required: true, message: '请输入参数路径' },
2487
+ {
2488
+ pattern: /^[a-zA-Z_$][a-zA-Z0-9_$.]*$/,
2489
+ message: '请输入有效的参数路径,如:userId 或 data.user.id'
2490
+ }
2491
+ ]}
2492
+ style={{ flex: 1, margin: 0 }}
2493
+ >
2494
+ <Input placeholder="参数路径(如:userId 或 data.user.id)" />
2495
+ </Form.Item>
2496
+ <Form.Item
2497
+ {...restField}
2498
+ name={[name, 'paramValue']}
2499
+ rules={[{ required: true, message: '请输入期望值' }]}
2500
+ style={{ flex: 1, margin: 0 }}
2501
+ >
2502
+ <Input placeholder="期望值(如:123 或 admin)" />
2503
+ </Form.Item>
2504
+ <Form.Item
2505
+ {...restField}
2506
+ name={[name, 'matchType']}
2507
+ style={{ width: 100, margin: 0 }}
2508
+ initialValue="exact"
2509
+ >
2510
+ <Select placeholder="匹配类型">
2511
+ <Option value="exact">精确匹配</Option>
2512
+ <Option value="contains">包含</Option>
2513
+ <Option value="regex">正则</Option>
2514
+ </Select>
2515
+ </Form.Item>
2516
+ <Button
2517
+ type="text"
2518
+ danger
2519
+ icon={<DeleteOutlined />}
2520
+ onClick={() => remove(name)}
2521
+ />
2522
+ </div>
2523
+ ))}
2524
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginTop: '8px' }}>
2525
+ <Button
2526
+ type="dashed"
2527
+ onClick={() => add()}
2528
+ icon={<PlusOutlined />}
2529
+ style={{ flex: 1 }}
2530
+ >
2531
+ 添加参数匹配规则
2532
+ </Button>
2533
+ <Popover
2534
+ content={
2535
+ <div style={{ maxWidth: '300px' }}>
2536
+ <div style={{ marginBottom: '8px', fontWeight: 'bold' }}>匹配规则说明:</div>
2537
+ <div style={{ lineHeight: '1.6' }}>
2538
+ <div>• <strong>精确匹配</strong>:参数值完全相等</div>
2539
+ <div>• <strong>包含</strong>:参数值包含指定内容</div>
2540
+ <div>• <strong>正则</strong>:参数值符合正则表达式</div>
2541
+ <div>• <strong>嵌套路径</strong>:使用点号分隔,如 data.user.id</div>
2542
+ </div>
2543
+ </div>
2544
+ }
2545
+ title="参数匹配规则"
2546
+ trigger="click"
2547
+ placement="top"
2548
+ >
2549
+ <Button
2550
+ type="text"
2551
+ icon={<QuestionCircleOutlined />}
2552
+ style={{
2553
+ color: '#999',
2554
+ padding: '4px'
2555
+ }}
2556
+ size="small"
2557
+ />
2558
+ </Popover>
2559
+ </div>
2560
+ </>
2561
+ )}
2562
+ </Form.List>
2563
+ </Form.Item>
2564
+
2565
+ {/* 隐藏的表单字段用于存储响应数据 */}
2566
+ <Form.Item
2567
+ name="responses"
2568
+ initialValue={[]}
2569
+ hidden
2570
+ >
2571
+ <Input />
2572
+ </Form.Item>
2573
+ <Form.Item name="activeResponseId" hidden>
2574
+ <Input />
2575
+ </Form.Item>
2576
+
2577
+ {/* 使用新的合并组件 */}
2578
+ <Form.Item noStyle shouldUpdate>
2579
+ {({ getFieldValue }) => {
2580
+ let responses = getFieldValue('responses') || [];
2581
+ const activeResponseId = getFieldValue('activeResponseId');
2582
+
2583
+ if (!Array.isArray(responses)) {
2584
+ try {
2585
+ if (typeof responses === 'string') {
2586
+ responses = JSON.parse(responses);
2587
+ }
2588
+ } catch (e) {
2589
+ responses = [];
2590
+ }
2591
+ }
2592
+
2593
+ return (
2594
+ <ResponseContentEditor
2595
+ form={form}
2596
+ responses={responses}
2597
+ activeResponseId={activeResponseId}
2598
+ onPreview={handlePreview}
2599
+ />
2600
+ );
2601
+ }}
2602
+ </Form.Item>
2603
+ </>
2604
+ );
2605
+ } else if (proxyType === 'redirect' || proxyType === 'url_redirect') {
2606
+ return (
2607
+ <>
2608
+ <Form.Item
2609
+ name="targetUrl"
2610
+ label="重定向目标URL"
2611
+ rules={[
2612
+ { required: true, message: '请输入重定向目标URL' },
2613
+ {
2614
+ validator(_, value) {
2615
+ if (!value) return Promise.resolve();
2616
+ try {
2617
+ new URL(value);
2618
+ return Promise.resolve();
2619
+ } catch (e) {
2620
+ return Promise.reject(new Error('请输入有效的URL格式(包括http://或https://)'));
2621
+ }
2622
+ }
2623
+ }
2624
+ ]}
2625
+ tooltip="重定向的目标URL,必须是完整的URL格式"
2626
+ >
2627
+ <Input
2628
+ placeholder="例如:https://api.example.com/users"
2629
+ />
2630
+ </Form.Item>
2631
+
2632
+ {/* 自定义请求头设置 */}
2633
+ <Form.Item label="自定义请求头">
2634
+ <Form.List name="headerItems">
2635
+ {(fields, { add, remove }) => (
2636
+ <>
2637
+ {fields.map(({ key, name, ...restField }) => (
2638
+ <div key={key} style={{ display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '8px' }}>
2639
+ <Form.Item
2640
+ {...restField}
2641
+ name={[name, 'headerName']}
2642
+ rules={[{ required: true, message: '请输入请求头名称' }]}
2643
+ style={{ flex: 1, margin: 0 }}
2644
+ >
2645
+ <Input placeholder="请求头名称(如:Authorization)" />
2646
+ </Form.Item>
2647
+ <Form.Item
2648
+ {...restField}
2649
+ name={[name, 'headerValue']}
2650
+ rules={[{ required: true, message: '请输入请求头值' }]}
2651
+ style={{ flex: 1, margin: 0 }}
2652
+ >
2653
+ <Input placeholder="请求头值(如:Bearer token123)" />
2654
+ </Form.Item>
2655
+ <Button
2656
+ type="text"
2657
+ danger
2658
+ icon={<DeleteOutlined />}
2659
+ onClick={() => remove(name)}
2660
+ />
2661
+ </div>
2662
+ ))}
2663
+ <Button
2664
+ type="dashed"
2665
+ onClick={() => add()}
2666
+ icon={<PlusOutlined />}
2667
+ style={{ width: '100%', marginTop: '8px' }}
2668
+ >
2669
+ 添加请求头
2670
+ </Button>
2671
+ </>
2672
+ )}
2673
+ </Form.List>
2674
+ </Form.Item>
2675
+ </>
2676
+ );
2677
+ }
2678
+
2679
+ return null;
2680
+ }}
2681
+ </Form.Item>
2682
+ </div>
2683
+ </div>
2684
+ </Form>
2685
+ </div>
2686
+ </Modal>
2687
+ )}
2007
2688
 
2008
2689
  {/* 使用拆分后的预览模态框组件 */}
2009
2690
  <PreviewModal
@@ -2012,15 +2693,26 @@ const InterfaceManagement = () => {
2012
2693
  previewContent={previewContent}
2013
2694
  />
2014
2695
 
2015
- {/* 数据管理弹窗 */}
2016
- <DataManagementModal
2017
- visible={dataModalVisible}
2018
- onCancel={() => { setDataModalVisible(false); setDataModalRecord(null); }}
2019
- interfaceItem={dataModalRecord}
2020
- onSaved={() => {
2021
- fetchInterfaces();
2022
- }}
2023
- />
2696
+ {/* 数据管理弹窗/抽屉 */}
2697
+ {displayMode === 'drawer' ? (
2698
+ <DataManagementDrawer
2699
+ visible={dataModalVisible}
2700
+ onCancel={() => { setDataModalVisible(false); setDataModalRecord(null); }}
2701
+ interfaceItem={dataModalRecord}
2702
+ onSaved={() => {
2703
+ fetchInterfaces();
2704
+ }}
2705
+ />
2706
+ ) : (
2707
+ <DataManagementModal
2708
+ visible={dataModalVisible}
2709
+ onCancel={() => { setDataModalVisible(false); setDataModalRecord(null); }}
2710
+ interfaceItem={dataModalRecord}
2711
+ onSaved={() => {
2712
+ fetchInterfaces();
2713
+ }}
2714
+ />
2715
+ )}
2024
2716
  </div>
2025
2717
  </AppLayout>
2026
2718
  );