⚡️ perf: Defer Visactor chart libs to dashboard; minimize home bundle
Home started loading `/assets/visactor-*.js` due to static imports of `@visactor/react-vchart` and the Semi theme in dashboard components/hooks. This change moves chart dependencies to lazy/dynamic imports so they load only on dashboard routes. Changes - StatsCards.jsx: replace static `VChart` import with `React.lazy` + `Suspense` (fallback: null) - ChartsPanel.jsx: replace static `VChart` import with `React.lazy` + `Suspense` (fallback: null) - useDashboardCharts.js: remove static `initVChartSemiTheme` import; dynamically import and initialize the theme inside `useEffect` with a cancel guard Behavior - Home page no longer downloads `visactor` chunks on first load - Chart libraries are fetched only when visiting `/console` (dashboard) - No functional changes to chart rendering Files - web/src/components/dashboard/StatsCards.jsx - web/src/components/dashboard/ChartsPanel.jsx - web/src/hooks/dashboard/useDashboardCharts.js Verification - Build the app (`npm run build`) and open `/`: no `/assets/visactor-*.js` requests - Navigate to `/console`: `visactor` chunks load and charts render as expected Breaking Changes - None Follow-ups - If needed, further trim homepage bundle by reducing heavy icon sets on the hero section
This commit is contained in:
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
For commercial licensing, please contact support@quantumnous.com
|
For commercial licensing, please contact support@quantumnous.com
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import { Card, Tabs, TabPane } from '@douyinfe/semi-ui';
|
import { Card, Tabs, TabPane } from '@douyinfe/semi-ui';
|
||||||
import { PieChart } from 'lucide-react';
|
import { PieChart } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
@@ -25,7 +25,9 @@ import {
|
|||||||
IconPulse,
|
IconPulse,
|
||||||
IconPieChart2Stroked
|
IconPieChart2Stroked
|
||||||
} from '@douyinfe/semi-icons';
|
} from '@douyinfe/semi-icons';
|
||||||
import { VChart } from '@visactor/react-vchart';
|
const LazyVChart = React.lazy(() =>
|
||||||
|
import('@visactor/react-vchart').then(m => ({ default: m.VChart }))
|
||||||
|
);
|
||||||
|
|
||||||
const ChartsPanel = ({
|
const ChartsPanel = ({
|
||||||
activeChartTab,
|
activeChartTab,
|
||||||
@@ -86,28 +88,36 @@ const ChartsPanel = ({
|
|||||||
>
|
>
|
||||||
<div className="h-96 p-2">
|
<div className="h-96 p-2">
|
||||||
{activeChartTab === '1' && (
|
{activeChartTab === '1' && (
|
||||||
<VChart
|
<Suspense fallback={null}>
|
||||||
spec={spec_line}
|
<LazyVChart
|
||||||
option={CHART_CONFIG}
|
spec={spec_line}
|
||||||
/>
|
option={CHART_CONFIG}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
)}
|
)}
|
||||||
{activeChartTab === '2' && (
|
{activeChartTab === '2' && (
|
||||||
<VChart
|
<Suspense fallback={null}>
|
||||||
spec={spec_model_line}
|
<LazyVChart
|
||||||
option={CHART_CONFIG}
|
spec={spec_model_line}
|
||||||
/>
|
option={CHART_CONFIG}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
)}
|
)}
|
||||||
{activeChartTab === '3' && (
|
{activeChartTab === '3' && (
|
||||||
<VChart
|
<Suspense fallback={null}>
|
||||||
spec={spec_pie}
|
<LazyVChart
|
||||||
option={CHART_CONFIG}
|
spec={spec_pie}
|
||||||
/>
|
option={CHART_CONFIG}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
)}
|
)}
|
||||||
{activeChartTab === '4' && (
|
{activeChartTab === '4' && (
|
||||||
<VChart
|
<Suspense fallback={null}>
|
||||||
spec={spec_rank_bar}
|
<LazyVChart
|
||||||
option={CHART_CONFIG}
|
spec={spec_rank_bar}
|
||||||
/>
|
option={CHART_CONFIG}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -17,9 +17,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
For commercial licensing, please contact support@quantumnous.com
|
For commercial licensing, please contact support@quantumnous.com
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import { Card, Avatar, Skeleton } from '@douyinfe/semi-ui';
|
import { Card, Avatar, Skeleton } from '@douyinfe/semi-ui';
|
||||||
import { VChart } from '@visactor/react-vchart';
|
|
||||||
|
const LazyVChart = React.lazy(() =>
|
||||||
|
import('@visactor/react-vchart').then(m => ({ default: m.VChart }))
|
||||||
|
);
|
||||||
|
|
||||||
const StatsCards = ({
|
const StatsCards = ({
|
||||||
groupedStatsData,
|
groupedStatsData,
|
||||||
@@ -74,10 +77,12 @@ const StatsCards = ({
|
|||||||
</div>
|
</div>
|
||||||
{(loading || (item.trendData && item.trendData.length > 0)) && (
|
{(loading || (item.trendData && item.trendData.length > 0)) && (
|
||||||
<div className="w-24 h-10">
|
<div className="w-24 h-10">
|
||||||
<VChart
|
<Suspense fallback={null}>
|
||||||
spec={getTrendSpec(item.trendData, item.trendColor)}
|
<LazyVChart
|
||||||
option={CHART_CONFIG}
|
spec={getTrendSpec(item.trendData, item.trendColor)}
|
||||||
/>
|
option={CHART_CONFIG}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ const AccountManagement = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs type="line" defaultActiveKey="binding">
|
<Tabs type="card" defaultActiveKey="binding">
|
||||||
{/* 账户绑定 Tab */}
|
{/* 账户绑定 Tab */}
|
||||||
<TabPane
|
<TabPane
|
||||||
tab={
|
tab={
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ const NotificationSettings = ({
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
{() => (
|
{() => (
|
||||||
<Tabs type="line" defaultActiveKey="notification">
|
<Tabs type="card" defaultActiveKey="notification">
|
||||||
{/* 通知配置 Tab */}
|
{/* 通知配置 Tab */}
|
||||||
<TabPane
|
<TabPane
|
||||||
tab={
|
tab={
|
||||||
|
|||||||
@@ -19,12 +19,10 @@ For commercial licensing, please contact support@quantumnous.com
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Avatar, Card, Tag, Divider, Typography } from '@douyinfe/semi-ui';
|
import { Avatar, Card, Tag, Divider, Typography } from '@douyinfe/semi-ui';
|
||||||
import { isRoot, isAdmin, renderQuota } from '../../../../helpers';
|
import { isRoot, isAdmin, renderQuota, stringToColor } from '../../../../helpers';
|
||||||
import { useTheme } from '../../../../context/Theme';
|
|
||||||
import { Coins, BarChart2, Users } from 'lucide-react';
|
import { Coins, BarChart2, Users } from 'lucide-react';
|
||||||
|
|
||||||
const UserInfoHeader = ({ t, userState }) => {
|
const UserInfoHeader = ({ t, userState }) => {
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const getUsername = () => {
|
const getUsername = () => {
|
||||||
if (userState.user) {
|
if (userState.user) {
|
||||||
@@ -44,16 +42,7 @@ const UserInfoHeader = ({ t, userState }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card className="!rounded-2xl">
|
||||||
className="!rounded-2xl !border-0"
|
|
||||||
style={{
|
|
||||||
background: theme === 'dark'
|
|
||||||
? 'linear-gradient(135deg, #1e293b 0%, #334155 50%, #475569 100%)'
|
|
||||||
: 'linear-gradient(135deg, #f8fafc 0%, #e2e8f0 50%, #cbd5e1 100%)',
|
|
||||||
position: 'relative'
|
|
||||||
}}
|
|
||||||
bodyStyle={{ padding: 0 }}
|
|
||||||
>
|
|
||||||
{/* 装饰性背景元素 */}
|
{/* 装饰性背景元素 */}
|
||||||
<div className="absolute inset-0 overflow-hidden">
|
<div className="absolute inset-0 overflow-hidden">
|
||||||
<div className="absolute -top-10 -right-10 w-40 h-40 bg-slate-400 dark:bg-slate-500 opacity-5 rounded-full"></div>
|
<div className="absolute -top-10 -right-10 w-40 h-40 bg-slate-400 dark:bg-slate-500 opacity-5 rounded-full"></div>
|
||||||
@@ -61,12 +50,13 @@ const UserInfoHeader = ({ t, userState }) => {
|
|||||||
<div className="absolute top-1/2 right-1/4 w-24 h-24 bg-slate-400 dark:bg-slate-500 opacity-6 rounded-full"></div>
|
<div className="absolute top-1/2 right-1/4 w-24 h-24 bg-slate-400 dark:bg-slate-500 opacity-6 rounded-full"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative p-4 sm:p-6 md:p-8 text-gray-600 dark:text-gray-300">
|
<div className="relative text-gray-600 dark:text-gray-300">
|
||||||
<div className="flex justify-between items-start mb-4 sm:mb-6">
|
<div className="flex justify-between items-start mb-4 sm:mb-6">
|
||||||
<div className="flex items-center flex-1 min-w-0">
|
<div className="flex items-center flex-1 min-w-0">
|
||||||
<Avatar
|
<Avatar
|
||||||
size='large'
|
size='large'
|
||||||
className="mr-3 sm:mr-4 shadow-md flex-shrink-0 bg-slate-500 dark:bg-slate-400"
|
className="mr-3 sm:mr-4 shadow-md flex-shrink-0"
|
||||||
|
color={stringToColor(getUsername())}
|
||||||
>
|
>
|
||||||
{getAvatarText()}
|
{getAvatarText()}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
@@ -113,7 +103,7 @@ const UserInfoHeader = ({ t, userState }) => {
|
|||||||
|
|
||||||
{/* 右上角统计信息(Semi UI 卡片) */}
|
{/* 右上角统计信息(Semi UI 卡片) */}
|
||||||
<div className="hidden sm:block flex-shrink-0 ml-2">
|
<div className="hidden sm:block flex-shrink-0 ml-2">
|
||||||
<Card size="small" className="!rounded-xl !border-0 shadow-sm" bodyStyle={{ padding: '8px 12px' }}>
|
<Card size="small" className="!rounded-xl shadow-sm" bodyStyle={{ padding: '8px 12px' }}>
|
||||||
<div className="flex items-center gap-3 lg:gap-4">
|
<div className="flex items-center gap-3 lg:gap-4">
|
||||||
<div className="flex items-center justify-end gap-2">
|
<div className="flex items-center justify-end gap-2">
|
||||||
<Coins size={16} className="text-slate-600 dark:text-slate-300" />
|
<Coins size={16} className="text-slate-600 dark:text-slate-300" />
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ For commercial licensing, please contact support@quantumnous.com
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
import { initVChartSemiTheme } from '@visactor/vchart-semi-theme';
|
|
||||||
import {
|
import {
|
||||||
modelColorMap,
|
modelColorMap,
|
||||||
renderNumber,
|
renderNumber,
|
||||||
@@ -418,9 +417,19 @@ export const useDashboardCharts = (
|
|||||||
|
|
||||||
// ========== 初始化图表主题 ==========
|
// ========== 初始化图表主题 ==========
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initVChartSemiTheme({
|
// 动态加载 visactor 主题,避免首页首屏加载
|
||||||
isWatchingThemeSwitch: true,
|
let canceled = false;
|
||||||
});
|
(async () => {
|
||||||
|
try {
|
||||||
|
const mod = await import('@visactor/vchart-semi-theme');
|
||||||
|
if (!canceled && mod?.initVChartSemiTheme) {
|
||||||
|
mod.initVChartSemiTheme({ isWatchingThemeSwitch: true });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 忽略加载失败,图表页再尝试
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
return () => { canceled = true; };
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user