mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-06 21:14:09 +08:00
可视化编排添加命令配置功能
This commit is contained in:
parent
8c564f8d97
commit
2cb0c6bb32
@ -4,9 +4,9 @@
|
||||
ref="sidebar"
|
||||
:canCommandSave="canCommandSave"
|
||||
:quickcommandInfo="quickcommandInfo"
|
||||
:allQuickCommandTags="allQuickCommandTags"
|
||||
class="absolute-left shadow-1"
|
||||
:style="{
|
||||
width: sideBarWidth + 'px',
|
||||
zIndex: 1,
|
||||
transform: isFullscreen ? 'translateX(-100%)' : 'translateX(0)',
|
||||
transition: 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
@ -64,7 +64,11 @@
|
||||
|
||||
<!-- 可视化编排 -->
|
||||
<q-dialog v-model="showComposer" maximized>
|
||||
<CommandComposer ref="composer" @use-composer="handleComposer" />
|
||||
<CommandComposer
|
||||
ref="composer"
|
||||
@action="handleComposerAction"
|
||||
:model-value="{ flows }"
|
||||
/>
|
||||
</q-dialog>
|
||||
|
||||
<!-- 运行结果 -->
|
||||
@ -78,6 +82,7 @@ import CommandLanguageBar from "components/editor/CommandLanguageBar";
|
||||
import EditorTools from "components/editor/EditorTools";
|
||||
import CommandRunResult from "components/CommandRunResult";
|
||||
import CommandComposer from "components/composer/CommandComposer.vue";
|
||||
import programs from "js/options/programs.js";
|
||||
|
||||
// 预加载 MonacoEditor
|
||||
const MonacoEditorPromise = import("components/editor/MonacoEditor");
|
||||
@ -109,13 +114,22 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
programLanguages: Object.keys(this.$root.programs),
|
||||
programLanguages: Object.keys(programs),
|
||||
sideBarWidth: 200,
|
||||
languageBarHeight: 40,
|
||||
showComposer: false,
|
||||
isRunCodePage: this.action.type === "run",
|
||||
canCommandSave: this.action.type !== "run",
|
||||
showSidebar: this.action.type !== "run",
|
||||
flows: [
|
||||
{
|
||||
id: "main",
|
||||
name: "main",
|
||||
label: "主流程",
|
||||
commands: [],
|
||||
customVariables: [],
|
||||
},
|
||||
],
|
||||
quickcommandInfo: {
|
||||
program: "quickcommand",
|
||||
cmd: "",
|
||||
@ -141,15 +155,9 @@ export default {
|
||||
required: true,
|
||||
},
|
||||
allQuickCommandTags: Array,
|
||||
isLeaving: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.commandInit();
|
||||
},
|
||||
mounted() {
|
||||
this.commandInit();
|
||||
this.sidebarInit();
|
||||
},
|
||||
computed: {
|
||||
@ -196,7 +204,7 @@ export default {
|
||||
// 匹配编程语言
|
||||
matchLanguage() {
|
||||
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
|
||||
);
|
||||
if (language.length) {
|
||||
@ -205,7 +213,7 @@ export default {
|
||||
},
|
||||
// 设置编程语言
|
||||
setLanguage(language) {
|
||||
let highlight = this.$root.programs[language].highlight;
|
||||
let highlight = programs[language].highlight;
|
||||
this.$refs.editor.setEditorLanguage(highlight ? highlight : language);
|
||||
},
|
||||
insertText(text) {
|
||||
@ -216,14 +224,16 @@ export default {
|
||||
this.$refs.editor.setEditorValue(text);
|
||||
this.$refs.editor.formatDocument();
|
||||
},
|
||||
handleComposer({ type, code }) {
|
||||
switch (type) {
|
||||
handleComposerAction(actionType, actionData) {
|
||||
switch (actionType) {
|
||||
case "run":
|
||||
return this.runCurrentCommand(code);
|
||||
return this.runCurrentCommand(actionData);
|
||||
case "insert":
|
||||
return this.insertText(code);
|
||||
return this.insertText(actionData);
|
||||
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 ResultArea from "components/ResultArea.vue";
|
||||
import ResultMenu from "components/popup/ResultMenu.vue";
|
||||
import { generateFlowsCode } from "js/composer/generateCode";
|
||||
import { getValidCommand } from "js/commandManager";
|
||||
|
||||
export default {
|
||||
components: { ResultArea, ResultMenu },
|
||||
@ -127,11 +129,20 @@ export default {
|
||||
methods: {
|
||||
// 运行命令
|
||||
async runCurrentCommand(currentCommand) {
|
||||
let command = window.lodashM.cloneDeep(currentCommand);
|
||||
// 如果是composer命令,则动态生成cmd
|
||||
if (command.program === "quickcomposer") {
|
||||
command.cmd = generateFlowsCode(command.flows);
|
||||
}
|
||||
this.$root.isRunningCommand = true;
|
||||
await this.getTempPayload(currentCommand);
|
||||
if (currentCommand.cmd.includes("{{subinput"))
|
||||
return this.setSubInput(currentCommand);
|
||||
this.fire(currentCommand);
|
||||
try {
|
||||
await this.getTempPayload(command);
|
||||
} catch (error) {
|
||||
return quickcommand.showMessageBox(error.toString(), "error");
|
||||
}
|
||||
// 如果命令包含子输入框,则设置子输入框
|
||||
if (command.cmd.includes("{{subinput")) return this.setSubInput(command);
|
||||
this.fire(command);
|
||||
},
|
||||
async fire(currentCommand) {
|
||||
currentCommand.cmd = this.assignSpecialVars(currentCommand.cmd);
|
||||
@ -147,6 +158,7 @@ export default {
|
||||
let resultOpts = { outPlugin, action, earlyExit };
|
||||
switch (currentCommand.program) {
|
||||
case "quickcommand":
|
||||
case "quickcomposer":
|
||||
window.runCodeInSandbox(
|
||||
currentCommand.cmd,
|
||||
(stdout, stderr) => this.handleResult(stdout, stderr, resultOpts),
|
||||
@ -278,11 +290,15 @@ export default {
|
||||
// payload 临时赋值
|
||||
async getTempPayload(currentCommand) {
|
||||
if (!this.needTempPayload) return;
|
||||
let type =
|
||||
currentCommand.cmdType || currentCommand.features?.cmds[0].type;
|
||||
currentCommand = getValidCommand(currentCommand);
|
||||
const firstCmd = currentCommand.features.cmds[0];
|
||||
const type = firstCmd.type || "text";
|
||||
this.$root.enterData = {
|
||||
type: type || "text",
|
||||
payload: await commandTypes[type]?.tempPayload?.(),
|
||||
type,
|
||||
payload:
|
||||
type === "text"
|
||||
? firstCmd
|
||||
: (await commandTypes[type]?.tempPayload?.()) || {},
|
||||
};
|
||||
},
|
||||
handleResult(stdout, stderr, options) {
|
||||
|
@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<CommandComposer
|
||||
ref="composer"
|
||||
@use-composer="handleComposer"
|
||||
:show-close-button="false"
|
||||
@action="handleComposerAction"
|
||||
v-model="quickcommandInfo"
|
||||
:show-close-button="!isRunComposePage"
|
||||
class="fixed-full"
|
||||
/>
|
||||
<!-- 运行结果 -->
|
||||
@ -12,29 +13,131 @@
|
||||
<script>
|
||||
import CommandComposer from "components/composer/CommandComposer.vue";
|
||||
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 {
|
||||
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: {
|
||||
action: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
allQuickCommandTags: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isRunComposePage: this.action.type === "composer",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleComposer({ type, code }) {
|
||||
switch (type) {
|
||||
handleComposerAction(actionType, command) {
|
||||
switch (actionType) {
|
||||
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) {
|
||||
if (!cmd) return;
|
||||
let command = {
|
||||
cmd: cmd,
|
||||
output: "text",
|
||||
program: "quickcommand",
|
||||
};
|
||||
runCurrentCommand(command) {
|
||||
this.$refs.result.runCurrentCommand(command);
|
||||
},
|
||||
},
|
||||
|
@ -46,7 +46,7 @@
|
||||
width="16px"
|
||||
/>
|
||||
<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>
|
||||
</q-card-section>
|
||||
@ -75,6 +75,12 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
program() {
|
||||
if (this.commandInfo.program === "quickcomposer") {
|
||||
return {
|
||||
...this.programs.quickcommand,
|
||||
shortName: "可视化",
|
||||
};
|
||||
}
|
||||
return this.programs[this.commandInfo.program];
|
||||
},
|
||||
programName() {
|
||||
|
@ -70,6 +70,12 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
program() {
|
||||
if (this.commandInfo.program === "quickcomposer") {
|
||||
return {
|
||||
...this.programs.quickcommand,
|
||||
name: "可视化编排",
|
||||
};
|
||||
}
|
||||
return this.programs[this.commandInfo.program];
|
||||
},
|
||||
},
|
||||
|
@ -10,8 +10,10 @@
|
||||
<!-- 右侧命令流程 -->
|
||||
<div class="col command-section">
|
||||
<FlowTabs
|
||||
@action="handleComposer"
|
||||
@action="handleAction"
|
||||
:show-close-button="showCloseButton"
|
||||
:model-value="modelValue"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -44,12 +46,16 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
emits: ["use-composer"],
|
||||
emits: ["action", "update:modelValue"],
|
||||
methods: {
|
||||
handleComposer(type, code) {
|
||||
handleAction(actionType, actionData) {
|
||||
// 直接转发事件和代码
|
||||
this.$emit("use-composer", { type, code });
|
||||
this.$emit("action", actionType, actionData);
|
||||
},
|
||||
findCommandNeedLoading(flow) {
|
||||
// 暂时只在运行单独命令时显示载入界面,因为运行整个命令流时,如果不打印输出,是无法判断什么时候运行结束的,
|
||||
|
@ -197,7 +197,7 @@ export default defineComponent({
|
||||
};
|
||||
const consoleLogVars =
|
||||
this.getAvailableOutputVariableName(outputVariable);
|
||||
const tempFlow = {
|
||||
const tempFlows = [{
|
||||
name: "main",
|
||||
commands: [
|
||||
tempCommand,
|
||||
@ -207,9 +207,10 @@ export default defineComponent({
|
||||
console.log(${consoleLogVars})
|
||||
}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
this.$emit("run", tempFlow);
|
||||
],
|
||||
},
|
||||
];
|
||||
this.$emit("run", tempFlows);
|
||||
},
|
||||
handleToggleCollapse() {
|
||||
if (this.localCommand.isControlFlow) {
|
||||
|
@ -82,10 +82,6 @@ export default defineComponent({
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
generateCode: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
showCloseButton: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
|
@ -59,9 +59,9 @@
|
||||
</div>
|
||||
|
||||
<ComposerButtons
|
||||
:generate-code="generateAllFlowCode"
|
||||
:is-all-collapsed="isAllCollapsed"
|
||||
:show-close-button="showCloseButton"
|
||||
:flows="flows"
|
||||
@action="handleAction"
|
||||
/>
|
||||
</div>
|
||||
@ -72,10 +72,15 @@
|
||||
:key="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
|
||||
class="flow-wrapper"
|
||||
v-model="flow.commands"
|
||||
:generate-code="() => generateFlowCode(flow)"
|
||||
:show-close-button="flows.length > 1"
|
||||
@action="(type, payload) => handleAction(type, payload)"
|
||||
ref="flowRefs"
|
||||
@ -84,7 +89,7 @@
|
||||
v-model="showVariableManager"
|
||||
:flow="flow"
|
||||
:variables="flow.customVariables"
|
||||
@update-flow="Sub(flow)"
|
||||
@update-flow="updateFlows(flow)"
|
||||
:is-main-flow="flow.id === 'main'"
|
||||
:output-variables="outputVariables"
|
||||
class="variable-panel"
|
||||
@ -99,10 +104,10 @@ import draggable from "vuedraggable";
|
||||
import ComposerFlow from "components/composer/ComposerFlow.vue";
|
||||
import ComposerButtons from "components/composer/flow/ComposerButtons.vue";
|
||||
import FlowManager from "components/composer/flow/FlowManager.vue";
|
||||
import { generateCode } from "js/composer/generateCode";
|
||||
import { findCommandByValue } from "js/composer/composerConfig";
|
||||
import CommandConfig from "components/editor/CommandConfig.vue";
|
||||
import { generateUniqSuffix } from "js/composer/variableManager";
|
||||
import { getUniqueId } from "js/common/uuid";
|
||||
|
||||
export default defineComponent({
|
||||
name: "FlowTabs",
|
||||
components: {
|
||||
@ -110,23 +115,44 @@ export default defineComponent({
|
||||
ComposerButtons,
|
||||
draggable,
|
||||
FlowManager,
|
||||
CommandConfig,
|
||||
},
|
||||
emits: ["update:modelValue", "action"],
|
||||
props: {
|
||||
showCloseButton: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const mainFlow = ref({
|
||||
id: "main",
|
||||
name: "main",
|
||||
label: "主流程",
|
||||
commands: [],
|
||||
customVariables: [],
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
||||
const subFlows = ref([]);
|
||||
const subFlows = computed({
|
||||
get: () => flows.value.slice(1),
|
||||
set: (newVal) => {
|
||||
const newFlows = [mainFlow.value, ...newVal];
|
||||
updateFlows(newFlows);
|
||||
},
|
||||
});
|
||||
|
||||
// 获取所有函数
|
||||
const getCurrentFunctions = () => {
|
||||
@ -140,8 +166,6 @@ export default defineComponent({
|
||||
};
|
||||
provide("getCurrentFunctions", getCurrentFunctions);
|
||||
|
||||
const flows = computed(() => [mainFlow.value, ...subFlows.value]);
|
||||
|
||||
const activeTab = ref("main");
|
||||
|
||||
const getCurrentFlow = () => {
|
||||
@ -208,6 +232,7 @@ export default defineComponent({
|
||||
subFlows,
|
||||
activeTab,
|
||||
getOutputVariables,
|
||||
updateFlows,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
@ -217,6 +242,12 @@ export default defineComponent({
|
||||
outputVariables: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
commandConfig() {
|
||||
const { tags, output, features } = this.modelValue;
|
||||
return { tags, output, features };
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
generateFlowName(baseName = "func_") {
|
||||
return (
|
||||
@ -249,7 +280,7 @@ export default defineComponent({
|
||||
});
|
||||
}
|
||||
|
||||
this.subFlows.push(newFlow);
|
||||
this.subFlows = [...this.subFlows, newFlow];
|
||||
|
||||
if (options.silent) {
|
||||
return;
|
||||
@ -263,7 +294,10 @@ export default defineComponent({
|
||||
removeFlow(flow) {
|
||||
const index = this.subFlows.findIndex((f) => f.id === flow.id);
|
||||
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;
|
||||
}
|
||||
},
|
||||
@ -279,25 +313,13 @@ export default defineComponent({
|
||||
// 完全更新参数
|
||||
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) {
|
||||
switch (type) {
|
||||
case "save":
|
||||
this.saveFlows();
|
||||
break;
|
||||
case "load":
|
||||
this.loadFlows();
|
||||
break;
|
||||
case "run":
|
||||
this.runFlows(payload);
|
||||
this.runCommand(payload);
|
||||
break;
|
||||
case "collapseAll":
|
||||
this.collapseAll();
|
||||
@ -308,6 +330,9 @@ export default defineComponent({
|
||||
case "toggleVariableManager":
|
||||
this.toggleVariableManager();
|
||||
break;
|
||||
case "close":
|
||||
this.$emit("action", "close");
|
||||
break;
|
||||
case "addFlow":
|
||||
// 处理新函数创建
|
||||
const index = this.subFlows.findIndex((f) => f.name === payload.name);
|
||||
@ -319,7 +344,7 @@ export default defineComponent({
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this.$emit("action", type, this.generateAllFlowCode());
|
||||
this.$emit("action", type, payload);
|
||||
}
|
||||
},
|
||||
toggleVariableManager() {
|
||||
@ -327,61 +352,17 @@ export default defineComponent({
|
||||
this.outputVariables = this.getOutputVariables();
|
||||
},
|
||||
saveFlows() {
|
||||
const flowsData = this.flows.map((flow) => ({
|
||||
...flow,
|
||||
commands: flow.commands.map((cmd) => {
|
||||
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("保存成功");
|
||||
this.$emit("action", "save", {
|
||||
...this.modelValue,
|
||||
flows: this.flows,
|
||||
});
|
||||
},
|
||||
loadFlows() {
|
||||
const savedFlows = localStorage.getItem("quickcomposer.flows");
|
||||
if (!savedFlows) return;
|
||||
|
||||
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.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("已运行");
|
||||
}
|
||||
runCommand(flows = this.flows) {
|
||||
const command = {
|
||||
...this.modelValue,
|
||||
flows,
|
||||
};
|
||||
this.$emit("action", "run", command);
|
||||
},
|
||||
collapseAll() {
|
||||
this.$refs.flowRefs.forEach((flow) => {
|
||||
@ -399,9 +380,12 @@ export default defineComponent({
|
||||
this.activeTab = flow.id;
|
||||
this.toggleVariableManager();
|
||||
},
|
||||
Sub(flow) {
|
||||
this.mainFlow = flow[0];
|
||||
this.subFlows = flow.slice(1);
|
||||
updateCommandConfig(newVal) {
|
||||
const newModelValue = {
|
||||
...this.modelValue,
|
||||
...newVal,
|
||||
};
|
||||
this.$emit("update:modelValue", newModelValue);
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -556,4 +540,8 @@ export default defineComponent({
|
||||
border-bottom: 2px solid var(--q-primary);
|
||||
transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
}
|
||||
|
||||
.command-config-panel {
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
|
@ -57,9 +57,6 @@
|
||||
<q-btn dense flat icon="save" @click="$emit('action', 'save')">
|
||||
<q-tooltip>保存</q-tooltip>
|
||||
</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-tooltip>预览代码</q-tooltip>
|
||||
</q-btn>
|
||||
@ -92,15 +89,12 @@
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import { generateFlowsCode } from "js/composer/generateCode";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ComposerButtons",
|
||||
|
||||
props: {
|
||||
generateCode: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
isAllCollapsed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@ -109,6 +103,10 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
flows: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
|
||||
emits: ["action"],
|
||||
@ -124,7 +122,7 @@ export default defineComponent({
|
||||
watch: {
|
||||
isVisible(val) {
|
||||
if (val) {
|
||||
this.code = this.generateCode();
|
||||
this.code = generateFlowsCode(this.flows);
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -172,16 +170,6 @@ export default defineComponent({
|
||||
|
||||
/* 自定义滚动条 */
|
||||
.preview-code::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.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);
|
||||
width: 5px;
|
||||
}
|
||||
</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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
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 importAll from "js/common/importAll.js";
|
||||
import utoolsFull from "js/utools.js";
|
||||
import { getUniqueId } from "js/common/uuid.js";
|
||||
import outputTypes from "js/options/outputTypes.js";
|
||||
|
||||
// 默认命令
|
||||
const defaultCommands = importAll(
|
||||
@ -16,6 +18,50 @@ const state = reactive({
|
||||
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() {
|
||||
// 获取已启用的命令
|
||||
@ -59,6 +105,11 @@ export function useCommandManager() {
|
||||
|
||||
// 保存命令
|
||||
const saveCommand = (command) => {
|
||||
try {
|
||||
command = getValidCommand(command);
|
||||
} catch (e) {
|
||||
return quickcommand.showMessageBox(e.toString(), "error");
|
||||
}
|
||||
const code = command.features.code;
|
||||
state.allQuickCommands[code] = command;
|
||||
|
||||
@ -70,7 +121,7 @@ export function useCommandManager() {
|
||||
utoolsFull.whole.setFeature(command.features);
|
||||
|
||||
if (!isDefaultCommand(code)) {
|
||||
utoolsFull.putDB(window.lodashM.cloneDeep(command), "qc_" + code);
|
||||
utoolsFull.putDB(command, "qc_" + code);
|
||||
}
|
||||
|
||||
getAllQuickCommandTags();
|
||||
|
@ -1,7 +1,9 @@
|
||||
export const getUniqueId = () => {
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
||||
export const getUniqueId = (options = {}) => {
|
||||
const { short = false } = options;
|
||||
const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
||||
const r = (Math.random() * 16) | 0;
|
||||
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
return short ? uuid.substring(0, 8) : uuid;
|
||||
};
|
||||
|
@ -133,3 +133,10 @@ export function generateCode(flow) {
|
||||
|
||||
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 = [
|
||||
"关键词",
|
||||
"关键词1",
|
||||
"关键词2",
|
||||
{
|
||||
type: "img",
|
||||
label: "图片匹配",
|
||||
@ -41,12 +42,12 @@ const jsonSample = [
|
||||
},
|
||||
];
|
||||
|
||||
const commandTypes = {
|
||||
export default {
|
||||
key: {
|
||||
name: "key",
|
||||
label: "关键词",
|
||||
icon: "font_download",
|
||||
color: "teal",
|
||||
color: "blue",
|
||||
matchLabel: "关键词",
|
||||
desc: "直接在主输入框输入对应关键字,最通用的一种模式,关键字可以设置多个",
|
||||
valueType: "array",
|
||||
@ -57,7 +58,7 @@ const commandTypes = {
|
||||
},
|
||||
regex: {
|
||||
name: "regex",
|
||||
label: "正则/划词",
|
||||
label: "正则",
|
||||
icon: "rule",
|
||||
color: "cyan",
|
||||
matchLabel: "正则",
|
||||
@ -82,7 +83,7 @@ const commandTypes = {
|
||||
over: {
|
||||
name: "over",
|
||||
label: "所有文本",
|
||||
matchLabel: "无需设置",
|
||||
matchLabel: "无需配置",
|
||||
icon: "emergency",
|
||||
color: "light-green",
|
||||
desc: "匹配主输入框的所有文本,但只有在该文本未设置对应的插件或功能时才生效",
|
||||
@ -104,7 +105,7 @@ const commandTypes = {
|
||||
},
|
||||
window: {
|
||||
name: "window",
|
||||
label: "窗口/进程",
|
||||
label: "窗口",
|
||||
matchLabel: "进程名",
|
||||
icon: "widgets",
|
||||
color: "indigo",
|
||||
@ -154,7 +155,7 @@ const commandTypes = {
|
||||
},
|
||||
files: {
|
||||
name: "files",
|
||||
label: "复制/选中文件",
|
||||
label: "文件",
|
||||
matchLabel: "正则",
|
||||
icon: "description",
|
||||
color: "light-blue",
|
||||
@ -200,5 +201,3 @@ const commandTypes = {
|
||||
jsonSample: jsonSample,
|
||||
},
|
||||
};
|
||||
|
||||
export default commandTypes;
|
||||
|
@ -27,7 +27,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
runCurrentCommand(command) {
|
||||
this.$refs.result.runCurrentCommand(window.lodashM.cloneDeep(command));
|
||||
this.$refs.result.runCurrentCommand(command);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -221,9 +221,7 @@ export default {
|
||||
}
|
||||
},
|
||||
runCommand(code) {
|
||||
this.$refs.result.runCurrentCommand(
|
||||
window.lodashM.cloneDeep(this.allQuickCommands[code])
|
||||
);
|
||||
this.$refs.result.runCurrentCommand(this.allQuickCommands[code]);
|
||||
},
|
||||
// 启用命令
|
||||
enableCommand(code) {
|
||||
@ -241,13 +239,16 @@ export default {
|
||||
}
|
||||
},
|
||||
// 编辑命令
|
||||
editCommand(command) {
|
||||
editCommand(commandOrCode) {
|
||||
// 即可传入 code,也可直接传入 command
|
||||
if (typeof command === "string") command = this.allQuickCommands[command];
|
||||
const command =
|
||||
typeof commandOrCode === "string"
|
||||
? this.allQuickCommands[commandOrCode]
|
||||
: commandOrCode;
|
||||
this.commandEditorAction = {
|
||||
type: "edit",
|
||||
data: window.lodashM.cloneDeep(command),
|
||||
component: "CommandEditor",
|
||||
component: command.flows ? "ComposerEditor" : "CommandEditor",
|
||||
};
|
||||
this.isEditorShow = true;
|
||||
},
|
||||
@ -349,6 +350,7 @@ export default {
|
||||
saveCommand(command) {
|
||||
const code = this.commandManager.saveCommand(command);
|
||||
this.locateToCommand(command.tags, code);
|
||||
quickcommand.showMessageBox("保存成功!");
|
||||
},
|
||||
editorEvent(event) {
|
||||
switch (event.type) {
|
||||
@ -394,7 +396,7 @@ export default {
|
||||
|
||||
// 更新存储
|
||||
this.commandManager.state.allQuickCommands = {
|
||||
...this.commandManager.state.allQuickCommands,
|
||||
...this.allQuickCommands,
|
||||
...tagCommands,
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user