whistle.mock-plugins 1.2.0-beta.9 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/VERSION.md +9 -6
- package/app/public/js/index.js +1 -1
- package/app/public/js/index.js.LICENSE.txt +0 -2
- package/package.json +1 -1
- package/ui/src/pages/InterfaceManagement.js +131 -76
- package/ui/src/pages/InterfaceManagementV2.js +133 -78
- package/ui/src/pages/MockDataV2.js +6 -134
- package/ui/src/styles/mock-data.css +0 -139
|
@@ -156,5 +156,3 @@
|
|
|
156
156
|
/** */
|
|
157
157
|
|
|
158
158
|
/** */
|
|
159
|
-
|
|
160
|
-
/** */
|
package/package.json
CHANGED
|
@@ -4,12 +4,12 @@ import AppLayout from '../components/AppLayout';
|
|
|
4
4
|
import {
|
|
5
5
|
Table, Button, Modal, Form, Input, Select, message, Switch,
|
|
6
6
|
Popconfirm, Alert, Space, Card, Badge, Tooltip, Row, Col,
|
|
7
|
-
Popover, Checkbox, Tag, Input as AntInput, Radio, Drawer
|
|
7
|
+
Popover, Checkbox, Tag, Input as AntInput, Radio, Drawer
|
|
8
8
|
} from 'antd';
|
|
9
9
|
import {
|
|
10
10
|
PlusOutlined, EditOutlined, DeleteOutlined,
|
|
11
11
|
FileTextOutlined, PlusCircleOutlined, SettingOutlined,
|
|
12
|
-
SearchOutlined, FilterOutlined, ReloadOutlined
|
|
12
|
+
SearchOutlined, FilterOutlined, ReloadOutlined, CopyOutlined
|
|
13
13
|
} from '@ant-design/icons';
|
|
14
14
|
import '../styles/interface-management.css';
|
|
15
15
|
import axios from 'axios';
|
|
@@ -64,11 +64,11 @@ const InterfaceManagement = () => {
|
|
|
64
64
|
const [groups, setGroups] = useState([]);
|
|
65
65
|
const [selectedGroup, setSelectedGroup] = useState(null);
|
|
66
66
|
const [groupActionLoading, setGroupActionLoading] = useState(false);
|
|
67
|
-
const [newGroupName, setNewGroupName] = useState('');
|
|
68
67
|
|
|
69
68
|
// 模态框状态
|
|
70
69
|
const [modalVisible, setModalVisible] = useState(false);
|
|
71
70
|
const [previewVisible, setPreviewVisible] = useState(false);
|
|
71
|
+
const [isDuplicateMode, setIsDuplicateMode] = useState(false);
|
|
72
72
|
|
|
73
73
|
// 列配置状态
|
|
74
74
|
const [columnConfigVisible, setColumnConfigVisible] = useState(false);
|
|
@@ -217,17 +217,6 @@ const InterfaceManagement = () => {
|
|
|
217
217
|
setSelectedGroup(null);
|
|
218
218
|
}
|
|
219
219
|
};
|
|
220
|
-
|
|
221
|
-
// 添加新分组
|
|
222
|
-
const addNewGroup = () => {
|
|
223
|
-
if (newGroupName.trim() && !groups.includes(newGroupName.trim())) {
|
|
224
|
-
const updatedGroups = [...groups, newGroupName.trim()].sort();
|
|
225
|
-
setGroups(updatedGroups);
|
|
226
|
-
setNewGroupName('');
|
|
227
|
-
return newGroupName.trim();
|
|
228
|
-
}
|
|
229
|
-
return null;
|
|
230
|
-
};
|
|
231
220
|
|
|
232
221
|
// 根据分组和搜索关键词筛选接口
|
|
233
222
|
const getFilteredInterfaces = () => {
|
|
@@ -259,6 +248,7 @@ const InterfaceManagement = () => {
|
|
|
259
248
|
return;
|
|
260
249
|
}
|
|
261
250
|
form.resetFields();
|
|
251
|
+
setIsDuplicateMode(false);
|
|
262
252
|
|
|
263
253
|
// 创建默认响应
|
|
264
254
|
const defaultResponseId = generateResponseId();
|
|
@@ -298,6 +288,7 @@ const InterfaceManagement = () => {
|
|
|
298
288
|
|
|
299
289
|
// 重置表单
|
|
300
290
|
form.resetFields();
|
|
291
|
+
setIsDuplicateMode(false);
|
|
301
292
|
|
|
302
293
|
// 处理响应数据
|
|
303
294
|
let responses = [];
|
|
@@ -357,6 +348,91 @@ const InterfaceManagement = () => {
|
|
|
357
348
|
setModalVisible(true);
|
|
358
349
|
};
|
|
359
350
|
|
|
351
|
+
// 复制新增接口
|
|
352
|
+
const handleDuplicate = (record) => {
|
|
353
|
+
if (!selectedFeatureId) {
|
|
354
|
+
message.warning('请先选择一个功能模块');
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (!record) {
|
|
358
|
+
message.warning('接口数据不完整,无法复制');
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
form.resetFields();
|
|
363
|
+
|
|
364
|
+
// 深拷贝源数据
|
|
365
|
+
const source = JSON.parse(JSON.stringify(record));
|
|
366
|
+
|
|
367
|
+
// 处理响应数据
|
|
368
|
+
let responses = [];
|
|
369
|
+
let activeResponseId = '';
|
|
370
|
+
|
|
371
|
+
if (Array.isArray(source.responses) && source.responses.length > 0) {
|
|
372
|
+
// 为每条响应生成新的ID,并重映射activeResponseId
|
|
373
|
+
const idMap = new Map();
|
|
374
|
+
responses = source.responses.map((resp) => {
|
|
375
|
+
const newId = generateResponseId();
|
|
376
|
+
idMap.set(resp.id, newId);
|
|
377
|
+
return {
|
|
378
|
+
id: newId,
|
|
379
|
+
name: resp.name || '未命名响应',
|
|
380
|
+
description: resp.description || '',
|
|
381
|
+
content: resp.content || '{}'
|
|
382
|
+
};
|
|
383
|
+
});
|
|
384
|
+
const mapped = idMap.get(source.activeResponseId) || responses[0]?.id;
|
|
385
|
+
activeResponseId = mapped || '';
|
|
386
|
+
} else if (source.responseContent) {
|
|
387
|
+
// 兼容旧数据格式
|
|
388
|
+
const defaultResponseId = generateResponseId();
|
|
389
|
+
responses = [{
|
|
390
|
+
id: defaultResponseId,
|
|
391
|
+
name: '默认响应',
|
|
392
|
+
description: '',
|
|
393
|
+
content: source.responseContent
|
|
394
|
+
}];
|
|
395
|
+
activeResponseId = defaultResponseId;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// 处理自定义请求头 -> 表单需要的数组结构
|
|
399
|
+
let headerItems = [];
|
|
400
|
+
if (source.headers && typeof source.headers === 'object') {
|
|
401
|
+
headerItems = Object.entries(source.headers).map(([headerName, headerValue]) => ({
|
|
402
|
+
headerName,
|
|
403
|
+
headerValue
|
|
404
|
+
}));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// 分组值保证为字符串
|
|
408
|
+
let groupValue = source.group;
|
|
409
|
+
if (Array.isArray(groupValue)) {
|
|
410
|
+
groupValue = groupValue.length > 0 ? groupValue[0] : undefined;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// 设定表单初值
|
|
414
|
+
form.setFieldsValue({
|
|
415
|
+
name: `${source.name || '未命名接口'} - 副本`,
|
|
416
|
+
group: groupValue || undefined,
|
|
417
|
+
pattern: source.urlPattern,
|
|
418
|
+
proxyType: source.proxyType || 'response',
|
|
419
|
+
statusCode: (source.httpStatus || source.statusCode || 200).toString(),
|
|
420
|
+
contentType: source.contentType || 'application/json; charset=utf-8',
|
|
421
|
+
responses,
|
|
422
|
+
activeResponseId,
|
|
423
|
+
httpMethod: source.httpMethod || source.method || 'ALL',
|
|
424
|
+
targetUrl: source.targetUrl || '',
|
|
425
|
+
headerItems,
|
|
426
|
+
paramMatchers: Array.isArray(source.paramMatchers) ? source.paramMatchers : [],
|
|
427
|
+
responseDelay: source.responseDelay ? String(source.responseDelay) : '0'
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
setCurrentResponseId(activeResponseId || null);
|
|
431
|
+
setEditingInterface(null); // 新增模式
|
|
432
|
+
setIsDuplicateMode(true);
|
|
433
|
+
setModalVisible(true);
|
|
434
|
+
};
|
|
435
|
+
|
|
360
436
|
const handleDeleteInterface = async (id) => {
|
|
361
437
|
try {
|
|
362
438
|
const response = await axios.delete(`/cgi-bin/interfaces?id=${id}`);
|
|
@@ -397,8 +473,9 @@ const InterfaceManagement = () => {
|
|
|
397
473
|
|
|
398
474
|
const handleSubmit = async () => {
|
|
399
475
|
try {
|
|
400
|
-
// 表单验证
|
|
401
|
-
|
|
476
|
+
// 表单验证 - 先验证所有字段,再获取所有字段值(包括折叠的)
|
|
477
|
+
await form.validateFields();
|
|
478
|
+
const values = form.getFieldsValue(true); // true表示获取所有字段,包括未渲染的
|
|
402
479
|
console.log('表单提交数据:', values);
|
|
403
480
|
|
|
404
481
|
// 处理自定义请求头
|
|
@@ -417,19 +494,19 @@ const InterfaceManagement = () => {
|
|
|
417
494
|
groupValue = groupValue.length > 0 ? groupValue[0] : '';
|
|
418
495
|
}
|
|
419
496
|
|
|
420
|
-
// 构建接口数据
|
|
497
|
+
// 构建接口数据 - 在编辑模式下,确保关键字段不丢失
|
|
421
498
|
const interfaceData = {
|
|
422
|
-
name: values.name,
|
|
423
|
-
group: groupValue || '', // 确保group不为undefined
|
|
424
|
-
urlPattern: values.pattern,
|
|
425
|
-
proxyType: values.proxyType,
|
|
499
|
+
name: values.name || (editingInterface ? editingInterface.name : ''),
|
|
500
|
+
group: groupValue || (editingInterface ? editingInterface.group : '') || '', // 确保group不为undefined
|
|
501
|
+
urlPattern: values.pattern || (editingInterface ? editingInterface.urlPattern : ''),
|
|
502
|
+
proxyType: values.proxyType || (editingInterface ? editingInterface.proxyType : 'response'),
|
|
426
503
|
featureId: selectedFeatureId,
|
|
427
|
-
responses: values.responses,
|
|
428
|
-
activeResponseId: values.activeResponseId,
|
|
429
|
-
httpStatus: parseInt(values.statusCode, 10), // 转换为数字
|
|
430
|
-
contentType: values.contentType,
|
|
431
|
-
responseDelay: parseInt(values.responseDelay, 10) || 0,
|
|
432
|
-
httpMethod: values.httpMethod,
|
|
504
|
+
responses: values.responses || (editingInterface ? editingInterface.responses : []),
|
|
505
|
+
activeResponseId: values.activeResponseId || (editingInterface ? editingInterface.activeResponseId : null),
|
|
506
|
+
httpStatus: parseInt(values.statusCode, 10) || (editingInterface ? editingInterface.httpStatus : 200), // 转换为数字
|
|
507
|
+
contentType: values.contentType || (editingInterface ? editingInterface.contentType : 'application/json; charset=utf-8'),
|
|
508
|
+
responseDelay: parseInt(values.responseDelay, 10) || (editingInterface ? editingInterface.responseDelay : 0) || 0,
|
|
509
|
+
httpMethod: values.httpMethod || (editingInterface ? editingInterface.httpMethod : 'ALL'),
|
|
433
510
|
active: true
|
|
434
511
|
};
|
|
435
512
|
|
|
@@ -441,13 +518,11 @@ const InterfaceManagement = () => {
|
|
|
441
518
|
interfaceData.headers = headers;
|
|
442
519
|
}
|
|
443
520
|
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
);
|
|
450
|
-
}
|
|
521
|
+
// 添加参数匹配规则(始终发送,空数组用于清空后端旧值)
|
|
522
|
+
const filteredParamMatchers = Array.isArray(values.paramMatchers)
|
|
523
|
+
? values.paramMatchers.filter(matcher => matcher && matcher.paramPath && matcher.paramValue)
|
|
524
|
+
: [];
|
|
525
|
+
interfaceData.paramMatchers = filteredParamMatchers;
|
|
451
526
|
|
|
452
527
|
console.log('提交接口数据:', interfaceData);
|
|
453
528
|
|
|
@@ -488,6 +563,7 @@ const InterfaceManagement = () => {
|
|
|
488
563
|
|
|
489
564
|
const handleCancel = () => {
|
|
490
565
|
setModalVisible(false);
|
|
566
|
+
setIsDuplicateMode(false);
|
|
491
567
|
};
|
|
492
568
|
|
|
493
569
|
const handleSelectFeature = (featureId) => {
|
|
@@ -917,7 +993,7 @@ const InterfaceManagement = () => {
|
|
|
917
993
|
{
|
|
918
994
|
title: '操作',
|
|
919
995
|
key: 'action',
|
|
920
|
-
width:
|
|
996
|
+
width: 220,
|
|
921
997
|
render: (_, record) => (
|
|
922
998
|
<div className="action-buttons">
|
|
923
999
|
<Button
|
|
@@ -925,6 +1001,13 @@ const InterfaceManagement = () => {
|
|
|
925
1001
|
icon={<EditOutlined />}
|
|
926
1002
|
onClick={() => handleEditInterface(record)}
|
|
927
1003
|
/>
|
|
1004
|
+
<Tooltip title="复制新增">
|
|
1005
|
+
<Button
|
|
1006
|
+
type="text"
|
|
1007
|
+
icon={<CopyOutlined />}
|
|
1008
|
+
onClick={() => handleDuplicate(record)}
|
|
1009
|
+
/>
|
|
1010
|
+
</Tooltip>
|
|
928
1011
|
<Popconfirm
|
|
929
1012
|
title="确定要删除此接口吗?"
|
|
930
1013
|
onConfirm={() => handleDeleteInterface(record.id)}
|
|
@@ -1264,7 +1347,7 @@ const InterfaceManagement = () => {
|
|
|
1264
1347
|
|
|
1265
1348
|
{/* 接口编辑/创建模态框 */}
|
|
1266
1349
|
<Modal
|
|
1267
|
-
title={editingInterface ? '编辑接口' : '添加接口'}
|
|
1350
|
+
title={editingInterface ? '编辑接口' : (isDuplicateMode ? '复制新增接口' : '添加接口')}
|
|
1268
1351
|
open={modalVisible}
|
|
1269
1352
|
onOk={handleSubmit}
|
|
1270
1353
|
onCancel={handleCancel}
|
|
@@ -1310,55 +1393,27 @@ const InterfaceManagement = () => {
|
|
|
1310
1393
|
tooltip="为接口设置分组,便于管理和批量操作"
|
|
1311
1394
|
>
|
|
1312
1395
|
<Select
|
|
1313
|
-
placeholder="
|
|
1396
|
+
placeholder="选择或创建分组"
|
|
1314
1397
|
showSearch
|
|
1315
1398
|
allowClear
|
|
1316
1399
|
style={{ width: '100%' }}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1400
|
+
mode="tags"
|
|
1401
|
+
maxTagCount="responsive"
|
|
1402
|
+
maxTagTextLength={10}
|
|
1403
|
+
onChange={(value) => {
|
|
1404
|
+
// 如果值是数组(tags模式),只取第一个元素
|
|
1405
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
1406
|
+
// 如果第一个元素是空字符串,则设置为undefined
|
|
1407
|
+
const groupValue = value[0] === '' ? undefined : value[0];
|
|
1408
|
+
form.setFieldsValue({ group: groupValue });
|
|
1409
|
+
}
|
|
1410
|
+
}}
|
|
1321
1411
|
onInputKeyDown={(e) => {
|
|
1322
1412
|
// 防止回车键提交表单
|
|
1323
1413
|
if (e.key === 'Enter') {
|
|
1324
1414
|
e.stopPropagation();
|
|
1325
1415
|
}
|
|
1326
1416
|
}}
|
|
1327
|
-
dropdownRender={(menu) => (
|
|
1328
|
-
<div>
|
|
1329
|
-
{menu}
|
|
1330
|
-
<Divider style={{ margin: '4px 0' }} />
|
|
1331
|
-
<div style={{ display: 'flex', flexWrap: 'nowrap', padding: 8 }}>
|
|
1332
|
-
<Input
|
|
1333
|
-
style={{ flex: 'auto' }}
|
|
1334
|
-
value={newGroupName}
|
|
1335
|
-
onChange={(e) => setNewGroupName(e.target.value)}
|
|
1336
|
-
placeholder="输入新分组名称"
|
|
1337
|
-
onPressEnter={(e) => {
|
|
1338
|
-
e.stopPropagation();
|
|
1339
|
-
const groupName = addNewGroup();
|
|
1340
|
-
if (groupName) {
|
|
1341
|
-
form.setFieldsValue({ group: groupName });
|
|
1342
|
-
}
|
|
1343
|
-
}}
|
|
1344
|
-
/>
|
|
1345
|
-
<Button
|
|
1346
|
-
type="text"
|
|
1347
|
-
icon={<PlusOutlined />}
|
|
1348
|
-
style={{ flex: 'none', padding: '4px 8px', cursor: 'pointer' }}
|
|
1349
|
-
onClick={(e) => {
|
|
1350
|
-
e.stopPropagation();
|
|
1351
|
-
const groupName = addNewGroup();
|
|
1352
|
-
if (groupName) {
|
|
1353
|
-
form.setFieldsValue({ group: groupName });
|
|
1354
|
-
}
|
|
1355
|
-
}}
|
|
1356
|
-
>
|
|
1357
|
-
添加
|
|
1358
|
-
</Button>
|
|
1359
|
-
</div>
|
|
1360
|
-
</div>
|
|
1361
|
-
)}
|
|
1362
1417
|
>
|
|
1363
1418
|
{Array.from(new Set(interfaces.map(item => item.group).filter(Boolean))).map(group => (
|
|
1364
1419
|
<Option key={group} value={group}>{group}</Option>
|
|
@@ -10,12 +10,12 @@ import AppLayout from '../components/AppLayout';
|
|
|
10
10
|
import {
|
|
11
11
|
Table, Button, Modal, Form, Input, Select, message, Switch,
|
|
12
12
|
Popconfirm, Alert, Space, Card, Badge, Tooltip, Row, Col,
|
|
13
|
-
Popover, Checkbox, Tag, Input as AntInput, Radio, Drawer, Dropdown, Menu, Collapse
|
|
13
|
+
Popover, Checkbox, Tag, Input as AntInput, Radio, Drawer, Dropdown, Menu, Collapse
|
|
14
14
|
} from 'antd';
|
|
15
15
|
import {
|
|
16
16
|
PlusOutlined, EditOutlined, DeleteOutlined,
|
|
17
17
|
FileTextOutlined, PlusCircleOutlined, SettingOutlined,
|
|
18
|
-
SearchOutlined, FilterOutlined, ReloadOutlined, DownOutlined, QuestionCircleOutlined
|
|
18
|
+
SearchOutlined, FilterOutlined, ReloadOutlined, DownOutlined, QuestionCircleOutlined, CopyOutlined
|
|
19
19
|
} from '@ant-design/icons';
|
|
20
20
|
import '../styles/interface-management.css';
|
|
21
21
|
import axios from 'axios';
|
|
@@ -70,11 +70,11 @@ const InterfaceManagement = () => {
|
|
|
70
70
|
const [groups, setGroups] = useState([]);
|
|
71
71
|
const [selectedGroup, setSelectedGroup] = useState(null);
|
|
72
72
|
const [groupActionLoading, setGroupActionLoading] = useState(false);
|
|
73
|
-
const [newGroupName, setNewGroupName] = useState('');
|
|
74
73
|
|
|
75
74
|
// 模态框状态
|
|
76
75
|
const [modalVisible, setModalVisible] = useState(false);
|
|
77
76
|
const [previewVisible, setPreviewVisible] = useState(false);
|
|
77
|
+
const [isDuplicateMode, setIsDuplicateMode] = useState(false);
|
|
78
78
|
|
|
79
79
|
// 列配置状态
|
|
80
80
|
const [columnConfigVisible, setColumnConfigVisible] = useState(false);
|
|
@@ -223,17 +223,6 @@ const InterfaceManagement = () => {
|
|
|
223
223
|
setSelectedGroup(null);
|
|
224
224
|
}
|
|
225
225
|
};
|
|
226
|
-
|
|
227
|
-
// 添加新分组
|
|
228
|
-
const addNewGroup = () => {
|
|
229
|
-
if (newGroupName.trim() && !groups.includes(newGroupName.trim())) {
|
|
230
|
-
const updatedGroups = [...groups, newGroupName.trim()].sort();
|
|
231
|
-
setGroups(updatedGroups);
|
|
232
|
-
setNewGroupName('');
|
|
233
|
-
return newGroupName.trim();
|
|
234
|
-
}
|
|
235
|
-
return null;
|
|
236
|
-
};
|
|
237
226
|
|
|
238
227
|
// 根据分组和搜索关键词筛选接口
|
|
239
228
|
const getFilteredInterfaces = () => {
|
|
@@ -265,6 +254,7 @@ const InterfaceManagement = () => {
|
|
|
265
254
|
return;
|
|
266
255
|
}
|
|
267
256
|
form.resetFields();
|
|
257
|
+
setIsDuplicateMode(false);
|
|
268
258
|
|
|
269
259
|
// 创建默认响应
|
|
270
260
|
const defaultResponseId = generateResponseId();
|
|
@@ -304,6 +294,7 @@ const InterfaceManagement = () => {
|
|
|
304
294
|
|
|
305
295
|
// 重置表单
|
|
306
296
|
form.resetFields();
|
|
297
|
+
setIsDuplicateMode(false);
|
|
307
298
|
|
|
308
299
|
// 处理响应数据
|
|
309
300
|
let responses = [];
|
|
@@ -363,6 +354,91 @@ const InterfaceManagement = () => {
|
|
|
363
354
|
setModalVisible(true);
|
|
364
355
|
};
|
|
365
356
|
|
|
357
|
+
// 复制新增接口
|
|
358
|
+
const handleDuplicate = (record) => {
|
|
359
|
+
if (!selectedFeatureId) {
|
|
360
|
+
message.warning('请先选择一个功能模块');
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (!record) {
|
|
364
|
+
message.warning('接口数据不完整,无法复制');
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
form.resetFields();
|
|
369
|
+
|
|
370
|
+
// 深拷贝源数据
|
|
371
|
+
const source = JSON.parse(JSON.stringify(record));
|
|
372
|
+
|
|
373
|
+
// 处理响应数据
|
|
374
|
+
let responses = [];
|
|
375
|
+
let activeResponseId = '';
|
|
376
|
+
|
|
377
|
+
if (Array.isArray(source.responses) && source.responses.length > 0) {
|
|
378
|
+
// 为每条响应生成新的ID,并重映射activeResponseId
|
|
379
|
+
const idMap = new Map();
|
|
380
|
+
responses = source.responses.map((resp) => {
|
|
381
|
+
const newId = generateResponseId();
|
|
382
|
+
idMap.set(resp.id, newId);
|
|
383
|
+
return {
|
|
384
|
+
id: newId,
|
|
385
|
+
name: resp.name || '未命名响应',
|
|
386
|
+
description: resp.description || '',
|
|
387
|
+
content: resp.content || '{}'
|
|
388
|
+
};
|
|
389
|
+
});
|
|
390
|
+
const mapped = idMap.get(source.activeResponseId) || responses[0]?.id;
|
|
391
|
+
activeResponseId = mapped || '';
|
|
392
|
+
} else if (source.responseContent) {
|
|
393
|
+
// 兼容旧数据格式
|
|
394
|
+
const defaultResponseId = generateResponseId();
|
|
395
|
+
responses = [{
|
|
396
|
+
id: defaultResponseId,
|
|
397
|
+
name: '默认响应',
|
|
398
|
+
description: '',
|
|
399
|
+
content: source.responseContent
|
|
400
|
+
}];
|
|
401
|
+
activeResponseId = defaultResponseId;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// 处理自定义请求头 -> 表单需要的数组结构
|
|
405
|
+
let headerItems = [];
|
|
406
|
+
if (source.headers && typeof source.headers === 'object') {
|
|
407
|
+
headerItems = Object.entries(source.headers).map(([headerName, headerValue]) => ({
|
|
408
|
+
headerName,
|
|
409
|
+
headerValue
|
|
410
|
+
}));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// 分组值保证为字符串
|
|
414
|
+
let groupValue = source.group;
|
|
415
|
+
if (Array.isArray(groupValue)) {
|
|
416
|
+
groupValue = groupValue.length > 0 ? groupValue[0] : undefined;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// 设定表单初值
|
|
420
|
+
form.setFieldsValue({
|
|
421
|
+
name: `${source.name || '未命名接口'} - 副本`,
|
|
422
|
+
group: groupValue || undefined,
|
|
423
|
+
pattern: source.urlPattern,
|
|
424
|
+
proxyType: source.proxyType || 'response',
|
|
425
|
+
statusCode: (source.httpStatus || source.statusCode || 200).toString(),
|
|
426
|
+
contentType: source.contentType || 'application/json; charset=utf-8',
|
|
427
|
+
responses,
|
|
428
|
+
activeResponseId,
|
|
429
|
+
httpMethod: source.httpMethod || source.method || 'ALL',
|
|
430
|
+
targetUrl: source.targetUrl || '',
|
|
431
|
+
headerItems,
|
|
432
|
+
paramMatchers: Array.isArray(source.paramMatchers) ? source.paramMatchers : [],
|
|
433
|
+
responseDelay: source.responseDelay ? String(source.responseDelay) : '0'
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
setCurrentResponseId(activeResponseId || null);
|
|
437
|
+
setEditingInterface(null); // 新增模式
|
|
438
|
+
setIsDuplicateMode(true);
|
|
439
|
+
setModalVisible(true);
|
|
440
|
+
};
|
|
441
|
+
|
|
366
442
|
const handleDeleteInterface = async (id) => {
|
|
367
443
|
try {
|
|
368
444
|
const response = await axios.delete(`/cgi-bin/interfaces?id=${id}`);
|
|
@@ -403,8 +479,9 @@ const InterfaceManagement = () => {
|
|
|
403
479
|
|
|
404
480
|
const handleSubmit = async () => {
|
|
405
481
|
try {
|
|
406
|
-
// 表单验证
|
|
407
|
-
|
|
482
|
+
// 表单验证 - 先验证所有字段,再获取所有字段值(包括折叠的)
|
|
483
|
+
await form.validateFields();
|
|
484
|
+
const values = form.getFieldsValue(true); // true表示获取所有字段,包括未渲染的
|
|
408
485
|
console.log('表单提交数据:', values);
|
|
409
486
|
|
|
410
487
|
// 处理自定义请求头
|
|
@@ -423,19 +500,19 @@ const InterfaceManagement = () => {
|
|
|
423
500
|
groupValue = groupValue.length > 0 ? groupValue[0] : '';
|
|
424
501
|
}
|
|
425
502
|
|
|
426
|
-
// 构建接口数据
|
|
503
|
+
// 构建接口数据 - 在编辑模式下,确保关键字段不丢失
|
|
427
504
|
const interfaceData = {
|
|
428
|
-
name: values.name,
|
|
429
|
-
group: groupValue || '', // 确保group不为undefined
|
|
430
|
-
urlPattern: values.pattern,
|
|
431
|
-
proxyType: values.proxyType,
|
|
505
|
+
name: values.name || (editingInterface ? editingInterface.name : ''),
|
|
506
|
+
group: groupValue || (editingInterface ? editingInterface.group : '') || '', // 确保group不为undefined
|
|
507
|
+
urlPattern: values.pattern || (editingInterface ? editingInterface.urlPattern : ''),
|
|
508
|
+
proxyType: values.proxyType || (editingInterface ? editingInterface.proxyType : 'response'),
|
|
432
509
|
featureId: selectedFeatureId,
|
|
433
|
-
responses: values.responses,
|
|
434
|
-
activeResponseId: values.activeResponseId,
|
|
435
|
-
httpStatus: parseInt(values.statusCode, 10), // 转换为数字
|
|
436
|
-
contentType: values.contentType,
|
|
437
|
-
responseDelay: parseInt(values.responseDelay, 10) || 0,
|
|
438
|
-
httpMethod: values.httpMethod,
|
|
510
|
+
responses: values.responses || (editingInterface ? editingInterface.responses : []),
|
|
511
|
+
activeResponseId: values.activeResponseId || (editingInterface ? editingInterface.activeResponseId : null),
|
|
512
|
+
httpStatus: parseInt(values.statusCode, 10) || (editingInterface ? editingInterface.httpStatus : 200), // 转换为数字
|
|
513
|
+
contentType: values.contentType || (editingInterface ? editingInterface.contentType : 'application/json; charset=utf-8'),
|
|
514
|
+
responseDelay: parseInt(values.responseDelay, 10) || (editingInterface ? editingInterface.responseDelay : 0) || 0,
|
|
515
|
+
httpMethod: values.httpMethod || (editingInterface ? editingInterface.httpMethod : 'ALL'),
|
|
439
516
|
active: true
|
|
440
517
|
};
|
|
441
518
|
|
|
@@ -447,13 +524,11 @@ const InterfaceManagement = () => {
|
|
|
447
524
|
interfaceData.headers = headers;
|
|
448
525
|
}
|
|
449
526
|
|
|
450
|
-
//
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
);
|
|
456
|
-
}
|
|
527
|
+
// 添加参数匹配规则(始终发送,空数组用于清空后端旧值)
|
|
528
|
+
const filteredParamMatchers = Array.isArray(values.paramMatchers)
|
|
529
|
+
? values.paramMatchers.filter(matcher => matcher && matcher.paramPath && matcher.paramValue)
|
|
530
|
+
: [];
|
|
531
|
+
interfaceData.paramMatchers = filteredParamMatchers;
|
|
457
532
|
|
|
458
533
|
console.log('提交接口数据:', interfaceData);
|
|
459
534
|
|
|
@@ -494,6 +569,7 @@ const InterfaceManagement = () => {
|
|
|
494
569
|
|
|
495
570
|
const handleCancel = () => {
|
|
496
571
|
setModalVisible(false);
|
|
572
|
+
setIsDuplicateMode(false);
|
|
497
573
|
};
|
|
498
574
|
|
|
499
575
|
const handleSelectFeature = (featureId) => {
|
|
@@ -923,7 +999,7 @@ const InterfaceManagement = () => {
|
|
|
923
999
|
{
|
|
924
1000
|
title: '操作',
|
|
925
1001
|
key: 'action',
|
|
926
|
-
width:
|
|
1002
|
+
width: 220,
|
|
927
1003
|
render: (_, record) => (
|
|
928
1004
|
<div className="action-buttons">
|
|
929
1005
|
<Button
|
|
@@ -931,6 +1007,13 @@ const InterfaceManagement = () => {
|
|
|
931
1007
|
icon={<EditOutlined />}
|
|
932
1008
|
onClick={() => handleEditInterface(record)}
|
|
933
1009
|
/>
|
|
1010
|
+
<Tooltip title="复制新增">
|
|
1011
|
+
<Button
|
|
1012
|
+
type="text"
|
|
1013
|
+
icon={<CopyOutlined />}
|
|
1014
|
+
onClick={() => handleDuplicate(record)}
|
|
1015
|
+
/>
|
|
1016
|
+
</Tooltip>
|
|
934
1017
|
<Popconfirm
|
|
935
1018
|
title="确定要删除此接口吗?"
|
|
936
1019
|
onConfirm={() => handleDeleteInterface(record.id)}
|
|
@@ -1283,7 +1366,7 @@ const InterfaceManagement = () => {
|
|
|
1283
1366
|
alignItems: 'center',
|
|
1284
1367
|
gap: '8px'
|
|
1285
1368
|
}}>
|
|
1286
|
-
<span>{editingInterface ? '编辑接口' : '添加接口'}</span>
|
|
1369
|
+
<span>{editingInterface ? '编辑接口' : (isDuplicateMode ? '复制新增接口' : '添加接口')}</span>
|
|
1287
1370
|
<Badge
|
|
1288
1371
|
count="配置向导"
|
|
1289
1372
|
style={{
|
|
@@ -1332,8 +1415,8 @@ const InterfaceManagement = () => {
|
|
|
1332
1415
|
<Collapse
|
|
1333
1416
|
defaultActiveKey={
|
|
1334
1417
|
editingInterface && editingInterface.proxyType === 'response'
|
|
1335
|
-
? ['3']
|
|
1336
|
-
: ['1', '2', '3']
|
|
1418
|
+
? ['3'] // mock代理场景:只展开第3步(高级配置)
|
|
1419
|
+
: ['1', '2', '3'] // 其他场景:展开所有步骤
|
|
1337
1420
|
}
|
|
1338
1421
|
style={{ marginBottom: '16px' }}
|
|
1339
1422
|
ghost
|
|
@@ -1365,55 +1448,27 @@ const InterfaceManagement = () => {
|
|
|
1365
1448
|
tooltip="为接口设置分组,便于管理和批量操作"
|
|
1366
1449
|
>
|
|
1367
1450
|
<Select
|
|
1368
|
-
placeholder="
|
|
1451
|
+
placeholder="选择或创建分组"
|
|
1369
1452
|
showSearch
|
|
1370
1453
|
allowClear
|
|
1371
1454
|
style={{ width: '100%' }}
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1455
|
+
mode="tags"
|
|
1456
|
+
maxTagCount="responsive"
|
|
1457
|
+
maxTagTextLength={10}
|
|
1458
|
+
onChange={(value) => {
|
|
1459
|
+
// 如果值是数组(tags模式),只取第一个元素
|
|
1460
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
1461
|
+
// 如果第一个元素是空字符串,则设置为undefined
|
|
1462
|
+
const groupValue = value[0] === '' ? undefined : value[0];
|
|
1463
|
+
form.setFieldsValue({ group: groupValue });
|
|
1464
|
+
}
|
|
1465
|
+
}}
|
|
1376
1466
|
onInputKeyDown={(e) => {
|
|
1377
1467
|
// 防止回车键提交表单
|
|
1378
1468
|
if (e.key === 'Enter') {
|
|
1379
1469
|
e.stopPropagation();
|
|
1380
1470
|
}
|
|
1381
1471
|
}}
|
|
1382
|
-
dropdownRender={(menu) => (
|
|
1383
|
-
<div>
|
|
1384
|
-
{menu}
|
|
1385
|
-
<Divider style={{ margin: '4px 0' }} />
|
|
1386
|
-
<div style={{ display: 'flex', flexWrap: 'nowrap', padding: 8 }}>
|
|
1387
|
-
<Input
|
|
1388
|
-
style={{ flex: 'auto' }}
|
|
1389
|
-
value={newGroupName}
|
|
1390
|
-
onChange={(e) => setNewGroupName(e.target.value)}
|
|
1391
|
-
placeholder="输入新分组名称"
|
|
1392
|
-
onPressEnter={(e) => {
|
|
1393
|
-
e.stopPropagation();
|
|
1394
|
-
const groupName = addNewGroup();
|
|
1395
|
-
if (groupName) {
|
|
1396
|
-
form.setFieldsValue({ group: groupName });
|
|
1397
|
-
}
|
|
1398
|
-
}}
|
|
1399
|
-
/>
|
|
1400
|
-
<Button
|
|
1401
|
-
type="text"
|
|
1402
|
-
icon={<PlusOutlined />}
|
|
1403
|
-
style={{ flex: 'none', padding: '4px 8px', cursor: 'pointer' }}
|
|
1404
|
-
onClick={(e) => {
|
|
1405
|
-
e.stopPropagation();
|
|
1406
|
-
const groupName = addNewGroup();
|
|
1407
|
-
if (groupName) {
|
|
1408
|
-
form.setFieldsValue({ group: groupName });
|
|
1409
|
-
}
|
|
1410
|
-
}}
|
|
1411
|
-
>
|
|
1412
|
-
添加
|
|
1413
|
-
</Button>
|
|
1414
|
-
</div>
|
|
1415
|
-
</div>
|
|
1416
|
-
)}
|
|
1417
1472
|
>
|
|
1418
1473
|
{Array.from(new Set(interfaces.map(item => item.group).filter(Boolean))).map(group => (
|
|
1419
1474
|
<Option key={group} value={group}>{group}</Option>
|