编排分类调整

This commit is contained in:
fofolee
2025-01-05 23:14:52 +08:00
parent a6cc1c8737
commit 296c231c44
31 changed files with 59 additions and 147 deletions

View File

@@ -0,0 +1,554 @@
<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.updateModelValue(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);
},
getSummary(argvs) {
return argvs.method + " " + argvs.url.value;
},
updateModelValue(argvs) {
this.$emit("update:modelValue", {
...this.modelValue,
summary: this.getSummary(argvs),
argvs,
code: this.generateCode(argvs),
});
},
},
mounted() {
if (!this.modelValue.argvs && !this.modelValue.code) {
this.updateModelValue(this.defaultArgvs);
}
},
});
</script>

View File

@@ -28,7 +28,6 @@
label="主机名"
icon="dns"
class="col"
v-if="needsHostname"
/>
<!-- IP地址输入 -->