whistle.mock-plugins 1.2.0-beta.13 → 1.2.0-beta.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whistle.mock-plugins",
3
- "version": "1.2.0-beta.13",
3
+ "version": "1.2.0-beta.15",
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}`);
@@ -430,13 +518,11 @@ const InterfaceManagement = () => {
430
518
  interfaceData.headers = headers;
431
519
  }
432
520
 
433
- // 添加参数匹配规则
434
- if (values.paramMatchers && Array.isArray(values.paramMatchers) && values.paramMatchers.length > 0) {
435
- // 过滤掉空的匹配规则
436
- interfaceData.paramMatchers = values.paramMatchers.filter(matcher =>
437
- matcher && matcher.paramPath && matcher.paramValue
438
- );
439
- }
521
+ // 添加参数匹配规则(始终发送,空数组用于清空后端旧值)
522
+ const filteredParamMatchers = Array.isArray(values.paramMatchers)
523
+ ? values.paramMatchers.filter(matcher => matcher && matcher.paramPath && matcher.paramValue)
524
+ : [];
525
+ interfaceData.paramMatchers = filteredParamMatchers;
440
526
 
441
527
  console.log('提交接口数据:', interfaceData);
442
528
 
@@ -477,6 +563,7 @@ const InterfaceManagement = () => {
477
563
 
478
564
  const handleCancel = () => {
479
565
  setModalVisible(false);
566
+ setIsDuplicateMode(false);
480
567
  };
481
568
 
482
569
  const handleSelectFeature = (featureId) => {
@@ -906,7 +993,7 @@ const InterfaceManagement = () => {
906
993
  {
907
994
  title: '操作',
908
995
  key: 'action',
909
- width: 180,
996
+ width: 220,
910
997
  render: (_, record) => (
911
998
  <div className="action-buttons">
912
999
  <Button
@@ -914,6 +1001,13 @@ const InterfaceManagement = () => {
914
1001
  icon={<EditOutlined />}
915
1002
  onClick={() => handleEditInterface(record)}
916
1003
  />
1004
+ <Tooltip title="复制新增">
1005
+ <Button
1006
+ type="text"
1007
+ icon={<CopyOutlined />}
1008
+ onClick={() => handleDuplicate(record)}
1009
+ />
1010
+ </Tooltip>
917
1011
  <Popconfirm
918
1012
  title="确定要删除此接口吗?"
919
1013
  onConfirm={() => handleDeleteInterface(record.id)}
@@ -1253,7 +1347,7 @@ const InterfaceManagement = () => {
1253
1347
 
1254
1348
  {/* 接口编辑/创建模态框 */}
1255
1349
  <Modal
1256
- title={editingInterface ? '编辑接口' : '添加接口'}
1350
+ title={editingInterface ? '编辑接口' : (isDuplicateMode ? '复制新增接口' : '添加接口')}
1257
1351
  open={modalVisible}
1258
1352
  onOk={handleSubmit}
1259
1353
  onCancel={handleCancel}
@@ -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}`);
@@ -436,13 +524,11 @@ const InterfaceManagement = () => {
436
524
  interfaceData.headers = headers;
437
525
  }
438
526
 
439
- // 添加参数匹配规则
440
- if (values.paramMatchers && Array.isArray(values.paramMatchers) && values.paramMatchers.length > 0) {
441
- // 过滤掉空的匹配规则
442
- interfaceData.paramMatchers = values.paramMatchers.filter(matcher =>
443
- matcher && matcher.paramPath && matcher.paramValue
444
- );
445
- }
527
+ // 添加参数匹配规则(始终发送,空数组用于清空后端旧值)
528
+ const filteredParamMatchers = Array.isArray(values.paramMatchers)
529
+ ? values.paramMatchers.filter(matcher => matcher && matcher.paramPath && matcher.paramValue)
530
+ : [];
531
+ interfaceData.paramMatchers = filteredParamMatchers;
446
532
 
447
533
  console.log('提交接口数据:', interfaceData);
448
534
 
@@ -483,6 +569,7 @@ const InterfaceManagement = () => {
483
569
 
484
570
  const handleCancel = () => {
485
571
  setModalVisible(false);
572
+ setIsDuplicateMode(false);
486
573
  };
487
574
 
488
575
  const handleSelectFeature = (featureId) => {
@@ -912,7 +999,7 @@ const InterfaceManagement = () => {
912
999
  {
913
1000
  title: '操作',
914
1001
  key: 'action',
915
- width: 180,
1002
+ width: 220,
916
1003
  render: (_, record) => (
917
1004
  <div className="action-buttons">
918
1005
  <Button
@@ -920,6 +1007,13 @@ const InterfaceManagement = () => {
920
1007
  icon={<EditOutlined />}
921
1008
  onClick={() => handleEditInterface(record)}
922
1009
  />
1010
+ <Tooltip title="复制新增">
1011
+ <Button
1012
+ type="text"
1013
+ icon={<CopyOutlined />}
1014
+ onClick={() => handleDuplicate(record)}
1015
+ />
1016
+ </Tooltip>
923
1017
  <Popconfirm
924
1018
  title="确定要删除此接口吗?"
925
1019
  onConfirm={() => handleDeleteInterface(record.id)}
@@ -1272,7 +1366,7 @@ const InterfaceManagement = () => {
1272
1366
  alignItems: 'center',
1273
1367
  gap: '8px'
1274
1368
  }}>
1275
- <span>{editingInterface ? '编辑接口' : '添加接口'}</span>
1369
+ <span>{editingInterface ? '编辑接口' : (isDuplicateMode ? '复制新增接口' : '添加接口')}</span>
1276
1370
  <Badge
1277
1371
  count="配置向导"
1278
1372
  style={{