mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-07 13:34:08 +08:00
使用通用组件重构Ubrowser的operations部分
This commit is contained in:
parent
ae8edfd710
commit
5a0675d913
@ -18,7 +18,9 @@
|
||||
{ 'check-btn--selected': isSelected(option.value) },
|
||||
]"
|
||||
:style="{
|
||||
flex: `1 0 ${100 / options.length}%`,
|
||||
flex: `1 0 calc(${100 / options.length}% - ${
|
||||
(4 * (options.length - 1)) / options.length
|
||||
}px)`,
|
||||
}"
|
||||
@click="toggleOption(option.value)"
|
||||
>
|
||||
@ -108,7 +110,7 @@ export default defineComponent({
|
||||
height: auto !important;
|
||||
min-height: 32px;
|
||||
font-size: 12px;
|
||||
padding: 4px 12px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px !important;
|
||||
transition: all 0.3s;
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
|
@ -168,7 +168,10 @@ export default defineComponent({
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
created() {
|
||||
if (!this.modelValue || Object.keys(this.modelValue).length === 0) {
|
||||
if (
|
||||
(!this.modelValue || Object.keys(this.modelValue).length === 0) &&
|
||||
!this.options?.disableAdd
|
||||
) {
|
||||
this.$emit("update:modelValue", { "": newVarInputVal("str") });
|
||||
}
|
||||
},
|
||||
|
175
src/components/composer/common/FunctionInput.vue
Normal file
175
src/components/composer/common/FunctionInput.vue
Normal file
@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<div class="row q-col-gutter-sm function-input">
|
||||
<div class="col-12">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-3">
|
||||
<q-select
|
||||
:model-value="params"
|
||||
use-input
|
||||
use-chips
|
||||
multiple
|
||||
dense
|
||||
borderless
|
||||
hide-dropdown-icon
|
||||
options-dense
|
||||
input-debounce="0"
|
||||
new-value-mode="add-unique"
|
||||
label="参数"
|
||||
@update:model-value="updateParams"
|
||||
@input-value="handleInput"
|
||||
@blur="handleBlur"
|
||||
ref="paramSelect"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<div class="text-primary func-symbol">(</div>
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<div class="text-primary func-symbol">)</div>
|
||||
</template>
|
||||
</q-select>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<q-input
|
||||
:model-value="funcContent"
|
||||
:label="label"
|
||||
type="textarea"
|
||||
dense
|
||||
borderless
|
||||
:placeholder="placeholder"
|
||||
style="font-family: Consolas, Monaco, 'Courier New'"
|
||||
autogrow
|
||||
@update:model-value="updateContent"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<div class="text-primary func-symbol">=> {</div>
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<div class="text-primary func-symbol">}</div>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import { newVarInputVal } from "src/js/composer/varInputValManager";
|
||||
|
||||
export default defineComponent({
|
||||
name: "FunctionInput",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: newVarInputVal("var", ""),
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: "函数内容",
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: "code",
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
emits: ["update:model-value"],
|
||||
computed: {
|
||||
funcStr() {
|
||||
return this.modelValue.value;
|
||||
},
|
||||
params() {
|
||||
const match = this.funcStr.match(/^\((.*?)\)/);
|
||||
if (!match) return [];
|
||||
return match[1]
|
||||
.split(",")
|
||||
.map((p) => p.trim())
|
||||
.filter((p) => p);
|
||||
},
|
||||
funcContent() {
|
||||
const match = this.funcStr.match(/\{([\s\S]*)\}/);
|
||||
if (!match) return "";
|
||||
return match[1].trim();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateParams(newParams) {
|
||||
const functionStr = `(${newParams.join(", ")}) => { ${
|
||||
this.funcContent
|
||||
} }`;
|
||||
this.updateModelValue(functionStr);
|
||||
},
|
||||
updateContent(newContent) {
|
||||
const functionStr = `(${this.params.join(", ")}) => { ${newContent} }`;
|
||||
this.updateModelValue(functionStr);
|
||||
},
|
||||
handleInput(val) {
|
||||
if (!val) return;
|
||||
|
||||
if (val.includes(",") || val.includes(" ")) {
|
||||
const newParams = val
|
||||
.split(/[,\s]+/)
|
||||
.map((p) => p.trim())
|
||||
.filter((p) => p);
|
||||
|
||||
const allParams = [...new Set([...this.params, ...newParams])];
|
||||
this.updateParams(allParams);
|
||||
this.$refs.paramSelect.updateInputValue("");
|
||||
}
|
||||
},
|
||||
handleBlur() {
|
||||
const inputValue = this.$refs.paramSelect.inputValue;
|
||||
if (inputValue && !this.params.includes(inputValue)) {
|
||||
const newParams = [...this.params, inputValue];
|
||||
this.updateParams(newParams);
|
||||
this.$refs.paramSelect.updateInputValue("");
|
||||
}
|
||||
},
|
||||
updateModelValue(newVal) {
|
||||
this.$emit("update:model-value", newVarInputVal("var", newVal));
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.function-input :deep(.q-field__control) .text-primary.func-symbol {
|
||||
font-size: 24px !important;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.function-input :deep(.q-select .q-field__native > div) {
|
||||
display: flex !important;
|
||||
flex-wrap: nowrap !important;
|
||||
flex: 0 0 auto !important;
|
||||
}
|
||||
|
||||
.function-input :deep(.q-select .q-chip) {
|
||||
flex: 0 0 auto !important;
|
||||
margin-right: 4px !important;
|
||||
}
|
||||
|
||||
.function-input :deep(.q-select__input::-webkit-scrollbar),
|
||||
.function-input :deep(.q-select .q-field__native::-webkit-scrollbar) {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
@ -74,6 +74,7 @@
|
||||
filled
|
||||
emit-value
|
||||
map-options
|
||||
options-dense
|
||||
:options="contentTypes"
|
||||
@update:model-value="
|
||||
updateArgvs('headers.Content-Type', $event)
|
||||
|
@ -23,6 +23,7 @@ import ControlInput from "components/composer/common/ControlInput.vue";
|
||||
import CheckGroup from "components/composer/common/CheckGroup.vue";
|
||||
import CheckButton from "components/composer/common/CheckButton.vue";
|
||||
import TimeInput from "components/composer/common/TimeInput.vue";
|
||||
import FunctionInput from "components/composer/common/FunctionInput.vue";
|
||||
import { QInput, QSelect, QToggle, QCheckbox } from "quasar";
|
||||
const CodeEditor = defineAsyncComponent(() =>
|
||||
import("components/composer/common/CodeEditor.vue")
|
||||
@ -45,6 +46,7 @@ export default defineComponent({
|
||||
QCheckbox,
|
||||
TimeInput,
|
||||
CodeEditor,
|
||||
FunctionInput,
|
||||
},
|
||||
props: {
|
||||
config: {
|
||||
@ -85,6 +87,7 @@ export default defineComponent({
|
||||
if (this.isQuasarSelect) {
|
||||
props.emitValue = true;
|
||||
props.mapOptions = true;
|
||||
props.optionsDense = true;
|
||||
}
|
||||
|
||||
return props;
|
||||
|
@ -1,126 +1,161 @@
|
||||
<template>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<!-- 基础配置 -->
|
||||
<div class="col-12">
|
||||
<VariableInput
|
||||
v-model="localConfigs.goto.url"
|
||||
label="网址"
|
||||
icon="link"
|
||||
@update:model-value="updateConfigs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Headers配置 -->
|
||||
<div class="col-12">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-12">
|
||||
<VariableInput
|
||||
v-model="localConfigs.goto.headers.Referer"
|
||||
label="Referer"
|
||||
icon="link"
|
||||
@update:model-value="updateConfigs"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col">
|
||||
<VariableInput
|
||||
v-model="localConfigs.goto.headers.userAgent"
|
||||
label="User-Agent"
|
||||
icon="devices"
|
||||
@update:model-value="updateConfigs"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-select
|
||||
:model-value="selectedUA"
|
||||
@update:model-value="handleUAChange"
|
||||
:options="userAgentOptions"
|
||||
label="常用 UA"
|
||||
dense
|
||||
filled
|
||||
emit-value
|
||||
map-options
|
||||
options-dense
|
||||
style="min-width: 150px"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="list" />
|
||||
</template>
|
||||
</q-select>
|
||||
</div>
|
||||
<div class="ubrowser-basic">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<!-- 基础配置 -->
|
||||
<div class="col-12">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-9">
|
||||
<VariableInput
|
||||
v-model="url"
|
||||
label="网址"
|
||||
icon="link"
|
||||
class="col"
|
||||
:options="{
|
||||
items: userAgentOptions,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<NumberInput
|
||||
v-model="timeout"
|
||||
label="超时时间(ms)"
|
||||
icon="timer"
|
||||
:min="0"
|
||||
:step="1000"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 超时配置 -->
|
||||
<div class="col-12">
|
||||
<NumberInput
|
||||
v-model="localConfigs.goto.timeout"
|
||||
icon="timer"
|
||||
label="超时时间(ms)"
|
||||
@update:model-value="updateConfigs"
|
||||
/>
|
||||
<!-- Headers配置 -->
|
||||
<div class="col-12">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-12">
|
||||
<VariableInput
|
||||
v-model="userAgent"
|
||||
label="User-Agent"
|
||||
icon="devices"
|
||||
:options="{
|
||||
items: userAgentOptions,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<DictEditor
|
||||
v-model="otherHeaders"
|
||||
label="其他请求头"
|
||||
:options="{
|
||||
optionKeys: commonHeaders,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, computed } from "vue";
|
||||
import { userAgent } from "js/options/httpOptions";
|
||||
import VariableInput from "components/composer/common/VariableInput.vue";
|
||||
import NumberInput from "components/composer/common/NumberInput.vue";
|
||||
import DictEditor from "components/composer/common/DictEditor.vue";
|
||||
import { userAgent, commonHeaders } from "js/options/httpOptions";
|
||||
import { newVarInputVal } from "js/composer/varInputValManager";
|
||||
export default defineComponent({
|
||||
|
||||
export default {
|
||||
name: "UBrowserBasic",
|
||||
components: {
|
||||
VariableInput,
|
||||
NumberInput,
|
||||
DictEditor,
|
||||
},
|
||||
props: {
|
||||
configs: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:configs"],
|
||||
setup(props, { emit }) {
|
||||
const selectedUA = ref(null);
|
||||
|
||||
// 使用 computed 处理配置
|
||||
const localConfigs = computed({
|
||||
get: () => props.configs,
|
||||
set: (val) => {
|
||||
emit("update:configs", val);
|
||||
},
|
||||
});
|
||||
|
||||
// 更新配置
|
||||
const updateConfigs = () => {
|
||||
emit("update:configs", localConfigs.value);
|
||||
};
|
||||
|
||||
// 处理 UA 选择
|
||||
const handleUAChange = (val) => {
|
||||
if (!val) return;
|
||||
|
||||
const newConfigs = window.lodashM.cloneDeep(props.configs);
|
||||
if (!newConfigs.goto.headers) {
|
||||
newConfigs.goto.headers = {};
|
||||
}
|
||||
newConfigs.goto.headers.userAgent = newVarInputVal("str", val);
|
||||
emit("update:configs", newConfigs);
|
||||
selectedUA.value = null;
|
||||
};
|
||||
|
||||
emits: ["update:model-value"],
|
||||
data() {
|
||||
return {
|
||||
selectedUA,
|
||||
localConfigs,
|
||||
selectedUA: null,
|
||||
userAgentOptions: userAgent,
|
||||
updateConfigs,
|
||||
handleUAChange,
|
||||
commonHeaders: commonHeaders,
|
||||
};
|
||||
},
|
||||
});
|
||||
computed: {
|
||||
url: {
|
||||
get() {
|
||||
return this.modelValue.url || newVarInputVal("str", "");
|
||||
},
|
||||
set(value) {
|
||||
this.updateField("url", value);
|
||||
},
|
||||
},
|
||||
userAgent: {
|
||||
get() {
|
||||
return (
|
||||
this.modelValue.headers?.["User-Agent"] || newVarInputVal("str", "")
|
||||
);
|
||||
},
|
||||
set(value) {
|
||||
this.updateHeaders("User-Agent", value);
|
||||
},
|
||||
},
|
||||
timeout: {
|
||||
get() {
|
||||
return this.modelValue.timeout || 60000;
|
||||
},
|
||||
set(value) {
|
||||
this.updateField("timeout", value);
|
||||
},
|
||||
},
|
||||
otherHeaders: {
|
||||
get() {
|
||||
if (!this.modelValue.headers) return {};
|
||||
const standardHeaders = ["User-Agent"];
|
||||
return Object.entries(this.modelValue.headers)
|
||||
.filter(([key]) => !standardHeaders.includes(key))
|
||||
.reduce((acc, [key, value]) => {
|
||||
acc[key] = value;
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
set(value) {
|
||||
const userAgent = this.modelValue.headers?.["User-Agent"];
|
||||
const newHeaders = {
|
||||
...(userAgent ? { "User-Agent": userAgent } : {}),
|
||||
...value,
|
||||
};
|
||||
this.updateField("headers", newHeaders);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateField(field, value) {
|
||||
this.$emit("update:model-value", {
|
||||
...this.modelValue,
|
||||
[field]: value,
|
||||
});
|
||||
},
|
||||
updateHeaders(key, value) {
|
||||
const headers = {
|
||||
...this.modelValue.headers,
|
||||
[key]: value,
|
||||
};
|
||||
this.updateField("headers", headers);
|
||||
},
|
||||
handleUAChange(value) {
|
||||
if (!value) return;
|
||||
this.updateHeaders("User-Agent", newVarInputVal("str", value));
|
||||
this.selectedUA = null;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ubrowser-basic {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,52 +1,57 @@
|
||||
<template>
|
||||
<div class="ubrowser-editor">
|
||||
<!-- 标签页导航 -->
|
||||
<q-tabs
|
||||
v-model="step"
|
||||
class="ubrowser-tabs"
|
||||
v-model="tab"
|
||||
dense
|
||||
class="text-grey"
|
||||
active-color="primary"
|
||||
indicator-color="primary"
|
||||
align="left"
|
||||
narrow-indicator
|
||||
no-caps
|
||||
inline-label
|
||||
active-class="ubrowser-tabs-active"
|
||||
outside-arrows
|
||||
mobile-arrows
|
||||
>
|
||||
<q-tab name="1" icon="settings" label="基础参数" size="sm" />
|
||||
<q-tab name="2" icon="touch_app" label="浏览器操作" size="sm" />
|
||||
<q-tab name="3" icon="settings_applications" label="运行参数" size="sm" />
|
||||
<q-tab name="1" label="基础配置" icon="link" class="q-px-sm" />
|
||||
<q-tab name="2" label="操作配置" icon="touch_app" class="q-px-sm" />
|
||||
<q-tab name="3" label="运行配置" icon="settings" class="q-px-sm" />
|
||||
</q-tabs>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<q-tab-panels v-model="step" class="ubrowser-panels">
|
||||
<q-separator />
|
||||
|
||||
<q-tab-panels v-model="tab" class="ubrowser-panels" animated>
|
||||
<q-tab-panel name="1" class="panel-content">
|
||||
<UBrowserBasic :configs="configs" @update:configs="updateConfigs" />
|
||||
<UBrowserBasic
|
||||
v-model="argvs.goto"
|
||||
@update:model-value="updateArgvs('goto', $event)"
|
||||
/>
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="2" class="panel-content">
|
||||
<UBrowserOperations
|
||||
:configs="configs"
|
||||
:selected-actions="selectedActions"
|
||||
@update:configs="updateConfigs"
|
||||
@update:selected-actions="(val) => (selectedActions = val)"
|
||||
@remove-action="removeAction"
|
||||
v-model="argvs.operations"
|
||||
@update:model-value="updateArgvs('operations', $event)"
|
||||
/>
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="3" class="panel-content">
|
||||
<UBrowserRun :configs="configs" @update:configs="updateConfigs" />
|
||||
<UBrowserRun
|
||||
v-model="argvs.run"
|
||||
@update:model-value="updateArgvs('run', $event)"
|
||||
/>
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, computed } from "vue";
|
||||
import UBrowserBasic from "./UBrowserBasic.vue";
|
||||
import UBrowserOperations from "./UBrowserOperations.vue";
|
||||
import UBrowserRun from "./UBrowserRun.vue";
|
||||
import { defaultUBrowserConfigs } from "js/composer/ubrowserConfig";
|
||||
import UBrowserBasic from "components/composer/ubrowser/UBrowserBasic.vue";
|
||||
import UBrowserOperations from "components/composer/ubrowser/UBrowserOperations.vue";
|
||||
import UBrowserRun from "components/composer/ubrowser/UBrowserRun.vue";
|
||||
import { generateUBrowserCode } from "js/composer/generateUBrowserCode";
|
||||
|
||||
export default defineComponent({
|
||||
export default {
|
||||
name: "UBrowserEditor",
|
||||
components: {
|
||||
UBrowserBasic,
|
||||
@ -59,76 +64,59 @@ export default defineComponent({
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
setup(props, { emit }) {
|
||||
// 基础状态
|
||||
const step = ref("1");
|
||||
const selectedActions = ref([]);
|
||||
|
||||
// 初始化配置,确保包含 run 参数
|
||||
const localConfigs = ref(window.lodashM.cloneDeep(defaultUBrowserConfigs));
|
||||
if (props.modelValue?.argvs) {
|
||||
// 合并配置,保留默认的 run 参数
|
||||
localConfigs.value = {
|
||||
...localConfigs.value,
|
||||
...props.modelValue.argvs,
|
||||
run: {
|
||||
...localConfigs.value.run,
|
||||
...props.modelValue.argvs.run,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const getSummary = (argvs) => {
|
||||
return argvs.goto.url.value;
|
||||
};
|
||||
|
||||
// 计算 argvs
|
||||
const argvs = computed({
|
||||
get: () => localConfigs.value,
|
||||
set: (val) => {
|
||||
// 确保保留 run 参数
|
||||
const newConfigs = {
|
||||
...val,
|
||||
run: {
|
||||
...localConfigs.value.run,
|
||||
...val.run,
|
||||
},
|
||||
};
|
||||
localConfigs.value = newConfigs;
|
||||
emit("update:modelValue", {
|
||||
...props.modelValue,
|
||||
argvs: newConfigs,
|
||||
summary: getSummary(newConfigs),
|
||||
code: generateUBrowserCode(newConfigs, selectedActions.value),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// 更新配置
|
||||
const updateConfigs = (newConfigs) => {
|
||||
argvs.value = window.lodashM.cloneDeep(newConfigs);
|
||||
};
|
||||
|
||||
// 移除操作
|
||||
const removeAction = (action) => {
|
||||
selectedActions.value = selectedActions.value.filter(
|
||||
(a) => a.id !== action.id
|
||||
);
|
||||
const newConfigs = { ...argvs.value };
|
||||
delete newConfigs[action.value];
|
||||
argvs.value = newConfigs;
|
||||
};
|
||||
|
||||
emits: ["update:model-value"],
|
||||
data() {
|
||||
return {
|
||||
step,
|
||||
selectedActions,
|
||||
configs: argvs,
|
||||
updateConfigs,
|
||||
removeAction,
|
||||
tab: "1",
|
||||
defaultArgvs: {
|
||||
goto: [],
|
||||
operations: [],
|
||||
run: {},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
computed: {
|
||||
configs() {
|
||||
return this.modelValue;
|
||||
},
|
||||
argvs() {
|
||||
return this.modelValue.argvs || this.defaultArgvs;
|
||||
},
|
||||
summary() {
|
||||
const goto = this.argvs.goto?.url || "";
|
||||
return `访问 ${goto}`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initializeConfigs();
|
||||
},
|
||||
methods: {
|
||||
initializeConfigs() {
|
||||
if (!this.modelValue.argvs) {
|
||||
this.updateModelValue(this.defaultArgvs);
|
||||
}
|
||||
},
|
||||
generateCode() {
|
||||
return generateUBrowserCode(this.argvs);
|
||||
},
|
||||
updateArgvs(key, value) {
|
||||
const newArgvs = { ...this.argvs };
|
||||
newArgvs[key] = value;
|
||||
this.updateModelValue(newArgvs);
|
||||
},
|
||||
updateModelValue(argvs) {
|
||||
this.$emit("update:model-value", {
|
||||
...this.modelValue,
|
||||
argvs,
|
||||
summary: this.summary,
|
||||
code: this.generateCode(),
|
||||
});
|
||||
},
|
||||
parseCodeToArgvs(code) {
|
||||
// TODO: 实现代码解析逻辑
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@ -137,42 +125,67 @@ export default defineComponent({
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ubrowser-tabs-active {
|
||||
color: var(--q-primary);
|
||||
}
|
||||
|
||||
.ubrowser-tabs {
|
||||
flex-shrink: 0;
|
||||
background-color: var(--q-card-background);
|
||||
}
|
||||
|
||||
.ubrowser-panels {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background-color: transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ubrowser-panels :deep(.q-tab-panel) {
|
||||
padding: 0;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
padding: 8px;
|
||||
min-height: 200px;
|
||||
padding: 12px;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ubrowser-panels :deep(.q-tab-panel) {
|
||||
padding: 0;
|
||||
.ubrowser-editor :deep(.q-tabs) {
|
||||
min-height: 36px;
|
||||
padding: 0 8px;
|
||||
background-color: var(--q-card-background);
|
||||
}
|
||||
|
||||
.ubrowser-tabs :deep(.q-tab) {
|
||||
min-height: 40px;
|
||||
padding: 0 16px;
|
||||
.ubrowser-editor :deep(.q-tab) {
|
||||
min-height: 36px;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
text-transform: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.ubrowser-tabs :deep(.q-tab__icon) {
|
||||
font-size: 20px;
|
||||
.ubrowser-editor :deep(.q-tab--active) {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ubrowser-tabs :deep(.q-tab__label) {
|
||||
font-size: 14px;
|
||||
.ubrowser-editor :deep(.q-tab__icon) {
|
||||
font-size: 16px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.ubrowser-editor :deep(.q-tab__label) {
|
||||
line-height: 1.2;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.ubrowser-editor :deep(.q-tab__indicator) {
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
/* 暗色模式适配 */
|
||||
.body--dark .ubrowser-editor {
|
||||
background-color: var(--q-dark);
|
||||
}
|
||||
|
||||
.body--dark .ubrowser-editor :deep(.q-tabs) {
|
||||
background-color: var(--q-dark);
|
||||
}
|
||||
</style>
|
||||
|
@ -1,179 +1,174 @@
|
||||
<template>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-12">
|
||||
<!-- 操作选择网格 -->
|
||||
<div class="row q-col-gutter-xs">
|
||||
<div
|
||||
v-for="action in ubrowserOperationConfigs"
|
||||
:key="action.value"
|
||||
class="col-2"
|
||||
>
|
||||
<q-card
|
||||
flat
|
||||
bordered
|
||||
class="action-card cursor-pointer"
|
||||
:class="{
|
||||
'action-selected': selectedActions.some(
|
||||
(a) => a.value === action.value
|
||||
),
|
||||
}"
|
||||
@click="toggleAction(action)"
|
||||
<div class="ubrowser-operations">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-12">
|
||||
<!-- 操作选择网格 -->
|
||||
<div class="row q-col-gutter-xs">
|
||||
<div
|
||||
v-for="[actionKey, action] in Object.entries(operationsMap)"
|
||||
:key="actionKey"
|
||||
class="col-2"
|
||||
>
|
||||
<div class="q-pa-xs text-caption text-wrap text-center">
|
||||
{{ action.label }}
|
||||
</div>
|
||||
</q-card>
|
||||
<q-card
|
||||
flat
|
||||
bordered
|
||||
class="action-card cursor-pointer"
|
||||
:class="{
|
||||
'action-selected': selectedActionKeys.includes(actionKey),
|
||||
}"
|
||||
@click="toggleAction(actionKey)"
|
||||
>
|
||||
<div class="q-pa-xs text-caption text-wrap text-center">
|
||||
{{ action.label }}
|
||||
</div>
|
||||
</q-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 已选操作列表 -->
|
||||
<q-list separator class="operation-list q-mt-md">
|
||||
<div
|
||||
v-for="(action, index) in selectedActions"
|
||||
:key="action.id"
|
||||
class="operation-item"
|
||||
>
|
||||
<div class="row items-center justify-between">
|
||||
<q-chip
|
||||
square
|
||||
removable
|
||||
@remove="$emit('remove-action', action)"
|
||||
class="text-caption q-mx-none q-mb-sm"
|
||||
>
|
||||
<q-avatar color="primary">
|
||||
<q-icon
|
||||
color="white"
|
||||
:name="getActionProps(action, 'icon') || 'touch_app'"
|
||||
size="14px"
|
||||
/>
|
||||
</q-avatar>
|
||||
<div class="q-mx-sm">{{ action.label }}</div>
|
||||
</q-chip>
|
||||
<div class="row items-start q-gutter-xs">
|
||||
<q-btn
|
||||
round
|
||||
dense
|
||||
color="primary"
|
||||
icon="north"
|
||||
v-show="index !== 0"
|
||||
@click="moveAction(index, -1)"
|
||||
size="xs"
|
||||
class="q-mb-xs move-btn"
|
||||
/>
|
||||
<q-btn
|
||||
round
|
||||
dense
|
||||
color="primary"
|
||||
icon="south"
|
||||
v-show="index !== selectedActions.length - 1"
|
||||
@click="moveAction(index, 1)"
|
||||
size="xs"
|
||||
class="move-btn"
|
||||
<!-- 已选操作列表 -->
|
||||
<q-list separator class="operation-list q-mt-md">
|
||||
<div
|
||||
v-for="(selectedActionKey, index) in selectedActionKeys"
|
||||
:key="selectedActionKey"
|
||||
class="operation-item"
|
||||
>
|
||||
<div class="row items-center justify-between">
|
||||
<q-chip
|
||||
square
|
||||
removable
|
||||
@remove="toggleAction(selectedActionKey)"
|
||||
class="text-caption q-mb-sm"
|
||||
:style="{
|
||||
paddingLeft: '7px',
|
||||
}"
|
||||
>
|
||||
<q-avatar color="primary">
|
||||
<q-icon
|
||||
color="white"
|
||||
:name="operationsMap[selectedActionKey].icon || 'touch_app'"
|
||||
size="14px"
|
||||
/>
|
||||
</q-avatar>
|
||||
<div class="q-mx-sm">
|
||||
{{ operationsMap[selectedActionKey].label }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="getActionProps(action, 'config')">
|
||||
<UBrowserOperation
|
||||
:configs="configs"
|
||||
:action="action.value"
|
||||
:fields="getActionProps(action, 'config')"
|
||||
@update:configs="$emit('update:configs', $event)"
|
||||
</q-chip>
|
||||
<div class="row items-start q-gutter-xs">
|
||||
<q-btn
|
||||
round
|
||||
dense
|
||||
color="primary"
|
||||
icon="north"
|
||||
v-show="index !== 0"
|
||||
@click="moveAction(index, -1)"
|
||||
size="xs"
|
||||
class="q-mb-xs move-btn"
|
||||
/>
|
||||
<q-btn
|
||||
round
|
||||
dense
|
||||
color="primary"
|
||||
icon="south"
|
||||
v-show="index !== selectedActionKeys.length - 1"
|
||||
@click="moveAction(index, 1)"
|
||||
size="xs"
|
||||
class="move-btn"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-list>
|
||||
</div>
|
||||
<div
|
||||
v-if="operationsMap[selectedActionKey].config"
|
||||
class="operation-config"
|
||||
>
|
||||
<ParamInput
|
||||
:configs="operationsMap[selectedActionKey].config"
|
||||
:values="selectedActionArgs[index]"
|
||||
@update="
|
||||
(argvIndex, argvVal) =>
|
||||
updateActionArgs(argvIndex, argvVal, index)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import { ubrowserOperationConfigs } from "js/composer/composerConfig";
|
||||
import UBrowserOperation from "./operations/UBrowserOperation.vue";
|
||||
import { ubrowserOperationConfigs } from "js/composer/ubrowserConfig";
|
||||
import ParamInput from "components/composer/param/ParamInput.vue";
|
||||
|
||||
export default defineComponent({
|
||||
export default {
|
||||
name: "UBrowserOperations",
|
||||
components: {
|
||||
UBrowserOperation,
|
||||
ParamInput,
|
||||
},
|
||||
props: {
|
||||
configs: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
selectedActions: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
emits: ["remove-action", "update:selectedActions", "update:configs"],
|
||||
setup() {
|
||||
emits: ["update:model-value"],
|
||||
data() {
|
||||
return {
|
||||
ubrowserOperationConfigs,
|
||||
operationsMap: ubrowserOperationConfigs,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedActionKeys() {
|
||||
return this.modelValue.map((x) => x.key || x.value);
|
||||
},
|
||||
selectedActionArgs() {
|
||||
return this.modelValue.map((x) => x.args);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
moveAction(index, direction) {
|
||||
const newIndex = index + direction;
|
||||
if (newIndex >= 0 && newIndex < this.selectedActions.length) {
|
||||
const actions = [...this.selectedActions];
|
||||
[actions[index], actions[newIndex]] = [
|
||||
actions[newIndex],
|
||||
actions[index],
|
||||
if (newIndex >= 0 && newIndex < this.selectedActionKeys.length) {
|
||||
const newOperation = [...this.modelValue];
|
||||
[newOperation[index], newOperation[newIndex]] = [
|
||||
newOperation[newIndex],
|
||||
newOperation[index],
|
||||
];
|
||||
this.$emit("update:selectedActions", actions);
|
||||
this.$emit("update:model-value", newOperation);
|
||||
}
|
||||
},
|
||||
toggleAction(action) {
|
||||
const index = this.selectedActions.findIndex(
|
||||
(a) => a.value === action.value
|
||||
);
|
||||
|
||||
if (index === -1) {
|
||||
// 添加操作
|
||||
const newAction = {
|
||||
...action,
|
||||
id: this.$root.getUniqueId(),
|
||||
argv: "",
|
||||
saveOutput: false,
|
||||
cmd: action.value || action.cmd,
|
||||
value: action.value || action.cmd,
|
||||
};
|
||||
|
||||
this.$emit("update:selectedActions", [
|
||||
...this.selectedActions,
|
||||
newAction,
|
||||
]);
|
||||
|
||||
// 初始化配置对象
|
||||
const { config } = action;
|
||||
if (config) {
|
||||
const newConfigs = { ...this.configs };
|
||||
if (!newConfigs[action.value]) {
|
||||
newConfigs[action.value] = {};
|
||||
// 设置默认值
|
||||
config.forEach((field) => {
|
||||
if (field.defaultValue !== undefined) {
|
||||
newConfigs[action.value][field.key] = field.defaultValue;
|
||||
}
|
||||
});
|
||||
this.$emit("update:configs", newConfigs);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
toggleAction(actionKey) {
|
||||
const index = this.selectedActionKeys.indexOf(actionKey);
|
||||
let newOperation = [...this.modelValue];
|
||||
if (index !== -1) {
|
||||
// 移除操作
|
||||
const newActions = [...this.selectedActions];
|
||||
newActions.splice(index, 1);
|
||||
this.$emit("update:selectedActions", newActions);
|
||||
newOperation.splice(index, 1);
|
||||
} else {
|
||||
// 添加操作
|
||||
const { config, value } = this.operationsMap[actionKey];
|
||||
const args = config?.length
|
||||
? config.map((field) => field.defaultValue)
|
||||
: [];
|
||||
|
||||
const newOperationItem = { value, args };
|
||||
if (actionKey !== value) {
|
||||
newOperationItem.key = actionKey;
|
||||
}
|
||||
|
||||
newOperation.push(newOperationItem);
|
||||
}
|
||||
this.$emit("update:model-value", newOperation);
|
||||
},
|
||||
getActionProps(action, key) {
|
||||
return this.ubrowserOperationConfigs.find(
|
||||
(a) => a.value === action.value
|
||||
)?.[key];
|
||||
updateActionArgs(argvIndex, argvVal, actionIndex) {
|
||||
const newOperation = [...this.modelValue];
|
||||
const newArgs = [...newOperation[actionIndex].args];
|
||||
newArgs[argvIndex] = argvVal;
|
||||
newOperation[actionIndex].args = newArgs;
|
||||
this.$emit("update:model-value", newOperation);
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -196,14 +191,10 @@ export default defineComponent({
|
||||
padding: 2px 4px;
|
||||
border-color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
/*
|
||||
.operation-item:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.body--dark .operation-item:hover {
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
} */
|
||||
.operation-config {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.move-btn {
|
||||
opacity: 0.6;
|
||||
@ -214,32 +205,9 @@ export default defineComponent({
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.operation-item:hover .delete-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.text-subtitle2 {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.q-item-section {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.operation-item:hover .q-item-section {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.action-card {
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
/* min-height: 42px; */
|
||||
}
|
||||
|
||||
.action-card:hover {
|
||||
|
@ -1,193 +1,152 @@
|
||||
<template>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<!-- 窗口显示控制 -->
|
||||
<div class="col-12">
|
||||
<div class="row items-center q-gutter-x-md">
|
||||
<q-checkbox
|
||||
:model-value="localConfigs.run.show"
|
||||
label="显示窗口"
|
||||
@update:model-value="updateConfig('show', $event)"
|
||||
/>
|
||||
<q-checkbox
|
||||
:model-value="localConfigs.run.center"
|
||||
label="居中显示"
|
||||
@update:model-value="updateConfig('center', $event)"
|
||||
/>
|
||||
<q-checkbox
|
||||
:model-value="localConfigs.run.alwaysOnTop"
|
||||
label="总在最前"
|
||||
@update:model-value="updateConfig('alwaysOnTop', $event)"
|
||||
/>
|
||||
<q-checkbox
|
||||
:model-value="localConfigs.run.fullscreen"
|
||||
label="全屏显示"
|
||||
@update:model-value="updateConfig('fullscreen', $event)"
|
||||
/>
|
||||
<q-checkbox
|
||||
:model-value="localConfigs.run.fullscreenable"
|
||||
label="允许全屏"
|
||||
@update:model-value="updateConfig('fullscreenable', $event)"
|
||||
/>
|
||||
<div class="ubrowser-run">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<!-- 窗口显示控制 -->
|
||||
<div class="col-12">
|
||||
<div class="row items-center q-gutter-x-md">
|
||||
<q-checkbox v-model="fields.show" label="显示窗口" />
|
||||
<q-checkbox v-model="fields.center" label="居中显示" />
|
||||
<q-checkbox v-model="fields.alwaysOnTop" label="总在最前" />
|
||||
<q-checkbox v-model="fields.fullscreen" label="全屏显示" />
|
||||
<q-checkbox v-model="fields.fullscreenable" label="允许全屏" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 窗口尺寸和位置 -->
|
||||
<div class="col-12">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-3">
|
||||
<NumberInput
|
||||
v-model="localConfigs.run.width"
|
||||
label="窗口宽度"
|
||||
@update:model-value="updateConfig('width', $event)"
|
||||
/>
|
||||
<!-- 窗口尺寸和位置 -->
|
||||
<div class="col-12">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-3">
|
||||
<NumberInput v-model="fields.width" label="窗口宽度" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<NumberInput v-model="fields.height" label="窗口高度" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<NumberInput v-model="fields.x" label="X坐标" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<NumberInput v-model="fields.y" label="Y坐标" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<NumberInput
|
||||
v-model="localConfigs.run.height"
|
||||
label="窗口高度"
|
||||
@update:model-value="updateConfig('height', $event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 最大最小尺寸 -->
|
||||
<div class="col-12">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-3">
|
||||
<NumberInput v-model="fields.minWidth" label="最小宽度" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<NumberInput v-model="fields.minHeight" label="最小高度" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<NumberInput v-model="fields.maxWidth" label="最大宽度" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<NumberInput v-model="fields.maxHeight" label="最大高度" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<NumberInput
|
||||
v-model="localConfigs.run.x"
|
||||
label="X坐标"
|
||||
@update:model-value="updateConfig('x', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<NumberInput
|
||||
v-model="localConfigs.run.y"
|
||||
label="Y坐标"
|
||||
@update:model-value="updateConfig('y', $event)"
|
||||
</div>
|
||||
|
||||
<!-- 窗口行为控制 -->
|
||||
<div class="col-12">
|
||||
<div class="row items-center q-gutter-x-md">
|
||||
<q-checkbox v-model="fields.resizable" label="可调整大小" />
|
||||
<q-checkbox v-model="fields.movable" label="可移动" />
|
||||
<q-checkbox v-model="fields.minimizable" label="可最小化" />
|
||||
<q-checkbox v-model="fields.maximizable" label="可最大化" />
|
||||
<q-checkbox
|
||||
v-model="fields.enableLargerThanScreen"
|
||||
label="允许超出屏幕"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 最大最小尺寸 -->
|
||||
<div class="col-12">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-3">
|
||||
<NumberInput
|
||||
v-model="localConfigs.run.minWidth"
|
||||
label="最小宽度"
|
||||
@update:model-value="updateConfig('minWidth', $event)"
|
||||
/>
|
||||
<!-- 透明度控制 -->
|
||||
<div class="col-12">
|
||||
<div class="row items-center q-px-sm" style="height: 36px">
|
||||
<div class="q-mr-md" style="font-size: 12px">透明度</div>
|
||||
<q-slider
|
||||
v-model="fields.opacity"
|
||||
class="col"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:step="0.1"
|
||||
label
|
||||
color="primary"
|
||||
dense
|
||||
>
|
||||
<template v-slot:thumb-label>
|
||||
{{ fields.opacity.toFixed(1) }}
|
||||
</template>
|
||||
</q-slider>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<NumberInput
|
||||
v-model="localConfigs.run.minHeight"
|
||||
label="最小高度"
|
||||
@update:model-value="updateConfig('minHeight', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<NumberInput
|
||||
v-model="localConfigs.run.maxWidth"
|
||||
label="最大宽度"
|
||||
@update:model-value="updateConfig('maxWidth', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<NumberInput
|
||||
v-model="localConfigs.run.maxHeight"
|
||||
label="最大高度"
|
||||
@update:model-value="updateConfig('maxHeight', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 窗口行为控制 -->
|
||||
<div class="col-12">
|
||||
<div class="row items-center q-gutter-x-md">
|
||||
<q-checkbox
|
||||
:model-value="localConfigs.run.resizable"
|
||||
label="可调整大小"
|
||||
@update:model-value="updateConfig('resizable', $event)"
|
||||
/>
|
||||
<q-checkbox
|
||||
:model-value="localConfigs.run.movable"
|
||||
label="可移动"
|
||||
@update:model-value="updateConfig('movable', $event)"
|
||||
/>
|
||||
<q-checkbox
|
||||
:model-value="localConfigs.run.minimizable"
|
||||
label="可最小化"
|
||||
@update:model-value="updateConfig('minimizable', $event)"
|
||||
/>
|
||||
<q-checkbox
|
||||
:model-value="localConfigs.run.maximizable"
|
||||
label="可最大化"
|
||||
@update:model-value="updateConfig('maximizable', $event)"
|
||||
/>
|
||||
<q-checkbox
|
||||
:model-value="localConfigs.run.enableLargerThanScreen"
|
||||
label="允许超出屏幕"
|
||||
@update:model-value="updateConfig('enableLargerThanScreen', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 透明度控制 -->
|
||||
<div class="col-12">
|
||||
<div class="row items-center q-px-sm" style="height: 36px">
|
||||
<div class="q-mr-md" style="font-size: 12px">透明度</div>
|
||||
<q-slider
|
||||
class="col"
|
||||
v-model="localConfigs.run.opacity"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:step="0.1"
|
||||
label
|
||||
color="primary"
|
||||
dense
|
||||
@update:model-value="updateConfig('opacity', $event)"
|
||||
>
|
||||
<template v-slot:thumb-label>
|
||||
{{ localConfigs.run.opacity.toFixed(1) }}
|
||||
</template>
|
||||
</q-slider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, computed } from "vue";
|
||||
import NumberInput from "components/composer/common/NumberInput.vue";
|
||||
|
||||
export default defineComponent({
|
||||
const defaultValues = {
|
||||
show: true,
|
||||
center: false,
|
||||
alwaysOnTop: false,
|
||||
fullscreen: false,
|
||||
fullscreenable: true,
|
||||
width: 800,
|
||||
height: 600,
|
||||
x: undefined,
|
||||
y: undefined,
|
||||
minWidth: 0,
|
||||
minHeight: 0,
|
||||
maxWidth: undefined,
|
||||
maxHeight: undefined,
|
||||
resizable: true,
|
||||
movable: true,
|
||||
minimizable: true,
|
||||
maximizable: true,
|
||||
enableLargerThanScreen: false,
|
||||
opacity: 1,
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "UBrowserRun",
|
||||
components: {
|
||||
NumberInput,
|
||||
},
|
||||
props: {
|
||||
configs: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:configs"],
|
||||
setup(props, { emit }) {
|
||||
const localConfigs = ref(window.lodashM.cloneDeep(props.configs));
|
||||
|
||||
// 监听 configs 变化
|
||||
const watchConfigs = computed(() => props.configs);
|
||||
watchConfigs.value &&
|
||||
(localConfigs.value = window.lodashM.cloneDeep(props.configs));
|
||||
|
||||
// 更新配置
|
||||
const updateConfig = (key, value) => {
|
||||
localConfigs.value.run[key] = value;
|
||||
emit("update:configs", window.lodashM.cloneDeep(localConfigs.value));
|
||||
};
|
||||
|
||||
return {
|
||||
localConfigs,
|
||||
updateConfig,
|
||||
};
|
||||
emits: ["update:model-value"],
|
||||
computed: {
|
||||
fields() {
|
||||
return Object.keys(defaultValues).reduce((acc, field) => {
|
||||
Object.defineProperty(acc, field, {
|
||||
get: () => this.modelValue[field] ?? defaultValues[field],
|
||||
set: (value) => this.updateField(field, value),
|
||||
});
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
},
|
||||
});
|
||||
methods: {
|
||||
updateField(field, value) {
|
||||
this.$emit("update:model-value", {
|
||||
...this.modelValue,
|
||||
[field]: value,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ubrowser-run {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,105 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div
|
||||
v-for="(cookie, index) in modelValue || [{}]"
|
||||
:key="index"
|
||||
class="col-12"
|
||||
>
|
||||
<div class="row items-center q-gutter-x-sm">
|
||||
<div class="col">
|
||||
<VariableInput
|
||||
:model-value="cookie.name"
|
||||
label="名称"
|
||||
icon="label"
|
||||
@update:model-value="
|
||||
(value) => handleUpdate(index, 'name', value)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VariableInput
|
||||
:model-value="cookie.value"
|
||||
label="值"
|
||||
icon="edit"
|
||||
@update:model-value="
|
||||
(value) => handleUpdate(index, 'value', value)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
dense
|
||||
color="negative"
|
||||
icon="remove"
|
||||
@click="removeCookie(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
color="primary"
|
||||
icon="add"
|
||||
label="添加Cookie"
|
||||
@click="addCookie"
|
||||
class="q-mt-xs"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import VariableInput from "components/composer/common/VariableInput.vue";
|
||||
import { newVarInputVal } from "js/composer/varInputValManager";
|
||||
export default defineComponent({
|
||||
name: "UBrowserCookieList",
|
||||
components: {
|
||||
VariableInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => [
|
||||
{
|
||||
name: newVarInputVal("str"),
|
||||
value: newVarInputVal("str"),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
methods: {
|
||||
addCookie() {
|
||||
const newValue = [
|
||||
...this.modelValue,
|
||||
{
|
||||
name: newVarInputVal("str"),
|
||||
value: newVarInputVal("str"),
|
||||
},
|
||||
];
|
||||
this.$emit("update:modelValue", newValue);
|
||||
},
|
||||
removeCookie(index) {
|
||||
const newValue = [...this.modelValue];
|
||||
newValue.splice(index, 1);
|
||||
if (newValue.length === 0) {
|
||||
newValue.push({
|
||||
name: newVarInputVal("str"),
|
||||
value: newVarInputVal("str"),
|
||||
});
|
||||
}
|
||||
this.$emit("update:modelValue", newValue);
|
||||
},
|
||||
handleUpdate(index, field, value) {
|
||||
const newValue = [...this.modelValue];
|
||||
newValue[index] = { ...newValue[index], [field]: value };
|
||||
this.$emit("update:modelValue", newValue);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
@ -1,72 +0,0 @@
|
||||
<template>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col">
|
||||
<VariableInput
|
||||
icon="label"
|
||||
:model-value="modelValue"
|
||||
:label="label"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-select
|
||||
v-model="selectedDevice"
|
||||
:options="deviceOptions"
|
||||
label="常用设备"
|
||||
dense
|
||||
filled
|
||||
emit-value
|
||||
map-options
|
||||
options-dense
|
||||
style="min-width: 150px"
|
||||
@update:model-value="handleDeviceSelect"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="list" />
|
||||
</template>
|
||||
</q-select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import { deviceName } from "js/options/httpOptions";
|
||||
import VariableInput from "components/composer/common/VariableInput.vue";
|
||||
import { newVarInputVal } from "js/composer/varInputValManager";
|
||||
export default defineComponent({
|
||||
name: "UBrowserDeviceName",
|
||||
components: {
|
||||
VariableInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => newVarInputVal("str"),
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
data() {
|
||||
return {
|
||||
selectedDevice: null,
|
||||
deviceOptions: deviceName,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleDeviceSelect(value) {
|
||||
if (value) {
|
||||
this.$emit("update:modelValue", value);
|
||||
this.selectedDevice = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
@ -1,79 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div
|
||||
v-for="(file, index) in modelValue || []"
|
||||
:key="index"
|
||||
class="col-12"
|
||||
>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col">
|
||||
<VariableInput
|
||||
:model-value="modelValue[index]"
|
||||
label="文件路径"
|
||||
:command="{ icon: 'folder' }"
|
||||
@update:model-value="(value) => handleUpdate(index, value)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
dense
|
||||
color="negative"
|
||||
icon="remove"
|
||||
@click="removeFile(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
color="primary"
|
||||
icon="add"
|
||||
label="添加文件"
|
||||
@click="addFile"
|
||||
class="q-mt-xs"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import VariableInput from "components/composer/common/VariableInput.vue";
|
||||
import { newVarInputVal } from "js/composer/varInputValManager";
|
||||
export default defineComponent({
|
||||
name: "UBrowserFileList",
|
||||
components: {
|
||||
VariableInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
methods: {
|
||||
addFile() {
|
||||
const newValue = [
|
||||
...(this.modelValue || []),
|
||||
newVarInputVal("str"),
|
||||
];
|
||||
this.$emit("update:modelValue", newValue);
|
||||
},
|
||||
removeFile(index) {
|
||||
const newValue = [...this.modelValue];
|
||||
newValue.splice(index, 1);
|
||||
this.$emit("update:modelValue", newValue);
|
||||
},
|
||||
handleUpdate(index, value) {
|
||||
const newValue = [...this.modelValue];
|
||||
newValue[index] = value;
|
||||
this.$emit("update:modelValue", newValue);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
@ -1,237 +0,0 @@
|
||||
<template>
|
||||
<div class="row q-col-gutter-sm ubrowser-function-input">
|
||||
<div class="col-12">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-3">
|
||||
<q-select
|
||||
v-model="localParams"
|
||||
use-input
|
||||
use-chips
|
||||
multiple
|
||||
dense
|
||||
borderless
|
||||
hide-dropdown-icon
|
||||
options-dense
|
||||
input-debounce="0"
|
||||
new-value-mode="add-unique"
|
||||
label="参数"
|
||||
@update:model-value="updateParams"
|
||||
@input-value="handleInput"
|
||||
@blur="handleBlur"
|
||||
ref="paramSelect"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<div class="text-primary func-symbol">(</div>
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<div class="text-primary func-symbol">)</div>
|
||||
</template>
|
||||
</q-select>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<q-input
|
||||
v-model="localFunction"
|
||||
:label="label"
|
||||
type="textarea"
|
||||
dense
|
||||
borderless
|
||||
style="font-family: Consolas, Monaco, 'Courier New'"
|
||||
autogrow
|
||||
@update:model-value="updateFunction"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<div class="text-primary func-symbol">=> {</div>
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<div class="text-primary func-symbol">}</div>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="localParams.length">
|
||||
<div v-for="param in localParams" :key="param" class="col-12">
|
||||
<div class="row q-col-gutter-sm items-center">
|
||||
<div class="col-3">
|
||||
<q-chip
|
||||
dense
|
||||
color="primary"
|
||||
text-color="white"
|
||||
removable
|
||||
@remove="removeParam(param)"
|
||||
>
|
||||
{{ param }}
|
||||
</q-chip>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<q-input
|
||||
v-model="paramValues[param]"
|
||||
:label="`传递给参数 ${param} 的值`"
|
||||
dense
|
||||
filled
|
||||
@update:model-value="updateParamValue(param, $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UBrowserFunctionInput",
|
||||
props: {
|
||||
function: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
args: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: "函数内容",
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: "code",
|
||||
},
|
||||
},
|
||||
emits: ["update:function", "update:args"],
|
||||
data() {
|
||||
return {
|
||||
localFunction: "",
|
||||
localParams: [],
|
||||
paramValues: {},
|
||||
newParamName: "",
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 初始化本地数据
|
||||
this.localFunction = this.function;
|
||||
this.localParams = this.args?.map((arg) => arg.name) || [];
|
||||
this.paramValues = Object.fromEntries(
|
||||
this.args?.map((arg) => [arg.name, arg.value]) || []
|
||||
);
|
||||
},
|
||||
methods: {
|
||||
updateFunction(value) {
|
||||
this.localFunction = value;
|
||||
this.emitUpdate();
|
||||
},
|
||||
updateParams(value) {
|
||||
this.localParams = value;
|
||||
this.emitUpdate();
|
||||
},
|
||||
removeParam(param) {
|
||||
const index = this.localParams.indexOf(param);
|
||||
if (index > -1) {
|
||||
this.localParams.splice(index, 1);
|
||||
delete this.paramValues[param];
|
||||
this.emitUpdate();
|
||||
}
|
||||
},
|
||||
updateParamValue(param, value) {
|
||||
this.paramValues[param] = value;
|
||||
this.emitUpdate();
|
||||
},
|
||||
emitUpdate() {
|
||||
this.$emit("update:function", this.localFunction);
|
||||
this.$emit(
|
||||
"update:args",
|
||||
this.localParams.map((name) => ({
|
||||
name,
|
||||
value: this.paramValues[name] || "",
|
||||
}))
|
||||
);
|
||||
},
|
||||
handleInput(val) {
|
||||
if (!val) return;
|
||||
this.newParamName = val;
|
||||
|
||||
if (val.includes(",") || val.includes(" ")) {
|
||||
const params = val
|
||||
.split(/[,\s]+/)
|
||||
.map((p) => p.trim())
|
||||
.filter((p) => p);
|
||||
params.forEach((param) => {
|
||||
if (param && !this.localParams.includes(param)) {
|
||||
this.localParams = [...this.localParams, param];
|
||||
this.paramValues[param] = "";
|
||||
}
|
||||
});
|
||||
this.newParamName = "";
|
||||
this.emitUpdate();
|
||||
this.$refs.paramSelect.updateInputValue("");
|
||||
}
|
||||
},
|
||||
handleBlur() {
|
||||
if (this.newParamName && !this.localParams.includes(this.newParamName)) {
|
||||
this.localParams = [...this.localParams, this.newParamName];
|
||||
this.paramValues[this.newParamName] = "";
|
||||
this.newParamName = "";
|
||||
this.emitUpdate();
|
||||
this.$refs.paramSelect.updateInputValue("");
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
function: {
|
||||
handler(newValue) {
|
||||
this.localFunction = newValue;
|
||||
},
|
||||
},
|
||||
args: {
|
||||
deep: true,
|
||||
handler(newValue) {
|
||||
this.localParams = newValue?.map((arg) => arg.name) || [];
|
||||
this.paramValues = Object.fromEntries(
|
||||
newValue?.map((arg) => [arg.name, arg.value]) || []
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ubrowser-function-input :deep(.q-field__control) .text-primary.func-symbol {
|
||||
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>
|
@ -1,87 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="text-caption q-mb-sm">{{ label }}</div>
|
||||
<div
|
||||
v-for="(param, index) in modelValue || []"
|
||||
:key="index"
|
||||
class="row q-col-gutter-sm q-mb-sm"
|
||||
>
|
||||
<div class="col-5">
|
||||
<VariableInput
|
||||
:model-value="param.name"
|
||||
label="参数名"
|
||||
icon="label"
|
||||
@update:model-value="(value) => handleUpdate(index, 'name', value)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<VariableInput
|
||||
:model-value="param.value"
|
||||
label="传递给参数的值"
|
||||
icon="edit"
|
||||
@update:model-value="(value) => handleUpdate(index, 'value', value)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
dense
|
||||
color="negative"
|
||||
icon="remove"
|
||||
@click="removeParam(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
color="primary"
|
||||
icon="add"
|
||||
label="添加参数"
|
||||
@click="addParam"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import VariableInput from "components/composer/common/VariableInput.vue";
|
||||
import { newVarInputVal } from "js/composer/varInputValManager";
|
||||
export default defineComponent({
|
||||
name: "UBrowserNamedParamList",
|
||||
components: {
|
||||
VariableInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => [newVarInputVal("str"), newVarInputVal("str")],
|
||||
},
|
||||
label: String,
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
methods: {
|
||||
addParam() {
|
||||
const newValue = [
|
||||
...(this.modelValue || []),
|
||||
{
|
||||
name: newVarInputVal("str"),
|
||||
value: newVarInputVal("str"),
|
||||
},
|
||||
];
|
||||
this.$emit("update:modelValue", newValue);
|
||||
},
|
||||
removeParam(index) {
|
||||
const newValue = [...this.modelValue];
|
||||
newValue.splice(index, 1);
|
||||
this.$emit("update:modelValue", newValue);
|
||||
},
|
||||
handleUpdate(index, field, value) {
|
||||
const newValue = [...this.modelValue];
|
||||
newValue[index] = { ...newValue[index], [field]: value };
|
||||
this.$emit("update:modelValue", newValue);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
@ -1,266 +0,0 @@
|
||||
<template>
|
||||
<div class="row q-col-gutter-sm items-center">
|
||||
<template v-for="field in fields" :key="field.key">
|
||||
<div
|
||||
v-if="!field.showWhen || fieldValue[field.showWhen] === field.showValue"
|
||||
:class="['col', field.width ? `col-${field.width}` : 'col-12']"
|
||||
>
|
||||
<!-- 复选框组 -->
|
||||
<template v-if="field.type === 'checkbox-group'">
|
||||
<q-option-group
|
||||
:model-value="
|
||||
Array.isArray(fieldValue[field.key]) ? fieldValue[field.key] : []
|
||||
"
|
||||
:options="field.options"
|
||||
type="checkbox"
|
||||
class="row items-center"
|
||||
inline
|
||||
dense
|
||||
@update:model-value="updateValue(field.key, $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 是/否选择 -->
|
||||
<template v-else-if="field.type === 'boolean-toggle'">
|
||||
<div class="row items-center no-wrap">
|
||||
<q-badge class="q-pa-xs">{{ field.label }}</q-badge>
|
||||
<q-btn-toggle
|
||||
:model-value="fieldValue[field.key] ? 'true' : 'false'"
|
||||
:options="[
|
||||
{ label: '是', value: 'true' },
|
||||
{ label: '否', value: 'false' },
|
||||
]"
|
||||
dense
|
||||
flat
|
||||
no-caps
|
||||
spread
|
||||
class="button-group"
|
||||
@update:model-value="updateValue(field.key, $event === 'true')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 基本输入类型的处理 -->
|
||||
<template v-if="field.type === 'varInput'">
|
||||
<!-- 设备名称特殊处理 -->
|
||||
<template v-if="field.key === 'deviceName'">
|
||||
<UBrowserDeviceName
|
||||
v-model="fieldValue[field.key]"
|
||||
:label="field.label"
|
||||
:icon="field.icon"
|
||||
@update:model-value="updateValue(field.key, $event)"
|
||||
/>
|
||||
</template>
|
||||
<!-- 普通输入框 -->
|
||||
<template v-else>
|
||||
<VariableInput
|
||||
:model-value="fieldValue[field.key]"
|
||||
:label="field.label"
|
||||
:icon="field.icon"
|
||||
@update:model-value="updateValue(field.key, $event)"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 数字输入框 -->
|
||||
<template v-else-if="field.type === 'numInput'">
|
||||
<NumberInput
|
||||
v-model="fieldValue[field.key]"
|
||||
:label="field.label"
|
||||
:icon="field.icon"
|
||||
@update:model-value="updateValue(field.key, $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 选择框 -->
|
||||
<template v-else-if="field.type === 'select'">
|
||||
<q-select
|
||||
:model-value="fieldValue[field.key]"
|
||||
:label="field.label"
|
||||
:options="field.options"
|
||||
dense
|
||||
filled
|
||||
emit-value
|
||||
map-options
|
||||
@update:model-value="updateValue(field.key, $event)"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon :name="field.icon" />
|
||||
</template>
|
||||
</q-select>
|
||||
</template>
|
||||
|
||||
<!-- Cookie列表 -->
|
||||
<template v-else-if="field.type === 'cookie-list'">
|
||||
<UBrowserCookieList
|
||||
v-model="fieldValue[field.key]"
|
||||
@update:model-value="updateValue(field.key, $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 命名参数列表 -->
|
||||
<template v-else-if="field.type === 'named-param-list'">
|
||||
<UBrowserNamedParamList
|
||||
v-model="fieldValue[field.key]"
|
||||
:label="field.label"
|
||||
@update:model-value="updateValue(field.key, $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<template v-else-if="field.type === 'file-list'">
|
||||
<UBrowserFileList
|
||||
v-model="fieldValue[field.key]"
|
||||
@update:model-value="updateValue(field.key, $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 按钮组 -->
|
||||
<template v-else-if="field.type === 'button-toggle'">
|
||||
<div class="row items-center no-wrap">
|
||||
<q-badge class="q-pa-xs">{{ field.label }}</q-badge>
|
||||
<q-btn-toggle
|
||||
:model-value="fieldValue[field.key]"
|
||||
:options="field.options"
|
||||
dense
|
||||
flat
|
||||
no-caps
|
||||
spread
|
||||
class="button-group"
|
||||
@update:model-value="updateValue(field.key, $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 设备尺寸 -->
|
||||
<template v-else-if="field.type === 'device-size'">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<VariableInput
|
||||
v-model.number="fieldValue.size[key]"
|
||||
class="col-6"
|
||||
v-for="key in ['width', 'height']"
|
||||
:key="key"
|
||||
label="宽度"
|
||||
icon="width"
|
||||
@update:model-value="
|
||||
updateValue(field.key, {
|
||||
...fieldValue.size,
|
||||
[key]: $event,
|
||||
})
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 带参数的函数输入 -->
|
||||
<template v-else-if="field.type === 'function-with-params'">
|
||||
<UBrowserFunctionInput
|
||||
v-model:function="fieldValue.function"
|
||||
v-model:args="fieldValue.args"
|
||||
:label="field.label"
|
||||
:icon="field.icon"
|
||||
@update:function="(value) => updateValue('function', value)"
|
||||
@update:args="(value) => updateValue('args', value)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, computed, ref, onMounted } from "vue";
|
||||
import { get, set } from "lodash";
|
||||
import UBrowserFunctionInput from "./UBrowserFunctionInput.vue";
|
||||
import UBrowserFileList from "./UBrowserFileList.vue";
|
||||
import UBrowserCookieList from "./UBrowserCookieList.vue";
|
||||
import UBrowserNamedParamList from "./UBrowserNamedParamList.vue";
|
||||
import UBrowserDeviceName from "./UBrowserDeviceName.vue";
|
||||
import VariableInput from "components/composer/common/VariableInput.vue";
|
||||
import NumberInput from "components/composer/common/NumberInput.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UBrowserOperation",
|
||||
components: {
|
||||
UBrowserFunctionInput,
|
||||
UBrowserFileList,
|
||||
UBrowserCookieList,
|
||||
UBrowserNamedParamList,
|
||||
UBrowserDeviceName,
|
||||
VariableInput,
|
||||
NumberInput,
|
||||
},
|
||||
props: {
|
||||
configs: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
action: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
fields: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:configs"],
|
||||
setup(props, { emit }) {
|
||||
const fieldValue = ref({});
|
||||
|
||||
// 初始化字段值
|
||||
onMounted(() => {
|
||||
props.fields.forEach((field) => {
|
||||
const value = get(props.configs[props.action], field.key);
|
||||
// 根据字段类型设置适当的默认值
|
||||
if (field.type === "function-with-params") {
|
||||
fieldValue.value.function = value?.function || "";
|
||||
fieldValue.value.args = value?.args || [];
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultValue =
|
||||
field.type === "checkbox-group"
|
||||
? []
|
||||
: field.type === "checkbox"
|
||||
? field.defaultValue || false
|
||||
: field.defaultValue;
|
||||
|
||||
fieldValue.value[field.key] = Array.isArray(value)
|
||||
? value
|
||||
: defaultValue;
|
||||
});
|
||||
});
|
||||
|
||||
// 更新值的方法
|
||||
const updateValue = (key, value) => {
|
||||
fieldValue.value[key] = value;
|
||||
|
||||
const newConfigs = { ...props.configs };
|
||||
if (!newConfigs[props.action]) {
|
||||
newConfigs[props.action] = {};
|
||||
}
|
||||
|
||||
set(newConfigs[props.action], key, value);
|
||||
emit("update:configs", newConfigs);
|
||||
};
|
||||
|
||||
return {
|
||||
fieldValue,
|
||||
updateValue,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.button-group {
|
||||
flex: 1;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.button-group :deep(.q-btn) {
|
||||
min-height: 24px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
@ -375,7 +375,8 @@ export const browserCommands = {
|
||||
placeholder: "输入JavaScript代码,使用return返回结果",
|
||||
},
|
||||
{
|
||||
topLabel: "要传递的参数",
|
||||
label: "要传递的参数",
|
||||
isCollapse: false,
|
||||
component: "DictEditor",
|
||||
icon: "data_array",
|
||||
width: 12,
|
||||
|
@ -1,361 +1,102 @@
|
||||
/**
|
||||
* 生成 UBrowser 代码
|
||||
* @param {Object} configs UBrowser 配置对象
|
||||
* @param {Array} selectedActions 已选择的操作列表
|
||||
* @param {Object} argvs UBrowser 配置对象
|
||||
* @returns {string} 生成的代码
|
||||
*/
|
||||
import { stringifyVarInputVal } from "./varInputValManager";
|
||||
import { stringifyArgv } from "./formatString";
|
||||
|
||||
// 生成 goto 参数字符串
|
||||
function generateGotoArgs(goto) {
|
||||
const args = [];
|
||||
|
||||
// URL
|
||||
const urlStr = stringifyArgv(goto.url);
|
||||
args.push(urlStr);
|
||||
|
||||
// Headers
|
||||
if (goto.headers?.Referer?.value || goto.headers?.userAgent?.value) {
|
||||
const headers = {};
|
||||
if (goto.headers.Referer?.value) {
|
||||
headers.Referer = goto.headers.Referer;
|
||||
}
|
||||
if (goto.headers.userAgent?.value) {
|
||||
headers.userAgent = goto.headers.userAgent;
|
||||
}
|
||||
console.log("Headers:", JSON.stringify(headers, null, 2));
|
||||
args.push(stringifyArgv(headers, true));
|
||||
}
|
||||
|
||||
// Timeout
|
||||
if (goto.timeout !== 60000) {
|
||||
args.push(goto.timeout);
|
||||
}
|
||||
|
||||
return args.join(", ");
|
||||
}
|
||||
|
||||
// 生成 run 参数字符串
|
||||
function generateRunArgs(run) {
|
||||
const options = {};
|
||||
const defaultValues = {
|
||||
show: true,
|
||||
center: true,
|
||||
alwaysOnTop: false,
|
||||
fullscreen: false,
|
||||
fullscreenable: true,
|
||||
resizable: true,
|
||||
movable: true,
|
||||
minimizable: true,
|
||||
maximizable: true,
|
||||
enableLargerThanScreen: false,
|
||||
opacity: 1,
|
||||
};
|
||||
|
||||
// 窗口显示控制
|
||||
if (run.show !== undefined && run.show !== defaultValues.show)
|
||||
options.show = run.show;
|
||||
if (run.center !== undefined && run.center !== defaultValues.center)
|
||||
options.center = run.center;
|
||||
if (
|
||||
run.alwaysOnTop !== undefined &&
|
||||
run.alwaysOnTop !== defaultValues.alwaysOnTop
|
||||
)
|
||||
options.alwaysOnTop = run.alwaysOnTop;
|
||||
if (
|
||||
run.fullscreen !== undefined &&
|
||||
run.fullscreen !== defaultValues.fullscreen
|
||||
)
|
||||
options.fullscreen = run.fullscreen;
|
||||
if (
|
||||
run.fullscreenable !== undefined &&
|
||||
run.fullscreenable !== defaultValues.fullscreenable
|
||||
)
|
||||
options.fullscreenable = run.fullscreenable;
|
||||
|
||||
// 窗口尺寸和位置 - 只有设置了值才添加
|
||||
if (run.width !== undefined && run.width > 0) options.width = run.width;
|
||||
if (run.height !== undefined && run.height > 0) options.height = run.height;
|
||||
if (run.x !== undefined && run.x !== 0) options.x = run.x;
|
||||
if (run.y !== undefined && run.y !== 0) options.y = run.y;
|
||||
|
||||
// 最大最小尺寸 - 只有设置了值才添加
|
||||
if (run.minWidth !== undefined && run.minWidth > 0)
|
||||
options.minWidth = run.minWidth;
|
||||
if (run.minHeight !== undefined && run.minHeight > 0)
|
||||
options.minHeight = run.minHeight;
|
||||
if (run.maxWidth !== undefined && run.maxWidth > 0)
|
||||
options.maxWidth = run.maxWidth;
|
||||
if (run.maxHeight !== undefined && run.maxHeight > 0)
|
||||
options.maxHeight = run.maxHeight;
|
||||
|
||||
// 窗口行为控制
|
||||
if (run.resizable !== undefined && run.resizable !== defaultValues.resizable)
|
||||
options.resizable = run.resizable;
|
||||
if (run.movable !== undefined && run.movable !== defaultValues.movable)
|
||||
options.movable = run.movable;
|
||||
if (
|
||||
run.minimizable !== undefined &&
|
||||
run.minimizable !== defaultValues.minimizable
|
||||
)
|
||||
options.minimizable = run.minimizable;
|
||||
if (
|
||||
run.maximizable !== undefined &&
|
||||
run.maximizable !== defaultValues.maximizable
|
||||
)
|
||||
options.maximizable = run.maximizable;
|
||||
if (
|
||||
run.enableLargerThanScreen !== undefined &&
|
||||
run.enableLargerThanScreen !== defaultValues.enableLargerThanScreen
|
||||
)
|
||||
options.enableLargerThanScreen = run.enableLargerThanScreen;
|
||||
|
||||
// 透明度 - 只有不是1时才添加
|
||||
if (run.opacity !== undefined && run.opacity !== defaultValues.opacity)
|
||||
options.opacity = run.opacity;
|
||||
|
||||
// 其他参数 - 只有设置了值才添加
|
||||
if (run.headless) options.headless = run.headless;
|
||||
if (run.devtools) options.devtools = run.devtools;
|
||||
if (run.timeout && run.timeout !== 60000) options.timeout = run.timeout;
|
||||
if (run.proxy) options.proxy = run.proxy;
|
||||
if (run.viewport) options.viewport = run.viewport;
|
||||
|
||||
return Object.keys(options).length ? stringifyArgv(options) : "";
|
||||
}
|
||||
|
||||
// 生成操作参数字符串
|
||||
function generateActionArgs(action, config) {
|
||||
console.log(
|
||||
"Generating args for action:",
|
||||
action,
|
||||
"config:",
|
||||
JSON.stringify(config, null, 2)
|
||||
);
|
||||
if (!config) return "";
|
||||
|
||||
let result;
|
||||
switch (action) {
|
||||
case "wait":
|
||||
result = generateWaitArgs(config);
|
||||
break;
|
||||
case "click":
|
||||
case "mousedown":
|
||||
case "mouseup":
|
||||
case "focus":
|
||||
result = stringifyArgv(config.selector);
|
||||
break;
|
||||
case "css":
|
||||
case "paste":
|
||||
result = stringifyArgv(config.value);
|
||||
break;
|
||||
case "press":
|
||||
result = generatePressArgs(config);
|
||||
break;
|
||||
case "screenshot":
|
||||
result = generateScreenshotArgs(config);
|
||||
break;
|
||||
case "pdf":
|
||||
result = generatePdfArgs(config);
|
||||
break;
|
||||
case "device":
|
||||
result = generateDeviceArgs(config);
|
||||
break;
|
||||
case "cookies":
|
||||
case "removeCookies":
|
||||
result = stringifyArgv(config.name);
|
||||
break;
|
||||
case "setCookies":
|
||||
result = generateSetCookiesArgs(config);
|
||||
break;
|
||||
case "evaluate":
|
||||
result = generateEvaluateArgs(config);
|
||||
break;
|
||||
case "when":
|
||||
result = generateWhenArgs(config);
|
||||
break;
|
||||
case "file":
|
||||
result = generateFileArgs(config);
|
||||
break;
|
||||
case "value":
|
||||
result = generateValueArgs(config);
|
||||
break;
|
||||
case "check":
|
||||
result = generateCheckArgs(config);
|
||||
break;
|
||||
case "scroll":
|
||||
result = generateScrollArgs(config);
|
||||
break;
|
||||
case "download":
|
||||
result = generateDownloadArgs(config);
|
||||
break;
|
||||
case "devTools":
|
||||
result = stringifyArgv(config.mode);
|
||||
break;
|
||||
default:
|
||||
result = "";
|
||||
}
|
||||
console.log(
|
||||
"Generated args for action:",
|
||||
action,
|
||||
"result:",
|
||||
JSON.stringify(result)
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 生成 wait 参数字符串
|
||||
function generateWaitArgs(config) {
|
||||
switch (config.type) {
|
||||
case "selector":
|
||||
return stringifyArgv(config.selector);
|
||||
case "function":
|
||||
return config.function;
|
||||
case "time":
|
||||
return config.time;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// 生成 press 参数字符串
|
||||
function generatePressArgs(config) {
|
||||
const args = [stringifyArgv(config.key)];
|
||||
if (config.modifiers?.length) {
|
||||
args.push(JSON.stringify(config.modifiers));
|
||||
}
|
||||
return args.join(", ");
|
||||
}
|
||||
|
||||
// 生成 screenshot 参数字符串
|
||||
function generateScreenshotArgs(config) {
|
||||
const args = [];
|
||||
if (config.rect) {
|
||||
args.push(stringifyArgv(config.rect));
|
||||
} else if (config.selector) {
|
||||
args.push(stringifyArgv(config.selector));
|
||||
}
|
||||
if (config.savePath) {
|
||||
args.push(stringifyArgv(config.savePath));
|
||||
}
|
||||
return args.join(", ");
|
||||
}
|
||||
|
||||
// 生成 pdf 参数字符串
|
||||
function generatePdfArgs(config) {
|
||||
const args = [];
|
||||
if (config.savePath) {
|
||||
args.push(stringifyArgv(config.savePath));
|
||||
}
|
||||
if (config.options) {
|
||||
args.push(stringifyArgv(config.options));
|
||||
}
|
||||
return args.join(", ");
|
||||
}
|
||||
|
||||
// 生成 device 参数字符串
|
||||
function generateDeviceArgs(config) {
|
||||
if (config.type === "preset") {
|
||||
return stringifyArgv(config.deviceName);
|
||||
} else {
|
||||
const options = {};
|
||||
if (config.size) options.size = config.size;
|
||||
if (config.useragent) options.userAgent = config.useragent;
|
||||
return stringifyArgv(options);
|
||||
}
|
||||
}
|
||||
|
||||
// 生成 setCookies 参数字符串
|
||||
function generateSetCookiesArgs(config) {
|
||||
if (!config.items?.length) return "[]";
|
||||
return stringifyArgv(config.items);
|
||||
}
|
||||
|
||||
// 生成 evaluate 参数字符串
|
||||
function generateEvaluateArgs(config) {
|
||||
const args = [config.function];
|
||||
if (config.args?.length) {
|
||||
args.push(...config.args.map(stringifyArgv));
|
||||
}
|
||||
return args.join(", ");
|
||||
}
|
||||
|
||||
// 生成 when 参数字符串
|
||||
function generateWhenArgs(config) {
|
||||
if (config.type === "function") {
|
||||
return config.function;
|
||||
} else {
|
||||
return stringifyArgv(config.selector);
|
||||
}
|
||||
}
|
||||
|
||||
// 生成 file 参数字符串
|
||||
function generateFileArgs(config) {
|
||||
const args = [stringifyArgv(config.selector)];
|
||||
if (config.files) {
|
||||
args.push(stringifyArgv(config.files));
|
||||
}
|
||||
return args.join(", ");
|
||||
}
|
||||
|
||||
// 生成 value 参数字符串
|
||||
function generateValueArgs(config) {
|
||||
return `${stringifyArgv(config.selector)}, ${stringifyArgv(
|
||||
config.value
|
||||
)}`;
|
||||
}
|
||||
|
||||
// 生成 check 参数字符串
|
||||
function generateCheckArgs(config) {
|
||||
return `${stringifyArgv(config.selector)}, ${config.checked}`;
|
||||
}
|
||||
|
||||
// 生成 scroll 参数字符串
|
||||
function generateScrollArgs(config) {
|
||||
if (config.type === "element") {
|
||||
return stringifyArgv(config.selector);
|
||||
} else {
|
||||
if (config.x !== undefined) {
|
||||
return `${config.x}, ${config.y}`;
|
||||
} else {
|
||||
return String(config.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 生成 download 参数字符串
|
||||
function generateDownloadArgs(config) {
|
||||
const args = [stringifyArgv(config.url)];
|
||||
if (config.savePath) {
|
||||
args.push(stringifyArgv(config.savePath));
|
||||
}
|
||||
return args.join(", ");
|
||||
}
|
||||
// ubrowser 默认运行配置
|
||||
const defaultRunConfigs = {
|
||||
show: true,
|
||||
width: 800,
|
||||
height: 600,
|
||||
center: true,
|
||||
minWidth: 0,
|
||||
minHeight: 0,
|
||||
resizable: true,
|
||||
movable: true,
|
||||
minimizable: true,
|
||||
maximizable: true,
|
||||
alwaysOnTop: false,
|
||||
fullscreen: false,
|
||||
fullscreenable: true,
|
||||
enableLargerThanScreen: false,
|
||||
opacity: 1,
|
||||
};
|
||||
|
||||
// 生成完整的 ubrowser 代码
|
||||
export function generateUBrowserCode(configs, selectedActions) {
|
||||
const lines = [];
|
||||
const indent = " ";
|
||||
export function generateUBrowserCode(argvs) {
|
||||
const lines = ["utools.ubrowser"];
|
||||
|
||||
// 添加 goto 参数
|
||||
if (configs.goto) {
|
||||
const gotoArgs = generateGotoArgs(configs.goto);
|
||||
lines.push(`${indent}goto(${gotoArgs}),`);
|
||||
// 首先添加 goto 操作
|
||||
if (argvs.goto) {
|
||||
const args = [];
|
||||
// url
|
||||
if (argvs.goto.url) {
|
||||
args.push(stringifyVarInputVal(argvs.goto.url));
|
||||
}
|
||||
|
||||
// headers
|
||||
const headers = {};
|
||||
// 处理标准headers
|
||||
Object.entries(argvs.goto.headers || {}).forEach(([key, value]) => {
|
||||
if (value?.value) {
|
||||
headers[key] = value.value;
|
||||
}
|
||||
});
|
||||
// 处理其他headers
|
||||
Object.entries(argvs.goto.otherHeaders || {}).forEach(([key, value]) => {
|
||||
if (value?.value) {
|
||||
headers[key] = value.value;
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(headers).length > 0) {
|
||||
args.push(stringifyArgv(headers));
|
||||
}
|
||||
|
||||
// timeout
|
||||
if (argvs.goto.timeout) {
|
||||
if (args.length === 1) {
|
||||
args.push("undefined");
|
||||
}
|
||||
args.push(argvs.goto.timeout);
|
||||
}
|
||||
|
||||
lines[0] += `.goto(${args.join(", ")})`;
|
||||
}
|
||||
|
||||
// 添加选中的操作
|
||||
if (selectedActions?.length) {
|
||||
selectedActions.forEach((action) => {
|
||||
const args = generateActionArgs(action.value, configs[action.value]);
|
||||
lines.push(`${indent}${action.value}(${args}),`);
|
||||
// 添加其他操作
|
||||
if (argvs.operations?.length) {
|
||||
argvs.operations.forEach(({ value, args }) => {
|
||||
if (!args?.length) return;
|
||||
|
||||
const stringifiedArgs = args
|
||||
.map((arg) => stringifyArgv(arg))
|
||||
.filter(Boolean);
|
||||
|
||||
lines.push(` .${value}(${stringifiedArgs.join(", ")})`);
|
||||
});
|
||||
}
|
||||
|
||||
// 添加 run 参数
|
||||
const runArgs = generateRunArgs(configs.run || {});
|
||||
const runLine = runArgs ? `${indent}run(${runArgs})` : `${indent}run()`;
|
||||
lines.push(runLine);
|
||||
// 最后添加 run 配置(只包含非默认值)
|
||||
if (argvs.run) {
|
||||
const runOptions = {};
|
||||
Object.entries(argvs.run).forEach(([key, value]) => {
|
||||
if (value !== defaultRunConfigs[key]) {
|
||||
runOptions[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
// 生成最终代码
|
||||
return `utools.ubrowser\n${lines.join("\n")}`;
|
||||
if (Object.keys(runOptions).length > 0) {
|
||||
lines.push(
|
||||
` .run(${JSON.stringify(runOptions, null, 2).replace(/\n/g, "\n ")})`
|
||||
);
|
||||
} else {
|
||||
lines.push(" .run()");
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user