whistle.mockbubu 2.2.0-beta.1 → 2.2.0-beta.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.
@@ -124,7 +124,6 @@ async function matchByConditions(params) {
124
124
 
125
125
  const allMatch = rule.conditions.every((cond) => {
126
126
  const sourceParams = cond.source === 'query' ? queryParams : payloadParams
127
- // 精确匹配:参数名和参数值必须完全相等(都转为字符串比较)
128
127
  return String(sourceParams[cond.key]) === String(cond.value)
129
128
  })
130
129
 
@@ -182,11 +181,9 @@ function extractQueryParams(originalReq) {
182
181
  }
183
182
 
184
183
  /**
185
- * 从原始请求中提取 payload 参数(仅支持 JSON,只提取第一层 key-value)
184
+ * 从原始请求中提取 payload 参数(支持 JSON 和 x-www-form-urlencoded,只提取第一层 key-value)
186
185
  * 来源:req.originalReq.body
187
186
  *
188
- * 非 JSON payload 时返回空对象,条件匹配只会比较 query 部分。
189
- *
190
187
  * @param {Object} originalReq - Whistle 原始请求对象
191
188
  * @returns {Object} payload 参数 key-value 对(值统一转为字符串)
192
189
  */
@@ -194,14 +191,15 @@ function extractPayloadParams(originalReq) {
194
191
  if (!originalReq) return {}
195
192
 
196
193
  try {
197
- let body = originalReq.body || ''
194
+ // 优先使用 mock 路径预缓冲的请求体,fallback Whistle 预解析的 body
195
+ let body = originalReq._reqBody || originalReq.body || ''
198
196
 
199
197
  // Buffer 转字符串
200
198
  if (Buffer.isBuffer(body)) {
201
199
  body = body.toString('utf8')
202
200
  }
203
201
 
204
- // 修复3:body 可能已被 Whistle 预解析为对象,直接提取而非走 JSON.parse
202
+ // body 可能已被 Whistle 预解析为对象,直接提取
205
203
  if (typeof body === 'object' && body !== null) {
206
204
  return extractFlatParams(body)
207
205
  }
@@ -210,12 +208,26 @@ function extractPayloadParams(originalReq) {
210
208
  return {}
211
209
  }
212
210
 
213
- const parsed = JSON.parse(body)
214
- if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
215
- return {}
211
+ // 尝试 JSON 格式
212
+ try {
213
+ const parsed = JSON.parse(body)
214
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
215
+ return extractFlatParams(parsed)
216
+ }
217
+ } catch {
218
+ // 不是 JSON,继续尝试
216
219
  }
217
220
 
218
- return extractFlatParams(parsed)
221
+ // 尝试 application/x-www-form-urlencoded 格式
222
+ if (body.includes('=')) {
223
+ const result = {}
224
+ new URLSearchParams(body).forEach((value, key) => {
225
+ result[key] = value
226
+ })
227
+ if (Object.keys(result).length > 0) return result
228
+ }
229
+
230
+ return {}
219
231
  } catch {
220
232
  return {}
221
233
  }
@@ -23,6 +23,19 @@ const {
23
23
  } = require('./capture-handler')
24
24
  const pluginModeManager = require('../plugin-mode-manager')
25
25
 
26
+ /**
27
+ * 从 req 流缓冲请求体,供条件匹配使用
28
+ * 仅在 mock 路径调用,此时 req 流未被 pipe,可安全读取
29
+ */
30
+ function bufferReqBody(req) {
31
+ return new Promise((resolve) => {
32
+ const chunks = []
33
+ req.on('data', chunk => chunks.push(chunk))
34
+ req.on('end', () => resolve(Buffer.concat(chunks)))
35
+ req.on('error', () => resolve(Buffer.alloc(0)))
36
+ })
37
+ }
38
+
26
39
  module.exports = (server, options) => {
27
40
  console.log('[mockbubu] 🚀 server.js 模块已加载!')
28
41
  const { storage } = options
@@ -101,6 +114,7 @@ module.exports = (server, options) => {
101
114
 
102
115
  // 有 mock 且有数据 → 返回 mock(不捕获)
103
116
  console.log(`[mockbubu] ✅ Mock-Only 模式: 返回 Mock 数据(不捕获) | URL: ${url}`)
117
+ originalReq._reqBody = await bufferReqBody(req)
104
118
  const mockOnlyResult = await sendMockResponse(res, sourceData, {
105
119
  mockVersion,
106
120
  groupManager,
@@ -117,6 +131,7 @@ module.exports = (server, options) => {
117
131
 
118
132
  // 场景 1: Mock 启用 + 有源数据 → 返回 Mock 数据
119
133
  if (mock && sourceData) {
134
+ originalReq._reqBody = await bufferReqBody(req)
120
135
  const mockResult = await sendMockResponse(res, sourceData, {
121
136
  mockVersion,
122
137
  groupManager,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whistle.mockbubu",
3
- "version": "2.2.0-beta.1",
3
+ "version": "2.2.0-beta.2",
4
4
  "description": "mock response data",
5
5
  "scripts": {
6
6
  "lint": "eslint . --ext .js",
package/public/js/app.js CHANGED
@@ -2173,10 +2173,19 @@ __webpack_require__.r(__webpack_exports__);
2173
2173
  /* harmony import */ var core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_7__);
2174
2174
  /* harmony import */ var core_js_modules_es_iterator_some_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! core-js/modules/es.iterator.some.js */ "./node_modules/core-js/modules/es.iterator.some.js");
2175
2175
  /* harmony import */ var core_js_modules_es_iterator_some_js__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_some_js__WEBPACK_IMPORTED_MODULE_8__);
2176
- /* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! vue */ "./node_modules/vue/dist/vue.runtime.esm.js");
2177
- /* harmony import */ var _common_JsonEditor_vue__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ../common/JsonEditor.vue */ "./src/components/common/JsonEditor.vue");
2178
- /* harmony import */ var _MatchConditionEditor_vue__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./MatchConditionEditor.vue */ "./src/components/content/MatchConditionEditor.vue");
2179
- /* harmony import */ var _service__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ../../service */ "./src/service/index.js");
2176
+ /* harmony import */ var core_js_modules_web_url_search_params_delete_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! core-js/modules/web.url-search-params.delete.js */ "./node_modules/core-js/modules/web.url-search-params.delete.js");
2177
+ /* harmony import */ var core_js_modules_web_url_search_params_delete_js__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_web_url_search_params_delete_js__WEBPACK_IMPORTED_MODULE_9__);
2178
+ /* harmony import */ var core_js_modules_web_url_search_params_has_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! core-js/modules/web.url-search-params.has.js */ "./node_modules/core-js/modules/web.url-search-params.has.js");
2179
+ /* harmony import */ var core_js_modules_web_url_search_params_has_js__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_web_url_search_params_has_js__WEBPACK_IMPORTED_MODULE_10__);
2180
+ /* harmony import */ var core_js_modules_web_url_search_params_size_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! core-js/modules/web.url-search-params.size.js */ "./node_modules/core-js/modules/web.url-search-params.size.js");
2181
+ /* harmony import */ var core_js_modules_web_url_search_params_size_js__WEBPACK_IMPORTED_MODULE_11___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_web_url_search_params_size_js__WEBPACK_IMPORTED_MODULE_11__);
2182
+ /* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! vue */ "./node_modules/vue/dist/vue.runtime.esm.js");
2183
+ /* harmony import */ var _common_JsonEditor_vue__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ../common/JsonEditor.vue */ "./src/components/common/JsonEditor.vue");
2184
+ /* harmony import */ var _MatchConditionEditor_vue__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./MatchConditionEditor.vue */ "./src/components/content/MatchConditionEditor.vue");
2185
+ /* harmony import */ var _service__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ../../service */ "./src/service/index.js");
2186
+
2187
+
2188
+
2180
2189
 
2181
2190
 
2182
2191
 
@@ -2214,17 +2223,17 @@ const __default__ = {
2214
2223
  }) {
2215
2224
  const props = __props;
2216
2225
  const MATCH_MODE_VALUE = '__match_mode__';
2217
- const editRef = (0,vue__WEBPACK_IMPORTED_MODULE_9__.ref)(null);
2218
- const fullScreenEditorRef = (0,vue__WEBPACK_IMPORTED_MODULE_9__.ref)(null);
2219
- const versionNameInput = (0,vue__WEBPACK_IMPORTED_MODULE_9__.ref)(null);
2220
- const matchRuleNameInput = (0,vue__WEBPACK_IMPORTED_MODULE_9__.ref)(null);
2221
- const responseList = (0,vue__WEBPACK_IMPORTED_MODULE_9__.ref)([]);
2222
- const currentFile = (0,vue__WEBPACK_IMPORTED_MODULE_9__.ref)({
2226
+ const editRef = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(null);
2227
+ const fullScreenEditorRef = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(null);
2228
+ const versionNameInput = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(null);
2229
+ const matchRuleNameInput = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(null);
2230
+ const responseList = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)([]);
2231
+ const currentFile = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)({
2223
2232
  content: {}
2224
2233
  });
2225
- const hasJsonError = (0,vue__WEBPACK_IMPORTED_MODULE_9__.ref)(false); // JSON 格式错误标志
2226
- const hasFullScreenJsonError = (0,vue__WEBPACK_IMPORTED_MODULE_9__.ref)(false); // 全屏编辑器 JSON 格式错误标志
2227
- const versionModal = (0,vue__WEBPACK_IMPORTED_MODULE_9__.reactive)({
2234
+ const hasJsonError = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(false); // JSON 格式错误标志
2235
+ const hasFullScreenJsonError = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(false); // 全屏编辑器 JSON 格式错误标志
2236
+ const versionModal = (0,vue__WEBPACK_IMPORTED_MODULE_12__.reactive)({
2228
2237
  visible: false,
2229
2238
  status: 'create',
2230
2239
  // 'create' | 'edit'
@@ -2232,24 +2241,24 @@ const __default__ = {
2232
2241
  description: '',
2233
2242
  editingVersion: null // 正在编辑的版本对象
2234
2243
  });
2235
- const fullScreenModal = (0,vue__WEBPACK_IMPORTED_MODULE_9__.reactive)({
2244
+ const fullScreenModal = (0,vue__WEBPACK_IMPORTED_MODULE_12__.reactive)({
2236
2245
  visible: false,
2237
2246
  content: {}
2238
2247
  });
2239
2248
 
2240
2249
  // ========== 条件匹配模式状态 ==========
2241
2250
  // 当前显示模式:'version' = 版本直选,'match' = 条件匹配
2242
- const mockMode = (0,vue__WEBPACK_IMPORTED_MODULE_9__.ref)('version');
2251
+ const mockMode = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)('version');
2243
2252
  // 模式下拉是否展开
2244
- const modeDropdownVisible = (0,vue__WEBPACK_IMPORTED_MODULE_9__.ref)(false);
2253
+ const modeDropdownVisible = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(false);
2245
2254
  // 匹配规则列表
2246
- const matchRuleList = (0,vue__WEBPACK_IMPORTED_MODULE_9__.ref)([]);
2255
+ const matchRuleList = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)([]);
2247
2256
  // 当前选中的匹配规则(深拷贝,供编辑用)
2248
- const currentMatchRule = (0,vue__WEBPACK_IMPORTED_MODULE_9__.ref)(null);
2257
+ const currentMatchRule = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(null);
2249
2258
  // 条件或内容是否有变更(控制保存按钮显示)
2250
- const matchRuleChanged = (0,vue__WEBPACK_IMPORTED_MODULE_9__.ref)(false);
2259
+ const matchRuleChanged = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(false);
2251
2260
  // 匹配规则创建/编辑弹窗
2252
- const matchRuleModal = (0,vue__WEBPACK_IMPORTED_MODULE_9__.reactive)({
2261
+ const matchRuleModal = (0,vue__WEBPACK_IMPORTED_MODULE_12__.reactive)({
2253
2262
  visible: false,
2254
2263
  status: 'create',
2255
2264
  // 'create' | 'edit'
@@ -2258,19 +2267,63 @@ const __default__ = {
2258
2267
  editingRule: null
2259
2268
  });
2260
2269
  // 捕获到的参数(来自 file.json session,用于"从捕获参数选择")
2261
- const capturedQuery = (0,vue__WEBPACK_IMPORTED_MODULE_9__.computed)(() => props.api.query || {});
2262
- const capturedPayload = (0,vue__WEBPACK_IMPORTED_MODULE_9__.computed)(() => {
2270
+ const capturedQuery = (0,vue__WEBPACK_IMPORTED_MODULE_12__.computed)(() => props.api.query || {});
2271
+ const capturedPayload = (0,vue__WEBPACK_IMPORTED_MODULE_12__.computed)(() => {
2263
2272
  const payload = props.api.payload;
2264
2273
  if (!payload) return {};
2265
- // payload 结构: { raw, parsed, type },取 parsed.data
2266
- return payload?.parsed?.data || {};
2274
+ // 结构化对象格式: { raw, parsed: { type, data } }
2275
+ if (typeof payload === 'object' && payload.parsed?.data) {
2276
+ return payload.parsed.data;
2277
+ }
2278
+ // 普通对象(Whistle 预解析),提取第一层 string/number/boolean
2279
+ if (typeof payload === 'object' && !Array.isArray(payload)) {
2280
+ const result = {};
2281
+ Object.keys(payload).forEach(key => {
2282
+ const val = payload[key];
2283
+ if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
2284
+ result[key] = String(val);
2285
+ }
2286
+ });
2287
+ return result;
2288
+ }
2289
+ if (typeof payload === 'string') {
2290
+ // JSON 格式
2291
+ try {
2292
+ const parsed = JSON.parse(payload);
2293
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
2294
+ const result = {};
2295
+ Object.keys(parsed).forEach(key => {
2296
+ const val = parsed[key];
2297
+ if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
2298
+ result[key] = String(val);
2299
+ }
2300
+ });
2301
+ return result;
2302
+ }
2303
+ } catch {
2304
+ // 不是 JSON,继续尝试
2305
+ }
2306
+ // application/x-www-form-urlencoded 格式
2307
+ if (payload.includes('=')) {
2308
+ try {
2309
+ const result = {};
2310
+ new URLSearchParams(payload).forEach((value, key) => {
2311
+ result[key] = value;
2312
+ });
2313
+ if (Object.keys(result).length > 0) return result;
2314
+ } catch {
2315
+ // 解析失败
2316
+ }
2317
+ }
2318
+ }
2319
+ return {};
2267
2320
  });
2268
- const name = (0,vue__WEBPACK_IMPORTED_MODULE_9__.computed)(() => props.api.name);
2269
- const mock = (0,vue__WEBPACK_IMPORTED_MODULE_9__.computed)(() => props.api.mock);
2270
- const mockVersion = (0,vue__WEBPACK_IMPORTED_MODULE_9__.computed)(() => props.api.mockVersion);
2271
- const date = (0,vue__WEBPACK_IMPORTED_MODULE_9__.computed)(() => props.api.date);
2272
- const isSourceReadonly = (0,vue__WEBPACK_IMPORTED_MODULE_9__.computed)(() => mockMode.value === 'version' && currentFile.value.type === 'source');
2273
- const activeEditorContent = (0,vue__WEBPACK_IMPORTED_MODULE_9__.computed)(() => mockMode.value === 'match' ? currentMatchRule.value?.content : currentFile.value.content);
2321
+ const name = (0,vue__WEBPACK_IMPORTED_MODULE_12__.computed)(() => props.api.name);
2322
+ const mock = (0,vue__WEBPACK_IMPORTED_MODULE_12__.computed)(() => props.api.mock);
2323
+ const mockVersion = (0,vue__WEBPACK_IMPORTED_MODULE_12__.computed)(() => props.api.mockVersion);
2324
+ const date = (0,vue__WEBPACK_IMPORTED_MODULE_12__.computed)(() => props.api.date);
2325
+ const isSourceReadonly = (0,vue__WEBPACK_IMPORTED_MODULE_12__.computed)(() => mockMode.value === 'version' && currentFile.value.type === 'source');
2326
+ const activeEditorContent = (0,vue__WEBPACK_IMPORTED_MODULE_12__.computed)(() => mockMode.value === 'match' ? currentMatchRule.value?.content : currentFile.value.content);
2274
2327
 
2275
2328
  /**
2276
2329
  * 获取 source 版本数据(仅用于未保存文件)
@@ -2335,7 +2388,7 @@ const __default__ = {
2335
2388
  */
2336
2389
  const getHistoryVersions = () => {
2337
2390
  const fileId = props.api.id;
2338
- return (0,_service__WEBPACK_IMPORTED_MODULE_12__.getVersions)({
2391
+ return (0,_service__WEBPACK_IMPORTED_MODULE_15__.getVersions)({
2339
2392
  fileId
2340
2393
  }).then(res => {
2341
2394
  console.log('[ResponsePanel] 后端返回的版本数据:', res);
@@ -2414,7 +2467,7 @@ const __default__ = {
2414
2467
  }
2415
2468
  };
2416
2469
  const deleteVersionHandler = (item, index) => {
2417
- (0,_service__WEBPACK_IMPORTED_MODULE_12__.deleteVersion)({
2470
+ (0,_service__WEBPACK_IMPORTED_MODULE_15__.deleteVersion)({
2418
2471
  url: props.api.name,
2419
2472
  fileId: props.api.id,
2420
2473
  versionId: item.id
@@ -2489,7 +2542,7 @@ const __default__ = {
2489
2542
  // 未保存文件:先保存文件,再创建版本
2490
2543
  console.log('[ResponsePanel] 文件未保存,先保存文件');
2491
2544
  try {
2492
- const saveResult = await (0,_service__WEBPACK_IMPORTED_MODULE_12__.saveFile)({
2545
+ const saveResult = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.saveFile)({
2493
2546
  url: props.api.url,
2494
2547
  method: props.api.method,
2495
2548
  status: props.api.status,
@@ -2520,7 +2573,7 @@ const __default__ = {
2520
2573
  return;
2521
2574
  }
2522
2575
  }
2523
- (0,_service__WEBPACK_IMPORTED_MODULE_12__.addVersion)({
2576
+ (0,_service__WEBPACK_IMPORTED_MODULE_15__.addVersion)({
2524
2577
  versionName: versionModal.name,
2525
2578
  fileId,
2526
2579
  description: versionModal.description,
@@ -2550,7 +2603,7 @@ const __default__ = {
2550
2603
  // 添加版本成功后,重新获取版本列表(确保数据同步)
2551
2604
  // ⚠️ 关键修复:使用保存的 fileId,而不是 props.api.id(可能仍是 undefined)
2552
2605
  console.log('[ResponsePanel] 使用 fileId 刷新版本列表:', fileId);
2553
- (0,_service__WEBPACK_IMPORTED_MODULE_12__.getVersions)({
2606
+ (0,_service__WEBPACK_IMPORTED_MODULE_15__.getVersions)({
2554
2607
  fileId
2555
2608
  }).then(versionRes => {
2556
2609
  console.log('[ResponsePanel] 后端返回的版本数据:', versionRes);
@@ -2621,7 +2674,7 @@ const __default__ = {
2621
2674
  }
2622
2675
 
2623
2676
  // 调用后端API更新版本元信息
2624
- (0,_service__WEBPACK_IMPORTED_MODULE_12__.updateVersionMeta)({
2677
+ (0,_service__WEBPACK_IMPORTED_MODULE_15__.updateVersionMeta)({
2625
2678
  url: props.api.name,
2626
2679
  fileId: props.api.id,
2627
2680
  versionId: currentFile.value.id,
@@ -2691,7 +2744,7 @@ const __default__ = {
2691
2744
  effect: tempEffect
2692
2745
  };
2693
2746
  const versionName = item.type === 'history' ? item.filename : '';
2694
- (0,_service__WEBPACK_IMPORTED_MODULE_12__.setMockVersion)({
2747
+ (0,_service__WEBPACK_IMPORTED_MODULE_15__.setMockVersion)({
2695
2748
  url: props.api.name,
2696
2749
  versionName
2697
2750
  }) // Changed from name to url
@@ -2713,7 +2766,7 @@ const __default__ = {
2713
2766
  currentFile.value.effect = true;
2714
2767
  };
2715
2768
  const updateHistory = () => {
2716
- (0,_service__WEBPACK_IMPORTED_MODULE_12__.updateVersionContent)({
2769
+ (0,_service__WEBPACK_IMPORTED_MODULE_15__.updateVersionContent)({
2717
2770
  versionId: currentFile.value.id,
2718
2771
  content: currentFile.value.content,
2719
2772
  fileId: props.api.id
@@ -2757,7 +2810,7 @@ const __default__ = {
2757
2810
  responseList.value = source ? [source, ...versions] : versions;
2758
2811
  };
2759
2812
  const updateApiDataHandler = () => {
2760
- (0,_service__WEBPACK_IMPORTED_MODULE_12__.updateApiData)(props.api.name, currentFile.value.content).then(res => {
2813
+ (0,_service__WEBPACK_IMPORTED_MODULE_15__.updateApiData)(props.api.name, currentFile.value.content).then(res => {
2761
2814
  const {
2762
2815
  code
2763
2816
  } = res;
@@ -2818,7 +2871,7 @@ const __default__ = {
2818
2871
  /** 保存当前匹配规则(条件 + 内容) */
2819
2872
  const updateMatchRuleHandler = async () => {
2820
2873
  if (!currentMatchRule.value) return;
2821
- const res = await (0,_service__WEBPACK_IMPORTED_MODULE_12__.updateMatchVersion)({
2874
+ const res = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.updateMatchVersion)({
2822
2875
  url: props.api.name,
2823
2876
  matchVersionId: currentMatchRule.value.id,
2824
2877
  conditions: currentMatchRule.value.conditions,
@@ -3023,13 +3076,36 @@ const __default__ = {
3023
3076
  const switchMode = async targetMode => {
3024
3077
  modeDropdownVisible.value = false;
3025
3078
  if (targetMode === mockMode.value) return;
3026
- if (!props.api.id) {
3027
- element_ui_lib_message__WEBPACK_IMPORTED_MODULE_2___default().warning('未保存的文件需要先保存后才能使用条件匹配');
3028
- return;
3029
- }
3030
3079
  if (targetMode === 'match') {
3080
+ // 未保存文件:先自动保存,再切换
3081
+ if (!props.api.id) {
3082
+ try {
3083
+ const saveResult = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.saveFile)({
3084
+ url: props.api.url,
3085
+ method: props.api.method,
3086
+ status: props.api.status,
3087
+ session: props.api.session,
3088
+ config: {
3089
+ mock: props.api.mock || false,
3090
+ locked: props.api.locked || false
3091
+ }
3092
+ });
3093
+ if (saveResult.code !== 200) {
3094
+ element_ui_lib_message__WEBPACK_IMPORTED_MODULE_2___default().error(`保存文件失败: ${saveResult.msg || '未知错误'}`);
3095
+ return;
3096
+ }
3097
+ emit('fileSaved', {
3098
+ url: props.api.url,
3099
+ savedFile: saveResult.data.file
3100
+ });
3101
+ } catch (err) {
3102
+ element_ui_lib_message__WEBPACK_IMPORTED_MODULE_2___default().error(`保存文件失败: ${err.message || '未知错误'}`);
3103
+ return;
3104
+ }
3105
+ }
3106
+
3031
3107
  // 切换为条件匹配
3032
- const res = await (0,_service__WEBPACK_IMPORTED_MODULE_12__.setMatchMode)({
3108
+ const res = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.setMatchMode)({
3033
3109
  url: props.api.name,
3034
3110
  enable: true
3035
3111
  });
@@ -3046,7 +3122,7 @@ const __default__ = {
3046
3122
  emit('changeMockVersion', MATCH_MODE_VALUE);
3047
3123
  } else {
3048
3124
  // 切换回版本直选
3049
- const res = await (0,_service__WEBPACK_IMPORTED_MODULE_12__.setMatchMode)({
3125
+ const res = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.setMatchMode)({
3050
3126
  url: props.api.name,
3051
3127
  enable: false
3052
3128
  });
@@ -3070,7 +3146,7 @@ const __default__ = {
3070
3146
  /** 加载匹配规则列表 */
3071
3147
  const loadMatchVersions = async () => {
3072
3148
  if (!props.api.id || !props.api.name) return;
3073
- const res = await (0,_service__WEBPACK_IMPORTED_MODULE_12__.getMatchVersions)({
3149
+ const res = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.getMatchVersions)({
3074
3150
  url: props.api.name
3075
3151
  });
3076
3152
  if (res.code !== 200) {
@@ -3150,7 +3226,7 @@ const __default__ = {
3150
3226
  return;
3151
3227
  }
3152
3228
  if (matchRuleModal.status === 'create') {
3153
- const res = await (0,_service__WEBPACK_IMPORTED_MODULE_12__.addMatchVersion)({
3229
+ const res = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.addMatchVersion)({
3154
3230
  url: props.api.name,
3155
3231
  name: matchRuleModal.name.trim(),
3156
3232
  description: matchRuleModal.description
@@ -3169,7 +3245,7 @@ const __default__ = {
3169
3245
  element_ui_lib_message__WEBPACK_IMPORTED_MODULE_2___default().success('创建成功');
3170
3246
  } else {
3171
3247
  // 编辑元信息
3172
- const res = await (0,_service__WEBPACK_IMPORTED_MODULE_12__.updateMatchVersionMeta)({
3248
+ const res = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.updateMatchVersionMeta)({
3173
3249
  url: props.api.name,
3174
3250
  matchVersionId: matchRuleModal.editingRule.id,
3175
3251
  name: matchRuleModal.name.trim(),
@@ -3203,7 +3279,7 @@ const __default__ = {
3203
3279
  } catch {
3204
3280
  return; // 取消
3205
3281
  }
3206
- const res = await (0,_service__WEBPACK_IMPORTED_MODULE_12__.deleteMatchVersion)({
3282
+ const res = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.deleteMatchVersion)({
3207
3283
  url: props.api.name,
3208
3284
  matchVersionId: rule.id
3209
3285
  });
@@ -3238,7 +3314,7 @@ const __default__ = {
3238
3314
  * - 已保存文件: file-${id}
3239
3315
  * - 未保存文件: cache-${url}-${captureTime}
3240
3316
  */
3241
- (0,vue__WEBPACK_IMPORTED_MODULE_9__.watch)(() => props.rowKey, (newRowKey, oldRowKey) => {
3317
+ (0,vue__WEBPACK_IMPORTED_MODULE_12__.watch)(() => props.rowKey, (newRowKey, oldRowKey) => {
3242
3318
  // 判断是否为已保存文件(已保存文件有 id,未保存文件没有)
3243
3319
  const isSavedFile = !!props.api.id;
3244
3320
 
@@ -3384,8 +3460,8 @@ const __default__ = {
3384
3460
  handleMatchRuleConfirm,
3385
3461
  deleteMatchRuleHandler,
3386
3462
  refreshVersions,
3387
- JsonEditor: _common_JsonEditor_vue__WEBPACK_IMPORTED_MODULE_10__["default"],
3388
- MatchConditionEditor: _MatchConditionEditor_vue__WEBPACK_IMPORTED_MODULE_11__["default"]
3463
+ JsonEditor: _common_JsonEditor_vue__WEBPACK_IMPORTED_MODULE_13__["default"],
3464
+ MatchConditionEditor: _MatchConditionEditor_vue__WEBPACK_IMPORTED_MODULE_14__["default"]
3389
3465
  };
3390
3466
  }
3391
3467
  }));
@@ -5274,7 +5350,7 @@ var render = function render() {
5274
5350
  staticClass: "response-panel"
5275
5351
  }, [_c("div", {
5276
5352
  staticClass: "response-panel__header"
5277
- }, [_setup.props.api.id ? _c("div", {
5353
+ }, [_setup.props.api ? _c("div", {
5278
5354
  staticClass: "response-panel__mode-selector"
5279
5355
  }, [_c("div", {
5280
5356
  staticClass: "response-panel__mode-btn",