feat(task): 添加任务过程编辑功能

- 新增 ProcessCard 组件用于展示和编辑任务流程
- 实现双击编辑任务描述功能
- 添加编辑状态下的卡片式输入界面
- 支持保存和取消编辑操作
- 实现鼠标悬停高亮效果
- 添加颜色处理函数用于界面美化
- 集成到 TaskResult 组件中展示任务过程
- 支持动态创建和管理任务流程连接线
- 添加额外产物编辑功能
- 实现按钮交互状态管理
- 添加滚动和折叠面板事件处理
- 集成 AgentAllocation 组件用于智能体分配
- 实现椭圆框交互效果展示选中状态
- 添加智能体等级颜色配置
- 支持智能体选中状态切换和排序
This commit is contained in:
zhaoweijie
2025-12-15 20:47:51 +08:00
parent 5dace5f788
commit 907310365a
4 changed files with 1933 additions and 0 deletions

View File

@@ -0,0 +1,284 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useAgentsStore } from '@/stores/modules/agents'
import { getAgentMapIcon } from '@/layout/components/config.ts'
import SvgIcon from '@/components/SvgIcon/index.vue'
const emit = defineEmits<{
(e: 'close'): void
}>()
const agentsStore = useAgentsStore()
// 获取当前选中任务的智能体列表
const currentTaskAgents = computed(() => {
return agentsStore.currentTask?.AgentSelection || []
})
// 获取智能体列表
const agents = computed(() => agentsStore.agents)
// 选中状态管理 - 初始选中当前任务的智能体
const selectedAgents = ref<string[]>([])
// 初始化选中当前任务的智能体
const initializeSelectedAgents = () => {
selectedAgents.value = [...currentTaskAgents.value]
}
// 切换选中状态
const toggleSelectAgent = (agentName: string) => {
const index = selectedAgents.value.indexOf(agentName)
if (index > -1) {
// 如果已选中,则取消选中
selectedAgents.value.splice(index, 1)
} else {
// 如果未选中,则添加到选中列表末尾
selectedAgents.value.push(agentName)
}
}
// 检查是否选中
const isAgentSelected = (agentName: string) => {
return selectedAgents.value.includes(agentName)
}
// 获取排序后的智能体列表 - 选中的智能体排在前面,保持原有顺序
const sortedAgents = computed(() => {
const selected = agents.value.filter(agent => selectedAgents.value.includes(agent.Name))
const unselected = agents.value.filter(agent => !selectedAgents.value.includes(agent.Name))
return [...selected, ...unselected]
})
// 颜色等级配置 - 从蓝色到白色的5个等级
const colorLevels = [
{ level: 5, color: '#1d59dc', textColor: '#FFFFFF' }, // 深蓝色
{ level: 4, color: '#4a71c7', textColor: '#FFFFFF' }, // 中蓝色
{ level: 3, color: '#6c8ed7', textColor: '#333333' }, // 浅蓝色
{ level: 2, color: '#9cb7f0', textColor: '#333333' }, // 更浅蓝
{ level: 1, color: '#c8d9fc', textColor: '#333333' } // 接近白色
]
// 为每个智能体分配随机等级(实际应用中应该根据业务逻辑分配)
const getAgentLevel = (agentIndex: number) => {
// 这里使用简单的算法分配等级,实际应用中应该根据智能体的能力、经验等分配
return ((agentIndex % 5) + 1) % 6
}
// 获取对应等级的颜色配置
const getLevelConfig = (level: number) => {
return colorLevels.find(config => config.level === level) || colorLevels[0]
}
// 组件挂载时初始化选中状态
onMounted(() => {
initializeSelectedAgents()
})
// 关闭弹窗
const handleClose = () => {
emit('close')
}
</script>
<template>
<div class="agent-allocation">
<!-- 头部 -->
<div class="allocation-header">
<span class="header-title">智能体分配</span>
<el-button class="close-button" text circle @click="handleClose" title="关闭">
<span class="close-icon"></span>
</el-button>
</div>
<!-- 分割线 -->
<el-divider class="header-divider" />
<!-- 内容区 -->
<div class="allocation-content">
<!-- 智能体整体容器 -->
<div class="agents-container selectagent">
<div
v-for="(agent, index) in agents"
:key="agent.Name"
class="agent-item"
:title="agent.Name"
@click="toggleSelectAgent(agent.Name)"
>
<!-- 头像部分独立元素 -->
<div class="agent-avatar" :style="{ background: getAgentMapIcon(agent.Name).color }">
<SvgIcon :icon-class="getAgentMapIcon(agent.Name).icon" size="24px" color="#fff" />
</div>
<!-- 等级色值部分独立元素通过CSS定位跟随头像 -->
<div
class="color-level"
:style="{
backgroundColor: getLevelConfig(getAgentLevel(index)).color,
color: getLevelConfig(getAgentLevel(index)).textColor
}"
>
{{ getAgentLevel(index) }}
</div>
</div>
</div>
<!-- 空状态 -->
<div v-if="agents.length === 0" class="empty-state">
<el-empty description="暂无智能体数据" />
<div class="empty-tip">请先在智能体库中上传智能体信息</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.agent-allocation {
height: 100%;
display: flex;
flex-direction: column;
.allocation-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
.header-title {
font-size: 18px;
font-weight: bold;
color: var(--color-text-title);
}
.close-button {
padding: 4px;
border: none;
&:hover {
background-color: var(--color-bg-hover);
}
}
}
.header-divider {
margin: 0;
border-color: var(--color-border);
}
.allocation-content {
flex: 1;
padding: 30px 20px;
overflow-y: auto;
display: flex;
flex-direction: column;
align-items: flex-start; // 改为靠左对齐
gap: 20px;
.agents-container {
display: flex;
gap: 0; // 确保容器内元素之间没有间隙
justify-content: flex-start; // 靠左对齐
align-items: flex-start; // 改为顶部对齐
flex-wrap: wrap;
max-width: 100%;
overflow-x: auto;
.agent-item {
display: flex;
flex-direction: column;
align-items: center;
margin: 0 0px; // 确保item之间没有水平间距
padding: 0; // 移除内边距
.agent-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid var(--color-border);
transition: transform 0.2s ease;
}
.color-level {
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: bold;
transition: all 0.3s ease;
cursor: pointer;
margin-top: 8px; // 头像和等级卡片之间的垂直间距
}
}
}
.empty-state {
text-align: center;
padding: 40px 0;
width: 100%;
.empty-tip {
margin-top: 16px;
color: var(--color-text-secondary);
font-size: 14px;
}
}
}
}
// 响应式设计
@media (max-width: 768px) {
.agent-allocation {
.allocation-content {
padding: 20px 16px;
gap: 16px;
.agents-container {
.agent-item {
margin: 0 6px;
.agent-avatar {
width: 40px;
height: 40px;
}
.color-level {
width: 40px;
height: 40px;
font-size: 14px;
margin-top: 8px; // 保持一致的垂直间距
}
}
}
}
}
}
// 小屏幕适配
@media (max-width: 480px) {
.agent-allocation {
.allocation-content {
.agents-container {
.agent-item {
margin: 0 4px;
.agent-avatar {
width: 35px;
height: 35px;
}
.color-level {
width: 35px;
height: 35px;
font-size: 12px;
margin-top: 8px; // 保持一致的垂直间距
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,505 @@
<script setup lang="ts">
import { computed, ref, onMounted, watch } from 'vue'
import { useAgentsStore } from '@/stores/modules/agents'
import { getAgentMapIcon } from '@/layout/components/config.ts'
import SvgIcon from '@/components/SvgIcon/index.vue'
const emit = defineEmits<{
(e: 'close'): void
}>()
const agentsStore = useAgentsStore()
// 获取当前选中任务的智能体列表
const currentTaskAgents = computed(() => {
return agentsStore.currentTask?.AgentSelection || []
})
// 获取智能体列表
const agents = computed(() => agentsStore.agents)
// 选中状态管理 - 初始选中当前任务的智能体
const selectedAgents = ref<string[]>([])
// 初始化选中当前任务的智能体
const initializeSelectedAgents = () => {
selectedAgents.value = [...currentTaskAgents.value]
}
// 切换选中状态
const toggleSelectAgent = (agentName: string) => {
const index = selectedAgents.value.indexOf(agentName)
if (index > -1) {
// 如果已选中,则取消选中
selectedAgents.value.splice(index, 1)
} else {
// 如果未选中,则添加到选中列表末尾
selectedAgents.value.push(agentName)
}
}
// 检查是否选中
const isAgentSelected = (agentName: string) => {
return selectedAgents.value.includes(agentName)
}
// 获取排序后的智能体列表 - 选中的智能体排在前面,保持原有顺序
const sortedAgents = computed(() => {
const selected = agents.value.filter(agent => selectedAgents.value.includes(agent.Name))
const unselected = agents.value.filter(agent => !selectedAgents.value.includes(agent.Name))
return [...selected, ...unselected]
})
// 颜色等级配置 - 从蓝色到白色的5个等级
const colorLevels = [
{ level: 5, color: '#1d59dc', textColor: '#FFFFFF' }, // 深蓝色
{ level: 4, color: '#4a71c7', textColor: '#FFFFFF' }, // 中蓝色
{ level: 3, color: '#6c8ed7', textColor: '#333333' }, // 浅蓝色
{ level: 2, color: '#9cb7f0', textColor: '#333333' }, // 更浅蓝
{ level: 1, color: '#c8d9fc', textColor: '#333333' } // 接近白色
]
// 为每个智能体分配随机等级(实际应用中应该根据业务逻辑分配)
const getAgentLevel = (agentIndex: number) => {
// 这里使用简单的算法分配等级,实际应用中应该根据智能体的能力、经验等分配
return ((agentIndex % 5) + 1) % 6
}
// 获取对应等级的颜色配置
const getLevelConfig = (level: number) => {
return colorLevels.find(config => config.level === level) || colorLevels[0]
}
// ========== 新增:椭圆框交互逻辑 ==========
const ellipseBoxRef = ref<HTMLElement>()
const isEllipseVisible = ref(false)
const ellipseStyle = ref({
width: '0px',
height: '0px',
left: '0px',
top: '0px',
opacity: 0
})
// 椭圆框动画配置
const ellipseAnimationConfig = {
duration: 300,
easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)'
}
// 更新椭圆框位置和大小
const updateEllipseBox = () => {
if (!ellipseBoxRef.value || selectedAgents.value.length === 0) {
isEllipseVisible.value = false
ellipseStyle.value.opacity = 0
return
}
// 获取所有选中头像的位置
const selectedAvatars = document.querySelectorAll('.agent-item.selected .agent-avatar')
if (selectedAvatars.length === 0) {
isEllipseVisible.value = false
ellipseStyle.value.opacity = 0
return
}
// 计算椭圆框的位置和尺寸
let minLeft = Infinity
let maxRight = -Infinity
let minTop = Infinity
let maxBottom = -Infinity
selectedAvatars.forEach(avatar => {
const rect = avatar.getBoundingClientRect()
const containerRect = ellipseBoxRef.value!.parentElement!.getBoundingClientRect()
const left = rect.left - containerRect.left
const right = rect.right - containerRect.left
const top = rect.top - containerRect.top
const bottom = rect.bottom - containerRect.top
minLeft = Math.min(minLeft, left)
maxRight = Math.max(maxRight, right)
minTop = Math.min(minTop, top)
maxBottom = Math.max(maxBottom, bottom)
})
// 计算椭圆框的尺寸(增加一些内边距)
const padding = 8
const width = maxRight - minLeft + padding * 2
const height = maxBottom - minTop + padding * 2
ellipseStyle.value = {
width: `${width}px`,
height: `${height}px`,
left: `${minLeft - padding}px`,
top: `${minTop - padding}px`,
opacity: 1
}
isEllipseVisible.value = true
}
// 监听选中状态变化,更新椭圆框
watch(
selectedAgents,
() => {
setTimeout(() => {
updateEllipseBox()
}, 50) // 等待DOM更新
},
{ deep: true }
)
// 组件挂载时初始化选中状态
onMounted(() => {
initializeSelectedAgents()
// 初始渲染后更新椭圆框
setTimeout(() => {
updateEllipseBox()
}, 100)
})
// 关闭弹窗
const handleClose = () => {
emit('close')
}
</script>
<template>
<div class="agent-allocation">
<!-- 头部 -->
<div class="allocation-header">
<span class="header-title">智能体分配</span>
<el-button class="close-button" text circle @click="handleClose" title="关闭">
<span class="close-icon"></span>
</el-button>
</div>
<!-- 分割线 -->
<el-divider class="header-divider" />
<!-- 内容区 -->
<div class="allocation-content">
<!-- 椭圆框容器 -->
<div class="ellipse-box-container">
<!-- 椭圆框动画元素 -->
<div
ref="ellipseBoxRef"
class="ellipse-box"
:class="{ visible: isEllipseVisible }"
:style="ellipseStyle"
/>
<!-- 智能体整体容器 -->
<div class="agents-container">
<div
v-for="(agent, index) in agents"
:key="agent.Name"
class="agent-item"
:class="{ selected: isAgentSelected(agent.Name) }"
:title="agent.Name"
@click="toggleSelectAgent(agent.Name)"
>
<!-- 头像部分独立元素 -->
<div
class="agent-avatar"
:style="{
background: getAgentMapIcon(agent.Name).color,
borderColor: isAgentSelected(agent.Name) ? '#409eff' : 'var(--color-border)'
}"
>
<SvgIcon :icon-class="getAgentMapIcon(agent.Name).icon" size="24px" color="#fff" />
<!-- 选中标记 -->
<div v-if="isAgentSelected(agent.Name)" class="selected-indicator">
<svg-icon icon-class="check" size="12px" color="#fff" />
</div>
</div>
<!-- 等级色值部分独立元素通过CSS定位跟随头像 -->
<div
class="color-level"
:style="{
backgroundColor: getLevelConfig(getAgentLevel(index)).color,
color: getLevelConfig(getAgentLevel(index)).textColor
}"
>
{{ getAgentLevel(index) }}
</div>
<!-- 智能体名称 -->
<div class="agent-name">{{ agent.Name }}</div>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-if="agents.length === 0" class="empty-state">
<el-empty description="暂无智能体数据" />
<div class="empty-tip">请先在智能体库中上传智能体信息</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.agent-allocation {
height: 100%;
display: flex;
flex-direction: column;
.allocation-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
.header-title {
font-size: 18px;
font-weight: bold;
color: var(--color-text-title);
}
.close-button {
padding: 4px;
border: none;
&:hover {
background-color: var(--color-bg-hover);
}
}
}
.header-divider {
margin: 0;
border-color: var(--color-border);
}
.allocation-content {
flex: 1;
padding: 30px 20px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 20px;
// 椭圆框容器
.ellipse-box-container {
position: relative;
width: 100%;
min-height: 150px;
// 椭圆框样式
.ellipse-box {
position: absolute;
border: 2px solid #409eff;
border-radius: 50px; // 椭圆形状
background: linear-gradient(
135deg,
rgba(64, 158, 255, 0.1) 0%,
rgba(64, 158, 255, 0.05) 100%
);
pointer-events: none; // 允许点击穿透
z-index: 1;
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
opacity: 0;
&.visible {
opacity: 1;
}
// 椭圆框动画
&::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
border: 2px solid rgba(64, 158, 255, 0.3);
border-radius: 50px;
animation: pulse 2s infinite;
z-index: -1;
}
}
}
.agents-container {
display: flex;
flex-wrap: wrap;
gap: 16px;
justify-content: flex-start;
position: relative;
z-index: 2; // 确保头像在椭圆框上方
.agent-item {
display: flex;
flex-direction: column;
align-items: center;
width: 80px;
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
cursor: pointer;
// 选中状态的动画效果
&.selected {
transform: scale(1.05);
z-index: 10;
// 让选中头像移动到前面
order: -1;
}
.agent-avatar {
width: 56px;
height: 56px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 3px solid var(--color-border);
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
&:hover {
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
// 选中指示器
.selected-indicator {
position: absolute;
bottom: -4px;
right: -4px;
width: 20px;
height: 20px;
background: #409eff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid white;
}
}
.color-level {
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: bold;
margin-top: 8px;
transition: all 0.3s ease;
}
.agent-name {
margin-top: 6px;
font-size: 12px;
text-align: center;
color: var(--color-text-secondary);
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.empty-state {
text-align: center;
padding: 40px 0;
width: 100%;
.empty-tip {
margin-top: 16px;
color: var(--color-text-secondary);
font-size: 14px;
}
}
}
}
// 椭圆框脉冲动画
@keyframes pulse {
0% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.5;
transform: scale(1.02);
}
100% {
opacity: 1;
transform: scale(1);
}
}
// 响应式设计
@media (max-width: 768px) {
.agent-allocation {
.allocation-content {
padding: 20px 16px;
gap: 16px;
.agents-container {
gap: 12px;
.agent-item {
width: 70px;
.agent-avatar {
width: 50px;
height: 50px;
}
.color-level {
width: 28px;
height: 28px;
font-size: 12px;
}
.agent-name {
font-size: 11px;
}
}
}
}
}
}
// 小屏幕适配
@media (max-width: 480px) {
.agent-allocation {
.allocation-content {
.agents-container {
gap: 8px;
.agent-item {
width: 65px;
.agent-avatar {
width: 46px;
height: 46px;
}
.color-level {
width: 26px;
height: 26px;
font-size: 11px;
}
.agent-name {
font-size: 10px;
}
}
}
}
}
}
</style>