diff --git a/frontend/app/chat/page.tsx b/frontend/app/chat/page.tsx index 8ec74c9..fb25834 100644 --- a/frontend/app/chat/page.tsx +++ b/frontend/app/chat/page.tsx @@ -4,10 +4,11 @@ import { useEffect, useState, useSyncExternalStore } from "react"; import { ChatWidget } from "@/components/visitor/ChatWidget"; import { FloatingButton } from "@/components/visitor/FloatingButton"; import { isChatEmbedMode } from "@/lib/chat-embed"; +import { getOrCreateVisitorId } from "@/lib/visitor-id"; /** * 访客聊天页面 - * - 独立打开:右下角浮动按钮 + 聊天小窗 + * - 独立打开:默认展开聊天小窗(仍保留右下角浮动按钮) * - iframe / ?embed=1:直接铺满,供宿主站点一次点击打开 iframe 即可使用 */ export default function ChatPage() { @@ -19,17 +20,16 @@ export default function ChatPage() { () => false ); - // 初始化访客 ID(使用 localStorage 保持连续性) useEffect(() => { - let stored = window.localStorage.getItem("visitor_id"); - if (!stored) { - stored = `${Date.now()}${Math.floor(Math.random() * 100000)}`; - window.localStorage.setItem("visitor_id", stored); - } - const parsed = Number.parseInt(stored, 10); - setVisitorId(Number.isNaN(parsed) ? null : parsed); + setVisitorId(getOrCreateVisitorId()); }, []); + useEffect(() => { + if (!embedded && visitorId !== null) { + setIsOpen(true); + } + }, [embedded, visitorId]); + const handleToggle = () => { setIsOpen((prev) => !prev); }; diff --git a/frontend/components/layout/Footer.tsx b/frontend/components/layout/Footer.tsx index 8845ae0..248eb6a 100644 --- a/frontend/components/layout/Footer.tsx +++ b/frontend/components/layout/Footer.tsx @@ -5,10 +5,15 @@ import { Github, Mail, MessageSquare, Users } from "lucide-react"; import { websiteConfig } from "@/lib/website-config"; import { useI18n } from "@/lib/i18n/provider"; +interface FooterProps { + /** 首页打开右下角客服小窗;不传则回退为链到首页并带 openChat 参数 */ + onOpenChat?: () => void; +} + /** * 官网底部页脚 */ -export function Footer() { +export function Footer({ onOpenChat }: FooterProps) { const { t } = useI18n(); return ( @@ -145,10 +150,20 @@ export function Footer() {
  • - - - {t("footer.onlineChat")} - + + {onOpenChat ? ( + + ) : ( + + {t("footer.onlineChat")} + + )}
  • diff --git a/frontend/components/marketing/HomePageClient.tsx b/frontend/components/marketing/HomePageClient.tsx index a870fa7..99e7f7a 100644 --- a/frontend/components/marketing/HomePageClient.tsx +++ b/frontend/components/marketing/HomePageClient.tsx @@ -28,6 +28,7 @@ import { Header } from "@/components/layout/Header"; import { Footer } from "@/components/layout/Footer"; import { FadeIn, FadeInStagger, FadeInItem } from "@/components/ui/fade-in"; import { websiteConfig } from "@/lib/website-config"; +import { getOrCreateVisitorId } from "@/lib/visitor-id"; import { stats } from "@/lib/stats-config"; import type { I18nKey } from "@/lib/i18n/dict"; import { useI18n } from "@/lib/i18n/provider"; @@ -149,15 +150,20 @@ export function HomePageClient() { const [activeScreenshot, setActiveScreenshot] = useState(0); useEffect(() => { - let stored = window.localStorage.getItem("visitor_id"); - if (!stored) { - stored = `${Date.now()}${Math.floor(Math.random() * 100000)}`; - window.localStorage.setItem("visitor_id", stored); - } - const parsed = Number.parseInt(stored, 10); - setVisitorId(Number.isNaN(parsed) ? null : parsed); + setVisitorId(getOrCreateVisitorId()); }, []); + useEffect(() => { + const params = new URLSearchParams(window.location.search); + if (params.get("openChat") === "1") { + handleOpenChat(); + params.delete("openChat"); + const next = `${window.location.pathname}${params.toString() ? `?${params}` : ""}`; + window.history.replaceState(null, "", next); + } + // eslint-disable-next-line react-hooks/exhaustive-deps -- 仅响应 URL 参数 + }, [visitorId]); + const handleToggleChat = () => setIsChatOpen((prev) => !prev); const handleOpenChat = () => { @@ -469,7 +475,7 @@ export function HomePageClient() { -