mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2026-03-03 03:20:36 +08:00
组件位置调整
This commit is contained in:
129
src/components/composer/ubrowser/UBrowserBasic.vue
Normal file
129
src/components/composer/ubrowser/UBrowserBasic.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<!-- 基础配置 -->
|
||||
<div class="col-12">
|
||||
<VariableInput
|
||||
v-model="localConfigs.goto.url"
|
||||
label="网址"
|
||||
:command="{ 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"
|
||||
:command="{ 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"
|
||||
:command="{ icon: 'devices' }"
|
||||
@update:model-value="updateConfigs"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-select
|
||||
v-model="selectedUA"
|
||||
: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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 超时配置 -->
|
||||
<div class="col-12">
|
||||
<VariableInput
|
||||
v-model="localConfigs.goto.timeout"
|
||||
:command="{ icon: 'timer', inputType: 'number' }"
|
||||
label="超时时间(ms)"
|
||||
@update:model-value="updateConfigs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import { userAgent } from "js/options/httpHeaders";
|
||||
import VariableInput from "components/composer/ui/VariableInput.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UBrowserBasic",
|
||||
components: {
|
||||
VariableInput,
|
||||
},
|
||||
props: {
|
||||
configs: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedUA: null,
|
||||
localConfigs: {
|
||||
useragent: {
|
||||
preset: null,
|
||||
value: "",
|
||||
},
|
||||
goto: {
|
||||
url: "",
|
||||
headers: {
|
||||
Referer: "",
|
||||
userAgent: "",
|
||||
},
|
||||
timeout: 60000,
|
||||
},
|
||||
},
|
||||
userAgentOptions: userAgent,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 初始化本地配置
|
||||
this.localConfigs = window.lodashM.cloneDeep(this.configs);
|
||||
},
|
||||
methods: {
|
||||
updateConfigs() {
|
||||
this.$emit("update:configs", window.lodashM.cloneDeep(this.localConfigs));
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
configs: {
|
||||
deep: true,
|
||||
handler(newConfigs) {
|
||||
this.localConfigs = window.lodashM.cloneDeep(newConfigs);
|
||||
},
|
||||
},
|
||||
selectedUA(value) {
|
||||
if (value) {
|
||||
this.localConfigs.goto.headers.userAgent = value;
|
||||
this.updateConfigs();
|
||||
this.selectedUA = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
143
src/components/composer/ubrowser/UBrowserEditor.vue
Normal file
143
src/components/composer/ubrowser/UBrowserEditor.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div class="ubrowser-editor">
|
||||
<q-stepper
|
||||
v-model="step"
|
||||
vertical
|
||||
color="primary"
|
||||
header-nav
|
||||
animated
|
||||
alternative-labels
|
||||
flat
|
||||
class="ubrowser-stepper"
|
||||
>
|
||||
<!-- 基础参数步骤 -->
|
||||
<q-step :name="1" title="基础参数" icon="settings" :done="step > 1">
|
||||
<UBrowserBasic :configs="configs" @update:configs="updateConfigs" />
|
||||
</q-step>
|
||||
|
||||
<!-- 浏览器操作步骤 -->
|
||||
<q-step :name="2" title="浏览器操作" icon="touch_app" :done="step > 2">
|
||||
<UBrowserOperations
|
||||
:configs="configs"
|
||||
@update:configs="updateConfigs"
|
||||
v-model:selected-actions="selectedActions"
|
||||
@remove-action="removeAction"
|
||||
/>
|
||||
</q-step>
|
||||
|
||||
<!-- 运行参数步骤 -->
|
||||
<q-step
|
||||
:name="3"
|
||||
title="运行参数"
|
||||
icon="settings_applications"
|
||||
class="q-pb-md"
|
||||
>
|
||||
<UBrowserRun :configs="configs" @update:configs="updateConfigs" />
|
||||
</q-step>
|
||||
</q-stepper>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ubrowser-editor {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ubrowser-stepper {
|
||||
box-shadow: none;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.body--dark .ubrowser-stepper {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.ubrowser-stepper :deep(.q-stepper__header) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ubrowser-stepper :deep(.q-stepper__step-inner) {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import UBrowserBasic from "./UBrowserBasic.vue";
|
||||
import UBrowserOperations from "./UBrowserOperations.vue";
|
||||
import UBrowserRun from "./UBrowserRun.vue";
|
||||
import { defaultUBrowserConfigs } from "js/composer/ubrowserConfig";
|
||||
import { generateUBrowserCode } from "js/composer/generateUBrowserCode";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UBrowserEditor",
|
||||
components: {
|
||||
UBrowserBasic,
|
||||
UBrowserOperations,
|
||||
UBrowserRun,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
data() {
|
||||
return {
|
||||
step: 1,
|
||||
selectedActions: [],
|
||||
configs: window.lodashM.cloneDeep(defaultUBrowserConfigs),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
updateConfigs(newConfigs) {
|
||||
this.configs = newConfigs;
|
||||
},
|
||||
removeAction(action) {
|
||||
const newActions = this.selectedActions.filter((a) => a.id !== action.id);
|
||||
this.selectedActions = newActions;
|
||||
const newConfigs = { ...this.configs };
|
||||
delete newConfigs[action.value];
|
||||
this.configs = newConfigs;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
configs: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.$emit(
|
||||
"update:modelValue",
|
||||
generateUBrowserCode(this.configs, this.selectedActions)
|
||||
);
|
||||
},
|
||||
},
|
||||
selectedActions: {
|
||||
handler() {
|
||||
this.$emit(
|
||||
"update:modelValue",
|
||||
generateUBrowserCode(this.configs, this.selectedActions)
|
||||
);
|
||||
},
|
||||
},
|
||||
step: {
|
||||
handler() {
|
||||
this.$emit(
|
||||
"update:modelValue",
|
||||
generateUBrowserCode(this.configs, this.selectedActions)
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ubrowser-editor :deep(.q-stepper) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.ubrowser-editor :deep(.q-stepper__tab) {
|
||||
padding: 5px 25px;
|
||||
}
|
||||
</style>
|
||||
282
src/components/composer/ubrowser/UBrowserOperations.vue
Normal file
282
src/components/composer/ubrowser/UBrowserOperations.vue
Normal file
@@ -0,0 +1,282 @@
|
||||
<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="q-pa-xs text-caption text-wrap text-center">
|
||||
{{ action.label }}
|
||||
</div>
|
||||
</q-card>
|
||||
</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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="getActionProps(action, 'config')">
|
||||
<UBrowserOperation
|
||||
:configs="configs"
|
||||
:action="action.value"
|
||||
:fields="getActionProps(action, 'config')"
|
||||
@update:configs="$emit('update:configs', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-list>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import { ubrowserOperationConfigs } from "js/composer/composerConfig";
|
||||
import UBrowserOperation from "./operations/UBrowserOperation.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UBrowserOperations",
|
||||
components: {
|
||||
UBrowserOperation,
|
||||
},
|
||||
props: {
|
||||
configs: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
selectedActions: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ubrowserOperationConfigs: ubrowserOperationConfigs,
|
||||
};
|
||||
},
|
||||
emits: ["remove-action", "update:selectedActions", "update:configs"],
|
||||
methods: {
|
||||
moveAction(index, direction) {
|
||||
const newIndex = index + direction;
|
||||
if (newIndex >= 0 && newIndex < this.selectedActions.length) {
|
||||
const actions = [...this.selectedActions];
|
||||
const temp = actions[index];
|
||||
actions[index] = actions[newIndex];
|
||||
actions[newIndex] = temp;
|
||||
this.$emit("update:selectedActions", actions);
|
||||
}
|
||||
},
|
||||
toggleAction(action) {
|
||||
const index = this.selectedActions.findIndex(
|
||||
(a) => a.value === action.value
|
||||
);
|
||||
if (index === -1) {
|
||||
// 添加操作
|
||||
this.$emit("update:selectedActions", [
|
||||
...this.selectedActions,
|
||||
{
|
||||
...action,
|
||||
id: Date.now(),
|
||||
argv: "",
|
||||
saveOutput: false,
|
||||
useOutput: null,
|
||||
cmd: action.value || action.cmd,
|
||||
value: action.value || action.cmd,
|
||||
},
|
||||
]);
|
||||
|
||||
// 初始化配置对象
|
||||
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 {
|
||||
// 移除操作
|
||||
const newActions = [...this.selectedActions];
|
||||
newActions.splice(index, 1);
|
||||
this.$emit("update:selectedActions", newActions);
|
||||
}
|
||||
},
|
||||
getActionProps(action, key) {
|
||||
return this.ubrowserOperationConfigs.find(
|
||||
(a) => a.value === action.value
|
||||
)[key];
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.operation-list {
|
||||
min-height: 50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.operation-list :deep(.q-field) div,
|
||||
.operation-list :deep(div.q-checkbox__label) {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
.operation-item {
|
||||
transition: all 0.3s;
|
||||
border-radius: 4px;
|
||||
margin: 0;
|
||||
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);
|
||||
} */
|
||||
|
||||
.move-btn {
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.operation-item:hover .move-btn {
|
||||
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 {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
background: var(--q-primary-opacity-5);
|
||||
}
|
||||
|
||||
.action-selected {
|
||||
border-color: var(--q-primary);
|
||||
background: var(--q-primary-opacity-10);
|
||||
}
|
||||
|
||||
.body--dark .action-selected {
|
||||
background: var(--q-primary-opacity-40);
|
||||
}
|
||||
|
||||
.body--dark .action-card {
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.body--dark .action-card:hover {
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
background: var(--q-primary-opacity-20);
|
||||
}
|
||||
|
||||
.text-caption {
|
||||
font-size: 11px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.q-card__section {
|
||||
padding: 4px !important;
|
||||
}
|
||||
|
||||
.row.q-col-gutter-xs {
|
||||
margin: -2px;
|
||||
}
|
||||
|
||||
.row.q-col-gutter-xs > * {
|
||||
padding: 2px;
|
||||
}
|
||||
</style>
|
||||
202
src/components/composer/ubrowser/UBrowserRun.vue
Normal file
202
src/components/composer/ubrowser/UBrowserRun.vue
Normal file
@@ -0,0 +1,202 @@
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- 窗口尺寸和位置 -->
|
||||
<div class="col-12">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-3">
|
||||
<VariableInput
|
||||
v-model="localConfigs.run.width"
|
||||
label="窗口宽度"
|
||||
:command="{ inputType: 'number' }"
|
||||
@update:model-value="updateConfig('width', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<VariableInput
|
||||
v-model="localConfigs.run.height"
|
||||
label="窗口高度"
|
||||
:command="{ inputType: 'number' }"
|
||||
@update:model-value="updateConfig('height', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<VariableInput
|
||||
v-model="localConfigs.run.x"
|
||||
label="X坐标"
|
||||
:command="{ inputType: 'number' }"
|
||||
@update:model-value="updateConfig('x', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<VariableInput
|
||||
v-model="localConfigs.run.y"
|
||||
label="Y坐标"
|
||||
:command="{ inputType: 'number' }"
|
||||
@update:model-value="updateConfig('y', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 最大最小尺寸 -->
|
||||
<div class="col-12">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-3">
|
||||
<VariableInput
|
||||
v-model="localConfigs.run.minWidth"
|
||||
label="最小宽度"
|
||||
:command="{ inputType: 'number' }"
|
||||
@update:model-value="updateConfig('minWidth', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<VariableInput
|
||||
v-model="localConfigs.run.minHeight"
|
||||
label="最小高度"
|
||||
:command="{ inputType: 'number' }"
|
||||
@update:model-value="updateConfig('minHeight', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<VariableInput
|
||||
v-model="localConfigs.run.maxWidth"
|
||||
label="最大宽度"
|
||||
:command="{ inputType: 'number' }"
|
||||
@update:model-value="updateConfig('maxWidth', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<VariableInput
|
||||
v-model="localConfigs.run.maxHeight"
|
||||
label="最大高度"
|
||||
:command="{ inputType: 'number' }"
|
||||
@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" 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"
|
||||
switch-label-side
|
||||
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 } from "vue";
|
||||
import VariableInput from "components/composer/ui/VariableInput.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UBrowserRun",
|
||||
components: {
|
||||
VariableInput,
|
||||
},
|
||||
props: {
|
||||
configs: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:configs"],
|
||||
data() {
|
||||
return {
|
||||
localConfigs: window.lodashM.cloneDeep(this.configs),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
updateConfig(key, value) {
|
||||
this.localConfigs.run[key] = value;
|
||||
this.$emit("update:configs", window.lodashM.cloneDeep(this.localConfigs));
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
configs: {
|
||||
deep: true,
|
||||
handler(newConfigs) {
|
||||
this.localConfigs = window.lodashM.cloneDeep(newConfigs);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div class="row items-center no-wrap">
|
||||
<q-badge class="q-pa-xs">{{ label }}</q-badge>
|
||||
<q-btn-toggle
|
||||
:model-value="modelValue"
|
||||
:options="options"
|
||||
dense
|
||||
flat
|
||||
no-caps
|
||||
spread
|
||||
class="button-group"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UBrowserButtonToggle",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [String, Number, Boolean],
|
||||
required: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.button-group {
|
||||
flex: 1;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.button-group :deep(.q-btn) {
|
||||
min-height: 24px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div class="row items-center no-wrap">
|
||||
<q-badge class="q-pa-xs">{{ label }}</q-badge>
|
||||
<q-btn-toggle
|
||||
:model-value="modelValue ? 'true' : 'false'"
|
||||
:options="[
|
||||
{ label: '是', value: 'true' },
|
||||
{ label: '否', value: 'false' },
|
||||
]"
|
||||
dense
|
||||
flat
|
||||
no-caps
|
||||
spread
|
||||
class="button-group"
|
||||
@update:model-value="$emit('update:modelValue', $event === 'true')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UBrowserCheckbox",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div class="row items-center">
|
||||
<q-option-group
|
||||
:model-value="modelValue"
|
||||
:options="options"
|
||||
type="checkbox"
|
||||
inline
|
||||
dense
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UBrowserCheckboxGroup",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,91 @@
|
||||
<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="名称"
|
||||
:command="{ icon: 'label' }"
|
||||
@update:model-value="
|
||||
(value) => handleUpdate(index, 'name', value)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VariableInput
|
||||
:model-value="cookie.value"
|
||||
label="值"
|
||||
:command="{ 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/ui/VariableInput.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UBrowserCookieList",
|
||||
components: {
|
||||
VariableInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => [{ name: "", value: "" }],
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
methods: {
|
||||
addCookie() {
|
||||
const newValue = [...this.modelValue, { name: "", value: "" }];
|
||||
this.$emit("update:modelValue", newValue);
|
||||
},
|
||||
removeCookie(index) {
|
||||
const newValue = [...this.modelValue];
|
||||
newValue.splice(index, 1);
|
||||
if (newValue.length === 0) {
|
||||
newValue.push({ name: "", value: "" });
|
||||
}
|
||||
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>
|
||||
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col">
|
||||
<VariableInput
|
||||
:command="{ icon: icon }"
|
||||
: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/httpHeaders";
|
||||
import VariableInput from "components/composer/ui/VariableInput.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UBrowserDeviceName",
|
||||
components: {
|
||||
VariableInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
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>
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-6">
|
||||
<VariableInput
|
||||
v-model.number="size.width"
|
||||
label="宽度"
|
||||
:command="{ icon: 'width', inputType: 'number' }"
|
||||
@update:model-value="handleUpdate"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<VariableInput
|
||||
v-model.number="size.height"
|
||||
label="高度"
|
||||
:command="{ icon: 'height', inputType: 'number' }"
|
||||
@update:model-value="handleUpdate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import VariableInput from "components/composer/ui/VariableInput.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UBrowserDeviceSize",
|
||||
components: {
|
||||
VariableInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({ width: 0, height: 0 }),
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
data() {
|
||||
return {
|
||||
size: {
|
||||
width: this.modelValue.width,
|
||||
height: this.modelValue.height,
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleUpdate() {
|
||||
this.$emit("update:modelValue", { ...this.size });
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue: {
|
||||
deep: true,
|
||||
handler(newValue) {
|
||||
this.size = { ...newValue };
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,76 @@
|
||||
<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/ui/VariableInput.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UBrowserFileList",
|
||||
components: {
|
||||
VariableInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
methods: {
|
||||
addFile() {
|
||||
const newValue = [...(this.modelValue || []), ""];
|
||||
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>
|
||||
@@ -0,0 +1,237 @@
|
||||
<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: monospace, monoca, consola"
|
||||
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>
|
||||
@@ -0,0 +1,81 @@
|
||||
<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="参数名"
|
||||
:command="{ icon: 'label' }"
|
||||
@update:model-value="(value) => handleUpdate(index, 'name', value)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<VariableInput
|
||||
:model-value="param.value"
|
||||
label="传递给参数的值"
|
||||
:command="{ 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/ui/VariableInput.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UBrowserNamedParamList",
|
||||
components: {
|
||||
VariableInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => [{ name: "", value: "" }],
|
||||
},
|
||||
label: String,
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
methods: {
|
||||
addParam() {
|
||||
const newValue = [...(this.modelValue || []), { name: "", value: "" }];
|
||||
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>
|
||||
@@ -0,0 +1,244 @@
|
||||
<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'">
|
||||
<UBrowserCheckboxGroup
|
||||
v-model="fieldValue[field.key]"
|
||||
:options="field.options"
|
||||
@update:model-value="updateValue(field.key, $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 单个复选框 -->
|
||||
<template v-else-if="field.type === 'checkbox'">
|
||||
<UBrowserCheckbox
|
||||
v-model="fieldValue[field.key]"
|
||||
:label="field.label"
|
||||
@update:model-value="updateValue(field.key, $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 基本输入类型的处理 -->
|
||||
<template v-if="field.type === 'input'">
|
||||
<!-- 设备名称特殊处理 -->
|
||||
<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
|
||||
v-model="fieldValue[field.key]"
|
||||
:label="field.label"
|
||||
:command="{
|
||||
icon: field.icon,
|
||||
inputType: field.inputType,
|
||||
}"
|
||||
@update:model-value="updateValue(field.key, $event)"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 文本区域 -->
|
||||
<template v-else-if="field.type === 'textarea'">
|
||||
<UBrowserTextarea
|
||||
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'">
|
||||
<UBrowserSelect
|
||||
v-model="fieldValue[field.key]"
|
||||
:label="field.label"
|
||||
:icon="field.icon"
|
||||
:options="field.options"
|
||||
@update:model-value="updateValue(field.key, $event)"
|
||||
/>
|
||||
</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'">
|
||||
<UBrowserButtonToggle
|
||||
v-model="fieldValue[field.key]"
|
||||
:label="field.label"
|
||||
:options="field.options"
|
||||
@update:model-value="updateValue(field.key, $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 设备尺寸 -->
|
||||
<template v-else-if="field.type === 'device-size'">
|
||||
<UBrowserDeviceSize
|
||||
v-model="fieldValue.size"
|
||||
@update:model-value="updateValue(field.key, $event)"
|
||||
/>
|
||||
</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 } from "vue";
|
||||
import { get, set } from "lodash";
|
||||
import UBrowserFunctionInput from "./UBrowserFunctionInput.vue";
|
||||
import UBrowserCheckbox from "./UBrowserCheckbox.vue";
|
||||
import UBrowserFileList from "./UBrowserFileList.vue";
|
||||
import UBrowserCookieList from "./UBrowserCookieList.vue";
|
||||
import UBrowserButtonToggle from "./UBrowserButtonToggle.vue";
|
||||
import UBrowserDeviceSize from "./UBrowserDeviceSize.vue";
|
||||
import UBrowserNamedParamList from "./UBrowserNamedParamList.vue";
|
||||
import UBrowserSelect from "./UBrowserSelect.vue";
|
||||
import UBrowserDeviceName from "./UBrowserDeviceName.vue";
|
||||
import UBrowserTextarea from "./UBrowserTextarea.vue";
|
||||
import VariableInput from "components/composer/ui/VariableInput.vue";
|
||||
import UBrowserCheckboxGroup from "./UBrowserCheckboxGroup.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UBrowserOperation",
|
||||
components: {
|
||||
UBrowserFunctionInput,
|
||||
UBrowserCheckbox,
|
||||
UBrowserFileList,
|
||||
UBrowserCookieList,
|
||||
UBrowserButtonToggle,
|
||||
UBrowserDeviceSize,
|
||||
UBrowserNamedParamList,
|
||||
UBrowserSelect,
|
||||
UBrowserDeviceName,
|
||||
UBrowserTextarea,
|
||||
VariableInput,
|
||||
UBrowserCheckboxGroup,
|
||||
},
|
||||
props: {
|
||||
configs: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
action: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
fields: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:configs"],
|
||||
data() {
|
||||
return {
|
||||
fieldValue: {},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 初始化字段值,确保有默认值
|
||||
this.fields.forEach((field) => {
|
||||
const value = get(this.configs[this.action], field.key);
|
||||
// 根据字段类型设置适当的默认值
|
||||
let defaultValue;
|
||||
if (field.type === "checkbox-group") {
|
||||
defaultValue = field.defaultValue || [];
|
||||
} else if (field.type === "checkbox") {
|
||||
defaultValue = field.defaultValue || false;
|
||||
} else if (field.type === "function-with-params") {
|
||||
// 为function-with-params类型设置特殊的默认值结构
|
||||
this.fieldValue.function = value?.function || "";
|
||||
this.fieldValue.args = value?.args || [];
|
||||
return; // 跳过后续的赋值
|
||||
} else {
|
||||
defaultValue = field.defaultValue;
|
||||
}
|
||||
this.fieldValue[field.key] = value !== undefined ? value : defaultValue;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
updateValue(key, value) {
|
||||
// 更新本地值
|
||||
this.fieldValue[key] = value;
|
||||
|
||||
// 创建新的配置对
|
||||
const newConfigs = { ...this.configs };
|
||||
if (!newConfigs[this.action]) {
|
||||
newConfigs[this.action] = {};
|
||||
}
|
||||
|
||||
// 使用 lodash 的 set 来处理嵌套路径
|
||||
set(newConfigs[this.action], key, value);
|
||||
|
||||
// 发出更新事件
|
||||
this.$emit("update:configs", newConfigs);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// 监听配置变化
|
||||
configs: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.fields.forEach((field) => {
|
||||
const value = get(this.configs[this.action], field.key);
|
||||
if (field.type === "function-with-params") {
|
||||
// 为function-with-params类型设置特殊的更新逻辑
|
||||
this.fieldValue.function =
|
||||
value?.function || this.fieldValue.function || "";
|
||||
this.fieldValue.args = value?.args || this.fieldValue.args || [];
|
||||
return;
|
||||
}
|
||||
if (value !== undefined) {
|
||||
this.fieldValue[field.key] = value;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<q-select
|
||||
:model-value="modelValue"
|
||||
:label="label"
|
||||
:options="options"
|
||||
dense
|
||||
filled
|
||||
emit-value
|
||||
map-options
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon :name="icon" />
|
||||
</template>
|
||||
</q-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UBrowserSelect",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: "",
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<q-input
|
||||
:model-value="modelValue"
|
||||
:label="label"
|
||||
type="textarea"
|
||||
dense
|
||||
filled
|
||||
autogrow
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon :name="icon" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UBrowserTextarea",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user