diff --git a/frontend/src/assets/icons/FangDa.svg b/frontend/src/assets/icons/FangDa.svg new file mode 100644 index 0000000..f37126e --- /dev/null +++ b/frontend/src/assets/icons/FangDa.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/SuoXiao.svg b/frontend/src/assets/icons/SuoXiao.svg new file mode 100644 index 0000000..43f715c --- /dev/null +++ b/frontend/src/assets/icons/SuoXiao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/PlanModification.vue b/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/PlanModification.vue index 332659a..1c7bd2d 100644 --- a/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/PlanModification.vue +++ b/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/PlanModification.vue @@ -42,6 +42,14 @@ /> + + + @@ -59,6 +67,7 @@ import '@vue-flow/core/dist/theme-default.css' import '@vue-flow/minimap/dist/style.css' import '@vue-flow/controls/dist/style.css' import TaskNode from './components/TaskNode.vue' +import DeleteConfirmDialog from '@/components/DeleteConfirmDialog/index.vue' const agentsStore = useAgentsStore() const selectionStore = useSelectionStore() @@ -98,6 +107,13 @@ const styledEdges = computed(() => { stroke: isHighlighted ? 'var(--color-node-active)' : 'var(--color-node-border)', strokeWidth: 2 }, + markerEnd: { + type: 'arrow' as any, + color: isHighlighted ? 'var(--color-node-active)' : 'var(--color-node-border)', + width: 20, + height: 20, + strokeWidth: 2 + }, animated: isHighlighted, class: isHighlighted ? 'edge-highlighted' : 'edge-normal' } @@ -381,7 +397,7 @@ const initializeFlow = () => { style: { stroke: '#e6a23c', strokeWidth: 2 }, markerEnd: { type: 'arrow' as any, - color: '#43a8aa', + color: '#e6a23c', width: 20, height: 20, strokeWidth: 2 @@ -405,7 +421,7 @@ const initializeFlow = () => { style: { stroke: '#409eff', strokeWidth: 2 }, markerEnd: { type: 'arrow' as any, - color: '#43a8aa', + color: '#409eff', width: 20, height: 20, strokeWidth: 2 @@ -714,10 +730,62 @@ const getNodeDeletable = (nodeId: string): { isDeletable: boolean; branchId: str } } -// 删除分支 +// 删除分支弹窗相关状态 +const deleteDialogVisible = ref(false) +const branchIdToDelete = ref(null) + +// 删除分支(显示确认对话框) const handleDeleteBranch = (branchId: string) => { - console.log('删除分支:', branchId) - // 后续实现删除逻辑 + branchIdToDelete.value = branchId + deleteDialogVisible.value = true +} + +// 确认删除分支 +const confirmDeleteBranch = async () => { + if (!branchIdToDelete.value) return + + const branchId = branchIdToDelete.value + + // 1. 从store中获取该分支的信息 + const allBranches = selectionStore.getAllFlowBranches() + // branchId是timestamp格式,需要通过edges中的data.branchId来匹配 + const branchToDelete = allBranches.find(b => + b.edges.some(e => e.data?.branchId === branchId) + ) + + if (!branchToDelete) { + ElMessage.error('未找到该分支') + deleteDialogVisible.value = false + return + } + + // 2. 从nodes中删除该分支的所有节点(排除根节点和主流程节点) + const branchNodeIds = branchToDelete.nodes + .filter(n => n.id !== 'root-goal' && !n.id.startsWith('task-')) + .map(n => n.id) + + nodes.value = nodes.value.filter(n => !branchNodeIds.includes(n.id)) + + // 3. 从edges中删除该分支的所有边 + const branchEdgeIds = branchToDelete.edges.map(e => e.id) + edges.value = edges.value.filter(e => !branchEdgeIds.includes(e.id)) + + // 4. 从selectionStore中删除该分支 + selectionStore.removeFlowBranch(branchToDelete.id) + + // 5. 保存到数据库 + await saveBranchesToDB() + + // 6. 关闭对话框并清理状态 + deleteDialogVisible.value = false + branchIdToDelete.value = null + + // 7. 刷新视图 + nextTick(() => { + fit({ padding: 0.15, duration: 300 }) + }) + + ElMessage.success('分支删除成功') } // 开始编辑任务 @@ -895,6 +963,7 @@ const handleAddBranch = async (taskId: string, branchContent: string) => { // 创建连接边 if (index === 0) { + const strokeColor = taskId === 'root-goal' ? '#409eff' : '#67c23a' const newEdge: Edge = { id: `edge-${taskId}-${taskNodeId}`, source: taskId, @@ -903,10 +972,10 @@ const handleAddBranch = async (taskId: string, branchContent: string) => { targetHandle: 'left', type: 'smoothstep', animated: true, - style: { stroke: '#409eff', strokeWidth: 2, strokeDasharray: '5,5' }, + style: { stroke: strokeColor, strokeWidth: 2, strokeDasharray: '5,5' }, markerEnd: { type: 'arrow' as any, - color: '#43a8aa', + color: strokeColor, width: 20, height: 20, strokeWidth: 2 @@ -930,7 +999,7 @@ const handleAddBranch = async (taskId: string, branchContent: string) => { style: { stroke: '#409eff', strokeWidth: 2 }, markerEnd: { type: 'arrow' as any, - color: '#43a8aa', + color: '#409eff', width: 20, height: 20, strokeWidth: 2 @@ -1172,7 +1241,7 @@ const handleAddBranch = async (taskId: string, branchContent: string) => { style: { stroke: '#67c23a', strokeWidth: 2, strokeDasharray: '5,5' }, markerEnd: { type: 'arrow' as any, - color: '#43a8aa', + color: '#67c23a', width: 20, height: 20, strokeWidth: 2 @@ -1197,7 +1266,7 @@ const handleAddBranch = async (taskId: string, branchContent: string) => { style: { stroke: '#67c23a', strokeWidth: 2 }, markerEnd: { type: 'arrow' as any, - color: '#43a8aa', + color: '#67c23a', width: 20, height: 20, strokeWidth: 2 diff --git a/frontend/src/layout/components/Main/TaskTemplate/UnifiedSettingsPanel.vue b/frontend/src/layout/components/Main/TaskTemplate/UnifiedSettingsPanel.vue index b91b364..46afcee 100644 --- a/frontend/src/layout/components/Main/TaskTemplate/UnifiedSettingsPanel.vue +++ b/frontend/src/layout/components/Main/TaskTemplate/UnifiedSettingsPanel.vue @@ -11,6 +11,7 @@ const agentsStore = useAgentsStore() // 面板显示状态 const visible = ref(false) const activeTab = ref('outline') +const isMaximized = ref(false) // 判断是否有流程数据 const planReady = computed(() => { @@ -65,6 +66,11 @@ const handleTabChange = (tabKey: string) => { activeTab.value = tabKey } +// 切换最大化/还原 +const toggleMaximize = () => { + isMaximized.value = !isMaximized.value +} + // 暴露给父组件 defineExpose({ open, @@ -76,7 +82,7 @@ defineExpose({ -
+
@@ -98,6 +104,9 @@ defineExpose({
+ @@ -129,11 +138,12 @@ defineExpose({ // 面板主体 .settings-panel { position: fixed; - bottom: 20px; - left: 50%; - margin-left: -40%; + bottom: 6%; + left: 0; + right: 0; width: 80%; max-width: 1200px; + margin: 0 auto; height: 70vh; background: var(--color-bg-three); border-radius: 20px; @@ -142,6 +152,17 @@ defineExpose({ display: flex; flex-direction: column; overflow: hidden; + transition: width 0.4s ease, height 0.4s ease, bottom 0.4s ease, border-radius 0.4s ease; + + &.is-maximized { + max-width: none; + left: 24px; + right: 24px; + width: auto; + height: calc(100% - 194px); + bottom: 24px; + border-radius: 20px; + } } // 标题栏(包含标签栏) @@ -161,6 +182,26 @@ defineExpose({ gap: 8px; } +.maximize-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: 60px; + + &:hover { + transform: rotate(180deg); + } +} + .close-btn { background: none; border: none; @@ -177,7 +218,7 @@ defineExpose({ right: 20px; &:hover { - transform: rotate(360deg); + transform: rotate(180deg); } } @@ -297,7 +338,7 @@ defineExpose({ // 动画效果 .slide-up-enter-active, .slide-up-leave-active { - transition: all 0.5s ease; + transition: opacity 0.5s ease, transform 0.5s ease; } .slide-up-enter-from,