From 8b9c493393a67abc20e43f287677378e16fe395b Mon Sep 17 00:00:00 2001
From: liailing1026 <1815388873@qq.com>
Date: Sat, 28 Feb 2026 09:57:27 +0800
Subject: [PATCH] =?UTF-8?q?feat:1.=E4=BB=BB=E5=8A=A1=E5=A4=A7=E7=BA=B2?=
=?UTF-8?q?=E7=BC=96=E6=8E=92=E7=AA=97=E5=8F=A3=E9=87=8D=E6=9E=84=E5=8F=8A?=
=?UTF-8?q?=E6=A0=B7=E5=BC=8F=E8=B0=83=E6=95=B42.=E5=88=A0=E9=99=A4?=
=?UTF-8?q?=E6=8C=89=E9=92=AE=E6=B7=BB=E5=8A=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/src/assets/icons/One.svg | 2 +-
frontend/src/assets/icons/Three.svg | 2 +-
frontend/src/assets/icons/Two.svg | 2 +-
frontend/src/components/BranchInput/index.vue | 163 ++++++
.../TaskSyllabus/Branch/PlanModification.vue | 99 +++-
.../Branch/components/RootNode.vue | 365 -------------
.../Branch/components/TaskNode.vue | 506 +++++++-----------
.../TaskTemplate/UnifiedSettingsPanel.vue | 308 +++++++++++
frontend/src/styles/theme.scss | 12 +
9 files changed, 762 insertions(+), 697 deletions(-)
create mode 100644 frontend/src/components/BranchInput/index.vue
delete mode 100644 frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/components/RootNode.vue
create mode 100644 frontend/src/layout/components/Main/TaskTemplate/UnifiedSettingsPanel.vue
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 @@
-
+
@@ -7,19 +7,27 @@
+
+
+
+
+
+
+
+
+
+
+
-
{{ task.StepName }}
-
-
-
+
-
+
-
+
-
-
-
-
-
-
-
+
@@ -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;
}