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.
@@ -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?id=${id}`, {
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 = interfaces.filter(item =>
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
- <div className="interface-management-header">
782
- <div className="feature-selector">
783
- <span>功能模块:</span>
784
- <Select
785
- value={selectedFeatureId}
786
- onChange={handleSelectFeature}
787
- style={{ width: 200 }}
788
- placeholder="选择功能模块"
789
- loading={featuresLoading}
790
- >
791
- {(features || []).map(feature => (
792
- <Option key={feature.id} value={feature.id}>
793
- {feature.name}
794
- {feature.active === false &&
795
- <span style={{ color: '#ff4d4f', marginLeft: 8 }}>(已禁用)</span>
796
- }
797
- </Option>
798
- ))}
799
- </Select>
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
- <Button
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
- {filteredInterfaces.length > 0 && !interfacesLoading && (
833
- <div style={{
834
- marginBottom: 16,
835
- padding: '8px 12px',
836
- background: '#f8f9fa',
837
- borderRadius: '4px',
838
- fontSize: '13px',
839
- color: '#666',
840
- display: 'flex',
841
- justifyContent: 'space-between',
842
- alignItems: 'center'
843
- }}>
844
- <span>
845
- 当前功能模块:<strong>{selectedFeature?.name}</strong>
846
- </span>
847
- <span>
848
- 共 {filteredInterfaces.length} 个接口,每页显示 {tableConfig.pageSize} 个
849
- {tableConfig.sortField && (
850
- <span style={{ marginLeft: 8 }}>
851
- | 按"{allColumns.find(col => col.key === tableConfig.sortField)?.title || tableConfig.sortField}"
852
- {tableConfig.sortOrder === 'ascend' ? '升序' : '降序'}排列
853
- </span>
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
- </span>
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
- <div className="interface-list-container">
923
- <Table
924
- columns={columns}
925
- dataSource={filteredInterfaces}
926
- rowKey="id"
927
- loading={interfacesLoading}
928
- onChange={handleTableChange}
929
- pagination={{
930
- current: tableConfig.current,
931
- pageSize: tableConfig.pageSize,
932
- showSizeChanger: true,
933
- showQuickJumper: true,
934
- showTotal: (total, range) => `共 ${total} 个接口,显示第 ${range[0]}-${range[1]} 个`,
935
- pageSizeOptions: ['10', '20', '50', '100'],
936
- size: 'default'
937
- }}
938
- locale={{ emptyText: '暂无接口配置' }}
939
- sortDirections={['ascend', 'descend']}
940
- />
941
- </div>
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\/users\/\d+/";
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显示不同的表单项 */}