whistle.mock-plugins 1.1.1-dev.8 → 1.1.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 +14 -16
- package/app/cgi-bin/interfaces.js +70 -9
- package/app/public/js/index.js +1 -1
- package/app/public/js/index.js.LICENSE.txt +6 -0
- package/lib/routes/interfaces.js +171 -0
- package/package.json +1 -1
- package/ui/src/pages/InterfaceManagement.js +568 -84
- package/ui/src/styles/interface-management.css +292 -16
|
@@ -4,11 +4,13 @@ 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
|
|
7
|
+
Popover, Checkbox, Tag, Input as AntInput, Radio, Drawer
|
|
8
8
|
} from 'antd';
|
|
9
9
|
import {
|
|
10
10
|
PlusOutlined, EditOutlined, DeleteOutlined,
|
|
11
|
-
FileTextOutlined, PlusCircleOutlined, SettingOutlined
|
|
11
|
+
FileTextOutlined, PlusCircleOutlined, SettingOutlined,
|
|
12
|
+
SearchOutlined, FilterOutlined, AppstoreOutlined,
|
|
13
|
+
UnorderedListOutlined, ReloadOutlined
|
|
12
14
|
} from '@ant-design/icons';
|
|
13
15
|
import '../styles/interface-management.css';
|
|
14
16
|
import axios from 'axios';
|
|
@@ -27,11 +29,13 @@ import {
|
|
|
27
29
|
} from '../components/interface-management';
|
|
28
30
|
|
|
29
31
|
const { Option } = Select;
|
|
32
|
+
const { Search } = AntInput;
|
|
30
33
|
|
|
31
34
|
// 列配置数据结构
|
|
32
35
|
const COLUMN_CONFIG = [
|
|
33
36
|
{ key: 'active', title: '状态', required: true },
|
|
34
37
|
{ key: 'name', title: '名称', required: true },
|
|
38
|
+
{ key: 'group', title: '分组', required: false },
|
|
35
39
|
{ key: 'urlPattern', title: 'URL匹配规则', required: false },
|
|
36
40
|
{ key: 'proxyType', title: '处理方式', required: false },
|
|
37
41
|
{ key: 'currentResponse', title: '当前响应', required: false },
|
|
@@ -57,6 +61,11 @@ const InterfaceManagement = () => {
|
|
|
57
61
|
const [interfacesLoading, setInterfacesLoading] = useState(false);
|
|
58
62
|
const [selectedFeatureId, setSelectedFeatureId] = useState(null);
|
|
59
63
|
|
|
64
|
+
// 分组状态
|
|
65
|
+
const [groups, setGroups] = useState([]);
|
|
66
|
+
const [selectedGroup, setSelectedGroup] = useState(null);
|
|
67
|
+
const [groupActionLoading, setGroupActionLoading] = useState(false);
|
|
68
|
+
|
|
60
69
|
// 模态框状态
|
|
61
70
|
const [modalVisible, setModalVisible] = useState(false);
|
|
62
71
|
const [previewVisible, setPreviewVisible] = useState(false);
|
|
@@ -69,6 +78,12 @@ const InterfaceManagement = () => {
|
|
|
69
78
|
const [previewContent, setPreviewContent] = useState(null);
|
|
70
79
|
const [currentResponseId, setCurrentResponseId] = useState(null);
|
|
71
80
|
|
|
81
|
+
// 搜索和视图状态
|
|
82
|
+
const [searchValue, setSearchValue] = useState('');
|
|
83
|
+
const [viewMode, setViewMode] = useState('table'); // 'table' or 'card'
|
|
84
|
+
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
|
|
85
|
+
const [drawerVisible, setDrawerVisible] = useState(false);
|
|
86
|
+
|
|
72
87
|
// 表单实例
|
|
73
88
|
const [form] = Form.useForm();
|
|
74
89
|
|
|
@@ -166,6 +181,10 @@ const InterfaceManagement = () => {
|
|
|
166
181
|
|
|
167
182
|
setInterfaces(processedInterfaces);
|
|
168
183
|
console.log('处理后的接口列表:', processedInterfaces);
|
|
184
|
+
console.log('接口分组信息:', processedInterfaces.map(item => ({ id: item.id, name: item.name, group: item.group })));
|
|
185
|
+
|
|
186
|
+
// 提取并更新分组列表
|
|
187
|
+
updateGroups(processedInterfaces);
|
|
169
188
|
} else {
|
|
170
189
|
console.warn('接口数据格式不正确:', response.data);
|
|
171
190
|
setInterfaces([]);
|
|
@@ -179,6 +198,50 @@ const InterfaceManagement = () => {
|
|
|
179
198
|
setInterfacesLoading(false);
|
|
180
199
|
}
|
|
181
200
|
};
|
|
201
|
+
|
|
202
|
+
// 提取并更新分组列表
|
|
203
|
+
const updateGroups = (interfaces) => {
|
|
204
|
+
// 从接口列表中提取所有分组
|
|
205
|
+
const groupSet = new Set();
|
|
206
|
+
interfaces.forEach(item => {
|
|
207
|
+
if (item.group) {
|
|
208
|
+
groupSet.add(item.group);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// 转换为数组并排序
|
|
213
|
+
const groupArray = Array.from(groupSet).sort();
|
|
214
|
+
setGroups(groupArray);
|
|
215
|
+
|
|
216
|
+
// 如果当前选中的分组不在列表中,重置选中的分组
|
|
217
|
+
if (selectedGroup && !groupArray.includes(selectedGroup)) {
|
|
218
|
+
setSelectedGroup(null);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// 根据分组和搜索关键词筛选接口
|
|
223
|
+
const getFilteredInterfaces = () => {
|
|
224
|
+
// 首先按功能模块筛选
|
|
225
|
+
let featureFiltered = interfaces.filter(item =>
|
|
226
|
+
!selectedFeatureId || item.featureId === selectedFeatureId
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// 然后按分组筛选
|
|
230
|
+
if (selectedGroup) {
|
|
231
|
+
featureFiltered = featureFiltered.filter(item => item.group === selectedGroup);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 最后按搜索关键词筛选
|
|
235
|
+
if (searchValue) {
|
|
236
|
+
const searchLower = searchValue.toLowerCase();
|
|
237
|
+
return featureFiltered.filter(item =>
|
|
238
|
+
(item.name && item.name.toLowerCase().includes(searchLower)) ||
|
|
239
|
+
(item.urlPattern && item.urlPattern.toLowerCase().includes(searchLower))
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return featureFiltered;
|
|
244
|
+
};
|
|
182
245
|
|
|
183
246
|
const handleAddInterface = () => {
|
|
184
247
|
if (!selectedFeatureId) {
|
|
@@ -206,7 +269,8 @@ const InterfaceManagement = () => {
|
|
|
206
269
|
activeResponseId: defaultResponseId,
|
|
207
270
|
httpMethod: 'ALL',
|
|
208
271
|
headerItems: [], // 初始化为空数组
|
|
209
|
-
paramMatchers: [] // 初始化为空数组
|
|
272
|
+
paramMatchers: [], // 初始化为空数组
|
|
273
|
+
group: undefined // 确保分组字段为undefined,而不是空字符串
|
|
210
274
|
});
|
|
211
275
|
|
|
212
276
|
setCurrentResponseId(defaultResponseId);
|
|
@@ -253,9 +317,16 @@ const InterfaceManagement = () => {
|
|
|
253
317
|
}));
|
|
254
318
|
}
|
|
255
319
|
|
|
320
|
+
// 处理分组值,确保它是字符串而不是数组
|
|
321
|
+
let groupValue = record.group;
|
|
322
|
+
if (Array.isArray(groupValue)) {
|
|
323
|
+
groupValue = groupValue.length > 0 ? groupValue[0] : undefined;
|
|
324
|
+
}
|
|
325
|
+
|
|
256
326
|
// 设置表单值
|
|
257
327
|
form.setFieldsValue({
|
|
258
328
|
name: record.name,
|
|
329
|
+
group: groupValue || undefined, // 使用undefined而不是空字符串
|
|
259
330
|
pattern: record.urlPattern,
|
|
260
331
|
proxyType: record.proxyType || 'response',
|
|
261
332
|
statusCode: (record.httpStatus || record.statusCode || 200).toString(),
|
|
@@ -269,6 +340,8 @@ const InterfaceManagement = () => {
|
|
|
269
340
|
responseDelay: record.responseDelay ? record.responseDelay.toString() : '0'
|
|
270
341
|
});
|
|
271
342
|
|
|
343
|
+
console.log('编辑接口时设置的分组值:', groupValue);
|
|
344
|
+
|
|
272
345
|
setCurrentResponseId(activeResponseId);
|
|
273
346
|
setEditingInterface(record);
|
|
274
347
|
setModalVisible(true);
|
|
@@ -294,12 +367,15 @@ const InterfaceManagement = () => {
|
|
|
294
367
|
|
|
295
368
|
const handleToggleActive = async (id, currentActive) => {
|
|
296
369
|
try {
|
|
297
|
-
const response = await axios.patch(`/cgi-bin/interfaces
|
|
370
|
+
const response = await axios.patch(`/cgi-bin/interfaces/${id}`, {
|
|
298
371
|
active: !currentActive
|
|
299
372
|
});
|
|
300
373
|
if (response.data && response.data.code === 0) {
|
|
301
374
|
message.success(`接口${!currentActive ? '启用' : '禁用'}成功`);
|
|
302
375
|
fetchInterfaces();
|
|
376
|
+
|
|
377
|
+
// 刷新规则缓存
|
|
378
|
+
refreshCacheAfterUpdate();
|
|
303
379
|
} else {
|
|
304
380
|
throw new Error(response.data?.message || `接口${!currentActive ? '启用' : '禁用'}失败`);
|
|
305
381
|
}
|
|
@@ -325,9 +401,16 @@ const InterfaceManagement = () => {
|
|
|
325
401
|
});
|
|
326
402
|
}
|
|
327
403
|
|
|
404
|
+
// 处理分组值,确保它是字符串而不是数组
|
|
405
|
+
let groupValue = values.group;
|
|
406
|
+
if (Array.isArray(groupValue)) {
|
|
407
|
+
groupValue = groupValue.length > 0 ? groupValue[0] : '';
|
|
408
|
+
}
|
|
409
|
+
|
|
328
410
|
// 构建接口数据
|
|
329
411
|
const interfaceData = {
|
|
330
412
|
name: values.name,
|
|
413
|
+
group: groupValue || '', // 确保group不为undefined
|
|
331
414
|
urlPattern: values.pattern,
|
|
332
415
|
proxyType: values.proxyType,
|
|
333
416
|
featureId: selectedFeatureId,
|
|
@@ -340,6 +423,8 @@ const InterfaceManagement = () => {
|
|
|
340
423
|
active: true
|
|
341
424
|
};
|
|
342
425
|
|
|
426
|
+
console.log('提交的分组值:', groupValue);
|
|
427
|
+
|
|
343
428
|
// 根据代理类型添加不同字段
|
|
344
429
|
if (values.proxyType === 'redirect' || values.proxyType === 'url_redirect') {
|
|
345
430
|
interfaceData.targetUrl = values.targetUrl;
|
|
@@ -374,6 +459,8 @@ const InterfaceManagement = () => {
|
|
|
374
459
|
const response = await axios.post('/cgi-bin/interfaces', interfaceData);
|
|
375
460
|
if (response.data && response.data.code === 0) {
|
|
376
461
|
message.success('接口创建成功');
|
|
462
|
+
console.log('接口创建成功,返回数据:', response.data);
|
|
463
|
+
console.log('返回的接口数据中的分组信息:', response.data.data?.group);
|
|
377
464
|
setModalVisible(false);
|
|
378
465
|
fetchInterfaces();
|
|
379
466
|
|
|
@@ -460,9 +547,7 @@ const InterfaceManagement = () => {
|
|
|
460
547
|
}
|
|
461
548
|
};
|
|
462
549
|
|
|
463
|
-
const filteredInterfaces =
|
|
464
|
-
!selectedFeatureId || item.featureId === selectedFeatureId
|
|
465
|
-
);
|
|
550
|
+
const filteredInterfaces = getFilteredInterfaces();
|
|
466
551
|
|
|
467
552
|
// 处理表格变化(排序、分页)
|
|
468
553
|
const handleTableChange = (pagination, filters, sorter) => {
|
|
@@ -505,6 +590,60 @@ const InterfaceManagement = () => {
|
|
|
505
590
|
setColumnConfigVisible(visible);
|
|
506
591
|
};
|
|
507
592
|
|
|
593
|
+
// 搜索处理
|
|
594
|
+
const handleSearch = (value) => {
|
|
595
|
+
setSearchValue(value);
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
// 清除搜索
|
|
599
|
+
const handleClearSearch = () => {
|
|
600
|
+
setSearchValue('');
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
// 表格行选择处理
|
|
604
|
+
const onSelectChange = (selectedKeys) => {
|
|
605
|
+
setSelectedRowKeys(selectedKeys);
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
// 切换视图模式
|
|
609
|
+
const toggleViewMode = () => {
|
|
610
|
+
setViewMode(viewMode === 'table' ? 'card' : 'table');
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// 刷新数据
|
|
614
|
+
const refreshData = () => {
|
|
615
|
+
fetchInterfaces();
|
|
616
|
+
message.success('数据已刷新');
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
// 批量操作接口状态
|
|
620
|
+
const handleBatchOperation = async (active) => {
|
|
621
|
+
if (!selectedRowKeys || selectedRowKeys.length === 0) {
|
|
622
|
+
message.warning('请先选择要操作的接口');
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
try {
|
|
627
|
+
setLoading(true);
|
|
628
|
+
const operations = selectedRowKeys.map(id =>
|
|
629
|
+
axios.patch(`/cgi-bin/interfaces/${id}`, { active })
|
|
630
|
+
);
|
|
631
|
+
|
|
632
|
+
await Promise.all(operations);
|
|
633
|
+
message.success(`已${active ? '启用' : '禁用'} ${selectedRowKeys.length} 个接口`);
|
|
634
|
+
setSelectedRowKeys([]);
|
|
635
|
+
fetchInterfaces();
|
|
636
|
+
|
|
637
|
+
// 刷新规则缓存
|
|
638
|
+
refreshCacheAfterUpdate();
|
|
639
|
+
} catch (error) {
|
|
640
|
+
console.error('批量操作失败:', error);
|
|
641
|
+
message.error('批量操作失败: ' + (error.response?.data?.message || error.message));
|
|
642
|
+
} finally {
|
|
643
|
+
setLoading(false);
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
|
|
508
647
|
// 定义所有可用的列
|
|
509
648
|
const allColumns = [
|
|
510
649
|
{
|
|
@@ -527,6 +666,27 @@ const InterfaceManagement = () => {
|
|
|
527
666
|
sorter: (a, b) => a.name.localeCompare(b.name),
|
|
528
667
|
render: (text) => <span className="interface-name">{text}</span>,
|
|
529
668
|
},
|
|
669
|
+
{
|
|
670
|
+
title: '分组',
|
|
671
|
+
dataIndex: 'group',
|
|
672
|
+
key: 'group',
|
|
673
|
+
width: 120,
|
|
674
|
+
sorter: (a, b) => {
|
|
675
|
+
const groupA = a.group || '';
|
|
676
|
+
const groupB = b.group || '';
|
|
677
|
+
return groupA.localeCompare(groupB);
|
|
678
|
+
},
|
|
679
|
+
render: (group) => {
|
|
680
|
+
if (!group) {
|
|
681
|
+
return <span style={{ color: '#999', fontStyle: 'italic' }}>未分组</span>;
|
|
682
|
+
}
|
|
683
|
+
return (
|
|
684
|
+
<Tag color="blue" style={{ cursor: 'pointer' }} onClick={() => setSelectedGroup(group)}>
|
|
685
|
+
{group}
|
|
686
|
+
</Tag>
|
|
687
|
+
);
|
|
688
|
+
},
|
|
689
|
+
},
|
|
530
690
|
{
|
|
531
691
|
title: 'URL匹配规则',
|
|
532
692
|
dataIndex: 'urlPattern',
|
|
@@ -775,38 +935,94 @@ const InterfaceManagement = () => {
|
|
|
775
935
|
const visibleColumns = tableConfig.visibleColumns || COLUMN_CONFIG.map(col => col.key);
|
|
776
936
|
const columns = allColumns.filter(col => visibleColumns.includes(col.key));
|
|
777
937
|
|
|
938
|
+
// 分组筛选和操作区域
|
|
939
|
+
const handleBatchToggleActive = async (active) => {
|
|
940
|
+
try {
|
|
941
|
+
setGroupActionLoading(true);
|
|
942
|
+
const response = await axios.patch(`/cgi-bin/interfaces?active=${active}`, {
|
|
943
|
+
featureId: selectedFeatureId,
|
|
944
|
+
group: selectedGroup
|
|
945
|
+
});
|
|
946
|
+
if (response.data && response.data.code === 0) {
|
|
947
|
+
message.success(`分组 "${selectedGroup}" 中的接口已成功${active ? '启用' : '禁用'}`);
|
|
948
|
+
fetchInterfaces();
|
|
949
|
+
} else {
|
|
950
|
+
throw new Error(response.data?.message || '批量操作失败');
|
|
951
|
+
}
|
|
952
|
+
} catch (error) {
|
|
953
|
+
console.error('批量操作失败:', error);
|
|
954
|
+
message.error(error.response?.data?.message || error.message || '批量操作失败');
|
|
955
|
+
} finally {
|
|
956
|
+
setGroupActionLoading(false);
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
|
|
778
960
|
return (
|
|
779
961
|
<AppLayout>
|
|
780
962
|
<div className="interface-management-container">
|
|
781
|
-
<
|
|
782
|
-
<div className="
|
|
783
|
-
<
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
{
|
|
795
|
-
<
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
963
|
+
<Card className="interface-header-card">
|
|
964
|
+
<div className="interface-management-header">
|
|
965
|
+
<div className="feature-selector-container">
|
|
966
|
+
<div className="feature-selector">
|
|
967
|
+
<span>功能模块:</span>
|
|
968
|
+
<Select
|
|
969
|
+
value={selectedFeatureId}
|
|
970
|
+
onChange={handleSelectFeature}
|
|
971
|
+
style={{ width: 240 }}
|
|
972
|
+
placeholder="选择功能模块"
|
|
973
|
+
loading={featuresLoading}
|
|
974
|
+
dropdownMatchSelectWidth={false}
|
|
975
|
+
>
|
|
976
|
+
{(features || []).map(feature => (
|
|
977
|
+
<Option key={feature.id} value={feature.id}>
|
|
978
|
+
{feature.name}
|
|
979
|
+
{feature.active === false &&
|
|
980
|
+
<span style={{ color: '#ff4d4f', marginLeft: 8 }}>(已禁用)</span>
|
|
981
|
+
}
|
|
982
|
+
</Option>
|
|
983
|
+
))}
|
|
984
|
+
</Select>
|
|
985
|
+
</div>
|
|
986
|
+
|
|
987
|
+
<Search
|
|
988
|
+
placeholder="搜索接口名称或URL"
|
|
989
|
+
onSearch={handleSearch}
|
|
990
|
+
value={searchValue}
|
|
991
|
+
onChange={(e) => setSearchValue(e.target.value)}
|
|
992
|
+
style={{ width: 250, marginLeft: 16 }}
|
|
993
|
+
enterButton={<SearchOutlined />}
|
|
994
|
+
allowClear
|
|
995
|
+
/>
|
|
996
|
+
</div>
|
|
997
|
+
|
|
998
|
+
<div className="interface-actions">
|
|
999
|
+
<Space>
|
|
1000
|
+
<Tooltip title="刷新数据">
|
|
1001
|
+
<Button
|
|
1002
|
+
icon={<ReloadOutlined />}
|
|
1003
|
+
onClick={refreshData}
|
|
1004
|
+
loading={interfacesLoading}
|
|
1005
|
+
/>
|
|
1006
|
+
</Tooltip>
|
|
1007
|
+
<Tooltip title={viewMode === 'table' ? '切换到卡片视图' : '切换到表格视图'}>
|
|
1008
|
+
<Button
|
|
1009
|
+
icon={viewMode === 'table' ? <AppstoreOutlined /> : <UnorderedListOutlined />}
|
|
1010
|
+
onClick={toggleViewMode}
|
|
1011
|
+
/>
|
|
1012
|
+
</Tooltip>
|
|
1013
|
+
<Button
|
|
1014
|
+
type="primary"
|
|
1015
|
+
icon={<PlusOutlined />}
|
|
1016
|
+
onClick={handleAddInterface}
|
|
1017
|
+
disabled={!selectedFeatureId || selectedFeature?.active === false}
|
|
1018
|
+
className="add-interface-button"
|
|
1019
|
+
>
|
|
1020
|
+
添加接口
|
|
1021
|
+
</Button>
|
|
1022
|
+
</Space>
|
|
1023
|
+
</div>
|
|
800
1024
|
</div>
|
|
801
|
-
|
|
802
|
-
type="primary"
|
|
803
|
-
icon={<PlusOutlined />}
|
|
804
|
-
onClick={handleAddInterface}
|
|
805
|
-
disabled={!selectedFeatureId || selectedFeature?.active === false}
|
|
806
|
-
>
|
|
807
|
-
添加接口
|
|
808
|
-
</Button>
|
|
809
|
-
</div>
|
|
1025
|
+
</Card>
|
|
810
1026
|
|
|
811
1027
|
{!features.length && (
|
|
812
1028
|
<Alert
|
|
@@ -828,33 +1044,129 @@ const InterfaceManagement = () => {
|
|
|
828
1044
|
/>
|
|
829
1045
|
)}
|
|
830
1046
|
|
|
1047
|
+
{/* 分组筛选和操作区域 */}
|
|
1048
|
+
<Card className="filter-card" bordered={false}>
|
|
1049
|
+
<div className="filter-header">
|
|
1050
|
+
<div className="filter-title">
|
|
1051
|
+
<FilterOutlined /> 分组筛选
|
|
1052
|
+
</div>
|
|
1053
|
+
{selectedGroup && (
|
|
1054
|
+
<Button
|
|
1055
|
+
type="link"
|
|
1056
|
+
onClick={() => setSelectedGroup(null)}
|
|
1057
|
+
size="small"
|
|
1058
|
+
>
|
|
1059
|
+
清除筛选
|
|
1060
|
+
</Button>
|
|
1061
|
+
)}
|
|
1062
|
+
</div>
|
|
1063
|
+
|
|
1064
|
+
<div className="group-tags-container">
|
|
1065
|
+
{groups.length === 0 ? (
|
|
1066
|
+
<div className="empty-groups">暂无分组</div>
|
|
1067
|
+
) : (
|
|
1068
|
+
<div className="group-tags">
|
|
1069
|
+
{groups.map(group => (
|
|
1070
|
+
<Tag
|
|
1071
|
+
key={group}
|
|
1072
|
+
color={selectedGroup === group ? "blue" : "default"}
|
|
1073
|
+
onClick={() => setSelectedGroup(group === selectedGroup ? null : group)}
|
|
1074
|
+
className={`group-tag ${selectedGroup === group ? 'active' : ''}`}
|
|
1075
|
+
>
|
|
1076
|
+
{group} ({interfaces.filter(item => item.group === group).length})
|
|
1077
|
+
</Tag>
|
|
1078
|
+
))}
|
|
1079
|
+
</div>
|
|
1080
|
+
)}
|
|
1081
|
+
</div>
|
|
1082
|
+
|
|
1083
|
+
{selectedGroup && (
|
|
1084
|
+
<div className="group-actions">
|
|
1085
|
+
<Popconfirm
|
|
1086
|
+
title={`确定要启用分组 "${selectedGroup}" 中的所有接口吗?`}
|
|
1087
|
+
onConfirm={() => handleBatchToggleActive(true)}
|
|
1088
|
+
okText="确定"
|
|
1089
|
+
cancelText="取消"
|
|
1090
|
+
>
|
|
1091
|
+
<Button
|
|
1092
|
+
type="primary"
|
|
1093
|
+
size="small"
|
|
1094
|
+
loading={groupActionLoading}
|
|
1095
|
+
>
|
|
1096
|
+
批量启用
|
|
1097
|
+
</Button>
|
|
1098
|
+
</Popconfirm>
|
|
1099
|
+
|
|
1100
|
+
<Popconfirm
|
|
1101
|
+
title={`确定要禁用分组 "${selectedGroup}" 中的所有接口吗?`}
|
|
1102
|
+
onConfirm={() => handleBatchToggleActive(false)}
|
|
1103
|
+
okText="确定"
|
|
1104
|
+
cancelText="取消"
|
|
1105
|
+
>
|
|
1106
|
+
<Button
|
|
1107
|
+
danger
|
|
1108
|
+
size="small"
|
|
1109
|
+
loading={groupActionLoading}
|
|
1110
|
+
>
|
|
1111
|
+
批量禁用
|
|
1112
|
+
</Button>
|
|
1113
|
+
</Popconfirm>
|
|
1114
|
+
</div>
|
|
1115
|
+
)}
|
|
1116
|
+
</Card>
|
|
1117
|
+
|
|
831
1118
|
{/* 接口列表状态栏 */}
|
|
832
|
-
{
|
|
833
|
-
<div
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
1119
|
+
<Card className="list-header-card" bordered={false}>
|
|
1120
|
+
<div className="list-header">
|
|
1121
|
+
<div className="list-header-info">
|
|
1122
|
+
<div className="feature-info">
|
|
1123
|
+
<span className="label">功能模块:</span>
|
|
1124
|
+
<span className="value">{selectedFeature?.name}</span>
|
|
1125
|
+
{selectedGroup && (
|
|
1126
|
+
<Tag color="blue" className="group-badge">{selectedGroup}</Tag>
|
|
1127
|
+
)}
|
|
1128
|
+
</div>
|
|
1129
|
+
<div className="interface-stats">
|
|
1130
|
+
{searchValue ? (
|
|
1131
|
+
<Badge
|
|
1132
|
+
count={`搜索"${searchValue}" - ${filteredInterfaces.length}个结果`}
|
|
1133
|
+
style={{ backgroundColor: '#108ee9' }}
|
|
1134
|
+
/>
|
|
1135
|
+
) : (
|
|
1136
|
+
<Badge
|
|
1137
|
+
count={`共 ${filteredInterfaces.length} 个接口`}
|
|
1138
|
+
style={{ backgroundColor: '#52c41a' }}
|
|
1139
|
+
/>
|
|
1140
|
+
)}
|
|
1141
|
+
</div>
|
|
1142
|
+
</div>
|
|
1143
|
+
<div className="batch-actions">
|
|
1144
|
+
{selectedRowKeys.length > 0 && (
|
|
1145
|
+
<Space>
|
|
1146
|
+
<span className="selected-count">
|
|
1147
|
+
已选择 {selectedRowKeys.length} 项
|
|
1148
|
+
</span>
|
|
1149
|
+
<Popconfirm
|
|
1150
|
+
title="确定要批量启用选中的接口吗?"
|
|
1151
|
+
onConfirm={() => handleBatchOperation(true)}
|
|
1152
|
+
okText="确定"
|
|
1153
|
+
cancelText="取消"
|
|
1154
|
+
>
|
|
1155
|
+
<Button size="small" type="primary">批量启用</Button>
|
|
1156
|
+
</Popconfirm>
|
|
1157
|
+
<Popconfirm
|
|
1158
|
+
title="确定要批量禁用选中的接口吗?"
|
|
1159
|
+
onConfirm={() => handleBatchOperation(false)}
|
|
1160
|
+
okText="确定"
|
|
1161
|
+
cancelText="取消"
|
|
1162
|
+
>
|
|
1163
|
+
<Button size="small" danger>批量禁用</Button>
|
|
1164
|
+
</Popconfirm>
|
|
1165
|
+
</Space>
|
|
854
1166
|
)}
|
|
855
|
-
</
|
|
1167
|
+
</div>
|
|
856
1168
|
</div>
|
|
857
|
-
|
|
1169
|
+
</Card>
|
|
858
1170
|
|
|
859
1171
|
{/* 列配置区域 */}
|
|
860
1172
|
<div style={{
|
|
@@ -919,26 +1231,156 @@ const InterfaceManagement = () => {
|
|
|
919
1231
|
</Popover>
|
|
920
1232
|
</div>
|
|
921
1233
|
|
|
922
|
-
<
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1234
|
+
<Card className="list-container-card" bordered={false} bodyStyle={{ padding: 0 }}>
|
|
1235
|
+
{/* 表格视图 */}
|
|
1236
|
+
{viewMode === 'table' && (
|
|
1237
|
+
<div className="interface-list-container">
|
|
1238
|
+
<Table
|
|
1239
|
+
columns={columns}
|
|
1240
|
+
dataSource={filteredInterfaces}
|
|
1241
|
+
rowKey="id"
|
|
1242
|
+
loading={interfacesLoading}
|
|
1243
|
+
onChange={handleTableChange}
|
|
1244
|
+
rowSelection={{
|
|
1245
|
+
selectedRowKeys,
|
|
1246
|
+
onChange: onSelectChange,
|
|
1247
|
+
}}
|
|
1248
|
+
size="middle"
|
|
1249
|
+
pagination={{
|
|
1250
|
+
current: tableConfig.current,
|
|
1251
|
+
pageSize: tableConfig.pageSize,
|
|
1252
|
+
showSizeChanger: true,
|
|
1253
|
+
showQuickJumper: true,
|
|
1254
|
+
showTotal: (total, range) => `共 ${total} 个接口,显示第 ${range[0]}-${range[1]} 个`,
|
|
1255
|
+
pageSizeOptions: ['10', '20', '50', '100'],
|
|
1256
|
+
size: 'default'
|
|
1257
|
+
}}
|
|
1258
|
+
locale={{ emptyText: '暂无接口配置' }}
|
|
1259
|
+
sortDirections={['ascend', 'descend']}
|
|
1260
|
+
className="interface-table"
|
|
1261
|
+
/>
|
|
1262
|
+
</div>
|
|
1263
|
+
)}
|
|
1264
|
+
|
|
1265
|
+
{/* 卡片视图 */}
|
|
1266
|
+
{viewMode === 'card' && (
|
|
1267
|
+
<div className="interface-card-view">
|
|
1268
|
+
<Row gutter={[16, 16]} style={{ padding: 16 }}>
|
|
1269
|
+
{interfacesLoading ? (
|
|
1270
|
+
[1,2,3,4,5,6].map(i => (
|
|
1271
|
+
<Col xs={24} sm={12} md={8} lg={8} xl={6} key={`loading-${i}`}>
|
|
1272
|
+
<Card loading style={{ height: 180 }} />
|
|
1273
|
+
</Col>
|
|
1274
|
+
))
|
|
1275
|
+
) : filteredInterfaces.length === 0 ? (
|
|
1276
|
+
<Col span={24}>
|
|
1277
|
+
<div className="empty-data">暂无接口配置</div>
|
|
1278
|
+
</Col>
|
|
1279
|
+
) : (
|
|
1280
|
+
filteredInterfaces.map(item => (
|
|
1281
|
+
<Col xs={24} sm={12} md={8} lg={8} xl={6} key={item.id}>
|
|
1282
|
+
<Card
|
|
1283
|
+
className={`interface-card ${item.active === false ? 'inactive' : ''}`}
|
|
1284
|
+
hoverable
|
|
1285
|
+
>
|
|
1286
|
+
<div className="interface-card-header">
|
|
1287
|
+
<Switch
|
|
1288
|
+
checked={item.active !== false}
|
|
1289
|
+
onChange={() => handleToggleActive(item.id, item.active)}
|
|
1290
|
+
size="small"
|
|
1291
|
+
className="interface-card-switch"
|
|
1292
|
+
/>
|
|
1293
|
+
<div className="interface-card-title">{item.name}</div>
|
|
1294
|
+
{item.group && (
|
|
1295
|
+
<Tag
|
|
1296
|
+
color="blue"
|
|
1297
|
+
className="interface-card-group"
|
|
1298
|
+
onClick={() => setSelectedGroup(item.group)}
|
|
1299
|
+
>
|
|
1300
|
+
{item.group}
|
|
1301
|
+
</Tag>
|
|
1302
|
+
)}
|
|
1303
|
+
</div>
|
|
1304
|
+
|
|
1305
|
+
<div className="interface-card-content">
|
|
1306
|
+
<div className="url-pattern-container">
|
|
1307
|
+
<Tooltip title={item.urlPattern}>
|
|
1308
|
+
<div className="url-pattern">{item.urlPattern}</div>
|
|
1309
|
+
</Tooltip>
|
|
1310
|
+
</div>
|
|
1311
|
+
|
|
1312
|
+
<div className="interface-card-info">
|
|
1313
|
+
<div className="info-item">
|
|
1314
|
+
<span className="info-label">处理方式:</span>
|
|
1315
|
+
<Badge
|
|
1316
|
+
color={proxyTypes.find(t => t.value === item.proxyType)?.color || '#999'}
|
|
1317
|
+
text={proxyTypes.find(t => t.value === item.proxyType)?.label || item.proxyType}
|
|
1318
|
+
className="info-value"
|
|
1319
|
+
/>
|
|
1320
|
+
</div>
|
|
1321
|
+
<div className="info-item">
|
|
1322
|
+
<span className="info-label">状态码:</span>
|
|
1323
|
+
<span className={`status-badge status-${item.httpStatus >= 400 ? 'error' : item.httpStatus >= 300 ? 'warning' : 'success'}`}>
|
|
1324
|
+
{item.httpStatus || 200}
|
|
1325
|
+
</span>
|
|
1326
|
+
</div>
|
|
1327
|
+
</div>
|
|
1328
|
+
</div>
|
|
1329
|
+
|
|
1330
|
+
<div className="interface-card-actions">
|
|
1331
|
+
<Button
|
|
1332
|
+
type="primary"
|
|
1333
|
+
size="small"
|
|
1334
|
+
icon={<EditOutlined />}
|
|
1335
|
+
onClick={() => handleEditInterface(item)}
|
|
1336
|
+
>
|
|
1337
|
+
编辑
|
|
1338
|
+
</Button>
|
|
1339
|
+
<Popconfirm
|
|
1340
|
+
title="确定要删除此接口吗?"
|
|
1341
|
+
onConfirm={() => handleDeleteInterface(item.id)}
|
|
1342
|
+
okText="确定"
|
|
1343
|
+
cancelText="取消"
|
|
1344
|
+
>
|
|
1345
|
+
<Button
|
|
1346
|
+
danger
|
|
1347
|
+
size="small"
|
|
1348
|
+
icon={<DeleteOutlined />}
|
|
1349
|
+
>
|
|
1350
|
+
删除
|
|
1351
|
+
</Button>
|
|
1352
|
+
</Popconfirm>
|
|
1353
|
+
</div>
|
|
1354
|
+
</Card>
|
|
1355
|
+
</Col>
|
|
1356
|
+
))
|
|
1357
|
+
)}
|
|
1358
|
+
</Row>
|
|
1359
|
+
|
|
1360
|
+
{/* 卡片视图分页 */}
|
|
1361
|
+
{filteredInterfaces.length > 0 && (
|
|
1362
|
+
<div className="card-pagination">
|
|
1363
|
+
<Pagination
|
|
1364
|
+
current={tableConfig.current}
|
|
1365
|
+
pageSize={tableConfig.pageSize}
|
|
1366
|
+
total={filteredInterfaces.length}
|
|
1367
|
+
showSizeChanger
|
|
1368
|
+
showQuickJumper
|
|
1369
|
+
showTotal={(total, range) => `共 ${total} 个接口,显示第 ${range[0]}-${range[1]} 个`}
|
|
1370
|
+
pageSizeOptions={['10', '20', '50', '100']}
|
|
1371
|
+
size="default"
|
|
1372
|
+
onChange={(page, pageSize) => {
|
|
1373
|
+
saveTableConfig({
|
|
1374
|
+
current: page,
|
|
1375
|
+
pageSize: pageSize
|
|
1376
|
+
});
|
|
1377
|
+
}}
|
|
1378
|
+
/>
|
|
1379
|
+
</div>
|
|
1380
|
+
)}
|
|
1381
|
+
</div>
|
|
1382
|
+
)}
|
|
1383
|
+
</Card>
|
|
942
1384
|
|
|
943
1385
|
{/* 接口编辑/创建模态框 */}
|
|
944
1386
|
<Modal
|
|
@@ -957,6 +1399,7 @@ const InterfaceManagement = () => {
|
|
|
957
1399
|
layout="vertical"
|
|
958
1400
|
initialValues={{
|
|
959
1401
|
name: '',
|
|
1402
|
+
group: '',
|
|
960
1403
|
pattern: '',
|
|
961
1404
|
proxyType: 'response',
|
|
962
1405
|
statusCode: '200',
|
|
@@ -969,7 +1412,7 @@ const InterfaceManagement = () => {
|
|
|
969
1412
|
responseDelay: '0'
|
|
970
1413
|
}}
|
|
971
1414
|
>
|
|
972
|
-
{/*
|
|
1415
|
+
{/* 基础信息表单项 */}
|
|
973
1416
|
<div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
|
|
974
1417
|
<div style={{ flex: 1 }}>
|
|
975
1418
|
<Form.Item
|
|
@@ -980,6 +1423,44 @@ const InterfaceManagement = () => {
|
|
|
980
1423
|
<Input placeholder="请输入接口名称" />
|
|
981
1424
|
</Form.Item>
|
|
982
1425
|
</div>
|
|
1426
|
+
<div style={{ flex: 1 }}>
|
|
1427
|
+
<Form.Item
|
|
1428
|
+
name="group"
|
|
1429
|
+
label="分组"
|
|
1430
|
+
tooltip="为接口设置分组,便于管理和批量操作"
|
|
1431
|
+
>
|
|
1432
|
+
<Select
|
|
1433
|
+
placeholder="选择或创建分组"
|
|
1434
|
+
showSearch
|
|
1435
|
+
allowClear
|
|
1436
|
+
style={{ width: '100%' }}
|
|
1437
|
+
mode="tags"
|
|
1438
|
+
maxTagCount="responsive"
|
|
1439
|
+
maxTagTextLength={10}
|
|
1440
|
+
onChange={(value) => {
|
|
1441
|
+
// 如果值是数组(tags模式),只取第一个元素
|
|
1442
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
1443
|
+
// 如果第一个元素是空字符串,则设置为undefined
|
|
1444
|
+
const groupValue = value[0] === '' ? undefined : value[0];
|
|
1445
|
+
form.setFieldsValue({ group: groupValue });
|
|
1446
|
+
}
|
|
1447
|
+
}}
|
|
1448
|
+
onInputKeyDown={(e) => {
|
|
1449
|
+
// 防止回车键提交表单
|
|
1450
|
+
if (e.key === 'Enter') {
|
|
1451
|
+
e.stopPropagation();
|
|
1452
|
+
}
|
|
1453
|
+
}}
|
|
1454
|
+
>
|
|
1455
|
+
{Array.from(new Set(interfaces.map(item => item.group).filter(Boolean))).map(group => (
|
|
1456
|
+
<Option key={group} value={group}>{group}</Option>
|
|
1457
|
+
))}
|
|
1458
|
+
</Select>
|
|
1459
|
+
</Form.Item>
|
|
1460
|
+
</div>
|
|
1461
|
+
</div>
|
|
1462
|
+
|
|
1463
|
+
<div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
|
|
983
1464
|
<div style={{ flex: 1 }}>
|
|
984
1465
|
<Form.Item
|
|
985
1466
|
name="pattern"
|
|
@@ -1059,9 +1540,6 @@ const InterfaceManagement = () => {
|
|
|
1059
1540
|
/>
|
|
1060
1541
|
</Form.Item>
|
|
1061
1542
|
</div>
|
|
1062
|
-
</div>
|
|
1063
|
-
|
|
1064
|
-
<div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
|
|
1065
1543
|
<div style={{ flex: 1 }}>
|
|
1066
1544
|
<Form.Item
|
|
1067
1545
|
name="proxyType"
|
|
@@ -1089,7 +1567,7 @@ const InterfaceManagement = () => {
|
|
|
1089
1567
|
const patternInput = document.querySelector('input[placeholder="根据选择的处理方式输入相应格式的URL"]');
|
|
1090
1568
|
if (patternInput) {
|
|
1091
1569
|
if (value === 'response') {
|
|
1092
|
-
patternInput.placeholder = "例如:/api/users,/api/users/*,/api
|
|
1570
|
+
patternInput.placeholder = "例如:/api/users,/api/users/*,/api\\/users\\/\\d+/";
|
|
1093
1571
|
} else if (value === 'redirect') {
|
|
1094
1572
|
patternInput.placeholder = "例如:https://example.com/api";
|
|
1095
1573
|
} else if (value === 'url_redirect') {
|
|
@@ -1103,6 +1581,9 @@ const InterfaceManagement = () => {
|
|
|
1103
1581
|
</Select>
|
|
1104
1582
|
</Form.Item>
|
|
1105
1583
|
</div>
|
|
1584
|
+
</div>
|
|
1585
|
+
|
|
1586
|
+
<div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
|
|
1106
1587
|
<div style={{ flex: 1 }}>
|
|
1107
1588
|
<Form.Item
|
|
1108
1589
|
name="httpMethod"
|
|
@@ -1116,6 +1597,9 @@ const InterfaceManagement = () => {
|
|
|
1116
1597
|
</Select>
|
|
1117
1598
|
</Form.Item>
|
|
1118
1599
|
</div>
|
|
1600
|
+
<div style={{ flex: 1 }}>
|
|
1601
|
+
{/* 这里可以留空或添加其他字段 */}
|
|
1602
|
+
</div>
|
|
1119
1603
|
</div>
|
|
1120
1604
|
|
|
1121
1605
|
{/* 根据proxyType显示不同的表单项 */}
|