whistle.mock-plugins 1.0.0

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.
Files changed (68) hide show
  1. package/API.md +247 -0
  2. package/README.md +91 -0
  3. package/app/cgi-bin/features.js +285 -0
  4. package/app/cgi-bin/index.js +31 -0
  5. package/app/cgi-bin/interfaces.js +409 -0
  6. package/app/cgi-bin/logs.js +151 -0
  7. package/app/cgi-bin/test-interface.js +152 -0
  8. package/app/public/index.html +1 -0
  9. package/app/public/js/index.js +2 -0
  10. package/app/public/js/index.js.LICENSE.txt +150 -0
  11. package/app/util/index.js +24 -0
  12. package/index.html +52 -0
  13. package/initialize.js +32 -0
  14. package/lib/dataManager.js +500 -0
  15. package/lib/index.js +3 -0
  16. package/lib/models.js +64 -0
  17. package/lib/routes/features.js +213 -0
  18. package/lib/routes/interfaces.js +304 -0
  19. package/lib/routes/proxy.js +239 -0
  20. package/lib/ruleManager.js +551 -0
  21. package/lib/rules-server.js +267 -0
  22. package/lib/server.js +805 -0
  23. package/lib/storage.js +16 -0
  24. package/lib/ui-server.js +68 -0
  25. package/package.json +51 -0
  26. package/public/bundle.js +2 -0
  27. package/public/bundle.js.LICENSE.txt +38 -0
  28. package/public/index.html +1 -0
  29. package/public/plugin.mock-plugin.html +14 -0
  30. package/rules.txt +22 -0
  31. package/test/rulesServer.test.js +120 -0
  32. package/ui/src/App.css +173 -0
  33. package/ui/src/App.js +24 -0
  34. package/ui/src/components/AppLayout.js +149 -0
  35. package/ui/src/components/FeatureCard.js +53 -0
  36. package/ui/src/components/Layout.js +94 -0
  37. package/ui/src/components/MainNav.js +42 -0
  38. package/ui/src/components/PageContainer.js +16 -0
  39. package/ui/src/index.html +11 -0
  40. package/ui/src/index.js +43 -0
  41. package/ui/src/pages/FileProxy.js +235 -0
  42. package/ui/src/pages/FilesPage.js +292 -0
  43. package/ui/src/pages/Home.js +128 -0
  44. package/ui/src/pages/HomePage.js +195 -0
  45. package/ui/src/pages/InterfaceManagement.js +829 -0
  46. package/ui/src/pages/InterfacePage.js +342 -0
  47. package/ui/src/pages/MockData.js +472 -0
  48. package/ui/src/pages/Settings.js +192 -0
  49. package/ui/src/pages/SettingsPage.js +165 -0
  50. package/ui/src/pages/UrlRedirect.js +225 -0
  51. package/ui/src/pages/Welcome.js +155 -0
  52. package/ui/src/services/api.js +186 -0
  53. package/ui/src/styles/app-layout.css +337 -0
  54. package/ui/src/styles/feature-card.css +172 -0
  55. package/ui/src/styles/file-proxy.css +301 -0
  56. package/ui/src/styles/global.css +224 -0
  57. package/ui/src/styles/home.css +123 -0
  58. package/ui/src/styles/index.css +419 -0
  59. package/ui/src/styles/interface-management.css +694 -0
  60. package/ui/src/styles/layout.css +159 -0
  61. package/ui/src/styles/logs-page.css +427 -0
  62. package/ui/src/styles/main-nav.css +52 -0
  63. package/ui/src/styles/mock-data.css +850 -0
  64. package/ui/src/styles/page-container.css +11 -0
  65. package/ui/src/styles/settings.css +208 -0
  66. package/ui/src/styles/url-redirect.css +279 -0
  67. package/ui/src/styles/welcome.css +282 -0
  68. package/webpack.config.js +49 -0
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import { Link, useLocation } from 'react-router-dom';
3
+ import '../styles/main-nav.css';
4
+
5
+ const MainNav = () => {
6
+ const location = useLocation();
7
+ const pathname = location.pathname;
8
+
9
+ // 检查当前路径是否匹配导航项
10
+ const isActive = (path) => {
11
+ if (path === '/' && pathname === '/') return true;
12
+ if (path !== '/' && pathname.startsWith(path)) return true;
13
+ return false;
14
+ };
15
+
16
+ return (
17
+ <div className="main-nav">
18
+ <div className="main-header">
19
+ <h1>Whistle Mock 数据工厂</h1>
20
+ </div>
21
+ <nav className="nav-tabs">
22
+ <Link to="/" className={`nav-tab ${isActive('/') ? 'active' : ''}`}>
23
+ 首页
24
+ </Link>
25
+ <Link to="/mock" className={`nav-tab ${isActive('/mock') ? 'active' : ''}`}>
26
+ Mock数据
27
+ </Link>
28
+ <Link to="/file-proxy" className={`nav-tab ${isActive('/file-proxy') ? 'active' : ''}`}>
29
+ 接口配置
30
+ </Link>
31
+ <Link to="/url-redirect" className={`nav-tab ${isActive('/url-redirect') ? 'active' : ''}`}>
32
+ 规则管理
33
+ </Link>
34
+ <Link to="/settings" className={`nav-tab ${isActive('/settings') ? 'active' : ''}`}>
35
+ 设置
36
+ </Link>
37
+ </nav>
38
+ </div>
39
+ );
40
+ };
41
+
42
+ export default MainNav;
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import MainNav from './MainNav';
3
+ import '../styles/page-container.css';
4
+
5
+ const PageContainer = ({ children }) => {
6
+ return (
7
+ <div className="page-container">
8
+ <MainNav />
9
+ <div className="page-content">
10
+ {children}
11
+ </div>
12
+ </div>
13
+ );
14
+ };
15
+
16
+ export default PageContainer;
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Whistle Mock 数据工厂</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ </body>
11
+ </html>
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
4
+
5
+ // 导入样式
6
+ import 'antd/dist/reset.css'; // 导入antd样式,使用适合的版本
7
+ import './styles/global.css';
8
+ import './App.css';
9
+
10
+ // 导入页面组件
11
+ import MockData from './pages/MockData';
12
+ import InterfaceManagement from './pages/InterfaceManagement';
13
+ import Settings from './pages/Settings';
14
+
15
+ // 导入布局组件
16
+ import AppLayout from './components/AppLayout';
17
+
18
+ const App = () => {
19
+ return (
20
+ <Router>
21
+ <div className="App">
22
+ <Switch>
23
+ <Route exact path="/" component={MockData} />
24
+ <Route exact path="/interface" component={InterfaceManagement} />
25
+ <Route path="/interface/:featureId" component={InterfaceManagement} />
26
+ <Route path="/settings" component={Settings} />
27
+ <Redirect to="/" />
28
+ </Switch>
29
+ </div>
30
+ </Router>
31
+ );
32
+ };
33
+
34
+ // 确保使用正确的渲染方法,兼容React 18和旧版本
35
+ const root = document.getElementById('root');
36
+ if (ReactDOM.createRoot) {
37
+ // React 18+
38
+ const rootElement = ReactDOM.createRoot(root);
39
+ rootElement.render(<App />);
40
+ } else {
41
+ // 旧版本React
42
+ ReactDOM.render(<App />, root);
43
+ }
@@ -0,0 +1,235 @@
1
+ import React, { useState } from 'react';
2
+ import AppLayout from '../components/AppLayout';
3
+ import '../styles/file-proxy.css';
4
+
5
+ const FileProxy = () => {
6
+ const [fileProxies, setFileProxies] = useState([
7
+ {
8
+ id: 1,
9
+ localPath: '/path/to/local/file.json',
10
+ remotePath: '/api/data',
11
+ description: '用户数据接口',
12
+ active: true
13
+ },
14
+ {
15
+ id: 2,
16
+ localPath: '/path/to/local/orders.json',
17
+ remotePath: '/api/orders',
18
+ description: '订单数据接口',
19
+ active: false
20
+ },
21
+ {
22
+ id: 3,
23
+ localPath: '/path/to/local/products.json',
24
+ remotePath: '/api/products',
25
+ description: '商品数据接口',
26
+ active: true
27
+ }
28
+ ]);
29
+
30
+ const [isModalOpen, setIsModalOpen] = useState(false);
31
+ const [currentProxy, setCurrentProxy] = useState(null);
32
+ const [localPath, setLocalPath] = useState('');
33
+ const [remotePath, setRemotePath] = useState('');
34
+ const [description, setDescription] = useState('');
35
+
36
+ const openModal = (proxy = null) => {
37
+ if (proxy) {
38
+ setCurrentProxy(proxy);
39
+ setLocalPath(proxy.localPath);
40
+ setRemotePath(proxy.remotePath);
41
+ setDescription(proxy.description);
42
+ } else {
43
+ setCurrentProxy(null);
44
+ setLocalPath('');
45
+ setRemotePath('');
46
+ setDescription('');
47
+ }
48
+ setIsModalOpen(true);
49
+ };
50
+
51
+ const closeModal = () => {
52
+ setIsModalOpen(false);
53
+ };
54
+
55
+ const handleSubmit = (e) => {
56
+ e.preventDefault();
57
+
58
+ if (!localPath || !remotePath) {
59
+ alert('请填写本地文件路径和远程路径');
60
+ return;
61
+ }
62
+
63
+ if (currentProxy) {
64
+ // 编辑现有配置
65
+ setFileProxies(fileProxies.map(proxy =>
66
+ proxy.id === currentProxy.id
67
+ ? { ...proxy, localPath, remotePath, description }
68
+ : proxy
69
+ ));
70
+ } else {
71
+ // 添加新配置
72
+ const newProxy = {
73
+ id: fileProxies.length > 0 ? Math.max(...fileProxies.map(p => p.id)) + 1 : 1,
74
+ localPath,
75
+ remotePath,
76
+ description,
77
+ active: true
78
+ };
79
+ setFileProxies([...fileProxies, newProxy]);
80
+ }
81
+
82
+ closeModal();
83
+ };
84
+
85
+ const toggleActive = (id) => {
86
+ setFileProxies(fileProxies.map(proxy =>
87
+ proxy.id === id ? { ...proxy, active: !proxy.active } : proxy
88
+ ));
89
+ };
90
+
91
+ const deleteProxy = (id) => {
92
+ if (window.confirm('确定要删除此条配置吗?')) {
93
+ setFileProxies(fileProxies.filter(proxy => proxy.id !== id));
94
+ }
95
+ };
96
+
97
+ return (
98
+ <AppLayout>
99
+ <div className="file-proxy-container">
100
+ <div className="page-title-bar">
101
+ <div>
102
+ <h1 className="page-title">文件代理配置</h1>
103
+ <div className="page-description">
104
+ 配置文件代理规则,支持本地文件和远程文件
105
+ </div>
106
+ </div>
107
+ <div className="page-actions">
108
+ <button className="add-button" onClick={() => openModal()}>
109
+ 添加文件代理
110
+ </button>
111
+ </div>
112
+ </div>
113
+
114
+ <div className="proxy-list-container">
115
+ {fileProxies.length === 0 ? (
116
+ <div className="empty-data">
117
+ <div className="empty-icon">📁</div>
118
+ <div className="empty-text">暂无文件代理配置</div>
119
+ <button className="add-button" onClick={() => openModal()}>
120
+ 添加文件代理
121
+ </button>
122
+ </div>
123
+ ) : (
124
+ <table className="proxy-table">
125
+ <thead>
126
+ <tr>
127
+ <th>状态</th>
128
+ <th>本地文件路径</th>
129
+ <th>远程路径</th>
130
+ <th>描述</th>
131
+ <th>操作</th>
132
+ </tr>
133
+ </thead>
134
+ <tbody>
135
+ {fileProxies.map(proxy => (
136
+ <tr key={proxy.id} className={proxy.active ? '' : 'inactive'}>
137
+ <td>
138
+ <label className="switch">
139
+ <input
140
+ type="checkbox"
141
+ checked={proxy.active}
142
+ onChange={() => toggleActive(proxy.id)}
143
+ />
144
+ <span className="slider"></span>
145
+ </label>
146
+ </td>
147
+ <td className="local-path">{proxy.localPath}</td>
148
+ <td>{proxy.remotePath}</td>
149
+ <td>{proxy.description || '-'}</td>
150
+ <td>
151
+ <div className="action-buttons">
152
+ <button
153
+ className="edit-button"
154
+ onClick={() => openModal(proxy)}
155
+ >
156
+ 编辑
157
+ </button>
158
+ <button
159
+ className="delete-button"
160
+ onClick={() => deleteProxy(proxy.id)}
161
+ >
162
+ 删除
163
+ </button>
164
+ </div>
165
+ </td>
166
+ </tr>
167
+ ))}
168
+ </tbody>
169
+ </table>
170
+ )}
171
+ </div>
172
+
173
+ {isModalOpen && (
174
+ <div className="modal-overlay">
175
+ <div className="modal">
176
+ <div className="modal-header">
177
+ <h2>{currentProxy ? '编辑文件代理' : '添加文件代理'}</h2>
178
+ <button className="close-button" onClick={closeModal}>×</button>
179
+ </div>
180
+ <form className="proxy-form" onSubmit={handleSubmit}>
181
+ <div className="form-group">
182
+ <label>本地文件路径</label>
183
+ <div className="file-input-group">
184
+ <input
185
+ type="text"
186
+ value={localPath}
187
+ onChange={(e) => setLocalPath(e.target.value)}
188
+ placeholder="/path/to/local/file.json"
189
+ required
190
+ />
191
+ <button type="button" className="browse-button">浏览...</button>
192
+ </div>
193
+ <div className="help-text">选择本地文件作为数据源</div>
194
+ </div>
195
+
196
+ <div className="form-group">
197
+ <label>远程路径</label>
198
+ <input
199
+ type="text"
200
+ value={remotePath}
201
+ onChange={(e) => setRemotePath(e.target.value)}
202
+ placeholder="/api/data"
203
+ required
204
+ />
205
+ <div className="help-text">访问此路径时将返回本地文件内容</div>
206
+ </div>
207
+
208
+ <div className="form-group">
209
+ <label>描述(可选)</label>
210
+ <input
211
+ type="text"
212
+ value={description}
213
+ onChange={(e) => setDescription(e.target.value)}
214
+ placeholder="接口描述"
215
+ />
216
+ </div>
217
+
218
+ <div className="form-actions">
219
+ <button type="button" className="cancel-button" onClick={closeModal}>
220
+ 取消
221
+ </button>
222
+ <button type="submit" className="submit-button">
223
+ {currentProxy ? '保存修改' : '添加代理'}
224
+ </button>
225
+ </div>
226
+ </form>
227
+ </div>
228
+ </div>
229
+ )}
230
+ </div>
231
+ </AppLayout>
232
+ );
233
+ };
234
+
235
+ export default FileProxy;
@@ -0,0 +1,292 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Card, Table, Button, Space, Input, Tree, Modal, message, Breadcrumb, Typography } from 'antd';
3
+ import {
4
+ FileOutlined,
5
+ FolderOutlined,
6
+ PlusOutlined,
7
+ EditOutlined,
8
+ DeleteOutlined,
9
+ SaveOutlined,
10
+ UploadOutlined,
11
+ ExclamationCircleOutlined,
12
+ FolderAddOutlined
13
+ } from '@ant-design/icons';
14
+ import { ProxyAPI } from '../services/api';
15
+
16
+ const { Text } = Typography;
17
+ const { DirectoryTree } = Tree;
18
+ const { confirm } = Modal;
19
+ const { TextArea } = Input;
20
+
21
+ const FilesPage = () => {
22
+ const [files, setFiles] = useState([]);
23
+ const [loading, setLoading] = useState(false);
24
+ const [currentPath, setCurrentPath] = useState('');
25
+ const [breadcrumbs, setBreadcrumbs] = useState([{ title: '根目录', path: '' }]);
26
+ const [fileContent, setFileContent] = useState('');
27
+ const [currentFile, setCurrentFile] = useState(null);
28
+ const [fileModalVisible, setFileModalVisible] = useState(false);
29
+ const [folderModalVisible, setFolderModalVisible] = useState(false);
30
+ const [newFolderName, setNewFolderName] = useState('');
31
+
32
+ useEffect(() => {
33
+ fetchFiles();
34
+ }, [currentPath]);
35
+
36
+ const fetchFiles = async () => {
37
+ setLoading(true);
38
+ try {
39
+ const data = await ProxyAPI.listFiles(currentPath);
40
+ setFiles(data);
41
+ } catch (error) {
42
+ console.error("获取文件列表失败:", error);
43
+ message.error('获取文件列表失败');
44
+ } finally {
45
+ setLoading(false);
46
+ }
47
+ };
48
+
49
+ const handleOpenFile = async (file) => {
50
+ if (file.isDirectory) {
51
+ navigateToFolder(file.path);
52
+ } else {
53
+ try {
54
+ setLoading(true);
55
+ const data = await ProxyAPI.getFile(file.path);
56
+ setCurrentFile(file);
57
+ setFileContent(data.content);
58
+ setFileModalVisible(true);
59
+ } catch (error) {
60
+ console.error("获取文件内容失败:", error);
61
+ message.error('获取文件内容失败');
62
+ } finally {
63
+ setLoading(false);
64
+ }
65
+ }
66
+ };
67
+
68
+ const navigateToFolder = (path) => {
69
+ setCurrentPath(path);
70
+
71
+ // 更新面包屑
72
+ const parts = path.split('/').filter(Boolean);
73
+ const crumbs = [{ title: '根目录', path: '' }];
74
+
75
+ let currentPathSegment = '';
76
+ parts.forEach(part => {
77
+ currentPathSegment += currentPathSegment ? `/${part}` : part;
78
+ crumbs.push({
79
+ title: part,
80
+ path: currentPathSegment
81
+ });
82
+ });
83
+
84
+ setBreadcrumbs(crumbs);
85
+ };
86
+
87
+ const handleBreadcrumbClick = (path) => {
88
+ navigateToFolder(path);
89
+ };
90
+
91
+ const handleSaveFile = async () => {
92
+ try {
93
+ setLoading(true);
94
+ await ProxyAPI.saveFile(currentFile.path, fileContent);
95
+ setFileModalVisible(false);
96
+ message.success('文件保存成功');
97
+ fetchFiles();
98
+ } catch (error) {
99
+ console.error("保存文件失败:", error);
100
+ message.error('保存文件失败');
101
+ } finally {
102
+ setLoading(false);
103
+ }
104
+ };
105
+
106
+ const handleDeleteFile = (file) => {
107
+ confirm({
108
+ title: '确认删除',
109
+ icon: <ExclamationCircleOutlined />,
110
+ content: `确定要删除${file.isDirectory ? '文件夹' : '文件'} "${file.name}" 吗?`,
111
+ okText: '确认',
112
+ cancelText: '取消',
113
+ onOk: async () => {
114
+ try {
115
+ await ProxyAPI.deleteFile(file.path);
116
+ message.success('删除成功');
117
+ fetchFiles();
118
+ } catch (error) {
119
+ console.error("删除失败:", error);
120
+ message.error('删除失败');
121
+ }
122
+ },
123
+ });
124
+ };
125
+
126
+ const handleCreateFile = async () => {
127
+ const newFileName = `new-file-${Date.now()}.json`;
128
+ const newFilePath = currentPath ? `${currentPath}/${newFileName}` : newFileName;
129
+
130
+ try {
131
+ await ProxyAPI.saveFile(newFilePath, '{\n "data": "新文件内容"\n}');
132
+ message.success('文件创建成功');
133
+ fetchFiles();
134
+
135
+ // 创建后立即打开编辑
136
+ const newFile = { path: newFilePath, name: newFileName, isDirectory: false };
137
+ handleOpenFile(newFile);
138
+ } catch (error) {
139
+ console.error("创建文件失败:", error);
140
+ message.error('创建文件失败');
141
+ }
142
+ };
143
+
144
+ const handleCreateFolder = () => {
145
+ setNewFolderName('');
146
+ setFolderModalVisible(true);
147
+ };
148
+
149
+ const handleFolderModalOk = async () => {
150
+ if (!newFolderName.trim()) {
151
+ message.error('请输入文件夹名称');
152
+ return;
153
+ }
154
+
155
+ const newFolderPath = currentPath ? `${currentPath}/${newFolderName}` : newFolderName;
156
+
157
+ try {
158
+ // 通过创建一个占位文件来创建文件夹
159
+ await ProxyAPI.saveFile(`${newFolderPath}/.placeholder`, '');
160
+ setFolderModalVisible(false);
161
+ message.success('文件夹创建成功');
162
+ fetchFiles();
163
+ } catch (error) {
164
+ console.error("创建文件夹失败:", error);
165
+ message.error('创建文件夹失败');
166
+ }
167
+ };
168
+
169
+ const columns = [
170
+ {
171
+ title: '名称',
172
+ dataIndex: 'name',
173
+ key: 'name',
174
+ render: (text, record) => (
175
+ <Space>
176
+ {record.isDirectory ? <FolderOutlined /> : <FileOutlined />}
177
+ <a onClick={() => handleOpenFile(record)}>{text}</a>
178
+ </Space>
179
+ ),
180
+ },
181
+ {
182
+ title: '类型',
183
+ dataIndex: 'isDirectory',
184
+ key: 'type',
185
+ width: 100,
186
+ render: (isDirectory) => isDirectory ? '文件夹' : '文件',
187
+ },
188
+ {
189
+ title: '大小',
190
+ dataIndex: 'size',
191
+ key: 'size',
192
+ width: 120,
193
+ render: (size, record) => record.isDirectory ? '-' : `${size} 字节`,
194
+ },
195
+ {
196
+ title: '修改时间',
197
+ dataIndex: 'mtime',
198
+ key: 'mtime',
199
+ width: 200,
200
+ render: (text) => text ? new Date(text).toLocaleString() : '-',
201
+ },
202
+ {
203
+ title: '操作',
204
+ key: 'action',
205
+ width: 120,
206
+ render: (_, record) => (
207
+ <Button type="text" danger icon={<DeleteOutlined />} onClick={() => handleDeleteFile(record)}>
208
+ 删除
209
+ </Button>
210
+ ),
211
+ },
212
+ ];
213
+
214
+ return (
215
+ <div className="files-page">
216
+ <Card
217
+ title="文件管理"
218
+ extra={
219
+ <Space>
220
+ <Button type="primary" icon={<PlusOutlined />} onClick={handleCreateFile}>
221
+ 新建文件
222
+ </Button>
223
+ <Button icon={<FolderAddOutlined />} onClick={handleCreateFolder}>
224
+ 新建文件夹
225
+ </Button>
226
+ </Space>
227
+ }
228
+ >
229
+ <Breadcrumb style={{ marginBottom: '16px' }}>
230
+ {breadcrumbs.map((crumb, index) => (
231
+ <Breadcrumb.Item key={index}>
232
+ <a onClick={() => handleBreadcrumbClick(crumb.path)}>{crumb.title}</a>
233
+ </Breadcrumb.Item>
234
+ ))}
235
+ </Breadcrumb>
236
+
237
+ <Table
238
+ columns={columns}
239
+ dataSource={files}
240
+ rowKey="path"
241
+ loading={loading}
242
+ pagination={false}
243
+ />
244
+ </Card>
245
+
246
+ <Modal
247
+ title={currentFile?.name || '文件内容'}
248
+ open={fileModalVisible}
249
+ onCancel={() => setFileModalVisible(false)}
250
+ footer={[
251
+ <Button key="cancel" onClick={() => setFileModalVisible(false)}>
252
+ 取消
253
+ </Button>,
254
+ <Button
255
+ key="save"
256
+ type="primary"
257
+ icon={<SaveOutlined />}
258
+ onClick={handleSaveFile}
259
+ loading={loading}
260
+ >
261
+ 保存
262
+ </Button>,
263
+ ]}
264
+ width={800}
265
+ >
266
+ <TextArea
267
+ value={fileContent}
268
+ onChange={(e) => setFileContent(e.target.value)}
269
+ autoSize={{ minRows: 20, maxRows: 30 }}
270
+ />
271
+ </Modal>
272
+
273
+ <Modal
274
+ title="新建文件夹"
275
+ open={folderModalVisible}
276
+ onOk={handleFolderModalOk}
277
+ onCancel={() => setFolderModalVisible(false)}
278
+ okText="创建"
279
+ cancelText="取消"
280
+ >
281
+ <Input
282
+ placeholder="请输入文件夹名称"
283
+ value={newFolderName}
284
+ onChange={(e) => setNewFolderName(e.target.value)}
285
+ onPressEnter={handleFolderModalOk}
286
+ />
287
+ </Modal>
288
+ </div>
289
+ );
290
+ };
291
+
292
+ export default FilesPage;