whistle.mock-plugins 1.1.3-beta.2 → 1.1.3-dev.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.
@@ -1,48 +1,36 @@
1
1
  /**
2
- * MockDataV2.js - 功能模块管理页面(重构后版本)
3
- * 这是重构后的新版本界面,提供更好的用户体验和功能优化
4
- * 当前内容与V1版本相同,等待后续重构
2
+ * MockDataV2.js - 功能模块管理页面(V2版本)
3
+ * 功能和UI与V1版本完全一致,仅保留V2版本标识
5
4
  */
6
5
 
7
6
  import React, { useState, useEffect } from 'react';
8
7
  import { useHistory } from 'react-router-dom';
9
8
  import AppLayout from '../components/AppLayout';
10
- import {
11
- Form, Input, Modal, Button, message, Card, Switch,
12
- Row, Col, Badge, Popconfirm, Alert, Space, Select,
13
- Tooltip, Menu, Dropdown, Pagination, AutoComplete
14
- } from 'antd';
15
- import {
16
- PlusOutlined, DeleteOutlined, EditOutlined, SettingOutlined,
17
- ExportOutlined, ImportOutlined, SearchOutlined, FilterOutlined,
18
- SortAscendingOutlined, ReloadOutlined, ClearOutlined, DownloadOutlined
19
- } from '@ant-design/icons';
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';
20
11
  import '../styles/mock-data.css';
12
+ import axios from 'axios';
21
13
 
22
- const { Option } = Select;
14
+ const { Text, Title, Paragraph } = Typography;
23
15
  const { TextArea } = Input;
24
16
 
25
- // 刷新缓存工具函数
17
+ // 刷新缓存服务
26
18
  const flushCache = async () => {
27
19
  try {
28
- await fetch('/cgi-bin/flushCache', { method: 'POST' });
20
+ const response = await axios.get(`/_flush_cache`);
21
+ return response.data;
29
22
  } catch (error) {
30
23
  console.error('刷新缓存失败:', error);
31
24
  throw error;
32
25
  }
33
26
  };
34
27
 
35
-
36
28
  const MockData = () => {
37
29
  const history = useHistory();
38
30
  const [mockFeatures, setMockFeatures] = useState([]);
39
31
  const [showModal, setShowModal] = useState(false);
40
32
  const [currentFeature, setCurrentFeature] = useState(null);
41
- const [formData, setFormData] = useState({
42
- name: '',
43
- description: '',
44
- active: true
45
- });
33
+
46
34
  const [loading, setLoading] = useState(true);
47
35
  const [form] = Form.useForm();
48
36
 
@@ -91,353 +79,435 @@ const MockData = () => {
91
79
  };
92
80
 
93
81
  const openModal = (feature = null) => {
94
- setCurrentFeature(feature);
95
82
  if (feature) {
96
- setFormData({
97
- name: feature.name,
98
- description: feature.description || '',
99
- active: feature.active !== false
100
- });
83
+ setCurrentFeature(feature);
101
84
  form.setFieldsValue({
102
85
  name: feature.name,
103
- description: feature.description || '',
86
+ description: feature.description,
104
87
  active: feature.active !== false
105
88
  });
106
89
  } else {
107
- setFormData({
108
- name: '',
109
- description: '',
90
+ setCurrentFeature(null);
91
+ form.resetFields();
92
+ form.setFieldsValue({
110
93
  active: true
111
94
  });
112
- form.resetFields();
113
95
  }
114
96
  setShowModal(true);
115
97
  };
116
98
 
117
- const handleSubmit = async (values) => {
118
- console.log('表单提交数据:', values);
119
- try {
120
- const submitData = {
121
- name: values.name.trim(),
122
- description: values.description?.trim() || '',
123
- active: values.active !== false
124
- };
125
-
126
- console.log('处理后的提交数据:', submitData);
99
+ const closeModal = () => {
100
+ setShowModal(false);
101
+ };
127
102
 
128
- if (currentFeature) {
129
- // 更新功能模块
130
- const response = await fetch(`/cgi-bin/features?id=${currentFeature.id}`, {
131
- method: 'PUT',
132
- headers: {
133
- 'Content-Type': 'application/json',
134
- },
135
- body: JSON.stringify(submitData),
136
- });
137
-
138
- const result = await response.json();
139
- console.log('更新响应:', result);
140
-
141
- if (result.code === 0) {
142
- message.success('功能模块更新成功');
143
- setShowModal(false);
144
- fetchFeatures();
145
-
146
- // 刷新缓存
147
- await flushCache();
148
- } else {
149
- message.error('功能模块更新失败: ' + (result.message || '未知错误'));
150
- }
151
- } else {
152
- // 创建新功能模块
153
- const response = await fetch('/cgi-bin/features', {
154
- method: 'POST',
155
- headers: {
156
- 'Content-Type': 'application/json',
157
- },
158
- body: JSON.stringify(submitData),
159
- });
160
-
161
- const result = await response.json();
162
- console.log('创建响应:', result);
163
-
164
- if (result.code === 0) {
165
- message.success('功能模块创建成功');
166
- setShowModal(false);
167
- fetchFeatures();
168
-
169
- // 刷新缓存
170
- await flushCache();
171
- } else {
172
- message.error('功能模块创建失败: ' + (result.message || '未知错误'));
173
- }
174
- }
103
+ // 更新成功后刷新缓存
104
+ const refreshCacheAfterUpdate = async () => {
105
+ try {
106
+ await flushCache();
107
+ // 不显示提示,避免干扰主操作的反馈
175
108
  } catch (error) {
176
- console.error('操作失败:', error);
177
- message.error('操作失败: ' + error.message);
109
+ // 记录错误但不影响用户体验
110
+ console.error('刷新缓存失败,可能需要等待缓存自动过期:', error);
178
111
  }
179
112
  };
180
113
 
181
- const handleDelete = async (id) => {
114
+ const handleSubmit = async (values) => {
182
115
  try {
183
- const response = await fetch(`/cgi-bin/features?id=${id}`, {
184
- method: 'DELETE',
116
+ const featureData = {
117
+ ...values
118
+ };
119
+
120
+ // 如果是编辑已有功能,添加ID
121
+ if (currentFeature) {
122
+ featureData.id = currentFeature.id;
123
+ }
124
+
125
+ setLoading(true);
126
+ const response = await fetch('/cgi-bin/features', {
127
+ method: 'POST',
128
+ headers: {
129
+ 'Content-Type': 'application/json'
130
+ },
131
+ body: JSON.stringify(featureData)
185
132
  });
186
133
 
187
134
  const result = await response.json();
135
+
188
136
  if (result.code === 0) {
189
- message.success('功能模块删除成功');
137
+ // 刷新功能列表
138
+ message.success(currentFeature ? '功能模块更新成功' : '功能模块创建成功');
190
139
  fetchFeatures();
140
+ closeModal();
191
141
 
192
142
  // 刷新缓存
193
- await flushCache();
143
+ refreshCacheAfterUpdate();
194
144
  } else {
195
- message.error('功能模块删除失败: ' + (result.message || '未知错误'));
145
+ message.error('操作失败: ' + result.message);
196
146
  }
197
147
  } catch (error) {
198
- console.error('删除失败:', error);
199
- message.error('删除失败: ' + error.message);
148
+ console.error('保存功能模块错误:', error);
149
+ message.error('保存失败: ' + error.message);
150
+ } finally {
151
+ setLoading(false);
200
152
  }
201
153
  };
202
154
 
203
155
  const handleToggleActive = async (id, currentActive) => {
204
156
  try {
205
157
  const response = await fetch(`/cgi-bin/features?id=${id}`, {
206
- method: 'PUT',
158
+ method: 'PATCH',
207
159
  headers: {
208
- 'Content-Type': 'application/json',
160
+ 'Content-Type': 'application/json'
209
161
  },
210
162
  body: JSON.stringify({
211
163
  active: !currentActive
212
- }),
164
+ })
213
165
  });
214
166
 
215
167
  const result = await response.json();
168
+
216
169
  if (result.code === 0) {
217
- message.success(`功能模块${!currentActive ? '启用' : '禁用'}成功`);
218
- fetchFeatures();
170
+ message.success(`${currentActive ? '禁用' : '启用'}功能模块成功`);
171
+ // 更新本地状态
172
+ setMockFeatures(mockFeatures.map(feature =>
173
+ feature.id === id ? { ...feature, active: !currentActive } : feature
174
+ ));
219
175
 
220
- // 刷新缓存
221
- await flushCache();
176
+ // 刷新缓存 - 功能启用状态变化是最关键的需要刷新缓存的操作
177
+ refreshCacheAfterUpdate();
222
178
  } else {
223
- message.error(`功能模块${!currentActive ? '启用' : '禁用'}失败: ` + (result.message || '未知错误'));
179
+ message.error(`${currentActive ? '禁用' : '启用'}功能模块失败: ${result.message}`);
224
180
  }
225
181
  } catch (error) {
226
- console.error(`${!currentActive ? '启用' : '禁用'}失败:`, error);
227
- message.error(`${!currentActive ? '启用' : '禁用'}失败: ` + error.message);
182
+ console.error(`${currentActive ? '禁用' : '启用'}功能模块错误:`, error);
183
+ message.error(`操作失败: ${error.message}`);
228
184
  }
229
185
  };
230
186
 
231
- const gotoInterfaceManagement = (featureId) => {
232
- history.push(`/interface/${featureId}`);
187
+ const deleteFeature = async (id) => {
188
+ Modal.confirm({
189
+ title: '确定要删除此功能吗?',
190
+ content: '这将删除所有相关的接口和模拟数据,此操作无法恢复。',
191
+ okText: '删除',
192
+ okType: 'danger',
193
+ cancelText: '取消',
194
+ onOk: async () => {
195
+ try {
196
+ setLoading(true);
197
+ const response = await fetch(`/cgi-bin/features?id=${id}`, {
198
+ method: 'DELETE'
199
+ });
200
+
201
+ const result = await response.json();
202
+
203
+ if (result.code === 0) {
204
+ message.success('功能模块已成功删除');
205
+ // 更新本地状态
206
+ setMockFeatures(mockFeatures.filter(f => f.id !== id));
207
+
208
+ // 刷新缓存
209
+ refreshCacheAfterUpdate();
210
+ } else {
211
+ message.error('删除失败: ' + result.message);
212
+ }
213
+ } catch (error) {
214
+ console.error('删除功能错误:', error);
215
+ message.error('操作失败: ' + error.message);
216
+ } finally {
217
+ setLoading(false);
218
+ }
219
+ }
220
+ });
221
+ };
222
+
223
+ const viewInterfaces = (feature) => {
224
+ // 导航到该功能的接口列表页面
225
+ history.push(`/interface/${feature.id}`);
233
226
  };
234
227
 
235
- // 导出功能配置
236
- const exportFeatureConfig = async () => {
228
+ const exportFeatureConfig = async (feature) => {
237
229
  try {
238
- const response = await fetch('/cgi-bin/export-features');
230
+ // 获取该功能的所有接口
231
+ const response = await fetch(`/cgi-bin/interfaces?featureId=${feature.id}`);
239
232
  const result = await response.json();
240
233
 
241
- if (result.code === 0) {
242
- // 创建下载链接
243
- const dataStr = JSON.stringify(result.data, null, 2);
244
- const dataBlob = new Blob([dataStr], { type: 'application/json' });
245
- const url = URL.createObjectURL(dataBlob);
246
- const link = document.createElement('a');
247
- link.href = url;
248
- link.download = `whistle-mock-features-${new Date().getTime()}.json`;
249
- document.body.appendChild(link);
250
- link.click();
251
- document.body.removeChild(link);
252
- URL.revokeObjectURL(url);
253
-
254
- message.success('功能配置导出成功');
255
- } else {
256
- message.error('导出失败: ' + (result.message || '未知错误'));
257
- }
234
+ // 创建完整配置
235
+ const config = {
236
+ ...feature,
237
+ interfaces: result.code === 0 ? result.data : []
238
+ };
239
+
240
+ const dataStr = JSON.stringify(config, null, 2);
241
+ const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
242
+
243
+ const exportFileDefaultName = `whistle-mock-feature-${feature.id}.json`;
244
+
245
+ const linkElement = document.createElement('a');
246
+ linkElement.setAttribute('href', dataUri);
247
+ linkElement.setAttribute('download', exportFileDefaultName);
248
+ linkElement.click();
249
+
250
+ message.success('配置导出成功');
258
251
  } catch (error) {
259
- console.error('导出失败:', error);
252
+ console.error('导出配置错误:', error);
260
253
  message.error('导出失败: ' + error.message);
261
254
  }
262
255
  };
263
256
 
264
- // 导入功能配置
265
257
  const importFeatureConfig = () => {
266
258
  const input = document.createElement('input');
267
259
  input.type = 'file';
268
260
  input.accept = '.json';
261
+
269
262
  input.onchange = async (e) => {
270
263
  const file = e.target.files[0];
271
264
  if (!file) return;
272
265
 
273
- try {
274
- const text = await file.text();
275
- const data = JSON.parse(text);
276
-
277
- const response = await fetch('/cgi-bin/import-features', {
278
- method: 'POST',
279
- headers: {
280
- 'Content-Type': 'application/json',
281
- },
282
- body: JSON.stringify(data),
283
- });
284
-
285
- const result = await response.json();
286
- if (result.code === 0) {
287
- message.success('功能配置导入成功');
288
- fetchFeatures();
266
+ const reader = new FileReader();
267
+ reader.onload = async (event) => {
268
+ try {
269
+ const config = JSON.parse(event.target.result);
270
+
271
+ // 验证导入的配置
272
+ if (!config.name) {
273
+ message.error('无效的配置文件: 缺少功能名称');
274
+ return;
275
+ }
276
+
277
+ // 创建新功能
278
+ const featureData = {
279
+ name: config.name,
280
+ description: config.description || '',
281
+ active: config.active !== false
282
+ };
283
+
284
+ // 保存功能
285
+ const featureResponse = await fetch('/cgi-bin/features', {
286
+ method: 'POST',
287
+ headers: {
288
+ 'Content-Type': 'application/json'
289
+ },
290
+ body: JSON.stringify(featureData)
291
+ });
289
292
 
290
- // 刷新缓存
291
- await flushCache();
292
- } else {
293
- message.error('导入失败: ' + (result.message || '未知错误'));
293
+ const featureResult = await featureResponse.json();
294
+
295
+ if (featureResult.code === 0) {
296
+ const newFeature = featureResult.data;
297
+
298
+ // 导入接口配置
299
+ if (Array.isArray(config.interfaces) && config.interfaces.length > 0) {
300
+ message.loading({ content: '正在导入接口配置...', key: 'importInfo' });
301
+
302
+ let successCount = 0;
303
+ let errorCount = 0;
304
+
305
+ for (const interfaceItem of config.interfaces) {
306
+ try {
307
+ // 创建接口,使用新功能ID
308
+ const interfaceData = {
309
+ ...interfaceItem,
310
+ featureId: newFeature.id,
311
+ id: undefined // 不使用原接口ID
312
+ };
313
+
314
+ await fetch('/cgi-bin/interfaces', {
315
+ method: 'POST',
316
+ headers: {
317
+ 'Content-Type': 'application/json'
318
+ },
319
+ body: JSON.stringify(interfaceData)
320
+ });
321
+
322
+ successCount++;
323
+ } catch (error) {
324
+ console.error('导入接口配置失败:', error);
325
+ errorCount++;
326
+ }
327
+ }
328
+
329
+ if (errorCount > 0) {
330
+ message.info({ content: `导入完成,成功 ${successCount} 个接口,失败 ${errorCount} 个接口`, key: 'importInfo', duration: 3 });
331
+ } else {
332
+ message.success({ content: `成功导入 ${successCount} 个接口`, key: 'importInfo', duration: 2 });
333
+ }
334
+ } else {
335
+ message.success('功能导入成功(无接口配置)');
336
+ }
337
+
338
+ // 刷新功能列表
339
+ fetchFeatures();
340
+ } else {
341
+ message.error('导入功能失败: ' + featureResult.message);
342
+ }
343
+ } catch (error) {
344
+ console.error('导入配置错误:', error);
345
+ message.error('导入失败: ' + error.message);
294
346
  }
295
- } catch (error) {
296
- console.error('导入失败:', error);
297
- message.error('导入失败: ' + error.message);
298
- }
347
+ };
348
+
349
+ reader.readAsText(file);
299
350
  };
351
+
300
352
  input.click();
301
353
  };
302
354
 
303
- // 搜索和筛选状态
304
- const [searchText, setSearchText] = useState('');
305
- const [filterStatus, setFilterStatus] = useState('all'); // all, active, inactive
355
+ // 排序处理
356
+ const handleSortChange = (value) => {
357
+ saveListConfig({ sortBy: value, current: 1 });
358
+ };
306
359
 
307
- // 筛选和排序数据
308
- const getFilteredAndSortedData = () => {
309
- let filtered = [...mockFeatures];
360
+ const handleSortOrderChange = (value) => {
361
+ saveListConfig({ sortOrder: value, current: 1 });
362
+ };
310
363
 
311
- // 文本搜索
312
- if (searchText) {
313
- const searchLower = searchText.toLowerCase();
314
- filtered = filtered.filter(item =>
315
- item.name.toLowerCase().includes(searchLower) ||
316
- (item.description && item.description.toLowerCase().includes(searchLower))
317
- );
318
- }
364
+ const handlePageSizeChange = (value) => {
365
+ saveListConfig({ pageSize: value, current: 1 });
366
+ };
319
367
 
320
- // 状态筛选
321
- if (filterStatus === 'active') {
322
- filtered = filtered.filter(item => item.active !== false);
323
- } else if (filterStatus === 'inactive') {
324
- filtered = filtered.filter(item => item.active === false);
325
- }
368
+ const handlePageChange = (page, pageSize) => {
369
+ saveListConfig({ current: page, pageSize });
370
+ };
326
371
 
327
- // 排序
328
- filtered.sort((a, b) => {
372
+ // 获取排序后的功能列表
373
+ const getSortedFeatures = () => {
374
+ const sorted = [...mockFeatures].sort((a, b) => {
329
375
  let aValue, bValue;
330
376
 
331
377
  switch (listConfig.sortBy) {
332
378
  case 'name':
333
379
  aValue = a.name || '';
334
380
  bValue = b.name || '';
335
- break;
381
+ return listConfig.sortOrder === 'ascend'
382
+ ? aValue.localeCompare(bValue, 'zh-CN')
383
+ : bValue.localeCompare(aValue, 'zh-CN');
384
+
336
385
  case 'createdAt':
337
- aValue = new Date(a.createdAt || 0);
338
- bValue = new Date(b.createdAt || 0);
339
- break;
386
+ aValue = new Date(a.createdAt || 0).getTime();
387
+ bValue = new Date(b.createdAt || 0).getTime();
388
+ return listConfig.sortOrder === 'ascend' ? aValue - bValue : bValue - aValue;
389
+
340
390
  case 'interfaceCount':
341
391
  aValue = a.interfaceCount || 0;
342
392
  bValue = b.interfaceCount || 0;
343
- break;
393
+ return listConfig.sortOrder === 'ascend' ? aValue - bValue : bValue - aValue;
394
+
344
395
  case 'active':
345
- aValue = a.active === false ? 0 : 1;
346
- bValue = b.active === false ? 0 : 1;
347
- break;
396
+ aValue = a.active ? 1 : 0;
397
+ bValue = b.active ? 1 : 0;
398
+ return listConfig.sortOrder === 'ascend' ? aValue - bValue : bValue - aValue;
399
+
348
400
  default:
349
- aValue = a.name || '';
350
- bValue = b.name || '';
351
- }
352
-
353
- if (listConfig.sortOrder === 'descend') {
354
- return aValue < bValue ? 1 : aValue > bValue ? -1 : 0;
355
- } else {
356
- return aValue > bValue ? 1 : aValue < bValue ? -1 : 0;
401
+ return 0;
357
402
  }
358
403
  });
359
-
360
- return filtered;
404
+
405
+ return sorted;
361
406
  };
362
407
 
363
- const filteredData = getFilteredAndSortedData();
364
-
365
- // 分页计算
366
- const startIndex = (listConfig.current - 1) * listConfig.pageSize;
367
- const endIndex = startIndex + listConfig.pageSize;
368
- const paginatedData = filteredData.slice(startIndex, endIndex);
369
-
370
- // 排序菜单
371
- const sortMenu = (
372
- <Menu onClick={({ key }) => {
373
- const [sortBy, sortOrder] = key.split('-');
374
- saveListConfig({ sortBy, sortOrder, current: 1 });
375
- }}>
376
- <Menu.Item key="name-ascend">名称 (A-Z)</Menu.Item>
377
- <Menu.Item key="name-descend">名称 (Z-A)</Menu.Item>
378
- <Menu.Item key="createdAt-descend">创建时间 (新-旧)</Menu.Item>
379
- <Menu.Item key="createdAt-ascend">创建时间 (旧-新)</Menu.Item>
380
- <Menu.Item key="interfaceCount-descend">接口数量 (多-少)</Menu.Item>
381
- <Menu.Item key="interfaceCount-ascend">接口数量 (少-多)</Menu.Item>
382
- <Menu.Item key="active-descend">状态 (启用优先)</Menu.Item>
383
- <Menu.Item key="active-ascend">状态 (禁用优先)</Menu.Item>
384
- </Menu>
385
- );
408
+ // 获取分页后的功能列表
409
+ const getPaginatedFeatures = () => {
410
+ const sortedFeatures = getSortedFeatures();
411
+ const startIndex = (listConfig.current - 1) * listConfig.pageSize;
412
+ const endIndex = startIndex + listConfig.pageSize;
413
+ return sortedFeatures.slice(startIndex, endIndex);
414
+ };
386
415
 
416
+ // 渲染功能模块卡片
387
417
  const renderFeatureCard = (feature) => {
388
- const isActive = feature.active !== false;
418
+ const formattedDate = feature.createdAt
419
+ ? new Date(feature.createdAt).toLocaleDateString()
420
+ : '未知日期';
421
+
422
+ // 处理卡片点击事件
423
+ const handleCardClick = (e) => {
424
+ // 检查点击目标是否是开关或其父元素
425
+ const target = e.target;
426
+ const switchElement = target.closest('.ant-switch') || target.closest('.feature-name .ant-switch');
427
+
428
+ // 如果点击的是开关区域,则不执行跳转
429
+ if (switchElement) {
430
+ return;
431
+ }
432
+
433
+ // 检查是否点击了操作按钮区域
434
+ const actionElement = target.closest('.ant-card-actions') || target.closest('.ant-card-actions li');
435
+ if (actionElement) {
436
+ return;
437
+ }
438
+
439
+ // 执行跳转到接口管理界面
440
+ viewInterfaces(feature);
441
+ };
442
+
443
+ // 处理开关点击事件,阻止事件冒泡
444
+ const handleSwitchClick = (checked, e) => {
445
+ e.stopPropagation(); // 阻止事件冒泡到卡片
446
+ handleToggleActive(feature.id, feature.active);
447
+ };
389
448
 
390
449
  return (
391
- <Col xs={24} sm={12} md={8} lg={6} key={feature.id}>
450
+ <Col xs={24} sm={12} md={8} lg={6} key={feature.id} style={{ marginBottom: 16 }}>
392
451
  <Badge.Ribbon
393
- text={isActive ? "启用" : "禁用"}
394
- color={isActive ? "green" : "volcano"}
452
+ text={feature.active ? '已启用' : '已禁用'}
453
+ color={feature.active ? '#52c41a' : '#f5222d'}
454
+ style={{ display: 'block' }}
395
455
  >
396
456
  <Card
397
- className={`feature-card ${!isActive ? 'inactive' : ''}`}
398
- hoverable
457
+ className={`feature-card ${!feature.active ? 'inactive-feature' : ''}`}
458
+ onClick={handleCardClick}
459
+ style={{ cursor: 'pointer' }}
399
460
  actions={[
461
+ <Tooltip title="管理接口">
462
+ <ApiOutlined key="interfaces" onClick={(e) => {
463
+ e.stopPropagation();
464
+ viewInterfaces(feature);
465
+ }} />
466
+ </Tooltip>,
400
467
  <Tooltip title="编辑功能">
401
- <EditOutlined key="edit" onClick={() => openModal(feature)} />
468
+ <EditOutlined key="edit" onClick={(e) => {
469
+ e.stopPropagation();
470
+ openModal(feature);
471
+ }} />
402
472
  </Tooltip>,
403
- <Tooltip title="管理接口">
404
- <SettingOutlined key="setting" onClick={() => gotoInterfaceManagement(feature.id)} />
473
+ <Tooltip title="删除功能">
474
+ <DeleteOutlined key="delete" onClick={(e) => {
475
+ e.stopPropagation();
476
+ deleteFeature(feature.id);
477
+ }} />
405
478
  </Tooltip>,
406
- <Popconfirm
407
- title="确定删除此功能模块吗?"
408
- description="删除后相关的所有接口配置也会被删除,且无法恢复。"
409
- onConfirm={() => handleDelete(feature.id)}
410
- okText="确定"
411
- cancelText="取消"
412
- >
413
- <Tooltip title="删除功能">
414
- <DeleteOutlined key="delete" style={{ color: '#ff4d4f' }} />
415
- </Tooltip>
416
- </Popconfirm>,
479
+ <Tooltip title="导出配置">
480
+ <ExportOutlined key="export" onClick={(e) => {
481
+ e.stopPropagation();
482
+ exportFeatureConfig(feature);
483
+ }} />
484
+ </Tooltip>
417
485
  ]}
418
486
  >
419
- <div className="feature-card-header">
487
+ <div className="feature-card-content">
420
488
  <div className="feature-name">
421
- {feature.name}
422
- </div>
423
- <Switch
424
- checked={isActive}
425
- onChange={() => handleToggleActive(feature.id, isActive)}
426
- size="small"
427
- />
428
- </div>
429
- <div className="feature-description">
430
- {feature.description || '暂无描述'}
431
- </div>
432
- <div className="feature-stats">
433
- <div className="stat-item">
434
- <span className="stat-label">接口数量:</span>
435
- <span className="stat-value">{feature.interfaceCount || 0}</span>
489
+ <Title level={4} ellipsis={{ tooltip: feature.name }}>
490
+ {feature.name}
491
+ </Title>
492
+
493
+ <Switch
494
+ checked={feature.active}
495
+ onChange={handleSwitchClick}
496
+ size="small"
497
+ onClick={() => {event.stopPropagation()}}
498
+ />
436
499
  </div>
437
- <div className="stat-item">
438
- <span className="stat-label">创建时间:</span>
439
- <span className="stat-value">
440
- {feature.createdAt ? new Date(feature.createdAt).toLocaleDateString() : '未知'}
500
+
501
+ <Paragraph className="feature-description" ellipsis={{ rows: 2, expandable: false, tooltip: feature.description }}>
502
+ {feature.description || '无描述'}
503
+ </Paragraph>
504
+
505
+ <div className="feature-stat">
506
+ <span>
507
+ <InfoCircleOutlined /> {feature.interfaceCount || 0} 个接口
508
+ </span>
509
+ <span className="feature-date">
510
+ <CalendarOutlined /> {formattedDate}
441
511
  </span>
442
512
  </div>
443
513
  </div>
@@ -462,7 +532,7 @@ const MockData = () => {
462
532
  fontSize: '12px',
463
533
  zIndex: 1000
464
534
  }}>
465
- 重构后版本 V2
535
+ V2版本
466
536
  </div>
467
537
 
468
538
  <div className="page-title-bar">
@@ -493,208 +563,162 @@ const MockData = () => {
493
563
 
494
564
  {/* 排序和分页控制栏 */}
495
565
  {mockFeatures.length > 0 && !loading && (
496
- <Card className="filter-controls" bordered={false}>
497
- <div className="control-row">
498
- <div className="search-controls">
499
- <Input
500
- placeholder="搜索功能名称或描述"
501
- prefix={<SearchOutlined />}
502
- value={searchText}
503
- onChange={(e) => setSearchText(e.target.value)}
504
- style={{ width: 240 }}
505
- allowClear
506
- />
507
- <Select
508
- value={filterStatus}
509
- onChange={setFilterStatus}
510
- style={{ width: 120 }}
511
- >
512
- <Option value="all">全部状态</Option>
513
- <Option value="active">已启用</Option>
514
- <Option value="inactive">已禁用</Option>
515
- </Select>
516
- </div>
517
-
518
- <div className="action-controls">
519
- <Space>
520
- <Dropdown overlay={sortMenu} trigger={['click']}>
521
- <Button icon={<SortAscendingOutlined />}>
522
- 排序
523
- </Button>
524
- </Dropdown>
525
- <Button
526
- icon={<DownloadOutlined />}
527
- onClick={exportFeatureConfig}
528
- >
529
- 导出配置
530
- </Button>
531
- <Button
532
- icon={<ReloadOutlined />}
533
- onClick={fetchFeatures}
534
- loading={loading}
535
- >
536
- 刷新
537
- </Button>
538
- {(searchText || filterStatus !== 'all') && (
539
- <Button
540
- icon={<ClearOutlined />}
541
- onClick={() => {
542
- setSearchText('');
543
- setFilterStatus('all');
544
- }}
545
- >
546
- 清除筛选
547
- </Button>
548
- )}
549
- </Space>
550
- </div>
566
+ <div style={{
567
+ marginBottom: 16,
568
+ padding: '12px 16px',
569
+ background: '#fafafa',
570
+ borderRadius: '6px',
571
+ display: 'flex',
572
+ justifyContent: 'space-between',
573
+ alignItems: 'center',
574
+ flexWrap: 'wrap',
575
+ gap: '8px'
576
+ }}>
577
+ <div style={{ display: 'flex', alignItems: 'center', gap: '12px', flexWrap: 'wrap' }}>
578
+ <span style={{ fontSize: '14px', color: '#666' }}>
579
+ <SortAscendingOutlined /> 排序:
580
+ </span>
581
+ <Select
582
+ value={listConfig.sortBy}
583
+ onChange={handleSortChange}
584
+ style={{ width: 120 }}
585
+ size="small"
586
+ >
587
+ <Select.Option value="name">名称</Select.Option>
588
+ <Select.Option value="createdAt">创建时间</Select.Option>
589
+ <Select.Option value="interfaceCount">接口数量</Select.Option>
590
+ <Select.Option value="active">状态</Select.Option>
591
+ </Select>
592
+ <Select
593
+ value={listConfig.sortOrder}
594
+ onChange={handleSortOrderChange}
595
+ style={{ width: 80 }}
596
+ size="small"
597
+ >
598
+ <Select.Option value="ascend">升序</Select.Option>
599
+ <Select.Option value="descend">降序</Select.Option>
600
+ </Select>
551
601
  </div>
552
602
 
553
- <div className="result-info">
554
- <Space>
555
- <span>
556
- 共 {mockFeatures.length} 个功能模块
557
- {filteredData.length !== mockFeatures.length && (
558
- <span>,筛选后 {filteredData.length} 个</span>
559
- )}
560
- </span>
561
- <Badge
562
- count={mockFeatures.filter(f => f.active !== false).length}
563
- style={{ backgroundColor: '#52c41a' }}
564
- title="启用的功能数量"
565
- />
566
- <Badge
567
- count={mockFeatures.filter(f => f.active === false).length}
568
- style={{ backgroundColor: '#ff4d4f' }}
569
- title="禁用的功能数量"
570
- />
571
- </Space>
603
+ <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
604
+ <span style={{ fontSize: '14px', color: '#666' }}>每页显示:</span>
605
+ <Select
606
+ value={listConfig.pageSize}
607
+ onChange={handlePageSizeChange}
608
+ style={{ width: 80 }}
609
+ size="small"
610
+ >
611
+ <Select.Option value={8}>8</Select.Option>
612
+ <Select.Option value={12}>12</Select.Option>
613
+ <Select.Option value={16}>16</Select.Option>
614
+ <Select.Option value={24}>24</Select.Option>
615
+ </Select>
616
+ <span style={{ fontSize: '14px', color: '#999' }}>
617
+ 共 {mockFeatures.length} 个功能模块
618
+ </span>
572
619
  </div>
573
- </Card>
620
+ </div>
574
621
  )}
575
-
576
- {/* 功能模块列表 */}
622
+
577
623
  <div className="feature-list-container">
578
624
  {loading ? (
579
- <div className="loading-container">
580
- <Row gutter={[16, 16]}>
581
- {[1, 2, 3, 4].map(i => (
582
- <Col xs={24} sm={12} md={8} lg={6} key={i}>
583
- <Card loading style={{ height: 200 }} />
584
- </Col>
585
- ))}
586
- </Row>
625
+ <div className="loading">
626
+ <div className="loading-spinner"></div>
627
+ <div>正在加载功能模块...</div>
587
628
  </div>
588
- ) : mockFeatures.length === 0 ? (
589
- <Alert
590
- message="暂无功能模块"
591
- description="点击 '新建功能' 按钮创建第一个功能模块"
592
- type="info"
593
- showIcon
594
- style={{ textAlign: 'center', padding: '60px 20px' }}
595
- />
596
- ) : filteredData.length === 0 ? (
597
- <Alert
598
- message="无匹配结果"
599
- description="请调整搜索条件或筛选设置"
600
- type="warning"
601
- showIcon
602
- style={{ textAlign: 'center', padding: '60px 20px' }}
603
- />
604
- ) : (
629
+ ) : mockFeatures.length > 0 ? (
605
630
  <>
606
- <Row gutter={[16, 16]} className="feature-grid">
607
- {paginatedData.map(renderFeatureCard)}
631
+ <Row gutter={[16, 16]}>
632
+ {getPaginatedFeatures().map(feature => renderFeatureCard(feature))}
608
633
  </Row>
609
634
 
610
- {/* 分页控件 */}
611
- {filteredData.length > listConfig.pageSize && (
612
- <div className="pagination-container">
635
+ {/* 分页组件 */}
636
+ {mockFeatures.length > listConfig.pageSize && (
637
+ <div style={{
638
+ marginTop: 24,
639
+ textAlign: 'center',
640
+ padding: '16px',
641
+ borderTop: '1px solid #f0f0f0'
642
+ }}>
613
643
  <Pagination
614
644
  current={listConfig.current}
615
645
  pageSize={listConfig.pageSize}
616
- total={filteredData.length}
617
- showSizeChanger
618
- showQuickJumper
646
+ total={mockFeatures.length}
647
+ onChange={handlePageChange}
648
+ onShowSizeChange={handlePageChange}
649
+ showSizeChanger={true}
650
+ showQuickJumper={true}
619
651
  showTotal={(total, range) =>
620
- `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
652
+ `第 ${range[0]}-${range[1]} 条,共 ${total} 个功能模块`
621
653
  }
622
654
  pageSizeOptions={['8', '12', '16', '24']}
623
- onChange={(current, pageSize) => {
624
- saveListConfig({ current, pageSize });
625
- }}
655
+ size="default"
626
656
  />
627
657
  </div>
628
658
  )}
629
659
  </>
660
+ ) : (
661
+ <div className="empty-data">
662
+ <div className="empty-icon">📂</div>
663
+ <div className="empty-text">暂无功能,请点击"新建功能"按钮创建</div>
664
+ <div className="empty-actions">
665
+ <button className="create-button" onClick={() => openModal()}>
666
+ 创建新功能
667
+ </button>
668
+ <button className="import-button-large" onClick={importFeatureConfig}>
669
+ 导入已有功能
670
+ </button>
671
+ </div>
672
+ </div>
630
673
  )}
631
674
  </div>
632
-
633
- {/* 新建/编辑功能模块弹窗 */}
634
- <Modal
635
- title={currentFeature ? '编辑功能模块' : '新建功能模块'}
636
- open={showModal}
637
- onCancel={() => setShowModal(false)}
638
- footer={null}
639
- destroyOnClose
675
+ </div>
676
+
677
+ <Modal
678
+ title={currentFeature ? '编辑功能' : '新建功能'}
679
+ open={showModal}
680
+ onCancel={closeModal}
681
+ footer={null}
682
+ destroyOnClose
683
+ width={500}
684
+ >
685
+ <Form
686
+ form={form}
687
+ layout="vertical"
688
+ onFinish={handleSubmit}
689
+ initialValues={{
690
+ name: '',
691
+ description: '',
692
+ active: true
693
+ }}
640
694
  >
641
- <Form
642
- form={form}
643
- layout="vertical"
644
- onFinish={handleSubmit}
645
- initialValues={{
646
- name: '',
647
- description: '',
648
- active: true
649
- }}
695
+ <Form.Item
696
+ name="name"
697
+ label="功能名称"
698
+ rules={[{ required: true, message: '请输入功能名称' }]}
650
699
  >
651
- <Form.Item
652
- name="name"
653
- label="功能名称"
654
- rules={[
655
- { required: true, message: '请输入功能名称' },
656
- { max: 50, message: '功能名称不能超过50个字符' }
657
- ]}
658
- >
659
- <Input placeholder="请输入功能名称" />
660
- </Form.Item>
661
-
662
- <Form.Item
663
- name="description"
664
- label="功能描述"
665
- rules={[
666
- { max: 200, message: '功能描述不能超过200个字符' }
667
- ]}
668
- >
669
- <TextArea
670
- placeholder="请输入功能描述(可选)"
671
- rows={3}
672
- showCount
673
- maxLength={200}
674
- />
675
- </Form.Item>
676
-
677
- <Form.Item
678
- name="active"
679
- label="启用状态"
680
- valuePropName="checked"
681
- >
682
- <Switch checkedChildren="启用" unCheckedChildren="禁用" />
683
- </Form.Item>
684
-
685
- <Form.Item className="form-actions">
686
- <Space>
687
- <Button onClick={() => setShowModal(false)}>
688
- 取消
689
- </Button>
690
- <Button type="primary" htmlType="submit">
691
- {currentFeature ? '保存' : '创建'}
692
- </Button>
693
- </Space>
694
- </Form.Item>
695
- </Form>
696
- </Modal>
697
- </div>
700
+ <Input placeholder="请输入功能名称" />
701
+ </Form.Item>
702
+
703
+ <Form.Item
704
+ name="description"
705
+ label="功能描述"
706
+ >
707
+ <TextArea
708
+ placeholder="请输入功能描述(可选)"
709
+ autoSize={{ minRows: 3, maxRows: 6 }}
710
+ />
711
+ </Form.Item>
712
+ <div className="form-actions">
713
+ <Button onClick={closeModal}>
714
+ 取消
715
+ </Button>
716
+ <Button type="primary" htmlType="submit" loading={loading}>
717
+ 确定
718
+ </Button>
719
+ </div>
720
+ </Form>
721
+ </Modal>
698
722
  </AppLayout>
699
723
  );
700
724
  };