From 5007cd9f51384719de8d889fa45afadbafbfa61d Mon Sep 17 00:00:00 2001 From: Eamon <17516219072@163.com> Date: Tue, 25 Aug 2026 18:32:22 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E9=A6=96=E9=A1=B5=E5=B8=83?= =?UTF-8?q?=E5=B1=80=20&=20=E9=A6=96=E9=A1=B5=E7=A7=BB=E5=8A=A8=E7=AB=AF?= =?UTF-8?q?=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/logo.png | Bin 0 -> 5183 bytes src/home/_components/header/common.tsx | 382 ++++++++++++++------ src/home/_components/header/menu-mobile.tsx | 182 ++++++++++ src/home/header.tsx | 85 ++++- src/home/index.tsx | 257 +++++++++++-- src/lib/hooks/useMediaQuery.ts | 36 ++ 6 files changed, 788 insertions(+), 154 deletions(-) create mode 100644 public/logo.png create mode 100644 src/home/_components/header/menu-mobile.tsx create mode 100644 src/lib/hooks/useMediaQuery.ts diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b10583c7a7ba5b6d0d9ca32473b4bbf986caeb7c GIT binary patch literal 5183 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 literal 0 HcmV?d00001 diff --git a/src/home/_components/header/common.tsx b/src/home/_components/header/common.tsx index b4bbe04..1196685 100644 --- a/src/home/_components/header/common.tsx +++ b/src/home/_components/header/common.tsx @@ -1,12 +1,15 @@ +import { ArrowUp, ChevronDown, ChevronUp } from "lucide-react" import { useState } from "react" +import { cn } from "@/lib/utils" import gzh from "../assets/gzh.webp" -import phone from "../assets/phone.webp" -import qq from "../assets/qq.webp" -import top from "../assets/top.webp" -import wx from "../assets/wx.webp" +import phoneImg from "../assets/phone.webp" +import qqImg from "../assets/qq.webp" +import topImg from "../assets/top.webp" +import wxImg from "../assets/wx.webp" export default function Common() { const [activePopup, setActivePopup] = useState<"gzh" | "tel" | null>(null) + const [isExpanded, setIsExpanded] = useState(false) // QQ点击跳转 const handleQQClick = () => { @@ -20,118 +23,277 @@ export default function Common() { } return ( -
- {/* QQ */} -
{ - if (e.key === "Enter" || e.key === " ") { - handleQQClick() - } - }} - role="button" - tabIndex={0} - className="flex flex-col items-center justify-center py-2 text-white border-b border-white/15 hover:bg-white/10 transition-colors cursor-pointer" - > - QQ - QQ + <> + {/* ========== 桌面端 ========== */} +
+
{ + if (e.key === "Enter" || e.key === " ") { + handleQQClick() + } + }} + role="button" + tabIndex={0} + className="flex flex-col items-center justify-center py-2 text-white border-b border-white/15 hover:bg-white/10 transition-colors cursor-pointer" + > + QQ + QQ +
+ +
{ + if (e.key === "Enter" || e.key === " ") { + handleWxClick() + } + }} + role="button" + tabIndex={0} + className="flex flex-col items-center justify-center py-2 text-white border-b border-white/15 hover:bg-white/10 transition-colors cursor-pointer" + > + 微信 + 微信 +
+ +
setActivePopup("gzh")} + onMouseLeave={() => setActivePopup(null)} + onFocus={() => setActivePopup("gzh")} + onBlur={() => setActivePopup(null)} + onKeyDown={e => { + if (e.key === "Enter" || e.key === " ") { + setActivePopup(prev => (prev === "gzh" ? null : "gzh")) + } + }} + > + 公众号 + 公众号 + {activePopup === "gzh" && ( +
+ 公众号二维码 +

+ 实时响应 在线时间 8:30-23:00 +

+

+ 关注公众号可获取免费的到期提醒服务 +

+
+ )} +
+ +
setActivePopup("tel")} + onMouseLeave={() => setActivePopup(null)} + onFocus={() => setActivePopup("tel")} + onBlur={() => setActivePopup(null)} + onKeyDown={e => { + if (e.key === "Enter" || e.key === " ") { + setActivePopup(prev => (prev === "tel" ? null : "tel")) + } + }} + > + 电话 + 电话 + {activePopup === "tel" && ( +
+

400 800 9925

+

实时接通

+

+ 电话值班时间 8:30-23:00 +

+
+ )} +
+ +
- {/* 微信 */} -
{ - if (e.key === "Enter" || e.key === " ") { - handleWxClick() - } - }} - role="button" - tabIndex={0} - className="flex flex-col items-center justify-center py-2 text-white border-b border-white/15 hover:bg-white/10 transition-colors cursor-pointer" - > - 微信 - 微信 -
- - {/* 公众号 */} -
setActivePopup("gzh")} - onMouseLeave={() => setActivePopup(null)} - onFocus={() => setActivePopup("gzh")} - onBlur={() => setActivePopup(null)} - onKeyDown={e => { - if (e.key === "Enter" || e.key === " ") { - // toggle popup with keyboard - setActivePopup(prev => (prev === "gzh" ? null : "gzh")) - } - }} - > - 公众号 - 公众号 - - {/* 公众号悬浮弹窗 */} - {activePopup === "gzh" && ( -
- 公众号二维码 -

- 实时响应 在线时间 8:30-23:00 -

-

- 关注公众号可获取免费的到期提醒服务 -

+ {/* ========== 移动端:底部折叠面板 ========== */} +
+ {/* 折叠面板 */} +
+ {/* 拖拽条*/} +
setIsExpanded(!isExpanded)} + className="flex flex-col items-center justify-center py-2.5 cursor-pointer hover:bg-gray-50 transition-colors rounded-t-2xl" + > +
+
+ + {isExpanded ? "收起" : "联系我们"} + + {isExpanded ? ( + + ) : ( + + )} +
- )} -
- {/* 电话 */} -
setActivePopup("tel")} - onMouseLeave={() => setActivePopup(null)} - onFocus={() => setActivePopup("tel")} - onBlur={() => setActivePopup(null)} - onKeyDown={e => { - if (e.key === "Enter" || e.key === " ") { - setActivePopup(prev => (prev === "tel" ? null : "tel")) - } - }} - > - 电话 - 电话 + {/* 展开内容 */} +
+
+ {/* QQ */} +
+
+ QQ +
+
+

QQ客服

+

点击立即咨询

+
+
- {/* 电话悬浮弹窗 */} - {activePopup === "tel" && ( -
-

400 800 9925

-

实时接通

-

- 电话值班时间 8:30-23:00 -

+ {/* 微信 */} +
+
+ 微信 +
+
+

微信客服

+

点击立即咨询

+
+
+ + {/* 公众号 */} +
+ setActivePopup(activePopup === "gzh" ? null : "gzh") + } + className="flex items-center gap-4 p-3 bg-purple-50 rounded-xl hover:bg-purple-100 transition-colors cursor-pointer" + > +
+ 公众号 +
+
+

公众号

+

点击查看二维码

+
+ +
+ + {/* 公众号二维码展开 */} + {activePopup === "gzh" && ( +
+ 公众号二维码 +

+ 实时响应 在线时间 8:30-23:00 +

+

+ 关注公众号可获取免费的到期提醒服务 +

+
+ )} + + {/* 电话 */} +
+ setActivePopup(activePopup === "tel" ? null : "tel") + } + className="flex items-center gap-4 p-3 bg-orange-50 rounded-xl hover:bg-orange-100 transition-colors cursor-pointer" + > +
+ 电话 +
+
+

电话客服

+

点击查看号码

+
+ +
+ + {/* 电话展开 */} + {activePopup === "tel" && ( +
+

+ 400 800 9925 +

+

实时接通

+

+ 电话值班时间 8:30-23:00 +

+
+ )} + + {/* 回到顶部 */} + +
- )} +
- - {/* 回到顶部 */} - -
+ ) } diff --git a/src/home/_components/header/menu-mobile.tsx b/src/home/_components/header/menu-mobile.tsx new file mode 100644 index 0000000..18fe543 --- /dev/null +++ b/src/home/_components/header/menu-mobile.tsx @@ -0,0 +1,182 @@ +import { ChevronDown, X } from "lucide-react" +import { useState } from "react" +import { NavLink, useNavigate } from "react-router-dom" +import { logout } from "@/api/auth" +import { cn } from "@/lib/utils" + +export default function MobileMenu({ + authed, + onClose, +}: { + authed: boolean + onClose: () => void +}) { + const navigate = useNavigate() + const [productOpen, setProductOpen] = useState(false) + + const handleLogout = async () => { + await logout() + } + + const go = (path: string) => { + onClose() + navigate(path) + } + + const openExternal = (url: string) => { + onClose() + window.open(url, "_blank") + } + + return ( +
+ +
+ + {/* 菜单项 */} +
+ + 首页 + + + {/* 二级菜单 */} +
+ + {productOpen && ( +
+ go("/product")}>动静态IP + openExternal("https://lanhuip.com")}> + 新HTTP/S5 + + go("/product/http")}>HTTP/S5 + go("/product/routeros")}> + 软路由成本价 + + openExternal("http://vps.juip.com")}> + 动态Vps + +
+ )} +
+ + + IP线路表 + + + 软件下载 + + + 帮助/教程 + + + 下载客户端 + + + {authed && ( + <> + + + + )} +
+
+
+ ) +} + +const MobileNavLink = ({ + to, + onNavigate, + children, +}: { + to: string + onNavigate: () => void + children: React.ReactNode +}) => ( + + cn( + "w-full flex items-center px-3 py-2.5 rounded-lg text-sm transition-colors", + isActive + ? "bg-blue-50 text-blue-600 font-medium" + : "text-gray-700 hover:bg-gray-50", + ) + } + > + {children} + +) + +const SubItem = ({ + onClick, + children, +}: { + onClick: () => void + children: React.ReactNode +}) => ( + +) diff --git a/src/home/header.tsx b/src/home/header.tsx index a287846..4f402bd 100644 --- a/src/home/header.tsx +++ b/src/home/header.tsx @@ -1,11 +1,17 @@ -import { useRef, useState, useSyncExternalStore } from "react" -import { NavLink } from "react-router-dom" +import { Menu, User } from "lucide-react" +import { useEffect, useRef, useState, useSyncExternalStore } from "react" +import { NavLink, useNavigate } from "react-router-dom" +import { AUTH_EVENT, isAuthed } from "@/api/token" import ProductDropdown from "@/components/productDropdown" import ProfileOrLogin from "@/components/profileOrLogin" import Wrap from "@/components/wrap" +import { useMediaQuery } from "@/lib/hooks/useMediaQuery" import { cn } from "@/lib/utils" +import MobileMenu from "./_components/header/menu-mobile" export default function Header() { + const navigate = useNavigate() + const isWideDesktop = useMediaQuery("(min-width: 80rem)") const scroll = useSyncExternalStore( callback => { window.addEventListener("scroll", callback) @@ -22,6 +28,20 @@ export default function Header() { const handleEnter = () => setOpenMenu(true) const handleLeave = () => setOpenMenu(false) + // 移动端状态 + const [mobileMenu, setMobileMenu] = useState(false) + const [authed, setAuthed] = useState(isAuthed) + + useEffect(() => { + const update = () => setAuthed(isAuthed()) + window.addEventListener("focus", update) + window.addEventListener(AUTH_EVENT, update) + return () => { + window.removeEventListener("focus", update) + window.removeEventListener(AUTH_EVENT, update) + } + }, []) + return (
- -
+ + {/* 移动端左侧菜单栏 */} + {mobileMenu && ( + setMobileMenu(false)} /> + )}
) } @@ -147,15 +214,11 @@ const MenuItem = ({
{children} diff --git a/src/home/index.tsx b/src/home/index.tsx index 6fb2809..6920201 100644 --- a/src/home/index.tsx +++ b/src/home/index.tsx @@ -1,6 +1,9 @@ +import { ChevronLeft, ChevronRight } from "lucide-react" import type { ReactNode } from "react" +import { useRef, useState } from "react" import { useNavigate } from "react-router-dom" import Wrap from "@/components/wrap" +import { useIsMobile } from "@/lib/hooks/useMediaQuery" import banner from "./_assets/banner.webp" import s1 from "./_assets/s1.webp" import s2 from "./_assets/s2.webp" @@ -22,6 +25,35 @@ export default function HomePage() { const handleFreeTrial = () => { navigate("/login") } + + const scrollContainerRef = useRef(null) + const [showLeftArrow, setShowLeftArrow] = useState(false) + const [showRightArrow, setShowRightArrow] = useState(true) + const isMobile = useIsMobile() + + const scroll = (direction: "left" | "right") => { + if (!scrollContainerRef.current) return + const container = scrollContainerRef.current + const scrollAmount = container.clientWidth / 2 + const newScrollLeft = + direction === "left" + ? container.scrollLeft - scrollAmount + : container.scrollLeft + scrollAmount + container.scrollTo({ + left: newScrollLeft, + behavior: "smooth", + }) + } + + const handleScroll = () => { + if (!scrollContainerRef.current) return + const container = scrollContainerRef.current + setShowLeftArrow(container.scrollLeft > 10) + setShowRightArrow( + container.scrollLeft < container.scrollWidth - container.clientWidth - 10, + ) + } + return ( <> @@ -37,7 +69,7 @@ export default function HomePage() { /> -

+

聚集优质IP产品,尽在聚IP

@@ -68,17 +100,69 @@ export default function HomePage() {

-

+

国内三大运营商正规授权,千万级代理IP资源,权威与品质的双重保障

-
    - - - - - - -
+
+ {/* 左箭头 - 移动端显示 */} + {isMobile && showLeftArrow && ( + + )} + + {/* 右箭头 - 移动端显示 */} + {isMobile && showRightArrow && ( + + )} + + {/* 左右渐变遮罩 - 移动端显示 */} + {isMobile && ( + <> +
+
+ + )} + + {/* 滚动列表 */} +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+

@@ -92,7 +176,7 @@ export default function HomePage() { 产品

-
+

海量地区,遍布全国的优质IP代理资源

@@ -100,36 +184,37 @@ export default function HomePage() { 遍布全国的代理服务器节点为用户提供智能帮助

+
-
    -
  • -

    +

      +
    • +

      10万+

      -

      全国节点覆盖

      +

      全国节点覆盖

    • -
    • -

      +

    • +

      100万+

      -

      全国独享IP池

      +

      全国独享IP池

    • -
    • -

      +

    • +

      10万+

      -

      全国实时在线IP数

      +

      全国实时在线IP数

    • -
    • -

      +

    • +

      99%

      -

      IP可用率

      +

      IP可用率

-
+

满足您的多种业务需求

@@ -138,7 +223,65 @@ export default function HomePage() {

-
+ {/* ========== 移动端 ========== */} +
+
+

+ 爬虫抓取 +

+

+ 3000W高匿名IP,可以轻松抓取企业信息、分类信息、房地产信息、电商信息 +

+
+ +
+

+ 数据采集 +

+

+ 用于大数据采集的多样化利用场景,快速采集SEO数据优化、金融理财、地域信息数据 +

+
+ +
+

+ 营销推广 +

+

+ 论坛发帖、问答推广、网上购物、投票点赞等,根据渠道操作要求,定制IP完成营销推广 +

+
+ +
+

+ 电子商务 +

+

+ 可帮助用户切换网络,模拟多个独立账号,有助于用户参与限时促销活动 +

+
+ +
+

+ 游戏工作室 +

+

+ 保证IP稳定,不掉线,在有效时长内完成游戏试玩、升级、完成游戏工作室操作要求 +

+
+ +
+

+ 自媒体运营 +

+

+ 可帮助自媒体运营者维护账号独立性,为每个账号赋予独立IP,避免IP地址关联 +

+
+
+ + {/* ========== 桌面端 ========== */} +
图片
@@ -197,16 +340,65 @@ export default function HomePage() {
-
+

为您提供质量保障

- 国内三大运营商正规授权,千万级代理IP资源,权威与品质的双重保障 + 国内三大运营商正规授权,权威与品质的双重保障

-
+ {/* 移动端 */} +
+ {/* 售前咨询 */} +
+
+ 售前咨询 +
+
+

+ 售前咨询 +

+

+ 注册即享新人福利,专业的客户经理,多维度为您提供在线代理相关答疑 +

+
+
+ + {/* 售中服务 */} +
+
+ 售中服务 +
+
+

+ 售中服务 +

+

+ 全国100万+动态IP代理资源免费测试,先测后买让您安心使用 +

+
+
+ + {/* 售后保障 */} +
+
+ 售后保障 +
+
+

+ 售后保障 +

+

+ 1V1专属售后答疑,技术团队7*24小时在线支持提供专属解决方案 +

+
+
+
+ + {/* 桌面端 */} +
售前咨询 @@ -215,8 +407,7 @@ export default function HomePage() { 售前咨询

- 注册即享新人福利,专业的客户经理,多维度为您提供在线代理相关 - 答疑 + 注册即享新人福利,专业的客户经理,多维度为您提供在线代理相关答疑

@@ -368,13 +559,13 @@ function PageSection(props: { title: string; children: ReactNode }) { function ProductTypeCard(props: { icon: string; title: string }) { return ( -
  • +
    {props.title}

    {props.title}

    -
  • +
    ) } diff --git a/src/lib/hooks/useMediaQuery.ts b/src/lib/hooks/useMediaQuery.ts new file mode 100644 index 0000000..fd7c032 --- /dev/null +++ b/src/lib/hooks/useMediaQuery.ts @@ -0,0 +1,36 @@ +import { useSyncExternalStore } from "react" + +const subscribeMediaQuery = (query: string) => (callback: () => void) => { + if (typeof window === "undefined") return () => {} + const mql = window.matchMedia(query) + mql.addEventListener("change", callback) + return () => mql.removeEventListener("change", callback) +} + +const getMediaQuerySnapshot = (query: string) => () => { + if (typeof window === "undefined") return false + return window.matchMedia(query).matches +} + +export function useMediaQuery(query: string): boolean { + return useSyncExternalStore( + subscribeMediaQuery(query), + getMediaQuerySnapshot(query), + () => false, + ) +} + +// 移动端:< 768px(对应 Tailwind md 断点以下) +export function useIsMobile(): boolean { + return useMediaQuery("(max-width: 47.999rem)") +} + +// 桌面端:>= 768px +export function useIsDesktop(): boolean { + return useMediaQuery("(min-width: 48rem)") +} + +// 窄桌面/平板横屏:< 1280px(对应 Tailwind xl 断点以下) +export function useIsNarrowDesktop(): boolean { + return useMediaQuery("(max-width: 79.999rem)") +}