实现基本的命令可视化编排

This commit is contained in:
fofolee 2024-12-22 14:34:17 +08:00
parent dece5dbd11
commit 57de0b651b
7 changed files with 1188 additions and 2 deletions

View File

@ -38,7 +38,12 @@
<q-btn-group unelevated class="button-group">
<template v-if="modelValue.program === 'quickcommand'">
<q-btn
v-for="(item, index) in ['keyboard', 'rocket_launch', 'help_center']"
v-for="(item, index) in [
'keyboard',
'rocket_launch',
'help_center',
'view_timeline',
]"
:key="index"
dense
flat
@ -48,7 +53,7 @@
@click="handleQuickCommandAction(index)"
>
<q-tooltip>
{{ ["录制按键", "快捷动作", "查看文档"][index] }}
{{ ["录制按键", "快捷动作", "查看文档", "可视化编排"][index] }}
</q-tooltip>
</q-btn>
</template>
@ -163,18 +168,32 @@
<q-dialog v-model="showRecorder" position="bottom">
<KeyRecorder @sendKeys="addAction" />
</q-dialog>
<q-dialog v-model="showComposer" maximized>
<q-card class="full-height">
<q-card-section class="q-pa-md full-height">
<CommandComposer
ref="composer"
@run="handleComposerRun"
@apply="handleComposerApply"
@update:model-value="showComposer = false"
/>
</q-card-section>
</q-card>
</q-dialog>
</div>
</template>
<script>
import QuickAction from "components/popup/QuickAction";
import KeyRecorder from "components/popup/KeyRecorder";
import CommandComposer from "components/editor/composer/CommandComposer.vue";
export default {
name: "CommandLanguageBar",
components: {
QuickAction,
KeyRecorder,
CommandComposer,
},
props: {
modelValue: {
@ -198,6 +217,7 @@ export default {
return {
showActions: false,
showRecorder: false,
showComposer: false,
};
},
emits: [
@ -267,6 +287,7 @@ export default {
() => (this.showRecorder = true),
() => (this.showActions = true),
() => this.showHelp(),
() => (this.showComposer = true),
];
actions[index]();
},
@ -276,6 +297,20 @@ export default {
showHelp() {
window.showUb.docs();
},
handleComposerRun(code) {
this.$emit('add-action', code);
},
handleComposerApply(code) {
this.$emit('add-action', code);
this.showComposer = false;
},
applyComposerCommands() {
if (this.$refs.composer) {
const code = this.$refs.composer.generateCode();
this.$emit('add-action', code);
}
this.showComposer = false;
}
},
};
</script>

View File

@ -0,0 +1,159 @@
<template>
<div class="command-composer">
<!-- 固定头部 -->
<div class="composer-header q-pa-sm row items-center bg-white">
<div class="text-h6 text-weight-medium">可视化命令编排</div>
<q-space />
<q-btn flat round dense icon="close" v-close-popup />
</div>
<!-- 主体内容 -->
<div class="composer-body row no-wrap q-pa-sm">
<!-- 左侧命令列表 -->
<div class="col-3">
<div class="text-subtitle1 q-pb-sm">可用命令</div>
<q-scroll-area style="height: calc(100vh - 200px)">
<ComposerList
:commands="availableCommands"
@add-command="addCommand"
/>
</q-scroll-area>
</div>
<!-- 右侧命令流程 -->
<div class="col q-pl-md">
<div class="text-subtitle1 q-pb-sm">命令流程</div>
<q-scroll-area style="height: calc(100vh - 200px)">
<ComposerFlow v-model="commandFlow" @add-command="addCommand" />
</q-scroll-area>
</div>
</div>
<!-- 固定底部 -->
<div class="composer-footer q-pa-sm row items-center justify-end bg-white">
<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>
</div>
</template>
<script>
import { defineComponent } from "vue";
import ComposerList from "./ComposerList.vue";
import ComposerFlow from "./ComposerFlow.vue";
import { commandCategories } from "./composerConfig";
// commandCategories
const availableCommands = commandCategories.reduce((commands, category) => {
return commands.concat(
category.commands.map((cmd) => ({
type: category.label,
...cmd,
}))
);
}, []);
export default defineComponent({
name: "CommandComposer",
components: {
ComposerList,
ComposerFlow,
},
data() {
return {
commandFlow: [],
nextId: 1,
availableCommands,
};
},
methods: {
addCommand(action) {
this.commandFlow.push({
...action,
id: this.nextId++,
argv: "",
saveOutput: false,
useOutput: null,
cmd: action.value || action.cmd,
value: action.value || action.cmd,
});
},
generateCode() {
let code = [];
let outputVars = new Map();
this.commandFlow.forEach((cmd, index) => {
let line = "";
if (cmd.saveOutput) {
const varName = `output${index}`;
outputVars.set(index, varName);
line += `let ${varName} = `;
}
if (cmd.useOutput !== null) {
const inputVar = outputVars.get(cmd.useOutput);
line += `${cmd.value}(${inputVar})`;
} else {
const argv =
cmd.value !== "quickcommand.sleep" ? `"${cmd.argv}"` : cmd.argv;
line += `${cmd.value}(${argv})`;
}
code.push(line);
});
return code.join("\n");
},
runCommands() {
const code = this.generateCode();
this.$emit("run", code);
},
applyCommands() {
const code = this.generateCode();
this.$emit("apply", code);
this.$emit("update:modelValue", false);
},
},
});
</script>
<style scoped>
.command-composer {
display: flex;
flex-direction: column;
height: 100%;
}
.composer-header {
border-bottom: 1px solid #e0e0e0;
}
.composer-body {
flex: 1;
overflow: hidden;
background-color: #f5f5f5;
}
.composer-footer {
border-top: 1px solid #e0e0e0;
}
/* 滚动美化 */
:deep(.q-scrollarea__thumb) {
width: 6px;
opacity: 0.4;
transition: opacity 0.3s ease;
}
:deep(.q-scrollarea__thumb:hover) {
opacity: 0.8;
}
</style>

View File

@ -0,0 +1,176 @@
<template>
<div class="composer-card q-mb-sm">
<q-card flat bordered>
<q-card-section horizontal class="q-pa-sm">
<!-- 拖拽手柄 -->
<div class="drag-handle cursor-move q-mr-sm">
<q-icon name="drag_indicator" size="24px" class="text-grey-6" />
</div>
<div class="col">
<!-- 命令标题和描述 -->
<div class="row items-center q-mb-sm">
<div class="text-subtitle1">{{ command.label }}</div>
<q-space />
<!-- 输出开关 -->
<q-toggle
v-if="hasOutput"
v-model="saveOutputLocal"
label="保存输出"
dense
/>
<q-btn
flat
round
dense
icon="close"
@click="$emit('remove')"
/>
</div>
<!-- 参数输入 -->
<div class="row items-center">
<!-- 使用上一个命令的输出 -->
<template v-if="canUseOutput && availableOutputs.length > 0">
<q-select
v-model="useOutputLocal"
:options="availableOutputs"
dense
outlined
class="col"
emit-value
map-options
clearable
:label="placeholder"
@clear="handleClearOutput"
>
<template v-slot:prepend>
<q-icon name="input" />
</template>
<template v-slot:selected-item="scope">
<div class="row items-center">
<q-icon name="output" color="primary" size="xs" class="q-mr-xs" />
{{ scope.opt.label }}
</div>
</template>
</q-select>
</template>
<!-- 按键编辑器 -->
<template v-else-if="command.hasKeyRecorder">
<KeyEditor v-model="argvLocal" class="col" />
</template>
<!-- 普通参数输入 -->
<template v-else>
<q-input
v-model="argvLocal"
dense
outlined
class="col"
:label="placeholder"
>
<template v-slot:prepend>
<q-icon name="code" />
</template>
</q-input>
</template>
</div>
</div>
</q-card-section>
</q-card>
</div>
</template>
<script>
import { defineComponent } from 'vue'
import KeyEditor from './KeyEditor.vue'
export default defineComponent({
name: 'ComposerCard',
components: {
KeyEditor
},
props: {
command: {
type: Object,
required: true
},
hasOutput: {
type: Boolean,
default: false
},
canUseOutput: {
type: Boolean,
default: false
},
availableOutputs: {
type: Array,
default: () => []
},
placeholder: {
type: String,
default: ''
}
},
data() {
return {
showKeyRecorder: false
}
},
emits: ['remove', 'toggle-output', 'update:argv', 'update:use-output'],
computed: {
saveOutputLocal: {
get() {
return this.command.saveOutput
},
set(value) {
this.$emit('toggle-output')
}
},
argvLocal: {
get() {
return this.command.argv
},
set(value) {
this.$emit('update:argv', value)
}
},
useOutputLocal: {
get() {
return this.command.useOutput
},
set(value) {
this.$emit('update:use-output', value)
}
}
},
methods: {
handleClearOutput() {
this.$emit('update:use-output', null)
},
handleKeyRecord(keys) {
this.showKeyRecorder = false;
// keyTap("a","control")
const matches = keys.match(/keyTap\((.*)\)/)
if (matches && matches[1]) {
this.$emit('update:argv', matches[1]);
}
}
}
})
</script>
<style scoped>
.composer-card {
transition: all 0.3s ease;
}
.drag-handle {
display: flex;
align-items: center;
padding: 0 4px;
}
.drag-handle:hover {
color: var(--q-primary);
}
</style>

View File

@ -0,0 +1,204 @@
<template>
<div class="composer-flow">
<div
class="command-flow-container"
@dragover.prevent
@drop="onDrop"
>
<draggable
v-model="commands"
group="commands"
item-key="id"
class="flow-list"
handle=".drag-handle"
:animation="200"
>
<template #item="{ element, index }">
<transition
name="slide-fade"
mode="out-in"
appear
>
<div :key="element.id" class="flow-item">
<ComposerCard
:command="element"
:has-output="hasOutput(element)"
:can-use-output="canUseOutput(element, index)"
:available-outputs="getAvailableOutputs(index)"
:placeholder="getPlaceholder(element, index)"
@remove="removeCommand(index)"
@toggle-output="toggleSaveOutput(index)"
@update:argv="(val) => handleArgvChange(index, val)"
@update:use-output="(val) => handleUseOutputChange(index, val)"
/>
</div>
</transition>
</template>
</draggable>
<div v-if="commands.length === 0" class="empty-flow">
<div class="text-center text-grey-6">
<q-icon name="drag_indicator" size="32px" />
<div class="text-body2 q-mt-sm">从左侧拖拽命令到这里开始编排</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from 'vue'
import draggable from 'vuedraggable'
import ComposerCard from './ComposerCard.vue'
import { commandsWithOutput, commandsAcceptOutput } from './composerConfig'
export default defineComponent({
name: 'ComposerFlow',
components: {
draggable,
ComposerCard
},
props: {
modelValue: {
type: Array,
required: true
}
},
emits: ['update:modelValue', 'add-command'],
computed: {
commands: {
get() {
return this.modelValue
},
set(value) {
this.$emit('update:modelValue', value)
}
}
},
methods: {
onDrop(event) {
const actionData = JSON.parse(event.dataTransfer.getData('action'))
this.$emit('add-command', actionData)
document.querySelectorAll('.dragging').forEach(el => {
el.classList.remove('dragging')
})
},
removeCommand(index) {
const newCommands = [...this.commands]
newCommands.splice(index, 1)
this.$emit('update:modelValue', newCommands)
},
hasOutput(command) {
return commandsWithOutput[command.value] || false
},
canUseOutput(command, index) {
return commandsAcceptOutput[command.value] && this.getAvailableOutputs(index).length > 0
},
getAvailableOutputs(currentIndex) {
return this.commands
.slice(0, currentIndex)
.map((cmd, index) => ({
label: `${cmd.label} 的输出`,
value: index,
disable: !cmd.saveOutput
}))
.filter(item => !item.disable)
},
toggleSaveOutput(index) {
const newCommands = [...this.commands]
newCommands[index].saveOutput = !newCommands[index].saveOutput
if (!newCommands[index].saveOutput) {
newCommands.forEach((cmd, i) => {
if (i > index && cmd.useOutput === index) {
cmd.useOutput = null
}
})
}
this.$emit('update:modelValue', newCommands)
},
handleArgvChange(index, value) {
const newCommands = [...this.commands]
newCommands[index].argv = value
this.$emit('update:modelValue', newCommands)
},
handleUseOutputChange(index, value) {
const newCommands = [...this.commands]
newCommands[index].useOutput = value
if (value !== null) {
newCommands[index].argv = ''
}
this.$emit('update:modelValue', newCommands)
},
getPlaceholder(element, index) {
if (element.useOutput !== null) {
return `使用 ${this.commands[element.useOutput].label} 的输出`
}
return element.desc
}
}
})
</script>
<style scoped>
.composer-flow {
background-color: white;
border-radius: 8px;
}
.command-flow-container {
min-height: 100px;
padding: 8px;
background-color: #fafafa;
border-radius: 4px;
transition: all 0.3s ease;
}
.command-flow-container:empty {
border: 2px dashed #e0e0e0;
}
.flow-list {
min-height: 50px;
}
.empty-flow {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
border: 2px dashed #e0e0e0;
border-radius: 4px;
margin: 8px 0;
transition: all 0.3s ease;
}
.empty-flow:hover {
border-color: #bdbdbd;
background-color: #fafafa;
}
/* 拖拽时的视觉反馈 */
.command-flow-container.drag-over {
background-color: #f0f4ff;
border-color: #2196f3;
}
/* 滑动淡出动画 */
.slide-fade-enter-active,
.slide-fade-leave-active {
transition: all 0.3s ease;
}
.slide-fade-enter-from {
opacity: 0;
transform: translateX(-30px);
}
.slide-fade-leave-to {
opacity: 0;
transform: translateX(30px);
}
.flow-item {
transition: all 0.3s ease;
}
</style>

View File

@ -0,0 +1,118 @@
<template>
<div class="composer-list">
<q-list bordered separator class="rounded-borders">
<template v-for="category in commandCategories" :key="category.label">
<q-item-label header class="q-py-sm bg-grey-2">
<div class="row items-center">
<q-icon :name="category.icon" color="primary" size="sm" class="q-mr-sm" />
<span class="text-weight-medium">{{ category.label }}</span>
</div>
</q-item-label>
<q-item
v-for="command in getCategoryCommands(category)"
:key="command.value"
class="command-item q-py-xs"
draggable="true"
@dragstart="onDragStart($event, command)"
>
<q-item-section>
<q-item-label class="text-weight-medium">{{ command.label }}</q-item-label>
<q-item-label caption>{{ command.desc }}</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="drag_indicator" color="grey-6" size="16px" />
</q-item-section>
</q-item>
</template>
</q-list>
</div>
</template>
<script>
import { defineComponent } from 'vue'
import { commandCategories } from './composerConfig'
export default defineComponent({
name: 'ComposerList',
props: {
commands: {
type: Array,
required: true
}
},
data() {
return {
commandCategories
}
},
methods: {
getCategoryCommands(category) {
return this.commands.filter(cmd =>
category.commands.some(catCmd => catCmd.value === cmd.value || catCmd.value === cmd.cmd)
)
},
onDragStart(event, command) {
event.dataTransfer.setData('action', JSON.stringify({
...command,
cmd: command.value || command.cmd
}))
event.target.classList.add('dragging')
event.target.addEventListener('dragend', () => {
event.target.classList.remove('dragging')
}, { once: true })
}
}
})
</script>
<style scoped>
.composer-list {
background-color: white;
border-radius: 8px;
}
.command-item {
border: 1px solid #e0e0e0;
border-radius: 4px;
margin: 4px 8px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background: white;
cursor: grab;
}
.command-item:hover {
background-color: #f8f9fa;
transform: translateX(4px);
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.command-item.dragging {
opacity: 0.5;
transform: scale(0.95);
}
/* 分类标题样式 */
.q-item-label.header {
min-height: 32px;
padding: 4px 12px;
font-size: 0.9rem;
border-bottom: 1px solid #e0e0e0;
background-color: #f5f5f5;
}
/* 暗色模式适配 */
.body--dark .command-item {
border-color: #424242;
background: #1d1d1d;
}
.body--dark .command-item:hover {
background-color: #2d2d2d;
}
.body--dark .q-item-label.header {
border-color: #424242;
background-color: #2d2d2d;
}
</style>

View File

@ -0,0 +1,362 @@
<template>
<div class="key-editor">
<!-- 按键输入框 -->
<q-input
dense
outlined
readonly
class="col"
>
<template v-slot:prepend>
<!-- 修饰键 -->
<div class="row items-center q-gutter-x-xs">
<q-chip
v-for="(active, key) in modifiers"
:key="key"
:color="active ? 'primary' : 'grey-4'"
:text-color="active ? 'white' : 'grey-7'"
dense
clickable
class="modifier-chip"
@click="toggleModifier(key)"
>
{{ modifierLabels[key] }}
</q-chip>
</div>
</template>
<!-- 主按键显示 -->
<template v-slot:default>
<div class="main-key-container">
<div class="main-key">
{{ mainKeyDisplay }}
</div>
</div>
</template>
<template v-slot:append>
<q-separator vertical inset />
<!-- 选择按钮 -->
<q-btn
flat
round
dense
icon="edit"
@click="showKeySelect = true"
>
<q-tooltip>选择按键</q-tooltip>
</q-btn>
<q-separator vertical inset />
<!-- 录制按钮 -->
<q-btn
flat
round
dense
:icon="isRecording ? 'fiber_manual_record' : 'radio_button_unchecked'"
:color="isRecording ? 'negative' : 'primary'"
@click="toggleRecording"
>
<q-tooltip>{{ isRecording ? '停止录制' : '开始录制' }}</q-tooltip>
</q-btn>
</template>
</q-input>
<!-- 按键选择对话框 -->
<q-dialog v-model="showKeySelect">
<q-card style="min-width: 300px">
<q-card-section>
<div class="text-h6">选择按键</div>
</q-card-section>
<q-card-section class="q-gutter-y-md">
<!-- 主按键选择 -->
<q-select
v-model="mainKey"
:options="commonKeys"
label="选择用按键"
dense
outlined
emit-value
map-options
/>
<q-input
v-model="customKey"
label="或输入自定义按键"
dense
outlined
@update:model-value="mainKey = $event"
/>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="确定" color="primary" v-close-popup />
</q-card-actions>
</q-card>
</q-dialog>
</div>
</template>
<script>
import { defineComponent } from 'vue'
//
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0
export default defineComponent({
name: 'KeyEditor',
props: {
modelValue: {
type: String,
default: ''
}
},
data() {
return {
isRecording: false,
showKeySelect: false,
mainKey: '',
customKey: '',
modifiers: {
control: false,
alt: false,
shift: false,
command: false
},
modifierLabels: isMac ? {
control: '⌃',
alt: '⌥',
shift: '⇧',
command: '⌘'
} : {
control: 'Ctrl',
alt: 'Alt',
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: {
mainKeyDisplay() {
if (!this.mainKey) return ''
//
const specialKeyMap = {
'enter': '↵',
'tab': '⇥',
'space': '␣',
'backspace': '⌫',
'delete': '⌦',
'escape': '⎋',
'up': '↑',
'down': '↓',
'left': '←',
'right': '→'
}
return specialKeyMap[this.mainKey] ||
(this.mainKey.length === 1 ? this.mainKey.toUpperCase() :
this.mainKey.charAt(0).toUpperCase() + this.mainKey.slice(1))
}
},
watch: {
modelValue: {
immediate: true,
handler(val) {
if (val) {
this.parseKeyString(val)
}
}
}
},
methods: {
toggleModifier(key) {
this.modifiers[key] = !this.modifiers[key]
this.updateValue()
},
toggleRecording() {
if (!this.isRecording) {
this.startRecording()
} else {
this.stopRecording()
}
},
startRecording() {
this.isRecording = true
let lastKeyTime = 0
let lastKey = null
this.recordEvent = (event) => {
event.preventDefault()
const currentTime = Date.now()
//
Object.keys(this.modifiers).forEach(key => {
this.modifiers[key] = false
})
//
if (isMac) {
if (event.metaKey) this.modifiers.command = true
if (event.ctrlKey) this.modifiers.control = true
} else {
if (event.ctrlKey) this.modifiers.control = true
if (event.metaKey || event.winKey) this.modifiers.command = true
}
if (event.altKey) this.modifiers.alt = true
if (event.shiftKey) this.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',
'Control': 'control',
'Alt': 'alt',
'Shift': 'shift',
'Meta': 'command'
}
key = keyMap[event.code] || event.key.toLowerCase()
}
//
if (['control', 'alt', 'shift', 'command'].includes(key)) {
if (key === lastKey && (currentTime - lastKeyTime) < 500) {
this.mainKey = key
this.modifiers[key] = false //
this.stopRecording()
this.updateValue()
return
}
lastKey = key
lastKeyTime = currentTime
return
}
//
if (key === 'space' || !['meta', 'control', 'shift', 'alt', 'command'].includes(key)) {
this.mainKey = key
this.stopRecording()
this.updateValue()
}
}
document.addEventListener('keydown', this.recordEvent)
},
stopRecording() {
this.isRecording = false
document.removeEventListener('keydown', this.recordEvent)
},
updateValue() {
if (!this.mainKey) return
const activeModifiers = Object.entries(this.modifiers)
.filter(([_, active]) => active)
.map(([key]) => key)
// Mac command meta
.map(key => !isMac && key === 'command' ? 'meta' : key)
const args = [this.mainKey, ...activeModifiers]
this.$emit('update:modelValue', args.join('","'))
},
parseKeyString(val) {
try {
const args = val.split('","')
if (args.length > 0) {
this.mainKey = args[0]
Object.keys(this.modifiers).forEach(key => {
// Mac meta command
const modKey = !isMac && args.includes('meta') ? 'command' : key
this.modifiers[key] = args.includes(modKey)
})
}
} catch (e) {
console.error('Failed to parse key string:', e)
}
}
}
})
</script>
<style scoped>
.key-editor {
padding: 4px 0;
}
.modifier-chip {
height: 24px;
font-size: 13px;
margin: 0 2px;
}
.main-key-container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
min-width: 60px;
}
.main-key {
font-size: 13px;
font-weight: 500;
color: var(--q-primary);
line-height: 24px;
padding: 0 8px;
}
:deep(.q-field__control) {
padding: 0 8px;
min-height: 36px;
}
:deep(.q-field__prepend) {
padding-right: 0;
}
:deep(.q-field__native) {
display: none;
}
:deep(.q-field__control-container) {
padding: 0;
display: flex;
align-items: center;
}
</style>

View File

@ -0,0 +1,132 @@
// 定义命令图标映射
export const commandIcons = {
'open': 'folder_open',
'locate': 'location_on',
'visit': 'language',
'utools.ubrowser.goto': 'public',
'system': 'terminal',
'copyTo': 'content_copy',
'message': 'message',
'alert': 'warning',
'send': 'send',
'utools.redirect': 'alt_route',
'quickcommand.sleep': 'schedule',
'keyTap': 'keyboard'
}
// 定义命令分类
export const commandCategories = [
{
label: '文件操作',
icon: 'folder',
commands: [
{
value: "open",
label: "打开文件/文件夹/软件",
desc: "文件、文件夹或软件的绝对路径",
},
{
value: "locate",
label: "在文件管理器中定位文件",
desc: "要在文件管理器里显示的文件路径",
}
]
},
{
label: '网络操作',
icon: 'language',
commands: [
{
value: "visit",
label: "用默认浏览器打开网址",
desc: "要访问的网址链接",
},
{
value: "utools.ubrowser.goto",
label: "用ubrowser打开网址",
desc: "要访问的网址链接",
}
]
},
{
label: '系统操作',
icon: 'computer',
commands: [
{
value: "system",
label: "执行系统命令",
desc: "要执行的命令行",
},
{
value: "copyTo",
label: "将内容写入剪贴板",
desc: "要写入剪切板的内容",
}
]
},
{
label: '消息通知',
icon: 'notifications',
commands: [
{
value: "message",
label: "发送系统消息",
desc: "要发送的系统消息文本",
},
{
value: "alert",
label: "弹窗显示消息",
desc: "要弹窗显示的消息文本",
},
{
value: "send",
label: "发送文本到活动窗口",
desc: "要发送到窗口的文本内容",
}
]
},
{
label: '其他功能',
icon: 'more_horiz',
commands: [
{
value: "utools.redirect",
label: "转至指定插件",
desc: "要跳转至的插件名称",
},
{
value: "quickcommand.sleep",
label: "添加延时",
desc: "延迟的毫秒数",
}
]
},
{
label: '按键操作',
icon: 'keyboard',
commands: [
{
value: "keyTap",
label: "模拟按键",
desc: "模拟键盘按键",
hasKeyRecorder: true
}
]
}
]
// 定义哪些命令可以产生输出
export const commandsWithOutput = {
'system': true,
'open': true,
'locate': true,
'copyTo': true,
}
// 定义哪些命令可以接收输出
export const commandsAcceptOutput = {
'message': true,
'alert': true,
'send': true,
'copyTo': true,
}