whistle.mock-plugins 1.2.0 → 1.2.1-beta.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.
@@ -4,6 +4,12 @@
4
4
  http://jedwatson.github.io/classnames
5
5
  */
6
6
 
7
+ /*!
8
+ Copyright (c) 2018 Jed Watson.
9
+ Licensed under the MIT License (MIT), see
10
+ http://jedwatson.github.io/classnames
11
+ */
12
+
7
13
  /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
8
14
 
9
15
  /**
@@ -55,6 +61,13 @@
55
61
  * LICENSE file in the root directory of this source tree.
56
62
  */
57
63
 
64
+ /**!
65
+ * Sortable 1.15.6
66
+ * @author RubaXa <trash@rubaxa.org>
67
+ * @author owenm <owen23355@gmail.com>
68
+ * @license MIT
69
+ */
70
+
58
71
  /**![api](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTkxNy43IDE0OC44bC00Mi40LTQyLjRjLTEuNi0xLjYtMy42LTIuMy01LjctMi4zcy00LjEuOC01LjcgMi4zbC03Ni4xIDc2LjFhMTk5LjI3IDE5OS4yNyAwIDAwLTExMi4xLTM0LjNjLTUxLjIgMC0xMDIuNCAxOS41LTE0MS41IDU4LjZMNDMyLjMgMzA4LjdhOC4wMyA4LjAzIDAgMDAwIDExLjNMNzA0IDU5MS43YzEuNiAxLjYgMy42IDIuMyA1LjcgMi4zIDIgMCA0LjEtLjggNS43LTIuM2wxMDEuOS0xMDEuOWM2OC45LTY5IDc3LTE3NS43IDI0LjMtMjUzLjVsNzYuMS03Ni4xYzMuMS0zLjIgMy4xLTguMyAwLTExLjR6TTc2OS4xIDQ0MS43bC01OS40IDU5LjQtMTg2LjgtMTg2LjggNTkuNC01OS40YzI0LjktMjQuOSA1OC4xLTM4LjcgOTMuNC0zOC43IDM1LjMgMCA2OC40IDEzLjcgOTMuNCAzOC43IDI0LjkgMjQuOSAzOC43IDU4LjEgMzguNyA5My40IDAgMzUuMy0xMy44IDY4LjQtMzguNyA5My40em0tMTkwLjIgMTA1YTguMDMgOC4wMyAwIDAwLTExLjMgMEw1MDEgNjEzLjMgNDEwLjcgNTIzbDY2LjctNjYuN2MzLjEtMy4xIDMuMS04LjIgMC0xMS4zTDQ0MSA0MDguNmE4LjAzIDguMDMgMCAwMC0xMS4zIDBMMzYzIDQ3NS4zbC00My00M2E3Ljg1IDcuODUgMCAwMC01LjctMi4zYy0yIDAtNC4xLjgtNS43IDIuM0wyMDYuOCA1MzQuMmMtNjguOSA2OS03NyAxNzUuNy0yNC4zIDI1My41bC03Ni4xIDc2LjFhOC4wMyA4LjAzIDAgMDAwIDExLjNsNDIuNCA0Mi40YzEuNiAxLjYgMy42IDIuMyA1LjcgMi4zczQuMS0uOCA1LjctMi4zbDc2LjEtNzYuMWMzMy43IDIyLjkgNzIuOSAzNC4zIDExMi4xIDM0LjMgNTEuMiAwIDEwMi40LTE5LjUgMTQxLjUtNTguNmwxMDEuOS0xMDEuOWMzLjEtMy4xIDMuMS04LjIgMC0xMS4zbC00My00MyA2Ni43LTY2LjdjMy4xLTMuMSAzLjEtOC4yIDAtMTEuM2wtMzYuNi0zNi4yek00NDEuNyA3NjkuMWExMzEuMzIgMTMxLjMyIDAgMDEtOTMuNCAzOC43Yy0zNS4zIDAtNjguNC0xMy43LTkzLjQtMzguN2ExMzEuMzIgMTMxLjMyIDAgMDEtMzguNy05My40YzAtMzUuMyAxMy43LTY4LjQgMzguNy05My40bDU5LjQtNTkuNCAxODYuOCAxODYuOC01OS40IDU5LjR6IiAvPjwvc3ZnPg==) */
59
72
 
60
73
  /**![appstore](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTQ2NCAxNDRIMTYwYy04LjggMC0xNiA3LjItMTYgMTZ2MzA0YzAgOC44IDcuMiAxNiAxNiAxNmgzMDRjOC44IDAgMTYtNy4yIDE2LTE2VjE2MGMwLTguOC03LjItMTYtMTYtMTZ6bS01MiAyNjhIMjEyVjIxMmgyMDB2MjAwem00NTItMjY4SDU2MGMtOC44IDAtMTYgNy4yLTE2IDE2djMwNGMwIDguOCA3LjIgMTYgMTYgMTZoMzA0YzguOCAwIDE2LTcuMiAxNi0xNlYxNjBjMC04LjgtNy4yLTE2LTE2LTE2em0tNTIgMjY4SDYxMlYyMTJoMjAwdjIwMHpNNDY0IDU0NEgxNjBjLTguOCAwLTE2IDcuMi0xNiAxNnYzMDRjMCA4LjggNy4yIDE2IDE2IDE2aDMwNGM4LjggMCAxNi03LjIgMTYtMTZWNTYwYzAtOC44LTcuMi0xNi0xNi0xNnptLTUyIDI2OEgyMTJWNjEyaDIwMHYyMDB6bTQ1Mi0yNjhINTYwYy04LjggMC0xNiA3LjItMTYgMTZ2MzA0YzAgOC44IDcuMiAxNiAxNiAxNmgzMDRjOC44IDAgMTYtNy4yIDE2LTE2VjU2MGMwLTguOC03LjItMTYtMTYtMTZ6bS01MiAyNjhINjEyVjYxMmgyMDB2MjAweiIgLz48L3N2Zz4=) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whistle.mock-plugins",
3
- "version": "1.2.0",
3
+ "version": "1.2.1-beta.1",
4
4
  "description": "Whistle 插件,用于快速创建 API 模拟数据",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -39,6 +39,8 @@
39
39
  "jest": "^29.7.0",
40
40
  "react": "^18.2.0",
41
41
  "react-dom": "^18.2.0",
42
+ "react-sortablejs": "^6.1.4",
43
+ "sortablejs": "^1.15.3",
42
44
  "react-markdown": "^8.0.7",
43
45
  "style-loader": "^3.3.2",
44
46
  "webpack": "^5.79.0",
@@ -0,0 +1,110 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Modal, Form, Input, message } from 'antd';
3
+ import axios from 'axios';
4
+ import { ResponseContentEditor, PreviewModal, formatResponseContent, refreshCacheAfterUpdate } from '.';
5
+
6
+ const DataManagementModal = ({ visible, onCancel, interfaceItem, onSaved }) => {
7
+ const [form] = Form.useForm();
8
+ const [previewVisible, setPreviewVisible] = useState(false);
9
+ const [previewContent, setPreviewContent] = useState(null);
10
+
11
+ useEffect(() => {
12
+ if (visible && interfaceItem) {
13
+ let responses = Array.isArray(interfaceItem.responses) ? interfaceItem.responses : [];
14
+ let activeResponseId = interfaceItem.activeResponseId || (responses[0] && responses[0].id) || '';
15
+ form.setFieldsValue({ responses, activeResponseId });
16
+ }
17
+ }, [visible, interfaceItem]);
18
+
19
+ const handlePreview = () => {
20
+ const values = form.getFieldsValue(true);
21
+ const responses = Array.isArray(values.responses) ? values.responses : [];
22
+ const activeResponse = responses.find(r => r.id === values.activeResponseId) || responses[0];
23
+ if (!activeResponse) {
24
+ message.error('未找到有效的响应内容');
25
+ return;
26
+ }
27
+ const contentType = 'application/json; charset=utf-8';
28
+ const formatted = formatResponseContent(activeResponse.content || '', contentType);
29
+ setPreviewContent({ title: `预览: ${activeResponse.name || '未命名响应'}`, content: formatted, contentType, description: '' });
30
+ setPreviewVisible(true);
31
+ };
32
+
33
+ const handleOk = async () => {
34
+ try {
35
+ await form.validateFields();
36
+ const values = form.getFieldsValue(true);
37
+ const payload = {
38
+ responses: Array.isArray(values.responses) ? values.responses : [],
39
+ activeResponseId: values.activeResponseId || (values.responses && values.responses[0] && values.responses[0].id) || ''
40
+ };
41
+ if (!interfaceItem || !interfaceItem.id) {
42
+ message.error('缺少接口ID,无法保存');
43
+ return;
44
+ }
45
+ const res = await axios.patch(`/cgi-bin/interfaces/${interfaceItem.id}`, payload);
46
+ if (res.data && res.data.code === 0) {
47
+ message.success('保存成功');
48
+ await refreshCacheAfterUpdate();
49
+ if (onSaved) onSaved();
50
+ if (onCancel) onCancel();
51
+ } else {
52
+ throw new Error(res.data?.message || '保存失败');
53
+ }
54
+ } catch (err) {
55
+ if (err && err.errorFields) return; // 表单校验错误
56
+ console.error('保存失败', err);
57
+ message.error(err.response?.data?.message || err.message || '保存失败');
58
+ }
59
+ };
60
+
61
+ return (
62
+ <>
63
+ <Modal
64
+ title="数据管理"
65
+ open={visible}
66
+ onOk={handleOk}
67
+ onCancel={onCancel}
68
+ width={900}
69
+ destroyOnClose
70
+ okText="保存"
71
+ cancelText="取消"
72
+ bodyStyle={{ maxHeight: '70vh', overflow: 'auto', padding: '24px' }}
73
+ >
74
+ <Form form={form} layout="vertical" initialValues={{ responses: [], activeResponseId: '' }}>
75
+ <Form.Item name="responses" hidden>
76
+ <Input />
77
+ </Form.Item>
78
+ <Form.Item name="activeResponseId" hidden>
79
+ <Input />
80
+ </Form.Item>
81
+ <Form.Item noStyle shouldUpdate>
82
+ {({ getFieldValue }) => {
83
+ const responses = getFieldValue('responses') || [];
84
+ const activeResponseId = getFieldValue('activeResponseId') || (responses[0] && responses[0].id) || '';
85
+ return (
86
+ <ResponseContentEditor
87
+ form={form}
88
+ responses={responses}
89
+ activeResponseId={activeResponseId}
90
+ onPreview={handlePreview}
91
+ enableSorting={true}
92
+ />
93
+ );
94
+ }}
95
+ </Form.Item>
96
+ </Form>
97
+ </Modal>
98
+
99
+ <PreviewModal
100
+ visible={previewVisible}
101
+ onCancel={() => setPreviewVisible(false)}
102
+ previewContent={previewContent}
103
+ />
104
+ </>
105
+ );
106
+ };
107
+
108
+ export default DataManagementModal;
109
+
110
+
@@ -1,4 +1,5 @@
1
1
  import React, { useState, useEffect } from 'react';
2
+ import { ReactSortable } from 'react-sortablejs';
2
3
  import ReactDOM from 'react-dom';
3
4
  import { Card, Button, Space, Badge, Row, Col, Input, Select, Tooltip, message, AutoComplete } from 'antd';
4
5
  import {
@@ -22,7 +23,8 @@ const ResponseContentEditor = ({
22
23
  form,
23
24
  responses,
24
25
  activeResponseId,
25
- onPreview
26
+ onPreview,
27
+ enableSorting = false
26
28
  }) => {
27
29
  const { getFieldValue, setFieldsValue } = form;
28
30
  const [isEditing, setIsEditing] = useState(false);
@@ -32,6 +34,20 @@ const ResponseContentEditor = ({
32
34
  // 查找激活的响应
33
35
  const activeResponseIndex = responses.findIndex(r => r.id === activeResponseId);
34
36
  const activeResponse = activeResponseIndex >= 0 ? responses[activeResponseIndex] : null;
37
+ // 拖拽排序:基于 ReactSortable 管理一个本地列表镜像
38
+ const [sortableList, setSortableList] = useState(() => {
39
+ return Array.isArray(responses) ? responses.map(r => ({ ...r })) : [];
40
+ });
41
+
42
+ useEffect(() => {
43
+ setSortableList(Array.isArray(responses) ? responses.map(r => ({ ...r })) : []);
44
+ }, [responses]);
45
+
46
+ const handleAfterSort = (newList) => {
47
+ // 将排序后的列表写回表单 responses 字段;activeResponseId 不变
48
+ setFieldsValue({ responses: newList });
49
+ };
50
+
35
51
 
36
52
  // 全屏切换函数
37
53
  const toggleTextareaFullscreen = () => {
@@ -313,6 +329,48 @@ const ResponseContentEditor = ({
313
329
  style={{ marginBottom: 16 }}
314
330
  bodyStyle={{ padding: '16px' }}
315
331
  >
332
+ {enableSorting && Array.isArray(sortableList) && sortableList.length > 1 && (
333
+ <div style={{ marginBottom: '16px' }}>
334
+ <div style={{ marginBottom: '8px' }}>
335
+ <label style={{ fontSize: '12px', color: '#666', fontWeight: 'bold' }}>响应排序(拖拽调整顺序)</label>
336
+ </div>
337
+ <ReactSortable
338
+ list={sortableList}
339
+ setList={(newState) => {
340
+ setSortableList(newState);
341
+ handleAfterSort(newState);
342
+ }}
343
+ animation={150}
344
+ handle={undefined}
345
+ ghostClass="sortable-ghost"
346
+ >
347
+ {sortableList.map((item) => (
348
+ <div
349
+ key={item.id}
350
+ style={{
351
+ display: 'flex',
352
+ alignItems: 'center',
353
+ justifyContent: 'space-between',
354
+ padding: '6px 10px',
355
+ border: '1px solid #f0f0f0',
356
+ borderRadius: '4px',
357
+ marginBottom: '6px',
358
+ background: item.id === activeResponseId ? '#e6f7ff' : '#fff',
359
+ cursor: 'move'
360
+ }}
361
+ onClick={() => handleResponseSelect(item.id)}
362
+ title="拖拽以排序,点击选择"
363
+ >
364
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
365
+ <Badge status={item.id === activeResponseId ? 'processing' : 'default'} />
366
+ <span style={{ fontWeight: 500 }}>{item.name || '未命名响应'}</span>
367
+ </div>
368
+ <div style={{ color: '#999', fontSize: 12 }}>拖拽</div>
369
+ </div>
370
+ ))}
371
+ </ReactSortable>
372
+ </div>
373
+ )}
316
374
  {/* 响应选择和名称编辑合并区域 */}
317
375
  <div style={{ marginBottom: '16px' }}>
318
376
  <Row gutter={12} align="middle">
@@ -26,6 +26,7 @@ import {
26
26
  formatResponseContent,
27
27
  generateResponseId
28
28
  } from '../components/interface-management';
29
+ import DataManagementModal from '../components/interface-management/DataManagementModal';
29
30
 
30
31
  const { Option } = Select;
31
32
  const { Search } = AntInput;
@@ -69,6 +70,8 @@ const InterfaceManagement = () => {
69
70
  const [modalVisible, setModalVisible] = useState(false);
70
71
  const [previewVisible, setPreviewVisible] = useState(false);
71
72
  const [isDuplicateMode, setIsDuplicateMode] = useState(false);
73
+ const [dataModalVisible, setDataModalVisible] = useState(false);
74
+ const [dataModalRecord, setDataModalRecord] = useState(null);
72
75
 
73
76
  // 列配置状态
74
77
  const [columnConfigVisible, setColumnConfigVisible] = useState(false);
@@ -1001,6 +1004,15 @@ const InterfaceManagement = () => {
1001
1004
  icon={<EditOutlined />}
1002
1005
  onClick={() => handleEditInterface(record)}
1003
1006
  />
1007
+ {record.proxyType === 'response' && (
1008
+ <Tooltip title="数据管理">
1009
+ <Button
1010
+ type="text"
1011
+ icon={<FileTextOutlined />}
1012
+ onClick={() => { setDataModalRecord(record); setDataModalVisible(true); }}
1013
+ />
1014
+ </Tooltip>
1015
+ )}
1004
1016
  <Tooltip title="复制新增">
1005
1017
  <Button
1006
1018
  type="text"
@@ -1833,6 +1845,16 @@ const InterfaceManagement = () => {
1833
1845
  onCancel={() => setPreviewVisible(false)}
1834
1846
  previewContent={previewContent}
1835
1847
  />
1848
+
1849
+ {/* 数据管理弹窗 */}
1850
+ <DataManagementModal
1851
+ visible={dataModalVisible}
1852
+ onCancel={() => { setDataModalVisible(false); setDataModalRecord(null); }}
1853
+ interfaceItem={dataModalRecord}
1854
+ onSaved={() => {
1855
+ fetchInterfaces();
1856
+ }}
1857
+ />
1836
1858
  </div>
1837
1859
  </AppLayout>
1838
1860
  );
@@ -32,6 +32,7 @@ import {
32
32
  formatResponseContent,
33
33
  generateResponseId
34
34
  } from '../components/interface-management';
35
+ import DataManagementModal from '../components/interface-management/DataManagementModal';
35
36
 
36
37
  const { Option } = Select;
37
38
  const { Search } = AntInput;
@@ -75,6 +76,8 @@ const InterfaceManagement = () => {
75
76
  const [modalVisible, setModalVisible] = useState(false);
76
77
  const [previewVisible, setPreviewVisible] = useState(false);
77
78
  const [isDuplicateMode, setIsDuplicateMode] = useState(false);
79
+ const [dataModalVisible, setDataModalVisible] = useState(false);
80
+ const [dataModalRecord, setDataModalRecord] = useState(null);
78
81
 
79
82
  // 列配置状态
80
83
  const [columnConfigVisible, setColumnConfigVisible] = useState(false);
@@ -1007,6 +1010,15 @@ const InterfaceManagement = () => {
1007
1010
  icon={<EditOutlined />}
1008
1011
  onClick={() => handleEditInterface(record)}
1009
1012
  />
1013
+ {record.proxyType === 'response' && (
1014
+ <Tooltip title="数据管理">
1015
+ <Button
1016
+ type="text"
1017
+ icon={<FileTextOutlined />}
1018
+ onClick={() => { setDataModalRecord(record); setDataModalVisible(true); }}
1019
+ />
1020
+ </Tooltip>
1021
+ )}
1010
1022
  <Tooltip title="复制新增">
1011
1023
  <Button
1012
1024
  type="text"
@@ -1926,6 +1938,16 @@ const InterfaceManagement = () => {
1926
1938
  onCancel={() => setPreviewVisible(false)}
1927
1939
  previewContent={previewContent}
1928
1940
  />
1941
+
1942
+ {/* 数据管理弹窗 */}
1943
+ <DataManagementModal
1944
+ visible={dataModalVisible}
1945
+ onCancel={() => { setDataModalVisible(false); setDataModalRecord(null); }}
1946
+ interfaceItem={dataModalRecord}
1947
+ onSaved={() => {
1948
+ fetchInterfaces();
1949
+ }}
1950
+ />
1929
1951
  </div>
1930
1952
  </AppLayout>
1931
1953
  );