mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-12 16:54:06 +08:00
优化可编排界面的样式
This commit is contained in:
parent
2c8db1f473
commit
29ceb3c7ff
@ -169,16 +169,12 @@
|
||||
<KeyRecorder @sendKeys="addAction" />
|
||||
</q-dialog>
|
||||
<q-dialog v-model="showComposer" maximized>
|
||||
<q-card class="full-height">
|
||||
<q-card-section class="q-pa-md full-height">
|
||||
<CommandComposer
|
||||
ref="composer"
|
||||
@run="handleComposerRun"
|
||||
@apply="handleComposerApply"
|
||||
@update:model-value="showComposer = false"
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<CommandComposer
|
||||
ref="composer"
|
||||
@run="handleComposerRun"
|
||||
@apply="handleComposerApply"
|
||||
@update:model-value="showComposer = false"
|
||||
/>
|
||||
</q-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@ -298,19 +294,19 @@ export default {
|
||||
window.showUb.docs();
|
||||
},
|
||||
handleComposerRun(code) {
|
||||
this.$emit('add-action', code);
|
||||
this.$emit("add-action", code);
|
||||
},
|
||||
handleComposerApply(code) {
|
||||
this.$emit('add-action', code);
|
||||
this.$emit("add-action", code);
|
||||
this.showComposer = false;
|
||||
},
|
||||
applyComposerCommands() {
|
||||
if (this.$refs.composer) {
|
||||
const code = this.$refs.composer.generateCode();
|
||||
this.$emit('add-action', code);
|
||||
this.$emit("add-action", code);
|
||||
}
|
||||
this.showComposer = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -1,18 +1,11 @@
|
||||
<template>
|
||||
<div class="command-composer">
|
||||
<!-- 固定头部 -->
|
||||
<div class="composer-header q-pa-sm row items-center bg-white">
|
||||
<div class="text-h6 text-weight-medium">可视化命令编排</div>
|
||||
<q-space />
|
||||
<q-btn flat round dense icon="close" v-close-popup />
|
||||
</div>
|
||||
|
||||
<!-- 主体内容 -->
|
||||
<div class="composer-body row no-wrap q-pa-sm">
|
||||
<!-- 左侧命令列表 -->
|
||||
<div class="col-3">
|
||||
<div class="col-3 command-section">
|
||||
<div class="text-subtitle1 q-pb-sm">可用命令</div>
|
||||
<q-scroll-area style="height: calc(100vh - 200px)">
|
||||
<q-scroll-area class="command-scroll">
|
||||
<ComposerList
|
||||
:commands="availableCommands"
|
||||
@add-command="addCommand"
|
||||
@ -21,16 +14,16 @@
|
||||
</div>
|
||||
|
||||
<!-- 右侧命令流程 -->
|
||||
<div class="col q-pl-md">
|
||||
<div class="col q-pl-md command-section">
|
||||
<div class="text-subtitle1 q-pb-sm">命令流程</div>
|
||||
<q-scroll-area style="height: calc(100vh - 200px)">
|
||||
<q-scroll-area class="command-scroll">
|
||||
<ComposerFlow v-model="commandFlow" @add-command="addCommand" />
|
||||
</q-scroll-area>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 固定底部 -->
|
||||
<div class="composer-footer q-pa-sm row items-center justify-end bg-white">
|
||||
<div class="composer-footer q-pa-sm row items-center justify-end">
|
||||
<q-btn
|
||||
outline
|
||||
color="primary"
|
||||
@ -130,22 +123,37 @@ export default defineComponent({
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.composer-header {
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
.body--dark .command-composer {
|
||||
background-color: #303132;
|
||||
}
|
||||
|
||||
.composer-body {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.command-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.command-scroll {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.composer-footer {
|
||||
border-top: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.body--dark .composer-footer {
|
||||
border-top: 1px solid #676666;
|
||||
}
|
||||
|
||||
/* 滚动美化 */
|
||||
:deep(.q-scrollarea__thumb) {
|
||||
width: 6px;
|
||||
@ -156,4 +164,8 @@ export default defineComponent({
|
||||
:deep(.q-scrollarea__thumb:hover) {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
:deep(.q-scrollarea__content) {
|
||||
padding-right: 8px;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,15 +1,18 @@
|
||||
<template>
|
||||
<div class="composer-card q-mb-sm">
|
||||
<q-card flat bordered>
|
||||
<q-card-section horizontal class="q-pa-sm">
|
||||
<!-- 拖拽手柄 -->
|
||||
<div class="drag-handle cursor-move q-mr-sm">
|
||||
<q-icon name="drag_indicator" size="24px" class="text-grey-6" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="composer-card q-pa-xs"
|
||||
:class="{ 'can-drop': canDrop }"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<q-card class="command-item">
|
||||
<q-card-section class="q-pa-sm">
|
||||
<div class="col">
|
||||
<!-- 命令标题和描述 -->
|
||||
<div class="row items-center q-mb-sm">
|
||||
<!-- 拖拽手柄 -->
|
||||
<div class="drag-handle cursor-move q-mr-sm">
|
||||
<q-icon name="drag_indicator" size="18px" class="text-grey-6" />
|
||||
</div>
|
||||
<div class="text-subtitle1">{{ command.label }}</div>
|
||||
<q-space />
|
||||
<!-- 输出开关 -->
|
||||
@ -25,6 +28,7 @@
|
||||
dense
|
||||
icon="close"
|
||||
@click="$emit('remove')"
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -49,7 +53,12 @@
|
||||
</template>
|
||||
<template v-slot:selected-item="scope">
|
||||
<div class="row items-center">
|
||||
<q-icon name="output" color="primary" size="xs" class="q-mr-xs" />
|
||||
<q-icon
|
||||
name="output"
|
||||
color="primary"
|
||||
size="xs"
|
||||
class="q-mr-xs"
|
||||
/>
|
||||
{{ scope.opt.label }}
|
||||
</div>
|
||||
</template>
|
||||
@ -69,7 +78,7 @@
|
||||
:label="placeholder"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="code" />
|
||||
<q-icon name="text_fields" size="18px" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
@ -81,87 +90,167 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue'
|
||||
import KeyEditor from './KeyEditor.vue'
|
||||
import { defineComponent } from "vue";
|
||||
import KeyEditor from "./KeyEditor.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ComposerCard',
|
||||
name: "ComposerCard",
|
||||
components: {
|
||||
KeyEditor
|
||||
KeyEditor,
|
||||
},
|
||||
props: {
|
||||
command: {
|
||||
type: Object,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
hasOutput: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
canUseOutput: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
availableOutputs: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
default: () => [],
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
default: "",
|
||||
},
|
||||
canDrop: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showKeyRecorder: false
|
||||
}
|
||||
showKeyRecorder: false,
|
||||
};
|
||||
},
|
||||
emits: ['remove', 'toggle-output', 'update:argv', 'update:use-output'],
|
||||
emits: ["remove", "toggle-output", "update:argv", "update:use-output"],
|
||||
computed: {
|
||||
saveOutputLocal: {
|
||||
get() {
|
||||
return this.command.saveOutput
|
||||
return this.command.saveOutput;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('toggle-output')
|
||||
}
|
||||
this.$emit("toggle-output");
|
||||
},
|
||||
},
|
||||
argvLocal: {
|
||||
get() {
|
||||
return this.command.argv
|
||||
return this.command.argv;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('update:argv', value)
|
||||
}
|
||||
this.$emit("update:argv", value);
|
||||
},
|
||||
},
|
||||
useOutputLocal: {
|
||||
get() {
|
||||
return this.command.useOutput
|
||||
return this.command.useOutput;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('update:use-output', value)
|
||||
}
|
||||
}
|
||||
this.$emit("update:use-output", value);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClearOutput() {
|
||||
this.$emit('update:use-output', null)
|
||||
this.$emit("update:use-output", null);
|
||||
},
|
||||
handleKeyRecord(keys) {
|
||||
this.showKeyRecorder = false;
|
||||
// 从keyTap("a","control")格式中提取参数
|
||||
const matches = keys.match(/keyTap\((.*)\)/)
|
||||
const matches = keys.match(/keyTap\((.*)\)/);
|
||||
if (matches && matches[1]) {
|
||||
this.$emit('update:argv', matches[1]);
|
||||
this.$emit("update:argv", matches[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$el.classList.add("composer-card-enter-from");
|
||||
requestAnimationFrame(() => {
|
||||
this.$el.classList.remove("composer-card-enter-from");
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.composer-card {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transform-origin: center;
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
/* 进入动画 */
|
||||
.composer-card-enter-active {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.composer-card-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px) scale(0.95);
|
||||
}
|
||||
|
||||
/* 移除动画 */
|
||||
.composer-card-leave-active {
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.composer-card-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px) scale(0.95);
|
||||
}
|
||||
|
||||
/* 拖拽动画 */
|
||||
.composer-card:active {
|
||||
transform: scale(1.02);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.command-item {
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.command-item:hover {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.composer-card :deep(.q-field__label) {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 可放置状态动画 */
|
||||
.can-drop {
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.can-drop .command-item {
|
||||
border: 2px dashed var(--q-primary);
|
||||
background: rgba(var(--q-primary-rgb), 0.05);
|
||||
}
|
||||
|
||||
/* 暗色模式适配 */
|
||||
.body--dark .command-item {
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.body--dark .command-item:hover {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.body--dark .can-drop {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
|
@ -2,8 +2,9 @@
|
||||
<div class="composer-flow">
|
||||
<div
|
||||
class="command-flow-container"
|
||||
@dragover.prevent
|
||||
@dragover.prevent="onDragOver"
|
||||
@drop="onDrop"
|
||||
@dragleave.prevent="onDragLeave"
|
||||
>
|
||||
<draggable
|
||||
v-model="commands"
|
||||
@ -12,14 +13,19 @@
|
||||
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">
|
||||
<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"
|
||||
:has-output="hasOutput(element)"
|
||||
@ -41,57 +47,132 @@
|
||||
<div class="text-body2 q-mt-sm">从左侧拖拽命令到这里开始编排</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="drop-area"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue'
|
||||
import draggable from 'vuedraggable'
|
||||
import ComposerCard from './ComposerCard.vue'
|
||||
import { commandsWithOutput, commandsAcceptOutput } from './composerConfig'
|
||||
import { defineComponent } from "vue";
|
||||
import draggable from "vuedraggable";
|
||||
import ComposerCard from "./ComposerCard.vue";
|
||||
import { commandsWithOutput, commandsAcceptOutput } from "./composerConfig";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ComposerFlow',
|
||||
name: "ComposerFlow",
|
||||
components: {
|
||||
draggable,
|
||||
ComposerCard
|
||||
ComposerCard,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'add-command'],
|
||||
emits: ["update:modelValue", "add-command"],
|
||||
computed: {
|
||||
commands: {
|
||||
get() {
|
||||
return this.modelValue
|
||||
return this.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('update:modelValue', value)
|
||||
}
|
||||
this.$emit("update:modelValue", value);
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragIndex: -1,
|
||||
isDragging: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onDragStart() {
|
||||
this.isDragging = true;
|
||||
},
|
||||
|
||||
onDragEnd() {
|
||||
this.isDragging = false;
|
||||
this.dragIndex = -1;
|
||||
},
|
||||
|
||||
onDragOver(event) {
|
||||
if (!this.isDragging) {
|
||||
const rect = event.currentTarget.getBoundingClientRect();
|
||||
const items = this.$el.querySelectorAll('.flow-item');
|
||||
const mouseY = event.clientY;
|
||||
|
||||
// 找到最近的插入位置
|
||||
let closestIndex = -1;
|
||||
let minDistance = Infinity;
|
||||
|
||||
items.forEach((item, index) => {
|
||||
const itemRect = item.getBoundingClientRect();
|
||||
const itemCenter = itemRect.top + itemRect.height / 2;
|
||||
const distance = Math.abs(mouseY - itemCenter);
|
||||
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
closestIndex = index;
|
||||
}
|
||||
});
|
||||
|
||||
// 如果鼠标在最后一个元素下方,则设置为末尾
|
||||
const lastItem = items[items.length - 1];
|
||||
if (lastItem && mouseY > lastItem.getBoundingClientRect().bottom) {
|
||||
closestIndex = this.commands.length;
|
||||
}
|
||||
|
||||
this.dragIndex = closestIndex;
|
||||
}
|
||||
},
|
||||
|
||||
onDragLeave() {
|
||||
if (!this.isDragging) {
|
||||
this.dragIndex = -1;
|
||||
}
|
||||
},
|
||||
|
||||
onDrop(event) {
|
||||
const actionData = JSON.parse(event.dataTransfer.getData('action'))
|
||||
this.$emit('add-command', actionData)
|
||||
const actionData = JSON.parse(event.dataTransfer.getData('action'));
|
||||
const newCommand = {
|
||||
...actionData,
|
||||
id: Date.now(), // 或使用其他方式生成唯一ID
|
||||
argv: '',
|
||||
saveOutput: false,
|
||||
useOutput: null,
|
||||
cmd: actionData.value || actionData.cmd,
|
||||
value: actionData.value || actionData.cmd,
|
||||
};
|
||||
|
||||
const newCommands = [...this.commands];
|
||||
if (this.dragIndex >= 0) {
|
||||
newCommands.splice(this.dragIndex, 0, newCommand);
|
||||
} else {
|
||||
newCommands.push(newCommand);
|
||||
}
|
||||
|
||||
this.$emit('update:modelValue', newCommands);
|
||||
this.dragIndex = -1;
|
||||
|
||||
document.querySelectorAll('.dragging').forEach(el => {
|
||||
el.classList.remove('dragging')
|
||||
})
|
||||
el.classList.remove('dragging');
|
||||
});
|
||||
},
|
||||
removeCommand(index) {
|
||||
const newCommands = [...this.commands]
|
||||
newCommands.splice(index, 1)
|
||||
this.$emit('update:modelValue', newCommands)
|
||||
const newCommands = [...this.commands];
|
||||
newCommands.splice(index, 1);
|
||||
this.$emit("update:modelValue", newCommands);
|
||||
},
|
||||
hasOutput(command) {
|
||||
return commandsWithOutput[command.value] || false
|
||||
return commandsWithOutput[command.value] || false;
|
||||
},
|
||||
canUseOutput(command, index) {
|
||||
return commandsAcceptOutput[command.value] && this.getAvailableOutputs(index).length > 0
|
||||
return (
|
||||
commandsAcceptOutput[command.value] &&
|
||||
this.getAvailableOutputs(index).length > 0
|
||||
);
|
||||
},
|
||||
getAvailableOutputs(currentIndex) {
|
||||
return this.commands
|
||||
@ -99,67 +180,75 @@ export default defineComponent({
|
||||
.map((cmd, index) => ({
|
||||
label: `${cmd.label} 的输出`,
|
||||
value: index,
|
||||
disable: !cmd.saveOutput
|
||||
disable: !cmd.saveOutput,
|
||||
}))
|
||||
.filter(item => !item.disable)
|
||||
.filter((item) => !item.disable);
|
||||
},
|
||||
toggleSaveOutput(index) {
|
||||
const newCommands = [...this.commands]
|
||||
newCommands[index].saveOutput = !newCommands[index].saveOutput
|
||||
const newCommands = [...this.commands];
|
||||
newCommands[index].saveOutput = !newCommands[index].saveOutput;
|
||||
if (!newCommands[index].saveOutput) {
|
||||
newCommands.forEach((cmd, i) => {
|
||||
if (i > index && cmd.useOutput === index) {
|
||||
cmd.useOutput = null
|
||||
cmd.useOutput = null;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
this.$emit('update:modelValue', newCommands)
|
||||
this.$emit("update:modelValue", newCommands);
|
||||
},
|
||||
handleArgvChange(index, value) {
|
||||
const newCommands = [...this.commands]
|
||||
newCommands[index].argv = value
|
||||
this.$emit('update:modelValue', newCommands)
|
||||
const newCommands = [...this.commands];
|
||||
newCommands[index].argv = value;
|
||||
this.$emit("update:modelValue", newCommands);
|
||||
},
|
||||
handleUseOutputChange(index, value) {
|
||||
const newCommands = [...this.commands]
|
||||
newCommands[index].useOutput = value
|
||||
const newCommands = [...this.commands];
|
||||
newCommands[index].useOutput = value;
|
||||
if (value !== null) {
|
||||
newCommands[index].argv = ''
|
||||
newCommands[index].argv = "";
|
||||
}
|
||||
this.$emit('update:modelValue', newCommands)
|
||||
this.$emit("update:modelValue", newCommands);
|
||||
},
|
||||
getPlaceholder(element, index) {
|
||||
if (element.useOutput !== null) {
|
||||
return `使用 ${this.commands[element.useOutput].label} 的输出`
|
||||
return `使用 ${this.commands[element.useOutput].label} 的输出`;
|
||||
}
|
||||
return element.desc
|
||||
}
|
||||
}
|
||||
})
|
||||
return element.desc;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.composer-flow {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.command-flow-container {
|
||||
min-height: 100px;
|
||||
padding: 8px;
|
||||
background-color: #fafafa;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.command-flow-container:empty {
|
||||
border: 2px dashed #e0e0e0;
|
||||
.body--dark .command-flow-container {
|
||||
background-color: #303132;
|
||||
}
|
||||
|
||||
.flow-list {
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.drop-area {
|
||||
flex: 1;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.empty-flow {
|
||||
height: 100px;
|
||||
display: flex;
|
||||
@ -169,6 +258,11 @@ export default defineComponent({
|
||||
border-radius: 4px;
|
||||
margin: 8px 0;
|
||||
transition: all 0.3s ease;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.body--dark .empty-flow {
|
||||
border: 2px dashed #676666;
|
||||
}
|
||||
|
||||
.empty-flow:hover {
|
||||
@ -176,12 +270,22 @@ export default defineComponent({
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.body--dark .empty-flow:hover {
|
||||
border-color: #676666;
|
||||
background-color: #303132;
|
||||
}
|
||||
|
||||
/* 拖拽时的视觉反馈 */
|
||||
.command-flow-container.drag-over {
|
||||
background-color: #f0f4ff;
|
||||
border-color: #2196f3;
|
||||
}
|
||||
|
||||
.body--dark .command-flow-container.drag-over {
|
||||
background-color: #303132;
|
||||
border-color: #676666;
|
||||
}
|
||||
|
||||
/* 滑动淡出动画 */
|
||||
.slide-fade-enter-active,
|
||||
.slide-fade-leave-active {
|
||||
@ -198,7 +302,88 @@ export default defineComponent({
|
||||
transform: translateX(30px);
|
||||
}
|
||||
|
||||
/* 拖拽指示器基础样式 */
|
||||
.flow-item::before,
|
||||
.flow-item::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
height: 2px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(0, 0, 0, 0.08) 10%,
|
||||
rgba(0, 0, 0, 0.15) 50%,
|
||||
rgba(0, 0, 0, 0.08) 90%,
|
||||
transparent
|
||||
);
|
||||
opacity: 0;
|
||||
transform: scaleX(0.95) translateY(0);
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
pointer-events: none;
|
||||
filter: blur(0.2px);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.flow-item::before {
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.flow-item::after {
|
||||
bottom: -1px;
|
||||
}
|
||||
|
||||
/* 激活状态 - 插入到元素之前 */
|
||||
.flow-item.insert-before::before {
|
||||
opacity: 1;
|
||||
transform: scaleX(1) translateY(0);
|
||||
box-shadow:
|
||||
0 0 10px rgba(0, 0, 0, 0.03),
|
||||
0 0 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 激活状态 - 插入到最后 */
|
||||
.flow-item.insert-after::after {
|
||||
opacity: 1;
|
||||
transform: scaleX(1) translateY(0);
|
||||
box-shadow:
|
||||
0 0 10px rgba(0, 0, 0, 0.03),
|
||||
0 0 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 拖拽时的卡片效果 */
|
||||
.flow-item {
|
||||
transition: all 0.3s ease;
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.flow-item.insert-before {
|
||||
transform: translateY(3px);
|
||||
}
|
||||
|
||||
.flow-item.insert-after {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
/* 拖拽时相邻元素的间距调整 */
|
||||
.flow-item.insert-before + .flow-item {
|
||||
transform: translateY(3px);
|
||||
}
|
||||
|
||||
/* 暗色模式适配 */
|
||||
.body--dark .flow-item::before,
|
||||
.body--dark .flow-item::after {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.08) 10%,
|
||||
rgba(255, 255, 255, 0.15) 50%,
|
||||
rgba(255, 255, 255, 0.08) 90%,
|
||||
transparent
|
||||
);
|
||||
box-shadow:
|
||||
0 0 10px rgba(255, 255, 255, 0.03),
|
||||
0 0 4px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
</style>
|
||||
|
@ -2,9 +2,14 @@
|
||||
<div class="composer-list">
|
||||
<q-list bordered separator class="rounded-borders">
|
||||
<template v-for="category in commandCategories" :key="category.label">
|
||||
<q-item-label header class="q-py-sm bg-grey-2">
|
||||
<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" />
|
||||
<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>
|
||||
@ -17,8 +22,9 @@
|
||||
@dragstart="onDragStart($event, command)"
|
||||
>
|
||||
<q-item-section>
|
||||
<q-item-label class="text-weight-medium">{{ command.label }}</q-item-label>
|
||||
<q-item-label caption>{{ command.desc }}</q-item-label>
|
||||
<q-item-label class="text-weight-medium">{{
|
||||
command.label
|
||||
}}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-icon name="drag_indicator" color="grey-6" size="16px" />
|
||||
@ -30,61 +36,76 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue'
|
||||
import { commandCategories } from './composerConfig'
|
||||
import { defineComponent } from "vue";
|
||||
import { commandCategories } from "./composerConfig";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ComposerList',
|
||||
name: "ComposerList",
|
||||
props: {
|
||||
commands: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
commandCategories
|
||||
}
|
||||
commandCategories,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getCategoryCommands(category) {
|
||||
return this.commands.filter(cmd =>
|
||||
category.commands.some(catCmd => catCmd.value === cmd.value || catCmd.value === cmd.cmd)
|
||||
)
|
||||
return this.commands.filter((cmd) =>
|
||||
category.commands.some(
|
||||
(catCmd) => catCmd.value === cmd.value || catCmd.value === cmd.cmd
|
||||
)
|
||||
);
|
||||
},
|
||||
onDragStart(event, command) {
|
||||
event.dataTransfer.setData('action', JSON.stringify({
|
||||
...command,
|
||||
cmd: command.value || command.cmd
|
||||
}))
|
||||
event.target.classList.add('dragging')
|
||||
event.target.addEventListener('dragend', () => {
|
||||
event.target.classList.remove('dragging')
|
||||
}, { once: true })
|
||||
}
|
||||
}
|
||||
})
|
||||
event.dataTransfer.setData(
|
||||
"action",
|
||||
JSON.stringify({
|
||||
...command,
|
||||
cmd: command.value || command.cmd,
|
||||
})
|
||||
);
|
||||
event.target.classList.add("dragging");
|
||||
event.target.addEventListener(
|
||||
"dragend",
|
||||
() => {
|
||||
event.target.classList.remove("dragging");
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.composer-list {
|
||||
background-color: white;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 8px;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.command-item {
|
||||
border: 1px solid #e0e0e0;
|
||||
.body--dark .composer-list {
|
||||
background-color: rgba(32, 32, 32, 0.8);
|
||||
}
|
||||
|
||||
.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: white;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
cursor: grab;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.command-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
.command-item.q-item-type:hover {
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
transform: translateX(4px);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.command-item.dragging {
|
||||
@ -97,22 +118,22 @@ export default defineComponent({
|
||||
min-height: 32px;
|
||||
padding: 4px 12px;
|
||||
font-size: 0.9rem;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
background-color: #f5f5f5;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
/* 暗色模式适配 */
|
||||
.body--dark .command-item {
|
||||
border-color: #424242;
|
||||
background: #1d1d1d;
|
||||
.body--dark .command-item.q-item-type {
|
||||
border-color: rgba(255, 255, 255, 0.05);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.body--dark .command-item:hover {
|
||||
background-color: #2d2d2d;
|
||||
.body--dark .command-item.q-item-type:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.body--dark .q-item-label.header {
|
||||
border-color: #424242;
|
||||
background-color: #2d2d2d;
|
||||
border-color: rgba(255, 255, 255, 0.05);
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
</style>
|
||||
|
Loading…
x
Reference in New Issue
Block a user