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.
- package/API.md +247 -0
- package/README.md +91 -0
- package/app/cgi-bin/features.js +285 -0
- package/app/cgi-bin/index.js +31 -0
- package/app/cgi-bin/interfaces.js +409 -0
- package/app/cgi-bin/logs.js +151 -0
- package/app/cgi-bin/test-interface.js +152 -0
- package/app/public/index.html +1 -0
- package/app/public/js/index.js +2 -0
- package/app/public/js/index.js.LICENSE.txt +150 -0
- package/app/util/index.js +24 -0
- package/index.html +52 -0
- package/initialize.js +32 -0
- package/lib/dataManager.js +500 -0
- package/lib/index.js +3 -0
- package/lib/models.js +64 -0
- package/lib/routes/features.js +213 -0
- package/lib/routes/interfaces.js +304 -0
- package/lib/routes/proxy.js +239 -0
- package/lib/ruleManager.js +551 -0
- package/lib/rules-server.js +267 -0
- package/lib/server.js +805 -0
- package/lib/storage.js +16 -0
- package/lib/ui-server.js +68 -0
- package/package.json +51 -0
- package/public/bundle.js +2 -0
- package/public/bundle.js.LICENSE.txt +38 -0
- package/public/index.html +1 -0
- package/public/plugin.mock-plugin.html +14 -0
- package/rules.txt +22 -0
- package/test/rulesServer.test.js +120 -0
- package/ui/src/App.css +173 -0
- package/ui/src/App.js +24 -0
- package/ui/src/components/AppLayout.js +149 -0
- package/ui/src/components/FeatureCard.js +53 -0
- package/ui/src/components/Layout.js +94 -0
- package/ui/src/components/MainNav.js +42 -0
- package/ui/src/components/PageContainer.js +16 -0
- package/ui/src/index.html +11 -0
- package/ui/src/index.js +43 -0
- package/ui/src/pages/FileProxy.js +235 -0
- package/ui/src/pages/FilesPage.js +292 -0
- package/ui/src/pages/Home.js +128 -0
- package/ui/src/pages/HomePage.js +195 -0
- package/ui/src/pages/InterfaceManagement.js +829 -0
- package/ui/src/pages/InterfacePage.js +342 -0
- package/ui/src/pages/MockData.js +472 -0
- package/ui/src/pages/Settings.js +192 -0
- package/ui/src/pages/SettingsPage.js +165 -0
- package/ui/src/pages/UrlRedirect.js +225 -0
- package/ui/src/pages/Welcome.js +155 -0
- package/ui/src/services/api.js +186 -0
- package/ui/src/styles/app-layout.css +337 -0
- package/ui/src/styles/feature-card.css +172 -0
- package/ui/src/styles/file-proxy.css +301 -0
- package/ui/src/styles/global.css +224 -0
- package/ui/src/styles/home.css +123 -0
- package/ui/src/styles/index.css +419 -0
- package/ui/src/styles/interface-management.css +694 -0
- package/ui/src/styles/layout.css +159 -0
- package/ui/src/styles/logs-page.css +427 -0
- package/ui/src/styles/main-nav.css +52 -0
- package/ui/src/styles/mock-data.css +850 -0
- package/ui/src/styles/page-container.css +11 -0
- package/ui/src/styles/settings.css +208 -0
- package/ui/src/styles/url-redirect.css +279 -0
- package/ui/src/styles/welcome.css +282 -0
- package/webpack.config.js +49 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Card, Form, Input, Button, Switch, message, Divider, Typography, Space, Select } from 'antd';
|
|
3
|
+
import { SaveOutlined, ReloadOutlined } from '@ant-design/icons';
|
|
4
|
+
|
|
5
|
+
const { Title, Paragraph, Text } = Typography;
|
|
6
|
+
const { Option } = Select;
|
|
7
|
+
|
|
8
|
+
// 模拟设置 API
|
|
9
|
+
const SettingsAPI = {
|
|
10
|
+
getSettings: () => Promise.resolve({
|
|
11
|
+
mockFolder: './mocks',
|
|
12
|
+
enableAutoSave: true,
|
|
13
|
+
responseDelay: 200,
|
|
14
|
+
logLevel: 'info',
|
|
15
|
+
}),
|
|
16
|
+
saveSettings: (settings) => Promise.resolve(settings)
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const SettingsPage = () => {
|
|
20
|
+
const [form] = Form.useForm();
|
|
21
|
+
const [loading, setLoading] = useState(false);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
fetchSettings();
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
const fetchSettings = async () => {
|
|
28
|
+
setLoading(true);
|
|
29
|
+
try {
|
|
30
|
+
const settings = await SettingsAPI.getSettings();
|
|
31
|
+
form.setFieldsValue(settings);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error("获取设置失败:", error);
|
|
34
|
+
message.error('获取设置失败');
|
|
35
|
+
} finally {
|
|
36
|
+
setLoading(false);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const handleSave = async () => {
|
|
41
|
+
try {
|
|
42
|
+
const values = await form.validateFields();
|
|
43
|
+
setLoading(true);
|
|
44
|
+
await SettingsAPI.saveSettings(values);
|
|
45
|
+
message.success('设置保存成功');
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error("保存设置失败:", error);
|
|
48
|
+
message.error('保存设置失败');
|
|
49
|
+
} finally {
|
|
50
|
+
setLoading(false);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleReset = () => {
|
|
55
|
+
form.resetFields();
|
|
56
|
+
fetchSettings();
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className="settings-page">
|
|
61
|
+
<Card title="全局设置">
|
|
62
|
+
<Form
|
|
63
|
+
form={form}
|
|
64
|
+
layout="vertical"
|
|
65
|
+
initialValues={{
|
|
66
|
+
mockFolder: './mocks',
|
|
67
|
+
enableAutoSave: true,
|
|
68
|
+
responseDelay: 200,
|
|
69
|
+
logLevel: 'info',
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
<Title level={4}>基本设置</Title>
|
|
73
|
+
<Form.Item
|
|
74
|
+
name="mockFolder"
|
|
75
|
+
label="Mock 数据文件夹路径"
|
|
76
|
+
rules={[{ required: true, message: '请输入 Mock 数据文件夹路径!' }]}
|
|
77
|
+
>
|
|
78
|
+
<Input placeholder="./mocks" />
|
|
79
|
+
</Form.Item>
|
|
80
|
+
|
|
81
|
+
<Form.Item
|
|
82
|
+
name="enableAutoSave"
|
|
83
|
+
label="启用自动保存"
|
|
84
|
+
valuePropName="checked"
|
|
85
|
+
>
|
|
86
|
+
<Switch />
|
|
87
|
+
</Form.Item>
|
|
88
|
+
|
|
89
|
+
<Form.Item
|
|
90
|
+
name="responseDelay"
|
|
91
|
+
label="默认响应延迟(ms)"
|
|
92
|
+
rules={[{ required: true, message: '请输入默认响应延迟!' }]}
|
|
93
|
+
>
|
|
94
|
+
<Input type="number" placeholder="200" />
|
|
95
|
+
</Form.Item>
|
|
96
|
+
|
|
97
|
+
<Form.Item
|
|
98
|
+
name="logLevel"
|
|
99
|
+
label="日志级别"
|
|
100
|
+
rules={[{ required: true, message: '请选择日志级别!' }]}
|
|
101
|
+
>
|
|
102
|
+
<Select placeholder="请选择日志级别">
|
|
103
|
+
<Option value="debug">Debug</Option>
|
|
104
|
+
<Option value="info">Info</Option>
|
|
105
|
+
<Option value="warn">Warning</Option>
|
|
106
|
+
<Option value="error">Error</Option>
|
|
107
|
+
</Select>
|
|
108
|
+
</Form.Item>
|
|
109
|
+
|
|
110
|
+
<Divider />
|
|
111
|
+
|
|
112
|
+
<Title level={4}>使用说明</Title>
|
|
113
|
+
<Paragraph>
|
|
114
|
+
Whistle Mock 插件用于快速创建 API 模拟数据,支持以下功能:
|
|
115
|
+
</Paragraph>
|
|
116
|
+
|
|
117
|
+
<Paragraph>
|
|
118
|
+
<ul>
|
|
119
|
+
<li>
|
|
120
|
+
<Text strong>接口管理</Text> - 创建和管理模拟接口,支持 GET、POST 等多种请求方法
|
|
121
|
+
</li>
|
|
122
|
+
<li>
|
|
123
|
+
<Text strong>文件管理</Text> - 编辑 JSON 文件作为模拟数据源
|
|
124
|
+
</li>
|
|
125
|
+
<li>
|
|
126
|
+
<Text strong>Mock 模板语法</Text> - 支持使用 Mock.js 模板语法生成随机数据
|
|
127
|
+
</li>
|
|
128
|
+
<li>
|
|
129
|
+
<Text strong>路径匹配规则</Text> - 支持通配符和正则表达式匹配请求路径
|
|
130
|
+
</li>
|
|
131
|
+
</ul>
|
|
132
|
+
</Paragraph>
|
|
133
|
+
|
|
134
|
+
<Paragraph>
|
|
135
|
+
要启用 Whistle Mock 插件,请在 Whistle 界面添加以下规则:
|
|
136
|
+
<pre>
|
|
137
|
+
example.com mock://
|
|
138
|
+
</pre>
|
|
139
|
+
</Paragraph>
|
|
140
|
+
|
|
141
|
+
<Divider />
|
|
142
|
+
|
|
143
|
+
<Space>
|
|
144
|
+
<Button
|
|
145
|
+
type="primary"
|
|
146
|
+
icon={<SaveOutlined />}
|
|
147
|
+
onClick={handleSave}
|
|
148
|
+
loading={loading}
|
|
149
|
+
>
|
|
150
|
+
保存设置
|
|
151
|
+
</Button>
|
|
152
|
+
<Button
|
|
153
|
+
icon={<ReloadOutlined />}
|
|
154
|
+
onClick={handleReset}
|
|
155
|
+
>
|
|
156
|
+
重置
|
|
157
|
+
</Button>
|
|
158
|
+
</Space>
|
|
159
|
+
</Form>
|
|
160
|
+
</Card>
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export default SettingsPage;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import AppLayout from '../components/AppLayout';
|
|
3
|
+
import '../styles/url-redirect.css';
|
|
4
|
+
|
|
5
|
+
const UrlRedirect = () => {
|
|
6
|
+
const [urlRedirects, setUrlRedirects] = useState([
|
|
7
|
+
{
|
|
8
|
+
id: 1,
|
|
9
|
+
sourceUrl: 'https://api.example.com/users',
|
|
10
|
+
targetUrl: 'http://localhost:3000/mock/users',
|
|
11
|
+
description: '用户API重定向到本地',
|
|
12
|
+
active: true
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 2,
|
|
16
|
+
sourceUrl: 'https://api.example.com/products',
|
|
17
|
+
targetUrl: 'http://localhost:3000/mock/products',
|
|
18
|
+
description: '产品API重定向到本地',
|
|
19
|
+
active: false
|
|
20
|
+
}
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
24
|
+
const [currentRedirect, setCurrentRedirect] = useState(null);
|
|
25
|
+
const [sourceUrl, setSourceUrl] = useState('');
|
|
26
|
+
const [targetUrl, setTargetUrl] = useState('');
|
|
27
|
+
const [description, setDescription] = useState('');
|
|
28
|
+
|
|
29
|
+
const openModal = (redirect = null) => {
|
|
30
|
+
if (redirect) {
|
|
31
|
+
setCurrentRedirect(redirect);
|
|
32
|
+
setSourceUrl(redirect.sourceUrl);
|
|
33
|
+
setTargetUrl(redirect.targetUrl);
|
|
34
|
+
setDescription(redirect.description);
|
|
35
|
+
} else {
|
|
36
|
+
setCurrentRedirect(null);
|
|
37
|
+
setSourceUrl('');
|
|
38
|
+
setTargetUrl('');
|
|
39
|
+
setDescription('');
|
|
40
|
+
}
|
|
41
|
+
setIsModalOpen(true);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const closeModal = () => {
|
|
45
|
+
setIsModalOpen(false);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handleSubmit = (e) => {
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
|
|
51
|
+
if (!sourceUrl || !targetUrl) {
|
|
52
|
+
alert('请填写源URL和目标URL');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (currentRedirect) {
|
|
57
|
+
// 编辑现有配置
|
|
58
|
+
setUrlRedirects(urlRedirects.map(redirect =>
|
|
59
|
+
redirect.id === currentRedirect.id
|
|
60
|
+
? { ...redirect, sourceUrl, targetUrl, description }
|
|
61
|
+
: redirect
|
|
62
|
+
));
|
|
63
|
+
} else {
|
|
64
|
+
// 添加新配置
|
|
65
|
+
const newRedirect = {
|
|
66
|
+
id: urlRedirects.length > 0 ? Math.max(...urlRedirects.map(r => r.id)) + 1 : 1,
|
|
67
|
+
sourceUrl,
|
|
68
|
+
targetUrl,
|
|
69
|
+
description,
|
|
70
|
+
active: true
|
|
71
|
+
};
|
|
72
|
+
setUrlRedirects([...urlRedirects, newRedirect]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
closeModal();
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const toggleActive = (id) => {
|
|
79
|
+
setUrlRedirects(urlRedirects.map(redirect =>
|
|
80
|
+
redirect.id === id ? { ...redirect, active: !redirect.active } : redirect
|
|
81
|
+
));
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const deleteRedirect = (id) => {
|
|
85
|
+
if (window.confirm('确定要删除此条重定向配置吗?')) {
|
|
86
|
+
setUrlRedirects(urlRedirects.filter(redirect => redirect.id !== id));
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<AppLayout>
|
|
92
|
+
<div className="url-redirect-container">
|
|
93
|
+
<div className="page-title-bar">
|
|
94
|
+
<div>
|
|
95
|
+
<h1 className="page-title">URL重定向配置</h1>
|
|
96
|
+
<div className="page-description">
|
|
97
|
+
配置URL重定向规则,支持路径和查询参数匹配
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
<div className="page-actions">
|
|
101
|
+
<button className="add-button" onClick={() => openModal()}>
|
|
102
|
+
添加URL重定向
|
|
103
|
+
</button>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div className="redirect-list-container">
|
|
108
|
+
{urlRedirects.length === 0 ? (
|
|
109
|
+
<div className="empty-data">
|
|
110
|
+
<div className="empty-icon">🔄</div>
|
|
111
|
+
<div className="empty-text">暂无URL重定向配置</div>
|
|
112
|
+
<button className="add-button" onClick={() => openModal()}>
|
|
113
|
+
添加URL重定向
|
|
114
|
+
</button>
|
|
115
|
+
</div>
|
|
116
|
+
) : (
|
|
117
|
+
<table className="redirect-table">
|
|
118
|
+
<thead>
|
|
119
|
+
<tr>
|
|
120
|
+
<th>状态</th>
|
|
121
|
+
<th>源URL</th>
|
|
122
|
+
<th>目标URL</th>
|
|
123
|
+
<th>描述</th>
|
|
124
|
+
<th>操作</th>
|
|
125
|
+
</tr>
|
|
126
|
+
</thead>
|
|
127
|
+
<tbody>
|
|
128
|
+
{urlRedirects.map(redirect => (
|
|
129
|
+
<tr key={redirect.id} className={redirect.active ? '' : 'inactive'}>
|
|
130
|
+
<td>
|
|
131
|
+
<label className="switch">
|
|
132
|
+
<input
|
|
133
|
+
type="checkbox"
|
|
134
|
+
checked={redirect.active}
|
|
135
|
+
onChange={() => toggleActive(redirect.id)}
|
|
136
|
+
/>
|
|
137
|
+
<span className="slider"></span>
|
|
138
|
+
</label>
|
|
139
|
+
</td>
|
|
140
|
+
<td className="url-text">{redirect.sourceUrl}</td>
|
|
141
|
+
<td className="url-text">{redirect.targetUrl}</td>
|
|
142
|
+
<td>{redirect.description || '-'}</td>
|
|
143
|
+
<td>
|
|
144
|
+
<div className="action-buttons">
|
|
145
|
+
<button
|
|
146
|
+
className="edit-button"
|
|
147
|
+
onClick={() => openModal(redirect)}
|
|
148
|
+
>
|
|
149
|
+
编辑
|
|
150
|
+
</button>
|
|
151
|
+
<button
|
|
152
|
+
className="delete-button"
|
|
153
|
+
onClick={() => deleteRedirect(redirect.id)}
|
|
154
|
+
>
|
|
155
|
+
删除
|
|
156
|
+
</button>
|
|
157
|
+
</div>
|
|
158
|
+
</td>
|
|
159
|
+
</tr>
|
|
160
|
+
))}
|
|
161
|
+
</tbody>
|
|
162
|
+
</table>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
{isModalOpen && (
|
|
167
|
+
<div className="modal-overlay">
|
|
168
|
+
<div className="modal">
|
|
169
|
+
<div className="modal-header">
|
|
170
|
+
<h2>{currentRedirect ? '编辑URL重定向' : '添加URL重定向'}</h2>
|
|
171
|
+
<button className="close-button" onClick={closeModal}>×</button>
|
|
172
|
+
</div>
|
|
173
|
+
<form className="redirect-form" onSubmit={handleSubmit}>
|
|
174
|
+
<div className="form-group">
|
|
175
|
+
<label>源URL</label>
|
|
176
|
+
<input
|
|
177
|
+
type="text"
|
|
178
|
+
value={sourceUrl}
|
|
179
|
+
onChange={(e) => setSourceUrl(e.target.value)}
|
|
180
|
+
placeholder="https://api.example.com/endpoint"
|
|
181
|
+
required
|
|
182
|
+
/>
|
|
183
|
+
<div className="help-text">需要重定向的原始URL</div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<div className="form-group">
|
|
187
|
+
<label>目标URL</label>
|
|
188
|
+
<input
|
|
189
|
+
type="text"
|
|
190
|
+
value={targetUrl}
|
|
191
|
+
onChange={(e) => setTargetUrl(e.target.value)}
|
|
192
|
+
placeholder="http://localhost:3000/mock/endpoint"
|
|
193
|
+
required
|
|
194
|
+
/>
|
|
195
|
+
<div className="help-text">重定向的目标地址</div>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<div className="form-group">
|
|
199
|
+
<label>描述(可选)</label>
|
|
200
|
+
<input
|
|
201
|
+
type="text"
|
|
202
|
+
value={description}
|
|
203
|
+
onChange={(e) => setDescription(e.target.value)}
|
|
204
|
+
placeholder="重定向描述"
|
|
205
|
+
/>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<div className="form-actions">
|
|
209
|
+
<button type="button" className="cancel-button" onClick={closeModal}>
|
|
210
|
+
取消
|
|
211
|
+
</button>
|
|
212
|
+
<button type="submit" className="submit-button">
|
|
213
|
+
{currentRedirect ? '保存修改' : '添加重定向'}
|
|
214
|
+
</button>
|
|
215
|
+
</div>
|
|
216
|
+
</form>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
</AppLayout>
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export default UrlRedirect;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Link } from 'react-router-dom';
|
|
3
|
+
import AppLayout from '../components/AppLayout';
|
|
4
|
+
import '../styles/welcome.css';
|
|
5
|
+
|
|
6
|
+
const Welcome = () => {
|
|
7
|
+
// 模拟统计数据
|
|
8
|
+
const stats = {
|
|
9
|
+
featuresCount: 3,
|
|
10
|
+
interfacesCount: 15,
|
|
11
|
+
activeInterfacesCount: 12,
|
|
12
|
+
requestsHandledCount: 256
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// 模拟最近活动数据
|
|
16
|
+
const recentActivities = [
|
|
17
|
+
{ id: 1, type: '创建', target: '功能', name: '商品列表', time: '2023-11-05 14:30' },
|
|
18
|
+
{ id: 2, type: '修改', target: '接口', name: '用户登录', time: '2023-11-05 14:15' },
|
|
19
|
+
{ id: 3, type: '启用', target: '接口', name: '获取用户信息', time: '2023-11-05 13:45' },
|
|
20
|
+
{ id: 4, type: '创建', target: '接口', name: '商品详情', time: '2023-11-05 13:30' },
|
|
21
|
+
{ id: 5, type: '禁用', target: '功能', name: '购物车', time: '2023-11-05 11:20' }
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<AppLayout>
|
|
26
|
+
<div className="welcome-container">
|
|
27
|
+
<div className="welcome-header">
|
|
28
|
+
<h1>欢迎使用 Whistle Mock 数据工厂</h1>
|
|
29
|
+
<p>快速创建和管理模拟数据,提高前端开发效率</p>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div className="cards-container">
|
|
33
|
+
{/* 功能概览卡片 */}
|
|
34
|
+
<div className="card stats-card">
|
|
35
|
+
<div className="card-header">
|
|
36
|
+
<div className="card-icon">📊</div>
|
|
37
|
+
<h2>功能概览</h2>
|
|
38
|
+
</div>
|
|
39
|
+
<div className="card-content">
|
|
40
|
+
<div className="stat-item">
|
|
41
|
+
<div className="stat-value">{stats.featuresCount}</div>
|
|
42
|
+
<div className="stat-label">功能模块</div>
|
|
43
|
+
</div>
|
|
44
|
+
<div className="stat-item">
|
|
45
|
+
<div className="stat-value">{stats.interfacesCount}</div>
|
|
46
|
+
<div className="stat-label">接口配置</div>
|
|
47
|
+
</div>
|
|
48
|
+
<div className="stat-item">
|
|
49
|
+
<div className="stat-value">{stats.activeInterfacesCount}</div>
|
|
50
|
+
<div className="stat-label">启用接口</div>
|
|
51
|
+
</div>
|
|
52
|
+
<div className="stat-item">
|
|
53
|
+
<div className="stat-value">{stats.requestsHandledCount}</div>
|
|
54
|
+
<div className="stat-label">已处理请求</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
{/* 快速开始卡片 */}
|
|
60
|
+
<div className="card quickstart-card">
|
|
61
|
+
<div className="card-header">
|
|
62
|
+
<div className="card-icon">🚀</div>
|
|
63
|
+
<h2>快速开始</h2>
|
|
64
|
+
</div>
|
|
65
|
+
<div className="card-content">
|
|
66
|
+
<div className="steps">
|
|
67
|
+
<div className="step">
|
|
68
|
+
<div className="step-number">1</div>
|
|
69
|
+
<div className="step-content">
|
|
70
|
+
<div className="step-title">创建功能模块</div>
|
|
71
|
+
<div className="step-desc">创建一个功能模块,用于组织相关接口</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="step">
|
|
75
|
+
<div className="step-number">2</div>
|
|
76
|
+
<div className="step-content">
|
|
77
|
+
<div className="step-title">添加接口配置</div>
|
|
78
|
+
<div className="step-desc">配置API接口的URL匹配规则和响应数据</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<div className="step">
|
|
82
|
+
<div className="step-number">3</div>
|
|
83
|
+
<div className="step-content">
|
|
84
|
+
<div className="step-title">启用接口使用</div>
|
|
85
|
+
<div className="step-desc">启用功能模块和接口,即可在Whistle中使用</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
<div className="quickstart-actions">
|
|
90
|
+
<Link to="/mock-data">
|
|
91
|
+
<button className="quickstart-button">开始创建功能</button>
|
|
92
|
+
</Link>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
{/* 使用说明卡片 */}
|
|
98
|
+
<div className="card usage-card">
|
|
99
|
+
<div className="card-header">
|
|
100
|
+
<div className="card-icon">📘</div>
|
|
101
|
+
<h2>使用说明</h2>
|
|
102
|
+
</div>
|
|
103
|
+
<div className="card-content">
|
|
104
|
+
<div className="usage-item">
|
|
105
|
+
<div className="usage-title">添加Whistle规则</div>
|
|
106
|
+
<div className="usage-desc">
|
|
107
|
+
在Whistle中添加如下规则:
|
|
108
|
+
<pre className="code-block">example.com mock://</pre>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
<div className="usage-item">
|
|
112
|
+
<div className="usage-title">匹配规则说明</div>
|
|
113
|
+
<div className="usage-desc">
|
|
114
|
+
支持多种匹配方式:
|
|
115
|
+
<ul className="usage-list">
|
|
116
|
+
<li>精确匹配: <code>/api/users</code></li>
|
|
117
|
+
<li>通配符匹配: <code>/api/users/*</code></li>
|
|
118
|
+
<li>正则匹配: <code>/\/api\/products\/\d+/</code></li>
|
|
119
|
+
</ul>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{/* 最近活动 */}
|
|
127
|
+
<div className="recent-activities">
|
|
128
|
+
<div className="section-header">
|
|
129
|
+
<h2>最近活动</h2>
|
|
130
|
+
</div>
|
|
131
|
+
<div className="activities-list">
|
|
132
|
+
{recentActivities.map(activity => (
|
|
133
|
+
<div key={activity.id} className="activity-item">
|
|
134
|
+
<div className="activity-icon">
|
|
135
|
+
{activity.type === '创建' ? '➕' :
|
|
136
|
+
activity.type === '修改' ? '✏️' :
|
|
137
|
+
activity.type === '启用' ? '✅' :
|
|
138
|
+
activity.type === '禁用' ? '❌' : '🔄'}
|
|
139
|
+
</div>
|
|
140
|
+
<div className="activity-content">
|
|
141
|
+
<div className="activity-title">
|
|
142
|
+
{activity.type}了{activity.target}「{activity.name}」
|
|
143
|
+
</div>
|
|
144
|
+
<div className="activity-time">{activity.time}</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
))}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</AppLayout>
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export default Welcome;
|