"use client"; import { useEffect, useState } from "react"; import dynamic from "next/dynamic"; import type { WidgetHostConfig } from "@/lib/widget/config"; import { generateUUID } from "@/lib/utils"; const STORAGE_KEY = "cs-agent-widget-test-config"; type TestConfig = WidgetHostConfig; function getWidgetRootPath(pathname: string): string { return pathname.startsWith("/widget") ? "/widget" : ""; } function getWidgetSdkUrl(baseUrl: string, pathname: string): string { return `${baseUrl.replace(/\/$/, "")}${getWidgetRootPath(pathname)}/sdk/cs-agent-widget.js`; } function generateRandomSubject(): string { return `用户${generateUUID().replace(/-/g, "").slice(0, 8)}`; } function buildDefaultConfig(baseUrl: string): TestConfig { return { channelId: "", baseUrl, apiBaseUrl: baseUrl, title: "在线客服", subtitle: "贝壳AI客服为您服务", position: "right", themeColor: "#0f6cbd", width: "680px", subject: generateRandomSubject(), }; } function getInitialConfig(): TestConfig { if (typeof window === "undefined") { return buildDefaultConfig(""); } const origin = window.location.origin; const query = new URLSearchParams(window.location.search); const savedText = window.localStorage.getItem(STORAGE_KEY); const savedConfig = savedText ? (JSON.parse(savedText) as Partial) : {}; return { ...buildDefaultConfig(origin), ...savedConfig, channelId: query.get("channelId") ?? savedConfig.channelId ?? "", baseUrl: query.get("baseUrl") ?? savedConfig.baseUrl ?? origin, apiBaseUrl: query.get("apiBaseUrl") ?? savedConfig.apiBaseUrl ?? savedConfig.baseUrl ?? origin, width: query.get("width") ?? savedConfig.width ?? "680px", subject: query.get("subject") ?? savedConfig.subject ?? generateRandomSubject(), }; } function removeMountedWidget() { if (typeof window === "undefined") { return; } document .querySelectorAll( '[data-cs-agent-widget="launcher"], [data-cs-agent-widget="frame"], [data-cs-agent-widget="script"]', ) .forEach((node) => node.remove()); delete window.CSAgentConfig; delete window.__CS_AGENT_WIDGET_CONFIG__; delete (window as Window & { __CS_AGENT_WIDGET_LOADED__?: boolean }) .__CS_AGENT_WIDGET_LOADED__; } function injectWidget(config: TestConfig) { removeMountedWidget(); window.CSAgentConfig = config; const script = document.createElement("script"); script.async = true; script.src = getWidgetSdkUrl(window.location.origin, window.location.pathname); script.dataset.csAgentWidget = "script"; document.body.appendChild(script); } function WidgetTestPageInner() { const [config, setConfig] = useState(getInitialConfig); const [status, setStatus] = useState(() => getInitialConfig().channelId ? "Widget 已挂载" : "请先填写 channelId", ); useEffect(() => { if (config.channelId) { injectWidget(config); } else { removeMountedWidget(); } return () => { removeMountedWidget(); }; }, [config]); const currentConfig = config; function updateField( key: K, value: TestConfig[K], ) { setConfig((current) => (current ? { ...current, [key]: value } : current)); } function handleApply() { const nextConfig = { ...currentConfig, channelId: currentConfig.channelId.trim(), baseUrl: currentConfig.baseUrl.trim() || window.location.origin, apiBaseUrl: currentConfig.apiBaseUrl?.trim() || currentConfig.baseUrl.trim() || window.location.origin, title: currentConfig.title?.trim() || "在线客服", subtitle: currentConfig.subtitle?.trim() || "", themeColor: currentConfig.themeColor?.trim() || "#0f6cbd", width: currentConfig.width?.trim() || "680px", }; setConfig(nextConfig); window.localStorage.setItem(STORAGE_KEY, JSON.stringify(nextConfig)); if (!nextConfig.channelId) { removeMountedWidget(); setStatus("请先填写 channelId"); return; } injectWidget(nextConfig); setStatus("Widget 已挂载"); } const sdkUrl = getWidgetSdkUrl(currentConfig.baseUrl, window.location.pathname); const snippet = ` `; return (
{status}

这段脚本就是外部业务系统接入时需要放到宿主页面里的最小配置。

            {snippet}
          
); } const WidgetTestPage = dynamic(async () => WidgetTestPageInner, { ssr: false, }); export default WidgetTestPage;