whistle.mock-plugins 1.2.0 → 1.2.1-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.
@@ -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=) */
@@ -137,6 +150,8 @@
137
150
 
138
151
  /**![menu-unfold](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTQwOCA0NDJoNDgwYzQuNCAwIDgtMy42IDgtOHYtNTZjMC00LjQtMy42LTgtOC04SDQwOGMtNC40IDAtOCAzLjYtOCA4djU2YzAgNC40IDMuNiA4IDggOHptLTggMjA0YzAgNC40IDMuNiA4IDggOGg0ODBjNC40IDAgOC0zLjYgOC04di01NmMwLTQuNC0zLjYtOC04LThINDA4Yy00LjQgMC04IDMuNi04IDh2NTZ6bTUwNC00ODZIMTIwYy00LjQgMC04IDMuNi04IDh2NTZjMCA0LjQgMy42IDggOCA4aDc4NGM0LjQgMCA4LTMuNiA4LTh2LTU2YzAtNC40LTMuNi04LTgtOHptMCA2MzJIMTIwYy00LjQgMC04IDMuNi04IDh2NTZjMCA0LjQgMy42IDggOCA4aDc4NGM0LjQgMCA4LTMuNiA4LTh2LTU2YzAtNC40LTMuNi04LTgtOHpNMTQyLjQgNjQyLjFMMjk4LjcgNTE5YTguODQgOC44NCAwIDAwMC0xMy45TDE0Mi40IDM4MS45Yy01LjgtNC42LTE0LjQtLjUtMTQuNCA2Ljl2MjQ2LjNhOC45IDguOSAwIDAwMTQuNCA3eiIgLz48L3N2Zz4=) */
139
152
 
153
+ /**![menu](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTkwNCAxNjBIMTIwYy00LjQgMC04IDMuNi04IDh2NjRjMCA0LjQgMy42IDggOCA4aDc4NGM0LjQgMCA4LTMuNiA4LTh2LTY0YzAtNC40LTMuNi04LTgtOHptMCA2MjRIMTIwYy00LjQgMC04IDMuNi04IDh2NjRjMCA0LjQgMy42IDggOCA4aDc4NGM0LjQgMCA4LTMuNiA4LTh2LTY0YzAtNC40LTMuNi04LTgtOHptMC0zMTJIMTIwYy00LjQgMC04IDMuNi04IDh2NjRjMCA0LjQgMy42IDggOCA4aDc4NGM0LjQgMCA4LTMuNiA4LTh2LTY0YzAtNC40LTMuNi04LTgtOHoiIC8+PC9zdmc+) */
154
+
140
155
  /**![minus-square](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTMyOCA1NDRoMzY4YzQuNCAwIDgtMy42IDgtOHYtNDhjMC00LjQtMy42LTgtOC04SDMyOGMtNC40IDAtOCAzLjYtOCA4djQ4YzAgNC40IDMuNiA4IDggOHoiIC8+PHBhdGggZD0iTTg4MCAxMTJIMTQ0Yy0xNy43IDAtMzIgMTQuMy0zMiAzMnY3MzZjMCAxNy43IDE0LjMgMzIgMzIgMzJoNzM2YzE3LjcgMCAzMi0xNC4zIDMyLTMyVjE0NGMwLTE3LjctMTQuMy0zMi0zMi0zMnptLTQwIDcyOEgxODRWMTg0aDY1NnY2NTZ6IiAvPjwvc3ZnPg==) */
141
156
 
142
157
  /**![plus-circle](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTY5NiA0ODBINTQ0VjMyOGMwLTQuNC0zLjYtOC04LThoLTQ4Yy00LjQgMC04IDMuNi04IDh2MTUySDMyOGMtNC40IDAtOCAzLjYtOCA4djQ4YzAgNC40IDMuNiA4IDggOGgxNTJ2MTUyYzAgNC40IDMuNiA4IDggOGg0OGM0LjQgMCA4LTMuNiA4LThWNTQ0aDE1MmM0LjQgMCA4LTMuNiA4LTh2LTQ4YzAtNC40LTMuNi04LTgtOHoiIC8+PHBhdGggZD0iTTUxMiA2NEMyNjQuNiA2NCA2NCAyNjQuNiA2NCA1MTJzMjAwLjYgNDQ4IDQ0OCA0NDggNDQ4LTIwMC42IDQ0OC00NDhTNzU5LjQgNjQgNTEyIDY0em0wIDgyMGMtMjA1LjQgMC0zNzItMTY2LjYtMzcyLTM3MnMxNjYuNi0zNzIgMzcyLTM3MiAzNzIgMTY2LjYgMzcyIDM3Mi0xNjYuNiAzNzItMzcyIDM3MnoiIC8+PC9zdmc+) */
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.2",
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={1040}
69
+ destroyOnClose
70
+ okText="保存"
71
+ cancelText="取消"
72
+ bodyStyle={{ maxHeight: '80vh', 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 {
@@ -11,7 +12,8 @@ import {
11
12
  CheckCircleOutlined,
12
13
  EditOutlined,
13
14
  FullscreenOutlined,
14
- FullscreenExitOutlined
15
+ FullscreenExitOutlined,
16
+ MenuOutlined
15
17
  } from '@ant-design/icons';
16
18
  import { generateResponseId } from './utils';
17
19
 
@@ -22,16 +24,32 @@ const ResponseContentEditor = ({
22
24
  form,
23
25
  responses,
24
26
  activeResponseId,
25
- onPreview
27
+ onPreview,
28
+ enableSorting = false
26
29
  }) => {
27
30
  const { getFieldValue, setFieldsValue } = form;
28
31
  const [isEditing, setIsEditing] = useState(false);
29
32
  const [editingName, setEditingName] = useState('');
30
33
  const [isTextareaFullscreen, setIsTextareaFullscreen] = useState(false);
34
+ const [showSorting, setShowSorting] = useState(false);
31
35
 
32
36
  // 查找激活的响应
33
37
  const activeResponseIndex = responses.findIndex(r => r.id === activeResponseId);
34
38
  const activeResponse = activeResponseIndex >= 0 ? responses[activeResponseIndex] : null;
39
+ // 拖拽排序:基于 ReactSortable 管理一个本地列表镜像
40
+ const [sortableList, setSortableList] = useState(() => {
41
+ return Array.isArray(responses) ? responses.map(r => ({ ...r })) : [];
42
+ });
43
+
44
+ useEffect(() => {
45
+ setSortableList(Array.isArray(responses) ? responses.map(r => ({ ...r })) : []);
46
+ }, [responses]);
47
+
48
+ const handleAfterSort = (newList) => {
49
+ // 将排序后的列表写回表单 responses 字段;activeResponseId 不变
50
+ setFieldsValue({ responses: newList });
51
+ };
52
+
35
53
 
36
54
  // 全屏切换函数
37
55
  const toggleTextareaFullscreen = () => {
@@ -263,6 +281,17 @@ const ResponseContentEditor = ({
263
281
  }
264
282
  extra={
265
283
  <Space size="small">
284
+ {enableSorting && (
285
+ <Button
286
+ type="default"
287
+ icon={<MenuOutlined />}
288
+ onClick={() => setShowSorting(!showSorting)}
289
+ size="small"
290
+ style={{ display: 'flex', alignItems: 'center', gap: '4px' }}
291
+ >
292
+ 排序
293
+ </Button>
294
+ )}
266
295
  <Button
267
296
  type="default"
268
297
  icon={<PlusCircleOutlined />}
@@ -313,6 +342,31 @@ const ResponseContentEditor = ({
313
342
  style={{ marginBottom: 16 }}
314
343
  bodyStyle={{ padding: '16px' }}
315
344
  >
345
+ {enableSorting && showSorting && Array.isArray(sortableList) && sortableList.length > 1 && (
346
+ <div style={{ marginBottom: 12 }}>
347
+ <ReactSortable
348
+ list={sortableList}
349
+ setList={(newState) => {
350
+ setSortableList(newState);
351
+ handleAfterSort(newState);
352
+ }}
353
+ animation={150}
354
+ ghostClass="sortable-ghost"
355
+ style={{ display: 'flex', gap: 8, overflowX: 'auto', paddingBottom: 4 }}
356
+ >
357
+ {sortableList.map((item) => (
358
+ <div
359
+ key={item.id}
360
+ className={`sortable-chip${item.id === activeResponseId ? ' sortable-chip-active' : ''}`}
361
+ onClick={() => handleResponseSelect(item.id)}
362
+ title={item.name || '未命名响应'}
363
+ >
364
+ {item.name || '未命名响应'}
365
+ </div>
366
+ ))}
367
+ </ReactSortable>
368
+ </div>
369
+ )}
316
370
  {/* 响应选择和名称编辑合并区域 */}
317
371
  <div style={{ marginBottom: '16px' }}>
318
372
  <Row gutter={12} align="middle">
@@ -445,7 +499,7 @@ const ResponseContentEditor = ({
445
499
 
446
500
  <div style={{ position: 'relative' }}>
447
501
  <TextArea
448
- rows={12}
502
+ rows={18}
449
503
  placeholder="请输入响应内容..."
450
504
  style={{
451
505
  fontFamily: 'Monaco, Menlo, "Ubuntu Mono", monospace',
@@ -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
  );
@@ -1144,6 +1144,37 @@
1144
1144
  background: #e6f4ff;
1145
1145
  }
1146
1146
 
1147
+ /* 紧凑排序 Chips */
1148
+ .sortable-chip {
1149
+ display: inline-flex;
1150
+ align-items: center;
1151
+ padding: 2px 10px;
1152
+ height: 24px;
1153
+ border: 1px solid #e6e6e6;
1154
+ border-radius: 12px;
1155
+ background: #fff;
1156
+ font-size: 12px;
1157
+ color: #595959;
1158
+ cursor: move;
1159
+ user-select: none;
1160
+ white-space: nowrap;
1161
+ }
1162
+
1163
+ .sortable-chip:hover {
1164
+ border-color: #bfbfbf;
1165
+ box-shadow: 0 1px 2px rgba(0,0,0,0.06);
1166
+ }
1167
+
1168
+ .sortable-chip-active {
1169
+ background: #e6f7ff;
1170
+ border-color: #91d5ff;
1171
+ color: #1677ff;
1172
+ }
1173
+
1174
+ .sortable-ghost {
1175
+ opacity: 0.6;
1176
+ }
1177
+
1147
1178
  /* 接口弹窗样式 */
1148
1179
  .interface-modal .ant-modal-header {
1149
1180
  border-bottom: 1px solid #f0f0f0;