优化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">
<q-select
v-model="localConfig.method"
:options="[
'GET',
'POST',
'PUT',
'DELETE',
'PATCH',
'HEAD',
'OPTIONS',
]"
:options="methods"
label="请求方法"
dense
filled
@ -39,183 +31,130 @@
</div>
<!-- 响应设置 -->
<div class="col-12">
<div class="row q-col-gutter-sm">
<div class="col-3">
<q-select
v-model="localConfig.responseType"
filled
dense
emit-value
map-options
:options="['json', 'text', 'blob', 'arraybuffer']"
label="响应类型"
@update:model-value="updateConfig"
>
<template v-slot:prepend>
<q-icon name="data_object" />
</template>
</q-select>
</div>
<div class="col">
<VariableInput
v-model="localConfig.maxRedirects"
label="最大重定向次数"
:command="{ icon: 'repeat', inputType: 'number' }"
<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="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"
/>
</div>
<div class="col">
<VariableInput
v-model="localConfig.timeout"
label="超时时间(ms)"
:command="{ icon: 'timer', inputType: 'number' }"
</q-tab-panel>
<q-tab-panel name="params" class="q-pa-sm">
<DictEditor
v-model="localConfig.params"
@update:model-value="updateConfig"
/>
</div>
</div>
</div>
</q-tab-panel>
<!-- Headers -->
<!-- Content-Type -->
<q-select
v-model="localConfig.headers['Content-Type']"
label="Content-Type"
dense
filled
emit-value
map-options
:options="contentTypes"
class="col-12"
@update:model-value="updateConfig"
>
<template v-slot:prepend>
<q-icon name="data_object" />
</template>
</q-select>
<!-- User-Agent -->
<div class="col-12 row q-col-gutter-sm">
<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>
<!-- 请求体 -->
<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>
<!-- 其他通用 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"
:modelValue="getFieldValue(field.key)"
v-bind="field.props"
@update:modelValue="(val) => setFieldValue(field.key, val)"
/>
</div>
</div>
</q-tab-panel>
</template>
</q-tab-panels>
</div>
</div>
</template>
@ -225,15 +164,19 @@ import { defineComponent } from "vue";
import VariableInput from "components/composer/ui/VariableInput.vue";
import DictEditor from "components/composer/ui/DictEditor.vue";
import { formatJsonVariables } from "js/composer/formatString";
import { userAgent, commonHeaders, contentTypes } from "js/options/httpHeaders";
import BorderLabel from "components/composer/ui/BorderLabel.vue";
import {
userAgent,
commonHeaders,
contentTypes,
methods,
responseTypes,
} from "js/options/httpOptions";
export default defineComponent({
name: "AxiosConfigEditor",
components: {
VariableInput,
DictEditor,
BorderLabel,
},
props: {
modelValue: {
@ -267,7 +210,8 @@ export default defineComponent({
url: "",
method: "GET",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": userAgent[0].value,
"Content-Type": contentTypes[0].value,
},
params: {},
data: {},
@ -288,12 +232,147 @@ export default defineComponent({
},
...initialConfig,
},
methods,
userAgentOptions: userAgent,
contentTypes,
commonHeaderOptions: commonHeaders
.filter((h) => !["User-Agent", "Content-Type"].includes(h.value))
.map((h) => h.value),
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() {
@ -306,6 +385,9 @@ export default defineComponent({
hasRequestData() {
return ["PUT", "POST", "PATCH"].includes(this.localConfig.method);
},
visibleTabs() {
return this.tabs.filter((tab) => !tab.condition || tab.condition());
},
},
methods: {
updateConfig() {
@ -341,6 +423,16 @@ export default defineComponent({
this.localConfig.headers["User-Agent"] = value;
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: {
modelValue: {

View File

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

View File

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

View File

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