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"))
- }
- }}
- >
-
-

-
- 实时响应 在线时间 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 */}
+
+
+

+
+
+
- {/* 电话悬浮弹窗 */}
- {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 (
+
+
+
+
+ {/* 头部:logo + 关闭 */}
+
+
+
+
+ 聚IP
+
+
+
+
+
+ {/* 菜单项 */}
+
+
+ 首页
+
+
+ {/* 二级菜单 */}
+
+
+ {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}
-
+
)
}
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)")
+}