2025-01-05 11:04:20 +08:00

550 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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="argvs.method"
:options="methods"
label="请求方法"
dense
filled
emit-value
map-options
@update:model-value="updateArgvs('method', $event)"
>
<template v-slot:prepend>
<q-icon name="send" />
</template>
</q-select>
</div>
<div class="col">
<VariableInput
:model-value="argvs.url"
@update:model-value="updateArgvs('url', $event)"
label="请求地址"
icon="link"
class="col-grow"
/>
</div>
</div>
</div>
<!-- 响应设置 -->
<div class="col-12">
<q-tabs
v-model="activeTab"
dense
class="text-grey q-py-none"
active-color="primary"
indicator-color="primary"
align="justify"
narrow-indicator
inline-label
>
<q-tab
v-for="tab in visibleTabs"
:key="tab.name"
:name="tab.name"
class="q-px-xs text-caption"
style="min-height: 32px"
>
<template v-slot:default>
<div class="row items-center no-wrap">
<q-icon :name="tab.icon" size="16px" />
<div class="text-caption q-ml-xs" style="font-size: 11px">
{{ tab.label }}
</div>
</div>
</template>
</q-tab>
</q-tabs>
<q-separator />
<q-tab-panels v-model="activeTab" animated class="q-px-none">
<!-- Headers tab -->
<q-tab-panel name="headers" class="q-pa-sm">
<div class="row q-col-gutter-sm">
<div class="col-12">
<q-select
v-model="argvs.headers['Content-Type']"
label="Content-Type"
dense
filled
emit-value
map-options
:options="contentTypes"
@update:model-value="
updateArgvs('headers.Content-Type', $event)
"
>
<template v-slot:prepend>
<q-icon name="data_object" />
</template>
</q-select>
</div>
<div class="col">
<VariableInput
:model-value="argvs.headers['User-Agent']"
@update:model-value="updateArgvs('headers.User-Agent', $event)"
label="User Agent"
icon="devices"
class="col-grow"
/>
</div>
<div class="col-auto flex items-center">
<q-btn-dropdown flat dense dropdown-icon="menu">
<q-list>
<q-item
v-for="ua in userAgentOptions"
:key="ua.value"
clickable
v-close-popup
@click="setUserAgent(ua.value)"
>
<q-item-section>
<q-item-label>{{ ua.label }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
<div class="col-12">
<DictEditor
v-model="argvs.otherHeaders"
:options="{
items: commonHeaderOptions,
}"
@update:model-value="updateHeaders"
/>
</div>
</div>
</q-tab-panel>
<!-- 请求体和URL参数 tabs -->
<q-tab-panel v-if="hasRequestData" name="data" class="q-pa-sm">
<DictEditor
v-model="argvs.data"
@update:model-value="updateArgvs('data', $event)"
/>
</q-tab-panel>
<q-tab-panel name="params" class="q-pa-sm">
<DictEditor
v-model="argvs.params"
@update:model-value="updateArgvs('params', $event)"
/>
</q-tab-panel>
<!-- 其他通用 panels -->
<template v-for="panel in commonPanels" :key="panel.name">
<q-tab-panel :name="panel.name" class="q-pa-sm">
<div class="row q-col-gutter-sm">
<div
v-for="field in panel.fields"
:key="field.key"
:class="field.colClass || 'col-4'"
>
<component
:is="field.component"
:model-value="getFieldValue(field.key)"
v-bind="field.props"
@update:model-value="(val) => setFieldValue(field.key, val)"
/>
</div>
</div>
</q-tab-panel>
</template>
</q-tab-panels>
</div>
</div>
</template>
<script>
import { defineComponent } from "vue";
import VariableInput from "components/composer/ui/VariableInput.vue";
import NumberInput from "components/composer/ui/NumberInput.vue";
import DictEditor from "components/composer/ui/DictEditor.vue";
import {
stringifyObject,
stringifyWithType,
parseFunction,
} from "js/composer/formatString";
import {
userAgent,
commonHeaders,
contentTypes,
methods,
responseTypes,
} from "js/options/httpOptions";
export default defineComponent({
name: "AxiosConfigEditor",
components: {
VariableInput,
DictEditor,
NumberInput,
},
props: {
modelValue: {
type: Object,
required: true,
},
},
emits: ["update:modelValue"],
data() {
return {
methods,
userAgentOptions: userAgent,
contentTypes,
commonHeaderOptions: commonHeaders
.filter((h) => !["User-Agent", "Content-Type"].includes(h.value))
.map((h) => h.value),
activeTab: "headers",
defaultArgvs: {
url: {
value: "",
isString: true,
__varInputVal__: true,
},
method: "GET",
headers: {
"User-Agent": {
value: userAgent[0].value,
isString: true,
__varInputVal__: true,
},
"Content-Type": contentTypes[0].value,
},
otherHeaders: {},
params: {},
data: {},
timeout: 0,
maxRedirects: 5,
responseType: "json",
auth: {
username: {
value: "",
isString: true,
__varInputVal__: true,
},
password: {
value: "",
isString: true,
__varInputVal__: true,
},
},
proxy: {
host: {
value: "",
isString: true,
__varInputVal__: true,
},
port: null,
auth: {
username: {
value: "",
isString: true,
__varInputVal__: true,
},
password: {
value: "",
isString: true,
__varInputVal__: true,
},
},
},
},
commonPanels: [
{
name: "response",
fields: [
{
key: "responseType",
component: "q-select",
props: {
label: "响应类型",
filled: true,
dense: true,
"emit-value": true,
"map-options": true,
options: responseTypes,
"prepend-icon": "data_object",
},
},
{
key: "maxRedirects",
component: "NumberInput",
props: {
label: "最大重定向次数",
icon: "repeat",
},
},
{
key: "timeout",
component: "NumberInput",
props: {
label: "超时时间(ms)",
icon: "timer",
},
},
],
},
{
name: "auth",
fields: [
{
key: "auth.username",
component: "VariableInput",
colClass: "col-6",
props: {
label: "用户名",
icon: "person",
class: "col-grow",
},
},
{
key: "auth.password",
component: "VariableInput",
colClass: "col-6",
props: {
label: "密码",
icon: "password",
class: "col-grow",
},
},
],
},
{
name: "proxy",
fields: [
{
key: "proxy.host",
component: "VariableInput",
colClass: "col-6",
props: {
label: "主机",
icon: "dns",
class: "col-grow",
},
},
{
key: "proxy.port",
component: "NumberInput",
colClass: "col-6",
props: {
label: "端口",
icon: "router",
class: "col-grow",
},
},
{
key: "proxy.auth.username",
component: "VariableInput",
colClass: "col-6",
props: {
label: "用户名",
icon: "person",
class: "col-grow",
},
},
{
key: "proxy.auth.password",
component: "VariableInput",
colClass: "col-6",
props: {
label: "密码",
icon: "password",
class: "col-grow",
},
},
],
},
],
tabs: [
{
name: "headers",
icon: "list_alt",
label: "请求头",
},
{
name: "data",
icon: "data_object",
label: "请求体",
condition: () => this.hasRequestData,
},
{
name: "params",
icon: "link",
label: "URL参数",
},
{
name: "response",
icon: "settings",
label: "响应",
},
{
name: "auth",
icon: "security",
label: "认证",
},
{
name: "proxy",
icon: "dns",
label: "代理",
},
],
};
},
computed: {
argvs() {
return (
this.modelValue.argvs || this.parseCodeToArgvs(this.modelValue.code)
);
},
hasRequestData() {
return ["PUT", "POST", "PATCH"].includes(this.argvs.method);
},
visibleTabs() {
return this.tabs.filter((tab) => !tab.condition || tab.condition());
},
},
methods: {
// 解析代码为参数
parseCodeToArgvs(code) {
const argvs = window.lodashM.cloneDeep(this.defaultArgvs);
if (!code) return argvs;
const method = code.match(/axios\.(\w+)/)?.[1].toUpperCase();
const hasData = ["PUT", "POST", "PATCH"].includes(method);
// 有请求体时config 是第三个参数
const configIndex = hasData ? 2 : 1;
try {
// 定义需要使用 variableInput 格式的路径
const variableFormatPaths = [
"arg0", // url参数
`arg${configIndex}.params.**`, // 所有params对象下的字段
`arg${configIndex}.headers.**`, // 所有headers对象下的字段
`arg${configIndex}.auth.**`, // 所有认证相关字段
`arg${configIndex}.proxy.host`, // 代理主机
`arg${configIndex}.proxy.auth.**`, // 代理认证字段
`!arg${configIndex}.headers.Content-Type`, // ontent-Type
];
if (hasData) variableFormatPaths.push(`arg1.**`);
// 返回所有解析出来的参数组成的数组
const result = parseFunction(code, {
variableFormatPaths,
});
if (!result) {
console.warn("axios 参数解析失败:", code);
return argvs;
}
const url = result.args[0] || "";
const data = hasData ? result.args[1] : {};
const config = result.args[hasData ? 2 : 1] || {};
const {
"Content-Type": contentType,
"User-Agent": userAgent,
...otherHeaders
} = config.headers;
config.headers = {
"Content-Type": contentType,
"User-Agent": userAgent,
};
Object.assign(argvs, { url, data, method, otherHeaders, ...config });
} catch (e) {
console.warn("axios 参数解析失败:", code);
}
return argvs;
},
// 从参数生成代码
generateCode(argvs = this.argvs) {
const { url, method, data, otherHeaders, ...restConfig } = argvs;
restConfig.headers = {
...restConfig.headers,
...otherHeaders,
};
if (!url) return;
const configStr = Object.keys(restConfig).length
? `, ${stringifyObject(restConfig)}`
: "";
return `${this.modelValue.value}.${method.toLowerCase()}(${stringifyWithType(url)}${
this.hasRequestData ? `, ${stringifyObject(data)}` : ""
}${configStr})`;
},
updateArgvs(key, value) {
const argvs = { ...this.argvs };
const keys = key.split(".");
const lastKey = keys.pop();
const target = keys.reduce((obj, key) => obj[key], argvs);
target[lastKey] = value;
// 特殊处理
if (key === "method") {
if (!this.hasRequestData) {
argvs.data = {};
}
}
this.$emit("update:modelValue", {
...this.modelValue,
argvs,
code: this.generateCode(argvs),
});
},
updateHeaders(headers) {
// 保留 Content-Type 和 User-Agent
const { "Content-Type": contentType, "User-Agent": userAgent } =
this.argvs.headers;
// 将普通字符串值转换为对象格式
const formattedHeaders = Object.entries(headers).reduce(
(acc, [key, value]) => {
acc[key] =
typeof value === "string"
? { value, isString: true, __varInputVal__: true }
: value;
return acc;
},
{}
);
// 重置 headers只保留特殊字段
const newHeaders = {
"Content-Type": contentType,
...(userAgent ? { "User-Agent": userAgent } : {}),
...formattedHeaders,
};
this.updateArgvs("headers", newHeaders);
},
setUserAgent(value) {
this.updateArgvs("headers.User-Agent", {
value,
isString: true,
__varInputVal__: true,
});
},
getFieldValue(path) {
return path.split(".").reduce((obj, key) => obj?.[key], this.argvs);
},
setFieldValue(path, value) {
this.updateArgvs(path, value);
},
},
mounted() {
if (!this.modelValue.argvs && !this.modelValue.code) {
this.$emit("update:modelValue", {
...this.modelValue,
argvs: this.defaultArgvs,
code: this.generateCode(this.defaultArgvs),
});
}
},
});
</script>