import { CaretDownFilled, DownOutlined, GithubOutlined, LinkOutlined, MenuOutlined, WechatOutlined, } from '@ant-design/icons'; import { Alert, Button, Dropdown, Menu, Modal, Popover } from 'antd'; import cx from 'classnames'; import { FormattedMessage, Link, useLocale, useSiteData } from 'dumi'; import { get, map, size } from 'lodash-es'; import React, {useEffect, useMemo, useState} from 'react'; import { useMedia } from 'react-use'; import { getPurePathname } from '../../utils/location'; import { ic, icWithLocale } from '../hooks'; import { INav, Navs } from './Navs'; import { Products } from './Products'; import { Search } from './Search'; import { findVersion } from './utils'; import type { IC } from '../../types'; import { Assistant } from '@petercatai/assistant'; import '@petercatai/assistant/style'; import { useLocation } from 'react-router-dom'; import { determineUserType } from '../../utils/user'; import styles from './index.module.less'; export type HeaderProps = { /** 网站 header 标题 */ title?: string; /** 网站 的 meta 标签 */ metas?: { title: IC | string; description: IC | string; }; /** 官网子包所在路径 */ sitePackagePath?: string; pathPrefix?: string; /** 子标题 */ subTitle?: React.ReactNode; /** 子标题的链接 */ subTitleHref?: string; /** 文档和演示的菜单数据 */ navs?: INav[]; /** 是否显示搜索框 */ showSearch?: boolean; /** 是否显示 Github 图标 */ showGithubCorner?: boolean; /** 是否显示 Github Star */ showGithubStar?: boolean; /** 是否显示切换语言选项 */ showLanguageSwitcher?: boolean; /** * 国内镜像相关的信息 */ internalSite?: { url: string; name: object; }; /** 切换语言的回调 */ onLanguageChange?: (language: string) => void; /** 是否二维码 */ showWxQrcode?: boolean; /** 自定义 logo */ logo?: { img?: React.ReactNode; link?: string; }; siteUrl?: string; /** github 仓库地址 */ githubUrl?: string; /** 默认语言 */ defaultLanguage?: 'zh' | 'en'; /** 自定义 Link */ Link?: React.ComponentType; /** 底色是否透明 */ transparent?: boolean; /** 是否首页模式 */ isHomePage?: boolean; /** 是否是AntV官网 */ isAntVSite?: boolean; /** AntV root 域名,直接用主题的可不传 */ rootDomain?: string; /** 是否显示 AntV 产品卡片 */ showAntVProductsCard?: boolean; /** * 当前版本 */ version?: string; /** 展示版本切换 */ versions?: { [key: string]: string }; /** 展示周边生态 */ ecosystems?: Array<{ name: Record; url: string; }>; /** 头部搜索框配置 */ searchOptions?: { docsearchOptions: { versionV3: boolean; apiKey: string; indexName: string; appId: string; sort?: string[]; }; }; /** 是否开启用户反馈功能 */ feedback?: boolean; /** petercat 配置 */ petercat?: { token: string; show: boolean; }; /** 是否显示 links 研发小蜜 */ links?: boolean; /** 页面头部公告 */ announcement?: { title: IC; icon: string; link: { url: string; text: IC; }; }; showWeavefox: { internal: boolean | string; public: boolean | string; }; isInternalUser: boolean; }; function redirectChinaMirror(chinaMirrorOrigin: string) { window.location.href = window.location.href.replace(window.location.origin, chinaMirrorOrigin); } const ANNOUNCEMENT_LOCALSTORAGE_ID = 'ANNOUNCEMENT_LOCALSTORAGE_ID'; /** * 头部菜单 */ const HeaderComponent: React.FC = ({ subTitle = '', navs = [], showSearch = true, showGithubCorner = true, showAntVProductsCard = true, showLanguageSwitcher = true, showWeavefox = {}, isInternalUser, logo, onLanguageChange, // 默认就使用 AntV 的公众号 showWxQrcode = true, siteUrl, githubUrl = 'https://github.com/antvis', defaultLanguage, transparent, isHomePage, isAntVSite = false, rootDomain = '', version, versions, internalSite, ecosystems, announcement, }) => { const isAntVHome = isAntVSite && isHomePage; // 是否为AntV官网首页 const [bannerVisible, setBannerVisible] = useState(false); const showChinaMirror: boolean = !!internalSite; const chinaMirrorUrl: string = get(internalSite, 'url'); const [chinaMirrorHintVisible, updateChinaMirrorHintVisible] = useState(false); const locale = useLocale(); const lang = locale.id; useEffect(() => { const timeout = setTimeout(() => { if ( showChinaMirror && lang === 'zh' && !localStorage.getItem('china-mirror-no-more-hint') && window.location.host.includes('antv.vision') ) { updateChinaMirrorHintVisible(true); } }, 5000); return () => { clearTimeout(timeout); }; }); const announcementTitle = icWithLocale(announcement?.title, lang); const announcementLinkTitle = icWithLocale(announcement?.link?.text, lang); useEffect(() => { setBannerVisible(!!announcementTitle && localStorage.getItem(ANNOUNCEMENT_LOCALSTORAGE_ID) !== 'true'); }, [announcementTitle]); function onBannerClose() { localStorage.setItem(ANNOUNCEMENT_LOCALSTORAGE_ID, 'true'); setBannerVisible(false); } const [productMenuVisible, setProductMenuVisible] = useState(false); let productMenuHovering = false; const onProductMouseEnter = (e: React.MouseEvent) => { productMenuHovering = true; e.persist(); setTimeout(() => { if (e.target instanceof Element && e.target.matches(':hover')) { setProductMenuVisible(true); } }, 200); }; const onProductMouseLeave = (e: React.MouseEvent) => { e.persist(); productMenuHovering = false; setTimeout(() => { if (productMenuHovering) { return; } setProductMenuVisible(false); }, 200); }; const onToggleProductMenuVisible = () => { setProductMenuVisible(!productMenuVisible); }; const [popupMenuVisible, setPopupMenuVisible] = useState(false); const onTogglePopupMenuVisible = () => { setPopupMenuVisible(!popupMenuVisible); }; const { img, link } = { img: ( logo ), link: '', ...logo, }; const { pathname } = useLocation(); useEffect(() => { if (popupMenuVisible) { setPopupMenuVisible(false); } }, [pathname]); // 移动端下弹出菜单时,禁止页面滚动 useEffect(() => { if (popupMenuVisible) { document.documentElement!.style.overflow = 'hidden'; } else { document.documentElement!.style.overflow = ''; } return () => { document.documentElement!.style.overflow = ''; }; }, [popupMenuVisible]); const isWide = useMedia('(min-width: 767.99px)', true); const menuIcon = !isWide ? : null; const productItemProps = isWide ? { onMouseEnter: onProductMouseEnter, onMouseLeave: onProductMouseLeave, onClick: onToggleProductMenuVisible, } : { onClick: onToggleProductMenuVisible, }; const handleSwitchLanguage = () => { onLanguageChange?.(lang); }; const weavefox = useMemo(() => { const configKey = isInternalUser ? 'internal' : 'public'; const configValue = showWeavefox[configKey]; if (!configValue) return <>; const defaultLink = isInternalUser ? 'https://weavefox.antgroup-inc.cn/agent/@huiyu.zjt/AntV' : 'https://weavefox.alipay.com/agent/@ufox-b8tydq-0758/202505AP7vfl00422922'; const href = typeof configValue === 'string' ? configValue : defaultLink; return (
  • AI生码
  • ); }, [isInternalUser, showWeavefox]); const menu = (
      { /** 最左侧的菜单,一般是 教程、API、示例,或者其他自定义,有配置文件中的 `navs` 决定 */ size(navs) ? : null } {weavefox} { /** 生态产品 */ size(ecosystems) ? (
    • {map(ecosystems, ({ url, name: ecosystemName }) => ( {ecosystemName?.[lang]} ))} } > {}
    • ) : null } {showChinaMirror && isWide ? (
      🇨🇳 AntV 系列网站部署在 gh-pages 上,若访问速度不佳,可以前往国内镜像站点。
      } open={chinaMirrorHintVisible} placement="bottomRight" align={{ offset: [-12, -16], }} >
    • { e.preventDefault(); redirectChinaMirror(chinaMirrorUrl); }} > {ic(get(internalSite, 'name'))} {!isAntVHome && }
    • ) : null} {showChinaMirror && !isWide && ( { updateChinaMirrorHintVisible(false); }} onOk={() => redirectChinaMirror(chinaMirrorUrl)} cancelButtonProps={{ onClick: () => { localStorage.setItem('china-mirror-no-more-hint', Date.now().toString()); updateChinaMirrorHintVisible(false); }, }} >
      🇨🇳 AntV 系列网站部署在 gh-pages 上,若访问速度不佳,可以前往 { e.preventDefault(); window.location.href = chinaMirrorUrl; }} className={styles.remindHref} > {ic(get(internalSite, 'name'))} 站点。
      )} { /** 产品列表 */ showAntVProductsCard && (
    • {} {!isAntVHome ? ( antv logo arrow ) : ( )}
    • ) } { /** 版本列表 */ versions && (
    • ({ label: ( {version} ), key: version, })), selectable: true, defaultSelectedKeys: [findVersion(version, Object.keys(versions))], }} > {findVersion(version, Object.keys(versions))}
    • ) } { /** 切换网站语言 */ showLanguageSwitcher && (
    • ) } { /** 微信公众号 */ showWxQrcode && (
    • } title="微信扫一扫关注" styles={{ body: { padding: 2, }, root: { width: 128, height: 128, }, }} >
    • ) } { /** GitHub icon */ showGithubCorner && (
    • ) }
    ); return (
    {bannerVisible && announcementTitle && ( {announcement?.icon && }
    {announcementTitle}
    {announcementLinkTitle && {announcementLinkTitle}} } type="info" banner closable showIcon={false} onClose={onBannerClose} /> )}

    {img}

    {!isAntVHome && subTitle && ( <>

    {subTitle}

    )} {showSearch && !isAntVHome && }
    ); }; const Header: React.FC> = (props) => { const { themeConfig } = useSiteData() as any; const { title, siteUrl, githubUrl, isAntVSite, subTitleHref, internalSite, showSearch, showGithubCorner, showGithubStars, showLanguageSwitcher, showWxQrcode, defaultLanguage, showAntVProductsCard, version, versions, ecosystems, navs, docsearchOptions, announcement, petercat, links, showWeavefox, } = themeConfig; const searchOptions = { docsearchOptions, }; const { pathname } = useLocation(); const isHomePage = ['/', ''].includes(getPurePathname(pathname)); const lang = useLocale().id; const [isInternalUser, setIsInternalUser] = useState(undefined); const headerProps = { subTitle: icWithLocale(title, lang), subTitleHref, githubUrl, isAntVSite, siteUrl, internalSite, showSearch, showGithubCorner, showGithubStars, showLanguageSwitcher, showWxQrcode, defaultLanguage, showAntVProductsCard, version, versions, ecosystems, navs, searchOptions, isHomePage, transparent: isHomePage && isAntVSite, announcement, petercat, showWeavefox, isInternalUser, }; const isPetercatShow = petercat?.show; useEffect(() => { const checkUserType = async () => { const result = await determineUserType(); setIsInternalUser(result); }; checkUserType(); }, []); useEffect(() => { let script: HTMLScriptElement | null = null; if (isInternalUser && links) { script = document.createElement('script'); script.src = 'https://links.alipay.com/widgetInit/67a96a296b6fa80490bdf892'; script.async = true; document.body.appendChild(script); } return () => { if (script) { document.body.removeChild(script); } }; }, [isInternalUser]); return ( <> {isPetercatShow && isInternalUser === false && ( )} ); }; export default Header;