mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-30 21:13:02 +08:00
编排界面添加ubrowser
This commit is contained in:
parent
29ceb3c7ff
commit
52fc7f408d
@ -23,17 +23,10 @@
|
||||
</div>
|
||||
|
||||
<!-- 固定底部 -->
|
||||
<div class="composer-footer q-pa-sm row items-center justify-end">
|
||||
<q-btn
|
||||
outline
|
||||
color="primary"
|
||||
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 class="composer-footer q-pa-sm q-gutter-sm row justify-end">
|
||||
<q-btn label="取消" v-close-popup />
|
||||
<q-btn color="primary" label="确认" @click="applyCommands" />
|
||||
<q-btn color="positive" label="运行" @click="runCommands" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -68,6 +68,13 @@
|
||||
<template v-else-if="command.hasKeyRecorder">
|
||||
<KeyEditor v-model="argvLocal" class="col" />
|
||||
</template>
|
||||
<!-- UBrowser编辑器 -->
|
||||
<template v-else-if="command.hasUBrowserEditor">
|
||||
<UBrowserEditor
|
||||
v-model="argvLocal"
|
||||
class="col"
|
||||
/>
|
||||
</template>
|
||||
<!-- 普通参数输入 -->
|
||||
<template v-else>
|
||||
<q-input
|
||||
@ -92,11 +99,13 @@
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import KeyEditor from "./KeyEditor.vue";
|
||||
import UBrowserEditor from './ubrowser/UBrowserEditor.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: "ComposerCard",
|
||||
components: {
|
||||
KeyEditor,
|
||||
UBrowserEditor
|
||||
},
|
||||
props: {
|
||||
command: {
|
||||
@ -208,10 +217,10 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
/* 拖拽动画 */
|
||||
.composer-card:active {
|
||||
transform: scale(1.02);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
/* .composer-card:active { */
|
||||
/* transform: scale(1.02); */
|
||||
/* transition: transform 0.2s; */
|
||||
/* } */
|
||||
|
||||
.command-item {
|
||||
transition: all 0.3s ease;
|
||||
|
@ -45,6 +45,12 @@ export const commandCategories = [
|
||||
value: "utools.ubrowser.goto",
|
||||
label: "用ubrowser打开网址",
|
||||
desc: "要访问的网址链接",
|
||||
},
|
||||
{
|
||||
value: "ubrowser",
|
||||
label: "UBrowser浏览器操作",
|
||||
desc: "配置UBrowser浏览器操作",
|
||||
hasUBrowserEditor: true
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -121,6 +127,7 @@ export const commandsWithOutput = {
|
||||
'open': true,
|
||||
'locate': 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