warehouse-mock 1.0.0 → 1.1.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/README.md CHANGED
@@ -1,90 +1,39 @@
1
- # warehouse-mock
1
+ # warehouse-mock-plugin
2
2
 
3
- 一个专为 Vue 2 项目设计的 Webpack 插件,支持 RPC 风格接口 mock,零业务代码侵入,实时更新。
3
+ <div align="center">
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/warehouse-mock.svg)](https://www.npmjs.com/package/warehouse-mock)
6
- [![license](https://img.shields.io/npm/l/warehouse-mock.svg)](https://github.com/CodeMomentYY/warehouse-mock/blob/main/LICENSE)
5
+ 极简 Vue Mock 插件,零业务代码侵入,完美支持 RPC 风格接口
7
6
 
8
- ## ✨ 特性
7
+ [![npm version](https://img.shields.io/npm/v/warehouse-mock-plugin.svg)](https://www.npmjs.com/package/warehouse-mock-plugin)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
9
9
 
10
- - ✅ **RPC 风格接口支持**: 完美支持 `/api?user.account.getInfo` 格式
11
- - ✅ **零业务代码侵入**: 只需修改配置文件,无需改动业务逻辑
12
- - ✅ **实时更新**: 修改 Mock 数据后刷新页面即可,无需重启服务
13
- - ✅ **按需 Mock**: 只拦截配置了 Mock 数据的接口,其他接口不受影响
14
- - ✅ **TypeScript 编写**: 类型安全,易于维护
15
- - ✅ **兼容性强**: 支持 Webpack 4/5,Vue CLI 3/4/5
10
+ </div>
16
11
 
17
- ## 📦 安装
12
+ ## 安装
18
13
 
19
14
  ```bash
20
- npm install warehouse-mock --save-dev
15
+ npm install warehouse-mock-plugin --save-dev
21
16
  ```
22
17
 
23
- ## 🚀 快速开始
18
+ ## 快速开始
24
19
 
25
20
  ### 1. 配置 vue.config.js
26
21
 
27
22
  ```javascript
28
- const WarehouseMockPlugin = require('warehouse-mock');
29
- const webpack = require('webpack');
30
- const path = require('path');
23
+ const WarehouseMockPlugin = require('warehouse-mock-plugin');
31
24
 
32
25
  const isMock = process.env.MOCK === 'true';
33
- const mockPlugin = isMock ? new WarehouseMockPlugin({
34
- mockPath: path.resolve(__dirname, 'warehouseMock'),
35
- }) : null;
36
26
 
37
27
  module.exports = {
38
28
  configureWebpack: config => {
39
- if (isMock && mockPlugin) {
40
- config.plugins.push(
41
- new webpack.DefinePlugin({
42
- 'process.env.VUE_APP_MOCK': JSON.stringify('true'),
43
- })
44
- );
45
- config.plugins.push(mockPlugin);
29
+ if (isMock) {
30
+ config.plugins.push(new WarehouseMockPlugin());
46
31
  }
47
- },
48
-
49
- devServer: {
50
- // Vue CLI 5.x (Webpack 5)
51
- setupMiddlewares: (middlewares, devServer) => {
52
- if (isMock && mockPlugin) {
53
- return mockPlugin.setupMiddlewares(middlewares, devServer);
54
- }
55
- return middlewares;
56
- },
57
-
58
- // Vue CLI 3.x/4.x (Webpack 4) 使用这个:
59
- // before: isMock && mockPlugin ? (app) => mockPlugin.runBefore(app) : undefined,
60
32
  }
61
33
  };
62
34
  ```
63
35
 
64
- ### 2. 修改 API 配置
65
-
66
- 在 `src/const/config.js` 中添加 Mock 模式判断:
67
-
68
- ```javascript
69
- const config = { env: process.env.VUE_APP_ENV || 'dev' };
70
-
71
- // Mock 模式优先判断
72
- if (process.env.VUE_APP_MOCK === 'true') {
73
- Object.assign(config, {
74
- API: '/mock-api',
75
- });
76
- } else if (config.env === 'dev') {
77
- Object.assign(config, {
78
- API: 'https://fat-api.hellobike.com/api',
79
- });
80
- }
81
-
82
- export default config;
83
- ```
84
-
85
- ### 3. 添加 Mock 脚本
86
-
87
- 在 `package.json` 中:
36
+ ### 2. 添加 Mock 脚本
88
37
 
89
38
  ```json
90
39
  {
@@ -94,114 +43,133 @@ export default config;
94
43
  }
95
44
  ```
96
45
 
97
- ### 4. 创建 Mock 数据
46
+ ### 3. 配置 API 切换
98
47
 
99
- 创建 `warehouseMock` 目录并添加 Mock 数据文件:
48
+ `src/const/config.js` 中:
100
49
 
101
- **warehouseMock/user.account.getInfo.json**:
102
- ```json
103
- {
104
- "code": 0,
105
- "msg": "success",
106
- "data": {
107
- "userId": 12345,
108
- "userName": "Mock 测试用户"
109
- }
50
+ ```javascript
51
+ if (process.env.VUE_APP_MOCK === 'true') {
52
+ config.API = '/mock-api';
53
+ } else {
54
+ config.API = 'https://your-api.com';
110
55
  }
111
56
  ```
112
57
 
58
+ ### 4. 创建 Mock 数据
59
+
60
+ 在项目根目录创建 `warehouseMock/` 文件夹,添加 JSON 文件:
61
+
62
+ ```
63
+ warehouseMock/
64
+ ├── user.account.getInfo.json
65
+ └── user.taurus.pointInfo.json
66
+ ```
67
+
113
68
  ### 5. 启动
114
69
 
115
70
  ```bash
116
71
  npm run mock
117
72
  ```
118
73
 
119
- ## ⚙️ 配置选项
74
+ ## 配置选项
120
75
 
121
76
  ```typescript
122
- new WarehouseMockPlugin({
123
- // 必填:Mock 数据存放目录
124
- mockPath: string;
77
+ interface MockPluginOptions {
78
+ // Mock 数据目录,默认 'warehouseMock'
79
+ mockPath?: string;
125
80
 
126
- // 可选:需要拦截的 API 路径前缀,默认 ['/api', '/mock-api']
81
+ // 需要拦截的 API 路径前缀,默认 ['/api', '/mock-api']
127
82
  apiPrefixes?: string[];
128
83
 
129
- // 可选:本地 API 路径前缀,默认 '/mock-api'
84
+ // 本地代理路径前缀,默认 '/mock-api'
130
85
  localApiPrefix?: string;
131
- })
86
+
87
+ // 是否启用,默认 true
88
+ enabled?: boolean;
89
+
90
+ // 是否自动注入环境变量 VUE_APP_MOCK,默认 true
91
+ injectEnv?: boolean;
92
+
93
+ // 响应延迟(毫秒),模拟网络延迟,默认 0
94
+ delay?: number;
95
+
96
+ // 代理配置:未匹配的请求转发到真实 API
97
+ proxy?: {
98
+ target: string;
99
+ changeOrigin?: boolean;
100
+ };
101
+ }
132
102
  ```
133
103
 
134
- ## 📝 Mock 文件命名规则
135
-
136
- ### RPC 风格(推荐)
137
-
138
- | 请求 URL | Mock 文件名 |
139
- |---------|-----------|
140
- | `/api?user.account.getInfo` | `user.account.getInfo.json` |
141
- | `/api?user.taurus.pointInfo` | `user.taurus.pointInfo.json` |
142
- | `/api?common.welfare.banner.query` | `common.welfare.banner.query.json` |
143
-
144
- ### RESTful 风格
104
+ ## 使用示例
145
105
 
146
- | 请求 URL | Mock 文件名(二选一) |
147
- |---------|---------------------|
148
- | `/api/user/info` | `api_user_info.json` (扁平化) |
149
- | `/api/user/info` | `api/user/info.json` (嵌套) |
106
+ ### 基础配置
150
107
 
151
- ## 🔍 调试工具
152
-
153
- ### 查看可用 Mock 列表
154
-
155
- 访问: `http://localhost:8080/__mock_list__`
156
-
157
- ### 直接访问 Mock 数据
158
-
159
- 访问: `http://localhost:8080/mock-api?user.account.getInfo`
108
+ ```javascript
109
+ new WarehouseMockPlugin({
110
+ mockPath: 'warehouseMock',
111
+ apiPrefixes: ['/api', '/mock-api']
112
+ })
113
+ ```
160
114
 
161
- ## 💡 为什么选择 warehouse-mock?
115
+ ### 代理模式
162
116
 
163
- ### 适用场景
117
+ 未匹配的请求转发到真实 API:
164
118
 
165
- - ✅ 后端接口未就绪,需要前端先行开发
166
- - ✅ 后端接口不稳定,需要本地 Mock 数据测试
167
- - ✅ 需要模拟特殊场景(错误、超时、边界数据等)
168
- - ✅ 使用 RPC 风格接口的项目(如 hellobike
119
+ ```javascript
120
+ new WarehouseMockPlugin({
121
+ proxy: {
122
+ target: 'https://fat-api.hellobike.com',
123
+ changeOrigin: true
124
+ }
125
+ })
126
+ ```
169
127
 
170
- ### 对比其他方案
128
+ ### 模拟网络延迟
171
129
 
172
- | 方案 | warehouse-mock | Mock.js | json-server |
173
- |-----|---------------|---------|-------------|
174
- | 零代码侵入 | ✅ | ❌ 需修改业务代码 | ❌ 需单独启动服务 |
175
- | RPC 支持 | ✅ | ❌ | ❌ |
176
- | 实时更新 | ✅ | ❌ | ✅ |
177
- | TypeScript | ✅ | ❌ | ❌ |
178
- | Webpack 集成 | ✅ | ❌ | ❌ |
130
+ ```javascript
131
+ new WarehouseMockPlugin({
132
+ delay: 500 // 500ms 延迟
133
+ })
134
+ ```
179
135
 
180
- ## 📚 完整示例
136
+ ## Mock 文件命名规则
181
137
 
182
- 查看完整的示例项目:[example-vue2](https://github.com/CodeMomentYY/warehouse-mock/tree/main/packages/example-vue2)
138
+ ### RPC 风格 (推荐)
183
139
 
184
- ## 常见问题
140
+ | 请求 URL | Mock 文件名 |
141
+ |---------|-----------|
142
+ | `GET /api?user.account.getInfo` | `user.account.getInfo.json` |
143
+ | `POST /mock-api?user.taurus.pointInfo` | `user.taurus.pointInfo.json` |
185
144
 
186
- **Q: Mock 没有生效?**
145
+ ### RESTful 风格
187
146
 
188
- 确保在 `src/const/config.js` 中添加了 Mock 模式判断,将 API 地址改为 `/mock-api`。
147
+ | 请求 URL | Mock 文件名 |
148
+ |---------|-----------|
149
+ | `GET /api/user/info` | `api_user_info.json` (扁平化) |
150
+ | `GET /api/user/info` | `api/user/info.json` (嵌套) |
189
151
 
190
- **Q: 修改 Mock 数据后需要重启服务吗?**
152
+ ## 调试工具
191
153
 
192
- 不需要!直接刷新浏览器页面即可。
154
+ ### 查看可用 Mock 列表
193
155
 
194
- **Q: 可以只 Mock 部分接口吗?**
156
+ 访问: `http://localhost:8080/__mock_list__`
195
157
 
196
- 可以!只有存在对应 JSON 文件的接口才会使用 Mock 数据。
158
+ ### 直接访问 Mock 数据
197
159
 
198
- ## 📄 许可证
160
+ 访问: `http://localhost:8080/mock-api?user.account.getInfo`
199
161
 
200
- MIT © CodeMomentYY
162
+ ## 核心特性
201
163
 
202
- ## 🔗 相关链接
164
+ - **极简配置** - vue.config.js 只需 3 行代码
165
+ - ✅ **零业务侵入** - 无需修改接口调用代码
166
+ - ✅ **RPC 风格支持** - 原生支持 RPC 接口
167
+ - ✅ **实时热更新** - 修改 Mock 数据,刷新即可
168
+ - ✅ **按需拦截** - 只拦截配置了 Mock 文件的接口
169
+ - ✅ **代理模式** - 未匹配请求转发到真实 API
170
+ - ✅ **自动注入环境变量** - 无需手动配置 DefinePlugin
171
+ - ✅ **兼容性强** - 支持 Webpack 4/5,Vue CLI 3/4/5
203
172
 
204
- - [GitHub 仓库](https://github.com/CodeMomentYY/warehouse-mock)
205
- - [问题反馈](https://github.com/CodeMomentYY/warehouse-mock/issues)
206
- - [更新日志](https://github.com/CodeMomentYY/warehouse-mock/releases)
173
+ ## License
207
174
 
175
+ MIT
package/dist/index.d.ts CHANGED
@@ -1,13 +1,26 @@
1
1
  import { Compiler } from 'webpack';
2
2
  interface MockPluginOptions {
3
- mockPath: string;
3
+ mockPath?: string;
4
4
  apiPrefixes?: string[];
5
5
  localApiPrefix?: string;
6
+ enabled?: boolean;
7
+ proxy?: {
8
+ target: string;
9
+ changeOrigin?: boolean;
10
+ };
11
+ injectEnv?: boolean;
12
+ delay?: number;
13
+ admin?: {
14
+ enabled?: boolean;
15
+ port?: number;
16
+ };
6
17
  }
7
18
  declare class WarehouseMockPlugin {
8
19
  private options;
9
20
  private resolvedMockPath;
10
- constructor(options: MockPluginOptions);
21
+ private isEnabled;
22
+ private adminServer;
23
+ constructor(options?: MockPluginOptions);
11
24
  /**
12
25
  * 实时扫描 mock 目录,获取所有 mock 文件名列表
13
26
  */
@@ -17,6 +30,14 @@ declare class WarehouseMockPlugin {
17
30
  */
18
31
  getLocalApiPrefix(): string;
19
32
  apply(compiler: Compiler): void;
33
+ /**
34
+ * 启动管理后台服务
35
+ */
36
+ private startAdminServer;
37
+ /**
38
+ * 自动注入环境变量 VUE_APP_MOCK
39
+ */
40
+ private injectEnvironmentVariable;
20
41
  /**
21
42
  * 公共方法:设置中间件 (Webpack 5 / Vue CLI 5+)
22
43
  */
@@ -28,6 +49,11 @@ declare class WarehouseMockPlugin {
28
49
  private getResolvedMockPath;
29
50
  private ensureMockDirectory;
30
51
  private createDemoFile;
52
+ private readInterfaceConfig;
31
53
  private handleRequest;
54
+ /**
55
+ * 代理请求到真实 API(改进版)
56
+ */
57
+ private proxyRequest;
32
58
  }
33
59
  export = WarehouseMockPlugin;
package/dist/index.js CHANGED
@@ -6,9 +6,32 @@ const fs_1 = __importDefault(require("fs"));
6
6
  const path_1 = __importDefault(require("path"));
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
8
  class WarehouseMockPlugin {
9
- constructor(options) {
9
+ constructor(options = {}) {
10
+ var _a, _b;
10
11
  this.resolvedMockPath = '';
11
- this.options = Object.assign({ apiPrefixes: ['/api', '/mock-api'], localApiPrefix: '/mock-api' }, options);
12
+ this.isEnabled = true;
13
+ this.adminServer = null;
14
+ // 默认配置
15
+ this.options = {
16
+ mockPath: options.mockPath || 'warehouseMock',
17
+ apiPrefixes: options.apiPrefixes || ['/api', '/mock-api'],
18
+ localApiPrefix: options.localApiPrefix || '/mock-api',
19
+ enabled: options.enabled !== undefined ? options.enabled : true,
20
+ proxy: options.proxy || undefined,
21
+ injectEnv: options.injectEnv !== undefined ? options.injectEnv : true,
22
+ delay: options.delay || 0,
23
+ admin: {
24
+ enabled: ((_a = options.admin) === null || _a === void 0 ? void 0 : _a.enabled) !== undefined ? options.admin.enabled : true,
25
+ port: ((_b = options.admin) === null || _b === void 0 ? void 0 : _b.port) || 3100,
26
+ },
27
+ };
28
+ // 判断是否启用(支持环境变量控制)
29
+ if (process.env.MOCK === 'false' || process.env.VUE_APP_MOCK === 'false') {
30
+ this.isEnabled = false;
31
+ }
32
+ if (this.options.enabled === false) {
33
+ this.isEnabled = false;
34
+ }
12
35
  }
13
36
  /**
14
37
  * 实时扫描 mock 目录,获取所有 mock 文件名列表
@@ -33,7 +56,16 @@ class WarehouseMockPlugin {
33
56
  return this.options.localApiPrefix || '/mock-api';
34
57
  }
35
58
  apply(compiler) {
59
+ // 如果未启用,直接返回
60
+ if (!this.isEnabled) {
61
+ console.log(chalk_1.default.yellow('[WarehouseMock] Mock 模式未启用'));
62
+ return;
63
+ }
36
64
  this.resolvedMockPath = path_1.default.resolve(compiler.context, this.options.mockPath);
65
+ // 自动注入环境变量 VUE_APP_MOCK
66
+ if (this.options.injectEnv) {
67
+ this.injectEnvironmentVariable(compiler);
68
+ }
37
69
  // 确保 mock 目录存在
38
70
  if (!fs_1.default.existsSync(this.resolvedMockPath)) {
39
71
  try {
@@ -46,21 +78,96 @@ class WarehouseMockPlugin {
46
78
  }
47
79
  }
48
80
  const mockFileList = this.getMockFileList();
49
- console.log(chalk_1.default.cyan(`[WarehouseMock] 已加载 ${mockFileList.length} 个 mock 文件: ${mockFileList.join(', ')}`));
50
- const devServerOptions = compiler.options.devServer || {};
51
- const originalSetupMiddlewares = devServerOptions.setupMiddlewares;
52
- if (!compiler.options.devServer) {
53
- compiler.options.devServer = {};
54
- }
55
- // 适用于 Webpack 5 (setupMiddlewares)
56
- compiler.options.devServer.setupMiddlewares = (middlewares, devServer) => {
57
- return this.setupMiddlewares(middlewares, devServer, originalSetupMiddlewares);
58
- };
59
- // 适用于 Webpack 4 (before)
60
- const originalBefore = devServerOptions.before;
61
- compiler.options.devServer.before = (app, server, compilerArg) => {
62
- this.runBefore(app, server, compilerArg, originalBefore);
63
- };
81
+ console.log(chalk_1.default.cyan(`[WarehouseMock] 已加载 ${mockFileList.length} 个 mock 文件`));
82
+ if (mockFileList.length > 0) {
83
+ console.log(chalk_1.default.gray(` → ${mockFileList.join(', ')}`));
84
+ }
85
+ if (this.options.proxy) {
86
+ console.log(chalk_1.default.cyan(`[WarehouseMock] 代理模式: 未匹配请求 → ${this.options.proxy.target}`));
87
+ }
88
+ // 启动管理后台
89
+ if (this.options.admin && this.options.admin.enabled) {
90
+ this.startAdminServer();
91
+ }
92
+ // 注意:devServer 的配置需要在 vue.config.js 中手动配置
93
+ // 对于 Webpack 5 使用 setupMiddlewares
94
+ // 对于 Webpack 4 使用 before
95
+ }
96
+ /**
97
+ * 启动管理后台服务
98
+ */
99
+ startAdminServer() {
100
+ var _a;
101
+ try {
102
+ // 动态导入管理后台服务
103
+ let createAdminServer;
104
+ try {
105
+ // 尝试导入已安装的包
106
+ createAdminServer = require('warehouse-mock-admin').createAdminServer;
107
+ }
108
+ catch (e) {
109
+ // 如果找不到,尝试相对路径(开发环境)
110
+ const adminPath = require('path').resolve(__dirname, '../../mock-admin/dist/server/index.js');
111
+ createAdminServer = require(adminPath).createAdminServer;
112
+ }
113
+ if (!createAdminServer) {
114
+ throw new Error('无法加载管理后台模块');
115
+ }
116
+ const adminResult = createAdminServer({
117
+ mockPath: this.resolvedMockPath,
118
+ port: ((_a = this.options.admin) === null || _a === void 0 ? void 0 : _a.port) || 3100,
119
+ });
120
+ this.adminServer = adminResult;
121
+ console.log(chalk_1.default.green(`\n╭────────────────────────────────────────────────────╮`));
122
+ console.log(chalk_1.default.green(`│ 🎨 Mock 数据管理后台已启动 │`));
123
+ console.log(chalk_1.default.green(`│ │`));
124
+ console.log(chalk_1.default.green(`│ ➜ 访问地址: ${chalk_1.default.cyan(adminResult.url).padEnd(31)} │`));
125
+ console.log(chalk_1.default.green(`│ │`));
126
+ console.log(chalk_1.default.green(`│ 在浏览器中打开上面的地址来管理 Mock 数据 │`));
127
+ console.log(chalk_1.default.green(`╰────────────────────────────────────────────────────╯\n`));
128
+ }
129
+ catch (err) {
130
+ console.log(chalk_1.default.yellow('[WarehouseMock] 管理后台包未安装或加载失败,跳过启动'));
131
+ console.log(chalk_1.default.gray(' 提示:运行 npm install warehouse-mock-admin 来安装管理后台'));
132
+ if (err.message && !err.message.includes('Cannot find module')) {
133
+ console.log(chalk_1.default.gray(` 错误详情: ${err.message}`));
134
+ }
135
+ }
136
+ }
137
+ /**
138
+ * 自动注入环境变量 VUE_APP_MOCK
139
+ */
140
+ injectEnvironmentVariable(compiler) {
141
+ try {
142
+ // 使用 compiler 的 webpack 实例
143
+ const { webpack } = compiler;
144
+ if (webpack && webpack.DefinePlugin) {
145
+ const definePlugin = new webpack.DefinePlugin({
146
+ 'process.env.VUE_APP_MOCK': JSON.stringify('true'),
147
+ });
148
+ if (!compiler.options.plugins) {
149
+ compiler.options.plugins = [];
150
+ }
151
+ compiler.options.plugins.push(definePlugin);
152
+ console.log(chalk_1.default.gray('[WarehouseMock] 已注入环境变量: VUE_APP_MOCK=true'));
153
+ }
154
+ else {
155
+ // 备用方案:直接require webpack
156
+ const webpackModule = require('webpack');
157
+ const definePlugin = new webpackModule.DefinePlugin({
158
+ 'process.env.VUE_APP_MOCK': JSON.stringify('true'),
159
+ });
160
+ if (!compiler.options.plugins) {
161
+ compiler.options.plugins = [];
162
+ }
163
+ compiler.options.plugins.push(definePlugin);
164
+ console.log(chalk_1.default.gray('[WarehouseMock] 已注入环境变量: VUE_APP_MOCK=true'));
165
+ }
166
+ }
167
+ catch (err) {
168
+ console.error(chalk_1.default.red(`[WarehouseMock] 注入环境变量失败: ${err}`));
169
+ console.log(chalk_1.default.yellow('[WarehouseMock] 请在 vue.config.js 中手动配置 DefinePlugin'));
170
+ }
64
171
  }
65
172
  /**
66
173
  * 公共方法:设置中间件 (Webpack 5 / Vue CLI 5+)
@@ -130,8 +237,22 @@ class WarehouseMockPlugin {
130
237
  console.error(chalk_1.default.yellow(`[WarehouseMock] 创建示例文件失败: ${e}`));
131
238
  }
132
239
  }
240
+ // 读取接口配置
241
+ readInterfaceConfig(apiName, mockPath) {
242
+ const configPath = path_1.default.join(mockPath, apiName, '.config.json');
243
+ if (fs_1.default.existsSync(configPath)) {
244
+ try {
245
+ const content = fs_1.default.readFileSync(configPath, 'utf-8');
246
+ return JSON.parse(content);
247
+ }
248
+ catch (e) {
249
+ return null;
250
+ }
251
+ }
252
+ return null;
253
+ }
133
254
  handleRequest(req, res, next, mockPath) {
134
- var _a, _b;
255
+ var _a, _b, _c;
135
256
  const url = req.path || ((_a = req.url) === null || _a === void 0 ? void 0 : _a.split('?')[0]);
136
257
  if (!url) {
137
258
  return next();
@@ -144,6 +265,8 @@ class WarehouseMockPlugin {
144
265
  res.end(JSON.stringify({
145
266
  mockList: fileList,
146
267
  localApiPrefix: this.getLocalApiPrefix(),
268
+ proxy: ((_b = this.options.proxy) === null || _b === void 0 ? void 0 : _b.target) || null,
269
+ enabled: this.isEnabled,
147
270
  }));
148
271
  return;
149
272
  }
@@ -153,38 +276,132 @@ class WarehouseMockPlugin {
153
276
  }
154
277
  // 检查是否在允许的 API 前缀范围内
155
278
  const { apiPrefixes } = this.options;
156
- const isApiRequest = (_b = apiPrefixes === null || apiPrefixes === void 0 ? void 0 : apiPrefixes.some((prefix) => url.startsWith(prefix))) !== null && _b !== void 0 ? _b : true;
279
+ const isApiRequest = (_c = apiPrefixes === null || apiPrefixes === void 0 ? void 0 : apiPrefixes.some((prefix) => url.startsWith(prefix))) !== null && _c !== void 0 ? _c : true;
157
280
  if (!isApiRequest) {
158
281
  return next();
159
282
  }
160
283
  let filePath = '';
161
284
  let matched = false;
162
285
  let matchedName = '';
286
+ let interfaceConfig = null;
163
287
  // 1. 优先尝试 Query String 匹配 (RPC 风格接口,如 /api?user.taurus.pointInfo)
164
288
  if (req.url && req.url.includes('?')) {
165
289
  const queryPart = req.url.split('?')[1];
166
290
  if (queryPart) {
167
291
  const params = new URLSearchParams(queryPart);
168
292
  for (const key of params.keys()) {
169
- // 尝试匹配 key.json (例如 user.taurus.pointInfo.json)
170
- const queryFilePath = path_1.default.join(mockPath, `${key}.json`);
171
- if (fs_1.default.existsSync(queryFilePath) && fs_1.default.statSync(queryFilePath).isFile()) {
172
- filePath = queryFilePath;
173
- matchedName = key;
174
- matched = true;
175
- break;
293
+ // 先尝试目录模式(优先,支持多场景)
294
+ const dirPath = path_1.default.join(mockPath, key);
295
+ if (fs_1.default.existsSync(dirPath) && fs_1.default.statSync(dirPath).isDirectory()) {
296
+ // 读取接口配置(新格式)
297
+ const config = this.readInterfaceConfig(key, mockPath);
298
+ if (config) {
299
+ // 新格式:检查是否启用
300
+ if (config.enabled === false) {
301
+ console.log(chalk_1.default.yellow(`[WarehouseMock] ✗ 接口已禁用: ${key}`));
302
+ break; // 接口被禁用,不拦截
303
+ }
304
+ // 根据 activeScene 读取场景文件
305
+ const activeScene = config.activeScene || 'default';
306
+ const sceneFilePath = path_1.default.join(dirPath, `${activeScene}.json`);
307
+ if (fs_1.default.existsSync(sceneFilePath)) {
308
+ filePath = sceneFilePath;
309
+ matchedName = key;
310
+ matched = true;
311
+ interfaceConfig = config;
312
+ break;
313
+ }
314
+ }
315
+ else {
316
+ // 旧格式:查找 mock: true 的文件(向后兼容)
317
+ const files = fs_1.default.readdirSync(dirPath).filter(f => f.endsWith('.json') && f !== '.config.json');
318
+ for (const file of files) {
319
+ const fullPath = path_1.default.join(dirPath, file);
320
+ try {
321
+ const content = fs_1.default.readFileSync(fullPath, 'utf-8');
322
+ const jsonData = JSON.parse(content);
323
+ if (jsonData.mock === true) {
324
+ filePath = fullPath;
325
+ matchedName = key;
326
+ matched = true;
327
+ break;
328
+ }
329
+ }
330
+ catch (e) {
331
+ // 文件解析失败,跳过
332
+ }
333
+ }
334
+ if (matched)
335
+ break;
336
+ }
176
337
  }
177
- // 也尝试匹配 value (例如 method=user.taurus.pointInfo)
178
- const value = params.get(key);
179
- if (value) {
180
- const valueFilePath = path_1.default.join(mockPath, `${value}.json`);
181
- if (fs_1.default.existsSync(valueFilePath) && fs_1.default.statSync(valueFilePath).isFile()) {
182
- filePath = valueFilePath;
183
- matchedName = value;
338
+ // 再尝试单文件模式(向后兼容)
339
+ if (!matched) {
340
+ const queryFilePath = path_1.default.join(mockPath, `${key}.json`);
341
+ if (fs_1.default.existsSync(queryFilePath) && fs_1.default.statSync(queryFilePath).isFile()) {
342
+ filePath = queryFilePath;
343
+ matchedName = key;
184
344
  matched = true;
185
345
  break;
186
346
  }
187
347
  }
348
+ // 也尝试匹配 value (例如 method=user.taurus.pointInfo)
349
+ const value = params.get(key);
350
+ if (value && !matched) {
351
+ const valueDirPath = path_1.default.join(mockPath, value);
352
+ if (fs_1.default.existsSync(valueDirPath) && fs_1.default.statSync(valueDirPath).isDirectory()) {
353
+ // 读取接口配置(新格式)
354
+ const config = this.readInterfaceConfig(value, mockPath);
355
+ if (config) {
356
+ // 新格式:检查是否启用
357
+ if (config.enabled === false) {
358
+ console.log(chalk_1.default.yellow(`[WarehouseMock] ✗ 接口已禁用: ${value}`));
359
+ break;
360
+ }
361
+ // 根据 activeScene 读取场景文件
362
+ const activeScene = config.activeScene || 'default';
363
+ const sceneFilePath = path_1.default.join(valueDirPath, `${activeScene}.json`);
364
+ if (fs_1.default.existsSync(sceneFilePath)) {
365
+ filePath = sceneFilePath;
366
+ matchedName = value;
367
+ matched = true;
368
+ interfaceConfig = config;
369
+ break;
370
+ }
371
+ }
372
+ else {
373
+ // 旧格式:查找 mock: true 的文件(向后兼容)
374
+ const files = fs_1.default.readdirSync(valueDirPath).filter(f => f.endsWith('.json') && f !== '.config.json');
375
+ for (const file of files) {
376
+ const fullPath = path_1.default.join(valueDirPath, file);
377
+ try {
378
+ const content = fs_1.default.readFileSync(fullPath, 'utf-8');
379
+ const jsonData = JSON.parse(content);
380
+ if (jsonData.mock === true) {
381
+ filePath = fullPath;
382
+ matchedName = value;
383
+ matched = true;
384
+ break;
385
+ }
386
+ }
387
+ catch (e) {
388
+ // 跳过
389
+ }
390
+ }
391
+ if (matched)
392
+ break;
393
+ }
394
+ }
395
+ if (!matched) {
396
+ const valueFilePath = path_1.default.join(mockPath, `${value}.json`);
397
+ if (fs_1.default.existsSync(valueFilePath) && fs_1.default.statSync(valueFilePath).isFile()) {
398
+ filePath = valueFilePath;
399
+ matchedName = value;
400
+ matched = true;
401
+ break;
402
+ }
403
+ }
404
+ }
188
405
  }
189
406
  }
190
407
  }
@@ -216,29 +433,160 @@ class WarehouseMockPlugin {
216
433
  }
217
434
  }
218
435
  if (matched) {
219
- console.log(chalk_1.default.green(`[WarehouseMock] 拦截: ${matchedName} -> ${path_1.default.basename(filePath)}`));
220
- try {
221
- const data = fs_1.default.readFileSync(filePath, 'utf-8');
436
+ console.log(chalk_1.default.green(`[WarehouseMock] 拦截: ${req.url || url}`));
437
+ // 获取延时配置(优先使用接口级别的 delay)
438
+ const delayTime = (interfaceConfig === null || interfaceConfig === void 0 ? void 0 : interfaceConfig.delay) !== undefined
439
+ ? interfaceConfig.delay
440
+ : this.options.delay || 0;
441
+ // 获取场景名称
442
+ const sceneName = (interfaceConfig === null || interfaceConfig === void 0 ? void 0 : interfaceConfig.activeScene) || path_1.default.basename(filePath, '.json');
443
+ console.log(chalk_1.default.gray(` → 场景: ${sceneName}`));
444
+ if (delayTime > 0) {
445
+ console.log(chalk_1.default.gray(` → 延迟: ${delayTime}ms`));
446
+ }
447
+ // 模拟网络延迟
448
+ const respond = () => {
222
449
  try {
223
- const jsonData = JSON.parse(data);
450
+ const fileContent = fs_1.default.readFileSync(filePath, 'utf-8');
451
+ try {
452
+ const jsonData = JSON.parse(fileContent);
453
+ // 新格式:纯净的场景数据(不包含 mock、scene、delay 等元数据)
454
+ // 旧格式:包含 mock、scene、delay、data 字段
455
+ let responseData = jsonData;
456
+ // 兼容旧格式
457
+ if (jsonData.mock !== undefined && jsonData.data !== undefined) {
458
+ responseData = jsonData.data;
459
+ }
460
+ res.setHeader('Content-Type', 'application/json');
461
+ res.setHeader('X-Mock-By', 'WarehouseMock');
462
+ res.setHeader('X-Mock-Scene', sceneName);
463
+ res.end(JSON.stringify(responseData));
464
+ }
465
+ catch (jsonErr) {
466
+ console.warn(chalk_1.default.yellow(`[WarehouseMock] 无效的 JSON 文件: ${filePath}`));
467
+ res.setHeader('Content-Type', 'text/plain');
468
+ res.end(fileContent);
469
+ }
470
+ }
471
+ catch (e) {
472
+ console.error(chalk_1.default.red(`[WarehouseMock] 读取 Mock 文件失败: ${e}`));
473
+ res.statusCode = 500;
474
+ res.end('Mock Read Error');
475
+ }
476
+ };
477
+ // 应用延迟
478
+ if (delayTime > 0) {
479
+ setTimeout(respond, delayTime);
480
+ }
481
+ else {
482
+ respond();
483
+ }
484
+ return;
485
+ }
486
+ // 未匹配到 Mock 文件
487
+ if (this.options.proxy) {
488
+ // 如果配置了代理,转发到真实 API
489
+ console.log(chalk_1.default.gray(`[WarehouseMock] ⊳ 代理: ${req.url || url} → ${this.options.proxy.target}`));
490
+ this.proxyRequest(req, res, next);
491
+ }
492
+ else {
493
+ // 未配置代理,直接放行
494
+ next();
495
+ }
496
+ }
497
+ /**
498
+ * 代理请求到真实 API(改进版)
499
+ */
500
+ proxyRequest(req, res, next) {
501
+ if (!this.options.proxy) {
502
+ return next();
503
+ }
504
+ try {
505
+ const http = require('http');
506
+ const https = require('https');
507
+ const url = require('url');
508
+ // 构建完整的目标 URL
509
+ // 将 /mock-api?xxx 转换为 真实API/api?xxx
510
+ const targetUrl = this.options.proxy.target + req.url.replace('/mock-api', '/api');
511
+ const parsedUrl = url.parse(targetUrl);
512
+ const isHttps = parsedUrl.protocol === 'https:';
513
+ const lib = isHttps ? https : http;
514
+ // 清理请求头
515
+ const headers = Object.assign({}, req.headers);
516
+ delete headers.host;
517
+ headers.host = parsedUrl.hostname;
518
+ // 创建代理请求
519
+ const proxyReq = lib.request({
520
+ hostname: parsedUrl.hostname,
521
+ port: parsedUrl.port || (isHttps ? 443 : 80),
522
+ path: parsedUrl.path,
523
+ method: req.method,
524
+ headers: headers,
525
+ timeout: 30000, // 30秒超时
526
+ }, (proxyRes) => {
527
+ // 转发响应头
528
+ res.writeHead(proxyRes.statusCode, proxyRes.headers);
529
+ // 转发响应体
530
+ proxyRes.pipe(res);
531
+ });
532
+ // 错误处理
533
+ proxyReq.on('error', (err) => {
534
+ console.error(chalk_1.default.red(`[WarehouseMock] 代理请求失败: ${err.message}`));
535
+ console.error(chalk_1.default.red(` 目标地址: ${targetUrl}`));
536
+ // 返回友好的错误信息
537
+ if (!res.headersSent) {
538
+ res.statusCode = 502;
224
539
  res.setHeader('Content-Type', 'application/json');
225
- res.end(JSON.stringify(jsonData));
540
+ res.end(JSON.stringify({
541
+ code: -1,
542
+ msg: `代理请求失败: ${err.message}`,
543
+ error: 'PROXY_ERROR'
544
+ }));
226
545
  }
227
- catch (jsonErr) {
228
- console.warn(chalk_1.default.yellow(`[WarehouseMock] 无效的 JSON 文件: ${filePath}`));
229
- res.setHeader('Content-Type', 'text/plain');
230
- res.end(data);
546
+ });
547
+ // 超时处理
548
+ proxyReq.on('timeout', () => {
549
+ proxyReq.destroy();
550
+ if (!res.headersSent) {
551
+ res.statusCode = 504;
552
+ res.setHeader('Content-Type', 'application/json');
553
+ res.end(JSON.stringify({
554
+ code: -1,
555
+ msg: '代理请求超时',
556
+ error: 'PROXY_TIMEOUT'
557
+ }));
231
558
  }
232
- return;
559
+ });
560
+ // 转发请求体
561
+ if (req.method === 'POST' || req.method === 'PUT') {
562
+ // 处理 POST/PUT 请求的 body
563
+ let body = '';
564
+ req.on('data', (chunk) => {
565
+ body += chunk.toString();
566
+ });
567
+ req.on('end', () => {
568
+ if (body) {
569
+ proxyReq.write(body);
570
+ }
571
+ proxyReq.end();
572
+ });
233
573
  }
234
- catch (e) {
235
- console.error(chalk_1.default.red(`[WarehouseMock] 读取 Mock 文件失败: ${e}`));
574
+ else {
575
+ proxyReq.end();
576
+ }
577
+ }
578
+ catch (err) {
579
+ console.error(chalk_1.default.red(`[WarehouseMock] 代理配置错误: ${err}`));
580
+ if (!res.headersSent) {
236
581
  res.statusCode = 500;
237
- res.end('Mock Read Error');
238
- return;
582
+ res.setHeader('Content-Type', 'application/json');
583
+ res.end(JSON.stringify({
584
+ code: -1,
585
+ msg: `代理配置错误: ${err}`,
586
+ error: 'PROXY_CONFIG_ERROR'
587
+ }));
239
588
  }
240
589
  }
241
- next();
242
590
  }
243
591
  }
244
592
  module.exports = WarehouseMockPlugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "warehouse-mock",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "一个专为 Vue 2 项目设计的 Webpack 插件,支持 RPC 风格接口 mock,零业务代码侵入,实时更新",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -38,13 +38,16 @@
38
38
  "chalk": "^4.1.2",
39
39
  "chokidar": "^3.5.3"
40
40
  },
41
+ "optionalDependencies": {
42
+ "warehouse-mock-admin": "^1.0.0"
43
+ },
41
44
  "devDependencies": {
42
- "typescript": "^5.0.0",
43
- "webpack": "^5.0.0",
44
- "webpack-dev-server": "^4.0.0",
45
+ "@types/node": "^18.0.0",
45
46
  "@types/webpack": "^5.0.0",
46
47
  "@types/webpack-dev-server": "^4.0.0",
47
- "@types/node": "^18.0.0"
48
+ "typescript": "^5.0.0",
49
+ "webpack": "^5.0.0",
50
+ "webpack-dev-server": "^4.0.0"
48
51
  },
49
52
  "peerDependencies": {
50
53
  "webpack": "^4.0.0 || ^5.0.0"
@@ -53,4 +56,3 @@
53
56
  "node": ">=12.0.0"
54
57
  }
55
58
  }
56
-