Просмотр исходного кода

⚡️ 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
t0ng7u 4 месяцев назад
Родитель
Сommit
b67a42e0a8

+ 28 - 18
web/src/components/dashboard/ChartsPanel.jsx

@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 For commercial licensing, please contact [email protected]
 */
 
-import React from 'react';
+import React, { Suspense } from 'react';
 import { Card, Tabs, TabPane } from '@douyinfe/semi-ui';
 import { PieChart } from 'lucide-react';
 import {
@@ -25,7 +25,9 @@ import {
   IconPulse,
   IconPieChart2Stroked
 } 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 = ({
   activeChartTab,
@@ -86,28 +88,36 @@ const ChartsPanel = ({
     >
       <div className="h-96 p-2">
         {activeChartTab === '1' && (
-          <VChart
-            spec={spec_line}
-            option={CHART_CONFIG}
-          />
+          <Suspense fallback={null}>
+            <LazyVChart
+              spec={spec_line}
+              option={CHART_CONFIG}
+            />
+          </Suspense>
         )}
         {activeChartTab === '2' && (
-          <VChart
-            spec={spec_model_line}
-            option={CHART_CONFIG}
-          />
+          <Suspense fallback={null}>
+            <LazyVChart
+              spec={spec_model_line}
+              option={CHART_CONFIG}
+            />
+          </Suspense>
         )}
         {activeChartTab === '3' && (
-          <VChart
-            spec={spec_pie}
-            option={CHART_CONFIG}
-          />
+          <Suspense fallback={null}>
+            <LazyVChart
+              spec={spec_pie}
+              option={CHART_CONFIG}
+            />
+          </Suspense>
         )}
         {activeChartTab === '4' && (
-          <VChart
-            spec={spec_rank_bar}
-            option={CHART_CONFIG}
-          />
+          <Suspense fallback={null}>
+            <LazyVChart
+              spec={spec_rank_bar}
+              option={CHART_CONFIG}
+            />
+          </Suspense>
         )}
       </div>
     </Card>

+ 11 - 6
web/src/components/dashboard/StatsCards.jsx

@@ -17,9 +17,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 For commercial licensing, please contact [email protected]
 */
 
-import React from 'react';
+import React, { Suspense } from 'react';
 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 = ({
   groupedStatsData,
@@ -74,10 +77,12 @@ const StatsCards = ({
                   </div>
                   {(loading || (item.trendData && item.trendData.length > 0)) && (
                     <div className="w-24 h-10">
-                      <VChart
-                        spec={getTrendSpec(item.trendData, item.trendColor)}
-                        option={CHART_CONFIG}
-                      />
+                      <Suspense fallback={null}>
+                        <LazyVChart
+                          spec={getTrendSpec(item.trendData, item.trendColor)}
+                          option={CHART_CONFIG}
+                        />
+                      </Suspense>
                     </div>
                   )}
                 </div>

+ 1 - 1
web/src/components/settings/personal/cards/AccountManagement.js

@@ -71,7 +71,7 @@ const AccountManagement = ({
         </div>
       </div>
 
-      <Tabs type="line" defaultActiveKey="binding">
+      <Tabs type="card" defaultActiveKey="binding">
         {/* 账户绑定 Tab */}
         <TabPane
           tab={

+ 1 - 1
web/src/components/settings/personal/cards/NotificationSettings.js

@@ -106,7 +106,7 @@ const NotificationSettings = ({
         onSubmit={handleSubmit}
       >
         {() => (
-          <Tabs type="line" defaultActiveKey="notification">
+          <Tabs type="card" defaultActiveKey="notification">
             {/* 通知配置 Tab */}
             <TabPane
               tab={

+ 6 - 16
web/src/components/settings/personal/components/UserInfoHeader.js

@@ -19,12 +19,10 @@ For commercial licensing, please contact [email protected]
 
 import React from 'react';
 import { Avatar, Card, Tag, Divider, Typography } from '@douyinfe/semi-ui';
-import { isRoot, isAdmin, renderQuota } from '../../../../helpers';
-import { useTheme } from '../../../../context/Theme';
+import { isRoot, isAdmin, renderQuota, stringToColor } from '../../../../helpers';
 import { Coins, BarChart2, Users } from 'lucide-react';
 
 const UserInfoHeader = ({ t, userState }) => {
-  const theme = useTheme();
 
   const getUsername = () => {
     if (userState.user) {
@@ -44,16 +42,7 @@ const UserInfoHeader = ({ t, userState }) => {
   };
 
   return (
-    <Card
-      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 }}
-    >
+    <Card className="!rounded-2xl">
       {/* 装饰性背景元素 */}
       <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>
@@ -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>
 
-      <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 items-center flex-1 min-w-0">
             <Avatar
               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()}
             </Avatar>
@@ -113,7 +103,7 @@ const UserInfoHeader = ({ t, userState }) => {
 
           {/* 右上角统计信息(Semi UI 卡片) */}
           <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 justify-end gap-2">
                   <Coins size={16} className="text-slate-600 dark:text-slate-300" />

+ 13 - 4
web/src/hooks/dashboard/useDashboardCharts.js

@@ -18,7 +18,6 @@ For commercial licensing, please contact [email protected]
 */
 
 import { useState, useCallback, useEffect } from 'react';
-import { initVChartSemiTheme } from '@visactor/vchart-semi-theme';
 import {
   modelColorMap,
   renderNumber,
@@ -418,9 +417,19 @@ export const useDashboardCharts = (
 
   // ========== 初始化图表主题 ==========
   useEffect(() => {
-    initVChartSemiTheme({
-      isWatchingThemeSwitch: true,
-    });
+    // 动态加载 visactor 主题,避免首页首屏加载
+    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 {