feat: restore dashboard workspace and analytics
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
"home": "首页",
|
||||
"controlCenter": "控制中心",
|
||||
"workspace": "工作台",
|
||||
"analytics": "分析页",
|
||||
"aiPlatform": "AI 平台",
|
||||
"aiAgent": "智能体",
|
||||
"workflowOrchestration": "流程编排",
|
||||
|
||||
@@ -194,6 +194,7 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||
'../views/ai-platform/workflow/editor/index.vue',
|
||||
'../views/ai-platform/workflow/index.vue',
|
||||
'../views/ai-platform/workflow-runs/index.vue',
|
||||
'../views/dashboard/analytics/index.vue',
|
||||
'../views/dashboard/workspace/index.vue',
|
||||
]);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export const LIGHT_MENU_NAMES = new Set([
|
||||
'ControlCenter',
|
||||
'Dashboard',
|
||||
'DashboardAnalytics',
|
||||
'DashboardWorkspace',
|
||||
'AIAgent',
|
||||
'AIKnowledgeDetail',
|
||||
|
||||
@@ -12,6 +12,15 @@ const routes: RouteRecordRaw[] = [
|
||||
path: '/dashboard',
|
||||
redirect: '/dashboard/workspace',
|
||||
children: [
|
||||
{
|
||||
component: () => import('#/views/dashboard/analytics/index.vue'),
|
||||
meta: {
|
||||
hideInMenu: true,
|
||||
title: 'menu-title.analytics',
|
||||
},
|
||||
name: 'DashboardAnalytics',
|
||||
path: '/dashboard/analytics',
|
||||
},
|
||||
{
|
||||
component: () => import('#/views/dashboard/workspace/index.vue'),
|
||||
meta: {
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
onMounted(() => {
|
||||
renderEcharts({
|
||||
grid: {
|
||||
bottom: 0,
|
||||
containLabel: true,
|
||||
left: '1%',
|
||||
right: '1%',
|
||||
top: '2%',
|
||||
},
|
||||
legend: {
|
||||
data: ['智能体会话', '工作流运行'],
|
||||
top: 0,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
areaStyle: {},
|
||||
data: [
|
||||
120, 280, 560, 1100, 1680, 2360, 3100, 2860, 3520, 4180, 4950,
|
||||
4620, 5280, 6100, 6800, 7200, 7600, 8300,
|
||||
],
|
||||
itemStyle: {
|
||||
color: '#2563eb',
|
||||
},
|
||||
name: '智能体会话',
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
},
|
||||
{
|
||||
areaStyle: {},
|
||||
data: [
|
||||
60, 140, 320, 680, 920, 1380, 1820, 1700, 2260, 2600, 3120, 3480,
|
||||
3860, 4260, 4700, 5120, 5600, 6100,
|
||||
],
|
||||
itemStyle: {
|
||||
color: '#16a34a',
|
||||
},
|
||||
name: '工作流运行',
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
color: '#2563eb',
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
trigger: 'axis',
|
||||
},
|
||||
xAxis: {
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
boundaryGap: false,
|
||||
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'solid',
|
||||
width: 1,
|
||||
},
|
||||
show: true,
|
||||
},
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
max: 9000,
|
||||
splitArea: {
|
||||
show: true,
|
||||
},
|
||||
splitNumber: 4,
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EchartsUI ref="chartRef" />
|
||||
</template>
|
||||
@@ -0,0 +1,69 @@
|
||||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
onMounted(() => {
|
||||
renderEcharts({
|
||||
legend: {
|
||||
bottom: 0,
|
||||
data: ['节点事件', 'LLM 调用'],
|
||||
},
|
||||
radar: {
|
||||
indicator: [
|
||||
{ name: '开始' },
|
||||
{ name: '节点完成' },
|
||||
{ name: '并行分支' },
|
||||
{ name: '子流程' },
|
||||
{ name: '等待输入' },
|
||||
{ name: '错误' },
|
||||
],
|
||||
radius: '60%',
|
||||
splitNumber: 8,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
areaStyle: {
|
||||
opacity: 0.9,
|
||||
shadowBlur: 0,
|
||||
shadowColor: 'rgba(0,0,0,.2)',
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 10,
|
||||
},
|
||||
data: [
|
||||
{
|
||||
itemStyle: {
|
||||
color: '#2563eb',
|
||||
},
|
||||
name: '节点事件',
|
||||
value: [92, 86, 68, 56, 42, 18],
|
||||
},
|
||||
{
|
||||
itemStyle: {
|
||||
color: '#16a34a',
|
||||
},
|
||||
name: 'LLM 调用',
|
||||
value: [78, 72, 40, 36, 24, 12],
|
||||
},
|
||||
],
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderWidth: 2,
|
||||
},
|
||||
symbolSize: 0,
|
||||
type: 'radar',
|
||||
},
|
||||
],
|
||||
tooltip: {},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EchartsUI ref="chartRef" />
|
||||
</template>
|
||||
@@ -0,0 +1,43 @@
|
||||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
onMounted(() => {
|
||||
renderEcharts({
|
||||
series: [
|
||||
{
|
||||
animationDelay() {
|
||||
return Math.random() * 400;
|
||||
},
|
||||
animationEasing: 'exponentialInOut',
|
||||
animationType: 'scale',
|
||||
center: ['50%', '50%'],
|
||||
color: ['#2563eb', '#16a34a', '#f97316', '#7c3aed'],
|
||||
data: [
|
||||
{ name: '自主执行', value: 520 },
|
||||
{ name: '对话流', value: 430 },
|
||||
{ name: '子流程', value: 310 },
|
||||
{ name: '并行协作', value: 390 },
|
||||
].sort((a, b) => a.value - b.value),
|
||||
name: '协作类型',
|
||||
radius: '80%',
|
||||
roseType: 'radius',
|
||||
type: 'pie',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EchartsUI ref="chartRef" />
|
||||
</template>
|
||||
@@ -0,0 +1,64 @@
|
||||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
onMounted(() => {
|
||||
renderEcharts({
|
||||
legend: {
|
||||
bottom: '2%',
|
||||
left: 'center',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
animationDelay() {
|
||||
return Math.random() * 100;
|
||||
},
|
||||
animationEasing: 'exponentialInOut',
|
||||
animationType: 'scale',
|
||||
avoidLabelOverlap: false,
|
||||
color: ['#2563eb', '#16a34a', '#f97316', '#7c3aed'],
|
||||
data: [
|
||||
{ name: '智能体聊天', value: 1048 },
|
||||
{ name: '工作流运行', value: 735 },
|
||||
{ name: '模型配置', value: 420 },
|
||||
{ name: '知识库检索', value: 360 },
|
||||
],
|
||||
emphasis: {
|
||||
label: {
|
||||
fontSize: '12',
|
||||
fontWeight: 'bold',
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
position: 'center',
|
||||
show: false,
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
name: '调用来源',
|
||||
radius: ['40%', '65%'],
|
||||
type: 'pie',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EchartsUI ref="chartRef" />
|
||||
</template>
|
||||
@@ -0,0 +1,58 @@
|
||||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
onMounted(() => {
|
||||
renderEcharts({
|
||||
grid: {
|
||||
bottom: 0,
|
||||
containLabel: true,
|
||||
left: '1%',
|
||||
right: '1%',
|
||||
top: '2%',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
barMaxWidth: 80,
|
||||
data: [
|
||||
1200, 1800, 2600, 3200, 4100, 5200, 6100, 6800, 7600, 8400, 9300,
|
||||
10_200,
|
||||
],
|
||||
itemStyle: {
|
||||
color: '#2563eb',
|
||||
},
|
||||
name: '运行次数',
|
||||
type: 'bar',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
color: '#2563eb',
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
trigger: 'axis',
|
||||
},
|
||||
xAxis: {
|
||||
data: Array.from({ length: 12 }).map((_item, index) => `${index + 1}月`),
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: {
|
||||
max: 12_000,
|
||||
splitNumber: 4,
|
||||
type: 'value',
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EchartsUI ref="chartRef" />
|
||||
</template>
|
||||
@@ -0,0 +1,93 @@
|
||||
<script lang="ts" setup>
|
||||
import type { AnalysisOverviewItem } from '@vben/common-ui';
|
||||
import type { TabOption } from '@vben/types';
|
||||
|
||||
import {
|
||||
AnalysisChartCard,
|
||||
AnalysisChartsTabs,
|
||||
AnalysisOverview,
|
||||
} from '@vben/common-ui';
|
||||
import { Activity, Bot, Cpu, GitBranch } from '@vben/icons';
|
||||
|
||||
import AnalyticsTrends from './analytics-trends.vue';
|
||||
import AnalyticsVisitsData from './analytics-visits-data.vue';
|
||||
import AnalyticsVisitsSales from './analytics-visits-sales.vue';
|
||||
import AnalyticsVisitsSource from './analytics-visits-source.vue';
|
||||
import AnalyticsVisits from './analytics-visits.vue';
|
||||
|
||||
defineOptions({ name: 'DashboardAnalytics' });
|
||||
|
||||
const overviewItems: AnalysisOverviewItem[] = [
|
||||
{
|
||||
icon: Bot,
|
||||
title: '智能体',
|
||||
totalTitle: '已发布智能体',
|
||||
totalValue: 18,
|
||||
value: 6,
|
||||
},
|
||||
{
|
||||
icon: GitBranch,
|
||||
title: '工作流',
|
||||
totalTitle: '启用工作流',
|
||||
totalValue: 12,
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
icon: Cpu,
|
||||
title: '模型调用',
|
||||
totalTitle: '近 30 天调用',
|
||||
totalValue: 86_400,
|
||||
value: 3200,
|
||||
},
|
||||
{
|
||||
icon: Activity,
|
||||
title: '运行事件',
|
||||
totalTitle: '已记录事件',
|
||||
totalValue: 128_000,
|
||||
value: 9800,
|
||||
},
|
||||
];
|
||||
|
||||
const chartTabs: TabOption[] = [
|
||||
{
|
||||
label: '协作趋势',
|
||||
value: 'trends',
|
||||
},
|
||||
{
|
||||
label: '月度运行',
|
||||
value: 'visits',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-3">
|
||||
<AnalysisOverview :items="overviewItems" />
|
||||
<AnalysisChartsTabs :tabs="chartTabs" class="mt-5">
|
||||
<template #trends>
|
||||
<AnalyticsTrends />
|
||||
</template>
|
||||
<template #visits>
|
||||
<AnalyticsVisits />
|
||||
</template>
|
||||
</AnalysisChartsTabs>
|
||||
|
||||
<div class="mt-5 w-full md:flex">
|
||||
<AnalysisChartCard
|
||||
class="mt-5 md:mr-4 md:mt-0 md:w-1/3"
|
||||
title="事件分布"
|
||||
>
|
||||
<AnalyticsVisitsData />
|
||||
</AnalysisChartCard>
|
||||
<AnalysisChartCard
|
||||
class="mt-5 md:mr-4 md:mt-0 md:w-1/3"
|
||||
title="调用来源"
|
||||
>
|
||||
<AnalyticsVisitsSource />
|
||||
</AnalysisChartCard>
|
||||
<AnalysisChartCard class="mt-5 md:mt-0 md:w-1/3" title="协作类型">
|
||||
<AnalyticsVisitsSales />
|
||||
</AnalysisChartCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,534 +1,266 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
WorkbenchProjectItem,
|
||||
WorkbenchQuickNavItem,
|
||||
WorkbenchTodoItem,
|
||||
WorkbenchTrendItem,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { Page } from '@vben/common-ui/es/page';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
import {
|
||||
Bell,
|
||||
WorkbenchHeader,
|
||||
WorkbenchProject,
|
||||
WorkbenchQuickNav,
|
||||
WorkbenchTodo,
|
||||
WorkbenchTrends,
|
||||
} from '@vben/common-ui';
|
||||
import {
|
||||
Bot,
|
||||
Brain,
|
||||
GitFork,
|
||||
Database,
|
||||
GitBranch,
|
||||
History,
|
||||
Inbox,
|
||||
KeyRound,
|
||||
LayoutDashboard,
|
||||
Megaphone,
|
||||
MessageSquare,
|
||||
Menu,
|
||||
Settings,
|
||||
Shield,
|
||||
ShieldCheck,
|
||||
Users,
|
||||
Workflow,
|
||||
} from '@vben/icons';
|
||||
|
||||
import { ElButton, ElCard, ElTag } from 'element-plus';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
import { openWindow } from '@vben/utils';
|
||||
|
||||
defineOptions({ name: 'DashboardWorkspace' });
|
||||
|
||||
type Accent = 'blue' | 'cyan' | 'green' | 'orange' | 'violet';
|
||||
|
||||
interface MetricItem {
|
||||
accent: Accent;
|
||||
label: string;
|
||||
title: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface NavItem {
|
||||
accent: Accent;
|
||||
description: string;
|
||||
icon: any;
|
||||
path: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const displayName = computed(
|
||||
() => userStore.userInfo?.realName || userStore.userInfo?.username || '管理员',
|
||||
);
|
||||
|
||||
const metrics: MetricItem[] = [
|
||||
{
|
||||
accent: 'blue',
|
||||
label: '协作入口',
|
||||
title: '智能体',
|
||||
value: 'Agent',
|
||||
},
|
||||
{
|
||||
accent: 'cyan',
|
||||
label: '运行闭环',
|
||||
title: '流程编排',
|
||||
value: 'Flow',
|
||||
},
|
||||
{
|
||||
accent: 'green',
|
||||
label: '基础治理',
|
||||
title: '权限与角色',
|
||||
value: 'Auth',
|
||||
},
|
||||
{
|
||||
accent: 'orange',
|
||||
label: '运营触达',
|
||||
title: '公告消息',
|
||||
value: 'Notice',
|
||||
},
|
||||
const headerStats = [
|
||||
{ label: '智能体', value: '18' },
|
||||
{ label: '工作流', value: '12' },
|
||||
{ label: '运行事件', value: '12.8w' },
|
||||
];
|
||||
|
||||
const aiNavItems: NavItem[] = [
|
||||
const projectItems: WorkbenchProjectItem[] = [
|
||||
{
|
||||
accent: 'blue',
|
||||
description: '创建、发布和调试业务智能体',
|
||||
color: '#2563eb',
|
||||
content: '管理已发布智能体,维护角色、提示词、工具能力和对话入口。',
|
||||
date: 'AI 平台',
|
||||
group: '智能体',
|
||||
icon: Bot,
|
||||
path: '/ai-platform/agent',
|
||||
title: '智能体管理',
|
||||
url: '/ai-platform/agent',
|
||||
},
|
||||
{
|
||||
accent: 'cyan',
|
||||
description: '编排多智能体、LLM 和工具节点',
|
||||
icon: Workflow,
|
||||
path: '/ai-platform/workflow',
|
||||
color: '#16a34a',
|
||||
content: '编排自主、对话流、子流程和并行节点,构建协作式任务链路。',
|
||||
date: 'AI 平台',
|
||||
group: '流程',
|
||||
icon: GitBranch,
|
||||
title: '流程编排',
|
||||
url: '/ai-platform/workflow',
|
||||
},
|
||||
{
|
||||
accent: 'violet',
|
||||
description: '查看运行事件、节点状态和失败原因',
|
||||
icon: GitFork,
|
||||
path: '/ai-platform/workflow-runs',
|
||||
color: '#f97316',
|
||||
content: '查看节点状态、LLM 调用、等待输入、错误和最终输出。',
|
||||
date: '可观测',
|
||||
group: '运行',
|
||||
icon: History,
|
||||
title: '执行历史',
|
||||
url: '/ai-platform/workflow-runs',
|
||||
},
|
||||
{
|
||||
accent: 'green',
|
||||
description: '维护可用模型和默认 Chat 模型',
|
||||
icon: Brain,
|
||||
path: '/ai-platform/model',
|
||||
title: '模型配置',
|
||||
},
|
||||
];
|
||||
|
||||
const adminNavItems: NavItem[] = [
|
||||
{
|
||||
accent: 'green',
|
||||
description: '维护后台账号、状态和基础资料',
|
||||
icon: Users,
|
||||
path: '/system/user',
|
||||
title: '用户管理',
|
||||
},
|
||||
{
|
||||
accent: 'violet',
|
||||
description: '管理角色权限和资源范围',
|
||||
icon: Shield,
|
||||
path: '/system/role',
|
||||
title: '角色权限',
|
||||
},
|
||||
{
|
||||
accent: 'blue',
|
||||
description: '维护菜单、路由和页面入口',
|
||||
icon: KeyRound,
|
||||
path: '/system/menu',
|
||||
title: '菜单管理',
|
||||
},
|
||||
{
|
||||
accent: 'orange',
|
||||
description: '发布公告并查看阅读反馈',
|
||||
icon: Megaphone,
|
||||
path: '/message/announcement',
|
||||
title: '公告管理',
|
||||
},
|
||||
];
|
||||
|
||||
const runtimeItems = [
|
||||
{
|
||||
description: '聊天、流程运行和系统通知会集中进入消息中心。',
|
||||
icon: MessageSquare,
|
||||
path: '/message/list',
|
||||
title: '消息中心',
|
||||
},
|
||||
{
|
||||
description: '检查最新公告和未读提醒。',
|
||||
icon: Bell,
|
||||
path: '/message/announcement-list',
|
||||
title: '公告列表',
|
||||
},
|
||||
{
|
||||
description: '调整系统配置、模型参数和界面偏好。',
|
||||
color: '#7c3aed',
|
||||
content: '维护默认 chat 模型和供应商,保障智能体运行时可解析模型。',
|
||||
date: '配置',
|
||||
group: '模型',
|
||||
icon: Settings,
|
||||
path: '/system/config',
|
||||
title: '系统配置',
|
||||
title: '模型配置',
|
||||
url: '/ai-platform/model',
|
||||
},
|
||||
{
|
||||
color: '#0f766e',
|
||||
content: '沉淀业务知识、提示词资料和协作过程中的可复用上下文。',
|
||||
date: '知识',
|
||||
group: '资料',
|
||||
icon: Database,
|
||||
title: '知识库',
|
||||
url: '/ai-platform/knowledge-base',
|
||||
},
|
||||
{
|
||||
color: '#475569',
|
||||
content: '查看智能体、流程、模型调用和基础管理的整体运行态势。',
|
||||
date: '看板',
|
||||
group: '分析',
|
||||
icon: LayoutDashboard,
|
||||
title: '运行分析',
|
||||
url: '/dashboard/analytics',
|
||||
},
|
||||
];
|
||||
|
||||
function go(path: string) {
|
||||
router.push(path);
|
||||
const quickNavItems: WorkbenchQuickNavItem[] = [
|
||||
{
|
||||
color: '#2563eb',
|
||||
icon: Users,
|
||||
title: '用户管理',
|
||||
url: '/system/user',
|
||||
},
|
||||
{
|
||||
color: '#7c3aed',
|
||||
icon: ShieldCheck,
|
||||
title: '角色权限',
|
||||
url: '/system/role',
|
||||
},
|
||||
{
|
||||
color: '#0f766e',
|
||||
icon: KeyRound,
|
||||
title: 'API 权限',
|
||||
url: '/system/new-permission',
|
||||
},
|
||||
{
|
||||
color: '#f97316',
|
||||
icon: Menu,
|
||||
title: '菜单管理',
|
||||
url: '/system/menu',
|
||||
},
|
||||
{
|
||||
color: '#16a34a',
|
||||
icon: Megaphone,
|
||||
title: '公告管理',
|
||||
url: '/message/announcement',
|
||||
},
|
||||
{
|
||||
color: '#475569',
|
||||
icon: Inbox,
|
||||
title: '公告列表',
|
||||
url: '/message/announcement-list',
|
||||
},
|
||||
];
|
||||
|
||||
const todoItems = ref<WorkbenchTodoItem[]>([
|
||||
{
|
||||
completed: false,
|
||||
content: '用真实业务需求触发业务需求分析师,确认能输出可测试需求、边界和下一步。',
|
||||
date: '今日',
|
||||
title: '验证业务需求分析师',
|
||||
},
|
||||
{
|
||||
completed: false,
|
||||
content: '检查 start、node_start、llm_chunk、error、complete 等事件在前端是否可读。',
|
||||
date: '今日',
|
||||
title: '检查 SSE 运行事件',
|
||||
},
|
||||
{
|
||||
completed: true,
|
||||
content: '发布智能体允许 model_id 为空,运行时回退到启用的默认 chat 模型。',
|
||||
date: '已完成',
|
||||
title: '模型兜底策略',
|
||||
},
|
||||
{
|
||||
completed: false,
|
||||
content: '继续观察 AI 平台、智能体聊天和运行记录页面的首屏加载与预取效果。',
|
||||
date: '本周',
|
||||
title: '页面切换性能',
|
||||
},
|
||||
{
|
||||
completed: true,
|
||||
content: '保留 systemd 非 Docker 部署路径,Docker 入口仅作为可选方式保留。',
|
||||
date: '已完成',
|
||||
title: '部署路径收敛',
|
||||
},
|
||||
]);
|
||||
|
||||
const trendItems: WorkbenchTrendItem[] = [
|
||||
{
|
||||
avatar: 'svg:avatar-1',
|
||||
content: '智能体运行时已增加默认 chat 模型解析,避免 <a>model_id 为空</a> 直接失败。',
|
||||
date: '刚刚',
|
||||
title: '运行时',
|
||||
},
|
||||
{
|
||||
avatar: 'svg:avatar-2',
|
||||
content: '工作流运行记录聚焦节点状态、错误原因、子流程和并行分支结果。',
|
||||
date: '1 小时前',
|
||||
title: '可观测性',
|
||||
},
|
||||
{
|
||||
avatar: 'svg:avatar-3',
|
||||
content: '用户、角色、权限、菜单和公告作为轻量后台的基础模块保留。',
|
||||
date: '今天',
|
||||
title: '基础后台',
|
||||
},
|
||||
{
|
||||
avatar: 'svg:avatar-4',
|
||||
content: 'AI 平台保留智能体、流程编排、模型配置、知识库和执行历史。',
|
||||
date: '今天',
|
||||
title: 'AI 平台',
|
||||
},
|
||||
{
|
||||
avatar: 'svg:avatar-1',
|
||||
content: '线上部署路径固定为 <a>/ai-agent-admin</a>,后端由 systemd 托管。',
|
||||
date: '本周',
|
||||
title: '部署',
|
||||
},
|
||||
];
|
||||
|
||||
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
|
||||
if (nav.url?.startsWith('http')) {
|
||||
openWindow(nav.url);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nav.url?.startsWith('/')) {
|
||||
router.push(nav.url).catch((error) => {
|
||||
console.error('Navigation failed:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<div class="workspace-page">
|
||||
<section class="workspace-hero">
|
||||
<div class="min-w-0">
|
||||
<div class="hero-kicker">AI Agent Admin</div>
|
||||
<h1>你好,{{ displayName }}</h1>
|
||||
<p>
|
||||
当前后台保留基础治理和 AI 协作主链路,优先处理智能体、流程编排、运行观测和权限管理。
|
||||
</p>
|
||||
</div>
|
||||
<div class="hero-actions">
|
||||
<ElButton type="primary" @click="go('/ai-platform/agent')">
|
||||
<Bot class="mr-1 size-4" />
|
||||
智能体
|
||||
</ElButton>
|
||||
<ElButton @click="go('/ai-platform/workflow')">
|
||||
<Workflow class="mr-1 size-4" />
|
||||
流程编排
|
||||
</ElButton>
|
||||
</div>
|
||||
</section>
|
||||
<div class="p-3">
|
||||
<WorkbenchHeader
|
||||
:avatar="userStore.userInfo?.avatar || preferences.app.defaultAvatar"
|
||||
:stats="headerStats"
|
||||
>
|
||||
<template #title>
|
||||
早安,{{ userStore.userInfo?.realName || userStore.userInfo?.username || '管理员' }},这里是 AI Agent Admin 工作台
|
||||
</template>
|
||||
<template #description>
|
||||
保留 zq-ai-admin 成熟后台体验,聚焦智能体协作、流程编排、模型配置、知识库和基础权限治理。
|
||||
</template>
|
||||
</WorkbenchHeader>
|
||||
|
||||
<section class="metric-grid">
|
||||
<ElCard
|
||||
v-for="item in metrics"
|
||||
:key="item.title"
|
||||
class="metric-card"
|
||||
shadow="never"
|
||||
>
|
||||
<div class="metric-top">
|
||||
<span :class="['accent-dot', `accent-${item.accent}`]"></span>
|
||||
<span>{{ item.label }}</span>
|
||||
</div>
|
||||
<div class="metric-value">{{ item.value }}</div>
|
||||
<div class="metric-title">{{ item.title }}</div>
|
||||
</ElCard>
|
||||
</section>
|
||||
|
||||
<section class="content-grid">
|
||||
<ElCard class="panel-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="panel-header">
|
||||
<span>AI 协作</span>
|
||||
<ElTag effect="plain" size="small">核心链路</ElTag>
|
||||
</div>
|
||||
</template>
|
||||
<div class="nav-grid">
|
||||
<button
|
||||
v-for="item in aiNavItems"
|
||||
:key="item.path"
|
||||
class="nav-tile"
|
||||
type="button"
|
||||
@click="go(item.path)"
|
||||
>
|
||||
<span :class="['tile-icon', `tile-${item.accent}`]">
|
||||
<component :is="item.icon" class="size-5" />
|
||||
</span>
|
||||
<span class="tile-copy">
|
||||
<strong>{{ item.title }}</strong>
|
||||
<span>{{ item.description }}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</ElCard>
|
||||
|
||||
<ElCard class="panel-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="panel-header">
|
||||
<span>基础后台</span>
|
||||
<ElTag effect="plain" size="small" type="success">已保留</ElTag>
|
||||
</div>
|
||||
</template>
|
||||
<div class="nav-grid">
|
||||
<button
|
||||
v-for="item in adminNavItems"
|
||||
:key="item.path"
|
||||
class="nav-tile"
|
||||
type="button"
|
||||
@click="go(item.path)"
|
||||
>
|
||||
<span :class="['tile-icon', `tile-${item.accent}`]">
|
||||
<component :is="item.icon" class="size-5" />
|
||||
</span>
|
||||
<span class="tile-copy">
|
||||
<strong>{{ item.title }}</strong>
|
||||
<span>{{ item.description }}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</ElCard>
|
||||
</section>
|
||||
|
||||
<ElCard class="panel-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="panel-header">
|
||||
<span>运维与消息</span>
|
||||
<ElTag effect="plain" size="small" type="warning">轻量入口</ElTag>
|
||||
</div>
|
||||
</template>
|
||||
<div class="runtime-list">
|
||||
<button
|
||||
v-for="item in runtimeItems"
|
||||
:key="item.path"
|
||||
class="runtime-row"
|
||||
type="button"
|
||||
@click="go(item.path)"
|
||||
>
|
||||
<span class="runtime-icon">
|
||||
<component :is="item.icon" class="size-4" />
|
||||
</span>
|
||||
<span class="runtime-copy">
|
||||
<strong>{{ item.title }}</strong>
|
||||
<span>{{ item.description }}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</ElCard>
|
||||
<div class="mt-5 flex flex-col lg:flex-row">
|
||||
<div class="mr-4 w-full lg:w-3/5">
|
||||
<WorkbenchProject
|
||||
:items="projectItems"
|
||||
title="核心入口"
|
||||
@click="navTo"
|
||||
/>
|
||||
<WorkbenchTrends
|
||||
:items="trendItems"
|
||||
class="mt-5"
|
||||
title="运行动态"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full lg:w-2/5">
|
||||
<WorkbenchQuickNav
|
||||
:items="quickNavItems"
|
||||
class="mt-5 lg:mt-0"
|
||||
title="基础管理"
|
||||
@click="navTo"
|
||||
/>
|
||||
<WorkbenchTodo
|
||||
:items="todoItems"
|
||||
class="mt-5"
|
||||
title="协作闭环待办"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.workspace-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.workspace-hero {
|
||||
align-items: center;
|
||||
background:
|
||||
linear-gradient(135deg, rgb(36 99 235 / 8%), transparent 42%),
|
||||
var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: space-between;
|
||||
padding: 22px 24px;
|
||||
}
|
||||
|
||||
.hero-kicker {
|
||||
color: var(--el-color-primary);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.workspace-hero h1 {
|
||||
color: var(--el-text-color-primary);
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.workspace-hero p {
|
||||
color: var(--el-text-color-regular);
|
||||
line-height: 1.7;
|
||||
margin: 8px 0 0;
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.metric-grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.metric-card,
|
||||
.panel-card {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.metric-card :deep(.el-card__body) {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.metric-top {
|
||||
align-items: center;
|
||||
color: var(--el-text-color-secondary);
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.accent-dot {
|
||||
border-radius: 999px;
|
||||
display: inline-flex;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.accent-blue {
|
||||
background: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.accent-cyan {
|
||||
background: var(--el-color-info);
|
||||
}
|
||||
|
||||
.accent-green {
|
||||
background: var(--el-color-success);
|
||||
}
|
||||
|
||||
.accent-orange {
|
||||
background: var(--el-color-warning);
|
||||
}
|
||||
|
||||
.accent-violet {
|
||||
background: var(--el-color-danger);
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
color: var(--el-text-color-primary);
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.metric-title {
|
||||
color: var(--el-text-color-regular);
|
||||
font-size: 13px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.content-grid {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
align-items: center;
|
||||
color: var(--el-text-color-primary);
|
||||
display: flex;
|
||||
font-weight: 700;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.nav-grid {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.nav-tile,
|
||||
.runtime-row {
|
||||
align-items: center;
|
||||
background: var(--el-bg-color-page);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
padding: 14px;
|
||||
text-align: left;
|
||||
transition:
|
||||
border-color 180ms ease,
|
||||
box-shadow 180ms ease,
|
||||
transform 180ms ease;
|
||||
}
|
||||
|
||||
.nav-tile:hover,
|
||||
.runtime-row:hover {
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
box-shadow: 0 8px 24px rgb(15 23 42 / 8%);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.tile-icon,
|
||||
.runtime-icon {
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
height: 38px;
|
||||
justify-content: center;
|
||||
width: 38px;
|
||||
}
|
||||
|
||||
.tile-blue {
|
||||
background: var(--el-color-primary-light-9);
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.tile-cyan {
|
||||
background: var(--el-color-info-light-9);
|
||||
color: var(--el-color-info);
|
||||
}
|
||||
|
||||
.tile-green {
|
||||
background: var(--el-color-success-light-9);
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
|
||||
.tile-orange {
|
||||
background: var(--el-color-warning-light-9);
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
|
||||
.tile-violet {
|
||||
background: var(--el-color-danger-light-9);
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
|
||||
.tile-copy,
|
||||
.runtime-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tile-copy strong,
|
||||
.runtime-copy strong {
|
||||
color: var(--el-text-color-primary);
|
||||
font-size: 14px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.tile-copy span,
|
||||
.runtime-copy span {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.runtime-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.runtime-icon {
|
||||
background: var(--el-fill-color-light);
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.metric-grid,
|
||||
.runtime-list {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.workspace-hero,
|
||||
.hero-actions {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content-grid,
|
||||
.metric-grid,
|
||||
.nav-grid,
|
||||
.runtime-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user