mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-10 23:54:57 +08:00
编排的命令列表支持搜索
This commit is contained in:
parent
dfc1c7e2e3
commit
29d7064afc
@ -3,33 +3,18 @@
|
||||
<!-- 主体内容 -->
|
||||
<div class="composer-body row no-wrap">
|
||||
<!-- 左侧命令列表 -->
|
||||
<div class="col-3 command-section command-list">
|
||||
<!-- <div class="section-header">
|
||||
<q-icon name="list" size="20px" class="q-mr-sm text-primary" />
|
||||
<span class="text-subtitle1">可用命令</span>
|
||||
</div> -->
|
||||
<q-scroll-area class="command-scroll">
|
||||
<ComposerList
|
||||
:commands="availableCommands"
|
||||
@add-command="addCommand"
|
||||
/>
|
||||
</q-scroll-area>
|
||||
<div class="col-3 command-section">
|
||||
<ComposerList :commands="availableCommands" @add-command="addCommand" />
|
||||
</div>
|
||||
|
||||
<!-- 右侧命令流程 -->
|
||||
<div class="col command-section command-flow">
|
||||
<div class="section-header">
|
||||
<q-icon name="timeline" size="20px" class="q-mr-sm text-primary" />
|
||||
<span class="text-subtitle1">命令流程</span>
|
||||
<q-space />
|
||||
<ComposerButtons
|
||||
:generate-code="generateFlowCode"
|
||||
@action="handleComposer"
|
||||
/>
|
||||
</div>
|
||||
<q-scroll-area class="command-scroll">
|
||||
<ComposerFlow v-model="commandFlow" @add-command="addCommand" />
|
||||
</q-scroll-area>
|
||||
<div class="col command-section">
|
||||
<ComposerFlow
|
||||
v-model="commandFlow"
|
||||
:generate-code="generateFlowCode"
|
||||
@add-command="addCommand"
|
||||
@action="handleComposer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -39,7 +24,6 @@
|
||||
import { defineComponent, provide, ref } from "vue";
|
||||
import ComposerList from "./ComposerList.vue";
|
||||
import ComposerFlow from "./ComposerFlow.vue";
|
||||
import ComposerButtons from "./ComposerButtons.vue";
|
||||
import { commandCategories } from "js/composer/composerConfig";
|
||||
import { generateCode } from "js/composer/generateCode";
|
||||
// 从commandCategories中提取所有命令
|
||||
@ -57,7 +41,6 @@ export default defineComponent({
|
||||
components: {
|
||||
ComposerList,
|
||||
ComposerFlow,
|
||||
ComposerButtons,
|
||||
},
|
||||
setup() {
|
||||
const variables = ref([]);
|
||||
@ -154,25 +137,6 @@ export default defineComponent({
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
height: 28px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.body--dark .section-header {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.command-scroll {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 滚动美化 */
|
||||
:deep(.q-scrollarea__thumb) {
|
||||
width: 2px;
|
||||
|
@ -98,7 +98,7 @@ export default defineComponent({
|
||||
|
||||
.composer-buttons > .q-btn:hover {
|
||||
opacity: 1;
|
||||
transform: translateY(-2px);
|
||||
transform: translateY(-1px);
|
||||
color: var(--q-primary);
|
||||
}
|
||||
|
||||
|
@ -1,55 +1,67 @@
|
||||
<template>
|
||||
<div class="composer-flow">
|
||||
<div
|
||||
class="command-flow-container"
|
||||
@dragover.prevent="onDragOver"
|
||||
@drop="onDrop"
|
||||
@dragleave.prevent="onDragLeave"
|
||||
>
|
||||
<draggable
|
||||
v-model="commands"
|
||||
group="commands"
|
||||
item-key="id"
|
||||
class="flow-list"
|
||||
handle=".drag-handle"
|
||||
:animation="200"
|
||||
@start="onDragStart"
|
||||
@end="onDragEnd"
|
||||
<div class="section-header">
|
||||
<q-icon name="timeline" size="20px" class="q-mx-sm text-primary" />
|
||||
<span class="text-subtitle1">命令流程</span>
|
||||
<q-space />
|
||||
<ComposerButtons
|
||||
:generate-code="generateCode"
|
||||
@action="$emit('action', $event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<q-scroll-area class="command-scroll">
|
||||
<div
|
||||
class="command-flow-container"
|
||||
@dragover.prevent="onDragOver"
|
||||
@drop="onDrop"
|
||||
@dragleave.prevent="onDragLeave"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<transition name="slide-fade" mode="out-in" appear>
|
||||
<div
|
||||
:key="element.id"
|
||||
class="flow-item"
|
||||
:class="{
|
||||
'insert-before': dragIndex === index,
|
||||
'insert-after':
|
||||
dragIndex === commands.length &&
|
||||
index === commands.length - 1,
|
||||
}"
|
||||
>
|
||||
<ComposerCard
|
||||
:command="element"
|
||||
:placeholder="getPlaceholder(element, index)"
|
||||
@remove="removeCommand(index)"
|
||||
@toggle-output="toggleSaveOutput(index)"
|
||||
@update:argv="(val) => handleArgvChange(index, val)"
|
||||
@update:command="(val) => updateCommand(index, val)"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
</draggable>
|
||||
<div v-if="commands.length === 0" class="empty-flow">
|
||||
<div class="text-center text-grey-6">
|
||||
<q-icon name="drag_indicator" size="32px" />
|
||||
<div class="text-body2 q-mt-sm">从左侧拖拽命令到这里开始编排</div>
|
||||
<draggable
|
||||
v-model="commands"
|
||||
group="commands"
|
||||
item-key="id"
|
||||
class="flow-list"
|
||||
handle=".drag-handle"
|
||||
:animation="200"
|
||||
@start="onDragStart"
|
||||
@end="onDragEnd"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<transition name="slide-fade" mode="out-in" appear>
|
||||
<div
|
||||
:key="element.id"
|
||||
class="flow-item"
|
||||
:class="{
|
||||
'insert-before': dragIndex === index,
|
||||
'insert-after':
|
||||
dragIndex === commands.length &&
|
||||
index === commands.length - 1,
|
||||
}"
|
||||
>
|
||||
<ComposerCard
|
||||
:command="element"
|
||||
:placeholder="getPlaceholder(element, index)"
|
||||
@remove="removeCommand(index)"
|
||||
@toggle-output="toggleSaveOutput(index)"
|
||||
@update:argv="(val) => handleArgvChange(index, val)"
|
||||
@update:command="(val) => updateCommand(index, val)"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
</draggable>
|
||||
<div v-if="commands.length === 0" class="empty-flow">
|
||||
<div class="text-center text-grey-6">
|
||||
<q-icon name="drag_indicator" size="32px" />
|
||||
<div class="text-body2 q-mt-sm">从左侧拖拽命令到这里开始编排</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="drop-area">
|
||||
<q-icon name="add" size="32px" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="drop-area">
|
||||
<q-icon name="add" size="32px" />
|
||||
</div>
|
||||
</div>
|
||||
</q-scroll-area>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -57,20 +69,26 @@
|
||||
import { defineComponent, inject } from "vue";
|
||||
import draggable from "vuedraggable";
|
||||
import ComposerCard from "./ComposerCard.vue";
|
||||
import ComposerButtons from "./ComposerButtons.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ComposerFlow",
|
||||
components: {
|
||||
draggable,
|
||||
ComposerCard,
|
||||
ComposerButtons,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
generateCode: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue", "add-command"],
|
||||
emits: ["update:modelValue", "add-command", "action"],
|
||||
computed: {
|
||||
commands: {
|
||||
get() {
|
||||
@ -230,8 +248,25 @@ export default defineComponent({
|
||||
|
||||
<style scoped>
|
||||
.composer-flow {
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
flex-shrink: 0;
|
||||
padding: 0 8px;
|
||||
height: 30px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.command-scroll {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.command-flow-container {
|
||||
@ -239,7 +274,6 @@ export default defineComponent({
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
@ -391,4 +425,8 @@ export default defineComponent({
|
||||
box-shadow: 0 0 10px rgba(255, 255, 255, 0.03),
|
||||
0 0 4px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.body--dark .section-header {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
@ -1,43 +1,75 @@
|
||||
<template>
|
||||
<div class="composer-list">
|
||||
<q-list separator class="rounded-borders">
|
||||
<template v-for="category in commandCategories" :key="category.label">
|
||||
<q-item-label header class="q-py-sm">
|
||||
<div class="row items-center">
|
||||
<q-icon
|
||||
:name="category.icon"
|
||||
color="primary"
|
||||
size="sm"
|
||||
class="q-mr-sm"
|
||||
/>
|
||||
<span class="text-weight-medium">{{ category.label }}</span>
|
||||
</div>
|
||||
</q-item-label>
|
||||
<div class="section-header">
|
||||
<q-input
|
||||
v-model="searchQuery"
|
||||
dense
|
||||
borderless
|
||||
placeholder="搜索命令..."
|
||||
class="search-input"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="search" size="sm" />
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
|
||||
<q-item
|
||||
v-for="command in getCategoryCommands(category)"
|
||||
:key="command.value"
|
||||
class="command-item q-py-xs"
|
||||
draggable="true"
|
||||
@dragstart="onDragStart($event, command)"
|
||||
>
|
||||
<q-item-section>
|
||||
<q-item-label class="text-weight-medium">{{
|
||||
command.label
|
||||
}}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side style="padding-left: 8px">
|
||||
<q-icon name="drag_indicator" color="grey-6" size="16px" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-list>
|
||||
<q-scroll-area class="command-scroll">
|
||||
<div>
|
||||
<q-list separator class="rounded-borders">
|
||||
<template
|
||||
v-for="category in filteredCategories"
|
||||
:key="category.label"
|
||||
>
|
||||
<q-expansion-item
|
||||
:label="category.label"
|
||||
:icon="category.icon"
|
||||
:default-opened="!!searchQuery || category.defaultOpened"
|
||||
dense-toggle
|
||||
class="category-item"
|
||||
header-class="category-header"
|
||||
>
|
||||
<template v-slot:header>
|
||||
<div class="row items-center">
|
||||
<q-icon
|
||||
:name="category.icon"
|
||||
color="primary"
|
||||
size="sm"
|
||||
class="q-mr-sm"
|
||||
/>
|
||||
<span class="text-weight-medium">{{ category.label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<q-item
|
||||
v-for="command in getCategoryCommands(category)"
|
||||
:key="command.value"
|
||||
class="command-item q-py-xs"
|
||||
draggable="true"
|
||||
@dragstart="onDragStart($event, command)"
|
||||
>
|
||||
<q-item-section>
|
||||
<q-item-label
|
||||
class="text-weight-medium"
|
||||
v-html="highlightText(command.label)"
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section side style="padding-left: 8px">
|
||||
<q-icon name="drag_indicator" color="grey-6" size="16px" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-expansion-item>
|
||||
</template>
|
||||
</q-list>
|
||||
</div>
|
||||
</q-scroll-area>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import { commandCategories } from "js/composer/composerConfig";
|
||||
import pinyinMatch from "pinyin-match";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ComposerList",
|
||||
@ -50,8 +82,33 @@ export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
commandCategories,
|
||||
searchQuery: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
filteredCategories() {
|
||||
if (!this.searchQuery) return this.commandCategories;
|
||||
|
||||
const query = this.searchQuery.toLowerCase();
|
||||
return this.commandCategories
|
||||
.map((category) => ({
|
||||
...category,
|
||||
commands: this.commands
|
||||
.filter(
|
||||
(cmd) =>
|
||||
(cmd.label && pinyinMatch.match(cmd.label, query)) ||
|
||||
(cmd.value && pinyinMatch.match(cmd.value, query))
|
||||
)
|
||||
.filter((cmd) =>
|
||||
category.commands.some(
|
||||
(catCmd) =>
|
||||
catCmd.value === cmd.value || catCmd.value === cmd.cmd
|
||||
)
|
||||
),
|
||||
}))
|
||||
.filter((category) => category.commands.length > 0);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getCategoryCommands(category) {
|
||||
return this.commands.filter((cmd) =>
|
||||
@ -77,33 +134,98 @@ export default defineComponent({
|
||||
{ once: true }
|
||||
);
|
||||
},
|
||||
highlightText(text) {
|
||||
if (!this.searchQuery) return text;
|
||||
|
||||
const matches = pinyinMatch.match(text, this.searchQuery);
|
||||
if (!matches) return text;
|
||||
|
||||
const [start, end] = matches;
|
||||
return (
|
||||
text.slice(0, start) +
|
||||
`<span class="highlight">${text.slice(start, end + 1)}</span>` +
|
||||
text.slice(end + 1)
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.composer-list {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 8px;
|
||||
border-color: transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
flex-shrink: 0;
|
||||
padding: 0 8px;
|
||||
height: 30px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.command-scroll {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-input :deep(.q-field__control) {
|
||||
height: 100%;
|
||||
padding: 0 4px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.search-input :deep(.q-field__native) {
|
||||
padding: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.search-input :deep(.q-field__marginal) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.composer-list :deep(.q-expansion-item) {
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.body--dark .composer-list {
|
||||
background-color: rgba(32, 32, 32, 0.8);
|
||||
}
|
||||
|
||||
.category-item {
|
||||
margin: 4px 0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.q-item.category-header) {
|
||||
min-height: 40px;
|
||||
margin: 0 8px;
|
||||
padding: 0 4px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.command-item.q-item-type {
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
margin: 4px 8px;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
cursor: grab;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.command-item.q-item-type:hover {
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
transform: translateX(4px);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
@ -113,15 +235,6 @@ export default defineComponent({
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* 分类标题样式 */
|
||||
.q-item-label.header {
|
||||
min-height: 32px;
|
||||
padding: 4px 12px;
|
||||
font-size: 0.9rem;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
/* 暗色模式适配 */
|
||||
.body--dark .command-item.q-item-type {
|
||||
border-color: rgba(255, 255, 255, 0.05);
|
||||
@ -132,8 +245,14 @@ export default defineComponent({
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.body--dark .q-item-label.header {
|
||||
border-color: rgba(255, 255, 255, 0.05);
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
.body--dark .section-header {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.command-item :deep(.highlight) {
|
||||
color: var(--q-primary);
|
||||
font-weight: bold;
|
||||
padding: 0 1px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,6 +1,7 @@
|
||||
export const encodeCommands = {
|
||||
label: "编码解码",
|
||||
icon: "code",
|
||||
defaultOpened: false,
|
||||
commands: [
|
||||
{
|
||||
value: "(text=>Buffer.from(text).toString('base64'))",
|
||||
|
@ -1,6 +1,7 @@
|
||||
export const fileCommands = {
|
||||
label: "文件操作",
|
||||
icon: "folder",
|
||||
defaultOpened: true,
|
||||
commands: [
|
||||
{
|
||||
value: "open",
|
||||
|
@ -1,6 +1,7 @@
|
||||
export const keyCommands = {
|
||||
label: "按键操作",
|
||||
icon: "keyboard",
|
||||
defaultOpened: false,
|
||||
commands: [
|
||||
{
|
||||
value: "keyTap",
|
||||
|
@ -1,6 +1,7 @@
|
||||
export const networkCommands = {
|
||||
label: "网络操作",
|
||||
icon: "language",
|
||||
defaultOpened: true,
|
||||
commands: [
|
||||
{
|
||||
value: "visit",
|
||||
|
@ -1,6 +1,7 @@
|
||||
export const notifyCommands = {
|
||||
label: "消息通知",
|
||||
icon: "notifications",
|
||||
defaultOpened: false,
|
||||
commands: [
|
||||
{
|
||||
value: "console.log",
|
||||
|
@ -1,6 +1,7 @@
|
||||
export const otherCommands = {
|
||||
label: "其他功能",
|
||||
icon: "more_horiz",
|
||||
defaultOpened: false,
|
||||
commands: [
|
||||
{
|
||||
value: "utools.redirect",
|
||||
|
@ -1,6 +1,7 @@
|
||||
export const systemCommands = {
|
||||
label: "系统操作",
|
||||
icon: "computer",
|
||||
defaultOpened: false,
|
||||
commands: [
|
||||
{
|
||||
value: "system",
|
||||
|
Loading…
x
Reference in New Issue
Block a user