Files
AgentCoord/frontend/src/layout/components/Main/TaskTemplate/UnifiedSettingsPanel.vue

309 lines
6.7 KiB
Vue

<script setup lang="ts">
import { ref, computed } from 'vue'
import { useAgentsStore } from '@/stores'
import SvgIcon from '@/components/SvgIcon/index.vue'
import PlanModification from './TaskSyllabus/Branch/PlanModification.vue'
import AgentAllocation from './TaskSyllabus/components/AgentAllocation.vue'
import PlanTask from './TaskProcess/components/PlanTask.vue'
const agentsStore = useAgentsStore()
// 面板显示状态
const visible = ref(false)
const activeTab = ref('outline')
// 判断是否有流程数据
const planReady = computed(() => {
return agentsStore.agentRawPlan.data !== undefined
})
// 判断是否选中了任务
const hasSelectedTask = computed(() => {
return !!agentsStore.currentTask
})
// 标签页配置
const tabs = [
{ key: 'outline', label: '任务大纲编排', icon: 'One', required: '' },
{ key: 'agent', label: '专家智能体评选', icon: 'Two', required: 'selectedTask' },
{ key: 'process', label: '执行过程编排', icon: 'Three', required: 'selectedTask' }
]
// 判断标签是否可用
const isTabDisabled = (tab: typeof tabs[0]) => {
if (tab.required === 'selectedTask') {
return !hasSelectedTask.value
}
return false
}
// 获取禁用原因的提示文本
const getDisabledReason = (tab: typeof tabs[0]) => {
if (tab.required === 'selectedTask' && !hasSelectedTask.value) {
return '请先在任务大纲中选中一个任务'
}
return ''
}
// 打开面板
const open = () => {
visible.value = true
// 默认选中可用的第一个标签
const firstAvailableTab = tabs.find(t => !isTabDisabled(t))
if (firstAvailableTab) {
activeTab.value = firstAvailableTab.key
}
}
// 关闭面板
const close = () => {
visible.value = false
}
// 切换标签
const handleTabChange = (tabKey: string) => {
activeTab.value = tabKey
}
// 暴露给父组件
defineExpose({
open,
close
})
</script>
<template>
<Teleport to="body">
<!-- 面板主体 -->
<Transition name="slide-up">
<div v-if="visible" class="settings-panel">
<!-- 标签栏 -->
<div class="panel-header">
<div class="tabs-container">
<div
v-for="tab in tabs"
:key="tab.key"
:class="[
'tab-item',
{ 'is-active': activeTab === tab.key },
{ 'is-disabled': isTabDisabled(tab) }
]"
:title="getDisabledReason(tab)"
@click="!isTabDisabled(tab) && handleTabChange(tab.key)"
>
<SvgIcon :icon-class="tab.icon" size="18px" />
<span class="tab-label">{{ tab.label }}</span>
<span v-if="isTabDisabled(tab)" class="tab-disabled-hint">
{{ getDisabledReason(tab) }}
</span>
</div>
</div>
<button class="close-btn" @click="close">
<SvgIcon icon-class="close" size="20px" />
</button>
</div>
<!-- 内容区域 -->
<div class="panel-content">
<!-- 任务大纲 -->
<div v-show="activeTab === 'outline'" class="tab-content first-tab">
<PlanModification />
</div>
<!-- 智能体 -->
<div v-show="activeTab === 'agent'" class="tab-content">
<AgentAllocation />
</div>
<!-- 任务过程 -->
<div v-show="activeTab === 'process'" class="tab-content">
<PlanTask />
</div>
</div>
</div>
</Transition>
</Teleport>
</template>
<style scoped lang="scss">
// 面板主体
.settings-panel {
position: fixed;
bottom: 20px;
left: 50%;
margin-left: -40%;
width: 80%;
max-width: 1200px;
height: 70vh;
background: var(--color-bg-three);
border-radius: 20px;
box-shadow: 0 -4px 30px rgba(0, 0, 0.4);
z-index: 3000;
display: flex;
flex-direction: column;
overflow: hidden;
}
// 标题栏(包含标签栏)
.panel-header {
display: flex;
align-items: center;
justify-content: center;
padding: 10px 10px 0 30px;
flex-shrink: 0;
position: relative;
}
.tabs-container {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.close-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: 20px;
&:hover {
transform: rotate(360deg);
}
}
.tab-item {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 20px;
cursor: pointer;
color: var(--color-text-secondary);
border-bottom: 2px solid transparent;
transition: all 0.2s;
position: relative;
margin: 0 4px;
&:hover:not(.is-disabled) {
color: var(--color-text-primary);
}
&.is-active {
color: var(--color-primary);
background: var(--color-settings-panel-content-bg);
border-radius: 5px 5px 0 0;
}
&:hover:not(.is-disabled)::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: calc(100% - 8px);
height: calc(100% - 8px);
background: var(--color-bg-detail);
border-radius: 40px;
z-index: -1;
}
&.is-disabled {
cursor: not-allowed;
opacity: 0.5;
}
:deep(.svg-icon) {
color: var(--color-text-secondary);
}
&:hover:not(.is-disabled) :deep(.svg-icon) {
color: var(--color-text-primary);
}
&.is-active :deep(.svg-icon) {
color: var(--color-primary);
}
}
.tab-label {
font-size: 14px;
font-weight: 500;
}
.tab-disabled-hint {
position: absolute;
bottom: -30px;
left: 50%;
transform: translateX(-50%);
background: var(--color-bg-detail);
color: var(--color-text-secondary);
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.tab-item.is-disabled:hover .tab-disabled-hint {
opacity: 1;
}
// 内容区域
.panel-content {
flex: 1;
overflow: hidden;
position: relative;
background: var(--color-settings-panel-content-bg);
margin: 0 10px 10px;
border-radius: 5px;
}
.tab-content {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: auto;
}
// 空状态
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: var(--color-text-secondary);
gap: 16px;
p {
font-size: 14px;
}
}
// 动画效果
.slide-up-enter-active,
.slide-up-leave-active {
transition: all 0.5s ease;
}
.slide-up-enter-from,
.slide-up-leave-to {
opacity: 0;
transform: translateY(100%);
}
</style>