whistle.mock-plugins 1.0.0
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/API.md +247 -0
- package/README.md +91 -0
- package/app/cgi-bin/features.js +285 -0
- package/app/cgi-bin/index.js +31 -0
- package/app/cgi-bin/interfaces.js +409 -0
- package/app/cgi-bin/logs.js +151 -0
- package/app/cgi-bin/test-interface.js +152 -0
- package/app/public/index.html +1 -0
- package/app/public/js/index.js +2 -0
- package/app/public/js/index.js.LICENSE.txt +150 -0
- package/app/util/index.js +24 -0
- package/index.html +52 -0
- package/initialize.js +32 -0
- package/lib/dataManager.js +500 -0
- package/lib/index.js +3 -0
- package/lib/models.js +64 -0
- package/lib/routes/features.js +213 -0
- package/lib/routes/interfaces.js +304 -0
- package/lib/routes/proxy.js +239 -0
- package/lib/ruleManager.js +551 -0
- package/lib/rules-server.js +267 -0
- package/lib/server.js +805 -0
- package/lib/storage.js +16 -0
- package/lib/ui-server.js +68 -0
- package/package.json +51 -0
- package/public/bundle.js +2 -0
- package/public/bundle.js.LICENSE.txt +38 -0
- package/public/index.html +1 -0
- package/public/plugin.mock-plugin.html +14 -0
- package/rules.txt +22 -0
- package/test/rulesServer.test.js +120 -0
- package/ui/src/App.css +173 -0
- package/ui/src/App.js +24 -0
- package/ui/src/components/AppLayout.js +149 -0
- package/ui/src/components/FeatureCard.js +53 -0
- package/ui/src/components/Layout.js +94 -0
- package/ui/src/components/MainNav.js +42 -0
- package/ui/src/components/PageContainer.js +16 -0
- package/ui/src/index.html +11 -0
- package/ui/src/index.js +43 -0
- package/ui/src/pages/FileProxy.js +235 -0
- package/ui/src/pages/FilesPage.js +292 -0
- package/ui/src/pages/Home.js +128 -0
- package/ui/src/pages/HomePage.js +195 -0
- package/ui/src/pages/InterfaceManagement.js +829 -0
- package/ui/src/pages/InterfacePage.js +342 -0
- package/ui/src/pages/MockData.js +472 -0
- package/ui/src/pages/Settings.js +192 -0
- package/ui/src/pages/SettingsPage.js +165 -0
- package/ui/src/pages/UrlRedirect.js +225 -0
- package/ui/src/pages/Welcome.js +155 -0
- package/ui/src/services/api.js +186 -0
- package/ui/src/styles/app-layout.css +337 -0
- package/ui/src/styles/feature-card.css +172 -0
- package/ui/src/styles/file-proxy.css +301 -0
- package/ui/src/styles/global.css +224 -0
- package/ui/src/styles/home.css +123 -0
- package/ui/src/styles/index.css +419 -0
- package/ui/src/styles/interface-management.css +694 -0
- package/ui/src/styles/layout.css +159 -0
- package/ui/src/styles/logs-page.css +427 -0
- package/ui/src/styles/main-nav.css +52 -0
- package/ui/src/styles/mock-data.css +850 -0
- package/ui/src/styles/page-container.css +11 -0
- package/ui/src/styles/settings.css +208 -0
- package/ui/src/styles/url-redirect.css +279 -0
- package/ui/src/styles/welcome.css +282 -0
- package/webpack.config.js +49 -0
|
@@ -0,0 +1,472 @@
|
|
|
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 } from 'antd';
|
|
5
|
+
import { PlusOutlined, EditOutlined, DeleteOutlined, ApiOutlined, ExportOutlined, InfoCircleOutlined, CalendarOutlined } from '@ant-design/icons';
|
|
6
|
+
import '../styles/mock-data.css';
|
|
7
|
+
|
|
8
|
+
const { Text, Title, Paragraph } = Typography;
|
|
9
|
+
const { TextArea } = Input;
|
|
10
|
+
|
|
11
|
+
const MockData = () => {
|
|
12
|
+
const history = useHistory();
|
|
13
|
+
const [mockFeatures, setMockFeatures] = useState([]);
|
|
14
|
+
const [showModal, setShowModal] = useState(false);
|
|
15
|
+
const [currentFeature, setCurrentFeature] = useState(null);
|
|
16
|
+
const [formData, setFormData] = useState({
|
|
17
|
+
name: '',
|
|
18
|
+
description: '',
|
|
19
|
+
active: true
|
|
20
|
+
});
|
|
21
|
+
const [loading, setLoading] = useState(true);
|
|
22
|
+
const [form] = Form.useForm();
|
|
23
|
+
|
|
24
|
+
// 加载功能列表
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
fetchFeatures();
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
// 获取所有功能模块
|
|
30
|
+
const fetchFeatures = async () => {
|
|
31
|
+
try {
|
|
32
|
+
setLoading(true);
|
|
33
|
+
const response = await fetch('/cgi-bin/features');
|
|
34
|
+
const result = await response.json();
|
|
35
|
+
|
|
36
|
+
if (result.code === 0) {
|
|
37
|
+
setMockFeatures(result.data || []);
|
|
38
|
+
} else {
|
|
39
|
+
console.error('获取功能模块失败:', result.message);
|
|
40
|
+
message.error('获取功能模块失败: ' + result.message);
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error('获取功能模块错误:', error);
|
|
44
|
+
message.error('获取功能模块失败, 请检查网络连接');
|
|
45
|
+
} finally {
|
|
46
|
+
setLoading(false);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const openModal = (feature = null) => {
|
|
51
|
+
if (feature) {
|
|
52
|
+
setCurrentFeature(feature);
|
|
53
|
+
form.setFieldsValue({
|
|
54
|
+
name: feature.name,
|
|
55
|
+
description: feature.description,
|
|
56
|
+
active: feature.active !== false
|
|
57
|
+
});
|
|
58
|
+
} else {
|
|
59
|
+
setCurrentFeature(null);
|
|
60
|
+
form.resetFields();
|
|
61
|
+
form.setFieldsValue({
|
|
62
|
+
active: true
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
setShowModal(true);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const closeModal = () => {
|
|
69
|
+
setShowModal(false);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const handleSubmit = async (values) => {
|
|
73
|
+
try {
|
|
74
|
+
const featureData = {
|
|
75
|
+
...values
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// 如果是编辑已有功能,添加ID
|
|
79
|
+
if (currentFeature) {
|
|
80
|
+
featureData.id = currentFeature.id;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setLoading(true);
|
|
84
|
+
const response = await fetch('/cgi-bin/features', {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: {
|
|
87
|
+
'Content-Type': 'application/json'
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify(featureData)
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const result = await response.json();
|
|
93
|
+
|
|
94
|
+
if (result.code === 0) {
|
|
95
|
+
// 刷新功能列表
|
|
96
|
+
message.success(currentFeature ? '功能模块更新成功' : '功能模块创建成功');
|
|
97
|
+
fetchFeatures();
|
|
98
|
+
closeModal();
|
|
99
|
+
} else {
|
|
100
|
+
message.error('操作失败: ' + result.message);
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error('保存功能模块错误:', error);
|
|
104
|
+
message.error('保存失败: ' + error.message);
|
|
105
|
+
} finally {
|
|
106
|
+
setLoading(false);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const handleToggleActive = async (id, currentActive) => {
|
|
111
|
+
try {
|
|
112
|
+
const response = await fetch(`/cgi-bin/features?id=${id}`, {
|
|
113
|
+
method: 'PATCH',
|
|
114
|
+
headers: {
|
|
115
|
+
'Content-Type': 'application/json'
|
|
116
|
+
},
|
|
117
|
+
body: JSON.stringify({
|
|
118
|
+
active: !currentActive
|
|
119
|
+
})
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const result = await response.json();
|
|
123
|
+
|
|
124
|
+
if (result.code === 0) {
|
|
125
|
+
message.success(`${currentActive ? '禁用' : '启用'}功能模块成功`);
|
|
126
|
+
// 更新本地状态
|
|
127
|
+
setMockFeatures(mockFeatures.map(feature =>
|
|
128
|
+
feature.id === id ? { ...feature, active: !currentActive } : feature
|
|
129
|
+
));
|
|
130
|
+
} else {
|
|
131
|
+
message.error(`${currentActive ? '禁用' : '启用'}功能模块失败: ${result.message}`);
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error(`${currentActive ? '禁用' : '启用'}功能模块错误:`, error);
|
|
135
|
+
message.error(`操作失败: ${error.message}`);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const deleteFeature = async (id) => {
|
|
140
|
+
Modal.confirm({
|
|
141
|
+
title: '确定要删除此功能吗?',
|
|
142
|
+
content: '这将删除所有相关的接口和模拟数据,此操作无法恢复。',
|
|
143
|
+
okText: '删除',
|
|
144
|
+
okType: 'danger',
|
|
145
|
+
cancelText: '取消',
|
|
146
|
+
onOk: async () => {
|
|
147
|
+
try {
|
|
148
|
+
setLoading(true);
|
|
149
|
+
const response = await fetch(`/cgi-bin/features?id=${id}`, {
|
|
150
|
+
method: 'DELETE'
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const result = await response.json();
|
|
154
|
+
|
|
155
|
+
if (result.code === 0) {
|
|
156
|
+
message.success('功能模块已成功删除');
|
|
157
|
+
// 更新本地状态
|
|
158
|
+
setMockFeatures(mockFeatures.filter(f => f.id !== id));
|
|
159
|
+
} else {
|
|
160
|
+
message.error('删除失败: ' + result.message);
|
|
161
|
+
}
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error('删除功能错误:', error);
|
|
164
|
+
message.error('操作失败: ' + error.message);
|
|
165
|
+
} finally {
|
|
166
|
+
setLoading(false);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const viewInterfaces = (feature) => {
|
|
173
|
+
// 导航到该功能的接口列表页面
|
|
174
|
+
history.push(`/interface/${feature.id}`);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const exportFeatureConfig = async (feature) => {
|
|
178
|
+
try {
|
|
179
|
+
// 获取该功能的所有接口
|
|
180
|
+
const response = await fetch(`/cgi-bin/interfaces?featureId=${feature.id}`);
|
|
181
|
+
const result = await response.json();
|
|
182
|
+
|
|
183
|
+
// 创建完整配置
|
|
184
|
+
const config = {
|
|
185
|
+
...feature,
|
|
186
|
+
interfaces: result.code === 0 ? result.data : []
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const dataStr = JSON.stringify(config, null, 2);
|
|
190
|
+
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
|
|
191
|
+
|
|
192
|
+
const exportFileDefaultName = `whistle-mock-feature-${feature.id}.json`;
|
|
193
|
+
|
|
194
|
+
const linkElement = document.createElement('a');
|
|
195
|
+
linkElement.setAttribute('href', dataUri);
|
|
196
|
+
linkElement.setAttribute('download', exportFileDefaultName);
|
|
197
|
+
linkElement.click();
|
|
198
|
+
|
|
199
|
+
message.success('配置导出成功');
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.error('导出配置错误:', error);
|
|
202
|
+
message.error('导出失败: ' + error.message);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const importFeatureConfig = () => {
|
|
207
|
+
const input = document.createElement('input');
|
|
208
|
+
input.type = 'file';
|
|
209
|
+
input.accept = '.json';
|
|
210
|
+
|
|
211
|
+
input.onchange = async (e) => {
|
|
212
|
+
const file = e.target.files[0];
|
|
213
|
+
if (!file) return;
|
|
214
|
+
|
|
215
|
+
const reader = new FileReader();
|
|
216
|
+
reader.onload = async (event) => {
|
|
217
|
+
try {
|
|
218
|
+
const config = JSON.parse(event.target.result);
|
|
219
|
+
|
|
220
|
+
// 验证导入的配置
|
|
221
|
+
if (!config.name) {
|
|
222
|
+
message.error('无效的配置文件: 缺少功能名称');
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 创建新功能
|
|
227
|
+
const featureData = {
|
|
228
|
+
name: config.name,
|
|
229
|
+
description: config.description || '',
|
|
230
|
+
active: config.active !== false
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// 保存功能
|
|
234
|
+
const featureResponse = await fetch('/cgi-bin/features', {
|
|
235
|
+
method: 'POST',
|
|
236
|
+
headers: {
|
|
237
|
+
'Content-Type': 'application/json'
|
|
238
|
+
},
|
|
239
|
+
body: JSON.stringify(featureData)
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const featureResult = await featureResponse.json();
|
|
243
|
+
|
|
244
|
+
if (featureResult.code === 0) {
|
|
245
|
+
const newFeature = featureResult.data;
|
|
246
|
+
|
|
247
|
+
// 导入接口配置
|
|
248
|
+
if (Array.isArray(config.interfaces) && config.interfaces.length > 0) {
|
|
249
|
+
message.loading({ content: '正在导入接口配置...', key: 'importInfo' });
|
|
250
|
+
|
|
251
|
+
let successCount = 0;
|
|
252
|
+
let errorCount = 0;
|
|
253
|
+
|
|
254
|
+
for (const interfaceItem of config.interfaces) {
|
|
255
|
+
try {
|
|
256
|
+
// 创建接口,使用新功能ID
|
|
257
|
+
const interfaceData = {
|
|
258
|
+
...interfaceItem,
|
|
259
|
+
featureId: newFeature.id,
|
|
260
|
+
id: undefined // 不使用原接口ID
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
await fetch('/cgi-bin/interfaces', {
|
|
264
|
+
method: 'POST',
|
|
265
|
+
headers: {
|
|
266
|
+
'Content-Type': 'application/json'
|
|
267
|
+
},
|
|
268
|
+
body: JSON.stringify(interfaceData)
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
successCount++;
|
|
272
|
+
} catch (error) {
|
|
273
|
+
console.error('导入接口配置失败:', error);
|
|
274
|
+
errorCount++;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (errorCount > 0) {
|
|
279
|
+
message.info({ content: `导入完成,成功 ${successCount} 个接口,失败 ${errorCount} 个接口`, key: 'importInfo', duration: 3 });
|
|
280
|
+
} else {
|
|
281
|
+
message.success({ content: `成功导入 ${successCount} 个接口`, key: 'importInfo', duration: 2 });
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
message.success('功能导入成功(无接口配置)');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 刷新功能列表
|
|
288
|
+
fetchFeatures();
|
|
289
|
+
} else {
|
|
290
|
+
message.error('导入功能失败: ' + featureResult.message);
|
|
291
|
+
}
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error('导入配置错误:', error);
|
|
294
|
+
message.error('导入失败: ' + error.message);
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
reader.readAsText(file);
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
input.click();
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// 渲染功能模块卡片
|
|
305
|
+
const renderFeatureCard = (feature) => {
|
|
306
|
+
const formattedDate = feature.createdAt
|
|
307
|
+
? new Date(feature.createdAt).toLocaleDateString()
|
|
308
|
+
: '未知日期';
|
|
309
|
+
|
|
310
|
+
return (
|
|
311
|
+
<Col xs={24} sm={12} md={8} lg={6} key={feature.id} style={{ marginBottom: 16 }}>
|
|
312
|
+
<Badge.Ribbon
|
|
313
|
+
text={feature.active ? '已启用' : '已禁用'}
|
|
314
|
+
color={feature.active ? '#52c41a' : '#f5222d'}
|
|
315
|
+
style={{ display: 'block' }}
|
|
316
|
+
>
|
|
317
|
+
<Card
|
|
318
|
+
hoverable
|
|
319
|
+
className={`feature-card ${!feature.active ? 'inactive-feature' : ''}`}
|
|
320
|
+
actions={[
|
|
321
|
+
<Tooltip title="管理接口">
|
|
322
|
+
<ApiOutlined key="interfaces" onClick={() => viewInterfaces(feature)} />
|
|
323
|
+
</Tooltip>,
|
|
324
|
+
<Tooltip title="编辑功能">
|
|
325
|
+
<EditOutlined key="edit" onClick={() => openModal(feature)} />
|
|
326
|
+
</Tooltip>,
|
|
327
|
+
<Tooltip title="删除功能">
|
|
328
|
+
<DeleteOutlined key="delete" onClick={() => deleteFeature(feature.id)} />
|
|
329
|
+
</Tooltip>,
|
|
330
|
+
<Tooltip title="导出配置">
|
|
331
|
+
<ExportOutlined key="export" onClick={() => exportFeatureConfig(feature)} />
|
|
332
|
+
</Tooltip>
|
|
333
|
+
]}
|
|
334
|
+
>
|
|
335
|
+
<div className="feature-card-content">
|
|
336
|
+
<div className="feature-name">
|
|
337
|
+
<Title level={4} ellipsis={{ tooltip: feature.name }}>
|
|
338
|
+
{feature.name}
|
|
339
|
+
</Title>
|
|
340
|
+
|
|
341
|
+
<Switch
|
|
342
|
+
checked={feature.active}
|
|
343
|
+
onChange={() => handleToggleActive(feature.id, feature.active)}
|
|
344
|
+
size="small"
|
|
345
|
+
/>
|
|
346
|
+
</div>
|
|
347
|
+
|
|
348
|
+
<Paragraph className="feature-description" ellipsis={{ rows: 2, expandable: false, tooltip: feature.description }}>
|
|
349
|
+
{feature.description || '无描述'}
|
|
350
|
+
</Paragraph>
|
|
351
|
+
|
|
352
|
+
<div className="feature-stat">
|
|
353
|
+
<span>
|
|
354
|
+
<InfoCircleOutlined /> {feature.interfaceCount || 0} 个接口
|
|
355
|
+
</span>
|
|
356
|
+
<span className="feature-date">
|
|
357
|
+
<CalendarOutlined /> {formattedDate}
|
|
358
|
+
</span>
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
</Card>
|
|
362
|
+
</Badge.Ribbon>
|
|
363
|
+
</Col>
|
|
364
|
+
);
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
return (
|
|
368
|
+
<AppLayout>
|
|
369
|
+
<div className="page-container">
|
|
370
|
+
<div className="page-title-bar">
|
|
371
|
+
<div>
|
|
372
|
+
<h1 className="page-title">功能模块管理</h1>
|
|
373
|
+
<div className="page-description">
|
|
374
|
+
创建和管理功能模块,为每个功能配置独立的接口
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
<div className="page-actions">
|
|
378
|
+
<Button
|
|
379
|
+
type="primary"
|
|
380
|
+
icon={<PlusOutlined />}
|
|
381
|
+
onClick={() => openModal()}
|
|
382
|
+
>
|
|
383
|
+
新建功能
|
|
384
|
+
</Button>
|
|
385
|
+
<Button
|
|
386
|
+
type="primary"
|
|
387
|
+
icon={<ExportOutlined />}
|
|
388
|
+
onClick={importFeatureConfig}
|
|
389
|
+
style={{ backgroundColor: '#52c41a', borderColor: '#52c41a' }}
|
|
390
|
+
>
|
|
391
|
+
导入功能
|
|
392
|
+
</Button>
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
|
|
396
|
+
<div className="feature-list-container">
|
|
397
|
+
{loading ? (
|
|
398
|
+
<div className="loading">
|
|
399
|
+
<div className="loading-spinner"></div>
|
|
400
|
+
<div>正在加载功能模块...</div>
|
|
401
|
+
</div>
|
|
402
|
+
) : mockFeatures.length > 0 ? (
|
|
403
|
+
<Row gutter={[16, 16]}>
|
|
404
|
+
{mockFeatures.map(feature => renderFeatureCard(feature))}
|
|
405
|
+
</Row>
|
|
406
|
+
) : (
|
|
407
|
+
<div className="empty-data">
|
|
408
|
+
<div className="empty-icon">📂</div>
|
|
409
|
+
<div className="empty-text">暂无功能,请点击"新建功能"按钮创建</div>
|
|
410
|
+
<div className="empty-actions">
|
|
411
|
+
<button className="create-button" onClick={() => openModal()}>
|
|
412
|
+
创建新功能
|
|
413
|
+
</button>
|
|
414
|
+
<button className="import-button-large" onClick={importFeatureConfig}>
|
|
415
|
+
导入已有功能
|
|
416
|
+
</button>
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
)}
|
|
420
|
+
</div>
|
|
421
|
+
</div>
|
|
422
|
+
|
|
423
|
+
<Modal
|
|
424
|
+
title={currentFeature ? '编辑功能' : '新建功能'}
|
|
425
|
+
open={showModal}
|
|
426
|
+
onCancel={closeModal}
|
|
427
|
+
footer={null}
|
|
428
|
+
destroyOnClose
|
|
429
|
+
width={500}
|
|
430
|
+
>
|
|
431
|
+
<Form
|
|
432
|
+
form={form}
|
|
433
|
+
layout="vertical"
|
|
434
|
+
onFinish={handleSubmit}
|
|
435
|
+
initialValues={{
|
|
436
|
+
name: '',
|
|
437
|
+
description: '',
|
|
438
|
+
active: true
|
|
439
|
+
}}
|
|
440
|
+
>
|
|
441
|
+
<Form.Item
|
|
442
|
+
name="name"
|
|
443
|
+
label="功能名称"
|
|
444
|
+
rules={[{ required: true, message: '请输入功能名称' }]}
|
|
445
|
+
>
|
|
446
|
+
<Input placeholder="请输入功能名称" />
|
|
447
|
+
</Form.Item>
|
|
448
|
+
|
|
449
|
+
<Form.Item
|
|
450
|
+
name="description"
|
|
451
|
+
label="功能描述"
|
|
452
|
+
>
|
|
453
|
+
<TextArea
|
|
454
|
+
placeholder="请输入功能描述(可选)"
|
|
455
|
+
autoSize={{ minRows: 3, maxRows: 6 }}
|
|
456
|
+
/>
|
|
457
|
+
</Form.Item>
|
|
458
|
+
<div className="form-actions">
|
|
459
|
+
<Button onClick={closeModal}>
|
|
460
|
+
取消
|
|
461
|
+
</Button>
|
|
462
|
+
<Button type="primary" htmlType="submit" loading={loading}>
|
|
463
|
+
确定
|
|
464
|
+
</Button>
|
|
465
|
+
</div>
|
|
466
|
+
</Form>
|
|
467
|
+
</Modal>
|
|
468
|
+
</AppLayout>
|
|
469
|
+
);
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
export default MockData;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import AppLayout from '../components/AppLayout';
|
|
3
|
+
import { Form, Input, Button, Switch, Card, message, Divider, Typography, Space, Alert, InputNumber } from 'antd';
|
|
4
|
+
import { SaveOutlined, UndoOutlined } from '@ant-design/icons';
|
|
5
|
+
import '../styles/settings.css';
|
|
6
|
+
|
|
7
|
+
const { Title, Text } = Typography;
|
|
8
|
+
|
|
9
|
+
const Settings = () => {
|
|
10
|
+
const [form] = Form.useForm();
|
|
11
|
+
const [loading, setLoading] = useState(false);
|
|
12
|
+
const [settings, setSettings] = useState(null);
|
|
13
|
+
|
|
14
|
+
// 获取当前设置
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
fetchSettings();
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
19
|
+
const fetchSettings = async () => {
|
|
20
|
+
try {
|
|
21
|
+
setLoading(true);
|
|
22
|
+
// 这里假设有一个获取设置的接口
|
|
23
|
+
// 为了示例,我们使用默认设置
|
|
24
|
+
const defaultSettings = {
|
|
25
|
+
logRetention: 7,
|
|
26
|
+
enableLogging: true,
|
|
27
|
+
enableAutoRefresh: false,
|
|
28
|
+
maxLogEntries: 10000,
|
|
29
|
+
notificationEnabled: true,
|
|
30
|
+
mockjsEnabled: true,
|
|
31
|
+
responseDelay: 0
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
setSettings(defaultSettings);
|
|
35
|
+
form.setFieldsValue(defaultSettings);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('获取设置失败:', error);
|
|
38
|
+
message.error('获取设置失败');
|
|
39
|
+
} finally {
|
|
40
|
+
setLoading(false);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const handleSave = async (values) => {
|
|
45
|
+
try {
|
|
46
|
+
setLoading(true);
|
|
47
|
+
// 这里假设有一个保存设置的接口
|
|
48
|
+
// 现在只是模拟保存成功
|
|
49
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
50
|
+
|
|
51
|
+
setSettings(values);
|
|
52
|
+
message.success('设置已保存');
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('保存设置失败:', error);
|
|
55
|
+
message.error('保存设置失败');
|
|
56
|
+
} finally {
|
|
57
|
+
setLoading(false);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const handleReset = () => {
|
|
62
|
+
if (settings) {
|
|
63
|
+
form.setFieldsValue(settings);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<AppLayout>
|
|
69
|
+
<div className="page-container">
|
|
70
|
+
<div className="page-title-bar">
|
|
71
|
+
<div>
|
|
72
|
+
<h1 className="page-title">系统设置</h1>
|
|
73
|
+
<div className="page-description">
|
|
74
|
+
配置插件的全局设置项,这些设置将影响所有功能模块
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<Alert
|
|
80
|
+
message="设置功能正在开发中"
|
|
81
|
+
description="目前部分设置可能不生效,我们正在努力完善此功能"
|
|
82
|
+
type="info"
|
|
83
|
+
showIcon
|
|
84
|
+
style={{ marginBottom: 20 }}
|
|
85
|
+
/>
|
|
86
|
+
|
|
87
|
+
<Form
|
|
88
|
+
form={form}
|
|
89
|
+
layout="vertical"
|
|
90
|
+
onFinish={handleSave}
|
|
91
|
+
initialValues={settings || {}}
|
|
92
|
+
>
|
|
93
|
+
<Card title="日志设置" className="settings-card">
|
|
94
|
+
<Form.Item
|
|
95
|
+
name="enableLogging"
|
|
96
|
+
label="启用请求日志记录"
|
|
97
|
+
valuePropName="checked"
|
|
98
|
+
>
|
|
99
|
+
<Switch />
|
|
100
|
+
</Form.Item>
|
|
101
|
+
|
|
102
|
+
<Form.Item
|
|
103
|
+
name="logRetention"
|
|
104
|
+
label="日志保留天数"
|
|
105
|
+
rules={[{ required: true, message: '请输入日志保留天数' }]}
|
|
106
|
+
tooltip="超过设定天数的日志将被自动清理"
|
|
107
|
+
>
|
|
108
|
+
<InputNumber min={1} max={90} />
|
|
109
|
+
</Form.Item>
|
|
110
|
+
|
|
111
|
+
<Form.Item
|
|
112
|
+
name="maxLogEntries"
|
|
113
|
+
label="最大日志条数"
|
|
114
|
+
rules={[{ required: true, message: '请输入最大日志条数' }]}
|
|
115
|
+
tooltip="超过设定条数的日志将被清理"
|
|
116
|
+
>
|
|
117
|
+
<InputNumber min={1000} max={100000} step={1000} />
|
|
118
|
+
</Form.Item>
|
|
119
|
+
</Card>
|
|
120
|
+
|
|
121
|
+
<Card title="接口模拟设置" className="settings-card">
|
|
122
|
+
<Form.Item
|
|
123
|
+
name="mockjsEnabled"
|
|
124
|
+
label="启用 Mock.js 数据模拟"
|
|
125
|
+
valuePropName="checked"
|
|
126
|
+
tooltip="使用 Mock.js 生成随机数据"
|
|
127
|
+
>
|
|
128
|
+
<Switch />
|
|
129
|
+
</Form.Item>
|
|
130
|
+
|
|
131
|
+
<Form.Item
|
|
132
|
+
name="responseDelay"
|
|
133
|
+
label="全局响应延迟(毫秒)"
|
|
134
|
+
tooltip="所有接口默认的响应延迟时间"
|
|
135
|
+
>
|
|
136
|
+
<InputNumber min={0} max={10000} />
|
|
137
|
+
</Form.Item>
|
|
138
|
+
|
|
139
|
+
<Form.Item
|
|
140
|
+
name="notificationEnabled"
|
|
141
|
+
label="启用请求匹配通知"
|
|
142
|
+
valuePropName="checked"
|
|
143
|
+
tooltip="接口匹配成功时显示通知"
|
|
144
|
+
>
|
|
145
|
+
<Switch />
|
|
146
|
+
</Form.Item>
|
|
147
|
+
</Card>
|
|
148
|
+
|
|
149
|
+
<Card title="界面设置" className="settings-card">
|
|
150
|
+
<Form.Item
|
|
151
|
+
name="enableAutoRefresh"
|
|
152
|
+
label="启用页面自动刷新"
|
|
153
|
+
valuePropName="checked"
|
|
154
|
+
tooltip="启用后,页面数据将定期自动刷新"
|
|
155
|
+
>
|
|
156
|
+
<Switch />
|
|
157
|
+
</Form.Item>
|
|
158
|
+
|
|
159
|
+
<Form.Item
|
|
160
|
+
name="theme"
|
|
161
|
+
label="界面主题"
|
|
162
|
+
>
|
|
163
|
+
<Input disabled placeholder="暂不支持自定义主题" />
|
|
164
|
+
</Form.Item>
|
|
165
|
+
</Card>
|
|
166
|
+
|
|
167
|
+
<div className="settings-actions">
|
|
168
|
+
<Space>
|
|
169
|
+
<Button
|
|
170
|
+
type="default"
|
|
171
|
+
icon={<UndoOutlined />}
|
|
172
|
+
onClick={handleReset}
|
|
173
|
+
>
|
|
174
|
+
重置
|
|
175
|
+
</Button>
|
|
176
|
+
<Button
|
|
177
|
+
type="primary"
|
|
178
|
+
icon={<SaveOutlined />}
|
|
179
|
+
htmlType="submit"
|
|
180
|
+
loading={loading}
|
|
181
|
+
>
|
|
182
|
+
保存设置
|
|
183
|
+
</Button>
|
|
184
|
+
</Space>
|
|
185
|
+
</div>
|
|
186
|
+
</Form>
|
|
187
|
+
</div>
|
|
188
|
+
</AppLayout>
|
|
189
|
+
);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export default Settings;
|