feat:1.任务大纲编排窗口重构及样式调整2.删除按钮添加
This commit is contained in:
@@ -1 +1 @@
|
|||||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772093306648" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10322" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M512 64C264.576 64 64 264.576 64 512c0 247.422 200.576 448 448 448 247.422 0 448-200.578 448-448C960 264.576 759.422 64 512 64zM593.63 755.834l-107.18 0L486.45 384.388l-68.612 37.82-27.47-89.816 119.986-64.226 83.278 0L593.632 755.834z" fill="#ffffff" p-id="10323"></path></svg>
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772093306648" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10322" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M512 64C264.576 64 64 264.576 64 512c0 247.422 200.576 448 448 448 247.422 0 448-200.578 448-448C960 264.576 759.422 64 512 64zM593.63 755.834l-107.18 0L486.45 384.388l-68.612 37.82-27.47-89.816 119.986-64.226 83.278 0L593.632 755.834z" p-id="10323"></path></svg>
|
||||||
|
Before Width: | Height: | Size: 610 B After Width: | Height: | Size: 596 B |
@@ -1 +1 @@
|
|||||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772093383090" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11068" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M512 64C264.576 64 64 264.576 64 512c0 247.422 200.576 448 448 448 247.422 0 448-200.578 448-448C960 264.576 759.422 64 512 64zM620.256 717.658c-32.132 29.45-77.228 45.014-130.41 45.014-54.27 0-98.822-16.862-120.484-31.298l-12.756-8.504 31.068-92.17 21.712 14.476c13.388 8.926 45.072 22.208 77.488 22.208 33.73 0 69.77-16.142 69.77-61.45 0-31.142-21.452-64.422-81.66-64.422l-50.32 0 0-89.344 49.13 0c28.724 0 69.176-16.298 69.176-52.532 0-27.742-17.812-43.02-50.154-43.02-25.572 0-52.78 12.508-68.308 23.218l-21.586 14.886-30.758-88.038 11.884-8.758c22.908-16.884 69.19-36.6 124.226-36.6 100.06 0 144.848 64.092 144.848 127.61 0 41.306-19.084 77.362-52.648 102.036 37.34 21.14 66.918 60.562 66.918 117.34C667.394 650.968 650.652 689.802 620.256 717.658z" p-id="11069" fill="#ffffff"></path></svg>
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772093383090" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11068" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M512 64C264.576 64 64 264.576 64 512c0 247.422 200.576 448 448 448 247.422 0 448-200.578 448-448C960 264.576 759.422 64 512 64zM620.256 717.658c-32.132 29.45-77.228 45.014-130.41 45.014-54.27 0-98.822-16.862-120.484-31.298l-12.756-8.504 31.068-92.17 21.712 14.476c13.388 8.926 45.072 22.208 77.488 22.208 33.73 0 69.77-16.142 69.77-61.45 0-31.142-21.452-64.422-81.66-64.422l-50.32 0 0-89.344 49.13 0c28.724 0 69.176-16.298 69.176-52.532 0-27.742-17.812-43.02-50.154-43.02-25.572 0-52.78 12.508-68.308 23.218l-21.586 14.886-30.758-88.038 11.884-8.758c22.908-16.884 69.19-36.6 124.226-36.6 100.06 0 144.848 64.092 144.848 127.61 0 41.306-19.084 77.362-52.648 102.036 37.34 21.14 66.918 60.562 66.918 117.34C667.394 650.968 650.652 689.802 620.256 717.658z" p-id="11069"></path></svg>
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1 +1 @@
|
|||||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772093350376" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10815" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M512 64c-247.296 0-448 200.704-448 448s200.704 448 448 448 448-200.704 448-448-200.704-448-448-448zM355.84 759.296v-70.144l52.736-55.296c95.744-97.792 141.312-154.624 141.312-211.968 0-40.96-19.456-61.44-57.344-61.44-29.184 0-56.32 16.896-73.728 30.72l-20.48 16.384-36.864-87.552 11.264-9.728c35.328-29.184 83.968-46.08 133.632-46.08 48.64 0 88.576 15.872 115.712 46.08 23.552 26.624 36.864 63.488 36.864 103.936 0 94.208-69.12 172.032-139.776 243.2l-4.608 4.608h153.6v97.28h-312.32z" fill="#ffffff" p-id="10816"></path></svg>
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772093350376" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10815" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M512 64c-247.296 0-448 200.704-448 448s200.704 448 448 448 448-200.704 448-448-200.704-448-448-448zM355.84 759.296v-70.144l52.736-55.296c95.744-97.792 141.312-154.624 141.312-211.968 0-40.96-19.456-61.44-57.344-61.44-29.184 0-56.32 16.896-73.728 30.72l-20.48 16.384-36.864-87.552 11.264-9.728c35.328-29.184 83.968-46.08 133.632-46.08 48.64 0 88.576 15.872 115.712 46.08 23.552 26.624 36.864 63.488 36.864 103.936 0 94.208-69.12 172.032-139.776 243.2l-4.608 4.608h153.6v97.28h-312.32z"p-id="10816"></path></svg>
|
||||||
|
Before Width: | Height: | Size: 858 B After Width: | Height: | Size: 842 B |
163
frontend/src/components/BranchInput/index.vue
Normal file
163
frontend/src/components/BranchInput/index.vue
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
<template>
|
||||||
|
<div class="external-input-container">
|
||||||
|
<div class="branch-input-container">
|
||||||
|
<el-input
|
||||||
|
v-model="inputValue"
|
||||||
|
placeholder="输入分支需求..."
|
||||||
|
size="small"
|
||||||
|
class="branch-input"
|
||||||
|
@keydown="handleKeydown"
|
||||||
|
ref="inputRef"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="submit-icon"
|
||||||
|
:class="{ 'is-disabled': !inputValue.trim() }"
|
||||||
|
@click="handleSubmit"
|
||||||
|
>
|
||||||
|
<svg-icon icon-class="paper-plane" size="14px" color="#fff" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, nextTick } from 'vue'
|
||||||
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue?: string
|
||||||
|
placeholder?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
modelValue: '',
|
||||||
|
placeholder: '输入分支需求...'
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:modelValue', value: string): void
|
||||||
|
(e: 'submit', value: string): void
|
||||||
|
(e: 'cancel'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const inputValue = ref(props.modelValue)
|
||||||
|
const inputRef = ref<InstanceType<typeof HTMLInputElement>>()
|
||||||
|
|
||||||
|
// 聚焦输入框
|
||||||
|
const focus = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
inputRef.value?.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
focus
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (inputValue.value.trim()) {
|
||||||
|
emit('submit', inputValue.value.trim())
|
||||||
|
inputValue.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
inputValue.value = ''
|
||||||
|
emit('cancel')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeydown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault()
|
||||||
|
handleSubmit()
|
||||||
|
} else if (event.key === 'Escape') {
|
||||||
|
handleCancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.external-input-container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -80px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 260px;
|
||||||
|
background: linear-gradient(var(--color-card-bg-task), var(--color-card-bg-task)) padding-box,
|
||||||
|
linear-gradient(to right, var(--color-accent), var(--color-accent-secondary)) border-box;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 30px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 100;
|
||||||
|
animation: slideDown 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-input-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-input {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-focus {
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__inner {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text-taskbar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(to right, var(--color-accent), var(--color-accent-secondary));
|
||||||
|
|
||||||
|
&:hover:not(.is-disabled) {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(.is-disabled) {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50%) translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
>
|
>
|
||||||
<VueFlow
|
<VueFlow
|
||||||
v-model:nodes="nodes"
|
v-model:nodes="nodes"
|
||||||
v-model:edges="edges"
|
v-model:edges="styledEdges"
|
||||||
:default-zoom="0.5"
|
:default-zoom="0.5"
|
||||||
:min-zoom="0.5"
|
:min-zoom="0.5"
|
||||||
:delete-key-code="null"
|
:delete-key-code="null"
|
||||||
@@ -15,9 +15,9 @@
|
|||||||
class="flow-container"
|
class="flow-container"
|
||||||
@node-click="onNodeClick"
|
@node-click="onNodeClick"
|
||||||
>
|
>
|
||||||
<!-- 根节点 -->
|
<!-- 节点 -->
|
||||||
<template #node-root="nodeProps">
|
<template #node-root="nodeProps">
|
||||||
<RootNode
|
<TaskNode
|
||||||
v-bind="nodeProps"
|
v-bind="nodeProps"
|
||||||
:is-adding-branch="currentAddingBranchNodeId === nodeProps.id"
|
:is-adding-branch="currentAddingBranchNodeId === nodeProps.id"
|
||||||
@add-branch="handleAddBranch"
|
@add-branch="handleAddBranch"
|
||||||
@@ -33,12 +33,12 @@
|
|||||||
v-bind="nodeProps"
|
v-bind="nodeProps"
|
||||||
:is-adding-branch="currentAddingBranchNodeId === nodeProps.id"
|
:is-adding-branch="currentAddingBranchNodeId === nodeProps.id"
|
||||||
:is-branch-selected="selectedNodeIds.has(nodeProps.id)"
|
:is-branch-selected="selectedNodeIds.has(nodeProps.id)"
|
||||||
|
:is-deletable="getNodeDeletable(nodeProps.id).isDeletable"
|
||||||
|
:branch-id="getNodeDeletable(nodeProps.id).branchId"
|
||||||
@add-branch="handleAddBranch"
|
@add-branch="handleAddBranch"
|
||||||
@start-add-branch="handleStartAddBranch"
|
@start-add-branch="handleStartAddBranch"
|
||||||
@cancel-add-branch="handleCancelAddBranch"
|
@cancel-add-branch="handleCancelAddBranch"
|
||||||
@save-task="handleSaveTask"
|
@delete-branch="handleDeleteBranch"
|
||||||
@edit-task="handleEditTask"
|
|
||||||
@cancel-edit="handleCancelEdit"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</VueFlow>
|
</VueFlow>
|
||||||
@@ -58,7 +58,6 @@ import '@vue-flow/core/dist/style.css'
|
|||||||
import '@vue-flow/core/dist/theme-default.css'
|
import '@vue-flow/core/dist/theme-default.css'
|
||||||
import '@vue-flow/minimap/dist/style.css'
|
import '@vue-flow/minimap/dist/style.css'
|
||||||
import '@vue-flow/controls/dist/style.css'
|
import '@vue-flow/controls/dist/style.css'
|
||||||
import RootNode from './components/RootNode.vue'
|
|
||||||
import TaskNode from './components/TaskNode.vue'
|
import TaskNode from './components/TaskNode.vue'
|
||||||
|
|
||||||
const agentsStore = useAgentsStore()
|
const agentsStore = useAgentsStore()
|
||||||
@@ -75,7 +74,7 @@ const nodes = ref<Node[]>([])
|
|||||||
const edges = ref<Edge[]>([])
|
const edges = ref<Edge[]>([])
|
||||||
|
|
||||||
// Vue Flow 实例方法
|
// Vue Flow 实例方法
|
||||||
const { fitView: fit, setTransform, updateNode, findNode } = useVueFlow()
|
const { fitView: fit, setTransform, updateNode, findNode, getNodes, getEdges } = useVueFlow()
|
||||||
|
|
||||||
// 当前正在添加分支的节点 ID
|
// 当前正在添加分支的节点 ID
|
||||||
const currentAddingBranchNodeId = ref<string | null>(null)
|
const currentAddingBranchNodeId = ref<string | null>(null)
|
||||||
@@ -83,6 +82,28 @@ const currentAddingBranchNodeId = ref<string | null>(null)
|
|||||||
// 当前选中的节点ID集合
|
// 当前选中的节点ID集合
|
||||||
const selectedNodeIds = ref<Set<string>>(new Set())
|
const selectedNodeIds = ref<Set<string>>(new Set())
|
||||||
|
|
||||||
|
// 带样式的边数据
|
||||||
|
const styledEdges = computed(() => {
|
||||||
|
return edges.value.map(edge => {
|
||||||
|
// 根节点永远参与高亮
|
||||||
|
const sourceIsRoot = edge.source === 'root-goal'
|
||||||
|
const targetIsRoot = edge.target === 'root-goal'
|
||||||
|
const sourceSelected = selectedNodeIds.value.has(edge.source) || sourceIsRoot
|
||||||
|
const targetSelected = selectedNodeIds.value.has(edge.target) || targetIsRoot
|
||||||
|
const isHighlighted = sourceSelected && targetSelected
|
||||||
|
|
||||||
|
return {
|
||||||
|
...edge,
|
||||||
|
style: {
|
||||||
|
stroke: isHighlighted ? 'var(--color-node-active)' : 'var(--color-node-border)',
|
||||||
|
strokeWidth: 2
|
||||||
|
},
|
||||||
|
animated: isHighlighted,
|
||||||
|
class: isHighlighted ? 'edge-highlighted' : 'edge-normal'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// 回溯到根节点的路径
|
// 回溯到根节点的路径
|
||||||
const getPathToRoot = (targetNodeId: string): string[] => {
|
const getPathToRoot = (targetNodeId: string): string[] => {
|
||||||
const path: string[] = []
|
const path: string[] = []
|
||||||
@@ -672,6 +693,33 @@ const handleCancelAddBranch = () => {
|
|||||||
currentAddingBranchNodeId.value = null
|
currentAddingBranchNodeId.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 判断节点是否可删除(分支从底部出发连接的节点)
|
||||||
|
const getNodeDeletable = (nodeId: string): { isDeletable: boolean; branchId: string | null } => {
|
||||||
|
// 找到指向该节点的边
|
||||||
|
const incomingEdges = edges.value.filter(edge => edge.target === nodeId)
|
||||||
|
|
||||||
|
// 检查是否有从底部(sourceHandle='bottom')连接过来的边
|
||||||
|
const bottomEdge = incomingEdges.find(edge => edge.sourceHandle === 'bottom')
|
||||||
|
|
||||||
|
if (bottomEdge && bottomEdge.data?.branchId) {
|
||||||
|
return {
|
||||||
|
isDeletable: true,
|
||||||
|
branchId: bottomEdge.data.branchId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isDeletable: false,
|
||||||
|
branchId: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除分支
|
||||||
|
const handleDeleteBranch = (branchId: string) => {
|
||||||
|
console.log('删除分支:', branchId)
|
||||||
|
// 后续实现删除逻辑
|
||||||
|
}
|
||||||
|
|
||||||
// 开始编辑任务
|
// 开始编辑任务
|
||||||
const handleEditTask = (nodeId: string) => {
|
const handleEditTask = (nodeId: string) => {
|
||||||
// 更新节点的编辑状态
|
// 更新节点的编辑状态
|
||||||
@@ -862,6 +910,9 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
|
|||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
strokeWidth: 2
|
strokeWidth: 2
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
branchId: String(timestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
edges.value.push(newEdge)
|
edges.value.push(newEdge)
|
||||||
@@ -967,7 +1018,7 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
|
|||||||
|
|
||||||
// 串行填充所有任务的详情(避免并发问题导致字段混乱)
|
// 串行填充所有任务的详情(避免并发问题导致字段混乱)
|
||||||
for (let i = 0; i < newTasks.length; i++) {
|
for (let i = 0; i < newTasks.length; i++) {
|
||||||
await fillStepWithRetry(newTasks[i], i)
|
await fillStepWithRetry(newTasks[i]!, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新 store 中的任务数据
|
// 更新 store 中的任务数据
|
||||||
@@ -1125,13 +1176,16 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
|
|||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
strokeWidth: 2
|
strokeWidth: 2
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
branchId: String(timestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
edges.value.push(newEdge)
|
edges.value.push(newEdge)
|
||||||
newBranchEdges.push(newEdge)
|
newBranchEdges.push(newEdge)
|
||||||
} else {
|
} else {
|
||||||
// 后续任务连接到前一个任务(左右连接)
|
// 后续任务连接到前一个任务(左右连接)
|
||||||
const prevTaskNodeId = taskNodeIds[index - 1]
|
const prevTaskNodeId = taskNodeIds[index - 1]!
|
||||||
const newEdge: Edge = {
|
const newEdge: Edge = {
|
||||||
id: `edge-${prevTaskNodeId}-${taskNodeId}`,
|
id: `edge-${prevTaskNodeId}-${taskNodeId}`,
|
||||||
source: prevTaskNodeId,
|
source: prevTaskNodeId,
|
||||||
@@ -1202,7 +1256,7 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
|
|||||||
newTasks[index]!.Collaboration_Brief_frontEnd = filledTask.Collaboration_Brief_frontEnd
|
newTasks[index]!.Collaboration_Brief_frontEnd = filledTask.Collaboration_Brief_frontEnd
|
||||||
|
|
||||||
// 更新节点数据
|
// 更新节点数据
|
||||||
const taskNodeId = taskNodeIds[index]
|
const taskNodeId = taskNodeIds[index]!
|
||||||
updateNode(taskNodeId, {
|
updateNode(taskNodeId, {
|
||||||
data: {
|
data: {
|
||||||
...findNode(taskNodeId)?.data,
|
...findNode(taskNodeId)?.data,
|
||||||
@@ -1231,7 +1285,7 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
|
|||||||
|
|
||||||
// 串行填充所有任务的详情(避免并发问题导致字段混乱)
|
// 串行填充所有任务的详情(避免并发问题导致字段混乱)
|
||||||
for (let i = 0; i < newTasks.length; i++) {
|
for (let i = 0; i < newTasks.length; i++) {
|
||||||
await fillStepWithRetry(newTasks[i], i)
|
await fillStepWithRetry(newTasks[i]!, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新 store 中的任务数据
|
// 更新 store 中的任务数据
|
||||||
@@ -1312,7 +1366,7 @@ defineExpose({
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: var(--color-bg-detail);
|
background: var(--color-settings-panel-content-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan-modification-header {
|
.plan-modification-header {
|
||||||
@@ -1343,7 +1397,7 @@ defineExpose({
|
|||||||
.flow-container {
|
.flow-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: var(--color-bg-detail);
|
background: var(--color-settings-panel-content-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vue Flow 节点样式
|
// Vue Flow 节点样式
|
||||||
@@ -1351,11 +1405,20 @@ defineExpose({
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.vue-flow__edge-path) {
|
|
||||||
stroke-width: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.vue-flow__edge.selected .vue-flow__edge-path) {
|
:deep(.vue-flow__edge.selected .vue-flow__edge-path) {
|
||||||
stroke: #409eff;
|
stroke: #409eff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 节点连线样式
|
||||||
|
:deep(.vue-flow__edge-path) {
|
||||||
|
stroke: var(--color-node-border);
|
||||||
|
stroke-width: 2;
|
||||||
|
stroke-dasharray: 5 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.vue-flow__edge.edge-highlighted .vue-flow__edge-path) {
|
||||||
|
stroke: var(--color-node-active) !important;
|
||||||
|
stroke-width: 2;
|
||||||
|
stroke-dasharray: 5 5;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,365 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="root-node-wrapper">
|
|
||||||
<!-- 左侧连接点(输入) -->
|
|
||||||
<Handle type="target" :position="Position.Left" id="left" />
|
|
||||||
<!-- 右侧连接点(输出) -->
|
|
||||||
<Handle type="source" :position="Position.Right" id="right" />
|
|
||||||
<!-- 底部连接点(用于分支) -->
|
|
||||||
<Handle type="source" :position="Position.Bottom" id="bottom" />
|
|
||||||
|
|
||||||
<el-card class="root-node-card" :shadow="true">
|
|
||||||
<!-- 目标内容 -->
|
|
||||||
<div class="goal-content">
|
|
||||||
<div class="goal-label">初始目标</div>
|
|
||||||
<div class="goal-text">{{ goal }}</div>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<!-- 底部添加按钮 -->
|
|
||||||
<div v-if="!isAddingBranch" class="external-add-btn" @click="startAddBranch">
|
|
||||||
<el-icon :size="20" color="#409eff">
|
|
||||||
<CirclePlus />
|
|
||||||
</el-icon>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 取消按钮(输入框显示时) -->
|
|
||||||
<div v-else class="external-add-btn cancel-btn" @click="cancelAddBranch">
|
|
||||||
<el-icon :size="20" color="#f56c6c">
|
|
||||||
<Remove />
|
|
||||||
</el-icon>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 外部输入框 -->
|
|
||||||
<div v-if="isAddingBranch" class="external-input-container">
|
|
||||||
<el-input
|
|
||||||
v-model="branchInput"
|
|
||||||
placeholder="输入分支需求..."
|
|
||||||
size="small"
|
|
||||||
@keydown="handleBranchKeydown"
|
|
||||||
ref="branchInputRef"
|
|
||||||
class="branch-input"
|
|
||||||
>
|
|
||||||
<template #suffix>
|
|
||||||
<svg-icon
|
|
||||||
icon-class="paper-plane"
|
|
||||||
size="16px"
|
|
||||||
color="#409eff"
|
|
||||||
class="submit-icon"
|
|
||||||
:class="{ 'is-disabled': !branchInput.trim() }"
|
|
||||||
@click="submitBranch"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, computed, nextTick } from 'vue'
|
|
||||||
import { CirclePlus, Remove } from '@element-plus/icons-vue'
|
|
||||||
import { Handle, Position } from '@vue-flow/core'
|
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
|
||||||
interface RootNodeData {
|
|
||||||
goal: string
|
|
||||||
initialInput?: string[] | string
|
|
||||||
isRoot?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
id: string
|
|
||||||
data: RootNodeData
|
|
||||||
isAddingBranch?: boolean
|
|
||||||
[key: string]: any
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: 'add-branch', nodeId: string, branchContent: string): void
|
|
||||||
(e: 'start-add-branch', nodeId: string): void
|
|
||||||
(e: 'cancel-add-branch'): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const goal = computed(() => props.data.goal || '')
|
|
||||||
|
|
||||||
const initialInput = computed(() => props.data.initialInput)
|
|
||||||
|
|
||||||
const displayInput = computed(() => {
|
|
||||||
if (!initialInput.value) return false
|
|
||||||
if (Array.isArray(initialInput.value)) {
|
|
||||||
return initialInput.value.length > 0
|
|
||||||
}
|
|
||||||
return initialInput.value.trim().length > 0
|
|
||||||
})
|
|
||||||
|
|
||||||
// 分支添加相关状态(使用父组件传入的 prop)
|
|
||||||
const branchInput = ref('')
|
|
||||||
const branchInputRef = ref<InstanceType<typeof HTMLInputElement>>()
|
|
||||||
|
|
||||||
// 计算属性,使用父组件传入的状态
|
|
||||||
const isAddingBranch = computed(() => props.isAddingBranch || false)
|
|
||||||
|
|
||||||
// 分支添加相关方法
|
|
||||||
const startAddBranch = () => {
|
|
||||||
emit('start-add-branch', props.id)
|
|
||||||
branchInput.value = ''
|
|
||||||
nextTick(() => {
|
|
||||||
branchInputRef.value?.focus()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelAddBranch = () => {
|
|
||||||
emit('cancel-add-branch')
|
|
||||||
branchInput.value = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const submitBranch = () => {
|
|
||||||
if (branchInput.value.trim()) {
|
|
||||||
emit('add-branch', props.id, branchInput.value.trim())
|
|
||||||
branchInput.value = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBranchKeydown = (event: KeyboardEvent) => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
event.preventDefault()
|
|
||||||
submitBranch()
|
|
||||||
} else if (event.key === 'Escape') {
|
|
||||||
cancelAddBranch()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.root-node-card {
|
|
||||||
width: 200px;
|
|
||||||
min-height: 100px;
|
|
||||||
background: var(--color-bg-three);
|
|
||||||
border: 2px solid #409eff;
|
|
||||||
box-sizing: border-box;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow: 0 6px 16px rgba(64, 158, 255, 0.3);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-card__body) {
|
|
||||||
padding: 16px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.root-badge {
|
|
||||||
position: absolute;
|
|
||||||
top: -10px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.goal-icon {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.goal-content {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.goal-label {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #409eff;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.goal-text {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-text-title-header);
|
|
||||||
line-height: 1.6;
|
|
||||||
word-break: break-word;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.initial-input {
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-divider {
|
|
||||||
height: 1px;
|
|
||||||
background: linear-gradient(to right, transparent, #409eff, transparent);
|
|
||||||
margin: 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-label {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-list {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 4px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-text {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
text-align: center;
|
|
||||||
word-break: break-word;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 节点包装器
|
|
||||||
.root-node-wrapper {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 外部添加按钮
|
|
||||||
.external-add-btn {
|
|
||||||
position: absolute;
|
|
||||||
bottom: -20px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #fff;
|
|
||||||
border: 2px solid #409eff;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
z-index: 10;
|
|
||||||
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #409eff;
|
|
||||||
transform: translateX(-50%) scale(1.1);
|
|
||||||
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
|
||||||
|
|
||||||
.el-icon {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: translateX(-50%) scale(0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取消按钮样式
|
|
||||||
&.cancel-btn {
|
|
||||||
border-color: #f56c6c;
|
|
||||||
box-shadow: 0 2px 8px rgba(245, 108, 108, 0.2);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #f56c6c;
|
|
||||||
box-shadow: 0 4px 12px rgba(245, 108, 108, 0.3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 外部输入容器
|
|
||||||
.external-input-container {
|
|
||||||
position: absolute;
|
|
||||||
bottom: -80px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: 260px;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #dcdfe6;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
|
||||||
z-index: 100;
|
|
||||||
animation: slideDown 0.2s ease-out;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: -6px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
background: #fff;
|
|
||||||
border-left: 1px solid #dcdfe6;
|
|
||||||
border-top: 1px solid #dcdfe6;
|
|
||||||
transform: translateX(-50%) rotate(45deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slideDown {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(-50%) translateY(-10px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateX(-50%) translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.branch-input {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
:deep(.el-input__wrapper) {
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid #dcdfe6;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: #c0c4cc;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-focus {
|
|
||||||
border-color: #409eff;
|
|
||||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-input__inner {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #000;
|
|
||||||
padding-right: 40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交图标样式
|
|
||||||
.submit-icon {
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
&:hover:not(.is-disabled) {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not(.is-disabled) {
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-disabled {
|
|
||||||
opacity: 0.4;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="task-node-wrapper">
|
<div class="node-wrapper">
|
||||||
<!-- 左侧连接点(输入) -->
|
<!-- 左侧连接点(输入) -->
|
||||||
<Handle type="target" :position="Position.Left" id="left" />
|
<Handle type="target" :position="Position.Left" id="left" />
|
||||||
<!-- 右侧连接点(输出) -->
|
<!-- 右侧连接点(输出) -->
|
||||||
@@ -7,19 +7,27 @@
|
|||||||
<!-- 底部连接点(用于分支) -->
|
<!-- 底部连接点(用于分支) -->
|
||||||
<Handle type="source" :position="Position.Bottom" id="bottom" />
|
<Handle type="source" :position="Position.Bottom" id="bottom" />
|
||||||
|
|
||||||
|
<!-- 删除按钮 - 仅在可删除的分支节点上显示,在左侧 -->
|
||||||
|
<div v-if="isDeletable" class="node-delete-btn left" @click.stop="handleDeleteBranch">
|
||||||
|
<SvgIcon icon-class="close" size="14px" color="#d8d8d8" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 根节点内容 -->
|
||||||
|
<el-card v-if="isRoot" class="node-card root-style" :shadow="true">
|
||||||
|
<div class="goal-content">
|
||||||
|
<div class="goal-label">初始目标</div>
|
||||||
|
<div class="goal-text">{{ goal }}</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 任务节点内容 -->
|
||||||
<el-card
|
<el-card
|
||||||
class="task-node-card"
|
v-else
|
||||||
:class="{
|
class="node-card task-style"
|
||||||
'is-editing': isEditing,
|
:class="{ 'is-branch-selected': isBranchSelected }"
|
||||||
'is-active': isActive,
|
|
||||||
'is-branch-selected': props.isBranchSelected
|
|
||||||
}"
|
|
||||||
:shadow="true"
|
:shadow="true"
|
||||||
>
|
>
|
||||||
<!-- 任务名称 -->
|
|
||||||
<div class="task-name">{{ task.StepName }}</div>
|
<div class="task-name">{{ task.StepName }}</div>
|
||||||
|
|
||||||
<!-- 智能体列表 -->
|
|
||||||
<div class="agents-container">
|
<div class="agents-container">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
v-for="agentSelection in task.AgentSelection"
|
v-for="agentSelection in task.AgentSelection"
|
||||||
@@ -45,42 +53,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- 底部添加按钮(卡片外部) -->
|
<!-- 底部添加按钮 -->
|
||||||
<div v-if="!isAddingBranch" class="external-add-btn" @click="startAddBranch">
|
<div v-if="!isAddingBranch" class="external-add-btn" @click="startAddBranch">
|
||||||
<el-icon :size="20" color="#409eff">
|
<el-icon :size="40" color="#000000">
|
||||||
<CirclePlus />
|
<CirclePlus />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 取消按钮(输入框显示时) -->
|
<!-- 取消按钮(输入框显示时) -->
|
||||||
<div v-else class="external-add-btn cancel-btn" @click="cancelAddBranch">
|
<div v-else class="external-add-btn cancel-btn" @click="cancelAddBranch">
|
||||||
<el-icon :size="20" color="#f56c6c">
|
<el-icon :size="40" color="#f56c6c">
|
||||||
<Remove />
|
<Remove />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 外部输入框 -->
|
<!-- 外部输入框 -->
|
||||||
<div v-if="isAddingBranch" class="external-input-container">
|
<BranchInput
|
||||||
<el-input
|
v-if="isAddingBranch"
|
||||||
v-model="branchInput"
|
ref="branchInputRef"
|
||||||
placeholder="输入分支需求..."
|
@submit="handleBranchSubmit"
|
||||||
size="small"
|
@cancel="handleBranchCancel"
|
||||||
@keydown="handleBranchKeydown"
|
/>
|
||||||
ref="branchInputRef"
|
|
||||||
class="branch-input"
|
|
||||||
>
|
|
||||||
<template #suffix>
|
|
||||||
<svg-icon
|
|
||||||
icon-class="paper-plane"
|
|
||||||
size="16px"
|
|
||||||
color="#409eff"
|
|
||||||
class="submit-icon"
|
|
||||||
:class="{ 'is-disabled': !branchInput.trim() }"
|
|
||||||
@click="submitBranch"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -90,85 +83,83 @@ import { CirclePlus, Remove } from '@element-plus/icons-vue'
|
|||||||
import { Handle, Position } from '@vue-flow/core'
|
import { Handle, Position } from '@vue-flow/core'
|
||||||
import { type IRawStepTask } from '@/stores'
|
import { type IRawStepTask } from '@/stores'
|
||||||
import { getAgentMapIcon } from '@/layout/components/config'
|
import { getAgentMapIcon } from '@/layout/components/config'
|
||||||
|
import BranchInput from '@/components/BranchInput/index.vue'
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
|
|
||||||
|
interface RootNodeData {
|
||||||
|
goal: string
|
||||||
|
initialInput?: string[] | string
|
||||||
|
isRoot?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
interface TaskNodeData {
|
interface TaskNodeData {
|
||||||
task: IRawStepTask
|
task: IRawStepTask
|
||||||
isEditing: boolean
|
isEditing?: boolean
|
||||||
editingContent: string
|
editingContent?: string
|
||||||
|
updateKey?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用更宽松的类型定义来避免类型错误
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
id: string
|
id: string
|
||||||
data: TaskNodeData
|
data: RootNodeData | TaskNodeData
|
||||||
isAddingBranch?: boolean
|
isAddingBranch?: boolean
|
||||||
isBranchSelected?: boolean // 是否属于选中的分支路径
|
isBranchSelected?: boolean
|
||||||
|
isDeletable?: boolean
|
||||||
|
branchId?: string
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'edit-task', nodeId: string): void
|
|
||||||
(e: 'save-task', nodeId: string, content: string): void
|
|
||||||
(e: 'cancel-edit', nodeId: string): void
|
|
||||||
(e: 'add-branch', nodeId: string, branchContent: string): void
|
(e: 'add-branch', nodeId: string, branchContent: string): void
|
||||||
(e: 'start-add-branch', nodeId: string): void
|
(e: 'start-add-branch', nodeId: string): void
|
||||||
(e: 'cancel-add-branch'): void
|
(e: 'cancel-add-branch'): void
|
||||||
|
(e: 'delete-branch', branchId: string): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const editingContent = ref(props.data.task.TaskContent || '')
|
// 判断是否为根节点
|
||||||
|
const isRoot = computed(() => {
|
||||||
|
const data = props.data as RootNodeData
|
||||||
|
return !!data.goal || data.isRoot
|
||||||
|
})
|
||||||
|
|
||||||
// 分支添加相关状态(使用父组件传入的 prop)
|
// 根节点数据
|
||||||
const branchInput = ref('')
|
const goal = computed(() => {
|
||||||
const branchInputRef = ref<InstanceType<typeof HTMLInputElement>>()
|
const data = props.data as RootNodeData
|
||||||
|
return data.goal || ''
|
||||||
|
})
|
||||||
|
|
||||||
// 计算属性,使用父组件传入的状态
|
const initialInput = computed(() => {
|
||||||
const isAddingBranch = computed(() => props.isAddingBranch || false)
|
const data = props.data as RootNodeData
|
||||||
|
return data.initialInput
|
||||||
|
})
|
||||||
|
|
||||||
const isEditing = computed({
|
const displayInput = computed(() => {
|
||||||
get: () => props.data.isEditing,
|
if (!initialInput.value) return false
|
||||||
set: value => {
|
if (Array.isArray(initialInput.value)) {
|
||||||
if (!value) {
|
return initialInput.value.length > 0
|
||||||
emit('cancel-edit', props.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return initialInput.value.trim().length > 0
|
||||||
})
|
})
|
||||||
|
|
||||||
const isActive = computed(() => {
|
// 任务节点数据
|
||||||
return props.data.task.StepName === props.data.task.StepName
|
const task = computed(() => {
|
||||||
|
const data = props.data as TaskNodeData
|
||||||
|
return data.task
|
||||||
})
|
})
|
||||||
|
|
||||||
const task = computed(() => props.data.task)
|
|
||||||
|
|
||||||
const getAgentIcon = (agentName: string) => {
|
const getAgentIcon = (agentName: string) => {
|
||||||
return getAgentMapIcon(agentName)
|
return getAgentMapIcon(agentName)
|
||||||
}
|
}
|
||||||
|
|
||||||
const startEdit = () => {
|
// 分支输入框组件引用
|
||||||
emit('edit-task', props.id)
|
const branchInputRef = ref<InstanceType<typeof BranchInput>>()
|
||||||
}
|
|
||||||
|
|
||||||
const saveEdit = () => {
|
// 计算属性,使用父组件传入的状态
|
||||||
emit('save-task', props.id, editingContent.value)
|
const isAddingBranch = computed(() => props.isAddingBranch || false)
|
||||||
}
|
|
||||||
|
|
||||||
const cancelEdit = () => {
|
|
||||||
emit('cancel-edit', props.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleKeydown = (event: KeyboardEvent) => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
event.preventDefault()
|
|
||||||
saveEdit()
|
|
||||||
} else if (event.key === 'Escape') {
|
|
||||||
cancelEdit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分支添加相关方法
|
// 分支添加相关方法
|
||||||
const startAddBranch = () => {
|
const startAddBranch = () => {
|
||||||
emit('start-add-branch', props.id)
|
emit('start-add-branch', props.id)
|
||||||
branchInput.value = ''
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
branchInputRef.value?.focus()
|
branchInputRef.value?.focus()
|
||||||
})
|
})
|
||||||
@@ -176,107 +167,138 @@ const startAddBranch = () => {
|
|||||||
|
|
||||||
const cancelAddBranch = () => {
|
const cancelAddBranch = () => {
|
||||||
emit('cancel-add-branch')
|
emit('cancel-add-branch')
|
||||||
branchInput.value = ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const submitBranch = () => {
|
const handleBranchSubmit = (value: string) => {
|
||||||
if (branchInput.value.trim()) {
|
emit('add-branch', props.id, value)
|
||||||
emit('add-branch', props.id, branchInput.value.trim())
|
}
|
||||||
branchInput.value = ''
|
|
||||||
|
const handleBranchCancel = () => {
|
||||||
|
emit('cancel-add-branch')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除分支
|
||||||
|
const handleDeleteBranch = () => {
|
||||||
|
if (props.branchId) {
|
||||||
|
emit('delete-branch', props.branchId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleBranchKeydown = (event: KeyboardEvent) => {
|
// 判断是否显示删除按钮
|
||||||
if (event.key === 'Enter') {
|
const isDeletable = computed(() => props.isDeletable || false)
|
||||||
event.preventDefault()
|
|
||||||
submitBranch()
|
|
||||||
} else if (event.key === 'Escape') {
|
|
||||||
cancelAddBranch()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.task-node-card {
|
.node-wrapper {
|
||||||
width: 150px;
|
position: relative;
|
||||||
min-height: 100px;
|
display: flex;
|
||||||
background-color: var(--color-card-bg-task);
|
flex-direction: column;
|
||||||
border: 1px solid var(--color-card-border-task);
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-card {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
transition: all 0.2s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
&.root-style {
|
||||||
background-color: var(--color-card-bg-task-hover);
|
width: 200px;
|
||||||
border-color: var(--color-card-border-hover);
|
min-height: 100px;
|
||||||
box-shadow: var(--color-card-shadow-hover);
|
background: linear-gradient(var(--color-card-bg-task), var(--color-card-bg-task)) padding-box,
|
||||||
}
|
linear-gradient(to right, var(--color-accent), var(--color-accent-secondary)) border-box;
|
||||||
|
border: 2px solid transparent;
|
||||||
&.is-active {
|
|
||||||
border-color: var(--color-primary);
|
|
||||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分支选中高亮样式(绿色)
|
|
||||||
&.is-branch-selected {
|
|
||||||
border-color: #67c23a;
|
|
||||||
box-shadow: 0 0 0 3px rgba(103, 194, 58, 0.3);
|
|
||||||
background-color: rgba(103, 194, 58, 0.05);
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: #67c23a;
|
background: linear-gradient(var(--color-card-bg-task), var(--color-card-bg-task)) padding-box,
|
||||||
box-shadow: 0 0 0 3px rgba(103, 194, 58, 0.4);
|
linear-gradient(to right, var(--color-accent), var(--color-accent-secondary)) border-box;
|
||||||
background-color: rgba(103, 194, 58, 0.08);
|
border: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-editing {
|
&.task-style {
|
||||||
cursor: default;
|
width: 150px;
|
||||||
}
|
min-height: 100px;
|
||||||
|
background-color: var(--color-card-bg-task);
|
||||||
|
border: 2px solid var(--color-node-border);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
:deep(.el-card__body) {
|
&:hover {
|
||||||
padding: 12px;
|
background-color: var(--color-card-bg-task-hover);
|
||||||
display: flex;
|
border-color: var(--color-node-border);
|
||||||
flex-direction: column;
|
box-shadow: var(--color-card-shadow-hover);
|
||||||
gap: 8px;
|
}
|
||||||
position: relative;
|
|
||||||
|
// 分支选中高亮样式(渐变边框)
|
||||||
|
&.is-branch-selected {
|
||||||
|
background: linear-gradient(var(--color-card-bg-task), var(--color-card-bg-task)) padding-box,
|
||||||
|
linear-gradient(to right, var(--color-accent), var(--color-accent-secondary)) border-box;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: linear-gradient(
|
||||||
|
var(--color-card-bg-task-hover),
|
||||||
|
var(--color-card-bg-task-hover)
|
||||||
|
)
|
||||||
|
padding-box,
|
||||||
|
linear-gradient(to right, var(--color-accent), var(--color-accent-secondary)) border-box;
|
||||||
|
box-shadow: var(--color-card-shadow-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
padding: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 根节点样式
|
||||||
|
.goal-content {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goal-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
background: linear-gradient(to right, var(--color-accent), var(--color-accent-secondary));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goal-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-title-header);
|
||||||
|
line-height: 1.6;
|
||||||
|
word-break: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务节点样式
|
||||||
.task-name {
|
.task-name {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--color-text-title-header);
|
color: var(--color-text-title-header);
|
||||||
word-break: break-word;
|
overflow: hidden;
|
||||||
}
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
.divider {
|
max-width: 130px;
|
||||||
height: 1px;
|
|
||||||
width: 100%;
|
|
||||||
background: var(--color-border-separate);
|
|
||||||
margin: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-content {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
min-height: 40px;
|
|
||||||
word-break: break-word;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-content-editing {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.agents-container {
|
.agents-container {
|
||||||
@@ -300,44 +322,11 @@ const handleBranchKeydown = (event: KeyboardEvent) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-outputs {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-label {
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-tooltip {
|
.agent-tooltip {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编辑器样式
|
|
||||||
.task-content-editor {
|
|
||||||
:deep(.el-textarea__inner) {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid #dcdfe6;
|
|
||||||
border-radius: 4px;
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 节点包装器(用于容纳外部按钮)
|
|
||||||
.task-node-wrapper {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 外部添加按钮
|
// 外部添加按钮
|
||||||
.external-add-btn {
|
.external-add-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -357,16 +346,6 @@ const handleBranchKeydown = (event: KeyboardEvent) => {
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #409eff;
|
|
||||||
transform: translateX(-50%) scale(1.1);
|
|
||||||
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
|
||||||
|
|
||||||
.el-icon {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
transform: translateX(-50%) scale(0.95);
|
transform: translateX(-50%) scale(0.95);
|
||||||
}
|
}
|
||||||
@@ -375,41 +354,6 @@ const handleBranchKeydown = (event: KeyboardEvent) => {
|
|||||||
&.cancel-btn {
|
&.cancel-btn {
|
||||||
border-color: #f56c6c;
|
border-color: #f56c6c;
|
||||||
box-shadow: 0 2px 8px rgba(245, 108, 108, 0.2);
|
box-shadow: 0 2px 8px rgba(245, 108, 108, 0.2);
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #f56c6c;
|
|
||||||
box-shadow: 0 4px 12px rgba(245, 108, 108, 0.3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 外部输入容器
|
|
||||||
.external-input-container {
|
|
||||||
position: absolute;
|
|
||||||
bottom: -80px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: 260px;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #dcdfe6;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
|
||||||
z-index: 100;
|
|
||||||
animation: slideDown 0.2s ease-out;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: -6px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
background: #fff;
|
|
||||||
border-left: 1px solid #dcdfe6;
|
|
||||||
border-top: 1px solid #dcdfe6;
|
|
||||||
transform: translateX(-50%) rotate(45deg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,89 +368,29 @@ const handleBranchKeydown = (event: KeyboardEvent) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-branch-section {
|
// 节点删除按钮
|
||||||
margin-top: 8px;
|
.node-delete-btn {
|
||||||
padding-top: 8px;
|
position: absolute;
|
||||||
border-top: 1px dashed var(--color-border-separate);
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #2d2d2d;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
justify-content: center;
|
||||||
|
|
||||||
.add-branch-btn {
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
display: flex;
|
z-index: 100;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
// 左侧位置
|
||||||
|
&.left {
|
||||||
|
left: -100px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: scale(1.1);
|
background-color: #0c0c0c;
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.branch-input-container {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.branch-input {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
:deep(.el-input__wrapper) {
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid #dcdfe6;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: #c0c4cc;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-focus {
|
|
||||||
border-color: #409eff;
|
|
||||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-input__inner {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #000;
|
|
||||||
padding-right: 40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交图标样式
|
|
||||||
.submit-icon {
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
&:hover:not(.is-disabled) {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not(.is-disabled) {
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-disabled {
|
|
||||||
opacity: 0.4;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-adding-branch {
|
|
||||||
.task-node-card {
|
|
||||||
border-color: #409eff;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,308 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { useAgentsStore } from '@/stores'
|
||||||
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
|
import PlanModification from './TaskSyllabus/Branch/PlanModification.vue'
|
||||||
|
import AgentAllocation from './TaskSyllabus/components/AgentAllocation.vue'
|
||||||
|
import PlanTask from './TaskProcess/components/PlanTask.vue'
|
||||||
|
|
||||||
|
const agentsStore = useAgentsStore()
|
||||||
|
|
||||||
|
// 面板显示状态
|
||||||
|
const visible = ref(false)
|
||||||
|
const activeTab = ref('outline')
|
||||||
|
|
||||||
|
// 判断是否有流程数据
|
||||||
|
const planReady = computed(() => {
|
||||||
|
return agentsStore.agentRawPlan.data !== undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
// 判断是否选中了任务
|
||||||
|
const hasSelectedTask = computed(() => {
|
||||||
|
return !!agentsStore.currentTask
|
||||||
|
})
|
||||||
|
|
||||||
|
// 标签页配置
|
||||||
|
const tabs = [
|
||||||
|
{ key: 'outline', label: '任务大纲编排', icon: 'One', required: '' },
|
||||||
|
{ key: 'agent', label: '专家智能体评选', icon: 'Two', required: 'selectedTask' },
|
||||||
|
{ key: 'process', label: '执行过程编排', icon: 'Three', required: 'selectedTask' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 判断标签是否可用
|
||||||
|
const isTabDisabled = (tab: typeof tabs[0]) => {
|
||||||
|
if (tab.required === 'selectedTask') {
|
||||||
|
return !hasSelectedTask.value
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取禁用原因的提示文本
|
||||||
|
const getDisabledReason = (tab: typeof tabs[0]) => {
|
||||||
|
if (tab.required === 'selectedTask' && !hasSelectedTask.value) {
|
||||||
|
return '请先在任务大纲中选中一个任务'
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开面板
|
||||||
|
const open = () => {
|
||||||
|
visible.value = true
|
||||||
|
// 默认选中可用的第一个标签
|
||||||
|
const firstAvailableTab = tabs.find(t => !isTabDisabled(t))
|
||||||
|
if (firstAvailableTab) {
|
||||||
|
activeTab.value = firstAvailableTab.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭面板
|
||||||
|
const close = () => {
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换标签
|
||||||
|
const handleTabChange = (tabKey: string) => {
|
||||||
|
activeTab.value = tabKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露给父组件
|
||||||
|
defineExpose({
|
||||||
|
open,
|
||||||
|
close
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Teleport to="body">
|
||||||
|
<!-- 面板主体 -->
|
||||||
|
<Transition name="slide-up">
|
||||||
|
<div v-if="visible" class="settings-panel">
|
||||||
|
<!-- 标签栏 -->
|
||||||
|
<div class="panel-header">
|
||||||
|
<div class="tabs-container">
|
||||||
|
<div
|
||||||
|
v-for="tab in tabs"
|
||||||
|
:key="tab.key"
|
||||||
|
:class="[
|
||||||
|
'tab-item',
|
||||||
|
{ 'is-active': activeTab === tab.key },
|
||||||
|
{ 'is-disabled': isTabDisabled(tab) }
|
||||||
|
]"
|
||||||
|
:title="getDisabledReason(tab)"
|
||||||
|
@click="!isTabDisabled(tab) && handleTabChange(tab.key)"
|
||||||
|
>
|
||||||
|
<SvgIcon :icon-class="tab.icon" size="18px" />
|
||||||
|
<span class="tab-label">{{ tab.label }}</span>
|
||||||
|
<span v-if="isTabDisabled(tab)" class="tab-disabled-hint">
|
||||||
|
{{ getDisabledReason(tab) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="close-btn" @click="close">
|
||||||
|
<SvgIcon icon-class="close" size="20px" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<div class="panel-content">
|
||||||
|
<!-- 任务大纲 -->
|
||||||
|
<div v-show="activeTab === 'outline'" class="tab-content first-tab">
|
||||||
|
<PlanModification />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 智能体 -->
|
||||||
|
<div v-show="activeTab === 'agent'" class="tab-content">
|
||||||
|
<AgentAllocation />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 任务过程 -->
|
||||||
|
<div v-show="activeTab === 'process'" class="tab-content">
|
||||||
|
<PlanTask />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
// 面板主体
|
||||||
|
.settings-panel {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -40%;
|
||||||
|
width: 80%;
|
||||||
|
max-width: 1200px;
|
||||||
|
height: 70vh;
|
||||||
|
background: var(--color-bg-three);
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 -4px 30px rgba(0, 0, 0.4);
|
||||||
|
z-index: 3000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标题栏(包含标签栏)
|
||||||
|
.panel-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px 10px 0 30px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
transition: all 0.3s;
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
transition: all 0.2s;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 4px;
|
||||||
|
|
||||||
|
&:hover:not(.is-disabled) {
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
color: var(--color-primary);
|
||||||
|
background: var(--color-settings-panel-content-bg);
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(.is-disabled)::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: calc(100% - 8px);
|
||||||
|
height: calc(100% - 8px);
|
||||||
|
background: var(--color-bg-detail);
|
||||||
|
border-radius: 40px;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.svg-icon) {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(.is-disabled) :deep(.svg-icon) {
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active :deep(.svg-icon) {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-disabled-hint {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -30px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: var(--color-bg-detail);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.is-disabled:hover .tab-disabled-hint {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内容区域
|
||||||
|
.panel-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
background: var(--color-settings-panel-content-bg);
|
||||||
|
margin: 0 10px 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 空状态
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动画效果
|
||||||
|
.slide-up-enter-active,
|
||||||
|
.slide-up-leave-active {
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-up-enter-from,
|
||||||
|
.slide-up-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -60,6 +60,10 @@
|
|||||||
--color-card-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.17);
|
--color-card-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.17);
|
||||||
// 任务大纲边框颜色
|
// 任务大纲边框颜色
|
||||||
--color-card-border-task: #E0E0E0; //边框颜色#FFFFFF60
|
--color-card-border-task: #E0E0E0; //边框颜色#FFFFFF60
|
||||||
|
// 任务大纲节点边框颜色(固定#585858)
|
||||||
|
--color-node-border: #CCCCCC;
|
||||||
|
// 任务大纲节点连线高亮颜色(固定#00C7D2)
|
||||||
|
--color-node-active: #00C7D2;
|
||||||
// 执行结果卡片颜色
|
// 执行结果卡片颜色
|
||||||
--color-card-bg-result: #ececec;
|
--color-card-bg-result: #ececec;
|
||||||
// 执行结果边框颜色
|
// 执行结果边框颜色
|
||||||
@@ -118,6 +122,8 @@
|
|||||||
--color-task-edit-hover-border: #D6D6D6;
|
--color-task-edit-hover-border: #D6D6D6;
|
||||||
// 编辑图标背景色
|
// 编辑图标背景色
|
||||||
--color-edit-icon-bg: rgba(255, 255, 255, 0.5);
|
--color-edit-icon-bg: rgba(255, 255, 255, 0.5);
|
||||||
|
// 设置面板内容区背景颜色
|
||||||
|
--color-settings-panel-content-bg: #f7f7f7;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +185,10 @@ html.dark {
|
|||||||
--color-card-bg-task-hover: #171b22;
|
--color-card-bg-task-hover: #171b22;
|
||||||
// 任务大纲边框颜色
|
// 任务大纲边框颜色
|
||||||
--color-card-border-task: #151515;
|
--color-card-border-task: #151515;
|
||||||
|
// 任务大纲节点边框颜色(固定#585858)
|
||||||
|
--color-node-border: #585858;
|
||||||
|
// 任务大纲节点连线高亮颜色(固定#00C7D2)
|
||||||
|
--color-node-active: #00C7D2;
|
||||||
// 执行结果卡片颜色
|
// 执行结果卡片颜色
|
||||||
--color-card-bg-result: #1a1a1a;
|
--color-card-bg-result: #1a1a1a;
|
||||||
// 执行结果边框颜色
|
// 执行结果边框颜色
|
||||||
@@ -241,6 +251,8 @@ html.dark {
|
|||||||
--color-task-edit-hover-border: #ADA9A7;
|
--color-task-edit-hover-border: #ADA9A7;
|
||||||
// 编辑图标背景色
|
// 编辑图标背景色
|
||||||
--color-edit-icon-bg: rgba(255, 255, 255, 0.5);
|
--color-edit-icon-bg: rgba(255, 255, 255, 0.5);
|
||||||
|
// 设置面板内容区背景颜色
|
||||||
|
--color-settings-panel-content-bg: #1c222a;
|
||||||
|
|
||||||
--el-fill-color-blank: transparent !important;
|
--el-fill-color-blank: transparent !important;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user