whistle.mock-plugins 1.2.2-beta.2 → 1.2.2-beta.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whistle.mock-plugins",
3
- "version": "1.2.2-beta.2",
3
+ "version": "1.2.2-beta.4",
4
4
  "description": "Whistle 插件,用于快速创建 API 模拟数据",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -0,0 +1,115 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Drawer, Form, Input, message, Button, Space } from 'antd';
3
+ import axios from 'axios';
4
+ import { ResponseContentEditor, PreviewModal, formatResponseContent, refreshCacheAfterUpdate } from '.';
5
+
6
+ const DataManagementDrawer = ({ 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
+ <Drawer
64
+ title="数据管理"
65
+ placement="right"
66
+ width={1040}
67
+ open={visible}
68
+ onClose={onCancel}
69
+ destroyOnClose
70
+ bodyStyle={{ paddingBottom: 80 }}
71
+ footer={
72
+ <div style={{ textAlign: 'right' }}>
73
+ <Space>
74
+ <Button onClick={onCancel}>取消</Button>
75
+ <Button type="primary" onClick={handleOk}>保存</Button>
76
+ </Space>
77
+ </div>
78
+ }
79
+ >
80
+ <Form form={form} layout="vertical" initialValues={{ responses: [], activeResponseId: '' }}>
81
+ <Form.Item name="responses" hidden>
82
+ <Input />
83
+ </Form.Item>
84
+ <Form.Item name="activeResponseId" hidden>
85
+ <Input />
86
+ </Form.Item>
87
+ <Form.Item noStyle shouldUpdate>
88
+ {({ getFieldValue }) => {
89
+ const responses = getFieldValue('responses') || [];
90
+ const activeResponseId = getFieldValue('activeResponseId') || (responses[0] && responses[0].id) || '';
91
+ return (
92
+ <ResponseContentEditor
93
+ form={form}
94
+ responses={responses}
95
+ activeResponseId={activeResponseId}
96
+ onPreview={handlePreview}
97
+ enableSorting={true}
98
+ />
99
+ );
100
+ }}
101
+ </Form.Item>
102
+ </Form>
103
+ </Drawer>
104
+
105
+ <PreviewModal
106
+ visible={previewVisible}
107
+ onCancel={() => setPreviewVisible(false)}
108
+ previewContent={previewContent}
109
+ />
110
+ </>
111
+ );
112
+ };
113
+
114
+ export default DataManagementDrawer;
115
+
@@ -2,5 +2,6 @@
2
2
  export { default as ResponseContentEditor } from './ResponseContentEditor';
3
3
  export { default as InterfaceTestModal } from './InterfaceTestModal';
4
4
  export { default as PreviewModal } from './PreviewModal';
5
+ export { default as DataManagementDrawer } from './DataManagementDrawer';
5
6
  export * from './constants';
6
7
  export * from './utils';
@@ -32,6 +32,7 @@ import {
32
32
  generateResponseId
33
33
  } from '../components/interface-management';
34
34
  import DataManagementModal from '../components/interface-management/DataManagementModal';
35
+ import { DataManagementDrawer } from '../components/interface-management';
35
36
 
36
37
  const { Option } = Select;
37
38
  const { Search } = AntInput;
@@ -91,6 +92,9 @@ const InterfaceManagement = () => {
91
92
  const [selectedRowKeys, setSelectedRowKeys] = useState([]);
92
93
  const [drawerVisible, setDrawerVisible] = useState(false);
93
94
 
95
+ // 数据管理显示模式
96
+ const [displayMode, setDisplayMode] = useState('modal');
97
+
94
98
  // 表单实例
95
99
  const [form] = Form.useForm();
96
100
 
@@ -138,6 +142,29 @@ const InterfaceManagement = () => {
138
142
  }
139
143
  }, [selectedFeatureId]);
140
144
 
145
+ // 监听数据管理显示模式变化
146
+ useEffect(() => {
147
+ // 初始化时从 localStorage 读取配置
148
+ const savedMode = localStorage.getItem('dataManagementDisplayMode');
149
+ if (savedMode) {
150
+ setDisplayMode(savedMode);
151
+ }
152
+
153
+ // 监听 storage 事件,实现设置立即生效
154
+ const handleStorageChange = () => {
155
+ const newMode = localStorage.getItem('dataManagementDisplayMode');
156
+ if (newMode) {
157
+ setDisplayMode(newMode);
158
+ }
159
+ };
160
+
161
+ window.addEventListener('storage', handleStorageChange);
162
+
163
+ return () => {
164
+ window.removeEventListener('storage', handleStorageChange);
165
+ };
166
+ }, []);
167
+
141
168
  // 获取当前选中的功能模块
142
169
  const selectedFeature = features.find(f => f.id === selectedFeatureId);
143
170
 
@@ -693,7 +720,17 @@ const InterfaceManagement = () => {
693
720
  saveTableConfig({
694
721
  visibleColumns: defaultColumns,
695
722
  columnWidths: {
696
- currentResponse: 180
723
+ active: 80,
724
+ name: 150,
725
+ group: 120,
726
+ urlPattern: 250,
727
+ proxyType: 120,
728
+ currentResponse: 180,
729
+ responseDelay: 100,
730
+ httpStatus: 100,
731
+ contentType: 120,
732
+ targetUrl: 200,
733
+ customHeaders: 100
697
734
  }
698
735
  });
699
736
  };
@@ -740,11 +777,21 @@ const InterfaceManagement = () => {
740
777
 
741
778
  const startX = e.clientX;
742
779
  const startWidth = width;
780
+ let currentWidth = startWidth;
743
781
 
744
782
  const handleMouseMove = (moveEvent) => {
745
783
  const deltaX = moveEvent.clientX - startX;
746
- const newWidth = Math.max(50, startWidth + deltaX); // 最小宽度 50px
747
- onResize(columnKey, newWidth);
784
+ currentWidth = Math.max(50, startWidth + deltaX); // 最小宽度 50px
785
+
786
+ // 实时更新列宽(不保存到 localStorage)
787
+ const newColumnWidths = {
788
+ ...tableConfig.columnWidths,
789
+ [columnKey]: currentWidth
790
+ };
791
+ setTableConfig(prev => ({
792
+ ...prev,
793
+ columnWidths: newColumnWidths
794
+ }));
748
795
  };
749
796
 
750
797
  const handleMouseUp = () => {
@@ -752,6 +799,9 @@ const InterfaceManagement = () => {
752
799
  document.removeEventListener('mouseup', handleMouseUp);
753
800
  document.body.style.cursor = '';
754
801
  document.body.style.userSelect = '';
802
+
803
+ // 拖动结束后保存到 localStorage
804
+ onResize(columnKey, currentWidth);
755
805
  };
756
806
 
757
807
  document.addEventListener('mousemove', handleMouseMove);
@@ -815,7 +865,12 @@ const InterfaceManagement = () => {
815
865
  title: '状态',
816
866
  dataIndex: 'active',
817
867
  key: 'active',
818
- width: 80,
868
+ width: tableConfig.columnWidths?.active || 80,
869
+ onHeaderCell: (column) => ({
870
+ width: column.width,
871
+ columnKey: 'active',
872
+ onResize: handleResize,
873
+ }),
819
874
  render: (active, record) => (
820
875
  <Switch
821
876
  checked={active !== false}
@@ -828,6 +883,12 @@ const InterfaceManagement = () => {
828
883
  title: '名称',
829
884
  dataIndex: 'name',
830
885
  key: 'name',
886
+ width: tableConfig.columnWidths?.name || 150,
887
+ onHeaderCell: (column) => ({
888
+ width: column.width,
889
+ columnKey: 'name',
890
+ onResize: handleResize,
891
+ }),
831
892
  sorter: (a, b) => a.name.localeCompare(b.name),
832
893
  render: (text) => <span className="interface-name">{text}</span>,
833
894
  },
@@ -835,7 +896,12 @@ const InterfaceManagement = () => {
835
896
  title: '分组',
836
897
  dataIndex: 'group',
837
898
  key: 'group',
838
- width: 120,
899
+ width: tableConfig.columnWidths?.group || 120,
900
+ onHeaderCell: (column) => ({
901
+ width: column.width,
902
+ columnKey: 'group',
903
+ onResize: handleResize,
904
+ }),
839
905
  sorter: (a, b) => {
840
906
  const groupA = a.group || '';
841
907
  const groupB = b.group || '';
@@ -856,6 +922,12 @@ const InterfaceManagement = () => {
856
922
  title: 'URL匹配规则',
857
923
  dataIndex: 'urlPattern',
858
924
  key: 'urlPattern',
925
+ width: tableConfig.columnWidths?.urlPattern || 250,
926
+ onHeaderCell: (column) => ({
927
+ width: column.width,
928
+ columnKey: 'urlPattern',
929
+ onResize: handleResize,
930
+ }),
859
931
  ellipsis: true,
860
932
  render: (text) => <span className="url-pattern">{text}</span>,
861
933
  },
@@ -863,7 +935,12 @@ const InterfaceManagement = () => {
863
935
  title: '处理方式',
864
936
  dataIndex: 'proxyType',
865
937
  key: 'proxyType',
866
- width: 120,
938
+ width: tableConfig.columnWidths?.proxyType || 120,
939
+ onHeaderCell: (column) => ({
940
+ width: column.width,
941
+ columnKey: 'proxyType',
942
+ onResize: handleResize,
943
+ }),
867
944
  render: (text) => {
868
945
  const proxyType = proxyTypes.find(item => item.value === text);
869
946
  return (
@@ -879,11 +956,15 @@ const InterfaceManagement = () => {
879
956
  dataIndex: 'activeResponseId',
880
957
  key: 'currentResponse',
881
958
  width: tableConfig.columnWidths?.currentResponse || 180,
959
+ className: 'current-response-column',
882
960
  onHeaderCell: (column) => ({
883
961
  width: column.width,
884
962
  columnKey: 'currentResponse',
885
963
  onResize: handleResize,
886
964
  }),
965
+ onCell: () => ({
966
+ className: 'current-response-cell',
967
+ }),
887
968
  render: (activeResponseId, record) => {
888
969
  // 如果没有响应数据,返回空
889
970
  if (!record.responses || !Array.isArray(record.responses) || record.responses.length === 0) {
@@ -917,7 +998,12 @@ const InterfaceManagement = () => {
917
998
  title: '延迟(毫秒)',
918
999
  dataIndex: 'responseDelay',
919
1000
  key: 'responseDelay',
920
- width: 100,
1001
+ width: tableConfig.columnWidths?.responseDelay || 100,
1002
+ onHeaderCell: (column) => ({
1003
+ width: column.width,
1004
+ columnKey: 'responseDelay',
1005
+ onResize: handleResize,
1006
+ }),
921
1007
  sorter: (a, b) => (a.responseDelay || 0) - (b.responseDelay || 0),
922
1008
  render: (delay) => {
923
1009
  const delayValue = parseInt(delay, 10) || 0;
@@ -932,7 +1018,12 @@ const InterfaceManagement = () => {
932
1018
  title: '状态码',
933
1019
  dataIndex: 'httpStatus',
934
1020
  key: 'httpStatus',
935
- width: 100,
1021
+ width: tableConfig.columnWidths?.httpStatus || 100,
1022
+ onHeaderCell: (column) => ({
1023
+ width: column.width,
1024
+ columnKey: 'httpStatus',
1025
+ onResize: handleResize,
1026
+ }),
936
1027
  render: (status) => {
937
1028
  const statusCode = status || 200;
938
1029
  let statusClass = 'status-success';
@@ -948,7 +1039,12 @@ const InterfaceManagement = () => {
948
1039
  title: '内容类型',
949
1040
  dataIndex: 'contentType',
950
1041
  key: 'contentType',
951
- width: 120,
1042
+ width: tableConfig.columnWidths?.contentType || 120,
1043
+ onHeaderCell: (column) => ({
1044
+ width: column.width,
1045
+ columnKey: 'contentType',
1046
+ onResize: handleResize,
1047
+ }),
952
1048
  sorter: (a, b) => {
953
1049
  const aType = a.contentType || '';
954
1050
  const bType = b.contentType || '';
@@ -967,6 +1063,12 @@ const InterfaceManagement = () => {
967
1063
  title: '目标URL',
968
1064
  dataIndex: 'targetUrl',
969
1065
  key: 'targetUrl',
1066
+ width: tableConfig.columnWidths?.targetUrl || 200,
1067
+ onHeaderCell: (column) => ({
1068
+ width: column.width,
1069
+ columnKey: 'targetUrl',
1070
+ onResize: handleResize,
1071
+ }),
970
1072
  ellipsis: true,
971
1073
  sorter: (a, b) => {
972
1074
  const aUrl = a.targetUrl || '';
@@ -982,7 +1084,12 @@ const InterfaceManagement = () => {
982
1084
  title: '自定义头',
983
1085
  dataIndex: 'customHeaders',
984
1086
  key: 'customHeaders',
985
- width: 100,
1087
+ width: tableConfig.columnWidths?.customHeaders || 100,
1088
+ onHeaderCell: (column) => ({
1089
+ width: column.width,
1090
+ columnKey: 'customHeaders',
1091
+ onResize: handleResize,
1092
+ }),
986
1093
  sorter: (a, b) => {
987
1094
  const aCount = Object.keys(a.customHeaders || {}).length;
988
1095
  const bCount = Object.keys(b.customHeaders || {}).length;
@@ -2008,15 +2115,26 @@ const InterfaceManagement = () => {
2008
2115
  previewContent={previewContent}
2009
2116
  />
2010
2117
 
2011
- {/* 数据管理弹窗 */}
2012
- <DataManagementModal
2013
- visible={dataModalVisible}
2014
- onCancel={() => { setDataModalVisible(false); setDataModalRecord(null); }}
2015
- interfaceItem={dataModalRecord}
2016
- onSaved={() => {
2017
- fetchInterfaces();
2018
- }}
2019
- />
2118
+ {/* 数据管理弹窗/抽屉 */}
2119
+ {displayMode === 'drawer' ? (
2120
+ <DataManagementDrawer
2121
+ visible={dataModalVisible}
2122
+ onCancel={() => { setDataModalVisible(false); setDataModalRecord(null); }}
2123
+ interfaceItem={dataModalRecord}
2124
+ onSaved={() => {
2125
+ fetchInterfaces();
2126
+ }}
2127
+ />
2128
+ ) : (
2129
+ <DataManagementModal
2130
+ visible={dataModalVisible}
2131
+ onCancel={() => { setDataModalVisible(false); setDataModalRecord(null); }}
2132
+ interfaceItem={dataModalRecord}
2133
+ onSaved={() => {
2134
+ fetchInterfaces();
2135
+ }}
2136
+ />
2137
+ )}
2020
2138
  </div>
2021
2139
  </AppLayout>
2022
2140
  );
@@ -1,11 +1,30 @@
1
- import React from 'react';
1
+ import React, { useState, useEffect } from 'react';
2
2
  import AppLayout from '../components/AppLayout';
3
- import { Card, Typography, Empty } from 'antd';
3
+ import { Card, Typography, Switch, Space, Divider } from 'antd';
4
4
  import '../styles/settings.css';
5
5
 
6
6
  const { Title, Text, Paragraph } = Typography;
7
7
 
8
8
  const Settings = () => {
9
+ const [displayMode, setDisplayMode] = useState('modal');
10
+
11
+ useEffect(() => {
12
+ // 从 localStorage 读取配置
13
+ const savedMode = localStorage.getItem('dataManagementDisplayMode');
14
+ if (savedMode) {
15
+ setDisplayMode(savedMode);
16
+ }
17
+ }, []);
18
+
19
+ const handleDisplayModeChange = (checked) => {
20
+ const newMode = checked ? 'drawer' : 'modal';
21
+ setDisplayMode(newMode);
22
+ localStorage.setItem('dataManagementDisplayMode', newMode);
23
+
24
+ // 触发 storage 事件,通知其他页面
25
+ window.dispatchEvent(new Event('storage'));
26
+ };
27
+
9
28
  return (
10
29
  <AppLayout>
11
30
  <div className="page-container">
@@ -19,10 +38,25 @@ const Settings = () => {
19
38
  </div>
20
39
 
21
40
  <Card title="界面设置" className="settings-card">
22
- <Empty
23
- description="暂无可配置项"
24
- style={{ padding: '40px 0' }}
25
- />
41
+ <div className="settings-item">
42
+ <div className="settings-item-header">
43
+ <div>
44
+ <div className="settings-item-title">数据管理交互</div>
45
+ <div className="settings-item-description">
46
+ 选择数据管理的显示方式
47
+ </div>
48
+ </div>
49
+ <Space>
50
+ <Text type="secondary">
51
+ {displayMode === 'drawer' ? '抽屉模式' : '弹窗模式'}
52
+ </Text>
53
+ <Switch
54
+ checked={displayMode === 'drawer'}
55
+ onChange={handleDisplayModeChange}
56
+ />
57
+ </Space>
58
+ </div>
59
+ </div>
26
60
  </Card>
27
61
  </div>
28
62
  </AppLayout>
@@ -294,20 +294,12 @@
294
294
  .interface-table td {
295
295
  padding: 12px 16px !important;
296
296
  white-space: nowrap;
297
- overflow: visible !important;
297
+ overflow: hidden;
298
298
  text-overflow: ellipsis;
299
299
  }
300
300
 
301
- /* 确保表格容器不裁剪下拉菜单 */
302
- .interface-list-container {
303
- overflow: visible !important;
304
- }
305
-
306
- .interface-list-container .ant-table-container {
307
- overflow: visible !important;
308
- }
309
-
310
- .interface-list-container .ant-table-body {
301
+ /* 当前响应列需要 overflow: visible 以显示完整的下拉菜单 */
302
+ .interface-table td.current-response-cell {
311
303
  overflow: visible !important;
312
304
  }
313
305
 
@@ -33,6 +33,35 @@
33
33
  font-weight: 500;
34
34
  }
35
35
 
36
+ /* 设置项样式 */
37
+ .settings-item {
38
+ padding: 16px 0;
39
+ border-bottom: 1px solid #f0f0f0;
40
+ }
41
+
42
+ .settings-item:last-child {
43
+ border-bottom: none;
44
+ }
45
+
46
+ .settings-item-header {
47
+ display: flex;
48
+ justify-content: space-between;
49
+ align-items: center;
50
+ }
51
+
52
+ .settings-item-title {
53
+ font-size: 14px;
54
+ font-weight: 500;
55
+ color: #262626;
56
+ margin-bottom: 4px;
57
+ }
58
+
59
+ .settings-item-description {
60
+ font-size: 12px;
61
+ color: #8c8c8c;
62
+ line-height: 1.5;
63
+ }
64
+
36
65
  .settings-section {
37
66
  margin-bottom: 32px;
38
67
  }