编排补齐utools窗口交互和内容输入的功能

This commit is contained in:
fofolee 2025-01-09 17:55:50 +08:00
parent 036b6fa934
commit 682f6d0bcd
7 changed files with 338 additions and 102 deletions

View File

@ -1,12 +1,10 @@
const { findImage } = require("./imageFinder"); const { findImage } = require("./imageFinder");
const { captureScreen } = require("./screenCapture"); const { captureScreen } = require("./screenCapture");
const sendText = require("./sendText");
const { keyboardTap, keySequence } = require("./keyboardTap"); const { keyboardTap, keySequence } = require("./keyboardTap");
module.exports = { module.exports = {
findImage, findImage,
captureScreen, captureScreen,
sendText,
keyboardTap, keyboardTap,
keySequence, keySequence,
}; };

View File

@ -1,11 +0,0 @@
const sendText = (text, simulateCopy = false) => {
if (simulateCopy) {
window.utools.copyText(text);
quickcommand.sleep(200);
quickcommand.simulatePaste();
} else {
window.utools.hideMainWindowTypeString(text);
}
};
module.exports = sendText;

View File

@ -7,42 +7,69 @@
> >
<div class="dict-editor"> <div class="dict-editor">
<div <div
v-for="(item, index) in items" v-for="(item, index) in allItems"
:key="index" :key="`${item.type}-${index}`"
class="row q-col-gutter-sm items-center" class="row q-col-gutter-sm items-center"
> >
<div class="col-4"> <div class="col-4">
<q-select <template v-if="item.type === 'fixed'">
v-if="options?.items" <q-input
:model-value="item.key" :model-value="item.key"
:options="options.items" :label="getKeyLabel(item.key)"
label="名称" dense
dense filled
filled readonly
use-input disable
input-debounce="0" >
:hide-selected="!!inputValue" <template v-slot:prepend>
@filter="filterFn" <q-icon name="lock" />
@update:model-value="(val) => handleSelect(val, index)" </template>
@input-value="(val) => handleInput(val, index)" </q-input>
@blur="handleBlur" </template>
> <template v-else>
<template v-slot:prepend> <q-select
<q-icon name="code" /> v-if="options?.optionKeys"
</template> :model-value="item.key"
</q-select> :options="normalizedOptionKeys"
<q-input label="名称"
v-else dense
:model-value="item.key" filled
label="名称" use-input
dense input-debounce="0"
filled :hide-selected="!!inputValue"
@update:model-value="(val) => updateItemKey(val, index)" @filter="filterFn"
> @update:model-value="
<template v-slot:prepend> (val) => handleSelect(val, getEditableIndex(index))
<q-icon name="code" /> "
</template> @input-value="(val) => handleInput(val, getEditableIndex(index))"
</q-input> @blur="handleBlur"
>
<template v-slot:prepend>
<q-icon name="code" />
</template>
<template v-slot:option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section>
{{ getKeyLabel(scope.opt) }}
</q-item-section>
</q-item>
</template>
</q-select>
<q-input
v-else
:model-value="item.key"
label="名称"
dense
filled
@update:model-value="
(val) => updateItemKey(val, getEditableIndex(index))
"
>
<template v-slot:prepend>
<q-icon name="code" />
</template>
</q-input>
</template>
</div> </div>
<div class="col"> <div class="col">
<VariableInput <VariableInput
@ -50,12 +77,14 @@
label="值" label="值"
icon="code" icon="code"
class="col-grow" class="col-grow"
@update:model-value="(val) => updateItemValue(val, index)" @update:model-value="
(val) => updateItemValue(val, index, item.type === 'fixed')
"
/> />
</div> </div>
<div class="col-auto"> <div v-if="item.type !== 'fixed'" class="col-auto">
<div class="btn-container"> <div class="btn-container">
<template v-if="items.length === 1"> <template v-if="editableItems.length === 1">
<q-btn <q-btn
flat flat
dense dense
@ -65,14 +94,16 @@
@click="addItem" @click="addItem"
/> />
</template> </template>
<template v-else-if="index === items.length - 1"> <template
v-else-if="getEditableIndex(index) === editableItems.length - 1"
>
<q-btn <q-btn
flat flat
dense dense
size="sm" size="sm"
icon="remove" icon="remove"
class="top-btn" class="top-btn"
@click="removeItem(index)" @click="removeItem(getEditableIndex(index))"
/> />
<q-btn <q-btn
flat flat
@ -90,7 +121,7 @@
size="sm" size="sm"
icon="remove" icon="remove"
class="center-btn" class="center-btn"
@click="removeItem(index)" @click="removeItem(getEditableIndex(index))"
/> />
</template> </template>
</div> </div>
@ -112,7 +143,8 @@ import BorderLabel from "components/composer/common/BorderLabel.vue";
* *
* @property {Object} modelValue - 绑定的字典对象 * @property {Object} modelValue - 绑定的字典对象
* @property {Object} options - 配置选项 * @property {Object} options - 配置选项
* @property {String[]} [options.items] - 键名的下拉选择选项 * @property {String[]|Object[]} [options.optionKeys] - 可选键名
* @property {String[]|Object[]} [options.fixedKeys] - 固定键名
* *
* @example * @example
* // * //
@ -164,40 +196,105 @@ export default defineComponent({
emits: ["update:modelValue"], emits: ["update:modelValue"],
data() { data() {
const modelEntries = Object.entries(this.modelValue || {}); const modelEntries = Object.entries(this.modelValue || {});
const fixedKeys = this.normalizeKeys(this.options?.fixedKeys || []);
const fixedKeyValues = fixedKeys.map((key) => ({
type: "fixed",
key: key.value,
value:
modelEntries.find(([k]) => k === key.value)?.[1] ||
newVarInputVal("str"),
}));
const editableEntries = modelEntries.filter(
([key]) => !fixedKeys.some((k) => k.value === key)
);
return { return {
localItems: modelEntries.length fixedItems: fixedKeyValues,
? modelEntries.map(([key, value]) => ({ key, value })) localItems: editableEntries.length
: [ ? editableEntries.map(([key, value]) => ({
{ type: "editable",
key: "", key,
value: newVarInputVal("str"), value,
}, }))
], : [{ type: "editable", key: "", value: newVarInputVal("str") }],
filterOptions: this.options?.items || [], filterOptions: this.normalizeKeys(this.options?.optionKeys || []),
inputValue: "", inputValue: "",
}; };
}, },
computed: { computed: {
items: { editableItems: {
get() { get() {
return this.localItems; return this.localItems;
}, },
set(newItems) { set(newItems) {
this.localItems = newItems; this.localItems = newItems;
const dict = {}; this.updateModelValue();
newItems.forEach((item) => {
if (item.key) {
dict[item.key] = item.value;
}
});
this.$emit("update:modelValue", dict);
}, },
}, },
allItems() {
return [...this.fixedItems, ...this.localItems];
},
normalizedOptionKeys() {
return this.filterOptions;
},
}, },
methods: { methods: {
normalizeKeys(keys) {
return keys.map((key) => {
if (typeof key === "string") {
return { value: key, label: key };
}
return key;
});
},
getKeyLabel(key) {
if (typeof key === "object") return key.label;
const allKeys = [
...this.normalizeKeys(this.options?.fixedKeys || []),
...this.normalizeKeys(this.options?.optionKeys || []),
];
return allKeys.find((k) => k.value === key)?.label || key;
},
getEditableIndex(index) {
return index - this.fixedItems.length;
},
updateModelValue() {
const dict = {};
//
this.fixedItems.forEach((item) => {
if (item.key) {
dict[item.key] = item.value;
}
});
//
this.localItems.forEach((item) => {
if (item.key) {
dict[item.key] = item.value;
}
});
this.$emit("update:modelValue", dict);
},
updateItemValue(val, index, isFixed = false) {
if (isFixed) {
const newItems = [...this.fixedItems];
newItems[index].value = val;
this.fixedItems = newItems;
} else {
const newItems = [...this.localItems];
newItems[index].value = val;
this.localItems = newItems;
}
this.updateModelValue();
},
addItem() { addItem() {
this.items = [ this.localItems = [
...this.items, ...this.localItems,
{ {
key: "", key: "",
value: newVarInputVal("str"), value: newVarInputVal("str"),
@ -205,7 +302,7 @@ export default defineComponent({
]; ];
}, },
removeItem(index) { removeItem(index) {
const newItems = [...this.items]; const newItems = [...this.localItems];
newItems.splice(index, 1); newItems.splice(index, 1);
if (newItems.length === 0) { if (newItems.length === 0) {
newItems.push({ newItems.push({
@ -213,45 +310,46 @@ export default defineComponent({
value: newVarInputVal("str"), value: newVarInputVal("str"),
}); });
} }
this.items = newItems; this.localItems = newItems;
}, },
updateItemKey(val, index) { updateItemKey(val, index) {
const newItems = [...this.items]; const newItems = [...this.localItems];
newItems[index].key = val; newItems[index].key = val;
this.items = newItems; this.localItems = newItems;
},
updateItemValue(val, index) {
const newItems = [...this.items];
newItems[index].value = val;
this.items = newItems;
}, },
handleInput(val, index) { handleInput(val, index) {
this.inputValue = val; this.inputValue = val;
if (val && !this.filterOptions.includes(val)) { if (val) {
const newItems = [...this.items]; const newItems = [...this.localItems];
newItems[index].key = val; newItems[index].key = val;
this.items = newItems; this.localItems = newItems;
this.updateModelValue();
} }
}, },
handleSelect(val, index) { handleSelect(val, index) {
this.inputValue = ""; this.inputValue = "";
const newItems = [...this.items]; const newItems = [...this.localItems];
newItems[index].key = val; newItems[index].key = val.value || val;
this.items = newItems; this.localItems = newItems;
this.updateModelValue();
}, },
handleBlur() { handleBlur() {
this.inputValue = ""; this.inputValue = "";
}, },
filterFn(val, update) { filterFn(val, update) {
if (!this.options?.items) return; if (!this.options?.optionKeys) return;
update(() => { update(() => {
if (val === "") { if (val === "") {
this.filterOptions = this.options.items; this.filterOptions = this.normalizeKeys(this.options.optionKeys);
} else { } else {
const needle = val.toLowerCase(); const needle = val.toLowerCase();
this.filterOptions = this.options.items.filter( this.filterOptions = this.normalizeKeys(
(v) => v.toLowerCase().indexOf(needle) > -1 this.options.optionKeys
).filter(
(v) =>
v.label.toLowerCase().indexOf(needle) > -1 ||
v.value.toLowerCase().indexOf(needle) > -1
); );
} }
}); });

View File

@ -102,7 +102,7 @@
<DictEditor <DictEditor
v-model="argvs.otherHeaders" v-model="argvs.otherHeaders"
:options="{ :options="{
items: commonHeaderOptions, optionKeys: commonHeaderOptions,
}" }"
@update:model-value="updateHeaders" @update:model-value="updateHeaders"
/> />

View File

@ -33,7 +33,7 @@ export const simulateCommands = {
], ],
}, },
{ {
value: "quickcomposer.simulate.sendText", value: "utools.hideMainWindowTypeString",
label: "发送文本", label: "发送文本",
config: [ config: [
{ {
@ -41,18 +41,73 @@ export const simulateCommands = {
label: "要发送的文本内容", label: "要发送的文本内容",
type: "varInput", type: "varInput",
icon: "send", icon: "send",
width: 9, width: 12,
},
],
subCommands: [
{
value: "utools.hideMainWindowTypeString",
label: "模拟输入",
icon: "keyboard",
}, },
{ {
label: "发送方式", value: "utools.hideMainWindowPasteText",
type: "select", label: "模拟粘贴",
defaultValue: false, icon: "content_paste",
icon: "keyboard", },
options: [ ],
{ label: "模拟输入", value: false }, },
{ label: "模拟粘贴", value: true }, {
value: "utools.hideMainWindowPasteFile",
label: "模拟粘贴文件/图片",
icon: "file_copy",
subCommands: [
{
value: "utools.hideMainWindowPasteFile",
label: "粘贴文件",
icon: "file_copy",
config: [
{
key: "file",
label: "文件路径",
type: "varInput",
icon: "description",
width: 12,
options: {
dialog: {
type: "open",
options: {
title: "选择文件",
properties: [
"openFile",
"multiSelections",
"showHiddenFiles",
],
},
},
},
},
],
},
{
value: "utools.hideMainWindowPasteImage",
label: "粘贴图片",
icon: "image",
config: [
{
key: "image",
label: "图片路径/base64",
type: "varInput",
icon: "image",
width: 12,
options: {
dialog: {
title: "选择图片",
properties: ["openFile", "showHiddenFiles"],
},
},
},
], ],
width: 3,
}, },
], ],
}, },

View File

@ -1,3 +1,5 @@
import { newVarInputVal } from "js/composer/varInputValManager";
export const utoolsCommands = { export const utoolsCommands = {
label: "uTools功能", label: "uTools功能",
icon: "insights", icon: "insights",
@ -83,5 +85,95 @@ export const utoolsCommands = {
}, },
], ],
}, },
{
value: "utools.findInPage",
label: "插件内查找",
desc: "插件内查找",
icon: "search",
subCommands: [
{
value: "utools.findInPage",
label: "查找文本",
desc: "查找文本",
icon: "search",
config: [
{
key: "text",
label: "文本",
type: "varInput",
icon: "search",
width: 12,
},
{
key: "options",
label: "选项",
type: "dictEditor",
icon: "settings",
options: {
fixedKeys: [
{
value: "forward",
label: "向前查找",
},
{
value: "findNext",
label: "查找下一个",
},
{
value: "matchCase",
label: "区分大小写",
},
{
value: "wordStart",
label: "单词开头",
},
{
value: "medialCapitalAsWordStart",
label: "中缀大写作为单词开头",
},
],
},
defaultValue: {
forward: newVarInputVal("var", "true"),
findNext: newVarInputVal("var", "false"),
matchCase: newVarInputVal("var", "false"),
wordStart: newVarInputVal("var", "false"),
medialCapitalAsWordStart: newVarInputVal("var", "false"),
},
width: 12,
},
],
},
{
value: "utools.stopFindInPage",
label: "停止查找",
desc: "停止查找",
icon: "stop",
config: [
{
key: "action",
label: "动作",
type: "buttonGroup",
icon: "settings",
width: 12,
options: [
{ label: "清除选择", value: "clearSelection" },
{ label: "保持选择", value: "keepSelection" },
{ label: "激活选择", value: "activateSelection" },
],
defaultValue: "clearSelection",
},
],
},
],
},
{
value: "utools.getWindowType",
label: "获取当前窗口类型",
desc: "获取当前窗口类型",
icon: "window",
outputVariable: "windowType",
saveOutput: true,
},
], ],
}; };

View File

@ -424,6 +424,10 @@ interface UToolsApi {
* *
*/ */
startDrag(file: string | string[]): void; startDrag(file: string | string[]): void;
/**
*
*/
getWindowType(): "main" | "detach" | "browser";
/** /**
* *
*/ */