perf: 优化管理端生产加载体验

- 图标与重型组件按需加载,优化菜单预取和页面请求链路

- 开启 gzip 与静态缓存并修复子路由 KeepAlive 冲突
This commit is contained in:
2026-07-22 20:35:55 +08:00
parent 9f06c238d3
commit 19059fde96
27 changed files with 750 additions and 233 deletions

View File

@@ -4,7 +4,12 @@ import type { VNode } from 'vue';
import { computed, ref, useAttrs, watch, watchEffect } from 'vue';
import { usePagination } from '@easyflow/hooks';
import { EmptyIcon, Grip, listIcons } from '@easyflow/icons';
import {
EmptyIcon,
Grip,
listIcons,
loadIconCollection,
} from '@easyflow/icons';
import { $t } from '@easyflow/locales';
import {
@@ -75,6 +80,30 @@ const currentPage = ref(1);
const keyword = ref('');
const keywordDebounce = refDebounced(keyword, 300);
const innerIcons = ref<string[]>([]);
let iconLoadRequestId = 0;
const commonSvgIcons = [
'svg:talk',
'svg:plugin',
'svg:workflow',
'svg:approval',
'svg:knowledge',
'svg:resource',
'svg:data-center',
'svg:llm',
'svg:account',
'svg:role',
'svg:menu',
'svg:department',
'svg:time',
'svg:log',
'svg:chat-history',
'svg:setting',
'svg:api',
'svg:user-feedback',
'svg:oauth',
'svg:mcp',
];
/* 当检索关键词变化时,重置分页 */
watch(keywordDebounce, () => {
@@ -82,36 +111,39 @@ watch(keywordDebounce, () => {
setCurrentPage(1);
});
watchDebounced(
() => props.prefix,
async (prefix) => {
if (prefix && prefix !== 'svg' && props.autoFetchApi) {
innerIcons.value = [
'svg:talk',
'svg:plugin',
'svg:workflow',
'svg:approval',
'svg:knowledge',
'svg:resource',
'svg:data-center',
'svg:llm',
'svg:account',
'svg:role',
'svg:menu',
'svg:department',
'svg:time',
'svg:log',
'svg:chat-history',
'svg:setting',
'svg:api',
'svg:user-feedback',
'svg:oauth',
'svg:mcp',
...getLocalIconsData(prefix),
];
async function refreshInnerIcons(prefix?: string) {
if (!prefix || prefix === 'svg' || !props.autoFetchApi) {
return;
}
const requestId = ++iconLoadRequestId;
try {
await loadIconCollection(prefix);
} catch (error) {
console.error(`Failed to load icon collection: ${prefix}`, error);
}
if (requestId === iconLoadRequestId && prefix === props.prefix) {
innerIcons.value = [...commonSvgIcons, ...getLocalIconsData(prefix, true)];
}
}
watch(
visible,
(isVisible) => {
if (isVisible) {
void refreshInnerIcons(props.prefix);
}
},
{ immediate: true, debounce: 500, maxWait: 1000 },
{ immediate: true },
);
watchDebounced(
() => props.prefix,
(prefix) => {
if (visible.value) {
void refreshInnerIcons(prefix);
}
},
{ debounce: 300, maxWait: 600 },
);
const currentList = computed(() => {

View File

@@ -12,8 +12,8 @@ export const ICONS_MAP: Recordable<string[]> = {};
* @param prefix 图标集名称
* @returns 图标集中包含的所有图标名称
*/
export function getLocalIconsData(prefix: string): string[] {
if (Reflect.has(ICONS_MAP, prefix) && ICONS_MAP[prefix]) {
export function getLocalIconsData(prefix: string, refresh = false): string[] {
if (!refresh && Reflect.has(ICONS_MAP, prefix) && ICONS_MAP[prefix]) {
return ICONS_MAP[prefix];
}
ICONS_MAP[prefix] = listIcons('', prefix);