whistle.mock-plugins 1.3.1 → 1.4.0-beta.2
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 +29 -0
- package/app/cgi-bin/interfaces.js +2 -0
- package/app/cgi-bin/settings.js +1 -0
- package/app/cgi-bin/stats.js +22 -12
- package/app/cgi-bin/webdav.js +56 -0
- package/app/public/index.html +1 -1
- package/app/public/js/11.index.js +2 -0
- package/app/public/js/11.index.js.LICENSE.txt +5 -0
- package/app/public/js/704.index.js +2 -0
- package/app/public/js/704.index.js.LICENSE.txt +1 -0
- package/app/public/js/798.index.js +2 -0
- package/app/public/js/798.index.js.LICENSE.txt +53 -0
- package/app/public/js/807.index.js +2 -0
- package/app/public/js/807.index.js.LICENSE.txt +1 -0
- package/app/public/js/968.index.js +2 -0
- package/app/public/js/968.index.js.LICENSE.txt +12 -0
- package/app/public/js/index.js +1 -1
- package/app/public/js/index.js.LICENSE.txt +0 -173
- package/lib/routes/settings.js +1 -0
- package/lib/webdav-sync.js +806 -0
- package/package.json +9 -2
- package/API.md +0 -247
- package/COLUMN_RESIZE_IMPLEMENTATION.md +0 -162
- package/FEATURE_ENHANCEMENT.md +0 -157
- package/TEST_GUIDE.md +0 -156
- package/data/features.json +0 -1
- package/data/interfaces.json +0 -1
- package/index.html +0 -52
- package/initialize.js +0 -32
- package/public/bundle.js +0 -2
- package/public/bundle.js.LICENSE.txt +0 -38
- package/public/index.html +0 -1
- package/public/plugin.mock-plugin.html +0 -14
- package/rules.txt +0 -22
- package/ui/src/App.css +0 -173
- package/ui/src/components/AppLayout.js +0 -407
- package/ui/src/components/FeatureCard.js +0 -58
- package/ui/src/components/KeyboardShortcutsHelp.js +0 -58
- package/ui/src/components/Layout.js +0 -94
- package/ui/src/components/MainNav.js +0 -42
- package/ui/src/components/PageContainer.js +0 -16
- package/ui/src/components/VersionModal.js +0 -99
- package/ui/src/components/interface-management/DataManagementDrawer.js +0 -115
- package/ui/src/components/interface-management/DataManagementModal.js +0 -110
- package/ui/src/components/interface-management/InterfaceTestModal.js +0 -313
- package/ui/src/components/interface-management/PreviewModal.js +0 -48
- package/ui/src/components/interface-management/README.md +0 -184
- package/ui/src/components/interface-management/RealResponseRecorder.js +0 -165
- package/ui/src/components/interface-management/ResponseContentEditor.js +0 -645
- package/ui/src/components/interface-management/constants.js +0 -36
- package/ui/src/components/interface-management/index.js +0 -7
- package/ui/src/components/interface-management/utils.js +0 -103
- package/ui/src/hooks/useKeyboardShortcuts.js +0 -99
- package/ui/src/index.html +0 -14
- package/ui/src/index.js +0 -50
- package/ui/src/pages/InterfaceManagementV2.js +0 -2351
- package/ui/src/pages/MockDataV2.js +0 -1089
- package/ui/src/pages/Settings.js +0 -299
- package/ui/src/pages/Sniffer.js +0 -521
- package/ui/src/pages/Stats.js +0 -288
- package/ui/src/services/del_api.js +0 -192
- package/ui/src/styles/app-layout.css +0 -373
- package/ui/src/styles/cyber-theme.css +0 -1234
- package/ui/src/styles/earth-theme.css +0 -618
- package/ui/src/styles/global.css +0 -224
- package/ui/src/styles/interface-management.css +0 -1321
- package/ui/src/styles/mock-data.css +0 -1185
- package/ui/src/styles/settings.css +0 -252
- package/ui/src/styles/version-modal.css +0 -116
- package/webpack.config.js +0 -54
|
@@ -1,1089 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MockDataV2.js - 功能模块管理页面
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import React, { useState, useEffect } from 'react';
|
|
6
|
-
import { useRef } from 'react';
|
|
7
|
-
import { useHistory } from 'react-router-dom';
|
|
8
|
-
import AppLayout from '../components/AppLayout';
|
|
9
|
-
import { Card, Row, Col, Button, Modal, Form, Input, message, Switch, Empty, Spin, Typography, Tooltip, Badge, Select, Pagination, Table, Space, Tag, Checkbox } from 'antd';
|
|
10
|
-
import { PlusOutlined, EditOutlined, DeleteOutlined, ApiOutlined, ExportOutlined, ImportOutlined, InfoCircleOutlined, CalendarOutlined, SortAscendingOutlined, AppstoreOutlined, UnorderedListOutlined } from '@ant-design/icons';
|
|
11
|
-
import '../styles/mock-data.css';
|
|
12
|
-
import axios from 'axios';
|
|
13
|
-
|
|
14
|
-
const { Text, Title, Paragraph } = Typography;
|
|
15
|
-
const { TextArea } = Input;
|
|
16
|
-
|
|
17
|
-
// 刷新缓存服务
|
|
18
|
-
const flushCache = async () => {
|
|
19
|
-
try {
|
|
20
|
-
const response = await axios.get(`/_flush_cache`);
|
|
21
|
-
return response.data;
|
|
22
|
-
} catch (error) {
|
|
23
|
-
console.error('刷新缓存失败:', error);
|
|
24
|
-
throw error;
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const MockData = () => {
|
|
29
|
-
const history = useHistory();
|
|
30
|
-
const [mockFeatures, setMockFeatures] = useState([]);
|
|
31
|
-
const [showModal, setShowModal] = useState(false);
|
|
32
|
-
const [currentFeature, setCurrentFeature] = useState(null);
|
|
33
|
-
|
|
34
|
-
// 导出功能弹窗状态
|
|
35
|
-
const [showExportModal, setShowExportModal] = useState(false);
|
|
36
|
-
const [selectedExportIds, setSelectedExportIds] = useState([]);
|
|
37
|
-
const [exporting, setExporting] = useState(false);
|
|
38
|
-
|
|
39
|
-
const [loading, setLoading] = useState(true);
|
|
40
|
-
const [form] = Form.useForm();
|
|
41
|
-
|
|
42
|
-
// 排序和分页配置(支持缓存)
|
|
43
|
-
const [listConfig, setListConfig] = useState(() => {
|
|
44
|
-
const cached = localStorage.getItem('feature-list-config');
|
|
45
|
-
return cached ? JSON.parse(cached) : {
|
|
46
|
-
sortBy: 'name', // name, createdAt, interfaceCount, active
|
|
47
|
-
sortOrder: 'ascend', // ascend, descend
|
|
48
|
-
pageSize: 12,
|
|
49
|
-
current: 1,
|
|
50
|
-
viewMode: 'card' // card, list
|
|
51
|
-
};
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// 保存列表配置到localStorage
|
|
55
|
-
const saveListConfig = (config) => {
|
|
56
|
-
const newConfig = { ...listConfig, ...config };
|
|
57
|
-
setListConfig(newConfig);
|
|
58
|
-
localStorage.setItem('feature-list-config', JSON.stringify(newConfig));
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// 加载功能列表
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
fetchFeatures();
|
|
64
|
-
}, []);
|
|
65
|
-
|
|
66
|
-
// 键盘快捷键监听
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
const handleSave = () => {
|
|
69
|
-
if (showModal) {
|
|
70
|
-
handleSubmit();
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
const handleNew = () => {
|
|
74
|
-
openModal();
|
|
75
|
-
};
|
|
76
|
-
window.addEventListener('shortcut-save', handleSave);
|
|
77
|
-
window.addEventListener('shortcut-new-interface', handleNew);
|
|
78
|
-
return () => {
|
|
79
|
-
window.removeEventListener('shortcut-save', handleSave);
|
|
80
|
-
window.removeEventListener('shortcut-new-interface', handleNew);
|
|
81
|
-
};
|
|
82
|
-
}, [showModal]);
|
|
83
|
-
|
|
84
|
-
// 获取所有功能模块
|
|
85
|
-
const fetchFeatures = async () => {
|
|
86
|
-
try {
|
|
87
|
-
setLoading(true);
|
|
88
|
-
const response = await fetch('/cgi-bin/features');
|
|
89
|
-
const result = await response.json();
|
|
90
|
-
|
|
91
|
-
if (result.code === 0) {
|
|
92
|
-
setMockFeatures(result.data || []);
|
|
93
|
-
} else {
|
|
94
|
-
console.error('获取功能模块失败:', result.message);
|
|
95
|
-
message.error('获取功能模块失败: ' + result.message);
|
|
96
|
-
}
|
|
97
|
-
} catch (error) {
|
|
98
|
-
console.error('获取功能模块错误:', error);
|
|
99
|
-
message.error('获取功能模块失败, 请检查网络连接');
|
|
100
|
-
} finally {
|
|
101
|
-
setLoading(false);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const openModal = (feature = null) => {
|
|
106
|
-
if (feature) {
|
|
107
|
-
setCurrentFeature(feature);
|
|
108
|
-
form.setFieldsValue({
|
|
109
|
-
name: feature.name,
|
|
110
|
-
description: feature.description,
|
|
111
|
-
active: feature.active !== false
|
|
112
|
-
});
|
|
113
|
-
} else {
|
|
114
|
-
setCurrentFeature(null);
|
|
115
|
-
form.resetFields();
|
|
116
|
-
form.setFieldsValue({
|
|
117
|
-
active: true
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
setShowModal(true);
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const closeModal = () => {
|
|
124
|
-
setShowModal(false);
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
// 更新成功后刷新缓存
|
|
128
|
-
const refreshCacheAfterUpdate = async () => {
|
|
129
|
-
try {
|
|
130
|
-
await flushCache();
|
|
131
|
-
// 不显示提示,避免干扰主操作的反馈
|
|
132
|
-
} catch (error) {
|
|
133
|
-
// 记录错误但不影响用户体验
|
|
134
|
-
console.error('刷新缓存失败,可能需要等待缓存自动过期:', error);
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const handleSubmit = async (values) => {
|
|
139
|
-
try {
|
|
140
|
-
const featureData = {
|
|
141
|
-
...values
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
// 如果是编辑已有功能,添加ID
|
|
145
|
-
if (currentFeature) {
|
|
146
|
-
featureData.id = currentFeature.id;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
setLoading(true);
|
|
150
|
-
const response = await fetch('/cgi-bin/features', {
|
|
151
|
-
method: 'POST',
|
|
152
|
-
headers: {
|
|
153
|
-
'Content-Type': 'application/json'
|
|
154
|
-
},
|
|
155
|
-
body: JSON.stringify(featureData)
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
const result = await response.json();
|
|
159
|
-
|
|
160
|
-
if (result.code === 0) {
|
|
161
|
-
// 刷新功能列表
|
|
162
|
-
message.success(currentFeature ? '功能模块更新成功' : '功能模块创建成功');
|
|
163
|
-
fetchFeatures();
|
|
164
|
-
closeModal();
|
|
165
|
-
|
|
166
|
-
// 刷新缓存
|
|
167
|
-
refreshCacheAfterUpdate();
|
|
168
|
-
} else {
|
|
169
|
-
message.error('操作失败: ' + result.message);
|
|
170
|
-
}
|
|
171
|
-
} catch (error) {
|
|
172
|
-
console.error('保存功能模块错误:', error);
|
|
173
|
-
message.error('保存失败: ' + error.message);
|
|
174
|
-
} finally {
|
|
175
|
-
setLoading(false);
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
const handleToggleActive = async (id, currentActive) => {
|
|
180
|
-
try {
|
|
181
|
-
const response = await fetch(`/cgi-bin/features?id=${id}`, {
|
|
182
|
-
method: 'PATCH',
|
|
183
|
-
headers: {
|
|
184
|
-
'Content-Type': 'application/json'
|
|
185
|
-
},
|
|
186
|
-
body: JSON.stringify({
|
|
187
|
-
active: !currentActive
|
|
188
|
-
})
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
const result = await response.json();
|
|
192
|
-
|
|
193
|
-
if (result.code === 0) {
|
|
194
|
-
message.success(`${currentActive ? '禁用' : '启用'}功能模块成功`);
|
|
195
|
-
// 更新本地状态
|
|
196
|
-
setMockFeatures(mockFeatures.map(feature =>
|
|
197
|
-
feature.id === id ? { ...feature, active: !currentActive } : feature
|
|
198
|
-
));
|
|
199
|
-
|
|
200
|
-
// 刷新缓存 - 功能启用状态变化是最关键的需要刷新缓存的操作
|
|
201
|
-
refreshCacheAfterUpdate();
|
|
202
|
-
} else {
|
|
203
|
-
message.error(`${currentActive ? '禁用' : '启用'}功能模块失败: ${result.message}`);
|
|
204
|
-
}
|
|
205
|
-
} catch (error) {
|
|
206
|
-
console.error(`${currentActive ? '禁用' : '启用'}功能模块错误:`, error);
|
|
207
|
-
message.error(`操作失败: ${error.message}`);
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const deleteFeature = async (id) => {
|
|
212
|
-
Modal.confirm({
|
|
213
|
-
title: '确定要删除此功能吗?',
|
|
214
|
-
content: '这将删除所有相关的接口和模拟数据,此操作无法恢复。',
|
|
215
|
-
okText: '删除',
|
|
216
|
-
okType: 'danger',
|
|
217
|
-
cancelText: '取消',
|
|
218
|
-
onOk: async () => {
|
|
219
|
-
try {
|
|
220
|
-
setLoading(true);
|
|
221
|
-
const response = await fetch(`/cgi-bin/features?id=${id}`, {
|
|
222
|
-
method: 'DELETE'
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
const result = await response.json();
|
|
226
|
-
|
|
227
|
-
if (result.code === 0) {
|
|
228
|
-
message.success('功能模块已成功删除');
|
|
229
|
-
// 更新本地状态
|
|
230
|
-
setMockFeatures(mockFeatures.filter(f => f.id !== id));
|
|
231
|
-
|
|
232
|
-
// 刷新缓存
|
|
233
|
-
refreshCacheAfterUpdate();
|
|
234
|
-
} else {
|
|
235
|
-
message.error('删除失败: ' + result.message);
|
|
236
|
-
}
|
|
237
|
-
} catch (error) {
|
|
238
|
-
console.error('删除功能错误:', error);
|
|
239
|
-
message.error('操作失败: ' + error.message);
|
|
240
|
-
} finally {
|
|
241
|
-
setLoading(false);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
const viewInterfaces = (feature) => {
|
|
248
|
-
localStorage.setItem('selectedFeatureId', feature.id);
|
|
249
|
-
history.push(`/interface/${feature.id}`);
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
const exportFeatureConfig = async (feature) => {
|
|
253
|
-
try {
|
|
254
|
-
// 获取该功能的所有接口
|
|
255
|
-
const response = await fetch(`/cgi-bin/interfaces?featureId=${feature.id}`);
|
|
256
|
-
const result = await response.json();
|
|
257
|
-
|
|
258
|
-
// 创建完整配置
|
|
259
|
-
const config = {
|
|
260
|
-
...feature,
|
|
261
|
-
interfaces: result.code === 0 ? result.data : []
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
const dataStr = JSON.stringify(config, null, 2);
|
|
265
|
-
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
|
|
266
|
-
|
|
267
|
-
const exportFileDefaultName = `whistle-mock-feature-${feature.id}.json`;
|
|
268
|
-
|
|
269
|
-
const linkElement = document.createElement('a');
|
|
270
|
-
linkElement.setAttribute('href', dataUri);
|
|
271
|
-
linkElement.setAttribute('download', exportFileDefaultName);
|
|
272
|
-
linkElement.click();
|
|
273
|
-
|
|
274
|
-
message.success('配置导出成功');
|
|
275
|
-
} catch (error) {
|
|
276
|
-
console.error('导出配置错误:', error);
|
|
277
|
-
message.error('导出失败: ' + error.message);
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
// 打开导出弹窗(默认全选)
|
|
282
|
-
const openExportModal = () => {
|
|
283
|
-
if (mockFeatures.length === 0) {
|
|
284
|
-
message.warning('暂无可导出的功能模块');
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
setSelectedExportIds(mockFeatures.map(f => f.id));
|
|
288
|
-
setShowExportModal(true);
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
// 导出选中的功能模块
|
|
292
|
-
const handleExportSelected = async () => {
|
|
293
|
-
if (selectedExportIds.length === 0) {
|
|
294
|
-
message.warning('请至少选择一个功能模块');
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
try {
|
|
299
|
-
setExporting(true);
|
|
300
|
-
|
|
301
|
-
const selectedFeatures = mockFeatures.filter(f => selectedExportIds.includes(f.id));
|
|
302
|
-
const configs = [];
|
|
303
|
-
|
|
304
|
-
// 逐个拉取接口配置,组装成与单功能导出一致的结构
|
|
305
|
-
for (const feature of selectedFeatures) {
|
|
306
|
-
const response = await fetch(`/cgi-bin/interfaces?featureId=${feature.id}`);
|
|
307
|
-
const result = await response.json();
|
|
308
|
-
configs.push({
|
|
309
|
-
...feature,
|
|
310
|
-
interfaces: result.code === 0 ? result.data : []
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// 只选一个时导出单个对象,兼容旧版单功能导入;多个时导出数组
|
|
315
|
-
const exportData = configs.length === 1 ? configs[0] : configs;
|
|
316
|
-
const dataStr = JSON.stringify(exportData, null, 2);
|
|
317
|
-
const blob = new Blob([dataStr], { type: 'application/json;charset=utf-8' });
|
|
318
|
-
const url = URL.createObjectURL(blob);
|
|
319
|
-
|
|
320
|
-
const exportFileName = configs.length === 1
|
|
321
|
-
? `whistle-mock-feature-${configs[0].id}.json`
|
|
322
|
-
: `whistle-mock-features-${configs.length}.json`;
|
|
323
|
-
|
|
324
|
-
const linkElement = document.createElement('a');
|
|
325
|
-
linkElement.href = url;
|
|
326
|
-
linkElement.download = exportFileName;
|
|
327
|
-
linkElement.click();
|
|
328
|
-
URL.revokeObjectURL(url);
|
|
329
|
-
|
|
330
|
-
message.success(`成功导出 ${configs.length} 个功能模块`);
|
|
331
|
-
setShowExportModal(false);
|
|
332
|
-
} catch (error) {
|
|
333
|
-
console.error('导出配置错误:', error);
|
|
334
|
-
message.error('导出失败: ' + error.message);
|
|
335
|
-
} finally {
|
|
336
|
-
setExporting(false);
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
const importFeatureConfig = () => {
|
|
341
|
-
const input = document.createElement('input');
|
|
342
|
-
input.type = 'file';
|
|
343
|
-
input.accept = '.json';
|
|
344
|
-
|
|
345
|
-
input.onchange = async (e) => {
|
|
346
|
-
const file = e.target.files[0];
|
|
347
|
-
if (!file) return;
|
|
348
|
-
|
|
349
|
-
const reader = new FileReader();
|
|
350
|
-
reader.onload = async (event) => {
|
|
351
|
-
try {
|
|
352
|
-
const parsed = JSON.parse(event.target.result);
|
|
353
|
-
// 兼容单个功能(对象)与多个功能(数组)两种导出格式
|
|
354
|
-
const configs = Array.isArray(parsed) ? parsed : [parsed];
|
|
355
|
-
|
|
356
|
-
// 过滤掉缺少名称的无效配置
|
|
357
|
-
const validConfigs = configs.filter(config => config && config.name);
|
|
358
|
-
if (validConfigs.length === 0) {
|
|
359
|
-
message.error('无效的配置文件: 缺少功能名称');
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
message.loading({ content: '正在导入功能配置...', key: 'importInfo' });
|
|
364
|
-
|
|
365
|
-
let featureSuccess = 0;
|
|
366
|
-
let featureError = 0;
|
|
367
|
-
let interfaceSuccess = 0;
|
|
368
|
-
let interfaceError = 0;
|
|
369
|
-
|
|
370
|
-
for (const config of validConfigs) {
|
|
371
|
-
try {
|
|
372
|
-
// 创建新功能
|
|
373
|
-
const featureData = {
|
|
374
|
-
name: config.name,
|
|
375
|
-
description: config.description || '',
|
|
376
|
-
active: config.active !== false
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
const featureResponse = await fetch('/cgi-bin/features', {
|
|
380
|
-
method: 'POST',
|
|
381
|
-
headers: {
|
|
382
|
-
'Content-Type': 'application/json'
|
|
383
|
-
},
|
|
384
|
-
body: JSON.stringify(featureData)
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
const featureResult = await featureResponse.json();
|
|
388
|
-
|
|
389
|
-
if (featureResult.code === 0) {
|
|
390
|
-
featureSuccess++;
|
|
391
|
-
const newFeature = featureResult.data;
|
|
392
|
-
|
|
393
|
-
// 导入接口配置
|
|
394
|
-
if (Array.isArray(config.interfaces) && config.interfaces.length > 0) {
|
|
395
|
-
for (const interfaceItem of config.interfaces) {
|
|
396
|
-
try {
|
|
397
|
-
// 创建接口,使用新功能ID
|
|
398
|
-
const interfaceData = {
|
|
399
|
-
...interfaceItem,
|
|
400
|
-
featureId: newFeature.id,
|
|
401
|
-
id: undefined // 不使用原接口ID
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
await fetch('/cgi-bin/interfaces', {
|
|
405
|
-
method: 'POST',
|
|
406
|
-
headers: {
|
|
407
|
-
'Content-Type': 'application/json'
|
|
408
|
-
},
|
|
409
|
-
body: JSON.stringify(interfaceData)
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
interfaceSuccess++;
|
|
413
|
-
} catch (error) {
|
|
414
|
-
console.error('导入接口配置失败:', error);
|
|
415
|
-
interfaceError++;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
} else {
|
|
420
|
-
featureError++;
|
|
421
|
-
console.error('导入功能失败:', featureResult.message);
|
|
422
|
-
}
|
|
423
|
-
} catch (error) {
|
|
424
|
-
featureError++;
|
|
425
|
-
console.error('导入功能错误:', error);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
const interfaceSummary = interfaceSuccess > 0 || interfaceError > 0
|
|
430
|
-
? `,共导入 ${interfaceSuccess} 个接口${interfaceError > 0 ? `(${interfaceError} 个失败)` : ''}`
|
|
431
|
-
: '';
|
|
432
|
-
message.success({
|
|
433
|
-
content: `导入完成:成功 ${featureSuccess} 个功能${featureError > 0 ? `,失败 ${featureError} 个功能` : ''}${interfaceSummary}`,
|
|
434
|
-
key: 'importInfo',
|
|
435
|
-
duration: 3
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
// 刷新功能列表
|
|
439
|
-
fetchFeatures();
|
|
440
|
-
} catch (error) {
|
|
441
|
-
console.error('导入配置错误:', error);
|
|
442
|
-
message.error('导入失败: ' + error.message);
|
|
443
|
-
}
|
|
444
|
-
};
|
|
445
|
-
|
|
446
|
-
reader.readAsText(file);
|
|
447
|
-
};
|
|
448
|
-
|
|
449
|
-
input.click();
|
|
450
|
-
};
|
|
451
|
-
|
|
452
|
-
// 排序处理
|
|
453
|
-
const handleSortChange = (value) => {
|
|
454
|
-
saveListConfig({ sortBy: value, current: 1 });
|
|
455
|
-
};
|
|
456
|
-
|
|
457
|
-
const handleSortOrderChange = (value) => {
|
|
458
|
-
saveListConfig({ sortOrder: value, current: 1 });
|
|
459
|
-
};
|
|
460
|
-
|
|
461
|
-
const handlePageSizeChange = (value) => {
|
|
462
|
-
saveListConfig({ pageSize: value, current: 1 });
|
|
463
|
-
};
|
|
464
|
-
|
|
465
|
-
const handlePageChange = (page, pageSize) => {
|
|
466
|
-
saveListConfig({ current: page, pageSize });
|
|
467
|
-
};
|
|
468
|
-
|
|
469
|
-
const handleViewModeChange = (mode) => {
|
|
470
|
-
saveListConfig({ viewMode: mode, current: 1 });
|
|
471
|
-
};
|
|
472
|
-
|
|
473
|
-
// 获取排序后的功能列表
|
|
474
|
-
const getSortedFeatures = () => {
|
|
475
|
-
const sorted = [...mockFeatures].sort((a, b) => {
|
|
476
|
-
let aValue, bValue;
|
|
477
|
-
|
|
478
|
-
switch (listConfig.sortBy) {
|
|
479
|
-
case 'name':
|
|
480
|
-
aValue = a.name || '';
|
|
481
|
-
bValue = b.name || '';
|
|
482
|
-
return listConfig.sortOrder === 'ascend'
|
|
483
|
-
? aValue.localeCompare(bValue, 'zh-CN')
|
|
484
|
-
: bValue.localeCompare(aValue, 'zh-CN');
|
|
485
|
-
|
|
486
|
-
case 'createdAt':
|
|
487
|
-
aValue = new Date(a.createdAt || 0).getTime();
|
|
488
|
-
bValue = new Date(b.createdAt || 0).getTime();
|
|
489
|
-
return listConfig.sortOrder === 'ascend' ? aValue - bValue : bValue - aValue;
|
|
490
|
-
|
|
491
|
-
case 'interfaceCount':
|
|
492
|
-
aValue = a.interfaceCount || 0;
|
|
493
|
-
bValue = b.interfaceCount || 0;
|
|
494
|
-
return listConfig.sortOrder === 'ascend' ? aValue - bValue : bValue - aValue;
|
|
495
|
-
|
|
496
|
-
case 'active':
|
|
497
|
-
aValue = a.active ? 1 : 0;
|
|
498
|
-
bValue = b.active ? 1 : 0;
|
|
499
|
-
return listConfig.sortOrder === 'ascend' ? aValue - bValue : bValue - aValue;
|
|
500
|
-
|
|
501
|
-
default:
|
|
502
|
-
return 0;
|
|
503
|
-
}
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
return sorted;
|
|
507
|
-
};
|
|
508
|
-
|
|
509
|
-
// 获取分页后的功能列表
|
|
510
|
-
const getPaginatedFeatures = () => {
|
|
511
|
-
const sortedFeatures = getSortedFeatures();
|
|
512
|
-
const startIndex = (listConfig.current - 1) * listConfig.pageSize;
|
|
513
|
-
const endIndex = startIndex + listConfig.pageSize;
|
|
514
|
-
return sortedFeatures.slice(startIndex, endIndex);
|
|
515
|
-
};
|
|
516
|
-
|
|
517
|
-
// 渲染功能模块卡片
|
|
518
|
-
const renderFeatureCard = (feature) => {
|
|
519
|
-
const formattedDate = feature.createdAt
|
|
520
|
-
? new Date(feature.createdAt).toLocaleDateString()
|
|
521
|
-
: '未知日期';
|
|
522
|
-
|
|
523
|
-
// 处理卡片点击事件
|
|
524
|
-
const handleCardClick = (e) => {
|
|
525
|
-
// 检查点击目标是否是开关或其父元素
|
|
526
|
-
const target = e.target;
|
|
527
|
-
const switchElement = target.closest('.ant-switch') || target.closest('.feature-name .ant-switch');
|
|
528
|
-
|
|
529
|
-
// 如果点击的是开关区域,则不执行跳转
|
|
530
|
-
if (switchElement) {
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// 检查是否点击了操作按钮区域
|
|
535
|
-
const actionElement = target.closest('.ant-card-actions') || target.closest('.ant-card-actions li');
|
|
536
|
-
if (actionElement) {
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// 执行跳转到接口管理界面
|
|
541
|
-
viewInterfaces(feature);
|
|
542
|
-
};
|
|
543
|
-
|
|
544
|
-
// 处理开关点击事件,阻止事件冒泡
|
|
545
|
-
const handleSwitchClick = (checked, e) => {
|
|
546
|
-
e.stopPropagation(); // 阻止事件冒泡到卡片
|
|
547
|
-
handleToggleActive(feature.id, feature.active);
|
|
548
|
-
};
|
|
549
|
-
|
|
550
|
-
return (
|
|
551
|
-
<Col xs={24} sm={12} md={8} lg={6} key={feature.id} style={{ marginBottom: 16 }}>
|
|
552
|
-
<Badge.Ribbon
|
|
553
|
-
text={feature.active ? '已启用' : '已禁用'}
|
|
554
|
-
color={feature.active ? '#52c41a' : '#f5222d'}
|
|
555
|
-
style={{ display: 'block' }}
|
|
556
|
-
>
|
|
557
|
-
<Card
|
|
558
|
-
className={`feature-card ${!feature.active ? 'inactive-feature' : ''}`}
|
|
559
|
-
onClick={handleCardClick}
|
|
560
|
-
style={{ cursor: 'pointer' }}
|
|
561
|
-
actions={[
|
|
562
|
-
<Tooltip title="管理接口">
|
|
563
|
-
<ApiOutlined key="interfaces" onClick={(e) => {
|
|
564
|
-
e.stopPropagation();
|
|
565
|
-
viewInterfaces(feature);
|
|
566
|
-
}} />
|
|
567
|
-
</Tooltip>,
|
|
568
|
-
<Tooltip title="编辑功能">
|
|
569
|
-
<EditOutlined key="edit" onClick={(e) => {
|
|
570
|
-
e.stopPropagation();
|
|
571
|
-
openModal(feature);
|
|
572
|
-
}} />
|
|
573
|
-
</Tooltip>,
|
|
574
|
-
<Tooltip title="删除功能">
|
|
575
|
-
<DeleteOutlined key="delete" onClick={(e) => {
|
|
576
|
-
e.stopPropagation();
|
|
577
|
-
deleteFeature(feature.id);
|
|
578
|
-
}} />
|
|
579
|
-
</Tooltip>,
|
|
580
|
-
<Tooltip title="导出配置">
|
|
581
|
-
<ExportOutlined key="export" onClick={(e) => {
|
|
582
|
-
e.stopPropagation();
|
|
583
|
-
exportFeatureConfig(feature);
|
|
584
|
-
}} />
|
|
585
|
-
</Tooltip>
|
|
586
|
-
]}
|
|
587
|
-
>
|
|
588
|
-
<div className="feature-card-content">
|
|
589
|
-
<div className="feature-name">
|
|
590
|
-
<Title level={4} ellipsis={{ tooltip: feature.name }}>
|
|
591
|
-
{feature.name}
|
|
592
|
-
</Title>
|
|
593
|
-
|
|
594
|
-
<Switch
|
|
595
|
-
checked={feature.active}
|
|
596
|
-
onChange={handleSwitchClick}
|
|
597
|
-
size="small"
|
|
598
|
-
onClick={() => {event.stopPropagation()}}
|
|
599
|
-
/>
|
|
600
|
-
</div>
|
|
601
|
-
|
|
602
|
-
<Paragraph className="feature-description" ellipsis={{ rows: 2, expandable: false, tooltip: feature.description }}>
|
|
603
|
-
{feature.description || '无描述'}
|
|
604
|
-
</Paragraph>
|
|
605
|
-
|
|
606
|
-
<div className="feature-stat">
|
|
607
|
-
<span>
|
|
608
|
-
<InfoCircleOutlined /> {feature.interfaceCount || 0} 个接口
|
|
609
|
-
</span>
|
|
610
|
-
<span className="feature-date">
|
|
611
|
-
<CalendarOutlined /> {formattedDate}
|
|
612
|
-
</span>
|
|
613
|
-
</div>
|
|
614
|
-
</div>
|
|
615
|
-
</Card>
|
|
616
|
-
</Badge.Ribbon>
|
|
617
|
-
</Col>
|
|
618
|
-
);
|
|
619
|
-
};
|
|
620
|
-
|
|
621
|
-
// 渲染列表视图
|
|
622
|
-
const renderListView = () => {
|
|
623
|
-
const columns = [
|
|
624
|
-
{
|
|
625
|
-
title: '状态',
|
|
626
|
-
dataIndex: 'active',
|
|
627
|
-
key: 'active',
|
|
628
|
-
width: 100,
|
|
629
|
-
render: (active, record) => (
|
|
630
|
-
<Switch
|
|
631
|
-
checked={active}
|
|
632
|
-
onChange={(checked, e) => {
|
|
633
|
-
e.stopPropagation();
|
|
634
|
-
handleToggleActive(record.id, active);
|
|
635
|
-
}}
|
|
636
|
-
checkedChildren="启用"
|
|
637
|
-
unCheckedChildren="禁用"
|
|
638
|
-
/>
|
|
639
|
-
),
|
|
640
|
-
},
|
|
641
|
-
{
|
|
642
|
-
title: '功能名称',
|
|
643
|
-
dataIndex: 'name',
|
|
644
|
-
key: 'name',
|
|
645
|
-
ellipsis: { tooltip: true },
|
|
646
|
-
render: (name) => (
|
|
647
|
-
<Text strong style={{ fontSize: '14px' }}>{name}</Text>
|
|
648
|
-
),
|
|
649
|
-
},
|
|
650
|
-
{
|
|
651
|
-
title: '功能描述',
|
|
652
|
-
dataIndex: 'description',
|
|
653
|
-
key: 'description',
|
|
654
|
-
ellipsis: { tooltip: true },
|
|
655
|
-
render: (description) => (
|
|
656
|
-
<Text type="secondary">{description || '无描述'}</Text>
|
|
657
|
-
),
|
|
658
|
-
},
|
|
659
|
-
{
|
|
660
|
-
title: '接口数量',
|
|
661
|
-
dataIndex: 'interfaceCount',
|
|
662
|
-
key: 'interfaceCount',
|
|
663
|
-
width: 100,
|
|
664
|
-
align: 'center',
|
|
665
|
-
render: (count) => (
|
|
666
|
-
<Badge count={count || 0} showZero style={{ backgroundColor: '#1677ff' }} />
|
|
667
|
-
),
|
|
668
|
-
},
|
|
669
|
-
{
|
|
670
|
-
title: '创建时间',
|
|
671
|
-
dataIndex: 'createdAt',
|
|
672
|
-
key: 'createdAt',
|
|
673
|
-
width: 120,
|
|
674
|
-
render: (createdAt) => (
|
|
675
|
-
<Text type="secondary">
|
|
676
|
-
{createdAt ? new Date(createdAt).toLocaleDateString() : '未知日期'}
|
|
677
|
-
</Text>
|
|
678
|
-
),
|
|
679
|
-
},
|
|
680
|
-
{
|
|
681
|
-
title: '操作',
|
|
682
|
-
key: 'action',
|
|
683
|
-
width: 200,
|
|
684
|
-
fixed: 'right',
|
|
685
|
-
render: (_, record) => (
|
|
686
|
-
<Space size="small">
|
|
687
|
-
<Tooltip title="管理接口">
|
|
688
|
-
<Button
|
|
689
|
-
type="link"
|
|
690
|
-
size="small"
|
|
691
|
-
icon={<ApiOutlined />}
|
|
692
|
-
onClick={(e) => {
|
|
693
|
-
e.stopPropagation();
|
|
694
|
-
viewInterfaces(record);
|
|
695
|
-
}}
|
|
696
|
-
>
|
|
697
|
-
接口
|
|
698
|
-
</Button>
|
|
699
|
-
</Tooltip>
|
|
700
|
-
<Tooltip title="编辑">
|
|
701
|
-
<Button
|
|
702
|
-
type="link"
|
|
703
|
-
size="small"
|
|
704
|
-
icon={<EditOutlined />}
|
|
705
|
-
onClick={(e) => {
|
|
706
|
-
e.stopPropagation();
|
|
707
|
-
openModal(record);
|
|
708
|
-
}}
|
|
709
|
-
/>
|
|
710
|
-
</Tooltip>
|
|
711
|
-
<Tooltip title="导出">
|
|
712
|
-
<Button
|
|
713
|
-
type="link"
|
|
714
|
-
size="small"
|
|
715
|
-
icon={<ExportOutlined />}
|
|
716
|
-
onClick={(e) => {
|
|
717
|
-
e.stopPropagation();
|
|
718
|
-
exportFeatureConfig(record);
|
|
719
|
-
}}
|
|
720
|
-
/>
|
|
721
|
-
</Tooltip>
|
|
722
|
-
<Tooltip title="删除">
|
|
723
|
-
<Button
|
|
724
|
-
type="link"
|
|
725
|
-
size="small"
|
|
726
|
-
danger
|
|
727
|
-
icon={<DeleteOutlined />}
|
|
728
|
-
onClick={(e) => {
|
|
729
|
-
e.stopPropagation();
|
|
730
|
-
deleteFeature(record.id);
|
|
731
|
-
}}
|
|
732
|
-
/>
|
|
733
|
-
</Tooltip>
|
|
734
|
-
</Space>
|
|
735
|
-
),
|
|
736
|
-
},
|
|
737
|
-
];
|
|
738
|
-
|
|
739
|
-
return (
|
|
740
|
-
<Table
|
|
741
|
-
columns={columns}
|
|
742
|
-
dataSource={getPaginatedFeatures()}
|
|
743
|
-
rowKey="id"
|
|
744
|
-
pagination={false}
|
|
745
|
-
onRow={(record) => ({
|
|
746
|
-
onClick: () => viewInterfaces(record),
|
|
747
|
-
style: { cursor: 'pointer' }
|
|
748
|
-
})}
|
|
749
|
-
rowClassName={(record) => !record.active ? 'inactive-row' : ''}
|
|
750
|
-
/>
|
|
751
|
-
);
|
|
752
|
-
};
|
|
753
|
-
|
|
754
|
-
return (
|
|
755
|
-
<AppLayout>
|
|
756
|
-
<div className="page-container">
|
|
757
|
-
<div className="page-title-bar">
|
|
758
|
-
<div className="title-section">
|
|
759
|
-
<div className="title-with-badge">
|
|
760
|
-
<h1 className="page-title">功能模块管理</h1>
|
|
761
|
-
</div>
|
|
762
|
-
<div className="page-description">
|
|
763
|
-
创建和管理功能模块,为每个功能配置独立的接口
|
|
764
|
-
</div>
|
|
765
|
-
</div>
|
|
766
|
-
<div className="page-actions">
|
|
767
|
-
<Button
|
|
768
|
-
type="primary"
|
|
769
|
-
icon={<PlusOutlined />}
|
|
770
|
-
onClick={() => openModal()}
|
|
771
|
-
>
|
|
772
|
-
新建功能
|
|
773
|
-
</Button>
|
|
774
|
-
<Button
|
|
775
|
-
type="primary"
|
|
776
|
-
icon={<ExportOutlined />}
|
|
777
|
-
onClick={importFeatureConfig}
|
|
778
|
-
style={{ backgroundColor: '#52c41a', borderColor: '#52c41a' }}
|
|
779
|
-
>
|
|
780
|
-
导入功能
|
|
781
|
-
</Button>
|
|
782
|
-
<Button
|
|
783
|
-
type="primary"
|
|
784
|
-
icon={<ImportOutlined />}
|
|
785
|
-
onClick={openExportModal}
|
|
786
|
-
disabled={mockFeatures.length === 0}
|
|
787
|
-
style={mockFeatures.length === 0 ? undefined : { backgroundColor: '#fa8c16', borderColor: '#fa8c16' }}
|
|
788
|
-
>
|
|
789
|
-
导出功能
|
|
790
|
-
</Button>
|
|
791
|
-
</div>
|
|
792
|
-
</div>
|
|
793
|
-
|
|
794
|
-
{/* 控制面板 */}
|
|
795
|
-
{mockFeatures.length > 0 && !loading && (
|
|
796
|
-
<Card className="control-panel-card" style={{ marginBottom: 24 }}>
|
|
797
|
-
<Row gutter={[24, 16]} align="middle">
|
|
798
|
-
{/* 统计信息区域 */}
|
|
799
|
-
<Col xs={24} sm={12} md={8} lg={6}>
|
|
800
|
-
<div className="stats-section">
|
|
801
|
-
<div className="stats-item">
|
|
802
|
-
<span className="stats-number">{mockFeatures.length}</span>
|
|
803
|
-
<span className="stats-label">功能模块</span>
|
|
804
|
-
</div>
|
|
805
|
-
<div className="stats-item">
|
|
806
|
-
<span className="stats-number">
|
|
807
|
-
{mockFeatures.filter(f => f.active).length}
|
|
808
|
-
</span>
|
|
809
|
-
<span className="stats-label">已启用</span>
|
|
810
|
-
</div>
|
|
811
|
-
<div className="stats-item">
|
|
812
|
-
<span className="stats-number">
|
|
813
|
-
{mockFeatures.reduce((sum, f) => sum + (f.interfaceCount || 0), 0)}
|
|
814
|
-
</span>
|
|
815
|
-
<span className="stats-label">接口总数</span>
|
|
816
|
-
</div>
|
|
817
|
-
</div>
|
|
818
|
-
</Col>
|
|
819
|
-
|
|
820
|
-
{/* 排序和视图控制区域 */}
|
|
821
|
-
<Col xs={24} sm={12} md={8} lg={9}>
|
|
822
|
-
<div className="sort-controls">
|
|
823
|
-
<div className="control-group">
|
|
824
|
-
<span className="control-label">
|
|
825
|
-
<SortAscendingOutlined /> 排序方式
|
|
826
|
-
</span>
|
|
827
|
-
<Select
|
|
828
|
-
value={listConfig.sortBy}
|
|
829
|
-
onChange={handleSortChange}
|
|
830
|
-
style={{ width: 120 }}
|
|
831
|
-
size="middle"
|
|
832
|
-
>
|
|
833
|
-
<Select.Option value="name">名称</Select.Option>
|
|
834
|
-
<Select.Option value="createdAt">创建时间</Select.Option>
|
|
835
|
-
<Select.Option value="interfaceCount">接口数量</Select.Option>
|
|
836
|
-
<Select.Option value="active">状态</Select.Option>
|
|
837
|
-
</Select>
|
|
838
|
-
<Select
|
|
839
|
-
value={listConfig.sortOrder}
|
|
840
|
-
onChange={handleSortOrderChange}
|
|
841
|
-
style={{ width: 80 }}
|
|
842
|
-
size="middle"
|
|
843
|
-
>
|
|
844
|
-
<Select.Option value="ascend">升序</Select.Option>
|
|
845
|
-
<Select.Option value="descend">降序</Select.Option>
|
|
846
|
-
</Select>
|
|
847
|
-
</div>
|
|
848
|
-
</div>
|
|
849
|
-
</Col>
|
|
850
|
-
|
|
851
|
-
{/* 视图切换区域 */}
|
|
852
|
-
<Col xs={24} sm={12} md={4} lg={3}>
|
|
853
|
-
<div className="view-mode-controls">
|
|
854
|
-
<Button.Group>
|
|
855
|
-
<Tooltip title="卡片视图">
|
|
856
|
-
<Button
|
|
857
|
-
type={listConfig.viewMode === 'card' ? 'primary' : 'default'}
|
|
858
|
-
icon={<AppstoreOutlined />}
|
|
859
|
-
onClick={() => handleViewModeChange('card')}
|
|
860
|
-
/>
|
|
861
|
-
</Tooltip>
|
|
862
|
-
<Tooltip title="列表视图">
|
|
863
|
-
<Button
|
|
864
|
-
type={listConfig.viewMode === 'list' ? 'primary' : 'default'}
|
|
865
|
-
icon={<UnorderedListOutlined />}
|
|
866
|
-
onClick={() => handleViewModeChange('list')}
|
|
867
|
-
/>
|
|
868
|
-
</Tooltip>
|
|
869
|
-
</Button.Group>
|
|
870
|
-
</div>
|
|
871
|
-
</Col>
|
|
872
|
-
|
|
873
|
-
{/* 分页控制区域 */}
|
|
874
|
-
<Col xs={24} sm={24} md={12} lg={6}>
|
|
875
|
-
<div className="pagination-controls">
|
|
876
|
-
<div className="control-group">
|
|
877
|
-
<span className="control-label">每页显示</span>
|
|
878
|
-
<Select
|
|
879
|
-
value={listConfig.pageSize}
|
|
880
|
-
onChange={handlePageSizeChange}
|
|
881
|
-
style={{ width: 80 }}
|
|
882
|
-
size="middle"
|
|
883
|
-
>
|
|
884
|
-
<Select.Option value={8}>8</Select.Option>
|
|
885
|
-
<Select.Option value={12}>12</Select.Option>
|
|
886
|
-
<Select.Option value={16}>16</Select.Option>
|
|
887
|
-
<Select.Option value={24}>24</Select.Option>
|
|
888
|
-
</Select>
|
|
889
|
-
<span className="page-info">
|
|
890
|
-
第 {(listConfig.current - 1) * listConfig.pageSize + 1}-{Math.min(listConfig.current * listConfig.pageSize, mockFeatures.length)} 条
|
|
891
|
-
</span>
|
|
892
|
-
</div>
|
|
893
|
-
</div>
|
|
894
|
-
</Col>
|
|
895
|
-
</Row>
|
|
896
|
-
</Card>
|
|
897
|
-
)}
|
|
898
|
-
|
|
899
|
-
<Card className="feature-list-card">
|
|
900
|
-
{loading ? (
|
|
901
|
-
<div className="loading-state">
|
|
902
|
-
<Spin size="large" />
|
|
903
|
-
<div className="loading-text">正在加载功能模块...</div>
|
|
904
|
-
</div>
|
|
905
|
-
) : mockFeatures.length > 0 ? (
|
|
906
|
-
<>
|
|
907
|
-
{listConfig.viewMode === 'card' ? (
|
|
908
|
-
<Row gutter={[20, 20]} className="feature-grid">
|
|
909
|
-
{getPaginatedFeatures().map(feature => renderFeatureCard(feature))}
|
|
910
|
-
</Row>
|
|
911
|
-
) : (
|
|
912
|
-
renderListView()
|
|
913
|
-
)}
|
|
914
|
-
|
|
915
|
-
{/* 分页组件 */}
|
|
916
|
-
{mockFeatures.length > listConfig.pageSize && (
|
|
917
|
-
<div className="pagination-wrapper">
|
|
918
|
-
<Pagination
|
|
919
|
-
current={listConfig.current}
|
|
920
|
-
pageSize={listConfig.pageSize}
|
|
921
|
-
total={mockFeatures.length}
|
|
922
|
-
onChange={handlePageChange}
|
|
923
|
-
onShowSizeChange={handlePageChange}
|
|
924
|
-
showSizeChanger={false}
|
|
925
|
-
showQuickJumper={true}
|
|
926
|
-
showTotal={(total, range) =>
|
|
927
|
-
`第 ${range[0]}-${range[1]} 条,共 ${total} 个功能模块`
|
|
928
|
-
}
|
|
929
|
-
size="default"
|
|
930
|
-
className="custom-pagination"
|
|
931
|
-
/>
|
|
932
|
-
</div>
|
|
933
|
-
)}
|
|
934
|
-
</>
|
|
935
|
-
) : (
|
|
936
|
-
<Empty
|
|
937
|
-
className="custom-empty"
|
|
938
|
-
image={
|
|
939
|
-
<div className="empty-image">
|
|
940
|
-
<ApiOutlined style={{ fontSize: 64, color: '#d9d9d9' }} />
|
|
941
|
-
</div>
|
|
942
|
-
}
|
|
943
|
-
description={
|
|
944
|
-
<div className="empty-description">
|
|
945
|
-
<div className="empty-title">暂无功能模块</div>
|
|
946
|
-
<div className="empty-subtitle">创建您的第一个功能模块,开始管理接口</div>
|
|
947
|
-
</div>
|
|
948
|
-
}
|
|
949
|
-
>
|
|
950
|
-
<div className="empty-actions">
|
|
951
|
-
<Button type="primary" icon={<PlusOutlined />} onClick={() => openModal()}>
|
|
952
|
-
创建新功能
|
|
953
|
-
</Button>
|
|
954
|
-
<Button icon={<ExportOutlined />} onClick={importFeatureConfig}>
|
|
955
|
-
导入功能
|
|
956
|
-
</Button>
|
|
957
|
-
</div>
|
|
958
|
-
</Empty>
|
|
959
|
-
)}
|
|
960
|
-
</Card>
|
|
961
|
-
</div>
|
|
962
|
-
|
|
963
|
-
<Modal
|
|
964
|
-
title={currentFeature ? '编辑功能' : '新建功能'}
|
|
965
|
-
open={showModal}
|
|
966
|
-
onCancel={closeModal}
|
|
967
|
-
footer={null}
|
|
968
|
-
destroyOnClose
|
|
969
|
-
width={500}
|
|
970
|
-
>
|
|
971
|
-
<Form
|
|
972
|
-
form={form}
|
|
973
|
-
layout="vertical"
|
|
974
|
-
onFinish={handleSubmit}
|
|
975
|
-
initialValues={{
|
|
976
|
-
name: '',
|
|
977
|
-
description: '',
|
|
978
|
-
active: true
|
|
979
|
-
}}
|
|
980
|
-
>
|
|
981
|
-
<Form.Item
|
|
982
|
-
name="name"
|
|
983
|
-
label="功能名称"
|
|
984
|
-
rules={[{ required: true, message: '请输入功能名称' }]}
|
|
985
|
-
>
|
|
986
|
-
<Input placeholder="请输入功能名称" />
|
|
987
|
-
</Form.Item>
|
|
988
|
-
|
|
989
|
-
<Form.Item
|
|
990
|
-
name="description"
|
|
991
|
-
label="功能描述"
|
|
992
|
-
>
|
|
993
|
-
<TextArea
|
|
994
|
-
placeholder="请输入功能描述(可选)"
|
|
995
|
-
autoSize={{ minRows: 3, maxRows: 6 }}
|
|
996
|
-
/>
|
|
997
|
-
</Form.Item>
|
|
998
|
-
<div className="form-actions">
|
|
999
|
-
<Button onClick={closeModal}>
|
|
1000
|
-
取消
|
|
1001
|
-
</Button>
|
|
1002
|
-
<Button type="primary" htmlType="submit" loading={loading}>
|
|
1003
|
-
确定
|
|
1004
|
-
</Button>
|
|
1005
|
-
</div>
|
|
1006
|
-
</Form>
|
|
1007
|
-
</Modal>
|
|
1008
|
-
|
|
1009
|
-
{/* 导出功能弹窗 */}
|
|
1010
|
-
<Modal
|
|
1011
|
-
title="导出功能"
|
|
1012
|
-
open={showExportModal}
|
|
1013
|
-
onCancel={() => setShowExportModal(false)}
|
|
1014
|
-
footer={null}
|
|
1015
|
-
destroyOnClose
|
|
1016
|
-
width={520}
|
|
1017
|
-
>
|
|
1018
|
-
<div
|
|
1019
|
-
style={{
|
|
1020
|
-
display: 'flex',
|
|
1021
|
-
justifyContent: 'space-between',
|
|
1022
|
-
alignItems: 'center',
|
|
1023
|
-
marginBottom: 12
|
|
1024
|
-
}}
|
|
1025
|
-
>
|
|
1026
|
-
<Checkbox
|
|
1027
|
-
indeterminate={selectedExportIds.length > 0 && selectedExportIds.length < mockFeatures.length}
|
|
1028
|
-
checked={mockFeatures.length > 0 && selectedExportIds.length === mockFeatures.length}
|
|
1029
|
-
onChange={(e) => setSelectedExportIds(e.target.checked ? mockFeatures.map(f => f.id) : [])}
|
|
1030
|
-
>
|
|
1031
|
-
全选
|
|
1032
|
-
</Checkbox>
|
|
1033
|
-
<Text type="secondary">
|
|
1034
|
-
已选择 {selectedExportIds.length} / {mockFeatures.length}
|
|
1035
|
-
</Text>
|
|
1036
|
-
</div>
|
|
1037
|
-
|
|
1038
|
-
<div
|
|
1039
|
-
style={{
|
|
1040
|
-
maxHeight: 360,
|
|
1041
|
-
overflowY: 'auto',
|
|
1042
|
-
border: '1px solid #f0f0f0',
|
|
1043
|
-
borderRadius: 8,
|
|
1044
|
-
padding: '4px 12px'
|
|
1045
|
-
}}
|
|
1046
|
-
>
|
|
1047
|
-
<Checkbox.Group
|
|
1048
|
-
value={selectedExportIds}
|
|
1049
|
-
onChange={(values) => setSelectedExportIds(values)}
|
|
1050
|
-
style={{ width: '100%' }}
|
|
1051
|
-
>
|
|
1052
|
-
{mockFeatures.map(feature => (
|
|
1053
|
-
<div
|
|
1054
|
-
key={feature.id}
|
|
1055
|
-
style={{
|
|
1056
|
-
padding: '8px 0',
|
|
1057
|
-
borderBottom: '1px solid #fafafa'
|
|
1058
|
-
}}
|
|
1059
|
-
>
|
|
1060
|
-
<Checkbox value={feature.id} style={{ width: '100%' }}>
|
|
1061
|
-
<span style={{ fontWeight: 500 }}>{feature.name}</span>
|
|
1062
|
-
<span style={{ color: '#999', marginLeft: 8, fontSize: 12 }}>
|
|
1063
|
-
{feature.interfaceCount || 0} 个接口
|
|
1064
|
-
</span>
|
|
1065
|
-
</Checkbox>
|
|
1066
|
-
</div>
|
|
1067
|
-
))}
|
|
1068
|
-
</Checkbox.Group>
|
|
1069
|
-
</div>
|
|
1070
|
-
|
|
1071
|
-
<div className="form-actions" style={{ marginTop: 16 }}>
|
|
1072
|
-
<Button onClick={() => setShowExportModal(false)}>
|
|
1073
|
-
取消
|
|
1074
|
-
</Button>
|
|
1075
|
-
<Button
|
|
1076
|
-
type="primary"
|
|
1077
|
-
loading={exporting}
|
|
1078
|
-
disabled={selectedExportIds.length === 0}
|
|
1079
|
-
onClick={handleExportSelected}
|
|
1080
|
-
>
|
|
1081
|
-
导出 ({selectedExportIds.length})
|
|
1082
|
-
</Button>
|
|
1083
|
-
</div>
|
|
1084
|
-
</Modal>
|
|
1085
|
-
</AppLayout>
|
|
1086
|
-
);
|
|
1087
|
-
};
|
|
1088
|
-
|
|
1089
|
-
export default MockData;
|