json-api-mocker 2.0.0 → 2.0.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.
package/DESIGN.md ADDED
@@ -0,0 +1,227 @@
1
+ # JSON API Mocker 设计思路文档
2
+
3
+ ## 1. 项目架构设计
4
+
5
+ ### 1.1 核心模块划分
6
+ ```
7
+ src/
8
+ ├── types.ts # 类型定义
9
+ ├── server.ts # 服务器核心逻辑
10
+ ├── index.ts # 入口文件
11
+ └── cli.ts # 命令行工具
12
+ ```
13
+
14
+ ### 1.2 设计原则
15
+ - **配置驱动**: 通过 JSON 配置文件定义 API,而不是代码
16
+ - **约定优于配置**: 提供合理的默认值,减少配置复杂度
17
+ - **单一职责**: 每个模块只负责一个功能
18
+ - **开闭原则**: 扩展开放,修改关闭
19
+
20
+ ## 2. 关键技术决策
21
+
22
+ ### 2.1 为什么选择 JSON 配置文件
23
+ - **优点**:
24
+ - 零代码门槛
25
+ - 配置直观易读
26
+ - 方便版本控制
27
+ - 易于修改和共享
28
+ - **缺点**:
29
+ - 灵活性相对较低
30
+ - 不支持复杂逻辑
31
+
32
+ ### 2.2 为什么选择文件系统而不是数据库
33
+ - **优点**:
34
+ - 无需额外依赖
35
+ - 配置即数据,直观明了
36
+ - 方便迁移和备份
37
+ - **缺点**:
38
+ - 并发性能较差
39
+ - 不适合大规模数据
40
+
41
+ ### 2.3 为什么使用 TypeScript
42
+ - 类型安全
43
+ - 更好的开发体验
44
+ - 自动生成类型声明文件
45
+ - 便于维护和重构
46
+
47
+ ## 3. 核心功能实现
48
+
49
+ ### 3.1 路由系统设计
50
+ ```typescript
51
+ interface RouteConfig {
52
+ path: string;
53
+ methods: {
54
+ [key: string]: MethodConfig;
55
+ };
56
+ }
57
+
58
+ // 支持 RESTful API 的标准方法
59
+ type HttpMethod = 'get' | 'post' | 'put' | 'delete';
60
+ ```
61
+
62
+ ### 3.2 数据持久化方案
63
+ ```typescript
64
+ class MockServer {
65
+ private saveConfig() {
66
+ // 同步写入确保数据一致性
67
+ fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
68
+ }
69
+ }
70
+ ```
71
+
72
+ ### 3.3 分页实现
73
+ ```typescript
74
+ interface PaginationConfig {
75
+ enabled: boolean;
76
+ pageSize: number;
77
+ totalCount: number;
78
+ }
79
+
80
+ // 分页处理逻辑
81
+ const handlePagination = (data: any[], page: number, pageSize: number) => {
82
+ const start = (page - 1) * pageSize;
83
+ return data.slice(start, start + pageSize);
84
+ };
85
+ ```
86
+
87
+ ## 4. 问题解决方案
88
+
89
+ ### 4.1 并发写入问题
90
+ - **问题**: 多个请求同时修改配置文件可能导致数据不一致
91
+ - **解决方案**:
92
+ 1. 使用同步写入
93
+ 2. 考虑添加文件锁
94
+ 3. 实现写入队列
95
+
96
+ ### 4.2 动态路由参数
97
+ - **问题**: 如何支持 `/users/:id` 这样的动态路径
98
+ - **解决方案**:
99
+ ```typescript
100
+ // 使用 Express 的路由参数功能
101
+ app.get(`${baseProxy}${path}/:id`, (req, res) => {
102
+ const id = req.params.id;
103
+ // 处理逻辑
104
+ });
105
+ ```
106
+
107
+ ### 4.3 类型安全
108
+ - **问题**: 如何确保运行时类型安全
109
+ - **解决方案**:
110
+ ```typescript
111
+ // 请求体验证
112
+ interface RequestSchema {
113
+ [key: string]: 'string' | 'number' | 'boolean';
114
+ }
115
+
116
+ const validateRequest = (body: any, schema: RequestSchema) => {
117
+ // 实现验证逻辑
118
+ };
119
+ ```
120
+
121
+ ## 5. 性能优化
122
+
123
+ ### 5.1 当前实现
124
+ - 同步文件操作
125
+ - 内存中保存配置副本
126
+ - 简单的错误处理
127
+
128
+ ### 5.2 可能的优化方向
129
+ 1. **缓存优化**
130
+ ```typescript
131
+ class Cache {
132
+ private static instance: Cache;
133
+ private store: Map<string, any>;
134
+
135
+ public get(key: string) {
136
+ return this.store.get(key);
137
+ }
138
+ }
139
+ ```
140
+
141
+ 2. **并发处理**
142
+ ```typescript
143
+ class WriteQueue {
144
+ private queue: Array<() => Promise<void>>;
145
+
146
+ public async add(task: () => Promise<void>) {
147
+ this.queue.push(task);
148
+ await this.process();
149
+ }
150
+ }
151
+ ```
152
+
153
+ ## 6. 扩展性设计
154
+
155
+ ### 6.1 中间件支持
156
+ ```typescript
157
+ interface ServerConfig {
158
+ middleware?: {
159
+ before?: Function[];
160
+ after?: Function[];
161
+ };
162
+ }
163
+ ```
164
+
165
+ ### 6.2 自定义响应处理
166
+ ```typescript
167
+ interface MethodConfig {
168
+ transform?: (data: any) => any;
169
+ headers?: Record<string, string>;
170
+ }
171
+ ```
172
+
173
+ ## 7. 未来规划
174
+
175
+ ### 7.1 短期目标
176
+ - ✅ 添加请求日志 (Completed in v1.2.0)
177
+ - ✅ 支持文件上传 (Completed in v1.2.5)
178
+ - ✅ 添加测试用例 (Completed in v1.2.6)
179
+
180
+ ### 7.2 长期目标
181
+ - ✅ 提供 Web 界面 (Completed in v2.0.0)
182
+ - 支持 WebSocket
183
+ - 支持数据库存储
184
+ - 支持集群部署
185
+
186
+ ## 8. 最佳实践
187
+
188
+ ### 8.1 配置文件组织
189
+ ```json
190
+ {
191
+ "server": {
192
+ "port": 8080,
193
+ "baseProxy": "/api"
194
+ },
195
+ "routes": [
196
+ {
197
+ "path": "/users",
198
+ "methods": {
199
+ "get": { ... },
200
+ "post": { ... }
201
+ }
202
+ }
203
+ ]
204
+ }
205
+ ```
206
+
207
+ ### 8.2 错误处理
208
+ ```typescript
209
+ class ApiError extends Error {
210
+ constructor(
211
+ public status: number,
212
+ message: string
213
+ ) {
214
+ super(message);
215
+ }
216
+ }
217
+
218
+ const errorHandler = (err: Error, req: Request, res: Response) => {
219
+ if (err instanceof ApiError) {
220
+ res.status(err.status).json({ error: err.message });
221
+ }
222
+ };
223
+ ```
224
+
225
+ ## 9. 总结
226
+
227
+ 本项目的核心是通过配置驱动的方式简化 Mock 服务器的搭建过程。虽然在性能和功能上有一些限制,但对于前端开发中的 Mock 需求来说是一个很好的解决方案。通过合理的模块划分和接口设计,我们既保证了代码的可维护性,也为未来的功能扩展留下了空间。
package/README.ch.md CHANGED
@@ -30,15 +30,24 @@ npm install -g json-api-mocker
30
30
 
31
31
  ### 方式一:使用可视化界面(推荐)
32
32
 
33
- ```bash
34
- # 启动服务器和管理界面
35
- json-api-mocker -o # -o 参数会自动打开浏览器
33
+ 1. 创建 `data.json` 文件:
34
+ ```json
35
+ {
36
+ "server": {
37
+ "port": 3000,
38
+ "baseProxy": "/api"
39
+ },
40
+ "routes": []
41
+ }
42
+ ```
36
43
 
37
- # 指定端口
38
- json-api-mocker -p 3000 -o
44
+ 2. 启动服务器:
45
+ ```bash
46
+ # 启动服务器并打开浏览器
47
+ json-api-mocker -o
39
48
 
40
- # 指定配置文件
41
- json-api-mocker -c ./config.json -o
49
+ # 或者指定端口(默认是 3000)
50
+ json-api-mocker -p 8080 -o
42
51
  ```
43
52
 
44
53
  启动后,浏览器会自动打开管理界面,你可以:
package/README.md CHANGED
@@ -30,15 +30,24 @@ npm install -g json-api-mocker
30
30
 
31
31
  ### Method 1: Using Visual Interface (Recommended)
32
32
 
33
- ```bash
34
- # Start server and management interface
35
- json-api-mocker -o # -o will automatically open browser
33
+ 1. Create a `data.json` file:
34
+ ```json
35
+ {
36
+ "server": {
37
+ "port": 3000,
38
+ "baseProxy": "/api"
39
+ },
40
+ "routes": []
41
+ }
42
+ ```
36
43
 
37
- # Specify port
38
- json-api-mocker -p 3000 -o
44
+ 2. Start the server:
45
+ ```bash
46
+ # Start server and open browser
47
+ json-api-mocker -o
39
48
 
40
- # Specify config file
41
- json-api-mocker -c ./config.json -o
49
+ # Or specify port (default is 3000)
50
+ json-api-mocker -p 8080 -o
42
51
  ```
43
52
 
44
53
  After starting, the browser will automatically open the management interface, where you can:
package/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  "use strict";
3
3
  var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -10,6 +10,8 @@ const path_1 = require("path");
10
10
  const express_1 = __importDefault(require("express"));
11
11
  const http_1 = require("http");
12
12
  const open_1 = __importDefault(require("open"));
13
+ const WEB_PORT = 66888; // 固定前端页面端口
14
+ const WS_PORT = 88866; // 固定 WebSocket 端口
13
15
  commander_1.program
14
16
  .version(require('../package.json').version)
15
17
  .option('-c, --config <path>', '配置文件路径', 'data.json')
@@ -18,7 +20,11 @@ commander_1.program
18
20
  .parse(process.argv);
19
21
  const options = commander_1.program.opts();
20
22
  // 启动 API Mock 服务器
21
- (0, index_1.startServer)(options.config);
23
+ (0, index_1.startServer)({
24
+ config: options.config,
25
+ port: parseInt(options.port),
26
+ wsPort: WS_PORT
27
+ });
22
28
  // 创建静态文件服务器来托管前端页面
23
29
  const app = (0, express_1.default)();
24
30
  const httpServer = (0, http_1.createServer)(app);
@@ -28,12 +34,10 @@ app.use(express_1.default.static((0, path_1.join)(__dirname, '../web')));
28
34
  app.get('*', (req, res) => {
29
35
  res.sendFile((0, path_1.join)(__dirname, '../web/index.html'));
30
36
  });
31
- // 启动前端服务器
32
- const webPort = parseInt(options.port) + 1;
33
- httpServer.listen(webPort, () => {
34
- console.log(`Web UI is running on http://localhost:${webPort}`);
35
- // 如果指定了 --open 选项,自动打开浏览器
37
+ // 启动前端服务器(使用固定端口)
38
+ httpServer.listen(WEB_PORT, () => {
39
+ console.log(`Web UI is running on http://localhost:${WEB_PORT}`);
36
40
  if (options.open) {
37
- (0, open_1.default)(`http://localhost:${webPort}`);
41
+ (0, open_1.default)(`http://localhost:${WEB_PORT}`);
38
42
  }
39
43
  });
package/dist/index.d.ts CHANGED
@@ -1,3 +1,8 @@
1
- export declare function startServer(configPath?: string): void;
1
+ interface StartOptions {
2
+ config?: string;
3
+ port?: number;
4
+ wsPort?: number;
5
+ }
6
+ export declare function startServer(options?: StartOptions): void;
2
7
  export { MockServer } from './server';
3
8
  export * from './types';
package/dist/index.js CHANGED
@@ -21,12 +21,18 @@ exports.MockServer = exports.startServer = void 0;
21
21
  const fs_1 = __importDefault(require("fs"));
22
22
  const path_1 = __importDefault(require("path"));
23
23
  const server_1 = require("./server");
24
- function startServer(configPath = 'data.json') {
24
+ function startServer(options = {}) {
25
+ const configPath = options.config || 'data.json';
25
26
  const fullPath = path_1.default.resolve(process.cwd(), configPath);
26
27
  try {
27
28
  const configContent = fs_1.default.readFileSync(fullPath, 'utf-8');
28
29
  const config = JSON.parse(configContent);
29
- const server = new server_1.MockServer(config.server, configPath);
30
+ const serverConfig = {
31
+ ...config.server,
32
+ port: options.port || config.server.port,
33
+ wsPort: options.wsPort
34
+ };
35
+ const server = new server_1.MockServer(serverConfig, configPath);
30
36
  server.start();
31
37
  }
32
38
  catch (error) {
package/dist/server.js CHANGED
@@ -43,11 +43,10 @@ class MockServer {
43
43
  this.upload = (0, multer_1.default)({ storage });
44
44
  // 添加文件上传路由
45
45
  this.setupUploadRoute();
46
- // 初始化 WebSocket 服务器
47
- this.wss = new ws_1.WebSocketServer({ port: 8081 });
46
+ // 使用固定的 WebSocket 端口
47
+ this.wss = new ws_1.WebSocketServer({ port: serverConfig.wsPort || 88866 });
48
48
  this.wss.on('connection', (ws) => {
49
49
  this.clients.add(ws);
50
- // 发送现有日志
51
50
  ws.send(JSON.stringify({
52
51
  type: 'init',
53
52
  data: this.logs
package/dist/types.d.ts CHANGED
@@ -32,4 +32,5 @@ export interface RequestLog {
32
32
  export interface Config {
33
33
  port?: number;
34
34
  baseProxy?: string;
35
+ wsPort?: number;
35
36
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "json-api-mocker",
3
- "version": "2.0.0",
4
- "description": "A mock server with web UI",
3
+ "version": "2.0.2",
4
+ "description": "A mock server with visual management interface",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "bin": {
@@ -10,7 +10,11 @@
10
10
  "files": [
11
11
  "dist",
12
12
  "web",
13
- "README.md"
13
+ "README.ch.md",
14
+ "README.md",
15
+ "CONFIG.md",
16
+ "CONFIG.ch.md",
17
+ "DESIGN.md"
14
18
  ],
15
19
  "scripts": {
16
20
  "dev": "nodemon",
@@ -1 +1 @@
1
- .sidebar[data-v-333258da]{width:200px;background-color:#fff;border-right:1px solid #eee;padding:20px 0}.nav-item[data-v-333258da]{display:block;padding:10px 20px;color:#333;text-decoration:none}.nav-item[data-v-333258da]:hover{background-color:#f5f5f5}.nav-item.active[data-v-333258da]{background-color:#e6f7ff;color:#1890ff;border-right:3px solid #1890ff}.app-header[data-v-ecbe1cbc]{background:#fff;padding:0 24px;height:64px;display:flex;align-items:center;justify-content:space-between;box-shadow:0 2px 8px #0000001a}.logo[data-v-ecbe1cbc]{cursor:pointer}.logo h1[data-v-ecbe1cbc]{margin:0;font-size:20px;color:#1890ff}.right[data-v-ecbe1cbc]{display:flex;align-items:center}.github-link[data-v-ecbe1cbc]{color:#666;text-decoration:none;font-size:14px}.github-link[data-v-ecbe1cbc]:hover{color:#1890ff}.app-container[data-v-32f4c818]{min-height:100vh;display:flex;flex-direction:column}.main-container[data-v-32f4c818]{flex:1;display:flex}.content[data-v-32f4c818]{flex:1;padding:20px;background-color:#f5f5f5}.api-list[data-v-f00969e3]{padding:20px}.header[data-v-f00969e3]{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.create-btn[data-v-f00969e3]{padding:8px 16px;background-color:#1890ff;color:#fff;border:none;border-radius:4px;cursor:pointer}.create-btn[data-v-f00969e3]:hover{background-color:#40a9ff}.api-item[data-v-f00969e3]{display:flex;justify-content:space-between;align-items:center;padding:16px;background:#fff;border-radius:4px;margin-bottom:12px;box-shadow:0 2px 8px #0000001a}.methods[data-v-f00969e3]{display:flex;gap:8px;margin-top:8px}.method-tag[data-v-f00969e3]{padding:2px 8px;background:#e6f7ff;color:#1890ff;border-radius:4px;font-size:12px}.actions[data-v-f00969e3]{display:flex;gap:8px}.actions button[data-v-f00969e3]{padding:4px 12px;border:none;border-radius:4px;cursor:pointer;transition:all .3s}.edit-btn[data-v-f00969e3]{background:#1890ff;color:#fff}.edit-btn[data-v-f00969e3]:hover{background:#40a9ff}.delete-btn[data-v-f00969e3]{background:#ff4d4f;color:#fff}.delete-btn[data-v-f00969e3]:hover{background:#ff7875}.loading[data-v-f00969e3],.error[data-v-f00969e3],.empty[data-v-f00969e3]{text-align:center;padding:40px;color:#666}.error[data-v-f00969e3]{color:#ff4d4f}.api-editor[data-v-d5d7a1e1]{padding:20px}.header[data-v-d5d7a1e1]{margin-bottom:24px}.form[data-v-d5d7a1e1]{background:#fff;padding:24px;border-radius:8px;box-shadow:0 2px 8px #0000001a}.form-group[data-v-d5d7a1e1]{margin-bottom:24px;overflow:visible}.form-group label[data-v-d5d7a1e1]{display:block;margin-bottom:12px;color:#333;font-weight:500}.input[data-v-d5d7a1e1]{width:100%;padding:8px;border:1px solid #d9d9d9;border-radius:4px}.methods-section[data-v-d5d7a1e1]{margin-top:24px}.methods-header[data-v-d5d7a1e1]{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.method-selector[data-v-d5d7a1e1]{display:flex;gap:8px}.method-select[data-v-d5d7a1e1]{padding:4px 8px;border:1px solid #d9d9d9;border-radius:4px}.method-config[data-v-d5d7a1e1]{background:#f5f5f5;padding:24px;border-radius:4px;margin-bottom:24px;overflow:visible;min-height:600px}.method-header[data-v-d5d7a1e1]{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.editor-wrapper[data-v-d5d7a1e1]{position:relative;border:1px solid #d9d9d9;border-radius:4px;overflow:hidden;margin-bottom:8px;height:200px}.monaco-editor-container[data-v-d5d7a1e1]{width:100%;height:100%!important;display:block}.helper-text[data-v-d5d7a1e1]{margin-top:8px;font-size:12px;color:#666}.helper-text a[data-v-d5d7a1e1]{color:#1890ff;text-decoration:none}.actions[data-v-d5d7a1e1]{margin-top:24px;display:flex;justify-content:flex-end;gap:12px}.add-btn[data-v-d5d7a1e1]{padding:4px 12px;background:#1890ff;color:#fff;border:none;border-radius:4px;cursor:pointer}.remove-btn[data-v-d5d7a1e1]{padding:4px 8px;background:#ff4d4f;color:#fff;border:none;border-radius:4px;cursor:pointer}.save-btn[data-v-d5d7a1e1]{padding:8px 16px;background:#1890ff;color:#fff;border:none;border-radius:4px;cursor:pointer}.cancel-btn[data-v-d5d7a1e1]{padding:8px 16px;background:#f0f0f0;border:none;border-radius:4px;cursor:pointer}.form-group:has(label:contains("响应头")) .editor-wrapper[data-v-d5d7a1e1]{height:200px}[data-v-d5d7a1e1] .monaco-editor,[data-v-d5d7a1e1] .monaco-editor .overflow-guard{height:100%!important}.pagination-settings[data-v-d5d7a1e1]{display:flex;gap:20px;margin-top:10px}.fields-header[data-v-d5d7a1e1]{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}.field-item[data-v-d5d7a1e1]{display:flex;gap:10px;margin-bottom:10px}.field-name[data-v-d5d7a1e1],.field-type[data-v-d5d7a1e1],.field-mock[data-v-d5d7a1e1]{width:30%}.preview-textarea[data-v-d5d7a1e1]{width:100%;min-height:300px;padding:15px;font-family:monospace;background:#f5f5f5;border:1px solid #d9d9d9;border-radius:4px;resize:vertical}.data-count-settings[data-v-d5d7a1e1]{margin-top:10px}.data-count-settings input[data-v-d5d7a1e1]{width:200px}@media (max-width: 768px){.field-item[data-v-d5d7a1e1]{flex-direction:column}.field-name[data-v-d5d7a1e1],.field-type[data-v-d5d7a1e1],.field-mock[data-v-d5d7a1e1]{width:100%}}.dashboard[data-v-a73cca79]{padding:20px}.stats[data-v-a73cca79]{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:20px;margin:24px 0}.stat-card[data-v-a73cca79]{background:#fff;padding:20px;border-radius:8px;box-shadow:0 2px 8px #0000001a}.stat-value[data-v-a73cca79]{font-size:32px;font-weight:700;color:#1890ff;margin-top:8px}.logs-section[data-v-a73cca79]{background:#fff;padding:20px;border-radius:8px;box-shadow:0 2px 8px #0000001a}.logs-header[data-v-a73cca79]{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.clear-btn[data-v-a73cca79]{padding:4px 12px;background:#ff4d4f;color:#fff;border:none;border-radius:4px;cursor:pointer}.logs-table[data-v-a73cca79]{overflow-x:auto}table[data-v-a73cca79]{width:100%;border-collapse:collapse}th[data-v-a73cca79],td[data-v-a73cca79]{padding:12px;text-align:left;border-bottom:1px solid #f0f0f0}th[data-v-a73cca79]{background:#fafafa;font-weight:500}.success[data-v-a73cca79]{color:#52c41a}.error[data-v-a73cca79]{color:#ff4d4f}.pagination[data-v-a73cca79]{display:flex;justify-content:center;align-items:center;gap:12px;margin-top:20px}.pagination button[data-v-a73cca79]{padding:4px 12px;background:#1890ff;color:#fff;border:none;border-radius:4px;cursor:pointer}.pagination button[data-v-a73cca79]:disabled{background:#d9d9d9;cursor:not-allowed}.empty-logs[data-v-a73cca79]{text-align:center;padding:40px;color:#666}.params[data-v-a73cca79]{max-height:100px;overflow:auto;font-size:12px;background:#f5f5f5;padding:4px;border-radius:4px;margin:0}
1
+ .sidebar[data-v-333258da]{width:200px;background-color:#fff;border-right:1px solid #eee;padding:20px 0}.nav-item[data-v-333258da]{display:block;padding:10px 20px;color:#333;text-decoration:none}.nav-item[data-v-333258da]:hover{background-color:#f5f5f5}.nav-item.active[data-v-333258da]{background-color:#e6f7ff;color:#1890ff;border-right:3px solid #1890ff}.app-header[data-v-ecbe1cbc]{background:#fff;padding:0 24px;height:64px;display:flex;align-items:center;justify-content:space-between;box-shadow:0 2px 8px #0000001a}.logo[data-v-ecbe1cbc]{cursor:pointer}.logo h1[data-v-ecbe1cbc]{margin:0;font-size:20px;color:#1890ff}.right[data-v-ecbe1cbc]{display:flex;align-items:center}.github-link[data-v-ecbe1cbc]{color:#666;text-decoration:none;font-size:14px}.github-link[data-v-ecbe1cbc]:hover{color:#1890ff}.app-container[data-v-32f4c818]{min-height:100vh;display:flex;flex-direction:column}.main-container[data-v-32f4c818]{flex:1;display:flex}.content[data-v-32f4c818]{flex:1;padding:20px;background-color:#f5f5f5}.api-list[data-v-f00969e3]{padding:20px}.header[data-v-f00969e3]{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.create-btn[data-v-f00969e3]{padding:8px 16px;background-color:#1890ff;color:#fff;border:none;border-radius:4px;cursor:pointer}.create-btn[data-v-f00969e3]:hover{background-color:#40a9ff}.api-item[data-v-f00969e3]{display:flex;justify-content:space-between;align-items:center;padding:16px;background:#fff;border-radius:4px;margin-bottom:12px;box-shadow:0 2px 8px #0000001a}.methods[data-v-f00969e3]{display:flex;gap:8px;margin-top:8px}.method-tag[data-v-f00969e3]{padding:2px 8px;background:#e6f7ff;color:#1890ff;border-radius:4px;font-size:12px}.actions[data-v-f00969e3]{display:flex;gap:8px}.actions button[data-v-f00969e3]{padding:4px 12px;border:none;border-radius:4px;cursor:pointer;transition:all .3s}.edit-btn[data-v-f00969e3]{background:#1890ff;color:#fff}.edit-btn[data-v-f00969e3]:hover{background:#40a9ff}.delete-btn[data-v-f00969e3]{background:#ff4d4f;color:#fff}.delete-btn[data-v-f00969e3]:hover{background:#ff7875}.loading[data-v-f00969e3],.error[data-v-f00969e3],.empty[data-v-f00969e3]{text-align:center;padding:40px;color:#666}.error[data-v-f00969e3]{color:#ff4d4f}.api-editor[data-v-d5d7a1e1]{padding:20px}.header[data-v-d5d7a1e1]{margin-bottom:24px}.form[data-v-d5d7a1e1]{background:#fff;padding:24px;border-radius:8px;box-shadow:0 2px 8px #0000001a}.form-group[data-v-d5d7a1e1]{margin-bottom:24px;overflow:visible}.form-group label[data-v-d5d7a1e1]{display:block;margin-bottom:12px;color:#333;font-weight:500}.input[data-v-d5d7a1e1]{width:100%;padding:8px;border:1px solid #d9d9d9;border-radius:4px}.methods-section[data-v-d5d7a1e1]{margin-top:24px}.methods-header[data-v-d5d7a1e1]{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.method-selector[data-v-d5d7a1e1]{display:flex;gap:8px}.method-select[data-v-d5d7a1e1]{padding:4px 8px;border:1px solid #d9d9d9;border-radius:4px}.method-config[data-v-d5d7a1e1]{background:#f5f5f5;padding:24px;border-radius:4px;margin-bottom:24px;overflow:visible;min-height:600px}.method-header[data-v-d5d7a1e1]{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.editor-wrapper[data-v-d5d7a1e1]{position:relative;border:1px solid #d9d9d9;border-radius:4px;overflow:hidden;margin-bottom:8px;height:200px}.monaco-editor-container[data-v-d5d7a1e1]{width:100%;height:100%!important;display:block}.helper-text[data-v-d5d7a1e1]{margin-top:8px;font-size:12px;color:#666}.helper-text a[data-v-d5d7a1e1]{color:#1890ff;text-decoration:none}.actions[data-v-d5d7a1e1]{margin-top:24px;display:flex;justify-content:flex-end;gap:12px}.add-btn[data-v-d5d7a1e1]{padding:4px 12px;background:#1890ff;color:#fff;border:none;border-radius:4px;cursor:pointer}.remove-btn[data-v-d5d7a1e1]{padding:4px 8px;background:#ff4d4f;color:#fff;border:none;border-radius:4px;cursor:pointer}.save-btn[data-v-d5d7a1e1]{padding:8px 16px;background:#1890ff;color:#fff;border:none;border-radius:4px;cursor:pointer}.cancel-btn[data-v-d5d7a1e1]{padding:8px 16px;background:#f0f0f0;border:none;border-radius:4px;cursor:pointer}.form-group:has(label:contains("响应头")) .editor-wrapper[data-v-d5d7a1e1]{height:200px}[data-v-d5d7a1e1] .monaco-editor,[data-v-d5d7a1e1] .monaco-editor .overflow-guard{height:100%!important}.pagination-settings[data-v-d5d7a1e1]{display:flex;gap:20px;margin-top:10px}.fields-header[data-v-d5d7a1e1]{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}.field-item[data-v-d5d7a1e1]{display:flex;gap:10px;margin-bottom:10px}.field-name[data-v-d5d7a1e1],.field-type[data-v-d5d7a1e1],.field-mock[data-v-d5d7a1e1]{width:30%}.preview-textarea[data-v-d5d7a1e1]{width:100%;min-height:300px;padding:15px;font-family:monospace;background:#f5f5f5;border:1px solid #d9d9d9;border-radius:4px;resize:vertical}.data-count-settings[data-v-d5d7a1e1]{margin-top:10px}.data-count-settings input[data-v-d5d7a1e1]{width:200px}@media (max-width: 768px){.field-item[data-v-d5d7a1e1]{flex-direction:column}.field-name[data-v-d5d7a1e1],.field-type[data-v-d5d7a1e1],.field-mock[data-v-d5d7a1e1]{width:100%}}.dashboard[data-v-3f602eb2]{padding:20px}.stats[data-v-3f602eb2]{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:20px;margin:24px 0}.stat-card[data-v-3f602eb2]{background:#fff;padding:20px;border-radius:8px;box-shadow:0 2px 8px #0000001a}.stat-value[data-v-3f602eb2]{font-size:32px;font-weight:700;color:#1890ff;margin-top:8px}.logs-section[data-v-3f602eb2]{background:#fff;padding:20px;border-radius:8px;box-shadow:0 2px 8px #0000001a}.logs-header[data-v-3f602eb2]{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.clear-btn[data-v-3f602eb2]{padding:4px 12px;background:#ff4d4f;color:#fff;border:none;border-radius:4px;cursor:pointer}.logs-table[data-v-3f602eb2]{overflow-x:auto}table[data-v-3f602eb2]{width:100%;border-collapse:collapse}th[data-v-3f602eb2],td[data-v-3f602eb2]{padding:12px;text-align:left;border-bottom:1px solid #f0f0f0}th[data-v-3f602eb2]{background:#fafafa;font-weight:500}.success[data-v-3f602eb2]{color:#52c41a}.error[data-v-3f602eb2]{color:#ff4d4f}.pagination[data-v-3f602eb2]{display:flex;justify-content:center;align-items:center;gap:12px;margin-top:20px}.pagination button[data-v-3f602eb2]{padding:4px 12px;background:#1890ff;color:#fff;border:none;border-radius:4px;cursor:pointer}.pagination button[data-v-3f602eb2]:disabled{background:#d9d9d9;cursor:not-allowed}.empty-logs[data-v-3f602eb2]{text-align:center;padding:40px;color:#666}.params[data-v-3f602eb2]{max-height:100px;overflow:auto;font-size:12px;background:#f5f5f5;padding:4px;border-radius:4px;margin:0}