From d957e2750177fe930cfb51bfc889f3872ad40779 Mon Sep 17 00:00:00 2001 From: yujiangping Date: Mon, 12 Jan 2026 16:32:58 +0800 Subject: [PATCH] feat(homepage): add guided tour and version display functionality - Add version API endpoint and response interface in common.ts - Implement interactive guided tour with 4 steps for new users covering Model Management, Space Management, and User Management - Add tour translation keys for both English and Chinese locales - Add data-menu-id attributes to sidebar menu items for tour targeting - Create VersionCard component to display current platform version - Update GuideCard component with tour state management and navigation logic - Enhance homepage dashboard with version information display - Improve user onboarding experience with step-by-step guided navigation --- web/src/api/common.ts | 9 +++ web/src/components/SiderMenu/index.tsx | 6 +- web/src/i18n/en.ts | 10 +++ web/src/i18n/zh.ts | 10 +++ web/src/views/Index/components/GuideCard.tsx | 75 +++++++++++++++++-- .../views/Index/components/QuickActions.tsx | 74 ++++++++++-------- .../Index/components/TopCardList/index.tsx | 1 + .../views/Index/components/VersionCard.tsx | 41 ++++++++-- web/src/views/Index/index.tsx | 16 ++-- 9 files changed, 189 insertions(+), 53 deletions(-) diff --git a/web/src/api/common.ts b/web/src/api/common.ts index 9a33fd9a..025dcb4d 100644 --- a/web/src/api/common.ts +++ b/web/src/api/common.ts @@ -23,6 +23,10 @@ export interface DataResponse { new_apps_this_week: Number; app_week_growth_rate: Number } +export interface versionResponse{ + version: string; + introduction: string; +} // 首页数据统计 export const getDashboardData = `/home-page/workspaces` @@ -30,4 +34,9 @@ export const getDashboardData = `/home-page/workspaces` export const getDashboardStatistics = async () => { const response = await request.get(`/home-page/statistics`); return response as DataResponse; +}; +// 获取版本号 +export const getVersion = async () => { + const response = await request.get(`/home-page/version`); + return response as versionResponse; }; \ No newline at end of file diff --git a/web/src/components/SiderMenu/index.tsx b/web/src/components/SiderMenu/index.tsx index a39a0b4e..61a1836d 100644 --- a/web/src/components/SiderMenu/index.tsx +++ b/web/src/components/SiderMenu/index.tsx @@ -115,7 +115,11 @@ const Menu: FC<{ return { key: menu.path, title: menu.i18nKey ? t(menu.i18nKey) : menu.label, - label: menu.i18nKey ? t(menu.i18nKey) : menu.label, + label: ( + + {menu.i18nKey ? t(menu.i18nKey) : menu.label} + + ), icon: iconSrc ? { const { t } = useTranslation(); + const navigate = useNavigate(); + const [open, setOpen] = useState(false); + const [currentStep, setCurrentStep] = useState(0); + const startButtonRef = useRef(null); + + // Tour 步骤配置 + const steps: TourProps['steps'] = [ + { + title: t('indexTour.startTitle'), + description: t('indexTour.startDescription'), + target: () => startButtonRef.current!, + }, + { + title: t('indexTour.stepOne'), + description: t('indexTour.stepOneDescription'), + target: () => document.querySelector('[data-menu-id="/model"]') as HTMLElement, + }, + { + title: t('indexTour.stepTwo'), + description: t('indexTour.stepTwoDescription'), + target: () => document.querySelector('[data-menu-id="/space"]') as HTMLElement, + }, + { + title: t('indexTour.stepThree'), + description: t('indexTour.stepThreeDescription'), + target: () => document.querySelector('[data-menu-id="/user-management"]') as HTMLElement, + } + ]; + + // 开始引导 + const handleStartGuide = () => { + setCurrentStep(0); + setOpen(true); + }; + + // Tour 步骤变化处理 + const handleStepChange = (current: number) => { + setCurrentStep(current); + // 不再自动跳转页面,让用户通过点击菜单项来导航 + }; + + // Tour 完成处理 + const handleTourFinish = () => { + setOpen(false); + setCurrentStep(0); + // 完成后导航到模型管理页面 + navigate('/model'); + }; return ( + <>
-
+
{ t('index.getStarted')}
{ t('index.startedDesc')}
- - + */}
+ + setOpen(false)} + steps={steps} + current={currentStep} + onChange={handleStepChange} + onFinish={handleTourFinish} + /> + ); }; diff --git a/web/src/views/Index/components/QuickActions.tsx b/web/src/views/Index/components/QuickActions.tsx index 7cdb90ec..edf5166e 100644 --- a/web/src/views/Index/components/QuickActions.tsx +++ b/web/src/views/Index/components/QuickActions.tsx @@ -3,11 +3,7 @@ import { useTranslation } from 'react-i18next'; import modelIcon from '@/assets/images/index/model_mgt.svg' import spaceIcon from '@/assets/images/index/space_mgt.svg' -import workflowIcon from '@/assets/images/index/workflow_mgt.svg' import userIcon from '@/assets/images/index/user_mgt.svg' -import dataExportIcon from '@/assets/images/index/data_export.svg' -import logIcon from '@/assets/images/index/log_mgt.svg' -import noteIcon from '@/assets/images/index/note_mgt.svg' import helpCenterIcon from '@/assets/images/index/help_center.svg' interface QuickAction { key: string; @@ -22,14 +18,30 @@ interface QuickActionsProps { } const QuickActions: FC = ({ onNavigate }) => { - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); + + // 根据当前语言环境打开帮助中心 + const openHelpCenter = () => { + const currentLang = i18n.language; + const lang = currentLang === 'zh' ? 'zh' : 'en'; + const helpUrl = `https://docs.redbearai.com/s/${lang}-memorybear`; + + // 创建隐藏的 a 标签来避免弹窗拦截 + const link = document.createElement('a'); + link.href = helpUrl; + link.target = '_blank'; + link.rel = 'noopener noreferrer'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; const quickActions: QuickAction[] = [ { key: 'model-management', icon: modelIcon, title: t('quickActions.modelManagement'), - onClick: () => onNavigate?.('/model-management') + onClick: () => onNavigate?.('/model') }, { key: 'space-management', @@ -37,42 +49,42 @@ const QuickActions: FC = ({ onNavigate }) => { title: t('quickActions.spaceManagement'), onClick: () => onNavigate?.('/spce') }, - { - key: 'workflow-orchestration', - icon: workflowIcon, - title: t('quickActions.workflowOrchestration'), - onClick: () => onNavigate?.('/workflow') - }, + // { + // key: 'workflow-orchestration', + // icon: workflowIcon, + // title: t('quickActions.workflowOrchestration'), + // onClick: () => onNavigate?.('/workflow') + // }, { key: 'user-management', icon: userIcon, title: t('quickActions.userManagement'), onClick: () => onNavigate?.('/user-management') }, - { - key: 'data-export', - icon: dataExportIcon, - title: t('quickActions.dataExport'), - onClick: () => onNavigate?.('/') - }, - { - key: 'log-query', - icon: logIcon, - title: t('quickActions.logQuery'), - onClick: () => onNavigate?.('/log') - }, - { - key: 'notification-reminder', - icon: noteIcon, - title: t('quickActions.notificationReminder'), - onClick: () => onNavigate?.('/notification-reminder') - }, + // { + // key: 'data-export', + // icon: dataExportIcon, + // title: t('quickActions.dataExport'), + // onClick: () => onNavigate?.('/') + // }, + // { + // key: 'log-query', + // icon: logIcon, + // title: t('quickActions.logQuery'), + // onClick: () => onNavigate?.('/log') + // }, + // { + // key: 'notification-reminder', + // icon: noteIcon, + // title: t('quickActions.notificationReminder'), + // onClick: () => onNavigate?.('/notification-reminder') + // }, { key: 'help-center', icon: helpCenterIcon, title: t('quickActions.helpCenter'), - onClick: () => onNavigate?.('/help-center') + onClick: openHelpCenter } ]; diff --git a/web/src/views/Index/components/TopCardList/index.tsx b/web/src/views/Index/components/TopCardList/index.tsx index 273ce936..fbc434fd 100644 --- a/web/src/views/Index/components/TopCardList/index.tsx +++ b/web/src/views/Index/components/TopCardList/index.tsx @@ -59,6 +59,7 @@ const list = [ ] const TopCardList: FC<{data?: DataResponse}> = ({ data }) => { const { t } = useTranslation() + debugger return (
{list.map((item) => { diff --git a/web/src/views/Index/components/VersionCard.tsx b/web/src/views/Index/components/VersionCard.tsx index 3967dac4..9b7af54a 100644 --- a/web/src/views/Index/components/VersionCard.tsx +++ b/web/src/views/Index/components/VersionCard.tsx @@ -1,19 +1,48 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button } from 'antd'; import arrowRight from '@/assets/images/index/arrow_right.svg' +import { getVersion, type versionResponse } from '@/api/common' + const GuideCard: React.FC = () => { const { t } = useTranslation(); + const [versionInfo, setVersionInfo] = useState(null); + const [loading, setLoading] = useState(false); + useEffect(() => { + const fetchVersion = async () => { + try { + setLoading(true); + const response = await getVersion(); + setVersionInfo(response); + } catch (error) { + console.error('Failed to fetch version:', error); + } finally { + setLoading(false); + } + }; + + fetchVersion(); + }, []); + return ( -
-
+
+
{ t('index.latestUpdate')} + {versionInfo && ( + + {versionInfo.version} + + )}
- { t('index.latestUpdateDesc')} + {loading ? ( + t('index.loading') + ) : ( + versionInfo?.introduction || t('index.latestUpdateDesc') + )}
-
+ {/*
-
+
*/}
); }; diff --git a/web/src/views/Index/index.tsx b/web/src/views/Index/index.tsx index e6265d3b..3cab0276 100644 --- a/web/src/views/Index/index.tsx +++ b/web/src/views/Index/index.tsx @@ -1,7 +1,7 @@ import { useEffect, useState, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; -import { Row, Col, Space, Button } from 'antd'; +import { Space, Button } from 'antd'; import TopCardList from './components/TopCardList'; import GuideCard from './components/GuideCard'; import VersionCard from './components/VersionCard'; @@ -100,8 +100,8 @@ const Index = () => { return (
- - +
+
{ t('index.spaceTitle' )} @@ -121,8 +121,8 @@ const Index = () => { />
- - +
+
{/* 引导 */}
@@ -130,10 +130,10 @@ const Index = () => {
{/* 快捷操作 */}
- +
- - +
+