whistle.mock-plugins 1.2.0-beta.1 → 1.2.0-beta.10
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 +5 -0
- package/app/public/js/index.js +1 -1
- package/app/public/js/index.js.LICENSE.txt +4 -2
- package/package.json +1 -1
- package/ui/src/components/AppLayout.js +5 -3
- package/ui/src/pages/InterfaceManagement.js +53 -13
- package/ui/src/pages/InterfaceManagementV2.js +55 -15
- package/ui/src/pages/MockDataV2.js +134 -6
- package/ui/src/styles/app-layout.css +38 -2
- package/ui/src/styles/mock-data.css +139 -0
|
@@ -81,8 +81,6 @@
|
|
|
81
81
|
|
|
82
82
|
/** */
|
|
83
83
|
|
|
84
|
-
/** */
|
|
85
|
-
|
|
86
84
|
/** */
|
|
87
85
|
|
|
88
86
|
/** */
|
|
@@ -99,6 +97,8 @@
|
|
|
99
97
|
|
|
100
98
|
/** */
|
|
101
99
|
|
|
100
|
+
/** */
|
|
101
|
+
|
|
102
102
|
/** */
|
|
103
103
|
|
|
104
104
|
/** */
|
|
@@ -156,3 +156,5 @@
|
|
|
156
156
|
/** */
|
|
157
157
|
|
|
158
158
|
/** */
|
|
159
|
+
|
|
160
|
+
/** */
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
FileTextOutlined,
|
|
8
8
|
MenuFoldOutlined,
|
|
9
9
|
MenuUnfoldOutlined,
|
|
10
|
-
|
|
10
|
+
ExperimentOutlined
|
|
11
11
|
} from '@ant-design/icons';
|
|
12
12
|
import { Link, useLocation } from 'react-router-dom';
|
|
13
13
|
import VersionModal from './VersionModal';
|
|
@@ -93,8 +93,10 @@ const AppLayout = ({ children }) => {
|
|
|
93
93
|
<Header className="app-header" style={{ background: colorBgContainer }}>
|
|
94
94
|
<div className="header-left">
|
|
95
95
|
<div className="logo-container">
|
|
96
|
-
<
|
|
97
|
-
|
|
96
|
+
<div className="logo-icon-group">
|
|
97
|
+
<ExperimentOutlined className="logo-icon logo-icon-secondary" />
|
|
98
|
+
</div>
|
|
99
|
+
<div className="logo-text">MockMaster</div>
|
|
98
100
|
</div>
|
|
99
101
|
<Button
|
|
100
102
|
type="text"
|
|
@@ -4,7 +4,7 @@ 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, Divider
|
|
8
8
|
} from 'antd';
|
|
9
9
|
import {
|
|
10
10
|
PlusOutlined, EditOutlined, DeleteOutlined,
|
|
@@ -64,6 +64,7 @@ const InterfaceManagement = () => {
|
|
|
64
64
|
const [groups, setGroups] = useState([]);
|
|
65
65
|
const [selectedGroup, setSelectedGroup] = useState(null);
|
|
66
66
|
const [groupActionLoading, setGroupActionLoading] = useState(false);
|
|
67
|
+
const [newGroupName, setNewGroupName] = useState('');
|
|
67
68
|
|
|
68
69
|
// 模态框状态
|
|
69
70
|
const [modalVisible, setModalVisible] = useState(false);
|
|
@@ -216,6 +217,17 @@ const InterfaceManagement = () => {
|
|
|
216
217
|
setSelectedGroup(null);
|
|
217
218
|
}
|
|
218
219
|
};
|
|
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
|
+
};
|
|
219
231
|
|
|
220
232
|
// 根据分组和搜索关键词筛选接口
|
|
221
233
|
const getFilteredInterfaces = () => {
|
|
@@ -1298,27 +1310,55 @@ const InterfaceManagement = () => {
|
|
|
1298
1310
|
tooltip="为接口设置分组,便于管理和批量操作"
|
|
1299
1311
|
>
|
|
1300
1312
|
<Select
|
|
1301
|
-
placeholder="
|
|
1313
|
+
placeholder="选择或输入新分组名称"
|
|
1302
1314
|
showSearch
|
|
1303
1315
|
allowClear
|
|
1304
1316
|
style={{ width: '100%' }}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
// 如果值是数组(tags模式),只取第一个元素
|
|
1310
|
-
if (Array.isArray(value) && value.length > 0) {
|
|
1311
|
-
// 如果第一个元素是空字符串,则设置为undefined
|
|
1312
|
-
const groupValue = value[0] === '' ? undefined : value[0];
|
|
1313
|
-
form.setFieldsValue({ group: groupValue });
|
|
1314
|
-
}
|
|
1315
|
-
}}
|
|
1317
|
+
dropdownMatchSelectWidth={false}
|
|
1318
|
+
filterOption={(input, option) =>
|
|
1319
|
+
option?.children?.toLowerCase()?.indexOf(input.toLowerCase()) >= 0
|
|
1320
|
+
}
|
|
1316
1321
|
onInputKeyDown={(e) => {
|
|
1317
1322
|
// 防止回车键提交表单
|
|
1318
1323
|
if (e.key === 'Enter') {
|
|
1319
1324
|
e.stopPropagation();
|
|
1320
1325
|
}
|
|
1321
1326
|
}}
|
|
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
|
+
)}
|
|
1322
1362
|
>
|
|
1323
1363
|
{Array.from(new Set(interfaces.map(item => item.group).filter(Boolean))).map(group => (
|
|
1324
1364
|
<Option key={group} value={group}>{group}</Option>
|
|
@@ -10,7 +10,7 @@ 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, Divider
|
|
14
14
|
} from 'antd';
|
|
15
15
|
import {
|
|
16
16
|
PlusOutlined, EditOutlined, DeleteOutlined,
|
|
@@ -70,6 +70,7 @@ const InterfaceManagement = () => {
|
|
|
70
70
|
const [groups, setGroups] = useState([]);
|
|
71
71
|
const [selectedGroup, setSelectedGroup] = useState(null);
|
|
72
72
|
const [groupActionLoading, setGroupActionLoading] = useState(false);
|
|
73
|
+
const [newGroupName, setNewGroupName] = useState('');
|
|
73
74
|
|
|
74
75
|
// 模态框状态
|
|
75
76
|
const [modalVisible, setModalVisible] = useState(false);
|
|
@@ -222,6 +223,17 @@ const InterfaceManagement = () => {
|
|
|
222
223
|
setSelectedGroup(null);
|
|
223
224
|
}
|
|
224
225
|
};
|
|
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
|
+
};
|
|
225
237
|
|
|
226
238
|
// 根据分组和搜索关键词筛选接口
|
|
227
239
|
const getFilteredInterfaces = () => {
|
|
@@ -1319,8 +1331,8 @@ const InterfaceManagement = () => {
|
|
|
1319
1331
|
>
|
|
1320
1332
|
<Collapse
|
|
1321
1333
|
defaultActiveKey={
|
|
1322
|
-
editingInterface
|
|
1323
|
-
?
|
|
1334
|
+
editingInterface && editingInterface.proxyType === 'response'
|
|
1335
|
+
? ['3']
|
|
1324
1336
|
: ['1', '2', '3']
|
|
1325
1337
|
}
|
|
1326
1338
|
style={{ marginBottom: '16px' }}
|
|
@@ -1353,27 +1365,55 @@ const InterfaceManagement = () => {
|
|
|
1353
1365
|
tooltip="为接口设置分组,便于管理和批量操作"
|
|
1354
1366
|
>
|
|
1355
1367
|
<Select
|
|
1356
|
-
placeholder="
|
|
1368
|
+
placeholder="选择或输入新分组名称"
|
|
1357
1369
|
showSearch
|
|
1358
1370
|
allowClear
|
|
1359
1371
|
style={{ width: '100%' }}
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
// 如果值是数组(tags模式),只取第一个元素
|
|
1365
|
-
if (Array.isArray(value) && value.length > 0) {
|
|
1366
|
-
// 如果第一个元素是空字符串,则设置为undefined
|
|
1367
|
-
const groupValue = value[0] === '' ? undefined : value[0];
|
|
1368
|
-
form.setFieldsValue({ group: groupValue });
|
|
1369
|
-
}
|
|
1370
|
-
}}
|
|
1372
|
+
dropdownMatchSelectWidth={false}
|
|
1373
|
+
filterOption={(input, option) =>
|
|
1374
|
+
option?.children?.toLowerCase()?.indexOf(input.toLowerCase()) >= 0
|
|
1375
|
+
}
|
|
1371
1376
|
onInputKeyDown={(e) => {
|
|
1372
1377
|
// 防止回车键提交表单
|
|
1373
1378
|
if (e.key === 'Enter') {
|
|
1374
1379
|
e.stopPropagation();
|
|
1375
1380
|
}
|
|
1376
1381
|
}}
|
|
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
|
+
)}
|
|
1377
1417
|
>
|
|
1378
1418
|
{Array.from(new Set(interfaces.map(item => item.group).filter(Boolean))).map(group => (
|
|
1379
1419
|
<Option key={group} value={group}>{group}</Option>
|
|
@@ -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 } from 'antd';
|
|
10
|
-
import { PlusOutlined, EditOutlined, DeleteOutlined, ApiOutlined, ExportOutlined, InfoCircleOutlined, CalendarOutlined, SortAscendingOutlined } from '@ant-design/icons';
|
|
9
|
+
import { Card, Row, Col, Button, Modal, Form, Input, message, Switch, Empty, Spin, Typography, Tooltip, Badge, Select, Pagination, Tag, List } from 'antd';
|
|
10
|
+
import { PlusOutlined, EditOutlined, DeleteOutlined, ApiOutlined, ExportOutlined, InfoCircleOutlined, CalendarOutlined, SortAscendingOutlined, AppstoreOutlined, UnorderedListOutlined } from '@ant-design/icons';
|
|
11
11
|
import '../styles/mock-data.css';
|
|
12
12
|
import axios from 'axios';
|
|
13
13
|
|
|
@@ -34,6 +34,12 @@ 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
|
+
|
|
37
43
|
// 排序和分页配置(支持缓存)
|
|
38
44
|
const [listConfig, setListConfig] = useState(() => {
|
|
39
45
|
const cached = localStorage.getItem('feature-list-config');
|
|
@@ -51,6 +57,13 @@ const MockData = () => {
|
|
|
51
57
|
setListConfig(newConfig);
|
|
52
58
|
localStorage.setItem('feature-list-config', JSON.stringify(newConfig));
|
|
53
59
|
};
|
|
60
|
+
|
|
61
|
+
// 切换视图模式(卡片/列表)
|
|
62
|
+
const toggleViewMode = () => {
|
|
63
|
+
const newMode = viewMode === 'card' ? 'list' : 'card';
|
|
64
|
+
setViewMode(newMode);
|
|
65
|
+
localStorage.setItem('feature-view-mode', newMode);
|
|
66
|
+
};
|
|
54
67
|
|
|
55
68
|
// 加载功能列表
|
|
56
69
|
useEffect(() => {
|
|
@@ -413,6 +426,98 @@ const MockData = () => {
|
|
|
413
426
|
return sortedFeatures.slice(startIndex, endIndex);
|
|
414
427
|
};
|
|
415
428
|
|
|
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
|
+
|
|
416
521
|
// 渲染功能模块卡片
|
|
417
522
|
const renderFeatureCard = (feature) => {
|
|
418
523
|
const formattedDate = feature.createdAt
|
|
@@ -617,7 +722,7 @@ const MockData = () => {
|
|
|
617
722
|
</Col>
|
|
618
723
|
|
|
619
724
|
{/* 分页控制区域 */}
|
|
620
|
-
<Col xs={24} sm={
|
|
725
|
+
<Col xs={24} sm={12} md={5} lg={6}>
|
|
621
726
|
<div className="pagination-controls">
|
|
622
727
|
<div className="control-group">
|
|
623
728
|
<span className="control-label">每页显示</span>
|
|
@@ -638,6 +743,22 @@ const MockData = () => {
|
|
|
638
743
|
</div>
|
|
639
744
|
</div>
|
|
640
745
|
</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>
|
|
641
762
|
</Row>
|
|
642
763
|
</Card>
|
|
643
764
|
)}
|
|
@@ -650,9 +771,16 @@ const MockData = () => {
|
|
|
650
771
|
</div>
|
|
651
772
|
) : mockFeatures.length > 0 ? (
|
|
652
773
|
<>
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
774
|
+
{/* 根据视图模式渲染不同的视图 */}
|
|
775
|
+
{viewMode === 'card' ? (
|
|
776
|
+
<Row gutter={[20, 20]} className="feature-grid">
|
|
777
|
+
{getPaginatedFeatures().map(feature => renderFeatureCard(feature))}
|
|
778
|
+
</Row>
|
|
779
|
+
) : (
|
|
780
|
+
<div className="feature-list-view">
|
|
781
|
+
{getPaginatedFeatures().map(feature => renderFeatureListItem(feature))}
|
|
782
|
+
</div>
|
|
783
|
+
)}
|
|
656
784
|
|
|
657
785
|
{/* 分页组件 */}
|
|
658
786
|
{mockFeatures.length > listConfig.pageSize && (
|
|
@@ -221,10 +221,37 @@ body {
|
|
|
221
221
|
margin-right: 24px;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
.logo-icon-group {
|
|
225
|
+
position: relative;
|
|
226
|
+
width: 32px;
|
|
227
|
+
height: 32px;
|
|
228
|
+
margin-right: 12px;
|
|
229
|
+
display: flex;
|
|
230
|
+
align-items: center;
|
|
231
|
+
justify-content: center;
|
|
232
|
+
}
|
|
233
|
+
|
|
224
234
|
.logo-icon {
|
|
225
235
|
font-size: 24px;
|
|
226
236
|
color: #1890ff;
|
|
227
|
-
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.logo-icon-primary {
|
|
240
|
+
position: relative;
|
|
241
|
+
z-index: 2;
|
|
242
|
+
font-size: 22px;
|
|
243
|
+
display: none;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.logo-icon-secondary {
|
|
247
|
+
position: absolute;
|
|
248
|
+
right: -6px;
|
|
249
|
+
font-size: 16px;
|
|
250
|
+
color: #52c41a;
|
|
251
|
+
background: white;
|
|
252
|
+
border-radius: 50%;
|
|
253
|
+
box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
|
|
254
|
+
padding: 2px;
|
|
228
255
|
}
|
|
229
256
|
|
|
230
257
|
.logo-text {
|
|
@@ -232,6 +259,10 @@ body {
|
|
|
232
259
|
font-size: 18px;
|
|
233
260
|
font-weight: 600;
|
|
234
261
|
white-space: nowrap;
|
|
262
|
+
background: linear-gradient(135deg, #1890ff, #52c41a);
|
|
263
|
+
-webkit-background-clip: text;
|
|
264
|
+
-webkit-text-fill-color: transparent;
|
|
265
|
+
letter-spacing: -0.5px;
|
|
235
266
|
}
|
|
236
267
|
|
|
237
268
|
.collapse-button {
|
|
@@ -332,6 +363,11 @@ body {
|
|
|
332
363
|
}
|
|
333
364
|
|
|
334
365
|
.dark-theme .logo-text,
|
|
335
|
-
.dark-theme .logo-icon {
|
|
366
|
+
.dark-theme .logo-icon-primary {
|
|
336
367
|
color: #177ddc;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.dark-theme .logo-icon-secondary {
|
|
371
|
+
color: #49aa19;
|
|
372
|
+
background: #141414;
|
|
337
373
|
}
|