whistle.mockbubu 2.2.0-beta.1 → 2.2.0-beta.3
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.
|
@@ -78,6 +78,12 @@ async function captureResponse(params) {
|
|
|
78
78
|
return
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
const resContentType = svrRes.headers['content-type'] || ''
|
|
82
|
+
if (!resContentType.includes('application/json')) {
|
|
83
|
+
console.log(`[mockbubu] ⏭️ 非 JSON 响应,跳过捕获: ${filename} (Content-Type: ${resContentType})`)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
81
87
|
console.log(`[mockbubu 2025/12/8 ${new Date().toLocaleTimeString()}] 📦 响应接收完成,开始解压: ${filename}`)
|
|
82
88
|
// 解压响应体
|
|
83
89
|
const content = await handleBuffer2String({ body, encoding })
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
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/lib/utils/utils.js
CHANGED
|
@@ -370,6 +370,7 @@ const getRule = (originalReq) => {
|
|
|
370
370
|
const isJsonReq = (headers) => {
|
|
371
371
|
const result = (
|
|
372
372
|
(headers.accept && headers.accept.includes('application/json')) ||
|
|
373
|
+
headers.accept === '*/*' ||
|
|
373
374
|
headers['sec-fetch-dest'] === 'empty' ||
|
|
374
375
|
headers['Sec-Fetch-Dest'] === 'empty'
|
|
375
376
|
)
|
package/package.json
CHANGED
package/public/js/app.js
CHANGED
|
@@ -243,6 +243,36 @@ const __default__ = {
|
|
|
243
243
|
|
|
244
244
|
// 是否已加载已保存文件
|
|
245
245
|
let savedFilesLoaded = false;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 从 session 中提取 URL 查询参数
|
|
249
|
+
*
|
|
250
|
+
* x-whistle-full-url 是 Whistle 内部 header,只在 originalReq 阶段可靠。
|
|
251
|
+
* getSession() 返回的 session.req.headers 可能已被剥离该字段,
|
|
252
|
+
* 因此先尝试 x-whistle-full-url,不存在时 fallback 到 session.url(Whistle session 顶层字段,含完整 query string)。
|
|
253
|
+
*/
|
|
254
|
+
const extractQueryFromSession = session => {
|
|
255
|
+
const fullUrlEncoded = session?.req?.headers?.['x-whistle-full-url'];
|
|
256
|
+
if (fullUrlEncoded) {
|
|
257
|
+
try {
|
|
258
|
+
const urlObj = new URL(decodeURIComponent(fullUrlEncoded));
|
|
259
|
+
return Object.fromEntries(urlObj.searchParams);
|
|
260
|
+
} catch (e) {
|
|
261
|
+
// 解析失败,继续 fallback
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Whistle session 顶层 url 字段包含完整 URL(含 query string)
|
|
265
|
+
const reqUrl = session?.url;
|
|
266
|
+
if (reqUrl) {
|
|
267
|
+
try {
|
|
268
|
+
const urlObj = new URL(reqUrl);
|
|
269
|
+
return Object.fromEntries(urlObj.searchParams);
|
|
270
|
+
} catch (e) {
|
|
271
|
+
// 解析失败,返回空对象
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return {};
|
|
275
|
+
};
|
|
246
276
|
const getRules = rules => {
|
|
247
277
|
const {
|
|
248
278
|
defaultRules,
|
|
@@ -384,17 +414,7 @@ const __default__ = {
|
|
|
384
414
|
// 映射未保存数据的展示字段
|
|
385
415
|
const newCachedItems = filteredData.map(item => {
|
|
386
416
|
// 从 session 中提取 URL 查询参数
|
|
387
|
-
|
|
388
|
-
const fullUrlEncoded = item.session?.req?.headers?.['x-whistle-full-url'];
|
|
389
|
-
if (fullUrlEncoded) {
|
|
390
|
-
try {
|
|
391
|
-
const fullUrl = decodeURIComponent(fullUrlEncoded);
|
|
392
|
-
const urlObj = new URL(fullUrl);
|
|
393
|
-
query = Object.fromEntries(urlObj.searchParams);
|
|
394
|
-
} catch (e) {
|
|
395
|
-
// 静默处理URL解析错误
|
|
396
|
-
}
|
|
397
|
-
}
|
|
417
|
+
const query = extractQueryFromSession(item.session);
|
|
398
418
|
|
|
399
419
|
// 从 session 中提取 payload (请求体)
|
|
400
420
|
const payload = item.session?.req?.body || null;
|
|
@@ -515,17 +535,7 @@ const __default__ = {
|
|
|
515
535
|
}
|
|
516
536
|
|
|
517
537
|
// 从 session 提取 query 参数
|
|
518
|
-
|
|
519
|
-
const fullUrlEncoded = data.session?.req?.headers?.['x-whistle-full-url'];
|
|
520
|
-
if (fullUrlEncoded) {
|
|
521
|
-
try {
|
|
522
|
-
const fullUrl = decodeURIComponent(fullUrlEncoded);
|
|
523
|
-
const urlObj = new URL(fullUrl);
|
|
524
|
-
query = Object.fromEntries(urlObj.searchParams);
|
|
525
|
-
} catch (e) {
|
|
526
|
-
// 静默处理URL解析错误
|
|
527
|
-
}
|
|
528
|
-
}
|
|
538
|
+
const query = extractQueryFromSession(data.session);
|
|
529
539
|
|
|
530
540
|
// 使用 Object.assign 更新 currentRow,保留引用
|
|
531
541
|
Object.assign(currentRow.value, {
|
|
@@ -1271,12 +1281,13 @@ const __default__ = {
|
|
|
1271
1281
|
}
|
|
1272
1282
|
};
|
|
1273
1283
|
(0,vue__WEBPACK_IMPORTED_MODULE_21__.watch)(currentRow, val => {
|
|
1274
|
-
// 只对已保存的文件调用 getCurrentRowData
|
|
1275
|
-
// 未保存的缓存数据已经包含完整 session,不需要再次获取
|
|
1276
|
-
// 判断依据: 有 id 字段说明是已保存的文件,需要按需加载完整数据
|
|
1277
1284
|
if (val && val.id) {
|
|
1278
|
-
//
|
|
1285
|
+
// 已保存文件:按需加载完整数据(含 query/payload/headers)
|
|
1279
1286
|
getCurrentRowData(val);
|
|
1287
|
+
} else if (val && val.session) {
|
|
1288
|
+
// 缓存数据:session 已在轮询时附带,但 query 可能在旧版本代码下提取失败
|
|
1289
|
+
// 切换行时重新提取,确保展示正确
|
|
1290
|
+
val.query = extractQueryFromSession(val.session);
|
|
1280
1291
|
}
|
|
1281
1292
|
});
|
|
1282
1293
|
(0,vue__WEBPACK_IMPORTED_MODULE_21__.onMounted)(async () => {
|
|
@@ -1335,6 +1346,7 @@ const __default__ = {
|
|
|
1335
1346
|
pollingStartTime,
|
|
1336
1347
|
intervalId,
|
|
1337
1348
|
savedFilesLoaded,
|
|
1349
|
+
extractQueryFromSession,
|
|
1338
1350
|
getRules,
|
|
1339
1351
|
getInit,
|
|
1340
1352
|
pageShow,
|
|
@@ -2173,10 +2185,19 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
2173
2185
|
/* 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
2186
|
/* 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
2187
|
/* 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
|
|
2177
|
-
/* harmony import */ var
|
|
2178
|
-
/* harmony import */ var
|
|
2179
|
-
/* harmony import */ var
|
|
2188
|
+
/* 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");
|
|
2189
|
+
/* 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__);
|
|
2190
|
+
/* 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");
|
|
2191
|
+
/* 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__);
|
|
2192
|
+
/* 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");
|
|
2193
|
+
/* 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__);
|
|
2194
|
+
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! vue */ "./node_modules/vue/dist/vue.runtime.esm.js");
|
|
2195
|
+
/* harmony import */ var _common_JsonEditor_vue__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ../common/JsonEditor.vue */ "./src/components/common/JsonEditor.vue");
|
|
2196
|
+
/* harmony import */ var _MatchConditionEditor_vue__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./MatchConditionEditor.vue */ "./src/components/content/MatchConditionEditor.vue");
|
|
2197
|
+
/* harmony import */ var _service__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ../../service */ "./src/service/index.js");
|
|
2198
|
+
|
|
2199
|
+
|
|
2200
|
+
|
|
2180
2201
|
|
|
2181
2202
|
|
|
2182
2203
|
|
|
@@ -2214,17 +2235,17 @@ const __default__ = {
|
|
|
2214
2235
|
}) {
|
|
2215
2236
|
const props = __props;
|
|
2216
2237
|
const MATCH_MODE_VALUE = '__match_mode__';
|
|
2217
|
-
const editRef = (0,
|
|
2218
|
-
const fullScreenEditorRef = (0,
|
|
2219
|
-
const versionNameInput = (0,
|
|
2220
|
-
const matchRuleNameInput = (0,
|
|
2221
|
-
const responseList = (0,
|
|
2222
|
-
const currentFile = (0,
|
|
2238
|
+
const editRef = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(null);
|
|
2239
|
+
const fullScreenEditorRef = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(null);
|
|
2240
|
+
const versionNameInput = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(null);
|
|
2241
|
+
const matchRuleNameInput = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(null);
|
|
2242
|
+
const responseList = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)([]);
|
|
2243
|
+
const currentFile = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)({
|
|
2223
2244
|
content: {}
|
|
2224
2245
|
});
|
|
2225
|
-
const hasJsonError = (0,
|
|
2226
|
-
const hasFullScreenJsonError = (0,
|
|
2227
|
-
const versionModal = (0,
|
|
2246
|
+
const hasJsonError = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(false); // JSON 格式错误标志
|
|
2247
|
+
const hasFullScreenJsonError = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(false); // 全屏编辑器 JSON 格式错误标志
|
|
2248
|
+
const versionModal = (0,vue__WEBPACK_IMPORTED_MODULE_12__.reactive)({
|
|
2228
2249
|
visible: false,
|
|
2229
2250
|
status: 'create',
|
|
2230
2251
|
// 'create' | 'edit'
|
|
@@ -2232,24 +2253,24 @@ const __default__ = {
|
|
|
2232
2253
|
description: '',
|
|
2233
2254
|
editingVersion: null // 正在编辑的版本对象
|
|
2234
2255
|
});
|
|
2235
|
-
const fullScreenModal = (0,
|
|
2256
|
+
const fullScreenModal = (0,vue__WEBPACK_IMPORTED_MODULE_12__.reactive)({
|
|
2236
2257
|
visible: false,
|
|
2237
2258
|
content: {}
|
|
2238
2259
|
});
|
|
2239
2260
|
|
|
2240
2261
|
// ========== 条件匹配模式状态 ==========
|
|
2241
2262
|
// 当前显示模式:'version' = 版本直选,'match' = 条件匹配
|
|
2242
|
-
const mockMode = (0,
|
|
2263
|
+
const mockMode = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)('version');
|
|
2243
2264
|
// 模式下拉是否展开
|
|
2244
|
-
const modeDropdownVisible = (0,
|
|
2265
|
+
const modeDropdownVisible = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(false);
|
|
2245
2266
|
// 匹配规则列表
|
|
2246
|
-
const matchRuleList = (0,
|
|
2267
|
+
const matchRuleList = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)([]);
|
|
2247
2268
|
// 当前选中的匹配规则(深拷贝,供编辑用)
|
|
2248
|
-
const currentMatchRule = (0,
|
|
2269
|
+
const currentMatchRule = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(null);
|
|
2249
2270
|
// 条件或内容是否有变更(控制保存按钮显示)
|
|
2250
|
-
const matchRuleChanged = (0,
|
|
2271
|
+
const matchRuleChanged = (0,vue__WEBPACK_IMPORTED_MODULE_12__.ref)(false);
|
|
2251
2272
|
// 匹配规则创建/编辑弹窗
|
|
2252
|
-
const matchRuleModal = (0,
|
|
2273
|
+
const matchRuleModal = (0,vue__WEBPACK_IMPORTED_MODULE_12__.reactive)({
|
|
2253
2274
|
visible: false,
|
|
2254
2275
|
status: 'create',
|
|
2255
2276
|
// 'create' | 'edit'
|
|
@@ -2258,19 +2279,63 @@ const __default__ = {
|
|
|
2258
2279
|
editingRule: null
|
|
2259
2280
|
});
|
|
2260
2281
|
// 捕获到的参数(来自 file.json session,用于"从捕获参数选择")
|
|
2261
|
-
const capturedQuery = (0,
|
|
2262
|
-
const capturedPayload = (0,
|
|
2282
|
+
const capturedQuery = (0,vue__WEBPACK_IMPORTED_MODULE_12__.computed)(() => props.api.query || {});
|
|
2283
|
+
const capturedPayload = (0,vue__WEBPACK_IMPORTED_MODULE_12__.computed)(() => {
|
|
2263
2284
|
const payload = props.api.payload;
|
|
2264
2285
|
if (!payload) return {};
|
|
2265
|
-
//
|
|
2266
|
-
|
|
2286
|
+
// 结构化对象格式: { raw, parsed: { type, data } }
|
|
2287
|
+
if (typeof payload === 'object' && payload.parsed?.data) {
|
|
2288
|
+
return payload.parsed.data;
|
|
2289
|
+
}
|
|
2290
|
+
// 普通对象(Whistle 预解析),提取第一层 string/number/boolean
|
|
2291
|
+
if (typeof payload === 'object' && !Array.isArray(payload)) {
|
|
2292
|
+
const result = {};
|
|
2293
|
+
Object.keys(payload).forEach(key => {
|
|
2294
|
+
const val = payload[key];
|
|
2295
|
+
if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
|
|
2296
|
+
result[key] = String(val);
|
|
2297
|
+
}
|
|
2298
|
+
});
|
|
2299
|
+
return result;
|
|
2300
|
+
}
|
|
2301
|
+
if (typeof payload === 'string') {
|
|
2302
|
+
// JSON 格式
|
|
2303
|
+
try {
|
|
2304
|
+
const parsed = JSON.parse(payload);
|
|
2305
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
2306
|
+
const result = {};
|
|
2307
|
+
Object.keys(parsed).forEach(key => {
|
|
2308
|
+
const val = parsed[key];
|
|
2309
|
+
if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
|
|
2310
|
+
result[key] = String(val);
|
|
2311
|
+
}
|
|
2312
|
+
});
|
|
2313
|
+
return result;
|
|
2314
|
+
}
|
|
2315
|
+
} catch {
|
|
2316
|
+
// 不是 JSON,继续尝试
|
|
2317
|
+
}
|
|
2318
|
+
// application/x-www-form-urlencoded 格式
|
|
2319
|
+
if (payload.includes('=')) {
|
|
2320
|
+
try {
|
|
2321
|
+
const result = {};
|
|
2322
|
+
new URLSearchParams(payload).forEach((value, key) => {
|
|
2323
|
+
result[key] = value;
|
|
2324
|
+
});
|
|
2325
|
+
if (Object.keys(result).length > 0) return result;
|
|
2326
|
+
} catch {
|
|
2327
|
+
// 解析失败
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
return {};
|
|
2267
2332
|
});
|
|
2268
|
-
const name = (0,
|
|
2269
|
-
const mock = (0,
|
|
2270
|
-
const mockVersion = (0,
|
|
2271
|
-
const date = (0,
|
|
2272
|
-
const isSourceReadonly = (0,
|
|
2273
|
-
const activeEditorContent = (0,
|
|
2333
|
+
const name = (0,vue__WEBPACK_IMPORTED_MODULE_12__.computed)(() => props.api.name);
|
|
2334
|
+
const mock = (0,vue__WEBPACK_IMPORTED_MODULE_12__.computed)(() => props.api.mock);
|
|
2335
|
+
const mockVersion = (0,vue__WEBPACK_IMPORTED_MODULE_12__.computed)(() => props.api.mockVersion);
|
|
2336
|
+
const date = (0,vue__WEBPACK_IMPORTED_MODULE_12__.computed)(() => props.api.date);
|
|
2337
|
+
const isSourceReadonly = (0,vue__WEBPACK_IMPORTED_MODULE_12__.computed)(() => mockMode.value === 'version' && currentFile.value.type === 'source');
|
|
2338
|
+
const activeEditorContent = (0,vue__WEBPACK_IMPORTED_MODULE_12__.computed)(() => mockMode.value === 'match' ? currentMatchRule.value?.content : currentFile.value.content);
|
|
2274
2339
|
|
|
2275
2340
|
/**
|
|
2276
2341
|
* 获取 source 版本数据(仅用于未保存文件)
|
|
@@ -2335,7 +2400,7 @@ const __default__ = {
|
|
|
2335
2400
|
*/
|
|
2336
2401
|
const getHistoryVersions = () => {
|
|
2337
2402
|
const fileId = props.api.id;
|
|
2338
|
-
return (0,
|
|
2403
|
+
return (0,_service__WEBPACK_IMPORTED_MODULE_15__.getVersions)({
|
|
2339
2404
|
fileId
|
|
2340
2405
|
}).then(res => {
|
|
2341
2406
|
console.log('[ResponsePanel] 后端返回的版本数据:', res);
|
|
@@ -2414,7 +2479,7 @@ const __default__ = {
|
|
|
2414
2479
|
}
|
|
2415
2480
|
};
|
|
2416
2481
|
const deleteVersionHandler = (item, index) => {
|
|
2417
|
-
(0,
|
|
2482
|
+
(0,_service__WEBPACK_IMPORTED_MODULE_15__.deleteVersion)({
|
|
2418
2483
|
url: props.api.name,
|
|
2419
2484
|
fileId: props.api.id,
|
|
2420
2485
|
versionId: item.id
|
|
@@ -2489,7 +2554,7 @@ const __default__ = {
|
|
|
2489
2554
|
// 未保存文件:先保存文件,再创建版本
|
|
2490
2555
|
console.log('[ResponsePanel] 文件未保存,先保存文件');
|
|
2491
2556
|
try {
|
|
2492
|
-
const saveResult = await (0,
|
|
2557
|
+
const saveResult = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.saveFile)({
|
|
2493
2558
|
url: props.api.url,
|
|
2494
2559
|
method: props.api.method,
|
|
2495
2560
|
status: props.api.status,
|
|
@@ -2520,7 +2585,7 @@ const __default__ = {
|
|
|
2520
2585
|
return;
|
|
2521
2586
|
}
|
|
2522
2587
|
}
|
|
2523
|
-
(0,
|
|
2588
|
+
(0,_service__WEBPACK_IMPORTED_MODULE_15__.addVersion)({
|
|
2524
2589
|
versionName: versionModal.name,
|
|
2525
2590
|
fileId,
|
|
2526
2591
|
description: versionModal.description,
|
|
@@ -2550,7 +2615,7 @@ const __default__ = {
|
|
|
2550
2615
|
// 添加版本成功后,重新获取版本列表(确保数据同步)
|
|
2551
2616
|
// ⚠️ 关键修复:使用保存的 fileId,而不是 props.api.id(可能仍是 undefined)
|
|
2552
2617
|
console.log('[ResponsePanel] 使用 fileId 刷新版本列表:', fileId);
|
|
2553
|
-
(0,
|
|
2618
|
+
(0,_service__WEBPACK_IMPORTED_MODULE_15__.getVersions)({
|
|
2554
2619
|
fileId
|
|
2555
2620
|
}).then(versionRes => {
|
|
2556
2621
|
console.log('[ResponsePanel] 后端返回的版本数据:', versionRes);
|
|
@@ -2621,7 +2686,7 @@ const __default__ = {
|
|
|
2621
2686
|
}
|
|
2622
2687
|
|
|
2623
2688
|
// 调用后端API更新版本元信息
|
|
2624
|
-
(0,
|
|
2689
|
+
(0,_service__WEBPACK_IMPORTED_MODULE_15__.updateVersionMeta)({
|
|
2625
2690
|
url: props.api.name,
|
|
2626
2691
|
fileId: props.api.id,
|
|
2627
2692
|
versionId: currentFile.value.id,
|
|
@@ -2691,7 +2756,7 @@ const __default__ = {
|
|
|
2691
2756
|
effect: tempEffect
|
|
2692
2757
|
};
|
|
2693
2758
|
const versionName = item.type === 'history' ? item.filename : '';
|
|
2694
|
-
(0,
|
|
2759
|
+
(0,_service__WEBPACK_IMPORTED_MODULE_15__.setMockVersion)({
|
|
2695
2760
|
url: props.api.name,
|
|
2696
2761
|
versionName
|
|
2697
2762
|
}) // Changed from name to url
|
|
@@ -2713,7 +2778,7 @@ const __default__ = {
|
|
|
2713
2778
|
currentFile.value.effect = true;
|
|
2714
2779
|
};
|
|
2715
2780
|
const updateHistory = () => {
|
|
2716
|
-
(0,
|
|
2781
|
+
(0,_service__WEBPACK_IMPORTED_MODULE_15__.updateVersionContent)({
|
|
2717
2782
|
versionId: currentFile.value.id,
|
|
2718
2783
|
content: currentFile.value.content,
|
|
2719
2784
|
fileId: props.api.id
|
|
@@ -2757,7 +2822,7 @@ const __default__ = {
|
|
|
2757
2822
|
responseList.value = source ? [source, ...versions] : versions;
|
|
2758
2823
|
};
|
|
2759
2824
|
const updateApiDataHandler = () => {
|
|
2760
|
-
(0,
|
|
2825
|
+
(0,_service__WEBPACK_IMPORTED_MODULE_15__.updateApiData)(props.api.name, currentFile.value.content).then(res => {
|
|
2761
2826
|
const {
|
|
2762
2827
|
code
|
|
2763
2828
|
} = res;
|
|
@@ -2818,7 +2883,7 @@ const __default__ = {
|
|
|
2818
2883
|
/** 保存当前匹配规则(条件 + 内容) */
|
|
2819
2884
|
const updateMatchRuleHandler = async () => {
|
|
2820
2885
|
if (!currentMatchRule.value) return;
|
|
2821
|
-
const res = await (0,
|
|
2886
|
+
const res = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.updateMatchVersion)({
|
|
2822
2887
|
url: props.api.name,
|
|
2823
2888
|
matchVersionId: currentMatchRule.value.id,
|
|
2824
2889
|
conditions: currentMatchRule.value.conditions,
|
|
@@ -3023,13 +3088,36 @@ const __default__ = {
|
|
|
3023
3088
|
const switchMode = async targetMode => {
|
|
3024
3089
|
modeDropdownVisible.value = false;
|
|
3025
3090
|
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
3091
|
if (targetMode === 'match') {
|
|
3092
|
+
// 未保存文件:先自动保存,再切换
|
|
3093
|
+
if (!props.api.id) {
|
|
3094
|
+
try {
|
|
3095
|
+
const saveResult = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.saveFile)({
|
|
3096
|
+
url: props.api.url,
|
|
3097
|
+
method: props.api.method,
|
|
3098
|
+
status: props.api.status,
|
|
3099
|
+
session: props.api.session,
|
|
3100
|
+
config: {
|
|
3101
|
+
mock: props.api.mock || false,
|
|
3102
|
+
locked: props.api.locked || false
|
|
3103
|
+
}
|
|
3104
|
+
});
|
|
3105
|
+
if (saveResult.code !== 200) {
|
|
3106
|
+
element_ui_lib_message__WEBPACK_IMPORTED_MODULE_2___default().error(`保存文件失败: ${saveResult.msg || '未知错误'}`);
|
|
3107
|
+
return;
|
|
3108
|
+
}
|
|
3109
|
+
emit('fileSaved', {
|
|
3110
|
+
url: props.api.url,
|
|
3111
|
+
savedFile: saveResult.data.file
|
|
3112
|
+
});
|
|
3113
|
+
} catch (err) {
|
|
3114
|
+
element_ui_lib_message__WEBPACK_IMPORTED_MODULE_2___default().error(`保存文件失败: ${err.message || '未知错误'}`);
|
|
3115
|
+
return;
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3031
3119
|
// 切换为条件匹配
|
|
3032
|
-
const res = await (0,
|
|
3120
|
+
const res = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.setMatchMode)({
|
|
3033
3121
|
url: props.api.name,
|
|
3034
3122
|
enable: true
|
|
3035
3123
|
});
|
|
@@ -3046,7 +3134,7 @@ const __default__ = {
|
|
|
3046
3134
|
emit('changeMockVersion', MATCH_MODE_VALUE);
|
|
3047
3135
|
} else {
|
|
3048
3136
|
// 切换回版本直选
|
|
3049
|
-
const res = await (0,
|
|
3137
|
+
const res = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.setMatchMode)({
|
|
3050
3138
|
url: props.api.name,
|
|
3051
3139
|
enable: false
|
|
3052
3140
|
});
|
|
@@ -3070,7 +3158,7 @@ const __default__ = {
|
|
|
3070
3158
|
/** 加载匹配规则列表 */
|
|
3071
3159
|
const loadMatchVersions = async () => {
|
|
3072
3160
|
if (!props.api.id || !props.api.name) return;
|
|
3073
|
-
const res = await (0,
|
|
3161
|
+
const res = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.getMatchVersions)({
|
|
3074
3162
|
url: props.api.name
|
|
3075
3163
|
});
|
|
3076
3164
|
if (res.code !== 200) {
|
|
@@ -3150,7 +3238,7 @@ const __default__ = {
|
|
|
3150
3238
|
return;
|
|
3151
3239
|
}
|
|
3152
3240
|
if (matchRuleModal.status === 'create') {
|
|
3153
|
-
const res = await (0,
|
|
3241
|
+
const res = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.addMatchVersion)({
|
|
3154
3242
|
url: props.api.name,
|
|
3155
3243
|
name: matchRuleModal.name.trim(),
|
|
3156
3244
|
description: matchRuleModal.description
|
|
@@ -3169,7 +3257,7 @@ const __default__ = {
|
|
|
3169
3257
|
element_ui_lib_message__WEBPACK_IMPORTED_MODULE_2___default().success('创建成功');
|
|
3170
3258
|
} else {
|
|
3171
3259
|
// 编辑元信息
|
|
3172
|
-
const res = await (0,
|
|
3260
|
+
const res = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.updateMatchVersionMeta)({
|
|
3173
3261
|
url: props.api.name,
|
|
3174
3262
|
matchVersionId: matchRuleModal.editingRule.id,
|
|
3175
3263
|
name: matchRuleModal.name.trim(),
|
|
@@ -3203,7 +3291,7 @@ const __default__ = {
|
|
|
3203
3291
|
} catch {
|
|
3204
3292
|
return; // 取消
|
|
3205
3293
|
}
|
|
3206
|
-
const res = await (0,
|
|
3294
|
+
const res = await (0,_service__WEBPACK_IMPORTED_MODULE_15__.deleteMatchVersion)({
|
|
3207
3295
|
url: props.api.name,
|
|
3208
3296
|
matchVersionId: rule.id
|
|
3209
3297
|
});
|
|
@@ -3238,7 +3326,7 @@ const __default__ = {
|
|
|
3238
3326
|
* - 已保存文件: file-${id}
|
|
3239
3327
|
* - 未保存文件: cache-${url}-${captureTime}
|
|
3240
3328
|
*/
|
|
3241
|
-
(0,
|
|
3329
|
+
(0,vue__WEBPACK_IMPORTED_MODULE_12__.watch)(() => props.rowKey, (newRowKey, oldRowKey) => {
|
|
3242
3330
|
// 判断是否为已保存文件(已保存文件有 id,未保存文件没有)
|
|
3243
3331
|
const isSavedFile = !!props.api.id;
|
|
3244
3332
|
|
|
@@ -3384,8 +3472,8 @@ const __default__ = {
|
|
|
3384
3472
|
handleMatchRuleConfirm,
|
|
3385
3473
|
deleteMatchRuleHandler,
|
|
3386
3474
|
refreshVersions,
|
|
3387
|
-
JsonEditor:
|
|
3388
|
-
MatchConditionEditor:
|
|
3475
|
+
JsonEditor: _common_JsonEditor_vue__WEBPACK_IMPORTED_MODULE_13__["default"],
|
|
3476
|
+
MatchConditionEditor: _MatchConditionEditor_vue__WEBPACK_IMPORTED_MODULE_14__["default"]
|
|
3389
3477
|
};
|
|
3390
3478
|
}
|
|
3391
3479
|
}));
|
|
@@ -5274,7 +5362,7 @@ var render = function render() {
|
|
|
5274
5362
|
staticClass: "response-panel"
|
|
5275
5363
|
}, [_c("div", {
|
|
5276
5364
|
staticClass: "response-panel__header"
|
|
5277
|
-
}, [_setup.props.api
|
|
5365
|
+
}, [_setup.props.api ? _c("div", {
|
|
5278
5366
|
staticClass: "response-panel__mode-selector"
|
|
5279
5367
|
}, [_c("div", {
|
|
5280
5368
|
staticClass: "response-panel__mode-btn",
|