feat: replace dashboard renderer home

This commit is contained in:
2026-06-11 14:17:26 +08:00
parent 632e211a54
commit ecf29e2278
5 changed files with 878 additions and 277 deletions
+3 -4
View File
@@ -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);
}
-2
View File
@@ -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>