初步完成axios、fetch组件

This commit is contained in:
fofolee 2024-12-29 09:00:44 +08:00
parent 99cadf3130
commit e474e475d9
11 changed files with 982 additions and 80 deletions

View File

@ -84,6 +84,14 @@
<template v-else-if="command.hasUBrowserEditor">
<UBrowserEditor v-model="argvLocal" class="col" />
</template>
<!-- Axios编辑器 -->
<template v-else-if="command.hasAxiosEditor">
<AxiosConfigEditor v-model="argvLocal" class="col" />
</template>
<!-- Fetch编辑器 -->
<template v-else-if="command.hasFetchEditor">
<FetchConfigEditor v-model="argvLocal" class="col" />
</template>
<!-- 普通参数输入 -->
<template v-else>
<VariableInput
@ -106,6 +114,8 @@ import { defineComponent, inject } from "vue";
import KeyEditor from "./KeyEditor.vue";
import UBrowserEditor from "./ubrowser/UBrowserEditor.vue";
import VariableInput from "./VariableInput.vue";
import AxiosConfigEditor from "./http/AxiosConfigEditor.vue";
import FetchConfigEditor from "./http/FetchConfigEditor.vue";
import { validateVariableName } from "js/common/variableValidator";
export default defineComponent({
@ -114,6 +124,8 @@ export default defineComponent({
KeyEditor,
UBrowserEditor,
VariableInput,
AxiosConfigEditor,
FetchConfigEditor,
},
props: {
command: {
@ -150,12 +162,33 @@ export default defineComponent({
},
argvLocal: {
get() {
if (this.command.hasAxiosEditor || this.command.hasFetchEditor) {
//
if (
this.command.argv &&
!this.command.argv.includes("axios.") &&
!this.command.argv.includes("fetch(")
) {
try {
return JSON.parse(this.command.argv);
} catch (e) {
return {};
}
}
//
return this.command.argv || {};
}
return this.command.argv;
},
set(value) {
const updatedCommand = {
...this.command,
argv: value,
argv:
this.command.hasAxiosEditor || this.command.hasFetchEditor
? typeof value === "string"
? value
: JSON.stringify(value)
: value,
};
this.$emit("update:command", updatedCommand);
},

View File

@ -28,6 +28,7 @@
size="sm"
class="string-toggle"
@click="toggleStringType"
v-if="!hasSelectedVariable"
>
<q-tooltip>{{
isString
@ -45,6 +46,7 @@
}"
class="variable-dropdown"
size="sm"
v-if="variables.length"
>
<q-list class="variable-list">
<q-item-label header class="text-subtitle2">
@ -52,9 +54,9 @@
选择变量
</q-item-label>
<q-separator v-if="variables.length > 0" />
<q-separator />
<template v-if="variables.length > 0">
<template v-if="variables.length">
<q-item
v-for="variable in variables"
:key="variable.name"
@ -73,15 +75,6 @@
</q-item-section>
</q-item>
</template>
<q-item v-else class="text-grey-6">
<q-item-section class="text-center">
<q-item-label>暂无可用变量</q-item-label>
<q-item-label caption>
点击命令卡片的获取输出按钮输入保存的变量名
</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</template>
@ -237,7 +230,7 @@ export default defineComponent({
},
watch: {
// <EFBFBD><EFBFBD><EFBFBD>
//
modelValue: {
immediate: true,
handler(newVal) {
@ -354,8 +347,8 @@ export default defineComponent({
.number-controls {
border-left: 1px solid rgba(0, 0, 0, 0.12);
margin-left: 4px;
padding: 2px 4px;
/* margin-left: 4px; */
padding-left: 2px;
}
.body--dark .number-controls {
@ -368,7 +361,7 @@ export default defineComponent({
padding: 2px;
margin: 0;
min-height: 20px;
min-width: 24px;
/* min-width: 24px; */
}
.number-btn:hover {

View File

@ -0,0 +1,282 @@
<template>
<div class="row q-col-gutter-sm">
<!-- 基础配置 -->
<div class="col-12">
<div class="row q-col-gutter-sm">
<div class="col-3">
<q-select
v-model="localConfig.method"
:options="[
'GET',
'POST',
'PUT',
'DELETE',
'PATCH',
'HEAD',
'OPTIONS',
]"
label="请求方法"
dense
outlined
emit-value
map-options
@update:model-value="updateConfig"
>
<template v-slot:prepend>
<q-icon name="send" />
</template>
</q-select>
</div>
<div class="col">
<VariableInput
v-model="localConfig.url"
label="请求地址"
:command="{ icon: 'link' }"
@update:model-value="updateConfig"
/>
</div>
</div>
</div>
<!-- Headers -->
<div class="col-12">
<HeaderEditor
:headers="localConfig.headers"
@input="
(val) => {
localConfig.headers = val;
updateConfig();
}
"
/>
</div>
<!-- 请求参数 -->
<div class="col-12">
<VariableInput
v-model="localConfig.params"
label="URL参数"
:command="{ icon: 'link' }"
@update:model-value="updateConfig"
/>
</div>
<!-- 请求体 -->
<div class="col-12">
<VariableInput
v-model="localConfig.data"
label="请求体"
:command="{ icon: 'data_object' }"
@update:model-value="updateConfig"
/>
</div>
<!-- 超时设置 -->
<div class="col-12">
<VariableInput
v-model="localConfig.timeout"
label="超时时间(ms)"
:command="{ icon: 'timer', inputType: 'number' }"
@update:model-value="updateConfig"
/>
</div>
<!-- 其他选项 -->
<div class="col-12">
<div class="row q-col-gutter-sm">
<div class="col-6">
<q-checkbox
v-model="localConfig.withCredentials"
label="发送凭证"
@update:model-value="updateConfig"
/>
</div>
<div class="col-6">
<q-checkbox
v-model="localConfig.decompress"
label="自动解压"
@update:model-value="updateConfig"
/>
</div>
</div>
</div>
<!-- 响应类型 -->
<div class="col-12">
<q-select
v-model="localConfig.responseType"
:options="['json', 'text', 'blob', 'arraybuffer']"
label="响应类型"
dense
outlined
emit-value
map-options
@update:model-value="updateConfig"
>
<template v-slot:prepend>
<q-icon name="data_object" />
</template>
</q-select>
</div>
</div>
</template>
<script>
import { defineComponent } from "vue";
import VariableInput from "../VariableInput.vue";
import HeaderEditor from "./HeaderEditor.vue";
import { formatJsonVariables } from "js/composer/formatString";
export default defineComponent({
name: "AxiosConfigEditor",
components: {
VariableInput,
HeaderEditor,
},
props: {
modelValue: {
type: [Object, String],
default: () => ({}),
},
},
emits: ["update:modelValue"],
data() {
let initialConfig = {};
if (typeof this.modelValue === "string") {
try {
//
const match = this.modelValue.match(
/axios\.\w+\([^{]*({\s*[^]*})\s*\)/
);
if (match) {
initialConfig = JSON.parse(match[1]);
}
} catch (e) {
console.warn("Failed to parse config from code string");
}
} else {
initialConfig = this.modelValue;
}
return {
localConfig: {
url: "",
method: "GET",
headers: {},
params: "",
data: "",
timeout: 0,
withCredentials: false,
responseType: "json",
decompress: true,
...initialConfig,
},
};
},
created() {
// headers
this.headers = Object.entries(this.localConfig.headers || {}).map(
([name, value]) => ({ name, value })
);
},
methods: {
updateConfig() {
//
const config = { ...this.localConfig };
Object.keys(config).forEach((key) => {
if (
config[key] === "" ||
config[key] === null ||
config[key] === undefined
) {
delete config[key];
}
});
//
const { method = "GET", url, data, ...restConfig } = config;
if (!url) return;
let code = "";
const variableFields = ["headers", "timeout", "params", "data"];
if (method.toUpperCase() === "GET") {
code = `axios.get(${url}${
Object.keys(restConfig).length
? `, ${formatJsonVariables(restConfig, variableFields)}`
: ""
})`;
} else {
const { data: reqData, ...configWithoutData } = restConfig;
code = `axios.${method.toLowerCase()}(${url}${
reqData ? `, ${reqData}` : ", undefined"
}${
Object.keys(configWithoutData).length
? `, ${formatJsonVariables(restConfig, variableFields)}`
: ""
})`;
}
this.$emit("update:modelValue", code);
},
addHeader() {
this.headers.push({ name: "", value: "" });
},
removeHeader(index) {
this.headers.splice(index, 1);
this.updateHeaders();
},
updateHeaders() {
this.localConfig.headers = this.headers.reduce((acc, header) => {
if (header.name) {
// 使
if (
header.value &&
!header.value.startsWith('"') &&
!header.value.endsWith('"')
) {
acc[header.name] = header.value;
} else {
// <EFBFBD><EFBFBD>JSON使
try {
acc[header.name] = JSON.parse(header.value);
} catch (e) {
acc[header.name] = header.value;
}
}
}
return acc;
}, {});
this.updateConfig();
},
},
watch: {
modelValue: {
deep: true,
handler(newValue) {
if (typeof newValue === "string") {
//
try {
const config = JSON.parse(newValue);
this.localConfig = {
...this.localConfig,
...config,
};
this.headers = Object.entries(config.headers || {}).map(
([name, value]) => ({ name, value })
);
} catch (e) {
//
}
} else {
this.localConfig = {
...this.localConfig,
...newValue,
};
this.headers = Object.entries(this.localConfig.headers || {}).map(
([name, value]) => ({ name, value })
);
}
},
},
},
});
</script>

View File

@ -0,0 +1,276 @@
<template>
<div class="row q-col-gutter-sm">
<!-- 基础配置 -->
<div class="col-12">
<VariableInput
v-model="localConfig.url"
label="请求地址"
:command="{ icon: 'link' }"
@update:model-value="updateConfig"
/>
</div>
<div class="col-12">
<q-select
v-model="localConfig.method"
:options="['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']"
label="请求方法"
dense
outlined
emit-value
map-options
@update:model-value="updateConfig"
>
<template v-slot:prepend>
<q-icon name="send" />
</template>
</q-select>
</div>
<!-- Headers -->
<div class="col-12">
<HeaderEditor
:headers="localConfig.headers"
@input="
(val) => {
localConfig.headers = val;
updateConfig();
}
"
/>
</div>
<!-- 请求体 -->
<div class="col-12">
<VariableInput
v-model="localConfig.body"
label="请求体"
:command="{ icon: 'data_object' }"
@update:model-value="updateConfig"
/>
</div>
<!-- 重定向策略 -->
<div class="col-12">
<q-select
v-model="localConfig.redirect"
:options="['follow', 'error', 'manual']"
label="重定向策略"
dense
outlined
emit-value
map-options
@update:model-value="updateConfig"
>
<template v-slot:prepend>
<q-icon name="alt_route" />
</template>
</q-select>
</div>
<!-- 其他选项 -->
<div class="col-12">
<div class="row q-col-gutter-sm">
<div class="col-6">
<VariableInput
v-model="localConfig.follow"
label="最大重定向次数"
:command="{ icon: 'repeat', inputType: 'number' }"
@update:model-value="updateConfig"
/>
</div>
<div class="col-6">
<VariableInput
v-model="localConfig.timeout"
label="超时时间(ms)"
:command="{ icon: 'timer', inputType: 'number' }"
@update:model-value="updateConfig"
/>
</div>
</div>
</div>
<div class="col-12">
<div class="row q-col-gutter-sm">
<div class="col-6">
<VariableInput
v-model="localConfig.size"
label="最大响应大小(bytes)"
:command="{ icon: 'data_usage', inputType: 'number' }"
@update:model-value="updateConfig"
/>
</div>
<div class="col-6">
<q-checkbox
v-model="localConfig.compress"
label="启用压缩"
@update:model-value="updateConfig"
/>
</div>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from "vue";
import VariableInput from "../VariableInput.vue";
import HeaderEditor from "./HeaderEditor.vue";
export default defineComponent({
name: "FetchConfigEditor",
components: {
VariableInput,
HeaderEditor,
},
props: {
modelValue: {
type: [Object, String],
default: () => ({}),
},
},
emits: ["update:modelValue"],
data() {
let initialConfig = {};
if (typeof this.modelValue === "string") {
try {
//
const match = this.modelValue.match(/fetch\([^{]*({\s*[^]*})\s*\)/);
if (match) {
initialConfig = JSON.parse(match[1]);
}
} catch (e) {
console.warn("Failed to parse config from code string");
}
} else {
initialConfig = this.modelValue;
}
return {
localConfig: {
url: "",
method: "GET",
headers: {},
body: "",
redirect: "follow",
follow: 20,
timeout: 0,
size: 0,
compress: true,
...initialConfig,
},
};
},
created() {
// headers
this.headers = Object.entries(this.localConfig.headers || {}).map(
([name, value]) => ({ name, value })
);
},
methods: {
updateConfig() {
//
const config = { ...this.localConfig };
Object.keys(config).forEach((key) => {
if (
config[key] === "" ||
config[key] === null ||
config[key] === undefined
) {
delete config[key];
}
});
//
const { url, body, headers, ...init } = config;
if (!url) return;
//
if (body) {
init.body = body;
}
// headers
if (headers && Object.keys(headers).length) {
init.headers = headers;
}
const variableFields = [
"headers",
"body",
"redirect",
"follow",
"timeout",
"size",
];
const code = `fetch(${url}${
Object.keys(init).length
? `, ${formatJsonVariables(init, variableFields)}`
: ""
})`;
this.$emit("update:modelValue", code);
},
addHeader() {
this.headers.push({ name: "", value: "" });
},
removeHeader(index) {
this.headers.splice(index, 1);
this.updateHeaders();
},
updateHeaders() {
this.localConfig.headers = this.headers.reduce((acc, header) => {
if (header.name) {
// 使
if (
header.value &&
!header.value.startsWith('"') &&
!header.value.endsWith('"')
) {
acc[header.name] = header.value;
} else {
// JSON使
try {
acc[header.name] = JSON.parse(header.value);
} catch (e) {
acc[header.name] = header.value;
}
}
}
return acc;
}, {});
this.updateConfig();
},
},
watch: {
modelValue: {
deep: true,
handler(newValue) {
if (typeof newValue === "string") {
//
try {
const config = JSON.parse(newValue);
this.localConfig = {
...this.localConfig,
...config,
};
this.headers = Object.entries(config.headers || {}).map(
([name, value]) => ({ name, value })
);
} catch (e) {
//
}
} else {
this.localConfig = {
...this.localConfig,
...newValue,
};
this.headers = Object.entries(this.localConfig.headers || {}).map(
([name, value]) => ({ name, value })
);
}
},
},
},
});
</script>

View File

@ -0,0 +1,198 @@
<template>
<div class="q-pa-sm">
<!-- 添加新header按钮 -->
<div class="row items-center q-gutter-sm">
<q-select
v-model="newHeaderField"
:options="commonHeaders"
label="添加常用Header"
dense
outlined
emit-value
map-options
style="width: 200px"
@update:model-value="addCommonHeader"
>
<template v-slot:prepend>
<q-icon name="add" />
</template>
</q-select>
<q-btn flat round dense icon="add" @click="addCustomHeader" />
</div>
<!-- header列表 -->
<div
v-for="(header, index) in headersList"
:key="index"
class="row q-col-gutter-sm q-mt-sm items-center"
>
<!-- header名称 -->
<div class="col-4">
<q-input
v-if="!header.isCommon"
v-model="header.name"
label="Header名称"
dense
outlined
@update:model-value="emitUpdate"
/>
<q-input
v-else
:model-value="header.name"
label="Header名称"
dense
outlined
readonly
/>
</div>
<!-- header值 -->
<div class="col">
<div class="row items-center q-col-gutter-sm">
<!-- User-Agent特殊处理 -->
<template v-if="header.name === 'User-Agent'">
<div class="col">
<VariableInput
v-model="header.value"
label="User Agent"
:command="{ icon: 'devices' }"
@update:model-value="emitUpdate"
/>
</div>
<div class="col-auto">
<q-btn-dropdown flat dense icon="list">
<q-list>
<q-item
v-for="ua in userAgentOptions"
:key="ua.value"
clickable
v-close-popup
@click="setUserAgent(header, ua.value)"
>
<q-item-section>
<q-item-label>{{ ua.label }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
</template>
<!-- 其他header -->
<template v-else>
<div class="col">
<VariableInput
v-model="header.value"
:label="header.name"
:command="{ icon: 'code' }"
@update:model-value="emitUpdate"
/>
</div>
</template>
<!-- 删除按钮 -->
<div class="col-auto">
<q-btn
flat
round
dense
icon="delete"
@click="removeHeader(index)"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from "vue";
import VariableInput from "../VariableInput.vue";
import { userAgent, commonHeaders } from "js/options/httpHeaders";
export default defineComponent({
name: "HeaderEditor",
components: {
VariableInput,
},
props: {
headers: {
type: [Object, String],
default: () => ({}),
},
},
emits: ["input"],
data() {
let headersObj = {};
if (typeof this.headers === "string") {
try {
const match = this.headers.match(/headers":\s*({[^}]+})/);
if (match) {
headersObj = JSON.parse(match[1]);
}
} catch (e) {
console.warn("Failed to parse headers from code string");
}
} else if (typeof this.headers === "object") {
headersObj = this.headers;
}
return {
headersList: Object.entries(headersObj).map(([name, value]) => ({
name,
value: typeof value === "string" ? value : JSON.stringify(value),
isCommon: commonHeaders.some((h) => h.value === name),
})),
newHeaderField: null,
commonHeaders,
userAgentOptions: userAgent,
};
},
methods: {
addCommonHeader(headerName) {
if (!headerName) return;
if (this.headersList.some((h) => h.name === headerName)) {
this.newHeaderField = null;
return;
}
this.headersList.push({
name: headerName,
value: "",
isCommon: true,
});
this.$nextTick(() => {
this.newHeaderField = null;
});
this.emitUpdate();
},
addCustomHeader() {
this.headersList.push({
name: "",
value: "",
isCommon: false,
});
},
removeHeader(index) {
this.headersList.splice(index, 1);
this.emitUpdate();
},
setUserAgent(header, value) {
header.value = value;
this.emitUpdate();
},
emitUpdate() {
const headers = {};
this.headersList.forEach((header) => {
if (header.name && header.value) {
headers[header.name] = header.value;
}
});
this.$emit("input", headers);
},
},
});
</script>

View File

@ -67,6 +67,7 @@
<script>
import { defineComponent } from "vue";
import { userAgent } from "js/options/httpHeaders";
import VariableInput from "components/editor/composer/VariableInput.vue";
export default defineComponent({
@ -97,58 +98,7 @@ export default defineComponent({
timeout: 60000,
},
},
userAgentOptions: [
{
label: "Chrome (Windows)",
value:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
},
{
label: "Chrome (macOS)",
value:
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
},
{
label: "Chrome (Linux)",
value:
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
},
{
label: "IE 11",
value:
"Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko",
},
{
label: "微信 (Android)",
value:
"Mozilla/5.0 (Linux; Android 14; Pixel 8 Build/UQ1A.240205.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/122.0.6261.64 Mobile Safari/537.36 XWEB/1160027 MMWEBSDK/20231202 MMWEBID/2308 MicroMessenger/8.0.47.2560(0x28002F35) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64",
},
{
label: "微信 (iOS)",
value:
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.47(0x18002f2c) NetType/WIFI Language/zh_CN",
},
{
label: "iPhone",
value:
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
},
{
label: "iPad",
value:
"Mozilla/5.0 (iPad; CPU OS 17_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
},
{
label: "Android Phone",
value:
"Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36",
},
{
label: "Android Tablet",
value:
"Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
},
],
userAgentOptions: userAgent,
};
},
created() {

View File

@ -31,6 +31,7 @@
<script>
import { defineComponent } from "vue";
import { deviceName } from "js/options/httpHeaders";
import VariableInput from "components/editor/composer/VariableInput.vue";
export default defineComponent({
@ -56,18 +57,7 @@ export default defineComponent({
data() {
return {
selectedDevice: null,
deviceOptions: [
{ label: "iPhone 11", value: "iPhone 11" },
{ label: "iPhone X", value: "iPhone X" },
{ label: "iPad", value: "iPad" },
{ label: "iPhone 6/7/8 Plus", value: "iPhone 6/7/8 Plus" },
{ label: "iPhone 6/7/8", value: "iPhone 6/7/8" },
{ label: "iPhone 5/SE", value: "iPhone 5/SE" },
{ label: "HUAWEI Mate10", value: "HUAWEI Mate10" },
{ label: "HUAWEI Mate20", value: "HUAWEI Mate20" },
{ label: "HUAWEI Mate30", value: "HUAWEI Mate30" },
{ label: "HUAWEI Mate30 Pro", value: "HUAWEI Mate30 Pro" },
],
deviceOptions: deviceName,
};
},
methods: {

View File

@ -48,6 +48,22 @@ export const commandCategories = [
isAsync: true,
icon: "public",
},
{
value: "axios",
label: "发送HTTP请求(Axios)",
desc: "使用Axios发送HTTP请求",
hasAxiosEditor: true,
isAsync: true,
icon: "http",
},
{
value: "fetch",
label: "发送HTTP请求(Fetch)",
desc: "使用Fetch API发送HTTP请求",
hasFetchEditor: true,
isAsync: true,
icon: "http",
},
],
},
{

View File

@ -0,0 +1,85 @@
/**
* 处理来自VariableInput的值
* @param {string} value JSON.stringify后的值
* @returns {string} 处理后的值包含外层引号
*/
const processVariableValue = (value) => {
// 处理字符串模式:\"xxx\" -> "xxx"
if (value.startsWith('"\\"') && value.endsWith('\\""')) {
return `"${value.slice(3, -3)}"`;
}
// 处理非字符串模式:直接去掉外层引号
return value.slice(1, -1);
};
/**
* 递归处理对象的值
* @param {Object} obj 要处理的对象
* @param {string} parentPath 父路径
* @param {string[]|null} variableFields 需要处理的字段列表null表示处理所有字段
* @returns {string} 处理后的字符串
*/
const processObject = (obj, parentPath = "", variableFields) => {
let result = "{\n";
const entries = Object.entries(obj);
entries.forEach(([key, value], index) => {
const currentPath = parentPath ? `${parentPath}.${key}` : key;
let valueStr = "";
// 检查是否需要处理当前字段
const shouldProcess =
!variableFields || // 不传递variableFields则处理所有字段
variableFields.includes(parentPath) || // 父字段是完整处理字段
variableFields.includes(key) || // 当前字段是完整处理字段
variableFields.includes(currentPath) || // 当前路径精确匹配
variableFields.some((field) => field.startsWith(currentPath + ".")); // 当前路径是指定路径的父路径
// 处理对象类型
if (typeof value === "object" && value !== null) {
valueStr = processObject(value, currentPath, variableFields);
}
// 处理字符串类型
else if (typeof value === "string") {
if (shouldProcess) {
valueStr = processVariableValue(JSON.stringify(value));
} else {
valueStr = JSON.stringify(value);
}
}
// 其他类型直接 stringify
else {
valueStr = JSON.stringify(value);
}
// 添加缩进
const indent = " ".repeat(parentPath.split(".").length + 1);
result += `${indent}"${key}": ${valueStr}`;
if (index < entries.length - 1) result += ",";
result += "\n";
});
// 闭合括号的缩进
const closingIndent = " ".repeat(parentPath.split(".").length);
result += `${closingIndent}}`;
return result;
};
/**
* 处理JSON字符串中的值
* 只处理来自VariableInput的字段支持完整字段处理和指定路径处理
* 1. 完整字段处理 headers - 处理整个对象及其所有子字段
* 2. 指定路径处理 data.headers.Referer - 只处理特定路径
* 3. 不传递 variableFields 则处理所有字段
* @param {string} jsonStr JSON字符串
* @param {string[]|null} [variableFields] 需要处理的字段列表包括完整字段和指定路径不传则处理所有字段
* @returns {string} 处理后的字符串
*/
export const formatJsonVariables = (jsonObj, variableFields = null) => {
try {
return processObject(jsonObj, "", variableFields);
} catch (e) {
console.warn("Failed to process JSON variables:", e);
return JSON.stringify(jsonObj, null, 2);
}
};

View File

@ -14,6 +14,8 @@ export function generateCode(commandFlow) {
if (cmd.value === "ubrowser") {
line += `await ${cmd.argv}`;
} else if (cmd.value === "axios" || cmd.value === "fetch") {
line += `await ${cmd.argv}`;
} else {
line += `${cmd.isAsync ? "await " : ""}${cmd.value}(${cmd.argv})`;
}

View File

@ -0,0 +1,77 @@
export const userAgent = [
{
label: "Chrome (Windows)",
value:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
},
{
label: "Chrome (macOS)",
value:
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
},
{
label: "Chrome (Linux)",
value:
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
},
{
label: "IE 11",
value:
"Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko",
},
{
label: "微信 (Android)",
value:
"Mozilla/5.0 (Linux; Android 14; Pixel 8 Build/UQ1A.240205.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/122.0.6261.64 Mobile Safari/537.36 XWEB/1160027 MMWEBSDK/20231202 MMWEBID/2308 MicroMessenger/8.0.47.2560(0x28002F35) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64",
},
{
label: "微信 (iOS)",
value:
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.47(0x18002f2c) NetType/WIFI Language/zh_CN",
},
{
label: "iPhone",
value:
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
},
{
label: "iPad",
value:
"Mozilla/5.0 (iPad; CPU OS 17_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
},
{
label: "Android Phone",
value:
"Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36",
},
{
label: "Android Tablet",
value:
"Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
},
];
export const commonHeaders = [
{ label: "Content-Type", value: "Content-Type" },
{ label: "Authorization", value: "Authorization" },
{ label: "User-Agent", value: "User-Agent" },
{ label: "Accept", value: "Accept" },
{ label: "Accept-Language", value: "Accept-Language" },
{ label: "Accept-Encoding", value: "Accept-Encoding" },
{ label: "Cookie", value: "Cookie" },
{ label: "Origin", value: "Origin" },
{ label: "Referer", value: "Referer" },
];
export const deviceName = [
{ label: "iPhone 11", value: "iPhone 11" },
{ label: "iPhone X", value: "iPhone X" },
{ label: "iPad", value: "iPad" },
{ label: "iPhone 6/7/8 Plus", value: "iPhone 6/7/8 Plus" },
{ label: "iPhone 6/7/8", value: "iPhone 6/7/8" },
{ label: "iPhone 5/SE", value: "iPhone 5/SE" },
{ label: "HUAWEI Mate10", value: "HUAWEI Mate10" },
{ label: "HUAWEI Mate20", value: "HUAWEI Mate20" },
{ label: "HUAWEI Mate30", value: "HUAWEI Mate30" },
{ label: "HUAWEI Mate30 Pro", value: "HUAWEI Mate30 Pro" },
];