优化Axios界面及布局

This commit is contained in:
fofolee 2025-01-02 11:48:45 +08:00
parent 28f034814b
commit 15ad67a753
4 changed files with 291 additions and 187 deletions

View File

@ -6,15 +6,7 @@
<div class="col-3"> <div class="col-3">
<q-select <q-select
v-model="localConfig.method" v-model="localConfig.method"
:options="[ :options="methods"
'GET',
'POST',
'PUT',
'DELETE',
'PATCH',
'HEAD',
'OPTIONS',
]"
label="请求方法" label="请求方法"
dense dense
filled filled
@ -39,183 +31,130 @@
</div> </div>
<!-- 响应设置 --> <!-- 响应设置 -->
<div class="col-12"> <div class="col-12">
<div class="row q-col-gutter-sm"> <q-tabs
<div class="col-3"> v-model="activeTab"
<q-select dense
v-model="localConfig.responseType" class="text-grey q-py-none"
filled active-color="primary"
dense indicator-color="primary"
emit-value align="justify"
map-options narrow-indicator
:options="['json', 'text', 'blob', 'arraybuffer']" inline-label
label="响应类型" >
@update:model-value="updateConfig" <q-tab
> v-for="tab in visibleTabs"
<template v-slot:prepend> :key="tab.name"
<q-icon name="data_object" /> :name="tab.name"
</template> class="q-px-xs text-caption"
</q-select> style="min-height: 32px"
</div> >
<div class="col"> <template v-slot:default>
<VariableInput <div class="row items-center no-wrap">
v-model="localConfig.maxRedirects" <q-icon :name="tab.icon" size="16px" />
label="最大重定向次数" <div class="text-caption q-ml-xs" style="font-size: 11px">
:command="{ icon: 'repeat', inputType: 'number' }" {{ 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="localConfig.headers['Content-Type']"
label="Content-Type"
dense
filled
emit-value
map-options
:options="contentTypes"
@update:model-value="updateConfig"
>
<template v-slot:prepend>
<q-icon name="data_object" />
</template>
</q-select>
</div>
<div class="col">
<VariableInput
v-model="localConfig.headers['User-Agent']"
label="User Agent"
:command="{ icon: 'devices' }"
@update:model-value="updateConfig"
/>
</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="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="localConfig.data"
@update:model-value="updateConfig" @update:model-value="updateConfig"
/> />
</div> </q-tab-panel>
<div class="col">
<VariableInput <q-tab-panel name="params" class="q-pa-sm">
v-model="localConfig.timeout" <DictEditor
label="超时时间(ms)" v-model="localConfig.params"
:command="{ icon: 'timer', inputType: 'number' }"
@update:model-value="updateConfig" @update:model-value="updateConfig"
/> />
</div> </q-tab-panel>
</div>
</div>
<!-- Headers --> <!-- 其他通用 panels -->
<!-- Content-Type --> <template v-for="panel in commonPanels" :key="panel.name">
<q-select <q-tab-panel :name="panel.name" class="q-pa-sm">
v-model="localConfig.headers['Content-Type']" <div class="row q-col-gutter-sm">
label="Content-Type" <div
dense v-for="field in panel.fields"
filled :key="field.key"
emit-value :class="field.colClass || 'col-4'"
map-options >
:options="contentTypes" <component
class="col-12" :is="field.component"
@update:model-value="updateConfig" :modelValue="getFieldValue(field.key)"
> v-bind="field.props"
<template v-slot:prepend> @update:modelValue="(val) => setFieldValue(field.key, val)"
<q-icon name="data_object" /> />
</template> </div>
</q-select> </div>
<!-- User-Agent --> </q-tab-panel>
<div class="col-12 row q-col-gutter-sm"> </template>
<div class="col"> </q-tab-panels>
<VariableInput
v-model="localConfig.headers['User-Agent']"
label="User Agent"
:command="{ icon: 'devices' }"
@update:model-value="updateConfig"
/>
</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>
<!-- 请求体 -->
<div v-if="hasRequestData" class="col-12">
<BorderLabel label="请求体" :modelValue="false">
<DictEditor
v-model="localConfig.data"
@update:model-value="updateConfig"
/>
</BorderLabel>
</div>
<!-- Other Headers -->
<div class="col-12">
<BorderLabel label="Headers">
<DictEditor
v-model="otherHeaders"
:options="{
items: commonHeaderOptions,
}"
@update:model-value="updateHeaders"
/>
</BorderLabel>
</div>
<!-- 请求参数 -->
<div class="col-12">
<BorderLabel label="URL参数">
<DictEditor
v-model="localConfig.params"
@update:model-value="updateConfig"
/>
</BorderLabel>
</div>
<!-- 认证信息 -->
<div class="col-12">
<BorderLabel label="HTTP认证">
<div class="row q-col-gutter-sm">
<div class="col-6">
<VariableInput
v-model="localConfig.auth.username"
label="用户名"
:command="{ icon: 'person' }"
@update:model-value="updateConfig"
/>
</div>
<div class="col-6">
<VariableInput
v-model="localConfig.auth.password"
label="密码"
:command="{ icon: 'password' }"
@update:model-value="updateConfig"
/>
</div>
</div>
</BorderLabel>
</div>
<!-- 代理设置 -->
<div class="col-12">
<BorderLabel label="代理设置">
<div class="row q-col-gutter-sm">
<div class="col-3">
<VariableInput
v-model="localConfig.proxy.host"
label="主机"
:command="{ icon: 'dns' }"
@update:model-value="updateConfig"
/>
</div>
<div class="col-3">
<VariableInput
v-model="localConfig.proxy.port"
label="端口"
:command="{ icon: 'router', inputType: 'number' }"
@update:model-value="updateConfig"
/>
</div>
<div class="col-3">
<VariableInput
v-model="localConfig.proxy.auth.username"
label="用户名"
:command="{ icon: 'person' }"
@update:model-value="updateConfig"
/>
</div>
<div class="col-3">
<VariableInput
v-model="localConfig.proxy.auth.password"
label="密码"
:command="{ icon: 'password' }"
@update:model-value="updateConfig"
/>
</div>
</div>
</BorderLabel>
</div> </div>
</div> </div>
</template> </template>
@ -225,15 +164,19 @@ import { defineComponent } from "vue";
import VariableInput from "components/composer/ui/VariableInput.vue"; import VariableInput from "components/composer/ui/VariableInput.vue";
import DictEditor from "components/composer/ui/DictEditor.vue"; import DictEditor from "components/composer/ui/DictEditor.vue";
import { formatJsonVariables } from "js/composer/formatString"; import { formatJsonVariables } from "js/composer/formatString";
import { userAgent, commonHeaders, contentTypes } from "js/options/httpHeaders"; import {
import BorderLabel from "components/composer/ui/BorderLabel.vue"; userAgent,
commonHeaders,
contentTypes,
methods,
responseTypes,
} from "js/options/httpOptions";
export default defineComponent({ export default defineComponent({
name: "AxiosConfigEditor", name: "AxiosConfigEditor",
components: { components: {
VariableInput, VariableInput,
DictEditor, DictEditor,
BorderLabel,
}, },
props: { props: {
modelValue: { modelValue: {
@ -267,7 +210,8 @@ export default defineComponent({
url: "", url: "",
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/x-www-form-urlencoded", "User-Agent": userAgent[0].value,
"Content-Type": contentTypes[0].value,
}, },
params: {}, params: {},
data: {}, data: {},
@ -288,12 +232,147 @@ export default defineComponent({
}, },
...initialConfig, ...initialConfig,
}, },
methods,
userAgentOptions: userAgent, userAgentOptions: userAgent,
contentTypes, contentTypes,
commonHeaderOptions: commonHeaders commonHeaderOptions: commonHeaders
.filter((h) => !["User-Agent", "Content-Type"].includes(h.value)) .filter((h) => !["User-Agent", "Content-Type"].includes(h.value))
.map((h) => h.value), .map((h) => h.value),
otherHeaders: {}, otherHeaders: {},
activeTab: "headers",
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: "VariableInput",
props: {
label: "最大重定向次数",
command: { icon: "repeat", inputType: "number" },
},
},
{
key: "timeout",
component: "VariableInput",
props: {
label: "超时时间(ms)",
command: { icon: "timer", inputType: "number" },
},
},
],
},
{
name: "auth",
fields: [
{
key: "auth.username",
component: "VariableInput",
colClass: "col-6",
props: {
label: "用户名",
command: { icon: "person" },
},
},
{
key: "auth.password",
component: "VariableInput",
colClass: "col-6",
props: {
label: "密码",
command: { icon: "password" },
},
},
],
},
{
name: "proxy",
fields: [
{
key: "proxy.host",
component: "VariableInput",
colClass: "col-6",
props: {
label: "主机",
command: { icon: "dns" },
},
},
{
key: "proxy.port",
component: "VariableInput",
colClass: "col-6",
props: {
label: "端口",
command: { icon: "router", inputType: "number" },
},
},
{
key: "proxy.auth.username",
component: "VariableInput",
colClass: "col-6",
props: {
label: "用户名",
command: { icon: "person" },
},
},
{
key: "proxy.auth.password",
component: "VariableInput",
colClass: "col-6",
props: {
label: "密码",
command: { icon: "password" },
},
},
],
},
],
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: "代理",
},
],
}; };
}, },
created() { created() {
@ -306,6 +385,9 @@ export default defineComponent({
hasRequestData() { hasRequestData() {
return ["PUT", "POST", "PATCH"].includes(this.localConfig.method); return ["PUT", "POST", "PATCH"].includes(this.localConfig.method);
}, },
visibleTabs() {
return this.tabs.filter((tab) => !tab.condition || tab.condition());
},
}, },
methods: { methods: {
updateConfig() { updateConfig() {
@ -341,6 +423,16 @@ export default defineComponent({
this.localConfig.headers["User-Agent"] = value; this.localConfig.headers["User-Agent"] = value;
this.updateConfig(); this.updateConfig();
}, },
getFieldValue(path) {
return path.split(".").reduce((obj, key) => obj?.[key], this.localConfig);
},
setFieldValue(path, value) {
const keys = path.split(".");
const lastKey = keys.pop();
const target = keys.reduce((obj, key) => obj[key], this.localConfig);
target[lastKey] = value;
this.updateConfig();
},
}, },
watch: { watch: {
modelValue: { modelValue: {

View File

@ -67,7 +67,7 @@
<script> <script>
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { userAgent } from "js/options/httpHeaders"; import { userAgent } from "js/options/httpOptions";
import VariableInput from "components/composer/ui/VariableInput.vue"; import VariableInput from "components/composer/ui/VariableInput.vue";
export default defineComponent({ export default defineComponent({

View File

@ -31,7 +31,7 @@
<script> <script>
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { deviceName } from "js/options/httpHeaders"; import { deviceName } from "js/options/httpOptions";
import VariableInput from "components/composer/ui/VariableInput.vue"; import VariableInput from "components/composer/ui/VariableInput.vue";
export default defineComponent({ export default defineComponent({

View File

@ -113,3 +113,15 @@ export const contentTypes = [
value: "application/octet-stream", value: "application/octet-stream",
}, },
]; ];
export const methods = [
"GET",
"POST",
"PUT",
"DELETE",
"PATCH",
"HEAD",
"OPTIONS",
];
export const responseTypes = ["json", "text", "blob", "arraybuffer"];