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