whistle.mock-plugins 1.1.3-dev.9 → 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.
@@ -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
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,
17
17
  FileTextOutlined, PlusCircleOutlined, SettingOutlined,
18
- SearchOutlined, FilterOutlined, ReloadOutlined, DownOutlined
18
+ SearchOutlined, FilterOutlined, ReloadOutlined, DownOutlined, QuestionCircleOutlined
19
19
  } from '@ant-design/icons';
20
20
  import '../styles/interface-management.css';
21
21
  import axios from 'axios';
@@ -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 = () => {
@@ -679,40 +691,10 @@ const InterfaceManagement = () => {
679
691
  },
680
692
  render: (group) => {
681
693
  if (!group) {
682
- return (
683
- <span style={{
684
- color: '#a0aec0',
685
- fontStyle: 'italic',
686
- fontSize: '12px'
687
- }}>
688
- 未分组
689
- </span>
690
- );
694
+ return <span style={{ color: '#999', fontStyle: 'italic' }}>未分组</span>;
691
695
  }
692
696
  return (
693
- <Tag
694
- style={{
695
- cursor: 'pointer',
696
- background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
697
- border: 'none',
698
- borderRadius: '12px',
699
- color: '#ffffff',
700
- fontWeight: '600',
701
- fontSize: '11px',
702
- padding: '2px 8px',
703
- boxShadow: '0 2px 4px rgba(102, 126, 234, 0.3)',
704
- transition: 'all 0.2s ease'
705
- }}
706
- onClick={() => setSelectedGroup(group)}
707
- onMouseEnter={(e) => {
708
- e.target.style.transform = 'translateY(-1px)';
709
- e.target.style.boxShadow = '0 4px 8px rgba(102, 126, 234, 0.4)';
710
- }}
711
- onMouseLeave={(e) => {
712
- e.target.style.transform = 'translateY(0)';
713
- e.target.style.boxShadow = '0 2px 4px rgba(102, 126, 234, 0.3)';
714
- }}
715
- >
697
+ <Tag color="blue" style={{ cursor: 'pointer' }} onClick={() => setSelectedGroup(group)}>
716
698
  {group}
717
699
  </Tag>
718
700
  );
@@ -732,31 +714,11 @@ const InterfaceManagement = () => {
732
714
  width: 120,
733
715
  render: (text) => {
734
716
  const proxyType = proxyTypes.find(item => item.value === text);
735
- const color = proxyType?.color || '#999';
736
- const label = proxyType?.label || text;
737
-
738
717
  return (
739
- <span
740
- style={{
741
- display: 'inline-flex',
742
- alignItems: 'center',
743
- padding: '4px 10px',
744
- borderRadius: '12px',
745
- fontSize: '12px',
746
- fontWeight: '600',
747
- backgroundColor: color === '#1890ff' ? '#ebf8ff' :
748
- color === '#52c41a' ? '#f0fff4' :
749
- color === '#faad14' ? '#fffbf0' : '#f5f5f5',
750
- color: color === '#1890ff' ? '#3182ce' :
751
- color === '#52c41a' ? '#38a169' :
752
- color === '#faad14' ? '#d69e2e' : '#718096',
753
- border: `1px solid ${color === '#1890ff' ? '#bee3f8' :
754
- color === '#52c41a' ? '#c6f6d5' :
755
- color === '#faad14' ? '#feebc8' : '#e2e8f0'}`
756
- }}
757
- >
758
- {label}
759
- </span>
718
+ <Badge
719
+ color={proxyType?.color || '#999'}
720
+ text={proxyType?.label || text}
721
+ />
760
722
  );
761
723
  },
762
724
  },
@@ -780,22 +742,13 @@ const InterfaceManagement = () => {
780
742
  return (
781
743
  <Select
782
744
  value={activeResponseId || record.responses[0]?.id}
783
- style={{
784
- width: '100%',
785
- borderRadius: '6px'
786
- }}
745
+ style={{ width: '100%' }}
787
746
  onChange={(value) => handleResponseSwitch(record.id, value)}
788
747
  disabled={record.proxyType !== 'response'}
789
- size="small"
790
748
  >
791
749
  {record.responses.map(resp => (
792
750
  <Option key={resp.id} value={resp.id}>
793
- <span style={{
794
- fontSize: '12px',
795
- fontWeight: '500'
796
- }}>
797
- {resp.name}
798
- </span>
751
+ {resp.name}
799
752
  </Option>
800
753
  ))}
801
754
  </Select>
@@ -973,28 +926,18 @@ const InterfaceManagement = () => {
973
926
  width: 180,
974
927
  render: (_, record) => (
975
928
  <div className="action-buttons">
976
- <Tooltip title="编辑接口">
977
- <Button
978
- type="text"
979
- icon={<EditOutlined />}
980
- onClick={() => handleEditInterface(record)}
981
- aria-label="编辑接口"
982
- />
983
- </Tooltip>
929
+ <Button
930
+ type="text"
931
+ icon={<EditOutlined />}
932
+ onClick={() => handleEditInterface(record)}
933
+ />
984
934
  <Popconfirm
985
935
  title="确定要删除此接口吗?"
986
936
  onConfirm={() => handleDeleteInterface(record.id)}
987
937
  okText="确定"
988
938
  cancelText="取消"
989
939
  >
990
- <Tooltip title="删除接口">
991
- <Button
992
- type="text"
993
- danger
994
- icon={<DeleteOutlined />}
995
- aria-label="删除接口"
996
- />
997
- </Tooltip>
940
+ <Button type="text" danger icon={<DeleteOutlined />} />
998
941
  </Popconfirm>
999
942
  </div>
1000
943
  ),
@@ -1045,6 +988,27 @@ const InterfaceManagement = () => {
1045
988
  重构后版本 V2
1046
989
  </div>
1047
990
 
991
+ {/* 警告信息置顶显示 */}
992
+ {!features.length && (
993
+ <Alert
994
+ message="未找到功能模块"
995
+ description="请先在模拟数据页面创建功能模块,然后再添加接口"
996
+ type="warning"
997
+ showIcon
998
+ style={{ marginBottom: 16 }}
999
+ />
1000
+ )}
1001
+
1002
+ {selectedFeature?.active === false && (
1003
+ <Alert
1004
+ message="功能模块已禁用"
1005
+ description="当前功能模块已被禁用,所有关联接口不会生效。您可以在模拟数据页面启用此功能模块。"
1006
+ type="warning"
1007
+ showIcon
1008
+ style={{ marginBottom: 16 }}
1009
+ />
1010
+ )}
1011
+
1048
1012
  <Card className="interface-control-panel">
1049
1013
  <div className="control-panel-header">
1050
1014
  <div className="control-left">
@@ -1135,14 +1099,9 @@ const InterfaceManagement = () => {
1135
1099
  <div className="control-panel-stats">
1136
1100
  <div className="stats-left">
1137
1101
  <Space size="large">
1138
- <span className="stat-item">
1139
- <span className="stat-label">功能模块:</span>
1140
- <span className="stat-value">{selectedFeature?.name}</span>
1141
- </span>
1142
-
1143
1102
  {selectedGroup && (
1144
1103
  <span className="stat-item">
1145
- <span className="stat-label">分组:</span>
1104
+ <span className="stat-label">当前分组:</span>
1146
1105
  <Tag color="blue">{selectedGroup}</Tag>
1147
1106
  </span>
1148
1107
  )}
@@ -1268,16 +1227,13 @@ const InterfaceManagement = () => {
1268
1227
  </div>
1269
1228
  }
1270
1229
  >
1271
- <Tooltip title="自定义显示列">
1230
+ <Tooltip title="自定义表格显示列">
1272
1231
  <Button
1273
1232
  icon={<SettingOutlined />}
1274
1233
  type="text"
1275
- size="middle"
1276
- className="column-settings-btn"
1277
- style={{
1278
- marginLeft: (selectedRowKeys.length > 0 || selectedGroup) ? 16 : 0,
1279
- color: '#666'
1280
- }}
1234
+ size="small"
1235
+ className="inline-column-settings-btn"
1236
+ style={{ marginLeft: (selectedRowKeys.length > 0 || selectedGroup) ? 16 : 0 }}
1281
1237
  >
1282
1238
  列设置
1283
1239
  </Button>
@@ -1287,29 +1243,7 @@ const InterfaceManagement = () => {
1287
1243
  </div>
1288
1244
  </Card>
1289
1245
 
1290
- {!features.length && (
1291
- <Alert
1292
- message="未找到功能模块"
1293
- description="请先在模拟数据页面创建功能模块,然后再添加接口"
1294
- type="warning"
1295
- showIcon
1296
- style={{ marginBottom: 16 }}
1297
- />
1298
- )}
1299
-
1300
- {selectedFeature?.active === false && (
1301
- <Alert
1302
- message="功能模块已禁用"
1303
- description="当前功能模块已被禁用,所有关联接口不会生效。您可以在模拟数据页面启用此功能模块。"
1304
- type="warning"
1305
- showIcon
1306
- style={{ marginBottom: 16 }}
1307
- />
1308
- )}
1309
-
1310
-
1311
-
1312
- <Card className="list-container-card" bordered={false} bodyStyle={{ padding: 0 }}>
1246
+ <Card className="list-container-card" bordered={false} bodyStyle={{ padding: 0 }}>
1313
1247
  <div className="interface-list-container">
1314
1248
  <Table
1315
1249
  columns={columns}
@@ -1340,36 +1274,81 @@ const InterfaceManagement = () => {
1340
1274
 
1341
1275
  {/* 接口编辑/创建模态框 */}
1342
1276
  <Modal
1343
- title={editingInterface ? '编辑接口' : '添加接口'}
1277
+ title={
1278
+ <div style={{
1279
+ fontSize: '18px',
1280
+ fontWeight: 600,
1281
+ color: '#262626',
1282
+ display: 'flex',
1283
+ alignItems: 'center',
1284
+ gap: '8px'
1285
+ }}>
1286
+ <span>{editingInterface ? '编辑接口' : '添加接口'}</span>
1287
+ <Badge
1288
+ count="配置向导"
1289
+ style={{
1290
+ backgroundColor: '#f0f9ff',
1291
+ color: '#1677ff',
1292
+ fontSize: '11px',
1293
+ fontWeight: 'normal'
1294
+ }}
1295
+ />
1296
+ </div>
1297
+ }
1344
1298
  open={modalVisible}
1345
1299
  onOk={handleSubmit}
1346
1300
  onCancel={handleCancel}
1347
- width={800}
1301
+ width={1200}
1348
1302
  destroyOnClose
1349
1303
  okText={editingInterface ? '保存' : '创建'}
1350
1304
  cancelText="取消"
1351
- bodyStyle={{ maxHeight: '70vh', overflow: 'auto', padding: '24px' }}
1305
+ bodyStyle={{
1306
+ maxHeight: '75vh',
1307
+ overflow: 'auto',
1308
+ padding: '0',
1309
+ background: '#fafafa'
1310
+ }}
1311
+ className="interface-modal"
1352
1312
  >
1353
- <Form
1354
- form={form}
1355
- layout="vertical"
1356
- initialValues={{
1357
- name: '',
1358
- group: '',
1359
- pattern: '',
1360
- proxyType: 'response',
1361
- statusCode: '200',
1362
- contentType: 'application/json; charset=utf-8',
1363
- responses: [],
1364
- httpMethod: 'ALL',
1365
- targetUrl: '',
1366
- headerItems: [],
1367
- paramMatchers: [],
1368
- responseDelay: '0'
1369
- }}
1370
- >
1371
- {/* 基础信息表单项 */}
1372
- <div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
1313
+ <div style={{ padding: '24px' }}>
1314
+ <Form
1315
+ form={form}
1316
+ layout="vertical"
1317
+ initialValues={{
1318
+ name: '',
1319
+ group: '',
1320
+ pattern: '',
1321
+ proxyType: 'response',
1322
+ statusCode: '200',
1323
+ contentType: 'application/json; charset=utf-8',
1324
+ responses: [],
1325
+ httpMethod: 'ALL',
1326
+ targetUrl: '',
1327
+ headerItems: [],
1328
+ paramMatchers: [],
1329
+ responseDelay: '0'
1330
+ }}
1331
+ >
1332
+ <Collapse
1333
+ defaultActiveKey={
1334
+ editingInterface && editingInterface.proxyType === 'response'
1335
+ ? ['3']
1336
+ : ['1', '2', '3']
1337
+ }
1338
+ style={{ marginBottom: '16px' }}
1339
+ ghost
1340
+ >
1341
+ {/* 第一步:基础信息 */}
1342
+ <Collapse.Panel
1343
+ header={
1344
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
1345
+ <Badge count="1" style={{ backgroundColor: '#1677ff' }} />
1346
+ <span>基础信息</span>
1347
+ </div>
1348
+ }
1349
+ key="1"
1350
+ >
1351
+ <div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
1373
1352
  <div style={{ flex: 1 }}>
1374
1353
  <Form.Item
1375
1354
  name="name"
@@ -1386,37 +1365,76 @@ const InterfaceManagement = () => {
1386
1365
  tooltip="为接口设置分组,便于管理和批量操作"
1387
1366
  >
1388
1367
  <Select
1389
- placeholder="选择或创建分组"
1368
+ placeholder="选择或输入新分组名称"
1390
1369
  showSearch
1391
1370
  allowClear
1392
1371
  style={{ width: '100%' }}
1393
- mode="tags"
1394
- maxTagCount="responsive"
1395
- maxTagTextLength={10}
1396
- onChange={(value) => {
1397
- // 如果值是数组(tags模式),只取第一个元素
1398
- if (Array.isArray(value) && value.length > 0) {
1399
- // 如果第一个元素是空字符串,则设置为undefined
1400
- const groupValue = value[0] === '' ? undefined : value[0];
1401
- form.setFieldsValue({ group: groupValue });
1402
- }
1403
- }}
1372
+ dropdownMatchSelectWidth={false}
1373
+ filterOption={(input, option) =>
1374
+ option?.children?.toLowerCase()?.indexOf(input.toLowerCase()) >= 0
1375
+ }
1404
1376
  onInputKeyDown={(e) => {
1405
1377
  // 防止回车键提交表单
1406
1378
  if (e.key === 'Enter') {
1407
1379
  e.stopPropagation();
1408
1380
  }
1409
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
+ )}
1410
1417
  >
1411
1418
  {Array.from(new Set(interfaces.map(item => item.group).filter(Boolean))).map(group => (
1412
1419
  <Option key={group} value={group}>{group}</Option>
1413
1420
  ))}
1414
1421
  </Select>
1415
1422
  </Form.Item>
1416
- </div>
1417
- </div>
1418
-
1419
- <div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
1423
+ </div>
1424
+ </div>
1425
+ </Collapse.Panel>
1426
+
1427
+ {/* 第二步:URL配置 */}
1428
+ <Collapse.Panel
1429
+ header={
1430
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
1431
+ <Badge count="2" style={{ backgroundColor: '#1677ff' }} />
1432
+ <span>URL配置</span>
1433
+ </div>
1434
+ }
1435
+ key="2"
1436
+ >
1437
+ <div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
1420
1438
  <div style={{ flex: 1 }}>
1421
1439
  <Form.Item
1422
1440
  name="pattern"
@@ -1556,7 +1574,19 @@ const InterfaceManagement = () => {
1556
1574
  <div style={{ flex: 1 }}>
1557
1575
  {/* 这里可以留空或添加其他字段 */}
1558
1576
  </div>
1559
- </div>
1577
+ </div>
1578
+ </Collapse.Panel>
1579
+
1580
+ {/* 第三步:高级配置 */}
1581
+ <Collapse.Panel
1582
+ header={
1583
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
1584
+ <Badge count="3" style={{ backgroundColor: '#1677ff' }} />
1585
+ <span>高级配置</span>
1586
+ </div>
1587
+ }
1588
+ key="3"
1589
+ >
1560
1590
 
1561
1591
  {/* 根据proxyType显示不同的表单项 */}
1562
1592
  <Form.Item noStyle shouldUpdate={(prevValues, currentValues) => prevValues.proxyType !== currentValues.proxyType}>
@@ -1670,30 +1700,42 @@ const InterfaceManagement = () => {
1670
1700
  />
1671
1701
  </div>
1672
1702
  ))}
1673
- <Button
1674
- type="dashed"
1675
- onClick={() => add()}
1676
- icon={<PlusOutlined />}
1677
- style={{ width: '100%', marginTop: '8px' }}
1678
- >
1679
- 添加参数匹配规则
1680
- </Button>
1681
- {fields.length > 0 && (
1682
- <div style={{
1683
- fontSize: '12px',
1684
- color: '#666',
1685
- marginTop: '8px',
1686
- padding: '8px',
1687
- backgroundColor: '#f5f5f5',
1688
- borderRadius: '4px'
1689
- }}>
1690
- <div><strong>说明:</strong></div>
1691
- <div>• <strong>精确匹配</strong>:参数值完全相等</div>
1692
- <div>• <strong>包含</strong>:参数值包含指定内容</div>
1693
- <div>• <strong>正则</strong>:参数值符合正则表达式</div>
1694
- <div>• <strong>嵌套路径</strong>:使用点号分隔,如 data.user.id</div>
1695
- </div>
1696
- )}
1703
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginTop: '8px' }}>
1704
+ <Button
1705
+ type="dashed"
1706
+ onClick={() => add()}
1707
+ icon={<PlusOutlined />}
1708
+ style={{ flex: 1 }}
1709
+ >
1710
+ 添加参数匹配规则
1711
+ </Button>
1712
+ <Popover
1713
+ content={
1714
+ <div style={{ maxWidth: '300px' }}>
1715
+ <div style={{ marginBottom: '8px', fontWeight: 'bold' }}>匹配规则说明:</div>
1716
+ <div style={{ lineHeight: '1.6' }}>
1717
+ <div>• <strong>精确匹配</strong>:参数值完全相等</div>
1718
+ <div>• <strong>包含</strong>:参数值包含指定内容</div>
1719
+ <div>• <strong>正则</strong>:参数值符合正则表达式</div>
1720
+ <div>• <strong>嵌套路径</strong>:使用点号分隔,如 data.user.id</div>
1721
+ </div>
1722
+ </div>
1723
+ }
1724
+ title="参数匹配规则"
1725
+ trigger="click"
1726
+ placement="top"
1727
+ >
1728
+ <Button
1729
+ type="text"
1730
+ icon={<QuestionCircleOutlined />}
1731
+ style={{
1732
+ color: '#999',
1733
+ padding: '4px'
1734
+ }}
1735
+ size="small"
1736
+ />
1737
+ </Popover>
1738
+ </div>
1697
1739
  </>
1698
1740
  )}
1699
1741
  </Form.List>
@@ -1817,7 +1859,10 @@ const InterfaceManagement = () => {
1817
1859
  return null;
1818
1860
  }}
1819
1861
  </Form.Item>
1820
- </Form>
1862
+ </Collapse.Panel>
1863
+ </Collapse>
1864
+ </Form>
1865
+ </div>
1821
1866
  </Modal>
1822
1867
 
1823
1868
  {/* 使用拆分后的预览模态框组件 */}