diff --git a/frontend/src/assets/icons/One.svg b/frontend/src/assets/icons/One.svg index 332d940..f65a2ac 100644 --- a/frontend/src/assets/icons/One.svg +++ b/frontend/src/assets/icons/One.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/frontend/src/assets/icons/Three.svg b/frontend/src/assets/icons/Three.svg index ef5e4ae..7b71ea2 100644 --- a/frontend/src/assets/icons/Three.svg +++ b/frontend/src/assets/icons/Three.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/frontend/src/assets/icons/Two.svg b/frontend/src/assets/icons/Two.svg index cff6074..e7a5171 100644 --- a/frontend/src/assets/icons/Two.svg +++ b/frontend/src/assets/icons/Two.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/frontend/src/components/BranchInput/index.vue b/frontend/src/components/BranchInput/index.vue new file mode 100644 index 0000000..315eb4d --- /dev/null +++ b/frontend/src/components/BranchInput/index.vue @@ -0,0 +1,163 @@ + + + + + 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 9f5ae9a..332659a 100644 --- a/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/PlanModification.vue +++ b/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/PlanModification.vue @@ -7,7 +7,7 @@ > - + @@ -58,7 +58,6 @@ import '@vue-flow/core/dist/style.css' import '@vue-flow/core/dist/theme-default.css' import '@vue-flow/minimap/dist/style.css' import '@vue-flow/controls/dist/style.css' -import RootNode from './components/RootNode.vue' import TaskNode from './components/TaskNode.vue' const agentsStore = useAgentsStore() @@ -75,7 +74,7 @@ const nodes = ref([]) const edges = ref([]) // Vue Flow 实例方法 -const { fitView: fit, setTransform, updateNode, findNode } = useVueFlow() +const { fitView: fit, setTransform, updateNode, findNode, getNodes, getEdges } = useVueFlow() // 当前正在添加分支的节点 ID const currentAddingBranchNodeId = ref(null) @@ -83,6 +82,28 @@ const currentAddingBranchNodeId = ref(null) // 当前选中的节点ID集合 const selectedNodeIds = ref>(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 path: string[] = [] @@ -672,6 +693,33 @@ const handleCancelAddBranch = () => { 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) => { // 更新节点的编辑状态 @@ -862,6 +910,9 @@ const handleAddBranch = async (taskId: string, branchContent: string) => { width: 20, height: 20, strokeWidth: 2 + }, + data: { + branchId: String(timestamp) } } edges.value.push(newEdge) @@ -967,7 +1018,7 @@ const handleAddBranch = async (taskId: string, branchContent: string) => { // 串行填充所有任务的详情(避免并发问题导致字段混乱) for (let i = 0; i < newTasks.length; i++) { - await fillStepWithRetry(newTasks[i], i) + await fillStepWithRetry(newTasks[i]!, i) } // 更新 store 中的任务数据 @@ -1125,13 +1176,16 @@ const handleAddBranch = async (taskId: string, branchContent: string) => { width: 20, height: 20, strokeWidth: 2 + }, + data: { + branchId: String(timestamp) } } edges.value.push(newEdge) newBranchEdges.push(newEdge) } else { // 后续任务连接到前一个任务(左右连接) - const prevTaskNodeId = taskNodeIds[index - 1] + const prevTaskNodeId = taskNodeIds[index - 1]! const newEdge: Edge = { id: `edge-${prevTaskNodeId}-${taskNodeId}`, source: prevTaskNodeId, @@ -1202,7 +1256,7 @@ const handleAddBranch = async (taskId: string, branchContent: string) => { newTasks[index]!.Collaboration_Brief_frontEnd = filledTask.Collaboration_Brief_frontEnd // 更新节点数据 - const taskNodeId = taskNodeIds[index] + const taskNodeId = taskNodeIds[index]! updateNode(taskNodeId, { data: { ...findNode(taskNodeId)?.data, @@ -1231,7 +1285,7 @@ const handleAddBranch = async (taskId: string, branchContent: string) => { // 串行填充所有任务的详情(避免并发问题导致字段混乱) for (let i = 0; i < newTasks.length; i++) { - await fillStepWithRetry(newTasks[i], i) + await fillStepWithRetry(newTasks[i]!, i) } // 更新 store 中的任务数据 @@ -1312,7 +1366,7 @@ defineExpose({ height: 100%; display: flex; flex-direction: column; - background: var(--color-bg-detail); + background: var(--color-settings-panel-content-bg); } .plan-modification-header { @@ -1343,7 +1397,7 @@ defineExpose({ .flow-container { width: 100%; height: 100%; - background: var(--color-bg-detail); + background: var(--color-settings-panel-content-bg); } // Vue Flow 节点样式 @@ -1351,11 +1405,20 @@ defineExpose({ border-radius: 8px; } -:deep(.vue-flow__edge-path) { - stroke-width: 2; -} - :deep(.vue-flow__edge.selected .vue-flow__edge-path) { 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; +} diff --git a/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/components/RootNode.vue b/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/components/RootNode.vue deleted file mode 100644 index 1fd10a8..0000000 --- a/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/components/RootNode.vue +++ /dev/null @@ -1,365 +0,0 @@ - - - - - diff --git a/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/components/TaskNode.vue b/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/components/TaskNode.vue index e84bbc5..f7043e3 100644 --- a/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/components/TaskNode.vue +++ b/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/components/TaskNode.vue @@ -1,5 +1,5 @@ @@ -90,85 +83,83 @@ import { CirclePlus, Remove } from '@element-plus/icons-vue' import { Handle, Position } from '@vue-flow/core' import { type IRawStepTask } from '@/stores' import { getAgentMapIcon } from '@/layout/components/config' +import BranchInput from '@/components/BranchInput/index.vue' import SvgIcon from '@/components/SvgIcon/index.vue' +interface RootNodeData { + goal: string + initialInput?: string[] | string + isRoot?: boolean +} + interface TaskNodeData { task: IRawStepTask - isEditing: boolean - editingContent: string + isEditing?: boolean + editingContent?: string + updateKey?: number } -// 使用更宽松的类型定义来避免类型错误 const props = defineProps<{ id: string - data: TaskNodeData + data: RootNodeData | TaskNodeData isAddingBranch?: boolean - isBranchSelected?: boolean // 是否属于选中的分支路径 + isBranchSelected?: boolean + isDeletable?: boolean + branchId?: string [key: string]: any }>() 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: 'start-add-branch', nodeId: string): 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 branchInputRef = ref>() +// 根节点数据 +const goal = computed(() => { + const data = props.data as RootNodeData + return data.goal || '' +}) -// 计算属性,使用父组件传入的状态 -const isAddingBranch = computed(() => props.isAddingBranch || false) +const initialInput = computed(() => { + const data = props.data as RootNodeData + return data.initialInput +}) -const isEditing = computed({ - get: () => props.data.isEditing, - set: value => { - if (!value) { - emit('cancel-edit', props.id) - } +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 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) => { return getAgentMapIcon(agentName) } -const startEdit = () => { - emit('edit-task', props.id) -} +// 分支输入框组件引用 +const branchInputRef = ref>() -const saveEdit = () => { - emit('save-task', props.id, editingContent.value) -} - -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 isAddingBranch = computed(() => props.isAddingBranch || false) // 分支添加相关方法 const startAddBranch = () => { emit('start-add-branch', props.id) - branchInput.value = '' nextTick(() => { branchInputRef.value?.focus() }) @@ -176,107 +167,138 @@ const startAddBranch = () => { 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 handleBranchSubmit = (value: string) => { + emit('add-branch', props.id, 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') { - event.preventDefault() - submitBranch() - } else if (event.key === 'Escape') { - cancelAddBranch() - } -} +// 判断是否显示删除按钮 +const isDeletable = computed(() => props.isDeletable || false) diff --git a/frontend/src/layout/components/Main/TaskTemplate/UnifiedSettingsPanel.vue b/frontend/src/layout/components/Main/TaskTemplate/UnifiedSettingsPanel.vue new file mode 100644 index 0000000..b91b364 --- /dev/null +++ b/frontend/src/layout/components/Main/TaskTemplate/UnifiedSettingsPanel.vue @@ -0,0 +1,308 @@ + + + + + diff --git a/frontend/src/styles/theme.scss b/frontend/src/styles/theme.scss index ac67f5c..916bdf0 100644 --- a/frontend/src/styles/theme.scss +++ b/frontend/src/styles/theme.scss @@ -60,6 +60,10 @@ --color-card-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.17); // 任务大纲边框颜色 --color-card-border-task: #E0E0E0; //边框颜色#FFFFFF60 + // 任务大纲节点边框颜色(固定#585858) + --color-node-border: #CCCCCC; + // 任务大纲节点连线高亮颜色(固定#00C7D2) + --color-node-active: #00C7D2; // 执行结果卡片颜色 --color-card-bg-result: #ececec; // 执行结果边框颜色 @@ -118,6 +122,8 @@ --color-task-edit-hover-border: #D6D6D6; // 编辑图标背景色 --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-border-task: #151515; + // 任务大纲节点边框颜色(固定#585858) + --color-node-border: #585858; + // 任务大纲节点连线高亮颜色(固定#00C7D2) + --color-node-active: #00C7D2; // 执行结果卡片颜色 --color-card-bg-result: #1a1a1a; // 执行结果边框颜色 @@ -241,6 +251,8 @@ html.dark { --color-task-edit-hover-border: #ADA9A7; // 编辑图标背景色 --color-edit-icon-bg: rgba(255, 255, 255, 0.5); + // 设置面板内容区背景颜色 + --color-settings-panel-content-bg: #1c222a; --el-fill-color-blank: transparent !important; }