mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-08 14:34:13 +08:00
初步完成axios、fetch组件
This commit is contained in:
parent
99cadf3130
commit
e474e475d9
@ -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);
|
||||
},
|
||||
|
@ -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 {
|
||||
|
282
src/components/editor/composer/http/AxiosConfigEditor.vue
Normal file
282
src/components/editor/composer/http/AxiosConfigEditor.vue
Normal 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>
|
276
src/components/editor/composer/http/FetchConfigEditor.vue
Normal file
276
src/components/editor/composer/http/FetchConfigEditor.vue
Normal 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>
|
198
src/components/editor/composer/http/HeaderEditor.vue
Normal file
198
src/components/editor/composer/http/HeaderEditor.vue
Normal 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>
|
@ -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() {
|
||||
|
@ -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: {
|
||||
|
@ -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",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
85
src/js/composer/formatString.js
Normal file
85
src/js/composer/formatString.js
Normal 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);
|
||||
}
|
||||
};
|
@ -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})`;
|
||||
}
|
||||
|
77
src/js/options/httpHeaders.js
Normal file
77
src/js/options/httpHeaders.js
Normal 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" },
|
||||
];
|
Loading…
x
Reference in New Issue
Block a user