mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-07 21:46:12 +08:00
完成axios可视化
This commit is contained in:
parent
7c1ca78ec0
commit
d135f5b7a8
@ -93,7 +93,10 @@ module.exports = configure(function (ctx) {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
chain.resolve.alias.set("plugins", path.join(__dirname, "./src/plugins"));
|
chain.resolve.alias.set(
|
||||||
|
"plugins",
|
||||||
|
path.join(__dirname, "./src/plugins")
|
||||||
|
);
|
||||||
chain.resolve.alias.set("js", path.join(__dirname, "./src/js"));
|
chain.resolve.alias.set("js", path.join(__dirname, "./src/js"));
|
||||||
},
|
},
|
||||||
extendWebpack(cfg) {
|
extendWebpack(cfg) {
|
||||||
@ -194,7 +197,6 @@ module.exports = configure(function (ctx) {
|
|||||||
orientation: "portrait",
|
orientation: "portrait",
|
||||||
background_color: "#ffffff",
|
background_color: "#ffffff",
|
||||||
theme_color: "#027be3",
|
theme_color: "#027be3",
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
60
src/components/editor/composer/BorderLabel.vue
Normal file
60
src/components/editor/composer/BorderLabel.vue
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<template>
|
||||||
|
<div class="border-label" :data-label="label">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "BorderLabel",
|
||||||
|
props: {
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.border-label {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--border-color, rgba(0, 0, 0, 0.1));
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px;
|
||||||
|
position: relative;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-label::before {
|
||||||
|
content: attr(data-label);
|
||||||
|
position: absolute;
|
||||||
|
top: -9px;
|
||||||
|
left: 16px;
|
||||||
|
background: #fff;
|
||||||
|
color: rgba(0, 0, 0, 0.6);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
padding: 0 8px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-label::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 1px;
|
||||||
|
background: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body--dark .border-label {
|
||||||
|
--border-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body--dark .border-label::before {
|
||||||
|
background: #303133;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
</style>
|
@ -202,7 +202,7 @@ export default defineComponent({
|
|||||||
.command-composer :deep(.q-field--filled .q-field__control),
|
.command-composer :deep(.q-field--filled .q-field__control),
|
||||||
.command-composer :deep(.q-field--filled .q-field__control > *),
|
.command-composer :deep(.q-field--filled .q-field__control > *),
|
||||||
.command-composer
|
.command-composer
|
||||||
:deep(.q-field--filled.q-select--with-input .q-field__native) {
|
:deep(.q-field--filled:not(.q-field--labeled) .q-field__native) {
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
max-height: 36px !important;
|
max-height: 36px !important;
|
||||||
@ -236,9 +236,10 @@ export default defineComponent({
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 输入框背景颜色 */
|
/* 输入框背景颜色及内边距 */
|
||||||
.command-composer :deep(.q-field--filled .q-field__control) {
|
.command-composer :deep(.q-field--filled .q-field__control) {
|
||||||
background: rgba(0, 0, 0, 0.03);
|
background: rgba(0, 0, 0, 0.03);
|
||||||
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 输入框聚焦时的背景颜色 */
|
/* 输入框聚焦时的背景颜色 */
|
||||||
|
@ -88,12 +88,8 @@
|
|||||||
<template v-else-if="command.hasAxiosEditor">
|
<template v-else-if="command.hasAxiosEditor">
|
||||||
<AxiosConfigEditor v-model="argvLocal" class="col" />
|
<AxiosConfigEditor v-model="argvLocal" class="col" />
|
||||||
</template>
|
</template>
|
||||||
<!-- Fetch编辑器 -->
|
|
||||||
<template v-else-if="command.hasFetchEditor">
|
|
||||||
<FetchConfigEditor v-model="argvLocal" class="col" />
|
|
||||||
</template>
|
|
||||||
<!-- 普通参数输入 -->
|
<!-- 普通参数输入 -->
|
||||||
<template v-else>
|
<template v-else-if="!command.hasNoArgs">
|
||||||
<VariableInput
|
<VariableInput
|
||||||
v-model="argvLocal"
|
v-model="argvLocal"
|
||||||
:label="placeholder"
|
:label="placeholder"
|
||||||
@ -115,7 +111,6 @@ import KeyEditor from "./KeyEditor.vue";
|
|||||||
import UBrowserEditor from "./ubrowser/UBrowserEditor.vue";
|
import UBrowserEditor from "./ubrowser/UBrowserEditor.vue";
|
||||||
import VariableInput from "./VariableInput.vue";
|
import VariableInput from "./VariableInput.vue";
|
||||||
import AxiosConfigEditor from "./http/AxiosConfigEditor.vue";
|
import AxiosConfigEditor from "./http/AxiosConfigEditor.vue";
|
||||||
import FetchConfigEditor from "./http/FetchConfigEditor.vue";
|
|
||||||
import { validateVariableName } from "js/common/variableValidator";
|
import { validateVariableName } from "js/common/variableValidator";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@ -125,7 +120,6 @@ export default defineComponent({
|
|||||||
UBrowserEditor,
|
UBrowserEditor,
|
||||||
VariableInput,
|
VariableInput,
|
||||||
AxiosConfigEditor,
|
AxiosConfigEditor,
|
||||||
FetchConfigEditor,
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
command: {
|
command: {
|
||||||
@ -162,7 +156,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
argvLocal: {
|
argvLocal: {
|
||||||
get() {
|
get() {
|
||||||
if (this.command.hasAxiosEditor || this.command.hasFetchEditor) {
|
if (this.command.hasAxiosEditor) {
|
||||||
// 如果是编辑现有配置
|
// 如果是编辑现有配置
|
||||||
if (
|
if (
|
||||||
this.command.argv &&
|
this.command.argv &&
|
||||||
@ -183,12 +177,11 @@ export default defineComponent({
|
|||||||
set(value) {
|
set(value) {
|
||||||
const updatedCommand = {
|
const updatedCommand = {
|
||||||
...this.command,
|
...this.command,
|
||||||
argv:
|
argv: this.command.hasAxiosEditor
|
||||||
this.command.hasAxiosEditor || this.command.hasFetchEditor
|
? typeof value === "string"
|
||||||
? typeof value === "string"
|
? value
|
||||||
? value
|
: JSON.stringify(value)
|
||||||
: JSON.stringify(value)
|
: value,
|
||||||
: value,
|
|
||||||
};
|
};
|
||||||
this.$emit("update:command", updatedCommand);
|
this.$emit("update:command", updatedCommand);
|
||||||
},
|
},
|
||||||
|
261
src/components/editor/composer/DictEditor.vue
Normal file
261
src/components/editor/composer/DictEditor.vue
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dict-editor">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in items"
|
||||||
|
:key="index"
|
||||||
|
class="row q-col-gutter-sm items-center"
|
||||||
|
>
|
||||||
|
<div class="col-4">
|
||||||
|
<q-select
|
||||||
|
v-if="options?.items"
|
||||||
|
:model-value="item.key"
|
||||||
|
:options="options.items"
|
||||||
|
label="名称"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
use-input
|
||||||
|
input-debounce="0"
|
||||||
|
:hide-selected="!!inputValue"
|
||||||
|
@filter="filterFn"
|
||||||
|
@update:model-value="(val) => handleSelect(val, index)"
|
||||||
|
@input-value="(val) => handleInput(val, index)"
|
||||||
|
@blur="handleBlur"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<q-icon name="code" />
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
<q-input
|
||||||
|
v-else
|
||||||
|
:model-value="item.key"
|
||||||
|
label="名称"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
@update:model-value="(val) => updateItemKey(val, index)"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<q-icon name="code" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="item.value"
|
||||||
|
:label="item.key || '值'"
|
||||||
|
:command="{ icon: 'code' }"
|
||||||
|
@update:model-value="(val) => updateItemValue(val, index)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<div class="btn-container">
|
||||||
|
<template v-if="items.length === 1">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="sm"
|
||||||
|
icon="add"
|
||||||
|
class="center-btn"
|
||||||
|
@click="addItem"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="index === items.length - 1">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="sm"
|
||||||
|
icon="remove"
|
||||||
|
class="top-btn"
|
||||||
|
@click="removeItem(index)"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="sm"
|
||||||
|
icon="add"
|
||||||
|
class="bottom-btn"
|
||||||
|
@click="addItem"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="sm"
|
||||||
|
icon="remove"
|
||||||
|
class="center-btn"
|
||||||
|
@click="removeItem(index)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import VariableInput from "./VariableInput.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "DictEditor",
|
||||||
|
components: {
|
||||||
|
VariableInput,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ["update:modelValue"],
|
||||||
|
data() {
|
||||||
|
const modelEntries = Object.entries(this.modelValue || {});
|
||||||
|
return {
|
||||||
|
localItems: modelEntries.length
|
||||||
|
? modelEntries.map(([key, value]) => ({
|
||||||
|
key,
|
||||||
|
value: typeof value === "string" ? value : JSON.stringify(value),
|
||||||
|
}))
|
||||||
|
: [{ key: "", value: "" }],
|
||||||
|
filterOptions: this.options?.items || [],
|
||||||
|
inputValue: "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
items: {
|
||||||
|
get() {
|
||||||
|
return this.localItems;
|
||||||
|
},
|
||||||
|
set(newItems) {
|
||||||
|
this.localItems = newItems;
|
||||||
|
const dict = {};
|
||||||
|
newItems.forEach((item) => {
|
||||||
|
if (item.key && item.value) {
|
||||||
|
dict[item.key] = item.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.$emit("update:modelValue", dict);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addItem() {
|
||||||
|
this.items = [...this.items, { key: "", value: "" }];
|
||||||
|
},
|
||||||
|
removeItem(index) {
|
||||||
|
const newItems = [...this.items];
|
||||||
|
newItems.splice(index, 1);
|
||||||
|
if (newItems.length === 0) {
|
||||||
|
newItems.push({ key: "", value: "" });
|
||||||
|
}
|
||||||
|
this.items = newItems;
|
||||||
|
const dict = {};
|
||||||
|
newItems.forEach((item) => {
|
||||||
|
if (item.key && item.value) {
|
||||||
|
dict[item.key] = item.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.$emit("update:modelValue", dict);
|
||||||
|
},
|
||||||
|
updateItemKey(val, index) {
|
||||||
|
const newItems = [...this.items];
|
||||||
|
newItems[index].key = val;
|
||||||
|
this.items = newItems;
|
||||||
|
},
|
||||||
|
updateItemValue(val, index) {
|
||||||
|
const newItems = [...this.items];
|
||||||
|
newItems[index].value = val;
|
||||||
|
this.items = newItems;
|
||||||
|
},
|
||||||
|
handleInput(val, index) {
|
||||||
|
this.inputValue = val;
|
||||||
|
if (val && !this.filterOptions.includes(val)) {
|
||||||
|
const newItems = [...this.items];
|
||||||
|
newItems[index].key = val;
|
||||||
|
this.items = newItems;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSelect(val, index) {
|
||||||
|
this.inputValue = "";
|
||||||
|
const newItems = [...this.items];
|
||||||
|
newItems[index].key = val;
|
||||||
|
this.items = newItems;
|
||||||
|
},
|
||||||
|
handleBlur() {
|
||||||
|
this.inputValue = "";
|
||||||
|
},
|
||||||
|
filterFn(val, update) {
|
||||||
|
if (!this.options?.items) return;
|
||||||
|
|
||||||
|
update(() => {
|
||||||
|
if (val === "") {
|
||||||
|
this.filterOptions = this.options.items;
|
||||||
|
} else {
|
||||||
|
const needle = val.toLowerCase();
|
||||||
|
this.filterOptions = this.options.items.filter(
|
||||||
|
(v) => v.toLowerCase().indexOf(needle) > -1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dict-editor {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 防止输入框换行 */
|
||||||
|
:deep(.q-field__native) {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
position: relative;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container .q-btn {
|
||||||
|
position: absolute;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
min-height: 16px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container .center-btn {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container .top-btn {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container .bottom-btn {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.q-btn .q-icon) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.q-btn.q-btn--dense) {
|
||||||
|
padding: 0;
|
||||||
|
min-height: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -101,7 +101,6 @@
|
|||||||
<q-btn
|
<q-btn
|
||||||
flat
|
flat
|
||||||
dense
|
dense
|
||||||
round
|
|
||||||
icon="keyboard_arrow_up"
|
icon="keyboard_arrow_up"
|
||||||
size="xs"
|
size="xs"
|
||||||
class="number-btn"
|
class="number-btn"
|
||||||
@ -110,7 +109,6 @@
|
|||||||
<q-btn
|
<q-btn
|
||||||
flat
|
flat
|
||||||
dense
|
dense
|
||||||
round
|
|
||||||
icon="keyboard_arrow_down"
|
icon="keyboard_arrow_down"
|
||||||
size="xs"
|
size="xs"
|
||||||
class="number-btn"
|
class="number-btn"
|
||||||
@ -262,7 +260,7 @@ export default defineComponent({
|
|||||||
.string-toggle {
|
.string-toggle {
|
||||||
min-width: 24px;
|
min-width: 24px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
opacity: 0.8;
|
opacity: 0.6;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,6 +346,7 @@ export default defineComponent({
|
|||||||
.number-controls {
|
.number-controls {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
width: 32px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
@ -37,86 +37,185 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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="col-12">
|
||||||
<div class="row q-col-gutter-sm">
|
<div class="row q-col-gutter-sm">
|
||||||
<div class="col-6">
|
<div class="col-3">
|
||||||
<q-checkbox
|
<q-select
|
||||||
v-model="localConfig.withCredentials"
|
v-model="localConfig.responseType"
|
||||||
label="发送凭证"
|
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' }"
|
||||||
@update:model-value="updateConfig"
|
@update:model-value="updateConfig"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col">
|
||||||
<q-checkbox
|
<VariableInput
|
||||||
v-model="localConfig.decompress"
|
v-model="localConfig.timeout"
|
||||||
label="自动解压"
|
label="超时时间(ms)"
|
||||||
|
:command="{ icon: 'timer', inputType: 'number' }"
|
||||||
@update:model-value="updateConfig"
|
@update:model-value="updateConfig"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 响应类型 -->
|
<!-- Headers -->
|
||||||
<div class="col-12">
|
<!-- Content-Type -->
|
||||||
<q-select
|
<q-select
|
||||||
v-model="localConfig.responseType"
|
v-model="localConfig.headers['Content-Type']"
|
||||||
:options="['json', 'text', 'blob', 'arraybuffer']"
|
label="Content-Type"
|
||||||
label="响应类型"
|
dense
|
||||||
dense
|
filled
|
||||||
filled
|
emit-value
|
||||||
emit-value
|
map-options
|
||||||
map-options
|
:options="contentTypes"
|
||||||
@update:model-value="updateConfig"
|
class="col-12"
|
||||||
>
|
@update:model-value="updateConfig"
|
||||||
<template v-slot:prepend>
|
>
|
||||||
<q-icon name="data_object" />
|
<template v-slot:prepend>
|
||||||
</template>
|
<q-icon name="data_object" />
|
||||||
</q-select>
|
</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>
|
||||||
|
|
||||||
|
<!-- Other Headers -->
|
||||||
|
<div class="col-12" style="margin-top: -8px">
|
||||||
|
<BorderLabel label="Headers">
|
||||||
|
<DictEditor
|
||||||
|
v-model="otherHeaders"
|
||||||
|
:options="{
|
||||||
|
items: commonHeaderOptions,
|
||||||
|
}"
|
||||||
|
@update:model-value="updateHeaders"
|
||||||
|
/>
|
||||||
|
</BorderLabel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 请求体 -->
|
||||||
|
<div v-if="hasRequestData" class="col-12" style="margin-top: -8px">
|
||||||
|
<BorderLabel label="请求体">
|
||||||
|
<DictEditor
|
||||||
|
v-model="localConfig.data"
|
||||||
|
@update:model-value="updateConfig"
|
||||||
|
/>
|
||||||
|
</BorderLabel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 请求参数 -->
|
||||||
|
<div class="col-12" style="margin-top: -8px">
|
||||||
|
<BorderLabel label="URL参数">
|
||||||
|
<DictEditor
|
||||||
|
v-model="localConfig.params"
|
||||||
|
@update:model-value="updateConfig"
|
||||||
|
/>
|
||||||
|
</BorderLabel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 认证信息 -->
|
||||||
|
<div class="col-12" style="margin-top: -8px">
|
||||||
|
<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" style="margin-top: -8px">
|
||||||
|
<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>
|
||||||
@ -124,14 +223,17 @@
|
|||||||
<script>
|
<script>
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import VariableInput from "../VariableInput.vue";
|
import VariableInput from "../VariableInput.vue";
|
||||||
import HeaderEditor from "./HeaderEditor.vue";
|
import DictEditor from "../DictEditor.vue";
|
||||||
import { formatJsonVariables } from "js/composer/formatString";
|
import { formatJsonVariables } from "js/composer/formatString";
|
||||||
|
import { userAgent, commonHeaders, contentTypes } from "js/options/httpHeaders";
|
||||||
|
import BorderLabel from "../BorderLabel.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "AxiosConfigEditor",
|
name: "AxiosConfigEditor",
|
||||||
components: {
|
components: {
|
||||||
VariableInput,
|
VariableInput,
|
||||||
HeaderEditor,
|
DictEditor,
|
||||||
|
BorderLabel,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@ -144,7 +246,6 @@ export default defineComponent({
|
|||||||
let initialConfig = {};
|
let initialConfig = {};
|
||||||
if (typeof this.modelValue === "string") {
|
if (typeof this.modelValue === "string") {
|
||||||
try {
|
try {
|
||||||
// 尝试从代码字符串中提取配置对象
|
|
||||||
const match = this.modelValue.match(
|
const match = this.modelValue.match(
|
||||||
/axios\.\w+\([^{]*({\s*[^]*})\s*\)/
|
/axios\.\w+\([^{]*({\s*[^]*})\s*\)/
|
||||||
);
|
);
|
||||||
@ -162,15 +263,34 @@ export default defineComponent({
|
|||||||
localConfig: {
|
localConfig: {
|
||||||
url: "",
|
url: "",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {},
|
headers: {
|
||||||
params: "",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
data: "",
|
},
|
||||||
|
params: {},
|
||||||
|
data: {},
|
||||||
timeout: 0,
|
timeout: 0,
|
||||||
withCredentials: false,
|
maxRedirects: 5,
|
||||||
responseType: "json",
|
responseType: "json",
|
||||||
decompress: true,
|
auth: {
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
},
|
||||||
|
proxy: {
|
||||||
|
host: "",
|
||||||
|
port: "",
|
||||||
|
auth: {
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
...initialConfig,
|
...initialConfig,
|
||||||
},
|
},
|
||||||
|
userAgentOptions: userAgent,
|
||||||
|
contentTypes,
|
||||||
|
commonHeaderOptions: commonHeaders
|
||||||
|
.filter((h) => !["User-Agent", "Content-Type"].includes(h.value))
|
||||||
|
.map((h) => h.value),
|
||||||
|
otherHeaders: {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@ -179,72 +299,43 @@ export default defineComponent({
|
|||||||
([name, value]) => ({ name, value })
|
([name, value]) => ({ name, value })
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
hasRequestData() {
|
||||||
|
return ["PUT", "POST", "PATCH"].includes(this.localConfig.method);
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateConfig() {
|
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;
|
const { method = "GET", url, data, ...restConfig } = this.localConfig;
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
let code = "";
|
|
||||||
const variableFields = ["headers", "timeout", "params", "data"];
|
// 这两个字段非VariableInput获取,不进行处理
|
||||||
if (method.toUpperCase() === "GET") {
|
const excludeFields = ["headers.Content-Type", "responseType"];
|
||||||
code = `axios.get(${url}${
|
const configStr = Object.keys(restConfig).length
|
||||||
Object.keys(restConfig).length
|
? `, ${formatJsonVariables(restConfig, null, excludeFields)}`
|
||||||
? `, ${formatJsonVariables(restConfig, variableFields)}`
|
: "";
|
||||||
: ""
|
|
||||||
})`;
|
const code = `axios.${method.toLowerCase()}(${url}${
|
||||||
} else {
|
this.hasRequestData ? `, ${formatJsonVariables(data)}` : ""
|
||||||
const { data: reqData, ...configWithoutData } = restConfig;
|
}${configStr})?.data`;
|
||||||
code = `axios.${method.toLowerCase()}(${url}${
|
|
||||||
reqData ? `, ${reqData}` : ", undefined"
|
|
||||||
}${
|
|
||||||
Object.keys(configWithoutData).length
|
|
||||||
? `, ${formatJsonVariables(restConfig, variableFields)}`
|
|
||||||
: ""
|
|
||||||
})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$emit("update:modelValue", code);
|
this.$emit("update:modelValue", code);
|
||||||
},
|
},
|
||||||
addHeader() {
|
updateHeaders(headers) {
|
||||||
this.headers.push({ name: "", value: "" });
|
// 保留 Content-Type 和 User-Agent
|
||||||
|
const { "Content-Type": contentType, "User-Agent": userAgent } =
|
||||||
|
this.localConfig.headers;
|
||||||
|
// 重置 headers,只保留特殊字段
|
||||||
|
this.localConfig.headers = {
|
||||||
|
"Content-Type": contentType,
|
||||||
|
...(userAgent ? { "User-Agent": userAgent } : {}),
|
||||||
|
...headers,
|
||||||
|
};
|
||||||
|
this.updateConfig();
|
||||||
},
|
},
|
||||||
removeHeader(index) {
|
setUserAgent(value) {
|
||||||
this.headers.splice(index, 1);
|
this.localConfig.headers["User-Agent"] = value;
|
||||||
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();
|
this.updateConfig();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1,276 +0,0 @@
|
|||||||
<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
|
|
||||||
filled
|
|
||||||
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
|
|
||||||
filled
|
|
||||||
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>
|
|
@ -1,198 +0,0 @@
|
|||||||
<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
|
|
||||||
filled
|
|
||||||
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
|
|
||||||
filled
|
|
||||||
@update:model-value="emitUpdate"
|
|
||||||
/>
|
|
||||||
<q-input
|
|
||||||
v-else
|
|
||||||
:model-value="header.name"
|
|
||||||
label="Header名称"
|
|
||||||
dense
|
|
||||||
filled
|
|
||||||
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>
|
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="row q-col-gutter-sm">
|
<div class="row q-col-gutter-sm ubrowser-function-input">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="row q-col-gutter-sm">
|
<div class="row q-col-gutter-sm">
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@ -35,6 +35,7 @@
|
|||||||
type="textarea"
|
type="textarea"
|
||||||
dense
|
dense
|
||||||
borderless
|
borderless
|
||||||
|
style="font-family: monospace, monoca, consola"
|
||||||
autogrow
|
autogrow
|
||||||
@update:model-value="updateFunction"
|
@update:model-value="updateFunction"
|
||||||
>
|
>
|
||||||
@ -198,7 +199,39 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
:deep(.q-field__control) .text-primary.func-symbol {
|
.ubrowser-function-input :deep(.q-field__control) .text-primary.func-symbol {
|
||||||
font-size: 18px !important;
|
font-size: 24px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ubrowser-function-input :deep(.q-select__input) {
|
||||||
|
display: flex !important;
|
||||||
|
flex-wrap: nowrap !important;
|
||||||
|
overflow-x: auto !important;
|
||||||
|
scrollbar-width: none !important;
|
||||||
|
-ms-overflow-style: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ubrowser-function-input :deep(.q-select .q-field__native) {
|
||||||
|
display: flex !important;
|
||||||
|
flex-wrap: nowrap !important;
|
||||||
|
overflow-x: auto !important;
|
||||||
|
scrollbar-width: none !important;
|
||||||
|
-ms-overflow-style: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ubrowser-function-input :deep(.q-select .q-field__native > div) {
|
||||||
|
display: flex !important;
|
||||||
|
flex-wrap: nowrap !important;
|
||||||
|
flex: 0 0 auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ubrowser-function-input :deep(.q-select .q-chip) {
|
||||||
|
flex: 0 0 auto !important;
|
||||||
|
margin-right: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ubrowser-function-input :deep(.q-select__input::-webkit-scrollbar),
|
||||||
|
.ubrowser-function-input :deep(.q-select .q-field__native::-webkit-scrollbar) {
|
||||||
|
display: none !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -42,28 +42,20 @@ export const commandCategories = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "ubrowser",
|
value: "ubrowser",
|
||||||
label: "UBrowser浏览器操作",
|
label: "ubrowser浏览器操作",
|
||||||
desc: "配置UBrowser浏览器操作",
|
desc: "配置ubrowser浏览器操作",
|
||||||
hasUBrowserEditor: true,
|
hasUBrowserEditor: true,
|
||||||
isAsync: true,
|
isAsync: true,
|
||||||
icon: "public",
|
icon: "public",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "axios",
|
value: "axios",
|
||||||
label: "发送HTTP请求(Axios)",
|
label: "HTTP请求(Axios)",
|
||||||
desc: "使用Axios发送HTTP请求",
|
desc: "使用Axios发送HTTP请求",
|
||||||
hasAxiosEditor: true,
|
hasAxiosEditor: true,
|
||||||
isAsync: true,
|
isAsync: true,
|
||||||
icon: "http",
|
icon: "http",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
value: "fetch",
|
|
||||||
label: "发送HTTP请求(Fetch)",
|
|
||||||
desc: "使用Fetch API发送HTTP请求",
|
|
||||||
hasFetchEditor: true,
|
|
||||||
isAsync: true,
|
|
||||||
icon: "http",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -82,24 +74,31 @@ export const commandCategories = [
|
|||||||
desc: "要写入剪切板的内容",
|
desc: "要写入剪切板的内容",
|
||||||
icon: "content_copy",
|
icon: "content_copy",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: "electron.clipboard.readText",
|
||||||
|
label: "获取剪贴板内容",
|
||||||
|
desc: "获取剪贴板内容",
|
||||||
|
icon: "content_copy",
|
||||||
|
hasNoArgs: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "消息通知",
|
label: "消息通知",
|
||||||
icon: "notifications",
|
icon: "notifications",
|
||||||
commands: [
|
commands: [
|
||||||
|
{
|
||||||
|
value: "console.log",
|
||||||
|
label: "打印消息",
|
||||||
|
desc: "要打印的消息文本",
|
||||||
|
icon: "info",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: "message",
|
value: "message",
|
||||||
label: "发送系统消息",
|
label: "发送系统消息",
|
||||||
desc: "要发送的系统消息文本",
|
desc: "要发送的系统消息文本",
|
||||||
icon: "message",
|
icon: "message",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
value: "quickcommand.showMessageBox",
|
|
||||||
label: "弹窗显示消息",
|
|
||||||
desc: "要弹窗显示的消息文本",
|
|
||||||
icon: "warning",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
value: "send",
|
value: "send",
|
||||||
label: "发送文本到活动窗口",
|
label: "发送文本到活动窗口",
|
||||||
|
@ -12,14 +12,46 @@ const processVariableValue = (value) => {
|
|||||||
return value.slice(1, -1);
|
return value.slice(1, -1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查路径是否匹配或是目标路径的父路径
|
||||||
|
* @param {string} currentPath 当前路径
|
||||||
|
* @param {string[]} targetPaths 目标路径列表
|
||||||
|
* @returns {boolean} 是否匹配
|
||||||
|
*/
|
||||||
|
const isPathMatched = (currentPath, targetPaths) => {
|
||||||
|
if (!targetPaths) return false;
|
||||||
|
return targetPaths.some(
|
||||||
|
(path) =>
|
||||||
|
path === currentPath || // 精确匹配
|
||||||
|
path.startsWith(currentPath + ".") // 是父路径
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归移除对象中的空值
|
||||||
|
* @param {Object} obj 要处理的对象
|
||||||
|
* @returns {Object} 处理后的对象
|
||||||
|
*/
|
||||||
|
const removeEmptyValues = (obj) => {
|
||||||
|
return _.omitBy(obj, (value) => {
|
||||||
|
if (_.isNil(value) || value === "") return true;
|
||||||
|
if (typeof value === "object")
|
||||||
|
return _.isEmpty(removeEmptyValues(value));
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 递归处理对象的值
|
* 递归处理对象的值
|
||||||
* @param {Object} obj 要处理的对象
|
* @param {Object} obj 要处理的对象
|
||||||
* @param {string} parentPath 父路径
|
* @param {string} parentPath 父路径
|
||||||
* @param {string[]|null} variableFields 需要处理的字段列表,null表示处理所有字段
|
* @param {string[]|null} variableFields 需要处理的字段列表,null表示处理所有字段
|
||||||
|
* @param {string[]|null} excludeFields 需要排除的字段列表,即使匹配了处理条件也不处理
|
||||||
* @returns {string} 处理后的字符串
|
* @returns {string} 处理后的字符串
|
||||||
*/
|
*/
|
||||||
const processObject = (obj, parentPath = "", variableFields) => {
|
const processObject = (obj, parentPath = "", variableFields, excludeFields) => {
|
||||||
|
// 移除空值
|
||||||
|
obj = removeEmptyValues(obj);
|
||||||
let result = "{\n";
|
let result = "{\n";
|
||||||
const entries = Object.entries(obj);
|
const entries = Object.entries(obj);
|
||||||
|
|
||||||
@ -28,16 +60,20 @@ const processObject = (obj, parentPath = "", variableFields) => {
|
|||||||
let valueStr = "";
|
let valueStr = "";
|
||||||
|
|
||||||
// 检查是否需要处理当前字段
|
// 检查是否需要处理当前字段
|
||||||
const shouldProcess =
|
const isIncluded =
|
||||||
!variableFields || // 不传递variableFields则处理所有字段
|
!variableFields || isPathMatched(currentPath, variableFields);
|
||||||
variableFields.includes(parentPath) || // 父字段是完整处理字段
|
const isExcluded =
|
||||||
variableFields.includes(key) || // 当前字段是完整处理字段
|
excludeFields && isPathMatched(currentPath, excludeFields);
|
||||||
variableFields.includes(currentPath) || // 当前路径精确匹配
|
const shouldProcess = isIncluded && !isExcluded;
|
||||||
variableFields.some((field) => field.startsWith(currentPath + ".")); // 当前路径是指定路径的父路径
|
|
||||||
|
|
||||||
// 处理对象类型
|
// 处理对象类型
|
||||||
if (typeof value === "object" && value !== null) {
|
if (typeof value === "object" && value !== null) {
|
||||||
valueStr = processObject(value, currentPath, variableFields);
|
valueStr = processObject(
|
||||||
|
value,
|
||||||
|
currentPath,
|
||||||
|
variableFields,
|
||||||
|
excludeFields
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// 处理字符串类型
|
// 处理字符串类型
|
||||||
else if (typeof value === "string") {
|
else if (typeof value === "string") {
|
||||||
@ -71,13 +107,19 @@ const processObject = (obj, parentPath = "", variableFields) => {
|
|||||||
* 1. 完整字段处理:如 headers - 处理整个对象及其所有子字段
|
* 1. 完整字段处理:如 headers - 处理整个对象及其所有子字段
|
||||||
* 2. 指定路径处理:如 data.headers.Referer - 只处理特定路径
|
* 2. 指定路径处理:如 data.headers.Referer - 只处理特定路径
|
||||||
* 3. 不传递 variableFields 则处理所有字段
|
* 3. 不传递 variableFields 则处理所有字段
|
||||||
|
* 4. 可以通过 excludeFields 排除特定字段,即使匹配了处理条件也不处理
|
||||||
* @param {string} jsonStr JSON字符串
|
* @param {string} jsonStr JSON字符串
|
||||||
* @param {string[]|null} [variableFields] 需要处理的字段列表,包括完整字段和指定路径。不传则处理所有字段
|
* @param {string[]|null} [variableFields] 需要处理的字段列表,包括完整字段和指定路径。不传则处理所有字段
|
||||||
|
* @param {string[]|null} [excludeFields] 需要排除的字段列表,即使匹配了处理条件也不处理
|
||||||
* @returns {string} 处理后的字符串
|
* @returns {string} 处理后的字符串
|
||||||
*/
|
*/
|
||||||
export const formatJsonVariables = (jsonObj, variableFields = null) => {
|
export const formatJsonVariables = (
|
||||||
|
jsonObj,
|
||||||
|
variableFields = null,
|
||||||
|
excludeFields = null
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
return processObject(jsonObj, "", variableFields);
|
return processObject(jsonObj, "", variableFields, excludeFields);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Failed to process JSON variables:", e);
|
console.warn("Failed to process JSON variables:", e);
|
||||||
return JSON.stringify(jsonObj, null, 2);
|
return JSON.stringify(jsonObj, null, 2);
|
||||||
|
@ -55,12 +55,15 @@ export const commonHeaders = [
|
|||||||
{ label: "Content-Type", value: "Content-Type" },
|
{ label: "Content-Type", value: "Content-Type" },
|
||||||
{ label: "Authorization", value: "Authorization" },
|
{ label: "Authorization", value: "Authorization" },
|
||||||
{ label: "User-Agent", value: "User-Agent" },
|
{ label: "User-Agent", value: "User-Agent" },
|
||||||
|
{ label: "Cookie", value: "Cookie" },
|
||||||
{ label: "Accept", value: "Accept" },
|
{ label: "Accept", value: "Accept" },
|
||||||
{ label: "Accept-Language", value: "Accept-Language" },
|
{ label: "Accept-Language", value: "Accept-Language" },
|
||||||
{ label: "Accept-Encoding", value: "Accept-Encoding" },
|
{ label: "Accept-Encoding", value: "Accept-Encoding" },
|
||||||
{ label: "Cookie", value: "Cookie" },
|
|
||||||
{ label: "Origin", value: "Origin" },
|
{ label: "Origin", value: "Origin" },
|
||||||
{ label: "Referer", value: "Referer" },
|
{ label: "Referer", value: "Referer" },
|
||||||
|
{ label: "X-Requested-With", value: "X-Requested-With" },
|
||||||
|
{ label: "X-Forwarded-For", value: "X-Forwarded-For" },
|
||||||
|
{ label: "X-Real-IP", value: "X-Real-IP" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const deviceName = [
|
export const deviceName = [
|
||||||
@ -75,3 +78,38 @@ export const deviceName = [
|
|||||||
{ label: "HUAWEI Mate30", value: "HUAWEI Mate30" },
|
{ label: "HUAWEI Mate30", value: "HUAWEI Mate30" },
|
||||||
{ label: "HUAWEI Mate30 Pro", value: "HUAWEI Mate30 Pro" },
|
{ label: "HUAWEI Mate30 Pro", value: "HUAWEI Mate30 Pro" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const contentTypes = [
|
||||||
|
{
|
||||||
|
label: "application/json",
|
||||||
|
value: "application/json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "application/x-www-form-urlencoded",
|
||||||
|
value: "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "multipart/form-data",
|
||||||
|
value: "multipart/form-data",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "text/plain",
|
||||||
|
value: "text/plain",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "text/html",
|
||||||
|
value: "text/html",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "text/xml",
|
||||||
|
value: "text/xml",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "application/xml",
|
||||||
|
value: "application/xml",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "application/octet-stream",
|
||||||
|
value: "application/octet-stream",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user