whistle.mock-plugins 1.1.3-beta.2 → 1.1.3-dev.4

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.
@@ -25,51 +25,45 @@ const { TextArea } = Input;
25
25
  // 刷新缓存工具函数
26
26
  const flushCache = async () => {
27
27
  try {
28
- await fetch('/cgi-bin/flushCache', { method: 'POST' });
28
+ await fetch('/cgi-bin/flush-cache', { method: 'POST' });
29
+ console.log('缓存刷新成功');
29
30
  } catch (error) {
30
- console.error('刷新缓存失败:', error);
31
- throw error;
31
+ console.error('缓存刷新失败:', error);
32
32
  }
33
33
  };
34
34
 
35
-
36
35
  const MockData = () => {
37
36
  const history = useHistory();
38
- const [mockFeatures, setMockFeatures] = useState([]);
39
- const [showModal, setShowModal] = useState(false);
40
- const [currentFeature, setCurrentFeature] = useState(null);
41
- const [formData, setFormData] = useState({
42
- name: '',
43
- description: '',
44
- active: true
45
- });
46
- const [loading, setLoading] = useState(true);
37
+
38
+ // 功能模块状态
39
+ const [features, setFeatures] = useState([]);
47
40
  const [form] = Form.useForm();
41
+ const [loading, setLoading] = useState(false);
42
+ const [modalVisible, setModalVisible] = useState(false);
43
+ const [editingFeature, setEditingFeature] = useState(null);
48
44
 
49
- // 排序和分页配置(支持缓存)
50
- const [listConfig, setListConfig] = useState(() => {
51
- const cached = localStorage.getItem('feature-list-config');
52
- return cached ? JSON.parse(cached) : {
53
- sortBy: 'name', // name, createdAt, interfaceCount, active
54
- sortOrder: 'ascend', // ascend, descend
55
- pageSize: 12,
56
- current: 1
57
- };
58
- });
45
+ // 搜索和筛选状态
46
+ const [searchTerm, setSearchTerm] = useState('');
47
+ const [filteredFeatures, setFilteredFeatures] = useState([]);
48
+ const [statusFilter, setStatusFilter] = useState('all'); // all, active, inactive
49
+ const [sortBy, setSortBy] = useState('name'); // name, createTime, updateTime
59
50
 
60
- // 保存列表配置到localStorage
61
- const saveListConfig = (config) => {
62
- const newConfig = { ...listConfig, ...config };
63
- setListConfig(newConfig);
64
- localStorage.setItem('feature-list-config', JSON.stringify(newConfig));
65
- };
51
+ // 分页状态
52
+ const [currentPage, setCurrentPage] = useState(1);
53
+ const [pageSize, setPageSize] = useState(12);
54
+
55
+ // 导入/导出状态
56
+ const [exportLoading, setExportLoading] = useState(false);
57
+ const [importLoading, setImportLoading] = useState(false);
66
58
 
67
- // 加载功能列表
68
59
  useEffect(() => {
69
60
  fetchFeatures();
70
61
  }, []);
71
62
 
72
- // 获取所有功能模块
63
+ useEffect(() => {
64
+ filterAndSortFeatures();
65
+ }, [features, searchTerm, statusFilter, sortBy]);
66
+
73
67
  const fetchFeatures = async () => {
74
68
  try {
75
69
  setLoading(true);
@@ -77,114 +71,78 @@ const MockData = () => {
77
71
  const result = await response.json();
78
72
 
79
73
  if (result.code === 0) {
80
- setMockFeatures(result.data || []);
74
+ setFeatures(result.data || []);
81
75
  } else {
82
- console.error('获取功能模块失败:', result.message);
83
- message.error('获取功能模块失败: ' + result.message);
76
+ throw new Error(result.message || '获取功能模块失败');
84
77
  }
85
78
  } catch (error) {
86
- console.error('获取功能模块错误:', error);
87
- message.error('获取功能模块失败, 请检查网络连接');
79
+ console.error('获取功能模块失败:', error);
80
+ message.error('获取功能模块失败: ' + error.message);
81
+ setFeatures([]);
88
82
  } finally {
89
83
  setLoading(false);
90
84
  }
91
85
  };
92
86
 
93
- const openModal = (feature = null) => {
94
- setCurrentFeature(feature);
95
- if (feature) {
96
- setFormData({
97
- name: feature.name,
98
- description: feature.description || '',
99
- active: feature.active !== false
100
- });
101
- form.setFieldsValue({
102
- name: feature.name,
103
- description: feature.description || '',
104
- active: feature.active !== false
105
- });
106
- } else {
107
- setFormData({
108
- name: '',
109
- description: '',
110
- active: true
111
- });
112
- form.resetFields();
113
- }
114
- setShowModal(true);
115
- };
87
+ const filterAndSortFeatures = () => {
88
+ let filtered = [...features];
116
89
 
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
- };
90
+ // 搜索筛选
91
+ if (searchTerm) {
92
+ const term = searchTerm.toLowerCase();
93
+ filtered = filtered.filter(feature =>
94
+ feature.name.toLowerCase().includes(term) ||
95
+ (feature.description && feature.description.toLowerCase().includes(term))
96
+ );
97
+ }
125
98
 
126
- console.log('处理后的提交数据:', submitData);
99
+ // 状态筛选
100
+ if (statusFilter === 'active') {
101
+ filtered = filtered.filter(feature => feature.active !== false);
102
+ } else if (statusFilter === 'inactive') {
103
+ filtered = filtered.filter(feature => feature.active === false);
104
+ }
127
105
 
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
- }
106
+ // 排序
107
+ filtered.sort((a, b) => {
108
+ switch (sortBy) {
109
+ case 'name':
110
+ return a.name.localeCompare(b.name);
111
+ case 'createTime':
112
+ return new Date(b.createTime || 0) - new Date(a.createTime || 0);
113
+ case 'updateTime':
114
+ return new Date(b.updateTime || 0) - new Date(a.updateTime || 0);
115
+ default:
116
+ return 0;
174
117
  }
175
- } catch (error) {
176
- console.error('操作失败:', error);
177
- message.error('操作失败: ' + error.message);
178
- }
118
+ });
119
+
120
+ setFilteredFeatures(filtered);
121
+ setCurrentPage(1); // 重置到第一页
122
+ };
123
+
124
+ const handleAddFeature = () => {
125
+ form.resetFields();
126
+ setEditingFeature(null);
127
+ setModalVisible(true);
179
128
  };
180
129
 
181
- const handleDelete = async (id) => {
130
+ const handleEditFeature = (feature) => {
131
+ form.setFieldsValue({
132
+ name: feature.name,
133
+ description: feature.description
134
+ });
135
+ setEditingFeature(feature);
136
+ setModalVisible(true);
137
+ };
138
+
139
+ const handleDeleteFeature = async (id) => {
182
140
  try {
183
141
  const response = await fetch(`/cgi-bin/features?id=${id}`, {
184
- method: 'DELETE',
142
+ method: 'DELETE'
185
143
  });
186
-
187
144
  const result = await response.json();
145
+
188
146
  if (result.code === 0) {
189
147
  message.success('功能模块删除成功');
190
148
  fetchFeatures();
@@ -192,27 +150,29 @@ const MockData = () => {
192
150
  // 刷新缓存
193
151
  await flushCache();
194
152
  } else {
195
- message.error('功能模块删除失败: ' + (result.message || '未知错误'));
153
+ throw new Error(result.message || '删除失败');
196
154
  }
197
155
  } catch (error) {
198
- console.error('删除失败:', error);
199
- message.error('删除失败: ' + error.message);
156
+ console.error('删除功能模块失败:', error);
157
+ message.error('删除功能模块失败: ' + error.message);
200
158
  }
201
159
  };
202
160
 
203
161
  const handleToggleActive = async (id, currentActive) => {
204
162
  try {
205
- const response = await fetch(`/cgi-bin/features?id=${id}`, {
206
- method: 'PUT',
163
+ const response = await fetch(`/cgi-bin/features`, {
164
+ method: 'PATCH',
207
165
  headers: {
208
- 'Content-Type': 'application/json',
166
+ 'Content-Type': 'application/json'
209
167
  },
210
168
  body: JSON.stringify({
169
+ id,
211
170
  active: !currentActive
212
- }),
171
+ })
213
172
  });
214
173
 
215
174
  const result = await response.json();
175
+
216
176
  if (result.code === 0) {
217
177
  message.success(`功能模块${!currentActive ? '启用' : '禁用'}成功`);
218
178
  fetchFeatures();
@@ -220,236 +180,194 @@ const MockData = () => {
220
180
  // 刷新缓存
221
181
  await flushCache();
222
182
  } else {
223
- message.error(`功能模块${!currentActive ? '启用' : '禁用'}失败: ` + (result.message || '未知错误'));
183
+ throw new Error(result.message || '操作失败');
224
184
  }
225
185
  } catch (error) {
226
- console.error(`${!currentActive ? '启用' : '禁用'}失败:`, error);
227
- message.error(`${!currentActive ? '启用' : '禁用'}失败: ` + error.message);
186
+ console.error('切换功能模块状态失败:', error);
187
+ message.error('操作失败: ' + error.message);
228
188
  }
229
189
  };
230
190
 
231
- const gotoInterfaceManagement = (featureId) => {
232
- history.push(`/interface/${featureId}`);
233
- };
234
-
235
- // 导出功能配置
236
- const exportFeatureConfig = async () => {
191
+ const handleSubmit = async () => {
237
192
  try {
238
- const response = await fetch('/cgi-bin/export-features');
193
+ const values = await form.validateFields();
194
+
195
+ const requestData = {
196
+ name: values.name,
197
+ description: values.description || '',
198
+ active: true
199
+ };
200
+
201
+ const url = editingFeature
202
+ ? `/cgi-bin/features?id=${editingFeature.id}`
203
+ : '/cgi-bin/features';
204
+
205
+ const method = editingFeature ? 'PUT' : 'POST';
206
+
207
+ const response = await fetch(url, {
208
+ method,
209
+ headers: {
210
+ 'Content-Type': 'application/json'
211
+ },
212
+ body: JSON.stringify(requestData)
213
+ });
214
+
239
215
  const result = await response.json();
240
216
 
241
217
  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);
218
+ message.success(editingFeature ? '功能模块更新成功' : '功能模块创建成功');
219
+ setModalVisible(false);
220
+ fetchFeatures();
253
221
 
254
- message.success('功能配置导出成功');
222
+ // 刷新缓存
223
+ await flushCache();
255
224
  } else {
256
- message.error('导出失败: ' + (result.message || '未知错误'));
225
+ throw new Error(result.message || '操作失败');
226
+ }
227
+ } catch (error) {
228
+ console.error('提交失败:', error);
229
+ message.error('操作失败: ' + error.message);
230
+ }
231
+ };
232
+
233
+ const handleCancel = () => {
234
+ setModalVisible(false);
235
+ };
236
+
237
+ const handleNavigateToInterfaces = (featureId) => {
238
+ history.push(`/interface-management/${featureId}`);
239
+ };
240
+
241
+ // 导出功能
242
+ const handleExport = async () => {
243
+ try {
244
+ setExportLoading(true);
245
+ const response = await fetch('/cgi-bin/features/export');
246
+
247
+ if (!response.ok) {
248
+ throw new Error('导出失败');
257
249
  }
250
+
251
+ const blob = await response.blob();
252
+ const url = window.URL.createObjectURL(blob);
253
+ const a = document.createElement('a');
254
+ a.href = url;
255
+ a.download = `features-${new Date().toISOString().split('T')[0]}.json`;
256
+ document.body.appendChild(a);
257
+ a.click();
258
+ window.URL.revokeObjectURL(url);
259
+ document.body.removeChild(a);
260
+
261
+ message.success('导出成功');
258
262
  } catch (error) {
259
263
  console.error('导出失败:', error);
260
264
  message.error('导出失败: ' + error.message);
265
+ } finally {
266
+ setExportLoading(false);
261
267
  }
262
268
  };
263
269
 
264
- // 导入功能配置
265
- const importFeatureConfig = () => {
266
- const input = document.createElement('input');
267
- input.type = 'file';
268
- input.accept = '.json';
269
- input.onchange = async (e) => {
270
- const file = e.target.files[0];
271
- if (!file) return;
272
-
270
+ // 导入功能
271
+ const handleImport = (event) => {
272
+ const file = event.target.files[0];
273
+ if (!file) return;
274
+
275
+ const reader = new FileReader();
276
+ reader.onload = async (e) => {
273
277
  try {
274
- const text = await file.text();
275
- const data = JSON.parse(text);
278
+ setImportLoading(true);
279
+ const data = JSON.parse(e.target.result);
276
280
 
277
- const response = await fetch('/cgi-bin/import-features', {
281
+ const response = await fetch('/cgi-bin/features/import', {
278
282
  method: 'POST',
279
283
  headers: {
280
- 'Content-Type': 'application/json',
284
+ 'Content-Type': 'application/json'
281
285
  },
282
- body: JSON.stringify(data),
286
+ body: JSON.stringify(data)
283
287
  });
284
-
288
+
285
289
  const result = await response.json();
290
+
286
291
  if (result.code === 0) {
287
- message.success('功能配置导入成功');
292
+ message.success('导入成功');
288
293
  fetchFeatures();
289
-
290
- // 刷新缓存
291
- await flushCache();
292
294
  } else {
293
- message.error('导入失败: ' + (result.message || '未知错误'));
295
+ throw new Error(result.message || '导入失败');
294
296
  }
295
297
  } catch (error) {
296
298
  console.error('导入失败:', error);
297
299
  message.error('导入失败: ' + error.message);
300
+ } finally {
301
+ setImportLoading(false);
302
+ // 清除文件选择
303
+ event.target.value = '';
298
304
  }
299
305
  };
300
- input.click();
301
- };
302
306
 
303
- // 搜索和筛选状态
304
- const [searchText, setSearchText] = useState('');
305
- const [filterStatus, setFilterStatus] = useState('all'); // all, active, inactive
306
-
307
- // 筛选和排序数据
308
- const getFilteredAndSortedData = () => {
309
- let filtered = [...mockFeatures];
310
-
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
- }
319
-
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
- }
326
-
327
- // 排序
328
- filtered.sort((a, b) => {
329
- let aValue, bValue;
330
-
331
- switch (listConfig.sortBy) {
332
- case 'name':
333
- aValue = a.name || '';
334
- bValue = b.name || '';
335
- break;
336
- case 'createdAt':
337
- aValue = new Date(a.createdAt || 0);
338
- bValue = new Date(b.createdAt || 0);
339
- break;
340
- case 'interfaceCount':
341
- aValue = a.interfaceCount || 0;
342
- bValue = b.interfaceCount || 0;
343
- break;
344
- case 'active':
345
- aValue = a.active === false ? 0 : 1;
346
- bValue = b.active === false ? 0 : 1;
347
- break;
348
- default:
349
- aValue = a.name || '';
350
- bValue = b.name || '';
351
- }
307
+ reader.readAsText(file);
308
+ };
352
309
 
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;
357
- }
358
- });
310
+ // 清空搜索
311
+ const handleClearSearch = () => {
312
+ setSearchTerm('');
313
+ };
359
314
 
360
- return filtered;
315
+ // 刷新数据
316
+ const handleRefresh = () => {
317
+ fetchFeatures();
318
+ message.success('数据已刷新');
361
319
  };
362
320
 
363
- const filteredData = getFilteredAndSortedData();
321
+ // 获取分页数据
322
+ const getPaginatedFeatures = () => {
323
+ const startIndex = (currentPage - 1) * pageSize;
324
+ const endIndex = startIndex + pageSize;
325
+ return filteredFeatures.slice(startIndex, endIndex);
326
+ };
364
327
 
365
- // 分页计算
366
- const startIndex = (listConfig.current - 1) * listConfig.pageSize;
367
- const endIndex = startIndex + listConfig.pageSize;
368
- const paginatedData = filteredData.slice(startIndex, endIndex);
328
+ // 筛选菜单
329
+ const filterMenu = (
330
+ <Menu
331
+ selectedKeys={[statusFilter]}
332
+ onClick={({ key }) => setStatusFilter(key)}
333
+ >
334
+ <Menu.Item key="all">全部状态</Menu.Item>
335
+ <Menu.Item key="active">启用</Menu.Item>
336
+ <Menu.Item key="inactive">禁用</Menu.Item>
337
+ </Menu>
338
+ );
369
339
 
370
340
  // 排序菜单
371
341
  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>
342
+ <Menu
343
+ selectedKeys={[sortBy]}
344
+ onClick={({ key }) => setSortBy(key)}
345
+ >
346
+ <Menu.Item key="name">按名称排序</Menu.Item>
347
+ <Menu.Item key="createTime">按创建时间排序</Menu.Item>
348
+ <Menu.Item key="updateTime">按更新时间排序</Menu.Item>
384
349
  </Menu>
385
350
  );
386
351
 
387
- const renderFeatureCard = (feature) => {
388
- const isActive = feature.active !== false;
389
-
390
- return (
391
- <Col xs={24} sm={12} md={8} lg={6} key={feature.id}>
392
- <Badge.Ribbon
393
- text={isActive ? "启用" : "禁用"}
394
- color={isActive ? "green" : "volcano"}
395
- >
396
- <Card
397
- className={`feature-card ${!isActive ? 'inactive' : ''}`}
398
- hoverable
399
- actions={[
400
- <Tooltip title="编辑功能">
401
- <EditOutlined key="edit" onClick={() => openModal(feature)} />
402
- </Tooltip>,
403
- <Tooltip title="管理接口">
404
- <SettingOutlined key="setting" onClick={() => gotoInterfaceManagement(feature.id)} />
405
- </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>,
417
- ]}
418
- >
419
- <div className="feature-card-header">
420
- <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>
436
- </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() : '未知'}
441
- </span>
442
- </div>
443
- </div>
444
- </Card>
445
- </Badge.Ribbon>
446
- </Col>
447
- );
352
+ const getStatusText = (filter) => {
353
+ switch (filter) {
354
+ case 'active': return '启用';
355
+ case 'inactive': return '禁用';
356
+ default: return '全部状态';
357
+ }
358
+ };
359
+
360
+ const getSortText = (sort) => {
361
+ switch (sort) {
362
+ case 'createTime': return '创建时间';
363
+ case 'updateTime': return '更新时间';
364
+ default: return '名称';
365
+ }
448
366
  };
449
367
 
450
368
  return (
451
369
  <AppLayout>
452
- <div className="page-container">
370
+ <div className="mock-data-container">
453
371
  {/* V2版本标识 */}
454
372
  <div style={{
455
373
  position: 'absolute',
@@ -465,233 +383,302 @@ const MockData = () => {
465
383
  重构后版本 V2
466
384
  </div>
467
385
 
468
- <div className="page-title-bar">
469
- <div>
470
- <h1 className="page-title">功能模块管理</h1>
471
- <div className="page-description">
472
- 创建和管理功能模块,为每个功能配置独立的接口
473
- </div>
474
- </div>
475
- <div className="page-actions">
476
- <Button
477
- type="primary"
478
- icon={<PlusOutlined />}
479
- onClick={() => openModal()}
480
- >
481
- 新建功能
482
- </Button>
483
- <Button
484
- type="primary"
485
- icon={<ExportOutlined />}
486
- onClick={importFeatureConfig}
487
- style={{ backgroundColor: '#52c41a', borderColor: '#52c41a' }}
488
- >
489
- 导入功能
490
- </Button>
491
- </div>
492
- </div>
493
-
494
- {/* 排序和分页控制栏 */}
495
- {mockFeatures.length > 0 && !loading && (
496
- <Card className="filter-controls" bordered={false}>
497
- <div className="control-row">
498
- <div className="search-controls">
386
+ {/* 头部操作栏 */}
387
+ <Card className="header-card" bordered={false}>
388
+ <div className="header-content">
389
+ <div className="header-left">
390
+ <h2 className="page-title">功能模块管理</h2>
391
+ <div className="search-container">
499
392
  <Input
500
- placeholder="搜索功能名称或描述"
393
+ placeholder="搜索功能模块名称或描述"
394
+ value={searchTerm}
395
+ onChange={(e) => setSearchTerm(e.target.value)}
396
+ style={{ width: 300 }}
501
397
  prefix={<SearchOutlined />}
502
- value={searchText}
503
- onChange={(e) => setSearchText(e.target.value)}
504
- style={{ width: 240 }}
505
398
  allowClear
399
+ onClear={handleClearSearch}
506
400
  />
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
401
  </div>
517
-
518
- <div className="action-controls">
519
- <Space>
520
- <Dropdown overlay={sortMenu} trigger={['click']}>
521
- <Button icon={<SortAscendingOutlined />}>
522
- 排序
523
- </Button>
524
- </Dropdown>
402
+ </div>
403
+
404
+ <div className="header-right">
405
+ <Space size="middle">
406
+ <Tooltip title="刷新数据">
525
407
  <Button
526
- icon={<DownloadOutlined />}
527
- onClick={exportFeatureConfig}
528
- >
529
- 导出配置
408
+ icon={<ReloadOutlined />}
409
+ onClick={handleRefresh}
410
+ loading={loading}
411
+ />
412
+ </Tooltip>
413
+
414
+ <Dropdown overlay={filterMenu} trigger={['click']}>
415
+ <Button icon={<FilterOutlined />}>
416
+ {getStatusText(statusFilter)}
417
+ </Button>
418
+ </Dropdown>
419
+
420
+ <Dropdown overlay={sortMenu} trigger={['click']}>
421
+ <Button icon={<SortAscendingOutlined />}>
422
+ 按{getSortText(sortBy)}
530
423
  </Button>
424
+ </Dropdown>
425
+
426
+ <Button
427
+ icon={<ExportOutlined />}
428
+ onClick={handleExport}
429
+ loading={exportLoading}
430
+ >
431
+ 导出
432
+ </Button>
433
+
434
+ <div style={{ position: 'relative' }}>
435
+ <input
436
+ type="file"
437
+ accept=".json"
438
+ onChange={handleImport}
439
+ style={{ display: 'none' }}
440
+ id="import-input"
441
+ />
531
442
  <Button
532
- icon={<ReloadOutlined />}
533
- onClick={fetchFeatures}
534
- loading={loading}
443
+ icon={<ImportOutlined />}
444
+ loading={importLoading}
445
+ onClick={() => document.getElementById('import-input').click()}
535
446
  >
536
- 刷新
447
+ 导入
537
448
  </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>
551
- </div>
552
-
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
- />
449
+ </div>
450
+
451
+ <Button
452
+ type="primary"
453
+ icon={<PlusOutlined />}
454
+ onClick={handleAddFeature}
455
+ >
456
+ 添加功能模块
457
+ </Button>
571
458
  </Space>
572
459
  </div>
573
- </Card>
574
- )}
460
+ </div>
461
+ </Card>
462
+
463
+ {/* 统计信息卡片 */}
464
+ <Card className="stats-card" bordered={false}>
465
+ <Row gutter={[24, 24]}>
466
+ <Col xs={24} sm={8} md={6}>
467
+ <div className="stat-item">
468
+ <div className="stat-number">{features.length}</div>
469
+ <div className="stat-label">总功能模块</div>
470
+ </div>
471
+ </Col>
472
+ <Col xs={24} sm={8} md={6}>
473
+ <div className="stat-item">
474
+ <div className="stat-number text-success">
475
+ {features.filter(f => f.active !== false).length}
476
+ </div>
477
+ <div className="stat-label">启用模块</div>
478
+ </div>
479
+ </Col>
480
+ <Col xs={24} sm={8} md={6}>
481
+ <div className="stat-item">
482
+ <div className="stat-number text-warning">
483
+ {features.filter(f => f.active === false).length}
484
+ </div>
485
+ <div className="stat-label">禁用模块</div>
486
+ </div>
487
+ </Col>
488
+ <Col xs={24} sm={8} md={6}>
489
+ <div className="stat-item">
490
+ <div className="stat-number text-info">{filteredFeatures.length}</div>
491
+ <div className="stat-label">筛选结果</div>
492
+ </div>
493
+ </Col>
494
+ </Row>
495
+ </Card>
575
496
 
576
497
  {/* 功能模块列表 */}
577
- <div className="feature-list-container">
498
+ <Card className="list-card" bordered={false} title="功能模块列表">
578
499
  {loading ? (
579
500
  <div className="loading-container">
580
501
  <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 }} />
502
+ {[...Array(6)].map((_, index) => (
503
+ <Col xs={24} sm={12} md={8} lg={6} key={index}>
504
+ <Card loading />
584
505
  </Col>
585
506
  ))}
586
507
  </Row>
587
508
  </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
- />
509
+ ) : getPaginatedFeatures().length === 0 ? (
510
+ <div className="empty-state">
511
+ <div className="empty-icon">📦</div>
512
+ <div className="empty-text">
513
+ {searchTerm || statusFilter !== 'all' ? '没有找到匹配的功能模块' : '暂无功能模块'}
514
+ </div>
515
+ <div className="empty-description">
516
+ {searchTerm || statusFilter !== 'all' ? '尝试调整搜索条件或筛选器' : '点击上方按钮创建你的第一个功能模块'}
517
+ </div>
518
+ {!searchTerm && statusFilter === 'all' && (
519
+ <Button
520
+ type="primary"
521
+ icon={<PlusOutlined />}
522
+ onClick={handleAddFeature}
523
+ style={{ marginTop: 16 }}
524
+ >
525
+ 创建功能模块
526
+ </Button>
527
+ )}
528
+ </div>
604
529
  ) : (
605
530
  <>
606
- <Row gutter={[16, 16]} className="feature-grid">
607
- {paginatedData.map(renderFeatureCard)}
531
+ <Row gutter={[16, 16]}>
532
+ {getPaginatedFeatures().map(feature => (
533
+ <Col xs={24} sm={12} md={8} lg={6} key={feature.id}>
534
+ <Card
535
+ className={`feature-card ${feature.active === false ? 'inactive' : ''}`}
536
+ hoverable
537
+ actions={[
538
+ <Tooltip title="编辑">
539
+ <EditOutlined
540
+ key="edit"
541
+ onClick={() => handleEditFeature(feature)}
542
+ />
543
+ </Tooltip>,
544
+ <Tooltip title="接口管理">
545
+ <SettingOutlined
546
+ key="setting"
547
+ onClick={() => handleNavigateToInterfaces(feature.id)}
548
+ />
549
+ </Tooltip>,
550
+ <Popconfirm
551
+ title="确定要删除这个功能模块吗?"
552
+ description="删除后将无法恢复,请谨慎操作。"
553
+ onConfirm={() => handleDeleteFeature(feature.id)}
554
+ okText="确定"
555
+ cancelText="取消"
556
+ >
557
+ <Tooltip title="删除">
558
+ <DeleteOutlined key="delete" className="text-danger" />
559
+ </Tooltip>
560
+ </Popconfirm>
561
+ ]}
562
+ >
563
+ <div className="feature-card-header">
564
+ <div className="feature-card-title">
565
+ <span className="feature-name">{feature.name}</span>
566
+ <Switch
567
+ checked={feature.active !== false}
568
+ onChange={() => handleToggleActive(feature.id, feature.active !== false)}
569
+ size="small"
570
+ className="feature-switch"
571
+ />
572
+ </div>
573
+ <Badge
574
+ status={feature.active === false ? 'error' : 'success'}
575
+ text={feature.active === false ? '已禁用' : '已启用'}
576
+ className="feature-status"
577
+ />
578
+ </div>
579
+
580
+ <div className="feature-card-content">
581
+ {feature.description ? (
582
+ <p className="feature-description">
583
+ {feature.description}
584
+ </p>
585
+ ) : (
586
+ <p className="feature-description empty">
587
+ 暂无描述
588
+ </p>
589
+ )}
590
+
591
+ <div className="feature-meta">
592
+ {feature.createTime && (
593
+ <div className="meta-item">
594
+ <span className="meta-label">创建:</span>
595
+ <span className="meta-value">
596
+ {new Date(feature.createTime).toLocaleDateString()}
597
+ </span>
598
+ </div>
599
+ )}
600
+ {feature.updateTime && feature.updateTime !== feature.createTime && (
601
+ <div className="meta-item">
602
+ <span className="meta-label">更新:</span>
603
+ <span className="meta-value">
604
+ {new Date(feature.updateTime).toLocaleDateString()}
605
+ </span>
606
+ </div>
607
+ )}
608
+ </div>
609
+ </div>
610
+ </Card>
611
+ </Col>
612
+ ))}
608
613
  </Row>
609
614
 
610
- {/* 分页控件 */}
611
- {filteredData.length > listConfig.pageSize && (
615
+ {/* 分页 */}
616
+ {filteredFeatures.length > pageSize && (
612
617
  <div className="pagination-container">
613
618
  <Pagination
614
- current={listConfig.current}
615
- pageSize={listConfig.pageSize}
616
- total={filteredData.length}
619
+ current={currentPage}
620
+ pageSize={pageSize}
621
+ total={filteredFeatures.length}
617
622
  showSizeChanger
618
623
  showQuickJumper
619
624
  showTotal={(total, range) =>
620
- `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
625
+ `共 ${total} 个功能模块,显示第 ${range[0]}-${range[1]} 个`
621
626
  }
622
- pageSizeOptions={['8', '12', '16', '24']}
623
- onChange={(current, pageSize) => {
624
- saveListConfig({ current, pageSize });
627
+ pageSizeOptions={['8', '12', '24', '48']}
628
+ onChange={(page, size) => {
629
+ setCurrentPage(page);
630
+ setPageSize(size);
625
631
  }}
626
632
  />
627
633
  </div>
628
634
  )}
629
635
  </>
630
636
  )}
631
- </div>
637
+ </Card>
632
638
 
633
- {/* 新建/编辑功能模块弹窗 */}
639
+ {/* 添加/编辑功能模块模态框 */}
634
640
  <Modal
635
- title={currentFeature ? '编辑功能模块' : '新建功能模块'}
636
- open={showModal}
637
- onCancel={() => setShowModal(false)}
638
- footer={null}
641
+ title={editingFeature ? '编辑功能模块' : '添加功能模块'}
642
+ open={modalVisible}
643
+ onOk={handleSubmit}
644
+ onCancel={handleCancel}
645
+ okText={editingFeature ? '保存' : '创建'}
646
+ cancelText="取消"
639
647
  destroyOnClose
640
648
  >
641
649
  <Form
642
650
  form={form}
643
651
  layout="vertical"
644
- onFinish={handleSubmit}
645
652
  initialValues={{
646
653
  name: '',
647
- description: '',
648
- active: true
654
+ description: ''
649
655
  }}
650
656
  >
651
657
  <Form.Item
652
658
  name="name"
653
- label="功能名称"
659
+ label="功能模块名称"
654
660
  rules={[
655
- { required: true, message: '请输入功能名称' },
656
- { max: 50, message: '功能名称不能超过50个字符' }
661
+ { required: true, message: '请输入功能模块名称' },
662
+ { max: 50, message: '名称长度不能超过50个字符' }
657
663
  ]}
658
664
  >
659
- <Input placeholder="请输入功能名称" />
665
+ <Input placeholder="请输入功能模块名称" />
660
666
  </Form.Item>
661
667
 
662
668
  <Form.Item
663
669
  name="description"
664
- label="功能描述"
670
+ label="描述"
665
671
  rules={[
666
- { max: 200, message: '功能描述不能超过200个字符' }
672
+ { max: 200, message: '描述长度不能超过200个字符' }
667
673
  ]}
668
674
  >
669
- <TextArea
670
- placeholder="请输入功能描述(可选)"
675
+ <TextArea
676
+ placeholder="请输入功能模块描述(可选)"
671
677
  rows={3}
672
678
  showCount
673
679
  maxLength={200}
674
680
  />
675
681
  </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
682
  </Form>
696
683
  </Modal>
697
684
  </div>