mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-30 13:02:46 +08:00
编排界面添加ubrowser
This commit is contained in:
parent
29ceb3c7ff
commit
52fc7f408d
@ -23,17 +23,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 固定底部 -->
|
<!-- 固定底部 -->
|
||||||
<div class="composer-footer q-pa-sm row items-center justify-end">
|
<div class="composer-footer q-pa-sm q-gutter-sm row justify-end">
|
||||||
<q-btn
|
<q-btn label="取消" v-close-popup />
|
||||||
outline
|
<q-btn color="primary" label="确认" @click="applyCommands" />
|
||||||
color="primary"
|
<q-btn color="positive" label="运行" @click="runCommands" />
|
||||||
label="运行"
|
|
||||||
icon="play_arrow"
|
|
||||||
class="q-mr-sm"
|
|
||||||
@click="runCommands"
|
|
||||||
/>
|
|
||||||
<q-btn flat label="取消" v-close-popup />
|
|
||||||
<q-btn unelevated color="primary" label="确认" @click="applyCommands" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -68,6 +68,13 @@
|
|||||||
<template v-else-if="command.hasKeyRecorder">
|
<template v-else-if="command.hasKeyRecorder">
|
||||||
<KeyEditor v-model="argvLocal" class="col" />
|
<KeyEditor v-model="argvLocal" class="col" />
|
||||||
</template>
|
</template>
|
||||||
|
<!-- UBrowser编辑器 -->
|
||||||
|
<template v-else-if="command.hasUBrowserEditor">
|
||||||
|
<UBrowserEditor
|
||||||
|
v-model="argvLocal"
|
||||||
|
class="col"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
<!-- 普通参数输入 -->
|
<!-- 普通参数输入 -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<q-input
|
<q-input
|
||||||
@ -92,11 +99,13 @@
|
|||||||
<script>
|
<script>
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import KeyEditor from "./KeyEditor.vue";
|
import KeyEditor from "./KeyEditor.vue";
|
||||||
|
import UBrowserEditor from './ubrowser/UBrowserEditor.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "ComposerCard",
|
name: "ComposerCard",
|
||||||
components: {
|
components: {
|
||||||
KeyEditor,
|
KeyEditor,
|
||||||
|
UBrowserEditor
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
command: {
|
command: {
|
||||||
@ -208,10 +217,10 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 拖拽动画 */
|
/* 拖拽动画 */
|
||||||
.composer-card:active {
|
/* .composer-card:active { */
|
||||||
transform: scale(1.02);
|
/* transform: scale(1.02); */
|
||||||
transition: transform 0.2s;
|
/* transition: transform 0.2s; */
|
||||||
}
|
/* } */
|
||||||
|
|
||||||
.command-item {
|
.command-item {
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
@ -45,6 +45,12 @@ export const commandCategories = [
|
|||||||
value: "utools.ubrowser.goto",
|
value: "utools.ubrowser.goto",
|
||||||
label: "用ubrowser打开网址",
|
label: "用ubrowser打开网址",
|
||||||
desc: "要访问的网址链接",
|
desc: "要访问的网址链接",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "ubrowser",
|
||||||
|
label: "UBrowser浏览器操作",
|
||||||
|
desc: "配置UBrowser浏览器操作",
|
||||||
|
hasUBrowserEditor: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -121,6 +127,7 @@ export const commandsWithOutput = {
|
|||||||
'open': true,
|
'open': true,
|
||||||
'locate': true,
|
'locate': true,
|
||||||
'copyTo': true,
|
'copyTo': true,
|
||||||
|
'ubrowser': true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义哪些命令可以接收输出
|
// 定义哪些命令可以接收输出
|
||||||
|
86
src/components/editor/composer/ubrowser/UBrowserBasic.vue
Normal file
86
src/components/editor/composer/ubrowser/UBrowserBasic.vue
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<template>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<!-- UserAgent -->
|
||||||
|
<div class="col-12">
|
||||||
|
<UBrowserInput
|
||||||
|
:value="configs.useragent.value"
|
||||||
|
@update:modelValue="updateConfig('useragent.value', $event)"
|
||||||
|
label="UserAgent"
|
||||||
|
icon="person"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- URL -->
|
||||||
|
<div class="col-12">
|
||||||
|
<UBrowserInput
|
||||||
|
:value="configs.goto.url"
|
||||||
|
@update:modelValue="updateConfig('goto.url', $event)"
|
||||||
|
label="URL"
|
||||||
|
icon="link"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Headers -->
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="text-subtitle2 q-mb-sm">请求头</div>
|
||||||
|
<UBrowserInput
|
||||||
|
:value="configs.goto.headers.Referer"
|
||||||
|
@update:modelValue="updateConfig('goto.headers.Referer', $event)"
|
||||||
|
label="Referer"
|
||||||
|
icon="link"
|
||||||
|
class="q-mb-sm"
|
||||||
|
/>
|
||||||
|
<UBrowserInput
|
||||||
|
:value="configs.goto.headers.userAgent"
|
||||||
|
@update:modelValue="updateConfig('goto.headers.userAgent', $event)"
|
||||||
|
label="User-Agent"
|
||||||
|
icon="person"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Timeout -->
|
||||||
|
<div class="col-12">
|
||||||
|
<UBrowserInput
|
||||||
|
:value="configs.goto.timeout"
|
||||||
|
@update:modelValue="updateConfig('goto.timeout', $event)"
|
||||||
|
type="number"
|
||||||
|
label="超时时间(ms)"
|
||||||
|
icon="timer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import UBrowserInput from './operations/UBrowserInput.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'UBrowserBasic',
|
||||||
|
components: {
|
||||||
|
UBrowserInput
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
configs: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['update:configs'],
|
||||||
|
methods: {
|
||||||
|
updateConfig(path, value) {
|
||||||
|
const newConfigs = { ...this.configs };
|
||||||
|
const keys = path.split('.');
|
||||||
|
let current = newConfigs;
|
||||||
|
|
||||||
|
for (let i = 0; i < keys.length - 1; i++) {
|
||||||
|
current[keys[i]] = { ...current[keys[i]] };
|
||||||
|
current = current[keys[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
current[keys[keys.length - 1]] = value;
|
||||||
|
this.$emit('update:configs', newConfigs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
486
src/components/editor/composer/ubrowser/UBrowserEditor.vue
Normal file
486
src/components/editor/composer/ubrowser/UBrowserEditor.vue
Normal file
@ -0,0 +1,486 @@
|
|||||||
|
<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"
|
||||||
|
>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<div class="col-12">
|
||||||
|
<q-select
|
||||||
|
v-model="selectedActions"
|
||||||
|
:options="availableActions"
|
||||||
|
multiple
|
||||||
|
use-chips
|
||||||
|
outlined
|
||||||
|
dense
|
||||||
|
label="选择操作"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<UBrowserOperations
|
||||||
|
:configs="configs"
|
||||||
|
@update:configs="updateConfigs"
|
||||||
|
v-model:selected-actions="selectedActions"
|
||||||
|
@remove-action="removeAction"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import UBrowserBasic from './UBrowserBasic.vue';
|
||||||
|
import UBrowserOperations from './UBrowserOperations.vue';
|
||||||
|
import UBrowserRun from './UBrowserRun.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'UBrowserEditor',
|
||||||
|
components: {
|
||||||
|
UBrowserBasic,
|
||||||
|
UBrowserOperations,
|
||||||
|
UBrowserRun
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
step: 1,
|
||||||
|
selectedActions: [],
|
||||||
|
configs: {
|
||||||
|
// 基础参数
|
||||||
|
useragent: {
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
goto: {
|
||||||
|
url: '',
|
||||||
|
headers: {
|
||||||
|
Referer: '',
|
||||||
|
userAgent: ''
|
||||||
|
},
|
||||||
|
timeout: 60000
|
||||||
|
},
|
||||||
|
// 浏览器操作
|
||||||
|
wait: {
|
||||||
|
value: '',
|
||||||
|
timeout: 60000
|
||||||
|
},
|
||||||
|
click: {
|
||||||
|
selector: ''
|
||||||
|
},
|
||||||
|
css: {
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
press: {
|
||||||
|
key: '',
|
||||||
|
modifiers: []
|
||||||
|
},
|
||||||
|
paste: {
|
||||||
|
text: ''
|
||||||
|
},
|
||||||
|
screenshot: {
|
||||||
|
selector: '',
|
||||||
|
rect: { x: 0, y: 0, width: 0, height: 0 },
|
||||||
|
savePath: ''
|
||||||
|
},
|
||||||
|
pdf: {
|
||||||
|
options: {
|
||||||
|
marginsType: 0,
|
||||||
|
pageSize: 'A4'
|
||||||
|
},
|
||||||
|
savePath: ''
|
||||||
|
},
|
||||||
|
device: {
|
||||||
|
size: { width: 1280, height: 800 },
|
||||||
|
useragent: ''
|
||||||
|
},
|
||||||
|
cookies: {
|
||||||
|
name: ''
|
||||||
|
},
|
||||||
|
setCookies: {
|
||||||
|
items: [{ name: '', value: '' }]
|
||||||
|
},
|
||||||
|
removeCookies: {
|
||||||
|
name: ''
|
||||||
|
},
|
||||||
|
clearCookies: {
|
||||||
|
url: ''
|
||||||
|
},
|
||||||
|
evaluate: {
|
||||||
|
function: '',
|
||||||
|
params: []
|
||||||
|
},
|
||||||
|
when: {
|
||||||
|
condition: ''
|
||||||
|
},
|
||||||
|
mousedown: {
|
||||||
|
selector: ''
|
||||||
|
},
|
||||||
|
mouseup: {
|
||||||
|
selector: ''
|
||||||
|
},
|
||||||
|
file: {
|
||||||
|
selector: '',
|
||||||
|
files: []
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
selector: '',
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
check: {
|
||||||
|
selector: '',
|
||||||
|
checked: false
|
||||||
|
},
|
||||||
|
focus: {
|
||||||
|
selector: ''
|
||||||
|
},
|
||||||
|
scroll: {
|
||||||
|
target: '',
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
},
|
||||||
|
download: {
|
||||||
|
url: '',
|
||||||
|
savePath: ''
|
||||||
|
},
|
||||||
|
// 运行参数
|
||||||
|
run: {
|
||||||
|
show: true,
|
||||||
|
width: 1280,
|
||||||
|
height: 800,
|
||||||
|
x: undefined,
|
||||||
|
y: undefined,
|
||||||
|
center: true,
|
||||||
|
minWidth: 800,
|
||||||
|
minHeight: 600,
|
||||||
|
maxWidth: undefined,
|
||||||
|
maxHeight: undefined,
|
||||||
|
resizable: true,
|
||||||
|
movable: true,
|
||||||
|
minimizable: true,
|
||||||
|
maximizable: true,
|
||||||
|
alwaysOnTop: false,
|
||||||
|
fullscreen: false,
|
||||||
|
fullscreenable: true,
|
||||||
|
enableLargerThanScreen: false,
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
availableActions() {
|
||||||
|
return [
|
||||||
|
{ label: '等待', value: 'wait' },
|
||||||
|
{ label: '点击', value: 'click' },
|
||||||
|
{ label: '注入CSS', value: 'css' },
|
||||||
|
{ label: '按键', value: 'press' },
|
||||||
|
{ label: '粘贴', value: 'paste' },
|
||||||
|
{ label: '截图', value: 'screenshot' },
|
||||||
|
{ label: '导出PDF', value: 'pdf' },
|
||||||
|
{ label: '模拟设备', value: 'device' },
|
||||||
|
{ label: '获取Cookie', value: 'cookies' },
|
||||||
|
{ label: '设置Cookie', value: 'setCookies' },
|
||||||
|
{ label: '删除Cookie', value: 'removeCookies' },
|
||||||
|
{ label: '清除Cookie', value: 'clearCookies' },
|
||||||
|
{ label: '执行脚本', value: 'evaluate' },
|
||||||
|
{ label: '条件判断', value: 'when' },
|
||||||
|
{ label: '鼠标按下', value: 'mousedown' },
|
||||||
|
{ label: '鼠标释放', value: 'mouseup' },
|
||||||
|
{ label: '上传文件', value: 'file' },
|
||||||
|
{ label: '设置值', value: 'value' },
|
||||||
|
{ label: '选中状态', value: 'check' },
|
||||||
|
{ label: '获取焦点', value: 'focus' },
|
||||||
|
{ label: '滚动', value: 'scroll' },
|
||||||
|
{ label: '下载', value: 'download' },
|
||||||
|
{ label: '隐藏', value: 'hide' },
|
||||||
|
{ label: '显示', value: 'show' },
|
||||||
|
{ label: '开发工具', value: 'devTools' }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateConfigs(newConfigs) {
|
||||||
|
this.configs = newConfigs;
|
||||||
|
},
|
||||||
|
removeAction(action) {
|
||||||
|
const index = this.selectedActions.findIndex(a => a.value === action.value);
|
||||||
|
if (index > -1) {
|
||||||
|
this.selectedActions.splice(index, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
generateCode() {
|
||||||
|
let code = 'utools.ubrowser';
|
||||||
|
|
||||||
|
// 基础参数
|
||||||
|
if (this.configs.useragent.value) {
|
||||||
|
code += `.useragent('${this.configs.useragent.value}')`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.configs.goto.url) {
|
||||||
|
const gotoOptions = {};
|
||||||
|
if (this.configs.goto.headers.Referer) {
|
||||||
|
gotoOptions.headers = gotoOptions.headers || {};
|
||||||
|
gotoOptions.headers.Referer = this.configs.goto.headers.Referer;
|
||||||
|
}
|
||||||
|
if (this.configs.goto.headers.userAgent) {
|
||||||
|
gotoOptions.headers = gotoOptions.headers || {};
|
||||||
|
gotoOptions.headers['User-Agent'] = this.configs.goto.headers.userAgent;
|
||||||
|
}
|
||||||
|
if (this.configs.goto.timeout !== 60000) {
|
||||||
|
gotoOptions.timeout = this.configs.goto.timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
code += `.goto('${this.configs.goto.url}'${Object.keys(gotoOptions).length ? `, ${JSON.stringify(gotoOptions)}` : ''})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 浏览器操作
|
||||||
|
this.selectedActions.forEach(action => {
|
||||||
|
const config = this.configs[action.value];
|
||||||
|
switch (action.value) {
|
||||||
|
case 'wait':
|
||||||
|
if (config.value) {
|
||||||
|
code += `.wait('${config.value}'${config.timeout !== 60000 ? `, ${config.timeout}` : ''})`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'click':
|
||||||
|
if (config.selector) {
|
||||||
|
code += `.click('${config.selector}')`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'css':
|
||||||
|
if (config.value) {
|
||||||
|
code += `.css('${config.value}')`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'press':
|
||||||
|
if (config.key) {
|
||||||
|
const modifiers = config.modifiers.length ? `, ${JSON.stringify(config.modifiers)}` : '';
|
||||||
|
code += `.press('${config.key}'${modifiers})`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'paste':
|
||||||
|
if (config.text) {
|
||||||
|
code += `.paste('${config.text}')`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'screenshot':
|
||||||
|
if (config.selector || config.savePath) {
|
||||||
|
const options = {};
|
||||||
|
if (config.selector) options.selector = config.selector;
|
||||||
|
if (config.rect.width && config.rect.height) {
|
||||||
|
options.rect = config.rect;
|
||||||
|
}
|
||||||
|
code += `.screenshot('${config.savePath}'${Object.keys(options).length ? `, ${JSON.stringify(options)}` : ''})`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'pdf':
|
||||||
|
if (config.savePath) {
|
||||||
|
code += `.pdf('${config.savePath}'${config.options ? `, ${JSON.stringify(config.options)}` : ''})`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'device':
|
||||||
|
if (config.size.width && config.size.height) {
|
||||||
|
const options = {
|
||||||
|
size: config.size
|
||||||
|
};
|
||||||
|
if (config.useragent) options.useragent = config.useragent;
|
||||||
|
code += `.device(${JSON.stringify(options)})`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'cookies':
|
||||||
|
if (config.name) {
|
||||||
|
code += `.cookies('${config.name}')`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'setCookies':
|
||||||
|
if (config.items?.length) {
|
||||||
|
code += `.setCookies(${JSON.stringify(config.items)})`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'removeCookies':
|
||||||
|
if (config.name) {
|
||||||
|
code += `.removeCookies('${config.name}')`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'clearCookies':
|
||||||
|
code += `.clearCookies(${config.url ? `'${config.url}'` : ''})`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'evaluate':
|
||||||
|
if (config.function) {
|
||||||
|
const params = config.params.length ? `, ${JSON.stringify(config.params)}` : '';
|
||||||
|
code += `.evaluate(\`${config.function}\`${params})`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'when':
|
||||||
|
if (config.condition) {
|
||||||
|
code += `.when('${config.condition}')`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'mousedown':
|
||||||
|
case 'mouseup':
|
||||||
|
if (config.selector) {
|
||||||
|
code += `.${action.value}('${config.selector}')`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'file':
|
||||||
|
if (config.selector && config.files?.length) {
|
||||||
|
code += `.file('${config.selector}', ${JSON.stringify(config.files)})`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'value':
|
||||||
|
if (config.selector) {
|
||||||
|
code += `.value('${config.selector}', '${config.value}')`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'check':
|
||||||
|
if (config.selector) {
|
||||||
|
code += `.check('${config.selector}'${config.checked !== undefined ? `, ${config.checked}` : ''})`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'focus':
|
||||||
|
if (config.selector) {
|
||||||
|
code += `.focus('${config.selector}')`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'scroll':
|
||||||
|
if (config.x !== undefined || config.y !== undefined) {
|
||||||
|
const options = {};
|
||||||
|
if (config.target) options.target = config.target;
|
||||||
|
if (config.x !== undefined) options.x = config.x;
|
||||||
|
if (config.y !== undefined) options.y = config.y;
|
||||||
|
code += `.scroll(${JSON.stringify(options)})`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'download':
|
||||||
|
if (config.url) {
|
||||||
|
code += `.download('${config.url}'${config.savePath ? `, '${config.savePath}'` : ''})`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'hide':
|
||||||
|
case 'show':
|
||||||
|
case 'devTools':
|
||||||
|
code += `.${action.value}()`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 运行参数
|
||||||
|
const runOptions = {};
|
||||||
|
Object.entries(this.configs.run).forEach(([key, value]) => {
|
||||||
|
if (value !== undefined && value !== null) {
|
||||||
|
runOptions[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
code += `.run(${Object.keys(runOptions).length ? JSON.stringify(runOptions) : ''})`;
|
||||||
|
|
||||||
|
this.$emit('update:modelValue', code);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
configs: {
|
||||||
|
deep: true,
|
||||||
|
handler() {
|
||||||
|
this.generateCode();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectedActions: {
|
||||||
|
handler() {
|
||||||
|
this.generateCode();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
step: {
|
||||||
|
handler() {
|
||||||
|
this.generateCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<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;
|
||||||
|
}
|
||||||
|
</style>
|
455
src/components/editor/composer/ubrowser/UBrowserOperations.vue
Normal file
455
src/components/editor/composer/ubrowser/UBrowserOperations.vue
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
<template>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<div class="col-12">
|
||||||
|
<q-list separator class="operation-list">
|
||||||
|
<div
|
||||||
|
v-for="(action, index) in selectedActions"
|
||||||
|
:key="action.value"
|
||||||
|
class="operation-item"
|
||||||
|
>
|
||||||
|
<div class="row items-center justify-between">
|
||||||
|
<div class="row items-center">
|
||||||
|
<q-icon
|
||||||
|
:name="getActionIcon(action.value)"
|
||||||
|
size="xs"
|
||||||
|
class="q-mx-sm"
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
<div class="text-subtitle1">{{ action.label }}</div>
|
||||||
|
<div class="row items-center q-ml-md">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
dense
|
||||||
|
icon="north"
|
||||||
|
:disable="index === 0"
|
||||||
|
@click="moveAction(index, -1)"
|
||||||
|
size="xs"
|
||||||
|
class="q-mb-xs move-btn"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
dense
|
||||||
|
icon="south"
|
||||||
|
:disable="index === selectedActions.length - 1"
|
||||||
|
@click="moveAction(index, 1)"
|
||||||
|
size="xs"
|
||||||
|
class="move-btn"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
dense
|
||||||
|
icon="delete"
|
||||||
|
color="negative"
|
||||||
|
size="sm"
|
||||||
|
@click="$emit('remove-action', action)"
|
||||||
|
class="delete-btn"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="getOperationConfig(action.value)">
|
||||||
|
<UBrowserOperation
|
||||||
|
:configs="configs"
|
||||||
|
:action="action.value"
|
||||||
|
:fields="getOperationConfig(action.value)"
|
||||||
|
@update:configs="$emit('update:configs', $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import UBrowserOperation from "./operations/UBrowserOperation.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "UBrowserOperations",
|
||||||
|
components: {
|
||||||
|
UBrowserOperation,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
configs: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
selectedActions: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ["remove-action", "update:selectedActions"],
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getActionIcon(action) {
|
||||||
|
const iconMap = {
|
||||||
|
wait: "timer",
|
||||||
|
click: "mouse",
|
||||||
|
css: "style",
|
||||||
|
press: "keyboard",
|
||||||
|
paste: "content_paste",
|
||||||
|
screenshot: "photo_camera",
|
||||||
|
pdf: "picture_as_pdf",
|
||||||
|
device: "devices",
|
||||||
|
cookies: "cookie",
|
||||||
|
evaluate: "code",
|
||||||
|
when: "rule",
|
||||||
|
mousedown: "mouse",
|
||||||
|
mouseup: "mouse",
|
||||||
|
file: "upload_file",
|
||||||
|
value: "edit",
|
||||||
|
check: "check_box",
|
||||||
|
focus: "center_focus_strong",
|
||||||
|
scroll: "swap_vert",
|
||||||
|
download: "download",
|
||||||
|
hide: "visibility_off",
|
||||||
|
show: "visibility",
|
||||||
|
devTools: "developer_board",
|
||||||
|
};
|
||||||
|
return iconMap[action] || "touch_app";
|
||||||
|
},
|
||||||
|
getOperationConfig(action) {
|
||||||
|
const configs = {
|
||||||
|
wait: [
|
||||||
|
{
|
||||||
|
key: "value",
|
||||||
|
label: "等待时间(ms)或CSS选择器",
|
||||||
|
icon: "timer",
|
||||||
|
type: "input",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "timeout",
|
||||||
|
label: "超时时间(ms)",
|
||||||
|
icon: "timer_off",
|
||||||
|
type: "input",
|
||||||
|
inputType: "number",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
click: [
|
||||||
|
{
|
||||||
|
key: "selector",
|
||||||
|
label: "点击元素的CSS选择器",
|
||||||
|
icon: "mouse",
|
||||||
|
type: "input",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
css: [
|
||||||
|
{
|
||||||
|
key: "value",
|
||||||
|
label: "注入的CSS样式",
|
||||||
|
icon: "style",
|
||||||
|
type: "textarea",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
press: [
|
||||||
|
{ key: "key", label: "按键", icon: "keyboard", type: "input" },
|
||||||
|
{
|
||||||
|
key: "modifiers",
|
||||||
|
type: "checkbox-group",
|
||||||
|
options: [
|
||||||
|
{ label: "Ctrl", value: "ctrl" },
|
||||||
|
{ label: "Shift", value: "shift" },
|
||||||
|
{ label: "Alt", value: "alt" },
|
||||||
|
{ label: "Meta", value: "meta" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
paste: [
|
||||||
|
{
|
||||||
|
key: "text",
|
||||||
|
label: "粘贴内容",
|
||||||
|
icon: "content_paste",
|
||||||
|
type: "input",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
viewport: [
|
||||||
|
{
|
||||||
|
key: "width",
|
||||||
|
label: "视窗宽度",
|
||||||
|
icon: "width",
|
||||||
|
type: "input",
|
||||||
|
inputType: "number",
|
||||||
|
width: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "height",
|
||||||
|
label: "视窗高度",
|
||||||
|
icon: "height",
|
||||||
|
type: "input",
|
||||||
|
inputType: "number",
|
||||||
|
width: 6,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
screenshot: [
|
||||||
|
{ key: "selector", label: "元素选择器", icon: "crop", type: "input" },
|
||||||
|
{
|
||||||
|
key: "rect.x",
|
||||||
|
label: "X坐标",
|
||||||
|
icon: "drag_handle",
|
||||||
|
type: "input",
|
||||||
|
inputType: "number",
|
||||||
|
width: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "rect.y",
|
||||||
|
label: "Y坐标",
|
||||||
|
icon: "drag_handle",
|
||||||
|
type: "input",
|
||||||
|
inputType: "number",
|
||||||
|
width: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "rect.width",
|
||||||
|
label: "宽度",
|
||||||
|
icon: "width",
|
||||||
|
type: "input",
|
||||||
|
inputType: "number",
|
||||||
|
width: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "rect.height",
|
||||||
|
label: "高度",
|
||||||
|
icon: "height",
|
||||||
|
type: "input",
|
||||||
|
inputType: "number",
|
||||||
|
width: 6,
|
||||||
|
},
|
||||||
|
{ key: "savePath", label: "保存路径", icon: "save", type: "input" },
|
||||||
|
],
|
||||||
|
pdf: [
|
||||||
|
{
|
||||||
|
key: "options.marginsType",
|
||||||
|
label: "边距类型",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ label: "默认边距", value: 0 },
|
||||||
|
{ label: "无边距", value: 1 },
|
||||||
|
{ label: "最小边距", value: 2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "options.pageSize",
|
||||||
|
label: "页面大小",
|
||||||
|
type: "select",
|
||||||
|
options: ["A3", "A4", "A5", "Legal", "Letter", "Tabloid"],
|
||||||
|
},
|
||||||
|
{ key: "savePath", label: "保存路径", icon: "save", type: "input" },
|
||||||
|
],
|
||||||
|
device: [
|
||||||
|
{
|
||||||
|
key: "size.width",
|
||||||
|
label: "设备宽度",
|
||||||
|
icon: "width",
|
||||||
|
type: "input",
|
||||||
|
inputType: "number",
|
||||||
|
width: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "size.height",
|
||||||
|
label: "设备高度",
|
||||||
|
icon: "height",
|
||||||
|
type: "input",
|
||||||
|
inputType: "number",
|
||||||
|
width: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "useragent",
|
||||||
|
label: "设备User-Agent",
|
||||||
|
icon: "phone_android",
|
||||||
|
type: "input",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
cookies: [
|
||||||
|
{ key: "name", label: "Cookie名称", icon: "cookie", type: "input" },
|
||||||
|
],
|
||||||
|
setCookies: [
|
||||||
|
{ key: "items", label: "Cookie列表", type: "cookie-list" },
|
||||||
|
],
|
||||||
|
removeCookies: [
|
||||||
|
{ key: "name", label: "Cookie名称", icon: "cookie", type: "input" },
|
||||||
|
],
|
||||||
|
clearCookies: [
|
||||||
|
{ key: "url", label: "URL(可选)", icon: "link", type: "input" },
|
||||||
|
],
|
||||||
|
evaluate: [
|
||||||
|
{
|
||||||
|
key: "function",
|
||||||
|
label: "JavaScript代码",
|
||||||
|
icon: "code",
|
||||||
|
type: "textarea",
|
||||||
|
},
|
||||||
|
{ key: "params", label: "参数列表", type: "param-list" },
|
||||||
|
],
|
||||||
|
when: [
|
||||||
|
{
|
||||||
|
key: "condition",
|
||||||
|
label: "条件(JavaScript表达式或选择器)",
|
||||||
|
icon: "rule",
|
||||||
|
type: "textarea",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
mousedown: [
|
||||||
|
{
|
||||||
|
key: "selector",
|
||||||
|
label: "按下元素选择器",
|
||||||
|
icon: "mouse",
|
||||||
|
type: "input",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
mouseup: [
|
||||||
|
{
|
||||||
|
key: "selector",
|
||||||
|
label: "释放元素选择器",
|
||||||
|
icon: "mouse",
|
||||||
|
type: "input",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
file: [
|
||||||
|
{
|
||||||
|
key: "selector",
|
||||||
|
label: "文件输入框选择器",
|
||||||
|
icon: "upload_file",
|
||||||
|
type: "input",
|
||||||
|
},
|
||||||
|
{ key: "files", label: "文件列表", type: "file-list" },
|
||||||
|
],
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
key: "selector",
|
||||||
|
label: "元素选择器",
|
||||||
|
icon: "input",
|
||||||
|
type: "input",
|
||||||
|
},
|
||||||
|
{ key: "value", label: "设置的值", icon: "edit", type: "input" },
|
||||||
|
],
|
||||||
|
check: [
|
||||||
|
{
|
||||||
|
key: "selector",
|
||||||
|
label: "复选框/选框选择器",
|
||||||
|
icon: "check_box",
|
||||||
|
type: "input",
|
||||||
|
},
|
||||||
|
{ key: "checked", label: "选中状态", type: "checkbox" },
|
||||||
|
],
|
||||||
|
focus: [
|
||||||
|
{
|
||||||
|
key: "selector",
|
||||||
|
label: "元素选择器",
|
||||||
|
icon: "center_focus_strong",
|
||||||
|
type: "input",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
scroll: [
|
||||||
|
{
|
||||||
|
key: "target",
|
||||||
|
label: "目标元素选择器(可选)",
|
||||||
|
icon: "swap_vert",
|
||||||
|
type: "input",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "x",
|
||||||
|
label: "X坐标",
|
||||||
|
icon: "drag_handle",
|
||||||
|
type: "input",
|
||||||
|
inputType: "number",
|
||||||
|
width: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "y",
|
||||||
|
label: "Y坐标",
|
||||||
|
icon: "drag_handle",
|
||||||
|
type: "input",
|
||||||
|
inputType: "number",
|
||||||
|
width: 6,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
download: [
|
||||||
|
{ key: "url", label: "下载URL", icon: "link", type: "input" },
|
||||||
|
{ key: "savePath", label: "保存路径", icon: "save", type: "input" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
return configs[action];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</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.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body--dark .operation-item {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
background: rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
</style>
|
201
src/components/editor/composer/ubrowser/UBrowserRun.vue
Normal file
201
src/components/editor/composer/ubrowser/UBrowserRun.vue
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
<template>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<!-- 基础设置 -->
|
||||||
|
<div class="col-12">
|
||||||
|
<q-checkbox
|
||||||
|
:value="configs.run.show"
|
||||||
|
@update:modelValue="updateConfig('run.show', $event)"
|
||||||
|
label="显示窗口"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 窗口大小 -->
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="text-subtitle2 q-mb-sm">窗口大小</div>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<UBrowserInput
|
||||||
|
:value="configs.run.width"
|
||||||
|
@update:modelValue="updateConfig('run.width', $event)"
|
||||||
|
type="number"
|
||||||
|
label="宽度"
|
||||||
|
:width="6"
|
||||||
|
icon="width"
|
||||||
|
/>
|
||||||
|
<UBrowserInput
|
||||||
|
:value="configs.run.height"
|
||||||
|
@update:modelValue="updateConfig('run.height', $event)"
|
||||||
|
type="number"
|
||||||
|
label="高度"
|
||||||
|
:width="6"
|
||||||
|
icon="height"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 窗口位置 -->
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="text-subtitle2 q-mb-sm">窗口位置</div>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<UBrowserInput
|
||||||
|
:value="configs.run.x"
|
||||||
|
@update:modelValue="updateConfig('run.x', $event)"
|
||||||
|
type="number"
|
||||||
|
label="X坐标"
|
||||||
|
:width="6"
|
||||||
|
icon="drag_handle"
|
||||||
|
/>
|
||||||
|
<UBrowserInput
|
||||||
|
:value="configs.run.y"
|
||||||
|
@update:modelValue="updateConfig('run.y', $event)"
|
||||||
|
type="number"
|
||||||
|
label="Y坐标"
|
||||||
|
:width="6"
|
||||||
|
icon="drag_handle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 窗口限制 -->
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="text-subtitle2 q-mb-sm">窗口限制</div>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<UBrowserInput
|
||||||
|
:value="configs.run.minWidth"
|
||||||
|
@update:modelValue="updateConfig('run.minWidth', $event)"
|
||||||
|
type="number"
|
||||||
|
label="最小宽度"
|
||||||
|
:width="6"
|
||||||
|
icon="width"
|
||||||
|
/>
|
||||||
|
<UBrowserInput
|
||||||
|
:value="configs.run.minHeight"
|
||||||
|
@update:modelValue="updateConfig('run.minHeight', $event)"
|
||||||
|
type="number"
|
||||||
|
label="最小高度"
|
||||||
|
:width="6"
|
||||||
|
icon="height"
|
||||||
|
/>
|
||||||
|
<UBrowserInput
|
||||||
|
:value="configs.run.maxWidth"
|
||||||
|
@update:modelValue="updateConfig('run.maxWidth', $event)"
|
||||||
|
type="number"
|
||||||
|
label="最大宽度"
|
||||||
|
:width="6"
|
||||||
|
icon="width"
|
||||||
|
/>
|
||||||
|
<UBrowserInput
|
||||||
|
:value="configs.run.maxHeight"
|
||||||
|
@update:modelValue="updateConfig('run.maxHeight', $event)"
|
||||||
|
type="number"
|
||||||
|
label="最大高度"
|
||||||
|
:width="6"
|
||||||
|
icon="height"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 窗口行为 -->
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="text-subtitle2 q-mb-sm">窗口行为</div>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<div class="col-12">
|
||||||
|
<q-checkbox
|
||||||
|
:value="configs.run.center"
|
||||||
|
@update:modelValue="updateConfig('run.center', $event)"
|
||||||
|
label="居中显示"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<q-checkbox
|
||||||
|
:value="configs.run.resizable"
|
||||||
|
@update:modelValue="updateConfig('run.resizable', $event)"
|
||||||
|
label="允许调整大小"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<q-checkbox
|
||||||
|
:value="configs.run.movable"
|
||||||
|
@update:modelValue="updateConfig('run.movable', $event)"
|
||||||
|
label="允许移动"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<q-checkbox
|
||||||
|
:value="configs.run.minimizable"
|
||||||
|
@update:modelValue="updateConfig('run.minimizable', $event)"
|
||||||
|
label="允许最小化"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<q-checkbox
|
||||||
|
:value="configs.run.maximizable"
|
||||||
|
@update:modelValue="updateConfig('run.maximizable', $event)"
|
||||||
|
label="允许最大化"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<q-checkbox
|
||||||
|
:value="configs.run.alwaysOnTop"
|
||||||
|
@update:modelValue="updateConfig('run.alwaysOnTop', $event)"
|
||||||
|
label="总是置顶"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<q-checkbox
|
||||||
|
:value="configs.run.fullscreen"
|
||||||
|
@update:modelValue="updateConfig('run.fullscreen', $event)"
|
||||||
|
label="全屏显示"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<q-checkbox
|
||||||
|
:value="configs.run.fullscreenable"
|
||||||
|
@update:modelValue="updateConfig('run.fullscreenable', $event)"
|
||||||
|
label="允许全屏"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<q-checkbox
|
||||||
|
:value="configs.run.enableLargerThanScreen"
|
||||||
|
@update:modelValue="updateConfig('run.enableLargerThanScreen', $event)"
|
||||||
|
label="允许超出屏幕大小"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import UBrowserInput from './operations/UBrowserInput.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'UBrowserRun',
|
||||||
|
components: {
|
||||||
|
UBrowserInput
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
configs: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['update:configs'],
|
||||||
|
methods: {
|
||||||
|
updateConfig(path, value) {
|
||||||
|
const newConfigs = { ...this.configs };
|
||||||
|
const keys = path.split('.');
|
||||||
|
let current = newConfigs;
|
||||||
|
|
||||||
|
for (let i = 0; i < keys.length - 1; i++) {
|
||||||
|
current[keys[i]] = { ...current[keys[i]] };
|
||||||
|
current = current[keys[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
current[keys[keys.length - 1]] = value;
|
||||||
|
this.$emit('update:configs', newConfigs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<div :class="fullWidth ? 'col-12' : 'col-' + width">
|
||||||
|
<q-input
|
||||||
|
:value="modelValue"
|
||||||
|
v-bind="$attrs"
|
||||||
|
filled
|
||||||
|
square
|
||||||
|
:dense="!large"
|
||||||
|
@update:modelValue="handleInput"
|
||||||
|
>
|
||||||
|
<template v-if="$slots.prepend" v-slot:prepend>
|
||||||
|
<slot name="prepend" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="icon" v-slot:prepend>
|
||||||
|
<q-icon :name="icon" />
|
||||||
|
</template>
|
||||||
|
<template v-if="$slots.append" v-slot:append>
|
||||||
|
<slot name="append" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "UBrowserInput",
|
||||||
|
inheritAttrs: false,
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 12,
|
||||||
|
},
|
||||||
|
fullWidth: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
flat: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
large: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ["update:modelValue"],
|
||||||
|
methods: {
|
||||||
|
handleInput(value) {
|
||||||
|
this.$emit("update:modelValue", value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
@ -0,0 +1,270 @@
|
|||||||
|
<template>
|
||||||
|
<div class="operation-item-argv q-mb-md">
|
||||||
|
<div class="text-subtitle2 q-mb-sm">{{ title }}</div>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<template v-for="(field, index) in fields" :key="index">
|
||||||
|
<!-- 输入框 -->
|
||||||
|
<template v-if="field.type === 'input'">
|
||||||
|
<UBrowserInput
|
||||||
|
:value="getFieldValue(field.key)"
|
||||||
|
@update:modelValue="updateFieldValue(field.key, $event)"
|
||||||
|
:label="field.label"
|
||||||
|
:icon="field.icon"
|
||||||
|
:type="field.inputType || 'text'"
|
||||||
|
:width="field.width"
|
||||||
|
v-bind="field.props || {}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 选择框 -->
|
||||||
|
<template v-if="field.type === 'select'">
|
||||||
|
<q-select
|
||||||
|
:value="getFieldValue(field.key)"
|
||||||
|
@update:modelValue="updateFieldValue(field.key, $event)"
|
||||||
|
:options="field.options"
|
||||||
|
:label="field.label"
|
||||||
|
outlined
|
||||||
|
dense
|
||||||
|
class="col-12"
|
||||||
|
v-bind="field.props || {}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 单个复选框 -->
|
||||||
|
<template v-if="field.type === 'checkbox'">
|
||||||
|
<div class="col-12">
|
||||||
|
<q-checkbox
|
||||||
|
:value="getFieldValue(field.key)"
|
||||||
|
@update:modelValue="updateFieldValue(field.key, $event)"
|
||||||
|
:label="field.label"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 复选框组 -->
|
||||||
|
<template v-if="field.type === 'checkbox-group'">
|
||||||
|
<q-option-group
|
||||||
|
:value="getFieldValue(field.key)"
|
||||||
|
@update:modelValue="updateFieldValue(field.key, $event)"
|
||||||
|
:options="field.options"
|
||||||
|
type="checkbox"
|
||||||
|
inline
|
||||||
|
class="col-12"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 文本域 -->
|
||||||
|
<template v-if="field.type === 'textarea'">
|
||||||
|
<UBrowserInput
|
||||||
|
:value="getFieldValue(field.key)"
|
||||||
|
@update:modelValue="updateFieldValue(field.key, $event)"
|
||||||
|
:label="field.label"
|
||||||
|
:icon="field.icon"
|
||||||
|
type="textarea"
|
||||||
|
autogrow
|
||||||
|
class="col-12"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Cookie列表 -->
|
||||||
|
<template v-if="field.type === 'cookie-list'">
|
||||||
|
<div class="col-12">
|
||||||
|
<div
|
||||||
|
v-for="(item, idx) in getFieldValue(field.key)"
|
||||||
|
:key="idx"
|
||||||
|
class="row q-col-gutter-sm q-mb-sm"
|
||||||
|
>
|
||||||
|
<UBrowserInput
|
||||||
|
:value="item.name"
|
||||||
|
@update:modelValue="
|
||||||
|
updateCookieField(field.key, idx, 'name', $event)
|
||||||
|
"
|
||||||
|
label="名称"
|
||||||
|
:width="5"
|
||||||
|
icon="label"
|
||||||
|
/>
|
||||||
|
<UBrowserInput
|
||||||
|
:value="item.value"
|
||||||
|
@update:modelValue="
|
||||||
|
updateCookieField(field.key, idx, 'value', $event)
|
||||||
|
"
|
||||||
|
label="值"
|
||||||
|
:width="5"
|
||||||
|
icon="edit"
|
||||||
|
/>
|
||||||
|
<div class="col-2 flex items-center">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
dense
|
||||||
|
color="negative"
|
||||||
|
icon="remove"
|
||||||
|
@click="removeCookie(field.key, idx)"
|
||||||
|
v-if="getFieldValue(field.key).length > 1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-btn
|
||||||
|
outline
|
||||||
|
color="primary"
|
||||||
|
label="添加Cookie"
|
||||||
|
icon="add"
|
||||||
|
@click="addCookie(field.key)"
|
||||||
|
class="q-mt-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 参数列表 -->
|
||||||
|
<template v-if="field.type === 'param-list'">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="row items-center q-gutter-sm q-mb-sm">
|
||||||
|
<UBrowserInput
|
||||||
|
v-model="newParam"
|
||||||
|
label="参数"
|
||||||
|
:width="10"
|
||||||
|
icon="functions"
|
||||||
|
/>
|
||||||
|
<q-btn flat round dense icon="add" @click="addParam(field.key)" />
|
||||||
|
</div>
|
||||||
|
<div v-if="getFieldValue(field.key).length > 0" class="q-mt-sm">
|
||||||
|
<q-chip
|
||||||
|
v-for="(param, idx) in getFieldValue(field.key)"
|
||||||
|
:key="idx"
|
||||||
|
removable
|
||||||
|
@remove="removeParam(field.key, idx)"
|
||||||
|
>
|
||||||
|
{{ param }}
|
||||||
|
</q-chip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 文件列表 -->
|
||||||
|
<template v-if="field.type === 'file-list'">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="row items-center q-gutter-sm q-mb-sm">
|
||||||
|
<UBrowserInput
|
||||||
|
v-model="newFile"
|
||||||
|
label="文件路径"
|
||||||
|
:width="10"
|
||||||
|
icon="upload_file"
|
||||||
|
/>
|
||||||
|
<q-btn flat round dense icon="add" @click="addFile(field.key)" />
|
||||||
|
</div>
|
||||||
|
<div v-if="getFieldValue(field.key).length > 0" class="q-mt-sm">
|
||||||
|
<q-chip
|
||||||
|
v-for="(file, idx) in getFieldValue(field.key)"
|
||||||
|
:key="idx"
|
||||||
|
removable
|
||||||
|
@remove="removeFile(field.key, idx)"
|
||||||
|
>
|
||||||
|
{{ file }}
|
||||||
|
</q-chip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import UBrowserInput from "./UBrowserInput.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "UBrowserOperation",
|
||||||
|
components: {
|
||||||
|
UBrowserInput,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
newParam: "",
|
||||||
|
newFile: "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
configs: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ["update:configs"],
|
||||||
|
methods: {
|
||||||
|
addCookie(key) {
|
||||||
|
const items = [...(this.getFieldValue(key) || [])];
|
||||||
|
items.push({ name: "", value: "" });
|
||||||
|
this.updateFieldValue(key, items);
|
||||||
|
},
|
||||||
|
removeCookie(key, index) {
|
||||||
|
const items = [...this.getFieldValue(key)];
|
||||||
|
items.splice(index, 1);
|
||||||
|
this.updateFieldValue(key, items);
|
||||||
|
},
|
||||||
|
updateCookieField(key, index, field, value) {
|
||||||
|
const items = [...this.getFieldValue(key)];
|
||||||
|
items[index] = {
|
||||||
|
...items[index],
|
||||||
|
[field]: value,
|
||||||
|
};
|
||||||
|
this.updateFieldValue(key, items);
|
||||||
|
},
|
||||||
|
addParam(key) {
|
||||||
|
if (this.newParam) {
|
||||||
|
const params = [...(this.getFieldValue(key) || [])];
|
||||||
|
params.push(this.newParam);
|
||||||
|
this.updateFieldValue(key, params);
|
||||||
|
this.newParam = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeParam(key, index) {
|
||||||
|
const params = [...this.getFieldValue(key)];
|
||||||
|
params.splice(index, 1);
|
||||||
|
this.updateFieldValue(key, params);
|
||||||
|
},
|
||||||
|
addFile(key) {
|
||||||
|
if (this.newFile) {
|
||||||
|
const files = [...(this.getFieldValue(key) || [])];
|
||||||
|
files.push(this.newFile);
|
||||||
|
this.updateFieldValue(key, files);
|
||||||
|
this.newFile = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeFile(key, index) {
|
||||||
|
const files = [...this.getFieldValue(key)];
|
||||||
|
files.splice(index, 1);
|
||||||
|
this.updateFieldValue(key, files);
|
||||||
|
},
|
||||||
|
getFieldValue(key) {
|
||||||
|
return this.configs[this.action][key];
|
||||||
|
},
|
||||||
|
updateFieldValue(key, value) {
|
||||||
|
this.$emit("update:configs", {
|
||||||
|
...this.configs,
|
||||||
|
[this.action]: {
|
||||||
|
...this.configs[this.action],
|
||||||
|
[key]: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.operation-item-argv {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
x
Reference in New Issue
Block a user