mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-08 06:16:27 +08:00
可视化编排添加命令配置功能
This commit is contained in:
parent
8c564f8d97
commit
2cb0c6bb32
@ -4,9 +4,9 @@
|
|||||||
ref="sidebar"
|
ref="sidebar"
|
||||||
:canCommandSave="canCommandSave"
|
:canCommandSave="canCommandSave"
|
||||||
:quickcommandInfo="quickcommandInfo"
|
:quickcommandInfo="quickcommandInfo"
|
||||||
|
:allQuickCommandTags="allQuickCommandTags"
|
||||||
class="absolute-left shadow-1"
|
class="absolute-left shadow-1"
|
||||||
:style="{
|
:style="{
|
||||||
width: sideBarWidth + 'px',
|
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
transform: isFullscreen ? 'translateX(-100%)' : 'translateX(0)',
|
transform: isFullscreen ? 'translateX(-100%)' : 'translateX(0)',
|
||||||
transition: 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
transition: 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
@ -64,7 +64,11 @@
|
|||||||
|
|
||||||
<!-- 可视化编排 -->
|
<!-- 可视化编排 -->
|
||||||
<q-dialog v-model="showComposer" maximized>
|
<q-dialog v-model="showComposer" maximized>
|
||||||
<CommandComposer ref="composer" @use-composer="handleComposer" />
|
<CommandComposer
|
||||||
|
ref="composer"
|
||||||
|
@action="handleComposerAction"
|
||||||
|
:model-value="{ flows }"
|
||||||
|
/>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
||||||
<!-- 运行结果 -->
|
<!-- 运行结果 -->
|
||||||
@ -78,6 +82,7 @@ import CommandLanguageBar from "components/editor/CommandLanguageBar";
|
|||||||
import EditorTools from "components/editor/EditorTools";
|
import EditorTools from "components/editor/EditorTools";
|
||||||
import CommandRunResult from "components/CommandRunResult";
|
import CommandRunResult from "components/CommandRunResult";
|
||||||
import CommandComposer from "components/composer/CommandComposer.vue";
|
import CommandComposer from "components/composer/CommandComposer.vue";
|
||||||
|
import programs from "js/options/programs.js";
|
||||||
|
|
||||||
// 预加载 MonacoEditor
|
// 预加载 MonacoEditor
|
||||||
const MonacoEditorPromise = import("components/editor/MonacoEditor");
|
const MonacoEditorPromise = import("components/editor/MonacoEditor");
|
||||||
@ -109,13 +114,22 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
programLanguages: Object.keys(this.$root.programs),
|
programLanguages: Object.keys(programs),
|
||||||
sideBarWidth: 200,
|
sideBarWidth: 200,
|
||||||
languageBarHeight: 40,
|
languageBarHeight: 40,
|
||||||
showComposer: false,
|
showComposer: false,
|
||||||
isRunCodePage: this.action.type === "run",
|
isRunCodePage: this.action.type === "run",
|
||||||
canCommandSave: this.action.type !== "run",
|
canCommandSave: this.action.type !== "run",
|
||||||
showSidebar: this.action.type !== "run",
|
showSidebar: this.action.type !== "run",
|
||||||
|
flows: [
|
||||||
|
{
|
||||||
|
id: "main",
|
||||||
|
name: "main",
|
||||||
|
label: "主流程",
|
||||||
|
commands: [],
|
||||||
|
customVariables: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
quickcommandInfo: {
|
quickcommandInfo: {
|
||||||
program: "quickcommand",
|
program: "quickcommand",
|
||||||
cmd: "",
|
cmd: "",
|
||||||
@ -141,15 +155,9 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
allQuickCommandTags: Array,
|
allQuickCommandTags: Array,
|
||||||
isLeaving: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.commandInit();
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.commandInit();
|
||||||
this.sidebarInit();
|
this.sidebarInit();
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -196,7 +204,7 @@ export default {
|
|||||||
// 匹配编程语言
|
// 匹配编程语言
|
||||||
matchLanguage() {
|
matchLanguage() {
|
||||||
if (!this.quickcommandInfo.customOptions.ext) return;
|
if (!this.quickcommandInfo.customOptions.ext) return;
|
||||||
let language = Object.values(this.$root.programs).filter(
|
let language = Object.values(programs).filter(
|
||||||
(program) => program.ext === this.quickcommandInfo.customOptions.ext
|
(program) => program.ext === this.quickcommandInfo.customOptions.ext
|
||||||
);
|
);
|
||||||
if (language.length) {
|
if (language.length) {
|
||||||
@ -205,7 +213,7 @@ export default {
|
|||||||
},
|
},
|
||||||
// 设置编程语言
|
// 设置编程语言
|
||||||
setLanguage(language) {
|
setLanguage(language) {
|
||||||
let highlight = this.$root.programs[language].highlight;
|
let highlight = programs[language].highlight;
|
||||||
this.$refs.editor.setEditorLanguage(highlight ? highlight : language);
|
this.$refs.editor.setEditorLanguage(highlight ? highlight : language);
|
||||||
},
|
},
|
||||||
insertText(text) {
|
insertText(text) {
|
||||||
@ -216,14 +224,16 @@ export default {
|
|||||||
this.$refs.editor.setEditorValue(text);
|
this.$refs.editor.setEditorValue(text);
|
||||||
this.$refs.editor.formatDocument();
|
this.$refs.editor.formatDocument();
|
||||||
},
|
},
|
||||||
handleComposer({ type, code }) {
|
handleComposerAction(actionType, actionData) {
|
||||||
switch (type) {
|
switch (actionType) {
|
||||||
case "run":
|
case "run":
|
||||||
return this.runCurrentCommand(code);
|
return this.runCurrentCommand(actionData);
|
||||||
case "insert":
|
case "insert":
|
||||||
return this.insertText(code);
|
return this.insertText(actionData);
|
||||||
case "apply":
|
case "apply":
|
||||||
return this.replaceText(code);
|
return this.replaceText(actionData);
|
||||||
|
case "close":
|
||||||
|
return this.showComposer = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 保存
|
// 保存
|
||||||
|
@ -75,6 +75,8 @@ import specialVars from "js/options/specialVars.js";
|
|||||||
import commandTypes from "js/options/commandTypes.js";
|
import commandTypes from "js/options/commandTypes.js";
|
||||||
import ResultArea from "components/ResultArea.vue";
|
import ResultArea from "components/ResultArea.vue";
|
||||||
import ResultMenu from "components/popup/ResultMenu.vue";
|
import ResultMenu from "components/popup/ResultMenu.vue";
|
||||||
|
import { generateFlowsCode } from "js/composer/generateCode";
|
||||||
|
import { getValidCommand } from "js/commandManager";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { ResultArea, ResultMenu },
|
components: { ResultArea, ResultMenu },
|
||||||
@ -127,11 +129,20 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
// 运行命令
|
// 运行命令
|
||||||
async runCurrentCommand(currentCommand) {
|
async runCurrentCommand(currentCommand) {
|
||||||
|
let command = window.lodashM.cloneDeep(currentCommand);
|
||||||
|
// 如果是composer命令,则动态生成cmd
|
||||||
|
if (command.program === "quickcomposer") {
|
||||||
|
command.cmd = generateFlowsCode(command.flows);
|
||||||
|
}
|
||||||
this.$root.isRunningCommand = true;
|
this.$root.isRunningCommand = true;
|
||||||
await this.getTempPayload(currentCommand);
|
try {
|
||||||
if (currentCommand.cmd.includes("{{subinput"))
|
await this.getTempPayload(command);
|
||||||
return this.setSubInput(currentCommand);
|
} catch (error) {
|
||||||
this.fire(currentCommand);
|
return quickcommand.showMessageBox(error.toString(), "error");
|
||||||
|
}
|
||||||
|
// 如果命令包含子输入框,则设置子输入框
|
||||||
|
if (command.cmd.includes("{{subinput")) return this.setSubInput(command);
|
||||||
|
this.fire(command);
|
||||||
},
|
},
|
||||||
async fire(currentCommand) {
|
async fire(currentCommand) {
|
||||||
currentCommand.cmd = this.assignSpecialVars(currentCommand.cmd);
|
currentCommand.cmd = this.assignSpecialVars(currentCommand.cmd);
|
||||||
@ -147,6 +158,7 @@ export default {
|
|||||||
let resultOpts = { outPlugin, action, earlyExit };
|
let resultOpts = { outPlugin, action, earlyExit };
|
||||||
switch (currentCommand.program) {
|
switch (currentCommand.program) {
|
||||||
case "quickcommand":
|
case "quickcommand":
|
||||||
|
case "quickcomposer":
|
||||||
window.runCodeInSandbox(
|
window.runCodeInSandbox(
|
||||||
currentCommand.cmd,
|
currentCommand.cmd,
|
||||||
(stdout, stderr) => this.handleResult(stdout, stderr, resultOpts),
|
(stdout, stderr) => this.handleResult(stdout, stderr, resultOpts),
|
||||||
@ -278,11 +290,15 @@ export default {
|
|||||||
// payload 临时赋值
|
// payload 临时赋值
|
||||||
async getTempPayload(currentCommand) {
|
async getTempPayload(currentCommand) {
|
||||||
if (!this.needTempPayload) return;
|
if (!this.needTempPayload) return;
|
||||||
let type =
|
currentCommand = getValidCommand(currentCommand);
|
||||||
currentCommand.cmdType || currentCommand.features?.cmds[0].type;
|
const firstCmd = currentCommand.features.cmds[0];
|
||||||
|
const type = firstCmd.type || "text";
|
||||||
this.$root.enterData = {
|
this.$root.enterData = {
|
||||||
type: type || "text",
|
type,
|
||||||
payload: await commandTypes[type]?.tempPayload?.(),
|
payload:
|
||||||
|
type === "text"
|
||||||
|
? firstCmd
|
||||||
|
: (await commandTypes[type]?.tempPayload?.()) || {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
handleResult(stdout, stderr, options) {
|
handleResult(stdout, stderr, options) {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<CommandComposer
|
<CommandComposer
|
||||||
ref="composer"
|
ref="composer"
|
||||||
@use-composer="handleComposer"
|
@action="handleComposerAction"
|
||||||
:show-close-button="false"
|
v-model="quickcommandInfo"
|
||||||
|
:show-close-button="!isRunComposePage"
|
||||||
class="fixed-full"
|
class="fixed-full"
|
||||||
/>
|
/>
|
||||||
<!-- 运行结果 -->
|
<!-- 运行结果 -->
|
||||||
@ -12,29 +13,131 @@
|
|||||||
<script>
|
<script>
|
||||||
import CommandComposer from "components/composer/CommandComposer.vue";
|
import CommandComposer from "components/composer/CommandComposer.vue";
|
||||||
import CommandRunResult from "components/CommandRunResult";
|
import CommandRunResult from "components/CommandRunResult";
|
||||||
|
import { findCommandByValue } from "js/composer/composerConfig";
|
||||||
|
import programs from "js/options/programs.js";
|
||||||
|
import { provide, ref } from "vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { CommandComposer, CommandRunResult },
|
components: { CommandComposer, CommandRunResult },
|
||||||
|
setup(props) {
|
||||||
|
provide("allQuickCommandTags", props.allQuickCommandTags);
|
||||||
|
|
||||||
|
const retoreToFullCommand = (command) => {
|
||||||
|
const { flows } = command;
|
||||||
|
if (!flows) return command;
|
||||||
|
const newFlows = flows.map((flow) => ({
|
||||||
|
...flow,
|
||||||
|
commands: flow.commands.map((cmd) => {
|
||||||
|
// 恢复所有属性
|
||||||
|
const command = findCommandByValue(cmd.value);
|
||||||
|
return {
|
||||||
|
...command,
|
||||||
|
...cmd,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
...command,
|
||||||
|
flows: newFlows,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLitedComposerCommand = (command) => {
|
||||||
|
const { flows } = command;
|
||||||
|
if (!flows) return command;
|
||||||
|
const newFlows = flows.map((flow) => ({
|
||||||
|
...flow,
|
||||||
|
commands: flow.commands.map((cmd) => {
|
||||||
|
const cmdCopy = { ...cmd };
|
||||||
|
// 移除不必要保存的属性
|
||||||
|
const uselessProps = [
|
||||||
|
"config",
|
||||||
|
"label",
|
||||||
|
"component",
|
||||||
|
"subCommands",
|
||||||
|
"outputs",
|
||||||
|
"options",
|
||||||
|
"icon",
|
||||||
|
"width",
|
||||||
|
"placeholder",
|
||||||
|
"summary",
|
||||||
|
"type",
|
||||||
|
];
|
||||||
|
uselessProps.forEach((prop) => delete cmdCopy[prop]);
|
||||||
|
return cmdCopy;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
...command,
|
||||||
|
flows: newFlows,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const commandAction = window.lodashM.cloneDeep(props.action);
|
||||||
|
const savedCommand = commandAction.data || {};
|
||||||
|
const defaultCommand = {
|
||||||
|
program: "quickcomposer",
|
||||||
|
features: {
|
||||||
|
icon: programs.quickcommand.icon,
|
||||||
|
explain: "",
|
||||||
|
platform: ["win32", "linux", "darwin"],
|
||||||
|
mainPush: false,
|
||||||
|
cmds: [""],
|
||||||
|
},
|
||||||
|
flows: [
|
||||||
|
{
|
||||||
|
id: "main",
|
||||||
|
name: "main",
|
||||||
|
label: "主流程",
|
||||||
|
commands: [],
|
||||||
|
customVariables: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
output: "text",
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
const quickcommandInfo = ref({
|
||||||
|
...defaultCommand,
|
||||||
|
...retoreToFullCommand(savedCommand),
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
quickcommandInfo,
|
||||||
|
getLitedComposerCommand,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
emits: ["editorEvent"],
|
||||||
props: {
|
props: {
|
||||||
action: {
|
action: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
allQuickCommandTags: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isRunComposePage: this.action.type === "composer",
|
||||||
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleComposer({ type, code }) {
|
handleComposerAction(actionType, command) {
|
||||||
switch (type) {
|
switch (actionType) {
|
||||||
case "run":
|
case "run":
|
||||||
return this.runCurrentCommand(code);
|
return this.runCurrentCommand(command);
|
||||||
|
case "close":
|
||||||
|
return this.$emit("editorEvent", {
|
||||||
|
type: "back",
|
||||||
|
});
|
||||||
|
case "save":
|
||||||
|
return this.$emit("editorEvent", {
|
||||||
|
type: "save",
|
||||||
|
data: this.getLitedComposerCommand(command),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
runCurrentCommand(cmd) {
|
runCurrentCommand(command) {
|
||||||
if (!cmd) return;
|
|
||||||
let command = {
|
|
||||||
cmd: cmd,
|
|
||||||
output: "text",
|
|
||||||
program: "quickcommand",
|
|
||||||
};
|
|
||||||
this.$refs.result.runCurrentCommand(command);
|
this.$refs.result.runCurrentCommand(command);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
width="16px"
|
width="16px"
|
||||||
/>
|
/>
|
||||||
<div>|</div>
|
<div>|</div>
|
||||||
<q-img :src="programs[commandInfo.program].icon" width="16px" />
|
<q-img :src="program.icon" width="16px" />
|
||||||
<div class="text-subtitle2">{{ programName }}</div>
|
<div class="text-subtitle2">{{ programName }}</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
@ -75,6 +75,12 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
program() {
|
program() {
|
||||||
|
if (this.commandInfo.program === "quickcomposer") {
|
||||||
|
return {
|
||||||
|
...this.programs.quickcommand,
|
||||||
|
shortName: "可视化",
|
||||||
|
};
|
||||||
|
}
|
||||||
return this.programs[this.commandInfo.program];
|
return this.programs[this.commandInfo.program];
|
||||||
},
|
},
|
||||||
programName() {
|
programName() {
|
||||||
|
@ -70,6 +70,12 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
program() {
|
program() {
|
||||||
|
if (this.commandInfo.program === "quickcomposer") {
|
||||||
|
return {
|
||||||
|
...this.programs.quickcommand,
|
||||||
|
name: "可视化编排",
|
||||||
|
};
|
||||||
|
}
|
||||||
return this.programs[this.commandInfo.program];
|
return this.programs[this.commandInfo.program];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -10,8 +10,10 @@
|
|||||||
<!-- 右侧命令流程 -->
|
<!-- 右侧命令流程 -->
|
||||||
<div class="col command-section">
|
<div class="col command-section">
|
||||||
<FlowTabs
|
<FlowTabs
|
||||||
@action="handleComposer"
|
@action="handleAction"
|
||||||
:show-close-button="showCloseButton"
|
:show-close-button="showCloseButton"
|
||||||
|
:model-value="modelValue"
|
||||||
|
@update:model-value="$emit('update:modelValue', $event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -44,12 +46,16 @@ export default defineComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
emits: ["use-composer"],
|
},
|
||||||
|
emits: ["action", "update:modelValue"],
|
||||||
methods: {
|
methods: {
|
||||||
handleComposer(type, code) {
|
handleAction(actionType, actionData) {
|
||||||
// 直接转发事件和代码
|
// 直接转发事件和代码
|
||||||
this.$emit("use-composer", { type, code });
|
this.$emit("action", actionType, actionData);
|
||||||
},
|
},
|
||||||
findCommandNeedLoading(flow) {
|
findCommandNeedLoading(flow) {
|
||||||
// 暂时只在运行单独命令时显示载入界面,因为运行整个命令流时,如果不打印输出,是无法判断什么时候运行结束的,
|
// 暂时只在运行单独命令时显示载入界面,因为运行整个命令流时,如果不打印输出,是无法判断什么时候运行结束的,
|
||||||
|
@ -197,7 +197,7 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
const consoleLogVars =
|
const consoleLogVars =
|
||||||
this.getAvailableOutputVariableName(outputVariable);
|
this.getAvailableOutputVariableName(outputVariable);
|
||||||
const tempFlow = {
|
const tempFlows = [{
|
||||||
name: "main",
|
name: "main",
|
||||||
commands: [
|
commands: [
|
||||||
tempCommand,
|
tempCommand,
|
||||||
@ -208,8 +208,9 @@ export default defineComponent({
|
|||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
},
|
||||||
this.$emit("run", tempFlow);
|
];
|
||||||
|
this.$emit("run", tempFlows);
|
||||||
},
|
},
|
||||||
handleToggleCollapse() {
|
handleToggleCollapse() {
|
||||||
if (this.localCommand.isControlFlow) {
|
if (this.localCommand.isControlFlow) {
|
||||||
|
@ -82,10 +82,6 @@ export default defineComponent({
|
|||||||
required: true,
|
required: true,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
generateCode: {
|
|
||||||
type: Function,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
showCloseButton: {
|
showCloseButton: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
|
@ -59,9 +59,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ComposerButtons
|
<ComposerButtons
|
||||||
:generate-code="generateAllFlowCode"
|
|
||||||
:is-all-collapsed="isAllCollapsed"
|
:is-all-collapsed="isAllCollapsed"
|
||||||
:show-close-button="showCloseButton"
|
:show-close-button="showCloseButton"
|
||||||
|
:flows="flows"
|
||||||
@action="handleAction"
|
@action="handleAction"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -72,10 +72,15 @@
|
|||||||
:key="flow.id"
|
:key="flow.id"
|
||||||
v-show="activeTab === flow.id"
|
v-show="activeTab === flow.id"
|
||||||
>
|
>
|
||||||
|
<CommandConfig
|
||||||
|
class="command-config-panel"
|
||||||
|
v-if="flow.id === 'main' && commandConfig.features"
|
||||||
|
:model-value="commandConfig"
|
||||||
|
@update:model-value="updateCommandConfig"
|
||||||
|
/>
|
||||||
<ComposerFlow
|
<ComposerFlow
|
||||||
class="flow-wrapper"
|
class="flow-wrapper"
|
||||||
v-model="flow.commands"
|
v-model="flow.commands"
|
||||||
:generate-code="() => generateFlowCode(flow)"
|
|
||||||
:show-close-button="flows.length > 1"
|
:show-close-button="flows.length > 1"
|
||||||
@action="(type, payload) => handleAction(type, payload)"
|
@action="(type, payload) => handleAction(type, payload)"
|
||||||
ref="flowRefs"
|
ref="flowRefs"
|
||||||
@ -84,7 +89,7 @@
|
|||||||
v-model="showVariableManager"
|
v-model="showVariableManager"
|
||||||
:flow="flow"
|
:flow="flow"
|
||||||
:variables="flow.customVariables"
|
:variables="flow.customVariables"
|
||||||
@update-flow="Sub(flow)"
|
@update-flow="updateFlows(flow)"
|
||||||
:is-main-flow="flow.id === 'main'"
|
:is-main-flow="flow.id === 'main'"
|
||||||
:output-variables="outputVariables"
|
:output-variables="outputVariables"
|
||||||
class="variable-panel"
|
class="variable-panel"
|
||||||
@ -99,10 +104,10 @@ import draggable from "vuedraggable";
|
|||||||
import ComposerFlow from "components/composer/ComposerFlow.vue";
|
import ComposerFlow from "components/composer/ComposerFlow.vue";
|
||||||
import ComposerButtons from "components/composer/flow/ComposerButtons.vue";
|
import ComposerButtons from "components/composer/flow/ComposerButtons.vue";
|
||||||
import FlowManager from "components/composer/flow/FlowManager.vue";
|
import FlowManager from "components/composer/flow/FlowManager.vue";
|
||||||
import { generateCode } from "js/composer/generateCode";
|
import CommandConfig from "components/editor/CommandConfig.vue";
|
||||||
import { findCommandByValue } from "js/composer/composerConfig";
|
|
||||||
import { generateUniqSuffix } from "js/composer/variableManager";
|
import { generateUniqSuffix } from "js/composer/variableManager";
|
||||||
import { getUniqueId } from "js/common/uuid";
|
import { getUniqueId } from "js/common/uuid";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "FlowTabs",
|
name: "FlowTabs",
|
||||||
components: {
|
components: {
|
||||||
@ -110,23 +115,44 @@ export default defineComponent({
|
|||||||
ComposerButtons,
|
ComposerButtons,
|
||||||
draggable,
|
draggable,
|
||||||
FlowManager,
|
FlowManager,
|
||||||
|
CommandConfig,
|
||||||
},
|
},
|
||||||
|
emits: ["update:modelValue", "action"],
|
||||||
props: {
|
props: {
|
||||||
showCloseButton: {
|
showCloseButton: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const updateFlows = (newFlows) => {
|
||||||
|
emit("update:modelValue", {
|
||||||
|
...props.modelValue,
|
||||||
|
flows: newFlows,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const flows = computed(() => props.modelValue.flows);
|
||||||
|
|
||||||
|
const mainFlow = computed({
|
||||||
|
get: () => flows.value[0],
|
||||||
|
set: (newVal) => {
|
||||||
|
const newFlows = [newVal, ...flows.value.slice(1)];
|
||||||
|
updateFlows(newFlows);
|
||||||
},
|
},
|
||||||
setup() {
|
|
||||||
const mainFlow = ref({
|
|
||||||
id: "main",
|
|
||||||
name: "main",
|
|
||||||
label: "主流程",
|
|
||||||
commands: [],
|
|
||||||
customVariables: [],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const subFlows = ref([]);
|
const subFlows = computed({
|
||||||
|
get: () => flows.value.slice(1),
|
||||||
|
set: (newVal) => {
|
||||||
|
const newFlows = [mainFlow.value, ...newVal];
|
||||||
|
updateFlows(newFlows);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// 获取所有函数
|
// 获取所有函数
|
||||||
const getCurrentFunctions = () => {
|
const getCurrentFunctions = () => {
|
||||||
@ -140,8 +166,6 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
provide("getCurrentFunctions", getCurrentFunctions);
|
provide("getCurrentFunctions", getCurrentFunctions);
|
||||||
|
|
||||||
const flows = computed(() => [mainFlow.value, ...subFlows.value]);
|
|
||||||
|
|
||||||
const activeTab = ref("main");
|
const activeTab = ref("main");
|
||||||
|
|
||||||
const getCurrentFlow = () => {
|
const getCurrentFlow = () => {
|
||||||
@ -208,6 +232,7 @@ export default defineComponent({
|
|||||||
subFlows,
|
subFlows,
|
||||||
activeTab,
|
activeTab,
|
||||||
getOutputVariables,
|
getOutputVariables,
|
||||||
|
updateFlows,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -217,6 +242,12 @@ export default defineComponent({
|
|||||||
outputVariables: [],
|
outputVariables: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
commandConfig() {
|
||||||
|
const { tags, output, features } = this.modelValue;
|
||||||
|
return { tags, output, features };
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
generateFlowName(baseName = "func_") {
|
generateFlowName(baseName = "func_") {
|
||||||
return (
|
return (
|
||||||
@ -249,7 +280,7 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.subFlows.push(newFlow);
|
this.subFlows = [...this.subFlows, newFlow];
|
||||||
|
|
||||||
if (options.silent) {
|
if (options.silent) {
|
||||||
return;
|
return;
|
||||||
@ -263,7 +294,10 @@ export default defineComponent({
|
|||||||
removeFlow(flow) {
|
removeFlow(flow) {
|
||||||
const index = this.subFlows.findIndex((f) => f.id === flow.id);
|
const index = this.subFlows.findIndex((f) => f.id === flow.id);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
this.subFlows.splice(index, 1);
|
this.subFlows = [
|
||||||
|
...this.subFlows.slice(0, index),
|
||||||
|
...this.subFlows.slice(index + 1),
|
||||||
|
];
|
||||||
this.activeTab = this.flows[0].id;
|
this.activeTab = this.flows[0].id;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -279,25 +313,13 @@ export default defineComponent({
|
|||||||
// 完全更新参数
|
// 完全更新参数
|
||||||
this.subFlows[index].customVariables = [...newParams, ...localVars];
|
this.subFlows[index].customVariables = [...newParams, ...localVars];
|
||||||
},
|
},
|
||||||
generateFlowCode(flow) {
|
|
||||||
return generateCode(flow);
|
|
||||||
},
|
|
||||||
generateAllFlowCode() {
|
|
||||||
// 生成所有flow的代码
|
|
||||||
return [...this.subFlows, this.mainFlow]
|
|
||||||
.map((flow) => this.generateFlowCode(flow))
|
|
||||||
.join("\n\n");
|
|
||||||
},
|
|
||||||
handleAction(type, payload) {
|
handleAction(type, payload) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "save":
|
case "save":
|
||||||
this.saveFlows();
|
this.saveFlows();
|
||||||
break;
|
break;
|
||||||
case "load":
|
|
||||||
this.loadFlows();
|
|
||||||
break;
|
|
||||||
case "run":
|
case "run":
|
||||||
this.runFlows(payload);
|
this.runCommand(payload);
|
||||||
break;
|
break;
|
||||||
case "collapseAll":
|
case "collapseAll":
|
||||||
this.collapseAll();
|
this.collapseAll();
|
||||||
@ -308,6 +330,9 @@ export default defineComponent({
|
|||||||
case "toggleVariableManager":
|
case "toggleVariableManager":
|
||||||
this.toggleVariableManager();
|
this.toggleVariableManager();
|
||||||
break;
|
break;
|
||||||
|
case "close":
|
||||||
|
this.$emit("action", "close");
|
||||||
|
break;
|
||||||
case "addFlow":
|
case "addFlow":
|
||||||
// 处理新函数创建
|
// 处理新函数创建
|
||||||
const index = this.subFlows.findIndex((f) => f.name === payload.name);
|
const index = this.subFlows.findIndex((f) => f.name === payload.name);
|
||||||
@ -319,7 +344,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this.$emit("action", type, this.generateAllFlowCode());
|
this.$emit("action", type, payload);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggleVariableManager() {
|
toggleVariableManager() {
|
||||||
@ -327,61 +352,17 @@ export default defineComponent({
|
|||||||
this.outputVariables = this.getOutputVariables();
|
this.outputVariables = this.getOutputVariables();
|
||||||
},
|
},
|
||||||
saveFlows() {
|
saveFlows() {
|
||||||
const flowsData = this.flows.map((flow) => ({
|
this.$emit("action", "save", {
|
||||||
...flow,
|
...this.modelValue,
|
||||||
commands: flow.commands.map((cmd) => {
|
flows: this.flows,
|
||||||
const cmdCopy = { ...cmd };
|
});
|
||||||
// 移除不必要保存的属性
|
|
||||||
const uselessProps = [
|
|
||||||
"config",
|
|
||||||
"code",
|
|
||||||
"label",
|
|
||||||
"component",
|
|
||||||
"subCommands",
|
|
||||||
"outputs",
|
|
||||||
"options",
|
|
||||||
"defaultValue",
|
|
||||||
"icon",
|
|
||||||
"width",
|
|
||||||
"placeholder",
|
|
||||||
"summary",
|
|
||||||
"type",
|
|
||||||
"defaultOutputVariable",
|
|
||||||
];
|
|
||||||
uselessProps.forEach((prop) => delete cmdCopy[prop]);
|
|
||||||
return cmdCopy;
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
localStorage.setItem("quickcomposer.flows", JSON.stringify(flowsData));
|
|
||||||
quickcommand.showMessageBox("保存成功");
|
|
||||||
},
|
},
|
||||||
loadFlows() {
|
runCommand(flows = this.flows) {
|
||||||
const savedFlows = localStorage.getItem("quickcomposer.flows");
|
const command = {
|
||||||
if (!savedFlows) return;
|
...this.modelValue,
|
||||||
|
flows,
|
||||||
const flowsData = JSON.parse(savedFlows);
|
|
||||||
const newFlows = flowsData.map((flow) => ({
|
|
||||||
...flow,
|
|
||||||
commands: flow.commands.map((cmd) => {
|
|
||||||
// 恢复所有属性
|
|
||||||
const command = findCommandByValue(cmd.value);
|
|
||||||
return {
|
|
||||||
...command,
|
|
||||||
...cmd,
|
|
||||||
};
|
};
|
||||||
}),
|
this.$emit("action", "run", command);
|
||||||
}));
|
|
||||||
this.Sub(newFlows);
|
|
||||||
this.activeTab = this.mainFlow.id;
|
|
||||||
},
|
|
||||||
runFlows(flow) {
|
|
||||||
const code = flow
|
|
||||||
? this.generateFlowCode(flow)
|
|
||||||
: this.generateAllFlowCode();
|
|
||||||
this.$emit("action", "run", code);
|
|
||||||
if (!code.includes("console.log")) {
|
|
||||||
quickcommand.showMessageBox("已运行");
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
collapseAll() {
|
collapseAll() {
|
||||||
this.$refs.flowRefs.forEach((flow) => {
|
this.$refs.flowRefs.forEach((flow) => {
|
||||||
@ -399,9 +380,12 @@ export default defineComponent({
|
|||||||
this.activeTab = flow.id;
|
this.activeTab = flow.id;
|
||||||
this.toggleVariableManager();
|
this.toggleVariableManager();
|
||||||
},
|
},
|
||||||
Sub(flow) {
|
updateCommandConfig(newVal) {
|
||||||
this.mainFlow = flow[0];
|
const newModelValue = {
|
||||||
this.subFlows = flow.slice(1);
|
...this.modelValue,
|
||||||
|
...newVal,
|
||||||
|
};
|
||||||
|
this.$emit("update:modelValue", newModelValue);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -556,4 +540,8 @@ export default defineComponent({
|
|||||||
border-bottom: 2px solid var(--q-primary);
|
border-bottom: 2px solid var(--q-primary);
|
||||||
transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.command-config-panel {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -57,9 +57,6 @@
|
|||||||
<q-btn dense flat icon="save" @click="$emit('action', 'save')">
|
<q-btn dense flat icon="save" @click="$emit('action', 'save')">
|
||||||
<q-tooltip>保存</q-tooltip>
|
<q-tooltip>保存</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn dense flat icon="history" @click="$emit('action', 'load')">
|
|
||||||
<q-tooltip>载入</q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
<q-btn flat dense icon="preview" @click="isVisible = true">
|
<q-btn flat dense icon="preview" @click="isVisible = true">
|
||||||
<q-tooltip>预览代码</q-tooltip>
|
<q-tooltip>预览代码</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
@ -92,15 +89,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
import { generateFlowsCode } from "js/composer/generateCode";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "ComposerButtons",
|
name: "ComposerButtons",
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
generateCode: {
|
|
||||||
type: Function,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
isAllCollapsed: {
|
isAllCollapsed: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@ -109,6 +103,10 @@ export default defineComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
flows: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
emits: ["action"],
|
emits: ["action"],
|
||||||
@ -124,7 +122,7 @@ export default defineComponent({
|
|||||||
watch: {
|
watch: {
|
||||||
isVisible(val) {
|
isVisible(val) {
|
||||||
if (val) {
|
if (val) {
|
||||||
this.code = this.generateCode();
|
this.code = generateFlowsCode(this.flows);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -172,16 +170,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
/* 自定义滚动条 */
|
/* 自定义滚动条 */
|
||||||
.preview-code::-webkit-scrollbar {
|
.preview-code::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: 5px;
|
||||||
}
|
|
||||||
|
|
||||||
.preview-code::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--q-primary-opacity-20);
|
|
||||||
border-radius: 3px;
|
|
||||||
transition: background 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-code::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--q-primary-opacity-30);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
336
src/components/editor/CommandConfig.vue
Normal file
336
src/components/editor/CommandConfig.vue
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
<template>
|
||||||
|
<q-expansion-item
|
||||||
|
v-model="isExpanded"
|
||||||
|
class="command-config"
|
||||||
|
@dragover="isExpanded = false"
|
||||||
|
>
|
||||||
|
<template v-slot:header>
|
||||||
|
<div class="row q-col-gutter-sm basic-config">
|
||||||
|
<div class="col-auto">
|
||||||
|
<q-avatar size="36px" square class="featureIco">
|
||||||
|
<q-img
|
||||||
|
@click.stop="showIconPicker = true"
|
||||||
|
:src="
|
||||||
|
currentCommand.features.icon
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</q-avatar>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<q-input
|
||||||
|
:model-value="currentCommand.features.explain"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
@update:model-value="updateCommand('features.explain', $event)"
|
||||||
|
placeholder="名称"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon name="drive_file_rename_outline" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 展开的配置项 -->
|
||||||
|
<div class="expanded-config">
|
||||||
|
<!-- 匹配规则 -->
|
||||||
|
<div class="config-section">
|
||||||
|
<div class="section-title">
|
||||||
|
<q-icon name="rule" size="16px" />
|
||||||
|
<span class="q-ml-sm">匹配规则</span>
|
||||||
|
<q-icon
|
||||||
|
name="help"
|
||||||
|
size="16px"
|
||||||
|
class="q-ml-sm cursor-pointer"
|
||||||
|
@click="showMatchRuleHelp"
|
||||||
|
>
|
||||||
|
<q-tooltip>查看帮助</q-tooltip>
|
||||||
|
</q-icon>
|
||||||
|
<q-icon
|
||||||
|
name="data_object"
|
||||||
|
size="16px"
|
||||||
|
class="q-ml-sm cursor-pointer"
|
||||||
|
:color="showMatchRuleJson ? 'primary' : 'grey'"
|
||||||
|
@click="showMatchRuleJson = !showMatchRuleJson"
|
||||||
|
>
|
||||||
|
<q-tooltip>编辑JSON配置</q-tooltip>
|
||||||
|
</q-icon>
|
||||||
|
</div>
|
||||||
|
<MatchRuleEditor
|
||||||
|
:showJson="showMatchRuleJson"
|
||||||
|
:model-value="currentCommand.features.cmds"
|
||||||
|
@update:model-value="updateCommand('features.cmds', $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 标签 -->
|
||||||
|
<div class="config-section">
|
||||||
|
<div class="section-title">
|
||||||
|
<q-icon name="label" size="16px" />
|
||||||
|
<span class="q-ml-sm">标签</span>
|
||||||
|
</div>
|
||||||
|
<q-select
|
||||||
|
:model-value="currentCommand.tags"
|
||||||
|
@update:model-value="updateCommand('tags', $event)"
|
||||||
|
:options="allQuickCommandTags"
|
||||||
|
dense
|
||||||
|
options-dense
|
||||||
|
filled
|
||||||
|
use-input
|
||||||
|
use-chips
|
||||||
|
multiple
|
||||||
|
hide-dropdown-icon
|
||||||
|
new-value-mode="add-unique"
|
||||||
|
placeholder="回车添加,最多3个"
|
||||||
|
max-values="3"
|
||||||
|
@new-value="tagVerify"
|
||||||
|
input-debounce="0"
|
||||||
|
ref="commandTagRef"
|
||||||
|
@blur="(e) => autoAddInputVal(e, $refs.commandTagRef)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 输出 -->
|
||||||
|
<div class="config-section">
|
||||||
|
<div class="section-title">
|
||||||
|
<q-icon name="output" size="16px" />
|
||||||
|
<span class="q-ml-sm">输出</span>
|
||||||
|
</div>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<div class="col-12">
|
||||||
|
<ButtonGroup
|
||||||
|
:model-value="currentCommand.output"
|
||||||
|
:options="outputTypesOptionsDy"
|
||||||
|
@update:model-value="updateCommand('output', $event)"
|
||||||
|
height="26px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="section-title">
|
||||||
|
<q-icon name="search" size="16px" />
|
||||||
|
<span class="q-ml-sm"
|
||||||
|
>搜索面板推送
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
size="xs"
|
||||||
|
icon="help"
|
||||||
|
@click="showMainPushHelp"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ButtonGroup
|
||||||
|
:model-value="currentCommand.features.mainPush"
|
||||||
|
:options="searchPushOptions"
|
||||||
|
@update:model-value="handleMainPushChange"
|
||||||
|
height="30px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 平台 -->
|
||||||
|
<div class="config-section">
|
||||||
|
<div class="section-title">
|
||||||
|
<q-icon name="devices" size="16px" />
|
||||||
|
<span class="q-ml-sm">平台</span>
|
||||||
|
</div>
|
||||||
|
<CheckGroup
|
||||||
|
:model-value="currentCommand.features.platform"
|
||||||
|
:options="Object.values(platformTypes)"
|
||||||
|
@update:model-value="updateCommand('features.platform', $event)"
|
||||||
|
height="30px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 图标选择对话框 -->
|
||||||
|
<q-dialog v-model="showIconPicker" position="left">
|
||||||
|
<iconPicker
|
||||||
|
@iconChanged="(dataUrl) => updateCommand('features.icon', dataUrl)"
|
||||||
|
ref="icon"
|
||||||
|
/>
|
||||||
|
</q-dialog>
|
||||||
|
</q-expansion-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, inject, computed } from "vue";
|
||||||
|
import iconPicker from "components/popup/IconPicker.vue";
|
||||||
|
import outputTypes from "js/options/outputTypes.js";
|
||||||
|
import platformTypes from "js/options/platformTypes.js";
|
||||||
|
import CheckGroup from "components/composer/common/CheckGroup.vue";
|
||||||
|
import ButtonGroup from "components/composer/common/ButtonGroup.vue";
|
||||||
|
import commandTypes from "js/options/commandTypes.js";
|
||||||
|
import MatchRuleEditor from "components/editor/MatchRuleEditor.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "CommandConfig",
|
||||||
|
components: {
|
||||||
|
iconPicker,
|
||||||
|
CheckGroup,
|
||||||
|
ButtonGroup,
|
||||||
|
MatchRuleEditor,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ["update:modelValue"],
|
||||||
|
setup(props) {
|
||||||
|
const allQuickCommandTags = inject("allQuickCommandTags").filter(
|
||||||
|
(tag) => !["默认", "未分类", "搜索结果"].includes(tag)
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentCommand = computed(() => props.modelValue);
|
||||||
|
|
||||||
|
return {
|
||||||
|
allQuickCommandTags,
|
||||||
|
currentCommand,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isExpanded: false,
|
||||||
|
showIconPicker: false,
|
||||||
|
showMatchRuleJson: false,
|
||||||
|
commandTypes,
|
||||||
|
outputTypes,
|
||||||
|
platformTypes,
|
||||||
|
isFileTypeDirectory: false,
|
||||||
|
searchPushOptions: [
|
||||||
|
{ value: false, label: "禁用(进入插件后才执行命令)" },
|
||||||
|
{ value: true, label: "启用(在uTools主搜索框直接执行命令)" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
commandTypesOptions() {
|
||||||
|
const options = Object.values(this.commandTypes);
|
||||||
|
return this.currentCommand.features.mainPush
|
||||||
|
? options.map((cmdType) =>
|
||||||
|
["regex", "over", "key"].includes(cmdType.name)
|
||||||
|
? cmdType
|
||||||
|
: { ...cmdType, disabled: true }
|
||||||
|
)
|
||||||
|
: options;
|
||||||
|
},
|
||||||
|
outputTypesOptionsDy() {
|
||||||
|
const options = Object.values(this.outputTypes);
|
||||||
|
return this.currentCommand.features.mainPush
|
||||||
|
? options.map((outputType) =>
|
||||||
|
outputType.name !== "text"
|
||||||
|
? { ...outputType, disabled: true }
|
||||||
|
: outputType
|
||||||
|
)
|
||||||
|
: options;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateCommand(path, value) {
|
||||||
|
const newCommand = { ...this.currentCommand };
|
||||||
|
const keys = path.split(".");
|
||||||
|
const lastKey = keys.pop();
|
||||||
|
const target = keys.reduce((obj, key) => obj[key], newCommand);
|
||||||
|
target[lastKey] = value;
|
||||||
|
this.$emit("update:modelValue", newCommand);
|
||||||
|
},
|
||||||
|
tagVerify(val, done) {
|
||||||
|
if (["默认", "未分类", "搜索结果"].includes(val)) {
|
||||||
|
return done(`_${val}_`);
|
||||||
|
}
|
||||||
|
done(val);
|
||||||
|
},
|
||||||
|
autoAddInputVal(e, ref) {
|
||||||
|
const inputValue = e.target.value;
|
||||||
|
if (!inputValue) return;
|
||||||
|
ref.add(inputValue, true);
|
||||||
|
},
|
||||||
|
handleMainPushChange(val) {
|
||||||
|
this.updateCommand("features.mainPush", val);
|
||||||
|
if (val) {
|
||||||
|
this.updateCommand("output", "text");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showMainPushHelp() {
|
||||||
|
window.showUb.help("#u0e9f1430");
|
||||||
|
},
|
||||||
|
showMatchRuleHelp() {
|
||||||
|
utools.ubrowser
|
||||||
|
.goto(
|
||||||
|
"https://www.u-tools.cn/docs/developer/information/plugin-json.html#%E5%8A%9F%E8%83%BD%E6%8C%87%E4%BB%A4"
|
||||||
|
)
|
||||||
|
.run();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.command-config {
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-config :deep(.q-field__inner) {
|
||||||
|
font-family: "Consolas", "Monaco", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.basic-config {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-config :deep(.q-item) {
|
||||||
|
padding: 0;
|
||||||
|
min-height: unset;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-config :deep(.q-item__section--side) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-config :deep(.q-item:hover) {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-config :deep(.q-focus-helper) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-section {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--q-primary);
|
||||||
|
padding: 4px 2px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureIco {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureIco:hover {
|
||||||
|
transform: scale(1.02) translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.expanded-config {
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 450px;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
</style>
|
449
src/components/editor/MatchRuleEditor.vue
Normal file
449
src/components/editor/MatchRuleEditor.vue
Normal file
@ -0,0 +1,449 @@
|
|||||||
|
<template>
|
||||||
|
<div class="match-rule-editor">
|
||||||
|
<!-- JSON编辑模式 -->
|
||||||
|
<div v-if="showJson" class="json-editor">
|
||||||
|
<q-input
|
||||||
|
:model-value="jsonText"
|
||||||
|
@update:model-value="updateJsonText($event)"
|
||||||
|
type="textarea"
|
||||||
|
filled
|
||||||
|
autogrow
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 可视化编辑模式 -->
|
||||||
|
<div v-else class="visual-editor">
|
||||||
|
<!-- 规则类型选择按钮组 -->
|
||||||
|
<div class="rule-type-buttons q-mb-sm q-mt-xs">
|
||||||
|
<div v-for="type in ruleTypeOptions" :key="type.value" class="col-auto">
|
||||||
|
<q-btn
|
||||||
|
:label="type.label"
|
||||||
|
:icon="type.icon"
|
||||||
|
:color="type.color"
|
||||||
|
outline
|
||||||
|
dense
|
||||||
|
class="rule-type-btn"
|
||||||
|
@click="addRuleByType(type.value)"
|
||||||
|
>
|
||||||
|
<q-badge
|
||||||
|
v-if="ruleTypeCounts[type.value]"
|
||||||
|
floating
|
||||||
|
:label="ruleTypeCounts[type.value]"
|
||||||
|
/>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 规则列表 -->
|
||||||
|
<div class="rules-container">
|
||||||
|
<!-- 关键词匹配规则组 -->
|
||||||
|
<div v-if="keyRules.length" class="key-rules-row">
|
||||||
|
<div
|
||||||
|
v-for="(rule, index) in keyRules"
|
||||||
|
:key="'key-' + index"
|
||||||
|
class="key-input-wrapper"
|
||||||
|
>
|
||||||
|
<q-input
|
||||||
|
:model-value="rule"
|
||||||
|
@update:model-value="updateModelValueByIndex($event, index)"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
:label="`关键词 ${index + 1}`"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon
|
||||||
|
name="cancel"
|
||||||
|
@click="removeRule(index)"
|
||||||
|
size="12px"
|
||||||
|
class="cursor-pointer text-grey"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 其他类型规则 -->
|
||||||
|
<div
|
||||||
|
v-for="(rule, index) in nonKeyRules"
|
||||||
|
:key="'other-' + index"
|
||||||
|
class="rule-item"
|
||||||
|
>
|
||||||
|
<div class="row items-center">
|
||||||
|
<div class="col-auto q-mr-sm">
|
||||||
|
<q-field dense filled>
|
||||||
|
<template v-slot:control>
|
||||||
|
<q-icon :name="commandTypes[rule.type].icon" />
|
||||||
|
<div class="q-mx-xs" style="user-select: none">
|
||||||
|
{{ commandTypes[rule.type].label }}
|
||||||
|
</div>
|
||||||
|
<q-icon
|
||||||
|
name="cancel"
|
||||||
|
@click="removeRule(index + keyRules.length)"
|
||||||
|
size="12px"
|
||||||
|
class="cursor-pointer text-grey"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 正则匹配 -->
|
||||||
|
<template v-if="rule.type === 'regex'">
|
||||||
|
<div class="col row q-gutter-sm">
|
||||||
|
<q-input
|
||||||
|
v-model="rule.match"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
label="匹配文本正则表达式"
|
||||||
|
placeholder="例:/xxx/,任意匹配的正则会被 uTools 忽略"
|
||||||
|
class="col"
|
||||||
|
@blur="validateRegex(rule)"
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
v-model.number="rule.minLength"
|
||||||
|
type="number"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
label="最小长度"
|
||||||
|
placeholder="可选"
|
||||||
|
style="width: 65px"
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
v-model.number="rule.maxLength"
|
||||||
|
type="number"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
label="最大长度"
|
||||||
|
placeholder="可选"
|
||||||
|
style="width: 65px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 文件匹配 -->
|
||||||
|
<template v-else-if="rule.type === 'files'">
|
||||||
|
<div class="col row q-gutter-sm">
|
||||||
|
<q-input
|
||||||
|
v-model="rule.match"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
label="匹配文件(夹)名正则表达式"
|
||||||
|
placeholder="可选,例:/xxx/"
|
||||||
|
class="col"
|
||||||
|
@blur="validateRegex(rule)"
|
||||||
|
/>
|
||||||
|
<q-select
|
||||||
|
:model-value="rule.fileType || 'file'"
|
||||||
|
@update:model-value="rule.fileType = $event"
|
||||||
|
:options="[
|
||||||
|
{ label: '文件', value: 'file' },
|
||||||
|
{ label: '文件夹', value: 'directory' },
|
||||||
|
]"
|
||||||
|
label="文件类型"
|
||||||
|
dense
|
||||||
|
options-dense
|
||||||
|
filled
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
class="col-2"
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
v-model.number="rule.minLength"
|
||||||
|
type="number"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
label="最小数量"
|
||||||
|
placeholder="可选"
|
||||||
|
style="width: 65px"
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
v-model.number="rule.maxLength"
|
||||||
|
type="number"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
label="最大数量"
|
||||||
|
placeholder="可选"
|
||||||
|
style="width: 65px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 窗口匹配 -->
|
||||||
|
<template v-else-if="rule.type === 'window'">
|
||||||
|
<div class="col row q-gutter-sm">
|
||||||
|
<q-input
|
||||||
|
:model-value="rule.match.app.join(',')"
|
||||||
|
@update:model-value="
|
||||||
|
rule.match.app = getArrayFromString($event)
|
||||||
|
"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
class="col"
|
||||||
|
label="程序/应用名,逗号隔开"
|
||||||
|
placeholder="例:xxx.exe,xxx.app"
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
v-model="rule.match.title"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
label="匹配窗口标题正则表达式"
|
||||||
|
placeholder="可选,例:/xxx/"
|
||||||
|
class="col-5"
|
||||||
|
@blur="validateRegex({ match: rule.match.title })"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 图片匹配 -->
|
||||||
|
<template v-else-if="rule.type === 'img'">
|
||||||
|
<q-field v-model="rule.label" dense filled class="col">
|
||||||
|
<template v-slot:control>
|
||||||
|
<div>无需配置</div>
|
||||||
|
</template>
|
||||||
|
</q-field>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 所有文本匹配 -->
|
||||||
|
<template v-else-if="rule.type === 'over'">
|
||||||
|
<div class="col row q-gutter-sm">
|
||||||
|
<q-input
|
||||||
|
v-model="rule.exclude"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
label="排除的正则表达式字符串"
|
||||||
|
placeholder="可选,例:/xxx/"
|
||||||
|
class="col"
|
||||||
|
@blur="validateRegex({ match: rule.exclude })"
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
v-model.number="rule.minLength"
|
||||||
|
type="number"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
label="最小长度"
|
||||||
|
placeholder="可选"
|
||||||
|
style="width: 65px"
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
v-model.number="rule.maxLength"
|
||||||
|
type="number"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
label="最大长度"
|
||||||
|
placeholder="可选"
|
||||||
|
style="width: 65px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import commandTypes from "js/options/commandTypes.js";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "MatchRuleEditor",
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
showJson: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
emits: ["update:modelValue"],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
ruleTypeOptions: Object.values(commandTypes)
|
||||||
|
.filter((type) => type.name !== "professional")
|
||||||
|
.map((type) => ({
|
||||||
|
label: type.label,
|
||||||
|
value: type.name,
|
||||||
|
icon: type.icon,
|
||||||
|
color: type.color,
|
||||||
|
})),
|
||||||
|
commandTypes,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
localRules() {
|
||||||
|
return [...this.keyRules, ...this.nonKeyRules];
|
||||||
|
},
|
||||||
|
|
||||||
|
// 关键词规则
|
||||||
|
keyRules() {
|
||||||
|
return this.modelValue.filter((rule) => typeof rule === "string");
|
||||||
|
},
|
||||||
|
|
||||||
|
// 非关键词规则
|
||||||
|
nonKeyRules() {
|
||||||
|
return this.modelValue.filter((rule) => typeof rule !== "string");
|
||||||
|
},
|
||||||
|
|
||||||
|
// JSON文本
|
||||||
|
jsonText() {
|
||||||
|
return JSON.stringify(this.modelValue, null, 2);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 计算每种规则类型的数量
|
||||||
|
ruleTypeCounts() {
|
||||||
|
return this.localRules.reduce((counts, rule) => {
|
||||||
|
const type = typeof rule === "string" ? "key" : rule.type;
|
||||||
|
counts[type] = (counts[type] || 0) + 1;
|
||||||
|
return counts;
|
||||||
|
}, {});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
validateRegex(rule) {
|
||||||
|
const matchValue = rule.match;
|
||||||
|
if (!matchValue) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!matchValue.startsWith("/")) {
|
||||||
|
rule.match = `/${matchValue}/`;
|
||||||
|
}
|
||||||
|
new RegExp(matchValue.replace(/^\/|\/[gimuy]*$/g, ""));
|
||||||
|
} catch (e) {
|
||||||
|
rule.match = "/./";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
removeRule(index) {
|
||||||
|
const newRules = [...this.localRules];
|
||||||
|
newRules.splice(index, 1);
|
||||||
|
if (newRules.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.updateModelValue(newRules);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateJsonText(newJsonText) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(newJsonText);
|
||||||
|
const validConfig = parsed.filter((rule) => {
|
||||||
|
if (typeof rule === "string") return true;
|
||||||
|
return Object.values(commandTypes).some(
|
||||||
|
(type) => type.name === rule.type
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.updateModelValue(validConfig);
|
||||||
|
} catch (_) {}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateModelValue(newRules) {
|
||||||
|
this.$emit("update:modelValue", newRules);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateModelValueByIndex(newValue, index) {
|
||||||
|
const newRules = [...this.localRules];
|
||||||
|
newRules[index] = newValue;
|
||||||
|
this.updateModelValue(newRules);
|
||||||
|
},
|
||||||
|
|
||||||
|
getArrayFromString(str) {
|
||||||
|
return str
|
||||||
|
.split(",")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((item) => item.trim());
|
||||||
|
},
|
||||||
|
|
||||||
|
// 根据类型添加新规则
|
||||||
|
addRuleByType(type) {
|
||||||
|
const newRules = [...this.localRules];
|
||||||
|
|
||||||
|
if (type === "key") {
|
||||||
|
// 在所有关键词后面插入新的关键词
|
||||||
|
newRules.splice(this.keyRules.length, 0, "");
|
||||||
|
} else {
|
||||||
|
// 非关键词类型,直接添加到末尾
|
||||||
|
newRules.push({
|
||||||
|
type,
|
||||||
|
match: {
|
||||||
|
window: { app: [] },
|
||||||
|
regex: "",
|
||||||
|
}[type],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateModelValue(newRules);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.rule-type-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-type-buttons :deep(.q-btn) {
|
||||||
|
padding: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
height: 24px;
|
||||||
|
min-width: 85px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body--dark .rule-type-count {
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-rules-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-input-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-input-wrapper :deep(.q-field__append) {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-input-wrapper :deep(.q-icon) {
|
||||||
|
font-size: 16px;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-input-wrapper :deep(.q-icon:hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-editor {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-editor :deep(.q-field__native) {
|
||||||
|
min-height: 200px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 隐藏默认的数字输入框箭头 - Chrome, Safari, Edge, Opera */
|
||||||
|
.match-rule-editor :deep(input[type="number"]::-webkit-outer-spin-button),
|
||||||
|
.match-rule-editor :deep(input[type="number"]::-webkit-inner-spin-button) {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
@ -25,6 +25,10 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.command-composer .q-field--filled.q-select--with-chips .q-field__control .q-chip {
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
/* 输入框图标大小 */
|
/* 输入框图标大小 */
|
||||||
.command-composer .q-field--filled .q-field__control .q-icon {
|
.command-composer .q-field--filled .q-field__control .q-icon {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
const winScpt = `Add-Type -TypeDefinition @"
|
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
public class Win32 {
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct RECT {
|
|
||||||
public int Left;
|
|
||||||
public int Top;
|
|
||||||
public int Right;
|
|
||||||
public int Bottom;
|
|
||||||
}
|
|
||||||
[DllImport("user32.dll", SetLastError=true)]
|
|
||||||
public static extern IntPtr GetForegroundWindow();
|
|
||||||
[DllImport("user32.dll", SetLastError=true)]
|
|
||||||
public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
|
|
||||||
}
|
|
||||||
"@
|
|
||||||
$foregroundWindow = [Win32]::GetForegroundWindow()
|
|
||||||
$windowRect = New-Object Win32+RECT
|
|
||||||
$_ = [Win32]::GetWindowRect($foregroundWindow, [ref]$windowRect)
|
|
||||||
$result = New-Object PSObject
|
|
||||||
$result | Add-Member -Type NoteProperty -Name Left -Value $windowRect.Left
|
|
||||||
$result | Add-Member -Type NoteProperty -Name Top -Value $windowRect.Top
|
|
||||||
$result | Add-Member -Type NoteProperty -Name Right -Value $windowRect.Right
|
|
||||||
$result | Add-Member -Type NoteProperty -Name Bottom -Value $windowRect.Bottom
|
|
||||||
$result | ConvertTo-Json`;
|
|
||||||
|
|
||||||
const macScpt = `tell application "System Events"
|
|
||||||
set frontmostProcess to first application process where it is frontmost
|
|
||||||
set frontmostWindow to first window of frontmostProcess
|
|
||||||
set {windowLeft, windowTop} to position of frontmostWindow
|
|
||||||
set {windowWidth, windowHeight} to size of frontmostWindow
|
|
||||||
set windowRight to windowLeft + windowWidth
|
|
||||||
set windowBottom to windowTop + windowHeight
|
|
||||||
end tell
|
|
||||||
return "{ \\"Left\\": " & windowLeft & ", \\"Top\\": " & windowTop & ", \\"Right\\": " & windowRight & ", \\"Bottom\\": " &windowBottom & " }"`;
|
|
||||||
|
|
||||||
const getForegroundWindowPos = async () => {
|
|
||||||
let foregroundWindowPos;
|
|
||||||
try {
|
|
||||||
if (window.utools.isWindows()) {
|
|
||||||
foregroundWindowPos = await window.quickcommand.runPowerShell(winScpt);
|
|
||||||
} else if (window.utools.isMacOS()) {
|
|
||||||
foregroundWindowPos = await window.quickcommand.runAppleScript(macScpt);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
if (!foregroundWindowPos) return;
|
|
||||||
return JSON.parse(foregroundWindowPos);
|
|
||||||
};
|
|
||||||
|
|
||||||
let autoDetach = async () => {
|
|
||||||
const foregroundWindowPos = await getForegroundWindowPos();
|
|
||||||
console.log(foregroundWindowPos);
|
|
||||||
if (foregroundWindowPos) {
|
|
||||||
const { Left, Top, Right, Bottom } = foregroundWindowPos;
|
|
||||||
let { x, y } = window.utools.getCursorScreenPoint();
|
|
||||||
window.utools.simulateMouseDoubleClick(Left + 200, Top + 30);
|
|
||||||
window.utools.simulateMouseMove(x, y);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
autoDetach,
|
|
||||||
};
|
|
@ -2,6 +2,8 @@ import { reactive } from "vue";
|
|||||||
import quickcommandParser from "js/common/quickcommandParser.js";
|
import quickcommandParser from "js/common/quickcommandParser.js";
|
||||||
import importAll from "js/common/importAll.js";
|
import importAll from "js/common/importAll.js";
|
||||||
import utoolsFull from "js/utools.js";
|
import utoolsFull from "js/utools.js";
|
||||||
|
import { getUniqueId } from "js/common/uuid.js";
|
||||||
|
import outputTypes from "js/options/outputTypes.js";
|
||||||
|
|
||||||
// 默认命令
|
// 默认命令
|
||||||
const defaultCommands = importAll(
|
const defaultCommands = importAll(
|
||||||
@ -16,6 +18,50 @@ const state = reactive({
|
|||||||
activatedQuickPanels: [],
|
activatedQuickPanels: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getCmdType = (cmds) => {
|
||||||
|
const firstCmdType = cmds[0].type || "key";
|
||||||
|
if (!cmds.find((x) => typeof x !== "string")) return "key";
|
||||||
|
if (!cmds.find((x) => x.type !== firstCmdType)) return firstCmdType;
|
||||||
|
return "professional";
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFeatureCode = (cmds) => {
|
||||||
|
return `${getCmdType(cmds)}_${getUniqueId({ short: true })}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLabeledCmds = (cmds, explain) => {
|
||||||
|
return cmds.map((cmd) => {
|
||||||
|
if (typeof cmd === "string") {
|
||||||
|
return cmd || explain;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...cmd,
|
||||||
|
label: cmd.label || explain,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getValidCommand = (command) => {
|
||||||
|
const { cmds, explain } = command.features;
|
||||||
|
if (!explain) throw "名称不能为空";
|
||||||
|
if (!Array.isArray(cmds)) throw "匹配规则格式错误";
|
||||||
|
|
||||||
|
// 未配置label或关键字时,直接使用名称
|
||||||
|
command.features.cmds = getLabeledCmds(cmds, explain);
|
||||||
|
|
||||||
|
// 不需要显示输入框的输入类型,添加mainHide属性
|
||||||
|
if (outputTypes[command.output].outPlugin) {
|
||||||
|
command.features.mainHide = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成唯一code
|
||||||
|
if (!command.features.code) {
|
||||||
|
command.features.code = getFeatureCode(cmds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return window.lodashM.cloneDeep(command);
|
||||||
|
};
|
||||||
|
|
||||||
// 使用函数工厂模式,确保每个组件获取自己的状态副本
|
// 使用函数工厂模式,确保每个组件获取自己的状态副本
|
||||||
export function useCommandManager() {
|
export function useCommandManager() {
|
||||||
// 获取已启用的命令
|
// 获取已启用的命令
|
||||||
@ -59,6 +105,11 @@ export function useCommandManager() {
|
|||||||
|
|
||||||
// 保存命令
|
// 保存命令
|
||||||
const saveCommand = (command) => {
|
const saveCommand = (command) => {
|
||||||
|
try {
|
||||||
|
command = getValidCommand(command);
|
||||||
|
} catch (e) {
|
||||||
|
return quickcommand.showMessageBox(e.toString(), "error");
|
||||||
|
}
|
||||||
const code = command.features.code;
|
const code = command.features.code;
|
||||||
state.allQuickCommands[code] = command;
|
state.allQuickCommands[code] = command;
|
||||||
|
|
||||||
@ -70,7 +121,7 @@ export function useCommandManager() {
|
|||||||
utoolsFull.whole.setFeature(command.features);
|
utoolsFull.whole.setFeature(command.features);
|
||||||
|
|
||||||
if (!isDefaultCommand(code)) {
|
if (!isDefaultCommand(code)) {
|
||||||
utoolsFull.putDB(window.lodashM.cloneDeep(command), "qc_" + code);
|
utoolsFull.putDB(command, "qc_" + code);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllQuickCommandTags();
|
getAllQuickCommandTags();
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
export const getUniqueId = () => {
|
export const getUniqueId = (options = {}) => {
|
||||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
const { short = false } = options;
|
||||||
|
const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
||||||
const r = (Math.random() * 16) | 0;
|
const r = (Math.random() * 16) | 0;
|
||||||
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
||||||
return v.toString(16);
|
return v.toString(16);
|
||||||
});
|
});
|
||||||
|
return short ? uuid.substring(0, 8) : uuid;
|
||||||
};
|
};
|
||||||
|
@ -133,3 +133,10 @@ export function generateCode(flow) {
|
|||||||
|
|
||||||
return finalCode;
|
return finalCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function generateFlowsCode(flows) {
|
||||||
|
const [mainFlow, ...subFlows] = flows;
|
||||||
|
return [...subFlows, mainFlow]
|
||||||
|
.map((flow) => generateCode(flow))
|
||||||
|
.join("\n\n");
|
||||||
|
}
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const jsonSample = [
|
const jsonSample = [
|
||||||
"关键词",
|
"关键词1",
|
||||||
|
"关键词2",
|
||||||
{
|
{
|
||||||
type: "img",
|
type: "img",
|
||||||
label: "图片匹配",
|
label: "图片匹配",
|
||||||
@ -41,12 +42,12 @@ const jsonSample = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const commandTypes = {
|
export default {
|
||||||
key: {
|
key: {
|
||||||
name: "key",
|
name: "key",
|
||||||
label: "关键词",
|
label: "关键词",
|
||||||
icon: "font_download",
|
icon: "font_download",
|
||||||
color: "teal",
|
color: "blue",
|
||||||
matchLabel: "关键词",
|
matchLabel: "关键词",
|
||||||
desc: "直接在主输入框输入对应关键字,最通用的一种模式,关键字可以设置多个",
|
desc: "直接在主输入框输入对应关键字,最通用的一种模式,关键字可以设置多个",
|
||||||
valueType: "array",
|
valueType: "array",
|
||||||
@ -57,7 +58,7 @@ const commandTypes = {
|
|||||||
},
|
},
|
||||||
regex: {
|
regex: {
|
||||||
name: "regex",
|
name: "regex",
|
||||||
label: "正则/划词",
|
label: "正则",
|
||||||
icon: "rule",
|
icon: "rule",
|
||||||
color: "cyan",
|
color: "cyan",
|
||||||
matchLabel: "正则",
|
matchLabel: "正则",
|
||||||
@ -82,7 +83,7 @@ const commandTypes = {
|
|||||||
over: {
|
over: {
|
||||||
name: "over",
|
name: "over",
|
||||||
label: "所有文本",
|
label: "所有文本",
|
||||||
matchLabel: "无需设置",
|
matchLabel: "无需配置",
|
||||||
icon: "emergency",
|
icon: "emergency",
|
||||||
color: "light-green",
|
color: "light-green",
|
||||||
desc: "匹配主输入框的所有文本,但只有在该文本未设置对应的插件或功能时才生效",
|
desc: "匹配主输入框的所有文本,但只有在该文本未设置对应的插件或功能时才生效",
|
||||||
@ -104,7 +105,7 @@ const commandTypes = {
|
|||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
name: "window",
|
name: "window",
|
||||||
label: "窗口/进程",
|
label: "窗口",
|
||||||
matchLabel: "进程名",
|
matchLabel: "进程名",
|
||||||
icon: "widgets",
|
icon: "widgets",
|
||||||
color: "indigo",
|
color: "indigo",
|
||||||
@ -154,7 +155,7 @@ const commandTypes = {
|
|||||||
},
|
},
|
||||||
files: {
|
files: {
|
||||||
name: "files",
|
name: "files",
|
||||||
label: "复制/选中文件",
|
label: "文件",
|
||||||
matchLabel: "正则",
|
matchLabel: "正则",
|
||||||
icon: "description",
|
icon: "description",
|
||||||
color: "light-blue",
|
color: "light-blue",
|
||||||
@ -200,5 +201,3 @@ const commandTypes = {
|
|||||||
jsonSample: jsonSample,
|
jsonSample: jsonSample,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default commandTypes;
|
|
||||||
|
@ -27,7 +27,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
runCurrentCommand(command) {
|
runCurrentCommand(command) {
|
||||||
this.$refs.result.runCurrentCommand(window.lodashM.cloneDeep(command));
|
this.$refs.result.runCurrentCommand(command);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -221,9 +221,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
runCommand(code) {
|
runCommand(code) {
|
||||||
this.$refs.result.runCurrentCommand(
|
this.$refs.result.runCurrentCommand(this.allQuickCommands[code]);
|
||||||
window.lodashM.cloneDeep(this.allQuickCommands[code])
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
// 启用命令
|
// 启用命令
|
||||||
enableCommand(code) {
|
enableCommand(code) {
|
||||||
@ -241,13 +239,16 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 编辑命令
|
// 编辑命令
|
||||||
editCommand(command) {
|
editCommand(commandOrCode) {
|
||||||
// 即可传入 code,也可直接传入 command
|
// 即可传入 code,也可直接传入 command
|
||||||
if (typeof command === "string") command = this.allQuickCommands[command];
|
const command =
|
||||||
|
typeof commandOrCode === "string"
|
||||||
|
? this.allQuickCommands[commandOrCode]
|
||||||
|
: commandOrCode;
|
||||||
this.commandEditorAction = {
|
this.commandEditorAction = {
|
||||||
type: "edit",
|
type: "edit",
|
||||||
data: window.lodashM.cloneDeep(command),
|
data: window.lodashM.cloneDeep(command),
|
||||||
component: "CommandEditor",
|
component: command.flows ? "ComposerEditor" : "CommandEditor",
|
||||||
};
|
};
|
||||||
this.isEditorShow = true;
|
this.isEditorShow = true;
|
||||||
},
|
},
|
||||||
@ -349,6 +350,7 @@ export default {
|
|||||||
saveCommand(command) {
|
saveCommand(command) {
|
||||||
const code = this.commandManager.saveCommand(command);
|
const code = this.commandManager.saveCommand(command);
|
||||||
this.locateToCommand(command.tags, code);
|
this.locateToCommand(command.tags, code);
|
||||||
|
quickcommand.showMessageBox("保存成功!");
|
||||||
},
|
},
|
||||||
editorEvent(event) {
|
editorEvent(event) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
@ -394,7 +396,7 @@ export default {
|
|||||||
|
|
||||||
// 更新存储
|
// 更新存储
|
||||||
this.commandManager.state.allQuickCommands = {
|
this.commandManager.state.allQuickCommands = {
|
||||||
...this.commandManager.state.allQuickCommands,
|
...this.allQuickCommands,
|
||||||
...tagCommands,
|
...tagCommands,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user