feat: replace dashboard renderer home
This commit is contained in:
@@ -16,14 +16,13 @@ import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
import { refreshTokenApi } from './core';
|
||||
import { refreshTokenApi } from './core/auth';
|
||||
|
||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
|
||||
async function showMessage(type: 'error' | 'warning', message: string) {
|
||||
const { ElMessage } = await import(
|
||||
'element-plus/es/components/message/index'
|
||||
);
|
||||
const { ElMessage } =
|
||||
await import('element-plus/es/components/message/index');
|
||||
ElMessage[type](message);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,12 +32,10 @@ const baseModules = import.meta.glob([
|
||||
const businessModules = import.meta.glob([
|
||||
'./langs/zh-CN/ai-platform.json',
|
||||
'./langs/zh-CN/announcement.json',
|
||||
'./langs/zh-CN/dashboard-design.json',
|
||||
'./langs/zh-CN/menu.json',
|
||||
'./langs/zh-CN/message.json',
|
||||
'./langs/zh-CN/permission.json',
|
||||
'./langs/zh-CN/role.json',
|
||||
'./langs/zh-CN/system-config.json',
|
||||
'./langs/zh-CN/system.json',
|
||||
'./langs/zh-CN/user.json',
|
||||
]);
|
||||
|
||||
@@ -0,0 +1,859 @@
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
AgentListItem,
|
||||
KnowledgeBaseListItem,
|
||||
WorkflowListItem,
|
||||
WorkflowRunListItem,
|
||||
} from '#/api/ai-platform/ai-platform';
|
||||
import type { Message } from '#/api/core/message';
|
||||
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import {
|
||||
Activity,
|
||||
Bell,
|
||||
BookOpen,
|
||||
Bot,
|
||||
Cpu,
|
||||
KeyRound,
|
||||
Megaphone,
|
||||
Menu,
|
||||
Rocket,
|
||||
Settings,
|
||||
ShieldCheck,
|
||||
Users,
|
||||
Workflow,
|
||||
} from '@vben/icons';
|
||||
|
||||
import {
|
||||
ElButton,
|
||||
ElEmpty,
|
||||
ElProgress,
|
||||
ElSkeleton,
|
||||
ElSkeletonItem,
|
||||
ElTag,
|
||||
} from 'element-plus';
|
||||
|
||||
import {
|
||||
getAgentListApi,
|
||||
getAllWorkflowRunsApi,
|
||||
getKnowledgeBaseListApi,
|
||||
getProviderListApi,
|
||||
getWorkflowListApi,
|
||||
} from '#/api/ai-platform/ai-platform';
|
||||
import { getAnnouncementListApi } from '#/api/core/announcement';
|
||||
import { getMessageListApi, getUnreadCountApi } from '#/api/core/message';
|
||||
import {
|
||||
getServerHealthOverviewApi,
|
||||
getServerHealthRealtimeApi,
|
||||
} from '#/api/core/server-health';
|
||||
|
||||
defineOptions({ name: 'AiAgentHome' });
|
||||
|
||||
interface OverviewMetric {
|
||||
accent: string;
|
||||
hint: string;
|
||||
label: string;
|
||||
route: string;
|
||||
value: number | string;
|
||||
}
|
||||
|
||||
interface QuickLink {
|
||||
desc: string;
|
||||
icon: any;
|
||||
label: string;
|
||||
route: string;
|
||||
tone: string;
|
||||
}
|
||||
|
||||
interface AnnouncementLite {
|
||||
created_at?: string;
|
||||
id: string;
|
||||
priority?: number;
|
||||
publisher_name?: string;
|
||||
summary?: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const loading = ref(true);
|
||||
const agentList = ref<AgentListItem[]>([]);
|
||||
const workflowList = ref<WorkflowListItem[]>([]);
|
||||
const knowledgeList = ref<KnowledgeBaseListItem[]>([]);
|
||||
const runList = ref<WorkflowRunListItem[]>([]);
|
||||
const announcements = ref<AnnouncementLite[]>([]);
|
||||
const messages = ref<Message[]>([]);
|
||||
const unreadTotal = ref(0);
|
||||
const providerTotal = ref(0);
|
||||
const cpuPercent = ref(0);
|
||||
const memoryPercent = ref(0);
|
||||
const uptimeText = ref('-');
|
||||
|
||||
const aiLinks: QuickLink[] = [
|
||||
{
|
||||
desc: '编排可复用 Agent,沉淀角色、工具和知识库能力',
|
||||
icon: Bot,
|
||||
label: '智能体',
|
||||
route: '/ai-platform/agent',
|
||||
tone: 'blue',
|
||||
},
|
||||
{
|
||||
desc: '用节点流程组织多智能体协作、LLM 调用和人工确认',
|
||||
icon: Workflow,
|
||||
label: '流程编排',
|
||||
route: '/ai-platform/workflow',
|
||||
tone: 'green',
|
||||
},
|
||||
{
|
||||
desc: '管理向量知识库、文档分段、检索日志和命中质量',
|
||||
icon: BookOpen,
|
||||
label: '知识库',
|
||||
route: '/ai-platform/knowledge-base',
|
||||
tone: 'amber',
|
||||
},
|
||||
{
|
||||
desc: '维护供应商、模型、Embedding 与 Rerank 能力',
|
||||
icon: Settings,
|
||||
label: '模型配置',
|
||||
route: '/ai-platform/model',
|
||||
tone: 'violet',
|
||||
},
|
||||
];
|
||||
|
||||
const adminLinks: QuickLink[] = [
|
||||
{
|
||||
desc: '账号、组织身份和后台访问入口',
|
||||
icon: Users,
|
||||
label: '用户管理',
|
||||
route: '/system/user',
|
||||
tone: 'blue',
|
||||
},
|
||||
{
|
||||
desc: '角色授权和数据范围控制',
|
||||
icon: ShieldCheck,
|
||||
label: '角色管理',
|
||||
route: '/system/role',
|
||||
tone: 'green',
|
||||
},
|
||||
{
|
||||
desc: '菜单、按钮和接口权限维护',
|
||||
icon: KeyRound,
|
||||
label: '权限管理',
|
||||
route: '/system/new-permission',
|
||||
tone: 'amber',
|
||||
},
|
||||
{
|
||||
desc: '后台菜单结构和动态路由配置',
|
||||
icon: Menu,
|
||||
label: '菜单管理',
|
||||
route: '/system/menu',
|
||||
tone: 'violet',
|
||||
},
|
||||
];
|
||||
|
||||
const metrics = computed<OverviewMetric[]>(() => [
|
||||
{
|
||||
accent: 'blue',
|
||||
hint: `${agentList.value.filter((item) => item.status === 'published').length} 个已发布`,
|
||||
label: '智能体',
|
||||
route: '/ai-platform/agent',
|
||||
value: agentList.value.length,
|
||||
},
|
||||
{
|
||||
accent: 'green',
|
||||
hint: `${workflowList.value.filter((item) => item.status === 'published').length} 个已发布`,
|
||||
label: '工作流',
|
||||
route: '/ai-platform/workflow',
|
||||
value: workflowList.value.length,
|
||||
},
|
||||
{
|
||||
accent: 'amber',
|
||||
hint: `${providerTotal.value} 个模型供应商`,
|
||||
label: '知识库',
|
||||
route: '/ai-platform/knowledge-base',
|
||||
value: knowledgeList.value.length,
|
||||
},
|
||||
{
|
||||
accent: 'violet',
|
||||
hint: `${unreadTotal.value} 条未读消息`,
|
||||
label: '执行记录',
|
||||
route: '/ai-platform/workflow-runs',
|
||||
value: runList.value.length,
|
||||
},
|
||||
]);
|
||||
|
||||
const recentRuns = computed(() => runList.value.slice(0, 5));
|
||||
const recentAnnouncements = computed(() => announcements.value.slice(0, 5));
|
||||
const recentMessages = computed(() => messages.value.slice(0, 5));
|
||||
|
||||
function go(route: string) {
|
||||
router.push(route);
|
||||
}
|
||||
|
||||
function formatTime(value?: string) {
|
||||
if (!value) return '-';
|
||||
return value.replace('T', ' ').slice(0, 16);
|
||||
}
|
||||
|
||||
function runStatusType(status: string) {
|
||||
if (status === 'completed') return 'success';
|
||||
if (status === 'failed' || status === 'stopped') return 'danger';
|
||||
if (status === 'running' || status === 'waiting') return 'warning';
|
||||
return 'info';
|
||||
}
|
||||
|
||||
function runStatusLabel(status: string) {
|
||||
const labels: Record<string, string> = {
|
||||
completed: '已完成',
|
||||
failed: '失败',
|
||||
pending: '等待中',
|
||||
running: '运行中',
|
||||
stopped: '已停止',
|
||||
waiting: '等待输入',
|
||||
};
|
||||
return labels[status] || status;
|
||||
}
|
||||
|
||||
function announcementType(priority?: number) {
|
||||
if (priority === 2) return 'danger';
|
||||
if (priority === 1) return 'warning';
|
||||
return 'info';
|
||||
}
|
||||
|
||||
async function loadHomeData() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const results = await Promise.allSettled([
|
||||
getAgentListApi({ page: 1, pageSize: 8 }),
|
||||
getWorkflowListApi({ page: 1, pageSize: 8 }),
|
||||
getKnowledgeBaseListApi({ page: 1, pageSize: 8 }),
|
||||
getAllWorkflowRunsApi({ page: 1, pageSize: 8 }),
|
||||
getAnnouncementListApi({ page: 1, pageSize: 6, status: 'published' }),
|
||||
getMessageListApi({ page: 1, pageSize: 6, status: 'unread' }),
|
||||
getUnreadCountApi(),
|
||||
getProviderListApi({ page: 1, pageSize: 1 }),
|
||||
getServerHealthOverviewApi(),
|
||||
getServerHealthRealtimeApi(),
|
||||
] as const);
|
||||
|
||||
const [
|
||||
agentRes,
|
||||
workflowRes,
|
||||
knowledgeRes,
|
||||
runRes,
|
||||
announcementRes,
|
||||
messageRes,
|
||||
unreadRes,
|
||||
providerRes,
|
||||
overviewRes,
|
||||
realtimeRes,
|
||||
] = results;
|
||||
|
||||
if (agentRes.status === 'fulfilled') {
|
||||
agentList.value = agentRes.value.items || [];
|
||||
}
|
||||
if (workflowRes.status === 'fulfilled') {
|
||||
workflowList.value = workflowRes.value.items || [];
|
||||
}
|
||||
if (knowledgeRes.status === 'fulfilled') {
|
||||
knowledgeList.value = knowledgeRes.value.items || [];
|
||||
}
|
||||
if (runRes.status === 'fulfilled') {
|
||||
runList.value = runRes.value.items || [];
|
||||
}
|
||||
if (announcementRes.status === 'fulfilled') {
|
||||
announcements.value = announcementRes.value.items || [];
|
||||
}
|
||||
if (messageRes.status === 'fulfilled') {
|
||||
messages.value = messageRes.value.items || [];
|
||||
}
|
||||
if (unreadRes.status === 'fulfilled') {
|
||||
unreadTotal.value = unreadRes.value.total || 0;
|
||||
}
|
||||
if (providerRes.status === 'fulfilled') {
|
||||
providerTotal.value = providerRes.value.total || 0;
|
||||
}
|
||||
if (overviewRes.status === 'fulfilled') {
|
||||
uptimeText.value = overviewRes.value.boot_time?.uptime_formatted || '-';
|
||||
cpuPercent.value = overviewRes.value.cpu_info?.cpu_percent || 0;
|
||||
memoryPercent.value =
|
||||
overviewRes.value.memory_info?.virtual?.percent || 0;
|
||||
}
|
||||
if (realtimeRes.status === 'fulfilled') {
|
||||
cpuPercent.value = realtimeRes.value.cpu_percent || cpuPercent.value;
|
||||
memoryPercent.value =
|
||||
realtimeRes.value.memory_percent || memoryPercent.value;
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadHomeData);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ai-agent-home">
|
||||
<section class="home-hero">
|
||||
<div class="hero-copy">
|
||||
<div class="eyebrow">
|
||||
<Activity class="size-4" />
|
||||
AI Agent Admin
|
||||
</div>
|
||||
<h1>智能体协作控制中心</h1>
|
||||
<p>
|
||||
聚焦智能体、流程编排、LLM、知识库和基础权限治理,保留 zq_ai_admin
|
||||
的后台操作密度,移除在线开发链路的构建负担。
|
||||
</p>
|
||||
<div class="hero-actions">
|
||||
<ElButton
|
||||
type="primary"
|
||||
:icon="Rocket"
|
||||
@click="go('/ai-platform/agent')"
|
||||
>
|
||||
进入智能体
|
||||
</ElButton>
|
||||
<ElButton :icon="Workflow" @click="go('/ai-platform/workflow')">
|
||||
编排工作流
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ops-panel">
|
||||
<div class="panel-title">
|
||||
<Cpu class="size-4" />
|
||||
运行状态
|
||||
</div>
|
||||
<div class="ops-row">
|
||||
<span>CPU</span>
|
||||
<ElProgress :percentage="Math.round(cpuPercent)" :stroke-width="8" />
|
||||
</div>
|
||||
<div class="ops-row">
|
||||
<span>内存</span>
|
||||
<ElProgress
|
||||
:percentage="Math.round(memoryPercent)"
|
||||
:stroke-width="8"
|
||||
color="#16a34a"
|
||||
/>
|
||||
</div>
|
||||
<div class="ops-foot">
|
||||
<span>运行时长</span>
|
||||
<strong>{{ uptimeText }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ElSkeleton v-if="loading" animated :rows="8">
|
||||
<template #template>
|
||||
<div class="skeleton-grid">
|
||||
<ElSkeletonItem v-for="item in 8" :key="item" variant="rect" />
|
||||
</div>
|
||||
</template>
|
||||
</ElSkeleton>
|
||||
|
||||
<template v-else>
|
||||
<section class="metric-grid">
|
||||
<button
|
||||
v-for="metric in metrics"
|
||||
:key="metric.label"
|
||||
class="metric-card"
|
||||
:class="`tone-${metric.accent}`"
|
||||
type="button"
|
||||
@click="go(metric.route)"
|
||||
>
|
||||
<span>{{ metric.label }}</span>
|
||||
<strong>{{ metric.value }}</strong>
|
||||
<small>{{ metric.hint }}</small>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section class="content-grid">
|
||||
<div class="section-block span-7">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2>AI 协作入口</h2>
|
||||
<p>围绕 Agent 多角色协作和工作流执行的核心能力。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="link-grid">
|
||||
<button
|
||||
v-for="link in aiLinks"
|
||||
:key="link.route"
|
||||
class="link-item"
|
||||
:class="`tone-${link.tone}`"
|
||||
type="button"
|
||||
@click="go(link.route)"
|
||||
>
|
||||
<component :is="link.icon" class="size-5" />
|
||||
<span>{{ link.label }}</span>
|
||||
<small>{{ link.desc }}</small>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-block span-5">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2>最近执行</h2>
|
||||
<p>快速定位失败和等待输入的流程。</p>
|
||||
</div>
|
||||
<ElButton
|
||||
link
|
||||
type="primary"
|
||||
@click="go('/ai-platform/workflow-runs')"
|
||||
>
|
||||
查看全部
|
||||
</ElButton>
|
||||
</div>
|
||||
<div v-if="recentRuns.length > 0" class="run-list">
|
||||
<button
|
||||
v-for="run in recentRuns"
|
||||
:key="run.id"
|
||||
class="run-item"
|
||||
type="button"
|
||||
@click="go('/ai-platform/workflow-runs')"
|
||||
>
|
||||
<span>{{ run.workflow_name || run.id }}</span>
|
||||
<ElTag :type="runStatusType(run.status)" size="small">
|
||||
{{ runStatusLabel(run.status) }}
|
||||
</ElTag>
|
||||
<small>{{ formatTime(run.started_at) }}</small>
|
||||
</button>
|
||||
</div>
|
||||
<ElEmpty v-else description="暂无执行记录" />
|
||||
</div>
|
||||
|
||||
<div class="section-block span-5">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2>基础管理</h2>
|
||||
<p>保留后台系统稳定运行所需的基础治理模块。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-list">
|
||||
<button
|
||||
v-for="link in adminLinks"
|
||||
:key="link.route"
|
||||
class="admin-item"
|
||||
type="button"
|
||||
@click="go(link.route)"
|
||||
>
|
||||
<component :is="link.icon" class="size-4" />
|
||||
<span>{{ link.label }}</span>
|
||||
<small>{{ link.desc }}</small>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-block span-7">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2>消息与公告</h2>
|
||||
<p>公告管理、消息中心和未读提醒。</p>
|
||||
</div>
|
||||
<div class="head-actions">
|
||||
<ElButton
|
||||
link
|
||||
type="primary"
|
||||
@click="go('/message/announcement')"
|
||||
>
|
||||
公告管理
|
||||
</ElButton>
|
||||
<ElButton link type="primary" @click="go('/message/list')">
|
||||
消息中心
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="notice-columns">
|
||||
<div>
|
||||
<div class="sub-title">
|
||||
<Megaphone class="size-4" />
|
||||
最新公告
|
||||
</div>
|
||||
<div v-if="recentAnnouncements.length > 0" class="compact-list">
|
||||
<button
|
||||
v-for="item in recentAnnouncements"
|
||||
:key="item.id"
|
||||
type="button"
|
||||
@click="go('/message/announcement')"
|
||||
>
|
||||
<span>{{ item.title }}</span>
|
||||
<ElTag :type="announcementType(item.priority)" size="small">
|
||||
{{
|
||||
item.priority === 2
|
||||
? '紧急'
|
||||
: item.priority === 1
|
||||
? '重要'
|
||||
: '普通'
|
||||
}}
|
||||
</ElTag>
|
||||
</button>
|
||||
</div>
|
||||
<ElEmpty v-else description="暂无公告" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="sub-title">
|
||||
<Bell class="size-4" />
|
||||
未读消息
|
||||
</div>
|
||||
<div v-if="recentMessages.length > 0" class="compact-list">
|
||||
<button
|
||||
v-for="item in recentMessages"
|
||||
:key="item.id"
|
||||
type="button"
|
||||
@click="go('/message/list')"
|
||||
>
|
||||
<span>{{ item.title }}</span>
|
||||
<small>{{ formatTime(item.created_at) }}</small>
|
||||
</button>
|
||||
</div>
|
||||
<ElEmpty v-else description="暂无未读消息" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ai-agent-home {
|
||||
min-height: calc(100vh - 120px);
|
||||
padding: 18px;
|
||||
color: var(--el-text-color-primary);
|
||||
background:
|
||||
radial-gradient(circle at 12% 8%, rgb(37 99 235 / 8%), transparent 28%),
|
||||
linear-gradient(180deg, var(--el-bg-color-page), var(--el-bg-color));
|
||||
}
|
||||
|
||||
.home-hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 340px;
|
||||
gap: 18px;
|
||||
align-items: stretch;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.hero-copy,
|
||||
.ops-panel,
|
||||
.section-block,
|
||||
.metric-card {
|
||||
background: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 6px 20px rgb(15 23 42 / 6%);
|
||||
}
|
||||
|
||||
.hero-copy {
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.eyebrow,
|
||||
.panel-title,
|
||||
.sub-title {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
color: var(--el-color-primary);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.hero-copy h1 {
|
||||
margin: 14px 0 10px;
|
||||
font-size: 28px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.hero-copy p {
|
||||
max-width: 68ch;
|
||||
margin: 0;
|
||||
color: var(--el-text-color-regular);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.ops-panel {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.ops-row {
|
||||
display: grid;
|
||||
grid-template-columns: 46px minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
margin-top: 18px;
|
||||
color: var(--el-text-color-regular);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.ops-foot {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-top: 16px;
|
||||
margin-top: 18px;
|
||||
color: var(--el-text-color-regular);
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.metric-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.metric-card,
|
||||
.link-item,
|
||||
.admin-item,
|
||||
.run-item,
|
||||
.compact-list button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
padding: 16px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.metric-card span,
|
||||
.section-head p,
|
||||
.metric-card small,
|
||||
.link-item small,
|
||||
.admin-item small,
|
||||
.run-item small,
|
||||
.compact-list small {
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.metric-card strong {
|
||||
font-size: 26px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.content-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.span-5 {
|
||||
grid-column: span 5;
|
||||
}
|
||||
|
||||
.span-7 {
|
||||
grid-column: span 7;
|
||||
}
|
||||
|
||||
.section-block {
|
||||
min-height: 260px;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.section-head {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-head h2 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.section-head p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.head-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.link-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.link-item,
|
||||
.admin-item {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
gap: 5px 10px;
|
||||
align-items: center;
|
||||
padding: 14px;
|
||||
text-align: left;
|
||||
background: var(--el-fill-color-extra-light);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.link-item small {
|
||||
grid-column: 2;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.admin-list,
|
||||
.run-list,
|
||||
.compact-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.admin-item {
|
||||
grid-template-columns: auto 88px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.run-item,
|
||||
.compact-list button {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 6px 10px;
|
||||
align-items: center;
|
||||
padding: 11px 12px;
|
||||
text-align: left;
|
||||
background: var(--el-fill-color-extra-light);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.run-item span,
|
||||
.compact-list span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.run-item small {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.notice-columns {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tone-blue {
|
||||
--tone-color: 37 99 235;
|
||||
}
|
||||
|
||||
.tone-green {
|
||||
--tone-color: 22 163 74;
|
||||
}
|
||||
|
||||
.tone-amber {
|
||||
--tone-color: 217 119 6;
|
||||
}
|
||||
|
||||
.tone-violet {
|
||||
--tone-color: 124 58 237;
|
||||
}
|
||||
|
||||
.metric-card,
|
||||
.link-item {
|
||||
border-color: rgb(var(--tone-color) / 18%);
|
||||
}
|
||||
|
||||
.metric-card strong,
|
||||
.link-item svg {
|
||||
color: rgb(var(--tone-color));
|
||||
}
|
||||
|
||||
.metric-card:hover,
|
||||
.link-item:hover,
|
||||
.admin-item:hover,
|
||||
.run-item:hover,
|
||||
.compact-list button:hover {
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.metric-card,
|
||||
.link-item,
|
||||
.admin-item,
|
||||
.run-item,
|
||||
.compact-list button {
|
||||
transition:
|
||||
border-color 0.18s ease,
|
||||
transform 0.18s ease,
|
||||
box-shadow 0.18s ease;
|
||||
}
|
||||
|
||||
.skeleton-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.skeleton-grid :deep(.el-skeleton__item) {
|
||||
height: 130px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.home-hero,
|
||||
.notice-columns {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.metric-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.span-5,
|
||||
.span-7 {
|
||||
grid-column: span 12;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.ai-agent-home {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.hero-copy {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.hero-copy h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.metric-grid,
|
||||
.link-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.admin-item {
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.admin-item small {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.hero-actions,
|
||||
.head-actions {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,246 +0,0 @@
|
||||
import type { DashboardConfig } from '#/components/dashboard-design';
|
||||
|
||||
const AI_AGENT_HOME_CODE = 'main_home';
|
||||
|
||||
const widgetStyle = {
|
||||
backgroundColor: '',
|
||||
borderColor: '',
|
||||
borderRadius: 8,
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 0,
|
||||
padding: 16,
|
||||
shadowBlur: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowEnabled: true,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 1,
|
||||
titleAlign: 'left',
|
||||
titleColor: '',
|
||||
titleFontSize: 14,
|
||||
titleFontWeight: 'normal',
|
||||
titleShow: true,
|
||||
} as const;
|
||||
|
||||
const aiAgentAdminHomeConfig: DashboardConfig = {
|
||||
id: 'ai-agent-admin-main-home',
|
||||
name: '控制中心',
|
||||
columns: 12,
|
||||
rowHeight: 68,
|
||||
margin: [12, 12],
|
||||
backgroundColor: '',
|
||||
widgets: [
|
||||
{
|
||||
id: 'welcome',
|
||||
i: 'welcome',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 9,
|
||||
h: 2,
|
||||
minW: 4,
|
||||
minH: 2,
|
||||
type: 'welcome-card',
|
||||
title: '欢迎回来',
|
||||
props: {
|
||||
title: '欢迎回来',
|
||||
subtitle: '聚焦智能体协作、流程编排、知识库和基础权限治理',
|
||||
showTime: true,
|
||||
showWeather: false,
|
||||
},
|
||||
style: widgetStyle,
|
||||
},
|
||||
{
|
||||
id: 'weather',
|
||||
i: 'weather',
|
||||
x: 9,
|
||||
y: 0,
|
||||
w: 3,
|
||||
h: 2,
|
||||
minW: 2,
|
||||
minH: 2,
|
||||
type: 'weather',
|
||||
title: '今日天气',
|
||||
props: {
|
||||
title: '今日天气',
|
||||
cityName: '北京',
|
||||
latitude: 39.9042,
|
||||
longitude: 116.4074,
|
||||
autoLocate: false,
|
||||
refreshInterval: 30,
|
||||
},
|
||||
style: widgetStyle,
|
||||
},
|
||||
{
|
||||
id: 'ai-collaboration',
|
||||
i: 'ai-collaboration',
|
||||
x: 0,
|
||||
y: 2,
|
||||
w: 7,
|
||||
h: 3,
|
||||
minW: 6,
|
||||
minH: 2,
|
||||
type: 'quick-links',
|
||||
title: 'AI 协作',
|
||||
props: {
|
||||
title: 'AI 协作',
|
||||
columns: 4,
|
||||
rows: 2,
|
||||
menus: [
|
||||
{
|
||||
id: 'ai-agent',
|
||||
title: '智能体',
|
||||
icon: 'lucide:square-user',
|
||||
path: '/ai-platform/agent',
|
||||
},
|
||||
{
|
||||
id: 'ai-workflow',
|
||||
title: '流程编排',
|
||||
icon: 'lucide:workflow',
|
||||
path: '/ai-platform/workflow',
|
||||
},
|
||||
{
|
||||
id: 'ai-workflow-runs',
|
||||
title: '执行历史',
|
||||
icon: 'lucide:history',
|
||||
path: '/ai-platform/workflow-runs',
|
||||
},
|
||||
{
|
||||
id: 'ai-knowledge',
|
||||
title: '知识库',
|
||||
icon: 'lucide:library-big',
|
||||
path: '/ai-platform/knowledge-base',
|
||||
},
|
||||
{
|
||||
id: 'ai-model',
|
||||
title: '模型配置',
|
||||
icon: 'lucide:settings',
|
||||
path: '/ai-platform/model',
|
||||
},
|
||||
{
|
||||
id: 'codex-agent',
|
||||
title: 'Codex 助手',
|
||||
icon: 'lucide:code-2',
|
||||
path: '/agent-chat/codex',
|
||||
},
|
||||
],
|
||||
},
|
||||
style: widgetStyle,
|
||||
},
|
||||
{
|
||||
id: 'base-admin',
|
||||
i: 'base-admin',
|
||||
x: 7,
|
||||
y: 2,
|
||||
w: 5,
|
||||
h: 3,
|
||||
minW: 4,
|
||||
minH: 2,
|
||||
type: 'quick-links',
|
||||
title: '基础管理',
|
||||
props: {
|
||||
title: '基础管理',
|
||||
columns: 3,
|
||||
rows: 2,
|
||||
menus: [
|
||||
{
|
||||
id: 'system-user',
|
||||
title: '用户管理',
|
||||
icon: 'lucide:users',
|
||||
path: '/system/user',
|
||||
},
|
||||
{
|
||||
id: 'system-role',
|
||||
title: '角色管理',
|
||||
icon: 'lucide:shield-check',
|
||||
path: '/system/role',
|
||||
},
|
||||
{
|
||||
id: 'system-permission',
|
||||
title: '权限管理',
|
||||
icon: 'lucide:key-round',
|
||||
path: '/system/new-permission',
|
||||
},
|
||||
{
|
||||
id: 'system-menu',
|
||||
title: '菜单管理',
|
||||
icon: 'lucide:menu',
|
||||
path: '/system/menu',
|
||||
},
|
||||
{
|
||||
id: 'announcement',
|
||||
title: '公告管理',
|
||||
icon: 'lucide:megaphone',
|
||||
path: '/message/announcement',
|
||||
},
|
||||
{
|
||||
id: 'message',
|
||||
title: '消息中心',
|
||||
icon: 'lucide:bell',
|
||||
path: '/message/list',
|
||||
},
|
||||
],
|
||||
},
|
||||
style: widgetStyle,
|
||||
},
|
||||
{
|
||||
id: 'announcement-list',
|
||||
i: 'announcement-list',
|
||||
x: 0,
|
||||
y: 5,
|
||||
w: 4,
|
||||
h: 4,
|
||||
minW: 3,
|
||||
minH: 3,
|
||||
type: 'announcement-list',
|
||||
title: '最新公告',
|
||||
props: {
|
||||
title: '最新公告',
|
||||
limit: 6,
|
||||
},
|
||||
style: widgetStyle,
|
||||
},
|
||||
{
|
||||
id: 'notice-list',
|
||||
i: 'notice-list',
|
||||
x: 4,
|
||||
y: 5,
|
||||
w: 4,
|
||||
h: 4,
|
||||
minW: 3,
|
||||
minH: 3,
|
||||
type: 'notice-list',
|
||||
title: '消息通知',
|
||||
props: {
|
||||
title: '消息通知',
|
||||
limit: 6,
|
||||
},
|
||||
style: widgetStyle,
|
||||
},
|
||||
{
|
||||
id: 'server-monitor',
|
||||
i: 'server-monitor',
|
||||
x: 8,
|
||||
y: 5,
|
||||
w: 4,
|
||||
h: 4,
|
||||
minW: 4,
|
||||
minH: 3,
|
||||
type: 'server-monitor',
|
||||
title: '运行监控',
|
||||
props: {
|
||||
title: '运行监控',
|
||||
refreshInterval: 5000,
|
||||
},
|
||||
style: widgetStyle,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
function isAiAgentAdminHomeCode(code: string) {
|
||||
return code === AI_AGENT_HOME_CODE;
|
||||
}
|
||||
|
||||
function createAiAgentAdminHomeConfig() {
|
||||
return JSON.parse(JSON.stringify(aiAgentAdminHomeConfig)) as DashboardConfig;
|
||||
}
|
||||
|
||||
export { createAiAgentAdminHomeConfig, isAiAgentAdminHomeCode };
|
||||
@@ -1,24 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { ElEmpty, ElMessage, ElScrollbar } from 'element-plus';
|
||||
|
||||
import { getPageByCodeApi } from '#/api/core/page-manager';
|
||||
import DashboardRenderer from '#/components/dashboard-design/DashboardRenderer.vue';
|
||||
|
||||
import {
|
||||
createAiAgentAdminHomeConfig,
|
||||
isAiAgentAdminHomeCode,
|
||||
} from './ai-agent-home-config';
|
||||
import AiAgentHome from './AiAgentHome.vue';
|
||||
|
||||
defineOptions({ name: 'PageRender' });
|
||||
|
||||
const AI_AGENT_HOME_CODE = 'main_home';
|
||||
const route = useRoute();
|
||||
const loading = ref(false);
|
||||
const pageConfig = ref<string>('');
|
||||
const pageName = ref('');
|
||||
const pageCode = ref('');
|
||||
const isHomePage = computed(() => pageCode.value === AI_AGENT_HOME_CODE);
|
||||
|
||||
function getPageCode(): string {
|
||||
if (route.query.pageCode) {
|
||||
@@ -49,19 +43,9 @@ async function loadPageData() {
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
if (isAiAgentAdminHomeCode(code)) {
|
||||
const config = createAiAgentAdminHomeConfig();
|
||||
pageName.value = config.name;
|
||||
pageConfig.value = JSON.stringify(config);
|
||||
return;
|
||||
if (!isHomePage.value) {
|
||||
ElMessage.warning('轻量版已停用在线页面渲染');
|
||||
}
|
||||
|
||||
const page = await getPageByCodeApi(code);
|
||||
pageName.value = page.name;
|
||||
pageConfig.value =
|
||||
page.page_config && Object.keys(page.page_config).length > 0
|
||||
? JSON.stringify(page.page_config)
|
||||
: '';
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || '加载页面失败');
|
||||
} finally {
|
||||
@@ -83,9 +67,16 @@ watch(
|
||||
|
||||
<template>
|
||||
<ElScrollbar class="rounded-[8px] py-3">
|
||||
<div v-loading="loading" class="h-[calc(100vh-120px)] rounded-[8px] px-3">
|
||||
<DashboardRenderer v-if="pageConfig" :config="pageConfig" />
|
||||
<ElEmpty v-else-if="!loading" description="暂无页面配置" />
|
||||
<div
|
||||
v-loading="loading"
|
||||
class="min-h-[calc(100vh-120px)] rounded-[8px] px-3"
|
||||
>
|
||||
<AiAgentHome v-if="isHomePage" />
|
||||
<ElEmpty
|
||||
v-else-if="!loading"
|
||||
description="轻量版已移除在线页面渲染"
|
||||
class="py-20"
|
||||
/>
|
||||
</div>
|
||||
</ElScrollbar>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user