feat:1.任务大纲编排窗口重构及样式调整2.删除按钮添加

This commit is contained in:
liailing1026
2026-02-28 09:57:27 +08:00
parent 1cf43fec7c
commit 8b9c493393
9 changed files with 762 additions and 697 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"
placeholder="输入分支需求..."
size="small"
@keydown="handleBranchKeydown"
ref="branchInputRef" ref="branchInputRef"
class="branch-input" @submit="handleBranchSubmit"
> @cancel="handleBranchCancel"
<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(() => {
// 分支添加相关状态(使用父组件传入的 prop const data = props.data as RootNodeData
const branchInput = ref('') return !!data.goal || data.isRoot
const branchInputRef = ref<InstanceType<typeof HTMLInputElement>>()
// 计算属性,使用父组件传入的状态
const isAddingBranch = computed(() => props.isAddingBranch || false)
const isEditing = computed({
get: () => props.data.isEditing,
set: value => {
if (!value) {
emit('cancel-edit', props.id)
}
}
}) })
const isActive = computed(() => { // 根节点数据
return props.data.task.StepName === props.data.task.StepName const goal = computed(() => {
const data = props.data as RootNodeData
return data.goal || ''
}) })
const task = computed(() => props.data.task) const initialInput = computed(() => {
const data = props.data as RootNodeData
return 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
})
// 任务节点数据
const task = computed(() => {
const data = props.data as TaskNodeData
return 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,64 +167,92 @@ 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 {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.node-card {
box-sizing: border-box;
&.root-style {
width: 200px;
min-height: 100px;
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;
&: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;
}
:deep(.el-card__body) {
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
position: relative;
}
}
&.task-style {
width: 150px; width: 150px;
min-height: 100px; min-height: 100px;
background-color: var(--color-card-bg-task); background-color: var(--color-card-bg-task);
border: 1px solid var(--color-card-border-task); border: 2px solid var(--color-node-border);
box-sizing: border-box;
transition: all 0.2s ease; transition: all 0.2s ease;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background-color: var(--color-card-bg-task-hover); background-color: var(--color-card-bg-task-hover);
border-color: var(--color-card-border-hover); border-color: var(--color-node-border);
box-shadow: var(--color-card-shadow-hover); box-shadow: var(--color-card-shadow-hover);
} }
&.is-active { // 分支选中高亮样式(渐变边框)
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
// 分支选中高亮样式(绿色)
&.is-branch-selected { &.is-branch-selected {
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.3); linear-gradient(to right, var(--color-accent), var(--color-accent-secondary)) border-box;
background-color: rgba(103, 194, 58, 0.05); border: 2px solid transparent;
box-sizing: border-box;
&:hover { &:hover {
border-color: #67c23a; background: linear-gradient(
box-shadow: 0 0 0 3px rgba(103, 194, 58, 0.4); var(--color-card-bg-task-hover),
background-color: rgba(103, 194, 58, 0.08); 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);
} }
} }
&.is-editing {
cursor: default;
}
:deep(.el-card__body) { :deep(.el-card__body) {
padding: 12px; padding: 12px;
display: flex; display: flex;
@@ -241,42 +260,45 @@ const handleBranchKeydown = (event: KeyboardEvent) => {
gap: 8px; gap: 8px;
position: relative; 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>

View File

@@ -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>

View File

@@ -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;
} }