perf(desktop): lazy-load renderer routes and pause polling when hidden

Drop the eager Antd global import in favour of unplugin-vue-components
on-demand resolution, async route components, defineAsyncComponent for
the shell/login split, and a dynamic Modal import inside
showClientActionError. Pair that with a visibility-aware runtime poller
that stops the 15s snapshot refresh when the window is hidden and kicks
off an immediate refresh on re-show. Refresh DesktopShell nav with
inline icons and a calmer active/hover treatment, and land
ActionButton/DisclosurePanel/PaginationBar primitives for upcoming
views.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-20 11:08:09 +08:00
parent ef868f81a1
commit 73dfbbe8b5
12 changed files with 579 additions and 53 deletions
@@ -1,6 +1,13 @@
<script setup lang="ts">
import { computed } from "vue";
import { RouterLink, RouterView, useRoute } from "vue-router";
import {
AppstoreOutlined,
SendOutlined,
LinkOutlined,
RobotOutlined,
ToolOutlined,
} from "@ant-design/icons-vue";
import { useDesktopRuntime } from "../composables/useDesktopRuntime";
import { useDesktopSession } from "../composables/useDesktopSession";
@@ -30,30 +37,35 @@ const navItems = computed(() => {
title: "控制台",
description: "运行总览、活动流和总体健康度",
count: data?.summary.issuesOpen ?? 0,
icon: AppstoreOutlined,
},
{
to: "/publish-management",
title: "发布管理",
description: "查看待发布队列、历史发送结果,并对文章再次发送",
count: data?.summary.queuedTasks ?? 0,
icon: SendOutlined,
},
{
to: "/media-platforms",
title: "媒体平台",
description: "发布媒体授权、多个账号绑定与表格化管理",
count: accounts.filter((item) => publishIDs.has(item.platform)).length,
icon: LinkOutlined,
},
{
to: "/ai-platforms",
title: "AI 平台",
description: "AI 平台一平台一绑定,按卡片查看状态与归属",
count: accounts.filter((item) => monitoringIDs.has(item.platform)).length,
icon: RobotOutlined,
},
{
to: "/diagnostics",
title: "诊断",
description: "vault、scheduler、transport 与原始快照",
count: data?.clients.length ?? 0,
icon: ToolOutlined,
},
];
});
@@ -90,6 +102,7 @@ const clientLabel = computed(
@click="handleNavClick(item.to)"
>
<div class="nav-copy">
<component :is="item.icon" class="nav-icon" />
<strong>{{ item.title }}</strong>
</div>
<span class="nav-count">{{ item.count }}</span>
@@ -207,40 +220,56 @@ h1 {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 16px;
border-radius: 12px;
padding: 12px 16px;
border-radius: 8px;
text-decoration: none;
background: #ffffff;
border: 1px solid #e6edf5;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
background: transparent;
border: 1px solid transparent;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.nav-link:hover {
border-color: #91caff;
box-shadow: 0 4px 12px rgba(22, 119, 255, 0.08);
transform: translateY(-1px);
background: #f8fafc;
}
.nav-link.active {
background: #f0f7ff;
border-color: #1677ff;
box-shadow: 0 4px 12px rgba(22, 119, 255, 0.1);
background: #e6f4ff;
border-color: #e6f4ff;
}
.nav-copy {
display: grid;
gap: 4px;
display: flex;
align-items: center;
gap: 12px;
}
.nav-icon {
font-size: 16px;
color: #8c8c8c;
transition: color 0.2s;
}
.nav-link:hover .nav-icon {
color: #595959;
}
.nav-link.active .nav-icon {
color: #1677ff;
}
.nav-copy strong {
font-size: 14px;
font-weight: 600;
color: #1a1a1a;
font-weight: 500;
color: #4b5563;
}
.nav-link:hover .nav-copy strong {
color: #1f2937;
}
.nav-link.active .nav-copy strong {
color: #1677ff;
font-weight: 600;
}
.nav-count {