模拟按键新增常用按键,模拟操作新增按键序列,系统操作新增关闭进程,新增uTools功能分类,添加部分命令

This commit is contained in:
fofolee 2025-01-09 01:01:15 +08:00
parent d2c3b7999c
commit b676801b9a
10 changed files with 1277 additions and 68 deletions

View File

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

View File

@ -0,0 +1,34 @@
const keyboardTap = (keys, options = {}) => {
const { repeatCount = 1, repeatInterval = 0, keyDelay = 0 } = options;
// 执行重复操作
const repeat = () => {
for (let i = 0; i < repeatCount; i++) {
// 执行按键操作
window.utools.simulateKeyboardTap(...keys);
// 如果有重复间隔且不是最后一次,则等待
if (repeatInterval > 0 && i < repeatCount - 1) {
quickcommand.sleep(repeatInterval);
}
}
// 如果有按键后延迟,则等待
if (keyDelay > 0) {
quickcommand.sleep(keyDelay);
}
};
return repeat();
};
const keySequence = (sequence, { interval = 100 } = {}) => {
sequence.forEach((keys, index) => {
keyboardTap(keys);
if (index < sequence.length - 1) {
quickcommand.sleep(interval);
}
});
};
module.exports = { keyboardTap, keySequence };

View File

@ -51,7 +51,52 @@
</q-badge>
</template>
<template v-slot:append>
<q-btn
flat
dense
round
icon="more_vert"
color="primary"
class="q-ml-sm"
@click.stop
>
<q-menu anchor="bottom right" self="top right">
<q-list style="min-width: 200px">
<template
v-for="(shortcut, index) in commonShortcuts"
:key="index"
>
<template v-if="shortcut.header">
<q-item-label
v-if="shortcut.show === undefined || shortcut.show"
header
class="q-mt-sm"
>
{{ shortcut.label }}
</q-item-label>
<q-separator
v-if="shortcut.show === undefined || shortcut.show"
/>
</template>
<q-item
v-else-if="shortcut.show === undefined || shortcut.show"
clickable
v-close-popup
@click="applyShortcut(shortcut)"
>
<q-item-section>
<q-item-label>{{ shortcut.label }}</q-item-label>
</q-item-section>
<q-item-section side>
<q-item-label caption>
{{ formatShortcut(shortcut) }}
</q-item-label>
</q-item-section>
</q-item>
</template>
</q-list>
</q-menu>
</q-btn>
</template>
</q-select>
<!-- 录制按钮 -->
@ -104,11 +149,250 @@
<script>
import { defineComponent } from "vue";
import NumberInput from "../common/NumberInput.vue";
import NumberInput from "components/composer/common/NumberInput.vue";
import { parseFunction } from "js/composer/formatString";
//
const isMac = window.utools.isMacOs();
//
const commonKeys = [
{ label: "Enter ↵", value: "enter" },
{ label: "Tab ⇥", value: "tab" },
{ label: "Space", value: "space" },
{ label: "Backspace ⌫", value: "backspace" },
{ label: "Delete ⌦", value: "delete" },
{ label: "Escape ⎋", value: "escape" },
{ label: "↑", value: "up" },
{ label: "↓", value: "down" },
{ label: "←", value: "left" },
{ label: "→", value: "right" },
{ label: "Home", value: "home" },
{ label: "End", value: "end" },
{ label: "Page Up", value: "pageup" },
{ label: "Page Down", value: "pagedown" },
];
//
const commonShortcuts = [
// Windows
{
label: "Windows 快捷键",
header: true,
show: !isMac,
},
{
label: "任务管理器",
mainKey: "delete",
modifiers: { control: true, shift: true, alt: true },
show: !isMac,
},
{
label: "运行",
mainKey: "r",
modifiers: { command: true },
show: !isMac,
},
{
label: "切换到地址栏",
mainKey: "l",
modifiers: { control: true },
show: !isMac,
},
{
label: "切换窗口",
mainKey: "tab",
modifiers: { alt: true },
show: !isMac,
},
{
label: "锁定电脑",
mainKey: "l",
modifiers: { command: true },
show: !isMac,
},
{
label: "显示桌面",
mainKey: "d",
modifiers: { command: true },
show: !isMac,
},
{
label: "文件资源管理器",
mainKey: "e",
modifiers: { command: true },
show: !isMac,
},
{
label: "快速访问设置",
mainKey: "a",
modifiers: { command: true },
show: !isMac,
},
{
label: "打开开始菜单",
mainKey: "escape",
modifiers: { control: true },
show: !isMac,
},
{
label: "系统属性",
mainKey: "pause",
modifiers: { command: true },
show: !isMac,
},
// macOS
{
label: "macOS 快捷键",
header: true,
show: isMac,
},
{
label: "强制退出",
mainKey: "escape",
modifiers: { command: true, alt: true },
show: isMac,
},
{
label: "截图",
mainKey: "3",
modifiers: { command: true, shift: true },
show: isMac,
},
{
label: "区域截图",
mainKey: "4",
modifiers: { command: true, shift: true },
show: isMac,
},
{
label: "前往文件夹",
mainKey: "g",
modifiers: { command: true, shift: true },
show: isMac,
},
{
label: "新建文件夹",
mainKey: "n",
modifiers: { command: true, shift: true },
show: isMac,
},
{
label: "显示隐藏文件",
mainKey: ".",
modifiers: { command: true, shift: true },
show: isMac,
},
{
label: "访达偏好设置",
mainKey: ",",
modifiers: { command: true },
show: isMac,
},
{
label: "清空废纸篓",
mainKey: "backspace",
modifiers: { command: true, shift: true },
show: isMac,
},
{
label: "排序方式",
mainKey: "j",
modifiers: { command: true },
show: isMac,
},
{
label: "显示简介",
mainKey: "i",
modifiers: { command: true },
show: isMac,
},
{
label: "最小化所有窗口",
mainKey: "m",
modifiers: { command: true, alt: true },
show: isMac,
},
{
label: "调度中心",
mainKey: "up",
modifiers: { control: true },
show: isMac,
},
//
{
label: "通用操作",
header: true,
},
{
label: "复制",
mainKey: "c",
modifiers: isMac ? { command: true } : { control: true },
},
{
label: "粘贴",
mainKey: "v",
modifiers: isMac ? { command: true } : { control: true },
},
{
label: "剪切",
mainKey: "x",
modifiers: isMac ? { command: true } : { control: true },
},
{
label: "撤销",
mainKey: "z",
modifiers: isMac ? { command: true } : { control: true },
},
{
label: "重做",
mainKey: isMac ? "z" : "y",
modifiers: isMac ? { command: true, shift: true } : { control: true },
},
{
label: "全选",
mainKey: "a",
modifiers: isMac ? { command: true } : { control: true },
},
{
label: "保存",
mainKey: "s",
modifiers: isMac ? { command: true } : { control: true },
},
{
label: "查找",
mainKey: "f",
modifiers: isMac ? { command: true } : { control: true },
},
{
label: "关闭窗口",
mainKey: isMac ? "w" : "f4",
modifiers: isMac ? { command: true } : { alt: true },
},
{
label: "刷新",
mainKey: "r",
modifiers: isMac ? { command: true } : { control: true },
},
{
label: "新建",
mainKey: "n",
modifiers: isMac ? { command: true } : { control: true },
},
{
label: "打印",
mainKey: "p",
modifiers: isMac ? { command: true } : { control: true },
},
{
label: "删除",
mainKey: "delete",
modifiers: {},
},
];
export default defineComponent({
name: "KeyEditor",
components: {
@ -149,22 +433,6 @@ export default defineComponent({
shift: "Shift",
command: "Win",
},
commonKeys: [
{ label: "Enter ↵", value: "enter" },
{ label: "Tab ⇥", value: "tab" },
{ label: "Space", value: "space" },
{ label: "Backspace ⌫", value: "backspace" },
{ label: "Delete ⌦", value: "delete" },
{ label: "Escape ⎋", value: "escape" },
{ label: "↑", value: "up" },
{ label: "↓", value: "down" },
{ label: "←", value: "left" },
{ label: "→", value: "right" },
{ label: "Home", value: "home" },
{ label: "End", value: "end" },
{ label: "Page Up", value: "pageup" },
{ label: "Page Down", value: "pagedown" },
],
};
},
computed: {
@ -196,6 +464,12 @@ export default defineComponent({
this.argvs.mainKey.slice(1))
);
},
commonKeys() {
return commonKeys;
},
commonShortcuts() {
return commonShortcuts;
},
},
methods: {
toggleModifier(key) {
@ -327,7 +601,7 @@ export default defineComponent({
// Mac command meta
.map((key) => (!isMac && key === "command" ? "meta" : key));
const args = [argvs.mainKey, ...activeModifiers];
const keys = [argvs.mainKey, ...activeModifiers];
//
const options = {};
@ -337,11 +611,11 @@ export default defineComponent({
if (argvs.keyDelay > 0) options.keyDelay = argvs.keyDelay;
if (Object.keys(options).length > 0) {
return `${this.modelValue.value}("${args.join(
'","'
)}", ${JSON.stringify(options)})`;
return `${this.modelValue.value}(${JSON.stringify(
keys
)}, ${JSON.stringify(options)})`;
}
return `${this.modelValue.value}("${args.join('","')}")`;
return `${this.modelValue.value}(${JSON.stringify(keys)})`;
},
updateValue(argv) {
const newArgvs = {
@ -358,35 +632,27 @@ export default defineComponent({
const argvs = window.lodashM.cloneDeep(this.defaultArgvs);
if (!code) return argvs;
try {
// keyTap
const cleanVal = code.replace(/^keyTap\("/, "").replace(/"\)$/, "");
//
const parts = cleanVal.split(/,\s*/);
const keyParts = parts[0]
.split('","')
.map((arg) => arg.replace(/^"|"$/g, ""));
const result = parseFunction(code);
if (!result || !result.argvs || !result.argvs[0]) return argvs;
if (keyParts.length > 0) {
argvs.mainKey = keyParts[0];
const keys = result.argvs[0];
const options = result.argvs[1] || {};
if (keys.length > 0) {
argvs.mainKey = keys[0];
Object.keys(argvs.modifiers).forEach((key) => {
// Mac meta command
const modKey =
!isMac && keyParts.includes("meta") ? "command" : key;
argvs.modifiers[key] = keyParts.includes(modKey);
const modKey = !isMac && key === "command" ? "meta" : key;
argvs.modifiers[key] = keys.slice(1).includes(modKey);
});
}
//
if (parts.length > 1) {
try {
const options = JSON.parse(parts[1]);
if (options) {
if (options.repeatCount) argvs.repeatCount = options.repeatCount;
if (options.repeatInterval)
argvs.repeatInterval = options.repeatInterval;
if (options.keyDelay) argvs.keyDelay = options.keyDelay;
} catch (e) {
console.warn("Failed to parse key options:", e);
}
}
return argvs;
@ -421,6 +687,22 @@ export default defineComponent({
this.updateValue({ mainKey: val.data });
}
},
applyShortcut(shortcut) {
this.updateValue({
mainKey: shortcut.mainKey,
modifiers: {
...this.defaultArgvs.modifiers,
...shortcut.modifiers,
},
});
},
formatShortcut(shortcut) {
const modifiers = Object.entries(shortcut.modifiers)
.filter(([_, active]) => active)
.map(([key]) => this.modifierLabels[key])
.join(" + ");
return `${modifiers} + ${shortcut.mainKey}`;
},
},
mounted() {
if (!this.modelValue.code && !this.modelValue.argvs) {

View File

@ -0,0 +1,761 @@
<template>
<div class="key-sequence-editor">
<!-- 录制控制区 -->
<div class="row items-center justify-between q-mb-sm">
<div class="row items-center q-gutter-sm">
<q-btn
:color="isRecording ? 'negative' : 'primary'"
:icon="isRecording ? 'stop' : 'fiber_manual_record'"
:label="isRecording ? '停止' : '录制'"
dense
size="sm"
class="recording-btn"
@click="toggleRecording"
:style="{
height: '36px',
}"
>
<q-tooltip>{{
isRecording ? "停止录制" : "开始录制按键序列"
}}</q-tooltip>
</q-btn>
<q-btn-group
unelevated
:style="{
height: '36px',
}"
>
<!-- 通用快捷键 -->
<q-btn size="sm" flat color="primary" icon="keyboard">
<q-tooltip>通用快捷键</q-tooltip>
<q-menu anchor="bottom left" self="top left" :offset="[0, 4]">
<q-list style="min-width: 150px">
<q-item-label header class="text-primary"
>通用快捷键</q-item-label
>
<q-item
v-for="item in commonShortcuts"
:key="item.label"
clickable
dense
@click="appendSequence(item.sequence)"
>
<q-item-section>{{ item.label }}</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
<!-- Vim快捷键 -->
<q-btn size="sm" flat color="primary" icon="code">
<q-tooltip>Vim快捷键</q-tooltip>
<q-menu anchor="bottom left" self="top left" :offset="[0, 4]">
<q-list style="min-width: 150px">
<q-item-label header class="text-primary"
>Vim快捷键</q-item-label
>
<q-item
v-for="item in vimShortcuts"
:key="item.label"
clickable
dense
@click="appendSequence(item.sequence)"
>
<q-item-section>{{ item.label }}</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
<!-- Tmux快捷键 -->
<q-btn size="sm" flat color="primary" icon="terminal">
<q-tooltip>Tmux快捷键</q-tooltip>
<q-menu anchor="bottom left" self="top left" :offset="[0, 4]">
<q-list style="min-width: 150px">
<q-item-label header class="text-primary"
>Tmux快捷键</q-item-label
>
<q-item
v-for="item in tmuxShortcuts"
:key="item.label"
clickable
dense
@click="appendSequence(item.sequence)"
>
<q-item-section>{{ item.label }}</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
<!-- Chrome快捷键 -->
<q-btn size="sm" flat color="primary" icon="public">
<q-tooltip>Chrome快捷键</q-tooltip>
<q-menu anchor="bottom left" self="top left" :offset="[0, 4]">
<q-list style="min-width: 150px">
<q-item-label header class="text-primary"
>Chrome快捷键</q-item-label
>
<q-item
v-for="item in chromeShortcuts"
:key="item.label"
clickable
v-close-popup
dense
@click="appendSequence(item.sequence)"
>
<q-item-section>{{ item.label }}</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</q-btn-group>
<number-input
v-if="argvs.sequence.length > 1"
:model-value="argvs.interval"
label="间隔(ms)"
icon="timer"
class="q-ml-md interval-input"
@update:model-value="updateInterval"
/>
</div>
<div class="row items-center" v-if="argvs.sequence.length > 0">
<q-badge color="primary" text-color="white" class="q-mr-xs">
{{ argvs.sequence.length }}
</q-badge>
<span class="text-grey-7 text-caption">个按键</span>
<q-btn
flat
dense
round
size="sm"
color="grey-7"
icon="clear_all"
class="q-ml-xs"
@click="clearSequence"
>
<q-tooltip>清空序列</q-tooltip>
</q-btn>
</div>
</div>
<!-- 序列显示区 -->
<div v-if="argvs.sequence.length > 0" class="sequence-list q-mb-sm">
<div class="row q-col-gutter-sm">
<draggable
v-model="argvs.sequence"
item-key="id"
handle=".drag-handle"
:animation="200"
ghost-class="ghost"
class="row full-width"
@change="updateValue"
>
<template #item="{ element, index }">
<div class="col-4 q-py-xs">
<div
class="row items-center justify-center no-wrap hover-show-actions sequence-item"
>
<!-- 拖拽手柄 -->
<div class="col-auto q-mr-xs cursor-move drag-handle">
<q-icon name="drag_indicator" size="14px" color="grey-7" />
</div>
<!-- 序号 -->
<div class="col-auto q-mr-xs text-grey-7 sequence-number">
{{ index + 1 }}.
</div>
<!-- 按键显示 -->
<div class="row items-center justify-center no-wrap">
<!-- 修饰键 -->
<template
v-for="(active, key) in element.modifiers"
:key="key"
>
<q-chip v-if="active" dense square class="modifier-chip">
{{ modifierLabels[key] }}
</q-chip>
</template>
<!-- 主按键 -->
<q-chip
color="primary"
text-color="white"
dense
square
class="main-key"
>
{{ formatMainKey(element.mainKey) }}
</q-chip>
</div>
<!-- 操作按钮 -->
<div class="col-auto action-buttons">
<q-btn
flat
round
dense
size="xs"
color="grey-7"
icon="close"
@click="removeKey(index)"
>
<q-tooltip>删除此按键</q-tooltip>
</q-btn>
</div>
</div>
</div>
</template>
</draggable>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from "vue";
import NumberInput from "../common/NumberInput.vue";
import draggable from "vuedraggable";
import { parseFunction } from "js/composer/formatString";
//
const isMac = window.utools.isMacOs();
//
const commonShortcuts = [
{
label: "复制",
sequence: [{ mainKey: "c", modifiers: { command: true } }],
},
{
label: "粘贴",
sequence: [{ mainKey: "v", modifiers: { command: true } }],
},
{
label: "剪切",
sequence: [{ mainKey: "x", modifiers: { command: true } }],
},
{
label: "全选",
sequence: [{ mainKey: "a", modifiers: { command: true } }],
},
{
label: "撤销",
sequence: [{ mainKey: "z", modifiers: { command: true } }],
},
{
label: "重做",
sequence: [{ mainKey: "z", modifiers: { command: true, shift: true } }],
},
{
label: "保存",
sequence: [{ mainKey: "s", modifiers: { command: true } }],
},
{
label: "查找",
sequence: [{ mainKey: "f", modifiers: { command: true } }],
},
{
label: "替换",
sequence: [{ mainKey: "h", modifiers: { command: true } }],
},
{
label: "关闭窗口",
sequence: [{ mainKey: "w", modifiers: { command: true } }],
},
];
// Vim
const vimShortcuts = [
{
label: "保存",
sequence: [
{ mainKey: "escape", modifiers: {} },
{ mainKey: ":", modifiers: { shift: true } },
{ mainKey: "w", modifiers: {} },
{ mainKey: "enter", modifiers: {} },
],
},
{
label: "退出",
sequence: [
{ mainKey: "escape", modifiers: {} },
{ mainKey: ":", modifiers: { shift: true } },
{ mainKey: "q", modifiers: {} },
{ mainKey: "enter", modifiers: {} },
],
},
{
label: "强制退出",
sequence: [
{ mainKey: "escape", modifiers: {} },
{ mainKey: ":", modifiers: { shift: true } },
{ mainKey: "q", modifiers: {} },
{ mainKey: "!", modifiers: { shift: true } },
{ mainKey: "enter", modifiers: {} },
],
},
{
label: "删除整行",
sequence: [
{ mainKey: "d", modifiers: {} },
{ mainKey: "d", modifiers: {} },
],
},
{
label: "复制整行",
sequence: [
{ mainKey: "y", modifiers: {} },
{ mainKey: "y", modifiers: {} },
],
},
{
label: "粘贴",
sequence: [{ mainKey: "p", modifiers: {} }],
},
{
label: "行首",
sequence: [{ mainKey: "0", modifiers: {} }],
},
{
label: "行尾",
sequence: [{ mainKey: "$", modifiers: { shift: true } }],
},
{
label: "文件头",
sequence: [
{ mainKey: "g", modifiers: {} },
{ mainKey: "g", modifiers: {} },
],
},
{
label: "文件尾",
sequence: [
{ mainKey: "g", modifiers: {} },
{ mainKey: "g", modifiers: {} },
],
},
];
// Tmux
const tmuxShortcuts = [
{
label: "新建窗口",
sequence: [
{ mainKey: "b", modifiers: { control: true } },
{ mainKey: "c", modifiers: {} },
],
},
{
label: "水平分割",
sequence: [
{ mainKey: "b", modifiers: { control: true } },
{ mainKey: '"', modifiers: { shift: true } },
],
},
{
label: "垂直分割",
sequence: [
{ mainKey: "b", modifiers: { control: true } },
{ mainKey: "%", modifiers: { shift: true } },
],
},
{
label: "切换窗口",
sequence: [
{ mainKey: "b", modifiers: { control: true } },
{ mainKey: "n", modifiers: {} },
],
},
{
label: "关闭面板",
sequence: [
{ mainKey: "b", modifiers: { control: true } },
{ mainKey: "x", modifiers: {} },
],
},
];
// Chrome
const chromeShortcuts = [
{
label: "新标签页",
sequence: [{ mainKey: "t", modifiers: { command: true } }],
},
{
label: "关闭标签页",
sequence: [{ mainKey: "w", modifiers: { command: true } }],
},
{
label: "重新打开关闭的标签页",
sequence: [{ mainKey: "t", modifiers: { command: true, shift: true } }],
},
{
label: "切换到下一个标签页",
sequence: [{ mainKey: "tab", modifiers: { control: true } }],
},
{
label: "切换到上一个标签页",
sequence: [{ mainKey: "tab", modifiers: { control: true, shift: true } }],
},
];
export default defineComponent({
name: "KeySequenceEditor",
components: {
NumberInput,
draggable,
},
props: {
modelValue: {
type: Object,
required: true,
},
},
data() {
return {
isRecording: false,
defaultArgvs: {
sequence: [],
interval: 100,
},
modifierLabels: isMac
? {
control: "⌃",
alt: "⌥",
shift: "⇧",
command: "⌘",
}
: {
control: "Ctrl",
alt: "Alt",
shift: "Shift",
command: "Win",
},
commonShortcuts,
vimShortcuts,
tmuxShortcuts,
chromeShortcuts,
};
},
computed: {
argvs() {
return (
this.modelValue.argvs || this.parseCodeToArgvs(this.modelValue.code)
);
},
},
methods: {
toggleRecording() {
if (!this.isRecording) {
this.startRecording();
} else {
this.stopRecording();
}
},
startRecording() {
this.isRecording = true;
this.recordEvent = (event) => {
event.preventDefault();
//
const keyRecord = {
mainKey: "",
modifiers: {
control: false,
alt: false,
shift: false,
command: false,
},
id: Date.now() + Math.random(),
};
//
if (isMac) {
if (event.metaKey) keyRecord.modifiers.command = true;
if (event.ctrlKey) keyRecord.modifiers.control = true;
} else {
if (event.ctrlKey) keyRecord.modifiers.control = true;
if (event.metaKey || event.winKey) keyRecord.modifiers.command = true;
}
if (event.altKey) keyRecord.modifiers.alt = true;
if (event.shiftKey) keyRecord.modifiers.shift = true;
//
let key = null;
//
if (event.code.startsWith("Key")) {
key = event.code.slice(-1).toLowerCase();
}
//
else if (event.code.startsWith("Digit")) {
key = event.code.slice(-1);
}
//
else if (event.code.startsWith("F") && !isNaN(event.code.slice(1))) {
key = event.code.toLowerCase();
}
//
else {
const keyMap = {
ArrowUp: "up",
ArrowDown: "down",
ArrowLeft: "left",
ArrowRight: "right",
Enter: "enter",
Space: "space",
Escape: "escape",
Delete: "delete",
Backspace: "backspace",
Tab: "tab",
Home: "home",
End: "end",
PageUp: "pageup",
PageDown: "pagedown",
};
key = keyMap[event.code] || event.key.toLowerCase();
}
//
if (!["control", "alt", "shift", "command", "meta"].includes(key)) {
keyRecord.mainKey = key;
this.argvs.sequence.push(keyRecord);
this.updateValue();
}
};
document.addEventListener("keydown", this.recordEvent);
},
stopRecording() {
this.isRecording = false;
document.removeEventListener("keydown", this.recordEvent);
},
removeKey(index) {
this.argvs.sequence.splice(index, 1);
this.updateValue();
},
clearSequence() {
this.argvs.sequence = [];
this.updateValue();
},
updateInterval(value) {
this.argvs.interval = value;
this.updateValue();
},
formatMainKey(key) {
if (!key) return "";
//
const specialKeyMap = {
enter: "↵",
tab: "⇥",
space: "␣",
backspace: "⌫",
delete: "⌦",
escape: "⎋",
up: "↑",
down: "↓",
left: "←",
right: "→",
};
return (
specialKeyMap[key] ||
(key.length === 1
? key.toUpperCase()
: key.charAt(0).toUpperCase() + key.slice(1))
);
},
generateCode() {
if (this.argvs.sequence.length === 0) return;
//
const keySequence = this.argvs.sequence.map((item) => {
const activeModifiers = Object.entries(item.modifiers)
.filter(([_, active]) => active)
.map(([key]) => key)
// Mac command meta
.map((key) => (!isMac && key === "command" ? "meta" : key));
return [item.mainKey, ...activeModifiers];
});
//
const options =
this.argvs.sequence.length > 1 ? { interval: this.argvs.interval } : {};
if (Object.keys(options).length > 0) {
return `${this.modelValue.value}(${JSON.stringify(
keySequence
)}, ${JSON.stringify(options)})`;
}
return `${this.modelValue.value}(${JSON.stringify(keySequence)})`;
},
updateValue() {
this.$emit("update:modelValue", {
...this.modelValue,
argvs: this.argvs,
code: this.generateCode(),
});
},
appendSequence(newSequence) {
const startId = Date.now();
this.argvs.sequence.push(
...newSequence.map((item, index) => ({
...item,
id: startId + index,
}))
);
this.updateValue();
},
parseCodeToArgvs(code) {
const argvs = window.lodashM.cloneDeep(this.defaultArgvs);
if (!code) return argvs;
try {
const match = code.match(/\((.*)\)/s);
if (match) {
const args = eval(`[${match[1]}]`);
if (Array.isArray(args[0])) {
argvs.sequence = args[0].map((keys) => {
const mainKey = keys[0];
const modifiers = {
control: keys.includes("control"),
alt: keys.includes("alt"),
shift: keys.includes("shift"),
command:
!isMac && keys.includes("meta")
? true
: keys.includes("command"),
};
return { mainKey, modifiers, id: Date.now() + Math.random() };
});
if (args[1] && args[1].interval) {
argvs.interval = args[1].interval;
}
}
}
} catch (e) {
console.error("Failed to parse existing code:", e);
}
return argvs;
},
},
mounted() {
if (!this.modelValue.code && !this.modelValue.argvs) {
this.updateValue();
}
},
});
</script>
<style scoped>
.key-sequence-editor {
padding: 4px;
}
.recording-btn {
min-width: 70px;
height: 28px;
font-size: 14px;
}
.recording-btn :deep(.q-icon.on-left) {
margin-right: 2px;
}
.sequence-list {
border: 1px solid var(--q-primary);
border-radius: 4px;
padding: 4px;
max-height: 300px;
overflow-y: auto;
}
.sequence-item {
user-select: none;
padding: 2px 4px;
border-radius: 4px;
transition: background-color 0.2s;
min-height: 28px;
}
.sequence-item > div {
flex: 0 0 auto;
}
.sequence-item > .row {
flex: 1 1 auto;
}
.sequence-item:hover {
background-color: rgba(0, 0, 0, 0.03);
}
.body--dark .sequence-item:hover {
background-color: rgba(255, 255, 255, 0.03);
}
.sequence-number {
font-size: 11px;
min-width: 14px;
opacity: 0.7;
}
.modifier-chip {
height: 16px;
font-size: 11px;
margin: 0 1px;
background-color: var(--q-primary);
color: white;
min-width: 32px;
padding: 0 4px;
}
.main-key {
height: 16px;
font-size: 11px;
margin: 0 1px;
min-width: 32px;
padding: 0 4px;
}
.modifier-chip :deep(.q-chip__content),
.main-key :deep(.q-chip__content) {
justify-content: center;
text-align: center;
}
.hover-show-actions .action-buttons {
opacity: 0;
transition: opacity 0.2s;
}
.hover-show-actions:hover .action-buttons {
opacity: 1;
}
.ghost {
opacity: 0.5;
background: var(--q-primary) !important;
}
/* 滚动条样式 */
.sequence-list::-webkit-scrollbar {
width: 4px;
}
.sequence-list::-webkit-scrollbar-track {
background: transparent;
}
.sequence-list::-webkit-scrollbar-thumb {
background: var(--q-primary);
border-radius: 2px;
}
.interval-input {
width: 130px;
}
.drag-handle {
cursor: grab;
}
</style>

View File

@ -1,14 +1,17 @@
import { defineAsyncComponent } from "vue";
// UI Components
// 模拟操作组件
export const KeyEditor = defineAsyncComponent(() =>
import("src/components/composer/simulate/KeyEditor.vue")
);
export const ImageSearchEditor = defineAsyncComponent(() =>
import("components/composer/simulate/ImageSearchEditor.vue")
);
export const KeySequenceEditor = defineAsyncComponent(() =>
import("src/components/composer/simulate/KeySequenceEditor.vue")
);
// Control Flow Components
// 控制流组件
export const ConditionalJudgment = defineAsyncComponent(() =>
import("components/composer/control/ConditionalJudgment.vue")
);
@ -31,18 +34,24 @@ export const TryCatchControl = defineAsyncComponent(() =>
import("components/composer/control/TryCatchControl.vue")
);
// Editor Components
// 网络组件
export const UBrowserEditor = defineAsyncComponent(() =>
import("components/composer/ubrowser/UBrowserEditor.vue")
);
export const AxiosConfigEditor = defineAsyncComponent(() =>
import("src/components/composer/network/AxiosConfigEditor.vue")
);
// 数据组件
export const RegexEditor = defineAsyncComponent(() =>
import("components/composer/data/regex/RegexEditor.vue")
);
// Crypto Components
export const ZlibEditor = defineAsyncComponent(() =>
import("src/components/composer/data/ZlibEditor.vue")
);
// 加密组件
export const SymmetricCryptoEditor = defineAsyncComponent(() =>
import("src/components/composer/coding/SymmetricCryptoEditor.vue")
);
@ -50,20 +59,17 @@ export const AsymmetricCryptoEditor = defineAsyncComponent(() =>
import("src/components/composer/coding/AsymmetricCryptoEditor.vue")
);
// File Components
// 文件组件
export const FileOperationEditor = defineAsyncComponent(() =>
import("components/composer/file/FileOperationEditor.vue")
);
// System Components
// 系统组件
export const SystemCommandEditor = defineAsyncComponent(() =>
import("components/composer/system/SystemCommandEditor.vue")
);
export const ZlibEditor = defineAsyncComponent(() =>
import("src/components/composer/data/ZlibEditor.vue")
);
// UI组件
export const SelectListEditor = defineAsyncComponent(() =>
import("components/composer/ui/SelectListEditor.vue")
);

View File

@ -10,12 +10,14 @@ import { uiCommands } from "./uiCommands";
import { codingCommands } from "./codingCommand";
import { mathCommands } from "./mathCommands";
import { userdataCommands } from "./userdataCommands";
import { utoolsCommands } from "./utoolsCommand";
export const commandCategories = [
fileCommands,
networkCommands,
systemCommands,
notifyCommands,
utoolsCommands,
dataCommands,
codingCommands,
controlCommands,

View File

@ -3,18 +3,6 @@ export const otherCommands = {
icon: "more_horiz",
defaultOpened: false,
commands: [
{
value: "utools.redirect",
label: "转至指定插件",
config: [
{
key: "pluginName",
label: "要跳转至的插件名称",
type: "varInput",
icon: "alt_route",
},
],
},
{
value: "quickcommand.sleep",
label: "添加延时",

View File

@ -4,11 +4,34 @@ export const simulateCommands = {
defaultOpened: false,
commands: [
{
value: "utools.simulateKeyboardTap",
value: "quickcomposer.simulate.keyboardTap",
label: "模拟按键",
config: [],
component: "KeyEditor",
},
{
value: "quickcomposer.simulate.keySequence",
label: "按键序列",
description: "按顺序执行多个按键操作",
component: "KeySequenceEditor",
},
{
value: "quickcommand.simulateCopy",
label: "模拟复制粘贴",
config: [],
functionSelector: [
{
value: "quickcommand.simulateCopy",
label: "复制",
icon: "content_copy",
},
{
value: "quickcommand.simulatePaste",
label: "粘贴",
icon: "content_paste",
},
],
},
{
value: "quickcomposer.simulate.sendText",
label: "发送文本",

View File

@ -1,3 +1,5 @@
import { newVarInputVal } from "js/composer/varInputValManager";
export const systemCommands = {
label: "系统操作",
icon: "computer",
@ -299,5 +301,26 @@ export const systemCommands = {
},
],
},
{
value: "quickcommand.kill",
label: "关闭进程",
desc: "关闭指定进程",
icon: "dangerous",
config: [
{
label: "进程ID",
type: "numInput",
icon: "developer_board",
width: 6,
},
{
label: "信号",
type: "varInput",
icon: "signal_cellular_alt",
defaultValue: newVarInputVal("str", "SIGTERM"),
width: 6,
},
],
},
],
};

View File

@ -0,0 +1,87 @@
export const utoolsCommands = {
label: "uTools功能",
icon: "insights",
commands: [
{
value: "utools.hideMainWindow",
label: "隐藏主窗口",
desc: "隐藏主窗口",
icon: "visibility_off",
},
{
value: "quickcommand.wakeUtools",
label: "唤醒uTools",
desc: "唤醒uTools",
icon: "visibility",
},
{
value: "utools.setExpendHeight",
label: "设置uTools高度",
desc: "设置uTools高度",
icon: "height",
config: [
{
key: "height",
label: "高度",
type: "numInput",
icon: "straighten",
width: 12,
},
],
},
{
value: "utools.outPlugin",
label: "退出插件",
desc: "退出插件",
icon: "exit_to_app",
config: [
{
key: "isKill",
type: "select",
options: [
{ label: "杀死插件进程", value: true },
{ label: "插件隐藏到后台", value: false },
],
defaultValue: false,
icon: "logout",
},
],
},
{
value: "utools.isDarkColors",
label: "是否深色模式",
desc: "是否深色模式",
icon: "dark_mode",
outputVariable: "isDark",
saveOutput: true,
},
{
value: "utools.getUser",
label: "获取用户信息",
desc: "获取用户信息",
icon: "person",
outputVariable: "{avatar,nickname,type}",
saveOutput: true,
},
{
value: "utools.redirect",
label: "转至指定插件",
config: [
{
key: "pluginName",
label: "要跳转至的插件名称",
type: "varInput",
icon: "alt_route",
width: 6,
},
{
key: "payload",
label: "传递给插件的文本",
type: "varInput",
icon: "alt_route",
width: 6,
},
],
},
],
};