whistle.mock-plugins 1.3.0 → 1.3.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 +6 -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/pages/MockDataV2.js +234 -70
|
@@ -153,6 +153,8 @@
|
|
|
153
153
|
|
|
154
154
|
/** */
|
|
155
155
|
|
|
156
|
+
/** */
|
|
157
|
+
|
|
156
158
|
/** */
|
|
157
159
|
|
|
158
160
|
/** */
|
package/package.json
CHANGED
|
@@ -6,8 +6,8 @@ import React, { useState, useEffect } from 'react';
|
|
|
6
6
|
import { useRef } from 'react';
|
|
7
7
|
import { useHistory } from 'react-router-dom';
|
|
8
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 } from 'antd';
|
|
10
|
-
import { PlusOutlined, EditOutlined, DeleteOutlined, ApiOutlined, ExportOutlined, InfoCircleOutlined, CalendarOutlined, SortAscendingOutlined, AppstoreOutlined, UnorderedListOutlined } from '@ant-design/icons';
|
|
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
11
|
import '../styles/mock-data.css';
|
|
12
12
|
import axios from 'axios';
|
|
13
13
|
|
|
@@ -31,6 +31,11 @@ const MockData = () => {
|
|
|
31
31
|
const [showModal, setShowModal] = useState(false);
|
|
32
32
|
const [currentFeature, setCurrentFeature] = useState(null);
|
|
33
33
|
|
|
34
|
+
// 导出功能弹窗状态
|
|
35
|
+
const [showExportModal, setShowExportModal] = useState(false);
|
|
36
|
+
const [selectedExportIds, setSelectedExportIds] = useState([]);
|
|
37
|
+
const [exporting, setExporting] = useState(false);
|
|
38
|
+
|
|
34
39
|
const [loading, setLoading] = useState(true);
|
|
35
40
|
const [form] = Form.useForm();
|
|
36
41
|
|
|
@@ -273,6 +278,65 @@ const MockData = () => {
|
|
|
273
278
|
}
|
|
274
279
|
};
|
|
275
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
|
+
|
|
276
340
|
const importFeatureConfig = () => {
|
|
277
341
|
const input = document.createElement('input');
|
|
278
342
|
input.type = 'file';
|
|
@@ -285,80 +349,94 @@ const MockData = () => {
|
|
|
285
349
|
const reader = new FileReader();
|
|
286
350
|
reader.onload = async (event) => {
|
|
287
351
|
try {
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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) {
|
|
292
359
|
message.error('无效的配置文件: 缺少功能名称');
|
|
293
360
|
return;
|
|
294
361
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
+
}
|
|
345
418
|
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (errorCount > 0) {
|
|
349
|
-
message.info({ content: `导入完成,成功 ${successCount} 个接口,失败 ${errorCount} 个接口`, key: 'importInfo', duration: 3 });
|
|
350
419
|
} else {
|
|
351
|
-
|
|
420
|
+
featureError++;
|
|
421
|
+
console.error('导入功能失败:', featureResult.message);
|
|
352
422
|
}
|
|
353
|
-
}
|
|
354
|
-
|
|
423
|
+
} catch (error) {
|
|
424
|
+
featureError++;
|
|
425
|
+
console.error('导入功能错误:', error);
|
|
355
426
|
}
|
|
356
|
-
|
|
357
|
-
// 刷新功能列表
|
|
358
|
-
fetchFeatures();
|
|
359
|
-
} else {
|
|
360
|
-
message.error('导入功能失败: ' + featureResult.message);
|
|
361
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();
|
|
362
440
|
} catch (error) {
|
|
363
441
|
console.error('导入配置错误:', error);
|
|
364
442
|
message.error('导入失败: ' + error.message);
|
|
@@ -693,14 +771,23 @@ const MockData = () => {
|
|
|
693
771
|
>
|
|
694
772
|
新建功能
|
|
695
773
|
</Button>
|
|
696
|
-
<Button
|
|
697
|
-
type="primary"
|
|
774
|
+
<Button
|
|
775
|
+
type="primary"
|
|
698
776
|
icon={<ExportOutlined />}
|
|
699
777
|
onClick={importFeatureConfig}
|
|
700
778
|
style={{ backgroundColor: '#52c41a', borderColor: '#52c41a' }}
|
|
701
779
|
>
|
|
702
780
|
导入功能
|
|
703
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>
|
|
704
791
|
</div>
|
|
705
792
|
</div>
|
|
706
793
|
|
|
@@ -918,6 +1005,83 @@ const MockData = () => {
|
|
|
918
1005
|
</div>
|
|
919
1006
|
</Form>
|
|
920
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>
|
|
921
1085
|
</AppLayout>
|
|
922
1086
|
);
|
|
923
1087
|
};
|