mirror of
https://github.com/2930134478/AI-CS.git
synced 2026-06-15 00:44:30 +08:00
修复嵌入双按钮问题
This commit is contained in:
@@ -197,7 +197,8 @@ npm run dev
|
||||
|
||||
## 集成访客小窗到你的网站(iframe)
|
||||
|
||||
把下面代码放到你网站的 `</body>` 前,核心是把 `src` 指向你自己的部署域名的 `/chat`:
|
||||
把下面代码放到你网站的 `</body>` 前,核心是把 `src` 指向你自己的部署域名的 `/chat`。
|
||||
**说明**:`/chat` 在 iframe 内会自动进入嵌入模式(也可显式加 `?embed=1`),只显示聊天界面,不会出现第二个浮动按钮;宿主站点保留外层「打开/关闭」按钮即可,**点一次**即可对话。
|
||||
|
||||
```html
|
||||
<div id="ai-cs-widget" style="position: fixed; bottom: 20px; right: 20px; z-index: 9999;">
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useSyncExternalStore } from "react";
|
||||
import { ChatWidget } from "@/components/visitor/ChatWidget";
|
||||
import { FloatingButton } from "@/components/visitor/FloatingButton";
|
||||
import { isChatEmbedMode } from "@/lib/chat-embed";
|
||||
|
||||
/**
|
||||
* 访客聊天页面
|
||||
* 使用小窗插件形式,显示浮动按钮和聊天小窗
|
||||
* - 独立打开:右下角浮动按钮 + 聊天小窗
|
||||
* - iframe / ?embed=1:直接铺满,供宿主站点一次点击打开 iframe 即可使用
|
||||
*/
|
||||
export default function ChatPage() {
|
||||
const [visitorId, setVisitorId] = useState<number | null>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const embedded = useSyncExternalStore(
|
||||
() => () => {},
|
||||
() => isChatEmbedMode(),
|
||||
() => false
|
||||
);
|
||||
|
||||
// 初始化访客 ID(使用 localStorage 保持连续性)
|
||||
useEffect(() => {
|
||||
@@ -27,19 +34,32 @@ export default function ChatPage() {
|
||||
setIsOpen((prev) => !prev);
|
||||
};
|
||||
|
||||
const loadingShell = embedded ? "h-[100dvh] w-full" : "min-h-screen";
|
||||
|
||||
if (visitorId === null) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen bg-muted/30 text-muted-foreground">
|
||||
<div className={`flex items-center justify-center ${loadingShell} bg-muted/30 text-muted-foreground`}>
|
||||
正在初始化...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (embedded) {
|
||||
return (
|
||||
<div className="h-[100dvh] w-full overflow-hidden bg-white">
|
||||
<ChatWidget
|
||||
visitorId={visitorId}
|
||||
isOpen
|
||||
embedded
|
||||
onToggle={() => {}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 浮动按钮 */}
|
||||
<FloatingButton onClick={handleToggle} isOpen={isOpen} />
|
||||
{/* 聊天小窗 */}
|
||||
{isOpen && (
|
||||
<ChatWidget visitorId={visitorId} isOpen={isOpen} onToggle={handleToggle} />
|
||||
)}
|
||||
|
||||
@@ -45,6 +45,8 @@ import { LanguageSwitcher } from "@/components/i18n/LanguageSwitcher";
|
||||
interface ChatWidgetProps {
|
||||
visitorId: number;
|
||||
isOpen: boolean;
|
||||
/** iframe 嵌入:铺满容器,不使用 fixed + portal */
|
||||
embedded?: boolean;
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
@@ -94,7 +96,12 @@ const CHAT_WIDGET_PANEL_MAX_W =
|
||||
* 聊天小窗组件
|
||||
* 提供小窗形式的聊天界面,支持展开/收起
|
||||
*/
|
||||
export function ChatWidget({ visitorId, isOpen, onToggle }: ChatWidgetProps) {
|
||||
export function ChatWidget({
|
||||
visitorId,
|
||||
isOpen,
|
||||
embedded = false,
|
||||
onToggle,
|
||||
}: ChatWidgetProps) {
|
||||
const { t } = useI18n();
|
||||
const WEB_SEARCH_PREF_KEY = "visitor_widget_need_web_search";
|
||||
// 数据分析:每次由关→开上报一次小窗打开(供后台「小窗打开次数」统计)
|
||||
@@ -710,20 +717,19 @@ export function ChatWidget({ visitorId, isOpen, onToggle }: ChatWidgetProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 挂到 body,避免页面内祖先的 transform/filter/backdrop-filter 在 Chrome 等浏览器中
|
||||
// 成为 fixed 定位的包含块,导致小窗错位到视口中上部而右下角按钮仍正常。
|
||||
if (typeof document === "undefined") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
const panel = (
|
||||
<Card
|
||||
className={cn(
|
||||
"fixed bottom-20 right-4 sm:bottom-24 sm:right-6 flex flex-col shadow-[0_24px_60px_-24px_rgba(2,6,23,0.35)] z-40 border border-slate-200 overflow-hidden rounded-2xl bg-white text-slate-900 ring-1 ring-slate-200/80",
|
||||
CHAT_WIDGET_PANEL_MAX_W,
|
||||
CHAT_WIDGET_PANEL_WIDTH,
|
||||
CHAT_WIDGET_PANEL_MAX_H,
|
||||
CHAT_WIDGET_PANEL_H
|
||||
"flex flex-col overflow-hidden bg-white text-slate-900",
|
||||
embedded
|
||||
? "h-full w-full min-h-0 rounded-none border-0 shadow-none ring-0"
|
||||
: cn(
|
||||
"fixed bottom-20 right-4 sm:bottom-24 sm:right-6 shadow-[0_24px_60px_-24px_rgba(2,6,23,0.35)] z-40 border border-slate-200 rounded-2xl ring-1 ring-slate-200/80",
|
||||
CHAT_WIDGET_PANEL_MAX_W,
|
||||
CHAT_WIDGET_PANEL_WIDTH,
|
||||
CHAT_WIDGET_PANEL_MAX_H,
|
||||
CHAT_WIDGET_PANEL_H
|
||||
)
|
||||
)}
|
||||
>
|
||||
{/* 头部:回归品牌蓝色系,保持轻量与一致 */}
|
||||
@@ -999,8 +1005,19 @@ export function ChatWidget({ visitorId, isOpen, onToggle }: ChatWidgetProps) {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Card>,
|
||||
document.body
|
||||
</Card>
|
||||
);
|
||||
|
||||
if (embedded) {
|
||||
return panel;
|
||||
}
|
||||
|
||||
// 挂到 body,避免页面内祖先的 transform/filter/backdrop-filter 在 Chrome 等浏览器中
|
||||
// 成为 fixed 定位的包含块,导致小窗错位到视口中上部而右下角按钮仍正常。
|
||||
if (typeof document === "undefined") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createPortal(panel, document.body);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 访客 /chat 是否处于「被宿主 iframe 嵌入」模式。
|
||||
* 嵌入时不展示页内 FloatingButton,直接铺满 iframe 显示 ChatWidget。
|
||||
*/
|
||||
export function isChatEmbedMode(): boolean {
|
||||
if (typeof window === "undefined") return false;
|
||||
|
||||
const q = new URLSearchParams(window.location.search);
|
||||
if (q.get("embed") === "1" || q.get("embed") === "true") return true;
|
||||
|
||||
try {
|
||||
return window.self !== window.top;
|
||||
} catch {
|
||||
// 跨域 iframe 访问 top 会抛错,说明正在被外部站点嵌入
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,10 @@
|
||||
function createChatWindow() {
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.id = 'ai-cs-widget-iframe';
|
||||
iframe.src = config.chatPageUrl || `${config.apiUrl.replace('/api', '')}/chat`;
|
||||
const baseChat =
|
||||
config.chatPageUrl || `${config.apiUrl.replace('/api', '')}/chat`;
|
||||
const sep = baseChat.includes('?') ? '&' : '?';
|
||||
iframe.src = `${baseChat}${sep}embed=1`;
|
||||
iframe.style.cssText = `
|
||||
position: fixed;
|
||||
bottom: 5rem;
|
||||
|
||||
Reference in New Issue
Block a user