From ceee955b44129bf6555850c25b62ed658262f1f7 Mon Sep 17 00:00:00 2001 From: liailing1026 <1815388873@qq.com> Date: Sun, 1 Mar 2026 16:58:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=B8=93=E5=AE=B6=E6=99=BA=E8=83=BD?= =?UTF-8?q?=E4=BD=93=E8=AF=84=E9=80=89=E9=A1=B5=E9=9D=A2=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/server.py | 91 ++ frontend/src/api/index.ts | 37 + .../components/AgentAllocation.vue | 1122 +++++++++++++---- 3 files changed, 987 insertions(+), 263 deletions(-) diff --git a/backend/server.py b/backend/server.py index 97cb618..fc868cf 100644 --- a/backend/server.py +++ b/backend/server.py @@ -1489,6 +1489,97 @@ def handle_agent_select_modify_add_aspect_ws(data): }) +@socketio.on('agent_select_modify_delete_aspect') +def handle_agent_select_modify_delete_aspect_ws(data): + """ + WebSocket版本:删除评估维度 + + 请求格式: + { + "id": "request-id", + "data": { + "task_id": "task-id", + "step_id": "step-id", + "aspect_name": "要删除的维度名称" + } + } + """ + request_id = data.get('id') + incoming_data = data.get('data', {}) + task_id = incoming_data.get('task_id') + step_id = incoming_data.get('step_id') + aspect_name = incoming_data.get('aspect_name') + + if not task_id or not aspect_name: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': '缺少必要参数 task_id 或 aspect_name' + }) + return + + try: + with get_db_context() as db: + # 获取现有的 agent_scores 数据 + task = MultiAgentTaskCRUD.get_by_id(db, task_id) + if not task: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': f'任务不存在: {task_id}' + }) + return + + existing_scores = task.agent_scores or {} + + # 如果指定了 step_id,则只更新该步骤的评分;否则更新所有步骤 + if step_id and step_id in existing_scores: + step_scores = existing_scores[step_id] + # 从 aspectList 中移除该维度 + if 'aspectList' in step_scores and aspect_name in step_scores['aspectList']: + step_scores['aspectList'] = [a for a in step_scores['aspectList'] if a != aspect_name] + # 从每个 agent 的评分中移除该维度 + if 'agentScores' in step_scores: + for agent_name in step_scores['agentScores']: + if aspect_name in step_scores['agentScores'][agent_name]: + del step_scores['agentScores'][agent_name][aspect_name] + print(f"[agent_select_modify_delete_aspect] 已删除维度 from step_id={step_id}, 维度={aspect_name}") + else: + # 遍历所有步骤,移除该维度 + for sid, step_scores in existing_scores.items(): + if 'aspectList' in step_scores and aspect_name in step_scores['aspectList']: + step_scores['aspectList'] = [a for a in step_scores['aspectList'] if a != aspect_name] + if 'agentScores' in step_scores: + for agent_name in step_scores['agentScores']: + if aspect_name in step_scores['agentScores'][agent_name]: + del step_scores['agentScores'][agent_name][aspect_name] + print(f"[agent_select_modify_delete_aspect] 已删除所有步骤中的维度,维度={aspect_name}") + + # 保存更新后的评分数据到数据库 + db.execute( + text("UPDATE multi_agent_tasks SET agent_scores = :scores WHERE task_id = :id"), + {"scores": json.dumps(existing_scores), "id": task_id} + ) + db.commit() + + emit('response', { + 'id': request_id, + 'status': 'success', + 'data': { + "message": f"维度 '{aspect_name}' 删除成功", + "task_id": task_id, + "deleted_aspect": aspect_name + } + }) + + except Exception as e: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': str(e) + }) + + @socketio.on('set_agents') def handle_set_agents_ws(data): """ diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 1f9566e..1a96562 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -552,6 +552,43 @@ class Api { agentScores, } } + + /** + * 删除评估维度 + * @param taskId 任务ID + * @param stepId 步骤ID(可选,不传则删除所有步骤中的该维度) + * @param aspectName 要删除的维度名称 + * @returns 是否删除成功 + */ + agentSelectModifyDeleteAspect = async ( + taskId: string, + aspectName: string, + stepId?: string + ): Promise => { + if (!websocket.connected) { + throw new Error('WebSocket未连接') + } + + try { + const rawResponse = await websocket.send( + 'agent_select_modify_delete_aspect', + { + task_id: taskId, + step_id: stepId || '', + aspect_name: aspectName, + }, + undefined, + undefined + ) + + const response = this.extractResponse<{ status: string }>(rawResponse) + return response?.status === 'success' || false + } catch (error) { + console.error('删除维度失败:', error) + return false + } + } + /** * 向正在执行的任务追加新步骤 * @param executionId 执行ID diff --git a/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/components/AgentAllocation.vue b/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/components/AgentAllocation.vue index c2dcddc..0d1643a 100644 --- a/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/components/AgentAllocation.vue +++ b/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/components/AgentAllocation.vue @@ -4,6 +4,7 @@ import { useAgentsStore, useSelectionStore } from '@/stores' import { convertToTaskProcess } from '@/stores/modules/agents' import { getAgentMapIcon } from '@/layout/components/config' import SvgIcon from '@/components/SvgIcon/index.vue' +import DeleteConfirmDialog from '@/components/DeleteConfirmDialog/index.vue' import api from '@/api' const agentsStore = useAgentsStore() const selectionStore = useSelectionStore() @@ -44,8 +45,43 @@ const confirmedAgentGroups = computed(() => { return agentsStore.getConfirmedAgentGroups(taskId) }) +// 任务大纲列表 - 从 agentRawPlan 获取 +const taskOutlineList = computed(() => { + const collaborationProcess = agentsStore.agentRawPlan.data?.['Collaboration Process'] || [] + return collaborationProcess +}) + +// 选中的任务大纲项 +const selectedTaskOutline = ref('') + +// 判断任务大纲项是否被选中 +const isTaskOutlineSelected = (task: { StepName?: string }) => { + return selectedTaskOutline.value === task.StepName +} + +// 选择任务大纲项 +const selectTaskOutline = (task: { StepName?: string; Id?: string }) => { + if (task.StepName) { + selectedTaskOutline.value = task.StepName + // 切换到对应的任务 + if (task.Id) { + const foundTask = taskOutlineList.value.find(t => t.Id === task.Id) + if (foundTask) { + agentsStore.setCurrentTask(foundTask) + } + } + } +} + // 搜索框的值 const searchValue = ref('') +// 是否显示新增维度输入框 +const showDimensionInput = ref(false) + +// 切换新增维度输入框显示 +const toggleDimensionInput = () => { + showDimensionInput.value = !showDimensionInput.value +} // 标志位:防止重复初始化 const isInitializing = ref(false) @@ -155,8 +191,9 @@ const handleSubmit = async () => { } } - // 清空输入框 + // 清空输入框并隐藏 searchValue.value = '' + showDimensionInput.value = false } catch (error) { console.error('添加新维度失败:', error) } finally { @@ -339,7 +376,15 @@ const isAgentGroupSelected = (agentNames: string[]) => { return agentNames.every(agentName => selectedAssignmentGroup.value.includes(agentName)) } -// 获取选中agent的索引范围 +// 切换显示模式:头像或名称 +const showMode = ref<'avatar' | 'name'>('avatar') + +// 切换显示模式 +const toggleShowMode = () => { + showMode.value = showMode.value === 'avatar' ? 'name' : 'avatar' +} + +// 获取选中agent的索引范围(竖向增长 = 选中多个agent时,虚线框从上往下延伸) const getSelectedAgentIndexes = () => { const indexes: number[] = [] agentsHeatmapData.value.forEach((agentData, index) => { @@ -350,7 +395,7 @@ const getSelectedAgentIndexes = () => { return indexes.sort((a, b) => a - b) } -// 计算虚线框的样式 +// 计算虚线框的样式(竖向增长:Y轴方向延伸) const getSelectionBoxStyle = () => { const indexes = getSelectedAgentIndexes() if (indexes.length === 0) { @@ -360,16 +405,18 @@ const getSelectionBoxStyle = () => { const firstIndex: number = indexes[0] ?? 0 const lastIndex: number = indexes[indexes.length - 1] ?? 0 - // 每列的宽度为40px (heatmap-column和heatmap-column-header的宽度) - const columnWidth = 40 - const headerHeight = 40 + // X轴是维度(水平),Y轴是agent(垂直) + // 选中框在Y轴方向延伸(竖向增长),只选中第一列(agent头像/名称列) + const rowHeight = 40 // 每个agent行的高度 + const headerHeight = 40 // 表头高度 const boxPadding = 4 + const agentLabelWidth = 80 // agent名称/头像列宽度 return { - left: `${firstIndex * columnWidth}px`, - top: '0px', - width: `${(lastIndex - firstIndex + 1) * columnWidth}px`, - height: `${headerHeight}px`, + left: '0px', // 从第一列开始 + top: `${headerHeight + firstIndex * rowHeight}px`, + width: `${agentLabelWidth}px`, // 只覆盖第一列(agent名称/头像列) + height: `${(lastIndex - firstIndex + 1) * rowHeight}px`, padding: `${boxPadding}px` } } @@ -653,55 +700,185 @@ watch( // 获取热力图颜色(评分1-5,颜色从浅到深) const getHeatmapColor = (score: number) => { const colors = [ - '#f7f7f7', // 1分 - 最浅 - '#d4e5f7', // 2分 - '#89b4e8', // 3分 - '#4575b4', // 4分 - '#313695' // 5分 - 最深 + '#C8D9FC', // 1分 - 最浅 + '#9CB7F0', // 2分 + '#6C8ED7', // 3分 + '#4A71C7', // 4分 + '#1D59DC' // 5分 - 最深 ] return colors[score - 1] || colors[0] } + +// 删除维度弹窗相关状态 +const deleteDialogVisible = ref(false) +const dimensionToDelete = ref(null) + +// 删除维度(显示确认对话框) +const handleDeleteDimension = (dimension: string, event: Event) => { + event.stopPropagation() // 阻止冒泡,避免触发维度选择 + dimensionToDelete.value = dimension + deleteDialogVisible.value = true +} + +// 确认删除维度 +const confirmDeleteDimension = async () => { + if (!dimensionToDelete.value) return + + const dimension = dimensionToDelete.value + + // 先获取要删除的维度索引(在修改数据之前) + const dimensionIndex = scoreDimensions.value.indexOf(dimension) + if (dimensionIndex === -1) { + deleteDialogVisible.value = false + dimensionToDelete.value = null + return + } + + // 从 scoreDimensions 中移除该维度 + scoreDimensions.value.splice(dimensionIndex, 1) + + // 从每个 agent 的评分数据中移除该维度(使用之前保存的索引) + agentsHeatmapData.value.forEach(agentData => { + if (agentData.scores.length > dimensionIndex) { + agentData.scores.splice(dimensionIndex, 1) + } + if (agentData.scoreDetails && agentData.scoreDetails.length > dimensionIndex) { + agentData.scoreDetails.splice(dimensionIndex, 1) + } + }) + + // 同步更新 store 中的数据 + const taskId = currentTask.value?.Id + if (taskId) { + const existingTaskData = agentsStore.getTaskScoreData(taskId) + if (existingTaskData) { + // 从 aspectList 中移除该维度 + const storeAspectIndex = existingTaskData.aspectList.indexOf(dimension) + if (storeAspectIndex > -1) { + existingTaskData.aspectList.splice(storeAspectIndex, 1) + } + // 从每个 agent 的评分中移除该维度 + for (const agentName in existingTaskData.agentScores) { + if (existingTaskData.agentScores[agentName]?.[dimension]) { + delete existingTaskData.agentScores[agentName][dimension] + } + } + // 保存到 store + agentsStore.setTaskScoreData(taskId, existingTaskData) + } + } + + // 兼容旧版本:更新全局存储 + const currentStoreData = agentsStore.IAgentSelectModifyAddRequest + if (currentStoreData && currentStoreData.aspectList) { + const globalAspectIndex = currentStoreData.aspectList.indexOf(dimension) + if (globalAspectIndex > -1) { + currentStoreData.aspectList.splice(globalAspectIndex, 1) + } + for (const agentName in currentStoreData.agentScores) { + if (currentStoreData.agentScores[agentName]?.[dimension]) { + delete currentStoreData.agentScores[agentName][dimension] + } + } + } + + // 同步删除数据库中的维度数据 + const dbTaskId = (window as any).__CURRENT_TASK_ID__ + const stepTaskId = currentTask.value?.Id + if (dbTaskId && stepTaskId) { + try { + // 同时传入大任务ID和步骤ID,只删除当前步骤的该维度 + await api.agentSelectModifyDeleteAspect(dbTaskId, dimension, stepTaskId) + console.log(`[confirmDeleteDimension] 已同步删除数据库中的维度: ${dimension}, step_id: ${stepTaskId}`) + } catch (error) { + console.error('同步删除数据库维度失败:', error) + } + } + + // 关闭对话框并清理状态 + deleteDialogVisible.value = false + dimensionToDelete.value = null +} @@ -861,21 +1103,27 @@ const getHeatmapColor = (score: number) => { height: 100%; gap: 0; - // 左侧区域 - 30% - .allocation-left { - width: 20%; + // 最左侧区域 - 任务大纲流程 - 18% + .allocation-task-outline { + width: 18%; height: 100%; } - // 中间缝隙 - 1% + // 缝隙 - 1% .allocation-gap { width: 1%; height: 100%; } - // 右侧区域 - 69% + // 左侧区域 - 智能体 - 15% + .allocation-left { + width: 15%; + height: 100%; + } + + // 右侧区域 - 智能体对比 - 65% .allocation-right { - width: 79%; + width: 65%; height: 100%; } @@ -883,7 +1131,6 @@ const getHeatmapColor = (score: number) => { width: 100%; height: 100%; background-color: var(--color-card-bg-task); - border: 1px solid var(--color-card-border-task); border-radius: 8px; padding: 16px; box-sizing: border-box; @@ -902,80 +1149,209 @@ const getHeatmapColor = (score: number) => { } } - .agents-grid { + // 任务大纲列表样式 + .task-outline-list { + flex: 1; + overflow-y: auto; display: flex; - flex-direction: row; - align-items: center; - justify-content: center; + flex-direction: column; gap: 8px; - padding: 12px; - min-height: 48px; - max-height: 48px; - border: 1px solid var(--color-card-border-task); - border-radius: 10px; - overflow-x: auto; - overflow-y: hidden; - .agent-item { - display: flex; - align-items: center; - justify-content: center; + .task-outline-item { + padding: 10px 12px; + border: 1px solid var(--color-card-border-task); + border-radius: 8px; + font-size: 14px; + color: var(--color-text-title-header); cursor: pointer; transition: all 0.2s ease; - flex-shrink: 0; + background-color: transparent; + box-sizing: border-box; - .agent-icon { - width: 30px; - height: 30px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s ease; - border: 3px solid transparent; + &:hover { + background-color: var(--color-card-bg-task-hover); + box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.5); + } + + // 选中状态 - 渐变边框样式 + &.is-selected { + border: 2px solid transparent; + 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; + font-weight: 600; } } } - // Assignment卡片选中效果 - .agents-grid.agent-group-selected { - border: 2px solid var(--color-accent); - box-shadow: 0 0 8px rgba(64, 158, 255, 0.3); + // 智能体分组样式 + .agent-group { + margin-bottom: 12px; + + // 分组标题(独立容器) + .agent-group-title { + display: flex; + align-items: center; + justify-content: space-between; + padding: 4px 8px; + font-size: 10px; + font-weight: 600; + color: var(--color-text-title-header); + cursor: pointer; + + .agent-group-title-text { + flex: 1; + } + + .agent-group-delete { + cursor: pointer; + padding: 2px; + border-radius: 4px; + transition: all 0.3s ease; + + &:hover { + background-color: rgba(0, 0, 0, 0.1); + color: var(--color-text-secondary); + transform: rotate(180deg); + } + } + } + + // 智能体列表容器(选中时有渐变边框) + .agent-list { + display: flex; + flex-direction: column; + border: 1px solid var(--color-card-border-task); + border-radius: 8px; + overflow: hidden; + + // 列表选中效果 + &.agent-list-selected { + border: 2px solid transparent; + 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; + } + } + + .agent-item { + display: flex; + align-items: center; + padding: 8px 12px; + cursor: pointer; + transition: all 0.2s ease; + border-bottom: 1px solid var(--color-border-separate); + + &:last-child { + border-bottom: none; + } + + &:hover { + background-color: var(--color-card-bg-task-hover); + } + + .agent-info { + display: flex; + align-items: center; + gap: 8px; + + .agent-icon { + width: 24px; + height: 24px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + } + + .agent-name { + font-size: 13px; + color: var(--color-text-title-header); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + } } .comparison-content { flex: 1; - overflow: auto; + overflow: visible; display: flex; flex-direction: column; align-items: flex-start; + padding: 10px; + } + + // 热力矩阵包装容器 - 包含矩阵和触发器列 + .heatmap-wrapper { + display: flex; + flex-direction: row; + position: relative; + flex-shrink: 0; + min-width: max-content; + align-items: stretch; + + // 新增维度触发器列 - 右侧固定宽度,随矩阵高度拉伸 + .add-dimension-trigger-column { + width: 20px; + min-height: 40px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + background-color: var(--color-bg-list); + transition: all 0.2s ease; + box-sizing: border-box; + flex-shrink: 0; + position: relative; + border: 1px solid var(--color-border-separate); + border-left: none; + border-radius: 0 10px 10px 0; + + .svg-icon { + color: rgba(216, 216, 216, 0.6); + transition: all 0.2s ease; + } + + &:hover .svg-icon { + color: rgba(216, 216, 216, 1); + } + } } .heatmap-container { display: flex; - flex-direction: row; - flex-wrap: nowrap; + flex-direction: column; gap: 0; position: relative; align-content: flex-start; flex-shrink: 0; min-width: max-content; + border-radius: 10px 0 0 10px; + background-color: var(--color-card-bg-task); + // border: 1px solid var(--color-border-separate); + border-right: none; + overflow: visible; - // 虚线选择框 + // 虚线选择框(竖向增长 = Y轴方向延伸) .selection-box { position: absolute; - border: 2px dashed var(--color-accent); - border-radius: 8px; + border: 2px solid transparent; + border-radius: 6px; pointer-events: none; box-sizing: border-box; - background-color: rgba(64, 158, 255, 0.05); + border-image: linear-gradient(to bottom, var(--color-accent), var(--color-accent-secondary)) 1; z-index: 10; - // 确定按钮 + // 确定按钮 - 位于选中框底部居中 .confirm-button { position: absolute; - right: -8px; + left: 50%; bottom: -8px; + transform: translateX(-50%); width: 20px; height: 20px; background-color: var(--color-card-bg-task); @@ -990,7 +1366,7 @@ const getHeatmapColor = (score: number) => { z-index: 11; &:hover { - transform: scale(1.1); + transform: translateX(-50%) scale(1.1); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); } @@ -1007,103 +1383,228 @@ const getHeatmapColor = (score: number) => { @keyframes spin { from { - transform: rotate(0deg); + transform: translateX(-50%) rotate(0deg); } to { - transform: rotate(360deg); + transform: translateX(-50%) rotate(360deg); } } } - .heatmap-column { + // 维度名称行(X轴 = 水平方向) + .heatmap-dimension-header { display: flex; - flex-direction: column; + flex-direction: row; gap: 0; - transition: all 0.2s ease; - position: relative; - padding: 0; - border-radius: 6px; + flex-shrink: 0; + overflow: visible; - &:hover { - background-color: rgba(0, 0, 0, 0.02); - } - - .heatmap-column-header { - width: 40px; + // 角落格子:显示模式切换 + .heatmap-corner { + width: 80px; height: 40px; display: flex; align-items: center; justify-content: center; - flex-shrink: 0; + background-color: rgba(255, 255, 255, 0.05); + border: 1px solid var(--color-border-separate); + font-size: 12px; + color: var(--color-text-secondary); cursor: pointer; transition: all 0.2s ease; + flex-shrink: 0; + box-sizing: border-box; + gap: 4px; - .agent-avatar { - width: 32px; - height: 32px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; + .separator { + color: var(--color-border-separate); + } + + span { + cursor: pointer; + border-radius: 4px; + transition: all 0.2s ease; + + &.is-active { + color: var(--color-accent); + font-weight: 600; + } + } + + &:hover { + background-color: #50555d; } } - .heatmap-cell { - width: 40px; + // 维度名称单元格 + .dimension-header-cell { + width: 80px; height: 40px; display: flex; align-items: center; justify-content: center; - font-size: 14px; - font-weight: bold; - cursor: default; - transition: all 0.2s ease; - flex-shrink: 0; - - &:hover { - transform: scale(1.1); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); - z-index: 1; - } - } - } - - .dimension-labels { - position: sticky; - left: 0; - margin-left: 8px; - display: flex; - flex-direction: column; - justify-content: flex-start; - padding-top: 40px; // 对齐第一个评分单元格 - flex-shrink: 0; - max-width: 200px; // - - .dimension-label { - min-width: 60px; // 改为最小宽度,允许根据内容自适应 - width: auto; // 宽度自适应 - height: 40px; - display: flex; - align-items: center; - justify-content: flex-start; - padding-left: 8px; - padding-right: 8px; // 添加右侧内边距,确保文字不会紧贴边缘 - font-size: 12px; + position: relative; + font-size: 10px; font-weight: 600; color: var(--color-text-title-header); - text-align: left; + text-align: center; cursor: pointer; user-select: none; transition: all 0.2s ease; - white-space: nowrap; // 防止文字换行 + border: 1px solid var(--color-border-separate); + background-color: var(--color-card-bg-task); + flex-shrink: 0; + box-sizing: border-box; + padding: 2px; + word-break: break-all; + line-height: 1.2; + + // 单数列 + &.is-odd { + background-color: #3a4049; + } + // 双数列 + &.is-even { + background-color: #2f353e; + } &:hover { color: #409eff; + background-color: #50555d; } &.is-selected { color: #409eff; font-weight: 700; + background-color: rgba(64, 158, 255, 0.1); + } + + .dimension-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .dimension-delete-btn { + position: absolute; + top: -6px; + right: -6px; + width: 14px; + height: 14px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + cursor: pointer; + opacity: 0; + transition: opacity 0.2s ease; + color: #fff; + background-color: #2d2d2d; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); + z-index: 10; + + &:hover { + background-color: #000; + transform: scale(1.1); + } + } + + &:hover .dimension-delete-btn { + opacity: 1; + } + } + } + + // agent数据行(Y轴 = 垂直方向) + .heatmap-agent-row { + display: flex; + flex-direction: row; + gap: 0; + flex-shrink: 0; + + // agent名称/头像标签 + .agent-label-cell { + width: 80px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + box-sizing: border-box; + border: 1px solid var(--color-border-separate); + background-color: var(--color-card-bg-task); + + // 单数行 + &.is-odd-row { + background-color: #3a4049; + } + // 双数行 + &.is-even-row { + background-color: #2f353e; + } + + .agent-label-content { + width: 80px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + background-color: #50555d; + } + + .agent-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + } + + .agent-name-display { + font-size: 10px; + color: var(--color-text-title-header); + text-align: center; + word-break: break-all; + line-height: 1.2; + max-width: 76px; + overflow: hidden; + text-overflow: ellipsis; + padding: 0 4px; + } + } + } + + // 评分单元格 + .heatmap-cell-wrapper { + width: 80px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + box-sizing: border-box; + .heatmap-cell { + width: 80px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + font-weight: bold; + cursor: pointer; + transition: all 0.2s ease; + box-sizing: border-box; + + &:hover { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + z-index: 1; + } } } } @@ -1118,6 +1619,101 @@ const getHeatmapColor = (score: number) => { color: var(--color-text-secondary); } + // 新增维度输入框区域 - BranchInput 样式复用 + .external-input-container { + position: absolute; + top: 50%; + transform: translateY(-50%); + right: 100%; + margin-right: 10px; + 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; + } + + .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: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + @keyframes slideRight { + from { + opacity: 0; + transform: translateX(10px); + } + to { + opacity: 1; + transform: translateX(0); + } + } + // 搜索输入框区域 .search-input-container { margin-top: 16px;