whistle.mock-plugins 1.2.0-beta.9 → 1.2.1-beta.1
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 +13 -6
- package/app/public/js/index.js +1 -1
- package/app/public/js/index.js.LICENSE.txt +13 -2
- package/package.json +3 -1
- package/ui/src/components/interface-management/DataManagementModal.js +110 -0
- package/ui/src/components/interface-management/ResponseContentEditor.js +59 -1
- package/ui/src/pages/InterfaceManagement.js +153 -76
- package/ui/src/pages/InterfaceManagementV2.js +155 -78
- package/ui/src/pages/MockDataV2.js +6 -134
- package/ui/src/styles/mock-data.css +0 -139
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
http://jedwatson.github.io/classnames
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
/*!
|
|
8
|
+
Copyright (c) 2018 Jed Watson.
|
|
9
|
+
Licensed under the MIT License (MIT), see
|
|
10
|
+
http://jedwatson.github.io/classnames
|
|
11
|
+
*/
|
|
12
|
+
|
|
7
13
|
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
|
8
14
|
|
|
9
15
|
/**
|
|
@@ -55,6 +61,13 @@
|
|
|
55
61
|
* LICENSE file in the root directory of this source tree.
|
|
56
62
|
*/
|
|
57
63
|
|
|
64
|
+
/**!
|
|
65
|
+
* Sortable 1.15.6
|
|
66
|
+
* @author RubaXa <trash@rubaxa.org>
|
|
67
|
+
* @author owenm <owen23355@gmail.com>
|
|
68
|
+
* @license MIT
|
|
69
|
+
*/
|
|
70
|
+
|
|
58
71
|
/** */
|
|
59
72
|
|
|
60
73
|
/** */
|
|
@@ -156,5 +169,3 @@
|
|
|
156
169
|
/** */
|
|
157
170
|
|
|
158
171
|
/** */
|
|
159
|
-
|
|
160
|
-
/** */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whistle.mock-plugins",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1-beta.1",
|
|
4
4
|
"description": "Whistle 插件,用于快速创建 API 模拟数据",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -39,6 +39,8 @@
|
|
|
39
39
|
"jest": "^29.7.0",
|
|
40
40
|
"react": "^18.2.0",
|
|
41
41
|
"react-dom": "^18.2.0",
|
|
42
|
+
"react-sortablejs": "^6.1.4",
|
|
43
|
+
"sortablejs": "^1.15.3",
|
|
42
44
|
"react-markdown": "^8.0.7",
|
|
43
45
|
"style-loader": "^3.3.2",
|
|
44
46
|
"webpack": "^5.79.0",
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { Modal, Form, Input, message } from 'antd';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import { ResponseContentEditor, PreviewModal, formatResponseContent, refreshCacheAfterUpdate } from '.';
|
|
5
|
+
|
|
6
|
+
const DataManagementModal = ({ visible, onCancel, interfaceItem, onSaved }) => {
|
|
7
|
+
const [form] = Form.useForm();
|
|
8
|
+
const [previewVisible, setPreviewVisible] = useState(false);
|
|
9
|
+
const [previewContent, setPreviewContent] = useState(null);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (visible && interfaceItem) {
|
|
13
|
+
let responses = Array.isArray(interfaceItem.responses) ? interfaceItem.responses : [];
|
|
14
|
+
let activeResponseId = interfaceItem.activeResponseId || (responses[0] && responses[0].id) || '';
|
|
15
|
+
form.setFieldsValue({ responses, activeResponseId });
|
|
16
|
+
}
|
|
17
|
+
}, [visible, interfaceItem]);
|
|
18
|
+
|
|
19
|
+
const handlePreview = () => {
|
|
20
|
+
const values = form.getFieldsValue(true);
|
|
21
|
+
const responses = Array.isArray(values.responses) ? values.responses : [];
|
|
22
|
+
const activeResponse = responses.find(r => r.id === values.activeResponseId) || responses[0];
|
|
23
|
+
if (!activeResponse) {
|
|
24
|
+
message.error('未找到有效的响应内容');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const contentType = 'application/json; charset=utf-8';
|
|
28
|
+
const formatted = formatResponseContent(activeResponse.content || '', contentType);
|
|
29
|
+
setPreviewContent({ title: `预览: ${activeResponse.name || '未命名响应'}`, content: formatted, contentType, description: '' });
|
|
30
|
+
setPreviewVisible(true);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const handleOk = async () => {
|
|
34
|
+
try {
|
|
35
|
+
await form.validateFields();
|
|
36
|
+
const values = form.getFieldsValue(true);
|
|
37
|
+
const payload = {
|
|
38
|
+
responses: Array.isArray(values.responses) ? values.responses : [],
|
|
39
|
+
activeResponseId: values.activeResponseId || (values.responses && values.responses[0] && values.responses[0].id) || ''
|
|
40
|
+
};
|
|
41
|
+
if (!interfaceItem || !interfaceItem.id) {
|
|
42
|
+
message.error('缺少接口ID,无法保存');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const res = await axios.patch(`/cgi-bin/interfaces/${interfaceItem.id}`, payload);
|
|
46
|
+
if (res.data && res.data.code === 0) {
|
|
47
|
+
message.success('保存成功');
|
|
48
|
+
await refreshCacheAfterUpdate();
|
|
49
|
+
if (onSaved) onSaved();
|
|
50
|
+
if (onCancel) onCancel();
|
|
51
|
+
} else {
|
|
52
|
+
throw new Error(res.data?.message || '保存失败');
|
|
53
|
+
}
|
|
54
|
+
} catch (err) {
|
|
55
|
+
if (err && err.errorFields) return; // 表单校验错误
|
|
56
|
+
console.error('保存失败', err);
|
|
57
|
+
message.error(err.response?.data?.message || err.message || '保存失败');
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<>
|
|
63
|
+
<Modal
|
|
64
|
+
title="数据管理"
|
|
65
|
+
open={visible}
|
|
66
|
+
onOk={handleOk}
|
|
67
|
+
onCancel={onCancel}
|
|
68
|
+
width={900}
|
|
69
|
+
destroyOnClose
|
|
70
|
+
okText="保存"
|
|
71
|
+
cancelText="取消"
|
|
72
|
+
bodyStyle={{ maxHeight: '70vh', overflow: 'auto', padding: '24px' }}
|
|
73
|
+
>
|
|
74
|
+
<Form form={form} layout="vertical" initialValues={{ responses: [], activeResponseId: '' }}>
|
|
75
|
+
<Form.Item name="responses" hidden>
|
|
76
|
+
<Input />
|
|
77
|
+
</Form.Item>
|
|
78
|
+
<Form.Item name="activeResponseId" hidden>
|
|
79
|
+
<Input />
|
|
80
|
+
</Form.Item>
|
|
81
|
+
<Form.Item noStyle shouldUpdate>
|
|
82
|
+
{({ getFieldValue }) => {
|
|
83
|
+
const responses = getFieldValue('responses') || [];
|
|
84
|
+
const activeResponseId = getFieldValue('activeResponseId') || (responses[0] && responses[0].id) || '';
|
|
85
|
+
return (
|
|
86
|
+
<ResponseContentEditor
|
|
87
|
+
form={form}
|
|
88
|
+
responses={responses}
|
|
89
|
+
activeResponseId={activeResponseId}
|
|
90
|
+
onPreview={handlePreview}
|
|
91
|
+
enableSorting={true}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
}}
|
|
95
|
+
</Form.Item>
|
|
96
|
+
</Form>
|
|
97
|
+
</Modal>
|
|
98
|
+
|
|
99
|
+
<PreviewModal
|
|
100
|
+
visible={previewVisible}
|
|
101
|
+
onCancel={() => setPreviewVisible(false)}
|
|
102
|
+
previewContent={previewContent}
|
|
103
|
+
/>
|
|
104
|
+
</>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export default DataManagementModal;
|
|
109
|
+
|
|
110
|
+
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { ReactSortable } from 'react-sortablejs';
|
|
2
3
|
import ReactDOM from 'react-dom';
|
|
3
4
|
import { Card, Button, Space, Badge, Row, Col, Input, Select, Tooltip, message, AutoComplete } from 'antd';
|
|
4
5
|
import {
|
|
@@ -22,7 +23,8 @@ const ResponseContentEditor = ({
|
|
|
22
23
|
form,
|
|
23
24
|
responses,
|
|
24
25
|
activeResponseId,
|
|
25
|
-
onPreview
|
|
26
|
+
onPreview,
|
|
27
|
+
enableSorting = false
|
|
26
28
|
}) => {
|
|
27
29
|
const { getFieldValue, setFieldsValue } = form;
|
|
28
30
|
const [isEditing, setIsEditing] = useState(false);
|
|
@@ -32,6 +34,20 @@ const ResponseContentEditor = ({
|
|
|
32
34
|
// 查找激活的响应
|
|
33
35
|
const activeResponseIndex = responses.findIndex(r => r.id === activeResponseId);
|
|
34
36
|
const activeResponse = activeResponseIndex >= 0 ? responses[activeResponseIndex] : null;
|
|
37
|
+
// 拖拽排序:基于 ReactSortable 管理一个本地列表镜像
|
|
38
|
+
const [sortableList, setSortableList] = useState(() => {
|
|
39
|
+
return Array.isArray(responses) ? responses.map(r => ({ ...r })) : [];
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
setSortableList(Array.isArray(responses) ? responses.map(r => ({ ...r })) : []);
|
|
44
|
+
}, [responses]);
|
|
45
|
+
|
|
46
|
+
const handleAfterSort = (newList) => {
|
|
47
|
+
// 将排序后的列表写回表单 responses 字段;activeResponseId 不变
|
|
48
|
+
setFieldsValue({ responses: newList });
|
|
49
|
+
};
|
|
50
|
+
|
|
35
51
|
|
|
36
52
|
// 全屏切换函数
|
|
37
53
|
const toggleTextareaFullscreen = () => {
|
|
@@ -313,6 +329,48 @@ const ResponseContentEditor = ({
|
|
|
313
329
|
style={{ marginBottom: 16 }}
|
|
314
330
|
bodyStyle={{ padding: '16px' }}
|
|
315
331
|
>
|
|
332
|
+
{enableSorting && Array.isArray(sortableList) && sortableList.length > 1 && (
|
|
333
|
+
<div style={{ marginBottom: '16px' }}>
|
|
334
|
+
<div style={{ marginBottom: '8px' }}>
|
|
335
|
+
<label style={{ fontSize: '12px', color: '#666', fontWeight: 'bold' }}>响应排序(拖拽调整顺序)</label>
|
|
336
|
+
</div>
|
|
337
|
+
<ReactSortable
|
|
338
|
+
list={sortableList}
|
|
339
|
+
setList={(newState) => {
|
|
340
|
+
setSortableList(newState);
|
|
341
|
+
handleAfterSort(newState);
|
|
342
|
+
}}
|
|
343
|
+
animation={150}
|
|
344
|
+
handle={undefined}
|
|
345
|
+
ghostClass="sortable-ghost"
|
|
346
|
+
>
|
|
347
|
+
{sortableList.map((item) => (
|
|
348
|
+
<div
|
|
349
|
+
key={item.id}
|
|
350
|
+
style={{
|
|
351
|
+
display: 'flex',
|
|
352
|
+
alignItems: 'center',
|
|
353
|
+
justifyContent: 'space-between',
|
|
354
|
+
padding: '6px 10px',
|
|
355
|
+
border: '1px solid #f0f0f0',
|
|
356
|
+
borderRadius: '4px',
|
|
357
|
+
marginBottom: '6px',
|
|
358
|
+
background: item.id === activeResponseId ? '#e6f7ff' : '#fff',
|
|
359
|
+
cursor: 'move'
|
|
360
|
+
}}
|
|
361
|
+
onClick={() => handleResponseSelect(item.id)}
|
|
362
|
+
title="拖拽以排序,点击选择"
|
|
363
|
+
>
|
|
364
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
365
|
+
<Badge status={item.id === activeResponseId ? 'processing' : 'default'} />
|
|
366
|
+
<span style={{ fontWeight: 500 }}>{item.name || '未命名响应'}</span>
|
|
367
|
+
</div>
|
|
368
|
+
<div style={{ color: '#999', fontSize: 12 }}>拖拽</div>
|
|
369
|
+
</div>
|
|
370
|
+
))}
|
|
371
|
+
</ReactSortable>
|
|
372
|
+
</div>
|
|
373
|
+
)}
|
|
316
374
|
{/* 响应选择和名称编辑合并区域 */}
|
|
317
375
|
<div style={{ marginBottom: '16px' }}>
|
|
318
376
|
<Row gutter={12} align="middle">
|
|
@@ -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';
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
formatResponseContent,
|
|
27
27
|
generateResponseId
|
|
28
28
|
} from '../components/interface-management';
|
|
29
|
+
import DataManagementModal from '../components/interface-management/DataManagementModal';
|
|
29
30
|
|
|
30
31
|
const { Option } = Select;
|
|
31
32
|
const { Search } = AntInput;
|
|
@@ -64,11 +65,13 @@ const InterfaceManagement = () => {
|
|
|
64
65
|
const [groups, setGroups] = useState([]);
|
|
65
66
|
const [selectedGroup, setSelectedGroup] = useState(null);
|
|
66
67
|
const [groupActionLoading, setGroupActionLoading] = useState(false);
|
|
67
|
-
const [newGroupName, setNewGroupName] = useState('');
|
|
68
68
|
|
|
69
69
|
// 模态框状态
|
|
70
70
|
const [modalVisible, setModalVisible] = useState(false);
|
|
71
71
|
const [previewVisible, setPreviewVisible] = useState(false);
|
|
72
|
+
const [isDuplicateMode, setIsDuplicateMode] = useState(false);
|
|
73
|
+
const [dataModalVisible, setDataModalVisible] = useState(false);
|
|
74
|
+
const [dataModalRecord, setDataModalRecord] = useState(null);
|
|
72
75
|
|
|
73
76
|
// 列配置状态
|
|
74
77
|
const [columnConfigVisible, setColumnConfigVisible] = useState(false);
|
|
@@ -217,17 +220,6 @@ const InterfaceManagement = () => {
|
|
|
217
220
|
setSelectedGroup(null);
|
|
218
221
|
}
|
|
219
222
|
};
|
|
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
223
|
|
|
232
224
|
// 根据分组和搜索关键词筛选接口
|
|
233
225
|
const getFilteredInterfaces = () => {
|
|
@@ -259,6 +251,7 @@ const InterfaceManagement = () => {
|
|
|
259
251
|
return;
|
|
260
252
|
}
|
|
261
253
|
form.resetFields();
|
|
254
|
+
setIsDuplicateMode(false);
|
|
262
255
|
|
|
263
256
|
// 创建默认响应
|
|
264
257
|
const defaultResponseId = generateResponseId();
|
|
@@ -298,6 +291,7 @@ const InterfaceManagement = () => {
|
|
|
298
291
|
|
|
299
292
|
// 重置表单
|
|
300
293
|
form.resetFields();
|
|
294
|
+
setIsDuplicateMode(false);
|
|
301
295
|
|
|
302
296
|
// 处理响应数据
|
|
303
297
|
let responses = [];
|
|
@@ -357,6 +351,91 @@ const InterfaceManagement = () => {
|
|
|
357
351
|
setModalVisible(true);
|
|
358
352
|
};
|
|
359
353
|
|
|
354
|
+
// 复制新增接口
|
|
355
|
+
const handleDuplicate = (record) => {
|
|
356
|
+
if (!selectedFeatureId) {
|
|
357
|
+
message.warning('请先选择一个功能模块');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (!record) {
|
|
361
|
+
message.warning('接口数据不完整,无法复制');
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
form.resetFields();
|
|
366
|
+
|
|
367
|
+
// 深拷贝源数据
|
|
368
|
+
const source = JSON.parse(JSON.stringify(record));
|
|
369
|
+
|
|
370
|
+
// 处理响应数据
|
|
371
|
+
let responses = [];
|
|
372
|
+
let activeResponseId = '';
|
|
373
|
+
|
|
374
|
+
if (Array.isArray(source.responses) && source.responses.length > 0) {
|
|
375
|
+
// 为每条响应生成新的ID,并重映射activeResponseId
|
|
376
|
+
const idMap = new Map();
|
|
377
|
+
responses = source.responses.map((resp) => {
|
|
378
|
+
const newId = generateResponseId();
|
|
379
|
+
idMap.set(resp.id, newId);
|
|
380
|
+
return {
|
|
381
|
+
id: newId,
|
|
382
|
+
name: resp.name || '未命名响应',
|
|
383
|
+
description: resp.description || '',
|
|
384
|
+
content: resp.content || '{}'
|
|
385
|
+
};
|
|
386
|
+
});
|
|
387
|
+
const mapped = idMap.get(source.activeResponseId) || responses[0]?.id;
|
|
388
|
+
activeResponseId = mapped || '';
|
|
389
|
+
} else if (source.responseContent) {
|
|
390
|
+
// 兼容旧数据格式
|
|
391
|
+
const defaultResponseId = generateResponseId();
|
|
392
|
+
responses = [{
|
|
393
|
+
id: defaultResponseId,
|
|
394
|
+
name: '默认响应',
|
|
395
|
+
description: '',
|
|
396
|
+
content: source.responseContent
|
|
397
|
+
}];
|
|
398
|
+
activeResponseId = defaultResponseId;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// 处理自定义请求头 -> 表单需要的数组结构
|
|
402
|
+
let headerItems = [];
|
|
403
|
+
if (source.headers && typeof source.headers === 'object') {
|
|
404
|
+
headerItems = Object.entries(source.headers).map(([headerName, headerValue]) => ({
|
|
405
|
+
headerName,
|
|
406
|
+
headerValue
|
|
407
|
+
}));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// 分组值保证为字符串
|
|
411
|
+
let groupValue = source.group;
|
|
412
|
+
if (Array.isArray(groupValue)) {
|
|
413
|
+
groupValue = groupValue.length > 0 ? groupValue[0] : undefined;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// 设定表单初值
|
|
417
|
+
form.setFieldsValue({
|
|
418
|
+
name: `${source.name || '未命名接口'} - 副本`,
|
|
419
|
+
group: groupValue || undefined,
|
|
420
|
+
pattern: source.urlPattern,
|
|
421
|
+
proxyType: source.proxyType || 'response',
|
|
422
|
+
statusCode: (source.httpStatus || source.statusCode || 200).toString(),
|
|
423
|
+
contentType: source.contentType || 'application/json; charset=utf-8',
|
|
424
|
+
responses,
|
|
425
|
+
activeResponseId,
|
|
426
|
+
httpMethod: source.httpMethod || source.method || 'ALL',
|
|
427
|
+
targetUrl: source.targetUrl || '',
|
|
428
|
+
headerItems,
|
|
429
|
+
paramMatchers: Array.isArray(source.paramMatchers) ? source.paramMatchers : [],
|
|
430
|
+
responseDelay: source.responseDelay ? String(source.responseDelay) : '0'
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
setCurrentResponseId(activeResponseId || null);
|
|
434
|
+
setEditingInterface(null); // 新增模式
|
|
435
|
+
setIsDuplicateMode(true);
|
|
436
|
+
setModalVisible(true);
|
|
437
|
+
};
|
|
438
|
+
|
|
360
439
|
const handleDeleteInterface = async (id) => {
|
|
361
440
|
try {
|
|
362
441
|
const response = await axios.delete(`/cgi-bin/interfaces?id=${id}`);
|
|
@@ -397,8 +476,9 @@ const InterfaceManagement = () => {
|
|
|
397
476
|
|
|
398
477
|
const handleSubmit = async () => {
|
|
399
478
|
try {
|
|
400
|
-
// 表单验证
|
|
401
|
-
|
|
479
|
+
// 表单验证 - 先验证所有字段,再获取所有字段值(包括折叠的)
|
|
480
|
+
await form.validateFields();
|
|
481
|
+
const values = form.getFieldsValue(true); // true表示获取所有字段,包括未渲染的
|
|
402
482
|
console.log('表单提交数据:', values);
|
|
403
483
|
|
|
404
484
|
// 处理自定义请求头
|
|
@@ -417,19 +497,19 @@ const InterfaceManagement = () => {
|
|
|
417
497
|
groupValue = groupValue.length > 0 ? groupValue[0] : '';
|
|
418
498
|
}
|
|
419
499
|
|
|
420
|
-
// 构建接口数据
|
|
500
|
+
// 构建接口数据 - 在编辑模式下,确保关键字段不丢失
|
|
421
501
|
const interfaceData = {
|
|
422
|
-
name: values.name,
|
|
423
|
-
group: groupValue || '', // 确保group不为undefined
|
|
424
|
-
urlPattern: values.pattern,
|
|
425
|
-
proxyType: values.proxyType,
|
|
502
|
+
name: values.name || (editingInterface ? editingInterface.name : ''),
|
|
503
|
+
group: groupValue || (editingInterface ? editingInterface.group : '') || '', // 确保group不为undefined
|
|
504
|
+
urlPattern: values.pattern || (editingInterface ? editingInterface.urlPattern : ''),
|
|
505
|
+
proxyType: values.proxyType || (editingInterface ? editingInterface.proxyType : 'response'),
|
|
426
506
|
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,
|
|
507
|
+
responses: values.responses || (editingInterface ? editingInterface.responses : []),
|
|
508
|
+
activeResponseId: values.activeResponseId || (editingInterface ? editingInterface.activeResponseId : null),
|
|
509
|
+
httpStatus: parseInt(values.statusCode, 10) || (editingInterface ? editingInterface.httpStatus : 200), // 转换为数字
|
|
510
|
+
contentType: values.contentType || (editingInterface ? editingInterface.contentType : 'application/json; charset=utf-8'),
|
|
511
|
+
responseDelay: parseInt(values.responseDelay, 10) || (editingInterface ? editingInterface.responseDelay : 0) || 0,
|
|
512
|
+
httpMethod: values.httpMethod || (editingInterface ? editingInterface.httpMethod : 'ALL'),
|
|
433
513
|
active: true
|
|
434
514
|
};
|
|
435
515
|
|
|
@@ -441,13 +521,11 @@ const InterfaceManagement = () => {
|
|
|
441
521
|
interfaceData.headers = headers;
|
|
442
522
|
}
|
|
443
523
|
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
);
|
|
450
|
-
}
|
|
524
|
+
// 添加参数匹配规则(始终发送,空数组用于清空后端旧值)
|
|
525
|
+
const filteredParamMatchers = Array.isArray(values.paramMatchers)
|
|
526
|
+
? values.paramMatchers.filter(matcher => matcher && matcher.paramPath && matcher.paramValue)
|
|
527
|
+
: [];
|
|
528
|
+
interfaceData.paramMatchers = filteredParamMatchers;
|
|
451
529
|
|
|
452
530
|
console.log('提交接口数据:', interfaceData);
|
|
453
531
|
|
|
@@ -488,6 +566,7 @@ const InterfaceManagement = () => {
|
|
|
488
566
|
|
|
489
567
|
const handleCancel = () => {
|
|
490
568
|
setModalVisible(false);
|
|
569
|
+
setIsDuplicateMode(false);
|
|
491
570
|
};
|
|
492
571
|
|
|
493
572
|
const handleSelectFeature = (featureId) => {
|
|
@@ -917,7 +996,7 @@ const InterfaceManagement = () => {
|
|
|
917
996
|
{
|
|
918
997
|
title: '操作',
|
|
919
998
|
key: 'action',
|
|
920
|
-
width:
|
|
999
|
+
width: 220,
|
|
921
1000
|
render: (_, record) => (
|
|
922
1001
|
<div className="action-buttons">
|
|
923
1002
|
<Button
|
|
@@ -925,6 +1004,22 @@ const InterfaceManagement = () => {
|
|
|
925
1004
|
icon={<EditOutlined />}
|
|
926
1005
|
onClick={() => handleEditInterface(record)}
|
|
927
1006
|
/>
|
|
1007
|
+
{record.proxyType === 'response' && (
|
|
1008
|
+
<Tooltip title="数据管理">
|
|
1009
|
+
<Button
|
|
1010
|
+
type="text"
|
|
1011
|
+
icon={<FileTextOutlined />}
|
|
1012
|
+
onClick={() => { setDataModalRecord(record); setDataModalVisible(true); }}
|
|
1013
|
+
/>
|
|
1014
|
+
</Tooltip>
|
|
1015
|
+
)}
|
|
1016
|
+
<Tooltip title="复制新增">
|
|
1017
|
+
<Button
|
|
1018
|
+
type="text"
|
|
1019
|
+
icon={<CopyOutlined />}
|
|
1020
|
+
onClick={() => handleDuplicate(record)}
|
|
1021
|
+
/>
|
|
1022
|
+
</Tooltip>
|
|
928
1023
|
<Popconfirm
|
|
929
1024
|
title="确定要删除此接口吗?"
|
|
930
1025
|
onConfirm={() => handleDeleteInterface(record.id)}
|
|
@@ -1264,7 +1359,7 @@ const InterfaceManagement = () => {
|
|
|
1264
1359
|
|
|
1265
1360
|
{/* 接口编辑/创建模态框 */}
|
|
1266
1361
|
<Modal
|
|
1267
|
-
title={editingInterface ? '编辑接口' : '添加接口'}
|
|
1362
|
+
title={editingInterface ? '编辑接口' : (isDuplicateMode ? '复制新增接口' : '添加接口')}
|
|
1268
1363
|
open={modalVisible}
|
|
1269
1364
|
onOk={handleSubmit}
|
|
1270
1365
|
onCancel={handleCancel}
|
|
@@ -1310,55 +1405,27 @@ const InterfaceManagement = () => {
|
|
|
1310
1405
|
tooltip="为接口设置分组,便于管理和批量操作"
|
|
1311
1406
|
>
|
|
1312
1407
|
<Select
|
|
1313
|
-
placeholder="
|
|
1408
|
+
placeholder="选择或创建分组"
|
|
1314
1409
|
showSearch
|
|
1315
1410
|
allowClear
|
|
1316
1411
|
style={{ width: '100%' }}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1412
|
+
mode="tags"
|
|
1413
|
+
maxTagCount="responsive"
|
|
1414
|
+
maxTagTextLength={10}
|
|
1415
|
+
onChange={(value) => {
|
|
1416
|
+
// 如果值是数组(tags模式),只取第一个元素
|
|
1417
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
1418
|
+
// 如果第一个元素是空字符串,则设置为undefined
|
|
1419
|
+
const groupValue = value[0] === '' ? undefined : value[0];
|
|
1420
|
+
form.setFieldsValue({ group: groupValue });
|
|
1421
|
+
}
|
|
1422
|
+
}}
|
|
1321
1423
|
onInputKeyDown={(e) => {
|
|
1322
1424
|
// 防止回车键提交表单
|
|
1323
1425
|
if (e.key === 'Enter') {
|
|
1324
1426
|
e.stopPropagation();
|
|
1325
1427
|
}
|
|
1326
1428
|
}}
|
|
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
1429
|
>
|
|
1363
1430
|
{Array.from(new Set(interfaces.map(item => item.group).filter(Boolean))).map(group => (
|
|
1364
1431
|
<Option key={group} value={group}>{group}</Option>
|
|
@@ -1778,6 +1845,16 @@ const InterfaceManagement = () => {
|
|
|
1778
1845
|
onCancel={() => setPreviewVisible(false)}
|
|
1779
1846
|
previewContent={previewContent}
|
|
1780
1847
|
/>
|
|
1848
|
+
|
|
1849
|
+
{/* 数据管理弹窗 */}
|
|
1850
|
+
<DataManagementModal
|
|
1851
|
+
visible={dataModalVisible}
|
|
1852
|
+
onCancel={() => { setDataModalVisible(false); setDataModalRecord(null); }}
|
|
1853
|
+
interfaceItem={dataModalRecord}
|
|
1854
|
+
onSaved={() => {
|
|
1855
|
+
fetchInterfaces();
|
|
1856
|
+
}}
|
|
1857
|
+
/>
|
|
1781
1858
|
</div>
|
|
1782
1859
|
</AppLayout>
|
|
1783
1860
|
);
|