whistle.mock-plugins 1.2.0-beta.8 → 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.
@@ -156,5 +156,3 @@
156
156
  /**![setting](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTkyNC44IDYyNS43bC02NS41LTU2YzMuMS0xOSA0LjctMzguNCA0LjctNTcuOHMtMS42LTM4LjgtNC43LTU3LjhsNjUuNS01NmEzMi4wMyAzMi4wMyAwIDAwOS4zLTM1LjJsLS45LTIuNmE0NDMuNzQgNDQzLjc0IDAgMDAtNzkuNy0xMzcuOWwtMS44LTIuMWEzMi4xMiAzMi4xMiAwIDAwLTM1LjEtOS41bC04MS4zIDI4LjljLTMwLTI0LjYtNjMuNS00NC05OS43LTU3LjZsLTE1LjctODVhMzIuMDUgMzIuMDUgMCAwMC0yNS44LTI1LjdsLTIuNy0uNWMtNTIuMS05LjQtMTA2LjktOS40LTE1OSAwbC0yLjcuNWEzMi4wNSAzMi4wNSAwIDAwLTI1LjggMjUuN2wtMTUuOCA4NS40YTM1MS44NiAzNTEuODYgMCAwMC05OSA1Ny40bC04MS45LTI5LjFhMzIgMzIgMCAwMC0zNS4xIDkuNWwtMS44IDIuMWE0NDYuMDIgNDQ2LjAyIDAgMDAtNzkuNyAxMzcuOWwtLjkgMi42Yy00LjUgMTIuNS0uOCAyNi41IDkuMyAzNS4ybDY2LjMgNTYuNmMtMy4xIDE4LjgtNC42IDM4LTQuNiA1Ny4xIDAgMTkuMiAxLjUgMzguNCA0LjYgNTcuMUw5OSA2MjUuNWEzMi4wMyAzMi4wMyAwIDAwLTkuMyAzNS4ybC45IDIuNmMxOC4xIDUwLjQgNDQuOSA5Ni45IDc5LjcgMTM3LjlsMS44IDIuMWEzMi4xMiAzMi4xMiAwIDAwMzUuMSA5LjVsODEuOS0yOS4xYzI5LjggMjQuNSA2My4xIDQzLjkgOTkgNTcuNGwxNS44IDg1LjRhMzIuMDUgMzIuMDUgMCAwMDI1LjggMjUuN2wyLjcuNWE0NDkuNCA0NDkuNCAwIDAwMTU5IDBsMi43LS41YTMyLjA1IDMyLjA1IDAgMDAyNS44LTI1LjdsMTUuNy04NWEzNTAgMzUwIDAgMDA5OS43LTU3LjZsODEuMyAyOC45YTMyIDMyIDAgMDAzNS4xLTkuNWwxLjgtMi4xYzM0LjgtNDEuMSA2MS42LTg3LjUgNzkuNy0xMzcuOWwuOS0yLjZjNC41LTEyLjMuOC0yNi4zLTkuMy0zNXpNNzg4LjMgNDY1LjljMi41IDE1LjEgMy44IDMwLjYgMy44IDQ2LjFzLTEuMyAzMS0zLjggNDYuMWwtNi42IDQwLjEgNzQuNyA2My45YTM3MC4wMyAzNzAuMDMgMCAwMS00Mi42IDczLjZMNzIxIDcwMi44bC0zMS40IDI1LjhjLTIzLjkgMTkuNi01MC41IDM1LTc5LjMgNDUuOGwtMzguMSAxNC4zLTE3LjkgOTdhMzc3LjUgMzc3LjUgMCAwMS04NSAwbC0xNy45LTk3LjItMzcuOC0xNC41Yy0yOC41LTEwLjgtNTUtMjYuMi03OC43LTQ1LjdsLTMxLjQtMjUuOS05My40IDMzLjJjLTE3LTIyLjktMzEuMi00Ny42LTQyLjYtNzMuNmw3NS41LTY0LjUtNi41LTQwYy0yLjQtMTQuOS0zLjctMzAuMy0zLjctNDUuNSAwLTE1LjMgMS4yLTMwLjYgMy43LTQ1LjVsNi41LTQwLTc1LjUtNjQuNWMxMS4zLTI2LjEgMjUuNi01MC43IDQyLjYtNzMuNmw5My40IDMzLjIgMzEuNC0yNS45YzIzLjctMTkuNSA1MC4yLTM0LjkgNzguNy00NS43bDM3LjktMTQuMyAxNy45LTk3LjJjMjguMS0zLjIgNTYuOC0zLjIgODUgMGwxNy45IDk3IDM4LjEgMTQuM2MyOC43IDEwLjggNTUuNCAyNi4yIDc5LjMgNDUuOGwzMS40IDI1LjggOTIuOC0zMi45YzE3IDIyLjkgMzEuMiA0Ny42IDQyLjYgNzMuNkw3ODEuOCA0MjZsNi41IDM5Ljl6TTUxMiAzMjZjLTk3LjIgMC0xNzYgNzguOC0xNzYgMTc2czc4LjggMTc2IDE3NiAxNzYgMTc2LTc4LjggMTc2LTE3Ni03OC44LTE3Ni0xNzYtMTc2em03OS4yIDI1NS4yQTExMS42IDExMS42IDAgMDE1MTIgNjE0Yy0yOS45IDAtNTgtMTEuNy03OS4yLTMyLjhBMTExLjYgMTExLjYgMCAwMTQwMCA1MDJjMC0yOS45IDExLjctNTggMzIuOC03OS4yQzQ1NCA0MDEuNiA0ODIuMSAzOTAgNTEyIDM5MGMyOS45IDAgNTggMTEuNiA3OS4yIDMyLjhBMTExLjYgMTExLjYgMCAwMTYyNCA1MDJjMCAyOS45LTExLjcgNTgtMzIuOCA3OS4yeiIgLz48L3N2Zz4=) */
157
157
 
158
158
  /**![sort-ascending](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTgzOS42IDQzMy44TDc0OSAxNTAuNWE5LjI0IDkuMjQgMCAwMC04LjktNi41aC03Ny40Yy00LjEgMC03LjYgMi42LTguOSA2LjVsLTkxLjMgMjgzLjNjLS4zLjktLjUgMS45LS41IDIuOSAwIDUuMSA0LjIgOS4zIDkuMyA5LjNoNTYuNGM0LjIgMCA3LjgtMi44IDktNi44bDE3LjUtNjEuNmg4OWwxNy4zIDYxLjVjMS4xIDQgNC44IDYuOCA5IDYuOGg2MS4yYzEgMCAxLjktLjEgMi44LS40IDIuNC0uOCA0LjMtMi40IDUuNS00LjYgMS4xLTIuMiAxLjMtNC43LjYtNy4xek02NjMuMyAzMjUuNWwzMi44LTExNi45aDYuM2wzMi4xIDExNi45aC03MS4yem0xNDMuNSA0OTIuOUg2NzcuMnYtLjRsMTMyLjYtMTg4LjljMS4xLTEuNiAxLjctMy40IDEuNy01LjR2LTM2LjRjMC01LjEtNC4yLTkuMy05LjMtOS4zaC0yMDRjLTUuMSAwLTkuMyA0LjItOS4zIDkuM3Y0M2MwIDUuMSA0LjIgOS4zIDkuMyA5LjNoMTIyLjZ2LjRMNTg3LjcgODI4LjlhOS4zNSA5LjM1IDAgMDAtMS43IDUuNHYzNi40YzAgNS4xIDQuMiA5LjMgOS4zIDkuM2gyMTEuNGM1LjEgMCA5LjMtNC4yIDkuMy05LjN2LTQzYTkuMiA5LjIgMCAwMC05LjItOS4zek00MTYgNzAyaC03NlYxNzJjMC00LjQtMy42LTgtOC04aC01NmMtNC40IDAtOCAzLjYtOCA4djUzMGgtNzZjLTYuNyAwLTEwLjUgNy44LTYuMyAxM2wxMTIgMTQxLjlhOCA4IDAgMDAxMi42IDBsMTEyLTE0MS45YzQuMS01LjIuNC0xMy02LjMtMTN6IiAvPjwvc3ZnPg==) */
159
-
160
- /**![unordered-list](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTkxMiAxOTJIMzI4Yy00LjQgMC04IDMuNi04IDh2NTZjMCA0LjQgMy42IDggOCA4aDU4NGM0LjQgMCA4LTMuNiA4LTh2LTU2YzAtNC40LTMuNi04LTgtOHptMCAyODRIMzI4Yy00LjQgMC04IDMuNi04IDh2NTZjMCA0LjQgMy42IDggOCA4aDU4NGM0LjQgMCA4LTMuNiA4LTh2LTU2YzAtNC40LTMuNi04LTgtOHptMCAyODRIMzI4Yy00LjQgMC04IDMuNi04IDh2NTZjMCA0LjQgMy42IDggOCA4aDU4NGM0LjQgMCA4LTMuNiA4LTh2LTU2YzAtNC40LTMuNi04LTgtOHpNMTA0IDIyOGE1NiA1NiAwIDEwMTEyIDAgNTYgNTYgMCAxMC0xMTIgMHptMCAyODRhNTYgNTYgMCAxMDExMiAwIDU2IDU2IDAgMTAtMTEyIDB6bTAgMjg0YTU2IDU2IDAgMTAxMTIgMCA1NiA1NiAwIDEwLTExMiAweiIgLz48L3N2Zz4=) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whistle.mock-plugins",
3
- "version": "1.2.0-beta.8",
3
+ "version": "1.2.0",
4
4
  "description": "Whistle 插件,用于快速创建 API 模拟数据",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -9,7 +9,7 @@ import {
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';
@@ -68,6 +68,7 @@ const InterfaceManagement = () => {
68
68
  // 模态框状态
69
69
  const [modalVisible, setModalVisible] = useState(false);
70
70
  const [previewVisible, setPreviewVisible] = useState(false);
71
+ const [isDuplicateMode, setIsDuplicateMode] = useState(false);
71
72
 
72
73
  // 列配置状态
73
74
  const [columnConfigVisible, setColumnConfigVisible] = useState(false);
@@ -247,6 +248,7 @@ const InterfaceManagement = () => {
247
248
  return;
248
249
  }
249
250
  form.resetFields();
251
+ setIsDuplicateMode(false);
250
252
 
251
253
  // 创建默认响应
252
254
  const defaultResponseId = generateResponseId();
@@ -286,6 +288,7 @@ const InterfaceManagement = () => {
286
288
 
287
289
  // 重置表单
288
290
  form.resetFields();
291
+ setIsDuplicateMode(false);
289
292
 
290
293
  // 处理响应数据
291
294
  let responses = [];
@@ -345,6 +348,91 @@ const InterfaceManagement = () => {
345
348
  setModalVisible(true);
346
349
  };
347
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
+
348
436
  const handleDeleteInterface = async (id) => {
349
437
  try {
350
438
  const response = await axios.delete(`/cgi-bin/interfaces?id=${id}`);
@@ -385,8 +473,9 @@ const InterfaceManagement = () => {
385
473
 
386
474
  const handleSubmit = async () => {
387
475
  try {
388
- // 表单验证
389
- const values = await form.validateFields();
476
+ // 表单验证 - 先验证所有字段,再获取所有字段值(包括折叠的)
477
+ await form.validateFields();
478
+ const values = form.getFieldsValue(true); // true表示获取所有字段,包括未渲染的
390
479
  console.log('表单提交数据:', values);
391
480
 
392
481
  // 处理自定义请求头
@@ -405,19 +494,19 @@ const InterfaceManagement = () => {
405
494
  groupValue = groupValue.length > 0 ? groupValue[0] : '';
406
495
  }
407
496
 
408
- // 构建接口数据
497
+ // 构建接口数据 - 在编辑模式下,确保关键字段不丢失
409
498
  const interfaceData = {
410
- name: values.name,
411
- group: groupValue || '', // 确保group不为undefined
412
- urlPattern: values.pattern,
413
- 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'),
414
503
  featureId: selectedFeatureId,
415
- responses: values.responses,
416
- activeResponseId: values.activeResponseId,
417
- httpStatus: parseInt(values.statusCode, 10), // 转换为数字
418
- contentType: values.contentType,
419
- responseDelay: parseInt(values.responseDelay, 10) || 0,
420
- 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'),
421
510
  active: true
422
511
  };
423
512
 
@@ -429,13 +518,11 @@ const InterfaceManagement = () => {
429
518
  interfaceData.headers = headers;
430
519
  }
431
520
 
432
- // 添加参数匹配规则
433
- if (values.paramMatchers && Array.isArray(values.paramMatchers) && values.paramMatchers.length > 0) {
434
- // 过滤掉空的匹配规则
435
- interfaceData.paramMatchers = values.paramMatchers.filter(matcher =>
436
- matcher && matcher.paramPath && matcher.paramValue
437
- );
438
- }
521
+ // 添加参数匹配规则(始终发送,空数组用于清空后端旧值)
522
+ const filteredParamMatchers = Array.isArray(values.paramMatchers)
523
+ ? values.paramMatchers.filter(matcher => matcher && matcher.paramPath && matcher.paramValue)
524
+ : [];
525
+ interfaceData.paramMatchers = filteredParamMatchers;
439
526
 
440
527
  console.log('提交接口数据:', interfaceData);
441
528
 
@@ -476,6 +563,7 @@ const InterfaceManagement = () => {
476
563
 
477
564
  const handleCancel = () => {
478
565
  setModalVisible(false);
566
+ setIsDuplicateMode(false);
479
567
  };
480
568
 
481
569
  const handleSelectFeature = (featureId) => {
@@ -905,7 +993,7 @@ const InterfaceManagement = () => {
905
993
  {
906
994
  title: '操作',
907
995
  key: 'action',
908
- width: 180,
996
+ width: 220,
909
997
  render: (_, record) => (
910
998
  <div className="action-buttons">
911
999
  <Button
@@ -913,6 +1001,13 @@ const InterfaceManagement = () => {
913
1001
  icon={<EditOutlined />}
914
1002
  onClick={() => handleEditInterface(record)}
915
1003
  />
1004
+ <Tooltip title="复制新增">
1005
+ <Button
1006
+ type="text"
1007
+ icon={<CopyOutlined />}
1008
+ onClick={() => handleDuplicate(record)}
1009
+ />
1010
+ </Tooltip>
916
1011
  <Popconfirm
917
1012
  title="确定要删除此接口吗?"
918
1013
  onConfirm={() => handleDeleteInterface(record.id)}
@@ -1252,7 +1347,7 @@ const InterfaceManagement = () => {
1252
1347
 
1253
1348
  {/* 接口编辑/创建模态框 */}
1254
1349
  <Modal
1255
- title={editingInterface ? '编辑接口' : '添加接口'}
1350
+ title={editingInterface ? '编辑接口' : (isDuplicateMode ? '复制新增接口' : '添加接口')}
1256
1351
  open={modalVisible}
1257
1352
  onOk={handleSubmit}
1258
1353
  onCancel={handleCancel}
@@ -1298,14 +1393,21 @@ const InterfaceManagement = () => {
1298
1393
  tooltip="为接口设置分组,便于管理和批量操作"
1299
1394
  >
1300
1395
  <Select
1301
- placeholder="选择或输入新分组名称"
1396
+ placeholder="选择或创建分组"
1302
1397
  showSearch
1303
1398
  allowClear
1304
1399
  style={{ width: '100%' }}
1305
- dropdownMatchSelectWidth={false}
1306
- filterOption={(input, option) =>
1307
- option?.children?.toLowerCase()?.indexOf(input.toLowerCase()) >= 0
1308
- }
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
+ }}
1309
1411
  onInputKeyDown={(e) => {
1310
1412
  // 防止回车键提交表单
1311
1413
  if (e.key === 'Enter') {
@@ -15,7 +15,7 @@ import {
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';
@@ -74,6 +74,7 @@ const InterfaceManagement = () => {
74
74
  // 模态框状态
75
75
  const [modalVisible, setModalVisible] = useState(false);
76
76
  const [previewVisible, setPreviewVisible] = useState(false);
77
+ const [isDuplicateMode, setIsDuplicateMode] = useState(false);
77
78
 
78
79
  // 列配置状态
79
80
  const [columnConfigVisible, setColumnConfigVisible] = useState(false);
@@ -253,6 +254,7 @@ const InterfaceManagement = () => {
253
254
  return;
254
255
  }
255
256
  form.resetFields();
257
+ setIsDuplicateMode(false);
256
258
 
257
259
  // 创建默认响应
258
260
  const defaultResponseId = generateResponseId();
@@ -292,6 +294,7 @@ const InterfaceManagement = () => {
292
294
 
293
295
  // 重置表单
294
296
  form.resetFields();
297
+ setIsDuplicateMode(false);
295
298
 
296
299
  // 处理响应数据
297
300
  let responses = [];
@@ -351,6 +354,91 @@ const InterfaceManagement = () => {
351
354
  setModalVisible(true);
352
355
  };
353
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
+
354
442
  const handleDeleteInterface = async (id) => {
355
443
  try {
356
444
  const response = await axios.delete(`/cgi-bin/interfaces?id=${id}`);
@@ -391,8 +479,9 @@ const InterfaceManagement = () => {
391
479
 
392
480
  const handleSubmit = async () => {
393
481
  try {
394
- // 表单验证
395
- const values = await form.validateFields();
482
+ // 表单验证 - 先验证所有字段,再获取所有字段值(包括折叠的)
483
+ await form.validateFields();
484
+ const values = form.getFieldsValue(true); // true表示获取所有字段,包括未渲染的
396
485
  console.log('表单提交数据:', values);
397
486
 
398
487
  // 处理自定义请求头
@@ -411,19 +500,19 @@ const InterfaceManagement = () => {
411
500
  groupValue = groupValue.length > 0 ? groupValue[0] : '';
412
501
  }
413
502
 
414
- // 构建接口数据
503
+ // 构建接口数据 - 在编辑模式下,确保关键字段不丢失
415
504
  const interfaceData = {
416
- name: values.name,
417
- group: groupValue || '', // 确保group不为undefined
418
- urlPattern: values.pattern,
419
- 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'),
420
509
  featureId: selectedFeatureId,
421
- responses: values.responses,
422
- activeResponseId: values.activeResponseId,
423
- httpStatus: parseInt(values.statusCode, 10), // 转换为数字
424
- contentType: values.contentType,
425
- responseDelay: parseInt(values.responseDelay, 10) || 0,
426
- 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'),
427
516
  active: true
428
517
  };
429
518
 
@@ -435,13 +524,11 @@ const InterfaceManagement = () => {
435
524
  interfaceData.headers = headers;
436
525
  }
437
526
 
438
- // 添加参数匹配规则
439
- if (values.paramMatchers && Array.isArray(values.paramMatchers) && values.paramMatchers.length > 0) {
440
- // 过滤掉空的匹配规则
441
- interfaceData.paramMatchers = values.paramMatchers.filter(matcher =>
442
- matcher && matcher.paramPath && matcher.paramValue
443
- );
444
- }
527
+ // 添加参数匹配规则(始终发送,空数组用于清空后端旧值)
528
+ const filteredParamMatchers = Array.isArray(values.paramMatchers)
529
+ ? values.paramMatchers.filter(matcher => matcher && matcher.paramPath && matcher.paramValue)
530
+ : [];
531
+ interfaceData.paramMatchers = filteredParamMatchers;
445
532
 
446
533
  console.log('提交接口数据:', interfaceData);
447
534
 
@@ -482,6 +569,7 @@ const InterfaceManagement = () => {
482
569
 
483
570
  const handleCancel = () => {
484
571
  setModalVisible(false);
572
+ setIsDuplicateMode(false);
485
573
  };
486
574
 
487
575
  const handleSelectFeature = (featureId) => {
@@ -911,7 +999,7 @@ const InterfaceManagement = () => {
911
999
  {
912
1000
  title: '操作',
913
1001
  key: 'action',
914
- width: 180,
1002
+ width: 220,
915
1003
  render: (_, record) => (
916
1004
  <div className="action-buttons">
917
1005
  <Button
@@ -919,6 +1007,13 @@ const InterfaceManagement = () => {
919
1007
  icon={<EditOutlined />}
920
1008
  onClick={() => handleEditInterface(record)}
921
1009
  />
1010
+ <Tooltip title="复制新增">
1011
+ <Button
1012
+ type="text"
1013
+ icon={<CopyOutlined />}
1014
+ onClick={() => handleDuplicate(record)}
1015
+ />
1016
+ </Tooltip>
922
1017
  <Popconfirm
923
1018
  title="确定要删除此接口吗?"
924
1019
  onConfirm={() => handleDeleteInterface(record.id)}
@@ -1271,7 +1366,7 @@ const InterfaceManagement = () => {
1271
1366
  alignItems: 'center',
1272
1367
  gap: '8px'
1273
1368
  }}>
1274
- <span>{editingInterface ? '编辑接口' : '添加接口'}</span>
1369
+ <span>{editingInterface ? '编辑接口' : (isDuplicateMode ? '复制新增接口' : '添加接口')}</span>
1275
1370
  <Badge
1276
1371
  count="配置向导"
1277
1372
  style={{
@@ -1320,8 +1415,8 @@ const InterfaceManagement = () => {
1320
1415
  <Collapse
1321
1416
  defaultActiveKey={
1322
1417
  editingInterface && editingInterface.proxyType === 'response'
1323
- ? ['3']
1324
- : ['1', '2', '3']
1418
+ ? ['3'] // mock代理场景:只展开第3步(高级配置)
1419
+ : ['1', '2', '3'] // 其他场景:展开所有步骤
1325
1420
  }
1326
1421
  style={{ marginBottom: '16px' }}
1327
1422
  ghost
@@ -1353,14 +1448,21 @@ const InterfaceManagement = () => {
1353
1448
  tooltip="为接口设置分组,便于管理和批量操作"
1354
1449
  >
1355
1450
  <Select
1356
- placeholder="选择或输入新分组名称"
1451
+ placeholder="选择或创建分组"
1357
1452
  showSearch
1358
1453
  allowClear
1359
1454
  style={{ width: '100%' }}
1360
- dropdownMatchSelectWidth={false}
1361
- filterOption={(input, option) =>
1362
- option?.children?.toLowerCase()?.indexOf(input.toLowerCase()) >= 0
1363
- }
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
+ }}
1364
1466
  onInputKeyDown={(e) => {
1365
1467
  // 防止回车键提交表单
1366
1468
  if (e.key === 'Enter') {