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.
@@ -153,6 +153,8 @@
153
153
 
154
154
  /**![holder](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTMwMCAyNzYuNWE1NiA1NiAwIDEwNTYtOTcgNTYgNTYgMCAwMC01NiA5N3ptMCAyODRhNTYgNTYgMCAxMDU2LTk3IDU2IDU2IDAgMDAtNTYgOTd6TTY0MCAyMjhhNTYgNTYgMCAxMDExMiAwIDU2IDU2IDAgMDAtMTEyIDB6bTAgMjg0YTU2IDU2IDAgMTAxMTIgMCA1NiA1NiAwIDAwLTExMiAwek0zMDAgODQ0LjVhNTYgNTYgMCAxMDU2LTk3IDU2IDU2IDAgMDAtNTYgOTd6TTY0MCA3OTZhNTYgNTYgMCAxMDExMiAwIDU2IDU2IDAgMDAtMTEyIDB6IiAvPjwvc3ZnPg==) */
155
155
 
156
+ /**![import](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIGZpbGwtcnVsZT0iZXZlbm9kZCIgdmlld0JveD0iNjQgNjQgODk2IDg5NiIgZm9jdXNhYmxlPSJmYWxzZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNODgwIDkxMkgxNDRjLTE3LjcgMC0zMi0xNC4zLTMyLTMyVjE0NGMwLTE3LjcgMTQuMy0zMiAzMi0zMmgzNjBjNC40IDAgOCAzLjYgOCA4djU2YzAgNC40LTMuNiA4LTggOEgxODR2NjU2aDY1NlY1MjBjMC00LjQgMy42LTggOC04aDU2YzQuNCAwIDggMy42IDggOHYzNjBjMCAxNy43LTE0LjMgMzItMzIgMzJ6TTY1My4zIDQyNC42bDUyLjIgNTIuMmE4LjAxIDguMDEgMCAwMS00LjcgMTMuNmwtMTc5LjQgMjFjLTUuMS42LTkuNS0zLjctOC45LTguOWwyMS0xNzkuNGMuOC02LjYgOC45LTkuNCAxMy42LTQuN2w1Mi40IDUyLjQgMjU2LjItMjU2LjJjMy4xLTMuMSA4LjItMy4xIDExLjMgMGw0Mi40IDQyLjRjMy4xIDMuMSAzLjEgOC4yIDAgMTEuM0w2NTMuMyA0MjQuNnoiIC8+PC9zdmc+) */
157
+
156
158
  /**![info-circle](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTUxMiA2NEMyNjQuNiA2NCA2NCAyNjQuNiA2NCA1MTJzMjAwLjYgNDQ4IDQ0OCA0NDggNDQ4LTIwMC42IDQ0OC00NDhTNzU5LjQgNjQgNTEyIDY0em0wIDgyMGMtMjA1LjQgMC0zNzItMTY2LjYtMzcyLTM3MnMxNjYuNi0zNzIgMzcyLTM3MiAzNzIgMTY2LjYgMzcyIDM3Mi0xNjYuNiAzNzItMzcyIDM3MnoiIC8+PHBhdGggZD0iTTQ2NCAzMzZhNDggNDggMCAxMDk2IDAgNDggNDggMCAxMC05NiAwem03MiAxMTJoLTQ4Yy00LjQgMC04IDMuNi04IDh2MjcyYzAgNC40IDMuNiA4IDggOGg0OGM0LjQgMCA4LTMuNiA4LThWNDU2YzAtNC40LTMuNi04LTgtOHoiIC8+PC9zdmc+) */
157
159
 
158
160
  /**![info-circle](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTUxMiA2NEMyNjQuNiA2NCA2NCAyNjQuNiA2NCA1MTJzMjAwLjYgNDQ4IDQ0OCA0NDggNDQ4LTIwMC42IDQ0OC00NDhTNzU5LjQgNjQgNTEyIDY0em0zMiA2NjRjMCA0LjQtMy42IDgtOCA4aC00OGMtNC40IDAtOC0zLjYtOC04VjQ1NmMwLTQuNCAzLjYtOCA4LThoNDhjNC40IDAgOCAzLjYgOCA4djI3MnptLTMyLTM0NGE0OC4wMSA0OC4wMSAwIDAxMC05NiA0OC4wMSA0OC4wMSAwIDAxMCA5NnoiIC8+PC9zdmc+) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whistle.mock-plugins",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Whistle 插件,用于快速创建 API 模拟数据",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -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 config = JSON.parse(event.target.result);
289
-
290
- // 验证导入的配置
291
- if (!config.name) {
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
- const featureData = {
298
- name: config.name,
299
- description: config.description || '',
300
- active: config.active !== false
301
- };
302
-
303
- // 保存功能
304
- const featureResponse = await fetch('/cgi-bin/features', {
305
- method: 'POST',
306
- headers: {
307
- 'Content-Type': 'application/json'
308
- },
309
- body: JSON.stringify(featureData)
310
- });
311
-
312
- const featureResult = await featureResponse.json();
313
-
314
- if (featureResult.code === 0) {
315
- const newFeature = featureResult.data;
316
-
317
- // 导入接口配置
318
- if (Array.isArray(config.interfaces) && config.interfaces.length > 0) {
319
- message.loading({ content: '正在导入接口配置...', key: 'importInfo' });
320
-
321
- let successCount = 0;
322
- let errorCount = 0;
323
-
324
- for (const interfaceItem of config.interfaces) {
325
- try {
326
- // 创建接口,使用新功能ID
327
- const interfaceData = {
328
- ...interfaceItem,
329
- featureId: newFeature.id,
330
- id: undefined // 不使用原接口ID
331
- };
332
-
333
- await fetch('/cgi-bin/interfaces', {
334
- method: 'POST',
335
- headers: {
336
- 'Content-Type': 'application/json'
337
- },
338
- body: JSON.stringify(interfaceData)
339
- });
340
-
341
- successCount++;
342
- } catch (error) {
343
- console.error('导入接口配置失败:', error);
344
- errorCount++;
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
- message.success({ content: `成功导入 ${successCount} 个接口`, key: 'importInfo', duration: 2 });
420
+ featureError++;
421
+ console.error('导入功能失败:', featureResult.message);
352
422
  }
353
- } else {
354
- message.success('功能导入成功(无接口配置)');
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
  };