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
|
@@ -10,12 +10,12 @@ import AppLayout from '../components/AppLayout';
|
|
|
10
10
|
import {
|
|
11
11
|
Table, Button, Modal, Form, Input, Select, message, Switch,
|
|
12
12
|
Popconfirm, Alert, Space, Card, Badge, Tooltip, Row, Col,
|
|
13
|
-
Popover, Checkbox, Tag, Input as AntInput, Radio, Drawer, Dropdown, Menu, Collapse
|
|
13
|
+
Popover, Checkbox, Tag, Input as AntInput, Radio, Drawer, Dropdown, Menu, Collapse
|
|
14
14
|
} from 'antd';
|
|
15
15
|
import {
|
|
16
16
|
PlusOutlined, EditOutlined, DeleteOutlined,
|
|
17
17
|
FileTextOutlined, PlusCircleOutlined, SettingOutlined,
|
|
18
|
-
SearchOutlined, FilterOutlined, ReloadOutlined, DownOutlined, QuestionCircleOutlined
|
|
18
|
+
SearchOutlined, FilterOutlined, ReloadOutlined, DownOutlined, QuestionCircleOutlined, CopyOutlined
|
|
19
19
|
} from '@ant-design/icons';
|
|
20
20
|
import '../styles/interface-management.css';
|
|
21
21
|
import axios from 'axios';
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
formatResponseContent,
|
|
33
33
|
generateResponseId
|
|
34
34
|
} from '../components/interface-management';
|
|
35
|
+
import DataManagementModal from '../components/interface-management/DataManagementModal';
|
|
35
36
|
|
|
36
37
|
const { Option } = Select;
|
|
37
38
|
const { Search } = AntInput;
|
|
@@ -70,11 +71,13 @@ const InterfaceManagement = () => {
|
|
|
70
71
|
const [groups, setGroups] = useState([]);
|
|
71
72
|
const [selectedGroup, setSelectedGroup] = useState(null);
|
|
72
73
|
const [groupActionLoading, setGroupActionLoading] = useState(false);
|
|
73
|
-
const [newGroupName, setNewGroupName] = useState('');
|
|
74
74
|
|
|
75
75
|
// 模态框状态
|
|
76
76
|
const [modalVisible, setModalVisible] = useState(false);
|
|
77
77
|
const [previewVisible, setPreviewVisible] = useState(false);
|
|
78
|
+
const [isDuplicateMode, setIsDuplicateMode] = useState(false);
|
|
79
|
+
const [dataModalVisible, setDataModalVisible] = useState(false);
|
|
80
|
+
const [dataModalRecord, setDataModalRecord] = useState(null);
|
|
78
81
|
|
|
79
82
|
// 列配置状态
|
|
80
83
|
const [columnConfigVisible, setColumnConfigVisible] = useState(false);
|
|
@@ -223,17 +226,6 @@ const InterfaceManagement = () => {
|
|
|
223
226
|
setSelectedGroup(null);
|
|
224
227
|
}
|
|
225
228
|
};
|
|
226
|
-
|
|
227
|
-
// 添加新分组
|
|
228
|
-
const addNewGroup = () => {
|
|
229
|
-
if (newGroupName.trim() && !groups.includes(newGroupName.trim())) {
|
|
230
|
-
const updatedGroups = [...groups, newGroupName.trim()].sort();
|
|
231
|
-
setGroups(updatedGroups);
|
|
232
|
-
setNewGroupName('');
|
|
233
|
-
return newGroupName.trim();
|
|
234
|
-
}
|
|
235
|
-
return null;
|
|
236
|
-
};
|
|
237
229
|
|
|
238
230
|
// 根据分组和搜索关键词筛选接口
|
|
239
231
|
const getFilteredInterfaces = () => {
|
|
@@ -265,6 +257,7 @@ const InterfaceManagement = () => {
|
|
|
265
257
|
return;
|
|
266
258
|
}
|
|
267
259
|
form.resetFields();
|
|
260
|
+
setIsDuplicateMode(false);
|
|
268
261
|
|
|
269
262
|
// 创建默认响应
|
|
270
263
|
const defaultResponseId = generateResponseId();
|
|
@@ -304,6 +297,7 @@ const InterfaceManagement = () => {
|
|
|
304
297
|
|
|
305
298
|
// 重置表单
|
|
306
299
|
form.resetFields();
|
|
300
|
+
setIsDuplicateMode(false);
|
|
307
301
|
|
|
308
302
|
// 处理响应数据
|
|
309
303
|
let responses = [];
|
|
@@ -363,6 +357,91 @@ const InterfaceManagement = () => {
|
|
|
363
357
|
setModalVisible(true);
|
|
364
358
|
};
|
|
365
359
|
|
|
360
|
+
// 复制新增接口
|
|
361
|
+
const handleDuplicate = (record) => {
|
|
362
|
+
if (!selectedFeatureId) {
|
|
363
|
+
message.warning('请先选择一个功能模块');
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (!record) {
|
|
367
|
+
message.warning('接口数据不完整,无法复制');
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
form.resetFields();
|
|
372
|
+
|
|
373
|
+
// 深拷贝源数据
|
|
374
|
+
const source = JSON.parse(JSON.stringify(record));
|
|
375
|
+
|
|
376
|
+
// 处理响应数据
|
|
377
|
+
let responses = [];
|
|
378
|
+
let activeResponseId = '';
|
|
379
|
+
|
|
380
|
+
if (Array.isArray(source.responses) && source.responses.length > 0) {
|
|
381
|
+
// 为每条响应生成新的ID,并重映射activeResponseId
|
|
382
|
+
const idMap = new Map();
|
|
383
|
+
responses = source.responses.map((resp) => {
|
|
384
|
+
const newId = generateResponseId();
|
|
385
|
+
idMap.set(resp.id, newId);
|
|
386
|
+
return {
|
|
387
|
+
id: newId,
|
|
388
|
+
name: resp.name || '未命名响应',
|
|
389
|
+
description: resp.description || '',
|
|
390
|
+
content: resp.content || '{}'
|
|
391
|
+
};
|
|
392
|
+
});
|
|
393
|
+
const mapped = idMap.get(source.activeResponseId) || responses[0]?.id;
|
|
394
|
+
activeResponseId = mapped || '';
|
|
395
|
+
} else if (source.responseContent) {
|
|
396
|
+
// 兼容旧数据格式
|
|
397
|
+
const defaultResponseId = generateResponseId();
|
|
398
|
+
responses = [{
|
|
399
|
+
id: defaultResponseId,
|
|
400
|
+
name: '默认响应',
|
|
401
|
+
description: '',
|
|
402
|
+
content: source.responseContent
|
|
403
|
+
}];
|
|
404
|
+
activeResponseId = defaultResponseId;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// 处理自定义请求头 -> 表单需要的数组结构
|
|
408
|
+
let headerItems = [];
|
|
409
|
+
if (source.headers && typeof source.headers === 'object') {
|
|
410
|
+
headerItems = Object.entries(source.headers).map(([headerName, headerValue]) => ({
|
|
411
|
+
headerName,
|
|
412
|
+
headerValue
|
|
413
|
+
}));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// 分组值保证为字符串
|
|
417
|
+
let groupValue = source.group;
|
|
418
|
+
if (Array.isArray(groupValue)) {
|
|
419
|
+
groupValue = groupValue.length > 0 ? groupValue[0] : undefined;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// 设定表单初值
|
|
423
|
+
form.setFieldsValue({
|
|
424
|
+
name: `${source.name || '未命名接口'} - 副本`,
|
|
425
|
+
group: groupValue || undefined,
|
|
426
|
+
pattern: source.urlPattern,
|
|
427
|
+
proxyType: source.proxyType || 'response',
|
|
428
|
+
statusCode: (source.httpStatus || source.statusCode || 200).toString(),
|
|
429
|
+
contentType: source.contentType || 'application/json; charset=utf-8',
|
|
430
|
+
responses,
|
|
431
|
+
activeResponseId,
|
|
432
|
+
httpMethod: source.httpMethod || source.method || 'ALL',
|
|
433
|
+
targetUrl: source.targetUrl || '',
|
|
434
|
+
headerItems,
|
|
435
|
+
paramMatchers: Array.isArray(source.paramMatchers) ? source.paramMatchers : [],
|
|
436
|
+
responseDelay: source.responseDelay ? String(source.responseDelay) : '0'
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
setCurrentResponseId(activeResponseId || null);
|
|
440
|
+
setEditingInterface(null); // 新增模式
|
|
441
|
+
setIsDuplicateMode(true);
|
|
442
|
+
setModalVisible(true);
|
|
443
|
+
};
|
|
444
|
+
|
|
366
445
|
const handleDeleteInterface = async (id) => {
|
|
367
446
|
try {
|
|
368
447
|
const response = await axios.delete(`/cgi-bin/interfaces?id=${id}`);
|
|
@@ -403,8 +482,9 @@ const InterfaceManagement = () => {
|
|
|
403
482
|
|
|
404
483
|
const handleSubmit = async () => {
|
|
405
484
|
try {
|
|
406
|
-
// 表单验证
|
|
407
|
-
|
|
485
|
+
// 表单验证 - 先验证所有字段,再获取所有字段值(包括折叠的)
|
|
486
|
+
await form.validateFields();
|
|
487
|
+
const values = form.getFieldsValue(true); // true表示获取所有字段,包括未渲染的
|
|
408
488
|
console.log('表单提交数据:', values);
|
|
409
489
|
|
|
410
490
|
// 处理自定义请求头
|
|
@@ -423,19 +503,19 @@ const InterfaceManagement = () => {
|
|
|
423
503
|
groupValue = groupValue.length > 0 ? groupValue[0] : '';
|
|
424
504
|
}
|
|
425
505
|
|
|
426
|
-
// 构建接口数据
|
|
506
|
+
// 构建接口数据 - 在编辑模式下,确保关键字段不丢失
|
|
427
507
|
const interfaceData = {
|
|
428
|
-
name: values.name,
|
|
429
|
-
group: groupValue || '', // 确保group不为undefined
|
|
430
|
-
urlPattern: values.pattern,
|
|
431
|
-
proxyType: values.proxyType,
|
|
508
|
+
name: values.name || (editingInterface ? editingInterface.name : ''),
|
|
509
|
+
group: groupValue || (editingInterface ? editingInterface.group : '') || '', // 确保group不为undefined
|
|
510
|
+
urlPattern: values.pattern || (editingInterface ? editingInterface.urlPattern : ''),
|
|
511
|
+
proxyType: values.proxyType || (editingInterface ? editingInterface.proxyType : 'response'),
|
|
432
512
|
featureId: selectedFeatureId,
|
|
433
|
-
responses: values.responses,
|
|
434
|
-
activeResponseId: values.activeResponseId,
|
|
435
|
-
httpStatus: parseInt(values.statusCode, 10), // 转换为数字
|
|
436
|
-
contentType: values.contentType,
|
|
437
|
-
responseDelay: parseInt(values.responseDelay, 10) || 0,
|
|
438
|
-
httpMethod: values.httpMethod,
|
|
513
|
+
responses: values.responses || (editingInterface ? editingInterface.responses : []),
|
|
514
|
+
activeResponseId: values.activeResponseId || (editingInterface ? editingInterface.activeResponseId : null),
|
|
515
|
+
httpStatus: parseInt(values.statusCode, 10) || (editingInterface ? editingInterface.httpStatus : 200), // 转换为数字
|
|
516
|
+
contentType: values.contentType || (editingInterface ? editingInterface.contentType : 'application/json; charset=utf-8'),
|
|
517
|
+
responseDelay: parseInt(values.responseDelay, 10) || (editingInterface ? editingInterface.responseDelay : 0) || 0,
|
|
518
|
+
httpMethod: values.httpMethod || (editingInterface ? editingInterface.httpMethod : 'ALL'),
|
|
439
519
|
active: true
|
|
440
520
|
};
|
|
441
521
|
|
|
@@ -447,13 +527,11 @@ const InterfaceManagement = () => {
|
|
|
447
527
|
interfaceData.headers = headers;
|
|
448
528
|
}
|
|
449
529
|
|
|
450
|
-
//
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
);
|
|
456
|
-
}
|
|
530
|
+
// 添加参数匹配规则(始终发送,空数组用于清空后端旧值)
|
|
531
|
+
const filteredParamMatchers = Array.isArray(values.paramMatchers)
|
|
532
|
+
? values.paramMatchers.filter(matcher => matcher && matcher.paramPath && matcher.paramValue)
|
|
533
|
+
: [];
|
|
534
|
+
interfaceData.paramMatchers = filteredParamMatchers;
|
|
457
535
|
|
|
458
536
|
console.log('提交接口数据:', interfaceData);
|
|
459
537
|
|
|
@@ -494,6 +572,7 @@ const InterfaceManagement = () => {
|
|
|
494
572
|
|
|
495
573
|
const handleCancel = () => {
|
|
496
574
|
setModalVisible(false);
|
|
575
|
+
setIsDuplicateMode(false);
|
|
497
576
|
};
|
|
498
577
|
|
|
499
578
|
const handleSelectFeature = (featureId) => {
|
|
@@ -923,7 +1002,7 @@ const InterfaceManagement = () => {
|
|
|
923
1002
|
{
|
|
924
1003
|
title: '操作',
|
|
925
1004
|
key: 'action',
|
|
926
|
-
width:
|
|
1005
|
+
width: 220,
|
|
927
1006
|
render: (_, record) => (
|
|
928
1007
|
<div className="action-buttons">
|
|
929
1008
|
<Button
|
|
@@ -931,6 +1010,22 @@ const InterfaceManagement = () => {
|
|
|
931
1010
|
icon={<EditOutlined />}
|
|
932
1011
|
onClick={() => handleEditInterface(record)}
|
|
933
1012
|
/>
|
|
1013
|
+
{record.proxyType === 'response' && (
|
|
1014
|
+
<Tooltip title="数据管理">
|
|
1015
|
+
<Button
|
|
1016
|
+
type="text"
|
|
1017
|
+
icon={<FileTextOutlined />}
|
|
1018
|
+
onClick={() => { setDataModalRecord(record); setDataModalVisible(true); }}
|
|
1019
|
+
/>
|
|
1020
|
+
</Tooltip>
|
|
1021
|
+
)}
|
|
1022
|
+
<Tooltip title="复制新增">
|
|
1023
|
+
<Button
|
|
1024
|
+
type="text"
|
|
1025
|
+
icon={<CopyOutlined />}
|
|
1026
|
+
onClick={() => handleDuplicate(record)}
|
|
1027
|
+
/>
|
|
1028
|
+
</Tooltip>
|
|
934
1029
|
<Popconfirm
|
|
935
1030
|
title="确定要删除此接口吗?"
|
|
936
1031
|
onConfirm={() => handleDeleteInterface(record.id)}
|
|
@@ -1283,7 +1378,7 @@ const InterfaceManagement = () => {
|
|
|
1283
1378
|
alignItems: 'center',
|
|
1284
1379
|
gap: '8px'
|
|
1285
1380
|
}}>
|
|
1286
|
-
<span>{editingInterface ? '编辑接口' : '添加接口'}</span>
|
|
1381
|
+
<span>{editingInterface ? '编辑接口' : (isDuplicateMode ? '复制新增接口' : '添加接口')}</span>
|
|
1287
1382
|
<Badge
|
|
1288
1383
|
count="配置向导"
|
|
1289
1384
|
style={{
|
|
@@ -1332,8 +1427,8 @@ const InterfaceManagement = () => {
|
|
|
1332
1427
|
<Collapse
|
|
1333
1428
|
defaultActiveKey={
|
|
1334
1429
|
editingInterface && editingInterface.proxyType === 'response'
|
|
1335
|
-
? ['3']
|
|
1336
|
-
: ['1', '2', '3']
|
|
1430
|
+
? ['3'] // mock代理场景:只展开第3步(高级配置)
|
|
1431
|
+
: ['1', '2', '3'] // 其他场景:展开所有步骤
|
|
1337
1432
|
}
|
|
1338
1433
|
style={{ marginBottom: '16px' }}
|
|
1339
1434
|
ghost
|
|
@@ -1365,55 +1460,27 @@ const InterfaceManagement = () => {
|
|
|
1365
1460
|
tooltip="为接口设置分组,便于管理和批量操作"
|
|
1366
1461
|
>
|
|
1367
1462
|
<Select
|
|
1368
|
-
placeholder="
|
|
1463
|
+
placeholder="选择或创建分组"
|
|
1369
1464
|
showSearch
|
|
1370
1465
|
allowClear
|
|
1371
1466
|
style={{ width: '100%' }}
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1467
|
+
mode="tags"
|
|
1468
|
+
maxTagCount="responsive"
|
|
1469
|
+
maxTagTextLength={10}
|
|
1470
|
+
onChange={(value) => {
|
|
1471
|
+
// 如果值是数组(tags模式),只取第一个元素
|
|
1472
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
1473
|
+
// 如果第一个元素是空字符串,则设置为undefined
|
|
1474
|
+
const groupValue = value[0] === '' ? undefined : value[0];
|
|
1475
|
+
form.setFieldsValue({ group: groupValue });
|
|
1476
|
+
}
|
|
1477
|
+
}}
|
|
1376
1478
|
onInputKeyDown={(e) => {
|
|
1377
1479
|
// 防止回车键提交表单
|
|
1378
1480
|
if (e.key === 'Enter') {
|
|
1379
1481
|
e.stopPropagation();
|
|
1380
1482
|
}
|
|
1381
1483
|
}}
|
|
1382
|
-
dropdownRender={(menu) => (
|
|
1383
|
-
<div>
|
|
1384
|
-
{menu}
|
|
1385
|
-
<Divider style={{ margin: '4px 0' }} />
|
|
1386
|
-
<div style={{ display: 'flex', flexWrap: 'nowrap', padding: 8 }}>
|
|
1387
|
-
<Input
|
|
1388
|
-
style={{ flex: 'auto' }}
|
|
1389
|
-
value={newGroupName}
|
|
1390
|
-
onChange={(e) => setNewGroupName(e.target.value)}
|
|
1391
|
-
placeholder="输入新分组名称"
|
|
1392
|
-
onPressEnter={(e) => {
|
|
1393
|
-
e.stopPropagation();
|
|
1394
|
-
const groupName = addNewGroup();
|
|
1395
|
-
if (groupName) {
|
|
1396
|
-
form.setFieldsValue({ group: groupName });
|
|
1397
|
-
}
|
|
1398
|
-
}}
|
|
1399
|
-
/>
|
|
1400
|
-
<Button
|
|
1401
|
-
type="text"
|
|
1402
|
-
icon={<PlusOutlined />}
|
|
1403
|
-
style={{ flex: 'none', padding: '4px 8px', cursor: 'pointer' }}
|
|
1404
|
-
onClick={(e) => {
|
|
1405
|
-
e.stopPropagation();
|
|
1406
|
-
const groupName = addNewGroup();
|
|
1407
|
-
if (groupName) {
|
|
1408
|
-
form.setFieldsValue({ group: groupName });
|
|
1409
|
-
}
|
|
1410
|
-
}}
|
|
1411
|
-
>
|
|
1412
|
-
添加
|
|
1413
|
-
</Button>
|
|
1414
|
-
</div>
|
|
1415
|
-
</div>
|
|
1416
|
-
)}
|
|
1417
1484
|
>
|
|
1418
1485
|
{Array.from(new Set(interfaces.map(item => item.group).filter(Boolean))).map(group => (
|
|
1419
1486
|
<Option key={group} value={group}>{group}</Option>
|
|
@@ -1871,6 +1938,16 @@ const InterfaceManagement = () => {
|
|
|
1871
1938
|
onCancel={() => setPreviewVisible(false)}
|
|
1872
1939
|
previewContent={previewContent}
|
|
1873
1940
|
/>
|
|
1941
|
+
|
|
1942
|
+
{/* 数据管理弹窗 */}
|
|
1943
|
+
<DataManagementModal
|
|
1944
|
+
visible={dataModalVisible}
|
|
1945
|
+
onCancel={() => { setDataModalVisible(false); setDataModalRecord(null); }}
|
|
1946
|
+
interfaceItem={dataModalRecord}
|
|
1947
|
+
onSaved={() => {
|
|
1948
|
+
fetchInterfaces();
|
|
1949
|
+
}}
|
|
1950
|
+
/>
|
|
1874
1951
|
</div>
|
|
1875
1952
|
</AppLayout>
|
|
1876
1953
|
);
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import React, { useState, useEffect } from 'react';
|
|
7
7
|
import { useHistory } from 'react-router-dom';
|
|
8
8
|
import AppLayout from '../components/AppLayout';
|
|
9
|
-
import { Card, Row, Col, Button, Modal, Form, Input, message, Switch, Empty, Spin, Typography, Tooltip, Badge, Select, Pagination
|
|
10
|
-
import { PlusOutlined, EditOutlined, DeleteOutlined, ApiOutlined, ExportOutlined, InfoCircleOutlined, CalendarOutlined, SortAscendingOutlined
|
|
9
|
+
import { Card, Row, Col, Button, Modal, Form, Input, message, Switch, Empty, Spin, Typography, Tooltip, Badge, Select, Pagination } from 'antd';
|
|
10
|
+
import { PlusOutlined, EditOutlined, DeleteOutlined, ApiOutlined, ExportOutlined, InfoCircleOutlined, CalendarOutlined, SortAscendingOutlined } from '@ant-design/icons';
|
|
11
11
|
import '../styles/mock-data.css';
|
|
12
12
|
import axios from 'axios';
|
|
13
13
|
|
|
@@ -34,12 +34,6 @@ const MockData = () => {
|
|
|
34
34
|
const [loading, setLoading] = useState(true);
|
|
35
35
|
const [form] = Form.useForm();
|
|
36
36
|
|
|
37
|
-
// 视图模式配置(卡片/列表)
|
|
38
|
-
const [viewMode, setViewMode] = useState(() => {
|
|
39
|
-
const savedViewMode = localStorage.getItem('feature-view-mode');
|
|
40
|
-
return savedViewMode === 'list' ? 'list' : 'card'; // 默认为卡片视图
|
|
41
|
-
});
|
|
42
|
-
|
|
43
37
|
// 排序和分页配置(支持缓存)
|
|
44
38
|
const [listConfig, setListConfig] = useState(() => {
|
|
45
39
|
const cached = localStorage.getItem('feature-list-config');
|
|
@@ -57,13 +51,6 @@ const MockData = () => {
|
|
|
57
51
|
setListConfig(newConfig);
|
|
58
52
|
localStorage.setItem('feature-list-config', JSON.stringify(newConfig));
|
|
59
53
|
};
|
|
60
|
-
|
|
61
|
-
// 切换视图模式(卡片/列表)
|
|
62
|
-
const toggleViewMode = () => {
|
|
63
|
-
const newMode = viewMode === 'card' ? 'list' : 'card';
|
|
64
|
-
setViewMode(newMode);
|
|
65
|
-
localStorage.setItem('feature-view-mode', newMode);
|
|
66
|
-
};
|
|
67
54
|
|
|
68
55
|
// 加载功能列表
|
|
69
56
|
useEffect(() => {
|
|
@@ -426,98 +413,6 @@ const MockData = () => {
|
|
|
426
413
|
return sortedFeatures.slice(startIndex, endIndex);
|
|
427
414
|
};
|
|
428
415
|
|
|
429
|
-
// 渲染功能模块列表项
|
|
430
|
-
const renderFeatureListItem = (feature) => {
|
|
431
|
-
const formattedDate = feature.createdAt
|
|
432
|
-
? new Date(feature.createdAt).toLocaleDateString()
|
|
433
|
-
: '未知日期';
|
|
434
|
-
|
|
435
|
-
return (
|
|
436
|
-
<div
|
|
437
|
-
key={feature.id}
|
|
438
|
-
className={`feature-list-item ${!feature.active ? 'inactive-feature' : ''}`}
|
|
439
|
-
onClick={() => viewInterfaces(feature)}
|
|
440
|
-
>
|
|
441
|
-
<div className="feature-list-content">
|
|
442
|
-
<div className="feature-list-main">
|
|
443
|
-
<div className="feature-list-name">
|
|
444
|
-
<h3>{feature.name}</h3>
|
|
445
|
-
{!feature.active && <Tag color="red">已禁用</Tag>}
|
|
446
|
-
{feature.active && <Tag color="green">已启用</Tag>}
|
|
447
|
-
</div>
|
|
448
|
-
<div className="feature-list-description">
|
|
449
|
-
{feature.description || '无描述'}
|
|
450
|
-
</div>
|
|
451
|
-
</div>
|
|
452
|
-
|
|
453
|
-
<div className="feature-list-meta">
|
|
454
|
-
<div className="feature-list-stats">
|
|
455
|
-
<span><InfoCircleOutlined /> {feature.interfaceCount || 0} 个接口</span>
|
|
456
|
-
<span><CalendarOutlined /> {formattedDate}</span>
|
|
457
|
-
</div>
|
|
458
|
-
</div>
|
|
459
|
-
</div>
|
|
460
|
-
|
|
461
|
-
<div className="feature-list-actions">
|
|
462
|
-
<Switch
|
|
463
|
-
checked={feature.active}
|
|
464
|
-
onChange={(checked, e) => {
|
|
465
|
-
e.stopPropagation();
|
|
466
|
-
handleToggleActive(feature.id, feature.active);
|
|
467
|
-
}}
|
|
468
|
-
onClick={(e) => e.stopPropagation()}
|
|
469
|
-
size="small"
|
|
470
|
-
/>
|
|
471
|
-
<Tooltip title="管理接口">
|
|
472
|
-
<Button
|
|
473
|
-
icon={<ApiOutlined />}
|
|
474
|
-
size="small"
|
|
475
|
-
type="text"
|
|
476
|
-
onClick={(e) => {
|
|
477
|
-
e.stopPropagation();
|
|
478
|
-
viewInterfaces(feature);
|
|
479
|
-
}}
|
|
480
|
-
/>
|
|
481
|
-
</Tooltip>
|
|
482
|
-
<Tooltip title="编辑功能">
|
|
483
|
-
<Button
|
|
484
|
-
icon={<EditOutlined />}
|
|
485
|
-
size="small"
|
|
486
|
-
type="text"
|
|
487
|
-
onClick={(e) => {
|
|
488
|
-
e.stopPropagation();
|
|
489
|
-
openModal(feature);
|
|
490
|
-
}}
|
|
491
|
-
/>
|
|
492
|
-
</Tooltip>
|
|
493
|
-
<Tooltip title="删除功能">
|
|
494
|
-
<Button
|
|
495
|
-
icon={<DeleteOutlined />}
|
|
496
|
-
size="small"
|
|
497
|
-
type="text"
|
|
498
|
-
danger
|
|
499
|
-
onClick={(e) => {
|
|
500
|
-
e.stopPropagation();
|
|
501
|
-
deleteFeature(feature.id);
|
|
502
|
-
}}
|
|
503
|
-
/>
|
|
504
|
-
</Tooltip>
|
|
505
|
-
<Tooltip title="导出配置">
|
|
506
|
-
<Button
|
|
507
|
-
icon={<ExportOutlined />}
|
|
508
|
-
size="small"
|
|
509
|
-
type="text"
|
|
510
|
-
onClick={(e) => {
|
|
511
|
-
e.stopPropagation();
|
|
512
|
-
exportFeatureConfig(feature);
|
|
513
|
-
}}
|
|
514
|
-
/>
|
|
515
|
-
</Tooltip>
|
|
516
|
-
</div>
|
|
517
|
-
</div>
|
|
518
|
-
);
|
|
519
|
-
};
|
|
520
|
-
|
|
521
416
|
// 渲染功能模块卡片
|
|
522
417
|
const renderFeatureCard = (feature) => {
|
|
523
418
|
const formattedDate = feature.createdAt
|
|
@@ -722,7 +617,7 @@ const MockData = () => {
|
|
|
722
617
|
</Col>
|
|
723
618
|
|
|
724
619
|
{/* 分页控制区域 */}
|
|
725
|
-
<Col xs={24} sm={
|
|
620
|
+
<Col xs={24} sm={24} md={8} lg={9}>
|
|
726
621
|
<div className="pagination-controls">
|
|
727
622
|
<div className="control-group">
|
|
728
623
|
<span className="control-label">每页显示</span>
|
|
@@ -743,22 +638,6 @@ const MockData = () => {
|
|
|
743
638
|
</div>
|
|
744
639
|
</div>
|
|
745
640
|
</Col>
|
|
746
|
-
|
|
747
|
-
{/* 视图切换按钮区域 */}
|
|
748
|
-
<Col xs={24} sm={12} md={3} lg={3}>
|
|
749
|
-
<div className="view-toggle-controls">
|
|
750
|
-
<Tooltip title={viewMode === 'card' ? '切换到列表视图' : '切换到卡片视图'}>
|
|
751
|
-
<Button
|
|
752
|
-
type="default"
|
|
753
|
-
icon={viewMode === 'card' ? <UnorderedListOutlined /> : <AppstoreOutlined />}
|
|
754
|
-
onClick={toggleViewMode}
|
|
755
|
-
className="view-toggle-button"
|
|
756
|
-
>
|
|
757
|
-
{viewMode === 'card' ? '列表视图' : '卡片视图'}
|
|
758
|
-
</Button>
|
|
759
|
-
</Tooltip>
|
|
760
|
-
</div>
|
|
761
|
-
</Col>
|
|
762
641
|
</Row>
|
|
763
642
|
</Card>
|
|
764
643
|
)}
|
|
@@ -771,16 +650,9 @@ const MockData = () => {
|
|
|
771
650
|
</div>
|
|
772
651
|
) : mockFeatures.length > 0 ? (
|
|
773
652
|
<>
|
|
774
|
-
{
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
{getPaginatedFeatures().map(feature => renderFeatureCard(feature))}
|
|
778
|
-
</Row>
|
|
779
|
-
) : (
|
|
780
|
-
<div className="feature-list-view">
|
|
781
|
-
{getPaginatedFeatures().map(feature => renderFeatureListItem(feature))}
|
|
782
|
-
</div>
|
|
783
|
-
)}
|
|
653
|
+
<Row gutter={[20, 20]} className="feature-grid">
|
|
654
|
+
{getPaginatedFeatures().map(feature => renderFeatureCard(feature))}
|
|
655
|
+
</Row>
|
|
784
656
|
|
|
785
657
|
{/* 分页组件 */}
|
|
786
658
|
{mockFeatures.length > listConfig.pageSize && (
|