201 lines
6.5 KiB
JavaScript
201 lines
6.5 KiB
JavaScript
// IIFC
|
|
((w, d) => {
|
|
const {
|
|
id = "",
|
|
hostId = 1,
|
|
autoReg = "true",
|
|
loginToken = "",
|
|
closeWidth = 48,
|
|
closeHeight = 48,
|
|
openWidth = 380,
|
|
openHeight = 680,
|
|
themeColor = "#1fe1f9",
|
|
title = "",
|
|
logo = "",
|
|
position = "right",
|
|
welcome = "",
|
|
popupTitle = "Need help?",
|
|
popupSubtitle = "Our staff are always ready to help!",
|
|
popupImage = "",
|
|
popupClosable = "true",
|
|
showPopup = "true",
|
|
useShadowDom = "false"
|
|
} = d.currentScript.dataset;
|
|
const _src = d.currentScript.src;
|
|
const origin = new URL(_src).origin;
|
|
|
|
// 如果有提示框内容,关闭状态的宽度和高度需要更大
|
|
const hasTooltip = popupTitle || popupSubtitle;
|
|
const actualCloseWidth = hasTooltip ? 320 : closeWidth;
|
|
const actualCloseHeight = hasTooltip ? 120 : closeHeight;
|
|
|
|
const baseStyles = {
|
|
position: "fixed",
|
|
[position]: "16px", // [position] = [right] or [left]
|
|
bottom: "16px",
|
|
border: "none",
|
|
zIndex: 9999
|
|
};
|
|
|
|
if (useShadowDom === "true") {
|
|
// Shadow DOM 模式 - 直接注入内容
|
|
const container = d.createElement("div");
|
|
container.id = "VOCECHAT_WIDGET_CONTAINER";
|
|
Object.assign(container.style, baseStyles);
|
|
// 不限制容器大小,让内容自己决定
|
|
container.style.pointerEvents = "none"; // 容器本身不响应点击,让点击穿透
|
|
|
|
const shadowRoot = container.attachShadow({ mode: "open" });
|
|
|
|
// 创建根容器
|
|
const rootDiv = d.createElement("div");
|
|
rootDiv.id = "root";
|
|
rootDiv.style.background = "transparent";
|
|
rootDiv.style.pointerEvents = "auto"; // 内容可以响应点击
|
|
|
|
// 创建 modal 容器
|
|
const modalDiv = d.createElement("div");
|
|
modalDiv.id = "root-modal";
|
|
|
|
shadowRoot.appendChild(rootDiv);
|
|
shadowRoot.appendChild(modalDiv);
|
|
|
|
d.body.appendChild(container);
|
|
|
|
// 先加载 asset manifest 获取正确的文件路径
|
|
fetch(`${origin}/asset-manifest.json`)
|
|
.then(res => res.json())
|
|
.then(manifest => {
|
|
// 加载样式
|
|
const styleLink = d.createElement("link");
|
|
styleLink.rel = "stylesheet";
|
|
styleLink.href = `${origin}${manifest.files["widget.css"]}`;
|
|
shadowRoot.appendChild(styleLink);
|
|
|
|
// 加载并执行 widget 脚本
|
|
const script = d.createElement("script");
|
|
script.type = "text/javascript";
|
|
script.src = `${origin}${manifest.files["widget.js"]}`;
|
|
script.onload = () => {
|
|
// 脚本加载完成后,需要手动初始化 React 应用到 shadow DOM
|
|
// 检测父窗口是否为移动端
|
|
const isMobile = w.innerWidth < 768;
|
|
const params = new URLSearchParams({
|
|
id,
|
|
host: hostId,
|
|
autoReg,
|
|
token: loginToken,
|
|
themeColor,
|
|
from: location.hostname,
|
|
welcome,
|
|
title,
|
|
logo,
|
|
popupTitle,
|
|
popupSubtitle,
|
|
popupImage,
|
|
popupClosable,
|
|
showPopup,
|
|
embed: "true",
|
|
closeWidth,
|
|
closeHeight,
|
|
openWidth,
|
|
openHeight,
|
|
isMobile
|
|
});
|
|
|
|
// 将参数传递给 shadow DOM 内的应用
|
|
if (w.VoceChatWidgetInit) {
|
|
w.VoceChatWidgetInit(shadowRoot, params.toString());
|
|
}
|
|
};
|
|
shadowRoot.appendChild(script);
|
|
})
|
|
.catch(err => {
|
|
console.error("Failed to load widget manifest:", err);
|
|
});
|
|
|
|
// 暴露全局函数用于打开 widget(简化版,只触发事件)
|
|
w.VoceChatWidget = {
|
|
open: function() {
|
|
const event = new CustomEvent("vocechat-widget-open");
|
|
shadowRoot.dispatchEvent(event);
|
|
},
|
|
close: function() {
|
|
const event = new CustomEvent("vocechat-widget-close");
|
|
shadowRoot.dispatchEvent(event);
|
|
},
|
|
toggle: function() {
|
|
const event = new CustomEvent("vocechat-widget-toggle");
|
|
shadowRoot.dispatchEvent(event);
|
|
}
|
|
};
|
|
} else {
|
|
// 传统 iframe 模式
|
|
const wrapper = d.createElement("iframe");
|
|
wrapper.id = "VOCECHAT_WIDGET";
|
|
Object.assign(wrapper.style, baseStyles);
|
|
// 检测父窗口是否为移动端
|
|
const isMobile = w.innerWidth < 768;
|
|
wrapper.src = `${origin}/widget.html?id=${id}&host=${hostId}&autoReg=${autoReg}&token=${loginToken}&themeColor=${encodeURIComponent(
|
|
themeColor
|
|
)}&from=${encodeURIComponent(location.hostname)}&welcome=${encodeURIComponent(welcome)}&title=${encodeURIComponent(title)}&logo=${encodeURIComponent(logo)}&popupTitle=${encodeURIComponent(popupTitle)}&popupSubtitle=${encodeURIComponent(popupSubtitle)}&popupImage=${encodeURIComponent(popupImage)}&popupClosable=${popupClosable}&showPopup=${showPopup}&isMobile=${isMobile}`;
|
|
wrapper.width = actualCloseWidth;
|
|
wrapper.height = actualCloseHeight;
|
|
wrapper.frameborder = 0;
|
|
wrapper.referrerPolicy = "unsafe-url";
|
|
|
|
w.addEventListener(
|
|
"message",
|
|
(event) => {
|
|
const { data: CMD } = event;
|
|
switch (CMD) {
|
|
case "OPEN":
|
|
wrapper.setAttribute("width", openWidth);
|
|
wrapper.setAttribute("height", openHeight);
|
|
break;
|
|
case "CLOSE":
|
|
wrapper.setAttribute("width", actualCloseWidth);
|
|
wrapper.setAttribute("height", actualCloseHeight);
|
|
break;
|
|
case "RELOAD_WITH_OPEN":
|
|
{
|
|
const url = new URL(wrapper.src);
|
|
url.searchParams.append("open", new Date().getTime());
|
|
console.log("new src", url.href);
|
|
wrapper.src = url.href;
|
|
wrapper.setAttribute("width", openWidth);
|
|
wrapper.setAttribute("height", openHeight);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
},
|
|
false
|
|
);
|
|
d.body.appendChild(wrapper);
|
|
|
|
// 暴露全局函数用于打开 widget
|
|
w.VoceChatWidget = {
|
|
open: function() {
|
|
wrapper.setAttribute("width", openWidth);
|
|
wrapper.setAttribute("height", openHeight);
|
|
wrapper.contentWindow?.postMessage("OPEN_FROM_PARENT", "*");
|
|
},
|
|
close: function() {
|
|
wrapper.setAttribute("width", actualCloseWidth);
|
|
wrapper.setAttribute("height", actualCloseHeight);
|
|
wrapper.contentWindow?.postMessage("CLOSE_FROM_PARENT", "*");
|
|
},
|
|
toggle: function() {
|
|
const currentWidth = wrapper.getAttribute("width");
|
|
if (currentWidth == actualCloseWidth) {
|
|
this.open();
|
|
} else {
|
|
this.close();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
})(window, document);
|